查看全课大纲(第 3 / 69 节)
  1. 1.NLP 的研究领域、应用与学习路径
  2. 2.自然语言的基本术语
  3. 3.信息论基础:信息熵与交叉熵
  4. 4.语言的进化与语义变迁
  5. 5.主观概率、客观概率与条件概率
  6. 6.贝叶斯原理与推理
  7. 7.随机变量:二项式、期望方差与联合概率
  8. 8.常见概率分布
  9. 9.辛普森悖论与贝叶斯解题实例
  10. 10.语言模型:语言概率与词袋模型
  11. 11.n-gram 语言模型(二元 / 三元 / N-Gram)
  12. 12.语言模型评价:交叉熵、困惑度与插值
  13. 13.朴素贝叶斯文本分类:垃圾邮件
  14. 14.拼写检查
  15. 15.中文分词:构词法、最大匹配、最优路径与 Jieba
  16. 16.词表示问题与语义相似度
  17. 17.TF-IDF 权重处理
  18. 18.One-Hot 表达与词表示的局限
  19. 19.Word2Vec 核心思想
  20. 20.Word2Vec 训练过程
  21. 21.Skip-gram 模型详解
  22. 22.词性标注任务与标注方法(规则 + N-Gram)
  23. 23.EM 算法与混合模型
  24. 24.高斯混合模型 GMM
  25. 25.从混合模型到隐马尔可夫模型
  26. 26.隐马尔可夫模型(HMM)推导与维特比解码
  27. 27.线性链条件随机场 CRF
  28. 28.贝叶斯网络与图模型
  29. 29.隐马尔可夫链与 HMM 推导
  30. 30.PLSA 主题模型
  31. 31.LSI 与 SVD 向量表达
  32. 32.LDA 与狄利克雷分布
  33. 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
  34. 34.上下文无关文法(CFG)与句法分析
  35. 35.概率上下文无关文法 PCFG
  36. 36.统计机器翻译:词对齐与翻译模型
  37. 37.统计机器翻译中的对齐模型与参数估计
  38. 38.从统计机器翻译到神经机器翻译
  39. 39.神经元与反向传播速览(指向深度学习课)
  40. 40.CNN 原理及其在 NLP 中的应用
  41. 41.循环神经网络的基本原理与 Elman / Jordan 网络
  42. 42.LSTM:长短期记忆网络的核心思想与分步实现
  43. 43.Encoder-Decoder 框架
  44. 44.Seq2Seq 与早期注意力机制
  45. 45.注意力机制的背景与动机
  46. 46.注意力模型实现原理与匹配函数
  47. 47.记忆网络:组成与计算
  48. 48.注意力机制的应用与历史延展
  49. 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
  50. 50.Multi-Head Attention、FFNN 与位置编码
  51. 51.层归一化、损失函数与 Universal Transformer 延展
  52. 52.预训练范式:从 ELMo 到 BERT
  53. 53.BERT 与掩码语言模型
  54. 54.BERT 下游任务微调与 GLUE 口径
  55. 55.GPT 与自回归语言建模
  56. 56.GPT-1 到 GPT-4 的演进
  57. 57.指令微调与 RLHF
  58. 58.分词器:BPE、WordPiece 与中文词表
  59. 59.上下文窗口与长文本处理
  60. 60.能力边界、成本与适用场景
  61. 61.文本分类实战:从朴素贝叶斯到逻辑回归
  62. 62.命名实体识别实战
  63. 63.文本生成与摘要实战
  64. 64.语义检索与向量库实战
  65. 65.RAG 知识库问答
  66. 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
  67. 67.幻觉与事实性问题
  68. 68.文本与知识结合(知识图谱压缩)
  69. 69.跨媒体信息检索与未来展望

信息论基础:信息熵与交叉熵

小象实战讲义 · 自然语言处理

信息论是自然语言处理乃至整个机器学习领域的数学基石之一。它为我们提供了一套严谨的数学工具,用以量化“信息”本身,并衡量概率分布之间的差异。本节我们将深入探讨信息熵与交叉熵这两个核心概念,理解它们如何从物理学的“熵”概念演化而来,并最终成为机器学习中至关重要的损失函数。掌握这些知识,是后续学习语言模型、词表示、乃至深度学习模型的基础。

