BERT 与 GPT:三类架构的取舍

NLP 系列的收尾总结课,用一张总表钉死 Encoder-only / Decoder-only / Encoder-Decoder 三类架构的差别,再把 BERT 与 GPT 的预训练任务、微调方式、适用任务逐项对照。


一、这份材料解决什么问题

前面的课时是分头讲的:Transformer 结构、注意力机制、词向量、ELMo/BERT/GPT 各自的细节。到这一课,材料做的是「横向收口」:先给一张三类架构总表,再把 BERT 和 GPT 分别拆到”输入表示 → 预训练任务 → 下游任务头 → 微调参数”这一级。

为什么这件事在被追问时值钱:

  • 评审者问架构对比时,考的不是”BERT 是双向、GPT 是单向”这种背结论,而是你能否把差别归因到 Attention Mask 这一行代码上,mask 的形状决定了模型能不能看未来,能不能看未来决定了它能做理解还是做生成。这是整份材料唯一的一条主线。
  • 在新闻分类项目新闻分类流水线里,这一课正好卡在第三级(BERT 微调)和第四级(LLM 零样本分类)的交界处:第三级用 BERT 系的 Encoder-only 做 10 分类,第四级用 LLM(Decoding 架构)做零样本/少样本分类。同一份数据、同一个任务,两类架构都上场了,评审者很自然会追问”那你为什么两个都用?各自解决了什么?”
  • 材料最后两节(ELMo/GPT/BERT 对比表、常见高频题)几乎就是原题清单,其中”为什么 BERT 不适合直接做自回归生成”是这份材料自己点出的高频题。

二、核心结论

  1. 三类架构的差别只有一个根:Attention 里允许谁看谁。Encoder 只有 padding mask,能看未来;Decoder 有 causal mask(上三角遮蔽),不能看未来;Encoder-Decoder 是前者给后者当条件。
  2. 双向 ≠ 两个单向拼起来。BERT 的”双向”是 Encoder 自注意力里没有 causal mask,每个 token 同时看左右两侧;ELMo 才是”左 LSTM + 右 LSTM 拼接”,两者不是同一回事。
  3. BERT = MLM + NSP,输出交给任务头;句子级任务取 [CLS] 的最终隐藏状态(或 pooler_output),token 级任务取每个 token 的隐藏状态。微调时只调任务头 + 整体小学习率微调。
  4. GPT = next token prediction,模型本身就是输出。GPT 基于 Transformer Decoder,但去掉了 Encoder-Decoder Attention,只留 Masked Multi-Head Attention + Feed Forward;做分类要自己加 pooling + 线性层,不是架构原生能力。
  5. 工程选型是四步走:先跑 baseline(中文 MacBERT / 通用 RoBERTa)→ 指标瓶颈换 DeBERTa → 抽取类任务换 SpanBERT → 资源受限再上蒸馏/量化/剪枝。

三、知识地图

3.1 原材料结构树

day12 BERT 与 GPT 架构
├── 1. NLP 主流架构对比……【掌握】              ← 全课总表(三类架构 × Mask × 用途 × 代表模型)
├── 2. BERT 模型介绍
│   ├── 2.1 学习目标
│   ├── 2.2 BERT 简介(2018 Google,双向预训练 + 微调范式,SQuAD/GLUE/MultiNLI)……【了解】
│   ├── 2.3 BERT 架构
│   │   ├── 三类 Embedding(Token / Segment / Position 相加)……【掌握】
│   │   ├── 多层 Transformer Encoder,无 causal mask 的"双向"……【掌握】
│   │   ├── 任务头设计:分类 / NER / QA……【掌握】
│   │   └── 句子级 vs token 级任务头对照表(6 行)……【掌握】
│   │   └── 工程调参建议(bs / lr / epoch / AdamW)……【掌握】
│   ├── 2.4 预训练任务:MLM(15%、80-10-10)+ NSP(50% 正负样本)……【理解】
│   ├── 2.5 小结 + 高频题速查表(6 组)……【理解】
├── 3. BERT 模型变体……【理解】
│   ├── RoBERTa / MacBERT / ALBERT / DeBERTa / ELECTRA
│   ├── SpanBERT / DistilBERT / ERNIE(百度)/ SciBERT·BioBERT
│   └── 校招项目落地顺序(baseline → 冲效果 → 专精 → 压缩)
├── 4. GPT 模型介绍
│   ├── 4.1 GPT 介绍(OpenAI,核心论文 2 篇,与 BERT 的单向/双向差别)……【了解】
│   ├── 4.2 GPT 架构(Decoder Block 去掉 Encoder-Decoder Attention)……【掌握】
│   ├── 4.3 训练过程:无监督预训练 + 有监督微调,目标函数 L1 / L2 / L3……【掌握】
│   ├── 4.3 GPT 下游任务微调类型表 + GPT-2 生成代码示例……【掌握】
│   └── 4.4 小结
├── 5. 扩展:BERT / GPT / ELMo 三者对比……【理解】
└── 6. 常见高频题(5 题)……【掌握】

3.2 权重标注说明

材料只在 BERT 与 GPT 两处写了「学习目标」(了解 / 掌握 / 理解),上表沿用材料的原标注;材料未标注的章节(第 1 节总表、第 3 节变体、第 6 节高频题),按”是否常被追问”给出的估读,已和材料自身的强调程度对齐(例如材料把总表放在全课第一节,且高频题里直接问架构差异,故标【掌握】)。


四、原理详解

4.1 三类架构 + 两种 Attention Mask(全课主线)

