A5 · Final Project(期末项目)

年份 2026(W26)—— 往年版本可能不同,见作业说明
分值 49%
截止 见日程(proposal → milestone → report)
算力 Default(Build GPT-2)需要 GPU;Custom 视题目而定
配哪几讲 候选(机器按术语匹配,供参考):第5讲 §五、Transformer 解码器:多头注意力与训练技巧;第5讲 §二、注意力:一种「软」的键值查找;第6讲 §六、2024 年之后的 NLP:从造架构到用模型;第6讲 §五、如何找题目:钉子、锤子与往届项目;第11讲 §1. 为什么要评测:一条开发流水线上的五种需求;第11讲 §9. 一致性、污染与偏差:评测的三大挑战

做什么

题面来自官方材料(本地原件在 _local/,官方链接见「材料」)。 编号是原件的编号(notebook 是 cell 序号、PDF 是页码),方便跟官方题面对号。 本项目不提供答案(D53)。

第 1 页

CS 224N:默认最终项目:构建 GPT-2 (Default Final Project: Build GPT-2)

目录 (Contents)

第 2 页

1 概述 (Overview)

在本作业中,你将构建 GPT-2,即 OpenAI 的 ChatGPT 语言模型的前身。具体来说,你将实现该架构中一些最重要的组件,从 HuggingFace 加载官方模型权重到你的实现中,并探索其在各种下游应用上的能力。这个默认最终项目分为两部分。

在第一部分中,你将填写缺失的代码块以完成 GPT-2。类似地,你将通过补全 Adam 优化器(用于训练语言模型的算法)的 step function 来实现它的一部分。最后,你将探索在两个 Sentiment Analysis 数据集上微调你的实现——即预测各种句子的情感(正面、负面、中性)——从而有效地把你的生成式语言模型转变为分类模型。

在第二部分中,你将微调你的模型来完成复述检测(paraphrase detection),即预测一个句子是否是另一个句子的复述。然而,你不是将模型微调为二分类,而是将其表述为完形填空式任务:当询问一个句子是否是另一个句子的复述时,生成单词“yes”或“no”。接下来,你将使用十四行诗数据集微调你的模型来完成诗歌生成任务。这将向你介绍多 token 生成。你将在一组更广泛的指标上评估模型生成的十四行诗,并对一些输出进行定性分析。

关于默认项目与自定义项目的说明 (Note on default project vs custom project): 投入默认最终项目的精力/工作量/难度,并不打算比自定义项目更少。只是,围绕自己提出问题与评估方法的那种特定难度被有意排除,让学生能够把同等量的精力集中在这个已提供的问题上。

1.1 项目理念 (Project Philosophy)

尽管你并不被要求实现原创内容,但最好的项目会追求某种形式的原创性(事实上,未来可能成为研究论文)。原创性不一定必须是完全全新的方法——对现有模型做小而动机充分的改动非常有价值,尤其是如果随后有良好的分析。如果你能定量和定性地证明你小而原创的改动改进了一个 state-of-the-art 模型(甚至更好的是,解释它解决了什么特定问题以及如何解决),那么你就做得非常出色了。

与自定义最终项目一样,默认最终项目是开放式的——需要你自己弄清楚要做什么。在许多情况下,做某事没有唯一正确答案——需要通过实验来确定哪种方式最好。我们期望你运用到目前为止从课程中获得的判断力和直觉来构建你的模型。关于评分标准的更多信息,见第 9 节。

注意: 本文件只描述默认最终项目的代码部分。关于书面部分的更多细节,见课程网站和讲义《CS224n: Project Proposal Instructions》。

2 任务描述 (Description of Tasks)

在本节中,我们描述你将需要探索的任务。

第 3 页

2.1 情感分析 (Sentiment Analysis)

语言理解中的一项基本任务是对文本的极性进行分类(即文本中表达的观点是正面、负面还是中性)。例如,情感分析可用于确定个人对特定产品、政治人物或新闻报道中的感受。

作为一个具体的数据集例子,Stanford Sentiment Treebank[注 1] [1] 由从电影评论中提取的 11,855 个单句组成。该数据集用 Stanford parser[注 2] 进行解析,共包含来自这些解析树的 215,154 个唯一短语,每个短语由 3 位人类评审员标注。每个短语都有一个标签:负面、有些负面、中性、有些正面或正面。

Movie Review: Light, silly, photographed with colour and depth, and rather a good time. 影评(中文): 轻松、愚蠢,以色彩和景深拍摄,相当愉快的时光。 Sentiment: Positive(正面)

Movie Review: Opening with some contrived banter, cliches and some loose ends, the screenplay only comes into its own in the second half. 影评(中文): 以一些做作的玩笑、陈词滥调和一些松散的线索开场,剧本直到后半段才真正出彩。 Sentiment: Neutral(中性)

Movie Review: ... a sour little movie at its core; an exploration of the emptiness that underlay the relentless gaiety of the 1920’s ... The film’s ending has a “ What was it all for?” 影评(中文): ……一部骨子里酸涩的小电影;一次对 1920 年代无休止欢愉之下空虚的探索……影片结尾有一句“这一切究竟为了什么?” Sentiment: Negative(负面)

2.2 复述检测 (Paraphrase Detection)

复述检测(Paraphrase Detection)是在大型段落语料库中寻找文本复述的任务。复述是“对他人书面或口头内容的改写”;因此,复述检测本质上试图确定特定单词或短语是否传达相同的语义 [2]。从研究角度看,复述检测是一项有趣的任务,因为它提供了一种衡量系统“理解”细粒度语义概念程度的指标。

作为一个具体的数据集例子,网站 Quora[注 3] 经常收到与其他问题重复的问题。为了更好地重定向用户并防止不必要的工作,Quora 发布了一个数据集,标注不同问题是否互为复述。

Question Pair: (1) "What is the step by step guide to invest in share market in india?", (2) "What is the step by step guide to invest in share market?" 中文: (1)“在印度股市投资的逐步指南是什么?” (2)“股市投资的逐步指南是什么?” Is Paraphrase: No(否)