💡 核心导读

  • 信息熵:量化一个随机事件或一个概率分布所包含的平均信息量(不确定性)。概率越均匀,熵越大;概率越集中,熵越小。
  • 交叉熵:衡量两个概率分布之间的差异。在机器学习中,常被用作损失函数,驱动模型学习到的分布逼近真实分布。
  • KL散度:交叉熵与信息熵之差,专门用于度量两个分布间的“距离”,但它不对称。
  • 应用连接:交叉熵损失是分类任务(如情感分析、语言模型)中最常用的目标函数,其背后原理正是最小化模型预测分布与真实标签分布之间的差异。

从物理熵到信息熵

熵的物理起源

“熵”(Entropy)最初是热力学和统计物理中的概念,用于描述系统的混乱或无序程度。一个高度有序、结构化的系统(如整齐排列的晶体)熵值较低;而一个混乱、无序的系统(如气体分子均匀扩散)熵值较高。著名的物理学家薛定谔在《生命是什么》一书中,将生命体描述为一个高度有序(低熵)的状态,而死亡则是一个熵增的过程。

香农的信息论

现代信息论的奠基者克劳德·香农(Claude Shannon)创造性地将“熵”的概念引入通信领域,用以量化“信息”。他提出,信息是用来消除不确定性的东西。一个事件发生的概率越小,它发生时带来的“信息量”就越大;反之,一个必然发生的事件(概率为1)带来的信息量为0。

基于这一直观理解,香农定义了单个事件 xx 的信息量(自信息)为: I(x)=−log⁡2P(x)I(x) = -\log_2 P(x) 其中 P(x)P(x) 是事件 xx 发生的概率。以2为底的对数使得信息量的单位是“比特”(bit)。

信息熵的定义

信息量衡量的是单个事件的信息。对于一个随机变量 XX,它有多个可能的取值 x1,x2,…,xnx_1, x_2, \ldots, x_n,每个取值对应一个概率 P(xi)P(x_i)。我们更关心这个随机变量整体的平均信息量,即信息熵,定义为所有可能事件信息量的期望值: H(X)=Ex∼P[I(x)]=−∑i=1nP(xi)log⁡2P(xi)H(X) = \mathbb{E}_{x \sim P}[I(x)] = -\sum_{i=1}^{n} P(x_i) \log_2 P(x_i)

对于连续随机变量,对应概念是微分熵(differential entropy,常记 h(X)h(X)): h(X)=−∫p(x)log⁡p(x)dxh(X) = -\int p(x) \log p(x) dx

需要注意:微分熵与离散熵不是一回事——它可以取负值,也不继承离散熵的非负性等全部性质;下面“越均匀熵越大、越集中熵越小”的解释,针对的是有限离散分布及固定取值集合。

让我们通过代码直观感受一下:

# 示例1:计算不同概率分布的信息熵
import math

# 定义一个计算熵的函数
def entropy(prob_list):
    """计算离散概率分布的熵(以2为底)"""
    h = 0.0
    for p in prob_list:
        if p > 0:  # 避免log(0)的错误
            h -= p * math.log2(p)
    return h

# 分布1:有一定偏向性
prob1 = [0.7, 0.1, 0.1, 0.1]
h1 = entropy(prob1)
print(f"分布 {prob1} 的熵为: {h1:.4f} bits")

# 分布2:极端集中(几乎确定是A)
prob2 = [0.97, 0.01, 0.01, 0.01]
h2 = entropy(prob2)
print(f"分布 {prob2} 的熵为: {h2:.4f} bits")

# 分布3:完全均匀(不确定性最大)
prob3 = [0.25, 0.25, 0.25, 0.25]
h3 = entropy(prob3)
print(f"分布 {prob3} 的熵为: {h3:.4f} bits")
分布 [0.7, 0.1, 0.1, 0.1] 的熵为: 1.3568 bits
分布 [0.97, 0.01, 0.01, 0.01] 的熵为: 0.2419 bits
分布 [0.25, 0.25, 0.25, 0.25] 的熵为: 2.0000 bits

从结果可以看出,分布越集中(如 prob2),熵越小;分布越均匀(如 prob3),熵越大,达到了最大值2比特(因为这里有4种等可能状态,需要2比特来编码)。

