熵、交叉熵、KL散度与Softmax

1. 熵(Shannon Entropy)

熵衡量的是一个概率分布的不确定性信息量。分布越均匀,熵越高;分布越集中,熵越低。

H(P)=−∑ipilog⁡pi 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)=−∑ipilog⁡qi 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)=∑ipilog⁡piqi 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)log⁡p(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∑ilog⁡q(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,数值上更稳定,也避免了两次浮点运算:

log⁡softmax(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,证据下界)的形式出现:

log⁡p(x)≥Eq(z∣x)[log⁡p(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         ← 加权的交叉熵

树模型中的信息量
├── 信息增益           ← 本质是互信息
└── 基尼不纯度         ← 熵的近似替代

这些概念从不同角度回答的都是同一个核心问题:如何量化不确定性,以及如何衡量两个概率分布之间的差异

相关推荐
隔壁大炮1 小时前
CNN图像分类案例
人工智能·pytorch·python·深度学习·算法·分类·cnn
量子炒饭大师1 小时前
【2026年全新 Images-2.0 使用教程】(附AI生图提示词+完整使用指南)
人工智能·chatgpt·ai生图·image 2.0
my1_1my1 小时前
AD-MT
人工智能·机器学习·机器翻译
littleM1 小时前
深度拆解 HermesAgent(七):CLI、安全与部署实践指南
人工智能·安全·架构
极智视界1 小时前
分类数据集 - 动物分类数据集下载
人工智能·yolo·数据集·图像分类·动物分类·算法训练
她说人狗殊途1 小时前
概率密度函数 & 累积分布函数
人工智能·机器学习
墨染天姬1 小时前
[AI]ai应用框架LangChain
人工智能·langchain
Flying pigs~~1 小时前
大模型Prompt-Tuning技术详解:从入门到进阶
人工智能·大模型·微调·prompt
FrontAI1 小时前
深入浅出 LangGraph —— 第8章:人机交互:中断与审批流程
人工智能·langchain·人机交互·ai agent·langgraph