第 9 讲:预训练(规模、系统、数据)

Natural Language Processing with Deep Learning · Lecture 09。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应:Stanford CS224N Winter 2026 Lecture 7(Pretraining);Winter 2025 Lecture 9(Pretraining);Manning 主讲的 Winter 2023 版第 9 讲(Pretraining,录像在 YouTube 上)。规模定律、训练系统、预训练数据的详细内容见同目录 CS336 第 1、2、5–9、11、13、14 讲。 配套代码:code/lecture09/(导入第 7 讲的 transformer.py 和第 8 讲的 toy_reviews.py;lm_utils.py 和预训练好的模型会被第 10 讲复用) 预计阅读时间:4–5 小时


0. 本讲导航

0.1 学完本讲,你应该能够

  1. 说出”先预训练、再微调”为什么有效:无标签文本里藏着什么知识,预训练怎样把它带给下游任务。
  2. 解释静态词向量的局限,以及 ELMo 为什么要用上下文相关的表示。
  3. 写出三种预训练目标——因果语言模型(GPT)、掩码语言模型(BERT)、span corruption(T5)——的输入和目标,解释 BERT 80/10/10 规则的原因。
  4. 比较编码器、编码器—解码器、解码器三种架构的优缺点和典型用法,说出”只用解码器”为什么最终成了主流。
  5. 区分全量微调、冻结 + 线性头、参数高效微调;知道预训练在数据少和分布偏移时价值最大。
  6. 解释零样本、少样本和上下文学习,以及为什么提示的写法和校准会影响结果。
  7. 用 C≈6NDC \approx 6ND 估算训练成本,说出 Kaplan 和 Chinchilla 规模定律的结论,以及”训练过度”小模型的理由。
  8. 说出预训练数据从哪里来、要做哪些处理(过滤、去重、配比),以及训练系统的基本思路(详见 CS336)。

0.2 先修知识

知识点用在哪里最低要求
第 7 讲全讲Transformer 的结构;因果掩码;编码器 / 解码器 / 编码器—解码器;参数量公式
第 8 讲第 7、8 节影评数据集;基线;学习曲线与训练集大小;分布偏移
第 1、2 讲第 2 节word2vec;分布假设;一词多义
第 5 讲第 4、6 节语言模型、困惑度、采样

0.3 本讲地图

第1节  动机:标注数据少,无标签文本多
第2节  从静态词向量到上下文相关的表示                ← 代码 02
第3节  预训练为什么有效                              ← 代码 02
第4节  编码器:BERT 与掩码语言模型                   ← 代码 01、02
第5节  编码器—解码器:T5 与 span corruption          ← 代码 01
第6节  解码器:GPT 与因果语言模型                    ← 代码 01、02
第7节  微调:把预训练模型用到具体任务上              ← 代码 03(本讲重点)
第8节  零样本、少样本与上下文学习                    ← 代码 04
第9节  规模:算力、规模定律、系统与数据              ← 代码 05
第10–14节 小结、与作业的关系、练习、延伸阅读、下一讲预告

关于本讲的实验数字:01、05 号代码在编写讲义时完整运行过,讲义引用了它们的实际输出。02、03、04 号代码需要预训练和微调(十几分钟到半小时),编写时只做了冒烟测试,讲义中只描述预期的趋势,具体数字请以你自己运行的结果为准——建议你运行之后,把数字填进对应的”动手”框里对照。


1. 动机:标注数据少,无标签文本多

第 8 讲的两个实验暴露了有监督学习的根本困难:

  • 标注数据少时,模型很差。 第 8 讲代码 02 演示 6:GRU 用 50~200 条标注数据只有 82% 左右,要 1000 条以上才到 96%。
  • 换一个领域,模型就遇到很多没见过的词。 第 8 讲代码 05 演示 3:在影评上训练的模型,用在餐厅点评上,碰到 delicious、rude 就束手无策。

标注数据又贵又慢:需要人一条条地读、判断,复杂的任务(句法分析、问答)还需要专家。而没有标签的文本几乎取之不尽:网页、书籍、新闻、百科、论坛、代码……

一个自然的问题:能不能先从海量无标签文本里学到关于语言的一般知识,再用少量标注数据学习具体任务?

这就是预训练(pretraining)的思想。它其实并不新:第 1、2 讲的 word2vec 就是在无标签文本上”预训练”的词向量,再拿去做下游任务。2018 年的变化是:不再只预训练词向量这一层,而是预训练整个网络。


2. 从静态词向量到上下文相关的表示

2.1 静态词向量的局限

word2vec、GloVe 给每个词一个固定的向量,不管这个词出现在什么句子里。这有两个明显的问题:

一词多义。 “bank” 在 “river bank”(河岸)和 “bank account”(银行账户)里意思完全不同,却共用同一个向量——这个向量只能是各种意思的某种”平均”(第 2 讲第 6 节)。

上下文改变意思。 更普遍的情况是,一个词的意思被它周围的词改变了。第 8 讲反复遇到的例子:

the plot was boring .    (贬义) the plot was not boring .  (boring 本身没变,整个短语变成了褒义)

一个只看 boring 的静态向量,无论如何都分不清这两句。

2.2 ELMo:用语言模型的隐藏状态做词的表示

ELMo(Peters 等 2018,Deep contextualized word representations)的想法:训练一个双向的 LSTM 语言模型(一个从左往右读、一个从右往左读),然后把每个词在语言模型里的隐藏状态当作这个词的表示。

隐藏状态”读过”了整个句子,所以同一个词在不同的句子里得到不同的向量——上下文相关的表示(contextualized representation)。ELMo 把这些表示加到当时各个任务最好的模型里,在问答、情感分析、命名实体识别等 6 个任务上都刷新了最好成绩。