Transformer 的自注意力本质是”每个 token 给所有 token 打分,然后加权求和”。这么一个全连接式的打分矩阵,如果不加限制,第 5 个 token 就能直接读到第 8 个 token 的信息。Mask 就是往这个打分矩阵上加的限制条件,限制方式不同,就分出了三类架构。

两种 mask:

掩码 作用 加在哪 谁在用
padding mask 把 batch 里补齐的 <pad> 位置屏蔽掉,不让模型把填充位当成真实语义 Encoder 自注意力、Decoder 的交叉注意力和自注意力 所有架构都要用
causal mask 上三角置为 -\infty,让位置 i 只能看到 \le i 的位置(含自己) Decoder 的自注意力 GPT / T5·BART 的 Decoder

关键区分:padding mask 是”工程必需”(不定长序列 batch 化),causal mask 是”建模选择”(决定能不能看未来)。BERT 的表格里写”仅 padding mask”,意思不是”没有 mask”,而是”没有因果遮蔽”,所以它能看未来,这就是”双向”。

为什么 causal mask 用 -\infty:自注意力是 \mathrm{softmax}(QK^T/\sqrt{d_k})V,把某个位置分数设成 -\infty,softmax 后它的权重严格为 0,等价于”看不见”。这也是为什么图里 Masked 矩阵上三角写的是 -inf 而不是 0,写 0 的话 softmax 后仍有正权重,等于没遮住。

三类架构一句话总结:

  • Encoder-only(BERT):全可见,产出”带上下文的表示”,擅长判别式任务。
  • Decoder-only(GPT):只能看左侧,产出”下一个词的概率分布”,擅长生成。
  • Encoder-Decoder(T5 / BART):Encoder 全可见地编码输入,Decoder 带因果遮蔽地生成输出,用交叉注意力把两者接起来,适合”输入一段 → 输出另一段”的翻译/摘要。

4.2 BERT:三类 Embedding 与”双向”的真实含义

输入表示:三类 Embedding 逐元素相加(不是拼接)得到输入向量:

  • Token Embedding:词/子词的向量,序列首位放 [CLS];
  • Segment Embedding:句子对任务里区分 A 句 / B 句;
  • Position Embedding:BERT 用的是可学习位置编码(和原始 Transformer 的固定正弦编码不同,这是材料明确点出的差别)。

模型真正接收的三个输入张量是 input_ids、attention_mask、token_type_ids(句对任务才用得上 token_type_ids)。

双向的含义:材料给的定义很干净,Transformer Encoder 的自注意力机制里没有因果掩码(causal mask),每个 token 可以同时看到左右两侧的 token。这句话在被追问时比”BERT 是双向的”值钱得多,因为它把结论还原成了机制。

任务头:模型本体不区分任务,靠头部切换。材料的对照表可以归纳成”看粒度、看标签数、看激活/损失”:

预测粒度 任务类型 用哪个特征表示 输出维度 激活 + 损失
句子级 单标签多分类(情感、意图、新闻主题) [CLS] 最终隐藏状态 / pooler_output 类别数 C Softmax + CrossEntropy
句子级 多标签多分类 同上 标签数 C Sigmoid + BCEWithLogits
句子级 单标签二分类(语义匹配、句对相似度) 两句 [CLS] 表示(拼接/差值/点乘/余弦) 1 Sigmoid + BCEWithLogits(也可 2 logits + Softmax + CE)
token 级 单标签多分类(NER、词性标注) 每个 token 的最终隐藏状态 标签数 C Softmax + CrossEntropy
token 级 抽取式 QA / 阅读理解 每个 token 的最终隐藏状态 2(start / end) Softmax + CE(start) + CE(end)
token 级 完形填空、MLM 被 mask 的 token 隐藏状态 词表大小 V Softmax + CrossEntropy(未 mask 位置 ignore_index=-100)

两个输出的区别(高频追问):

  • last_hidden_state:形状 (batch_size, seq_len, d_model),每个 token 一个向量,用于 token 级任务;
  • pooler_output:把 [CLS] 的向量过一层线性 + tanh,用于句子级分类。注意它带了 tanh 非线性,跟直接取 last_hidden_state[:, 0] 不是同一个东西。

工程调参建议(材料原表,可直接背):Batch size 16/32;学习率 2e-5 ~ 5e-5;Epoch 3~4(可增到 5);优化器 AdamW,weight_decay=0.01。

[CLS] 与 [SEP] 的作用:[CLS] 标记句子开始,它的最终隐藏状态被当作整句表示用于分类;[SEP] 分隔句子/文本段落并标记结束。序列形式:

[CLS] token1 token2 ... tokenN [SEP]                    # 单句任务
[CLS] 句A tokens [SEP] 句B tokens [SEP]                 # 句对任务

4.3 BERT 的预训练任务:MLM 与 NSP

MLM(Masked Language Model),解决”想双向就不能用标准语言模型”的矛盾。标准语言模型是”看左边预测下一个词”,天生单向;BERT 想同时看左右,就不能让模型知道要预测哪个词,于是改成”挖空填词”:

  • 随机选 15% 的 token 参与预测,其中:
  • 80% 替换为 [MASK]:强制模型学习上下文填空;
  • 10% 换成随机词:防止模型”背位置”,强化依赖上下文;
  • 10% 保留原词:减少分布偏移,让模型看到真实 token 形态。

这三档比例背后的逻辑是训练-推理不一致问题:下游任务里根本不存在 [MASK] 这个符号,如果 100% 都用 [MASK],模型学到的表示会和实际使用时的输入分布脱节,所以留 10% 原词兜底。

