🎯 目标:深入理解Transformer架构(整个大模型的基石),掌握BERT/GPT等预训练模型,熟练使用HuggingFace生态进行NLP任务开发。 📋 前置要求:阶段二(神经网络、CNN、RNN/LSTM、PyTorch、注意力机制基础)


本阶段知识依赖图

flowchart TD
    A["阶段二基础<br/>RNN/LSTM + 注意力机制"]
    A --> T["Transformer架构(最核心)"]
    T --> T1[Self-Attention] --> T1a[Multi-Head Attention]
    T --> T2[Positional Encoding]
    T --> T3[Feed-Forward Network]
    T --> T4["Add & Norm<br/>残差连接 + 层归一化"]
    T --> T5[Encoder Block] --> T5a["Encoder × N"]
    T --> T6[Decoder Block] --> T6a["Decoder × N"]
    A --> L[预训练语言模型]
    L --> L1["BERT(Encoder-only)"] --> L1a[文本分类、NER、阅读理解]
    L --> L2["GPT(Decoder-only)"] --> L2a[文本生成、对话]
    L --> L3[Seq2Seq模型] --> L3a[翻译、摘要]
    A --> H["HuggingFace生态(实战工具)"]
    H --> H1[Tokenizer] --> H1a[文本编码/解码]
    H --> H2[Datasets] --> H2a[数据集加载与处理]
    H --> H3[Pipeline] --> H3a[一行代码完成推理]
    H --> H4[Trainer] --> H4a[模型训练框架]
    H --> H5[Evaluate] --> H5a[评估指标]

模块一:Transformer架构——大模型的基石 ⭐⭐⭐

这是整个课程中最重要的一个模块。 Transformer是GPT、BERT、LLaMA、ChatGPT等所有现代大模型的基础架构。理解了Transformer,就理解了大模型的"骨架"。

1.1 从宏观视角理解Transformer

Transformer之前的问题——为什么需要它?

RNN/LSTM的两大致命缺陷

  1. 串行计算(效率低):处理第5个词时,必须等第1-4个词全部处理完 → 无法利用GPU的并行能力 → 一个1000词的句子,需要串行计算1000步。
  2. 长距离依赖(效果差):虽然LSTM缓解了梯度消失,但信息仍然需要"逐步传递"。第1个词的信息要经过999步才能到达第1000个词 → 每一步都有信息损失 → 超长序列效果仍然不好。

Transformer的革命性解决方案

  1. 完全抛弃循环结构,用Self-Attention替代 → 所有位置同时计算(并行),训练速度提升10-100倍。
  2. 任意两个位置直接相连(不需要逐步传递) → 第1个词和第1000个词之间只有一步之遥 → 天然支持长距离依赖。

Transformer的整体结构——先看大局

类比:翻译任务就像"理解+写作"两个步骤。

Encoder(编码器) = “理解” —— 读懂原文。输入:“I love AI”,输出:每个词的"理解向量"(包含上下文信息)。

Decoder(解码器) = “写作” —— 根据理解写出译文。输入:已生成的部分译文 + Encoder的理解,输出:下一个词的概率分布。

原论文标题:“Attention Is All You Need”(2017年,Google)

核心洞察:不需要循环(RNN),不需要卷积(CNN),只需要注意力机制就够了!

1.2 Self-Attention——Transformer的灵魂

直觉理解:Self-Attention到底在做什么?

类比:一场会议讨论

想象一个6人会议,讨论"The cat sat on the mat"这个句子的含义:

每个人(词)都要弄清楚"我和谁的关系最密切"。

“cat"发言时:

  • 看了看"The”:嗯,你是我的限定词,关系一般(权重0.05)
  • 看了看自己"cat":我当然了解自己(权重0.15)
  • 看了看"sat":你是我做的动作!关系最紧密(权重0.60)
  • 看了看"on":介词,关系不大(权重0.05)
  • 看了看了看"the":和我没关系(权重0.03)
  • 看了看了看"mat":我坐在你上面,有点关系(权重0.12)

