【CS224N】Lec 9 - Pretraining

词语结构与子词模型

固定词表的假设

  • 模型训练时会建立一个固定的词表(通常是几万词),每个词都会分配一个索引
  • 在测试时,所有未在词表中出现的新词(novel words) 都会被映射到一个特殊的符号 UNK(unknown)

Embedding 的问题

  • 词表内的词(如 hat, learn)有各自不同的 embedding
  • 词表外的所有词(taaaaasty, laern, Transformerify)都只能共享 同一个 UNK embedding
  • 这样会导致模型无法分辨 不同的新词、拼写错误或变体 —— 都被混为一个“未知词”

BPE 算法

核心思想

BPE 的目标是:

  • 不再把整词作为最小单位,而是学到 子词单元(subword tokens)
  • 把词表从「固定单词」变成「可以组合的子词」:这样就能减少 UNK(未知词) 问题

流程

  1. 初始化
    • 一开始,词表只包含 单个字符(a, b, c, …)和一个 “end-of-word” 符号(比如 “_”)。
  2. 统计最常见的相邻字符对
    • 在语料库里,找出出现频率最高的相邻字符对(比如 “a” 和 “b”)。
  3. 合并成新的子词
    • 把 “ab” 加入到词表中,作为一个新的 subword。
  4. 替换文本中的字符对
    • 在语料中把 “a b” 替换成新的 subword “ab”。
  5. 重复
    • 不断重复步骤 2–4,直到达到 预设的词表大小(比如 30k、50k)

我的问题: BPE 后如何保证序列长度一致

  1. 如果太长的话, 进行截断/滑动窗口
  2. 如果太短的话, 用 pad 补齐

Embedding 发展历程

2017 前的预训练词嵌入

  • 当时的做法(word2vec, GloVe 等):
    • 先训练好一个词向量表(每个词对应一个向量,和上下文无关)
    • 下游任务(QA、分类、翻译等)时,把句子里的每个词替换成对应的向量,再输入到 LSTM/Transformer 里
  • 局限性:
    1. embedding 没有上下文
      • 例如 “movie” 在任何句子里都是同一个向量
      • 所以模型必须在 下游训练 时自己学会利用上下文
    2. 数据需求很大
      • 下游任务的数据必须足够大,才能学出“语境依赖”
      • 但很多任务的数据其实很小(比如医学、法律 NLP)
    3. 参数大多随机初始化
      • 除了词向量是预训练的,其余网络层(LSTM/Transformer)都是随机的,训练难度大

现代 nlp 的做法

现代 NLP 的做法

  • 整个模型都预训练 (pretraining whole models)
    • 不仅 embedding,连 Transformer 的多层参数(self-attention, FFN)也都在海量无监督语料上训练好
    • 预训练目标通常是 遮盖部分输入 (masking / language modeling),让模型学会重建

预训练的优势

  • 更强的语言表示 (representations of language)
    • 模型已经学会了语义、语法、上下文依赖。
  • 更好的参数初始化
    • 下游任务只需要微调 (fine-tuning),不再从随机参数开始
    • 这大大减少了下游数据需求
  • 概率分布 (probability distribution over language)
    • 模型本质上学到了一个语言模型,可以生成、预测、补全文本

预训练

语言模型预训练

  • 语言建模任务 (Language Modeling Task)
    目标是建模:

    $$ pθ​(wt​∣w_{1:t−1}​) $$
    即在已知前文的情况下,预测下一个词的概率分布
  • 优点

    • 有大量数据可以用(无监督,所有文本都能用)
    • 模型可以是 LSTM, Transformer 等任意序列模型
  • 做法

    1. 用大规模语料训练一个语言模型
    2. 保存训练好的参数
    3. 在下游任务中加载这些参数作为初始化

预训练+微调范式

现代 NLP 的核心范式:

Step 1: Pretrain (on language modeling)

  • 用海量无监督语料,训练模型预测下一个词或填空(masked LM)
  • 学到 通用的语言知识(语法、语义、常识)

Step 2: Finetune (on your task)

  • 在下游任务(情感分析、QA、NER …)上,用少量标注数据进行微调
  • 模型已经具备语言理解能力,所以只需要学一点点任务特定知识