动手:代码 02_pretrain.py 演示 4 做了一个探针实验:短句 “the acting was boring .” / “the acting was not boring .”,只用形容词那个位置的向量,训练逻辑回归判断整个短句的褒贬;用一半形容词训练,在另一半没见过的形容词上测试。

  • 用输入词向量(静态):boring 永远是同一个向量,一半带 not 的短句必然判错,准确率应该在 50% 左右;
  • 用最后一层的隐藏状态(上下文相关):boring 的向量已经”读过”了前面的 not,准确率应该明显更高。

(编写讲义时只做了冒烟测试;请运行后对照你的数字。)

2.3 从”预训练表示”到”预训练整个模型”

ELMo 仍然是”把预训练的表示当作特征,喂给另一个针对任务设计的模型”。同一年出现了更彻底的做法:

  • ULMFiT(Howard & Ruder 2018):预训练一个 LSTM 语言模型,然后整个模型在目标任务上继续训练(微调),只在最后加一个分类层。
  • GPT(Radford 等 2018):同样的思路,换成 Transformer 解码器。
  • BERT(Devlin 等 2018):Transformer 编码器 + 掩码语言模型,在 11 个任务上大幅刷新纪录。

从此,“预训练 + 微调”成了 NLP 的标准范式:几乎所有的模型都从一个预训练好的 Transformer 出发,而不是从随机初始化开始。


3. 预训练为什么有效

3.1 语言模型是一个”包罗万象”的任务

要把”预测下一个词”做好,模型必须学会很多东西。看几个例子(下划线处是要预测的词):

句子要预测好,必须知道……
The Eiffel Tower is in the city of ___事实知识
She picked up the two books and put ___ on the shelf语法(复数代词)
Tom thanked Mary because ___ had helped him指代与常识
I grabbed a fork, a knife and a ___词汇语义(餐具这一类)
Two hours of my life I will never get back. The film was ___情感
He opened the fridge, found it empty, and decided to go to the ___推理(因果、意图)
2, 4, 6, 8, ___简单的数学规律

这些”知识”都没有被单独标注过,但都藏在大量文本的统计规律里。语言模型为了降低预测的损失,被迫把它们学进参数里。从这个角度看,预测下一个词是一个包含了无数子任务的”多任务学习”。

在我们的玩具世界里也是一样。影评的生成器让总结句(“i loved it” / “a waste of time”)大体和前面的评价一致。一个只学到局部语法的语言模型,生成的总结句和前文应该没有关系;一个学到了”整条评论在夸还是在骂”的模型,生成的总结句应该和前文一致。

动手:代码 02 演示 1 在 4 万条没有标签的评论(影评和餐厅点评各一半,约 100 万个词)上预训练一个迷你 GPT(3 层、64 维、4 个头,167,872 个参数),然后统计它生成的评论里”总结句和前文情感一致”的比例,和真实评论、以及”把总结句随机打乱”的对照(约 50%)比较。

预期:生成的评论基本合乎语法;总结句和前文的一致率应该接近真实评论,明显高于 50%——没有任何标签,模型自己学到了”情感”这个概念。

3.2 预训练模型里存着知识

既然语言模型必须学会事实知识,那么能不能直接从模型里”问”出来?Petroni 等(2019)用 BERT 做完形填空(“Dante was born in [MASK].”),发现它能答对相当一部分事实性问题——语言模型可以当作一种(不太可靠的)知识库。Roberts 等(2020)进一步发现,把 T5 微调成”闭卷问答”(不给任何参考文档,直接回答问题),它的表现随模型规模增大而提高。

这也带来了问题:模型的知识来自训练数据,数据里的错误、过时的信息和偏见也会被学进去(第 12、19 讲)。

3.3 预训练作为初始化

另一个角度:预训练给了微调一个好的起点。从随机参数开始,模型要从零学起,标注数据少时很容易过拟合;从预训练的参数开始,模型已经知道”褒义词长什么样""not 会反转意思”,微调只需要在这个基础上稍作调整,把已有的知识和任务的标签”连接”起来。

动手:代码 02 演示 3 检查预训练之后的词向量:找和 superb、boring、delicious、rude 最相似的词;并且只看词向量,用”离褒义词中心近还是离贬义词中心近”给 48 个形容词分褒贬(留一法)。

预期:预训练之后,褒义词的近邻大多是褒义词、贬义词的近邻大多是贬义词(分布假设,第 1 讲);只看词向量的褒贬分类准确率明显高于随机初始化的 50% 左右。这意味着:标注数据里只出现过 great 是褒义词,模型也能”猜到” superb 是褒义词。

3.4 子词分词(回顾)

预训练模型面对的是开放的词汇:新词、人名、拼写错误、各种语言。今天几乎所有模型都使用子词分词(subword tokenization),最常见的是 BPE(Byte Pair Encoding):从单个字符(或字节)开始,反复把最常见的相邻两个单元合并成一个新单元,直到词表达到预定的大小(GPT-2 是 50257 个)。常见的词是一个单元,罕见的词被拆成几个片段(“unhappiness” → “un” + “happiness”),任何字符串都能被表示,不存在”未登录词”。

BPE 的实现和细节见 CS336 第 1 讲;第 16 讲会讨论分词对多语言的影响。我们的玩具数据只有一百多个词,直接按词分即可。


4. 编码器:BERT 与掩码语言模型

4.1 问题:编码器不能做普通的语言模型

编码器是双向的:每个位置能看到整个句子。这对”理解”很有帮助(判断 bank 的意思时,右边的 account 也很重要),但它不能直接训练成”预测下一个词”——答案就在输入里。

4.2 掩码语言模型

BERT 的解决办法是掩码语言模型(Masked Language Model,MLM),也就是完形填空:随机遮住一部分词,让模型根据左右两边的上下文把它们猜出来。

具体规则:随机选出 15% 的词元作为预测目标,其中

  • 80% 换成特殊符号 [MASK];
  • 10% 换成一个随机的词;
  • 10% 保持不变。

损失只在这 15% 的位置上计算。

