PaddleSeg套件支持多种损失函数,Cross Entroy Loss(交叉熵)是一种很常用的损失函数,在图像分类中基本都会用到。一般在图像分类中,神经网络最终输出节点数目与类别数一致,形状为[batch_size, num_classes],样本标签直接使用类别的序号表示,形状为[batch_size, 1]。在paddle中计算交叉熵的函数为softmax_with_cross_entropy,一般比较常用的两个参数为logits和label,可以直接使用logits和代表类别序号的label进行计算。举个例子
import paddle.fluid as fluid
#这里会自动组装成batch,实际data的shape为[batch_size, 128],label的shape为[batch_size, 1]
#softmax_with_cross_entropy接收的两个参数的维度一致,只是在最后一个维度上形状不同,label在最后
#一个维度上的长度为1,代表的就是类别的编号,一般从0开始计数。
data = fluid.layers.data(name='data', shape=[128], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
fc = fluid.layers.fc(input=data, size=100)
out = fluid.layers.softmax_with_cross_entropy(logits=fc, label=label)
这里面softmax_with_cross_entropy首先会对logits进行softmax计算,公式如下:
s o f t m a x [ i , j ] = exp ( x [ i , j ] ) ∑ j ( e x p ( x [ i , j ] ) softmax[i, j] = \frac{\exp(x[i, j])}{\sum_j(exp(x[i, j])} softmax[i,j]=∑j(exp(x[i,j])exp(x[i,j])
然后再计算交叉熵,计算公式如下:
o
u
t
p
u
t
[
i
1
,
i
2
,
.
.
.
,
i
k
]
=
−
l
o
g
(
i
n
p
u
t
[
i
1
,
i
2
,
.
.
.
,
i
k
,
j
]
)
,
l
a
b
e
l
[
i
1
,
i
2
,
.
.
.
,
i
k
]
=
j
,
j
!
=
i
g
n
o
r
e
_
i
n
d
e
x
output[i_1, i_2, ..., i_k]=-log(input[i_1, i_2, ..., i_k, j]), label[i_1, i_2, ..., i_k] = j, j != ignore\_index
output[i1,i2,...,ik]=−log(input[i1,i2,...,ik,j]),label[i1,i2,...,ik]=j,j!=ignore_index
计算交叉熵的公式简单解释一下,就是将label转换为one hot形式,label向量中为1对应位置的logit值去计算-log值,如果logit的值越接近1,则损失值越小。如下图所示:
PaddleSeg中的交叉熵函数定义在paddleseg/models/losses/cross_entroy_loss.py函数中,下面我们来解析一下代码。
class CrossEntropyLoss(nn.Layer):
def __init__(self, ignore_index=255):
super(CrossEntropyLoss, self).__init__()
#保存需要忽略的类别序号
self.ignore_index = ignore_index
self.EPS = 1e-5
def forward(self, logit, label):
#比较label和logit的维度是否一致,一般传入label维度可能会比logit少1,
#soft_with_cross_entropy的参数要求维度数量一致,所以这里把label扩展一个维度
if len(label.shape) != len(logit.shape):
label = paddle.unsqueeze(label, 1)
#对logit和label进行转置,将通道转置到最后一个维度,原来的形状为[batch_size, channel, height, width]
#转置后形状为[batch_size, height, width, channel]
#这时logit的channel的维度长度与类别数目一致,label的channel维度为长度为1,保存的是类别序号。
logit = paddle.transpose(logit, [0, 2, 3, 1])
label = paddle.transpose(label, [0, 2, 3, 1])
#计算交叉熵
loss = F.softmax_with_cross_entropy(
logit, label, ignore_index=self.ignore_index, axis=-1)
#统计有效的像素的数量,这里执行后类型为boolean
mask = label != self.ignore_index
#boolean无法与float32运算,所以这里需要进行类型转换。
mask = paddle.cast(mask, 'float32')
#统计需要计算loss的像素的数量,如果有的label是需要忽略的,那么在mask对应的位置则为0。
loss = loss * mask
#计算整幅图像的损失值。如果图像中有忽略的部分,用损失值除以有效部分的占比,可以估算出整幅图像的损失值,
#这样保证了有忽略部分的图像和没有忽略的图像损失计算的都是整幅图像的损失值。
avg_loss = paddle.mean(loss) / (paddle.mean(mask) + self.EPS)
label.stop_gradient = True
mask.stop_gradient = True
return avg_loss
以上就是损失函数部分的解读。
PaddleSeg仓库地址:https://github.com/PaddlePaddle/PaddleSeg