深度学习【58】物体检测:yoloV2笔记之训练

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linmingan/article/details/82180320

之前基于yoloV2做过一段时间的物体检测,当时对yoloV2整个框架了如指掌。由于没有及时写笔记记录一下,现在回过头来很多东西都快忘光了。本篇博客不会记录如何使用darknet训练yoloV2,而更注重的的是整个yoloV2的算法,以及其中的一些细节。废话不多说,我们直接开始。

物体检测训练主函数

yoloV2的训练函数主函数在examples/detector.c中的train_detector函数,这里就不贴代码了,太长了。我们主要看看整体的流程。

函数一开始为初始化网络,或者导入预训练参数,以及其他设置,如gpu,以及根据使用的gpu个数设置学习率。

接着就开始训练,具体的:
1、确定网络输入大小
yoloV2的训练过程有一个很重要的点,那就是多尺度图像训练。我们都知道yoloV2的输入图片大小是416*416,但是在训练的时候并不是一直使用416*416的图片进行训练,而是每隔一段时间就改变输入图像的大小,其策略为:

int dim = (rand() % 10 + 10) * 32;
if (get_current_batch(net)+200 > net.max_batches) dim = 608;//最后200批次训练都只用输入大小为608*608

2、训练样本读取
代码在src/data.c中的load_data_detection函数中。
yoloV2在读取样本的时候对数据进行了扩充,分别有随机抖动,镜像和亮度、饱和度、曝光度的调整。其中随机抖动和镜像需要对box也进行相应的调整。

3、训练
根据使用的gpu个数进行训练,调用的是train_networks函数。

如果gpu个数大于1,那么每隔100个训练批次就进行参数合并。

region_layer

yoloV2的训练过程除了多尺度图片训练方式,以及数据扩充方式,另一个重点是最后的检测层region_layer。相应的代码在src/region_layrer.c中的forward_region_layer函数里面。

假设网络的输出是416*416,那么yoloV2的输出是尺寸是13*13;类别数为80,候选框的个数为5,那么通道数为(1+4+80)*5=425。每个候选框占85个通道,即第0到84通道属于第一个候选框,第85到169通道属于第二个候选框,以此类推。然后在每85个通道里面,存放顺序为:bbox(占4个通道)、是否是物体(占1个通道)以及类别数量(80个类别)。

另外yoloV2中利用聚类算法得到的anchor中,其w和h没有超过13*13。这是因为yoloV2,在聚类anchor时是将图片和bbox,resize到416*416,然后进行聚类。最后将聚类出来的anchor的w和h除以32,因为yoloV2的网络将416*416的图片缩小到13*13。这里就有个问题了,上面提到yoloV2有进行多尺度输入训练,也就是输入图片不一定是416*416。那么由416*416计算出来的anchor是否也应该一起调整呢?从作者的训练代码看似乎没有进行相应的调整。

1、sigmoid和softmax
region_layer一开始先将,是否物体和bbox相关的通道经过sigmoid激活函数,物体类别通道经过softmax:

for (b = 0; b < l.batch; ++b){
        for(n = 0; n < l.n; ++n){
            int index = entry_index(l, b, n*l.w*l.h, 0);//第b个batch,第n个anchor的bbox起始位置
            activate_array(l.output + index, 2*l.w*l.h, LOGISTIC);//将13*13个x,y偏移量通道经过sigmoid激活函数
            index = entry_index(l, b, n*l.w*l.h, l.coords);//获取是否是物体的起始位置
            if(!l.background) activate_array(l.output + index,   l.w*l.h, LOGISTIC);//13*13个是否是物体通道经过sigmoid激活函数
        }
    }

int index = entry_index(l, 0, 0, l.coords + !l.background);//获取物体类别起始位置
softmax_cpu(net.input + index, l.classes + l.background, l.batch*l.n, l.inputs/l.n, l.w*l.h, 1, l.w*l.h, 1, l.output + index);//将13*13中的每80个通道经过softmax函数

