# N-gram 模型

很多 LLM 的核心运行机制和学习范式是 “预测下一个词的生成”,以此来响应并生成用户需要的内容。

一个基础的问题是:当计算机看到一句话的前面几个词时,它如何预测下一个最可能出现的词是什么?或者,如何判断一句话是不是 “通顺自然”?N-gram 模型就是早期解决这类问题的一种经典方法,它简单、直观,为我们理解更复杂的语言模型打下了坚实的基础。

# 一、什么是 N-gram?—— 语言的 “小片段”

“N-gram” 这个名字听起来有点学术,但拆开来看就很容易理解:

  • N:代表一个自然数,比如 1、2、3 等等。它指的是我们关注的 “词语片段” 的长度。
  • gram:在这里可以理解为一个 “单元” 或 “元素”,在自然语言处理中,这个单元通常是一个词 (word),有时也可以是字符 (character)

所以,N-gram 模型,简单来说,就是一种基于统计的语言模型,它认为一个词的出现概率,可以由它前面 N-1 个词来近似决定。

我们来看几个例子:

  • Unigram (1-gram)

    :N=1。它假设每个词的出现都是独立的,与上下文无关。

    • 例如,句子 “我 爱 人工智能” 中的 unigrams 是:“我”、“爱”、“人工智能”。
  • Bigram (2-gram)

    :N=2。它假设一个词的出现概率,只与它前面紧邻的 1 个词有关。

    • 例如,句子 “我 爱 人工智能” 中的 bigrams 是:“(我,爱)”、“(爱,人工智能)”。
  • Trigram (3-gram)

    :N=3。它假设一个词的出现概率,与它前面紧邻的 2 个词有关。

    • 例如,句子 “我 爱 人工智能” 中的 trigrams 是:“(我,爱,人工智能)”。

# 二、N-gram 模型的核心思想:马尔可夫假设

要预测一句话中第 k 个词 w_k 是什么,最理想的情况是考虑它前面所有出现过的词: P(w_k | w_1, w_2, ..., w_{k-1}) 。 但这里有个大问题:如果句子很长,比如前面有 20 个词,那么 (w_1, w_2, ..., w_{19}) 这个组合在我们的训练数据中可能从未出现过!这样我们就无法估计 w_k 出现的概率了。

为了解决这个问题,N-gram 模型引入了一个重要的简化假设,称为马尔可夫假设 (Markov Assumption)。它认为: 一个词的出现,只与它前面有限的 N-1 个词有关,而与更早的词无关。

  • 对于 Unigram:P (w_k) ≈ P (w_k) (当前词的概率与前面所有词都无关)
  • 对于 Bigram:P (w_k | w_1, ..., w_{k-1}) ≈ P (w_k | w_{k-1}) (当前词的概率只与前 1 个词有关)
  • 对于 Trigram:P (w_k | w_1, ..., w_{k-1}) ≈ P (w_k | w_{k-2}, w_{k-1}) (当前词的概率只与前 2 个词有关)

这个假设大大简化了问题。就像我们玩积木,下一个积木怎么放,我们可能只看它下面那一块,或者下面两块,而不是看最底下的第一块积木。

# 三、如何构建(训练)一个 N-gram 模型?

