信息论与交叉熵:损失函数从哪来
配套视频
B站 · 3B1B《深度学习》系列 · 第 2~4 集 —— 梯度下降 / 反向传播,理解"损失函数到底在优化什么"
B站 · 搜索:StatQuest 交叉熵 / 信息熵 —— 选播放量最高的中文字幕版即可
⭐ 重点
信息量 = 惊讶程度:事件越不可能发生,它携带的信息量越大
熵 = 平均惊讶程度 = 不确定度:分布越平均,熵越大
交叉熵 = 用模型分布去"编码"真实数据的平均代价,这就是训练损失
KL 散度 = 交叉熵 − 熵:衡量两个分布差多远,最小化交叉熵等价于最小化 KL
困惑度 PPL = exp(交叉熵):LLM 评测最常用的指标,"模型平均在几个词之间犹豫"
⚡ 为什么这一篇是"核心中的核心":很多人学完 Transformer 还是说不清"训练到底在优化什么"。答案是:最小化交叉熵
4.1 信息量:用"惊讶程度"度量信息
先建立一个直觉:信息是用来消除不确定性的。太阳明天从东边升起——这句话没有信息量,因为你知道它一定发生。而"明天下雪"在七月就是大新闻。
于是信息量应该满足:
概率越小 → 信息量越大
两个独立事件的信息量应该相加(同时知道两件事 = 两份信息之和)
唯一(在连续、单调等条件下)满足这两条的构造是取对数:
自信息(self-information)
负号是为了让结果为正(因为 p ≤ 1,log p ≤ 0)。底数决定单位:底 2 是 比特(bit),底 e 是 奈特(nat)。深度学习默认用 ln(奈特),因为求导干净。
4.2 熵:平均惊讶程度 = 不确定度
有了单个事件的信息量,我们把整个分布的"平均信息量"算出来,这就是香农熵:
熵(Entropy)
注意 H(p) 只跟分布 p 自己有关,它是这个分布本身的不确定度,是"数据固有的难度"。它不依赖任何模型。
几个典型例子(用 ln,单位 nat):
训练就是要把模型的输出分布"推尖"——让正确词的 p 尽量大、其他词的 p 尽量小,从而降低交叉熵。
4.3 交叉熵:用模型分布去编码真实数据的代价
熵是"数据自己"的难度。但我们手上没有真实分布,只有模型预测的分布 q。用 q 去衡量真实分布 p 的数据,得到的就是交叉熵:
交叉熵(Cross-Entropy)
🔍 一个符号的差别,含义天差地别:熵是 −Σ p·log p,交叉熵是 −Σ p·log q。区别在于:期望是按真实分布 p 求的(数据说了算),而对数里装的是模型分布 q(模型说了算)。训练时 p 是固定的(数据不变),我们只能动 q——所以交叉熵对 q 是可优化的。
在监督训练的单个样本上,观测到的目标通常表示为 one-hot 标签(正确 token 为 1,其余为 0)。它是对未知真实条件分布的一次观测;代进经验交叉熵,只有目标 token 那一项留下:
语言模型的交叉熵损失(单个 token)
(w* = 正确词)
就是这么简单的一句话:损失 = 负的"模型给正确词的概率"的对数。模型给正确词 0.9 的概率 → 损失 0.105;只给 0.1 → 损失 2.303。给得越低,罚得越狠(对数曲线在接近 0 时爆炸)。
4.4 KL 散度:交叉熵比熵多出来的那部分
把交叉熵拆开,会得到一个漂亮的结构:
第 1 步 · 写出交叉熵
第 2 步 · 分子分母同乘 p,拆成两项
第 3 步 · 认出第一项就是熵,第二项定义为 KL 散度
KL 散度(相对熵)
KL 散度衡量"用 q 代替 p 会多付出多少代价",它永远非负,当且仅当 p = q 时取 0(这就是吉布斯不等式)。
⭐ 这一步是理解训练目标的关键
因为 H(p) 只跟数据有关、跟模型无关,是个常数,所以:
minq H(p, q) ⟺ minq DKL(p ‖ q) ⟺ 让模型分布 q 尽量逼近真实分布 p
4.5 从最大似然到交叉熵
训练目标是最大似然。现在把它推完,你会看到交叉熵是必然结果:
第 1 步 · 最大似然:让整份语料出现的概率最大
第 2 步 · 连乘不好优化,取对数变成求和(对数单调,不改变极值点)
第 3 步 · 优化器只会最小化,加负号翻转
第 4 步 · 除以 token 数 T 取平均,得到最终损失函数
交叉熵损失
⚠️ 一句话记住:最大似然 ⟺ 最小化负对数似然(NLL)⟺ 最小化交叉熵。三者是同一件事的三种说法。面试里被问到"语言模型的损失函数是什么",能把这句串起来讲,比背公式有用得多。
4.6 困惑度 PPL:交叉熵的"人话版"
交叉熵的单位是 nat,不好直观理解。取个指数就变成"平均在几个词之间犹豫":
困惑度(Perplexity)
(L 是平均交叉熵,单位 nat)
💎 工程上的意义:PPL 不需要人工任务标签,只要有带上下文的文本就能计算,常用于语言建模评估。但注意:PPL 低 ≠ 对话好用,而且只有在相同 tokenization、相同数据集和相同计算约定下才适合直接比较。较低 PPL 表示模型更擅长预测评测文本,并不直接衡量指令遵循、事实性或安全性。
用 NumPy 把四个指标全算一遍
import numpy as np
def entropy(p):
# 熵:只依赖 p 自己。0*log0 约定为 0,所以加个掩码
p = np.asarray(p, dtype=float)
m = p > 0
return -(p[m] * np.log(p[m])).sum()
def cross_entropy(p, q):
# 交叉熵:期望按 p 求,log 里装 q
p, q = np.asarray(p, float), np.asarray(q, float)
q = np.clip(q, 1e-12, 1.0) # 防止 log(0) 爆炸
return -(p * np.log(q)).sum()
def kl(p, q):
# KL = 交叉熵 - 熵
return cross_entropy(p, q) - entropy(p)
# ---- 语言模型的真实场景:正确答案只有一个词(one-hot)----
p_true = [1.0, 0.0, 0.0, 0.0]
for name, q in [
("模型很自信", [0.90, 0.05, 0.03, 0.02]),
("模型还行", [0.50, 0.20, 0.20, 0.10]),
("模型答错了", [0.05, 0.45, 0.30, 0.20]),
]:
ce = cross_entropy(p_true, q)
print(f"{name}: 交叉熵={ce:.3f} nat, 困惑度={np.exp(ce):.2f}")
# 输出:
# 模型很自信: 交叉熵=0.105 nat, 困惑度=1.11
# 模型还行: 交叉熵=0.693 nat, 困惑度=2.00
# 模型答错了: 交叉熵=2.996 nat, 困惑度=20.00
# ---- 验证 KL >= 0 ----
p = np.array([0.7, 0.2, 0.1])
q = np.array([0.5, 0.3, 0.2])
print(f"KL(p||q) = {kl(p, q):.4f}") # 0.0513
print(f"KL(q||p) = {kl(q, p):.4f}") # 0.0578 ← 注意不对称!⚠️ KL 不是距离:上面的输出里 KL(p‖q) ≠ KL(q‖p),所以 KL 散度不满足对称性,不是数学意义上的"距离"。这也是为什么 RLHF 里的 KL 惩罚项写 kl(πθ ‖ πref) 而不是反过来——方向不同,含义不同。
评论区