2、负样本处理
接下来是对13*13*5的候选框进行处理,主要是处理负样本,具体流程如下:

    1、对13*13*5中的每个候选框,根据anchor的w和h计算出归一化后的bbox,用pred变量表示。
            box pred = get_region_box(l.output, l.biases, n, box_index, i, j, l.w, l.h, l.w*l.h);
    2、对于pred这个或许框,从当前训练样本的所有真实bbox计算出最大的iou,用best_iou变量表示。
                    for(t = 0; t < 30; ++t){
                        box truth = float_to_box(net.truth + t*(l.coords + 1) + b*l.truths, 1);
                        if(!truth.x) break;
                        float iou = box_iou(pred, truth);
                        if (iou > best_iou) {
                            best_iou = iou;
                        }
                    }

    3、直接计算该候选框的不是物体的误差,即直接认为是负样本。
    l.delta[obj_index] = l.noobject_scale * (0 - l.output[obj_index]);
    4、如果best_iou大于某一阈值,比如0.6,则任务这个候选框是正样本,并且将步骤3计算的不是物体的误差改成0
                    if (best_iou > l.thresh) {
                        l.delta[obj_index] = 0;
                    }

    5、如果网络已经训练过的样本小于12800个,那么就是将第n个anchor设置为真实bbox,并计算误差。
                    if(*(net.seen) < 12800){
                        box truth = {0};
                        truth.x = (i + .5)/l.w;
                        truth.y = (j + .5)/l.h;
                        truth.w = l.biases[2*n]/l.w;
                        truth.h = l.biases[2*n+1]/l.h;
                        delta_region_box(truth, l.output, l.biases, n, box_index, i, j, l.w, l.h, l.delta, .01, l.w*l.h);
                    }

        这一步主要是想让网络开始训练的时候,13*13网格中的5个候选框都先接近对应的anchor。

根据anchor和网络输出计算预测的bbox代码如下:

box get_region_box(float *x, float *biases, int n, int index, int i, int j, int w, int h, int stride)
{
    box b;
    //计算xy13*13网格中第i,j位置分别加上预测的偏移量并除以13,将其归一化。
    b.x = (i + x[index + 0*stride]) / w;
    b.y = (j + x[index + 1*stride]) / h;
    //计算w,h。根据log(b.w/anchor_n_w)=x[index+2*stride]计算出b.w,然后在除以13,将其归一化。anchor_n_w表示第n个anchor的w。
    b.w = exp(x[index + 2*stride]) * biases[2*n]   / w;
    b.h = exp(x[index + 3*stride]) * biases[2*n+1] / h;
    return b;
}

3、正样本处理
在处理完13*13*5个候选框后,开始处理训练样本的所有真实bbox。具体如下:

    1、对每个真实的bbox,计算出其在13*13网格中的位置:
            i = (truth.x * l.w);
            j = (truth.y * l.h);
    2、找出5个候选框中与该真实bbox的iou最大的候选框。

            for(n = 0; n < l.n; ++n){
                int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0);
                box pred = get_region_box(l.output, l.biases, n, box_index, i, j, l.w, l.h, l.w*l.h);
                if(l.bias_match){//用anchor来计算iou,而不是预测的bbox
                    pred.w = l.biases[2*n]/l.w;
                    pred.h = l.biases[2*n+1]/l.h;
                }
                //printf("pred: (%f, %f) %f x %f\n", pred.x, pred.y, pred.w, pred.h);
                pred.x = 0;
                pred.y = 0;
                float iou = box_iou(pred, truth_shift);
                if (iou > best_iou){
                    best_iou = iou;
                    best_n = n;
                }
            }
    3、真实bbox和该候选框一起计算bbox回归误差。
            l.delta[obj_index] = l.object_scale * (1 - l.output[obj_index]);
            if (l.rescore) {//默认使用rescore
                l.delta[obj_index] = l.object_scale * (iou - l.output[obj_index]);
            }

    4、计算该候选框是物体的误差
    5、计算类别误差

在计算是否是物体的误差,bbox误差以及类别误差都会乘以相应的权重:

object_scale=5
noobject_scale=1
class_scale=1
coord_scale=1

另外,我们看看bbox误差的计算:

    float tx = (truth.x*w - i);//计算真实bbox的x,与i的偏移量
    float ty = (truth.y*h - j);//计算真实bbox的y,与j的偏移量
    float tw = log(truth.w*w / biases[2*n]);//计算真实bbox的w与anchor的w的比值的对数
    float th = log(truth.h*h / biases[2*n + 1]);//计算真实bbox的h与anchor的h的比值的对数

    //下面就是真实bbox的xy偏移量和w,h与anchor的w,h的比值的对数,跟网络输出计算mse误差了。
    delta[index + 0*stride] = scale * (tx - x[index + 0*stride]);
    delta[index + 1*stride] = scale * (ty - x[index + 1*stride]);
    delta[index + 2*stride] = scale * (tw - x[index + 2*stride]);
    delta[index + 3*stride] = scale * (th - x[index + 3*stride]);

从上面的负样本和正样本处理来看,yoloV2并没有进行难例子挖掘。而是直接将所有iou小于0.6的候选框当做负样本,正样本个数与真实bbox的个数一样。其他的iou大于0.6但不是正样本的候选框就不会更新是否是物体的误差和类别误差,而只是更新其与对应的anchor的bbox误差。

猜你喜欢

转载自blog.csdn.net/linmingan/article/details/82180320