假设在进入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=1Ceakeai∀i∈1…C
通过上式可以保证 ∑ i = 1 C y i = 1 \sum_{i=1}^{C}y_i=1 ∑i=1Cyi=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的。
g ( x ) = 1 1 + e − x g(x)=\frac{1}{1+e^{-x}} g(x)=1+e−x1
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函数 | 二分类交叉熵损失函数 |
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=1∑qyj(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=1∑nH(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个样本的损失,则将每个样本计算交叉熵损失,然后求均值
输入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均值。
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^)=−n1i∑n(yilog(yi^)+(1−yi)log(1−yi^))
注意: 上面是对一个样本的计算, 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=m1∑loss(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>)
Softmax函数与交叉熵