交叉熵与KL散度

交叉熵的定义

信息熵衡量的是一个分布自身的不确定性。而交叉熵(Cross-Entropy)则用于衡量两个概率分布之间的差异。给定真实分布 PP 和模型预测分布 QQ,交叉熵定义为: H(P,Q)=−∑iP(xi)log⁡2Q(xi)H(P, Q) = -\sum_{i} P(x_i) \log_2 Q(x_i)

注意,这里对数的内部是预测分布 QQ 的概率,但求期望时用的是真实分布 PP 的概率。交叉熵可以理解为:使用基于 QQ 的编码方案,来对来自 PP 分布的数据进行编码,所需的平均比特数。

KL散度:分布间的“距离”

为了更纯粹地衡量两个分布 PP 和 QQ 的差异,Kullback 和 Leibler 提出了以两人姓氏命名的 Kullback-Leibler散度(KL Divergence),也称为相对熵: DKL(P∣∣Q)=∑iP(xi)log⁡2P(xi)Q(xi)D_{KL}(P || Q) = \sum_{i} P(x_i) \log_2 \frac{P(x_i)}{Q(x_i)}

KL散度具有以下重要性质:

  1. 非负性:DKL(P∣∣Q)≥0D_{KL}(P || Q) \ge 0,当且仅当 P=QP = Q 时等号成立。这可以通过詹森不等式证明。
  2. 不对称性:一般不保证 DKL(P∣∣Q)=DKL(Q∣∣P)D_{KL}(P || Q) = D_{KL}(Q || P),两个方向通常并不相等(P=QP=Q 时两边同为 0)。因此它不是一个真正的“距离”度量,但能有效衡量用 QQ 来近似 PP 时造成的信息损失。

三者的关系

交叉熵、信息熵和KL散度之间存在一个优雅的关系: H(P,Q)=H(P)+DKL(P∣∣Q)H(P, Q) = H(P) + D_{KL}(P || Q)

推导: H(P,Q)=−∑iP(xi)log⁡2Q(xi)=−∑iP(xi)log⁡2P(xi)+∑iP(xi)log⁡2P(xi)−∑iP(xi)log⁡2Q(xi)=H(P)+∑iP(xi)log⁡2P(xi)Q(xi)=H(P)+DKL(P∣∣Q)\begin{aligned} H(P, Q) &= -\sum_i P(x_i) \log_2 Q(x_i) \\ &= -\sum_i P(x_i) \log_2 P(x_i) + \sum_i P(x_i) \log_2 P(x_i) - \sum_i P(x_i) \log_2 Q(x_i) \\ &= H(P) + \sum_i P(x_i) \log_2 \frac{P(x_i)}{Q(x_i)} \\ &= H(P) + D_{KL}(P || Q) \end{aligned}

这个关系非常关键:真实分布 PP 的信息熵 H(P)H(P) 是固定值。因此,最小化交叉熵 H(P,Q)H(P, Q),等价于最小化KL散度 DKL(P∣∣Q)D_{KL}(P || Q),也就是让预测分布 QQ 尽可能地逼近真实分布 PP。


# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import math
def entropy(prob_list):
    """计算离散概率分布的熵(以2为底)"""
    h = 0.0
    for p in prob_list:
        if p > 0:  # 避免log(0)的错误
            h -= p * math.log2(p)
    return h
prob1 = [0.7, 0.1, 0.1, 0.1]
h1 = entropy(prob1)

# 示例2:计算交叉熵和KL散度,验证 H(P,Q) = H(P) + D_KL(P||Q)
def cross_entropy(p_list, q_list):
    """计算分布P和Q之间的交叉熵;P>0 而 Q=0 时为 +∞"""
    ce = 0.0
    for p, q in zip(p_list, q_list):
        if p > 0:
            if q == 0:
                return math.inf
            ce -= p * math.log2(q)
    return ce

def kl_divergence(p_list, q_list):
    """计算分布P和Q之间的KL散度 D_KL(P||Q);P>0 而 Q=0 时为 +∞"""
    kl = 0.0
    for p, q in zip(p_list, q_list):
        if p > 0:
            if q == 0:
                return math.inf
            kl += p * math.log2(p / q)
    return kl