为什么不全部换成 [MASK]? 微调和实际使用时,输入里永远没有 [MASK]。如果预训练时被预测的位置总是 [MASK],模型就可以只在看到 [MASK] 时才认真”思考”,对正常的词就不费力建立好的表示——预训练和使用之间出现了不一致(mismatch)。混入随机词和原词之后,模型不知道哪些位置会被考、哪些词是被替换过的,只好对每个词都建立好的上下文表示。

动手:代码 01_pretraining_objectives.py 演示 1 展示了同一条评论在三种目标下的输入和目标;演示 2 在 1 万条评论上统计 lm_utils.mask_tokens 的结果:被选中作为预测目标的比例 15.0%,其中换成 <mask> 80.0%、换成随机词 9.9%、保持原词 10.1%(“换成随机词”时偶尔抽到原词本身,所以保持原词略多于 10%)。

4.3 BERT 的其他细节

  • 结构:BERT-base 12 层、768 维、12 个头,约 1.1 亿参数;BERT-large 24 层、1024 维、16 个头,约 3.4 亿参数。
  • 数据:BooksCorpus(约 8 亿词)+ 英文维基百科(约 25 亿词)。
  • 下一句预测(Next Sentence Prediction,NSP):BERT 还有第二个预训练任务——给两段文字,判断第二段是不是紧跟在第一段后面。后来 RoBERTa(Liu 等 2019)发现去掉 NSP 效果不变甚至更好;它还表明,BERT 其实训练得不够:用更多数据、更大的批、更久的训练、每轮重新随机遮盖(动态掩码),效果明显提高。
  • 特殊符号:输入开头加 [CLS],两段文字之间加 [SEP]。微调做分类时,用 [CLS] 位置的最后一层表示接一个分类层。

4.4 怎样微调 BERT

任务做法
句子分类(情感、主题)[CLS] 的表示 → 线性层
句子对分类(蕴含、复述)[CLS] 句子1 [SEP] 句子2 [SEP] → [CLS] 的表示 → 线性层
序列标注(命名实体识别)每个词元的表示 → 线性层
抽取式问答(SQuAD)[CLS] 问题 [SEP] 段落 [SEP],对段落中每个位置预测”答案从这里开始 / 在这里结束”的概率

所有任务都只在预训练模型上加一个很小的输出层,然后所有参数一起微调几轮。这种”一个模型 + 很薄的任务层”的做法,取代了之前为每个任务精心设计的各种网络结构。

4.5 BERT 的变体

  • SpanBERT(Joshi 等 2020):遮住连续的一段词,而不是随机的单个词——预测一整段更难,学到的表示对问答、指代消解更有用。
  • ELECTRA(Clark 等 2020):用一个小模型把一些词替换成”以假乱真”的词,让主模型判断每个词是不是被替换过。所有位置都提供训练信号,数据效率高得多(第 6.3 节)。
  • DistilBERT 等:通过知识蒸馏把大模型压缩成小模型。

4.6 编码器的局限

编码器擅长”理解”,却不能自然地生成文本:它没有”一个词一个词往后写”的机制。随着越来越多的任务被写成生成的形式(第 6、8 节),纯编码器模型逐渐淡出了大模型的舞台;但在需要高效地编码文本的场景(检索、分类、嵌入向量)中,它们仍然被广泛使用。

动手:代码 02 演示 2 在同样的语料上预训练一个迷你 BERT,然后让它做完形填空,例如:

  • “the acting was <mask> . i loved it .”
  • “the acting was not <mask> . i loved it .”
  • “the food was <mask> . highly recommended .”

预期:后面跟着 “i loved it” 时排在前面的是褒义词,跟着 “a waste of time” 时是贬义词——模型利用了右边的上下文。带 not 的句子是更难的测试:如果模型学到了”not + 贬义 = 夸奖”,排在前面的应该是贬义词;小模型不一定完全做到,看看你的结果。


5. 编码器—解码器:T5 与 span corruption

5.1 两全其美?

编码器—解码器结构(第 7 讲第 8.3 节)的编码器是双向的,适合理解输入;解码器是因果的,可以生成输出。怎样预训练它?

5.2 span corruption

T5(Raffel 等 2020,Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer)系统地比较了很多种预训练目标,最后选择了 span corruption:随机删掉几段连续的词(总共约 15%,每段平均 3 个词),在输入里用哨兵符号代替;解码器要生成”哨兵 + 被删掉的内容”。

原文:Thank you for inviting me to your party last week .
输入:Thank you <X> me to your party <Y> week .
目标:<X> for inviting <Y> last <Z>

和 BERT 相比:

  • 删掉的是连续的一段,模型要生成整段内容,学到的东西更接近”写作”;
  • 输入比原文短,目标也很短,训练更省计算。

动手:代码 01 演示 3 在 1 万条评论上统计 lm_utils.span_corrupt:被删掉的词占 15.0%,平均每段 2.95 个词;原文平均 26.0 个词,变成输入 23.4 个 + 目标 5.2 个。

5.3 一切都是”文本到文本”

T5 的另一个贡献是统一的格式:所有任务都写成”输入一段文本,输出一段文本”。

"translate English to German: That is good."   → "Das ist gut."
"summarize: <一篇文章>"                         → "<摘要>"
"cola sentence: The course is jumping well."    → "not acceptable"
"stsb sentence1: ... sentence2: ..."            → "3.8"

连分类和回归都写成生成”标签的文字”。同一个模型、同一个损失函数、同一套解码方法,可以处理所有任务——这为后来的”指令微调”(第 10 讲)铺平了道路。T5 还发布了清洗过的大规模网页语料 C4。

代表模型:T5、BART(Lewis 等 2020,用”打乱、删除、遮盖”等多种破坏方式预训练)、多语言的 mT5。


6. 解码器:GPT 与因果语言模型

6.1 GPT 系列

