Skip to content

可视化 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(PQ)=_xP(x)logP(x)Q(x)$$$$D_KL(PQ)=H(P,Q)H(P)

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


Python 动手实践

实例

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大)")
python
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(编码
知识蒸馏让学生模型的预测分布接近教师模型——最小化 KL(教师
PPO 强化学习用 KL 散度约束新旧策略的差异,防止更新过大

AI 思考中...

手算交叉熵损失

从零手写一个多元线性回归

基于 VitePress 构建,部署于 GitHub Pages