$"cat"的新表示 = 0.05 \times \text{The} + 0.15 \times \text{cat} + 0.60 \times \text{sat} + 0.05 \times \text{on} + 0.03 \times \text{the} + 0.12 \times \text{mat}$

现在"cat"的向量不仅包含自己的信息,还融合了所有相关词的信息!特别是"sat"的信息(权重最大),所以"cat"现在"知道"自己是句子的主语。

Self-Attention的数学计算:Query, Key, Value——完整推导

为什么要引入Q、K、V三个概念?

类比:图书馆找书

  • 你(Query):想找一本关于"深度学习"的书
  • 书架上的书(Key):每本书的标签/标题
  • 书的内容(Value):每本书的实际内容

匹配过程:

  1. 你的需求(Query)和每本书的标签(Key)做匹配 → 得到相关性分数
  2. 根据相关性分数,从书的内容(Value)中提取信息
  3. 相关性高的书,多提取一些;相关性低的书,少提取一些

具体计算步骤(带维度标注)

假设:句子长度 seq_len=4,模型维度 d_model=512,头数 n_heads=8,每头维度 $d_k = 64$

输入矩阵 $X$:(4, 512) — 4个词,每个词512维

Step 1:生成Q、K、V(通过3个不同的线性变换)

$$ \begin{aligned} Q &= X \cdot W_Q \quad (4, 512) \times (512, 64) = (4, 64) \quad \text{← 每个词的"需求"} \\ K &= X \cdot W_K \quad (4, 512) \times (512, 64) = (4, 64) \quad \text{← 每个词的"特征标签"} \\ V &= X \cdot W_V \quad (4, 512) \times (512, 64) = (4, 64) \quad \text{← 每个词的"实际内容"} \end{aligned} $$

为什么需要3个不同的变换?因为一个词的"我需要什么"(Q)、“我能提供什么”(K)、“我的内容”(V)是不同的。

Step 2:计算注意力分数

$$ \text{scores} = Q \cdot K^T \quad (4, 64) \times (64, 4) = (4, 4) \quad \text{← 每对词之间的相关性} $$

Step 3:缩放(除以 $\sqrt{d_k} = \sqrt{64} = 8$)

$$ \text{scaled\_scores} = \text{scores} / 8 $$

为什么要缩放?当 $d_k$ 很大时,$Q \cdot K^T$ 的值可能很大。大的输入值会导致softmax输出接近one-hot(梯度接近0)。除以 $\sqrt{d_k}$ 让方差回到1,softmax的梯度正常。

Step 4:Softmax归一化

$$ \text{attention\_weights} = \text{softmax}(\text{scaled\_scores}) \quad (4, 4) $$

现在每行都是一个概率分布,表示"每个词应该关注谁"。

Step 5:加权求和

$$ \text{output} = \text{attention\_weights} \cdot V \quad (4, 4) \times (4, 64) = (4, 64) $$

每个词的输出 = 所有词的Value的加权和(权重=注意力分数)。

一句话总结:Self-Attention让每个词都能"看到"句子中的所有其他词,并根据相关性加权聚合信息,生成包含上下文信息的新表示。

1.3 位置编码——告诉模型"词的顺序"

为什么需要位置编码?

核心问题:Self-Attention是"位置无关"的。

“狗咬人"和"人咬狗”——如果只看Self-Attention的计算,两个句子包含的词相同,Q、K、V的计算方式相同 → Self-Attention无法区分这两个句子!但它们的意思完全不同!→ 必须额外告诉模型"每个词在哪个位置"。

正弦位置编码——为什么用sin/cos?

$$ \begin{aligned} PE(pos, 2i) &= \sin(pos / 10000^{2i/d_{\text{model}}}) \\ PE(pos, 2i+1) &= \cos(pos / 10000^{2i/d_{\text{model}}}) \end{aligned} $$

其中 $pos$ 是词在句子中的位置(0, 1, 2, …),$i$ 是编码维度的索引(0, 1, 2, …, $d_{\text{model}}/2-1$),$d_{\text{model}}$ 是模型维度。

