YOLOv3损失函数个人见解
网络上对YOLOv3的讲解各有不同,还有用YOLOv1损失函数来代表YOLOv3函数的,这里我发表一下个人对YOLOv3损失函数的见解。
损失函数
在这里本人认为损失函数就像YOLOv3原作者在论文所说的:
- 边界框运用均方误差
- 置信度运用交叉熵
- 类别运用交叉熵
用公式表示如下:
在源码中边框坐标也有乘以scale(2-wh)。对于scale,我的理解是:wh越小,就表示面积越小,面积越小,在和anchor做比较的时候,iou也就会越小,scale便是用来弱化边界框尺寸对损失值的影响。而对于坐标,本人认为没这个必要,所以上述公式坐标那一块并没有乘以scale。
函数求导讲解
均方误差求导:
交叉熵函数求导:
logistic函数求导:
源码解释
本人认为项目源码中并没有直接给出损失函数的表达,而是直接跳过了这一步进行反向传播求梯度
源码中有一个很重要的变量delta,本人认为这便是存放梯度的
Delta =
- 类别:
delta[index + stride*n] = ((n == class_id) ? 1 : 0) - output[index + stride*n];
if (n == class_id && avg_cat) *avg_cat += output[index + stride*n];
- 置信度:
int obj_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 4);
avg_anyobj += l.output[obj_index];
l.delta[obj_index] = 0 - l.output[obj_index];
if (best_iou > l.ignore_thresh) {
l.delta[obj_index] = 0;
}
if (best_iou > l.truth_thresh) {
l.delta[obj_index] = 1 - l.output[obj_index];
- 边框:
float tx = (truth.x*lw - i);
float ty = (truth.y*lh - j);
float tw = log(truth.w*w / biases[2*n]);
float th = log(truth.h*h / biases[2*n + 1]);
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]);
这里要注意,源码中对坐标,置信度,类别的输出都做了logistic函数处理回归。代码如下:
int index = entry_index(l, b, n*l.w*l.h, 0);
activate_array_gpu(l.output_gpu + index, 2*l.w*l.h, LOGISTIC);
index = entry_index(l, b, n*l.w*l.h, 4);
activate_array_gpu(l.output_gpu + index, (1+l.classes)*l.w*l.h, LOGISTIC);
源码里有如下这一行代码,本人认为这段代码只能作为损失标准,但不作为正式损失函数。损失函数是用来反向传播调节误差的,而代码中l.cost只是单纯地进行赋值后就没再调用过这个变量,也就是说并没有进入反向传播中,进行反向传播的应该是delta这块,所以只能从delta这里逆推求损失函数。
*(l.cost) = pow(mag_array(l.delta, l.outputs * l.batch), 2);
注释:
float mag_array(float *a, int n)
{
int i;
float sum = 0;
for(i = 0; i < n; ++i){
sum += a[i]*a[i];
}
return sqrt(sum);
按类别和置信度运用交叉熵来推理,对于类别和置信度的反向求导如下(y(1-y)为logistic函数求导):
符合上述delta中类别和置信度的代码
而对于边框,大家可以先看下图注释:
按边框为均方误差同上面步骤来推理,delta本该为:
delta[index + 0*stride] = scale * (tx - x[index + 0*stride]) * (1 - x[index + 0*stride]) * x[index + 0*stride];
delta[index + 1*stride] = scale * (ty - x[index + 1*stride])* (1 - x[index + 1*stride]) * x[index + 1*stride];
但源码中却是下面代码(这里本人认为是原作者故意设置的,计算sigmoid(tx)之间而不是tx值之间的均方误差损失)。
x[index+0&stride]在源码中表示sigmoid(x),如果是按对sigmoid(x)逆推而不是x逆推,则损失函数可表示为:0.5*scale(tx-sigmoid(x))^2
delta[index + 0*stride] = scale * (tx - x[index + 0*stride]);
delta[index + 1*stride] = scale * (ty - x[index + 1*stride]);
以上是本人对YOLOv3中损失函数的理解,如有不对之处,望指出