> For the complete documentation index, see [llms.txt](https://inwt233.gitbook.io/ai-learning/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://inwt233.gitbook.io/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-02/day-012.md).

# Day 012：交叉熵为什么能评价语言模型

{% hint style="info" %}
**Day 010 已经会计算交叉熵与困惑度了，但凭什么"平均负对数概率"就是一个公正的评分？它到底在度量什么？理论上最低能得多少分？两个模型的分数差又该怎么解读？**
{% endhint %}

## 学习目标

完成本节后，你应该能够：

1. 用最优编码的视角解释熵的含义，并手算小分布的熵；
2. 写出交叉熵与 KL 散度的严格定义，并说清二者相差的常数是什么；
3. 证明分解公式 $$H(P,Q)=H(P)+D\_{\mathrm{KL}}(P\Vert Q)$$，并推出 $$H(P,Q)\ge H(P)$$；
4. 解释为什么固定测试集时，比较交叉熵等价于比较 KL 散度；
5. 说明模型分布在数据支撑上为零会导致无穷大交叉熵，并连回 Day 009 的平滑；
6. 区分语言模型评估中的三个角色：真实分布、测试集经验分布、模型分布；
7. 解释最小化训练集交叉熵与最大似然估计的等价性；
8. 识别 KL 不对称、跨事件空间比较分数等常见错误。

{% hint style="info" %}
Day 010 教了"怎么算"，今天回答"凭什么"。本节是语言模型线的收尾：理解了交叉熵的编码与 KL 解释，Day 019 的 softmax 交叉熵梯度、Day 058 的 SFT 损失掩码都会自然很多。明天 Day 013 回到检索线，解释 BM25 为什么优于简单 TF-IDF。
{% endhint %}

## 1. 问题：评分函数的合法性从哪里来

Day 010 的经验交叉熵是：

$$
H(\text{test},Q)
\=-\frac{1}{N}\sum\_{i=1}^{N}\log\_2 Q(w\_i\mid h\_i)
$$

当时只说"它平均给真实 Token 的概率越高越好"。这留下三个更深的疑问：

1. 这个量到底在度量什么物理含义？
2. 它有没有理论下限，最好的模型能得多少分？
3. 两个模型的分数之差，能不能解释成某种"差距"？

回答这三个问题的工具是信息论的三件套：**熵**、**交叉熵**、**KL 散度**（Kullback–Leibler divergence，相对熵）。Day 010 其实已经用了前两件的名字；今天把它们放到正确的理论位置上。

## 2. 熵：真实分布自身的不可压缩代价

### 2.1 从编码讲起

把"生成下一个 Token"想象成掷一个不均匀的骰子：符号 $$x$$ 以概率 $$P(x)$$ 出现。若要用 0/1 比特串无歧义地记录每次结果（前缀码：任何码字都不是另一个码字的前缀），信息论的基本结论是：给 $$x$$ 分配约 $$-\log\_2P(x)$$ 比特的码长是最优的——越可能的符号用越短的码。

于是每符号平均码长为：

$$
H(P)=-\sum\_x P(x)\log\_2 P(x)
$$

这就是**熵**（entropy）。它有两层等价含义：

* 编码视角：无论怎么设计码本，平均每符号压不到 $$H(P)$$ 比特以下；
* 不确定性视角：$$H(P)$$ 是"下一个符号平均有多难猜"。

### 2.2 手算一个四符号分布

设 $$P$$ 为 A、B、C、D 四个符号：

| 符号 |  P(x) | 最优码长 -log2 P | 对熵的贡献 |
| -: | ----: | -----------: | ----: |
|  A |   0.5 |        1 bit |   0.5 |
|  B |  0.25 |       2 bits |   0.5 |
|  C | 0.125 |       3 bits | 0.375 |
|  D | 0.125 |       3 bits | 0.375 |

$$
H(P)=0.5+0.5+0.375+0.375=1.75\text{ bits}
$$

四选一的不确定性若均匀（每符号 0.25），熵应为 2 bits；分布越不均匀，熵越低，越容易猜。

## 3. 交叉熵：拿错码本的代价

### 3.1 定义

现在数据仍来自 $$P$$，但码本是按另一个分布 $$Q$$ 优化的：符号 $$x$$ 分到 $$-\log\_2Q(x)$$ 比特。平均每符号实际付出的码长是：

$$
H(P,Q)=-\sum\_x P(x)\log\_2 Q(x)
$$

这就是**交叉熵**（cross-entropy）：**数据来自 P、按 Q 编码**的平均代价。

### 3.2 手算：均匀码本遇上偏斜分布

对上面的 $$P$$ 用均匀码本 $$Q(x)=0.25$$，每个符号一律 2 bits：

$$
H(P,Q)=\sum\_xP(x)\times2=2\text{ bits}
$$

比最优的 1.75 bits 多付了 0.25 bit。这 0.25 bit 就是"拿错码本"的纯损失——下一节给它起名字。

### 3.3 语言模型对应什么

语言模型 $$Q$$ 就是一本"码本"：它给每个"上下文 $$h$$ 后的 Token $$w$$"报价 $$-\log\_2Q(w\mid h)$$ 比特。真实语言按 $$P(w\mid h)$$ 出题。Day 010 的经验交叉熵（测试集上平均 $$-\log\_2Q(w\_i\mid h\_i)$$）正是 $$H(P,Q)$$ 的样本估计：**用模型的码本，编码真实语言，平均每个 Token 付出的比特数**。分数越低，码本越接近最优。

## 4. 分解定理：多付的代价就是 KL 散度

### 4.1 KL 散度的定义

$$
D\_{\mathrm{KL}}(P\Vert Q)=\sum\_xP(x)\log\_2\frac{P(x)}{Q(x)}
$$

KL 散度度量两个分布的"单向差距"：坚持用 $$Q$$ 而不用真实 $$P$$，平均每个符号多付出的比特数。

### 4.2 三行推导

把交叉熵按定义展开：

$$
\begin{aligned}
H(P,Q)
&=-\sum\_xP(x)\log\_2Q(x)\\
&=-\sum\_xP(x)\log\_2P(x)
+\sum\_xP(x)\log\_2\frac{P(x)}{Q(x)}\\
&=H(P)+D\_{\mathrm{KL}}(P\Vert Q)
\end{aligned}
$$

四符号例子里：$$H(P,Q)=2$$，$$H(P)=1.75$$，$$D\_{\mathrm{KL}}(P\Vert Q)=0.25$$，恰好 $$1.75+0.25=2$$。

### 4.3 三个直接推论

**推论一：**$$H(P,Q)\ge H(P)$$**，熵是地板。** 由于 $$-\log$$ 是凸函数，$$D\_{\mathrm{KL}}(P\Vert Q)\ge0$$ 恒成立，且当且仅当 $$Q$$ 在 $$P$$ 的支撑上处处等于 $$P$$ 时取 0。所以无论模型多强，交叉熵不可能低于测试分布自身的熵；完美的模型不是 0 分，而是恰好等于熵。

**推论二：固定测试集时，比较交叉熵就是比较 KL。** $$P$$ 固定，$$H(P)$$ 是常数，于是两个模型的交叉熵之差恰好等于它们的 KL 之差。这就是"交叉熵是合法评分"的完整论证：它给每个模型打出"距真实分布还有多远"的分数（差一个共同的常数）。

**推论三：支撑上为零，代价无穷大。** 若 $$P(x)>0$$ 而 $$Q(x)=0$$，则 $$-\log\_2Q(x)=+\infty$$。这从理论上解释了 Day 009 为什么要平滑（消灭零概率）、Day 010 为什么未平滑模型困惑度为无穷大：**给真实会发生的事件报了零概率，这个码本在数学上不可用**。

### 4.4 KL 不对称：方向有含义

交换两个分布，结果不同。取 $$P={A:0.9,B:0.1}$$，$$Q={A:0.5,B:0.5}$$：

* $$D\_{\mathrm{KL}}(P\Vert Q)\approx0.531004$$ bits
* $$D\_{\mathrm{KL}}(Q\Vert P)\approx0.736966$$ bits

KL 不是距离（不满足对称性和三角不等式）。方向在实践中重要：以数据分布为第一个参数的**前向 KL**（forward KL，$$D\_{\mathrm{KL}}(P\_{\text{data}}\Vert Q)$$）会严厉惩罚"数据需要、模型不给"的位置（支撑外为无穷大），逼迫 $$Q$$ 铺满数据的一切可能——这叫 mass covering（或反过来说，避免 zero-forcing）。语言模型训练用的正是前向 KL 的样本版（即交叉熵），这也是为什么未平滑的计数模型在评估时直接崩成无穷大。

{% hint style="info" %}
本节只要求理解方向性的含义与后果。前向/反向 KL 在拟合多峰分布时的不同行为，会在后续对齐与偏好优化（Day 065、Day 067）中再次出现。
{% endhint %}

## 5. 语言模型评估里的三个分布

把理论落到 Day 008–010 的场景，必须分清三个角色：

* **真实分布** $$P\_{\text{lang}}$$：人类语言真正的下一个 Token 分布。永远不可直接获得。
* **经验分布** $$\hat P$$：测试集实际呈现的分布（样本）。测试集越大，$$\hat P$$ 越接近 $$P\_{\text{lang}}$$。
* **模型分布** $$Q$$：被评估的语言模型。

Day 010 算出的经验交叉熵是 $$H(P\_{\text{lang}},Q)$$ 的**蒙特卡洛估计**：用样本平均 $$-\log\_2Q(w\_i\mid h\_i)$$ 去近似期望。这带来两点推论：

1. **分数带噪声**。测试集小，估计就抖动。Day 010 要求"先加总 loss、再除以有效 Token 数"的正确聚合，本质就是保持这个估计的无偏加权。
2. **比较必须同协议**。Day 010 列出的七个一致条件（同测试集、同 Tokenizer、同边界、同 mask……）现在有了理论解释：任何一个条件变化都意味着 $$\hat P$$ 或事件空间变了，地板 $$H(\hat P)$$ 和 KL 的意义随之改变。

另一个容易忽略的约束：交叉熵与 KL 都要求 $$P$$、$$Q$$ 定义在**同一个事件空间**上。Day 010 评估的是"给定历史的下一 Token"事件；若有人按"每字符"或"每字节"归一化，那就是另一个分布、另一块地板。

## 6. 与最大似然训练的关系

把 Day 010 的总 NLL 写开：

$$
-\sum\_{i=1}^{N}\log Q(w\_i\mid h\_i)
\=-N\sum\_{x}\hat P(x)\log Q(x)
\=N,H(\hat P,Q)
$$

其中 $$\hat P$$ 是训练集经验分布，$$x$$ 遍历训练事件。于是：

* **最小化训练集平均交叉熵** $$\Leftrightarrow$$ **最大化训练集似然**——这不是巧合或工程惯例，而是同一个目标函数的两种写法（差一个常数因子 $$N$$）。
* 深度学习框架里的 `CrossEntropyLoss` 因此得名。softmax、logits 与它的梯度细节留给 Day 019；SFT 中只对回答部分计损失的 label masking 留给 Day 058。

同时这也点明了过拟合的位置：训练优化的是 $$H(\hat P\_{\text{train}},Q)$$，评估关心 $$H(\hat P\_{\text{test}},Q)$$。两个经验分布不同（Day 011 末尾的分布偏移、Day 009 的数据稀疏），训练分数低不保证测试分数低。

## 7. Python 实现：从定义到语言模型实验

### 7.1 三个基本量与四符号验证

```python
from math import inf, log2

def entropy(p):
    """离散分布 P 的熵，单位 bit；跳过零概率项。"""
    return -sum(prob * log2(prob) for prob in p.values() if prob > 0)

def cross_entropy(p, q):
    """交叉熵 H(P,Q)：数据来自 P，用按 Q 设计的码本编码的平均代价。"""
    total = 0.0
    for symbol, prob in p.items():
        if prob > 0:
            if q.get(symbol, 0.0) == 0:
                return inf  # P 的支撑上 Q 为 0，代价无穷大。
            total -= prob * log2(q[symbol])
    return total

def kl_divergence(p, q):
    """KL 散度 D(P||Q)：相对 P 自身最优编码的额外代价。"""
    total = 0.0
    for symbol, prob in p.items():
        if prob > 0:
            if q.get(symbol, 0.0) == 0:
                return inf
            total += prob * log2(prob / q[symbol])
    return total


p = {"A": 0.5, "B": 0.25, "C": 0.125, "D": 0.125}
q_uniform = {"A": 0.25, "B": 0.25, "C": 0.25, "D": 0.25}
q_zero = {"A": 0.5, "B": 0.5, "C": 0.0, "D": 0.0}

print(f"H(P)        = {entropy(p):.6f} bits")
print(f"H(P, U)     = {cross_entropy(p, q_uniform):.6f} bits")
print(f"D(P||U)     = {kl_divergence(p, q_uniform):.6f} bits")
print(f"H(P)+D(P||U)= {entropy(p) + kl_divergence(p, q_uniform):.6f} bits")
print(f"H(P, Q0)    = {cross_entropy(p, q_zero):.6f} bits")
```

输出：

```
H(P)        = 1.750000 bits
H(P, U)     = 2.000000 bits
D(P||U)     = 0.250000 bits
H(P)+D(P||U)= 2.000000 bits
H(P, Q0)    = inf bits
```

与第 2、3 节的手算完全一致；分解恒等式精确成立；$$Q$$ 在 $$P$$ 支撑上为零时交叉熵为无穷大（推论三）。

### 7.2 unigram 语言模型实验

沿用 Day 008–010 的语料，训练一个 unigram 最大似然模型 $$Q\_{\text{mle}}$$，在测试集上做完整的分解计算：

```python
from collections import Counter

train_corpus = [
    ["我", "喜欢", "自然语言"],
    ["我", "喜欢", "机器学习"],
    ["我", "学习", "自然语言"],
    ["你", "喜欢", "自然语言"],
]
test_corpus = [
    ["你", "学习", "机器学习"],  # 与 Day 010 相同的测试集
    ["我", "喜欢", "自然语言"],
]

def next_token_events(corpus, bos="<s>", eos="</s>"):
    """展开 (上下文, 目标) 事件流；<s> 只作上下文，</s> 是预测目标。"""
    for sentence in corpus:
        sequence = [bos, *sentence, eos]
        yield from zip(sequence, sequence[1:])

# unigram 的最大似然 = 训练事件流中目标 Token 的经验分布。
train_targets = Counter(token for _, token in next_token_events(train_corpus))
total_train = sum(train_targets.values())
q_mle = {token: count / total_train for token, count in train_targets.items()}

# 测试集经验分布 P_test（unigram 事件空间就是 Token 本身）。
test_targets = Counter(token for _, token in next_token_events(test_corpus))
n_test = sum(test_targets.values())
p_test = {token: count / n_test for token, count in test_targets.items()}

vocab = sorted(set(train_targets) | set(test_targets))
q_uniform = {token: 1 / len(vocab) for token in vocab}

h_p = entropy(p_test)
h_mle = cross_entropy(p_test, q_mle)
h_unif = cross_entropy(p_test, q_uniform)
kl_mle = kl_divergence(p_test, q_mle)
kl_unif = kl_divergence(p_test, q_uniform)

print(f"测试事件数 n_test = {n_test}，词表 |V| = {len(vocab)}")
print(f"H(P_test)          = {h_p:.6f} bits/token   (PP = {2 ** h_p:.6f})")
print(f"H(P_test, Q_mle)   = {h_mle:.6f} bits/token   (PP = {2 ** h_mle:.6f})")
print(f"H(P_test, Q_unif)  = {h_unif:.6f} bits/token   (PP = {2 ** h_unif:.6f})")
print(f"D(P||Q_mle)        = {kl_mle:.6f} bits")
print(f"D(P||Q_unif)       = {kl_unif:.6f} bits")
print(f"分解检验 mle  : H(P)+D = {h_p + kl_mle:.6f}  vs  H(P,Q) = {h_mle:.6f}")
print(f"分解检验 unif : H(P)+D = {h_p + kl_unif:.6f}  vs  H(P,Q) = {h_unif:.6f}")

# 事件级核算：Day 010 的 total NLL / 事件数 应等于逐符号交叉熵。
event_nll = sum(-count * log2(q_mle[t]) for t, count in test_targets.items())
print(f"事件级核算：total NLL / n = {event_nll / n_test:.6f} bits/token")
assert abs(event_nll / n_test - h_mle) < 1e-12
```

输出：

```
测试事件数 n_test = 8，词表 |V| = 7
H(P_test)          = 2.750000 bits/token   (PP = 6.727171)
H(P_test, Q_mle)   = 2.905639 bits/token   (PP = 7.493497)
H(P_test, Q_unif)  = 2.807355 bits/token   (PP = 7.000000)
D(P||Q_mle)        = 0.155639 bits
D(P||Q_unif)       = 0.057355 bits
分解检验 mle  : H(P)+D = 2.905639  vs  H(P,Q) = 2.905639
分解检验 unif : H(P)+D = 2.807355  vs  H(P,Q) = 2.807355
事件级核算：total NLL / n = 2.905639 bits/token
```

### 7.3 逐条解读

* **地板是 2.75，不是 0**。$$H(\hat P)=2.75$$ 意味着：在这份测试集的 Token 边缘分布上，任何模型都不可能低于 2.75 bits/token。唯一达到 2.75 的"模型"是恰好等于 $$P\_{\text{test}}$$ 的分布——它对每个 Token 都报出了真实频率。推论一从数字上落地。
* **最大似然反而输给均匀猜测**。$$Q\_{\text{mle}}$$（2.905639）比均匀模型（2.807355）更差，KL 分别是 0.155639 与 0.057355。原因：测试集高频出现的 `你`、`学习`、`机器学习` 在训练集中各只出现 1 次（$$Q=1/16$$，4 bits），训练经验分布与测试经验分布发生了**偏移**。这正是 Day 009 平滑、Day 011 分布偏移讨论的量化展示：最大似然只对"训练分布等于测试分布"这一假设负责。
* **均匀模型的困惑度恰好是 7**。$$2^{2.807355}=7.000000$$——每步在 7 个等可能候选里均匀猜测，Day 010 的"有效分支数"直觉在这里精确兑现。
* **事件级核算与 Day 010 的协议完全一致**。逐符号交叉熵、按事件平均 NLL、困惑度，三者在同一事件空间下是同一个信息的三种写法。

### 7.4 一个必须澄清的"矛盾"：Day 010 的 bigram 为什么能低于 2.75

Day 010 中加 $$k=0.1$$ 的 bigram 模型在这个测试集上得到 1.673504 bits/token——比本节的"地板"2.75 还低。这不是违反推论一，而是**事件空间不同**：

* 本节的地板 2.75 是 **Token 边缘分布**的熵：不使用任何上下文时不可再低的代价；
* bigram 评估的是**给定上下文的条件事件**。测试集中每个上下文只出现一次，经验条件分布是点分布（下一个 Token 完全确定），其条件熵为 0——bigram 评估的地板是 0，不是 2.75。

条件信息只会减少不确定性（链式法则保证 $$H(w\mid h)\le H(w)$$），所以利用上下文的模型族天然能突破"无条件地板"。比较分数时，必须确认大家在同一个事件空间里（Day 010 的一致条件之五、之六）。

## 8. 边界与特异性

* **离散与连续**：Token 是离散符号，本节的熵非负、地板成立。连续分布的**微分熵**（differential entropy）可以为负，读扩散模型、连续潜变量文献时不要混用两套结论。
* **对数底**：bit 与 nat 只差常数因子 $$\ln2$$，KL 与交叉熵的**差值与排名**不受底数影响，但数值跨报告比较必须换算（Day 010 §11.2）。
* **支撑枚举**：公式要求能枚举共同事件空间。评估 Token 级模型时按词表枚举；若比较"按字符"与"按 Token"的分数，事件空间已不同。
* **有限样本噪声**：8 个事件的教学实验方差极大；真实评估应报告测试集规模，必要时给出多个切分下的波动范围。
* **本节结论的适用范围**：$$H(P,Q)\ge H(P)$$、KL 非负、分解恒等式对任意离散分布**恒成立**；"训练集上最小化交叉熵等价于最大似然"对参数化模型**恒成立**；而"哪个模型更好"的判断只在声明的测试协议内有效。

## 9. 工程权衡与失效模式

* **测试集太小**：估计噪声淹没模型差距。工程上要么扩大测试集，要么报告置信区间，绝不基于一两个点的差距下结论。
* **分布偏移**：训练分布与线上分布不同时，训练交叉熵的优化目标本身偏了（本节 MLE unigram 的教训）。
* **支撑漏洞**：任何 $$P>0$$、$$Q=0$$ 的位置直接把评估打成无穷大。计数模型必须平滑（Day 009）；神经模型的 softmax 理论上恒正，但数值下溢要用 log-softmax 处理（Day 019）。
* **评估协议漂移**：Tokenizer、边界、mask 任一变化都会改变事件空间和地板，历史分数不可直接比（Day 010 §11）。
* **指标边界**：交叉熵衡量概率拟合，不衡量事实性、安全性与下游任务效果（Day 010 §12）；对齐相关主题见第 10 周。

## 10. 常见误区

### 误区一：KL 散度是距离

不对称（0.531 对 0.737 的实测例）、不满足三角不等式。只能说"D(P||Q) 是坚持用 Q 时的额外代价"。

### 误区二：交叉熵可以低于熵

同一事件空间内不可能：$$H(P,Q)=H(P)+D\ge H(P)$$。若观察到"低于"，先检查是不是换了事件空间（第 7.4 节的 bigram 例子）。

### 误区三：KL 为零说明模型完美

$$D(\hat P\Vert Q)=0$$ 只说明模型与这份测试集的经验分布一致。换一份测试集、或放到真实语言分布上，差距可能重新出现。

### 误区四：交叉熵最小化和最大似然是两个目标

同一个目标的两种写法（第 6 节推导）。面试中把二者说成"相似但不同"是常见扣分点。

### 误区五：拿训练集交叉熵证明模型好

训练集经验分布不是评估对象。训练分数低 + 测试分数高 = 过拟合的标准信号（本节 MLE unigram 是微缩版）。

### 误区六：微分熵也非负

只有离散熵非负。连续分布的微分熵可为负，且"码长"解释需要加上量化精度才成立。

### 误区七：比较不同事件空间的"地板"

unigram 的地板是 Token 边缘熵，bigram 的地板是经验条件熵（本例为 0）。跨模型族的分数可比，但"距完美还有多远"要各自对各自的地板量。

## 练习与面试准备

### 30 秒口述题

请在 30 秒内回答：

> 为什么交叉熵能评价语言模型？它和 KL 散度是什么关系？

合格答案应包含：交叉熵是"用模型码本编码真实语言的平均比特代价"；分解为 $$H(P)+D\_{\mathrm{KL}}(P\Vert Q)$$；固定测试集时 $$H(P)$$ 是常数，比较交叉熵等价于比较模型与真实分布的 KL 差距；完美模型得分为熵而非 0。

### 基础题 1：手算三件套

$$P={A:0.5,B:0.5}$$，$$Q={A:0.75,B:0.25}$$。计算 $$H(P)$$、$$H(P,Q)$$、$$D\_{\mathrm{KL}}(P\Vert Q)$$，并检验分解恒等式。

<details>

<summary>参考答案</summary>

$$H(P)=1$$ bit。$$H(P,Q)=0.5\times(-\log\_20.75)+0.5\times(-\log\_20.25)\approx0.207519+1=1.207519$$ bits。$$D\_{\mathrm{KL}}(P\Vert Q)=1.207519-1=0.207519$$ bits。恒等式 $$H(P,Q)=H(P)+D$$ 成立：$$1+0.207519=1.207519$$。

</details>

### 基础题 2：无穷大的来源

模型 $$Q$$ 满足 $$Q(A)=0.3$$、$$Q(B)=0.7$$，数据 $$P(A)=0.01$$、$$P(B)=0.99$$；另一个模型 $$Q'$$ 有 $$Q'(A)=0$$。分别说明 $$H(P,Q)$$ 与 $$H(P,Q')$$ 的取值情况，并解释这如何连接 Day 009 的平滑。

<details>

<summary>参考答案</summary>

$$Q(A)>0$$，故 $$H(P,Q)$$ 有限：$$0.01\times(-\log\_20.3)+0.99\times(-\log\_20.7)\approx0.526797$$ bits。$$Q'(A)=0$$ 而 $$P(A)>0$$，$$H(P,Q')=+\infty$$：给真实事件报零概率的码本不可用。Day 009 的加 $$k$$ 平滑正是为了保证每个词表内事件 $$Q>0$$，从而评估有限。

</details>

### 进阶题 3：证明非负性

利用 $$-\log\_2$$ 的凸性（Jensen 不等式）证明 $$D\_{\mathrm{KL}}(P\Vert Q)\ge0$$，并指出取等条件。

<details>

<summary>参考答案</summary>

由 Jensen 不等式，$$\sum\_xP(x)\log\_2\frac{Q(x)}{P(x)}\le\log\_2\sum\_xP(x)\frac{Q(x)}{P(x)}=\log\_2\sum\_{x:P(x)>0}Q(x)\le\log\_21=0$$。取负即得 $$D\_{\mathrm{KL}}(P\Vert Q)\ge0$$。取等要求 $$Q(x)/P(x)$$ 在支撑上为常数且支撑内 $$Q$$ 全取到，即 $$Q=P$$（支撑上）。

</details>

### 进阶题 4：代码审查

下面的实现有什么问题？

```python
def kl(p, q):
    return sum(p[x] * log2(q[x] / p[x]) for x in p)
```

<details>

<summary>参考答案</summary>

比例写反了（算的是 $$D(Q\Vert P)$$ 的交叉项）；没有处理 $$q\[x]=0$$ 或缺键（会抛异常而不是返回无穷大）；隐含要求 $$q$$ 的键覆盖 $$p$$ 的支撑。正确写法应遍历 $$P$$ 的支撑、检查 $$Q$$，并累加 $$P(x)\log\_2(P(x)/Q(x))$$。

</details>

### 面试追问 5：为什么训练时最小化交叉熵而不是"直接最小化 KL"

二者在数据侧只差一个固定的 $$H(\hat P)$$：最小化训练集交叉熵**就是**最小化 $$D\_{\mathrm{KL}}(\hat P\Vert Q)$$。回答时点出"真实分布的熵不可知也无需知，它不随模型变化"即可。

### 面试追问 6：前向 KL 与反向 KL 的行为差异

前向 $$D\_{\mathrm{KL}}(P\Vert Q)$$ 对"数据有、模型无"的位置罚无穷大，迫使 $$Q$$ 覆盖所有数据模式（mass covering）；反向 $$D\_{\mathrm{KL}}(Q\Vert P)$$ 对"模型多给、数据没有"的位置惩罚较轻，倾向于集中到单个模式（mode seeking）。语言模型训练用前向方向；差异在第 10 周偏好优化里会变得关键。

### 项目答辩题 7：训练损失持续下降，测试困惑度却在上升

给出诊断路径与需要的证据。

答题要点：典型的过拟合信号，但先排除评估协议漂移（Tokenizer、mask、测试集版本是否变了）；画出训练/验证曲线确认交叉点；检查数据泄漏（测试集是否混入训练）；检查容量与正则（模型大小、dropout、数据量）；若确认过拟合，用早停或更多数据；报告证据应为曲线与协议对照表，而不是单点数字。

## 延伸阅读

1. [Shannon：A Mathematical Theory of Communication](https://doi.org/10.1145/584091.584099)\
   1948 年原始论文，熵、最优编码与信息论基础的起源，本节编码视角的思想来源。
2. [Cover & Thomas：Elements of Information Theory](https://doi.org/10.1002/0471200611)\
   信息论标准教科书，交叉熵、KL 散度、非负性与链式法则的严格论述（第 2 章）。
3. [Speech and Language Processing：N-gram Language Models](https://web.stanford.edu/~jurafsky/slp3/ed3book.pdf)\
   交叉熵与困惑度在语言模型评估中的标准定义，与 Day 008–010 一脉相承。
4. [PyTorch：KLDivLoss](https://docs.pytorch.org/docs/stable/generated/torch.nn.KLDivLoss.html)\
   工业实现的方向约定：input 是模型 Q 的对数概率、target 是数据 P 的概率，帮助核对方向不写反。

## 一页回顾

```
熵 H(P)：
-Σ P(x) log2 P(x)
= 描述 P 自身的平均最优码长 / 不确定性
交叉熵 H(P,Q)：
-Σ P(x) log2 Q(x)
= 数据来自 P，用按 Q 设计的码本编码的平均代价
= 语言模型评估的量（样本平均即经验交叉熵）

KL 散度：
D(P||Q) = Σ P(x) log2[P(x)/Q(x)]
H(P,Q) = H(P) + D(P||Q)

三个推论：
① H(P,Q) ≥ H(P)：熵是地板，完美模型得分 = 熵，不是 0
② 固定测试集 → H(P) 是常数
   比较交叉熵 = 比较 KL = 比较"距真实分布的差距"
③ P(x)>0 而 Q(x)=0 → H(P,Q) = +∞
   平滑（Day 009）的真正理由

KL 不对称：
前向 D(P_data||Q)：逼 Q 覆盖数据的一切可能（语言模型训练方向）
反向 D(Q||P_data)：允许 Q 集中到单一模式

三个分布角色：
P_lang   真实语言分布，不可得
P_hat    测试集经验分布（样本，有噪声）
Q        被评估的模型
经验交叉熵 = H(P_lang, Q) 的蒙特卡洛估计

训练等价性：
最小化训练集平均交叉熵 ⇔ 最大似然估计
过拟合 = 训练经验分布 ≠ 测试经验分布

本节实测（unigram 实验）：
H(P_test) = 2.75        地板
MLE unigram = 2.905639  KL = 0.155639（分布偏移，输给均匀）
均匀模型 = 2.807355     PP 恰好 = |V| = 7
bigram(Day 010) = 1.673504
  低于 2.75 不矛盾：条件事件空间的地板是 0
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://inwt233.gitbook.io/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-02/day-012.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
