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


本阶段知识依赖图

阶段二基础(RNN/LSTM + 注意力机制)
    ├──→ Transformer架构(⭐最核心)
    │       ├── Self-Attention ──→ Multi-Head Attention
    │       ├── Positional Encoding
    │       ├── Feed-Forward Network
    │       ├── Add & Norm(残差连接 + 层归一化)
    │       ├── Encoder Block ──→ Encoder × N
    │       └── Decoder Block ──→ Decoder × N
    ├──→ 预训练语言模型
    │       ├── BERT(Encoder-only)──→ 文本分类、NER、阅读理解
    │       ├── GPT(Decoder-only)──→ 文本生成、对话
    │       └── Seq2Seq模型(Encoder-Decoder)──→ 翻译、摘要
    └──→ HuggingFace生态(实战工具)
            ├── Tokenizer ──→ 文本编码/解码
            ├── Datasets ──→ 数据集加载与处理
            ├── Pipeline ──→ 一行代码完成推理
            ├── Trainer ──→ 模型训练框架
            └── Evaluate ──→ 评估指标

模块一: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的理解
    输出:下一个词的概率分布
                    ┌─────────────────────────────┐
                    │         Decoder              │
                    │  ┌───────────────────────┐  │
                    │  │  Output Embedding      │  │
                    │  │  + Positional Encoding │  │
                    │  └───────────┬───────────┘  │
                    │              ↓               │
                    │  ┌───────────────────────┐  │
                    │  │  Masked Multi-Head     │  │  ← 只能看已生成的词
                    │  │  Attention             │  │
                    │  └───────────┬───────────┘  │
                    │              ↓               │
                    │  ┌───────────────────────┐  │
                    │  │  Multi-Head Attention  │  │  ← 看Encoder的输出
                    │  │  (Encoder-Decoder)     │  │
                    │  └───────────┬───────────┘  │
                    │              ↓               │
                    │  ┌───────────────────────┐  │
                    │  │  Feed-Forward Network  │  │
                    │  └───────────────────────┘  │
                    │          × N层              │
                    └──────────────┬──────────────┘
                    ┌──────────────┴──────────────┐
                    │         Encoder              │
                    │  ┌───────────────────────┐  │
                    │  │  Input Embedding       │  │
                    │  │  + Positional Encoding │  │
                    │  └───────────┬───────────┘  │
                    │              ↓               │
                    │  ┌───────────────────────┐  │
                    │  │  Multi-Head            │  │  ← 所有词互相看
                    │  │  Self-Attention        │  │
                    │  └───────────┬───────────┘  │
                    │              ↓               │
                    │  ┌───────────────────────┐  │
                    │  │  Feed-Forward Network  │  │
                    │  └───────────────────────┘  │
                    │          × N层              │
                    └─────────────────────────────┘
                             Input序列

原论文标题:“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×The + 0.15×cat + 0.60×sat + 0.05×on + 0.03×the + 0.12×mat

现在"cat"的向量不仅包含自己的信息,还融合了所有相关词的信息!
特别是"s"的信息(权重最大),所以"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个不同的线性变换)
    Q = X · W_Q    (4, 512) × (512, 64) = (4, 64)   ← 每个词的"需求"
    K = X · W_K    (4, 512) × (512, 64) = (4, 64)   ← 每个词的"特征标签"
    V = X · W_V    (4, 512) × (512, 64) = (4, 64)   ← 每个词的"实际内容"

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

Step 2:计算注意力分数
    scores = Q · K^T    (4, 64) × (64, 4) = (4, 4)   ← 每对词之间的相关性

    例如:
         w1    w2    w3    w4
    w1 [0.1   0.8   0.05  0.05]
    w2 [0.7   0.1   0.1   0.1 ]
    w3 [0.05  0.1   0.7   0.15]
    w4 [0.1   0.05  0.2   0.65]

    → w1和w2的相关性很高(0.8),w3和w4的相关性很高(0.7)

Step 3:缩放(除以√d_k = √64 = 8)
    scaled_scores = scores / 8

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

Step 4:Softmax归一化
    attention_weights = softmax(scaled_scores)    (4, 4) — 每行和为1

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

Step 5:加权求和
    output = attention_weights · V    (4, 4) × (4, 64) = (4, 64)

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

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

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

为什么需要位置编码?

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

"狗咬人"和"人咬狗"——如果只看Self-Attention的计算:
- 两个句子包含的词相同
- Q、K、V的计算方式相同
→ Self-Attention无法区分这两个句子!

但它们的意思完全不同!→ 必须额外告诉模型"每个词在哪个位置"

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

PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

