在机器学习和深度学习中,交叉熵损失函数是衡量分类模型预测概率与真实标签之间差异的一种重要方法。本文将详细介绍二分类交叉熵和多分类交叉熵的定义、应用场景,同时会结合一些实例进行详细计算。
交叉熵
给定真实分布 P P P 和预测分布 Q,交叉熵定义为:
C E = − P ( x ) ⋅ l n Q ( x ) CE=-P(x)\cdot lnQ(x) CE=−P(x)⋅lnQ(x)
其中,
- P ( i ) P(i) P(i):真实分布中类别 i i i 的概率
- Q ( i ) Q(i) Q(i):预测分布中类别 i i i 的概率
二分类交叉熵
二分类交叉熵(Binary Cross Entropy, BCE)通常用于只有两个类别的分类问题。它的目的是最小化模型预测概率与目标标签之间的差异。
对于每个样本,模型输出一个介于0到1之间的实数,表示属于正样本的概率。因此,二分类的问题中,通常使用 s i g m o i d sigmoid sigmoid将输出映射到0-1之间,以表示正样本的概率。 s i g m o i d sigmoid sigmoid的函数表达形式为:
s i g m o i d ( z ) = σ ( z ) = e z 1 + e z = 1 1 + e − z sigmoid(z)=\sigma(z) =\frac{e^z}{1+e^z}=\frac{1}{1+e^{-z}} sigmoid(z)=σ(z)=1+ezez=1+e−z1
其中, z z z是模型的原始输出。
这是因为,二分类的问题中,只有两个类别:正样本、负样本,只需要求得正样本的概率 q q q,那么 1 − q 1-q 1−q 就是负样本的概率。
接下来可以引入,每个样本的二分类交叉熵的公式了:
B C E = − [ y ⋅ l n ( y ^ ) + ( 1 − y ) ⋅ l n ( 1 − y ^ ) ] BCE=-[y\cdot ln(\hat y)+(1-y)\cdot ln(1-\hat y)] BCE=−[y⋅ln(y^)+(1−y)⋅ln(1−y^)]
其中,
-
y y y:目标标签系数(正样本为1,负样本为0)
-
y ^ \hat y y^:模型预测为正样本的概率,一般为 s i g m o i d sigmoid sigmoid 激活函数的输出值
直观解释:
-
如果目标标签是正样本, B C E = − [ 1 ⋅ l n ( y ^ ) + 0 ⋅ l n ( 1 − y ^ ) ] = − l n ( y ^ ) = − l n ( 模型预测的正样本概率 ) BCE=-[1\cdot ln(\hat y)+0\cdot ln(1-\hat y)]=-ln(\hat y)=-ln(模型预测的正样本概率) BCE=−[1⋅ln(y^)+0⋅ln(1−y^)]=−ln(y^)=−ln(模型预测的正样本概率)
-
如果目标标签是负样本, B C E = − [ 0 ⋅ l n ( y ^ ) + 1 ⋅ l n ( 1 − y ^ ) ] = − l n ( 1 − y ^ ) = − l n ( 模型预测的负样本概率 ) BCE=-[0\cdot ln(\hat y)+1\cdot ln(1-\hat y)]=-ln(1-\hat y)=-ln(模型预测的负样本概率) BCE=−[0⋅ln(y^)+1⋅ln(1−y^)]=−ln(1−y^)=−ln(模型预测的负样本概率)
更根本的就是,如果目标标签是什么,那么 B C E BCE BCE就是模型预测成该目标标签的概率,经过 − l n -ln −ln的计算之后的结果。
由于模型预测的正样本的概率还是负样本的概率都是0到1之间的,此时 − l n -ln −ln的值都是正无穷到0之间的。也就是说,模型预测得越准,二分类交叉熵就越小。这也是符合预期的。
需要注意的是,这边 B C E BCE BCE计算出来的结果是一个样本的交叉熵。如果有多个样本,可以采用诸如 m e a n mean mean、 s u m sum sum等方式再进行处理。
代码实战
假设做垃圾邮件分类任务,目标标签 y y y表示某邮件是否为垃圾邮件。 y = 1 y=1 y=1,表示邮件是垃圾邮件, y = 0 y=0 y=0,表示邮件不是垃圾邮件。模型的预测概率 \\hat{y} 是邮件为是垃圾邮件的概率。
此时,有一封邮件是垃圾邮件(正样本、 y = 1 y=1 y=1),模型预测概率 y ^ = 0.8 \hat y = 0.8 y^=0.8(已经过 s i g m o i d sigmoid sigmoid):
B C E = − [ 1 ⋅ l n ( 0.8 ) + 0 ⋅ l n ( 0.2 ) ] = − l n ( 0.8 ) = 0.2231 BCE=-[1\cdot ln(0.8)+0\cdot ln(0.2)]=-ln(0.8)=0.2231 BCE=−[1⋅ln(0.8)+0⋅ln(0.2)]=−ln(0.8)=0.2231
此时,有一封邮件不是垃圾邮件(负样本、 y = 0 y=0 y=0),模型预测概率为 y ^ = 0.9 \hat y = 0.9 y^=0.9(已经过 s i g m o i d sigmoid sigmoid):
B C E = − [ 0 ⋅ l n ( 0.9 ) + 1 ⋅ l n ( 0.1 ) ] = − l n ( 0.1 ) = 2.3016 BCE=-[0\cdot ln(0.9)+1\cdot ln(0.1)]=-ln(0.1)=2.3016 BCE=−[0⋅ln(0.9)+1⋅ln(0.1)]=−ln(0.1)=2.3016
可以看出,前者模型预测比较靠谱,交叉熵就较小;后者模型预测不靠谱,交叉熵就较大。
从代码的角度看:
PyTorch 提供了实现二分类交叉熵的方式:torch.nn.functional.binary_cross_entropy。它的输入是经过 s i g m o i d sigmoid sigmoid之后的概率。
python
# 3个样本,模型预测值 (概率)
pred = torch.tensor([0.1, 0.7, 0.3]) # p = [0.1, 0.7, 0.3]
# 3个样本,目标标签 (正负样本混合)
target = torch.tensor([0.0, 1.0, 0.0]) # y = [0, 1, 0]
# 计算二分类交叉熵
loss = F.binary_cross_entropy(pred, target, reduction='none')
print(loss) # 输出: [-log(0.9), -log(0.7), -log(0.7)]
在实际任务中,负样本通常数量远多于正样本,因此可以通过权重来平衡正负样本的贡献。例如:
python
# 3个样本,模型预测值 (概率)
pred = torch.tensor([0.1, 0.7, 0.3]) # p = [0.1, 0.7, 0.3]
# 3个样本,目标标签
target = torch.tensor([0.0, 1.0, 0.0]) # y = [0, 1, 0]
# 3个样本,样本权重
weight = torch.tensor([0.5, 1.0, 0.5]) # 为负样本赋予较低权重
# 计算加权二分类交叉熵
loss = F.binary_cross_entropy(pred, target, weight=weight, reduction='mean')
print(loss)
多分类交叉熵
多分类交叉熵(Cross Entropy, CE)通常用于有多个类别(大于等于3个)的分类问题。它的目的是最小化模型预测概率与目标标签之间的差异。
对于每个样本,模型输出的多个介于0到1之间的实数,表示属于每个类别的概率,且所有类别概率的和为1。其中,概率最大的类别就是目标所属的分类。因此,通常使用 s o f t m a x softmax softmax函数能将一个向量的每个分量映射到0到1之间,以表示每个类别的概率。即对整个向量的输出做了归一化,保证所有分量输出的和为1。 s o f t m a x softmax softmax的函数表达形式为:
s o f t m a x i = S i = e z , i ∑ j = 1 N e z , j softmax_i=S_i=\frac{e^{z,i}}{\sum_{j=1}^Ne^{z,j}} softmaxi=Si=∑j=1Nez,jez,i
其中, z i z_i zi 是模型的原始输出, ( i = 1 , 2 , . . . , N ) (i=1,2,...,N) (i=1,2,...,N)。
这边需要和二分类的情况进行区分:
-
由于二分类只有两个类别,如果一个是 q q q,另一个自然是 1 − q 1-q 1−q,因此此时只要一个输出值就可以描述了。唯一需要做的,就是保证 q q q是在0到1之间就行了,此时 s i g m o i d sigmoid sigmoid就可以胜任;
-
由于多分类有多个类别,假设是 N N N个,那可不可以只知道 N − 1 N-1 N−1个,最后一个通过和为1计算出来呢?我们可以使用 s i g m o i d sigmoid sigmoid把那 N − 1 N-1 N−1个都转换到0到1之间,但是我们并不知道第 N N N个和前 N − 1 N-1 N−1个的相对大小关系,这是行不通的。此时 s o f t m a x softmax softmax才能胜任;
接下来可以引入,每个样本的多分类交叉熵的公式了:
C E = − ∑ i = 1 N y i ⋅ l n ( y ^ i ) CE=-\sum_{i=1}^Ny_i \cdot ln(\hat y_i) CE=−i=1∑Nyi⋅ln(y^i)
- N N N:类别数
- y i y_i yi :目标标签系数(采用 one-hot 编码表示,即目标类别对应的位置为 1,其他位置为 0)
- y ^ i \hat y_i y^i :模型预测为 i i i类别的概率,一般为 s o f t m a x softmax softmax 后的输出值
直观解释:
如果目标标签是类别 k k k,那么此时 y 1 , y 2 , . . . , y N y_1,y_2,...,y_N y1,y2,...,yN之间只有 y k y_k yk为1,其他都是0,那么此时多分类交叉熵可以简化成:
C E = − l n ( y ^ k ) = − l n ( 模型预测类别 k 的概率 ) CE=-ln(\hat y_k)=-ln(模型预测类别k的概率) CE=−ln(y^k)=−ln(模型预测类别k的概率)
更根本的就是,如果目标标签是什么,那么 C E CE CE就是模型预测成该目标标签的概率,经过 − l n -ln −ln的计算之后的结果。
由于模型预测到目标类别的概率是0到1之间的,此时 − l n -ln −ln的值都是正无穷到0之间的。也就是说,模型预测得越准,二分类交叉熵就越小。这也是符合预期。
从这里看,多分类交叉熵和二分类交叉熵,从本质上看,是一致的。只是二分类交叉熵为了省去 ∑ \sum ∑符号,采用计算上的一些讨巧方式,表示成:
B C E = − [ y ⋅ l n ( y ^ ) + ( 1 − y ) ⋅ l n ( 1 − y ^ ) ] BCE=-[y\cdot ln(\hat y)+(1-y)\cdot ln(1-\hat y)] BCE=−[y⋅ln(y^)+(1−y)⋅ln(1−y^)]
可能,从理解上,会绕一些。
也就是说, C E = − l n ( 模型预测类别 k 的概率 ) CE=-ln(模型预测类别k的概率) CE=−ln(模型预测类别k的概率),这个是一个通用的描述方式。对于二分类还是多分类都是适用的。
需要注意的是,这边 C E CE CE计算出来的结果是一个样本的交叉熵。如果有多个样本,可以采用诸如 m e a n mean mean、 s u m sum sum等方式再进行处理。
代码实战
假设做动物图片分类任务,有3个类别猫、狗、鸟。目标标签 y y y表示某图片是否属于目标动物,模型的预测概率 \\hat{y} 是该图片在3个类别上的概率。
此时,有一张图片是狗,模型预测概率 y ^ = [ 0.2 0.7 0.1 ] \hat y = \begin{bmatrix}0.2& 0.7& 0.1\end{bmatrix} y^=[0.20.70.1](已经过 s o f t m a x softmax softmax):
C E = − l n ( 0.7 ) = 0.3567 CE=-ln(0.7)=0.3567 CE=−ln(0.7)=0.3567
此时,有一张图片是鸟,模型预测概率 y ^ = [ 0.1 0.7 0.2 ] \hat y = \begin{bmatrix}0.1& 0.7& 0.2\end{bmatrix} y^=[0.10.70.2](已经过 s o f t m a x softmax softmax):
B C E = − l n ( 0.2 ) = 1.6094 BCE=-ln(0.2)=1.6094 BCE=−ln(0.2)=1.6094
可以看出,前者模型预测比较靠谱,交叉熵就较小;后者模型预测不靠谱,交叉熵就较大。
当然,更常见的情况下,模型预测的概率并不一定经过 s o f t m a x softmax softmax,此时交叉熵可以转化为:
C E = − ∑ i = 1 N y i ⋅ l n ( e y ^ i ∑ j = 1 N e y ^ j ) CE=-\sum_{i=1}^Ny_i \cdot ln(\frac{e^{\hat y_i}}{\sum_{j=1}^Ne^{\hat y_j}}) CE=−i=1∑Nyi⋅ln(∑j=1Ney^jey^i)
从代码的角度看:
PyTorch提供了实现多分类交叉熵的两种方式:torch.nn.functional.cross_entropy、torch.nn.CrossEntropyLoss。它的输入是没有经过 s o f t m a x softmax softmax的概率。
python
import torch
import torch.nn.functional as F
# 模型预测值 (logits)
pred = torch.tensor([[2.0, 1.0, 0.1], [0.5, 2.5, 0.3]]) # [B, C]
# 目标标签 (类别索引)
target = torch.tensor([0, 1]) # [B]
# 计算多分类交叉熵损失
loss = F.cross_entropy(pred, target, reduction="sum")
print(loss) # 输出: 标量损失值
python
import torch
import torch.nn as nn
# 模型预测值 (logits)
pred = torch.tensor([[2.0, 1.0, 0.1], [0.5, 2.5, 0.3]]) # [B, C]
# 目标标签 (类别索引)
target = torch.tensor([0, 1]) # [B]
# 定义损失函数
criterion = nn.CrossEntropyLoss(reduction="none")
# 计算多分类交叉熵损失
loss = criterion(pred, target)
print(loss) # 输出: 标量损失值
同时,也支持加权多分类交叉熵:
python
# 类别权重
weight = torch.tensor([0.7, 1.0, 1.2]) # [C]
# 定义损失函数
criterion = nn.CrossEntropyLoss(weight=weight, reduction="none")
# 计算加权多分类交叉熵损失
loss = criterion(pred, target)
print(loss)