信息论¶
信息论量化了信息、惊奇以及概率分布之间的差异。本文件涵盖熵、交叉熵、KL散度、互信息和惊奇度——这些概念是机器学习中每个分类损失函数、VAE目标以及数据压缩方案的基础。
-
信息论由克劳德·香农于1948年创立,为我们提供了一个量化信息的数学框架。它回答了这样的问题:一个事件应该让你感到多惊讶?一条消息携带了多少信息?两个概率分布有多大差异?
-
这些问题听起来抽象,但它们是机器学习损失函数、数据压缩和通信系统的基础。交叉熵损失(分类中最常见的损失函数)直接源于信息论。
-
从最简单的问题开始:单个事件携带多少信息?
-
惊奇度(也称为自信息)衡量一个事件令人惊讶的程度。如果一件非常可能的事情发生了,你几乎学不到什么。如果一件罕见的事情发生了,你会学到很多。
-
如果你住在沙漠里,有人告诉你天气晴朗,这信息量不大。如果他们告诉你正在下雪,这信息量就非常大。惊奇度形式化了这种直觉:
-
当我们使用 \(\log_2\) 时,单位是比特。一次公平的抛硬币的惊奇度为 \(-\log_2(0.5) = 1\) 比特。一个概率为 \(1/8\) 的事件的惊奇度为 \(\log_2(8)=3\) 比特。
-
为什么用对数而不用 \(1/p\)?三个原因:
- 确定事件(\(p=1\))应该给出零信息:\(\log(1)=0\) 但 \(1/1=1\)。
- 独立事件应该有可加的信息:\(\log(1/p_1p_2)=\log(1/p_1)+\log(1/p_2)\)。
- 我们需要一个平滑、行为良好的函数。\(1/p\) 会爆炸;\(\log(1/p)\) 增长平缓。
-
熵是期望的惊奇度,即从分布中采样每个事件时平均获得的信息量。它衡量分布的不确定性或“不可预测性”:
-
公平硬币的熵为 \(H = -0.5\log_2(0.5) - 0.5\log_2(0.5) = 1\) 比特。最大不确定性。
-
偏倚硬币 \(p=0.9\) 的熵为 \(H = -0.9\log_2(0.9) - 0.1\log_2(0.1) \approx 0.469\) 比特。不确定性较小,因此熵较小。
-
确定性事件(\(p=1\))的熵为 \(H=0\)。完全没有不确定性。
-
当所有结果等可能时,熵最大。对于 \(n\) 个等可能的结果,\(H = \log_2 n\)。公平骰子的熵为 \(\log_2 6 \approx 2.585\) 比特。
-
熵的实际含义是压缩。香农的信源编码定理指出,如果不丢失信息,你不能将数据压缩到低于其熵率。一幅每个像素都等可能的图像(最大熵)无法压缩。一幅大部分是白色的图像(低熵)可以很好地压缩。
-
快速感受一下规模:一个灰度像素(256个值)的最大熵为8比特。一幅1080p灰度图像最多包含 \(1920 \times 1080 \times 8 \approx 1660\) 万比特。真实图像的熵要低得多,因为相邻像素是相关的,这正是JPEG压缩有效的原因。
-
对于连续随机变量,离散求和变为积分。微分熵为:
-
方差为 \(\sigma^2\) 的高斯分布的微分熵为 \(h = \frac{1}{2}\log_2(2\pi e \sigma^2)\)。在所有具有相同方差的分布中,高斯分布的熵最大。这是高斯分布在建模中如此常见的原因之一:它在指定的均值和方差之外做了最少的假设。
-
互信息衡量知道一个变量能告诉你关于另一个变量的多少信息。它是观察到 \(Y\) 时 \(X\) 不确定性的减少量:
- 等价地:
-
如果 \(X\) 和 \(Y\) 独立,\(p(x,y)=p(x)p(y)\),互信息为零。它们越依赖,互信息越高。
-
在机器学习中,互信息用于特征选择(选择与目标具有高MI的特征)、信息瓶颈方法以及评估聚类质量。
-
交叉熵衡量使用为分布 \(q\) 优化的编码对来自分布 \(p\) 的事件进行编码所需的平均比特数:
-
如果 \(q\) 与 \(p\) 完全匹配,则交叉熵等于熵:\(H(p,p)=H(p)\)。如果 \(q\) 是一个不好的近似,交叉熵更高。“额外”的比特来自于不匹配。
-
这正是交叉熵成为机器学习中分类标准损失函数的原因。真实标签定义了 \(p\)(一个one-hot分布),模型预测的概率定义了 \(q\)。最小化交叉熵将 \(q\) 推向 \(p\):
-
对于真实类别为 \(c\) 的单个样本,这简化为 \(\mathcal{L} = -\log \hat{y}_c\)。损失是真实类别在模型预测下的惊奇度。如果模型对正确类别赋予高概率,损失就低。
-
KL散度(Kullback-Leibler散度,也称为相对熵)衡量一个分布与另一个分布的差异:
- KL散度是使用分布 \(q\) 而不是真实分布 \(p\) 的“额外成本”。它总是非负的(\(D_{\text{KL}} \ge 0\)),且仅当 \(p=q\) 时为零。
-
KL散度是不对称的:\(D_{\text{KL}}(p \| q) \ne D_{\text{KL}}(q \| p)\)。这种不对称性很重要。\(D_{\text{KL}}(p \| q)\) 惩罚 \(q\) 在 \(p\) 高概率的地方放置低概率(因为 \(\log(p/q)\) 会爆炸)。\(D_{\text{KL}}(q \| p)\) 则惩罚相反的情况。
-
这种不对称性导致了两种近似风格:
- 最小化 \(D_{\text{KL}}(p \| q)\) 产生矩匹配行为:\(q\) 覆盖 \(p\) 的所有模态,但可能过于分散。
- 最小化 \(D_{\text{KL}}(q \| p)\) 产生模式寻找行为:\(q\) 集中在一个模态上,但可能错过其他模态。这就是变分推断所使用的。
-
由于 \(H(p)\) 相对于模型是常数,最小化交叉熵 \(H(p,q)\) 等价于最小化 \(D_{\text{KL}}(p \| q)\)。这就是为什么我们可以使用交叉熵损失,并知道我们也在最小化真实分布与预测分布之间的KL散度。
-
KL散度在贝叶斯更新中扮演核心角色。后验 \(P(\theta | D)\) 是在KL散度意义上与先验 \(P(\theta)\) 最接近且与观测数据一致的分布。每一个新的观测都会更新后验,减少关于 \(\theta\) 的不确定性。
-
在变分自编码器(VAE)中,损失函数有两项:重建损失(交叉熵)和KL散度项,后者正则化潜在空间使其接近标准正态分布。
-
总结一下:熵告诉你分布的内在不确定性,交叉熵告诉你模型逼近真实情况的程度,KL散度告诉你两者之间的差距。这三个量构成了现代机器学习优化的支柱。
编程任务(使用 CoLab 或 notebook)¶
-
计算各种分布的熵,并验证对于给定数量的结果,均匀分布具有最大熵。
import jax.numpy as jnp def entropy(p): """以比特为单位计算熵。过滤掉零概率事件。""" p = p[p > 0] return -jnp.sum(p * jnp.log2(p)) # 公平骰子 fair = jnp.ones(6) / 6 print(f"公平骰子熵: {entropy(fair):.4f} 比特 (最大值 = log2(6) = {jnp.log2(6.):.4f})") # 有偏骰子 loaded = jnp.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.5]) print(f"有偏骰子熵: {entropy(loaded):.4f} 比特") # 确定性 det = jnp.array([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]) print(f"确定性: {entropy(det):.4f} 比特") # 公平硬币 coin = jnp.array([0.5, 0.5]) print(f"公平硬币熵: {entropy(coin):.4f} 比特") -
计算真实分布与几种近似之间的交叉熵和KL散度。验证 \(D_{\text{KL}}(p \| q) = H(p, q) - H(p)\)。
import jax.numpy as jnp def cross_entropy(p, q): return -jnp.sum(p * jnp.log2(jnp.clip(q, 1e-10, 1.0))) def kl_divergence(p, q): mask = p > 0 return jnp.sum(jnp.where(mask, p * jnp.log2(p / jnp.clip(q, 1e-10, 1.0)), 0.0)) def entropy(p): p = p[p > 0] return -jnp.sum(p * jnp.log2(p)) p = jnp.array([0.4, 0.3, 0.2, 0.1]) # 真实分布 for name, q in [("精确匹配", p), ("轻微失配", jnp.array([0.35, 0.30, 0.25, 0.10])), ("严重失配", jnp.array([0.1, 0.1, 0.1, 0.7]))]: h_p = entropy(p) h_pq = cross_entropy(p, q) kl = kl_divergence(p, q) print(f"{name:10}: H(p)={h_p:.4f}, H(p,q)={h_pq:.4f}, " f"KL={kl:.4f}, H(p,q)-H(p)={h_pq-h_p:.4f}") -
通过计算两个不同分布的 \(D_{\text{KL}}(p \| q)\) 和 \(D_{\text{KL}}(q \| p)\),证明KL散度是不对称的。
import jax.numpy as jnp def kl_div(p, q): mask = p > 0 return float(jnp.sum(jnp.where(mask, p * jnp.log2(p / jnp.clip(q, 1e-10, 1.0)), 0.0))) p = jnp.array([0.9, 0.1]) q = jnp.array([0.5, 0.5]) print(f"D_KL(p || q) = {kl_div(p, q):.4f}") print(f"D_KL(q || p) = {kl_div(q, p):.4f}") print("不一样!KL散度是不对称的。") -
模拟训练过程中的交叉熵损失。创建一个“真实”的one-hot标签,并展示随着模型预测概率的提高,损失如何下降。
import jax.numpy as jnp import matplotlib.pyplot as plt # 真实标签:4个类别中的第2类(索引从0开始) true_label = jnp.array([0, 0, 1, 0]) # 模拟预测的改善过程 steps = [] losses = [] for confidence in jnp.linspace(0.25, 0.99, 50): # 模型对类别2越来越自信 remaining = (1 - confidence) / 3 pred = jnp.array([remaining, remaining, confidence, remaining]) loss = -jnp.sum(true_label * jnp.log(jnp.clip(pred, 1e-10, 1.0))) steps.append(float(confidence)) losses.append(float(loss)) plt.figure(figsize=(8, 4)) plt.plot(steps, losses, color="#e74c3c", linewidth=2) plt.xlabel("模型对真实类别的置信度") plt.ylabel("交叉熵损失") plt.title("交叉熵损失随预测改善而下降") plt.grid(alpha=0.3) plt.show()