深入了解torch.nn.CrossEntropyLoss()

前言

做分类任务时,我们经常会用到nn.BCE(),nn.CrossEntropyLoss()做为模型的损失函数,以前的使用都是知其然而不知其所以然看到官网的input和output格式,把自己模型的输入输出设置成跟它一样跑通就好了,但显然这不是对待问题正确的做法,而且自己在写测试代码的时候也出现了报错,所以趁这次机会好好研究一下softmax()系列损失函数。下面的梳理将从argmax()到最后的交叉熵损失按自己的理解依次展开

argmax()

在一般的python程序中,我们在挑选极值点的方法一般是借助argmax(),函数返回极大值点的index(同样的,argmin()返回极小值点的index),但是此函数不可导,所以无法计算梯度。
但在神经网络中需要保证loss可以反向传播,即必须保证端到端的可微性,所以我们会用软化的argmax即softargmax代替传统的NMS(非极大值抑制)来挑选极值点位置,利用softargmax可以得到每个元素正则化后的值。其最大值所处的index期望值为:
![在这里插入图片描述](https://img-blog.csdnimg.cn/8f9e89359a2d464a8cd46e9bbb35dd7a.png#pic_center)
>>> x = np.array([-2, 3, -9.4, 5, 0])
>>> ex = np.sum(np.exp(x)/np.sum(np.exp(x))*np.array([0, 1, 2, 3, 4]))
>>> ex
2.766691018220332

softargmax()

从上面我们可以看到位置计算不够准确(理想为3,而实际输出是2.766691018220332),一个原因就是最大值的概率不够大,或者说增大相对最大值而减弱其他值的影响就可以得到更加准确的位置坐标。
可以看到,上式与softmax的期望值只有一个差别,即给向量的每个元素乘以beta
>>> x = x*10 # beta = 10
>>> ex = np.sum(np.exp(x)/np.sum(np.exp(x))*np.array([0, 1, 2, 3, 4]))
>>> ex
2.9999999958776926

softmax()

在softmax的机制中,为获得输出层的输出(即最终的输出),我们不是将sigmoid()作用其上,而是采用的所谓softmax()。其输入为向量,输出为0~1之间的向量,其和为1。在分类任务中作为概率出现在交叉熵损失函数中。
>>> x = np.array([0.5, 2, 1.4, 3, 9.1, -1])
>>> softmax = np.exp(x)/np.sum(np.exp(x))
>>> softmax
array([1.83418710e-04, 8.22025627e-04, 4.51137230e-04, 2.23449733e-03,
       9.96267995e-01, 4.09262461e-05])
>>> np.sum(softmax)
0.9999999999999999

### 我们用torch.softmax()来验证一下(因为np没有softmax()哦)

>>> x = torch.Tensor([0.5, 2, 1.4, 3, 9.1, -1])
>>> torch.softmax(x, dim = 0)
tensor([1.8342e-04, 8.2203e-04, 4.5114e-04, 2.2345e-03, 9.9627e-01, 4.0926e-05])
>>> torch.sum(torch.softmax(x, dim = 0))
tensor(1.)

logsoftmax()

logsoftmax()就是在softmax()之后对结果再多一次log操作,表达式如下:


logsoftmax可导
求导:


将原始数据x—>log(x),这个地方可能会有人有疑虑,为什么要在softmax()之后再加一次log操作,这是我看到的最棒的解释https://www.zhihu.com/question/358069078/answer/912691444作者说logsoftmax能够解决函数上溢和下溢的问题,加快运算速度,提高数据稳定性。简单说,因为softmax会进行指数操作,当xi的值过大或者过小的时候exp(x_i)可能会超过float的表示范围(即上溢),也可能四舍五入为0,导致下溢。而使用logsoftmax(),计算机实际实现的时候过程是下面这样的:
![在这里插入图片描述](https://img-blog.csdnimg.cn/28451030a45840cf9ba6d9e367b68365.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a6L5bqG5by6,size_20,color_FFFFFF,t_70,g_se,x_16)
>>> x = torch.Tensor([-4, 2, -3.2, 0, 7])
>>> softmax = torch.exp(x)/torch.sum(torch.exp(x))
>>> softmax
tensor([1.6574e-05, 6.6864e-03, 3.6886e-05, 9.0491e-04, 9.9236e-01])
>>> torch.sum(softmax)
tensor(1.)
>>> logsoftmax = torch.log(softmax)
>>> logsoftmax
tensor([-1.1008e+01, -5.0077e+00, -1.0208e+01, -7.0077e+00, -7.6741e-03])
>>> torch.sum(logsoftmax)
tensor(-33.2384)

NLLLoss()

有了前面知识的铺垫,下面我们来讲解一下和nn.CrossEntropyLoss()直接相关的NLLLoss(),即负对数似然损失函数(Negtive Log Likehood)
其中y_i是one_hot编码后的数据标签,NLLLoss()得到的结果即是y_i与logsoftmax()激活后的结果相乘再求均值再取反。(实际在用封装好的函数时,传入的标签无需进行one_hot编码)

>>> x = torch.randn(5, 5)
>>> target = torch.tensor([0, 2, 3, 1, 4])
>>> one_hot = F.one_hot(target).float()
>>> one_hot
tensor([[1., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0.],
        [0., 0., 0., 1., 0.],
        [0., 1., 0., 0., 0.],
        [0., 0., 0., 0., 1.]])
>>> softmax = torch.exp(x)/torch.sum(torch.exp(x), dim = 1).reshape(-1, 1)
>>>> softmax
tensor([[0.5820, 0.0178, 0.1052, 0.0535, 0.2415],
        [0.5053, 0.1861, 0.0541, 0.0890, 0.1655],
        [0.1686, 0.7009, 0.0704, 0.0398, 0.0203],
        [0.5175, 0.3266, 0.0382, 0.0620, 0.0558],
        [0.0827, 0.3674, 0.2062, 0.3027, 0.0410]])
>>> x
tensor([[ 1.5110, -1.9791, -0.1996, -0.8766,  0.6315],
        [ 0.9674, -0.0315, -1.2678, -0.7686, -0.1487],
        [ 0.9171,  2.3417,  0.0434, -0.5264, -1.2022],
        [ 1.4858,  1.0255, -1.1200, -0.6367, -0.7411],
        [-1.1258,  0.3654, -0.2123,  0.1717, -1.8274]])
>>> logsoftmax = torch.log(softmax)
>>> logsoftmax
tensor([[-0.5412, -4.0313, -2.2518, -2.9288, -1.4207],
        [-0.6826, -1.6815, -2.9178, -2.4186, -1.7987],
        [-1.7800, -0.3554, -2.6537, -3.2235, -3.8993],
        [-0.6588, -1.1191, -3.2647, -2.7814, -2.8858],
        [-2.4925, -1.0013, -1.5790, -1.1949, -3.1941]])
>>> llloss = -torch.sum(one_hot*logsoftmax)/target.shape[0]
>>> llloss
tensor(2.1992)

###下面用torch.nn.function实现一下以验证上述结果的正确性

>>> logsoftmax = F.log_softmax(x, dim = 1)
>>> nllloss = F.nll_loss(logsoftmax, target)
>>> nllloss
tensor(2.1992)

###最后我们直接用torch.nn.CrossEntropyLoss验证一下以上两种方法的正确性

>>> cross_entropy = F.cross_entropy(x, target)
>>> cross_entropy
tensor(2.1992)
>>> loss=nn.CrossEntropyLoss()
>>> loss(x,target)
tensor(2.1992)

也就是说交叉熵损失就是把log_softmax和nll_loss结合起来了而已

https://zhuanlan.zhihu.com/p/159477597
https://blog.csdn.net/Lyndon0_0/article/details/104457564

你可能感兴趣的:(pytorch,pytorch,python)