构建 N-gram 模型的过程,就像是教计算机 “阅读” 大量的文本,然后 “记住” 哪些词语组合更常见。

  1. 准备语料库 (Corpus) 和预处理

    • 首先,我们需要大量的文本数据,这叫做语料库。比如,很多新闻文章、小说、网页内容等。

    • 分词 (Tokenization)

      :对于中文这样的语言,词与词之间没有天然的空格,所以需要先进行分词。

      • 例如,句子 我喜欢吃苹果。 分词后变成: 喜欢 苹果 (标点也常被视为一个 token)
    • 添加句子边界符

      :为了能处理句子的开头和结尾,我们通常会在每个句子的开头加上一个特殊的 “开始” 标记(比如 < S>),在结尾加上 “结束” 标记(比如 </S>)。

      • 例如: <S> 我 喜欢 吃 苹果 。</S>
  2. 统计 N-gram 频次 (Counting Frequencies): 在处理好的语料库上,我们开始数数。我们要数出所有不同 N-gram 序列以及它们的 (N-1)-gram 前缀序列出现的次数。

    Bigram (N=2) 模型为例,对于句子 <S> 我 喜欢 吃 苹果 。</S>

    • 我们需要统计的 Bigrams (长度为 2 的序列) 及其频次:
      • Count( <S> , 我)
      • Count (我,喜欢)
      • Count (喜欢,吃)
      • Count (吃,苹果)
      • Count (苹果,。)
      • Count(。, </S> )
    • 我们还需要统计其前缀 Unigrams (长度为 1 的序列) 及其频次:
      • Count( <S> )
      • Count (我)
      • Count (喜欢)
      • Count (吃)
      • Count (苹果)
      • Count(。)

    假设我们的语料库很小,只有两句话:

    • <S> 我 喜欢 吃 苹果 </S>
    • <S> 我 喜欢 看 书 </S>

    那么部分频次统计如下:

    • Count( <S> ) = 2
    • Count (我) = 2
    • Count (喜欢) = 2
    • Count (吃) = 1
    • Count (苹果) = 1
    • Count (看) = 1
    • Count (书) = 1
    • Count( <S> , 我) = 2
    • Count (我,喜欢) = 2
    • Count (喜欢,吃) = 1
    • Count (喜欢,看) = 1
    • Count (吃,苹果) = 1
    • Count (看,书) = 1
  3. 计算条件概率 (Calculating Conditional Probabilities): 有了频次,我们就可以根据最大似然估计 (Maximum Likelihood Estimation, MLE) 来计算条件概率了。 公式为:P(w_n | w_1, ..., w_{n-1}) = Count(w_1, ..., w_{n-1}, w_n) / Count(w_1, ..., w_{n-1})

    以 Bigram 为例,P (w_i | w_{i-1}) = Count (w_{i-1}, w_i) / Count (w_{i-1})。 根据上面小语料库的例子:

    • P (我 | <S> ) = Count( <S> , 我) / Count ( <S> ) = 2 / 2 = 1
    • P (喜欢 | 我) = Count (我,喜欢) / Count (我) = 2 / 2 = 1
    • P (吃 | 喜欢) = Count (喜欢,吃) / Count (喜欢) = 1 / 2 = 0.5
    • P (看 | 喜欢) = Count (喜欢,看) / Count (喜欢) = 1 / 2 = 0.5
    • P (苹果 | 吃) = Count (吃,苹果) / Count (吃) = 1 / 1 = 1
    • P (书 | 看) = Count (看,书) / Count (看) = 1 / 1 = 1

    这样,我们就构建好了一个 Bigram 模型,它存储了所有这些条件概率。

# 四、N-gram 模型的应用

一旦模型构建完成,我们就可以用它来做很多事情:

  1. 计算句子概率: 一个句子的整体概率可以看作是其包含的所有 N-gram 概率的连乘。 例如,对于 Bigram 模型,句子 S = w_1 w_2 ... w_k 的概率是: P (S) = P (w_1 | <S> ) * P(w_2 | w_1) * P(w_3 | w_2) * ... * P(w_k | w_{k-1}) * P( </S> | w_k) 这个概率可以用来衡量一个句子是否 “自然” 或 “通顺”。在机器翻译或语音识别中,如果有多个候选结果,可以选概率最高的那个。
  2. 文本生成 (预测下一个词): 给定一个词或一段前文,我们可以预测下一个最可能出现的词。 比如,我们输入 "我 喜欢",模型会查找所有以 "喜欢" 开头的 bigram 的条件概率,例如 P (吃 | 喜欢) = 0.5, P (看 | 喜欢) = 0.5。如果还有 P (玩 | 喜欢) = 0.01 等等。 最简单的生成策略是贪心搜索:每次都选择条件概率最高的那个词作为下一个词。 例如,如果 P (吃 | 喜欢) 比 P (看 | 喜欢) 略高一点,模型就可能生成 "我 喜欢 吃"。然后,再根据 "吃" 来预测下一个词,比如 "苹果"。最终可能生成 "我 喜欢 吃 苹果"。