NSP(Next Sentence Prediction),建模句间关系。输入句子对 (A, B),预测 B 是不是 A 的真实下一句:50% 正样本(真实下一句)、50% 负样本(随机句子)。它服务的下游场景就是语义匹配、句对相似度、问答这种需要”两段文本关系”的任务。

小结(材料原文口径):BERT = 三类 Embedding + Transformer Encoder 堆叠 + 任务头;MLM 解决双向表征,NSP 建模句间关系;微调时只需调整任务头即可适配分类、匹配、QA、NER 等多任务。

4.4 BERT 变体:每个改动解决了什么问题

模型 核心架构与特点 关键优化 / 改进 适用场景
RoBERTa 与 BERT 架构一致 更大数据(16GB → 160GB)、更大 batch(256 → 8000)、更长训练、去 NSP、动态 Mask、Byte-BPE 编码 主流工业 baseline,效果全面优于 BERT
MacBERT 中文优化版 BERT MLM 改进:近义词替换 + 全词 + n-gram Mask;去 NSP → SOP 中文分类 / NER / QA 强,通用中文 baseline
ALBERT Transformer Encoder 词嵌入因式分解(低维 Embedding + 线性投影)、层间参数共享、去 NSP → SOP(句子顺序预测)、去掉 Dropout、长句 + n-gram Masking albert-tiny 仅 1.8M 参数,超轻量、推理快、精度略降,适合资源受限
DeBERTa 解耦注意力(content & position 分离) Disentangled Attention、相对位置编码、Mask 解耦 BERT 系最强性能,GLUE / SuperGLUE / SQuAD SOTA
ELECTRA 判别式预训练 Generator + Discriminator,预测 token 是否被替换 小模型性能接近大模型,效率最强
SpanBERT 强化 span 表示 Span Masking、SBO(Span Boundary Objective)、去 NSP 抽取式 QA、实体识别更强
DistilBERT BERT 蒸馏版 知识蒸馏、层数减半 40% 参数、60% 速度,精度保留约 97%
ERNIE(百度) 中文知识增强 实体/词级 Mask、知识图谱注入 中文信息抽取、搜索、对话强
SciBERT / BioBERT 领域专用预训练 学术 / 生物医学语料继续预训练 科研、医疗 NLP 明显优于通用模型

看这张表的方法:横向对比时抓一条主线,”改 MLM 的挖空方式”(MacBERT、SpanBERT、ERNIE、ALBERT)vs “改训练配方”(RoBERTa)vs “改注意力/位置编码”(DeBERTa)vs “改目标函数”(ELECTRA)vs “改体积”(ALBERT、DistilBERT)。这样记,被追问”你还知道哪些 BERT 变体”时能成串输出,而不是背名字。

材料给的校招项目落地顺序:

步骤 说明
Step1 先跑 baseline 中文用 MacBERT,英文/通用用 RoBERTa
Step2 再冲效果 指标瓶颈时换 DeBERTa
Step3 按任务换专精模型 抽取类任务优先 SpanBERT
Step4 资源受限再压缩 蒸馏 / 量化 / 剪枝,先保线上时延与成本

4.5 GPT 的架构:从 Decoder Block 到 12 层堆叠

GPT 与 BERT 的定位差别(材料原文):GPT 是单向语言模型,只利用前文预测下一个词 → 擅长文本生成(NLG);BERT 是双向语言模型,同时利用上下文 → 擅长文本理解(NLU)。

GPT 基于 Transformer Decoder 架构,但去掉了 Encoder-Decoder Attention。每个 Decoder Block 只保留两块:

- Masked Multi-Head Attention
- Feed Forward 层

GPT 的 Decoder Block:用红色框高亮被删掉的 Encoder-Decoder Attention

图注:这是 GPT Decoder 单层的结构图,自底向上串联。底部橙色块是 Masked Multi-Head Attention,往上经过 Add & Norm(浅绿色),再往上是被红框圈出的两块灰色半透明块 Multi-Head Attention + Add & Norm,这两块就是原版 Transformer Decoder Block 里接受 Encoder 输出的交叉注意力部分,被 GPT 删掉了,所以画成淡灰色(表示”不存在”)。再往上接 Feed Forward(蓝色)与顶部的 Add & Norm。图中每个子层都有绕行的残差连接箭头(Add 就是残差相加),注意力的三路输入箭头对应 Q、K、V。图底标注 GPT Decoder。这张图是”GPT 只保留 Masked Self-Attention + FFN”这句话的可视化证据。

Masked Multi-Head Attention 保证模型在预测下一个词时只能看到前文信息。材料给的例子非常直观:给定 4 个单词 [A, B, C, D],GPT 需要用 [A] 预测 B、用 [A, B] 预测 C、用 [A, B, C] 预测 D。所以当要预测 B 时,必须把 [B, C, D] 遮掩起来,注意 B 自己也被遮住,因为预测 B 时还没有 B。

Mask 矩阵示意:Softmax 之前把上三角置为 -inf,Softmax 之后上三角权重为 0

图注:图中四个 4\times4 矩阵,行列均为 A/B/C/D,展示同一个打分矩阵在 mask 前后的变化。左上「Softmax 前」是原始注意力分数(A 行:0.11、0.00、0.81、0.79;B 行:0.19、0.50、0.30、0.48;C 行:0.53、0.98、0.95、0.14;D 行:0.81、0.86、0.38、0.90)。箭头指向右上「Masked」:严格上三角被替换为 -inf,即 A 行只剩 0.11、B 行只剩 0.19 与 0.50、C 行只剩 0.53、0.98、0.95,对角线(自己看自己)与下三角(看左侧历史)全部保留。图中文字标注「Softmax 之前需要 Mask」点明了位置,mask 必须加在 softmax 之前。左下与右上内容相同(Masked),再经箭头到右下「Softmax」:上三角全部变成 0,每行和为 1,A 行成为 [1, 0, 0, 0](预测 B 时只能看到 A,权重全给 A),其余行按各自保留的分数归一化。图底标注「GPT Softmax」。