为什么选择正弦/余弦?三个巧妙的原因

  1. 每个位置有唯一的编码:sin和cos的组合可以唯一标识每个位置,就像每个GPS坐标是唯一的一样。
  2. 相对距离可以用线性变换表示:$PE(pos+k)$ 可以用 $PE(pos)$ 的线性变换来表示(因为 $\sin(a+b) = \sin(a)\cos(b) + \cos(a)\sin(b)$)→ 模型可以学到"距离关系"(相邻、隔一个、隔两个…)。
  3. 可以外推到更长的序列:正弦函数是周期性的,可以计算任意位置的编码 → 即使训练时没见过1000个词的句子,推理时也能处理。

词嵌入 + 位置编码——两者如何结合?

$$ \text{最终输入} = \text{Token Embedding} + \text{Positional Encoding} $$
  • Token Embedding:词本身的语义信息(可学习的参数矩阵)。“猫” → [0.2, 0.8, -0.1, …](512维向量)
  • Positional Encoding:词的位置信息(固定的正弦函数)。位置0 → [0.0, 1.0, 0.0, …](512维向量)

相加后:[0.2, 1.8, -0.1, …]。模型同时知道"这个词是猫"(语义)和"它在第1个位置"(位置)。

类比:就像给每个词发了一张"身份证",上面写着姓名(Token Embedding)和地址(Positional Encoding)。

1.4 多头注意力与Encoder Block

多头注意力——为什么要"多头"?

类比:多角度分析

单头注意力 = 一个人看问题,只能从一个角度理解。多头注意力 = 多个人同时看问题,每人从不同角度理解,最后综合。

例如理解"The animal didn’t cross the street because it was too tired":

  • Head 1 可能学到:“it"指代"animal”(指代关系)
  • Head 2 可能学到:“tired"修饰"animal”(修饰关系)
  • Head 3 可能学到:“didn’t cross"和"tired"有因果关系(逻辑关系)

单头很难同时学到这三种关系,但多头可以!

数学实现——维度追踪

假设 $d_{\text{model}} = 512$,$n_{\text{heads}} = 8$,$d_k = d_{\text{model}} / n_{\text{heads}} = 64$

输入 $X$:(batch, seq_len, 512)

$$ \text{head}_i = \text{Attention}(X \cdot W_{Q_i}, X \cdot W_{K_i}, X \cdot W_{V_i}) $$$$ = \text{softmax}\left(\frac{(X \cdot W_{Q_i})(X \cdot W_{K_i})^T}{\sqrt{64}}\right) \cdot (X \cdot W_{V_i}) $$

每个head的输出:(batch, seq_len, 64)

合并所有头:

$$ \text{MultiHead} = \text{Concat}(\text{head}_1, \ldots, \text{head}_8) \cdot W_O = (batch, \text{seq\_len}, 8 \times 64) \cdot (512, 512) = (batch, \text{seq\_len}, 512) $$

输出维度和输入维度相同(512),方便堆叠多层!

Encoder Block的完整结构——逐层理解

输入 $x$:(batch, seq_len, 512)

  1. Multi-Head Self-Attention(x, x, x) → 输出:(batch, seq_len, 512)
  2. Add & LayerNorm:$\text{norm}_1(x + \text{attn\_output})$。残差连接:保留原始信息 + 新学到的信息。层归一化:稳定数值范围。
  3. Feed-Forward Network:Linear(512 → 2048) → ReLU → Linear(2048 → 512)
  4. Add & LayerNorm:$\text{norm}_2(h + \text{ffn\_output})$

输出:(batch, seq_len, 512) ← 维度不变,可以堆叠N层。

Feed-Forward Network(FFN)——每个位置的"独立思考”

$$ \text{FFN}(x) = \max(0, x \cdot W_1 + b_1) \cdot W_2 + b_2 $$

维度变化:512 → 2048 → 512。先升维4倍(512→2048),增加表达能力,再降回原维度(2048→512),方便后续处理。

