模块M03: 自然语言处理基础¶
阶段: Stage 4 - 深度学习 预计学习时间: 3-4小时(理论)+ 3-4小时(实践) 难度: ⭐⭐⭐⭐ 中高等
📚 学习目标¶
完成本模块后,你将能够:
- ✅ 理解词嵌入技术(Word2Vec、GloVe、FastText)的原理与应用
- ✅ 掌握循环神经网络(RNN、LSTM、GRU)处理序列数据的机制
- ✅ 深入理解Transformer架构(Self-Attention、Multi-Head Attention)
- ✅ 熟悉预训练模型(BERT、GPT、T5)的原理与微调方法
- ✅ 能够完成文本分类、命名实体识别、机器翻译等NLP任务
- ✅ 掌握使用Hugging Face Transformers库进行模型微调
🎯 核心知识点¶
1. 词嵌入 (Word Embeddings)¶
1.1 为什么需要词嵌入?¶
传统表示方法的问题:
One-Hot编码:
vocab = ["king", "queen", "man", "woman", "apple"]
"king" = [1, 0, 0, 0, 0]
"queen" = [0, 1, 0, 0, 0]
缺点: - 维度灾难(词汇量10万 → 10万维向量) - 无法表示词语之间的语义关系 - 词向量正交(余弦相似度=0)
词嵌入的优势:
# 词嵌入将词映射到低维稠密向量(如300维)
"king" = [0.50, 0.33, ..., -0.21] # 300维
"queen" = [0.48, 0.31, ..., -0.19] # 语义相近
# 可以进行向量运算
vec("king") - vec("man") + vec("woman") ≈ vec("queen")
1.2 Word2Vec¶
两种训练方式:
1) CBOW (Continuous Bag of Words):
2) Skip-gram:
网络结构(Skip-gram):
训练技巧: - 负采样 (Negative Sampling): 不计算所有10000个词的softmax,只计算1个正样本+k个负样本(k=5-20) - 层次Softmax (Hierarchical Softmax): 使用二叉树结构,复杂度从O(V)降到O(log V)
代码示例 (使用Gensim):
from gensim.models import Word2Vec
# 训练Word2Vec模型
sentences = [["I", "love", "NLP"], ["Deep", "learning", "is", "fun"]]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, sg=1)
# 查询相似词
similar_words = model.wv.most_similar("love", topn=5)
print(similar_words)
# 向量运算
result = model.wv.most_similar(
positive=['woman', 'king'],
negative=['man'],
topn=1
)
print(result) # 输出: [('queen', 0.87)]
1.3 GloVe (Global Vectors)¶
核心思想: 结合全局统计信息(词共现矩阵)与局部上下文窗口。
训练目标:
其中: -X_ij: 词i和词j的共现次数
- w_i, w_j: 词向量
- f(X_ij): 权重函数(减少高频词影响)
GloVe vs Word2Vec: | 特性 | Word2Vec | GloVe | |------|----------|-------| | 训练方式 | 局部上下文窗口 | 全局共现矩阵 | | 训练速度 | 较快 | 较慢(需统计共现矩阵) | | 性能 | 略低 | 略高 | | 适用场景 | 大规模语料 | 中小规模语料 |
1.4 FastText¶
核心创新: 考虑**子词信息 (Subword Information)**
示例:
优势: - 处理**未登录词 (OOV, Out-of-Vocabulary): - 对**形态丰富的语言(如德语、土耳其语)效果更好
代码示例:
from gensim.models import FastText
model = FastText(sentences, vector_size=100, window=5, min_count=1)
# 处理未登录词
oov_vector = model.wv['unknownword'] # 可以生成向量
2. 循环神经网络 (RNN)¶
2.1 为什么需要RNN?¶
问题: 传统神经网络无法处理变长序列。
RNN的优势: - 共享参数(不同时间步使用相同权重) - 保持历史信息(隐藏状态记忆) - 可处理任意长度序列
RNN结构:
数学公式:
代码示例 (PyTorch):
import torch.nn as nn
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super().__init__()
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x: (batch, seq_len, input_size)
out, hidden = self.rnn(x)
# out: (batch, seq_len, hidden_size)
out = self.fc(out[:, -1, :]) # 取最后时间步
return out
2.2 RNN的问题:梯度消失/爆炸¶
梯度消失:
如果 W < 1,连乘T次后梯度→0(长期依赖消失)
如果 W > 1,连乘T次后梯度→∞(梯度爆炸)
解决方案: - 梯度裁剪 (Gradient Clipping): 限制梯度最大值 - 更好的激活函数: ReLU替代tanh - 门控机制: LSTM、GRU
2.3 LSTM (Long Short-Term Memory)¶
核心思想: 引入**记忆细胞 (Cell State)** 和**三个门控单元**。
LSTM结构:
输入门 遗忘门 输出门
i_t f_t o_t
↓ ↓ ↓
┌────────────────────────┐
│ Cell State (C_t) │
└────────────────────────┘
数学公式:
# 遗忘门: 决定丢弃多少旧记忆
f_t = σ(W_f · [h_{t-1}, x_t] + b_f)
# 输入门: 决定添加多少新信息
i_t = σ(W_i · [h_{t-1}, x_t] + b_i)
C̃_t = tanh(W_C · [h_{t-1}, x_t] + b_C)
# 更新记忆细胞
C_t = f_t ⊙ C_{t-1} + i_t ⊙ C̃_t
# 输出门: 决定输出多少信息
o_t = σ(W_o · [h_{t-1}, x_t] + b_o)
h_t = o_t ⊙ tanh(C_t)
直观理解: - 遗忘门: "忘记不重要的信息" - 输入门: "记住新的重要信息" - 输出门: "输出当前需要的信息"
代码示例:
class LSTMClassifier(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, num_classes)
def forward(self, x):
# x: (batch, seq_len)
embedded = self.embedding(x) # (batch, seq_len, embed_dim)
lstm_out, (hidden, cell) = self.lstm(embedded)
# 取最后隐藏状态
out = self.fc(hidden[-1]) # (batch, num_classes)
return out
2.4 GRU (Gated Recurrent Unit)¶
简化版LSTM: 合并记忆细胞和隐藏状态,只有2个门。
数学公式:
# 重置门
r_t = σ(W_r · [h_{t-1}, x_t])
# 更新门
z_t = σ(W_z · [h_{t-1}, x_t])
# 候选隐藏状态
h̃_t = tanh(W · [r_t ⊙ h_{t-1}, x_t])
# 最终隐藏状态
h_t = (1 - z_t) ⊙ h_{t-1} + z_t ⊙ h̃_t
GRU vs LSTM: | 特性 | LSTM | GRU | |------|------|-----| | 参数量 | 更多(4个门) | 更少(2个门) | | 训练速度 | 较慢 | 较快 | | 性能 | 略高(大数据集) | 略低 | | 适用场景 | 复杂任务 | 简单任务、资源受限 |
3. Transformer架构¶
3.1 为什么需要Transformer?¶
RNN/LSTM的局限: - ❌ 无法并行化(必须按时间步顺序计算) - ❌ 长期依赖问题(虽然LSTM缓解了,但未彻底解决) - ❌ 训练慢(尤其是长序列)
Transformer的优势: - ✅ 完全并行化(所有位置同时计算) - ✅ 长距离依赖直接建模(Self-Attention) - ✅ 可扩展性强(适合大规模预训练)
3.2 Self-Attention机制¶
核心思想: 计算序列中每个词与其他所有词的关联程度。
计算步骤:
1) 生成Q, K, V矩阵:
2) 计算注意力分数:
直观理解:
输入句子: "The cat sat on the mat"
对于单词"cat":
- Q_cat 与所有 K 计算相似度
- 得到注意力分数: [0.1, 0.6, 0.2, 0.05, 0.05]
(The, cat, sat, on, the)
- 对 V 加权求和得到 cat 的新表示
可视化:
The cat sat on the mat
The 0.5 0.2 0.1 0.1 0.05 0.05
cat 0.1 0.6 0.2 0.05 0.05 0.0
sat 0.1 0.3 0.4 0.1 0.05 0.05
...
代码实现:
import torch
import torch.nn as nn
class SelfAttention(nn.Module):
def __init__(self, embed_dim):
super().__init__()
self.embed_dim = embed_dim
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
# x: (batch, seq_len, embed_dim)
Q = self.query(x)
K = self.key(x)
V = self.value(x)
# 计算注意力分数
scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.embed_dim, dtype=torch.float32))
attention_weights = torch.softmax(scores, dim=-1)
# 加权求和
output = torch.matmul(attention_weights, V)
return output, attention_weights
3.3 Multi-Head Attention¶
核心思想: 多个注意力头并行学习不同的特征子空间。
公式:
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) · W_O
其中 head_i = Attention(Q·W_Q^i, K·W_K^i, V·W_V^i)
优势: - 不同头关注不同的语义信息 - Head 1: 语法关系(主谓宾) - Head 2: 语义关系(同义词) - Head 3: 位置关系(相邻词)
示例(8个头):
代码实现:
class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
self.out = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
batch_size, seq_len, embed_dim = x.size()
# 分割成多个头
Q = self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
K = self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
V = self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 计算注意力(并行计算所有头)
scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float32))
attention_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attention_weights, V)
# 合并多个头
output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, embed_dim)
return self.out(output)
3.4 位置编码 (Positional Encoding)¶
问题: Self-Attention对词序不敏感("cat sat" 和 "sat cat" 结果相同)
解决方案: 添加位置信息
正弦位置编码:
代码实现:
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(torch.log(torch.tensor(10000.0)) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :x.size(1)]
3.5 完整Transformer架构¶
Encoder-Decoder结构:
Encoder Block:
Decoder Block:
输入
↓
Masked Multi-Head Attention → Add & Norm
↓
Cross-Attention (with Encoder) → Add & Norm
↓
Feed Forward → Add & Norm
↓
输出
关键组件: - 残差连接 (Residual Connection): 缓解梯度消失 - Layer Normalization: 稳定训练 - Feed Forward Network: 2层全连接 + ReLU - Masked Attention: Decoder中防止看到未来信息
4. 预训练模型¶
4.1 预训练 + 微调范式¶
传统方法(从零训练):
预训练 + 微调:
4.2 BERT (Bidirectional Encoder Representations from Transformers)¶
核心思想: 双向编码器,使用**Masked Language Model**预训练。
预训练任务:
1) Masked Language Model (MLM):
2) Next Sentence Prediction (NSP):
模型架构:
BERT家族: | 模型 | 层数 | 隐藏维度 | 注意力头 | 参数量 | |------|------|---------|---------|--------| | BERT-Base | 12 | 768 | 12 | 110M | | BERT-Large | 24 | 1024 | 16 | 340M | | RoBERTa | 24 | 1024 | 16 | 355M (优化版BERT) | | ALBERT | 12 | 768 | 12 | 12M (参数共享) |
应用场景: - 文本分类 - 命名实体识别 (NER) - 问答系统 - 语义相似度
微调示例 (使用Hugging Face):
from transformers import BertForSequenceClassification, BertTokenizer, Trainer
# 加载预训练模型
model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 准备数据
texts = ["I love NLP", "This is terrible"]
labels = [1, 0]
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
inputs['labels'] = torch.tensor(labels)
# 微调
trainer = Trainer(model=model, train_dataset=inputs)
trainer.train()
4.3 GPT (Generative Pre-trained Transformer)¶
核心思想: 单向解码器,使用**自回归语言模型**预训练。
预训练任务: Causal Language Modeling
模型架构:
GPT演进: | 模型 | 发布年份 | 层数 | 参数量 | 关键特性 | |------|---------|------|--------|---------| | GPT-1 | 2018 | 12 | 117M | 首次提出预训练+微调 | | GPT-2 | 2019 | 48 | 1.5B | Zero-shot学习能力 | | GPT-3 | 2020 | 96 | 175B | Few-shot in-context learning | | GPT-4 | 2023 | ? | >1T | 多模态、更强推理能力 |
GPT vs BERT: | 特性 | BERT | GPT | |------|------|-----| | 架构 | Encoder-only | Decoder-only | | 注意力方向 | 双向 | 单向(因果) | | 预训练任务 | MLM + NSP | 自回归LM | | 适用任务 | 理解类(分类、NER) | 生成类(文本生成、对话) |
4.4 T5 (Text-to-Text Transfer Transformer)¶
核心思想: 将所有NLP任务统一为**文本到文本**转换。
任务统一格式:
# 翻译
"translate English to German: Hello" → "Hallo"
# 分类
"sentiment: This movie is great!" → "positive"
# 摘要
"summarize: [长文本]" → "简短摘要"
# 问答
"question: What is NLP? context: ..." → "答案"
优势: - 统一的输入输出格式 - 一个模型处理多个任务 - 可以轻松添加新任务
T5家族: - T5-Small: 60M参数 - T5-Base: 220M参数 - T5-Large: 770M参数 - T5-3B: 3B参数 - T5-11B: 11B参数
5. 下游任务与微调¶
5.1 文本分类 (Text Classification)¶
任务: 将文本分配到预定义类别
应用场景: - 情感分析(正面/负面) - 垃圾邮件检测 - 新闻分类
微调策略:
# 使用[CLS] token的表示进行分类
[CLS] token1 token2 ... [SEP]
↓
BERT Encoder
↓
[CLS]表示 → Linear → Softmax → 类别概率
5.2 命名实体识别 (NER)¶
任务: 识别文本中的实体(人名、地名、组织名等)
标注格式 (BIO):
微调策略:
# 对每个token进行分类
token1 token2 ... tokenN
↓ ↓ ↓
BERT Encoder
↓ ↓ ↓
Linear Linear Linear
↓ ↓ ↓
B-PER O B-LOC
代码示例:
from transformers import BertForTokenClassification
model = BertForTokenClassification.from_pretrained(
'bert-base-uncased',
num_labels=len(label_list) # B-PER, I-PER, B-LOC, ...
)
5.3 机器翻译 (Machine Translation)¶
任务: 将源语言文本翻译成目标语言
Transformer架构:
训练策略: - Teacher Forcing: 训练时使用真实目标作为Decoder输入 - Beam Search: 推理时保留top-k候选翻译
代码示例:
from transformers import MarianMTModel, MarianTokenizer
# 加载预训练翻译模型
model_name = "Helsinki-NLP/opus-mt-en-zh"
model = MarianMTModel.from_pretrained(model_name)
tokenizer = MarianTokenizer.from_pretrained(model_name)
# 翻译
text = "Hello, how are you?"
inputs = tokenizer(text, return_tensors="pt")
translated = model.generate(**inputs)
print(tokenizer.decode(translated[0], skip_special_tokens=True))
# 输出: "你好,你好吗?"
🛠️ 实践环节¶
任务1: 词嵌入可视化¶
目标: 使用t-SNE可视化Word2Vec词向量,观察语义聚类
关键代码 (notebooks/stage4/04-rnn-text-classification.ipynb 第2节):
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
# 提取词向量
words = ["king", "queen", "man", "woman", "apple", "orange"]
vectors = [model.wv[word] for word in words]
# 降维到2D
tsne = TSNE(n_components=2, random_state=42)
vectors_2d = tsne.fit_transform(vectors)
# 可视化
plt.figure(figsize=(10, 8))
for i, word in enumerate(words):
plt.scatter(vectors_2d[i, 0], vectors_2d[i, 1])
plt.annotate(word, (vectors_2d[i, 0], vectors_2d[i, 1]))
plt.show()
预期结果: "king"-"queen" 距离近,"apple"-"orange" 距离近
任务2: 使用LSTM进行情感分类¶
目标: 在IMDB电影评论数据集上训练LSTM分类器
步骤: 1. 加载IMDB数据集 2. 文本预处理(分词、截断/填充) 3. 构建LSTM模型 4. 训练并评估
预期结果: 测试集准确率 > 85%
任务3: 微调BERT进行文本分类¶
目标: 使用Hugging Face Transformers微调BERT
步骤: 1. 加载预训练BERT模型 2. 准备数据集(tokenization) 3. 定义训练参数 4. 使用Trainer API微调 5. 评估性能
预期结果: - 5 epochs内验证集准确率 > 90% - 对比LSTM: BERT准确率提升5-10%
任务4: 可视化Attention权重¶
目标: 理解Transformer如何关注不同词语
代码:
from transformers import BertTokenizer, BertModel
import matplotlib.pyplot as plt
model = BertModel.from_pretrained('bert-base-uncased', output_attentions=True)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
text = "The cat sat on the mat"
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)
# 提取第一层第一个头的注意力权重
attention = outputs.attentions[0][0, 0].detach().numpy()
# 可视化
plt.imshow(attention, cmap='viridis')
plt.xticks(range(len(tokens)), tokens, rotation=45)
plt.yticks(range(len(tokens)), tokens)
plt.colorbar()
plt.show()
📖 扩展阅读¶
经典论文¶
- Attention Is All You Need (Transformer, 2017)
- 链接: https://arxiv.org/abs/1706.03762
-
阅读时间: 2小时
-
BERT: Pre-training of Deep Bidirectional Transformers (2018)
- 链接: https://arxiv.org/abs/1810.04805
-
阅读时间: 1.5小时
-
Language Models are Few-Shot Learners (GPT-3, 2020)
- 链接: https://arxiv.org/abs/2005.14165
- 阅读时间: 2小时
在线资源¶
- CS224N (Stanford): http://web.stanford.edu/class/cs224n/
- Hugging Face Course: https://huggingface.co/course/
- The Illustrated Transformer: https://jalammar.github.io/illustrated-transformer/
实战项目推荐¶
完成本模块后,建议尝试以下项目:
- 🚀 P06: Transformer翻译系统 - 双框架实现(推荐)
- 🚀 P07: 预训练模型信息提取 - BERT微调
❓ 常见问题 (FAQ)¶
Q1: LSTM和Transformer如何选择?¶
A: 根据任务和数据规模选择: - 小数据集 (<10k样本): LSTM/GRU (参数少,不易过拟合) - 大数据集 (>100k样本): Transformer (性能更好) - 实时推理: LSTM (推理速度快) - 批量推理: Transformer (可并行)
Q2: 如何处理未登录词(OOV)?¶
A: 三种策略:
1. FastText: 使用子词信息
2. WordPiece/BPE: 分词算法(BERT使用)
3. <UNK> token: 替换为特殊标记
Q3: 为什么BERT不能生成文本?¶
A: BERT是**双向编码器**,训练时可以看到未来词,无法用于自回归生成。生成任务需要使用**单向解码器**(如GPT)。
Q4: 微调BERT时如何避免过拟合?¶
A: 5个技巧: 1. 使用较小学习率 (2e-5 - 5e-5) 2. 添加Dropout (0.1 - 0.3) 3. 冻结部分层(只微调后几层) 4. 使用Early Stopping 5. 数据增强(回译、同义词替换)
Q5: Transformer训练很慢怎么办?¶
A: 优化策略: 1. 减少序列长度: 512 → 128(如果任务允许) 2. 使用梯度累积: 模拟大batch size 3. 混合精度训练: FP16代替FP32 4. 使用预训练模型: 避免从零训练 5. 模型蒸馏: 用小模型学习大模型
✅ 学习检查清单¶
完成本模块后,你应该能够:
- 解释Word2Vec的Skip-gram和CBOW训练方式
- 说明LSTM如何解决RNN的梯度消失问题
- 手动计算Self-Attention的输出(给定Q, K, V)
- 解释Multi-Head Attention的优势
- 区分BERT和GPT的架构与预训练任务
- 使用Hugging Face Transformers微调预训练模型
- 可视化并解释Attention权重
- 比较不同NLP模型在特定任务上的性能
⏭️ 下一步¶
完成本模块后,你可以:
- 回顾总结: 复习模块M01和模块M02
- 实战项目: 从项目列表中选择NLP项目开始实践
- 深入研究: 阅读Transformer/BERT/GPT原论文
- 进阶学习: 进入阶段5: AIGC与大模型
准备好了吗?打开 04-rnn-text-classification.ipynb 开始动手实践! 🚀