数字小例子(自己验算一遍最稳):拿 B 行来说,保留的分数是 0.19 和 0.50,\mathrm{softmax}([0.19, 0.50]) = [0.42, 0.58],图里写成 0.48 / 0.52,是示意性取整,结构和结论一致(两个位置归一化、其余为 0)。再看 D 行:\mathrm{softmax}([0.81,0.86,0.38,0.90]) \approx [0.26, 0.28, 0.17, 0.29],和图里的 0.25/0.26/0.23/0.26 同量级。要记住的是两件事:mask 加在 softmax 之前;被遮位置在 softmax 后严格为 0。

层数与整体结构:典型 GPT-Decoder 数量是 12 层(GPT-2 / base 规模),大模型可达到 24 层或更多。

GPT 整体结构:Text & Position Embed → 12× (Masked Multi Self Attention + Layer Norm + Feed Forward + Layer Norm) → 双头输出

图注:自底向上看。最底部橙色块是 Text & Position Embed,只有文本嵌入与位置嵌入,没有 BERT 的 segment embedding(GPT 不需要句对输入)。往上是浅蓝色大框,左侧用括号标注 12x,表示这个块堆叠 12 次;块内自底向上依次为 Masked Multi Self Attention(粉红)→ 残差相加(⊕)→ Layer Norm(紫色)→ Feed Forward(黄色)→ 残差相加(⊕)→ Layer Norm。这种”子层 → 相加 → LayerNorm”的顺序是 post-norm 写法。块顶部分出两个绿色输出头:Text Prediction(语言模型头,预测下一个词)与 Task Classifier(下游任务头),这两个头正是 GPT “两阶段训练”的图形化:预训练用 Text Prediction,微调换 Task Classifier,底座共享。

4.6 GPT 的两阶段训练

GPT 的训练是典型的两阶段:第一阶段无监督预训练语言模型,第二阶段有监督下游任务微调。

阶段一:无监督预训练(next token prediction)

通过”由前文预测下一个词”学习语言规律,无需人工标注。输入是 tgt_input(前文文本序列),输出是 tgt_output(前文对应的下一个词组成的序列),本质就是用 tgt_input 预测对应的 tgt_output。

给定句子 U = [u_1, u_2, \dots, u_n],训练目标是最大化”由前文预测当前词”的概率:

L_1(U)=\sum_i \log P(u_i \mid u_{i-k},\cdots,u_{i-1};\Theta)

其中 k 是上下文窗口大小。输入表示为词嵌入与位置编码之和:

h_0 = U W_e + W_p

其中 W_e 是词嵌入矩阵,W_p 是位置编码矩阵。把 h_0 逐层传入 Decoder Block:

h_l = \mathrm{transformer\_block}(h_{l-1}),\quad l \in [1, L]

最后用顶层隐状态预测下一个词(注意这里用了词嵌入矩阵的转置做权重绑定,即输入嵌入和输出投影共享参数):

P(u)=\mathrm{softmax}(h_L W_e^{T})

阶段二:有监督下游任务微调

训练数据是输入序列 [x_1, \dots, x_n] 加标签 y,目标是据输入预测标签。在分类任务中,通常只取序列的最后一个隐藏状态 h_n 作为句子表示,再过一个输出线性层(通常是 softmax)预测标签:

P(y \mid x_1,\cdots,x_n)=\mathrm{softmax}(h_n W_y + b_y),\qquad
L_2=\sum_{(x,y)}\log P(y \mid x_1,\cdots,x_n)

这一步和 BERT 的对照点值得记牢:BERT 取的是句首 [CLS],GPT 取的是句尾的最后一个隐状态。原因是 GPT 单向,只有最后一个位置”看过”了整句话,句首位置只有它自己。这也解释了材料下面这张表里”GPT 做分类需要自定义下游网络”的原因。

两阶段目标合成最终的优化函数:

L_3 = L_2 + \lambda L_1

GPT 下游任务微调类型(材料原表):

任务类型 GPT 输出使用方式 是否需要自定义下游网络 备注 / 建议
文本生成(NLG) 直接使用 LM head logits 否 可直接生成文本,少量微调可提升生成质量
文本分类 / 回归 使用 last_hidden_state 或自定义 pooling 后的向量 是 定义线性层或 MLP,把 GPT 输出当特征表示
其他任务(问答 / 信息抽取) 同文本分类 是 可自定义 head,支持 token-level 或 span-level 输出

推理代码(材料原始示例,GPT-2 base 做 CPU 演示):

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_name = r"./model/gpt2"          # GPT-2 base,适合 CPU 演示

tokenizer = AutoTokenizer.from_pretrained(model_name, local_files_only=True)
model = AutoModelForCausalLM.from_pretrained(model_name, local_files_only=True)

prompt = "what do you like?"
input_ids = tokenizer(prompt, return_tensors="pt").input_ids

model.to(device)
with torch.no_grad():
    output = model.generate(input_ids.to(device), max_length=100)

print(tokenizer.decode(output[0], skip_special_tokens=True))

