第 4 章:从 n-gram 到神经语言模型¶
1. 本章要解决的问题¶
第 3 章里,我们已经回答了一个基础但非常重要的问题:
文本是怎么变成模型可以处理的输入的?
我们已经知道,一句话进入模型之前,通常会经历:
文本 -> token -> token id -> embedding
但这时新的问题马上就来了:
输入已经变成向量了,然后呢?
模型到底想学会什么?
更具体一点说,当我们把一句话喂给模型时,模型训练的目标究竟是什么?
例如给它前文:
I love
模型为什么会倾向于预测:
youdeepthis
而不是一个完全随机的 token?
这就是本章要解决的核心问题。
这一章的任务可以概括成一句话:
我们要建立“语言模型”这件事的最小完整直觉。
这里的“最小完整”很重要。
因为很多人在一开始接触大模型时,会直接把注意力放在:
- Transformer
- Attention
- GPT
- 预训练
这些现代概念上。
但如果你没有先搞清楚“语言模型本质上到底在预测什么”,后面看到再复杂的结构,都容易只停留在“会背名词”的状态。
所以这一章不急着讲复杂结构,而是先把一条最核心的主线讲明白:
语言模型是什么 -> n-gram 是怎么做的 -> 它为什么不够 -> 神经语言模型为什么出现 -> 为什么后来会走到 Transformer
从全书结构上看,这一章有三个作用:
- 它承接第 3 章的“输入表示”
- 它为第 5 章的 Attention 建立问题背景
- 它也为第 6 章 Transformer 和第 7 章 Mini-GPT 做目标层面的铺垫
如果第 3 章解决的是“文本怎么表示”,那么第 4 章解决的就是:
模型拿到这些表示之后,到底在学习什么预测任务。
2. 你学完后应该会什么¶
- 能用自己的话解释什么是语言模型
- 能理解 next-token prediction 和序列概率建模之间的关系
- 能写出 Chain Rule,并知道它为什么重要
- 能理解 unigram、bigram、trigram 的基本思想
- 能说明 n-gram 语言模型为什么会遇到数据稀疏和泛化差的问题
- 能理解神经语言模型相对 n-gram 的核心改进
- 能顺着这条历史脉络,过渡到后面的 Attention 和 Transformer
3. 什么是语言模型¶
先给一个尽量不绕的定义:
语言模型(Language Model, LM)做的事情是:
给定前文,预测下一个 token 出现的概率。
例如前文是:
I love
一个语言模型可能给出这样的预测:
you: 0.35deep: 0.18this: 0.12pizza: 0.05
这意味着什么?
意味着在模型看来,在当前上下文下,某些 token 比另一些 token 更合理、更可能出现。
所以语言模型不是在做“绝对正确答案”的判断,而是在做:
条件概率分布的估计。
3.1 next-token prediction 的直觉¶
如果把语言模型想得非常朴素一点,它其实像是一个“自动补全系统”。
你给它一句话的前半段,它根据过去看过的大量文本,判断后面最可能接什么。
例如:
My name isToday is aThe capital of France is
在不同上下文里,模型对“下一个 token”的判断会完全不同。
所以语言模型最核心的能力,不是记住固定答案,而是:
根据上下文动态调整对下一个 token 的概率判断。
3.2 它不仅是在补下一个词,也是在给整个句子建模¶
虽然训练时我们常说“预测下一个 token”,但这件事背后对应的是更一般的问题:
给整个 token 序列分配一个概率。
例如一个句子:
I love deep learning
如果一个模型觉得这个句子在语言上更自然、更常见,那么它就应该给这个序列更高的概率; 如果一个句子非常奇怪、几乎不符合语言习惯,那么它就应该给更低的概率。
所以:
- 从训练过程看,它是在一步一步预测下一个 token
- 从概率建模看,它是在为整个序列建模
这两件事,本质上是同一件事的两个视角。
4. Chain Rule:为什么可以一步一步预测¶
现在我们来把上面的直觉写成数学形式。
设一个 token 序列是:
那么这个完整序列的概率可以写成:
这就是概率论里的 Chain Rule。
它的意义非常大,因为它告诉我们:
如果你能不断回答“给定前文,下一个 token 的概率是多少”,那么你其实就已经可以给整个句子建模了。
换句话说,语言模型之所以可以训练成“next-token prediction”,并不是拍脑袋定出来的技巧,而是因为:
序列概率本来就可以分解成一连串条件概率。
4.1 一个最小例子¶
假设一句话是:
I love NLP
那么它的概率可以拆成:
这说明模型训练时其实在做三件事:
- 句子开头出现
I的概率有多大 - 已知前面是
I,下一个是love的概率有多大 - 已知前面是
I love,下一个是NLP的概率有多大
于是,一个看起来复杂的句子概率问题,就被拆成了一连串局部预测问题。
这就是现代语言模型训练目标的基础直觉。
5. n-gram:最早期的语言模型怎么做¶
既然语言模型的目标是估计:
那最直接的方法是什么?
最直接的方法当然是:
去统计训练语料里各种上下文和下一个词的共现次数。
这就是传统 n-gram 语言模型的基本思路。
5.1 unigram:完全不看上下文¶
最简单的语言模型叫 unigram model。
它做了一个非常强的简化:
每个 token 的出现概率只看它自己,不看上下文。
也就是说:
这当然很粗糙,但它给了我们最早的概率语言模型直觉:
一个词出现得越频繁,它的概率就越大。
例如在一个小语料里:
the出现 100 次cat出现 10 次banana出现 2 次
那么 unigram model 就会认为 the 比 cat 更可能出现。
问题是,它完全不知道上下文。
所以在 unigram 看来:
I eat后面接appleI eat后面接Monday
可能都只是看各自的全局词频,而不是看局部语义是否合理。
5.2 bigram:只看前一个 token¶
bigram model 往前走了一步。
它假设下一个 token 只依赖前一个 token:
例如如果我们想预测:
I am ___
bigram model 在真正做的事情其实是:
只看 am 后面在训练语料里通常跟什么。
如果训练集中:
am happy出现 40 次am tired出现 25 次am banana出现 0 次
那么模型自然会倾向于给 happy、tired 更高概率。
5.3 trigram:看前两个 token¶
再进一步,trigram model 假设下一个 token 只依赖前两个 token:
这比 bigram 更合理,因为语言通常不只依赖前一个词。
例如:
New York ___
如果只看前一个 token York,信息不够完整;
但如果看前两个 token New York,下一个词是 City 的概率就会更明显。
5.4 一般化到 n-gram¶
所谓 n-gram,本质上就是做一个统一的马尔可夫近似:
也就是:
预测下一个 token 时,不看全部历史,只看最近的 n-1 个 token。
于是:
- unigram:看 0 个历史 token
- bigram:看 1 个历史 token
- trigram:看 2 个历史 token
这就是 n-gram 语言模型名字的来源。
6. 一个最小 bigram 例子¶
为了让这个概念彻底落地,我们来看一个最小语料:
I love NLPI love deep learningI enjoy learning
如果我们只做 bigram 统计,那么模型会关心:
I后面出现过什么love后面出现过什么deep后面出现过什么
例如:
I 后面的统计可能是:
love: 2 次enjoy: 1 次
所以:
再看 love 后面:
NLP: 1 次deep: 1 次
于是:
如果现在前文是:
I
那么 bigram 模型会倾向于预测:
loveenjoy
如果前文是:
love
那么它会倾向于预测:
NLPdeep
这就是最原始的“根据历史预测下一个 token”。
从这里你也能看出来,n-gram 语言模型本质上很像一张巨大的条件概率统计表。
7. n-gram 为什么不够¶
n-gram 很重要,因为它第一次让“语言模型”这件事变得具体可算。
但它也有非常明显的问题。
7.1 数据稀疏:没见过就很难办¶
自然语言组合非常多。
即使语料已经不小,很多上下文组合仍然可能根本没有出现过。
例如训练集中也许出现过:
I love applesI love bananas
但没出现过:
I love mangoes
如果你用传统 n-gram 统计,很多没见过的组合就会直接拿不到可靠概率,甚至变成 0。
这就是数据稀疏问题。
它不是小毛病,而是语言建模里非常本质的问题:
语言是开放组合的,但纯统计表只能覆盖见过的组合。
7.2 上下文窗口太短¶
n-gram 的第二个问题是,它只能看固定长度的历史。
例如 trigram 最多只看前两个 token。
这意味着很多更长距离的信息都会丢失。
比如下面这个句子:
The students who studied hard for the exam were happy because they finally passed.
如果只看很短的局部窗口,模型可能无法稳定利用前面更远的位置来判断后面的词。
而真实语言中,长距离依赖非常常见。
7.3 泛化能力差¶
n-gram 的第三个问题更关键:
它不会“举一反三”。
例如:
I like applesI like bananas
这两个上下文在人类看来非常像。
但在 n-gram 的统计表里,它们本质上只是两条独立记录。
模型很难自动学会:
apples和bananas都是食物like apples和like bananas的模式相近- 没见过的相似搭配也许仍然合理
也就是说,n-gram 依赖的是“显式出现过没有”,而不是“结构上像不像”。
7.4 smoothing 能缓解,但不能根治¶
传统语言模型里确实发展出了很多 smoothing 方法,用来缓解“没见过组合概率为 0”的问题。
例如:
- Laplace smoothing
- Good-Turing
- Kneser-Ney
这些方法很重要,但它们主要是在统计层面做修补。
它们能缓解稀疏问题,却不能从根本上解决两个核心限制:
- 固定窗口太短
- 无法学习连续空间中的相似性
这就把我们自然带到了下一步:
能不能不用“查统计表”的方式,而是让模型自己学一个更有泛化能力的函数?
8. 从 n-gram 到神经语言模型¶
神经语言模型出现的核心动机,可以浓缩成一句话:
不要把语言模型做成一张巨大的离散统计表,而要把它做成一个可学习的函数。
这个变化非常关键。
因为一旦你不用“每种上下文单独记一份计数”,而是改成“共享参数的神经网络”,很多 n-gram 做不到的事情就开始变得可能。
8.1 分布式表示:词不再只是孤立编号¶
在第 3 章我们已经讲过 embedding。
它的意义在这里真正体现出来了。
传统 n-gram 里,一个 token 本质上只是离散符号。
但在神经语言模型里,一个 token 会先被映射成一个连续向量。
于是模型不再只看到:
apple是 id 1521banana是 id 2088
而是会看到它们各自的 embedding,并且这些向量可能在空间中比较接近。
这就给了模型一种非常重要的能力:
它可以在“相似 token”和“相似上下文”之间共享统计强度。
8.2 参数共享:没见过的组合也能有机会猜对¶
神经网络的优势不只是把 token 变成向量,更重要的是:
它学的是一个参数化函数。
可以粗略写成:
这里的 \( f_\theta \) 就是由参数 \( \theta \) 控制的神经网络。
这意味着:
- 不同上下文之间可以共享参数
- 相似模式可以互相帮助
- 即使某个具体组合没在训练里完整出现过,模型也可能因为见过很多相似模式而给出合理预测
这就是“泛化能力”提升的来源。
8.3 最小神经语言模型长什么样¶
最简单的神经语言模型可以这样理解:
- 把上下文 token id 映射成 embedding
- 把这些 embedding 拼接或汇总
- 送入一个神经网络
- 输出对整个词表的打分
- 经过 softmax,变成下一个 token 的概率分布
写成流程就是:
context ids -> embeddings -> neural network -> logits -> softmax -> next-token probabilities
这和 n-gram 的核心区别在于:
- n-gram:显式查表、做计数
- neural LM:隐式建模、共享参数
9. 从 MLP 到 RNN,再到 Transformer¶
神经语言模型本身也不是一步到位变成今天的 GPT。
它中间经历了几代典型结构。
9.1 MLP 语言模型¶
比较早期的一类神经语言模型,会取一个固定长度的上下文窗口, 把这些 token 的 embedding 拼接起来,然后送进 MLP。
它已经比 n-gram 更强,因为:
- 输入不再是离散计数,而是连续表示
- 参数可以共享
- 相似上下文可以迁移
但它仍然有一个明显限制:
窗口长度通常还是固定的。
9.2 RNN / LSTM 语言模型¶
后面出现的 RNN、LSTM、GRU 等序列模型,尝试解决固定窗口的问题。
它们的核心思想是:
把过去的信息压到隐藏状态里,随着序列一步一步往后传。
这样模型理论上可以利用更长的历史,而不必像 n-gram 或固定窗口 MLP 那样只盯着最近几个 token。
不过 RNN 类模型也有自己的问题,比如:
- 并行训练不方便
- 长距离依赖仍然难学
- 序列很长时训练效率和稳定性不理想
9.3 Transformer 语言模型¶
Transformer 的出现,把语言模型又往前推了一大步。
它最关键的变化是:
模型不再只靠一个递归状态逐步传信息,而是可以通过 Attention 直接查看上下文中的其他位置。
这让它在下面几件事上表现得更强:
- 更容易建模长距离依赖
- 更适合并行训练
- 更适合大规模扩展
这也是为什么现代大语言模型几乎都建立在 Transformer 之上。
从这个角度看,第 5 章和第 6 章其实不是突然换了一个新话题,而是在回答:
既然我们已经知道语言模型需要更强的上下文建模能力,那么 Attention 和 Transformer 是怎么做到的?
10. perplexity:语言模型好不好,怎么衡量¶
既然语言模型输出的是概率分布,那我们就需要一个指标来衡量:
模型对真实文本的预测到底好不好。
一个经典指标就是 perplexity,通常记作 PPL。
你可以先把它理解成:
模型面对真实下一个 token 时,有多“困惑”。
直觉上:
- 如果模型总能把高概率分给正确 token,那么 perplexity 会比较低
- 如果模型总是拿不准,或者经常把高概率给错 token,那么 perplexity 会比较高
所以一般来说:
perplexity 越低越好。
不过要注意两点:
- perplexity 主要适用于语言模型概率预测场景
- 它能反映“预测性”,但不直接等于“生成效果一定更自然”
这也是为什么现代 LLM 的评估不会只看 perplexity,还会结合下游任务和人工偏好评估。
11. 从直觉到实现:语言模型训练时到底在干什么¶
到这里,我们可以把训练流程串起来了。
假设有一句 token 序列:
[x1, x2, x3, x4, x5]
训练时通常会构造这样的输入输出对:
- 输入:
[x1],目标:预测x2 - 输入:
[x1, x2],目标:预测x3 - 输入:
[x1, x2, x3],目标:预测x4 - 输入:
[x1, x2, x3, x4],目标:预测x5
也就是说,语言模型训练的本质就是:
不断读取前文,然后让模型把真实下一个 token 的概率拉高。
如果你把它和第 2 章的训练循环联系起来,就会发现它其实非常统一:
- 前向传播:输出每一步对下一个 token 的概率分布
- 计算 loss:真实 token 的负对数似然
- 反向传播:更新参数
所以从工程视角看,语言模型并没有脱离你已经学过的训练框架; 它只是把监督信号换成了“下一个 token 是什么”。
12. 最小代码实现:bigram language model¶
下面给一个最小 bigram 统计实现,只用来帮助你建立直觉。
from collections import defaultdict, Counter
corpus = [
["I", "love", "NLP"],
["I", "love", "deep", "learning"],
["I", "enjoy", "learning"],
]
bigram_counts = defaultdict(Counter)
for sentence in corpus:
for prev_token, next_token in zip(sentence[:-1], sentence[1:]):
bigram_counts[prev_token][next_token] += 1
def predict_next(prev_token):
counter = bigram_counts[prev_token]
total = sum(counter.values())
return {token: count / total for token, count in counter.items()}
print(predict_next("I"))
print(predict_next("love"))
可能输出:
这个代码虽然非常简单,但已经完整体现了 n-gram 的核心思想:
- 用上下文做 key
- 用下一个 token 的计数构造条件概率
- 用统计频率来近似语言规律
如果你继续往前走一步,把“查统计表”换成“embedding + 神经网络”,就进入神经语言模型的世界了。
13. 常见误区¶
误区 1:语言模型就是在背答案¶
这不准确。
语言模型学到的是条件概率分布,而不是固定问答对。
看起来像“知道答案”,本质上是它在大量上下文统计和参数共享中学到了什么 token 更可能出现。
误区 2:next-token prediction 太简单,所以学不到复杂能力¶
这也是一个常见误解。
单步目标看起来简单,但因为训练数据量巨大、上下文丰富、模型参数很多, 所以为了持续把“下一个 token”预测好,模型会被迫学到:
- 语法
- 搭配
- 事实模式
- 推理痕迹中的统计结构
也就是说,简单目标不代表学到的能力简单。
误区 3:n-gram 和 Transformer 是完全无关的两件事¶
并不是。
它们都在做语言模型,只是建模条件概率的方式不同。
可以把 n-gram 看成语言模型发展的早期形态, 把神经语言模型和 Transformer 看成在同一目标上的更强实现。
14. 面试问题¶
Q1:语言模型本质上在做什么?¶
语言模型本质上是在估计 token 序列的概率。训练时通常表现为:给定前文,预测下一个 token 的条件概率。
Q2:为什么 next-token prediction 可以用于训练整个语言模型?¶
因为根据 Chain Rule,整个序列的联合概率可以分解成一系列“给定前文预测下一个 token”的条件概率乘积。
Q3:n-gram 的核心问题是什么?¶
核心问题包括:数据稀疏、上下文窗口太短、泛化能力差。它依赖离散统计,难以利用相似 token 和相似上下文之间的结构信息。
Q4:神经语言模型相比 n-gram 的主要提升是什么?¶
它通过 embedding 和参数共享,把离散统计表变成可学习函数,因而具有更好的泛化能力,也更容易建模复杂上下文。
Q5:为什么后来语言模型会走向 Transformer?¶
因为 Transformer 在长距离依赖建模、并行训练效率和大规模扩展性上,比早期方法更有优势。
15. 本章小结¶
这一章你应该抓住的,不是某个历史名词,而是一条非常稳定的主线:
- 语言模型的目标是估计序列概率
- 这可以通过 next-token prediction 来实现
- n-gram 是最早期的条件概率统计方法
- 它让语言模型变得可计算,但也暴露了数据稀疏、短窗口和泛化差的问题
- 神经语言模型通过 embedding 和参数共享,开始真正具备泛化能力
- Transformer 则是在这条路上继续发展出的更强结构
所以读完这一章之后,你应该已经知道:
后面要学 Attention,并不是因为它“很火”,而是因为我们已经遇到了一个真实的建模问题:
语言模型需要更强的上下文建模能力。
下一章,我们就正式进入这个问题的核心答案之一:
Attention 机制。