8语音与语言处理(第3版)

预训练语言模型

BERT与GPT

BERTGPT预训练微调

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第8章:预训练语言模型(Pre-trained Language Models)

一、导读

1.1 本章学习目标

预训练语言模型是 NLP 的"GPT 时刻"。通过在大规模无标注语料上进行预训练,模型学习到丰富的语言知识,然后通过微调适配各种下游任务。这种"预训练-微调"范式彻底改变了 NLP 的研究和应用方式。

本章的核心学习目标包括:

  • 理解预训练-微调范式:为什么先在大规模数据上预训练,再在小规模数据上微调?
  • 掌握 BERT 的架构和预训练任务:MLM 和 NSP 的设计动机
  • 掌握 GPT 系列的架构和预训练任务:自回归语言建模
  • 理解微调策略:全参数微调、参数高效微调(LoRA、Adapter)
  • 了解预训练模型的应用:文本分类、NER、问答、文本生成
  • 理解规模定律:模型规模、数据规模与性能的关系

1.2 为什么本章如此重要

预训练语言模型是当代 NLP 的基石。在 BERT 之前,每个 NLP 任务都需要从头训练专门的模型。BERT 证明了"一个模型,多个任务"的可行性——在预训练阶段学习通用的语言表示,在微调阶段适配特定任务。

这种范式转变带来了深远影响:

  • 降低了 NLP 的门槛:不需要大量标注数据和计算资源,直接使用预训练模型
  • 提升了任务性能:在几乎所有 NLP 基准上都取得了突破性进展
  • 统一了 NLP 任务:不同任务可以用相同的模型架构处理
  • 催生了大语言模型:GPT-3、GPT-4 等模型展示了规模带来的涌现能力

1.3 核心问题引导

为什么"预训练-微调"比"从头训练"更有效?迁移学习的理论基础是什么?

BERT 的双向编码和 GPT 的单向编码有什么本质区别?各自适合什么任务?

MLM(掩码语言模型)的预训练目标为什么有效?它有什么局限性?

微调时应该冻结哪些层?全参数微调和参数高效微调各有什么优缺点?

规模定律告诉我们什么?模型越大一定越好吗?

预训练模型面临哪些挑战?幻觉、偏见、计算成本如何解决?


二、核心概念详解

8.1 迁移学习与预训练

8.1.1 迁移学习的动机

传统 NLP 的问题:每个任务需要大量标注数据从头训练。标注数据昂贵、稀缺,且不同任务之间无法共享知识。

迁移学习的思路:在大规模无标注数据上预训练,学习通用的语言表示;在下游任务的标注数据上微调,适配特定需求。

8.1.2 预训练-微调范式

预训练阶段:
  大规模无标注语料 → 自监督学习 → 通用语言模型

微调阶段:
  通用语言模型 + 小规模标注数据 → 任务特定模型

8.2 BERT

8.2.1 架构

BERT (Bidirectional Encoder Representations from Transformers) 基于 Transformer 编码器:

  • BERT-Base:12 层,768 维,12 头,1.1 亿参数
  • BERT-Large:24 层,1024 维,16 头,3.4 亿参数

输入表示:Token Embedding + Segment Embedding + Position Embedding

8.2.2 预训练任务

掩码语言模型(MLM, Masked Language Model):随机遮盖输入中 15% 的 token,预测被遮盖的 token。

python
# MLM 示例
# 原始句子: "I love natural language processing"
# 遮盖后:   "I love [MASK] language [MASK]"
# 目标:     预测 "natural" 和 "processing"

下一句预测(NSP, Next Sentence Prediction):判断两个句子是否连续。

8.2.3 微调

微调时,在 BERT 顶部添加任务特定的输出层:

python
from transformers import BertForSequenceClassification, BertTokenizer
import torch

# 加载预训练模型(用于分类任务)
model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

# 准备输入
text = "This movie is absolutely wonderful!"
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=128)

# 前向传播
outputs = model(**inputs)
logits = outputs.logits
prediction = torch.argmax(logits, dim=-1)
print(f"Prediction: {prediction.item()}")  # 0 或 1

8.3 GPT 系列

8.3.1 架构

GPT (Generative Pre-trained Transformer) 基于 Transformer 解码器:

  • 使用掩码自注意力(只能看到前面的 token)
  • 自回归生成(逐个生成下一个 token)

8.3.2 预训练任务

自回归语言建模:

$$\mathcal{L} = -\sum_{i=1}^{N} \log P(x_i | x_1, \ldots, x_{i-1})$$

8.3.3 GPT-3 的涌现能力

GPT-3 (1750 亿参数) 展示了惊人的能力:

  • 零样本学习:无需示例就能完成任务
  • 少样本学习:通过提示中的几个示例学习
  • 上下文学习:在推理时动态适应新任务
python
# GPT-3 风格的少样本提示
prompt = """
Classify the sentiment as positive or negative.

Text: I love this product!
Sentiment: positive

Text: This is terrible.
Sentiment: negative

Text: The quality is amazing.
Sentiment:"""

# GPT-3 会生成 "positive"

8.4 BERT vs GPT

特性BERTGPT
架构Transformer 编码器Transformer 解码器
上下文双向单向(从左到右)
预训练目标MLM + NSP自回归 LM
适合任务理解类(分类、NER、问答)生成类(文本生成、对话)
微调方式添加分类头提示 + 生成

8.5 微调策略

8.5.1 全参数微调

更新模型的所有参数。效果好但计算成本高,且在小数据集上容易过拟合。

