查看全课大纲(第 88 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

KL散度与JS散度

小象实战讲义 · 基于深度学习的计算机视觉

在进入图像生成模型,特别是变分自编码器(VAE)和生成对抗网络(GAN)的深入探讨之前,我们需要先理解两个关键的数学概念:KL散度与JS散度。它们是衡量两个概率分布之间差异的核心工具,直接关系到VAE中潜在空间的约束以及GAN中生成器与判别器的博弈目标。本节将为你厘清熵、交叉熵、相对熵(KL散度)以及JS散度的定义、计算与直观意义,为后续学习奠定坚实的数学基础。

💡 核心导读

  • 熵:度量一个概率分布本身的“混乱程度”或“不确定性”,也代表最优编码下的平均编码长度。
  • 交叉熵:使用一个估计分布Q去编码来自真实分布P的样本所需的平均编码长度。它总是大于等于真实分布P的熵。
  • KL散度(相对熵):衡量两个概率分布P和Q之间差异的非对称性度量,等于交叉熵减去真实分布的熵。它不满足距离的性质。
  • JS散度:为了解决KL散度的非对称性问题而提出的对称性度量,其值域在0到1之间。

从熵到交叉熵

熵(Entropy)是信息论中的核心概念。对于一个离散随机变量,其概率分布为 PP,熵 H(P)H(P) 定义为:

H(P)=−∑xP(x)log⁡P(x)H(P) = -\sum_{x} P(x) \log P(x)

熵可以直观地理解为“混乱程度”或“不确定性”的度量。从编码的角度看,H(P)H(P) 表示按照真实分布 PP 来编码样本所需的最短平均编码长度(期望)。其单位通常是比特(当对数以2为底时)。

交叉熵(Cross-Entropy)则用于衡量当我们使用一个估计的分布 QQ 去编码来自真实分布 PP 的样本时,所需的平均编码长度。其定义为:

H(P,Q)=−∑xP(x)log⁡Q(x)H(P, Q) = -\sum_{x} P(x) \log Q(x)

这里,PP 是真实的数据分布(例如训练集的标签分布),QQ 是模型预测的分布(例如神经网络的softmax输出)。根据吉布斯不等式(Gibbs’ inequality),交叉熵 H(P,Q)H(P, Q) 总是大于或等于真实分布的熵 H(P)H(P),即 H(P,Q)≥H(P)H(P, Q) \ge H(P)。当且仅当 P=QP=Q 时取等号。

一个编码长度的例子

让我们通过一个具体的例子来理解熵和交叉熵的直观含义。

假设有一个数据集只包含四个字母:A, B, C, D。

  • 真实分布 pp:A和B出现的概率均为 1/21/2,C和D出现的概率均为 00。即 p=[1/2,1/2,0,0]p = [1/2, 1/2, 0, 0]。
  • 估计分布 qq:模型错误地估计四个字母等可能出现,即 q=[1/4,1/4,1/4,1/4]q = [1/4, 1/4, 1/4, 1/4]。

1. 计算真实分布的熵 H(p)H(p)(最优编码长度)

import numpy as np

# 定义真实分布 p
p = np.array([0.5, 0.5, 0.0, 0.0])
# 计算熵 H(p),以2为底
H_p = -np.sum(p * np.log2(p + 1e-12)) # 加一个小量避免log(0)
print(f"熵 H(p) (比特): {H_p:.4f}")
熵 H(p) (比特): 1.0000

计算过程:H(p)=−[0.5×log⁡2(0.5)+0.5×log⁡2(0.5)+0+0]=−[0.5×(−1)+0.5×(−1)]=1H(p) = -[0.5 \times \log_2(0.5) + 0.5 \times \log_2(0.5) + 0 + 0] = -[0.5 \times (-1) + 0.5 \times (-1)] = 1。

结果解读:只需要 1 比特 就能最优地编码这个数据。因为实际上只有A和B会出现,我们可以用0代表A,1代表B(或者反之)。猜一次就能确定是哪个字母。

2. 计算使用估计分布 qq 时的交叉熵 H(p,q)H(p, q)

import numpy as np

# 定义真实分布 p 和估计分布 q
p = np.array([0.5, 0.5, 0.0, 0.0])
q = np.array([0.25, 0.25, 0.25, 0.25])

# 计算交叉熵 H(p, q),以2为底
H_pq = -np.sum(p * np.log2(q + 1e-12)) # 同样避免log(0)
print(f"交叉熵 H(p, q) (比特): {H_pq:.4f}")
交叉熵 H(p, q) (比特): 2.0000

计算过程:H(p,q)=−[0.5×log⁡2(0.25)+0.5×log⁡2(0.25)+0+0]=−[0.5×(−2)+0.5×(−2)]=2H(p, q) = -[0.5 \times \log_2(0.25) + 0.5 \times \log_2(0.25) + 0 + 0] = -[0.5 \times (-2) + 0.5 \times (-2)] = 2。

结果解读:使用错误的分布 qq 进行编码,平均需要 2 比特。因为模型认为C和D也可能出现(各占1/4概率),所以它必须为所有四个字母分配码字,例如:00->A, 01->B, 10->C, 11->D。这就产生了编码冗余——为实际永远不会出现的C和D预留了编码空间,导致平均编码长度变长。

这个例子清晰地展示了交叉熵如何度量估计分布与真实分布之间的差异:差异越大,交叉熵相比真实熵的“额外开销”就越大。

KL散度:相对熵

KL散度(Kullback–Leibler divergence),也称为相对熵,直接量化了这种“额外开销”。它定义为交叉熵与真实熵之差:

DKL(P∥Q)=H(P,Q)−H(P)=∑xP(x)log⁡P(x)Q(x)D_{KL}(P \parallel Q) = H(P, Q) - H(P) = \sum_{x} P(x) \log \frac{P(x)}{Q(x)}

重要性质:

  1. 非负性:DKL(P∥Q)≥0D_{KL}(P \parallel Q) \ge 0。
  2. 同一性:当且仅当 P=QP=Q(几乎处处相等)时,DKL(P∥Q)=0D_{KL}(P \parallel Q) = 0。
  3. 非对称性:DKL(P∥Q)≠DKL(Q∥P)D_{KL}(P \parallel Q) \ne D_{KL}(Q \parallel P)。因此,KL散度不是距离度量(不满足对称性和三角不等式)。

在机器学习的语境下,PP 通常代表真实数据分布,QQ 代表模型学到的分布。KL散度衡量了用 QQ 来近似 PP 时造成的信息损失。

让我们计算之前例子中的KL散度。

import numpy as np

# 定义真实分布 p 和估计分布 q
p = np.array([0.5, 0.5, 0.0, 0.0])
q = np.array([0.25, 0.25, 0.25, 0.25])

# 计算KL散度 D_KL(p||q),以2为底
# 注意:当 p_i = 0 时,项为 0 * log(0/q) = 0,在计算中自动处理
D_kl_pq = np.sum(p * np.log2((p + 1e-12) / (q + 1e-12))) # 加小量保证数值稳定
print(f"KL散度 D_KL(p||q) (比特): {D_kl_pq:.4f}")

# 验证 D_KL = H(p,q) - H(p)
H_p = -np.sum(p * np.log2(p + 1e-12))
H_pq = -np.sum(p * np.log2(q + 1e-12))
print(f"验证: H(p,q) - H(p) = {H_pq - H_p:.4f}")
KL散度 D_KL(p||q) (比特): 1.0000
验证: H(p,q) - H(p) = 1.0000

结果 DKL(P∥Q)=1D_{KL}(P \parallel Q) = 1 比特,正好是交叉熵(2比特)减去真实熵(1比特)的值。这1比特的差异,正是由于错误估计分布所带来的额外编码成本。

JS散度:对称化的解决方案

KL散度的非对称性在某些场景下会带来问题。信息论的约定是 0log⁡0=00 \log 0 = 0:当某一点 P(x)=0P(x)=0 时,该项对 DKL(P∥Q)D_{KL}(P \parallel Q) 的贡献为 0,因此只要 QQ 的支撑集覆盖 PP 的支撑集(即凡是 P(x)>0P(x)>0 处都有 Q(x)>0Q(x)>0),DKL(P∥Q)D_{KL}(P \parallel Q) 仍是有限值;但反过来,只要存在一点 P(x)=0P(x)=0 且 Q(x)>0Q(x)>0,DKL(Q∥P)D_{KL}(Q \parallel P) 在该点的项 Q(x)log⁡(Q(x)/0)Q(x)\log(Q(x)/0) 就会发散,整个散度为 +∞+\infty。同一对分布、两个方向的散度一个是有限值、一个是无穷大,这种不对称性使得KL散度作为分布相似性度量时不够稳健。

JS散度(Jensen–Shannon divergence)通过对称化解决了这个问题。它定义为:

DJS(P∥Q)=12DKL(P∥M)+12DKL(Q∥M)D_{JS}(P \parallel Q) = \frac{1}{2} D_{KL}\left(P \parallel M\right) + \frac{1}{2} D_{KL}\left(Q \parallel M\right)

其中 M=12(P+Q)M = \frac{1}{2}(P + Q) 是两个分布的平均分布。

重要性质:

  1. 对称性:DJS(P∥Q)=DJS(Q∥P)D_{JS}(P \parallel Q) = D_{JS}(Q \parallel P)。
  2. 有界性:0≤DJS(P∥Q)≤10 \le D_{JS}(P \parallel Q) \le 1(当使用以2为底的对数时)。
  3. 同一性:当且仅当 P=QP=Q 时,DJS(P∥Q)=0D_{JS}(P \parallel Q) = 0。

JS散度可以理解为,先构造一个中间分布 MM,然后分别计算 PP 和 QQ 到 MM 的KL散度,再取平均。这样就得到了一个对称且平滑的度量。

import numpy as np

# 定义两个不同的分布 P 和 Q
P = np.array([0.7, 0.2, 0.1])
Q = np.array([0.1, 0.2, 0.7])

# 计算中间分布 M
M = 0.5 * (P + Q)

# 计算 KL 散度的辅助函数(以2为底)
def kl_div(p, q):
    # 只对 p>0 的项求和
    return np.sum(p[p>0] * np.log2(p[p>0] / q[p>0]))

# 计算 JS 散度
D_js_pq = 0.5 * kl_div(P, M) + 0.5 * kl_div(Q, M)
print(f"JS散度 D_JS(P||Q): {D_js_pq:.4f}")

# 验证对称性
D_js_qp = 0.5 * kl_div(Q, M) + 0.5 * kl_div(P, M)
print(f"验证对称性 D_JS(Q||P): {D_js_qp:.4f}")
print(f"两者是否相等: {np.isclose(D_js_pq, D_js_qp)}")
JS散度 D_JS(P||Q): 0.3651
验证对称性 D_JS(Q||P): 0.3651
两者是否相等: True

在生成模型中的应用

理解了这些概念后,我们就能明白它们在后续生成模型中的关键作用:

  • 在变分自编码器(VAE)中:损失函数包含一个重构误差项和一个正则化项。这个正则化项正是编码器输出的潜在变量分布与标准正态分布之间的 KL散度。它的作用是约束潜在空间具有良好结构(如连续性、平滑性),便于采样和解码。
  • 在生成对抗网络(GAN)中:在最优判别器下,原始GAN的生成器损失等价于最小化真实数据分布与生成数据分布之间的 JS散度(相差因子 2 和一个常数)。判别器的训练是在逼近这个最优判别器,而生成器的更新则推动两个分布的JS散度减小。然而,后续研究(如WGAN)也指出,当两个分布没有重叠或重叠可忽略时,JS散度会饱和,梯度消失,导致训练困难,从而引出了Wasserstein距离等改进方案。

📝 动手练一练

  1. 概念辨析:为什么说KL散度不是真正的“距离”?请至少列举两点原因。
  2. 手算验证:假设真实分布 P=[0.8,0.2]P = [0.8, 0.2],模型分布 Q=[0.5,0.5]Q = [0.5, 0.5]。请计算以2为底的熵 H(P)H(P)、交叉熵 H(P,Q)H(P, Q) 和KL散度 DKL(P∥Q)D_{KL}(P \parallel Q)。
👉 点击查看参考答案
  1. KL散度不是距离的原因:

    • 不满足对称性:DKL(P∥Q)≠DKL(Q∥P)D_{KL}(P \parallel Q) \ne D_{KL}(Q \parallel P),从A到B的“差异”不等于从B到A的“差异”。
    • 不满足三角不等式:距离要求 d(A,C)≤d(A,B)+d(B,C)d(A, C) \le d(A, B) + d(B, C),但KL散度不满足此性质。
  2. 手算验证:

    • 熵 H(P)=−[0.8×log⁡2(0.8)+0.2×log⁡2(0.2)]≈−[0.8×(−0.3219)+0.2×(−2.3219)]=0.7219H(P) = -[0.8 \times \log_2(0.8) + 0.2 \times \log_2(0.2)] \approx -[0.8 \times (-0.3219) + 0.2 \times (-2.3219)] = 0.7219 比特。
    • 交叉熵 H(P,Q)=−[0.8×log⁡2(0.5)+0.2×log⁡2(0.5)]=−[0.8×(−1)+0.2×(−1)]=1H(P, Q) = -[0.8 \times \log_2(0.5) + 0.2 \times \log_2(0.5)] = -[0.8 \times (-1) + 0.2 \times (-1)] = 1 比特。
    • KL散度 DKL(P∥Q)=H(P,Q)−H(P)≈1−0.7219=0.2781D_{KL}(P \parallel Q) = H(P, Q) - H(P) \approx 1 - 0.7219 = 0.2781 比特。 也可以通过公式验证:DKL(P∥Q)=0.8×log⁡2(0.8/0.5)+0.2×log⁡2(0.2/0.5)≈0.8×0.6781+0.2×(−1.3219)=0.2781D_{KL}(P \parallel Q) = 0.8 \times \log_2(0.8/0.5) + 0.2 \times \log_2(0.2/0.5) \approx 0.8 \times 0.6781 + 0.2 \times (-1.3219) = 0.2781。

本章小结

本节我们建立了衡量概率分布差异的数学框架。熵量化了分布自身的不确定性;交叉熵衡量了用错误分布进行编码的成本;KL散度(相对熵)是交叉熵超出真实熵的部分,直接度量了两个分布的差异,但它不对称;JS散度通过对KL散度进行对称化平均,得到了一个对称且有界的相似性度量。这些概念是理解VAE、GAN等现代生成模型损失函数设计的关键。

今天,生成模型的主流已转向扩散模型(DDPM、Stable Diffusion)与流匹配;但 KL/JS 仍是理解 VAE、GAN 以及分布对齐(如 RLHF 中的 KL 正则、知识蒸馏)的基础工具。

📋 行动清单

  • 理解熵、交叉熵、KL散度、JS散度的定义公式及其编码长度解释。
  • 能够手动计算简单离散分布的熵、交叉熵和KL散度。
  • 明确KL散度的非对称性与JS散度的对称性,并理解它们在生成模型(VAE、GAN)中的作用位置。

—— 小象教研组