交叉熵和softmax函数和sigmoid函数

1.softmax

假设在进入softmax函数之前,已经有模型输出 C C C值,其中 C C C是要预测的类别数,模型可以是全连接网络的输出 a a a,其输出个数为 C C C,即输出为 a 1 , a 2 , . . . , a C a_1, a_2, ..., a_C a1,a2,...,aC
所以对每个样本,它属于类别 i i i的概率为:
y i = e a i ∑ k = 1 C e a k ∀ i ∈ 1 … C y_{i}=\frac{e^{a_{i}}}{\sum_{k=1}^{C} e^{a_{k}}} \quad \forall i \in 1 \ldots C yi=k=1Ceakeaii1C
通过上式可以保证 ∑ i = 1 C y i = 1 \sum_{i=1}^{C}y_i=1 i=1Cyi=1,即属于各个类别的概率和为1。上式即为softmax计算公式。

1.1 softmax的计算数值稳定性

python中softmax函数为:

import numpy as np
def softmax(x):
    exp = np.exp(x)
    return exp/np.sum(exp)

当数值较大时会出现如下结果

>>> softmax([1000, 2000, 3000, 4000, 5000])
array([ nan,  nan,  nan,  nan,  nan])

这是因为在求exp(x)时候溢出了,一种简单有效避免该问题的方法就是让exp(x)中的x值不要那么大或那么小,在softmax函数的分式上下分别乘以一个非零常数:
y i = e a i ∑ k = 1 C e a k = E e a i ∑ k = 1 C E e a k = e a i + log ⁡ ( E ) ∑ k = 1 C e a k + log ⁡ ( E ) = e a i + F ∑ k = 1 C e a k + F y_{i}=\frac{e^{a_{i}}}{\sum_{k=1}^{C} e^{a_{k}}}=\frac{E e^{a_{i}}}{\sum_{k=1}^{C} E e^{a_{k}}}=\frac{e^{a_{i}+\log (E)}}{\sum_{k=1}^{C} e^{a_{k}+\log (E)}}=\frac{e^{a_{i}+F}}{\sum_{k=1}^{C} e^{a_{k}+F}} yi=k=1Ceakeai=k=1CEeakEeai=k=1Ceak+log(E)eai+log(E)=k=1Ceak+Feai+F

这里 l o g ( E ) log(E) log(E) 是个常数,所以可以令它等于 F F F。加上常数之后,等式与原来还是相等的,所以我们可以考虑怎么选取常数 F F F。我们的想法是让所有的输入在0附近,这样的值不会太大,所以可以让 F F F的值为:
F = m a x ( a 1 , a 2 , . . . , a C ) F=max(a_1, a_2,...,a_C) F=max(a1,a2,...,aC)
这样子将所有的输入平移到0附近(当然需要假设所有输入之间的数值上较为接近),同时,除了最大值,其他输入值都被平移成负数, e e e为底的指数函数,越小越接近0,这种方式比得到nan的结果更好。

def softmax(x):
    shift_x = x - np.max(x)
    exp_x = np.exp(shift_x)
    return exp_x / np.sum(exp_x)

>>> softmax([1000, 2000, 3000, 4000, 5000])
array([ 0.,  0.,  0.,  0.,  1.])

当然这种做法也不是最完美的,因为softmax函数不可能产生0值,但这总比出现nan的结果好,并且真实的结果也是非常接近0的。

2. sigmoid和softmax比较

g ( x ) = 1 1 + e − x g(x)=\frac{1}{1+e^{-x}} g(x)=1+ex1

sigmoid函数输出值的范围和softmax函数一样都是位于[0, 1]

两者区别
对于二值分类问题,softmax输出两个值,这两个值和为1;对于sigmoid来说,输出也是两个值,不过没有可加性,两个值各自是0到1的某个数;

例如:
我们预测文本分为A类和B类,神经网络输出为[x, y],在经过softmax后输出可能为[0.3, 0.7],代表算法认为是A类的概率为0.3, 是B类的概率0.7,相加为1;对于sigmoid输出可能是(0.4, 0.8),他们相加不为1,解释来说sigmoid认为输出第一位为A的概率是0.4, 不为A(为B)的概率0.6, 第二位为A的概率0.8,不为A的概率0.2;

综上我们可以得出以下结论:

分类问题名称 输出层使用激活函数 对应损失函数
二分类 sigmoid函数 二分类交叉熵损失函数
多分类 softmax函数 多类别交叉熵损失函数
多标签分类 sigmoid函数 二分类交叉熵损失函数

3. 求交叉熵的步骤

3.1 多类别交叉熵损失函数

1.将标签进行one_hot编码
2.神经网络输出后接softmax层,即预测值取softmax输出来进行交叉熵计算
3.使用交叉熵公式计算:
H ( y ( i ) , y ^ ( i ) ) = − ∑ j = 1 q y j ( i ) log ⁡ y ^ j ( i ) H\left( \boldsymbol{y}^{\left( i \right)},\boldsymbol{\hat{y}}^{\left( i \right)} \right) =-\sum_{j=1}^q{y_{j}^{\left( i \right)}}\log \hat{y}_{j}^{\left( i \right)} H(y(i),y^(i))=j=1qyj(i)logy^j(i)
上面是一个样本的交叉熵计算;batch_size样本交叉熵计算如下:
ℓ ( Θ ) = 1 n ∑ i = 1 n H ( y ( i ) , y ^ ( i ) ) \ell \left( \Theta \right) =\frac{1}{n}\sum_{i=1}^n{H}\left( \boldsymbol{y}^{\left( i \right)},\boldsymbol{\hat{y}}^{\left( i \right)} \right) (Θ)=n1i=1nH(y(i),y^(i))
即将每个样本计算交叉熵,然后求均值

