上一课我们用最大似然(MLE)把"概率假设"翻译成了"损失函数":分类得到交叉熵、高斯回归得到 MSE。但你可能还有个疑问——为什么那个 −∑ p log q 长这样?它到底在度量什么?为什么我们优化它,却说自己在"逼近真实分布"?这一课用信息论把这层窗户纸彻底捅破:交叉熵、KL 散度、最大似然其实是同一件事的三种说法,它们在一个恒等式上"三线汇合"。
读完这一课,你将能够
- 用"惊讶程度"解释自信息 \(-\log p\),并说清 bit 与 nat 的区别;
- 手算一个离散分布的熵 \(H(p)\)、交叉熵 \(H(p,q)\) 与 KL 散度 \(D_{\mathrm{KL}}(p\|q)\),并验证恒等式 \(H(p,q)=H(p)+D_{\mathrm{KL}}(p\|q)\);
- 说明为什么"最小化交叉熵 ⟺ 最小化 KL ⟺ 最大化似然",以及为何我们优化交叉熵而非直接优化 KL;
- 区分前向 KL(覆盖众峰)与反向 KL(锁单峰)的行为差异,并指出 KL 为什么不是距离;
- 用 numpy 实现 entropy/cross_entropy/kl,并理解 log-sum-exp 的数值稳定技巧。
一、自信息:一件事有多"令人惊讶"
先抛开公式,想一个现实问题:一条消息携带的"信息量"该怎么量化?
直觉上有三条要求。第一,越罕见的事,发生时信息量越大。"今天太阳照常升起"几乎是废话(信息量≈0),"楼下中了一注头奖"则信息量爆表。第二,必然发生的事(概率为 1)信息量应为 0。第三,两个独立事件一起发生,信息量应该相加:你告诉我"硬币正面"再告诉我"骰子是 6",总信息量等于分别告知之和。
什么函数同时满足"概率越小值越大""\(p=1\) 时为 0""乘法变加法"?答案是负对数:
\[ I(x) = -\log p(x). \]把它叫作自信息(self-information)或惊讶度(surprisal)。验证三条性质:\(p\to 0\) 时 \(-\log p\to+\infty\)(越罕见越惊讶);\(p=1\) 时 \(-\log 1=0\)(毫不惊讶);独立事件 \(p(x,y)=p(x)p(y)\),于是 \(-\log p(x,y)=-\log p(x)-\log p(y)\)(信息相加)。乘法变加法,正是对数的看家本领。
bit 还是 nat?只是换了把尺子。用 \(\log_2\) 时单位是 bit(比特):一次公平抛硬币的结果 \(p=1/2\),自信息 \(-\log_2(1/2)=1\) bit——恰好是"一个二进制位"能携带的信息。用自然对数 \(\ln\) 时单位是 nat(奈特)。两者只差一个常数因子:从 nat 换到 bit 要除以 \(\ln 2\),即 \(1\text{ nat}=1/\ln 2\approx 1.4427\text{ bit}\),反过来 \(1\text{ bit}=\ln 2\approx 0.6931\text{ nat}\)。深度学习里一律用 \(\ln\)(求导干净),所以本课公式默认 \(\ln\),只在讲"编码长度"直觉时用 \(\log_2\)。
二、熵:平均惊讶 = 平均不确定性 = 最优编码长度
自信息说的是"某一个事件"。但一个随机变量 \(X\) 会按分布 \(p\) 吐出各种取值,我们关心的是平均下来有多惊讶——这就是熵(entropy),即自信息关于 \(p\) 的期望:
\[ H(p) \;=\; \mathbb{E}_{x\sim p}\big[-\log p(x)\big] \;=\; -\sum_x p(x)\log p(x). \]它有三个等价的解读,建议你全都收下:
- 平均不确定性:分布越"摊平",你越猜不准下一个取值,熵越大。
- 最优编码的平均长度(用 \(\log_2\),单位 bit):信息论的核心结果(Shannon)是,如果你要为来自 \(p\) 的符号设计一套二进制编码,把常见符号编短、罕见符号编长,那么平均每个符号至少需要 \(H(p)\) 个 bit,且能逼近这个下界。熵就是"这堆数据本质上有多少信息、压缩极限是多少"。
- 平均惊讶:每次开盲盒的惊讶程度的长期平均。
两个极端把直觉钉牢:
- 完全确定 ⇒ 熵 = 0。若某个取值概率为 1,其余为 0,则 \(H=-1\cdot\log 1=0\)。没有任何不确定性,无需任何编码。
- 均匀分布 ⇒ 熵最大。\(n\) 个等可能取值时 \(H=\log n\)(这是 \(n\) 个取值能达到的上限)。最摸不准,最难压缩。
用偏置硬币把这条曲线画出来最清楚。一枚正面概率为 \(p\) 的硬币,其熵(伯努利熵)是
\[ H(p) = -p\log p-(1-p)\log(1-p). \]\(p=0\) 或 \(p=1\) 时硬币结果已注定,\(H=0\);\(p=0.5\) 时最公平、最难猜,熵取最大值(\(\ln 2\approx0.693\) nat,即 1 bit——回到第一节那把尺子,\(1\text{ bit}=\ln 2\text{ nat}\),两边对得上)。曲线是一个对称的拱形。
例题:三点分布的熵
设 \(p=(0.5,\,0.25,\,0.25)\)。逐项算自信息再加权平均(用 \(\ln\)):
\[ H(p) = -\big(0.5\ln 0.5 + 0.25\ln 0.25 + 0.25\ln 0.25\big). \]由 \(-\ln 0.5=0.6931,\ -\ln 0.25=1.3863\):
\[ H(p)=0.5(0.6931)+0.25(1.3863)+0.25(1.3863)=1.0397\ \text{nat}. \]换成 bit:\(1.0397/\ln 2 = 1.5\) bit。这个 \(1.5\) 很美——它说"用最优编码,平均每个符号要 1.5 个二进制位"。事实上把 0.5 那个符号编成 0(1 位)、另两个编成 10、11(各 2 位),平均长度 \(0.5\cdot1+0.25\cdot2+0.25\cdot2=1.5\) bit,正好达到熵的下界。
三、交叉熵:用"错的码本"去编码会多花多少
现实里我们几乎从不知道真分布 \(p\)。模型给出的是一个估计 \(q\)。假设真实数据来自 \(p\),但我们手里只有为 \(q\) 设计的那套编码(把 \(q\) 认为常见的符号编短),用这套"可能不对的码本"去编码真实数据,平均要花多长?这就是交叉熵(cross-entropy):
\[ H(p,q) \;=\; \mathbb{E}_{x\sim p}\big[-\log q(x)\big] \;=\; -\sum_x p(x)\log q(x). \]注意区别:抽样的频率用真分布 \(p\)(数据真的从这儿来),但每个符号的编码长度 \(-\log q(x)\) 用错的 \(q\)(我们以为它来自这儿)。如果 \(q=p\),码本完美匹配数据,交叉熵就退化成熵 \(H(p)\)。如果 \(q\neq p\),必然有浪费。这条"浪费不会是负的"由 Gibbs 不等式保证:
\[ \boxed{\,H(p,q)\ \ge\ H(p)\,}\qquad\text{等号当且仅当 } q=p. \]ML 和 ML 的联系:分类交叉熵就是这个交叉熵
上一课那个分类损失,正是交叉熵 \(H(p,q)\) 的特例。把真标签写成 one-hot 分布 \(p\)(正确类那一项为 1,其余为 0),把模型 softmax 输出写成预测分布 \(q=\hat y\)。代入定义:
\[ H(p,\hat y) = -\sum_c p_c\log \hat y_c = -1\cdot\log \hat y_{\text{正确类}} = -\log \hat y_{\text{正确类}}. \]求和里只有正确类那一项幸存(其余 \(p_c=0\)),剩下的恰好是上一课的 负对数似然(NLL)。"分类交叉熵 = NLL"在这里彻底对上了:one-hot 是一种特别"尖"的真分布。
四、KL 散度:交叉熵比熵多出来的那块"浪费"
Gibbs 不等式说 \(H(p,q)\ge H(p)\),那这个"多出来的差"本身就是个有用的量——它度量 \(q\) 偏离 \(p\) 有多远。给它起名 KL 散度(Kullback–Leibler divergence):
\[ D_{\mathrm{KL}}(p\|q) \;=\; H(p,q)-H(p) \;=\; \sum_x p(x)\log\frac{p(x)}{q(x)}. \]两种写法是同一回事(把 \(H(p,q)-H(p)\) 展开,\(-\sum p\log q+\sum p\log p=\sum p\log(p/q)\))。它的含义非常具体:因为用了错的码本 \(q\),每个符号平均多花的 bit 数。
KL 的三条性质,每条都要记住:
- 非负:\(D_{\mathrm{KL}}(p\|q)\ge 0\),当且仅当 \(p=q\) 时为 0(这正是 Gibbs 不等式的另一种说法)。
- 不对称:一般 \(D_{\mathrm{KL}}(p\|q)\neq D_{\mathrm{KL}}(q\|p)\)。"p 偏离 q"和"q 偏离 p"是两个不同的数。
- 不满足三角不等式,所以它不是一个距离(metric)。
易错:别把 KL 当"距离"。很多人脑子里把 \(D_{\mathrm{KL}}(p\|q)\) 想成 "\(p\) 和 \(q\) 之间的距离",这会害你。距离必须对称(\(d(a,b)=d(b,a)\))且满足三角不等式,KL 两条都不满足。正确的叫法是散度(divergence)——一个"从 \(p\) 看 \(q\) 有多别扭"的有向、不对称的度量。后面前向/反向 KL 行为迥异,根子就在这个不对称上。
五、★核心:三线汇合 —— 交叉熵、KL、最大似然是同一件事
把上面那个定义移项,得到本课的中心恒等式:
\[ \boxed{\,H(p,q) \;=\; H(p) \;+\; D_{\mathrm{KL}}(p\|q)\,} \]用一根柱子看最直观:底部是 \(H(p)\)(数据本身的不可压缩信息),上面叠一段 \(D_{\mathrm{KL}}(p\|q)\)(因模型不完美造成的浪费),加起来才是交叉熵 \(H(p,q)\)。\(q\) 越接近 \(p\),上面那段越短;\(q=p\) 时浪费归零,柱子缩到只剩 \(H(p)\)。
现在看关键一步。训练时,真分布 \(p\)(数据分布)是给定的、与模型参数 \(\theta\) 无关的——数据就摆在那,它的熵 \(H(p)\) 是个常数。我们只能调 \(q=q_\theta\)。于是对 \(\theta\) 求最小:
\[ \min_\theta\, H(p,q_\theta) \;=\; \min_\theta\Big[\underbrace{H(p)}_{\text{常数}} + D_{\mathrm{KL}}(p\|q_\theta)\Big] \;\Longleftrightarrow\; \min_\theta\, D_{\mathrm{KL}}(p\|q_\theta). \]常数项不影响极小值的位置。所以最小化交叉熵,等价于最小化 KL 散度。而上一课已证最小化交叉熵就是最大化似然。三者汇合:
\[ \min_\theta H(p,q_\theta)\;\Longleftrightarrow\;\min_\theta D_{\mathrm{KL}}(p\|q_\theta)\;\Longleftrightarrow\;\max_\theta\ \text{似然}. \]
这就是"我们优化交叉熵而不直接优化 KL"的全部原因:两者只差常数 \(H(p)\),梯度完全相同,而交叉熵更好算(不需要知道 \(H(p)\),one-hot 标签直接给出 \(-\log\hat y_{\text{正确}}\))。我们嘴上说"让模型逼近真分布"(最小化 KL),手上写的却是交叉熵——它们是同一个优化问题。
例题:验证三线汇合
真分布 \(p=(0.5,0.25,0.25)\),已算出 \(H(p)=1.0397\) nat。两个预测:
- \(q_1=(0.4,0.4,0.2)\):\(H(p,q_1)=-[0.5\ln0.4+0.25\ln0.4+0.25\ln0.2]=1.0896\)。则 \(D_{\mathrm{KL}}(p\|q_1)=1.0896-1.0397=0.0499\)。
- \(q_2=(0.1,0.45,0.45)\):\(H(p,q_2)=1.5505\),\(D_{\mathrm{KL}}(p\|q_2)=1.5505-1.0397=0.5108\)。
验证恒等式:\(H(p)+D_{\mathrm{KL}}(p\|q_1)=1.0397+0.0499=1.0896=H(p,q_1)\) ✓。两个都对得上。并且 \(q_1\) 比 \(q_2\) 离 \(p\) 近(KL 更小),所以 \(q_1\) 是更好的预测——交叉熵也确实更小,方向一致。
例题:one-hot 时交叉熵 = −log ŷ_正确
真标签是第 2 类,\(p=(0,1,0)\);模型预测 \(\hat y=(0.2,0.7,0.1)\)。交叉熵 \(H(p,\hat y)=-[0\cdot\ln0.2+1\cdot\ln0.7+0\cdot\ln0.1]=-\ln0.7=0.3567\)。直接算 \(-\ln\hat y_{\text{正确}}=-\ln0.7=0.3567\) ✓。注意此时 \(H(p)=0\)(one-hot 完全确定),所以 \(D_{\mathrm{KL}}(p\|\hat y)=H(p,\hat y)-0=0.3567\):交叉熵和 KL 数值相等。这解释了分类任务里"交叉熵就是 KL"——因为真标签的熵是 0。
六、前向 KL vs 反向 KL:覆盖众峰还是锁住一峰
KL 不对称,意味着"拿哪个分布当参照"会得到完全不同的拟合行为。这在生成模型里是生死攸关的设计选择。设真分布 \(p\) 是一个双峰分布,而我们的模型 \(q\) 只能是单峰(比如一个高斯)。
- 前向 KL(forward)\(D_{\mathrm{KL}}(p\|q)=\sum p\log(p/q)\):求和按 \(p\) 加权。哪里 \(p\) 大,哪里就被重点惩罚——而一旦某处 \(p>0\) 却 \(q\approx0\),\(\log(p/q)\to+\infty\),惩罚爆炸。所以 \(q\) 不敢在任何 \(p\) 有质量的地方留空,结果是 \(q\) 摊开、跨坐在两个峰之间去覆盖众峰(mean-seeking / mode-covering),也叫矩匹配。代价是峰谷之间的低概率区被 \(q\) 错误地赋了不小的概率。
- 反向 KL(reverse)\(D_{\mathrm{KL}}(q\|p)=\sum q\log(q/p)\):求和按 \(q\) 加权。哪里 \(q\) 大才计入惩罚;只要 \(q\) 在 \(p\) 很小的地方也很小,就几乎不受罚。于是 \(q\) 倾向于躲进 \(p\) 的某一个峰里、把另一个峰直接忽略——寻峰 / 锁单峰(mode-seeking)。代价是漏掉了 \(p\) 的其他峰(mode collapse)。
一句话记忆:前向 KL "宁可摊平也不留空"(覆盖),反向 KL "宁可漏掉也不摊错"(锁峰)。最大似然/交叉熵训练用的是前向 \(D_{\mathrm{KL}}(p\|q)\)(参照真分布 \(p\)),所以最大似然天然倾向"覆盖"。变分推断(VAE 的某些视角)和很多 RL 目标则用反向 KL,倾向"锁峰"。
七、互信息:两个变量共享了多少信息
熵度量单个变量的不确定性。如果观测了 \(Y\),对 \(X\) 的不确定性能减少多少?减少的那部分就是 \(X\) 与 \(Y\) 共享的信息——互信息(mutual information):
\[ I(X;Y) \;=\; H(X) - H(X\mid Y), \]其中 \(H(X\mid Y)\) 是条件熵(知道 \(Y\) 之后 \(X\) 还剩多少不确定性)。\(I(X;Y)\ge 0\),且 \(X,Y\) 独立时为 0(知道 \(Y\) 对猜 \(X\) 毫无帮助)。它还能写成一个 KL:\(I(X;Y)=D_{\mathrm{KL}}\big(p(x,y)\,\|\,p(x)p(y)\big)\)——即"联合分布"离"假装独立"有多远。
ML 和 ML 的联系:互信息是表示学习的理论底座
对比学习(contrastive learning,如 SimCLR/CLIP)的核心思想是:让同一张图的两个增强视图(或图文配对)的表示互信息尽量大。著名的 InfoNCE 损失与互信息密切相关——更准确地说,它是互信息的一个有偏下界,且这个界被 \(\log N\)(一个 batch 里的负样本数)封顶,所以"InfoNCE = 最大化互信息"只是一个近似口径,严格的对应留到 M8 表示学习那一课再展开。直觉层面记住"学到好表示" ≈ "保留尽量多与目标相关的互信息、丢掉无关的"就够用了。这一课的熵/KL 给了你读懂那些目标函数的语言。
八、数值稳定:log-sum-exp 与 softmax 减最大值
交叉熵的实现绕不开 \(\log\) 和 \(\exp\),而这两个函数在边界处会爆炸或下溢。两个必须掌握的工程技巧:
(1) softmax 减最大值。softmax \(\hat y_i=\dfrac{e^{z_i}}{\sum_j e^{z_j}}\) 里,若某个 logit \(z_i\) 很大,\(e^{z_i}\) 会溢出成 inf。利用一个事实:给所有 logit 同时减去常数 \(c\),softmax 值不变(分子分母同乘 \(e^{-c}\) 抵消)。取 \(c=\max_j z_j\),则最大的指数变成 \(e^0=1\),绝不溢出:
(2) log-sum-exp(LSE)。计算 \(\log\sum_j e^{z_j}\) 时同样先提最大值:
\[ \log\sum_j e^{z_j} \;=\; c + \log\sum_j e^{z_j-c},\qquad c=\max_j z_j. \]这样括号内最大项是 \(e^0=1\),既不溢出也不会全部下溢成 0。实践中千万别先 softmax 再取 log(两步各自损失精度),而是用一体化的 log_softmax = z - log_sum_exp(z)。框架里的 CrossEntropyLoss 内部正是这么做的。
易错:手写交叉熵时 log(0)。如果预测概率某项恰为 0 而真标签在该项非 0,\(\log 0=-\infty\),loss 变 nan。务必从 logits 出发用 log-softmax,或给概率加一个极小的 eps 裁剪。下面代码里我们用 np.where(p>0, ...) 来安全地跳过 \(p=0\) 的项(约定 \(0\log 0=0\))。
调一调,观察现象
下面三个小实验都只用 numpy + print,几秒跑完。动手改一改,亲眼看现象比记结论牢十倍。
任务 1:把分布从"尖"调到"平",看熵怎么变。改 p 从接近 one-hot 到均匀。预期现象:越均匀熵越大,均匀分布 \((1/3,1/3,1/3)\) 达到最大 \(\ln 3\approx1.0986\);接近 one-hot 时熵趋近 0。为什么:熵是平均不确定性,摊得越平越难猜。
import numpy as np
def entropy(p):
p = np.asarray(p, float)
return -np.sum(np.where(p>0, p*np.log(p), 0.0))
for p in [[0.98,0.01,0.01],[0.6,0.3,0.1],[1/3,1/3,1/3]]:
print(p, '-> H =', round(entropy(p),4), 'nat')
print('ln 3 =', round(np.log(3),4))
任务 2:让 q 越来越接近 p,看交叉熵塌向熵、KL 塌向 0。预期现象:随着 q 趋近 p,\(H(p,q)\) 单调下降逼近 \(H(p)\),\(D_{\mathrm{KL}}\) 逼近 0;当 q=p 时 KL 恰为 0。为什么:交叉熵的"浪费部分"就是 KL,码本对了浪费归零。
import numpy as np
def entropy(p):
p=np.asarray(p,float); return -np.sum(np.where(p>0,p*np.log(p),0.0))
def cross(p,q):
p=np.asarray(p,float); q=np.asarray(q,float)
return -np.sum(np.where(p>0,p*np.log(q),0.0))
p=np.array([0.5,0.25,0.25])
for t in [0.0,0.5,0.9,1.0]: # t=插值系数,t=1 时 q=p
q=(1-t)*np.array([1/3,1/3,1/3])+t*p
print('t=',t,'H(p,q)=',round(cross(p,q),4),'KL=',round(cross(p,q)-entropy(p),4))
print('H(p)=',round(entropy(p),4))
任务 3:亲手验证 KL 不对称。交换 p 和 q 的位置算两次 KL。预期现象:\(D_{\mathrm{KL}}(p\|q_1)=0.0499\) 而 \(D_{\mathrm{KL}}(q_1\|p)=0.0541\),两数不等。为什么:KL 是有向散度,求和加权用的分布不同(一次按 \(p\)、一次按 \(q_1\)),所以不对称——这也是它"不是距离"的铁证。
import numpy as np
def kl(p,q):
p=np.asarray(p,float); q=np.asarray(q,float)
return np.sum(np.where(p>0, p*np.log(p/q), 0.0))
p =np.array([0.5,0.25,0.25])
q1=np.array([0.4,0.4,0.2])
print('KL(p||q1) =', round(kl(p,q1),4))
print('KL(q1||p) =', round(kl(q1,p),4))
print('相等吗?', np.isclose(kl(p,q1), kl(q1,p)))
动手练习
- 三函数与恒等式。实现
entropy(p)、cross_entropy(p,q)、kl(p,q)三个函数(用np.where(p>0, ...)处理 \(0\log0\)),对 \(p=(0.5,0.25,0.25)\)、\(q=(0.4,0.4,0.2)\) 数值验证 \(H(p,q)=H(p)+D_{\mathrm{KL}}(p\|q)\)(断言np.isclose)。骨架:import numpy as np def entropy(p): p=np.asarray(p,float); return -np.sum(np.where(p>0,p*np.log(p),0.0)) def cross_entropy(p,q): p=np.asarray(p,float); q=np.asarray(q,float) return -np.sum(np.where(p>0,p*np.log(q),0.0)) def kl(p,q): p=np.asarray(p,float); q=np.asarray(q,float) return np.sum(np.where(p>0, p*np.log(p/q), 0.0)) p=np.array([0.5,0.25,0.25]); q=np.array([0.4,0.4,0.2]) print('H(p,q) =', cross_entropy(p,q)) print('H(p)+KL(p||q) =', entropy(p)+kl(p,q)) print('恒等式成立?', np.isclose(cross_entropy(p,q), entropy(p)+kl(p,q))) print('KL 不对称: ', kl(p,q), 'vs', kl(q,p)) - one-hot 验证。取真标签
p=[0,1,0]与预测yhat=[0.2,0.7,0.1],用上面的cross_entropy算交叉熵,并与-np.log(yhat[1])比较,确认相等;再算 \(H(p)\) 确认它是 0,从而 \(D_{\mathrm{KL}}=H(p,\hat y)\)。 - 稳定的 log-softmax。写一个
log_softmax(z)=z - (c + np.log(np.sum(np.exp(z-c))))(\(c=\)max),对z=[1000., 1001., 1002.]这种大 logit 测试不出现inf/nan;再用它实现nll = -log_softmax(z)[label]作为单样本交叉熵。 - 伯努利熵曲线(打印版)。对
p in np.linspace(0,1,11)打印 \(H(p)=-p\ln p-(1-p)\ln(1-p)\),确认最大值在 \(p=0.5\) 处约 0.6931 nat、两端为 0。注意:写np.where(p>0, p*np.log(p), 0)时,np.log(p)仍会在 \(p=0\) 处被求值并打印RuntimeWarning(虽然np.where最终选了 0、结果数值正确)。别被警告吓到——它不代表算错。要么用with np.errstate(divide='ignore', invalid='ignore'):包住计算抑制警告,要么先掩码取出p>0的项再log。骨架:import numpy as np def bern_H(p): with np.errstate(divide='ignore', invalid='ignore'): terms = np.where((p>0)&(p<1), -p*np.log(p)-(1-p)*np.log(1-p), 0.0) return terms ps = np.linspace(0,1,11) for p in ps: print('p=', round(float(p),1), 'H=', round(float(bern_H(p)),4)) - (选做)前向 vs 反向 KL 的偏好。真分布是离散双峰
p=[0.45,0.10,0.45](注意它已归一,三项和为 1——先assert np.isclose(p.sum(),1),不归一会让 KL 算出负值,违反 KL≥0)。在候选单峰qA=[0.9,0.05,0.05](锁左峰、几乎不给右峰质量)和qB=[0.4,0.2,0.4](覆盖双峰)中,分别算前向 \(D_{\mathrm{KL}}(p\|q)\) 与反向 \(D_{\mathrm{KL}}(q\|p)\)。预期现象:前向 KL \(D_{\mathrm{KL}}(p\|q_A)=0.7461\) 远大于 \(D_{\mathrm{KL}}(p\|q_B)=0.0367\)——前向明显偏爱覆盖型qB,因为锁左峰的qA在右峰处 \(q\approx0\) 而 \(p\) 有质量,被狠狠惩罚。反向 KL \(D_{\mathrm{KL}}(q_A\|p)=0.4793\)、\(D_{\mathrm{KL}}(q_B\|p)=0.0444\):反向对锁峰型qA的惩罚(0.4793)远小于前向对它的惩罚(0.7461)——这就是"反向相对宽容锁峰"的数值证据。import numpy as np def kl(p,q): p=np.asarray(p,float); q=np.asarray(q,float) return np.sum(np.where(p>0, p*np.log(p/q), 0.0)) p =np.array([0.45,0.10,0.45]); assert np.isclose(p.sum(),1) qA=np.array([0.9,0.05,0.05]); assert np.isclose(qA.sum(),1) # 锁左峰 qB=np.array([0.4,0.2,0.4]); assert np.isclose(qB.sum(),1) # 覆盖双峰 print('前向 KL(p||qA) =', round(kl(p,qA),4), ' 前向 KL(p||qB) =', round(kl(p,qB),4)) print('反向 KL(qA||p) =', round(kl(qA,p),4), ' 反向 KL(qB||p) =', round(kl(qB,p),4))
掌握自检
- 我能不查公式写出 \(I(x)=-\log p(x)\),并说清 \(p\to0\)、\(p=1\) 两端的行为,以及 bit 与 nat 的换算。
- 给定一个三点分布,我能手算它的熵,并解释为什么均匀时最大、确定时为 0。
- 我能解释交叉熵 \(H(p,q)\) 的"错码本编码长度"含义,并立刻说出 Gibbs 不等式 \(H(p,q)\ge H(p)\)。
- 我能默写并推导 \(H(p,q)=H(p)+D_{\mathrm{KL}}(p\|q)\),并用"\(H(p)\) 是常数"解释为什么最小化交叉熵 ⟺ 最小化 KL ⟺ 最大化似然。
- 有人说"KL 是 p 和 q 的距离",我能立刻指出两条反例(不对称、不满足三角不等式)。
- 我能说清前向 KL 覆盖众峰、反向 KL 锁单峰,以及最大似然用的是哪一个。
- 我知道为什么手写交叉熵要从 logits 走 log-softmax,以及减最大值的作用。
可以跳过/暂缓的内容
- 互信息的严格不等式与 InfoNCE 的推导(第七节):本课只需建立"互信息 = 共享信息 = 联合分布离假装独立有多远"的直觉。InfoNCE 是有偏、被 \(\log N\) 封顶的下界,它和互信息的精确关系留到 M8 表示学习那一课再展开,现在不必深究。
- 前向/反向 KL 在具体算法里的完整后果:本课只要求你能说出"覆盖 vs 锁峰"的方向。反向 KL 如何导致 VAE/变分推断的 mode collapse、它和 ELBO 的精确关系,到 VAE 那一课会专门处理。
- Gibbs 不等式 / KL≥0 的严格证明(用 Jensen 不等式对 \(\log\) 的凹性):本课把它当作"浪费不会为负"的事实直接用即可,正式证明可等学完凸性/Jensen 后再回头补。
埋点(下面这两处你会再见到 KL):① VAE 的 ELBO = 重构项 \(-\)(KL 正则项):那个 KL 把编码器输出的隐分布拉向先验 \(\mathcal{N}(0,I)\),防止它乱长。② RLHF 的目标 = 奖励 \(-\ \beta\cdot D_{\mathrm{KL}}(\pi\|\pi_{\text{ref}})\):那个 KL 像一根橡皮筋,把微调后的策略 \(\pi\) 拴在参考模型 \(\pi_{\text{ref}}\) 附近,防止它为了刷奖励而跑偏、说胡话。今天你建立的"KL = 偏离参照分布的代价"这个直觉,到那两课会直接复用。
下一课预告:我们已经会"度量两个分布有多远",但还有个更基础的能力没解决——怎么从一个分布里真的抽出样本来?下一课讲采样:逆变换采样、重参数化技巧(reparameterization)与蒙特卡洛估计。它们是 VAE、扩散模型、强化学习共同依赖的底层操作,也会让今天的 KL 正则项真正"动起来"。