为什么这种范式更有效?

  1. 预训练阶段

    • 我们先在大规模语料上做预训练,优化预训练损失函数:
      $$ \hat{\theta} \approx \arg \min_{\theta} \mathcal{L}_{\text{pretrain}}(\theta)$$ $$
    • 得到参数 $\hat{\theta}$(这是一个“好”的起点)
  2. 微调阶段

    • 在下游任务上,我们要优化:
      $$ \theta^* = \arg \min_{\theta} \mathcal{L}_{\text{finetune}}(\theta)$$ $$
    • 但是我们 不是从随机初始化开始,而是从 $\hat{\theta}$ 开始。
  3. 为什么有用?

    • SGD 粘滞性 (stickiness):随机梯度下降往往会在初始点附近收敛 → 从 $\hat{\theta}$ 出发,就更可能收敛到一个“好的局部极小值”
    • 局部极小值的质量:靠近 $\hat{\theta}$ 的解往往比随机初始化更优
    • 梯度更合理:在 $\hat{\theta}$附近,finetune loss 的梯度通常更有意义,优化更快

总结

  • 随机初始化 → finetune:难,容易收敛到糟糕的局部最小值,需要大量数据
  • 预训练初始化 $\hat{\theta}$ → finetune:快,好,能在少量下游数据上表现优异

三种架构的预训练

Encoder only

这里讲的是Encoder 模型(比如 BERT)在预训练时用的目标函数,也就是 Masked Language Modeling (MLM)

背景

  • 之前我们说过 语言模型预训练(GPT 的方式)是 单向的:
    $p(w_t \mid w_{1:t-1})$
    只能利用前文预测下一个词

  • Encoder (BERT)双向的,即它能同时看到前后文

    • 如果直接用 “预测下一个词” 任务,就破坏了双向性

解决方法:Masked LM (BERT 的核心)

  1. 做法
    • 随机把输入序列中的一部分 token 替换为 [MASK]
    • 模型的目标是预测这些被遮盖的 token 例子:
    • 输入句子:I went to the [MASK] to buy [MASK].
    • 模型需要预测:store, tea
  2. 数学表达
    • 输入:$\tilde{x}$(被 mask 掉的序列)
    • 学习目标:
      $p_\theta(x \mid \tilde{x})$
    • 只对被 mask 的位置计算 loss
  3. 好处
    • 利用 双向上下文,每个词都能看见左右两边的信息
    • 模型学到的 embedding 更适合理解语义,而不仅仅是预测下一个词

图解

  • 左边:输入序列(I, [MASK], to, the, [MASK] …)
  • 中间:Encoder(Transformer 编码器,双向 self-attention)
  • 右边:输出隐藏状态 h1,…,hTh_1, …, h_T,用来预测 mask 的词

BERT

掩码语言建模
  • BERT 的核心预训练目标是 Masked Language Modeling
    • 随机挑选 15% 的 token 来预测
    • 具体规则:
      1. 80% 的时候 → 把词替换成 [MASK]
        • 例子:I like pizza → I like [MASK]
      2. 10% 的时候 → 把词替换成一个随机词
        • 例子:I like pizza → I like car
      3. 10% 的时候 → 保持原样,但仍然要求模型预测它
        • 例子:I like pizza → I like pizza
  • 为什么要这样做?
    • 如果总是 [MASK],模型会“偷懒”,只学会预测 mask
    • 引入随机词和原词,迫使模型对 非 mask 的词也要构建强语义表示
    • 注意:在微调阶段不会出现 [MASK],所以这种训练方式避免了 pretrain-finetune gap
下一句预测 NSP
  • 除了 MLM,BERT 还设计了 下一个句子预测 (Next Sentence Prediction, NSP)
    • 输入:两个文本片段 A 和 B
    • 任务:预测 B 是否是 A 的真实后续句子
      • 50% 的情况 B 是 A 的后续
      • 50% 的情况 B 是随机抽的
    • BERT 用了 segment embeddings 来区分 A 和 B
  • 目的
    • 让模型学会理解句子之间的关系(不仅是单句的词级语义)
    • 对 QA、自然语言推理 (NLI) 等任务有帮助
  • 后来研究发现
    • NSP 的效果有限甚至可能负作用(RoBERTa 就去掉了 NSP)
    • 但 MLM 仍然是 BERT 的核心训练目标
为什么 NSP 没用?
NSP 的设计初衷

BERT 作者 (Devlin et al., 2018) 认为:

  • 语言理解不仅需要词级别的上下文,还需要句子间的关系
  • 所以加了 NSP 任务:判断句子 B 是否是句子 A 的真实后续