材料同时保留了这次运行的输出,这段输出本身就是两个极好的教学素材:

  • 警告 1:The attention mask and the pad token id were not set... Please pass your input's attention_mask to obtain reliable results.,单条输入看不出问题,但 batch 推理时不传 attention_mask,padding 位会被当成真实 token 参与注意力。
  • 警告 2:Setting pad_token_id to eos_token_id: 50256 for open-end generation.,GPT-2 的 tokenizer 默认没有 pad token,所以 transformers 退而用 eos_token_id=50256 充当 pad。这解释了为什么 GPT-2 微调分类任务时经常要手动 tokenizer.pad_token = tokenizer.eos_token。
  • 生成结果:"what do you like? I'm not sure. I'm not sure if I like it or not. ..." 反复重复同一句。这不是抽取稿写错了,而是未微调的小模型 + 纯贪心解码的典型退化现象,也侧面说明”zero-shot 能跑通”和”能用于生产”之间差着微调和解码策略(重复惩罚 / 采样温度)这一大截。

4.7 扩展:BERT / GPT / ELMo 三者对比

材料第 5 节把 ELMo 拉进来做三方对照,这张表的价值在于把”双向”这个词的历史讲清楚了:

模型 特征提取器 语言模型方向 优点 缺点 适用场景
ELMo 双层双向 LSTM 双向(左右单向 LSTM 拼接) 上下文动态词向量,解决多义词问题 LSTM 提特征能力弱,特征融合有限 目前很少使用
GPT Transformer Decoder 单向(只看上文) 特征提取能力强,自然语言生成能力好 单向限制,无法利用未来信息 文本生成、对话生成
BERT Transformer Encoder 双向(同时看上下文) 双向上下文理解能力强,多任务预训练(MLM + NSP) 模型庞大,训练成本高,不适合生成任务 文本分类、问答、命名实体识别等理解任务

读法:ELMo 的”双向”是把两个方向独立的 LSTM 输出拼起来,每个方向的 LSTM 内部依然是单向的;BERT 的”双向”是在同一层注意力里同时看到左右。前者是”两个单向的拼贴”,后者是”真正的全局可见”,这就是 Transformer 替换 LSTM 的核心理由之一。


五、易错点与常见误解

常见说法:”BERT 没有 mask”
实际上,BERT 有 padding mask,只是没有 causal mask。
为什么:材料的表格写的就是”仅 padding mask”。不定长序列 batch 化必须靠 padding mask 屏蔽补齐位,否则 <pad> 会污染注意力;BERT 缺的是”遮蔽未来”的那一种。

有人以为”BERT 是双向的,所以它是两个单向模型拼起来的”
其实 BERT 的双向来自 Encoder 自注意力里没有因果掩码,每个 token 一层之内就能同时看左右;两个单向 LSTM 拼接是 ELMo 的做法。
原因在于,材料在 2.3 节特意给了”双向”的定义,这句话是的得分点,答成 ELMo 式解释会直接暴露没看懂架构。

常听到的一种说法是:”GPT 用的是标准 Transformer Decoder Block”
真实情况是,GPT 基于 Transformer Decoder,但去掉了 Encoder-Decoder Attention,只保留 Masked Multi-Head Attention + Feed Forward。
背后的道理是,交叉注意力的 K、V 来自 Encoder 输出,而 GPT 没有 Encoder。图里被红框圈出的灰色块就是这个被删掉的部分;删掉之后 Decoder 才能单独当语言模型用。

容易想岔的地方:”mask 加在 softmax 之后就行”
事实正相反,必须加在 softmax 之前,把位置分数设成 -\infty。
之所以如此,softmax 之后置 0 再重新归一化虽然数值上也能凑出类似效果,但标准实现(也是图里画的)是”先 -\infty 再 softmax”,这样被遮位置权重严格为 0 且不需要二次归一化。图里明确标了「Softmax 之前需要 Mask」。

先说一个误解:”BERT 也能做生成,只是效果差一点”
实际上,结构上就不成立,双向注意力在预测当前位置时会看到未来 token,破坏了自回归的因果性。
为什么:材料高频题第 5 题的答案原文就是”双向注意力会看到未来信息,破坏自回归因果性”。生成时每步要预测下一个 token,而 BERT 的表示里已经混进了”下一个 token”本身,等于偷看答案。

常见说法:”GPT 做分类直接用它原生输出就行”
其实需要自定义下游网络:取 last_hidden_state 或自己做 pooling,再接线性层/MLP。
原因在于,GPT 的原生输出是词表上的概率分布(LM head logits),它没有像 BERT [CLS] 那样为”整句表示”预留的位置。材料的下游任务表明确写了文本分类”是否需要自定义下游网络 = 是”。

有人以为”MLM 就是 15% 的词全换成 [MASK]”
真实情况是,15% 是被选中参与预测的比例,其中只有 80% 换成 [MASK],10% 换随机词,10% 保持原词。
背后的道理是,材料把三档比例的原因写得很清楚,80% 学填空、10% 随机词防背位置、10% 原词减少训练与推理的分布偏移。少答那 20% 是常见的扣分点。

常听到的一种说法是:”MLM 的 loss 对所有 token 都算”
事实正相反,只对被 mask 的 token 计算,未 mask 位置用 ignore_index=-100 忽略。
之所以如此,这是材料任务头表最后一行”N 标签多分类(完形填空、MLM)”的原话,也是实际写 CrossEntropyLoss(ignore_index=-100) 时的标准做法。


六、横向对比与研究脉络

6.1 三类架构总表(本次核心,三列并排)