实例
假设有3个类别,对某一个样本来计算交叉熵:
设真实类别为3----->[0,0,1]
神经网络初始输出为[3,6,9];将其经过softmax层后---->[0.003, 0.047, 0.95]
那么交叉熵损失计算为:

-(0*lg0.003+0*lg0.047+1*lg0.95)= 0.02

如果是计算一个batch_size个样本的损失,则将每个样本计算交叉熵损失,然后求均值

3.1(2)torch中的nn.CrossEntropyLosss()使用

输入x: 不需要经过softmax,函数内部又softmax
输入target, 不需要进行one-hot,只需输入标签索引即可

import torch
import numpy as np
loss = torch.nn.CrossEntropyLoss()
# x表示输出分数,不需要经过softmax
x = torch.FloatTensor([[1,2,3]])
print("x:", x)
# y是真实的标签,不需要表示成onehot
y = torch.LongTensor([2])
# 手动计算loss
x_softmax = torch.softmax(x, dim=1)
print("x_softmax:", x_softmax)
print("loss by hand:", -np.log(0.6652))

l = loss(x, y)
print("real loss:", l)

输出

x: tensor([[1., 2., 3.]])
x_softmax: tensor([[0.0900, 0.2447, 0.6652]])
loss by hand: 0.40766753166336445
real loss: tensor(0.4076)

注意:输出是一个batch中所有loss均值。

3.2 二分类交叉熵损失函数

l o s s ( y , y ^ ) = − 1 n ∑ i n ( y i log ⁡ ( y i ^ ) + ( 1 − y i ) l o g ( 1 − y i ^ ) ) loss(y, \hat{y})=-\frac{1}{n}\sum_{i}^{n}(y_i\log(\hat{y_i})+(1-y_i)log(1-\hat{y_i})) loss(y,y^)=n1in(yilog(yi^)+(1yi)log(1yi^))

注意: 上面是对一个样本的计算, i i i是一个样本中的分值
对所有m个样本:
l o s s = 1 m ∑ l o s s ( y , y ^ ) loss = \frac{1}{m}\sum loss(y, \hat{y}) loss=m1loss(y,y^)

步骤
1.将标签进行one_hot编码
2.神经网络输出后接sigmoid层,即预测值取sigmoid输出来进行交叉熵计算
3.使用交叉熵公式计算

例子
神经网络输出数据:3个样本

       [[ 1.9072,  1.1079,  1.4906],
        [-0.6584, -0.0512,  0.7608],
        [-0.0614,  0.6583,  0.1095]]

经过sigmoid函数后:

       [[0.8707, 0.7517, 0.8162],
        [0.3411, 0.4872, 0.6815],
        [0.4847, 0.6589, 0.5273]]

设真实标签为:

				[[0, 1, 1], 
				 [1, 1, 1], 
				 [0, 0, 0]]

使用上面公式计算:
(1)对样本1:

r11 = 0 * math.log(0.8707) + (1-0) * math.log((1 - 0.8707))
r12 = 1 * math.log(0.7517) + (1-1) * math.log((1 - 0.7517))
r13 = 1 * math.log(0.8162) + (1-1) * math.log((1 - 0.8162))
r1 = -(r11 + r12 + r13) / 3
#0.8447112733378236

(2)对样本2:

r21 = 1 * math.log(0.3411) + (1-1) * math.log((1 - 0.3411))
r22 = 1 * math.log(0.4872) + (1-1) * math.log((1 - 0.4872))
r23 = 1 * math.log(0.6815) + (1-1) * math.log((1 - 0.6815))
r2 = -(r21 + r22 + r23) / 3
#0.7260397266631787

(3)对样本3:

r31 = 0 * math.log(0.4847) + (1-0) * math.log((1 - 0.4847))
r32 = 0 * math.log(0.6589) + (1-0) * math.log((1 - 0.6589))
r33 = 0 * math.log(0.5273) + (1-0) * math.log((1 - 0.5273))
r3 = -(r31 + r32 + r33) / 3
#0.8292933181294807

总共损失

bceloss = (r1 + r2 + r3) / 3 
0.8000147727101611

对应pytorch中的内置函数

loss = nn.BCELoss()
print(loss(nn.sigmoid(input), target))
tensor(0.8000, grad_fn=<BinaryCrossEntropyBackward>)

可以把sigmoid和bce的过程放到一起,使用内建的BCEWithLogitsLoss函数

loss = nn.BCEWithLogitsLoss()
print(loss(input, target))
tensor(0.8000, grad_fn=<BinaryCrossEntropyWithLogitsBackward>)

3. 参考

Softmax函数与交叉熵

你可能感兴趣的:(自然语言处理,神经网络,深度学习,python,机器学习,算法)