1884 字
9 分钟
统计语言模型:n-gram 与数据平滑

语言模型的定义#

语言模型(Language Model, LM)对语言符号 序列 的概率分布进行建模。给定一个词序列 w1,w2,…,wNw_1, w_2, \ldots, w_N,语言模型要估计它的联合概率:

P(w1,w2,…,wN)P(w_1, w_2, \ldots, w_N)

利用概率链式法则,联合概率可以写成条件概率的乘积:

P(w1,…,wN)=∏i=1NP(wi∣w1,…,wi−1)P(w_1, \ldots, w_N) = \prod_{i=1}^{N} P(w_i \mid w_1, \ldots, w_{i-1})

因此,语言建模的核心是估计“在给定全部历史的情况下,下一个词出现的概率”。这个概率既可以用于计算一个句子的合理性,也可以用于生成文本:每一步从条件分布中采样或取概率最大的词。

n-gram 模型#

直接估计 P(wi∣w1,…,wi−1)P(w_i \mid w_1, \ldots, w_{i-1}) 需要枚举任意长的历史,参数空间无法承受。n-gram 模型引入 马尔可夫假设:一个词出现的概率只取决于它前面最近的 n−1n-1 个词。

P(wi∣w1,…,wi−1)≈P(wi∣wi−n+1,…,wi−1)P(w_i \mid w_1, \ldots, w_{i-1}) \approx P(w_i \mid w_{i-n+1}, \ldots, w_{i-1})

于是整个序列的概率近似为:

P(w1,…,wN)≈∏i=1NP(wi∣wi−n+1,…,wi−1)P(w_1, \ldots, w_N) \approx \prod_{i=1}^{N} P(w_i \mid w_{i-n+1}, \ldots, w_{i-1})
  • n=1n=1:unigram,词与词独立,只看词频。
  • n=2n=2:bigram,当前词只看前一个词。
  • n=3n=3:trigram,当前词看前两个词。

nn 越大,模型能利用的上下文越长,但参数数量和数据稀疏问题也随之加剧。n-gram 的参数通常用极大似然估计(MLE)得到:

P(wi∣wi−n+1,…,wi−1)=count⁡(wi−n+1,…,wi)count⁡(wi−n+1,…,wi−1)P(w_i \mid w_{i-n+1}, \ldots, w_{i-1}) = \frac{\operatorname{count}(w_{i-n+1}, \ldots, w_i)}{\operatorname{count}(w_{i-n+1}, \ldots, w_{i-1})}

一个 bigram 的例子#

取一个很小的语料:

我 喜欢 苹果
我 喜欢 香蕉
他 喜欢 苹果

总词数为 9。unigram 与 bigram 的频数如下:

unigram频数概率bigram频数
我22/9我 喜欢2
喜欢33/9喜欢 苹果2
苹果22/9喜欢 香蕉1
香蕉11/9他 喜欢1
他11/9

由 bigram 频数可以算出条件概率:

P(喜欢|我) = count(我 喜欢) / count(我) = 2/2 = 1
P(苹果|喜欢) = count(喜欢 苹果) / count(喜欢) = 2/3
P(香蕉|喜欢) = count(喜欢 香蕉) / count(喜欢) = 1/3
P(喜欢|他) = count(他 喜欢) / count(他) = 1/1

对于测试句“我喜欢苹果”:

P_uni(我 喜欢 苹果) = 2/9 × 3/9 × 2/9 = 12/729
P_bi(我 喜欢 苹果) = 2/9 × 1 × 2/3 = 4/27

bigram 给了一个高得多的概率,因为它利用了词序信息。

用困惑度评价语言模型#

语言模型的常用内在评价指标是 困惑度(Perplexity, PPL)。给定长度为 NN 的测试序列:

PPL⁡(W)=P(w1,…,wN)−1/N=exp⁡(−1N∑i=1Nlog⁡P(wi∣w<i))\operatorname{PPL}(W) = P(w_1, \ldots, w_N)^{-1/N} = \exp\left(-\frac{1}{N}\sum_{i=1}^{N}\log P(w_i \mid w_{<i})\right)