关键:FFN对每个位置是独立计算的!位置1的FFN不看位置2、3、4的输出。FFN的作用是"独立思考":在Attention已经收集了全局信息后,每个位置独立地对这些信息做非线性变换。

Attention vs FFN的分工

  • Attention:收集信息(“看看别人说了什么”)→ 类比:看参考资料
  • FFN:处理信息(“想想自己该怎么理解”)→ 类比:独立答题

1.5 Masked Self-Attention与Decoder

为什么Decoder需要Mask?

类比:考试时不能偷看答案

训练时,我们知道完整的译文:“我 爱 大 模型”。但我们不能让模型"偷看"未来的词!

  • 预测"爱"时:只能看到 “<start>” 和 “我”
  • 预测"大"时:只能看到 “<start>"、“我” 和 “爱”
  • 预测"模型"时:只能看到 “<start>"、“我”、“爱” 和 “大”

如果不加Mask,模型会直接抄答案,什么都学不到!

Mask的实现——下三角矩阵

注意力分数矩阵(4个词):

start
start

✓ = 可以看到(正常计算注意力),✗ = 不能看到(设为 $-\infty$,softmax后变为0)

$$ \text{scores} = \text{scores.masked\_fill}(\text{mask} == 0, -10^9) $$

Encoder-Decoder Cross-Attention——连接两个世界的桥梁

Decoder的第二个Attention层(Cross-Attention):

  • Query来自Decoder(“我在找什么信息?")
  • Key和Value来自Encoder(“源语言提供了什么信息?")

类比:翻译时的"参考原文”。Query = 你正在翻译的当前词的需求(“我现在需要翻译’爱’,原文中哪里有相关信息?")。Key/Value = 原文中每个词的信息。

Cross-Attention让Decoder在生成每个译文词时,都能"回头看"原文的相关部分 → 这就是"注意力对齐”:自动学到"哪个译文词对应哪个原文词”。

1.6 Transformer代码实现

import torch
import torch.nn as nn
import math

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        
        self.W_Q = nn.Linear(d_model, d_model)
        self.W_K = nn.Linear(d_model, d_model)
        self.W_V = nn.Linear(d_model, d_model)
        self.W_O = nn.Linear(d_model, d_model)
    
    def forward(self, Q, K, V, mask=None):
        batch_size = Q.size(0)
        
        Q = self.W_Q(Q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
        K = self.W_K(K).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
        V = self.W_V(V).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
        
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
        
        attn_weights = torch.softmax(scores, dim=-1)
        context = torch.matmul(attn_weights, V)
        
        context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
        output = self.W_O(context)
        return output


class TransformerBlock(nn.Module):
    def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
        super().__init__()
        self.attention = MultiHeadAttention(d_model, n_heads)
        self.norm1 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model)
        )
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
    
    def forward(self, x, mask=None):
        attn_output = self.attention(x, x, x, mask)
        x = self.norm1(x + self.dropout(attn_output))
        
        ffn_output = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_output))
        
        return x

1.7 核心总结

Transformer的五大核心创新

  1. Self-Attention:让每个位置都能直接访问其他所有位置 → 解决了RNN的"逐步传递"问题
  2. Multi-Head Attention:从多个角度并行建模关系 → 同时捕获语法、语义、位置等多种关系
  3. Positional Encoding:用正弦/余弦函数注入位置信息 → 让模型知道"词的顺序”
  4. 残差连接 + LayerNorm:稳定深层网络训练 → 来自ResNet的智慧,让梯度可以"跳过"层
  5. 并行计算:所有位置同时计算 → 训练速度比RNN快10-100倍

Transformer vs RNN 对比

特性RNNTransformer
计算方式串行(逐步)并行(所有位置同时)
长距离依赖困难(梯度消失)容易(直接连接)
计算复杂度$O(n \cdot d^2)$$O(n^2 \cdot d)$
训练速度快(10-100倍)
序列长度限制无理论限制受限于显存($n^2$ 注意力)
参数量多(但效果更好)
实际表现较好显著更好(大模型时代的基石)