维度 Encoder-only Decoder-only Encoder-Decoder
结构类型 Transformer Encoder 堆叠 Transformer Decoder 堆叠(去掉 Encoder-Decoder Attention) Encoder + Decoder 完整结构
Attention Mask 仅 padding mask causal mask(上三角)+ padding mask Encoder:padding;Decoder:causal + padding
是否可看未来 能 不能 Encoder / Decoder (混合)
预训练目标 MLM + NSP(RoBERTa 等后续变体去 NSP) next token prediction 去噪/片段填充式的 seq2seq(材料未展开细节)
输出怎么用 取 [CLS](句级)或逐 token 隐状态(token 级)+ 任务头 直接使用 LM head logits 生成;分类需自建 pooling + 线性层 Decoder 逐 token 生成,交叉注意力接受 Encoder 输出
主要用途 文本分类、NER、语义匹配、搜索、抽取式 QA 对话、写作、代码生成 翻译、摘要、生成式 QA
代表模型 BERT、RoBERTa、DeBERTa、ERNIE GPT-4、LLaMA、Qwen T5、BART、mT5

一表记法:看”是否可看未来”这一列就够了,能看未来的是理解型(Encoder-only),不能看未来的是生成型(Decoder-only),一半能一半不能的是转换型(Encoder-Decoder)。

6.2 BERT vs GPT 逐项对照

对比维度 BERT GPT
架构来源 Transformer Encoder Transformer Decoder(去掉 Encoder-Decoder Attention)
注意力方向 双向(无 causal mask) 单向(causal mask)
预训练任务 MLM + NSP next token prediction(因果语言建模)
训练目标 恢复被遮蔽的词 / 预测句子关系 最大化 L_1(U)=\sum_i \log P(u_i \mid u_{i-k},\cdots,u_{i-1};\Theta)
句子表示取哪 句首 [CLS](或 pooler_output) 句尾最后一个隐状态 h_n(或自定义 pooling)
输出层权重 与输入嵌入无关(MLM 头独立) 与词嵌入矩阵转置共享:P(u)=\mathrm{softmax}(h_L W_e^T)
擅长 文本理解(NLU) 文本生成(NLG)
下游适配方式 换任务头即可(分类 / NER / QA / 句对) 生成任务直接用;判别任务需自建网络
双向位置编码 可学习位置编码 + segment embedding 位置嵌入 + 文本嵌入,无 segment embedding
典型落地 分类、NER、语义匹配、搜索排序 对话、写作、代码生成

6.3 演进脉络(谁解决了谁的什么问题)

  1. ELMo(LSTM 时代):解决静态词向量无法区分多义词的问题,但它用”左右两个单向 LSTM 拼接”实现双向,特征提取能力受限于 LSTM。
  2. Transformer(基础架构):用自注意力替换循环结构,成为通用预训练底座;Encoder 与 Decoder 用不同的 mask 承担不同职能。
  3. GPT(Decoder 路线):保留因果遮蔽的 Decoder 单干,用 next token prediction 预训练,把”生成”做成架构的原生能力,接口自然进化成 prompt。
  4. BERT(Encoder 路线):去掉因果遮蔽换来真正的双向表示;为了在双向条件下还能训练,发明 MLM(挖空填词)绕开”不能看未来就不能预测下一个词”的矛盾;再用 NSP 补句间关系。配套的”预训练 + 微调”范式让同一个底座适配多任务。
  5. BERT 变体群:RoBERTa 证明”去 NSP + 更大数据/batch + 动态 Mask”就够了(等于反过来质疑 NSP 的价值);ALBERT / DistilBERT 解决参数与推理成本;SpanBERT / ELECTRA 分别针对抽取式任务和预训练效率;DeBERTa 用解耦注意力 + 相对位置编码把 Encoder 路线的效果推到顶;MacBERT / ERNIE 针对中文做知识增强。
  6. Decoder-only 的回归:GPT 系列沿着”更大 + 更单向 + prompt 化”继续走,LLaMA / Qwen 等同路线模型把生成式路线做成主流;而 Encoder-only 依然稳稳占住判别式任务(分类 / NER / 匹配)的性价比位置,两条路线不是替代关系,而是分工关系(材料把 ELMo/GPT/BERT 三种设计放在同一张表里对照,正是这个意思)。

只写有把握的部分:材料没有给出 GPT 各代的具体年份、参数量和榜单分数,本报告也不做补充,避免出现细节错误。


七、在新闻分类项目中的落地

项目里这个知识点出现在哪一步:新闻分类项目的四模型对比路线是「TF-IDF + 随机森林 → FastText → BERT(transformers 微调)→ LLM(DeepSeek API 零样本/少样本分类)」。第三级和第四级正好各站一类架构:BERT 是 Encoder-only + MLM 预训练 + 任务头微调;DeepSeek 是 Decoder-only 的生成式模型,靠 prompt 里塞类别说明做分类。所以这份材料是同时给第三级和第四级做原理背书的收尾课。