Question Pair: (1) "I am a Capricorn Sun Cap moon and cap rising...what does that say about me?", (2) "I’m a triple Capricorn (Sun, Moon and ascendant in Capricorn) What does this say about me? 中文: (1)“我是摩羯座太阳、摩羯座月亮和摩羯座上升……这说明了我的什么?” (2)“我是三重摩羯座(太阳、月亮和上升都在摩羯座)这说明了我的什么?” Is Paraphrase: Yes(是)

2.3 十四行诗生成 (Sonnet Generation)

十四行诗生成是一项更开放式的任务,我们把它作为现实文本生成任务的一个例子提供给你,你可能想用语言模型来完成这类任务。十四行诗是一种具有相对严格结构的诗体,因此适合小型语言模型学习。我们希望看到你的模型在微调后生成哪些有趣的新十四行诗!

下面的例子是威廉·莎士比亚的第 5 首十四行诗。注意交替押韵格式和 14 行的长度。

[注 1] https://nlp.stanford.edu/sentiment/treebank.html [注 2] https://nlp.stanford.edu/software/lex-parser.shtml [注 3] https://www.quora.com/q/quoradata/First-Quora-Dataset-Release-Question-Pairs

第 4 页

那些时光,以轻柔的工巧,
塑造出人人凝望的可爱目光,
终将变成暴君,对那同一副容颜,
并对那本以美取胜者施以不公:
因为永不停歇的时间把夏天引向
狰狞的冬天,并在那里将它摧折;
汁液被霜冻遏制,繁茂的叶子尽去,
美被雪覆盖,到处一片荒芜:
那么,若夏天未留下它的蒸馏物,
一滴被囚在玻璃墙中的液体,
美的效果便会随美一同丧失,
既无它自身,也无对它曾为何物的记忆:
但被蒸馏的花朵,虽与冬天相遇,
失去的只是外表;其精髓仍甜美地活着。

3 GPT: Generative Pretrained Transformer

GPT(Generative Pretrained Transformer)模型基于这样的思路:使用大量未标注文本,以无监督方式 pre-train 一个 decoder-only Transformer 架构,然后在下游任务上进行 fine-tuning。

GPT-1 由 OpenAI 于 2018 年在 [3] 中提出,是一个 decoder-only Transformer,以单向方式对文本建模,也就是说,它在每一层只对左侧上下文做 attention 来预测下一个词。它是最早的一批大规模语言模型之一,证明了在大量文本上进行纯无监督 pre-training,随后进行任务特定的 fine-tuning,可以显著提升各种 NLP 任务(如 question answering 或 sentiment analysis)的性能。关键洞见在于:通过先从原始文本中学习一般的语言和上下文模式,模型获得了“language understanding”,这对 sentiment classification 或 paraphrase detection 等不同应用广泛有用。

GPT-2 [4] 在 2019 年延续了这一趋势,大幅扩展了 GPT-1 的规模,并用多一个数量级的数据训练它。具体而言,差异如下:

GPT-1 和 GPT-2 对 NLP 研究和实践产生了变革性影响。它们展示了大规模无监督 pre-training 能在许多下游任务上带来显著提升,从而帮助推广了“pre-train, then fine-tune”范式。尤其是 GPT-2,展示了扩大规模——无论是在

第 5 页

数据还是模型规模方面——如何带来模型能力和语言生成质量的惊人飞跃。这激发了对更大模型(如 GPT-3)的进一步探索,并为强大语言模型的兴起奠定了基础;这些模型被用于从对话代理到创意文本生成的大量应用中。一个例证是:本节整段内容实际上是由 ChatGPT 撰写的,作者仅做了少量编辑。

第 6 页

4 Getting Started

本项目需要一台带 GPU 的机器来高效训练模型。为此,你可以使用 Google Cloud,与 Assignment 4 和 5 类似。

我们建议你在本地机器(或 Stanford 的某台机器,如 rice)上使用不带 GPU 的 PyTorch 开发代码,只有在调试完代码并准备好训练之后,才迁移到 Google Cloud VM。我们建议使用一个私有 GitHub repository 来管理代码库,并在两台机器之间以及团队成员之间同步文件。第一次按照本节 Getting Started 操作时,请在本地机器上进行。之后你将在 Google Cloud VM 上重复该过程。

当你处在合适的机器上后,在以下位置 clone 项目 GitHub repository:

https://github.com/stanfordnlp/cs224n_gpt

该 repository 包含 starter code 以及我们将使用的 GPT 模型的最小实现。我们鼓励你 git clone 我们的 repository,而不是简单下载,这样你可以轻松合并我们做出的任何 bug fix。事实上,你应该定期检查是否有需要下载的新修复。为此,进入 cs224n_gpt 目录并运行 git pull 命令。

如果你使用 GitHub 管理代码,必须将 repository 保持为 private。

4.1 Code overview

repository cs224n_gpt/ 包含以下文件:

第 7 页

此外,还有两个目录:

4.2 Setup

当你处在合适的机器上并已 clone 项目 repository 后,就可以运行 setup 命令了。

第 8 页

5 实现 GPT-2

我们已经为你提供了实现 GPT-2 的若干构建模块。本节将描述 GPT-2 模型,以及其中需要你实现的部分。

5.1 GPT-2 模型的细节

GPT-2 是一个 decoder-only transformer 模型,它使用先前 token 的序列来预测下一个 token。下面我们将带你过一遍 GPT-2 模型,并概述它如何训练。

Tokenization

GPT-2 模型使用 byte pair encoding (BPE) tokenization。虽然其功能被 Transformer library 的 Tokenizer 类隐藏起来,感兴趣的同学可以查看 Hugging Face 教程,以理解单词如何被分解成 token。也可以试用 OpenAI 提供的这个可视化工具,它展示了 OpenAI 最新模型用于对句子进行分词的 BPE。

图 1 图内文字: - Embedding Layer - Tokenize into BPE - Add position embeddings - Output the result - 输入句子:Hello, my name is Chris - Token Embeddings - Position Embeddings

图 1:GPT-2 中使用的 embedding layer。一个输入句子(或多个句子)会通过 byte pair encodings 分词成 token。可学习的位置 embeddings 被加到 token 上,结果从 embedding layer 输出。

Embedding Layer

在分词并将每个 token 转换为 id 之后,GPT-2 随后对每个 token 使用一个可训练的 embedding layer。后续部分使用的 input embeddings 是 token embeddings 与 position embeddings 之和。每个 embedding layer 的维度为 768,GPT-2 在单个样本中最多可以处理 1024 个 token。