模块二:BERT与GPT——预训练语言模型

2.1 BERT——双向理解的大模型

BERT的核心思想——为什么要"双向”?

类比:阅读理解

  • 单向(GPT):从左到右阅读,像"遮住后面的文字"。“I went to the bank to [???]” → 只知道前面是"bank",不知道后面是"deposit"还是"river" → 无法确定"bank"是银行还是河岸。
  • 双向(BERT):同时看前后文,像"通读全文后回答问题"。“I went to the [bank] to deposit money” → 看到了后面的"deposit",确定"bank"是银行。

双向理解 = 更好的语义理解能力。

BERT的两个预训练任务——从海量文本中学习语言

任务1:Masked Language Model(MLM)——完形填空

训练方式:随机遮盖15%的词,让模型预测被遮盖的词。输入:“The [MASK] sat on the mat”,目标:预测 [MASK] = “cat”。

为什么遮盖15%而不是100%?遮盖太多:上下文信息不足,模型猜不准。遮盖太少:训练效率低(每个句子只学一个词)。15%是实验验证的最佳比例。

为什么用[MASK]而不是直接删除?删除后模型知道"这里缺了一个词"。用[MASK]替换,模型需要根据上下文推断。

任务2:Next Sentence Prediction(NSP)——判断句子关系

训练方式:给模型两个句子,判断它们是否是连续的。

  • 正例:“他去超市买了牛奶。” + “然后回家做早餐。” → IsNext
  • 负例:“他去超市买了牛奶。” + “今天天气很好。” → NotNext

为什么需要NSP?很多NLP任务需要理解句子之间的关系(问答、推理、自然语言推断)。NSP让模型学到"句子之间的逻辑关系"。

BERT的架构——基于Transformer Encoder

BERT = Transformer Encoder × 12层(BERT-base)或24层(BERT-large)

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

输入表示(三种嵌入相加):Token Embedding + Segment Embedding(句子A/B标记)+ Position Embedding(位置信息)= 最终输入。

特殊标记:[CLS] 放在句子开头,用于分类任务的输出。[SEP] 分隔两个句子。[MASK] 遮盖标记。

BERT微调——一个预训练模型解决多种NLP任务

BERT的强大之处:预训练一次,微调多次。

文本分类:取[CLS]的输出 → 加一个分类头 → 完成。[CLS] 我 很 喜欢 这部 电影 → [CLS的输出] → Linear → softmax → 正面/负面。

命名实体识别:取每个token的输出 → 序列标注。[CLS] 小 明 在 北京 上学 → B-PER I-PER O B-LOC I-LOC O。

问答系统:取每个token的输出 → 预测答案的起止位置。问题:谁在北京上学?段落:小明在北京上学 → 预测答案起始位置=1(“小”),结束位置=2(“明”)→ 答案:“小明”。

核心思想:BERT已经"理解"了语言,只需要在上面加一个简单的分类头。

2.2 GPT——自回归生成的大模型

GPT的核心思想——预测下一个词

GPT = Generative Pre-trained Transformer = Transformer Decoder

训练目标极其简单:给定前文,预测下一个词。

  • “今天” → 预测"天气"
  • “今天天气” → 预测"很好"
  • “今天天气很好” → 预测"。"

这就是自回归(Autoregressive)生成:每次只生成一个词,然后把这个词加入输入,继续生成下一个词。就像"滚雪球"一样,越滚越大。

GPT vs BERT——核心区别对比

特性BERTGPT
架构Transformer EncoderTransformer Decoder
方向双向(同时看前后文)单向(只看前面的词)
训练目标遮盖词预测(MLM)预测下一个词(自回归)
注意力没有Mask,所有位置互相看有Mask,只能看前面的位置
擅长任务理解类(分类、NER、问答)生成类(文本生成、对话)
代表模型BERT, RoBERTa, ALBERTGPT-1/2/3/4, ChatGPT