python
from transformers import TrainingArguments, Trainer

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    learning_rate=2e-5,
    weight_decay=0.01,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
)

trainer.train()

8.5.2 参数高效微调

Adapter:在 Transformer 层中插入小型适配模块,只训练适配模块。

LoRA(Low-Rank Adaptation):在权重矩阵中添加低秩分解,只训练低秩矩阵。

$$W' = W + \Delta W = W + BA$$

其中 $B \in \mathbb{R}^{d \times r}$,$A \in \mathbb{R}^{r \times d}$,$r \ll d$。

python
from peft import LoraConfig, get_peft_model

# 配置 LoRA
lora_config = LoraConfig(
    r=8,           # 低秩维度
    lora_alpha=32, # 缩放因子
    target_modules=["query", "value"],  # 应用 LoRA 的模块
    lora_dropout=0.1,
    task_type="SEQ_CLS",
)

# 获取 LoRA 模型
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters()
# 可训练参数: 0.1% of total

8.6 规模定律(Scaling Laws)

Kaplan et al. (2020) 发现,模型性能(损失)与模型参数量 $N$、数据量 $D$、计算量 $C$ 之间存在幂律关系:

$$L(N) \propto N^{-\alpha_N}$$

这意味着可以通过增加规模来可预测地提升性能。但 Chinchilla (Hoffmann et al., 2022) 指出,模型参数和数据量应同步增长,否则浪费计算资源。


三、实战案例

3.1 使用 BERT 进行文本分类

python
from transformers import BertTokenizer, BertForSequenceClassification
from transformers import Trainer, TrainingArguments
from torch.utils.data import Dataset
import torch

class SentimentDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, max_len=128):
        self.texts = texts
        self.labels = labels
        self.tokenizer = tokenizer
        self.max_len = max_len

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        encoding = self.tokenizer(
            self.texts[idx],
            max_length=self.max_len,
            padding="max_length",
            truncation=True,
            return_tensors="pt",
        )
        return {
            "input_ids": encoding["input_ids"].squeeze(),
            "attention_mask": encoding["attention_mask"].squeeze(),
            "labels": torch.tensor(self.labels[idx], dtype=torch.long),
        }

# 数据
texts = ["这部电影太好看了", "非常失望,浪费钱", "剧情精彩,推荐", "无聊至极"]
labels = [1, 0, 1, 0]

# 模型
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=2)

# 训练
dataset = SentimentDataset(texts, labels, tokenizer)
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    learning_rate=2e-5,
)
trainer = Trainer(model=model, args=training_args, train_dataset=dataset)
trainer.train()

# 推理
test_text = "这个产品真的很棒"
inputs = tokenizer(test_text, return_tensors="pt", max_length=128, truncation=True, padding="max_length")
with torch.no_grad():
    outputs = model(**inputs)
    pred = torch.argmax(outputs.logits, dim=-1)
    print(f"'{test_text}' → {'正面' if pred.item() == 1 else '负面'}")

3.2 使用 GPT-2 进行文本生成

python
from transformers import GPT2LMHeadModel, GPT2Tokenizer

tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")

prompt = "Natural language processing is"
inputs = tokenizer(prompt, return_tensors="pt")

# 生成文本
outputs = model.generate(
    **inputs,
    max_new_tokens=50,
    temperature=0.7,
    top_k=50,
    top_p=0.95,
    do_sample=True,
    no_repeat_ngram_size=2,
)

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

3.3 使用 LoRA 进行高效微调

python
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType

# 基础模型
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
base_model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)

# LoRA 配置
lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    r=16,
    lora_alpha=32,
    target_modules=["query", "value"],
    lora_dropout=0.1,
)

# 获取 LoRA 模型
model = get_peft_model(base_model, lora_config)
print(f"Trainable parameters: {model.num_parameters(only_trainable=True) / 1e6:.2f}M")
print(f"Total parameters: {model.num_parameters() / 1e6:.2f}M")

四、本章小结

本章系统讲解了预训练语言模型:

预训练-微调范式:在大规模无标注数据上预训练,在小规模标注数据上微调。迁移学习降低了 NLP 的门槛。

BERT:基于 Transformer 编码器,双向上下文,MLM + NSP 预训练。适合理解类任务。

GPT 系列:基于 Transformer 解码器,单向自回归生成。适合生成类任务。GPT-3 展示了涌现能力和上下文学习。

微调策略:全参数微调效果好但成本高;LoRA 等参数高效方法大幅降低微调成本。

规模定律:性能随规模可预测地提升,但参数和数据需要同步增长。

预训练语言模型是当代 NLP 的核心技术。下一章我们将学习序列标注任务,这是 NLP 中最基础的任务之一。


五、思考与练习

思考题

BERT 的 MLM 预训练有什么局限性?为什么需要 15% 的遮盖率?

比较全参数微调和 LoRA 微调的优缺点。在什么场景下你会选择 LoRA?

GPT-3 的上下文学习是如何工作的?为什么模型不需要参数更新就能适应新任务?

预训练模型的幻觉问题如何解决?RAG 是一种有效的方案吗?

编程练习

使用 BERT 在 SST-2 数据集上进行情感分析微调。比较不同学习率和训练轮数的效果。

使用 LoRA 微调 BERT 进行 NER 任务。比较 LoRA 不同秩 r 对性能的影响。

使用 GPT-2 进行条件文本生成。实验不同的解码策略(贪心、束搜索、采样)对生成质量的影响。


8/12预训练语
0%