Yolov3模型框架darknet研究(十)彻底弄明白darknet中的batch和subdivisions

前言

在darknet的配置文件有两个参数:batch和subdivisions是比较令人费解的,如下所示。一般地,batch就是一次输入多少图片到神经网络中来计算loss,并反向update gradients。 但在darknet代码里面,含意稍微有些不同。

分析 

下面以batch=64,subdivisions=16为例,并结合代码来分析它们的真实意思。

首先在训练真正开始前,会根据cfg文件来搭建网络结构,其函数为parse_network_cfg(...)。在该函数里面会调用parse_net_options(...)来读取cfg中最顶上网络参数的值,这些参数值包括网络大小,学习率,当然也包括batch和subdivisions。

void parse_net_options(list *options, network *net)
{
    net->batch = option_find_int(options, "batch",1);
    net->learning_rate = option_find_float(options, "learning_rate", .001);
    net->momentum = option_find_float(options, "momentum", .9);
    net->decay = option_find_float(options, "decay", .0001);
    int subdivs = option_find_int(options, "subdivisions",1);
    net->time_steps = option_find_int_quiet(options, "time_steps",1);
    net->notruth = option_find_int_quiet(options, "notruth",0);
    net->batch /= subdivs;
    net->batch *= net->time_steps;
    net->subdivisions = subdivs;
    net->random = option_find_int_quiet(options, "random", 0);

... ...
}

从上面代码看到,net->batch, subdivs先从cfg中读取,比如说分别为64和16,重点来了,然后在倒数第4行,对net->batch做了一个除以subdivis转换,现在变成了4,然后在倒数第3行又做了一个乘net->time_steps的运算。

先解释time_steps。darknet其实是一个开源深度学习框架,它不仅仅支持yolov3目标检测,同时还可以分类,分割甚至RNN,LSTM等。只是后面这些功能用的人不多而已。。。 而time_steps是RNN中的概念,即在更新梯度时,不仅仅考虑当前的输入的一个batch,而且还考虑前面time_steps个batch,所以才在后面又对net->batch做了乘time_steps的运算。当然我们这里是目标检测,不用考虑以前的历史数据,自然time_steps等于1.

小结一下,在parse_net_options(...)中,经过上面几个转换,net->batch最终值为64/16=4, 而subdiviions为16.

然后开始读取images准备开始训练,每轮读取图片的数目为下面代码所示

int imgs = net->batch * net->subdivisions * ngpus;

这个代码很有疑惑性,如果不知道前面的转换,那么就很容易得到 imgs=64x16x1(假定单gpu,则ngpus=1 ),subdivision变成乘法因子啦???。其实错啦,这里的net->batch为4, 所以一个batch读取 的图片数目还是64 (=4x16x1)。

小结一下,在darknet代码中,net->batch值是恒为cfg中的batch值 / subdivision。所以cfg中的batch是指一次性读取多少张图片,而net->batch则是被subdivision分割成的小batch。 

有了这个结论,我们继续看代码中 net->batch 和subdivision到底是怎么来用的。

float train_network(network *net, data d)
{
    assert(d.X.rows % net->batch == 0);
    int batch = net->batch;
    int n = d.X.rows / batch;

    int i;
    float sum = 0;
    for(i = 0; i < n; ++i){
        get_next_batch(d, batch, i*batch, net->input, net->truth);
        float err = train_network_datum(net);
        sum += err;
    }
    return (float)sum/(n*batch);
}

上面 代码中,batch为net->batch,假设为4, 那么n=64/4=16即subdivision值。换句话说,subdivision为16的话,即将普通batch数目分割成16等份,每份的图片数目为4(即net->batch)。

接下来在for循环中,训练16次,每次训练4张图片。所以绕了半天,虽然每次读取了64张图片,但实际每次参加训练计算loss的图片数目只有net->batch张,即4张。同时要注意的是, 在 train_network_datum(net)中有行代码表明,16次训练完后才update 网络权值的。

if(((*net->seen)/net->batch)%net->subdivisions == 0) update_network(net);

总结

至此,已经比较绕了, 梳理一下,实际上网络是net->batch张图片进行训练(前向推理和反向传播),但是升级权值是在cfg中batch数目结束后进行的。它把平常所说的mini-batch SGD的batch分割成两个部分含义。前面是小batch图片训练,后面再是大batch训练后更新网络。 这样做的用意,应该是为了省内存吧。 

换个角度,如果内存足够的话,且想提高训练速度的话,应该是提高cfg中的batch值,并适当较少subdivision,这样net->batch适当变大,意味着每次参加训练的图片数目增加,循环训练次数subdivision变少。 以文中数值例子而言,将64和16改成64和8比较适宜些。 

后续更新:在GTX1080Ti上尝试用64和8的组合来训练,发现显存不够,只好还是用64和16。

 

 

 

 

 

 

  • 27
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ltshan139

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值