其中:
- pos:词在句子中的位置(0, 1, 2, ...)
- i:编码维度的索引(0, 1, 2, ..., d_model/2-1)
- d_model:模型维度

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

原因1:每个位置有唯一的编码
    sin和cos的组合可以唯一标识每个位置
    就像每个GPS坐标是唯一的一样

原因2:相对距离可以用线性变换表示
    PE(pos+k) 可以用 PE(pos) 的线性变换来表示
    (因为 sin(a+b) = sin(a)cos(b) + cos(a)sin(b))
    → 模型可以学到"距离关系"(相邻、隔一个、隔两个...)

原因3:可以外推到更长的序列
    正弦函数是周期性的,可以计算任意位置的编码
    → 即使训练时没见过1000个词的句子,推理时也能处理

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

最终输入 = Token Embedding + 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)
    地址:第1个位置(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_model=512, n_heads=8, d_k=d_model/n_heads=64

输入 X:(batch, seq_len, 512)

每个头的处理:
    head_i = Attention(X·W_Q_i, X·W_K_i, X·W_V_i)
           = softmax((X·W_Q_i)(X·W_K_i)^T / √64) · (X·W_V_i)
    
    每个head的输出:(batch, seq_len, 64)

合并所有头:
    MultiHead = Concat(head_1, ..., head_8) · W_O
              = (batch, seq_len, 8×64) · (512, 512)
              = (batch, seq_len, 512)

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

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

输入 x:(batch, seq_len, 512)
  ├──→ Multi-Head Self-Attention(x, x, x)
  │     输出:(batch, seq_len, 512)
  │        │
  │        ↓
  ├──→ Add & LayerNorm:norm₁(x + attn_output)
  │     残差连接:保留原始信息 + 新学到的信息
  │     层归一化:稳定数值范围
  │        │
  │        ↓
  ├──→ Feed-Forward Network
  │     Linear(512 → 2048) → ReLU → Linear(2048 → 512)
  │        │
  │        ↓
  └──→ Add & LayerNorm:norm₂(h + ffn_output)
        输出:(batch, seq_len, 512)  ← 维度不变,可以堆叠N层

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

FFN(x) = max(0, x · W₁ + b₁) · W₂ + b₂

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

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

Attention vs FFN的分工

Attention:收集信息("看看别人说了什么")
FFN:处理信息("想想自己该怎么理解")

类比:一场考试
Attention = 看参考资料(收集信息)
FFN = 独立答题(处理信息)

1.5 Masked Self-Attention与Decoder

为什么Decoder需要Mask?

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

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

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

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

Mask的实现——下三角矩阵

注意力分数矩阵(4个词):
    <start>  我    爱    大
<start>  ✓     ✗    ✗    ✗
我        ✓     ✓    ✗    ✗
爱        ✓     ✓    ✓    ✗
大        ✓     ✓    ✓    ✓

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

实现方式:
mask矩阵 = [[1, 0, 0, 0],
            [1, 1, 0, 0],
            [1, 1, 1, 0],
            [1, 1, 1, 1]]

scores = scores.masked_fill(mask == 0, -1e9)  # 0的位置设为-∞

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  # 每个头的维度
        
        # Q、K、V的线性变换矩阵
        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)
        
        # 线性变换并分头
        # (batch, seq_len, d_model) → (batch, n_heads, seq_len, d_k)
        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)
        
        # 计算注意力分数:Q·K^T / √d_k
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        
        # 应用Mask(如果有的话)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
        
        # Softmax归一化
        attn_weights = torch.softmax(scores, dim=-1)
        
        # 用注意力权重对V加权求和
        context = torch.matmul(attn_weights, V)
        
        # 合并多头:(batch, n_heads, seq_len, d_k) → (batch, seq_len, d_model)
        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):
        # Self-Attention + 残差连接 + LayerNorm
        attn_output = self.attention(x, x, x, mask)  # Q=K=V=x(自注意力)
        x = self.norm1(x + self.dropout(attn_output))
        
        # FFN + 残差连接 + LayerNorm
        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 对比

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

模块二: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"是银行
"I sat on the [bank] of the river"
→ 看到了后面的"river",确定"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——核心区别对比

特性              BERT                          GPT
────────────────────────────────────────────────────────
架构              Transformer Encoder            Transformer Decoder
方向              双向(同时看前后文)            单向(只看前面的词)
训练目标          遮盖词预测(MLM)               预测下一个词(自回归)
注意力            没有Mask,所有位置互相看        有Mask,只能看前面的位置
擅长任务          理解类(分类、NER、问答)       生成类(文本生成、对话)
代表模型          BERT, RoBERTa, ALBERT          GPT-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',    # 返回PyTorch张量
    padding=True,           # 填充到相同长度
    truncation=True         # 截断过长的文本
)
# inputs包含:
#   input_ids: [101, 2023, 3124, 4638, 5011, 2552, 4518, 1993, 102]
#              [CLS] 这   部   电影  真的  很   好看  [SEP]
#   attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1]  (哪些位置是真实词)
#   token_type_ids: [0, 0, 0, 0, 0, 0, 0, 0, 0]  (句子A/B标记)

