对应:CS224N 近几年的默认期末项目(实现精简版 GPT-2,并用于下游任务)。本讲不是官方讲座,而是把整门课串起来的一份”项目指南”。项目的具体要求每年会变化,以当年官方发布的项目说明为准。 配套代码:
code/lecture20/(需要 PyTorch;gpt2_minimal.py的--hf选项另外需要transformers和网络) 预计阅读时间:2.5 小时(真正做项目需要几周)
0. 本讲导航
0.1 学完本讲,你应该能够
- 说出默认项目的整体结构:实现 GPT-2 的关键组件 → 加载预训练权重并验证 → 在下游任务上微调 → 扩展与实验 → 写报告。
- 逐层说出 GPT-2 的结构、参数的名字和形状,以及它和第 7 讲迷你 GPT 的对应关系;算出 GPT-2 small 的 124,439,808 个参数从哪里来。
- 解释 GPT-2 的分词器(字节级 BPE),以及加载公开权重时要注意的细节(Conv1D 的转置、GELU 的近似、层归一化的 ε)。
- 实现 AdamW,并解释它和 Adam + L2 正则的区别。
- 把 GPT-2 用到分类、句对任务和文本生成上,知道每种任务常见的坑。
- 设计一组可信的实验(基线、消融、多个随机种子、置信区间、误差分析),写出一份好的项目报告。
0.2 先修知识
本讲用到几乎整门课的内容,最相关的是:第 7 讲(Transformer)、第 8 讲(实践方法)、第 9 讲(预训练与微调)、第 11 讲(LoRA)、第 13 讲(评估)、第 16 讲(分词)、第 19 讲(伦理考量)。
0.3 本讲地图
第1节 默认项目是什么
第2节 GPT-2 逐层回顾 ← gpt2_minimal.py
第3节 分词器与预训练权重:加载、验证、常见的错误 ← gpt2_minimal.py
第4节 优化器:AdamW ← 01_adamw_from_scratch.py
第5节 下游任务:分类、句对、生成
第6节 高效微调与训练技巧
第7节 扩展方向
第8节 实验设计
第9节 写报告
第10节 整门课回顾,以及接下来学什么
第11–12节 练习、延伸阅读
关于本讲的代码:两个文件都只做离线的自我检查(几秒钟),编写讲义时完整运行过;
gpt2_minimal.py --hf(下载权重并和 Hugging Face 的实现比对)需要transformers和网络,编写时的环境没有,没有运行过。
1. 默认项目是什么
CS224N 每年提供一个”默认项目”,给不想自己选题的同学一个结构清晰的起点。近几年的默认项目围绕 GPT-2 展开,大致包括:
- 实现:补全一个精简版 GPT-2 的关键代码:多头自注意力、Transformer 层、整个模型的前向传播,以及 AdamW 优化器;
- 验证:加载 OpenAI 公开的 GPT-2 预训练权重,确认你的实现和参考实现的输出一致,并且能生成通顺的文本;
- 下游任务:把预训练的 GPT-2 微调到几个任务上,比如情感分类、复述检测(判断两句话意思是否相同)、诗歌生成;
- 扩展:在基础版本上做改进(更好的微调方法、数据增强、多任务学习、参数高效微调……),用实验说明你的改进是否有效、为什么;
- 报告:写一份类似会议论文的报告。
具体用哪些任务、哪些数据集、评分标准是什么,每年都可能变化,请以当年的项目说明为准。下面的内容侧重于”不论具体要求怎样变都用得上”的知识和方法。
这个项目的价值在于把整门课串起来:第 7 讲的 Transformer、第 9 讲的预训练与微调、第 11 讲的高效适配、第 13 讲的评估、第 16 讲的分词,全都会用到。
2. GPT-2 逐层回顾
2.1 整体结构
GPT-2(Radford 等 2019)就是第 7 讲迷你 GPT 的”大号”版本:
词元编号 (B, T)
│ wte:词嵌入 (50257 × 768) wpe:位置嵌入 (1024 × 768)
▼
x = wte[idx] + wpe[0..T−1] → dropout
│
│ 重复 12 次(Block):
│ x = x + Attn(LN₁(x)) 多头因果自注意力:c_attn (768 → 2304)、c_proj (768 → 768)
│ x = x + MLP(LN₂(x)) 前馈网络:c_fc (768 → 3072)、GELU、c_proj (3072 → 768)
▼
ln_f:最后的层归一化
│
lm_head:输出层 (768 → 50257),权重和 wte 共享
▼
logits (B, T, 50257)
gpt2_minimal.py 实现了这个结构,参数的名字(transformer.wte.weight、transformer.h.0.attn.c_attn.weight……)和 Hugging Face 的 GPT-2 完全一致,这样就能直接装入公开的权重。
2.2 和第 7 讲的对应
| GPT-2 | 第 7 讲 transformer.py | 说明 |
|---|---|---|
wte、wpe | tok_emb、pos_emb | 可学习的位置嵌入,最长 1024 |
attn.c_attn、attn.c_proj | attn.qkv、attn.proj | 一次算出 Q、K、V;12 个头,每个 64 维 |
mlp.c_fc、mlp.c_proj | ffn.net[0]、ffn.net[2] | 中间层宽度 4D;GELU 用 tanh 近似 |
ln_1、ln_2、ln_f | ln1、ln2、ln_f | Pre-LN;ε = 1e-5 |
lm_head(和 wte 共享) | head(tie_weights=True) | 权重共享 |
GPT-2 相对于原始 Transformer(第 7 讲)的几处改动:Pre-LN(层归一化放在子层之前,并在最后加一个 ln_f)、GELU 激活、可学习的位置嵌入、权重共享,以及一个初始化细节:每个残差分支最后的投影层(c_proj)的初始化标准差再乘以 ( 是层数)。每一层都往残差流上加东西,层数越多加得越多,缩小这些投影层的初始值,残差流的方差就不会随层数增长。
2.3 参数量
第 7 讲推导过:一个块有 个参数。GPT-2 small(、12 层、词表 50257、位置 1024):
| 部分 | 参数 |
|---|---|
| 词嵌入 | 38,597,376 |
| 位置嵌入 | 786,432 |
| 12 个块 × | 85,054,464 |
| 最后的层归一化 | 1,536 |
| 输出层(和词嵌入共享,不另计) | 0 |
| 合计 | 124,439,808 |
gpt2_minimal.py 的自我检查实际构造了这个模型,数出的参数个数正好是 124,439,808。注意词嵌入占了三分之一,这是小模型的特点;模型越大,块的参数占比越高。
3. 分词器与预训练权重:加载、验证、常见的错误
3.1 分词器
GPT-2 用字节级 BPE(第 16 讲):基本单位是 256 个字节,加上 50,000 次合并,再加一个特殊词元 <|endoftext|>,一共 50,257 个词元。训练前先用一个正则表达式做预切分(把字母、数字、标点、空格分开,单词前面的空格并入单词),合并不跨越预切分的边界。所以 “Hello world” 被切成 “Hello” 和 ” world”(带前导空格)。
几个实际的注意事项(第 16 讲第 3.6 节):
- 前导空格很重要:” yes” 和 “yes” 是不同的词元。用”比较 yes / no 的概率”做分类时,要用模型在那个位置真正会生成的那个版本。
<|endoftext|>既是文档的分隔符,也常被用作填充符号;填充时一定要配合注意力掩码,并且在计算损失时忽略填充位置。
3.2 加载公开权重
OpenAI 的原始代码(以及 Hugging Face 的实现)用的是一种叫 Conv1D 的层,权重矩阵的形状是 (输入, 输出),而 PyTorch 的 nn.Linear 是 (输出, 输入)。加载时,c_attn、c_proj、c_fc 这四种权重要转置(gpt2_minimal.py 的 from_pretrained)。注意力里名叫 “bias” 的缓冲区其实是因果掩码,不是参数,要跳过。
3.3 怎样验证你的实现是对的
最可靠的办法是逐数比对:用同一个输入,比较你的模型和参考实现输出的 logits,最大差别应该在 以下(gpt2_minimal.py --hf 就做这件事)。如果对不上,逐层比对:先比嵌入层的输出,再比第一个块的输出……第一个对不上的地方就是 bug 所在。
更早的阶段,给每个模块写单元测试:
- 注意力:用很小的维度、固定的输入,和手算(或者一个最朴素的实现)比对;
- 因果性:改变最后一个词元,前面位置的输出不应该改变(
gpt2_minimal.py和第 7 讲都做了这个检查); - 初始损失:随机初始化时,预测下一个词元的损失应该约等于 (
gpt2_minimal.py的小配置:4.653,)。
编写 gpt2_minimal.py 的自我检查时,第一版把输入本身当作了目标(m(X, X)),初始损失只有 3.87,远低于 。原因是权重共享:残差流里保留着当前词元自己的嵌入,它和输出层的同一行做点积,模型在初始化时就偏向”预测自己”。改成预测下一个词元(m(X[:, :-1], X[:, 1:]))之后,又暴露了另一个 bug:切片得到的张量内存不连续,.view 会报错,要用 .reshape。一个”不对劲”的初始损失往往能揭示数据或实现的问题(第 8 讲第 4 节)。
3.4 常见的错误清单
| 症状 | 可能的原因 |
|---|---|
| 加载权重后输出乱码、损失很大 | 忘了转置 Conv1D 的权重;参数名对应错了 |
| 输出和参考实现差一点点( 量级) | GELU 用了精确版而不是 tanh 近似;层归一化的 ε 不同;注意力的缩放因子不对 |
| 训练时损失很低、生成却很差 | 因果掩码错了(模型偷看了后面的词元) |
| 批处理和逐条处理的结果不同 | 填充位置没有被掩码;位置编号对填充处理不当 |
| 微调后验证集表现时好时坏 | 评估时没有切换到 model.eval()(dropout 还开着) |
| 分类时取错了位置 | 右侧填充时,“最后一个词元”应该是最后一个真实的词元,而不是序列的最后一个位置 |
4. 优化器:AdamW
默认项目通常要求自己实现 AdamW(Loshchilov & Hutter 2019)。对每个参数 (梯度 ),第 步:
偏差修正:、 从 0 开始,前几步会偏小。 是 的指数滑动平均,,除以 就无偏了。
代码 01 的演示 1 把从零实现的 AdamW 和 torch.optim.AdamW 放在同一个小 GPT-2 上、用同样的数据训练 50 步,参数逐位相同(最大差别 0.0)。但编写时第一版用了一种数学上等价、运算顺序不同的写法(先算 、 再相除),50 步后的差别约为 :每一步只差 左右的浮点误差,但训练是一个”混沌”的过程,微小的差别会被一步步放大。比对自己的实现时,要么严格按参考实现的运算顺序写,要么只比较一步的结果。
为什么要”解耦”? Adam + L2 正则是把 加进梯度里,它会和损失的梯度一起被除以 。代码 01 的演示 2:两个参数的损失梯度都只是噪声,但大小不同,,学习率 0.001,训练 5000 步(20 次运行取平均):
| 参数 1(梯度小) | 参数 2(梯度大) | |
|---|---|---|
| Adam + L2 正则 | −0.001 | 0.624 |
| AdamW | 0.583 | 0.624 |
(AdamW 的理论值 。)在 Adam + L2 下,梯度小的参数很快被压到 0,梯度大的参数衰减得慢得多:同样的 ,正则化的强度却取决于参数的梯度有多大。AdamW 让所有参数按同样的比例衰减。今天训练 Transformer 几乎都用 AdamW;通常不对偏置和层归一化的参数做权重衰减。
5. 下游任务:分类、句对、生成
5.1 单句分类(如情感分类)
做法和第 9 讲 03 号文件、第 11 讲一样:取最后一个真实词元的隐藏状态(GPT-2 是因果模型,只有最后一个位置”读过”整句话),接一个线性分类头(gpt2_minimal.py 的 GPT2Classifier)。
- 右侧填充时,最后一个真实词元的位置 = 真实长度 − 1,要用注意力掩码排除填充;
- 预训练主体通常用比分类头小的学习率(比如主体 量级、分类头 量级),以免”冲掉”预训练学到的东西;
- 类别不平衡时,看宏平均 F1(第 13 讲)。
5.2 句对任务(如复述检测)
判断两句话是否意思相同。两种常见的做法:
- 分类头:把两句话拼起来(中间加分隔符),取最后一个词元的隐藏状态做二分类;
- 完形填空式(cloze):把任务写成一个提示,比如
Question 1: "..." Question 2: "..." Are these questions asking the same thing? Answer:,比较模型生成 ” yes” 和 ” no” 的概率(第 9 讲的零样本分类、第 11 讲的提示)。微调时只在答案词上计算损失。这种做法更接近预训练的目标,在数据少时往往更好。
注意拼接的顺序:两句话的地位对称,可以把 (A, B) 和 (B, A) 都放进训练数据(数据增强),测试时两个顺序取平均。
5.3 文本生成(如诗歌生成)
在目标风格的文本上继续训练语言模型(就是第 10 讲的 SFT,只是没有指令),再用第 5 讲的解码方法生成:
- 解码策略影响巨大:贪心解码容易重复;温度采样、top-k、top-p(核采样)在流畅和多样之间权衡;
- 评估是最难的部分:自动指标(chrF、BLEU 等和参考文本比对,第 13 讲)只能衡量很表面的相似,诗歌这种开放式的生成更需要人类评估或者大模型评委,而它们各有偏差(第 13 讲第 7 节);
- 结构约束(比如十四行诗的行数、韵脚)可以在解码时加入,也可以用强化学习、DPO 等方法(第 10 讲)优化。
6. 高效微调与训练技巧
- 全量微调还是 LoRA? GPT-2 small 只有 1.2 亿参数,全量微调在一张普通显卡上就可以;更大的版本(gpt2-large、gpt2-xl)或者显存有限时,用 LoRA(第 11 讲)。
gpt2_minimal.py的自我检查演示了怎样用第 11 讲的peft.add_lora给 GPT-2 的注意力层加 LoRA(参数名以attn.c_attn、attn.c_proj结尾)。比较全量微调和 LoRA 的效果、显存、速度,本身就是一个好的扩展实验。 - 学习率:微调的学习率比预训练小得多;用预热 + 衰减(第 7 讲的
warmup_cosine);不同层可以用不同的学习率(越靠近输出层越大,逐层学习率衰减)。 - 混合精度:用 bf16 训练能节省显存、加快速度(第 11 讲第 7.1 节)。
- 梯度累积:显存不够放大批量时,多算几个小批量再更新一次。
- 早停与检查点:在验证集上选最好的模型;测试集只在最后用一次(第 8 讲)。
- 先在一小批数据上过拟合(第 8 讲):确认模型、损失和数据管线都没问题,再开始正式训练。
7. 扩展方向
在基础版本跑通之后,项目的核心是扩展:提出一个改进,并用实验说明它是否有效、为什么。一些和本课内容相关的方向:
| 方向 | 相关讲次 | 例子 |
|---|---|---|
| 更好的微调 | 第 9、11 讲 | 逐层学习率衰减、正则化(如对抗扰动的平滑正则)、先在任务领域的无标签文本上继续预训练 |
| 参数高效微调 | 第 11 讲 | LoRA、适配器、提示微调的比较;不同的 、放在哪些层 |
| 数据 | 第 8 讲 | 数据增强(回译、同义改写)、清洗标注噪声、课程学习 |
| 多任务学习 | 第 9 讲 | 几个下游任务共享主体、交替训练,看任务之间是互相帮助还是互相干扰 |
| 对比学习 | 第 12、18 讲 | 用对比损失学习句子表示,用于复述检测 |
| 提示与上下文学习 | 第 11 讲 | 完形填空式的任务表述、少样本提示,和微调比较 |
| 偏好优化 | 第 10 讲 | 用 DPO 让生成更符合某种偏好(比如更押韵) |
| 推理与测试时计算 | 第 14、15 讲 | 采样多个候选、用验证器挑选 |
| 分析 | 第 13、17 讲 | 误差分析、探针、注意力和归因分析:模型到底学到了什么 |
好的扩展有一个清楚的假设(“我认为 X 会改善 Y,因为 Z”),并且设计实验去检验它,而不是把很多技巧堆在一起看分数有没有涨。
8. 实验设计
第 8、13 讲的方法在这里全部用得上:
- 基线:先建立可靠的基线(基础版本的微调、简单的非神经模型)。没有基线,任何改进都无从谈起。
- 一次只改一个变量:每个实验只改变一件事,才能知道效果来自哪里。
- 消融实验:你的方法有好几个组成部分时,逐个去掉,看每个部分贡献了多少。
- 多个随机种子:同样的设置跑 3~5 个随机种子,报告均值和标准差。随机种子带来的波动常常比方法之间的差别还大(第 8 讲第 6 节)。
- 置信区间与显著性:测试集上的分数要有误差棒;比较两个方法时用配对检验(第 13 讲第 6 节)。
- 公平的比较:给每种方法同样的调参预算、同样的计算预算;不要只给自己的方法仔细调参。
- 误差分析:看模型在哪些例子上出错,错误有什么共同点。好的误差分析常常比分数本身更有价值,也能启发下一个改进。
- 记录与可复现:记录每个实验的配置、随机种子、代码版本和结果(用 wandb 等工具,或者至少一个表格);报告里写清楚怎样复现。
计算预算:在动手之前估算一下每个实验要多长时间、一共要跑多少个实验,确保在截止日期之前能完成。先在小数据、小模型上快速迭代,确定方向之后再做完整的实验。
9. 写报告
9.1 结构
一份好的项目报告大致和一篇会议论文一样:
- 摘要:一段话说清楚问题、方法、主要结果;
- 引言:问题是什么、为什么重要、你做了什么、主要发现;
- 相关工作:已有的方法,你的工作和它们的关系;
- 方法:清楚到别人可以复现;用公式和图说明你的模型;
- 实验:数据、评估指标、基线、实验设置(超参数、计算资源)、结果(表格带误差棒);
- 分析:消融、误差分析、可视化,解释为什么有效或无效;
- 结论:总结发现、局限性、未来的工作;
- 伦理考量(第 19 讲):数据的来源和潜在偏见、可能的误用、对哪些人群效果可能更差。
9.2 一些建议
- 结论要被证据支持:不要说”显著提高”,除非做了显著性检验;不要从一个随机种子的结果得出结论。
- 负面结果也有价值:一个精心设计、但没有提升的实验,只要分析清楚了原因,同样是好的研究。如实报告,不要只挑好看的结果。
- 图表自明:每张图、每个表都要有清楚的标题和说明,读者不看正文也能看懂。
- 先写大纲:先确定”我要说服读者相信什么”,再安排实验和段落。
- 留出时间修改:初稿写完之后,隔一天再读一遍,常常能发现很多不清楚的地方。
10. 整门课回顾,以及接下来学什么
10.1 二十讲的主线
词怎么变成向量(1–2) → 神经网络怎么学习(3) → 句子的结构(4)
↓
序列建模:n 元模型 → RNN → LSTM → 注意力(5–6) → Transformer(7) → 怎么做实验(8)
↓
大语言模型:预训练(9)→ 后训练(10)→ 高效适配(11)→ 检索与智能体(12)→ 评估(13)
→ 推理:思维链(14)→ 验证与测试时计算(15)
↓
专题:分词与多语言(16)、可解释性(17)、多模态(18)、社会影响与开放问题(19)
↓
把它们串起来:精简 GPT-2 与下游任务(20)
贯穿始终的几个问题:
- 知识放在哪里? 规则 → 统计 → 参数 → 外部检索(第 1、12 讲);
- 为什么规模有效,规模不能解决什么?(第 9、15、19 讲);
- 怎样知道模型真的会了? 评估、可解释性、忠实性(第 13、14、17 讲);
- 怎样负责任地构建和使用这些技术?(第 19 讲)。
10.2 接下来学什么
- CS336(同目录):从零构建语言模型的系统与工程:分词器、GPU 与并行、规模定律、数据管线、对齐的大规模实现;
- CME295(同目录):Transformer 与大语言模型的另一个视角;
- CS234(同目录):强化学习,理解 RLHF、GRPO 背后的理论;
- 读论文、做项目:跟进 ACL、EMNLP、NeurIPS、ICLR 等会议的论文;在一个你感兴趣的问题上,从复现一篇论文开始。
11. 练习
练习 1(参数量)★:算出 GPT-2 medium(24 层、、16 个头、词表 50257、位置 1024)的参数量。词嵌入占多大比例?和 GPT-2 small 比呢?
参考答案
词嵌入 ;位置嵌入 ;每个块 ,24 个块共 ;ln_f 。合计 ,约 3.55 亿。词嵌入占约 14.5%;GPT-2 small 里占约 31%。模型越大,块的参数占比越高。
练习 2(加载权重)★:(a) 为什么加载 GPT-2 权重时,c_attn.weight 要转置,而 wte.weight 不用?(b) 如果忘了转置 c_attn.weight,代码会报错吗?
参考答案
(a) c_attn 在原始实现里是 Conv1D,权重形状 (768, 2304) = (输入, 输出),nn.Linear 需要 (2304, 768) = (输出, 输入);wte 是嵌入矩阵,两边的形状都是 (50257, 768),含义相同。
(b) c_attn 不是方阵,形状对不上,copy_ 会报错(gpt2_minimal.py 里也有形状断言)。但 attn.c_proj.weight 是 768 × 768 的方阵,忘了转置不会报错,模型能运行,输出却是错的。这是最隐蔽的一类 bug,只有逐数比对才能发现。
练习 3(AdamW)★★:(a) 第 1 步()时,没有偏差修正的 Adam 更新量是多少?有偏差修正呢?(设 、,忽略 和权重衰减,梯度为 。)(b) 解释为什么 Adam + L2 正则下,梯度大的参数衰减得慢。
参考答案
(a) ,。没有修正:更新量 ,步长被放大了约 3 倍(因为 被低估得更厉害)。有修正:,,更新量 。 (b) L2 正则的梯度 加在损失梯度上,整体被除以 ;梯度大的参数 大, 小,衰减就弱。AdamW 把衰减项移到了除法之外。
练习 4(分类的位置)★:一批句子右侧填充到长度 20,第一句的真实长度是 7。(a) 用 GPT-2 做分类时,应该取第几个位置的隐藏状态?(b) 如果错误地取了第 20 个位置,会怎样?(c) 如果用左侧填充呢?
参考答案
(a) 第 7 个位置(下标 6),最后一个真实词元。 (b) 第 20 个位置是填充符号,它的隐藏状态混合了真实词元和填充的信息(如果掩码处理得当,它看到的是真实词元,但它自己的输入是填充符号,和训练时的分布不同),分类效果会变差,而且不同长度的句子之间不一致。 (c) 左侧填充时,最后一个真实词元总在序列的最后一个位置,可以直接取最后一个位置;但位置编号要处理好(真实词元的位置应该从 0 开始,而不是从填充的个数开始),否则位置嵌入会错位。
练习 5(实验设计)★★:你提出了一个新的微调方法,在验证集上比基线高 1.2 个百分点(单个随机种子)。在写进报告之前,你还应该做哪些事?
参考答案
① 多跑几个随机种子(基线和新方法都跑),报告均值和标准差;② 在测试集上(只评估一次)用配对的方法算差值的置信区间或做显著性检验;③ 确认两者的调参预算、训练步数、计算量相同;④ 做消融,确认提升来自你认为的那个组成部分;⑤ 做误差分析,看提升出现在哪类例子上;⑥ 如果有多个下游任务,看提升是否一致。1.2 个百分点很可能在随机种子的波动范围之内(第 8 讲)。
练习 6(报告):为你的项目写一段”局限性与伦理考量”(第 19 讲),至少涉及数据、评估和可能的误用三个方面。
参考答案
开放题。一个例子(情感分类项目):数据来自电影评论网站,评论者以英语母语、特定年龄段的人群为主,模型在其他领域、方言或非母语者的文本上可能表现更差;评估只用了一个测试集和准确率,没有按人群拆开评估,也没有测试对否定、反讽的鲁棒性;情感分析可能被用于监控舆论或针对特定人群的言论,部署前应该评估不同人群上的错误率,并明确用途的边界。
练习 7(动手):
(a) 安装 transformers 后运行 python gpt2_minimal.py --hf,确认 logits 的最大差别在 以下,并看看贪心生成的文本。
(b) 故意制造练习 2(b) 里的 bug(不转置 attn.c_proj.weight),看输出会差多少、生成的文本变成什么样。
(c) 用 gpt2_minimal.py 加载 GPT-2,在第 8 讲的玩具影评上做情感分类:比较只训练分类头、全量微调、LoRA 三种做法的准确率和训练时间。
(d) 在代码 01 里实现”不对偏置和层归一化做权重衰减”(用两个参数组),和对所有参数做衰减的结果比较。
12. 延伸阅读
- Radford et al.,Language Models are Unsupervised Multitask Learners(2019):GPT-2。
- Vaswani et al.,Attention Is All You Need(2017)。
- Loshchilov & Hutter,Decoupled Weight Decay Regularization(2019):AdamW。
- Kingma & Ba,Adam: A Method for Stochastic Optimization(2015)。
- Hu et al.,LoRA(2021)。
- Andrej Karpathy 的 nanoGPT 和 “Let’s build GPT” 系列视频:从零实现 GPT-2 的优秀教程。
- CS224N 历年的默认项目说明和优秀项目报告(课程主页上可以找到)。
- Dodge et al.,Show Your Work: Improved Reporting of Experimental Results(2019):怎样可信地报告实验结果。
结语
这门课从”一个词怎么变成向量”开始,到”怎样构建、评估和负责任地使用大语言模型”结束。技术会继续快速变化,今天的方法几年后可能就过时了;但一些东西不会变:理解问题本身(语言有什么特点、任务真正要求什么)、对实验结果保持怀疑(基线、误差棒、消融)、追问”为什么有效”、以及思考技术对人的影响。希望这些讲义和代码能成为你继续探索的起点。