第三级(BERT 微调做 10 分类)具体怎么用:

  • 数据与格式:训练 18w / 验证 1w / 测试 1w,标题\t标签。用 AutoTokenizer 编码成 input_ids + attention_mask;单句分类任务不需要 token_type_ids。
  • 取哪个表示:句子级单标签多分类,按材料表格取 [CLS] 最终隐藏状态或 pooler_output,接 Linear(hidden_size, 10),Softmax + CrossEntropyLoss。如果后续要改成”多标签主题打标”(一篇文章挂多个标签),把激活换成 Sigmoid、损失换成 BCEWithLogitsLoss 即可,材料表格给了这组对应关系。
  • 关键参数(直接用材料建议):batch size 16/32;learning rate 2e-5 ~ 5e-5;epoch 3~4(不够再加到 5);AdamW,weight_decay=0.01。这里最容易踩的坑是学习率照搬 TF-IDF 那级调参的直觉,从零训练常用的 1e-3 量级会把预训练权重直接冲坏。
  • 模型选择(按材料落地顺序):Step1 中文 baseline 直接用 MacBERT(比原版 BERT 强,且是材料点名的中文通用基线);Step2 指标卡住再换 DeBERTa 冲效果;Step3 抽标题里的实体/机构这类抽取任务时换 SpanBERT;Step4 上线前做压缩。
  • 与压缩环节的衔接:材料列的 DistilBERT = 层数减半 + 知识蒸馏,效果是”40% 参数、60% 速度、精度约 97%”。项目里的模型压缩可以在两条路上走,一是换成蒸馏好的小模型(DistilBERT / albert-tiny 1.8M),二是对自己的 BERT 做动态量化(torch.quantization.quantize_dynamic())、非结构化剪枝(torch.nn.utils.prune.global_unstructured())。顺序上先保指标再压体积,与材料”Step4 资源受限再压缩”一致。

第四级(LLM 零样本/少样本分类)为什么要用 Decoder-only:不用训练、不用标注,把 10 个类别名和判定规则写进 prompt,直接让模型输出类别,这就是 causal LM 的原生能力(生成)。代价是每次调用都要走完整个 prompt 的前向,推理成本和稳定性都不如微调好的小模型,所以项目上的讲法是”LLM 做兜底/冷启动/难例补刀,BERT 做主力高吞吐”。

与前三级的配合关系(可以直接这么讲):

级别 方案 架构 在项目里承担的角色
1 TF-IDF + 随机森林 无神经网络 建立可解释的强基线,验证标签质量
2 FastText 浅层词袋 证明”简单模型能做到多少”,作为工程下限
3 BERT 微调 Encoder-only 主力分类器,要精度和吞吐
4 LLM 零样本/少样本 Decoder-only 无标注冷启动、新类别快速上线、难例诊断
5 模型压缩 — 把第 3 级压到可上线的时延与体积

实际坑(来自本课材料能推出来的两个):

  • GPT-2 系 tokenizer 没有 pad token:材料跑 GPT-2 生成时的原话警告 Setting pad_token_id to eos_token_id:50256。换成 Decoder-only 模型做 batch 分类时,必须显式设置 pad_token_id 并传 attention_mask,否则 padding 位参与注意力,结果会漂。
  • 选错架构会白干:拿 BERT 去做”自动生成新闻摘要”、拿 GPT 去做”高吞吐 NER”都会打得很别扭,前者是因果性被破坏,后者是每个 token 都要自建 head 且没有 [CLS] 这种句级表示可用。

八、重点问答

Q1:BERT 与 GPT 的核心架构差异是什么?

A:一句话是”掩码不同,导致能力不同”。BERT 用 Transformer Encoder,自注意力里没有因果掩码,每个 token 同时看左右两侧,是双向的,擅长理解类任务;GPT 用 Transformer Decoder,自注意力带 causal mask(上三角遮蔽),只能看前文,是单向的,擅长生成类任务。所以 BERT 的接口是”喂一段文本拿一个表示”,GPT 的接口是”喂一段前文拿下一个词的概率分布”。这个差别不是设计偏好,而是由 mask 决定的:能不能看未来,决定了它是理解型还是生成型。

Q2:为什么 BERT 做不了生成(不能直接做自回归生成)?

A:因为双向注意力会把未来信息带进来,破坏自回归的因果性。生成任务每一步要预测下一个 token,而 BERT 的表示里每个位置都已经看过左右两侧,等于预测当前位置时已经把”下一个 token”看进去了,属于偷看答案。它还缺两样东西:一个是自回归的解码接口,一个是像 GPT 那样把词嵌入矩阵转置当输出层、直接输出下一个词分布的训练目标。BERT 的 MLM 是”挖空填词”,只在被 mask 的位置上算 loss,天然不是用来逐词往外吐文本的。所以工程上也没人拿 BERT 做生成,需要生成就换 Decoder 路线。

Q3:那为什么 GPT 不适合做分类或者 NER?

A:不是绝对不能做,而是”不是它的原生强项,成本更高”。第一,架构上它是单向的,分类时只有最后一个位置的隐状态看过整句,早期位置信息不全,所以材料里说 GPT 做句子表示要取 h_n 或自己做 pooling,而 BERT 有 [CLS] 这个专门为句级表示准备的位置。第二,它的原生输出是词表上的概率分布(LM head logits),做分类必须自定义下游网络,加线性层或 MLP;NER 这种 token 级任务还得自己搭 token-level 或 span-level 的 head。第三,Masked 双向带来的表示质量对判别式任务更划算:同样的参数量下,BERT 在分类、NER、语义匹配上通常更省算力也更好调。所以工程上的选择是理解任务交给 Encoder-only,生成任务交给 Decoder-only。

Q4:新闻分类项目里为什么选 BERT 而不是 GPT 做分类?

A:因为这是典型的判别式任务,Encoder-only 是更划算的选择。我的任务是把新闻标题分到 10 个类别,输入和标签都固定,不需要生成任何文本,这正好是 BERT 的双向 Encoder 加任务头的主场:取 [CLS] 或 pooler_output,接一层线性到 10 类,Softmax + CrossEntropy 就能训。参数量、推理时延、微调成本都可控,配 batch 16/32、学习率 2e-5~5e-5、AdamW 就够。GPT 我不是不用,而是放在第四级做 LLM 零样本分类:它不需要标注、能靠 prompt 快速上新类别,但每次推理都要过完整个 prompt,成本和稳定性都不如微调好的小模型,所以定位是兜底和补充,主力还是 BERT。