# 3. 前向传播
outputs = model(**inputs, labels=torch.tensor([1]))  # label=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 预训练语言模型总结对比

模型      架构              方向    预训练任务       擅长任务
────────────────────────────────────────────────────────────────
BERT      Encoder           双向    MLM + NSP        分类、NER、问答
GPT       Decoder           单向    预测下一个词      文本生成、对话
T5        Encoder-Decoder   双向    文本到文本        翻译、摘要、所有任务
LLaMA     Decoder           单向    预测下一个词      通用大模型
ChatGLM   Prefix 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"]
  问题1:词表太大(英文几十万词)
  问题2:无法处理未登录词(OOV)—— "ChatGPT"如果不在词表中怎么办?

Character-level(按字符分割):
  "AI" → ["A", "I"]
  问题:序列太长(一个句子变成几百个字符),语义信息弱

Subword-level(按子词分割)——主流方案:
  "playing" → ["play", "##ing"]     (##表示这是词的后半部分)
  "ChatGPT" → ["Chat", "##G", "##PT"]
  
  优点:
  1. 词表大小适中(通常3万-5万)
  2. 能处理任何未登录词(总能拆成子词)
  3. 保留了语义信息("play"和"playing"共享"play")

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

from datasets import load_dataset

# 加载数据集(自动下载和缓存)
dataset = load_dataset('imdb')           # IMDB电影评论数据集
dataset = load_dataset('csv', data_files='my_data.csv')  # 自定义CSV

# 查看数据结构
print(dataset)
# DatasetDict({
#     train: Dataset({features: ['text', 'label'], num_rows: 25000})
#     test: Dataset({features: ['text', 'label'], num_rows: 25000})
# })

# 查看第一个样本
print(dataset['train'][0])
# {'text': 'I loved this movie...', 'label': 1}

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

tokenized_dataset = dataset.map(preprocess, batched=True)
# map函数会自动把tokenizer应用到每个样本上
# batched=True表示一次处理一批样本(更快)

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

from transformers import pipeline

# 情感分析——一行代码!
classifier = pipeline('sentiment-analysis')
result = classifier("I love this movie!")
# [{'label': 'POSITIVE', 'score': 0.9998}]

# 文本生成
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"]
)
# {'labels': ['positive', 'neutral', 'negative'], 'scores': [0.9, 0.07, 0.03]}

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,      # 每个设备的batch大小
    per_device_eval_batch_size=64,       # 评估时的batch大小
    warmup_steps=500,                    # 预热步数(学习率从0逐渐增大)
    weight_decay=0.01,                   # 权重衰减(L2正则化)
    logging_dir='./logs',                # 日志目录
    evaluation_strategy='epoch',         # 每个epoch评估一次
    save_strategy='epoch',               # 每个epoch保存一次
    load_best_model_at_end=True,         # 训练结束后加载最优模型
)

# 创建Trainer
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

# 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. 预测
from transformers import pipeline
classifier = pipeline('sentiment-analysis', model='./results/checkpoint-best')
print(classifier("这部电影太棒了!"))
# [{'label': 'POSITIVE', 'score': 0.999}]

模块四: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·K^T/√d_k → softmax → ·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. 微调 = 预训练模型 + 下游任务分类头 + 少量标注数据

📝 自测题

  1. Self-Attention:给定3个词的向量,手写Q/K/V的计算过程,标注每个矩阵的维度
  2. Multi-Head:解释为什么需要多头,一个头不行吗?用具体例子说明
  3. 位置编码:为什么Transformer需要位置编码而RNN不需要?解释正弦编码的三个优点
  4. Mask:解释Encoder的Self-Attention和Decoder的Masked Self-Attention的区别
  5. BERT vs GPT:从架构、训练目标、擅长任务三个维度对比
  6. BERT预训练:解释MLM和NSP两个预训练任务,为什么需要它们?
  7. 微调:为什么在预训练模型上微调比从头训练效果好?用"迁移学习"的直觉解释
  8. HuggingFace:用Pipeline实现零样本分类(写代码)
  9. Tokenizer:解释BPE分词算法的工作原理,为什么Subword-level是主流?
  10. 综合:设计一个完整的情感分析系统(从数据到部署),列出每个步骤

📚 推荐补充资源

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