从零开始的NLP:从影评分类到 Transformer
用可手算的小例子串起文本表示、词向量、RNN、Attention 与 Transformer,再看语言模型的训练、生成和计算成本。
“电影好看”和“电影不好看”只差一个字,判断却可能完全相反。对人来说,这很容易;对模型来说,首先得解决一个更基础的问题:这些文字要变成什么数字,才能让后面的计算保留这种差别?
这篇文章从影评分类出发,沿着几个问题展开:词频能表达什么,词向量怎样学出来,为什么还需要处理顺序,以及 Attention 如何让不同位置交换信息。最后再把这些计算放回 Transformer,看看它如何训练、生成文本,又把计算和显存花在了哪里。
只要知道机器学习大致是“用数据调整参数,再预测新数据”,就可以开始阅读。全文较长,可以分三段读:
| 阅读范围 | 主要问题 |
|---|---|
| 1—7:文本表示与词向量 | 文字如何变成数字,这些数字如何学出来? |
| 8—12:顺序与生成 | RNN、LSTM、语言模型和 Attention 各自改变了什么? |
| 13—18:Transformer 与应用 | 模型怎样计算、训练和评价,成本在哪里? |
文中的微型数据集、向量和分数均为教学构造,不是真实训练结果或性能测试。除另有说明外,计算使用未舍入值,展示时保留三位小数;把展示值代回公式,末位可能略有差异。
公式放在数字例子之后,每节的练习可以按需展开。文末附有公式速查、术语表和参考文献;配套 Python 脚本 能复算主要例子,只需要 Python 标准库。
1. 我们到底要让计算机做什么
1.1 先把任务缩小到“给影评分类”
假设我们收集了下面六条评论,并由人给出标签:
| 编号 | 评论 | 人工标签 |
|---|---|---|
| A | 电影好看 | 好评 |
| B | 剧情精彩 | 好评 |
| C | 电影难看 | 差评 |
| D | 剧情无聊 | 差评 |
| E | 电影不好看 | 差评 |
| F | 电影不难看 | 好评 |
为了方便演示,我们把“不难看”归为好评。实际业务中它也可能被标为中性,因此真实项目应先明确标注标准。
模型要学习的是:看到新的评论,例如“这部电影挺精彩”,给出好评的概率。
整个过程与你熟悉的分类任务一样:
1
已经标注的评论 → 训练模型 → 新评论 → 好评概率 → 按阈值给出类别
困难在于,房价预测可以直接输入“面积 80、楼层 6”,影评却是一串文字。我们首先需要把文字整理成机器学习能使用的数值特征。
NLP,即自然语言处理,就是让计算机对人类语言完成分类、抽取、检索、翻译、生成等任务。
1.2 有些模型要输出一个标签,有些要输出一段文字
现在先记住三类输出:
| 输入 | 想要的输出 | 任务名称 |
|---|---|---|
| 电影很好看 | 好评 | 文本分类 |
| 小王住在上海 | 人物:小王;地点:上海 | 实体识别 |
| 我喜欢这部电影 | I like this movie. | 机器翻译 |
分类通常给整段文字一个结果;实体识别需要指出原文中的位置;翻译要生成一个长度可能不同的新句子。输出不同,后面的模型设计就会不同。
1.3 第一个方法:人写规则
可以先写一个打分表:
1
2
3
好看、精彩:每出现一次加 1 分
难看、无聊:每出现一次减 1 分
最后分数大于 0 就判为好评
它能处理 A—D,却会把 E“电影不好看”误判为好评。
你可以再补一条“看到‘不’就反转”,但“不是不好看,只是太长”又需要更细的规则。
规则的价值是清楚、便宜、容易控制。困难是语言组合太多,维护例外的工作会越来越多。
能否从已经标注的评论里,让模型自己学哪些词重要?
小练习:“情感分析”是不是只能预测一个连续分数?
不是。这里预测好评或差评就是分类;也可以预测 1—5 分,还可以分别评价“剧情”和“演技”。任务如何定义决定输出形式。
2. 把一句话拆开,再给每一块一个编号
2.1 “拆成什么”是一项选择
对“电影不好看”,先采用下面的切法:
1
电影不好看 → [电影, 不, 好看]
每一块称为一个 token,这个拆分过程称为 tokenization。
暂时可以把 token 理解成“模型一次接收的一个文字单位”。它不一定是完整的词:也可以是汉字、词的一部分或字节。
本文前半部分为了好读,手工按词切分;这不是声称所有实际分词器都会这样切。
2.2 建一张编号表
把六条评论里用到的词整理成词表:
| 编号 | 词 |
|---|---|
| 0 | 电影 |
| 1 | 剧情 |
| 2 | 好看 |
| 3 | 精彩 |
| 4 | 难看 |
| 5 | 无聊 |
| 6 | 不 |
那么:
1
2
电影好看 → [电影, 好看] → [0, 2]
电影不好看 → [电影, 不, 好看] → [0, 6, 2]
这些数字只是编号。编号 4 不代表比编号 2 更强烈,“好看”和“精彩”的编号相邻也不代表它们在数学上更相似。
可以任意重新排列词表,只要整个模型始终使用同一套映射。
2.3 为什么不能只把编号直接塞进普通线性模型
假设你把“好看”的编号 2 直接当作特征数值,把“难看”的编号 4 直接当作特征数值。
普通数值运算会把 4 当成 2 的两倍。但词语没有这种大小关系,因此这个数值解释是错误的。
我们要用编号去“查表示”,而不是让编号本身承担语义。
2.4 如果模型遇到没见过的词
例如新评论是“镜头惊艳”。小词表里没有“镜头”和“惊艳”。
按整词处理的一个办法是用特殊编号 <UNK> 代表未知词。但不同未知词都变成同一符号,就丢掉了区别。
还可以换拆分粒度:
| 方案 | 示例 | 直接后果 |
|---|---|---|
| 按词 | [电影, 不, 好看] | 单位较完整,但需要收集很多词 |
| 按字 | [电, 影, 不, 好, 看] | 可组合新词,但输入更长 |
| 按子词 | 英文 playing 可能拆为 [play, ing] | 可以复用词的一部分,实际切分依赖词表 |
| 按字节 | 使用文本编码得到的字节 | 能覆盖任意字节序列,但不保证序列短 |
常见的 BPE 子词方法,会反复合并训练文本里经常相邻出现的小单位。它学习的是一种常用片段的切分规则,不会自动知道这些片段是什么意思。1
单位越小,词表往往越容易覆盖文本,但句子往往越长;单位越大,句子可能更短,但词表更大、生僻词更难处理。
小练习:把所有 token 编号都加 100,只要同步修改查表关系,会改变句子含义吗?
不会。编号只是地址;真正提供信息的是编号对应的表示和后续计算。
3. 最简单的数值表示:统计每个词出现了几次
3.1 从一张计数表开始
沿用上一章的七个词,并固定列顺序:
1
[电影, 剧情, 好看, 精彩, 难看, 无聊, 不]
“电影好看”中,“电影”出现一次,“好看”出现一次,其他词没出现:
1
电影好看 → [1, 0, 1, 0, 0, 0, 0]
“电影不好看”则是:
1
电影不好看 → [1, 0, 1, 0, 0, 0, 1]
这样,每条评论都变成长度相同的一排数字。这排数字称为向量。这里向量的每一列都有明确含义:对应词出现的次数。
这就是 Bag of Words,词袋,简称 BoW。名字来自一个比喻:把所有词放进袋子里,知道袋子里有什么,但不知道原先排列顺序。
3.2 现在可以接上熟悉的分类器了
给每个词一个权重。下面先人为给值,下一章再学习如何更新:
| 词 | 权重 |
|---|---|
| 电影、剧情 | 0 |
| 好看、精彩 | +1 |
| 难看、无聊 | -1 |
| 不 | 0 |
评论得分就是“次数 × 权重,再全部相加”。
1
2
电影好看:1×0 + 1×1 = 1
电影难看:1×0 + 1×(-1) = -1
为了把分数变成 0—1 之间的概率,使用 sigmoid 函数。先记住几个结果即可:
| 得分 | sigmoid 后的好评概率 |
|---|---|
| -2 | 0.119 |
| -1 | 0.269 |
| 0 | 0.500 |
| 1 | 0.731 |
| 2 | 0.881 |
于是“电影好看”的好评概率为 0.731,“电影难看”为 0.269。
如果你认识逻辑回归,到这里其实已经有了一个文本逻辑回归分类器。
用公式缩写:
\[z=w_1x_1+w_2x_2+\cdots+w_7x_7+b,\qquad p=\frac{1}{1+e^{-z}}\]这里 x 是词频,w 是对应权重,b 是所有输入共享的偏置。偏置可以理解为看具体词之前的基础倾向。
3.3 词袋究竟丢了什么
看两句完全由相同词组成的话:
1
2
我 喜欢 你
你 喜欢 我
它们的词袋相同,但“谁喜欢谁”不同。
再看两条评价:
1
2
好看,不是难看。
难看,不是好看。
如果采用同样的切词规则并忽略标点,两句话的词频也相同,却表达相反的判断。
一旦不同输入被转换成完全相同的特征,后面的分类器再复杂也无法仅凭这些特征把它们区分开。
这比“某个模型还不够大”更根本:信息在进入模型前已经丢了。
3.4 TF-IDF:给到处出现的词少一点权重
换一个只用于计算的小文档集合:
1
2
3
D1:电影 好看
D2:电影 精彩
D3:电影 无聊
“电影”出现在全部三篇里,不能帮助区分这三篇。“好看”只出现在一篇里,区分度更高。
一种简单的 IDF 算法是:
1
IDF = ln(文档总数 ÷ 包含这个词的文档数)
ln 是自然对数。你暂时只需要知道:ln(1)=0,ln(3)≈1.099,数越大,结果也越大。
因此:
1
2
电影的 IDF = ln(3÷3) = 0
好看的 IDF = ln(3÷1) ≈ 1.099
本例采用原始计数作为 TF,即当前文档里词出现的次数。TF-IDF 把 TF 与 IDF 相乘。
若列顺序是 [电影, 好看, 精彩, 无聊],D1 会变成:
1
2
普通词频:[1, 1, 0, 0]
TF-IDF:[0, 1.099, 0, 0]
这里采用自然对数且不做平滑、归一化。IDF 也可采用其他对数底数;实际工具还可能加平滑、加常数或归一化,因此比较数值时要先对齐定义。2
词袋和 TF-IDF 计算便宜、容易检查,对关键词明确的分类任务很有用;它们依然无法恢复被丢掉的顺序,也不会自动把同义词视为相近。
小练习:把词频换成 TF-IDF,能区分“我喜欢你”和“你喜欢我”吗?
不能。同一文档集合下,相同词频仍会得到相同 TF-IDF 向量。换权重没有恢复词序。
4. 让权重自己学习:完整走一次“预测—算错多少—更新”
4.1 只看一个词,避免同时处理太多数字
现在先把输入简化为:只判断是否出现“好看”。
1
2
3
4
输入特征 x = 1,表示“好看”出现一次。
正确标签 y = 1,表示好评。
初始权重 w = 0。
为了简化,本例不使用偏置。
预测分数:z=w×x=0。
好评概率:p=sigmoid(0)=0.5。
模型目前没有倾向,就像在两个选项之间犹豫。
4.2 用一个数字表示这次预测有多差
对于这条正确答案为好评的样本,使用:
1
损失 = -ln(模型给好评的概率)
所以:
| 好评概率 | 损失 |
|---|---|
| 0.1 | 2.303 |
| 0.5 | 0.693 |
| 0.9 | 0.105 |
正确答案的概率越高,损失越小。
对于差评样本,则关心差评概率 1-p。把两个情况合在一起,就是二分类交叉熵:
这里的 \(y\in\{0,1\}\),\(p\) 是模型估计的好评概率。交叉熵惩罚模型没有把概率分配给正确答案;估计概率也不能直接当作经过验证的实际正确率。3
4.3 更新一次,观察发生了什么
对于本例的 sigmoid 与交叉熵组合,权重的梯度是:
1
2
3
梯度 = (预测概率 - 正确标签) × 输入特征
= (0.5 - 1) × 1
= -0.5
梯度指示损失上升的方向,因此我们朝相反方向更新。学习率先取 0.2:
1
2
3
新权重 = 旧权重 - 学习率 × 梯度
= 0 - 0.2 × (-0.5)
= 0.1
用新权重重新预测:
1
2
3
新分数 = 0.1×1 = 0.1
新好评概率 ≈ 0.525
新损失 ≈ 0.644
好评概率从 0.500 升到 0.525,损失从 0.693 降到 0.644。模型已经朝正确方向移动了一小步。
这个例子的学习率使损失下降了;一般情况下,学习率过大仍可能让损失上升。真实训练会对很多样本重复这个过程。样本之间可能产生不同甚至相反的更新,模型需要找到整体上较好的参数,而不是记住这一条样本。
4.4 后面的模型仍然沿用这条训练链
后面你会遇到词向量、RNN 和 Transformer。内部计算越来越复杂,但训练的基本循环没有改变:
1
输入 → 预测 → 与答案比较 → 算损失 → 算梯度 → 更新参数
反向传播就是高效计算这条计算链上各个参数的梯度。它不会直接接收“你要理解否定句”这种人类解释,而是根据训练误差调整数字。
小练习:如果相同输入“好看”的标签换成差评,初始 p=0.5,梯度会是什么?
(0.5-0)×1=0.5。更新会减小权重,让好评概率下降。这也说明错误标注会把学习推向错误方向。
5. 给模型一点顺序:相邻词组与 N-gram
5.1 不只统计单个词,也统计相邻词组
对“电影 不 好看”,除了三个单词,还可以记录:
1
2
电影_不
不_好看
这叫二元词组,即 bigram。它可以让分类器单独学到“不_好看”与差评相关。
三个连续 token 组成 trigram。统称 N-gram,其中 N 表示一个连续片段包含多少个 token。
它比单词词袋保留了更多局部顺序,却仍不一定保留整句结构。
5.2 先尝试一个小小的“续写器”
暂时离开影评,收集下面三句话:
1
2
3
我 喝 茶
我 喝 茶
我 喝 水
看到“喝”后,你会猜接下来是什么?
训练文本里,“喝”有三次作为前一个词,其中两次后面是“茶”,一次是“水”。所以可以估计:
1
2
P(茶 | 喝) = 2÷3
P(水 | 喝) = 1÷3
P(茶 | 喝) 读作:“已经知道前面是‘喝’的条件下,接下来是‘茶’的概率。”竖线右边是条件。
这样就有了一个最简单的语言模型:它根据前文预测下一个 token。
5.3 为什么叫 bigram 语言模型
因为这里统计的是“前一个词 + 当前词”这两个词的组合。
因此,N-gram 语言模型一般用前 N-1 个 token 预测当前 token:bigram 看前一个;trigram 看前两个。
如果要给整句话打分,可以把每一步概率相乘:
1
2
3
4
句子概率 = 第一个词的概率
× 已知第一个词时第二个词的概率
× 已知前面词时第三个词的概率
× ……
上面的连乘来自概率链式法则;N-gram 另作一个近似,只保留最近的 N−1 个 token 作为条件。例如 bigram 将 \(P(w_t\mid w_{<t})\) 近似为 \(P(w_t\mid w_{t-1})\)。实际模型还会使用句首、句尾标记,明确句子如何开始和结束。4
5.4 没见过不等于不可能
上面的语料没有“喝 咖啡”。如果只按次数计算,它的概率就是零。
一种教学用办法是:给每个候选都先加一次。假设候选只有 [茶, 水, 咖啡]:
| 候选 | 原次数 | 加一次后 | 新概率 |
|---|---|---|---|
| 茶 | 2 | 3 | 3/6 |
| 水 | 1 | 2 | 2/6 |
| 咖啡 | 0 | 1 | 1/6 |
这叫平滑:为没见过的事件预留概率。这里的加一法便于手算,实际任务有更合适的平滑方法。4
5.5 为什么不能总是多记几个词
假设你想用完整前文“我今天下班之后特别想喝”来预测。很可能训练数据里一次都没出现过这段完整前文。
越长的组合越具体,也越难重复出现。词表大时,潜在组合数量增长得很快。
短窗口容易统计,但缺少远处信息;长窗口包含更多信息,但数据稀疏、存储增加。模型也难以把“喝茶”和“饮茶”之间的相似性共享起来。
让相似的词在数值表示上也有联系。
小练习:“加了 bigram 特征的分类器”和“bigram 语言模型”是一回事吗?
不是。前者把相邻词组当分类特征,输出类别;后者根据前一个 token 预测下一个 token 的概率。它们都用相邻词的统计,但任务不同。
6. 词向量:给每个词一张可以学习的“数值卡片”
6.1 为什么还需要另一种表示
假设分类训练里见过很多“好看”,很少见到“精彩”。词袋把这两个词放在完全不同的列,模型不会因为学好了“好看”就自动理解“精彩”。
我们希望每个词有一组可复用的特征。于是给每个词一排可学习的数字:
| 词 | 第一维 | 第二维 |
|---|---|---|
| 好看 | 1.0 | 0.0 |
| 精彩 | 0.8 | 0.2 |
| 难看 | -1.0 | 0.0 |
| 无聊 | -0.8 | 0.2 |
| 电影 | 0.0 | 1.0 |
这张表是人为构造的,特意让“好看”和“精彩”靠近,便于演示。真实训练不会预先规定第一维必须是褒贬,也不能保证反义词位于相反方向。
每个词对应的这一排数字称为 word embedding,词嵌入或词向量。
6.2 Embedding 层做的事情其实很具体
1
2
输入“好看”的编号 → 查表 → 取出 [1.0, 0.0]
输入“电影”的编号 → 查表 → 取出 [0.0, 1.0]
整张表可以看作一个矩阵:矩阵只是多排等长向量叠起来。
如果有 10,000 个词,每个词用 128 个数表示,那么这张表的形状是:
1
10,000 行 × 128 列
一条有 20 个 token 的句子查表后,就得到:
1
20 行 × 128 列
这里有两个容易混淆的数字:20 是句子长度,128 是每个 token 的特征数量。
6.3 为什么有的教程先讲 one-hot
如果词表有五个词,“好看”位于第一项,那么 one-hot 表示就是:
1
[1, 0, 0, 0, 0]
只有对应位置为 1,其余为 0。拿它与 embedding 表做适当的矩阵乘法,结果恰好就是选中对应行。
所以,one-hot 乘 embedding 表与按编号查行,在数学上等价。实际程序通常直接查表,避免构造一大串零。
6.4 把词向量合成一句话,先试最简单的平均
“电影 好看”有两个词向量:
1
2
电影:[0.0, 1.0]
好看:[1.0, 0.0]
对应维度相加,再除以词数:
1
句子向量 = [(0+1)/2, (1+0)/2] = [0.5, 0.5]
若分类器权重是 [2, 0],不使用偏置,则:
1
2
得分 = 2×0.5 + 0×0.5 = 1
好评概率 = sigmoid(1) ≈ 0.731
“电影 精彩”的平均向量是 [0.4, 0.6],得分 0.8,好评概率约 0.690。
由于两个褒义词的向量相近,同一套分类权重可以对它们产生相似判断。这说明词向量如何帮助共享特征。
6.5 这些数字可以在影评训练时一起更新
第四章介绍的训练过程不仅能更新最后的分类权重,也能通过反向传播更新查到的词向量。
1
2
3
评论 → 查词向量 → 求平均 → 分类 → 损失
↑ |
└──── 梯度更新 ──────┘
如果“好看”所在的很多正面评论都判断错了,训练就会逐步调整相关数字,帮助降低整体损失。
这叫端到端学习:多个可学习部分围绕同一个任务目标一起调整。
6.6 得到了什么,还没有得到什么
得到了:每个词可以用较少的连续特征表示;相似词有机会共享信息;表示可以为任务调整。
没有自动得到:语序、多义词消歧、可靠的句子理解。
平均值不依赖顺序,所以“我喜欢你”和“你喜欢我”的平均词向量还是相同。
另外,静态词表中的“苹果”在“吃苹果”和“苹果发布手机”里查出的初始向量相同。后面还需要上下文加工。
小练习:词向量有 128 维,是否表示研究者提前列出了 128 种语言属性?
通常不是。每一维是训练得到的数值特征,往往无法单独命名;信息可能分散在多个维度中。
7. 不标注好评差评,也能训练词向量吗:Word2Vec
7.1 从原文中制造一道填空题
假设有一句:
1
我 喜欢 看 电影
把“看”作为中心词,左右各取一个词:
1
2
输入:[喜欢, 电影]
答案:看
答案本来就在原文里,不需要请人额外标注。这就是从文本自身构造监督信号。
CBOW 采用“周围词预测中心词”的训练方向。
7.2 用小数字完整走一次预测
为了缩小计算,本例只允许三个输出候选:[看, 吃, 买]。
假设输入词向量是:
1
2
3
喜欢:[1, 0]
电影:[0, 1]
平均:[0.5, 0.5]
再准备一张输出侧向量表:
1
2
3
看:[2, 2]
吃:[0, 0]
买:[1, 1]
每个候选与平均向量做点积。点积就是对应位置相乘再相加。
1
2
3
看的得分:0.5×2 + 0.5×2 = 2
吃的得分:0.5×0 + 0.5×0 = 0
买的得分:0.5×1 + 0.5×1 = 1
这些分数还不是概率:它们没有保证为正,也没有保证加起来等于 1。
7.3 Softmax:把多个候选分数变成一组概率
步骤只有两个:先对每个分数计算指数,再除以所有指数的和。
| 候选 | 原分数 | 指数 | 除以总和 11.107 |
|---|---|---|---|
| 看 | 2 | 7.389 | 0.665 |
| 吃 | 0 | 1.000 | 0.090 |
| 买 | 1 | 2.718 | 0.245 |
三个概率相加为 1。正确答案是“看”,损失为:
1
-ln(0.665241…) ≈ 0.408
训练会调整输入词向量和输出侧向量,让大量真实上下文中的正确答案概率变高。
这里有两张向量表,是因为“作为输入提供特征”和“作为输出候选接受打分”承担不同角色。它们可以分别学习,不必相同。
7.4 还能手算一次词向量更新
沿用上面的例子。三个输出分数的梯度约为:
1
2
3
看:0.665 - 1 = -0.335
吃:0.090 - 0 = 0.090
买:0.245 - 0 = 0.245
平均输入向量收到的梯度,由这些数乘对应输出向量后相加:
1
2
(-0.335)×[2,2] + 0.090×[0,0] + 0.245×[1,1]
≈ [-0.425, -0.425]
平均操作把梯度平分给两个输入词,每个约得到 [-0.212,-0.212]。
学习率取 0.1,只展示输入侧更新:
1
2
喜欢:[1,0] - 0.1×[-0.212,-0.212] ≈ [1.021,0.021]
电影:[0,1] - 0.1×[-0.212,-0.212] ≈ [0.021,1.021]
这就是“词向量被学出来”的一次具体操作。完整训练通常还会更新输出侧;这里暂时固定输出侧,是为了单独观察输入向量如何变化。
7.5 Skip-gram 把预测方向倒过来
仍取“看”为中心,左右各一个词:
1
2
看 → 喜欢
看 → 电影
CBOW 是周围词预测中心词;Skip-gram 是中心词预测周围词。二者都是 Word2Vec 家族中的方法。5
如果“猫”和“狗”经常出现在相似语境中,它们会受到相似的训练压力,有机会形成相近的表示。但“好”和“坏”也会进入相似句式,因此相似向量不一定意味着同义。
7.6 词表太大时,为什么采用负采样
刚才只有三个候选,算三个分数即可。如果有 100,000 个候选,每个训练样本都完整打分就很贵。
负采样把任务改成:
1
2
真实附近词对:(看,电影)→ 应该判为真实共现
随机噪声词对:(看,香蕉)→ 应该判为抽样噪声
“噪声”只是这次抽样构造的标签,不表示这两个词在所有句子里绝不可能一起出现。
假设真实词对得分为 2,噪声词对得分为 -1:
1
2
3
真实词对被判为数据的概率:sigmoid(2) ≈ 0.881
噪声词对被判为噪声的概率:1-sigmoid(-1) = sigmoid(1) ≈ 0.731
合计损失:-ln(0.881)-ln(0.731) ≈ 0.440
我们只计算一个真实词对和少量噪声词对,而不是全部候选。但训练目标随之改变了:它主要学习区分词对,不直接提供全词表归一化的下一词概率。6
同样的上下文窗口下,CBOW 合并周围词后预测一次中心词,Skip-gram 则形成多个“中心词—周围词”训练对;实际速度与表示质量还取决于窗口、采样、语料和实现,不能仅凭名字判断。负采样减少打分量,同时改变目标。静态词向量仍需要后续模型结合当前句子消除歧义。56
小练习:Word2Vec 的训练答案来自哪里?需要每个词的人工语义标签吗?
答案来自原文中的邻近词或中心词,不需要人工给每个词标注含义。它从预测任务中学习可复用表示。
8. 词都认识了,为什么还是看不懂句子:RNN
8.1 平均词向量不记顺序
假设这两句话:
1
2
猫 追 狗
狗 追 猫
它们含有相同的词,因此词向量相加、求平均都会得到相同结果。
问题不是“猫”“狗”的词向量还没训练好,而是求和操作本来就不在乎顺序。
我们需要一种读完一个词之后,会更新记录、再接着读下一个词的计算。
8.2 先造一个极其简单的记事本
为了手算,假设词的特征只有一个数字:
1
2
3
猫:1
追:0
狗:2
这些是特征值,不是前面词表里的编号。数值仅用于演示顺序计算,不代表真实语义。
我们规定更新方式:
1
新记录 = 旧记录×0.5 + 当前词的特征
初始记录为 0。读“猫追狗”:
| 读到的词 | 旧记录 | 更新过程 | 新记录 |
|---|---|---|---|
| 猫 | 0 | 0×0.5+1 | 1 |
| 追 | 1 | 1×0.5+0 | 0.5 |
| 狗 | 0.5 | 0.5×0.5+2 | 2.25 |
读“狗追猫”:
| 读到的词 | 旧记录 | 更新过程 | 新记录 |
|---|---|---|---|
| 狗 | 0 | 0×0.5+2 | 2 |
| 追 | 2 | 2×0.5+0 | 1 |
| 猫 | 1 | 1×0.5+1 | 1.5 |
这次最终记录不同了:2.25 和 1.5。后面的模型终于有机会区分这两句话。
注意,这个小程序只说明“递归更新能够保留顺序差异”,不说明它已经懂得主语和宾语。
8.3 真正的 RNN 把一格记录扩展成很多格
真实模型的记录是一整个向量,叫隐藏状态 hidden state。例如每读一个词,就更新 128 个数。
1
初始状态 → 读“猫” → 状态1 → 读“追” → 状态2 → 读“狗” → 状态3
每一步使用同一套更新参数,而不是为每个位置单独建一套模型。
普通 RNN 的一种公式是:
\[h_t=\tanh(W_xx_t+W_hh_{t-1}+b)\]逐项读:
| 符号 | 含义 |
|---|---|
x_t | 第 t 个 token 的向量 |
h_(t-1) | 上一步记事本里的向量 |
W_x x_t | 把当前词加工成适合写入记录的信息 |
W_h h_(t-1) | 对已有记录进行加工 |
b | 偏置 |
tanh | 一个非线性函数,把每个结果压到 -1 到 1 之间 |
h_t | 更新后的记录 |
前面的手算例子省略了 tanh,并把矩阵缩成标量。它是结构演示,不是一个完整的标准 RNN 单元。
8.4 用这本记事本完成影评分类
1
2
3
4
5
电影 不 好看
↓ 按顺序查词向量、更新状态
最终状态
↓ 分类器
好评概率
训练仍然使用人工好评差评标签,仍然是第四章的预测、损失、梯度更新。只是梯度现在还要穿过多个时间步,调整状态更新参数和词向量。
8.5 普通 RNN 为什么容易忘记较早的信息
回到更新式“旧记录×0.5 + 新信息”。一个词的信息经过后面十次更新,就会被乘以:
1
0.5 的 10 次方 ≈ 0.000977
经过二十次:
1
0.5 的 20 次方 ≈ 0.000000954
早期信息的影响变得很小。反向学习时,也可能发生类似的连续缩小,叫梯度消失。若连续放大,则可能出现梯度爆炸。
真实 RNN 由矩阵和非线性函数决定,不是永远乘以 0.5;这个例子只用于说明“跨很多步连续相乘”的困难。
RNN 可以处理变长、有顺序的输入;流式推理时可保存固定大小状态。但每一步依赖上一步,时间维度上的计算难以全部同时进行,长期信息也容易在状态更新中丢失。7
小练习:RNN 处理 100 个词,需要为每个词准备不同的一套更新权重吗?
不需要。所有时间步共享更新权重,但每一步的输入和状态不同。
9. 让记事本学会保留和擦除:LSTM 与 GRU
9.1 不要每读一个词都以同样方式修改所有记录
想象一句较长的影评:
1
我不喜欢这部虽然演员很好、场景也很漂亮、但剧情混乱的电影。
前面的否定关系与后面的对象相隔很远。我们希望模型有能力保留重要信息,并选择性地加入新内容。
LSTM 增加了一份记忆状态,以及控制“保留多少、写入多少、输出多少”的门。
门 gate 在这里就是一组 0—1 之间的数。0 表示挡住,1 表示完全放行,中间值表示保留一定比例。它们也由网络根据输入和历史状态算出来,不是人写的 if 规则。
9.2 只看记忆中的一格
假设某一格原来是 0.8,这次模型算出:
1
2
3
旧记忆保留比例:0.9
新候选内容:0.5
新内容写入比例:0.2
那么:
1
新记忆 = 0.8×0.9 + 0.5×0.2 = 0.82
在理想化的门值下,保留比例为 1、写入比例为 0,旧记忆就原样保留;保留比例为 0,则旧内容被清除。实际 sigmoid 在有限实数输入下输出介于 0 和 1 之间的值,这里用端点说明极限情况。
再假设输出门为 0.7:
1
对外输出 = 0.7×tanh(0.82) ≈ 0.473
这里“记忆里保存什么”和“当前输出什么”已经可以有所区别。
9.3 再看公式,就能对应每一部分
\[c_t=f_t\odot c_{t-1}+i_t\odot g_t\] \[h_t=o_t\odot\tanh(c_t)\]| 符号 | 刚才例子中的值 | 含义 |
|---|---|---|
c_(t-1) | 0.8 | 旧记忆 |
f_t | 0.9 | 遗忘门:旧记忆保留多少 |
g_t | 0.5 | 新候选内容 |
i_t | 0.2 | 输入门:新内容写入多少 |
o_t | 0.7 | 输出门:当前暴露多少内容 |
c_t | 0.82 | 新记忆 |
h_t | 约 0.473 | 当前隐藏输出 |
符号 ⊙ 表示对应格子相乘。例如 [1,2] ⊙ [0.5,0.1] = [0.5,0.2]。
这些格子是可学习特征,不能默认某一格就叫“否定”。模型可能把一种语言关系分散表示在很多格中。
9.4 GRU 与双向模型分别改变什么
GRU 的全称是 Gated Recurrent Unit。它采用另一套较简化的门控更新方式,不像 LSTM 那样单独保留相同形式的两种状态。
一般来说,相同隐藏维度的 GRU 门控结构更少;效果是否更好需要实验。LSTM 和 GRU 都仍有逐步递归依赖。
如果已经拿到了完整句子,还可以从左往右读一遍、从右往左读一遍,再把两个方向的状态拼接。这叫双向 RNN 或双向 LSTM。
例如理解“苹果”时,右边是“很好吃”还是“发布手机”很有帮助。双向模型可以利用两侧信息。
但在一步一步续写时,未来词还不存在,不能把真实未来答案交给生成模型。
门控改善信息保留和梯度传播,但增加参数与计算,也不保证无限记忆;双向计算利用更多上下文,但要求这些上下文已经可用。7
小练习:LSTM 记忆更新里,若遗忘门为 1、输入门为 0,会怎样?
记忆状态原样保留。隐藏输出仍可能因输出门变化而不同。
10. 另一条处理顺序的路线:TextCNN 先寻找局部短语
10.1 不一定要逐词记忆,也可以滑动一个窗口
对句子:
1
这部 电影 不 太 好看
宽度为 3 的窗口依次看到:
1
2
3
[这部, 电影, 不]
[电影, 不, 太]
[不, 太, 好看]
模型可以学习一套在每个窗口都重复使用的打分规则,检测类似“不太好看”的局部模式。这就是文本卷积的一种直觉。
10.2 用一个小窗口手算
为了说明顺序,暂时用标量特征:
1
不:-1;太:0.5;好看:1
一个宽度为 3 的卷积核权重为 [2,1,1],不使用偏置:
1
2
原窗口 [不,太,好看]:2×(-1)+1×0.5+1×1 = -0.5
倒序 [好看,太,不]:2×1+1×0.5+1×(-1) = 1.5
因为窗口中不同位置使用不同权重,改变顺序会改变分数。真实模型使用多维词向量、多个卷积核和非线性函数。
常见分类方案还会取各窗口结果的最大值,表示“整句中有没有强烈出现某种模式”。这叫 max pooling,最大池化。
同一层的不同窗口可以并行计算,适合局部模式;远距离关系需要更大的感受野,例如堆叠多层、使用扩张卷积。最大池化还会弱化“具体出现在什么位置”的信息。8
小练习:窗口宽度只有 3 的单层卷积,能让相距 20 个词的两个位置在同一个局部窗口内直接交互吗?
不能。需要改变结构扩大感受野,或采用其他全局信息交互方式。
11. 从判断句子到写句子:语言模型如何训练与生成
11.1 把分类的选项从“好评差评”换成“词表中的 token”
回到续写任务。输入:
1
我 喜欢
模型输出:
| 下一个 token | 概率 |
|---|---|
| 电影 | 0.6 |
| 音乐 | 0.3 |
| 香蕉 | 0.1 |
这与第七章 softmax 的形式一样。区别是这里依据前文预测下一个 token,预测器可以由 N-gram、RNN、LSTM 或后面的 Transformer 实现。
语言模型是一类任务或概率模型,不专指 Transformer。
11.2 训练答案怎样准备
原文只有一句:
1
我 喜欢 电影
加上句首 <BOS> 和句尾 <EOS> 后,可以得到四次预测:
| 已知内容 | 正确的下一个 token |
|---|---|
<BOS> | 我 |
<BOS> 我 | 喜欢 |
<BOS> 我 喜欢 | 电影 |
<BOS> 我 喜欢 电影 | <EOS> |
在程序里,经常把输入序列和目标序列错开一位:
1
2
输入:[<BOS>, 我, 喜欢, 电影]
目标:[我, 喜欢, 电影, <EOS>]
每个位置都有正确答案,不需要人逐个出题。这叫自监督训练:监督信号由数据本身构造。
11.3 训练时给真实前文,生成时用自己刚写的前文
训练通常会给出真实历史,例如预测“电影”时,输入的“我喜欢”来自原文。这叫 teacher forcing。
生成时执行:
1
2
3
4
5
1. 输入“我”。
2. 预测并选择“喜欢”。
3. 把“喜欢”接到输入后面,得到“我喜欢”。
4. 再预测并选择“电影”。
5. 继续,直到出现结束标记或达到长度限制。
因此,生成早期如果选错词,后面的输入也会受到影响。训练和实际生成所遇到的前缀可能不同,这种差异常被称为 exposure bias。7
11.4 同一组概率,可以有不同的选词方式
对 [电影:0.6, 音乐:0.3, 香蕉:0.1]:
| 方法 | 这一步怎么选 | 需要知道的取舍 |
|---|---|---|
| 贪心 greedy | 直接选“电影” | 简单稳定,但逐步选最高不保证整句最好 |
| 随机采样 | 按 0.6、0.3、0.1 的概率抽取 | 同一输入可有不同输出,也可能抽到不合适的词 |
| top-k,k=2 | 保留电影和音乐,重新归一化为 2/3、1/3 | 固定保留候选数量 |
| top-p,p=0.8 | 从大到小取到累计概率至少 0.8,此处取电影和音乐 | 保留数量取决于分布形状 |
| beam search | 继续探索多条候选前缀,保留若干高分路径 | 增加推理工作;还要处理长度等因素 |
Temperature,温度,会改变概率分布的尖锐程度。可以直观理解为:较低温度更偏向原来高分的候选,较高温度更愿意考虑其他候选。
对于温度 \(T>0\),先把 softmax 之前的分数除以 T,再计算概率:
\[p_i(T)=\frac{\exp(z_i/T)}{\sum_j\exp(z_j/T)}\]\(T=1\) 保持原分布;\(T\to0^+\) 时概率集中到最高分候选,并列最高分需要另行处理。有些接口把 temperature=0 约定为贪心解码,不能直接把 0 代入上式。温度不会自动增加知识,也不会把错误事实变正确。9
11.5 用困惑度看模型给真实文本分配了多少概率
假设三个真实下一词分别得到概率:
1
0.5、0.25、0.125
先算每个损失,再取平均:
1
2
3
损失:[0.693, 1.386, 2.079]
平均损失:1.386
困惑度:exp(1.386294…) = 4
exp 是指数函数,它与 ln 互为逆运算。困惑度也叫 perplexity,简称 PPL。
如果每一步都在四个候选间完全均匀分配概率,真实词概率都是 1/4,也会得到 PPL=4。这是一个直觉类比,不表示上面的模型每一步真的只有四个候选。
困惑度低说明真实文本在模型看来更可预测,不直接说明回答事实正确。比较不同模型时还要保持测试内容、分词和计分方式可比。4
小练习:模型在“我喜欢”后给“电影”0.6 的概率,是不是表示它必须选“电影”?
不是。还取决于解码方法。贪心会选它,随机采样可能选其他候选。
12. 翻译长句为什么困难:先认识 Attention,再认识 Q、K、V
12.1 翻译要同时处理输入句和输出句
输入:
1
我 喜欢 猫
希望输出:
1
I like cats
早期的一种做法是分成两部分:
1
输入句 → 编码器 encoder → 一个最终记录 → 解码器 decoder → 输出句
编码器和解码器都可以用 RNN/LSTM。编码器负责读,解码器根据读到的内容和已生成词继续写。
这种输入序列到输出序列的任务常称为 sequence-to-sequence,简称 Seq2Seq。输入输出长度不需要相同。
12.2 整段输入只留一个最终记录,会丢细节
短句可能够用;长句里的人名、时间、否定和修饰关系都要压进固定长度向量,容易出现遗漏。
一个改善方法是:保留编码器每一步的状态。生成每个输出词时,再从这些状态中选择性地读取信息。
这就是这里的 Attention,注意力。它是一套计算权重并加权组合信息的方法,不是模型产生了人类式的主观注意。
12.3 先假设模型已经算出三个相关性分数
假设正在生成 cats。对于三个输入位置,模型算出:
| 输入位置 | 与当前生成步骤的相关性分数 |
|---|---|
| 我 | 0 |
| 喜欢 | 1 |
| 猫 | 2 |
分数如何算,下一章再解释。这里先关注分数之后发生什么。
经过第七章学过的 softmax:
| 输入位置 | 指数 | 注意力权重 |
|---|---|---|
| 我 | 1.000 | 0.090 |
| 喜欢 | 2.718 | 0.245 |
| 猫 | 7.389 | 0.665 |
这次读取“猫”位置的比例最大。
12.4 按比例合成一个当前可用的上下文
假设三个位置保存的内容向量是:
1
2
3
我:[1,0]
喜欢:[0,1]
猫:[1,1]
按上面的权重相乘再相加:
1
2
3
当前上下文
= 0.090×[1,0] + 0.245×[0,1] + 0.665×[1,1]
= [0.755,0.910]
解码器将这个上下文与自己的当前状态结合,再预测输出 token。
注意力结果是一个向量,不是直接输出“cats”。预测词的输出层仍然需要存在。
下次生成另一个词时,可以重新算权重、得到不同上下文。模型不必每一步都依赖同一份压缩记录。
12.5 训练会自动调整对齐方式
通常只需要提供“输入句—正确翻译”样本。生成损失可以通过这些计算反向传播,调整打分网络、编码器和解码器。
不一定要人工逐词标注“cats 对应 猫”。注意力提供了一条可以学习软对齐的路径。10
模型可以反复访问输入的不同位置,减轻单个向量的瓶颈;但必须保存更多状态,并为输入与输出位置计算匹配分数。注意力权重也不能单独证明模型得出预测的原因;高权重和对最终预测的因果贡献不是同一个量。11
小练习:为什么生成“我”和生成“猫”对应的英文时,不应强制使用相同的输入权重?
不同输出步骤需要不同输入信息。注意力允许每一步重新决定如何组合输入状态。
13. Transformer 的核心计算:一句话里的词互相读取信息
13.1 从“输出读输入”,到“输入内部互相读”
上一章是翻译过程中,输出侧根据需要读取输入侧。这种两组表示之间的注意力叫交叉注意力,cross-attention。
现在考虑一句话:
1
小王把书递给小李,因为他想让小李看看。
我们希望“他”这个位置的表示能够结合其他词,获得关于它所指对象的线索。
让同一句话里的每个位置都可以按权重读取其他位置,叫 self-attention,自注意力。这提供了利用上下文的能力,不保证模型每次都能正确消除歧义。
1
2
3
原始词向量:这个 token 通常怎么表示
↓ 读取当前句子中其他位置的信息
上下文化表示:这个 token 在这句话里怎么表示
13.2 Q、K、V 分别在解决哪一步
上一章把“相关性分数已经算好”当作前提。现在解释一种具体的打分方式。
每个位置的输入向量经过三种加工,得到:
| 名称 | 中文 | 在计算里承担什么工作 |
|---|---|---|
| Q:Query | 查询向量 | 用来与各位置匹配 |
| K:Key | 键向量 | 用来接受匹配打分 |
| V:Value | 值向量 | 用来在匹配之后被加权读取 |
“查询”和“键”的匹配决定权重,“值”决定实际汇总的内容。
在这里的自注意力中,三者通过各自的可学习矩阵从同一组输入变换出来:Q=XW_Q、K=XW_K、V=XW_V。交叉注意力则由读取侧生成 Q,被读取侧生成 K 和 V。12
所谓矩阵变换,可以理解为给原来的每一维特征不同权重,再组合成新特征。这里没有人工给每个词写“我正在找主语”的文字指令。
13.3 从输入表开始,把一次自注意力算到底
为避免语义猜测,只用三个位置 A、B、C。每个位置有两个特征:
| 位置 | 输入向量 X |
|---|---|
| A | [1,0] |
| B | [0,1] |
| C | [1,1] |
为了少算几步,让 Q 和 K 暂时保持输入不变,让 V 把第二维乘 2:
1
2
W_Q = W_K = [[1,0], [0,1]]
W_V = [[1,0], [0,2]]
得到:
| 位置 | Q | K | V |
|---|---|---|---|
| A | [1,0] | [1,0] | [1,0] |
| B | [0,1] | [0,1] | [0,2] |
| C | [1,1] | [1,1] | [1,2] |
实际模型的三套矩阵通常不同,这里只是选了方便手算的参数。
第一步:只看位置 A,把它的 Q 与每个 K 做点积。
1
2
3
A 对 A:1×1+0×0 = 1
A 对 B:1×0+0×1 = 0
A 对 C:1×1+0×1 = 1
第二步:缩放分数。
每个 Q/K 有两个数,所以除以 √2≈1.414:
1
[1,0,1] ÷ 1.414 ≈ [0.707,0,0.707]
这样做是为了控制点积的尺度。在常见的统计假设下,维度增加会使点积分数的波动变大,除以维度的平方根能缓和这个问题。
第三步:softmax。
1
2
3
指数:[2.028,1,2.028]
总和:5.056
权重:[0.401,0.198,0.401]
第四步:按权重组合 V。
1
2
3
A 的新表示
= 0.401×[1,0] + 0.198×[0,2] + 0.401×[1,2]
≈ [0.802,1.198]
位置 A 从原来的 [1,0] 变成了包含其他位置信息的结果。位置 B、C 也各自重复同样的过程,各自有自己的权重。
将所有位置的计算放在一起,就是常见公式:
\[\operatorname{Attention}(Q,K,V) =\operatorname{softmax}(QK^\top/\sqrt{d_k})V\]若有 n 个输入位置,则 \(Q,K\in\mathbb{R}^{n\times d_k}\)、\(V\in\mathbb{R}^{n\times d_v}\),输出形状为 \(n\times d_v\)。这个结果还没有包含多头拼接、输出投影和残差连接。
此时公式的每一步都有对应:QK^T 一次性计算点积表,除以平方根缩放,softmax 将每行变成权重,最后乘 V 完成加权求和。12
13.4 为什么注意力表是“句子长度 × 句子长度”
本例有三个读取位置,每个位置都比较三个候选,所以产生 3×3 的分数表:
1
2
3
4
读取 A 读取 B 读取 C
位置 A · · ·
位置 B · · ·
位置 C · · ·
句子长 100 个 token,就有 100×100 个匹配分数。这是后面二次成本的来源。
13.5 生成时必须防止偷看答案:causal mask
训练序列为:
1
2
输入:我 喜欢 电影
目标:喜欢 电影 <EOS>
计算第一个位置时,如果允许直接看到第二个输入词“喜欢”,模型就可以偷看答案。
所以自回归模型使用因果遮罩,规定一个位置只能读取自己和前面的位置:
| 读取者 | 我 | 喜欢 | 电影 |
|---|---|---|---|
| 我 | 可读 | 禁止 | 禁止 |
| 喜欢 | 可读 | 可读 | 禁止 |
| 电影 | 可读 | 可读 | 可读 |
被禁止的分数在 softmax 前设为负无穷,使其权重变成 0。
回到 A、B、C 的例子,若 A 是第一个位置:
1
2
3
4
原分数:[0.707,0,0.707]
遮罩后:[0.707,-∞,-∞]
权重:[1,0,0]
输出:[1,0]
另外一种 padding mask 用来忽略补齐长度的占位符。它和防止看未来的 causal mask 目的不同,不能混为一谈。
13.6 还需要知道词序:位置编码
如果只做无位置特征、无额外结构限制的全自注意力,把输入行交换,输出也相应交换;它没有从中获得“原来谁在前谁在后”的额外线索。
一种办法是给每个位置一个向量,再与 token 向量相加:
1
进入网络的表示 = token 向量 + 位置向量
于是同一个词出现在第一位和第五位,会有不同输入。
原始 Transformer 使用正弦、余弦位置编码,也比较过可学习的位置嵌入。12 RoPE 则通过对 Q/K 做依位置变化的旋转,让点积体现相对位置信息;它不是简单地给输入加一个位置向量。13
13.7 多头注意力:并行做几套不同的匹配
一套 Q/K/V 只提供一套匹配方式。多头注意力让模型学习多套投影,各自计算注意力,再把结果拼接、加工。
例如总维度是 128,分成 4 个 head,每个 head 可以使用 32 维。不是必然为每个 head 都再准备完整的 128 维。
多个 head 有机会关注不同关系,但不保证“第一头专门学主语,第二头专门学否定”。这种功能不是人工指定的。
13.8 完整 Transformer 层还包括什么
一个常见层的示意流程是:
1
2
3
4
5
6
7
8
9
输入
↓ 归一化
多头注意力:在不同位置之间交换信息
↓ 与原输入相加(残差)
中间表示
↓ 归一化
前馈网络 FFN:加工每个位置自己的特征
↓ 与中间表示相加(残差)
输出,交给下一层
三项组件分别解决不同需要:
| 组件 | 具体含义 |
|---|---|
| FFN | 每个位置经过小型神经网络,例如 128 维→512 维→128 维;中间有非线性函数 |
| 残差连接 | 把加工结果加回原表示,让网络容易保留原信息、学习增量变化 |
| LayerNorm | 对当前 token 的特征做归一化,再使用可学习缩放与偏移,帮助稳定训练 |
例如 [1,3] 的均值是 2,减去均值后是 [-1,1]。这只是归一化中的一个步骤;完整 LayerNorm 还涉及方差、数值稳定项和可学习参数。
不同 Transformer 变体在归一化位置、激活函数和位置处理上会有差别。此处展示 Pre-LN 布局;Vaswani 等人的原始 Transformer 使用的是子层残差相加后再归一化的 Post-LN 布局。1214 这张图只画一个自注意力块,编码器—解码器模型的解码层还包含交叉注意力。
堆叠多层之后,模型可以反复交换与加工信息。最后按任务接分类层或词表预测层。
小练习:Attention 得到向量 [0.802,1.198] 后,是否已经输出了一个词?
没有。它产生的是新的特征表示,还需要后续层和输出头计算类别或 token 的概率。
14. “更强”要付多少钱:从数格子理解计算与显存
14.1 先分清三种成本
| 成本 | 好理解的比喻 | 在模型里是什么 |
|---|---|---|
| 参数存储 | 保存多少张固定表格 | 词向量表、各种权重矩阵 |
| 中间结果存储 | 计算过程需要多少草稿纸 | 激活、注意力矩阵、KV cache 等 |
| 运算量与运行时间 | 做多少次计算、如何安排工人 | 乘加运算、并行度、内存读写等 |
一句话从 100 个词变成 200 个词,模型参数可以不变,但草稿纸和运算会增加。
14.2 自注意力的二次增长,可以直接数出来
| token 数量 | 每个位置比较多少个位置 | 匹配分数总数 |
|---|---|---|
| 4 | 4 | 16 |
| 8 | 8 | 64 |
| 16 | 16 | 256 |
长度翻倍,表格面积变为四倍。这就是标准全自注意力里 n² 的来源。
如果一句话长 4,096 个 token,一个 head 的表格有:
1
4,096×4,096 = 16,777,216 个元素
假设每个元素占 2 字节,显式保存这一张表就需要 32 MiB。32 个 head 合计 1 GiB。
这只计算一份表、不含 batch、其他层、梯度或别的中间结果。高效实现也不一定把整张表放入显存。
14.3 词表和特征维度也会增加成本
词表有 10,000 项,每个 token 128 维,则词向量表有 1,280,000 个数。每个数用 4 字节,约为 5.12 MB。
词表翻倍、维度不变,表格大小翻倍;维度也翻倍,则变成原来的四倍。
因此,更细的分词未必更便宜:词表可能变小,但输入可能更长;输入更长又会增加注意力工作。
14.4 为什么 Transformer 容易训练并行,生成却仍逐步进行
RNN 的第三个状态需要第二个状态,第二个状态需要第一个状态。存在时间步依赖。
Transformer 训练时完整输入已知。对同一层,可以同时计算多个位置,用 mask 保证不能看未来。不同层之间仍要逐层进行,并非整个模型只有一次计算。
自回归生成时,下一步输入要使用本步实际选出的 token。因此:
1
2
训练:答案序列已知,多个位置可以一起算。
生成:新 token 尚未选定,通常一步步向后写。
并行程度更高不等于数学运算一定更少,但常能更好地利用 GPU。
14.5 KV cache:把已经算过的内容保存下来
假设已经读完“我喜欢电影”,现在生成下一个 token。
在标准因果 Transformer 中,之前位置不会看到后来的词,因此这些位置已经计算出的 K/V 可以保留。生成新 token 时,计算新的 Q/K/V,再让新 Q 读取已有缓存。15
1
2
3
旧前缀:已有 K/V → 直接复用
新 token:计算新 K/V → 加入缓存
新 Q:与历史 K 做匹配,读取历史 V
这避免重复加工整个旧前缀,但历史缓存要占显存,而且新 token 通常仍要读取历史 K/V。使用缓存不代表每个生成步骤的全部成本都固定不变。
一种常见缓存大小估算是:
1
2
2 × 同时处理的序列数 × 层数 × 已缓存 token 数
× KV head 数 × 每个 head 的维度 × 每个数的字节数
前面的 2 表示 K 和 V 两份。这个式子假设各层维度相同、K 与 V 等宽、每条序列缓存长度相同;估算的是缓存张量的数据量,不含预分配空位、分页元数据、量化尺度等实现开销。
假设单条序列、32 层、8,192 个 token、8 个 KV heads、每头 128 维、每个数 2 字节:
1
缓存 = 2×1×32×8192×8×128×2 字节 = 1 GiB
这仍不包括模型权重和其他开销。这里要使用 KV head 数,不能一律代入 Query head 数:普通多头注意力两者相同,GQA/MQA 会让多个 Query heads 共享 K/V。16
14.6 常见优化到底省了什么
| 方法 | 主要动作 | 得到什么 | 付出或限制 |
|---|---|---|---|
| KV cache | 保存旧位置的 K/V | 少做前缀重复计算 | 缓存随长度增长 |
| GQA/MQA 16 | 多个 Query heads 共享较少 KV heads | 减少缓存与读取量 | 改变模型参数化和表示分配 |
| FlashAttention | 分块、融合计算,减少显存来回读写 | 避免显式保存完整注意力表,改善效率 | 全注意力的算术量仍随长度平方增长 |
| 量化 17 | 用更少位数表示部分数值 | 减少存储,某些硬件上可加速 | 存在数值误差,加速依赖实现 |
| 缩短输入 | 删除、截断或先检索再输入 | 直接减少处理长度 | 可能丢掉关键内容 |
FlashAttention 保持精确注意力的数学形式,主要改善计算组织和存储访问,不是把注意力替换成低秩近似。“精确”指算法没有对注意力作近似,不代表不同浮点实现的结果逐位相同。18
若关心复杂度,先限定为单条序列、单层、整段前向计算,并假设输入与隐藏维度同阶,FFN 中间维度与隐藏维度 d 成固定比例。长度为 n 时,普通稠密 RNN 约为 \(O(nd^2)\),标准 Transformer 层约为 \(O(nd^2+n^2d)\)。前一项包括投影和 FFN,后一项来自位置之间的交互;这里不计最后的全词表输出层。12
这不是使用 KV cache 后单步解码的成本。缓存长度为 n 时,单个新 token 在一层中的投影和 FFN 约为 \(O(d^2)\),读取完整历史的注意力约为 \(O(nd)\)。这些估算描述规模增长趋势,不给出实际耗时。15
参数量本身也要区分公开信息和猜测。例如《GPT-4 Technical Report》第 2 节明确说明没有披露模型大小,不能据此给 GPT-4 标上“100T 参数”等确定数字。19
小练习:输入长度翻倍,标准全注意力的匹配表有多大?模型权重也必须翻倍吗?
表格元素数变为四倍;模型权重不必变化。参数与运行时中间结果是不同的东西。
15. BERT、GPT、T5:同样使用 Transformer,训练题目却不同
15.1 为什么预训练能帮助新任务
如果只有几百条标注影评,从头训练一个复杂网络很容易学不好。
可以先让网络在大量原始文本上做自动构造的预测题,形成通用表示,再用影评标签进一步训练。
1
2
大量原始文本 → 预训练 → 已有语言相关表示
少量影评标签 → 任务适配 → 好评差评分类器
这种复用已有模型的做法属于迁移学习。预训练不是直接给模型逐条灌输词典定义,而是让它从训练任务中调整参数。
15.2 BERT:给你前后文,补出被遮住的部分
一个示意题目:
1
2
输入:这部电影非常 [MASK],我想再看一次。
答案:精彩
BERT 可以读取空白两侧的内容,因此右侧“想再看一次”也提供线索。这叫掩码语言建模,masked language modeling。
真实预训练不只使用这一种字面替换方式;原始 BERT 还有下一句预测任务。先理解“两侧上下文共同帮助恢复被选中 token”即可。20
拿它做影评分类时,可以在 [CLS] 等整句表示后接分类层;做实体识别时,可以在每个位置的表示后接标签预测层。
15.3 GPT 类因果语言模型:给你前文,接着写
题目变成:
1
2
输入:这部电影非常
答案:精彩
这里讨论 GPT 类模型的因果文本语言建模目标:用已知前文预测下一个 token。9生成时也自然地重复这个操作。
同一个生成接口可以表达很多任务:
1
2
3
请判断情感:这部电影很好看。答案:
请翻译:我喜欢电影。英文:
请总结下面这段文字:…… 摘要:
但把任务写成提示词,不保证模型在该任务上一定准确;能力与训练数据、训练目标、模型容量和提示有关。
15.4 T5:先把输入读完整,再生成另一个序列
T5 类编码器—解码器结构有两部分:编码器双向处理输入,解码器逐步生成输出,并读取编码器表示。
1
2
3
输入全文 → 双向编码器 → 输入的各位置表示
↓ 交叉注意力
已生成输出 → 因果解码器 → 下一个输出 token
这种结构适合表达翻译、摘要等条件生成问题。T5 的预训练包括恢复被破坏的文本,并把多种任务写成文本到文本的形式。21
15.5 如何区分架构、训练任务和应用
| 名称 | 属于哪一类 | 解释 |
|---|---|---|
| Transformer | 架构 | 规定信息如何在网络中流动 |
| 预测下一个 token | 训练任务 | 规定模型要答什么题 |
| BERT、T5 | 具体模型方案或家族 | 组合特定架构与训练设计 |
| 影评分类、NER、翻译 | 应用任务 | 规定用户真正需要的输出 |
| 微调 fine-tuning | 训练操作 | 从已有权重继续训练,使其适配数据或目标 |
不要把这些名字全部排成一条“谁取代谁”的队伍。BERT 做 NER 与 GPT 做生成可以是不同任务上的选择。
双向编码器便于利用完整输入做判断;因果模型便于连续生成;编码器—解码器明确分开读入与输出。固定分类任务可以只输出几个类别的分数,生成式完成同一任务通常需要更多输出计算。
小练习:为什么 BERT 可以看被遮词右边的内容,而自回归续写不能看真实的下一词?
二者答的题不同。填空任务提供空白两边的文本;续写任务中的未来文本尚不存在,给出它会泄漏答案。
16. 找出句子中的人名和地名:NER
16.1 从“整句一个答案”改成“每个位置一个答案”
输入:
1
小王 在 上海 工作
目标:
| token | 标签 |
|---|---|
| 小王 | 人物 |
| 在 | 其他 |
| 上海 | 地点 |
| 工作 | 其他 |
这叫 Named Entity Recognition,命名实体识别,简称 NER。实际需要识别哪些实体类型,由任务定义。
模型可以先用 LSTM 或 Transformer 生成每个位置的上下文化表示,再为每个位置预测标签。
16.2 一个实体跨了多个 token 怎么办
假设分词结果是:
1
小王 在 北京 大学 工作
我们想把“北京大学”识别为一个组织,不能把“北京”单独当作地名。
这里采用 BIO 的 IOB2 规范,每个实体都以 B 标签开始:
| token | BIO 标签 | 含义 |
|---|---|---|
| 小王 | B-PER | 一个人物实体的开始 |
| 在 | O | 不属于实体 |
| 北京 | B-ORG | 一个组织实体的开始 |
| 大学 | I-ORG | 继续上一个组织实体 |
| 工作 | O | 不属于实体 |
B 是 beginning,开始;I 是 inside,内部;O 是 outside,外部。PER、ORG 只是实体类型缩写。
16.3 为什么有时还需要 CRF
如果每个位置独立选最高概率标签,可能生成句首 I-ORG 一类不符合所采用 BIO 规范的序列。
这里使用线性链 CRF(条件随机场)给整条标签序列评分。它不仅看“这个词像什么标签”,还看“这个标签跟在前一个标签后面是否合适”。
例如,仅展示“北京 大学”两个位置,其他候选暂时忽略:
| 位置 | 标签 | 单位置分数 |
|---|---|---|
| 北京 | B-ORG | 2 |
| 北京 | O | 1 |
| 大学 | I-ORG | 1.5 |
| 大学 | O | 1.8 |
独立选择会选 B-ORG, O,第二个词被漏掉。
若模型学到转移分数 B-ORG → I-ORG 为 1,其他本例转移分数为 0:
1
2
3
[B-ORG, I-ORG]:2+1.5+1 = 4.5
[B-ORG, O]:2+1.8+0 = 3.8
[O, O]:1+1.8+0 = 2.8
联合评分就更倾向于把两个词组成完整实体。本例略去了起止转移分数。在线性链 CRF 中,训练时用前向算法计算序列概率的归一化项,解码时可用 Viterbi 算法找最高分路径;上面的加法只是路径分数,还不是归一化概率。
这里的转移分数是学习出来的软偏好。若要严格禁止不合法转移,需要明确加约束。22
16.4 为什么不能只看每个词的准确率
假设文章有 1,000 个 token,只有 20 个属于实体。一个把所有位置都预测成“其他”的模型,也有 98% token 准确率,但一个实体都没找出来。
NER 常按完整实体计算:类型和起止边界都正确,才算一次正确抽取。
若模型抽取出 10 个实体,其中 8 个正确,原文实际有 12 个实体:
1
2
3
精确率 precision:8/10 = 0.8
召回率 recall:8/12 ≈ 0.667
F1:两者的调和平均 ≈ 0.727
精确率问“找出来的有多少是对的”;召回率问“该找的找到了多少”。这里先汇总完整实体的正确数、预测数和标注数,再计算指标,相当于 micro 统计;若分母为零,需要说明评价工具采用的约定。22
独立 token 分类简单;联合标签建模增加结构一致性,但需要更多计算。生成式模型也能抽取实体,却需要另外检查原文对应、边界与输出格式。
小练习:原文实体是“北京大学”,模型只输出“北京”,能算一次完整正确的实体抽取吗?
在严格实体级评价中不能,边界不一致。仅仅认对其中一部分文字不够。
17. 从会续写到能回答:SFT、偏好优化、LoRA 与 RAG
17.1 会预测下一词,不等于已经学会按要求回答
设输入是:
1
2
问:这条影评是好评吗?“电影很好看。”
答:
我们希望回答“是,好评”。但只进行原始文本续写训练的模型,未必能稳定遵循这种问答格式。
于是可以提供很多“指令—合适回答”示例继续训练,称为 SFT,监督微调。
1
2
输入:请判断下面影评的情感:电影很好看。
目标回答:好评。
训练仍然可以使用下一词交叉熵,只是数据更接近我们期望的使用方式。有些实现只对回答部分计算损失。
17.2 如果两个回答都通顺,怎么表达“我更喜欢这个”
对于同一个问题,给出两个回答:
1
2
回答 A:好评。“很好看”表达了正面评价。
回答 B:不知道,电影和语言没有关系。
人工或其他评价流程可以标注 A 优于 B。这叫偏好数据。
一条典型的 RLHF 路线是:用偏好训练奖励模型,再用奖励信号优化生成模型,并限制它偏离参考模型过远。
DPO 则用偏好对直接构造训练损失,是另一种偏好优化方法。理解到这里即可,初学 NLP 不必先掌握强化学习优化细节。
这些训练能让行为更符合评价标准;但偏好质量、评价偏差和目标设计会影响结果,“更讨人喜欢”不自动等于“事实更准确”。23
17.3 LoRA:只学一个较小的权重改动
微调大模型时,更新全部参数可能很贵。LoRA 冻结原权重,用两个较小矩阵表达新增改动。
先看一个 4×4 的矩阵:它有 16 个参数。若只学一个 4×1 矩阵和一个 1×4 矩阵,两者相乘也得到 4×4 的改动,但只有 8 个可训练参数。
代价是:这种改动受结构限制,不能自由表达所有可能的 4×4 矩阵。
用公式写成:
\[W=W_0+\frac{\alpha}{r}BA,\qquad B\in\mathbb{R}^{d_{\mathrm{out}}\times r},\quad A\in\mathbb{R}^{r\times d_{\mathrm{in}}}\]这里 r 是低秩维度,\(\alpha/r\) 是原论文采用的缩放系数;取该系数为 1 时,才简写为“原权重 + B×A”。缩放不会改变下面两张矩阵的参数个数。
如果原矩阵是 4,096×4,096,取中间维度 r=8:
1
2
原矩阵参数:4096×4096 = 16,777,216
LoRA 新增参数:4096×8 + 8×4096 = 65,536
这一个矩阵的可训练参数减少到原来的 1/256。但原权重仍要保存,前向计算仍要执行,训练还会使用激活等内存,不能把这个比例当作整套显存或耗时下降比例。24
LoRA 是“怎么更新参数”的方法,SFT 是“用什么训练任务和数据”的方法,二者可以组合。
17.4 RAG:把需要的资料找出来,再放到问题旁边
假设你问:
1
课程资料中的第 19 课讲什么?
假设我们有一套课程资料,其中第 19 课介绍命名实体识别。模型参数里不一定保存了这套课程的可靠信息,可以先检索文档,取得相关段落,再把问题和段落一起交给模型。
1
2
3
4
5
问题
↓ 检索
找到“第 19 课:命名实体识别”的相关内容
↓ 与问题一起放入上下文
模型回答
这叫 Retrieval-Augmented Generation,检索增强生成,简称 RAG。25
检索可以采用关键词匹配,也可以把问题和文档转换成向量,找相似项;还可以组合多种检索并重排。
对刚学过词向量的你,要注意:文档检索常需要适合句子或文档相似度的表示,不能默认随便平均一组词向量就一定检索得好。
一个不考虑长度归一化差异的玩具打分例子:问题向量为 [1,0],两个文档向量是 [0.9,0.1]、[0.1,0.9],用点积打分分别得到 0.9 和 0.1,于是优先返回第一篇。真实系统需要考虑向量训练目标、归一化、检索指标和语料组织。
更新文档可以提供新信息,不必每次重训模型;但检索会增加工作,找错材料或生成时误用材料仍会出错。RAG 不等于已经把资料永久训练进参数。
17.5 把这些名字放在各自的位置上
| 你要解决的问题 | 对应方法 |
|---|---|
| 先获得可复用的语言相关能力 | 预训练 |
| 学会按指定任务或格式回答 | SFT |
| 学习哪一种回答更符合偏好 | RLHF、DPO 等偏好优化 |
| 减少微调时要更新的参数 | LoRA |
| 获取当前外部资料 | RAG |
| 执行计算、查询等操作 | 工具调用及相应系统设计 |
小练习:给模型提供一份新课程文档作为上下文,是不是一定修改了它的权重?
不是。普通提示和 RAG 通常只是改变这次输入;修改权重需要训练或明确的参数更新过程。
18. 把知识落到实验:怎样知道模型真的学会了
18.1 一个完整影评实验的流程
1
2
3
4
5
6
7
8
1. 明确标签标准:好评、差评,是否需要中性。
2. 收集并检查评论与标签。
3. 划分训练集、验证集、测试集。
4. 用训练集建立词表或拟合 TF-IDF 等统计。
5. 先训练一个简单基线。
6. 用验证集选择设置、定位错误。
7. 在独立测试集报告结果。
8. 检查真实错误样例、延迟和资源成本。
不要把六条教学评论当成足以证明泛化能力的数据集。它们用于理解计算,真实实验需要更多样本和合理的划分。
训练集负责学参数,验证集帮助选配置,测试集评估最终表现。若不断根据测试集调整方法,它就不再是独立测试。自己拟合词表、IDF 或归一化统计时,也应限制在训练集内,避免把验证集和测试集的信息提前用于训练。3
18.2 换模型之前,先看它错在哪里
| 错误例子 | 可能原因 | 应检查什么 |
|---|---|---|
| “不好看”判成好评 | 否定组合处理不足 | 训练样例、bigram、顺序模型 |
| 新词“惊艳”完全识别不了 | 词表覆盖或表示不足 | 分词、未知词、预训练表示 |
| 长影评漏掉最后的转折 | 截断或长距离处理问题 | 输入是否完整、状态或注意力行为 |
| 所有样本都判好评 | 类别偏斜或训练问题 | 标签分布、损失、预测概率 |
| 训练集很好、测试集很差 | 过拟合或分布不同 | 数据量、重复样本、领域差异 |
| 一改 batch size 结果异常 | 实现问题 | padding、mask、损失归一化 |
这些是排查方向,不是只凭一个例子就能确定的诊断。需要继续检查数据与计算。
18.3 加入 padding 后,平均值也要认真算
两条评论长度不同,为了一起计算,可能补成相同长度:
1
2
短句:[电影, 好看, <PAD>]
长句:[电影, 不, 好看]
如果短句的两个词向量分别是 [0,1]、[1,0],正确平均值是 [0.5,0.5]。
即使 <PAD> 向量是零,若把分母错误地写成 3,也会变成 [0.333,0.333]。因此不只要忽略 padding 内容,还要用真实 token 数量计算平均。
在注意力和损失计算中,也要按需要排除 padding。特殊 token 的编号由实际词表决定,本文没有给 <PAD> 固定编号。
18.4 比较方法时,比较同一个问题
| 方案 | 适合先验证什么 | 主要限制 |
|---|---|---|
| 规则 | 任务是否主要依靠少量明确模式 | 覆盖与维护成本 |
| TF-IDF + 线性分类器 | 关键词信息是否已经足够 | 顺序和语义共享有限 |
| 平均词向量 + 分类器 | 共享词特征能否改善结果 | 聚合后仍丢顺序 |
| CNN | 局部短语模式是否重要 | 感受野与位置保留 |
| RNN/LSTM/GRU | 顺序与流式状态是否重要 | 递归依赖与历史压缩 |
| 预训练 encoder | 通用上下文表示能否迁移 | 模型与适配成本 |
| 生成式语言模型 | 能否用统一接口处理多种输出 | 生成成本、格式与事实可靠性 |
应固定或明确说明数据划分、评价标准和资源条件。不能只因为一个复杂模型分数较高,就断定原因一定是架构;预训练数据、训练时长、输入长度都可能不同。
18.5 学完后可以逐个完成的练习
| 练习 | 要观察的结果 | 说明你理解了什么 |
|---|---|---|
| 手算“电影不好看”的词袋 | 得到七维计数 | 文本表示 |
| 运行一次逻辑回归更新 | 正确标签概率提高 | 参数如何学习 |
| 交换同一句话的词序 | 平均词向量不变,递归记录可能改变 | 顺序信息 |
| 手算 CBOW 的三个候选分数 | 得到 0.665、0.090、0.245 | 词向量训练目标 |
| 手算一个 Q 对三个 K 的分数 | 得到三项权重与汇总向量 | Attention |
| 给注意力加 causal mask | 未来位置权重变为零 | 防止答案泄漏 |
| 同一组概率换不同解码方式 | 输出选择方式不同 | 模型与解码的区别 |
| 严格核对 NER 边界 | 部分识别不算完整实体正确 | 任务评价 |
附录 A. 第一遍读完后,再看这些常见支线
这些内容有助于补齐演进关系,但不应挡在第一遍理解主线之前。
A.1 朴素贝叶斯:用词出现的概率来判断类别
假设在好评里出现“精彩”的概率估计为 0.4,在差评里为 0.02,两类先验概率各为 0.5。
只观察这一个词时,两个未归一化的类别分数是:
1
2
好评:0.5×0.4 = 0.2
差评:0.5×0.02 = 0.01
归一化后,好评概率为 0.2/(0.2+0.01)≈0.952。
这一步只是贝叶斯公式的一次计算,尚未用到“朴素”的条件独立假设。多词版本才会在给定类别后,按所选事件模型组合词的条件概率;这种简化便于估计,但语言里的词并不真的彼此独立。26
A.2 GloVe 与 fastText:词向量还有其他学习办法
GloVe 利用词的全局共现统计来学习向量。可以把它理解为先统计“哪些词经常一起出现”,再学习表示这些统计关系的数值。
fastText 把词向量与字符片段联系起来,例如让共享词形片段的词共享部分参数。这样对词形变化和未见过的整词可能更有帮助。
它们提供的是词表示方案,不自动解决整句话的语序或所有多义词问题。27
A.3 HMM 与 CRF:先把输出的顺序结构建模
HMM,隐马尔可夫模型,使用“状态转移”和“状态产生观测”的概率来描述序列。例如标签状态之间会转移,各标签生成某些词的可能性不同。
CRF 直接对“给定输入时,标签序列有多合适”进行建模。第十六章已经看到如何结合单位置分数与标签转移分数。
这条路线提醒我们:NLP 的序列结构不只存在于输入词之间,也存在于输出标签之间。22
A.4 上下文化表示并非 Transformer 首创
RNN、双向 LSTM 也能根据当前句子的上下文产生不同表示。Transformer 提供了一种更便于并行和直接跨位置交互的组织方式。
因此,不能把“静态 embedding → 上下文化表示”与“RNN → Transformer”简单等同。
A.5 这条阅读路线不等于发表时间线
本文先教静态词向量,再教 LSTM,是因为这种依赖顺序更容易理解。历史上 LSTM 早于 Word2Vec。
传统统计方法、循环网络、卷积网络、注意力和预训练也有长期并行发展的过程。每种方法服务的约束不同,不应理解为一次统一的升级换代。5、7、8、12
句法分析、关系和事件抽取、语音识别等也是相关领域的重要分支。本文只沿文本表示、序列建模和语言模型展开,不能代替整个 NLP 领域的知识地图。
附录 B. 公式速查
| 公式 | 读法 | 对应正文 |
|---|---|---|
z = w·x + b | 输入各项乘权重再相加 | 第 3 章 |
sigmoid(z) = 1/(1+exp(-z)) | 把一个分数转成 0—1 的数 | 第 3—4 章 |
softmax(z)_i = exp(z_i)/Σ_j exp(z_j) | 把一组候选分数转成概率 | 第 7 章 |
L = -ln p(正确答案) | 给正确答案的概率越小,惩罚越大 | 第 4、7、11 章 |
新参数 = 旧参数 - 学习率×梯度 | 朝降低损失的方向走一步 | 第 4 章 |
句子均值 = 词向量之和/真实词数 | 把变长输入汇总为固定长度 | 第 6、18 章 |
h_t = tanh(W_x x_t + W_h h_(t-1) + b) | 用当前词和旧状态更新 RNN | 第 8 章 |
c_t = f_t⊙c_(t-1) + i_t⊙g_t | 旧记忆保留一部分,加上新信息 | 第 9 章 |
h_t = o_t⊙tanh(c_t) | 从记忆产生当前隐藏输出 | 第 9 章 |
Attention = softmax(QK^T/√d_k + mask)V | 打分、遮罩、转权重、加权读取 | 第 13 章 |
PPL = exp(平均负对数概率) | 对平均语言模型损失做指数变换 | 第 11 章 |
F1 = 2PR/(P+R) | 综合精确率 P 与召回率 R | 第 16 章 |
LSTM 门控的进一步细节。若想知道门的数值从哪来,将当前词向量与旧隐藏状态拼接,记为 z:
1
2
3
4
5
z = [当前输入; 旧隐藏状态]
遗忘门 f = sigmoid(W_f z + b_f)
输入门 i = sigmoid(W_i z + b_i)
候选内容 g = tanh(W_g z + b_g)
输出门 o = sigmoid(W_o z + b_o)
各个 W 和 b 都通过训练学习。这些式子再接第九章的记忆更新即可,不需要人为设置每一个门。
一个 Pre-LN 自注意力块的进一步细节。以 Pre-LN 布局、略去 dropout 为例:
1
2
U = X + MultiHeadAttention(LayerNorm(X))
Y = U + FFN(LayerNorm(U))
FFN 通常包含两个线性变换和中间非线性函数,各位置独立应用同一套参数。编码器—解码器模型的解码层还要加入交叉注意力;不同变体的归一化和 FFN 形式也可能不同。1214
附录 C. 术语表
| 术语 | 这篇文章中的解释 |
|---|---|
| corpus,语料 | 用来学习或分析的一批文本 |
| token | 模型使用的一个文字处理单位 |
| vocabulary,词表 | token 与编号的对应表 |
| token ID | token 的地址编号,不是语义数值 |
| vector,向量 | 一排有固定长度的数字 |
| matrix,矩阵 | 多排等长数字组成的表 |
| dimension,维度 | 向量有多少个数,或数组各方向的大小 |
| tensor,张量 | 多维数值数组,向量和矩阵是常见例子 |
| feature,特征 | 模型用于预测的数值信息 |
| parameter,参数 | 训练时调整的数值,例如权重 |
| embedding | 从离散编号查到的可学习向量表示 |
| hidden state,隐藏状态 | 网络内部在某个位置形成的表示或递归记录 |
| context,上下文 | 当前预测能使用的周围文本或历史信息 |
| logits | softmax 之前的候选分数 |
| loss,损失 | 衡量当前预测有多不符合训练目标的数 |
| gradient,梯度 | 参数变化如何影响损失的局部信息 |
| backpropagation,反向传播 | 沿计算链高效计算各参数梯度的方法 |
| batch | 一次一起处理的一组样本 |
| padding | 为了对齐形状补入的占位项 |
| mask | 限制某些位置参与计算的规则或数值表 |
| attention | 按匹配权重读取和组合信息 |
| head | 一套注意力投影与计算分支 |
| encoder | 处理输入并产生表示的部分 |
| decoder | 根据可用信息产生输出序列的部分 |
| pretraining,预训练 | 先在较广的数据和目标上训练,供后续复用 |
| fine-tuning,微调 | 从已有参数出发继续训练 |
| inference,推理 | 使用模型计算输出,通常不更新参数 |
| decoding,解码 | 根据模型分数选择具体输出序列 |
| trade-off,取舍 | 为获得一种收益,需要付出的成本或接受的限制 |
附录 D. 复算文中的数字
下载配套脚本后,可以运行 python nlp_examples.py。
脚本只使用 Python 标准库,不下载数据、不训练大型模型、不改动任何数据文件。
它会依次打印词袋、TF-IDF、一次分类更新、一次 CBOW 输入向量更新、顺序记录、LSTM 单格、卷积窗口、采样候选、注意力、显存估算及评价指标。
脚本中的 RNN 记录、LSTM 单格和卷积窗口是正文的局部计算演示,不是完整模型实现。它们的意义是复算数字,不能用来证明真实 NLP 任务的效果。
参考文献与阅读说明
正文按概念转述资料,没有逐字引录论文段落。微型数据和手算结果用于解释计算,不对应文献中的实验数据。方法原理优先引用论文,工程行为引用官方文档。
Jurafsky 与 Martin 的《Speech and Language Processing》采用 2026 年 8 月 19 日第三版在线草稿;以下写出章节名,便于在后续版本调整编号时查找。动态教材与官方文档核对日期为 2026 年 9 月 10 日。教材目录
Rico Sennrich, Barry Haddow, Alexandra Birch. 2016. Neural Machine Translation of Rare Words with Subword Units. ACL. BPE 子词分割,参见第 3 节。 ↩︎
Christopher D. Manning, Prabhakar Raghavan, Hinrich Schütze. 2008. Introduction to Information Retrieval. Cambridge University Press, §6.2.1. IDF 定义;正文采用自然对数和原始词频,未做平滑或归一化。 ↩︎
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 4: Logistic Regression and Text Classification. sigmoid、交叉熵、梯度更新及数据划分。 ↩︎ ↩︎2
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 3: N-gram Language Models. 概率链式法则、N-gram 近似、平滑与困惑度。 ↩︎ ↩︎2 ↩︎3
Tomas Mikolov, Kai Chen, Greg Corrado, Jeffrey Dean. 2013. Efficient Estimation of Word Representations in Vector Space. 第 3 节与图 1:CBOW 和 Skip-gram 的预测方向。 ↩︎ ↩︎2 ↩︎3
Tomas Mikolov 等. 2013. Distributed Representations of Words and Phrases and their Compositionality. NeurIPS. §2.2、式 (4):负采样目标。实现可另看 TensorFlow 的 Word2Vec 教程,其批量损失写法不应与本文逐词对 sigmoid 损失直接混同。 ↩︎ ↩︎2
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 14: RNNs and LSTMs. 循环状态、梯度传播、门控与序列生成。 ↩︎ ↩︎2 ↩︎3 ↩︎4
Yoon Kim. 2014. Convolutional Neural Networks for Sentence Classification. EMNLP. 卷积窗口、非线性与 max-over-time pooling。正文标量例子省略了非线性。 ↩︎ ↩︎2
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 7: Transformers and Pretraining. 因果语言模型、预训练与生成采样;按本次草稿章节名引用。 ↩︎ ↩︎2
Dzmitry Bahdanau, Kyunghyun Cho, Yoshua Bengio. 2015. Neural Machine Translation by Jointly Learning to Align and Translate. ICLR. 预印本发表于 2014 年;输入表示瓶颈与可学习的软对齐。其加性打分不同于 Transformer 的缩放点积。 ↩︎
Sarthak Jain, Byron C. Wallace. 2019. Attention is not Explanation. NAACL. 支持“不能仅凭注意力权重认定预测原因”这一限定,不等于否定所有解释方法。 ↩︎
Ashish Vaswani 等. 2017. Attention Is All You Need. NeurIPS. §3.1—3.5、式 (1) 与表 1:Post-LN、注意力、位置编码及计算复杂度。本文含投影和 FFN 的成本是在这些组件上合并估算的。 ↩︎ ↩︎2 ↩︎3 ↩︎4 ↩︎5 ↩︎6 ↩︎7
Jianlin Su 等. 2021. RoFormer: Enhanced Transformer with Rotary Position Embedding. RoPE 的旋转位置编码;此处年份采用预印本首次发表时间。 ↩︎
Ruibin Xiong 等. 2020. On Layer Normalization in the Transformer Architecture. ICML, PMLR 119:10524–10533. Pre-LN 与 Post-LN 的位置区别及训练分析。 ↩︎ ↩︎2
Hugging Face. How caching works. Transformers documentation. 自回归推理中的 KV 复用;正文缓存字节数由张量维度相乘估算。 ↩︎ ↩︎2
Joshua Ainslie 等. 2023. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. EMNLP. 多个 Query heads 共享 KV heads,MHA、GQA 与 MQA 的关系。 ↩︎ ↩︎2
Tim Dettmers, Mike Lewis, Younes Belkada, Luke Zettlemoyer. 2022. LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale. NeurIPS. 量化方法的一项具体研究;存储收益与运行速度应分别评价。 ↩︎
Tri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré. 2022. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. NeurIPS. 分块计算、减少 HBM 读写与注意力中间结果存储。 ↩︎
OpenAI. 2023. GPT-4 Technical Report. 第 2 节明确说明报告没有披露模型大小等细节。 ↩︎
Jacob Devlin, Ming-Wei Chang, Kenton Lee, Kristina Toutanova. 2019. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. NAACL. 预印本发表于 2018 年;§3.1 为 MLM 与下一句预测。 ↩︎
Colin Raffel 等. 2020. Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer. JMLR 21(140):1–67. 预印本发表于 2019 年;文本到文本框架、编码器—解码器与去噪目标。 ↩︎
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 18: Sequence Labeling for Parts of Speech and Named Entities. 实体标注、线性链 CRF 与评价;HMM 另见 附录 A。 ↩︎ ↩︎2 ↩︎3
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 8: Post-training. 指令微调、偏好数据、RLHF 与 DPO。 ↩︎
Edward J. Hu 等. 2022. LoRA: Low-Rank Adaptation of Large Language Models. ICLR. 预印本发表于 2021 年;§4.1 为低秩分解与 α/r 缩放。 ↩︎
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 11: Information Retrieval and RAG. 稀疏与稠密检索、重排及检索增强生成。 ↩︎
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Appendix B: Naive Bayes Classification. 贝叶斯分类与条件独立假设。 ↩︎
Daniel Jurafsky, James H. Martin. 2026. Speech and Language Processing, Ch. 5: Embeddings. 分布式表示、Word2Vec、GloVe 与 fastText。 ↩︎