解码器就是第 5、7 讲的语言模型:每个位置只看左边,预测下一个词。

  • GPT(2018):12 层 Transformer 解码器,1.17 亿参数,在 BooksCorpus(7000 多本未出版的书)上预训练。微调时,把各种任务的输入”拼成一段文字”(比如蕴含任务:[START] 前提 [DELIM] 假设 [EXTRACT]),用最后一个位置的表示做分类。
  • GPT-2(2019):15 亿参数,在 WebText(约 40GB 网页文本)上训练。论文标题是 Language Models are Unsupervised Multitask Learners:不微调,只靠续写就能完成一些任务(零样本,第 8 节)。生成的文本流畅得让人吃惊,OpenAI 一度分阶段发布模型权重。
  • GPT-3(2020):1750 亿参数,约 3000 亿个词元。在提示里放几个例子,不更新任何参数,模型就能”照着做”——上下文学习(第 8.3 节)。

6.2 为什么”只用解码器”成了主流

2018~2020 年,BERT 式的编码器在理解类基准上领先;今天的大模型却几乎都是纯解码器。原因有几个:

  1. 简单、统一:一个模型、一个目标(预测下一个词),没有 [MASK] 带来的预训练和使用不一致。
  2. 所有任务都可以写成生成:分类、问答、翻译、写代码、对话……都是”给一段文字,续写一段文字”(T5 已经说明了这一点)。
  3. 训练信号多:每个位置都在预测,一条文本的每个词都提供训练信号(下面的演示)。
  4. 规模扩展得好:GPT-3 之后,人们发现扩大解码器的规模能持续带来新的能力。
  5. 天然支持生成和对话:这是今天大模型最主要的用途。

6.3 每条数据提供多少训练信号

动手:代码 01 演示 4。我们的评论平均 26.0 个词:

目标每条评论的训练信号
因果语言模型(每个位置预测下一个词,含 </s>)约 27.0 个
掩码语言模型(只预测 15%)约 3.9 个
span corruption(被删掉的词 + 哨兵)约 5.2 个

同样一条文本,因果语言模型得到的训练信号最多。BERT 每条数据只”学习”15% 的位置,这是它数据效率的一个弱点——ELECTRA 正是为了解决这个问题。

6.4 三种架构的小结

编码器(BERT)编码器—解码器(T5)解码器(GPT)
预训练目标掩码语言模型span corruption因果语言模型
看得到双向编码器双向,解码器单向单向
擅长理解:分类、标注、抽取、检索序列到序列:翻译、摘要生成,以及把一切写成生成
能不能直接生成不能能能
今天的代表文本嵌入、检索、分类模型翻译、语音识别(Whisper)几乎所有大语言模型

7. 微调:把预训练模型用到具体任务上

7.1 几种做法

做法训练哪些参数特点
全量微调(full fine-tuning)全部效果通常最好;每个任务要存一份完整的模型
冻结 + 线性头(线性探针)只有最后的分类层最便宜;衡量的是预训练表示本身有多好
参数高效微调(PEFT)一小部分新加的参数(LoRA、适配器、前缀……)效果接近全量微调,存储和计算省得多(第 11 讲)

微调时的几个经验:

  • 学习率比从零训练小(比如 BERT 常用 2×10−52 \times 10^{-5} ~ 5×10−55 \times 10^{-5}),训练几轮就够;太大的学习率可能”冲掉”预训练学到的东西(灾难性遗忘,catastrophic forgetting)。
  • 用验证集早停(第 8 讲)。
  • 微调的结果对随机种子相当敏感,尤其是数据少的时候(Dodge 等 2020),要多跑几个种子。

7.2 实验:预训练到底带来多少收益

代码 03_finetune.py 在第 8 讲的影评情感分类上,比较五种做法(标注数据 50、100、200、500、2000 条,每种 2 个随机种子):

做法说明
逻辑回归第 8 讲最好的基线(单个词 + 相邻两词)
从零训练和迷你 GPT 结构完全相同,但参数随机初始化
GPT 微调预训练的迷你 GPT + 分类头,所有参数一起微调(用最后一个词的隐藏状态)
BERT 微调预训练的迷你 BERT + 分类头,所有参数一起微调(对所有词的隐藏状态取平均)
GPT 冻结 + 线性头预训练的迷你 GPT 一个参数都不动,只训练分类头

“从零训练”和”GPT 微调”的唯一区别就是初始参数——这是一个干净的对照实验(第 8 讲第 9.2 节的消融)。

动手:运行代码 03(约 10~20 分钟),把结果填进下表:

标注条数501002005002000
逻辑回归
从零训练
GPT 微调
BERT 微调
GPT 冻结 + 线性头

预期的趋势(编写讲义时只做了冒烟测试):

  1. 标注数据少时,预训练过的模型明显好于从零训练的同一个模型,往往也好于逻辑回归;
  2. 标注数据越多,差距越小——数据足够多时,从零训练也能学会这些。预训练的价值在数据少时最大;
  3. 从零训练的 Transformer 在数据很少时可能还不如逻辑回归(参数多、容易过拟合);
  4. 冻结主体、只训练线性头,在数据很少时可能和全量微调差不多,数据多时通常不如全量微调。

如果你的结果不符合这些预期,先想一想为什么(学习率?训练步数?预训练是否充分?),这正是第 8 讲练习的东西。

7.3 没见过的词

预训练的一个直接价值:把无标签文本里的知识带给下游任务。标注数据只有 100 条时,很多形容词根本没在标注数据里出现过。

动手:代码 03 演示 2 把测试集分成两部分:“含有训练集里从没出现过的形容词”的评论和其余评论。

预期:逻辑回归和从零训练的模型,对没见过的形容词毫无办法(这个词的权重是 0 或随机的);预训练过的模型在预训练时见过这些词出现在什么样的上下文里,知道它们和哪些已知的词相似,所以优势应该在这部分评论上最明显。

7.4 在目标领域上预训练

