1. 熵(Shannon Entropy)
熵衡量的是一个概率分布的不确定性 或信息量。分布越均匀,熵越高;分布越集中,熵越低。
H(P)=−∑ipilogpi H(P) = -\sum_{i} p_i \log p_i H(P)=−i∑pilogpi
直觉理解:抛一枚均匀硬币(50/50),你完全不确定结果,熵最大;抛一枚两面都是正面的硬币,你完全确定结果,熵为 0。
python
import numpy as np
def entropy(p: np.ndarray) -> float:
"""
计算离散概率分布的香农熵
p: 概率分布,元素之和为1
"""
p = np.array(p, dtype=float)
# 过滤掉 p=0 的项,避免 log(0)(0*log(0) 定义为 0)
mask = p > 0
return -np.sum(p[mask] * np.log(p[mask]))
# 示例
p_uniform = [0.25, 0.25, 0.25, 0.25] # 均匀分布,熵最大
p_certain = [1.0, 0.0, 0.0, 0.0] # 确定分布,熵为 0
p_skewed = [0.7, 0.1, 0.1, 0.1] # 偏斜分布
print(f"均匀分布熵: {entropy(p_uniform):.4f}") # ln(4) ≈ 1.3863
print(f"确定分布熵: {entropy(p_certain):.4f}") # 0.0
print(f"偏斜分布熵: {entropy(p_skewed):.4f}")
2. 交叉熵(Cross-Entropy)
交叉熵衡量的是:用分布 (Q) 来编码真实分布 (P) 中的事件,平均需要多少比特(信息量)。
H(P,Q)=−∑ipilogqi H(P, Q) = -\sum_{i} p_i \log q_i H(P,Q)=−i∑pilogqi
在分类任务中,(P) 是真实标签(one-hot),(Q) 是模型预测的概率,交叉熵就是最常用的分类损失函数。
python
def cross_entropy(p: np.ndarray, q: np.ndarray) -> float:
"""
计算 H(P, Q)
p: 真实分布(如 one-hot 标签)
q: 预测分布(如 softmax 输出)
"""
p = np.array(p, dtype=float)
q = np.array(q, dtype=float)
# 加上极小值 epsilon 防止 log(0)
eps = 1e-12
return -np.sum(p * np.log(q + eps))
# 示例:真实类别为第0类
p_true = [1.0, 0.0, 0.0] # 真实标签(one-hot)
q_good = [0.9, 0.05, 0.05] # 好的预测
q_bad = [0.1, 0.5, 0.4] # 差的预测
print(f"好预测的交叉熵: {cross_entropy(p_true, q_good):.4f}") # 小
print(f"差预测的交叉熵: {cross_entropy(p_true, q_bad):.4f}") # 大
3. KL 散度(Kullback-Leibler Divergence)
KL 散度衡量分布 (Q) 相对于 (P) 的差异程度,也叫相对熵。
DKL(P∥Q)=∑ipilogpiqi D_{KL}(P \| Q) = \sum_{i} p_i \log \frac{p_i}{q_i} DKL(P∥Q)=i∑pilogqipi
关键性质:
- (D_{KL}(P | Q) \geq 0),当且仅当 (P = Q) 时等于 0(Gibbs 不等式)
- 不对称:(D_{KL}(P | Q) \neq D_{KL}(Q | P)),因此不是真正的"距离"
与熵和交叉熵的核心关系:
DKL(P∥Q)=H(P,Q)−H(P) D_{KL}(P \| Q) = H(P, Q) - H(P) DKL(P∥Q)=H(P,Q)−H(P)
这个公式揭示了三者的本质联系:KL散度 = 交叉熵 - 真实分布的熵 。在分类任务中,由于真实标签 (P) 是固定的(熵为常数),最小化交叉熵等价于最小化 KL 散度。
python
def kl_divergence(p: np.ndarray, q: np.ndarray) -> float:
"""
计算 D_KL(P || Q)
"""
p = np.array(p, dtype=float)
q = np.array(q, dtype=float)
eps = 1e-12
mask = p > 0
return np.sum(p[mask] * np.log(p[mask] / (q[mask] + eps)))
# 验证核心关系:KL = 交叉熵 - 熵
p = np.array([0.4, 0.3, 0.3])
q = np.array([0.2, 0.5, 0.3])
kl = kl_divergence(p, q)
h_p = entropy(p)
h_pq = cross_entropy(p, q)
print(f"H(P) = {h_p:.4f}")
print(f"H(P, Q) = {h_pq:.4f}")
print(f"KL(P||Q) = {kl:.4f}")
print(f"H(P,Q)-H(P) = {h_pq - h_p:.4f}") # 应与 KL 相等
print(f"非对称验证: KL(P||Q)={kl:.4f}, KL(Q||P)={kl_divergence(q, p):.4f}")
4. Softmax
Softmax 将任意实数向量(logits)映射为一个合法的概率分布(元素非负且和为 1)。
softmax(zi)=ezi∑jezj \text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j} e^{z_j}} softmax(zi)=∑jezjezi
数值稳定技巧:直接计算 (e^{z_i}) 可能溢出,实践中减去最大值:
softmax(zi)=ezi−zmax∑jezj−zmax \text{softmax}(z_i) = \frac{e^{z_i - z_{\max}}}{\sum_{j} e^{z_j - z_{\max}}} softmax(zi)=∑jezj−zmaxezi−zmax
python
def softmax(z: np.ndarray) -> np.ndarray:
"""
数值稳定的 softmax 实现
"""
z = np.array(z, dtype=float)
z_shifted = z - np.max(z) # 减去最大值,防止 exp 溢出
exp_z = np.exp(z_shifted)
return exp_z / np.sum(exp_z)
# 示例
logits = np.array([2.0, 1.0, 0.1])
probs = softmax(logits)
print(f"logits: {logits}")
print(f"softmax: {probs}")
print(f"概率和: {probs.sum():.4f}") # 必为 1.0
# 温度参数 T 控制分布的"尖锐程度"
def softmax_with_temperature(z, T=1.0):
return softmax(np.array(z) / T)
print("\n温度参数的影响:")
print(f"T=0.5 (更尖锐): {softmax_with_temperature(logits, T=0.5)}")
print(f"T=1.0 (标准): {softmax_with_temperature(logits, T=1.0)}")
print(f"T=5.0 (更均匀): {softmax_with_temperature(logits, T=5.0)}")
5. 完整流程:从 Logits 到 Loss
下面把所有概念串联起来,展示一次完整的神经网络前向传播与损失计算:
python
# -------------------------------------------------------
# 完整示例:多分类任务中所有概念的协同工作
# -------------------------------------------------------
np.random.seed(42)
# 模拟神经网络输出的 logits(未归一化得分)
logits = np.array([3.0, 1.0, 0.5, -0.5])
# 真实标签(类别 0),转为 one-hot
num_classes = 4
true_class = 0
p_true = np.eye(num_classes)[true_class] # [1, 0, 0, 0]
# Step 1: Softmax → 预测概率分布 Q
q_pred = softmax(logits)
print("=== 前向传播 ===")
print(f"Logits: {logits}")
print(f"Softmax 输出 Q: {q_pred.round(4)}")
print(f"真实分布 P: {p_true}")
# Step 2: 计算各个信息量
h_p = entropy(p_true) # one-hot 的熵为 0
h_pq = cross_entropy(p_true, q_pred) # 交叉熵损失
kl = kl_divergence(p_true, q_pred) # KL 散度
print("\n=== 信息量计算 ===")
print(f"H(P) = {h_p:.4f} ← 真实标签熵(one-hot 恒为 0)")
print(f"H(P,Q) = {h_pq:.4f} ← 交叉熵损失(训练目标)")
print(f"KL(P||Q) = {kl:.4f} ← KL 散度")
print(f"\n验证: H(P,Q) - H(P) = {h_pq - h_p:.4f} ≈ KL = {kl:.4f} ✓")
print("\n结论: one-hot 标签时,最小化交叉熵 = 最小化 KL 散度")
6. 概念关系总结
这几个概念形成了一个完整的体系,理清它们的关系对理解深度学习至关重要。
数学关系链:
DKL(P∥Q)⏟KL 散度=H(P,Q)⏟交叉熵−H(P)⏟信息熵 \underbrace{D_{KL}(P \| Q)}{\text{KL 散度}} = \underbrace{H(P, Q)}{\text{交叉熵}} - \underbrace{H(P)}_{\text{信息熵}} KL 散度 DKL(P∥Q)=交叉熵 H(P,Q)−信息熵 H(P)
在分类任务中的角色:
| 概念 | 作用 | 输入 |
|---|---|---|
| Softmax | 将 logits 转为概率 | 神经网络原始输出 |
| 熵 | 度量单个分布的不确定性 | 真实或预测分布 |
| 交叉熵 | 分类损失函数 | 真实分布 (P) + 预测分布 (Q) |
| KL 散度 | 度量两分布之间的差异 | 两个概率分布 |
核心洞察: 当真实标签为 one-hot 分布时,其自身的熵 (H§ = 0),因此交叉熵损失在数值上完全等于 KL 散度。这意味着用交叉熵训练分类器,本质上是在驱使模型的预测分布 (Q) 尽可能地"靠近"真实分布 P。
还有很多相关概念,大致可以分为以下几个方向:
1. 信息论的完整家族
前面只介绍了熵和KL散度,但信息论中还有几个紧密相关的概念共同构成一个完整体系。
**联合熵(Joint Entropy)**衡量两个变量联合分布的不确定性:
H(X,Y)=−∑x,yp(x,y)logp(x,y)H(X, Y) = -\sum_{x,y} p(x,y) \log p(x,y)H(X,Y)=−x,y∑p(x,y)logp(x,y)
**条件熵(Conditional Entropy)**衡量在已知 (X) 的条件下,(Y) 的剩余不确定性:
H(Y∣X)=H(X,Y)−H(X)H(Y \mid X) = H(X, Y) - H(X)H(Y∣X)=H(X,Y)−H(X)
**互信息(Mutual Information)**是最重要的延伸概念,衡量两个变量之间共享了多少信息:
I(X;Y)=H(X)+H(Y)−H(X,Y)=DKL(P(X,Y)∥P(X)P(Y))I(X; Y) = H(X) + H(Y) - H(X, Y) = D_{KL}(P(X,Y) \| P(X)P(Y))I(X;Y)=H(X)+H(Y)−H(X,Y)=DKL(P(X,Y)∥P(X)P(Y))
互信息与KL散度的等价形式非常深刻------它本质上是在度量联合分布与独立分布之间的差异。互信息为0当且仅当 (X) 与 (Y) 完全独立。它在特征选择、表示学习(如InfoNCE损失)和因果推断中都有核心地位。
**JS 散度(Jensen-Shannon Divergence)**是KL散度的对称化版本,解决了KL不对称的问题:
DJS(P∥Q)=12DKL (P ∥ P+Q2)+12DKL (Q ∥ P+Q2)D_{JS}(P \| Q) = \frac{1}{2} D_{KL}\!\left(P \,\Big\|\, \frac{P+Q}{2}\right) + \frac{1}{2} D_{KL}\!\left(Q \,\Big\|\, \frac{P+Q}{2}\right)DJS(P∥Q)=21DKL(P 2P+Q)+21DKL(Q 2P+Q)
JS散度的值域在 ([0, \log 2]) 之间,且 (\sqrt{D_{JS}}) 满足度量的三角不等式,比KL散度更"稳定"。原始GAN的理论推导就是建立在最小化JS散度之上的。
python
import numpy as np
def js_divergence(p, q):
p, q = np.array(p, dtype=float), np.array(q, dtype=float)
m = 0.5 * (p + q)
eps = 1e-12
kl = lambda a, b: np.sum(a[a>0] * np.log(a[a>0] / (b[a>0] + eps)))
return 0.5 * kl(p, m) + 0.5 * kl(q, m)
p = np.array([0.4, 0.3, 0.3])
q = np.array([0.2, 0.5, 0.3])
print(f"JS散度(P||Q) = {js_divergence(p, q):.4f}") # 对称
print(f"JS散度(Q||P) = {js_divergence(q, p):.4f}") # 与上面相等
2. 困惑度(Perplexity)
困惑度在NLP中极为常见,是交叉熵的直接指数化形式:
Perplexity(P,Q)=eH(P,Q)=e−1N∑ilogq(xi)\text{Perplexity}(P, Q) = e^{H(P,Q)} = e^{-\frac{1}{N}\sum_i \log q(x_i)}Perplexity(P,Q)=eH(P,Q)=e−N1∑ilogq(xi)
直觉上,困惑度代表模型在每一步预测时平均有多少个"等效选项"。一个语言模型在测试集上的困惑度为50,意味着模型平均每次预测时面临50个同等可能的选择。困惑度越低,模型越好。
python
def perplexity(p_true, q_pred):
ce = -np.sum(p_true * np.log(np.array(q_pred) + 1e-12))
return np.exp(ce)
# 好的预测 vs 差的预测
p = np.array([1.0, 0.0, 0.0])
print(f"好预测困惑度: {perplexity(p, [0.9, 0.05, 0.05]):.4f}") # 接近 1
print(f"差预测困惑度: {perplexity(p, [0.1, 0.5, 0.4]):.4f}") # 远大于 1
3. Wasserstein 距离
KL散度和JS散度有一个共同的缺陷:当两个分布的支撑集(support)没有重叠时,它们会发散到无穷大或退化为常数,梯度消失,无法指导优化。Wasserstein距离(也叫推土机距离,Earth Mover's Distance)解决了这个问题。
W(P,Q)=infγ∈Π(P,Q)E(x,y)∼γ[∥x−y∥]W(P, Q) = \inf_{\gamma \in \Pi(P,Q)} \mathbb{E}_{(x,y)\sim\gamma} [\|x - y\|]W(P,Q)=γ∈Π(P,Q)infE(x,y)∼γ[∥x−y∥]
其直觉是:把分布 (P) 看作一堆"土",把分布 (Q) 看作目标形状,Wasserstein距离是把土从 (P) 的形状"搬运"到 (Q) 的形状所需的最小搬运代价。即使两个分布完全不重叠,它也能给出一个有意义的、连续变化的距离值。这是WGAN(Wasserstein GAN)相比原始GAN训练更稳定的核心原因。
4. f-散度(f-Divergence)
KL散度、JS散度、TV距离(Total Variation)其实都是同一个大家族------f-散度的特例:
Df(P∥Q)=∑iqi⋅f (piqi)D_f(P \| Q) = \sum_i q_i \cdot f\!\left(\frac{p_i}{q_i}\right)Df(P∥Q)=i∑qi⋅f(qipi)
其中 (f) 是一个凸函数且 (f(1) = 0)。选取不同的 (f) 就得到不同的散度:
- (f(t) = t\log t) → KL散度
- (f(t) = -\log t) → 反向KL散度
- (f(t) = (\sqrt{t}-1)^2) → Hellinger距离
- (f(t) = |t-1|) → Total Variation距离
这个统一框架在变分推断和生成模型的理论分析中很有用。
5. 决策树中的信息量度
在树模型中,熵的思想以不同的形式出现。
**信息增益(Information Gain)**是决策树选择分裂特征的标准,本质上就是互信息:
IG(Y,X)=H(Y)−H(Y∣X)IG(Y, X) = H(Y) - H(Y \mid X)IG(Y,X)=H(Y)−H(Y∣X)
**基尼不纯度(Gini Impurity)**是熵的一个计算更廉价的近似替代,CART算法默认使用它:
Gini(P)=1−∑ipi2=∑i≠jpipj\text{Gini}(P) = 1 - \sum_i p_i^2 = \sum_{i \neq j} p_i p_jGini(P)=1−i∑pi2=i=j∑pipj
python
def gini_impurity(p):
p = np.array(p, dtype=float)
return 1 - np.sum(p ** 2)
def information_gain(h_parent, children_splits):
"""children_splits: list of (weight, distribution) tuples"""
h_children = sum(w * entropy(np.array(dist)) for w, dist in children_splits)
return h_parent - h_children
p = [0.5, 0.5]
print(f"均匀二分类 - 熵: {entropy(p):.4f}, 基尼: {gini_impurity(p):.4f}")
p2 = [0.9, 0.1]
print(f"偏斜二分类 - 熵: {entropy(p2):.4f}, 基尼: {gini_impurity(p2):.4f}")
6. Softmax 的变体与相关损失
Log-Softmax 是实际工程中更常用的形式,先取对数再softmax,数值上更稳定,也避免了两次浮点运算:
logsoftmax(zi)=zi−log∑jezj\log\text{softmax}(z_i) = z_i - \log\sum_j e^{z_j}logsoftmax(zi)=zi−logj∑ezj
**标签平滑(Label Smoothing)**是对one-hot标签的一种正则化,将真实分布从"完全确定"稍微平滑为:
pismooth=(1−ε)⋅pione-hot+εKp_i^{\text{smooth}} = (1 - \varepsilon) \cdot p_i^{\text{one-hot}} + \frac{\varepsilon}{K}pismooth=(1−ε)⋅pione-hot+Kε
这样做的效果是增大了真实分布的熵 (H§),防止模型过于自信,改善了泛化性能。
Focal Loss 是交叉熵的改进版,专门解决类别不平衡问题。它给易分类样本(预测概率高的)降低权重,让模型专注于难样本:
FL(pt)=−(1−pt)γlog(pt)\text{FL}(p_t) = -(1 - p_t)^\gamma \log(p_t)FL(pt)=−(1−pt)γlog(pt)
python
def focal_loss(p_true, q_pred, gamma=2.0):
q_pred = np.array(q_pred, dtype=float)
p_true = np.array(p_true, dtype=float)
eps = 1e-12
pt = np.sum(p_true * q_pred) # 真实类别的预测概率
return -((1 - pt) ** gamma) * np.log(pt + eps)
p = np.array([1.0, 0.0, 0.0])
q_easy = np.array([0.95, 0.03, 0.02]) # 容易样本
q_hard = np.array([0.4, 0.35, 0.25]) # 困难样本
print("标准交叉熵:")
print(f" 容易样本: {cross_entropy(p, q_easy):.4f}")
print(f" 困难样本: {cross_entropy(p, q_hard):.4f}")
print("Focal Loss (γ=2):")
print(f" 容易样本: {focal_loss(p, q_easy):.4f}") # 被大幅压低
print(f" 困难样本: {focal_loss(p, q_hard):.4f}") # 压低幅度小
7. 变分推断中的 ELBO
在VAE(变分自编码器)等生成模型中,KL散度以ELBO(Evidence Lower Bound,证据下界)的形式出现:
logp(x)≥Eq(z∣x)[logp(x∣z)]⏟重构损失−DKL(q(z∣x)∥p(z))⏟正则化项\log p(x) \geq \underbrace{\mathbb{E}{q(z|x)}[\log p(x|z)]}{\text{重构损失}} - \underbrace{D_{KL}(q(z|x) \| p(z))}_{\text{正则化项}}logp(x)≥重构损失 Eq(z∣x)[logp(x∣z)]−正则化项 DKL(q(z∣x)∥p(z))
这个公式把KL散度和交叉熵统一到了一个框架里:第一项是重构质量(类似交叉熵损失),第二项用KL散度约束隐变量的分布不能偏离先验太远。最大化ELBO就是VAE的训练目标。
总体关系图
信息论基础
├── 熵 H(P) ← 单分布不确定性
├── 联合熵 H(X,Y)
├── 条件熵 H(Y|X)
└── 互信息 I(X;Y) = H(X)+H(Y)-H(X,Y)
分布差异度量(散度家族)
├── KL 散度 ← 不对称,训练基础
├── JS 散度 ← 对称化的 KL,GAN 理论基础
├── Wasserstein 距离 ← 有几何意义,WGAN 基础
└── f-散度 ← 以上的统一框架
交叉熵的延伸
├── 困惑度 ← exp(交叉熵),NLP 评估
├── 标签平滑 ← 软化的交叉熵
└── Focal Loss ← 加权的交叉熵
树模型中的信息量
├── 信息增益 ← 本质是互信息
└── 基尼不纯度 ← 熵的近似替代
这些概念从不同角度回答的都是同一个核心问题:如何量化不确定性,以及如何衡量两个概率分布之间的差异。