Q5:BERT 的输入是什么?[CLS] 和 [SEP] 各有什么用?

A:输入形式是 [CLS] token1 token2 ... tokenN [SEP],句对任务再拼一个 [SEP] 和 B 句。模型实际吃三个张量:input_ids、attention_mask、token_type_ids(只有句对任务才需要)。[CLS] 标记句子开始,它的最终隐藏状态被当作整句表示,句子级分类就取它;[SEP] 用来分隔句子或文本段、标记结束。另外输入表示是三份 embedding 相加的:token embedding、segment embedding(区分 A/B 句)、position embedding,位置编码在 BERT 里是可学习的。

Q6:BERT 的预训练任务有哪些?为什么要这么设计?

A:两个,MLM 和 NSP。MLM 是为了在双向条件下还能训练,标准语言模型”看左边预测下一个词”天生单向,BERT 想同时看左右,就只能改成挖空填词:随机选 15% 的 token,其中 80% 换成 [MASK]、10% 换随机词、10% 保持原词。三档比例都是有用意的:[MASK] 学上下文填空,随机词防止模型背位置,保原词是为了减少训练和推理之间的分布偏移,毕竟下游根本见不到 [MASK]。NSP 是输入句子对预测 B 是不是 A 的下一句,50% 正样本 50% 负样本,用来建模句间关系。不过后续研究里 RoBERTa 直接去掉了 NSP,说明 MLM 才是主要贡献。

Q7:GPT 的训练目标是什么?它是怎么适配下游任务的?

A:目标就是因果语言建模,next token prediction,最大化”由前文预测当前词”的概率,形式是 L_1(U)=\sum_i \log P(u_i \mid u_{i-k},\cdots,u_{i-1};\Theta)。训练分两阶段:第一阶段无监督预训练,输入 tgt_input 输出 tgt_output,不用标注;第二阶段有监督微调,对输入序列预测标签 y,分类时取最后一个隐藏状态 h_n,过 softmax 得到 P(y\mid x_1,\dots,x_n),最终目标函数是 L_3 = L_2 + \lambda L_1。适配下游时按任务分三类:生成任务直接用 LM head logits,不用自建网络;分类/回归要取 last_hidden_state 或做 pooling 再加线性层/MLP;问答和信息抽取也属于要自定义 head 的那一类。

Q8:深度学习里的 mask 到底有几种?会不会搞混?

A:工程上常打交道的就两种,但作用完全不同。一种是 padding mask,处理不定长序列 batch 化补的 <pad>,所有架构都必须有,属于工程必需品;另一种是 causal mask,把注意力矩阵上三角设成 -\infty,只允许看当前位置及左侧,这是建模选择。关键细节是 mask 必须加在 softmax 之前,加完之后 softmax 里被遮位置权重严格为 0(图里从 -inf 变成 0 就是这个过程)。两类掩码组合起来的分布就是三类架构:Encoder 只有 padding,所以能看未来;Decoder 是 causal + padding,不能看未来;Encoder-Decoder 里 Encoder 和 Decoder 各取一种,所以算混合。

Q9:微调 BERT 的常用参数是什么?为什么要这样设?

A:Batch size 16 或 32,学习率 2e-5 到 5e-5,epoch 3 到 4、不够可以加到 5,优化器 AdamW,weight_decay 一般 0.01。学习率之所以这么小,是因为预训练权重已经是很好的初始点了,学习率一大会把预训练学到的知识冲掉,这是从零训练和微调最容易被搞混的地方。batch 不用太大的原因是 512 长度的序列显存占用高,而且小 batch 往往泛化更好。epoch 少是因为数据量不算大,跑多了容易过拟合,配合早停更稳。


九、延伸阅读

论文(材料中列出的)

  • BERT:《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》— https://arxiv.org/pdf/1810.04805.pdf
  • GPT:《Improving Language Understanding by Generative Pre-Training》(材料 4.1 节列出)
  • GPT-2:《Language Models are Unsupervised Multitask Learners》(材料 4.1 节列出)

论文(与第 3 节变体、第 5 节 trio 对照相关的经典标题)

  • RoBERTa:《RoBERTa: A Robustly Optimized BERT Pretraining Approach》
  • ALBERT:《ALBERT: A Lite BERT for Self-supervised Learning of Language Representations》
  • DeBERTa:《DeBERTa: Decoding-enhanced BERT with Disentangled Attention》
  • ELECTRA:《ELECTRA: Pre-training Text Encoders as Discriminators Rather Than Generators》
  • SpanBERT:《SpanBERT: Improving Pre-training by Representing and Predicting Spans》
  • DistilBERT:《DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter》
  • MacBERT:《Revisiting Pre-Trained Models for Chinese Natural Language Processing》
  • T5:《Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer》
  • BART:《BART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension》

官方文档与工具

  • HuggingFace Transformers 文档(AutoTokenizer / AutoModelForCausalLM / AutoModelForSequenceClassification / pooler_output 与 last_hidden_state 的返回结构)
  • HuggingFace Model Hub 上的中英文 Encoder 权重(BERT、RoBERTa、MacBERT、DeBERTa 等)
  • PyTorch 量化与剪枝 API 文档(torch.quantization.quantize_dynamic、torch.nn.utils.prune.global_unstructured),与项目最后一环的模型压缩对应

本系列内相关课时

  • 前序:Transformer 结构、注意力机制、词向量与 ELMo(本课第 5 节的对照表依赖这几课的基础)
  • 后续:BERT 微调实战、LLM 零样本分类(对应新闻分类项目第三、四级)
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