第 8 讲代码 05 演示 3:在影评上训练的逻辑回归,用到餐厅点评上准确率从 91.2% 掉到 85.4%,主要是因为不认识 delicious、rude 这些词。

我们的预训练语料里一半是餐厅点评(没有标签)。预训练过的模型知道 delicious 出现的上下文和 great 很像(后面常跟着 “highly recommended”),所以只用影评的标签微调,也有希望推广到餐厅点评。

动手:代码 03 演示 3 用 500 条影评的标签微调,同时在影评和餐厅点评上测试。预期:预训练过的模型在餐厅点评上掉得比逻辑回归少得多。

这对应着真实世界的一个常用做法:在目标领域的无标签文本上继续预训练(Gururangan 等 2020,Don’t Stop Pretraining)——比如医学、法律、代码、某个公司的内部文档。无标签的领域文本往往比标注数据容易获得得多。


8. 零样本、少样本与上下文学习

8.1 零样本:把任务写成续写

GPT-2 的论文提出:语言模型在预训练时已经见过无数”任务”的影子。网页上本来就有”文章 + TL;DR: + 摘要”、“英文句子 = 法文句子”这样的文本。所以不微调、不需要标注数据,只要把任务写成续写的形式,就能让模型完成任务——零样本(zero-shot)。比如在文章后面加上 “TL;DR:“,模型续写出来的就是摘要。

对分类任务,可以比较不同”答案”的概率:

y^=arg⁡max⁡y P(答案句y∣输入)\hat{y} = \arg\max_{y}\ P(\text{答案句}_y \mid \text{输入})

情感分类:评论后面接 “i loved it .” 和 “i hated it .”,哪个概率更高?代表每个类别的答案文字叫 verbalizer。

动手:代码 04_zero_shot.py 用预训练的迷你 GPT 做零样本情感分类。为了公平,测试评论里的总结句被删掉了(否则模型只要”抄”原来的总结句),标签用删掉之后的打分重新计算。比较了三组答案句(loved / hated、recommended / avoid、must see / waste of time)和”5 句正面 vs 5 句负面取平均”。

预期:没有用任何标注数据,准确率也应该明显高于 50%;不同的答案句,准确率可能差别不小。

8.2 提示很敏感,需要校准

零样本和少样本的结果对”怎么问”非常敏感:换一个答案词、换一种措辞、换例子的顺序,准确率都可能大幅变化(Zhao 等 2021)。一个常见的原因是:有些答案本身就更”常见”,不管输入是什么,模型都更倾向于说它,预测就偏向一边。

校准(calibration):用”答案句在空输入后面的概率”衡量这种先验偏好,把它减掉:

score(y)=log⁡P(答案句y∣输入)−log⁡P(答案句y∣空输入)\text{score}(y) = \log P(\text{答案句}_y \mid \text{输入}) - \log P(\text{答案句}_y \mid \text{空输入})

衡量的是”这条输入让答案 yy 的概率提高了多少”。

动手:代码 04 演示 2 比较校准前后的准确率和”预测为正面的比例”。预期:如果不校准时预测明显偏向某一边,校准后预测的比例更接近真实比例;如果本来就不怎么偏,作用就不大。演示 3 比较零样本相当于用多少条标注数据训练的逻辑回归。

8.3 少样本与上下文学习

GPT-3 的论文(Language Models are Few-Shot Learners,2020)展示了一种新的用法:在提示里先放几个”输入 → 输出”的例子,再放要回答的输入:

评论:The acting was superb. 情感:正面
评论:A total waste of time. 情感:负面
评论:I was never bored for a second. 情感:

模型不更新任何参数,只是读了这几个例子,就能照着格式和规律回答。这叫上下文学习(in-context learning,ICL)或少样本提示(few-shot prompting)。在很多任务上,GPT-3 的少样本表现接近甚至超过了专门微调的较小模型。

上下文学习为什么会出现?目前的理解还不完整。几个观点:

  • 预训练数据里有大量”一串相似的东西排在一起”的文本(列表、问答页面、表格),模型学会了”延续模式”;
  • 第 7 讲第 6.3 节的归纳头提供了一种”复制前面出现过的模式”的机制;
  • 有研究发现,例子的标签正确与否有时影响不大,而例子的格式和输入的分布影响很大(Min 等 2022)——模型更多是从例子里”认出任务”,而不是真的从例子里学习。

我们的迷你模型不会有这种能力:它只见过一条条孤立的评论,从没见过”例子 + 例子 + 问题”这样的文本,而且规模小了九个数量级。

8.4 涌现?

随着模型变大,一些能力似乎”突然出现”:小模型完全不会,过了某个规模之后迅速变好(Wei 等 2022,Emergent Abilities of Large Language Models)。但 Schaeffer 等(2023)指出,很多”涌现”可能是评估指标造成的错觉:用”完全答对才算对”这种不连续的指标,平滑的进步看起来就像突变;换成连续的指标(比如正确答案的对数概率),曲线往往是平滑的。这个问题仍有争论,第 13 讲还会讨论评估方法对结论的影响。


9. 规模:算力、规模定律、系统与数据

预训练最显著的特点是”大”。这一节只讲要点,详细内容见 CS336。

9.1 参数量和训练成本

第 7 讲给出了参数量的公式(每块 12d2+13d12d^2 + 13d)。训练的计算量有一个很好用的近似:

C≈6NDC \approx 6ND

NN 是参数量,DD 是训练的词元数。每个词元前向传播约 2N2N 次浮点运算(每个参数一次乘、一次加),反向传播约 4N4N 次(CS336 第 2 讲有推导)。

动手:代码 05_scaling_and_compute.py 演示 1 用第 7 讲的公式算出 GPT-2 四个版本的参数量:124,439,808、354,823,168、774,030,080、1,557,611,200,和公布的 124M、355M、774M、1.5B 完全吻合。我们的迷你 GPT 有 167,872 个参数,比 GPT-2 small 小约 700 倍。