困惑度可以理解为模型在每一步“平均要在多少个候选词中犹豫”。困惑度越低,模型对测试数据的预测越好。它本质上是测试集上平均负对数似然(交叉熵)的指数,详细推导见 困惑度。

要注意,困惑度只在相同的词表和分词方式下可比;不同 tokenizer 的困惑度不能直接比较。

数据稀疏与零概率#

n-gram 的 MLE 有一个致命问题:训练集中没有出现过的 n-gram,其概率为 0。只要测试句包含一个未见过的 bigram,整句概率就会变成 0,困惑度变成无穷大。

此外,多个概率连乘容易造成数值下溢,实际计算时通常在对数空间求和。

解决零概率问题的技术统称为 数据平滑(Smoothing):从高频事件中“借”一部分概率质量,分配给未观察到的事件。

加一与加 k 平滑#

最简单的平滑是加一平滑(Laplace smoothing):假设每个 n-gram 至少额外出现一次。

Padd-1(wi∣wi−1)=count⁡(wi−1,wi)+1count⁡(wi−1)+VP_{\text{add-1}}(w_i \mid w_{i-1}) = \frac{\operatorname{count}(w_{i-1}, w_i) + 1}{\operatorname{count}(w_{i-1}) + V}

其中 VV 是词表大小。分母加 VV 是为了保证所有条件概率之和为 1。

加一平滑对高频词影响不大,但对低频词和未登录词会“过度分配”概率,反而伤害模型。把 1 换成更小的 kk 得到加 k 平滑:

Padd-k(wi∣wi−1)=count⁡(wi−1,wi)+kcount⁡(wi−1)+kVP_{\text{add-}k}(w_i \mid w_{i-1}) = \frac{\operatorname{count}(w_{i-1}, w_i) + k}{\operatorname{count}(w_{i-1}) + kV}

kk 通常取 0<k<10 < k < 1。加 k 平滑简单,但在大词表上仍然不够精细。

插值与回退#

插值(Interpolation)把不同阶的 n-gram 概率加权组合,让高阶模型在数据充足时发挥作用,低阶模型在数据稀疏时兜底。以 trigram 为例:

P(wi∣wi−2,wi−1)=λ3P(wi∣wi−2,wi−1)+λ2P(wi∣wi−1)+λ1P(wi)P(w_i \mid w_{i-2}, w_{i-1}) = \lambda_3 P(w_i \mid w_{i-2}, w_{i-1}) + \lambda_2 P(w_i \mid w_{i-1}) + \lambda_1 P(w_i)

其中 λ1+λ2+λ3=1\lambda_1 + \lambda_2 + \lambda_3 = 1。权重可以固定,也可以根据语料用 EM 算法学习。

回退(Backoff)则不混合,而是按阶数从高到低尝试:高阶 n-gram 计数为 0 时,退到低阶;如果低阶也为 0,再继续退。Katz 回退会给低阶概率乘一个折扣因子,保证整体概率归一化:

PKatz(wi∣wi−1)={d⋅count⁡(wi−1,wi)count⁡(wi−1),count⁡(wi−1,wi)>0α(wi−1)⋅PKatz(wi),count⁡(wi−1,wi)=0P_{\text{Katz}}(w_i \mid w_{i-1}) = \begin{cases} d \cdot \dfrac{\operatorname{count}(w_{i-1}, w_i)}{\operatorname{count}(w_{i-1})}, & \operatorname{count}(w_{i-1}, w_i) > 0 \\[6pt] \alpha(w_{i-1}) \cdot P_{\text{Katz}}(w_i), & \operatorname{count}(w_{i-1}, w_i) = 0 \end{cases}

Good-Turing 平滑#

Good-Turing 的核心思想是:用“出现 r+1r+1 次的 n-gram 数量”来估计“出现 rr 次的 n-gram 应该分配多少概率”。令 NrN_r 表示恰好出现 rr 次的 n-gram 种类数,则调整后的计数为:

r∗=(r+1)Nr+1Nrr^* = (r+1)\frac{N_{r+1}}{N_r}

概率估计为 PGT=r∗/NP_{\text{GT}} = r^* / N,其中:

  • r∗r^*:调整后的计数;
  • NN:总的 n-gram 出现次数。

所有未出现的 n-gram 共享的总概率质量约为 N1/NN_1 / N。

Good-Turing 的直觉很优雅,但当 NrN_r 很小或为 0 时 r∗r^* 不稳定,实际系统通常会做平滑拟合(如 Simple Good-Turing)。它更多是后续折扣方法的理论基础。

Kneser-Ney 平滑#

Kneser-Ney(KN)是目前最经典的 n-gram 平滑方法之一。它观察到:一个词在低阶模型中出现的次数,不应该按原始词频计算,而应该按它出现在多少个不同的上下文中计算。这个量称为 接续计数(continuation count):

N1+(∙wi)=∣{wi−1:count⁡(wi−1,wi)>0}∣N_{1+}(\bullet w_i) = \left|\{w_{i-1} : \operatorname{count}(w_{i-1}, w_i) > 0\}\right|

以 bigram 为例,绝对折扣插值的 KN 形式为:

PKN(wi∣wi−1)=max⁡(count⁡(wi−1,wi)−D, 0)count⁡(wi−1)+λ(wi−1)⋅PKN(wi)P_{\text{KN}}(w_i \mid w_{i-1}) = \frac{\max\big(\operatorname{count}(w_{i-1}, w_i) - D,\ 0\big)}{\operatorname{count}(w_{i-1})} + \lambda(w_{i-1}) \cdot P_{\text{KN}}(w_i)

其中 DD 是折扣常数(通常取 0.50.5 到 0.750.75 左右),低阶概率用接续计数估计:

PKN(wi)=N1+(∙wi)∑wN1+(∙w)P_{\text{KN}}(w_i) = \frac{N_{1+}(\bullet w_i)}{\sum_{w} N_{1+}(\bullet w)}

插值权重为:

λ(wi−1)=D⋅N1+(wi−1∙)count⁡(wi−1)\lambda(w_{i-1}) = \frac{D \cdot N_{1+}(w_{i-1}\bullet)}{\operatorname{count}(w_{i-1})}

其中 N1+(wi−1∙)N_{1+}(w_{i-1}\bullet) 表示紧跟在 wi−1w_{i-1} 后面、出现过的不同词的数量。

KN 的关键改进在于低阶分布:像 “Francisco” 这样的词,原始词频可能不高,但它几乎只出现在 “San” 后面,接续计数会正确地反映这种上下文专一性。trigram 版本需要递归地对低阶使用接续计数,称为 Modified Kneser-Ney。

平滑方法怎么选#

方法核心思想优点局限
加一 / 加 k给所有事件加一个常数简单、必有正概率大词表上过度平滑
插值各阶概率加权混合灵活,可学习权重需要调参
回退高阶为零时退到低阶保留高阶信息需要折扣保证归一化
Good-Turing用 Nr+1/NrN_{r+1}/N_r 调整计数理论优雅稀疏时 NrN_r 不稳定
Kneser-Ney绝对折扣 + 接续计数实践中效果最好实现较复杂

n-gram 平滑只能缓解稀疏,无法从根本上解决“未见过的上下文”问题。神经网络语言模型通过分布式表示和参数共享,把相似的上下文映射到相近的表示,从而获得泛化能力,见 NNLM 与 RNNLM。

相关阅读#

统计语言模型:n-gram 与数据平滑
https://biscuit0613.github.io/posts/nlp/03-01-language-model/
作者
Biscuit
发布于
2026-07-18
许可协议
CC BY-NC-SA 4.0
MiniMind 代码导读(一):从 Token IDs 到训练 Loss
SAGA: 用自演化目标函数加速科学发现