# 五、N-gram 模型的局限性

尽管 N-gram 模型直观且在一定程度上有效,但它有几个显著的局限性:

  1. 无法捕捉长距离依赖 (Limited Context): 马尔可夫假设限制了模型只能 “看到” 前面 N-1 个词。如果一个词的出现依赖于更早之前的信息,N-gram 模型就无能为力了。
    • 例子:考虑句子:“我在法国巴黎长大,……,所以我能说一口流利的 ___。” 空缺处很可能是 “法语”。但如果我们的 N-gram 模型 N 值较小(比如 N=2 或 3),当它预测空缺处的词时,可能只看到 “流利的”,或者 “一口 流利的”。这些局部信息不足以推断出 “法语”,因为它依赖于远处的 “法国巴黎”。模型可能会基于 “流利的” 预测出更常见的搭配,如 “英语” 或 “中文”。
  2. 数据稀疏性 (Data Sparsity) 与零概率问题 (Zero Probability Problem): 这是 N-gram 模型(在不使用平滑技术时)最致命的问题。
    • 问题描述:在有限的训练语料中,很多词语组合可能从未出现过。例如,一个词组 “非常 可爱 的 小猫咪”,如果我们的训练数据中从来没有出现过 “可爱 的 小猫咪” 这个 trigram,那么 Count (可爱,的,小猫咪) 就会是 0。
    • 后果:根据概率计算公式,P (小猫咪 | 可爱,的) = 0 / Count (可爱,的) = 0。 这意味着,任何包含这个从未见过的 N-gram 的句子,其整体概率都会变成 0!这显然是不合理的,因为这个句子可能是完全正确和自然的。
    • N 值的影响:当 N 增大时,可能的 N-gram 组合数量会指数级增长。比如,一个有 10000 个词的词汇表,可能的 bigram 组合是 10000^2 = 1 亿,trigram 组合是 10000^3 = 1 万亿。即使有海量语料,也很难覆盖所有这些组合,导致大量的 N-gram 频次为零。因此,N 的值通常不会取得很大(常用 2、3,很少超过 5)。
  3. 缺乏真正的语义理解: N-gram 模型本质上是基于词串的表面统计,它并不理解词语的真正含义或句子背后的语法结构。
    • 例子:“狗 咬 人” 和 “人 咬 狗”。如果语料中 “狗 咬” 和 “咬 人” 的 bigram,以及 “人 咬” 和 “咬 狗” 的 bigram 都比较常见,模型可能会给这两句话相似的概率,但显然第二句在语义上是很奇怪的。

# 六、N-gram 的意义

尽管存在上述局限性,N-gram 模型在自然语言处理发展史上扮演了非常重要的角色。它简单、高效,为许多应用(如早期的机器翻译、拼写检查、输入法)提供了基础。

更重要的是,N-gram 模型暴露出的数据稀疏性无法捕捉长距离依赖等问题,极大地推动了后续语言模型的研究。为了解决这些问题,研究者们提出了平滑技术(我们这次没讨论)、以及更强大的神经网络语言模型(如 RNN, LSTM, Transformer 等),这些模型能够更好地学习词语的分布式表示(词向量),并捕捉更复杂的上下文信息,从而在各种 NLP 任务上取得了显著的突破。

本文参考《GPT 图解:大模型是怎样构建的》

最终内容由 gemini 2.5 pro 整理生成

更新于 阅读次数