熵、交叉熵、KL散度、Softmax 与分类损失函数
0. 今天学习的主线
今天主要学习了以下几个概念之间的关系:
信息量 ↓ 熵 Entropy ↓ 交叉熵 Cross Entropy ↓ 相对熵 / KL 散度 ↓ 神经网络分类问题中的损失函数 ↓ Softmax + CrossEntropyLoss ↓ Sigmoid + BCEWithLogitsLoss我一开始的疑惑是:
熵到底是什么?为什么它和期望放在一起?
交叉熵为什么可以作为神经网络分类任务的损失函数?
KL 散度和交叉熵到底是什么关系?
CrossEntropyLoss 和 BCEWithLogitsLoss 有什么区别?
Softmax 为什么经常和交叉熵绑定在一起?
通过今天的学习,我逐渐把这些概念串起来了。
1. 信息量
1.1 信息量的直观理解
一个事件发生的概率越小,它一旦发生,带来的信息量就越大。
比如:
“明天太阳升起”概率极大,所以信息量很小;
“明天东京下金条雨”概率极小,所以如果真的发生,信息量很大。
所以有一个核心直觉:
概率越大,信息量越小; 概率越小,信息量越大。信息量定义为:
I(x)=−logp(x)I(x) = -\log p(x)
其中:
p(x)表示事件x发生的概率;log用来把独立事件联合发生时的概率乘法变成信息量加法;负号是因为当
0 < p(x) <= 1时,log p(x) <= 0,加负号后信息量变成非负数。
1.2 为什么用 log?
如果两个事件 A 和 B 独立,那么:
P(A,B)=P(A)P(B)P(A,B) = P(A)P(B)
我们希望两个独立事件同时发生的信息量满足:
I(A,B)=I(A)+I(B)I(A,B) = I(A) + I(B)
如果定义:
I(x)=−logp(x)I(x) = -\log p(x)
那么:
I(A,B)=−log(P(A)P(B))I(A,B) = -\log(P(A)P(B))
根据对数性质:
−log(P(A)P(B))=−logP(A)−logP(B)-\log(P(A)P(B)) = -\log P(A) - \log P(B)
所以:
I(A,B)=I(A)+I(B)I(A,B) = I(A) + I(B)
因此,log的一个重要作用就是:
把概率的乘法结构转化为信息量的加法结构。2. 熵 Entropy
2.1 熵的直观理解
熵衡量的是一个随机系统的平均信息量,也可以理解为平均不确定性。
我的理解:
熵就是系统自己描述自己。更准确地说:
熵是一个分布自身的不确定性。熵的公式是:
H(P)=−∑xP(x)logP(x)H(P) = -\sum_x P(x)\log P(x)
或者写成期望形式:
H(X)=E[−logp(X)]H(X) = E[-\log p(X)]
这说明:
熵本质上是一种期望。它不是某一个事件的信息量,而是所有可能事件的信息量,按照各自发生概率加权求平均。
2.2 熵为什么和期望放在一起?
普通期望是:
E[X]=∑xxp(x)E[X] = \sum_x x p(x)
如果我们关心的不是X本身,而是X的某个函数g(X),那么:
E[g(X)]=∑xg(x)p(x)E[g(X)] = \sum_x g(x)p(x)
熵里面取:
g(X)=−logp(X)g(X) = -\log p(X)
所以:
H(X)=E[−logp(X)]H(X) = E[-\log p(X)]
也就是说:
熵 = 信息量的期望 = 平均信息量。2.3 熵的大小怎么理解?
例如两个系统:
PA=[0.5,0.5]P_A = [0.5, 0.5] PB=[0.99,0.01]P_B = [0.99, 0.01]
系统 A 的熵更大。
因为系统 A 的两个结果一样可能,最不确定。
系统 B 几乎一定出现第一个结果,不确定性很小。
所以:
分布越均匀,熵越大; 分布越集中,熵越小。3. 交叉熵 Cross Entropy
3.1 交叉熵的直观理解
熵是:
系统自己描述自己。交叉熵是:
用另一个系统来描述这个系统。假设真实分布是P,模型分布是Q。
交叉熵公式是:
H(P,Q)=−∑xP(x)logQ(x)H(P,Q) = -\sum_x P(x)\log Q(x)
它的含义是:
真实事件按照 P 分布发生,但我用 Q 分布去描述它,平均需要付出多少信息代价。注意:
H(P)=−∑xP(x)logP(x)H(P) = -\sum_x P(x)\log P(x) H(P,Q)=−∑xP(x)logQ(x)H(P,Q) = -\sum_x P(x)\log Q(x)
区别在于:
熵:前面是
P,log 里面也是P;交叉熵:前面是
P,log 里面是Q。
所以可以记成:
熵:P 自己描述 P; 交叉熵:用 Q 描述 P。3.2 交叉熵为什么可以衡量两个分布的差异?
如果Q很接近P,说明用Q描述P不会额外浪费太多信息,交叉熵会较小。
如果Q和P差很多,说明用Q描述P会很费劲,交叉熵会变大。
所以交叉熵可以用于衡量:
模型预测分布 Q 和真实分布 P 的差异。4. KL 散度 / 相对熵
4.1 KL 散度的直观理解
KL 散度也叫相对熵,用来衡量两个分布之间的差异。
我的理解:
KL 散度就是说: 我用另外一个系统 Q 描述系统 P, 相比我用 P 自己描述 P, 到底多困难。KL 散度公式是:
DKL(P∥Q)=∑xP(x)logP(x)Q(x)D_{KL}(P \| Q) = \sum_x P(x)\log \frac{P(x)}{Q(x)}
它可以展开成:
DKL(P∥Q)=H(P,Q)−H(P)D_{KL}(P \| Q) = H(P,Q) - H(P)
也就是:
KL散度=交叉熵−熵KL散度 = 交叉熵 - 熵
更准确地说:
DKL(P∥Q)=H(P,Q)−H(P)D_{KL}(P \| Q) = H(P,Q) - H(P)
其中:
H(P,Q)是用Q描述P的总代价;H(P)是用P自己描述自己的最优代价;两者相减,就是额外多出来的信息代价。
所以可以记成:
熵:自己描述自己; 交叉熵:用别人描述自己; KL 散度:用别人描述自己,比自己描述自己多出来的代价。4.2 KL 散度是不是距离?
KL 散度经常被口语化地说成“两个分布之间的距离”,但严格来说它不是数学意义上的距离。
原因之一是:
DKL(P∥Q)≠DKL(Q∥P)D_{KL}(P \| Q) \neq D_{KL}(Q \| P)
也就是说,KL 散度是非对称的。
但真正的距离通常要求对称性:
d(P,Q)=d(Q,P)d(P,Q) = d(Q,P)
所以更严谨的说法是:
KL 散度是衡量两个概率分布差异的一种非对称信息代价。5. 熵、交叉熵、KL 散度的记忆方式
5.1 看 log 里面是谁
熵
H(P)=−∑xP(x)logP(x)H(P) = -\sum_x P(x)\log P(x)
前面是P,log 里面也是P。
所以:
P 自己描述自己。交叉熵
H(P,Q)=−∑xP(x)logQ(x)H(P,Q) = -\sum_x P(x)\log Q(x)
前面是P,log 里面是Q。
所以:
真实事件按 P 发生,但用 Q 去描述。KL 散度
DKL(P∥Q)=∑xP(x)logP(x)Q(x)D_{KL}(P \| Q) = \sum_x P(x)\log \frac{P(x)}{Q(x)}
它比较的是:
P(x)Q(x)\frac{P(x)}{Q(x)}
所以:
KL 散度衡量的是 Q 和 P 的差异。但要注意方向:
DKL(P∥Q)D_{KL}(P \| Q)
表示站在P的角度,用Q去近似P。
5.2 三个概念一句话总结
熵 H(P):真实分布 P 自己的不确定性。 交叉熵 H(P,Q):真实分布是 P,但用模型分布 Q 去描述它的总代价。 KL 散度 D_KL(P||Q):用 Q 描述 P 时,比用 P 自己描述自己多出来的额外代价。6. 为什么机器学习里经常最小化交叉熵?
因为:
DKL(P∥Q)=H(P,Q)−H(P)D_{KL}(P \| Q) = H(P,Q) - H(P)
在监督学习中:
P是真实标签分布;Q是模型预测分布。
训练模型时,真实标签P是固定的,模型只能改变预测分布Q。
所以:
H(P)H(P)
只和真实标签有关,是常数。
因此:
minQDKL(P∥Q)\min_Q D_{KL}(P \| Q)
等价于:
minQH(P,Q)\min_Q H(P,Q)
也就是说:
最小化交叉熵,本质上也在最小化真实分布 P 和模型分布 Q 之间的 KL 散度。7. 分类问题中的交叉熵
7.1 多分类问题中的真实标签
在单标签多分类任务中,一个样本只属于一个类别。
例如类别为:
[猫, 狗, 兔子]如果真实答案是狗,那么真实分布可以写成 one-hot:
P=[0,1,0]P = [0,1,0]
模型预测分布可能是:
Q=[0.2,0.6,0.2]Q = [0.2,0.6,0.2]
交叉熵公式:
H(P,Q)=−∑iPilogQiH(P,Q) = -\sum_i P_i \log Q_i
代入:
H(P,Q)=−(0⋅log0.2+1⋅log0.6+0⋅log0.2)H(P,Q) = -(0\cdot \log 0.2 + 1\cdot \log 0.6 + 0\cdot \log 0.2)
所以:
H(P,Q)=−log0.6H(P,Q) = -\log 0.6
这说明:
在 one-hot 标签下,交叉熵只关心模型给真实类别分配了多少概率。7.2 分类交叉熵的本质
如果真实类别是第k类,那么:
CE=−logQkCE = -\log Q_k
其中Q_k是模型给真实类别的预测概率。
所以:
真实类别的预测概率越大,交叉熵越小; 真实类别的预测概率越小,交叉熵越大。例如:
Q真实类别=0.9Q_{\text{真实类别}} = 0.9
则:
−log0.9-\log 0.9
很小。
如果:
Q真实类别=0.01Q_{\text{真实类别}} = 0.01
则:
−log0.01-\log 0.01
很大。
所以分类训练可以理解成:
让模型越来越相信正确答案。8. 最大似然估计和交叉熵的关系
8.1 最大似然的思想
最大似然估计的思想是:
既然这些数据已经发生了,那就选择一组参数,使得这些数据发生的可能性最大。分类模型输出的是:
Pθ(y∣x)P_\theta(y|x)
表示在输入x的条件下,模型认为标签是y的概率。
训练数据为:
(x1,y1),(x2,y2),...,(xn,yn)(x_1,y_1),(x_2,y_2),...,(x_n,y_n)
整批数据的似然为:
L(θ)=∏i=1nPθ(yi∣xi)L(\theta)=\prod_{i=1}^{n}P_\theta(y_i|x_i)
最大似然就是:
maxθ∏i=1nPθ(yi∣xi)\max_\theta \prod_{i=1}^{n}P_\theta(y_i|x_i)
8.2 从最大似然到交叉熵
对似然取 log:
logL(θ)=∑i=1nlogPθ(yi∣xi)\log L(\theta)=\sum_{i=1}^{n}\log P_\theta(y_i|x_i)
最大化 log 似然等价于最小化负 log 似然:
minθ−∑i=1nlogPθ(yi∣xi)\min_\theta -\sum_{i=1}^{n}\log P_\theta(y_i|x_i)
而对于 one-hot 标签,单个样本的交叉熵就是:
−logPθ(yi∣xi)-\log P_\theta(y_i|x_i)
所以:
最小化交叉熵 = 最大化真实标签的似然。也就是说:
交叉熵不是凭空来的,它可以从最大似然估计自然推导出来。9. CrossEntropyLoss 和 BCEWithLogitsLoss 的区别
9.1 CrossEntropyLoss
CrossEntropyLoss 通常用于:
单标签多分类任务。特点是:
多个类别互斥,只能选一个。例如:
判断一张图片是猫、狗、兔子中的哪一个。真实标签是 one-hot:
[0,1,0][0,1,0]
但在 PyTorch 中通常直接用类别编号:
target = torch.tensor([1])模型输出 logits:
logits.shape = [batch_size, num_classes]使用:
loss_fn = torch.nn.CrossEntropyLoss() loss = loss_fn(logits, target)注意:
PyTorch 的 CrossEntropyLoss 直接接收 logits, 不需要手动 softmax。因为它内部已经包含:
LogSoftmax + NLLLoss9.2 BCEWithLogitsLoss
BCEWithLogitsLoss 通常用于:
二分类任务或多标签分类任务。它的关键不是“整个任务只有两个类别”,而是:
每个标签都是一个独立的是/否判断。例如多标签任务:
判断一张图片里是否有猫、是否有狗、是否有草地、是否有人。标签可以是:
[1,0,1,1][1,0,1,1]
表示:
有猫;
没有狗;
有草地;
有人。
这不是 one-hot,因为可以有多个 1。
此时每一维都是一个二元判断:
是不是猫? 是不是狗? 是不是草地? 是不是人?使用:
loss_fn = torch.nn.BCEWithLogitsLoss() loss = loss_fn(logits, target.float())其中 logits 的形状通常是:
logits.shape = [batch_size, num_labels]target 的形状也是:
target.shape = [batch_size, num_labels]9.3 CE 和 BCE 的核心区别
| 任务类型 | 类别关系 | 输出方式 | 损失函数 |
|---|---|---|---|
| 单标签多分类 | 类别互斥,只能选一个 | Softmax | CrossEntropyLoss |
| 二分类 | 是/不是 | Sigmoid | BCEWithLogitsLoss |
| 多标签分类 | 每个标签独立判断是/否 | Sigmoid | BCEWithLogitsLoss |
记忆方式:
CrossEntropyLoss:单选题。 BCEWithLogitsLoss:一堆判断题。或者:
CE:多个类别里选一个。 BCE:每个类别分别判断有没有。10. Softmax 为什么经常和交叉熵绑定在一起?
10.1 神经网络先输出 logits
神经网络最后一层通常先输出 logits。
例如三分类:
z=[2.0,1.0,0.1]z = [2.0,1.0,0.1]
这些 logits 是原始分数,不是概率。
它们可能:
有负数;
不在 0 到 1 之间;
加起来不等于 1。
所以不能直接拿来当概率。
10.2 Softmax 把 logits 转换成概率分布
Softmax 公式:
qi=ezi∑jezjq_i = \frac{e^{z_i}}{\sum_j e^{z_j}}
它把 logits 转成概率分布:
q=[0.66,0.24,0.10]q = [0.66,0.24,0.10]
满足:
q1+q2+q3=1q_1+q_2+q_3=1
并且:
0<qi<10<q_i<1
所以 softmax 的作用是:
把神经网络的原始打分转换成互斥类别上的概率分布。10.3 为什么单标签多分类需要 Softmax?
因为单标签多分类中,类别是互斥的。
例如一张图片只能是:
猫 / 狗 / 兔子中的一个。
所以:
P(猫)+P(狗)+P(兔子)=1P(猫)+P(狗)+P(兔子)=1
这刚好和 softmax 的输出特点一致。
10.4 Softmax + CrossEntropy 的分工
Softmax 的作用:
把 logits 变成概率分布。交叉熵的作用:
惩罚模型给真实类别的概率太低。整体流程是:
logits ↓ softmax ↓ 预测概率分布 Q ↓ 和真实 one-hot 分布 P 计算交叉熵 ↓ 反向传播更新参数所以:
Softmax 和交叉熵经常绑定,是因为它们一起解决了单标签多分类问题。10.5 PyTorch 中不要手动 softmax
在 PyTorch 中,正确写法是:
logits = model(x) loss = torch.nn.CrossEntropyLoss()(logits, target)不要写成:
prob = torch.softmax(logits, dim=1) loss = torch.nn.CrossEntropyLoss()(prob, target)因为CrossEntropyLoss内部已经做了稳定版本的 softmax 和 log 计算。
11. Softmax + CrossEntropy 的梯度直觉
如果:
q=softmax(z)q = softmax(z)
交叉熵损失:
L=−∑iyilogqiL = -\sum_i y_i \log q_i
那么对 logits 的梯度有一个非常简洁的形式:
∂L∂zi=qi−yi\frac{\partial L}{\partial z_i} = q_i - y_i
也就是说:
梯度 = 模型预测概率 - 真实标签概率。例如真实类别是猫:
y=[1,0,0]y = [1,0,0]
模型预测:
q=[0.66,0.24,0.10]q = [0.66,0.24,0.10]
那么:
q−y=[−0.34,0.24,0.10]q-y = [-0.34,0.24,0.10]
直观含义:
对正确类别猫,梯度会推动它的 logit 变大;
对错误类别狗和兔子,梯度会推动它们的 logit 变小。
所以 Softmax + CrossEntropy 的训练行为是:
提高正确类别的分数,压低错误类别的分数。这也是它们经常绑定的原因之一。
12. 今天我原本的疑惑和现在的理解
疑惑 1:熵到底是什么?
原本疑惑:
熵是不是系统的信息量?现在理解:
熵是随机系统的平均信息量,也就是平均不确定性。 它可以理解成系统自己描述自己的信息代价。疑惑 2:为什么熵和期望放在一起?
现在理解:
因为熵本质上就是信息量的期望。公式:
H(X)=E[−logp(X)]H(X)=E[-\log p(X)]
疑惑 3:交叉熵和 KL 散度怎么区分?
现在理解:
熵:自己描述自己。 交叉熵:用别人描述自己。 KL 散度:用别人描述自己,比自己描述自己多出来的代价。公式:
DKL(P∥Q)=H(P,Q)−H(P)D_{KL}(P \| Q)=H(P,Q)-H(P)
疑惑 4:分类为什么用交叉熵?
现在理解:
分类模型输出的是一个概率分布 Q; 真实标签可以看成 one-hot 分布 P; 交叉熵可以衡量 Q 和 P 的差异。在 one-hot 情况下:
CE=−logQ真实类别CE=-\log Q_{\text{真实类别}}
所以分类训练就是:
让模型给真实类别的概率越来越大。疑惑 5:二元交叉熵是不是只能用于两个类别?
现在理解:
不是。BCE 的“二元”指的是:
每个标签都是一个二元判断:是 / 不是。所以 BCE 可以用于:
二分类;
多标签分类。
疑惑 6:Softmax 为什么和交叉熵绑定?
现在理解:
Softmax 把 logits 转换成互斥类别上的概率分布; 交叉熵衡量这个预测分布和真实 one-hot 分布的差异。所以:
Softmax + CrossEntropyLoss 适合单标签多分类任务。13. 最终总结
今天学习内容可以浓缩成下面几句话:
信息量表示某个事件发生后带来的意外程度,概率越小,信息量越大。 熵是一个分布自身的平均信息量,可以理解为系统自己描述自己。 交叉熵是用另一个分布 Q 去描述真实分布 P 的平均代价。 KL 散度是用 Q 描述 P,相比用 P 自己描述自己,多出来的额外信息代价。 在分类任务中,真实标签 P 是固定的,模型预测分布 Q 是可训练的。 因此最小化交叉熵等价于让 Q 越来越接近 P。 对于单标签多分类任务,类别互斥,所以使用 softmax 把 logits 转成概率和为 1 的分布,再用 CrossEntropyLoss。 对于二分类或多标签任务,每个标签都是独立的是/否判断,所以使用 sigmoid + BCEWithLogitsLoss。14. 代码层面的最终记忆
单标签多分类
import torch import torch.nn as nn logits = model(x) # shape: [batch_size, num_classes] target = torch.tensor([0, 2, 1]) # 类别编号,不是 one-hot loss_fn = nn.CrossEntropyLoss() loss = loss_fn(logits, target)记住:
CrossEntropyLoss 接收 logits,不要手动 softmax。二分类 / 多标签分类
import torch import torch.nn as nn logits = model(x) # shape: [batch_size, num_labels] target = torch.tensor([[1, 0, 1, 1]]).float() loss_fn = nn.BCEWithLogitsLoss() loss = loss_fn(logits, target)记住:
BCEWithLogitsLoss 接收 logits,不要手动 sigmoid。15. 最后自测题
题 1
为什么 KL 散度不是严格意义上的距离?
答案:
因为 KL 散度不对称: D_KL(P||Q) 不一定等于 D_KL(Q||P)。题 2
真实类别是狗,类别顺序为:
[猫, 狗, 兔子]模型预测:
Q=[0.2,0.6,0.2]Q=[0.2,0.6,0.2]
交叉熵是多少?
答案:
−log0.6-\log 0.6
题 3
什么时候用 CrossEntropyLoss?什么时候用 BCEWithLogitsLoss?
答案:
单标签多分类,类别互斥,只能选一个,用 CrossEntropyLoss。 二分类或多标签分类,每个标签独立判断是/否,用 BCEWithLogitsLoss。