NSP 的问题
  1. 任务过于简单
    • NSP 只是判断 “B 是否是 A 的后续”,很多时候模型可以依赖 表面线索 来解题,比如:
      • 标点符号
      • 主题切换(topic shift)
    • 并不需要真正理解语义
  2. 和下游任务差距大
    • 很多下游任务(QA、NLI、NER)并不是在做“是不是后一句”的判断
    • NSP 学到的表示对这些任务 迁移价值有限
  3. 可能引入噪声
    • NSP 里“负样本”是随机拼接的句子,这些随机负例和真实任务的负例分布不一样
    • 这种 训练–推理分布差异 反而会影响效果
  4. 消耗了训练资源
    • NSP 占用了模型一部分学习能力,但收益不明显
    • RoBERTa 的结论是:如果把 NSP 去掉,把计算力都用在 MLM 上 → 表示效果更强

全参数微调和部分参数微调

Full Finetuning

  • 做法:下游任务时,把 预训练模型的全部参数(比如 Transformer 的每一层权重)都拿出来更新

  • 优点

    • 灵活,可以完全适配目标任务
    • 通常能达到最高的下游任务性能
  • 缺点

    • 参数量巨大,显存和算力消耗很高
    • 对小数据集容易 过拟合
    • 需要为每个下游任务存一整份模型参数(非常浪费)

Lightweight / Parameter-Efficient Finetuning (PEFT)

  • 做法:冻结大部分预训练参数,只训练 很少的新参数或已有参数的一部分
    • 比如:
      • Adapter:在每层插入一个小网络,只训练这个小网络的参数

      • LoRA (Low-Rank Adaptation):对权重矩阵做低秩分解,只训练低秩矩阵

      • Prefix-tuning / Prompt-tuning:训练一些额外的 token embedding,不改动主体模型

  • 优点
    • 训练 高效、低成本
    • 不容易过拟合(因为主体模型冻结)
    • 每个任务只需要存储一小份新参数
  • 缺点
    • 灵活性比 full finetuning 差
    • 在一些复杂任务上可能效果稍逊
Prefix-Tuning / Prompt-Tuning
  • 核心:不改动预训练模型的主体参数(全部冻结),而是 在输入前拼接一些可学习的向量(prefix 或 prompt)
  • 这些 learnable prefix 会被模型当作额外的“虚拟 token”处理,就像真的输入文本一样进入 Transformer。
过程
  1. 冻结大模型:预训练好的 Transformer / LSTM / ++ 不动
  2. 加入 prefix/prompt
    • 在输入序列前面加一段 learnable 向量
    • 这段 prefix 可以理解为“任务提示 + 可学习参数”
  3. 训练时:只更新 prefix 参数,不更新模型主体
  4. 推理时:prefix 作为上下文 + 任务适配器,帮助模型表现出对应的任务行为
优点
  • 极高参数效率:比如 GPT-3 175B 参数,prefix 只需几十万或几百万参数即可适配新任务
  • 不破坏原模型:主体保持 frozen,避免遗忘原有知识
  • 多任务共存:不同任务可以有不同的 prefix,在推理时选择性加载
区分 Prompt-tuning 和 Prefix-tuning
  • Prompt-tuning:在输入 embedding 层前加可学习 token(更像“虚拟单词”)
  • Prefix-tuning:不仅在 embedding 层,还在每一层的 attention key/value 前加 prefix
    • Prefix-tuning 相当于 更深层控制模型注意力,效果通常比 prompt-tuning 稍好
LoRA 的核心思想
  • 在 参数矩阵 W 上,不是直接更新 W(太大),而是只学习一个 低秩“差分”矩阵
  • 数学形式:
  • $W^′=W+AB$
    • $W∈R^{d×d}$:预训练好的大矩阵(冻结,不更新)。
    • $A∈R^{d×k},B∈R^{k×d}$:新增的低秩分解矩阵,秩 $k≪d$
    • 只训练 A 和 B,参数量大幅减少
为什么好?
  1. 参数效率高
    • 如果$d=4096$,全矩阵有 ~16M 参数
    • 设$k=8$,LoRA 只需 $2×4096×8≈65k$ 参数,缩小数百倍
  2. 兼容性强
    • 训练时只更新 $AB$,推理时合并 $W+AB$,无额外推理开销
  3. 容易训练
    • 比 Prefix-tuning 还稳定,因为它直接作用在权重矩阵空间里
  4. 多任务方便
    • 不同任务只需保存不同的 $AB$,不必重复保存整个模型

