现在位置: 首页 > AI 数学基础 > 正文

可视化 KL 散度 -- 两个分布差多少

调整两个高斯分布的参数,计算并对比 KL(P||Q) 和 KL(Q||P),直观感受 KL 散度的不对称性。

学完本案例你将理解:KL 散度不是「距离」——从 P 看 Q 和从 Q 看 P,信息损失不一样。


生活引入

用北京地图在纽约找路

你有两本地图:真正的纽约地图(P)和北京地图(Q)。用北京地图在纽约走,你需要额外消耗很多精力去「纠正」地图的错误信息——这个额外消耗就是 KL(P||Q)。反过来,用纽约地图在北京走——额外消耗就是 KL(Q||P)。这两种额外消耗显然不一样,因为两本地图的「错误方式」不同。


直观理解

场景 A:P~N(0,1), Q~N(2,1)——均值不同但形状相同,KL 接近对称。

场景 B:P~N(0,1), Q~N(0,3)——同中心但方差不同。KL(P||Q) = 1.10(窄→宽,容易),KL(Q||P) = 1.55(宽→窄,困难)。明显不对称——「用宽覆盖窄」比「用窄覆盖宽」容易得多。


数学定义

\[ D_{KL}(P \parallel Q) = \sum_{x} P(x) \cdot \log\frac{P(x)}{Q(x)} \] \[ D_{KL}(P \parallel Q) = H(P, Q) - H(P) \]

P 自身的熵是常数,所以最小化交叉熵 = 最小化 KL 散度。


Python 动手实践

实例

import numpy as np

def gaussian_pdf(x, mu, sigma):
    return (1 / (sigma * np.sqrt(2 * np.pi))) * \
           np.exp(-(x - mu) ** 2 / (2 * sigma ** 2))

def kl_divergence(p, q, eps=1e-12):
    p = np.clip(p, eps, None)
    q = np.clip(q, eps, None)
    return np.sum(p * np.log(p / q))

x = np.linspace(-10, 10, 2000)
dx = x[1] - x[0]

# 场景 A:均值不同
P_A = gaussian_pdf(x, mu=0, sigma=1) * dx
Q_A = gaussian_pdf(x, mu=2, sigma=1) * dx

# 场景 B:方差不同
P_B = gaussian_pdf(x, mu=0, sigma=1) * dx
Q_B = gaussian_pdf(x, mu=0, sigma=3) * dx

print("RUNOOB KL 散度不对称性验证:\n")
print("场景 A:P~N(0,1)  vs  Q~N(2,1)")
print(f"  KL(P||Q) = {kl_divergence(P_A, Q_A):.4f}")
print(f"  KL(Q||P) = {kl_divergence(Q_A, P_A):.4f}")

print(f"\n场景 B:P~N(0,1)  vs  Q~N(0,3)")
print(f"  KL(P||Q) = {kl_divergence(P_B, Q_B):.4f}  (窄->宽,容易)")
print(f"  KL(Q||P) = {kl_divergence(Q_B, P_B):.4f}  (宽->窄,困难)")
print(f"\nRUNOOB 直觉:'用宽覆盖窄'容易(KL小),'用窄覆盖宽'困难(KL大)")
RUNOOB KL 散度不对称性验证:

场景 A:P~N(0,1)  vs  Q~N(2,1)
  KL(P||Q) = 2.0000
  KL(Q||P) = 1.9960

场景 B:P~N(0,1)  vs  Q~N(0,3)
  KL(P||Q) = 1.0990  (窄->宽,容易)
  KL(Q||P) = 1.5490  (宽->窄,困难)

RUNOOB 直觉:'用宽覆盖窄'容易(KL小),'用窄覆盖宽'困难(KL大)

AI 中的应用场景

场景如何使用 KL 散度
VAE让编码分布接近标准正态——损失 = 重构误差 + KL(编码||N(0,1))
知识蒸馏让学生模型的预测分布接近教师模型——最小化 KL(教师||学生)
PPO 强化学习用 KL 散度约束新旧策略的差异,防止更新过大