演示 2(“1000 张 H100”按 bf16 峰值 989 TFLOP/s、40% 利用率粗略估算):

模型参数 NN词元 DDC=6NDC = 6ND1000 张 H100 约需几天
本讲的迷你 GPT1.7×1051.7 \times 10^54.3×1064.3 \times 10^64.4×10124.4 \times 10^{12}约 10−1010^{-10}
GPT-3(2020)1.75×10111.75 \times 10^{11}3.0×10113.0 \times 10^{11}3.15×10233.15 \times 10^{23}9.2
Chinchilla(2022)7×10107 \times 10^{10}1.4×10121.4 \times 10^{12}5.9×10235.9 \times 10^{23}17
LLaMA-2 7B(2023)7×1097 \times 10^{9}2×10122 \times 10^{12}8.4×10228.4 \times 10^{22}2.5
LLaMA-3 8B(2024)8×1098 \times 10^{9}1.5×10131.5 \times 10^{13}7.2×10237.2 \times 10^{23}21

GPT-3 的 3.15×10233.15 \times 10^{23} 次运算和论文报告的一致。实际花费远不止这些:还有调试、失败的实验、数据处理,以及当年更慢的硬件。

9.2 规模定律

Kaplan 等(2020)发现:语言模型的损失随参数量 NN、数据量 DD、算力 CC 按幂律下降——在对数坐标上几乎是直线,跨越好几个数量级。这意味着可以先在小模型上做实验,预测大模型的表现,再决定怎样分配预算。

Chinchilla(Hoffmann 等 2022)把损失写成

L(N,D)=E+ANα+BDβL(N, D) = E + \frac{A}{N^\alpha} + \frac{B}{D^\beta}

EE 是”不可约”的损失(文本本身的不确定性),后两项分别是”模型太小”和”数据太少”带来的损失。在 C=6NDC = 6ND 的约束下求最小值,结论是:算力增加时,参数量和数据量应该大致同比例增加,每个参数大约对应 20 个词元。按这个标准,GPT-3(每个参数不到 2 个词元)“模型太大、数据太少”;Chinchilla 用 700 亿参数、1.4 万亿词元,超过了 2800 亿参数的 Gopher。

动手:代码 05 演示 3 用两套拟合参数,算出不同算力下的最优分配:

算力 CC101910^{19}102110^{21}102310^{23}102510^{25}
D/ND/N(Hoffmann 等 2022 第 3 种方法的参数)32.149.978.3121.9
D/ND/N(Besiroglu 等 2024 的重新拟合)24.321.619.117.1

用 GPT-3 的算力,按重新拟合的参数:GPT-3 的分配预测损失 2.011;最优分配(约 530 亿参数、9900 亿词元)预测损失 1.995。

有意思的是:用原论文公布的第 3 种方法的参数,算出来的最优比例和”20”并不一致,而且随算力增长。Besiroglu 等(2024)重新分析了原论文的数据,发现原来的拟合有问题,重新拟合之后才和”约 20 个词元 / 参数”(原论文的另外两种方法得到的结论)吻合。规模定律是经验规律,具体的系数取决于数据、模型结构和拟合方法,要谨慎对待(CS336 第 9、11 讲)。

9.3 为什么今天的小模型都”训练过度”

Chinchilla 回答的是”训练算力固定时,怎样得到最好的模型”。但模型上线之后,推理的成本可能远超训练:每生成一个词元约需 2N2N 次运算,热门的产品要生成天文数字的词元。

动手:代码 05 演示 4。假设上线后总共生成 101310^{13} 个词元:70B 参数、按 Chinchilla 最优训练的模型,训练 5.9×10235.9 \times 10^{23} + 推理 1.4×10241.4 \times 10^{24},合计 2.0×10242.0 \times 10^{24};8B 参数、用 15 万亿词元”训练过度”的模型,训练 7.2×10237.2 \times 10^{23} + 推理 1.6×10231.6 \times 10^{23},合计 8.8×10238.8 \times 10^{23}。

所以今天常见的做法是:选一个比 Chinchilla 最优更小的模型,用多得多的数据把它”训练过度”(LLaMA-3 8B 每个参数上千个词元)——训练时多花一些算力,换来推理时省下大量算力。

9.4 训练系统(简介)

一个几百亿参数的模型,参数、梯度和优化器状态加起来要几百 GB 甚至上 TB,一张 GPU 放不下;训练要几百万 GPU 小时,必须用成千上万张 GPU 并行。主要的技术(详见 CS336 第 5–8 讲):

  • 数据并行:每张卡一份模型,处理不同的数据,再把梯度平均;ZeRO / FSDP 把参数和优化器状态切分到各张卡上,省显存。
  • 张量并行:把一个矩阵乘法切开,分到几张卡上同时算。
  • 流水线并行:把不同的层放在不同的卡上,像流水线一样传递。
  • 混合精度:用 16 位浮点数(bf16)计算,必要时保留 32 位的主副本。
  • 高效的注意力实现(FlashAttention,第 7 讲第 10.2 节)。

9.5 预训练数据(简介)

“数据决定了模型知道什么”。今天的预训练语料主要来自:网页(Common Crawl)、书籍、百科、学术论文、代码(GitHub)、论坛、多语言文本。从原始网页到可用的训练数据,要经过(详见 CS336 第 13、14 讲):

  1. 提取:从 HTML 里提取正文;
  2. 语言识别;
  3. 质量过滤:去掉垃圾、乱码、模板化的页面(常用一个”高质量文本”分类器打分,或者启发式规则);
  4. 有害内容和个人信息过滤;
  5. 去重:完全相同和近似重复的文档(MinHash 等)——重复数据会浪费算力,还会让模型死记硬背;
  6. 配比:网页、代码、书籍、数学等各占多少比例,对模型的能力影响很大;
  7. 去污染:去掉和评测基准重合的内容(第 13 讲;第 8 讲第 3.2 节的数据泄漏在大模型时代的样子)。

