第三阶段:NLP 与序列建模
3.1 文本的数字化表示
One-Hot 编码 — 最简单也最没用
python
vocab = ["我", "爱", "AI", "学习"]
# "AI" → [0, 0, 1, 0]
# "学习" → [0, 0, 0, 1]问题:
- 维度 = 词汇量(几万到几十万),极其稀疏
- 所有词之间距离相等,"猫"和"狗"的距离 = "猫"和"火箭"的距离
词频 — TF-IDF
- TF:词
在文档 中出现的频率 - IDF:逆文档频率,在所有文档中越常见的词权重越低
TF-IDF 解决了"常见词不重要"的问题,但依然无法捕获语义相似性。
词嵌入 (Word Embedding) — 2013 年的革命
词嵌入的核心思想:一个词的含义由它周围的词决定(分布语义假说,Distributional Hypothesis)。
python
"""
Word2Vec 跳字模型 (Skip-Gram) 的完整实现
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
class SkipGram(nn.Module):
"""
Skip-Gram: 用中心词预测上下文词
训练目标:最大化 log P(上下文词 | 中心词)
直观理解:
输入 "我 爱 深度 学习" → 中心词="深度"
目标: 最大化 P("爱"|"深度") + P("学习"|"深度")
"""
def __init__(self, vocab_size: int, embed_dim: int = 100):
super().__init__()
# 中心词嵌入: 将词索引映射到向量
self.center_embed = nn.Embedding(vocab_size, embed_dim)
# 上下文词嵌入: 注意!和中心词嵌入是不同的矩阵
self.context_embed = nn.Embedding(vocab_size, embed_dim)
# 初始化(小随机值)
nn.init.uniform_(self.center_embed.weight, -0.05, 0.05)
nn.init.uniform_(self.context_embed.weight, -0.05, 0.05)
def forward(self, center: torch.Tensor, pos_context: torch.Tensor,
neg_context: torch.Tensor):
"""
Args:
center: 中心词索引 (batch,)
pos_context: 正样本(真实上下文词) (batch,)
neg_context: 负样本(随机采样的假上下文词) (batch, num_neg)
"""
# 1. 获取嵌入
center_vec = self.center_embed(center) # (batch, embed_dim)
pos_vec = self.context_embed(pos_context) # (batch, embed_dim)
neg_vec = self.context_embed(neg_context) # (batch, num_neg, embed_dim)
# 2. 正样本得分(点积 → sigmoid 概率)
pos_score = (center_vec * pos_vec).sum(dim=1) # (batch,)
pos_score = torch.sigmoid(pos_score)
# 3. 负样本得分(点积 → sigmoid 概率)
neg_score = torch.bmm(neg_vec, center_vec.unsqueeze(2)).squeeze(2)
neg_score = torch.sigmoid(neg_score) # (batch, num_neg)
# 4. 损失 = -log(正样本概率) - Σlog(1-负样本概率)
pos_loss = -torch.log(pos_score + 1e-8).mean()
neg_loss = -torch.log(1 - neg_score + 1e-8).mean()
return pos_loss + neg_loss
# === 训练后使用 ===
def find_similar_words(model, word_idx, embedding_matrix, vocab, top_k=5):
"""在训练好的词向量中找最相似的词"""
word_vec = embedding_matrix[word_idx]
# 余弦相似度
similarities = F.cosine_similarity(
word_vec.unsqueeze(0), embedding_matrix, dim=1
)
top_indices = similarities.argsort(descending=True)[1:top_k+1]
return [(vocab[i], similarities[i].item()) for i in top_indices]
# 经典结果:
# vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")这一发现震惊了 NLP 界:词向量竟然可以"做算术"!语义关系被编码为向量空间中的方向。
GloVe — 基于全局统计的词向量
GloVe (Global Vectors) 结合了 Word2Vec 的优点和全局共现矩阵的信息:
其中
3.2 循环神经网络 (RNN)
RNN 是第一个能真正处理序列的神经网络。它的核心思想很简单:在处理当前词时,参考之前所有词的信息。
mermaid
graph LR
subgraph "RNN 展开 (Unrolled)"
H0["h₀<br/>(初始状态)"] --> H1["h₁ = tanh(W_h·h₀ + W_x·x₁)"]
X1["x₁<br/>'我'"] --> H1
H1 --> H2["h₂ = tanh(W_h·h₁ + W_x·x₂)"]
X2["x₂<br/>'爱'"] --> H2
H2 --> H3["h₃ = tanh(W_h·h₂ + W_x·x₃)"]
X3["x₃<br/>'AI'"] --> H3
end
H1 --> Y1["ŷ₁"]
H2 --> Y2["ŷ₂"]
H3 --> Y3["ŷ₃"]数学定义:
每一时刻的隐藏状态
RNN 的致命弱点
长距离依赖:
"我出生在法国。我在那里度过了童年。我会说法语。"
RNN要记住第1句的"法国",到第3句才能理解"法语"。但实际上:
- 经过 10+ 时间步后,梯度已经衰减到近乎 0(梯度消失)
- 模型"忘记"了前面的信息LSTM — 带"门控"的记忆网络
LSTM 通过三个"门"来解决长距离依赖问题:
mermaid
graph TD
subgraph "LSTM 单元内部结构"
CT1["c_{t-1}<br/>长期记忆"] --> GATE1["遗忘门 f_t<br/>σ(W_f·[h_{t-1}, x_t] + b_f)<br/>决定丢弃哪些旧记忆"]
GATE1 --> CT2["c_t (更新后)"]
CONCAT["拼接 [h_{t-1}, x_t]"] --> GATE2["输入门 i_t<br/>σ(W_i·[h_{t-1}, x_t] + b_i)<br/>决定写入哪些新信息"]
GATE2 --> CT2
CONCAT --> GATE3["候选记忆 c̃_t<br/>tanh(W_c·[h_{t-1}, x_t] + b_c)"]
GATE3 --> CT2
CT2 --> GATE4["输出门 o_t<br/>σ(W_o·[h_{t-1}, x_t] + b_o)<br/>决定输出多少"]
GATE4 --> HT["h_t = o_t ⊙ tanh(c_t)<br/>短期输出"]
end
style GATE1 fill:#e74c3c,color:#fff
style GATE2 fill:#2ecc71,color:#fff
style GATE4 fill:#3498db,color:#fff四个公式总结 LSTM:
表示逐元素乘法。 的输出在 (0,1) 之间 → "门"的直觉:0 = 完全关,1 = 完全开。
GRU — LSTM 的简化版
GRU 只有两个门(重置门 + 更新门),参数更少,效果接近:
Seq2Seq 与 Attention
RNN/LSTM 的最大问题:不论输入多长,都必须压缩到最后一个隐藏状态。Attention 机制解决了这个问题。
mermaid
graph TD
subgraph "Encoder"
E1["x₁"] --> E_H1["h₁"]
E2["x₂"] --> E_H2["h₂"]
E3["x₃"] --> E_H3["h₃"]
end
subgraph "Attention"
S1["Decoder 当前状态 s₁"] --> ATTN["计算注意力权重<br/>权重 = softmax(score(s₁, hᵢ))"]
E_H1 --> ATTN
E_H2 --> ATTN
E_H3 --> ATTN
ATTN --> CTX["上下文向量<br/>c = Σ αᵢ·hᵢ"]
end
subgraph "Decoder"
CTX --> D1["生成 y₁"]
D1 --> D2["生成 y₂"]
end
style ATTN fill:#e74c3c,color:#fff注意力权重的计算(以 Luong Attention 为例):
🧭 学习导航
← 上一阶段:classic-neural-networks | 返回总览 | 下一阶段:llm-engineering →
登录后即可发表评论 👇