核心区别:BERT像"阅读理解"——读完全文再回答。GPT像"写作"——一个字一个字地写。

GPT系列演进——从学术到改变世界

  • GPT-1 (2018):1.17亿参数,12层Decoder。证明了"预训练+微调"范式的有效性。
  • GPT-2 (2019):15亿参数,48层Decoder。展示了零样本能力(不需要微调就能做任务)。因为"太危险"而延迟发布。
  • GPT-3 (2020):1750亿参数,96层Decoder。展示了少样本学习能力(给几个例子就能做任务)。开启了"大模型时代"。
  • ChatGPT (2022):GPT-3.5 + RLHF(人类反馈强化学习)。通过人类反馈让模型更"对齐"人类意图。改变了整个AI行业。
  • GPT-4 (2023):多模态,推理能力大幅提升。可以理解图片、代码、数学。

2.3 BERT实战——微调中文分类

from transformers import BertTokenizer, BertForSequenceClassification
import torch

# 1. 加载预训练模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)

# 2. 数据预处理
text = "这部电影真的很好看"
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)

# 3. 前向传播
outputs = model(**inputs, labels=torch.tensor([1]))
loss = outputs.loss
logits = outputs.logits    # shape: (1, 2)

# 4. 预测
prediction = torch.argmax(logits, dim=1)  # 0=负面,1=正面
print(f"预测:{'正面' if prediction == 1 else '负面'}")

2.4 预训练语言模型总结对比

模型架构方向预训练任务擅长任务
BERTEncoder双向MLM + NSP分类、NER、问答
GPTDecoder单向预测下一个词文本生成、对话
T5Encoder-Decoder双向文本到文本翻译、摘要、所有任务
LLaMADecoder单向预测下一个词通用大模型
ChatGLMPrefix LM双向混合对话、理解

模块三:HuggingFace生态——NLP开发的瑞士军刀

3.1 Tokenizer——文本与数字的桥梁

Tokenizer到底在做什么?

人类理解文字(“我爱AI”),计算机理解数字([101, 2023, 9932, 102])。Tokenizer = 翻译官:把人类的文字翻译成计算机的数字,以及反过来。

完整流程:“我爱AI” → 分词[“我”, “爱”, “AI”] → 查词表 → [101, 2023, 9932, 102] → 添加特殊标记[CLS]…[SEP] → 填充/截断到固定长度。

不同的分词算法——为什么分词方式很重要?

  • Word-level(按词分割):“I love AI” → [“I”, “love”, “AI”]。问题:词表太大(英文几十万词),无法处理未登录词(OOV)。
  • Character-level(按字符分割):“AI” → [“A”, “I”]。问题:序列太长(一个句子变成几百个字符),语义信息弱。
  • Subword-level(按子词分割)——主流方案:“playing” → [“play”, “##ing”](##表示这是词的后半部分)。“ChatGPT” → [“Chat”, “##G”, “##PT”]。

Subword优点:

  1. 词表大小适中(通常3万-5万)
  2. 能处理任何未登录词(总能拆成子词)
  3. 保留了语义信息(“play"和"playing"共享"play”)

3.2 Datasets——数据集的统一接口

from datasets import load_dataset

# 加载数据集(自动下载和缓存)
dataset = load_dataset('imdb')

# 查看数据结构
print(dataset)

# 查看第一个样本
print(dataset['train'][0])

# 数据预处理(用map函数批量处理)
def preprocess(examples):
    return tokenizer(examples['text'], truncation=True, padding=True)

tokenized_dataset = dataset.map(preprocess, batched=True)

3.3 Pipeline——一行代码完成推理

from transformers import pipeline

# 情感分析——一行代码!
classifier = pipeline('sentiment-analysis')
result = classifier("I love this movie!")

# 文本生成
generator = pipeline('text-generation', model='gpt2')
result = generator("Once upon a time", max_length=50)

# 问答系统
qa = pipeline('question-answering')
result = qa(question="What is AI?", context="AI is artificial intelligence...")

# 命名实体识别
ner = pipeline('ner', grouped_entities=True)
result = ner("My name is John and I live in New York.")

# 零样本分类(不需要训练数据!)
zero_shot = pipeline('zero-shot-classification')
result = zero_shot("This is a great movie!", candidate_labels=["positive", "negative", "neutral"])

Pipeline的魔力:它自动帮你完成所有的预处理、模型加载、后处理。一行代码 = 完整的推理流程。

3.4 Trainer——标准化的训练框架

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=64,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir='./logs',
    evaluation_strategy='epoch',
    save_strategy='epoch',
    load_best_model_at_end=True,
)

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