这些步骤听起来像”工程细节”,实际上是今天各家模型差别的主要来源之一。


10. 本讲小结

  1. 动机:标注数据少而贵,无标签文本取之不尽。先在无标签文本上预训练,再在少量标注数据上微调。
  2. 上下文相关的表示:静态词向量分不清一词多义和否定;语言模型的隐藏状态”读过”整个句子(ELMo),同一个词在不同的句子里得到不同的表示。
  3. 为什么有效:预测下一个词是一个包罗万象的任务,迫使模型学会语法、语义、事实、情感、推理;预训练还给了微调一个好的起点。
  4. 三种架构:编码器(BERT,掩码语言模型,80/10/10 规则)擅长理解;编码器—解码器(T5,span corruption,文本到文本)擅长序列到序列;解码器(GPT,因果语言模型)简单、训练信号多、能把一切写成生成,成为大模型的主流。
  5. 微调:全量微调、冻结 + 线性头、参数高效微调。预训练的价值在数据少、遇到没见过的词、分布偏移时最大;在目标领域的无标签文本上继续预训练很有效。
  6. 零样本与少样本:把任务写成续写,比较答案的概率;结果对提示很敏感,需要校准。大模型能上下文学习:只看提示里的几个例子就能照着做。
  7. 规模:C≈6NDC \approx 6ND;损失随规模按幂律下降;Chinchilla:参数和数据同比例增加(约 20 个词元 / 参数);考虑推理成本时,“训练过度”的小模型更划算。训练系统靠多种并行;数据的过滤、去重、配比至关重要。

11. 与作业的关系

往年的 Transformer 作业(Winter 2021 起的 Self-Attention, Transformers, and Pretraining,在不同年份是第 4 或第 5 个作业)的编程部分和本讲直接相关:

  1. 基于 minGPT 实现一个小型 Transformer;
  2. 先在维基百科文本上用 span corruption 式的目标预训练,再微调去回答”某人出生在哪里”这样的问题;
  3. 比较有无预训练的准确率:不预训练时,模型几乎只能靠猜(和”永远回答同一个城市”的基线差不多);预训练之后明显提高——因为人物的出生地这类事实知识藏在预训练文本里,微调只是教会模型”以这种格式把它说出来”。这和本讲第 3.2 节、第 7.3 节的道理完全相同。

Winter 2026 的作业 4(大语言模型的基准测试与评估)会用到本讲的零样本 / 少样本提示(第 8 节)。

作业的具体题目以当年官方发布的版本为准。


12. 练习

练习 1(掩码规则)★:一个句子有 40 个词元(不含特殊符号)。按 BERT 的规则,期望有多少个位置被选中作为预测目标?其中期望有几个被换成 [MASK]、几个换成随机词、几个保持不变?模型在多少个位置上计算损失?

参考答案

40×15%=640 \times 15\% = 6 个被选中;其中约 4.8 个换成 [MASK],0.6 个换成随机词,0.6 个保持不变。损失只在这 6 个位置上计算(包括保持不变的那些——模型并不知道它们被选中了)。

练习 2(为什么 80/10/10)★★:(a) 如果被选中的位置 100% 换成 [MASK],会出现什么问题?(b) 如果 100% 保持原词不变,会出现什么问题?(c) 如果 100% 换成随机词呢?

参考答案

(a) 预训练和使用不一致:微调时输入里没有 [MASK],模型可能只为 [MASK] 位置建立好的表示。 (b) 任务变得平凡:答案就是输入里的这个词,模型只要”抄”自己就行,什么也学不到。 (c) 模型会学到”不要相信输入里的词”,对每个词都要怀疑它是不是被替换过,可能损害对正常输入的表示;而且没有任何位置是”明确需要填空”的。80/10/10 是一个经验上效果好的折中。

练习 3(span corruption)★:对 “the plot was not boring . i loved it .”,如果删掉 “not boring” 和 “it .”,写出 T5 的输入和目标。

参考答案

输入:the plot was <X> . i loved <Y>;目标:<X> not boring <Y> it .(T5 还会在目标末尾加一个结束哨兵或结束符号)。

练习 4(选架构)★:下面的任务各适合哪种预训练架构?为什么?(a) 给一个句子判断它是否有语法错误;(b) 把一段英文翻译成中文;(c) 和用户聊天;(d) 为一亿个文档建立向量索引,用于检索。

参考答案

(a) 编码器(双向理解足够,模型小而快),当然大的解码器也能做。(b) 编码器—解码器的经典用途;今天大的解码器也做得很好。(c) 解码器(生成、对话)。(d) 编码器(每个文档编码成一个向量,高效;今天的嵌入模型很多是编码器或由解码器改造而来)。实际选择还取决于有没有现成的好模型和计算预算。

练习 5(训练成本)★:一个 1B(10910^9)参数的模型,训练 200B(2×10112 \times 10^{11})个词元。(a) 训练的计算量是多少?(b) 用 8 张 A100(每张 bf16 峰值 312 TFLOP/s,利用率 40%),大约要多少天?(c) 按 Chinchilla 的”20 个词元 / 参数”,这个模型的数据算多还是算少?

参考答案

(a) 6×109×2×1011=1.2×10216 \times 10^9 \times 2 \times 10^{11} = 1.2 \times 10^{21} 次。 (b) 每秒 8×312×1012×0.4≈10158 \times 312 \times 10^{12} \times 0.4 \approx 10^{15} 次,需要 1.2×1061.2 \times 10^6 秒,约 14 天。 (c) 20 个词元 / 参数对应 2×10102 \times 10^{10} 个词元,这里是 200 个词元 / 参数,按 Chinchilla 是”训练过度”——如果模型要大量部署,这是合理的选择(第 9.3 节)。

