想再说一下,这个系列的代码注释我放到https://github.com/GiantPandaCV/darknet 这里了,有需要的可以点star或者fork哦,还在持续更新中。
前言
昨天结合代码详细解析了YOLOV1的损失函数,今天AlexeyAB版DarkNet的YOLOV2损失函数代码解析也来了。之前我详细分析过YOLOv2的损失函数
YOLOV2损失函数
为了方便你理解,还是把YOLOV2损失函数叙述一下。
YOLOV2对每个预测box的[x,y],confidence进行逻辑回归,类别进行softmax回归;
在Darknet中,损失函数可以用下图来进行表示:
可以看到这个损失函数是相当复杂的,损失函数的定义在Darknet/src/region_layer.c中。对于上面这一堆公式,我们先简单看一下,然后我们在源码中去找到对应部分。这里的
和
代表的是特征图的高宽,都为
,而A指的是Anchor个数,YOLOv2中是5,各个
值是各个loss部分的权重系数。我们将损失函数分成3大部分来解释:第一部分:
第一项需要好好解释一下,这个loss是计算background的置信度误差,这也是YOLO系列算法的特色,但是用哪些预测框来预测背景呢?这里需要计算各个预测框和所有的ground truth之间的IOU值,并且取最大值记作MaxIOU,如果该值小于一定的阈值,YOLOv2论文取了0.6,那么这个预测框就标记为background,需要计算
这么多倍的损失函数。为什么这个公式可以这样表达呢?因为我们有物体的话,那么
,如果没有物体
,我们把这个值带入到下面的公式就可以推出第一项啦!
第二部分:
这一部分是计算Anchor boxes和预测框的坐标误差,但是只在前12800个iter计算,这一项应该是促进网络学习到Anchor的形状。第三部分:
这一部分计算的是和ground truth匹配的预测框各部分的损失总和,包括坐标损失,置信度损失以及分类损失。 3.1 坐标损失 这里的匹配原则是指对于某个特定的ground truth,首先要计算其中心点落在哪个cell上,然后计算这个cell的5个先验框和grond truth的IOU值,计算IOU值的时候不考虑坐标只考虑形状,所以先将Anchor boxes和ground truth的中心都偏移到同一位置,然后计算出对应的IOU值,IOU值最大的先验框和ground truth匹配,对应的预测框用来预测这个ground truth。 3.2 置信度损失 在计算obj置信度时, 增加了一项
权重系数,也被称为rescore参数,当其为1时,损失是预测框和ground truth的真实IOU值(darknet中采用了这种实现方式)。而对于没有和ground truth匹配的先验框,除去那些Max_IOU低于阈值的,其它就全部忽略。YOLOv2和SSD与RPN网络的处理方式有很大不同,因为它们可以将一个ground truth分配给多个先验框。 3.3 分类损失 这个和YOLOv1一致,没什么好说的了。
在计算boxes的
和
误差时,YOLOv1中采用的是平方根以降低boxes的大小对误差的影响,而YOLOv2是直接计算,但是根据ground truth的大小对权重系数进行修正:l.coord_scale * (2 - truth.w*truth.h)(这里
和
都归一化到(0,1)),这样对于尺度较小的
其权重系数会更大一些,可以放大误差,起到和YOLOv1计算平方根相似的效果。
代码详解
#define DOABS 1
// 构建YOLOv2 region_layer层
// batch 一个batch中包含的图片数
// w 输入特征图的宽度
// h 输入特征图的高度
// n 一个cell预测多少个bbox
// classes 网络需要识别的物体类别数
// coord 一个bbox包含的[x,y,w,h]
region_layer make_region_layer(int batch, int w, int h, int n, int classes, int coords, int max_boxes)
{
region_layer l = { (LAYER_TYPE)0 };
l.type = REGION; //层类别
// 这些变量都可以参考darknet.h中的注释
l.n = n; //一个cell中预测多少个box
l.batch = batch; //一个batch中包含的图片数
l.h = h; //输入图片的宽度
l.w = w; //输入图片的宽度
l.classes = classes; //网络需要识别的物体类数
l.coords = coords; //定位一个物体所需的参数个数(一般值为4,包括矩形中心点坐标x,y以及长宽w,h)
l.cost = (float*)xcalloc(1, sizeof(float)); //目标函数值,为单精度浮点型指针
l.biases = (float*)xcalloc(n * 2, sizeof(float));
l.bias_updates = (float*)xcalloc(n * 2, sizeof(float));
l.outputs = h*w*n*(classes + coords + 1); //一张训练图片经过region_layer层后得到的输出元素个数(等于网格数*每个网格预测的矩形框数*每个矩形框的参数个数)
l.inputs = l.outputs; //一张训练图片输入到reigon_layer层的元素个数(注意是一张图片,对于region_layer,输入和输出的元素个数相等)
//每张图片含有的真实矩形框参数的个数(max_boxes表示一张图片中最多有max_boxes个ground truth矩形框,每个真实矩形框有
//5个参数,包括x,y,w,h四个定位参数,以及物体类别),注意max_boxes是darknet程序内写死的,实际上每张图片可能
//并没有max_boxes个真实矩形框,也能没有这么多参数,但为了保持一致性,还是会留着这么大的存储空间,只是其中的
//值为空而已.
l.max_boxes = max_boxes;
// GT: max_boxes*(4+1) 存储max_boxes个bbox的信息,这里是假设图片中GT bbox的数量是
//小于max_boxes的,这里是写死的;此处与yolov1是不同的
l.truths = max_boxes*(5);
// region层误差项(包含整个batch的)
l.delta = (float*)xcalloc(batch * l.outputs, sizeof(float));
// region层所有输出(包含整个batch的)
//region_layer的输出维度是l.out_w*l.out_h,等于输出的维度,输出的通道数为l.out_c,也即是输入的通道数,具体为:n*(classes+coords+1)
//YOLO检测模型将图片分成S*S个网格,每个网格又预测B个矩形框,最后一层输出的就是这些网格中包含的所有矩形框的信息
l.output = (float*)xcalloc(batch * l.outputs, size