Encoder - Decoder

问题背景

  • Encoder-only 模型(BERT) → 用 Masked LM(随机 mask 单个 token)

  • Decoder-only 模型(GPT) → 用 Causal LM(预测下一个 token)

  • Encoder–Decoder 模型(T5) → 需要一个 更适合 seq2seq 的预训练目标

Span Corruption(片段掩码)

T5 发现:

  • 单词级别的随机 mask(BERT 的 MLM)效果一般
  • 更好的做法是 mask 掉连续的片段(span),再让模型去恢复

例子

原句:

Thank you for inviting me to your party last week.

处理后(输入给 encoder):

Thank you <X> me to your party <Y> week.

目标输出(decoder 需要生成):

<X> for inviting <Y> last <Z>

特点

  1. mask 的单位是 片段 (span),而不是单词
  2. 每个 span 对应一个 唯一的占位符 token(< X >, < Y >, < Z >)
  3. Decoder 需要顺序生成这些被掩盖的内容

为什么有效?

  1. 比单词级别更贴近自然语言
    • 人类语言中被删除或缺失的部分往往是 词组/片段,而不是单个词
    • 比如 “for inviting” 整块缺失,更接近翻译/摘要等任务
  2. 更适合 Encoder–Decoder 结构
    • Encoder 输入是被挖空的句子
    • Decoder 生成的是 缺失内容,自然形成了一个 seq2seq 任务
  3. 保持语言建模特性
    • Decoder 还是在做语言建模,只不过目标是“补全缺失片段”

Decoder only

Decoder-only 预训练思想

  • 核心目标:自回归语言建模
    $p(wt​∣w_{1:t−1}​)$
    也就是预测下一个词
  • 预训练产物:得到能生成自然语言的 decoder
    在下游任务(比如分类)时,可以在最后一个 hidden state 上接一个线性分类器(A、b 随机初始化),再一起 finetune

GPT-1 (2018)

  • 提出 Generative Pretrained Transformer
  • 流程
    1. 大规模无监督预训练(语言建模)
    2. 下游任务小规模有监督微调
  • 输入格式:把任务改写成文本串输入 decoder,比如 NLI 就把 premise 和 hypothesis 拼接

GPT-2 (2019)

  • 参数规模提升到 1.5B
  • 发现:只靠预训练(不微调)也能在很多任务上直接生成“像样”的答案
  • 现象:生成的文本越来越连贯
  • 关键转变:少/零样本任务能力出现萌芽

GPT-3 (2020)

  • 参数量直接上到 175B
  • 关键发现
    • In-context learning:模型只需在输入里看到几个例子(few-shot),就能学会对应任务
    • 不需要梯度更新,只靠 prompt 里的示例就能完成新任务
  • 意义:模型参数本身学到的是通用分布,prompt 决定如何利用它

Scaling Efficiency

  • 背景:GPT-3 有 175B 参数,用了 300B tokens 训练
  • 问题:成本大致和 参数量 × tokens 成正比。GPT-3 的训练点并不是最优(太“胖”参数多,token 不够)
  • 对比表
    • LaMDA:137B 参数 / 168B tokens

    • GPT-3:175B / 300B

    • Jurassic:178B / 300B

    • Gopher:280B / 300B

    • MT-NLG:530B / 270B

    • Chinchilla:70B / 1.4T tokens ✅(小模型 + 大量数据 = 更高效,效果更优)

  • 结论
    GPT-3 并不是最佳的参数-数据比例。后续 Chinchilla scaling law 证明:更多 tokens、相对少一些参数,能得到更好的效果和泛化

Chain-of-Thought Prompting

  • 普通 Prompting:直接问 → 模型只给出答案(往往错,比如 27)
  • CoT Prompting:要求“逐步推理” → 模型先写推理过程,再得出答案
  • 原理
    • 相当于在 prefix 里提供了思维链条范例,模型学会模仿“逐步推理”的分布
    • 让大模型用“scratch pad”(草稿纸)去展开逻辑,而不是直接跳到结果
  • 意义
    • 极大提升模型在算术推理、符号推理、逻辑任务上的表现
    • 是 In-context learning 的一个典型 case
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计