2018 - BERT 與 GPT 預訓練典範#
案件摘要#
2018 年是 NLP 的「ImageNet 元年」:兩篇論文在半年內各自宣判——6 月 OpenAI 的 GPT-1、10 月 Google 的 BERT。兩者共享同一骨架(2017 年 Transformer)與同一信條(預訓練 + 微調),卻選了兩條互相排斥的訓練目標:
GPT 是 Transformer 解碼器的語言化:從左到右逐字預測。BERT 是 Transformer 編碼器的語言化:遮住 15% 的字,用雙向上下文猜出被遮的字。這是「雙向理解 vs 自回歸生成」的百年之爭的開庭日。
前因 -- 為什麼會有這個案子#
- 2013-米科洛夫詞向量.md(word2vec)已證明「無監督預訓練詞向量 + 微調」有效,但詞向量是靜態的:一詞一向量,無法處理多義(bank 是銀行還是河岸?)。
- 2017-Transformer注意力機制.md 交出了完美並行 + 直連長程的骨架——只差「拿來做什麼任務」。
- NLP 的長年困境:標註資料稀缺,每個任務(分詞、NER、問答)都要單獨從頭訓練——與影像界的 ImageNet 預訓練形成殘酷對比。
- 前驅線索:Dai & Le(2015)用 LSTM 做語言模型預訓練;ULMFiT(Howard & Ruder, 2018 年 1 月)確立「預訓練—微調」三步流程——但骨架還是 LSTM。
- ELMo(Peters et al., 2018 年 2 月)做出上下文相關詞向量(雙向 LSTM 拼接),但只是特徵拼接,不是端對端微調。
- 動機:OpenAI 想證明「生成式語言模型 + 規模」是通向 AGI 的路;Google 想證明「雙向理解」才是語言的本質——兩條路線的意識形態之爭正式開打。
線索與推理 -- 數學式、程式、理論#
第一條線索:GPT——Transformer 解碼器的語言化#
GPT-1 是 12 層 Transformer 解碼器(掩碼自注意力),在 BooksCorpus(約 7,000 本書)上做從左到右的語言模型:
訓練完後,微調時在最後加一個線性層做下游任務,損失混合:
GPT-1 在 12 項任務中 9 項刷新 SOTA。關鍵主張:語言模型本身就在學「理解」——預測下一個字需要語法、語義、常識的全部知識。
第二條線索:BERT——掩碼語言模型與雙向性#
GPT 的自回歸只能看左邊;BERT 主張理解需要雙向。但雙向 + 自回歸 = 自己看見答案(洩題)。BERT 的解法是掩碼語言模型(MLM):
- 隨機遮住 15% 的 token(80% 換
[MASK]、10% 換隨機詞、10% 保留) - 用整個(雙向)上下文預測被遮的 token
加上下一句預測(NSP):判斷句子 B 是否真的跟在 A 後面——給模型句際關係的線索。
第三條線索:兩條路線的對照表#
| GPT-1 | BERT | |
|---|---|---|
| 骨架 | Transformer 解碼器(12 層) | Transformer 編碼器(12/24 層) |
| 目標 | 自回歸 \(\log p(x_i \mid x_{<i})\) | 掩碼 \(\log p(x_i \mid x_{\setminus M})\) |
| 上下文 | 單向(左→右) | 雙向 |
| 擅長 | 生成、續寫 | 理解、分類、問答 |
| 資料 | BooksCorpus ~5GB | BooksCorpus + Wikipedia ~16GB |
| 參數 | 117M | 340M (LARGE) |
線索至此合攏:兩者都是「無監督預訓練 + 微調」典範的 Transformer 實現——差別只是掩碼的形狀。而這個差別決定了此後的意識形態分岔:BERT 稱霸理解榜,GPT 走向生成與規模。
第四條線索:[CLS] 與句子嵌入——理解任務的統一介面#
BERT 在輸入前加一個特殊 token [CLS],其最終隱狀態 \(\mathbf{h}_{[\text{CLS}]}\) 被用作整句的表示,餵入分類器。加上 [SEP] 分隔句對,BERT 用同一個模型統一了分類、問答、NLI、命名實體識別——11 項 NLP 任務全部 SOTA,GLUE 榜單直接碾壓(+7.7 分)。這是「一個模型、所有任務」的預訓練典範正式加冕。
第五條線索:成績單——兩條路線的第一次交鋒#
GLUE 榜與代表性任務的成績(2018 年底):
| 任務 | ELMo(特徵拼接) | GPT-1 | BERT-LARGE |
|---|---|---|---|
| MNLI(推論) | 80.7 | 82.1 | 86.7 |
| SQuAD v1.1(問答 F1) | 85.8 | 88.1 | 93.2 |
| CoLA(語法接受度) | 35.0 | 45.4 | 52.1 |
| GLUE 總分 | 79.3 | 82.3 | 80.5 → 微調後 82.1+ |
線索至此全部合攏:ELMo 是特徵拼接(半吊子),GPT-1 是單向自回歸(理解受限),BERT 以雙向 + 掩碼雙殺——2018 年的意識形態法庭,第一次判決是 BERT 勝訴。但判決書中藏著一枚未爆彈:GPT-1 的自回歸路線只是「參數與資料都不夠」——這枚彈在 2019、2020 年兩案中相繼引爆。
結案 -- 後果與影響#
- NLP 的 ImageNet 時刻:2018 年底起,「先預訓練、再微調」成為 NLP 的標準流程,工程師再也不從頭訓練模型。
- BERT 系後續:RoBERTa(2019,去掉 NSP、加倍資料)、ALBERT、DistilBERT——理解榜的軍備競賽;中文界有 BERT-wwm、MacBERT。
- GPT 系後續:GPT-1 的自回歸路線被 OpenAI 堅持到底——2019-GPT-2危險模型.md 的零樣本能力、2020-GPT-3規模湧現.md 的 in-context learning,最後通往 2022-ChatGPT與RLHF.md。
- 意識形態之爭的判詞:BERT 贏了理解榜,但輸了未來——生成式路線才能「無限續寫、任務自由」,掩碼式被鎖死在固定任務上。
- 預訓練典範外溢到全領域:語音(wav2vec 2.0)、影像(2021-ViT視覺Transformer.md 的 MAE 掩碼預訓練)、蛋白質(2021-AlphaFold2.md 與 ESM 語言模型)全部「先預訓練」。
- 伏筆:GPT-1 只有 117M 參數、只做微調——當參數再大一個數量級,「微調」本身將被淘汰,取而代之的是零樣本與提示。
關鍵人物與文獻#
- Alec Radford(OpenAI):GPT-1 的第一作者,GPT 系列的開山者。
- Jacob Devlin(Google):BERT 的第一作者。
- Radford et al., Improving Language Understanding by Generative Pre-Training, OpenAI, 2018。
- Devlin et al., BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding, NAACL 2019, arXiv:1810.04805。
- Peters et al., Deep Contextualized Word Representations(ELMo), 2018。
- Howard & Ruder, Universal Language Model Fine-tuning(ULMFiT), ACL 2018。
- Mikolov et al., Efficient Estimation of Word Representations(word2vec), 2013。
- 相關案件:2017-Transformer注意力機制.md、2019-GPT-2危險模型.md、2021-ViT視覺Transformer.md、2022-ChatGPT與RLHF.md(見「科學與歷史/人工智慧/」)
補充 -- 程式實作(python + pytorch)#
本案雙目標(自回歸 Σlog p(xᵢ|x_<i)+掩碼 Σlog p(xᵢ|x_{\M}))共用一具 Transformer 的最小可執行版本,見 _code/2018-BERTGPT.py(已實測可跑,CPU 約 1 分鐘;字元級 toy 語料):
# 2018 - BERT 與 GPT: 自回歸 LM + 掩碼 LM, 同一具 Transformer
import torch
import torch.nn as nn
import torch.nn.functional as F
class MiniTransformer(nn.Module):
def __init__(self, V, d=32, h=4):
super().__init__()
self.emb = nn.Embedding(V, d)
self.pos = nn.Embedding(32, d)
self.attn = nn.MultiheadAttention(d, h, batch_first=True)
self.ffn = nn.Sequential(nn.Linear(d, 64), nn.ReLU(), nn.Linear(64, d))
self.n1 = nn.LayerNorm(d)
self.n2 = nn.LayerNorm(d)
self.head = nn.Linear(d, V)
def forward(self, ids, causal):
T = ids.size(1)
x = self.emb(ids) + self.pos(torch.arange(T))
m = torch.triu(torch.ones(T, T, dtype=torch.bool), 1) if causal else None
a, _ = self.attn(x, x, x, attn_mask=m, need_weights=False)
x = self.n1(x + a) # Add & Norm (殘差配方)
x = self.n2(x + self.ffn(x))
return self.head(x)
def main():
torch.manual_seed(0)
sents = ["cats eat fish", "dogs eat meat", "birds eat seeds",
"cats drink milk", "dogs drink water", "birds sing songs"]
vocab = ["<m>", "[CLS]"] + sorted({c for s in sents for c in s})
ci = {c: i for i, c in enumerate(vocab)}
V = len(vocab)
data = [[ci["[CLS]"]] + [ci[c] for c in s] for s in sents]
T = max(map(len, data))
X = torch.tensor([r + [ci["<m>"]] * (T - len(r)) for r in data])
net = MiniTransformer(V)
opt = torch.optim.Adam(net.parameters(), lr=1e-2)
for ep in range(300):
opt.zero_grad()
logits = net(X, causal=True) # GPT 分支: 因果掩碼, 預測下一字元
lgpt = F.cross_entropy(logits[:, :-1].reshape(-1, V), X[:, 1:].reshape(-1))
mask = torch.rand_like(X.float()) < 0.15 # BERT 分支: 隨機掩碼, 雙向預測
mask[:, 0] = False
logits_b = net(torch.where(mask, ci["<m>"], X), causal=False)
lbert = F.cross_entropy(logits_b[mask], X[mask]) if mask.any() else 0
(lgpt + lbert).backward()
opt.step()
# (接龍 / 完形 / [CLS] 評估略, 見 _code/2018-BERTGPT.py 全文)
if __name__ == "__main__":
main()
執行結果(python3 _code/2018-BERTGPT.py,torch 2.12.0):
GPT自回歸損失=0.144 BERT掩碼損失=0.223 (同一具Transformer, 兩種目標)
接龍 'cats '-> [('e', 0.55), ('d', 0.44), ('m', 0.0)]
完形 'cats eat _' 填: 'f' (雙向上下文 -- BERT 的看家本領)
[CLS]距離: 鳥句-貓狗句=5.15 vs 貓句-狗句=3.73 (語義分群)
程式解說:causal=True/False 是全文唯一的開關——因果掩碼(上三角 -inf)即 GPT,只能看過去;無掩碼即 BERT,雙向全看。接龍 cats → e/d(eat/drink 都合理,機率如實反映語料)、完形填 f(fish,右側無上下文、左側 cats eat 定生死——單向模型在此只能猜)。[CLS] 距離鳥句–貓狗 5.15 大於貓–狗 3.73:句向量按語義分群,正是第四條線索「理解任務的統一介面」。實測附帶一課:[CLS] 必須走完整網路(注意力+FFN+兩次 Norm)才分群,只走半截即驗屍失敗——表徵在深處,不在淺層。