# 定义真实分布P和两个不同的预测分布Q1, Q2
P = [0.7, 0.3]
Q1 = [0.6, 0.4]  # 接近P
Q2 = [0.9, 0.1]  # 远离P

h_p = entropy(P)
ce1 = cross_entropy(P, Q1)
kl1 = kl_divergence(P, Q1)
ce2 = cross_entropy(P, Q2)
kl2 = kl_divergence(P, Q2)

print(f"真实分布 P 的熵 H(P): {h_p:.4f} bits")
print(f"\n预测分布 Q1: {Q1}")
print(f"交叉熵 H(P, Q1): {ce1:.4f} bits")
print(f"KL散度 D_KL(P||Q1): {kl1:.4f} bits")
print(f"验证 H(P) + D_KL(P||Q1) = {h_p + kl1:.4f} bits (应与 H(P,Q1) 相等)")

print(f"\n预测分布 Q2: {Q2}")
print(f"交叉熵 H(P, Q2): {ce2:.4f} bits")
print(f"KL散度 D_KL(P||Q2): {kl2:.4f} bits")
print(f"验证 H(P) + D_KL(P||Q2) = {h_p + kl2:.4f} bits (应与 H(P,Q2) 相等)")
真实分布 P 的熵 H(P): 0.8813 bits

预测分布 Q1: [0.6, 0.4]
交叉熵 H(P, Q1): 0.9125 bits
KL散度 D_KL(P||Q1): 0.0312 bits
验证 H(P) + D_KL(P||Q1) = 0.9125 bits (应与 H(P,Q1) 相等)

预测分布 Q2: [0.9, 0.1]
交叉熵 H(P, Q2): 1.1030 bits
KL散度 D_KL(P||Q2): 0.2217 bits
验证 H(P) + D_KL(P||Q2) = 1.1030 bits (应与 H(P,Q2) 相等)

可以看到,当预测分布 Q1Q1 更接近真实分布 PP 时,交叉熵和KL散度都更小。关系式 H(P,Q)=H(P)+DKL(P∣∣Q)H(P, Q) = H(P) + D_{KL}(P || Q) 也得到了验证。

交叉熵作为机器学习损失函数

从理论到实践

在分类任务中,我们通常有:

  • 真实分布 PP:通常是 One-Hot 编码 的标签。例如,对于一个三分类问题,类别2的真实标签是 [0,1,0][0, 1, 0]。
  • 预测分布 QQ:模型(例如一个Softmax层)输出的概率分布,如 [0.1,0.7,0.2][0.1, 0.7, 0.2]。

我们的目标是让 QQ 逼近 PP。由于 H(P)H(P) 是常数(One-Hot编码的熵为0,因为只有一个位置是1,其余为0,计算可得 0×log⁡20+1×log⁡21+...=00 \times \log_2 0 + 1 \times \log_2 1 + ... = 0),因此最小化交叉熵损失 H(P,Q)H(P, Q) 就直接等价于最小化 DKL(P∣∣Q)D_{KL}(P || Q),即让预测分布对准真实分布。

对于单个样本,其交叉熵损失为: L=−∑c=1Cyclog⁡(y^c)L = -\sum_{c=1}^{C} y_c \log(\hat{y}_c) 其中 CC 是类别数,ycy_c 是真实标签在类别 cc 上的值(0或1),y^c\hat{y}_c 是模型预测为类别 cc 的概率。

一个简化的情感分析示例

假设我们有一个二分类情感分析任务(正面/负面)。对于一个句子,经过模型(如逻辑回归)和Sigmoid函数后,我们得到一个标量预测概率 y^\hat{y},表示其为正面的概率。真实标签 yy 为1(正面)或0(负面)。

此时,交叉熵损失函数为: L=−[ylog⁡(y^)+(1−y)log⁡(1−y^)]L = -[y \log(\hat{y}) + (1-y) \log(1-\hat{y})]

这被称为二元交叉熵损失。工程实现常采用自然对数,此时损失单位为 nat;换底只相差一个正常数因子,不改变损失最小时的最优参数。


# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import math
def entropy(prob_list):
    """计算离散概率分布的熵(以2为底)"""
    h = 0.0
    for p in prob_list:
        if p > 0:  # 避免log(0)的错误
            h -= p * math.log2(p)
    return h
