loss角度理解目标检测的样本不平衡
从loss角度,从头梳理了下目标检测领域的样本平衡问题
1. softmax函数求导数
softmax函数:
\[a_i = \frac{e^{x_i}}{\sum_je^{x_j}}
\]
若:
\[x = [x_1, x_2, x_3] \\
y = softmax(x) = [a_1, a_2, a_3]
\]
求y对x的导数:
\[\frac{d_y}{d_x} = [\frac{d_{a_1}}{d_x}, \frac{d_{a_2}}{d_x}, \frac{d_{a_3}}{d_x}] \\
\frac{d_{a_1}}{d_x} = [a_1(1-a_1), -a_1*a_2, -a_1*a_3] \\
\frac{d_{a_2}}{d_x} = [-a_2*a_1, a_2(1-a_2), -a_2*a_3] \\
\frac{d_{a_3}}{d_x} = [-a_3*a_1, -a_3*a_2, a_3(1-a_3)] \\
\]
2. softmax函数结合交叉熵,求导数
交叉熵损失函数为:
\[loss = -\sum_iy^*_ilog{a_i}
\]
实际使用过程中标签\(y^*\)一般采用one_hot形式编码,求和形式可以继续简化,若:
\[y^* = [1, 0, 0]\\
y = softmax(x) = [a_1, a_2, a_3]\\
loss = -(1*loga_1 + 0*loga_2+0*loga_3)=-1*loga_1
\]
则loss对y的导数计算很简单:(交叉熵中的log一般用ln)
\[\frac{\partial loss}{\partial y}=[-\frac{1}{a_1}, 0, 0]
\]
综合上述可以得到loss对x的导数,假设如下:
\[模型输出:x = [x_1, x_2, x_3] \\
y = softmax(x) = [a_1, a_2, a_3]\\
标签:y^* = [1, 0, 0]\\
\]
则:
\[\frac{\partial loss}{\partial x} = \frac{\partial loss}{\partial y}\frac{\partial y}{\partial x}\\
=[-\frac{1}{a_1}, 0, 0] *\begin{bmatrix} a_1(1-a_1), -a_1a_2, -a_1a_3\\ -a_2a_1, a_2(1-a_2), -a_2a_3 \\-a_3a_1, -a_3a_2, a_3(1-a_3) \end{bmatrix}\\
=[a_1-1, a_2, a_3]
\]
整个推导过程看上去很复杂,但是一看最终的结果就发现,softmax+交叉熵求导太简单了,只需要softmax的值,就能知道导数了
3. softmax函数结合交叉熵,平均值后求导数
在实际使用中,我们往往计算的是一整张图片的loss,对于目标检测中,一张图片会预测多个anchor,所以经常需要进行求所有anchor的平均值,反向传播时,相应的梯度也是平均值,示意图如下:

上面测试代码如下:
import mxnet as mx
from mxnet import nd, autograd
from mxnet.gluon import nn, loss
criterion = loss.SoftmaxCrossEntropyLoss()
y = nd.array([[[1], [1], [0]], [[1],[1], [0]]]) # 这里是类别标签,未转化为one_hot形式
pred = nd.random.randn(2, 3, 3)
# y = nd.array([[1], [0]])
# pred = nd.random.randn(2, 3)
print(pred)
print(nd.softmax(pred))
print(nd.log_softmax(pred))
pred.attach_grad()
# print(y)
with autograd.record():
loss = criterion(pred, y)
loss.backward()
print(loss)
print(pred.grad)
了解了上述整个过程后, 我们需要思考一个问题:样本平衡问题?
假设一张图预测10000个anchor,假设一个极端的情况:只有一个正样本,其他都是负样本,正样本产生的loss很大,但是这个正常本的loss会被10000个anchor进行平均,loss也会变得很小。
同样,对于难易样本平衡,也会产生如此的效应

浙公网安备 33010602011771号