trainer.train()
results = trainer.evaluate()
trainer.save_model('./my_model')

3.5 实战——中文情感分类完整流程

from datasets import load_dataset
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
from transformers import pipeline

# 1. 加载数据
dataset = load_dataset('csv', data_files='ChnSentiCorp.csv')

# 2. 分词
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
def tokenize_function(examples):
    return tokenizer(examples['text'], padding='max_length', truncation=True, max_length=128)
tokenized = dataset.map(tokenize_function, batched=True)

# 3. 加载预训练模型
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)

# 4. 训练
training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=3,
    per_device_train_batch_size=32,
    learning_rate=2e-5,
    evaluation_strategy='epoch',
)
trainer = Trainer(model=model, args=training_args,
                  train_dataset=tokenized['train'], eval_dataset=tokenized['test'])
trainer.train()

# 5. 预测
classifier = pipeline('sentiment-analysis', model='./results/checkpoint-best')
print(classifier("这部电影太棒了!"))

模块四:NLP实战与综合复习

4.1 NLP高级实战任务

文本摘要——压缩信息

输入:长文本(如新闻文章)。输出:简短摘要。

  • 方法1:抽取式——从原文中挑选重要句子组合成摘要
  • 方法2:生成式——用模型重新"写"一段摘要(更灵活,但可能编造信息)

使用HuggingFace:

summarizer = pipeline('summarization')
summary = summarizer(long_text, max_length=50)

机器翻译——跨语言理解

使用预训练翻译模型:

translator = pipeline('translation_en_to_zh')
result = translator("I love deep learning")
# [{'translation_text': '我喜欢深度学习'}]

4.2 AI写诗项目——综合实战

项目流程:

  1. 数据准备:收集古诗数据集(如唐诗三百首)
  2. 数据预处理:分词、构建词表、转为数字序列
  3. 模型选择:使用GPT2-Chinese进行微调
  4. 训练:在古诗数据上微调模型
  5. 生成:给定开头(如"春风"),让模型生成完整诗歌

关键代码:

from transformers import GPT2LMHeadModel, BertTokenizer
model = GPT2LMHeadModel.from_pretrained('uer/gpt2-chinese-poem')

4.3 阶段总结——核心知识地图

  1. Transformer = Self-Attention + FFN + 残差连接 + LayerNorm
  2. Self-Attention = $Q \cdot K^T / \sqrt{d_k}$ → softmax → $\cdot V$
  3. Multi-Head = 多组Q/K/V并行计算,从不同角度理解
  4. 位置编码 = 正弦/余弦函数(注入顺序信息)
  5. BERT = Transformer Encoder × N(双向,擅长理解)
  6. GPT = Transformer Decoder × N(单向,擅长生成)
  7. HuggingFace = Tokenizer + Datasets + Pipeline + Trainer
  8. 微调 = 预训练模型 + 下游任务分类头 + 少量标注数据

📚 推荐补充资源

知识点推荐资源说明
TransformerJay Alammar《The Illustrated Transformer》图解Transformer,必看
BERTJay Alammar《The Illustrated BERT》图解BERT
GPTAndrej Karpathy《Let’s build GPT》从零实现GPT
HuggingFaceHuggingFace官方课程免费的NLP实战课程
注意力机制斯坦福CS224nNLP经典课程
TokenizerHuggingFace Tokenizers文档分词器详解