prob1 = [0.7, 0.1, 0.1, 0.1]
h1 = entropy(prob1)
def cross_entropy(p_list, q_list):
    """计算分布P和Q之间的交叉熵;P>0 而 Q=0 时为 +∞"""
    ce = 0.0
    for p, q in zip(p_list, q_list):
        if p > 0:
            if q == 0:
                return math.inf
            ce -= p * math.log2(q)
    return ce
def kl_divergence(p_list, q_list):
    """计算分布P和Q之间的KL散度 D_KL(P||Q);P>0 而 Q=0 时为 +∞"""
    kl = 0.0
    for p, q in zip(p_list, q_list):
        if p > 0:
            if q == 0:
                return math.inf
            kl += p * math.log2(p / q)
    return kl
P = [0.7, 0.3]
Q1 = [0.6, 0.4]  # 接近P
Q2 = [0.9, 0.1]  # 远离P
h_p = entropy(P)
ce1 = cross_entropy(P, Q1)
kl1 = kl_divergence(P, Q1)
ce2 = cross_entropy(P, Q2)
kl2 = kl_divergence(P, Q2)

# 示例3:二元交叉熵损失计算
def binary_cross_entropy(y_true, y_pred):
    """计算单个样本的二元交叉熵损失"""
    # 避免log(0)导致无穷大,对预测值进行微小裁剪
    epsilon = 1e-12
    y_pred = max(min(y_pred, 1 - epsilon), epsilon)
    loss = -(y_true * math.log(y_pred) + (1 - y_true) * math.log(1 - y_pred))
    return loss

# 假设模型对三个样本的预测概率
predictions = [0.9, 0.3, 0.6]
true_labels = [1, 0, 1]  # 正面,负面,正面

print("样本 | 真实标签 | 预测概率 | 交叉熵损失")
print("-" * 40)
total_loss = 0
for i, (y, y_hat) in enumerate(zip(true_labels, predictions)):
    loss = binary_cross_entropy(y, y_hat)
    total_loss += loss
    print(f"{i+1:^4} | {y:^8} | {y_hat:^10.2f} | {loss:.4f}")

average_loss = total_loss / len(predictions)
print(f"\n平均交叉熵损失: {average_loss:.4f}")
样本 | 真实标签 | 预测概率 | 交叉熵损失
----------------------------------------
 1   |    1     |    0.90    | 0.1054
 2   |    0     |    0.30    | 0.3567
 3   |    1     |    0.60    | 0.5108

平均交叉熵损失: 0.3243

可以看到,当预测正确且置信度高时(样本1,预测正面概率0.9,真实为正面),损失很小;样本2也预测正确(正面概率0.3,即负面概率0.7,与真实标签负面一致),但正确类别的置信度0.7低于样本1的0.9,因此损失更大;样本3同样预测正确,但置信度较低,损失也较大。模型训练的过程就是通过梯度下降等优化算法,调整参数,使这个平均损失最小化。

交叉熵与语言模型

在自然语言处理中,交叉熵损失有着更广泛的应用。在语言模型任务中,我们的目标是预测下一个词的概率分布。给定一个词序列 w1,w2,...,wt−1w_1, w_2, ..., w_{t-1},语言模型需要输出词汇表 VV 中所有词作为下一个词 wtw_t 的概率分布 QQ。

而“真实分布” PP 是什么?在训练时,我们知道下一个词实际是 wtw_t,因此真实分布是一个在 wtw_t 上概率为1的One-Hot分布。

语言模型的训练目标就是最小化其预测分布 QQ 与真实One-Hot分布 PP 之间的交叉熵。以2为底计算平均交叉熵 HavgH_{avg} 时,困惑度 PPL=2HavgPPL = 2^{H_{avg}};若改用自然对数,则 PPL=eHavgPPL = e^{H_{avg}}。这一指数形式就是后面会学到的衡量语言模型好坏的重要指标——困惑度(Perplexity)。

# 伪代码示意:语言模型中的交叉熵计算(概念层面)
# 假设词汇表 V = {"我", "爱", "自然", "语言", "处理"}
# 给定上下文:"我 爱"
# 真实下一个词:"自然"