练习 6(规模定律)★★:用 Chinchilla 的形式 L=E+A/Nα+B/DβL = E + A/N^\alpha + B/D^\beta,(a) 数据无限多时,损失的下限是什么?(b) 模型和数据都无限大时呢?(c) 为什么 EE 不是 0?

参考答案

(a) E+A/NαE + A/N^\alpha:模型大小限制了能达到的损失。 (b) EE。 (c) 文本本身有不确定性:同样的前文,后面可以合理地接很多不同的词(“今天我想吃……”)。即使是”完美”的模型,也只能给出正确的概率分布,交叉熵不会降到 0。EE 衡量的就是这种不可约的不确定性(也受分词方式影响)。

练习 7(零样本)★:用语言模型做零样本情感分类,答案句是 “Overall, it was great.” 和 “Overall, it was terrible.”。你发现模型把 90% 的评论都判成正面,而测试集一半是负面。(a) 可能是什么原因?(b) 怎样改进?

参考答案

(a) 答案句本身的先验不同:“great” 在训练文本里比 “terrible” 常见得多,不管评论是什么,P(great) 都偏高。也可能是两句话的长度、分词不同带来的偏差。 (b) 校准:减去答案句在空输入(或无意义的输入,比如 “N/A”)后面的对数概率;或者在一个小的验证集上调整判定阈值;或者换更”对称”的答案词;或者用几个不同的答案句取平均。

练习 8(微调)★★:你有一个预训练模型和 100 条标注数据。(a) 全量微调和”冻结 + 线性头”各有什么风险?(b) 怎样用验证集在两者之间做选择?

参考答案

(a) 全量微调:参数多、数据少,容易过拟合;学习率太大可能破坏预训练的表示;结果对随机种子敏感。冻结 + 线性头:如果预训练的表示里没有任务需要的信息(比如任务很特殊),线性头无能为力。 (b) 从标注数据里留出一部分(或者用交叉验证,第 8 讲第 3.5 节)做验证集,两种做法各跑几个随机种子,比较验证集上的平均表现,选更好的那个,最后在测试集上评估一次。也可以试试折中的做法:参数高效微调(第 11 讲)。

练习 9(动手):

(a) 运行代码 02、03、04,把结果填进第 7.2 节的表格。你的结果和”预期的趋势”一致吗?如果不一致,你认为原因是什么? (b) 在代码 02 中把预训练轮数改成 1,预训练不充分时,代码 03 中”GPT 微调”的优势还在吗? (c) 在代码 03 中只用影评做预训练语料,演示 3 的跨领域结果会怎样变化?这说明了什么? (d) 在代码 04 中设计你自己的答案句,找到一组准确率最高的。这样”在测试集上挑答案句”有什么问题?(提示:第 8 讲第 3.3 节)

练习 10(思考):BERT 预训练时,[MASK] 位置的预测是相互独立的:比如 “New [MASK] [MASK] is a big city” 中两个被遮住的词分别预测。这会带来什么问题?因果语言模型有没有这个问题?

参考答案

两个被遮住的词实际上是相关的(“York City” 和 “Delhi”……要搭配起来),独立预测可能得到 “York Delhi” 这样不一致的组合。所以 BERT 不适合直接用来生成文本。因果语言模型按链式法则逐个预测,第二个词的预测以第一个词为条件,没有这个问题(这也是 XLNet 等工作想解决的问题之一)。


13. 延伸阅读

  • Peters et al.,Deep Contextualized Word Representations(2018):ELMo。
  • Howard & Ruder,Universal Language Model Fine-tuning for Text Classification(2018):ULMFiT。
  • Radford et al.,Improving Language Understanding by Generative Pre-Training(2018):GPT;Language Models are Unsupervised Multitask Learners(2019):GPT-2。
  • Devlin et al.,BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding(2019)。
  • Liu et al.,RoBERTa: A Robustly Optimized BERT Pretraining Approach(2019)。
  • Raffel et al.,Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer(2020):T5,系统比较了各种预训练目标和架构。
  • Brown et al.,Language Models are Few-Shot Learners(2020):GPT-3 与上下文学习。
  • Kaplan et al.,Scaling Laws for Neural Language Models(2020);Hoffmann et al.,Training Compute-Optimal Large Language Models(2022):Chinchilla;Besiroglu et al.,Chinchilla Scaling: A Replication Attempt(2024)。
  • Gururangan et al.,Don’t Stop Pretraining: Adapt Language Models to Domains and Tasks(2020)。
  • Petroni et al.,Language Models as Knowledge Bases?(2019)。
  • Zhao et al.,Calibrate Before Use: Improving Few-Shot Performance of Language Models(2021)。
  • Schaeffer, Miranda & Koyejo,Are Emergent Abilities of Large Language Models a Mirage?(2023)。
  • CS224N 课程笔记:Pretraining(John Hewitt 编写)。
  • CS336(同目录):第 1 讲(分词)、第 2 讲(资源核算)、第 5–8 讲(GPU 与并行)、第 9、11 讲(规模定律)、第 13、14 讲(数据)。

14. 下一讲预告

预训练给了我们一个”什么都知道一点”的语言模型。但如果你问它 “Explain the moon landing to a six-year-old”,一个只做过预训练的模型可能会续写出更多类似的问题,而不是回答——它只会续写,不会听指令。下一讲(后训练:SFT、RLHF、DPO)回答:怎样把一个只会续写的模型变成有用、诚实、无害的助手?

  • 指令微调(SFT)与对话模板;
  • 人类偏好数据与奖励模型(Bradley–Terry 模型);
  • RLHF 的流程与 PPO 的直觉;
  • DPO:从 RLHF 的目标出发,推导出一个不需要强化学习的闭式解;
  • 奖励黑客、过度优化、谄媚等问题。

我们会在本讲预训练好的迷你 GPT 上做这些实验:用对话模板教它听指令,再用(模拟的)人类偏好数据和 DPO,让它写出更符合偏好的正面评论。

← 返回课程目录