可学习的 token embeddings 将各个 input id 映射为向量表示,以供后续使用。更具体地说,给定一些输入 token 索引[注 4] $w_1,\ldots,w_k\in\mathbb{N}$,embedding layer 会执行 embedding lookup,将这些索引转换为 token embeddings $v_1,\ldots,v_k\in\mathbb{R}^D$。

position embeddings 用于编码输入中不同单词的位置。与 token embeddings 一样,position embeddings 也是学习得到的 embeddings,它们是为 GPT-2 中 1024 个位置中的每一个、针对给定输入而学习的。

[注 4] token 索引是一个整数,它告诉你 embedding matrix 的哪一行(或哪一列)包含该单词的 embedding。

第 9 页

(接上页)我们称最后这个数字为“context length”;它表示模型在单个样本中一次最多能处理的 token 数。

图 2 图内组件: - Masked Multi-Headed Self-Attention - Layernorm - MLP - Layernorm

图 2:GPT-2 transformer layer。将 12 个这样的层依次堆叠,构成你将实现的(小型)GPT-2 版本。

GPT-2 Transformer Layer(gpt2_layer.GPT2Layer)

GPT-2(小版本)使用 12 个 Decoder Transformer 层。这些层最初在 Attention is All You Need [5] 中定义。GPT-2 模型的 Transformer 层如图 2 所示。对于这两个模型,它都由 masked multi-head attention、skip connections、multi-layer perceptron (MLP) 和 layernorm 层组成。

图 3:Scaled Dot-Product 与 Multi-Head Self-Attention。图来自 [5]。

第 10 页

Multiheaded Self-Attention

Multi-head Self-Attention 由在多个不同 head 上应用的 scaled-dot product 组成。具体来说,每个 head 的输入是一个 scaled-dot product,其中 queries 和 keys 的维度为 $d_k$,values 的维度为 $d_v$。GPT 计算 query 与所有 key 的点积,将每个点积除以 $\sqrt{d_k}$,然后应用 softmax 函数以获得 values 上的权重。在实践中,GPT 会同时对一组 queries 计算 attention 函数,这些 queries 被打包成矩阵 $Q$。keys 和 values 也分别被打包成矩阵 $K$ 和 $V$。因此,scaled dot-product attention 计算为:

$$ \operatorname{Attention}(Q,K,V)=\operatorname{Softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V $$

Multi-head attention 允许模型在不同位置联合关注来自不同表示子空间的信息。如果只有单个 attention head,平均化会抑制这一点。Multi-head attention 计算为:

$$ \operatorname{MultiHead}(Q,K,V)=\operatorname{Concat}(\operatorname{head}_1,\ldots,\operatorname{head}_h)W^O $$

其中

$$ \operatorname{head}_i=\operatorname{Attention}(QW_i^Q,KW_i^K,VW_i^V) $$

其中这些投影是参数矩阵 $W_i^Q\in\mathbb{R}^{d_{\text{model}}\times d_k}$、$W_i^K\in\mathbb{R}^{d_{\text{model}}\times d_k}$、$W_i^V\in\mathbb{R}^{d_{\text{model}}\times d_v}$,以及 $W^O\in\mathbb{R}^{h d_v\times d_{\text{model}}}$。

图 4 图内标签: - Attention Weights - Attention Mask - Post-Mask Weights

图 4:multi-headed self-attention 中在 softmax 归一化前进行 causal masking 的示例。

Masked Multiheaded Self-Attention(attention.CausalSelfAttention.attention)

GPT-2 不使用 multi-headed self-attention,而是使用 masked(等价地,causal)变体,以防止 token 关注未来位置。这是为了防止 token 在训练期间学会查找它们的 label,即紧邻的下一个 token。Masked multi-head self-attention 在 softmax 之前对 attention weights 应用 upper-triangular mask(torch.triu),以将未来位置的权重降低到约等于零。

Position-wise Feed-Forward Networks

除了 attention sublayer,每个 transformer layer 还包括两个带有 ReLU 激活函数的线性变换 [6]。

$$ \operatorname{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2 $$

因此,如本节所述,multi-head attention 包括:

  1. 使用对应的线性层对 queries、keys 和 values 进行线性投影。也就是说,对于每个 word piece embedding,GPT-2 创建一个维度为 $d_k$ 的 query 向量、一个同样维度为 $d_k$ 的 key 向量,以及一个维度为 $d_v$ 的 value 向量。

第 11 页

  1. 将向量拆分以进行 multi-head attention。
  2. 按照 Attention 公式计算每个 head 的 attended output。
  3. 拼接 multi-head attention 的输出,以恢复原始形状。
Dropout

最后我们注意到,GPT-2 在每个 attention layer 之后以及每个 MLP 之后、residual connection 之前都会应用 dropout。GPT-2 还会对 embeddings 与 positional encodings 的和应用 dropout。GPT-2 使用的设置为 $p_{\text{drop}}=0.1$。

GPT-2 output

如本节通篇所述,GPT-2 由以下部分组成:

  1. 一个 embedding layer,由 token embedding token_embedding 和 positional embedding pos_embedding 组成。
  2. GPT-2 layers,即由 12 个 GPTLayer(config.num_hidden_layers)堆叠而成。

经过相应层之后,输出包括:

  1. last_hidden_state:来自最后一个 GPTLayer 的、句子中每个 token 的 contextualized embedding(即 GPT-2 transformer layers 的输出)。
  2. last_token:最后一个 token embedding。
Training GPT-2

GPT-2 [4] 使用 next-token prediction 训练,即在给定先前上下文的情况下预测下一个单词。对于一个 token 序列 $x_1,x_2,\ldots,x_n$,模型被训练以最大化对数似然:

$$ \log P(x_1,x_2,\ldots,x_n) = \log \prod_{i=1}^n P(x_i\mid x_1,x_2,\ldots,x_{i-1}) = \sum_{i=1}^n \log P(x_i\mid x_1,x_2,\ldots,x_{i-1}) $$

目标是利用大规模 next-token prediction 预训练来教会模型通用的语言理解能力,并赋予它生成连贯文本的能力。之后,可以对该模型进行 fine-tune,以执行各种任务,例如情感分类、复述检测等,利用其预训练好的语言理解能力来解决这些任务。

与 2010 年代的其他语言模型不同,GPT-2 大幅增加了模型规模(从 GPT-1 的 117M 参数增加到 GPT-2 的 1.5B 参数)和训练样本数量。GPT-2 表明,autoregressive transformer 模型表现出强大的 scaling laws:性能随模型规模、数据集规模和训练计算预算呈线性-对数缩放。

5.2 需要实现的代码:Masked Multi-head Self-Attention 与 GPT-2 层

我们已经为你提供了 GPT-2 baseline 模型的大部分代码。在过完 GPT-2 Transformer 模型的基本结构之后,现在我们将描述需要实现的部分:

Masked Multi-head Self-Attention(attention.CausalSelfAttention.attention)

你应该实现的第一个函数是 masked multi-head attention layer。该层将一个 query 和一组 key-value pairs 映射到一个输出。输出计算为 values 的加权和,其中每个 value 的权重由一个函数计算,该函数接收 query 和对应的 key。你可以在 attention.CausalSelfAttention.attention 中实现这个 attention 函数。

第 12 页

GPT-2 Transformer Layers — modules.gpt2_layer.py

实现完 masked multi-head self-attention 层之后,接下来你可以实现各个部分,从而构建完整的 GPT transformer 层。对于 GPT-2,这些函数可以在 modules.gpt2_layer.add 与 modules.gpt2_layer.forward 中填写。

GPT-2 Model — models.gpt2.py

最后,你可以在 model 类中实现 token embedding 与 positional embedding 函数。这些函数可以在 models.gpt2.embed 中填写。

完成这些步骤后请注意,我们在 sanity_check.py 中提供了一个 sanity check 函数,用来测试你的实现。它会把你的实现与 Huggingface 上的官方 GPT-2 实现做比较。

python3 sanity_check.py

第 13 页

5.3 Adam Optimizer

你还要基于 Decoupled Weight Decay Regularization [7] 与 Adam: A Method for Stochastic Optimization [8],实现 Adam Optimizer 的 step() 函数,用来训练一个情感分类器。

Overview of the Adam Optimizer

Adam optimizer 是一种高效的随机优化方法,只需要一阶梯度。该方法通过估计梯度的一阶矩和二阶矩,为不同参数计算自适应的学习率。具体来说,在每一个时间步,算法更新梯度 $m_t$ 与梯度平方 $v_t$ 的指数滑动平均,其中超参数 $\beta_1, \beta_2 \in [0, 1)$ 控制这些平均值的指数衰减速率。由于这些滑动平均在初始时间步被初始化为 0,它们会偏向零。因此,这个算法的一个关键之处是进行偏差修正(bias correction),在每一个时间步得到 $\hat{m}_t$ 与 $\hat{v}_t$。完整的算法如下:

Algorithm 1 Adam algorithm。$g_t^2$ 表示逐元素平方 $g_t \odot g_t$。对向量的所有运算都是逐元素的。用 $B_1^t$ 和 $B_2^t$,我们表示 $B_1$ 与 $B_2$ 的 $t$ 次幂。

请注意,以牺牲清晰度为代价,上面的算法还有一个更高效的版本:把循环里的最后三行替换为下面两行:$\alpha_t \leftarrow \alpha \cdot \sqrt{1 - \beta_2^t}/(1 - \beta_1^t)$ 以及 $\theta_t \leftarrow \theta_{t-1} - \alpha_t \cdot m_t/(\sqrt{v_t} + \epsilon)$。

Code To Be Implemented: 实现 Adam Optimizer 的 step() 函数:optimizer.step

你应当实现 Adam Optimizer 的 step() 函数。我们的参考实现采用 Kigma and Ba [8] 第 2 节 “Algorithm” 末尾(也就是上面算法末尾)提到的“高效”偏差修正计算方式,来代替中间量 $\hat{m}$ 与 $\hat{v}$ 的方法。同样地,学习率应当被并入 weight decay 的更新中。你可以通过运行下面的命令来测试你的实现:

python3 optimizer_test.py

第 14 页

6 Sentiment Analysis with GPT-2

在实现了一个可运行的 GPT-2 模型之后,你现在要利用预训练好的模型权重,在两个数据集上做情感分析。除了直接运行这些预训练权重来分类不同句子之外,你还要(如同在 Assignment 5 中所做的那样)在每个数据集上对这些 embedding 进行 fine-tune,以获得更好的结果。你可以在 data 子文件夹中找到这两个数据集。

6.1 Datasets

Stanford Sentiment Treebank (SST) dataset

Stanford Sentiment Treebank[注 5] [1] 由从电影评论中抽取的 11,855 个单句组成。该数据集用 Stanford parser[注 6] 解析,包含来自这些解析树的共 215,154 个不重复短语,每个短语由 3 位人工标注者标注。每个短语带有一个标签,取值为 negative、somewhat negative、neutral、somewhat positive 或 positive。在这个项目中,你将利用 GPT-2 的 last token embedding 来预测这些情感分类标签。

概括来说,对于 SST 数据集我们有如下划分:

CFIMDB dataset

CFIMDB 数据集由 2,434 条极性很强的电影评论组成。每条电影评论带有一个二分类标签:negative 或 positive。我们注意到,其中许多评论长于一个句子。在这个项目中,你将利用 GPT-2 的 last token embedding 来预测这些情感分类。

概括来说,对于 CFIMDB 数据集我们有如下划分:

6.2 Code To Be Implemented: Sentiment Classification with GPT-2

在 classifer.py 文件中,你会看到一个 pipeline,它

  1. 调用 GPT 模型对句子进行编码,输出 last token 的表示
  2. 把编码后的表示送入句子分类任务
  3. 在下游任务(例如句子分类)上 fine-tune GPT-2 模型

在这个文件里,你要实现 GPT2SentimentClassifer。你要实现这个类,用 GPT-2 对句子进行编码,并取得每个句子的 last token 表示。[注 7] 然后这个类会对该表示施加 dropout,再用一个 linear layer 对它做投影,从而对句子进行分类。最后(这部分已经实现),模型必须能够根据我们使用的是预训练权重还是在进行 fine-tune 来调整它的参数。

[注 5] https://nlp.stanford.edu/sentiment/treebank.html

[注 6] https://nlp.stanford.edu/software/lex-parser.shtml

[注 7] 关于如何取得这个表示,见 models/gpt2.py 中的 forward 函数

第 15 页

6.3 Training GPT-2 for Sentiment Classification

对 SST 与 CFIMDB 两个数据集,你都应当用预训练的 embedding 和 fine-tune 后的 embedding,在 SST 与 CFIMDB 上测试你完成的模型。你可以用下面的命令运行训练:

python3 classifier.py –fine-tune-mode [last-linear-layer/full-model] –batch_size BATCH_SIZE –lr LR hidden_dropout_prob=RATE –epochs=NUM_EPOCHS

你应当使用 dev_out 和 test_out 标志,把每个数据集的结果分别输出到下列文件(运行 classifier.py 时这些文件应当会自动输出)。

predictions/last-linear-layer-sst-dev-out.csv
predictions/last-linear-layer-sst-test-out.csv
predictions/full-model-sst-dev-out.csv
predictions/full-model-sst-test-out.csv
predictions/last-linear-layer-cfimdb-dev-out.csv
predictions/last-linear-layer-cfimdb-test-out.csv
predictions/full-model-cfimdb-dev-out.csv
predictions/full-model-cfimdb-test-out.csv

作为 baseline,你的实现在 dev 数据集上应当得到与下面相近的结果:

Last Linear Layer for SST: Dev Accuracy: 0.462
Full Model for SST: Dev Accuracy: 0.513
Last Linear Layer for CFIMDB: Dev Accuracy: 0.861
Full Model for CFIMDB: Dev Accuracy: 0.976

你只可以使用训练集和我们的 dev 集来训练、调参和评估你的模型。对于这一部分,评分时我们主要看你的代码/实现(以及你在测试集上的准确率)。每个数据集的训练时间不应超过 5 分钟和 15 分钟(取决于你的 GPU)。

第 16 页

Submission Instructions for GPT-2

你将在 Gradescope 上提交本项目 GPT-2 这一部分:

  1. 确认下列文件存在于你的作业目录中这些指定的路径下:
modules/attention.py
modules/gpt2_layer.py
models/base_gpt.py
models/gpt2.py
classifier.py
optimizer.py
predictions/last-linear-layer-sst-dev-out.csv
predictions/last-linear-layer-sst-test-out.csv
predictions/full-model-sst-dev-out.csv
predictions/full-model-sst-test-out.csv
predictions/last-linear-layer-cfimdb-dev-out.csv
predictions/last-linear-layer-cfimdb-test-out.csv
predictions/full-model-cfimdb-dev-out.csv
predictions/full-model-cfimdb-test-out.csv
  1. 运行 prepare_submit.py,生成你的 cs224n_default_final_project_submission.zip 文件。

  2. 把这个 .zip 文件上传到 Gradescope 的 Default Final Project [Base]。

从大体上看,SST 与 CFIMDB 的 dev/test 数据集的提交文件应当如下所示:

id, Predicted_Sentiment
001fefa37a13cdd53fd82f617, 4
00415cf9abb539fbb7989beba, 2
00a4cc38bd041e9a4c4e545ff, 1
...
fffcaebf1e674a54ecb3c39df, 3

第 17 页

7 面向额外下游任务的扩展与改进

在这个项目的前半部分,我们专注于实现 GPT-2 的关键部分;在项目的其余部分(也是最终作业中占你成绩大部分的部分),你可以自由探索其他数据集,以更好地 fine-tune 或以其他方式调整模型,从而提升其在 paraphrase detection 以及 sonnet generation 上的表现。项目后一部分的目标,是从机器学习研究者的角度探索如何提升模型性能。

对于 paraphrase detection,我们将使用 Quora 数据集来测试你。你可以在 data 文件夹中找到 train、dev 和 test 数据集。你只能使用我们的训练集和 dev 集来训练、调参和评估模型。注意: 如果你使用这些数据集的官方测试数据来训练、调参或评估模型,或以任何方式手动修改你的 CSV solutions(提交结果),你就是在违反荣誉准则(honor code violation)。

对于 sonnet generation,我们提供一个小型 Shakespeare 十四行诗数据集,其中每个文档是一首十四行诗。你可以在 data 文件夹中找到 train、dev 和 test 数据集。你只能使用我们的训练集和 dev 集来训练、调参和评估模型。注意: 如果你使用这些数据集的官方测试数据来训练、调参或评估模型,或以任何方式手动修改你的 CSV solutions(提交结果),你就是在违反荣誉准则(honor code violation)。

7.1 完形填空式释义检测(Cloze-Style Paraphrase Detection)

Quora 是一个二分类任务,但 GPT-2 输出的是下一个 token 上的分布。你可以通过把二分类重新表述为一种“完形填空式”(cloze-style)任务,让 GPT-2 执行分类。具体来说,与其 fine-tune GPT-2 来做二分类,你可以改为把该任务表述为:询问模型一句话是否是另一句话的 paraphrase,并生成 “yes” 或 “no”。例如,输入:

Question Pair(问题对):(1) “What is the step by step guide to invest in share market in india?”,(2) “What is the step by step guide to invest in share market?”

可以重新表述为:

“What is the step by step guide to invest in share market?” 是 “What is the step by step guide to invest in share market in india?” 的 paraphrase 吗?

然后生成 “yes” 或 “no” 作为回答。

7.2 数据集概览(Dataset Overview)

Quora 数据集

Quora 数据集如第 1 节所述,包含 400,000 个问题对,标签指示特定实例是否互为 paraphrase。我们提供了该数据集的一个子集,划分如下。对于 Quora 数据集,划分如下:

鉴于该数据集是二分类标签,我们用来测试该数据集的指标是 accuracy(准确率)。

第 18 页

十四行诗数据集(Sonnet Dataset)

Sonnet 数据集包含 Shakespeare 创作的 154 首十四行诗。每首十四行诗由 14 行文本组成,遵循押韵方案 ABAB CDCD EFEF GG,其中被赋予相同字母 ID 的行彼此押韵。该数据集的划分如下:

训练时,你会使用训练十四行诗上的 cross-entropy loss 来 fine-tune 你的 GPT2,使其生成 Shakespeare 风格文本。然后在评估时,你会拿到 12 首留出(test)十四行诗中每首的前 3 行,并被要求生成其余部分。你以 test 为条件生成的十四行诗将使用 chrF 指标来评估生成语言与 Shakespeare 原始十四行诗的接近程度;chrF 是一种字符级 n-gram 比较指标,类似于 BLEU [9]。

7.3 代码概览(Code Overview)

对于项目的下一部分,你可以自由重新组织每个类中的函数、创建新类,或以其他方式改造你的代码。我们简要概述为 paraphrase detection 和 sonnet generation 两个任务提供的 starter code。

7.3.1 释义检测(Paraphrase Detection)

我们提供了函数定义,用于预测一个句子对是否互为 paraphrase。我们同样提供了现成代码,可加载 Quora 数据集的训练数据,并在提供的 dev 和 test 数据集划分上评估你的模型。我们建议你修改——或完全删除或替换——这些 starter code,换成你的扩展。下面简要概述 starter code:

第 19 页

7.3.2 Sonnet 生成

我们提供了一些用于生成你自己的 sonnet 的 starter code。关键的是,你会以 sonnet 的前 3 行作为条件来生成其余部分。你的模型将用 CHRF score 评估:你生成的 sonnets 与 William Shakespeare 的语言分布有多接近。

7.4 可能的扩展方向

有许多可能的扩展可以提升你的模型在 Quora 和 Sonnet 数据集上的性能。我们建议你为每一个想尝试的改进找一篇相关研究论文。这里我们提供一些建议,但你也可能在其他地方寻找提升模型性能的有趣方法。

用 PEFT 提升性能

原始论文: LoRA: Low-Rank Adaptation of Large Language Models [10]

原始论文: ReFT: Representation Finetuning for Language Models [11]

Parameter-efficient finetuning(PEFT)是一类 finetuning 技术,相对于 full finetuning 减少可训练参数数量。它通过冻结模型的大部分或全部(因此大多数参数不存储梯度)来实现这一点,从而减少训练时间以及存储 finetuned model 所需的内存(因为只需要对 base model 做权重更新)。因此,PEFT 能在给定的 GPU 内存和磁盘容量预算下,对更大的模型进行 finetuning。

文献中已经提出了多种 PEFT 方法。你项目的一个扩展可以包括:(1) 加载一个更大的 GPT-2 模型;(2) 为 GPT-2 实现一个 PEFT(或多个 PEFTs);(3) 在本讲义涵盖的任一任务或一个新任务上 finetune 这个更大的模型。

一些有用的资源包括 HuggingFace PEFT library(https://github.com/huggingface/peft)和 StanfordNLP pyreft library(https://github.com/stanfordnlp/pyreft)。

基于成对数据的偏好优化

原始论文: Direct Preference Optimization: Your Language Model is Secretly a Reward Model [12]

另一个可探索的方向是 Direct Preference Optimization(DPO),这是一种通过 pairwise comparison data 让语言模型与人类偏好对齐的方法。与其他基于 RL 的替代方法(例如 Reinforcement Learning from Human Feedback(RLHF))不同,DPO 提供了一种更稳定的训练范式,以监督方式运行。

第 20 页

DPO 的核心是使用成对样例训练模型:对于每个输入 prompt,我们都有一个更偏好的输出和一个较不偏好的备选输出。模型通过优化以下目标来学习给更偏好的输出分配更高概率:

$$\mathcal{L}_{\text{DPO}} = -\mathbb{E}_{(x,y_w,y_l)\sim\mathcal{D}}\left[\log\sigma\left(\beta\log\frac{\pi_\theta(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)} - \beta\log\frac{\pi_\theta(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}\right)\right]$$

其中:

更多细节请参考 [12]。

构造 pairwise data。

与标准 supervised fine-tuning 数据集不同,提供的 sonnet 和 paraphrase 数据集只包含“positive”或期望样本(例如正确的 paraphrases、规范 sonnet)。没有显式标注的“negative”或较不偏好的回复。要使用 DPO,你需要构造这样的 losing samples:

一旦有了这些成对数据(winning vs. losing),你就可以训练 GPT-2 模型,提高 winning 样本的 likelihood,同时降低 losing 样本的 likelihood。

如果你选择走 DPO,就要准备好仔细设计 pairwise data 的收集/构造过程。negative examples 应足够真实,以提供有意义的训练信号,同时又要能与高质量输出明确区分。

加速 Attention

原始论文: FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness [13]

原始论文: Longformer: The Long-Document Transformer [14]

尽管 Transformers 论文让 scaled dot product attention 流行起来,但仍存在数十种替代机制 [5]。例如,FlashAttention 使用 tiling 和 CUDA 优化来分块计算 attention,从而在长序列长度下更快 [13]。类似地,Sliding Window Attention 将时间复杂度从二次降低到线性(在 Longformer(用于长文档的 transformers)中提出)[14]。

一个扩展方向可以是试验不同类型的 attention 机制,并分析时间与模型效用之间的权衡。一些入门资源包括:https://paperswithcode.com/methods/category/attention-mechanisms 和 https://huggingface.co/blog/tomaarsen/attention-sinks。Triton 也可能帮得上你。

第 21 页

Questing for Quantization(探索量化)

Quantization 使我们能够通过用较低精度(例如 int8、bfloat16)表示模型权重,而不是用它训练时的高精度,来降低内存占用和计算成本。在这个扩展中,我们可以在不同量化级别上 fine-tune 并运行模型,分析 utility(paraphrase detection/sonnet quality)如何变化。Huggingface 提供了一份指南(https://huggingface.co/docs/optimum/en/concept_guides/quantization),但你也可以尝试实现自己的方法。

一个针对 GPT-2 的扩展可以是 quantization-aware fine-tuning,确保带量化的 fine-tuning 不会导致过拟合。入门时,可以看看 Quantization Adapters for GPT-2 [15]。

Preconditioning for Proficiency(ft. optimizers)——为熟练度做预条件

近来,二阶优化器(即用高阶项对梯度做 pre-condition 的优化器)在训练(或 fine-tune)LLM 中越来越受关注。具体来说,Google 正在用这些优化器训练其部分最大的模型。

这个扩展的重点是使用一种 pre-conditioning 方法来替代 Adam 来 fine-tune 你的模型。它应当训练得显著更快,利用二阶信息加速收敛。这些方法通过“preconditioning”梯度来起作用;说白了,就是对梯度向量施加一个变换,使其对曲率敏感。如果普通的更新规则类似:

$$\theta_{t+1} = \theta_t - \eta \nabla f(\theta_t)$$

那么使用 preconditioner $G$ 的 pre-conditioned gradient 就是:

$$\theta_{t+1} = \theta_t - \eta G \nabla f(\theta_t)$$

你可以使用 K-FAC [17]、Shampoo [18] 或完全不同的 pre-conditioning 方法 [20],可选的方法有很多。

Fine-Tuning with Regularized Optimization(用正则化优化进行 fine-tuning)

原始论文:SMART: Robust and Efficient Fine-Tuning for Pre-trained Natural Language Models through Principled Regularized Optimization [21]

激进的 fine-tuning 常常会导致过拟合。这可能使模型无法泛化到未见数据。为了以有原则的方式解决这个问题,Jiang 等人提出(1)Smoothness-inducing regularization,它有效管理模型复杂度;(2)Bregman proximal point optimization,它是 trust-region methods 的一个实例,可以防止激进更新。

Smoothness-Inducing Adversarial Regularization(平滑诱导对抗正则化)

具体来说,给定模型 $f(\cdot; \theta)$ 和目标任务的 $n$ 个数据点组成的集合 $\{(x_i, y_i)\}_{i=1}^{n}$,其中 $x_i$ 表示输入句子的 embedding

第 22 页

这些 embedding 来自语言模型的第一个 embedding 层,而 $y_i$ 是关联的标签;Jiang 等人的方法本质上通过求解以下优化问题来进行 fine-tuning:

$$\min_\theta L(\theta) + \lambda_s R_s(\theta) \tag{2}$$

其中 $L(\theta)$ 是如下定义的损失函数:

$$L(\theta) = \frac{1}{n}\sum_{i=1}^{n} l(f(x_i;\theta), y_i), \tag{3}$$

而 $l(\cdot,\cdot)$ 是取决于目标任务的损失函数,$\lambda_s > 0$ 是一个调节参数,$R_s(\theta)$ 是如下定义的 smoothness-inducing regularizer:

$$R_s(\theta) = \frac{1}{n}\sum_i \max_{\|\tilde{x}_i - x_i\|_p \leq \epsilon} l(f(\tilde{x}_i;\theta), f(x_i;\theta)), \tag{4}$$

其中 $\epsilon > 0$ 是一个调节参数。注意,对于分类任务,$f(\cdot;\theta)$ 输出一个 probability simplex,而 $l_s$ 选为对称化 KL-divergence,即:

$$l_s(P, Q) = D_{\mathrm{KL}}(P\,\|\,Q) + D_{\mathrm{KL}}(Q\,\|\,P) \tag{5}$$

Bergman Proximal Point Optimziation(Bregman 近端点优化)

Jiang 等人还提出了一类 Bregman point proximal point optimization[注 8] 方法来求解 Equation 2。这类优化方法在每次迭代时施加强惩罚,以防止模型激进更新。具体来说,他们使用一个预训练模型作为初始化,记为 $f(\cdot;\theta_0)$。在第 $(t+1)$ 次迭代时,vanilla Bregman proximal point(VBPP)方法采用:

$$\theta_{t+1} = \arg\min_\theta F(\theta) + \mu D_{\mathrm{Breg}}(\theta, \theta_t), \tag{6}$$

其中 $\mu > 0$ 是一个调节参数,$D_{\mathrm{Breg}}(\cdot,\cdot)$ 是如下定义的 Bregman divergence:

$$D_{\mathrm{Breg}}(\theta, \theta_t) = l_s(f(\tilde{x}_i;\theta), f(x_i;\theta_t)), \tag{7}$$

更多细节见 https://github.com/namisan/mt-dnn 和 [21]。

[注 8] https://www.stat.cmu.edu/~ryantibs/convexopt/lectures/bregman.pdf

Other improvements(其他改进)

除了训练上的改动之外,还有很多其他事情可以做来提升你的表现。本节的建议只是一些例子;运行必要的实验并做出必要的比较需要时间。记住,我们会评估你实验的彻底程度,所以不要忽视超参数搜索!

第 23 页

Other Approaches(其他方法)

这里展示的模型和技术远非穷尽。我们测试的任务上有很多已发表的论文——也可能有我们还没见过的新论文!此外,还有大量关于许多不同任务的深度学习研究,可能有助于改进你的模型[注 9]。这些论文可能包含有趣的想法,你可以应用它们来构建更稳健、语义更丰富的 embedding。

[注 9] http://nlpprogress.com/

第 24 页

7.5 Submission Instructions(提交说明)

你将在 Gradescope 上提交本项目的 Extensions 部分。

  1. 运行 prepare_submit.py。该命令应将你所有的 *.py 文件和 prediction *.cvs 文件收集到一个 zip 文件中。
  2. 确认生成的 cs224n_default_final_project_submission.zip 包含 predictions/* 目录中的模型预测,以及复现你结果所需的所有代码。
  3. 将你的 cs224n_default_final_project_submission.zip 文件上传到 Gradescope 上对应的作业。
  4. 将你的项目报告上传到 Gradescope 的 Default Final Project [written]。

第 25 页

8 提交到 Leaderboard(Submitting to the Leaderboard)

8.1 Overview(概述)

我们在 Gradescope 上托管了四个 leaderboard,你可以在那里将自己的表现与同学的表现进行比较。第一个和第二个 leaderboard 分别对应 Quora paraphrase detection dataset 的 test 和 dev 数据集[注 10]。第三个和第四个 leaderboard 分别对应 Sonnet generation 任务的 test 和 dev 数据集[注 11]。这些 leaderboard 可在以下链接找到:

  1. Paraphrase Dev:Default Final Project [Paraphrase Dev]
  2. Paraphrase Test:Default Final Project [Paraphrase Test]
  3. Sonnet Dev:Default Final Project [Sonnet Dev]
  4. Sonnet Test:Default Final Project [Sonnet Test]

对于 paraphrase 和 sonnet 两个任务:你向 dev leaderboard 提交的次数不限,但只允许向 test leaderboard 成功提交 3 次。对于最终报告,我们会要求你选择一个 test leaderboard 提交,作为最终表现的考量。因此,你必须至少向 test leaderboard 提交一次,但在完成开发你最好的模型之前,要小心不要用光 test 提交次数。

向 leaderboard 提交与在 Gradescope 上向 base autograder(implementation/sentiment predictions)提交完全相同。你将使用与 7.5 相同的步骤。尽管你的 .zip 结构应当相同,但 leaderboard autograder 唯一会查看的文件是 dev/test 集上的预测 CSV 文件(即 predictions/para-dev-output.csv 或 predictions/para-test-output.csv)。

从高层来看,Paraphrase 数据集的提交文件应当如下所示:

id Predicted_Is_Paraphrase
65977 641bc8de-b905-4ae4-a0ee-abea15fbc88e, 3919
176826 a9b4a342-5a5c-4f99-a104-9d1c4f26e0ab, 3919
237122 b3549639-8557-4848-9ea7-c48b27dcdb39, 8505
382296 828ef916-678f-43e3-8916-e08d0fdaeed0, 3919
...
368297 4198fb98-259f-4e4c-8d45-ef2347920fad, 8505

表头是必需的,第一列是每个 sample 的 ID(ID 定义在各自的 test/dev 文件中),最后一列是你预测的答案。行可以按任意顺序排列。对于 test 和 dev leaderboard,你必须为每个 sample 提交预测。

对于 Sonnet Generation,提交文件应当如下所示:

--Generated Sonnets--
0
Those lips that Love's own hand did make
Breathed forth the sound that said "I hate"

[注 10] 我们会展示你的模型在 Quora test/dev 数据集上的 accuracy。

[注 11] 我们会展示你生成的 Sonnets 的 CHRF 分数。

第 26 页

(接上页的 Sonnet 提交文件示例)

Some other generated text
...

你应当为 dev/test 测试集中各 12 首 sonnet 都给出 completion。

8.2 Submission Steps(提交步骤)

以下是向 leaderboard 提交 Paraphrase Detection 任务的具体步骤(Sonnet Generation 完全相同):

  1. 使用你的 ParaphraseGPT 模型生成预测 .csv 文件。使用 7.5 中的步骤(即运行 prepare_submit.py)生成你的 .zip,并确保相关的 .csv 位于 predictions/ 中。
  2. 使用上面的 URL 导航到 leaderboard。确保为你的 split(DEV vs. TEST)选择正确的 leaderboard,并记住你对 TEST 只有三次提交机会。
  3. 在 Gradescope 中找到 submit 按钮,选择要上传的提交 .zip。
  4. 点击 upload 并等待你的分数。提交输出会告诉你在 dev/test 数据集上的提交的 accuracy correlation。你在 leaderboard 上的排名取决于你最好的提交,而不一定是你最近的提交。请选择一个合适的 Leaderboard Name。

如果出现任何问题,应当会有有用的错误信息。如果你遇到无法理解的错误,请在 Ed 上发帖。

第 27 页

9 Grading Criteria(评分标准)

最终项目将进行整体评分(holistically)。这意味着在决定你的成绩时,我们会考察许多因素:你方法的创造力、复杂度与技术正确性;你探索并比较各种方法的彻底程度;你结果的质量;你投入的努力;以及你的 write-up、evaluation 和 error analysis 的质量。一般来说,实现更复杂的模型代表更多投入,而实现更不寻常的模型(例如本 handout 中未提到的模型)代表更多创造力。你不需要追求原创想法,但本课程中最好的项目会超越本 handout 中描述的想法,甚至可能本身就成为已发表的工作!

与往年一样,对于本项目的 part 2,你成绩的一个方面将是你在所有任务中相对于整个 leaderboard 的表现。注意,你在 leaderboard 上的结果质量只是我们评分时考虑的众多因素之一。我们的重点是评估人们有充分论证的研究问题、解释,以及能清楚评估这些问题的实验。

在 paraphrase detection 上没有预定义的 accuracy,在 sonnet generation 上也没有预定义的 perplexity 分数,来保证好成绩。尽管我们做了一些初步测试以得到一些大致分数,但无法事先说明在给定时间范围内学生合理可达到的分数分布。出于类似原因,对于 Section 7(或其他地方)提出的哪项 extension 能确保好成绩,也没有预定义规则。实现少量内容并取得好结果,同时进行彻底的实验/分析,要比实现大量不起作用或几乎不起作用的内容更好。此外,你的 writeup 和实验质量很重要:我们期望你有说服力地展示你的技术有效,并描述它们为什么有效(或它们在哪些情况下无效)。

与所有最终项目一样,更大的团队应完成相应更大的项目。对于人数更多的团队,我们会期望实现更复杂的内容、更彻底的实验,以及更好的结果。

第 28 页

10 Honor Code(荣誉守则)

任何一般适用于最终项目的 honor code 指南也适用于 default final project。

以下是一些与 default final project 特别相关的指南:

  1. 对于 sentiment analysis 任务,你不得使用已有的 GPT-2 实现作为起点,除非该实现是你自己编写的。
  2. 你可以自由地与其他团队讨论想法和实现细节(事实上,我们鼓励这样做!)。但是,在任何情况下,你都不得查看另一个 CS224n 团队的代码,也不得将他们的代码纳入你的项目。
  3. 如 Section 7 所述,以任何方式使用官方 Quora 和 Sonnets 训练与测试数据及其测试集,都违反 honor code。
  4. 在课程结束之前,不要公开分享你的代码(例如,放在公开的 GitHub repo 中)。

第 29 页

参考文献

(文献条目保留英文原样,便于按原题检索;编号与原题一致。)

第 30 页

材料

期末项目的官方文档(课程页 project/ 下 ✓)。本地副本在 _local/A5/(只服务本地、不外传)。

材料 链接
默认项目要求(Build GPT-2) DFP_Instructions.pdf
开题报告要求 Project_Proposal_Instructions.pdf
里程碑要求 Project_Milestone_Instructions.pdf
结题报告要求 Project_Report_Instructions.pdf
自选项目提示 custom-final-project-tips.pdf
往届获奖项目报告(参考用,不是要求 ✗) project.html

怎么自查

这份作业和书的关系

见上表「配哪几讲」。等题面中英对照做完,这里会换成章节标题原文,并加定位校验 (指引指向一个不存在的标题会被 nav_check 报出来)。