# 语言模型(如n-gram或神经网络)输出预测分布 Q
Q = {"我": 0.05, "爱": 0.05, "自然": 0.6, "语言": 0.2, "处理": 0.1}

# 真实分布 P (One-Hot)
P = {"我": 0.0, "爱": 0.0, "自然": 1.0, "语言": 0.0, "处理": 0.0}

# 交叉熵损失 H(P, Q) = -1 * log2(0.6) ≈ 0.737 bits
# 模型的目标是让这个损失在所有训练样本上平均最小化。

📝 动手练一练

  1. 概念辨析:信息熵、交叉熵、KL散度三者之间有何区别与联系?为什么在分类任务中,最小化交叉熵损失是合理的?
  2. 手算练习:假设一个三分类问题的真实标签为类别 B(One-Hot编码为 [0, 1, 0])。模型A的预测概率为 [0.2, 0.7, 0.1],模型B的预测概率为 [0.1, 0.8, 0.1]。请计算两个模型对于该样本的交叉熵损失(以2为底)。哪个模型的预测更好?损失值相差多少?
👉 点击查看参考答案
  1. 区别与联系:

    • 信息熵 H(P)H(P):衡量单个分布 PP 自身的不确定性。是固有属性。
    • KL散度 DKL(P∣∣Q)D_{KL}(P||Q):衡量两个分布 PP 和 QQ 之间的差异(非对称)。DKL(P∣∣Q)≥0D_{KL}(P||Q) \ge 0。
    • 交叉熵 H(P,Q)H(P, Q):衡量使用分布 QQ 来编码来自分布 PP 的数据所需的平均比特数。
    • 核心关系:H(P,Q)=H(P)+DKL(P∣∣Q)H(P, Q) = H(P) + D_{KL}(P||Q)。
    • 在分类任务中:真实标签分布 PP 通常是 One-Hot 编码,其信息熵 H(P)=0H(P)=0。因此,最小化交叉熵 H(P,Q)H(P, Q) 就等价于最小化 KL 散度 DKL(P∣∣Q)D_{KL}(P||Q),即直接让模型预测分布 QQ 逼近真实分布 PP。这就是其作为损失函数的数学原理。
  2. 手算练习:

    • 对于模型A:H(P,QA)=−(0×log⁡20.2+1×log⁡20.7+0×log⁡20.1)=−log⁡20.7≈0.5146H(P, Q_A) = - (0 \times \log_2 0.2 + 1 \times \log_2 0.7 + 0 \times \log_2 0.1) = -\log_2 0.7 \approx 0.5146 bits。
    • 对于模型B:H(P,QB)=−(0×log⁡20.1+1×log⁡20.8+0×log⁡20.1)=−log⁡20.8≈0.3219H(P, Q_B) = - (0 \times \log_2 0.1 + 1 \times \log_2 0.8 + 0 \times \log_2 0.1) = -\log_2 0.8 \approx 0.3219 bits。
    • 模型B的损失更小(0.3219 < 0.5146),因此模型B的预测更好。
    • 损失值相差约 0.5146−0.3219=0.19270.5146 - 0.3219 = 0.1927 bits。

本章小结

本节我们建立了自然语言处理中至关重要的信息论基础。我们从物理中的“熵”概念出发,理解了香农如何用它来量化信息。信息熵衡量不确定性,交叉熵衡量两个分布的差异,而KL散度则是这种差异的纯粹度量。最重要的洞见在于,对于固定的真实分布(如分类任务中的One-Hot标签),最小化交叉熵等价于让预测分布逼近真实分布,这使其天然地成为机器学习和深度学习中最核心的损失函数之一。

📋 行动清单

  • 理解信息熵的公式 H(X)=−∑P(x)log⁡P(x)H(X) = -\sum P(x)\log P(x) 并能解释其含义。
  • 掌握交叉熵 H(P,Q)H(P,Q) 与 KL散度 DKL(P∣∣Q)D_{KL}(P||Q) 的定义及关系 H(P,Q)=H(P)+DKL(P∣∣Q)H(P,Q) = H(P) + D_{KL}(P||Q)。
  • 能够解释为什么交叉熵可以作为分类任务的损失函数,并会计算简单情况下的交叉熵损失值。

—— 小象教研组