第7章:神经网络与Transformer(Neural Networks and Transformers)
一、导读
1.1 本章学习目标
Transformer 架构的提出是 NLP 历史上最重要的里程碑。它彻底抛弃了循环和卷积结构,完全基于注意力机制建模序列关系,在机器翻译、文本生成、语言理解等任务上取得了前所未有的突破。
本章的核心学习目标包括:
- 理解前馈神经网络:激活函数、反向传播、梯度下降
- 掌握循环神经网络(RNN):LSTM、GRU 的结构和梯度问题
- 深入理解注意力机制:从 Seq2Seq + Attention 到自注意力
- 掌握 Transformer 架构:编码器-解码器、多头注意力、位置编码
- 理解 Transformer 的优势:并行计算、长距离依赖建模
- 了解 Transformer 的变体:BERT(编码器)、GPT(解码器)、T5(编码器-解码器)
1.2 为什么本章如此重要
Transformer 是当代 NLP 的核心架构。从 BERT 到 GPT-4,几乎所有突破性的大语言模型都基于 Transformer。理解 Transformer 的工作原理,是理解现代 NLP 的前提。
Transformer 的成功源于三个关键创新:
自注意力机制:直接建模序列中任意两个位置的关系,不受距离限制
并行计算:不像 RNN 需要逐步处理序列,Transformer 可以并行处理所有位置
可扩展性:架构简洁,可以通过增加层数和参数量持续提升性能
1.3 核心问题引导
RNN 为什么难以处理长序列?梯度消失和梯度爆炸的原因是什么?
注意力机制解决了什么问题?它如何工作?
自注意力机制与 RNN 的注意力有什么区别?
Transformer 的位置编码为什么必要?有哪些实现方式?
多头注意力的直觉是什么?为什么需要多个注意力头?
为什么 Transformer 能够取代 RNN 成为 NLP 的主流架构?
二、核心概念详解
7.1 前馈神经网络回顾
7.1.1 基本结构
前馈神经网络(FFN)由输入层、隐藏层和输出层组成。每一层通过权重矩阵、偏置向量和激活函数进行变换:
$$\mathbf{h} = \sigma(\mathbf{W}\mathbf{x} + \mathbf{b})$$
7.1.2 激活函数
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
def relu(x):
return np.maximum(0, x)
def tanh(x):
return np.tanh(x)
def gelu(x):
return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))7.1.3 反向传播
反向传播算法通过链式法则计算损失函数对每个参数的梯度:
$$\frac{\partial \mathcal{L}}{\partial \mathbf{w}} = \frac{\partial \mathcal{L}}{\partial \mathbf{y}} \cdot \frac{\partial \mathbf{y}}{\partial \mathbf{w}}$$
7.2 循环神经网络(RNN)
7.2.1 基本 RNN
RNN 通过隐藏状态传递序列信息:
$$\mathbf{h}_t = \tanh(\mathbf{W}_{hh} \mathbf{h}_{t-1} + \mathbf{W}_{xh} \mathbf{x}_t + \mathbf{b}_h)$$
$$\mathbf{y}_t = \mathbf{W}_{hy} \mathbf{h}_t + \mathbf{b}_y$$
class SimpleRNN:
def __init__(self, input_dim, hidden_dim, output_dim):
self.Wxh = np.random.randn(hidden_dim, input_dim) * 0.01
self.Whh = np.random.randn(hidden_dim, hidden_dim) * 0.01
self.Why = np.random.randn(output_dim, hidden_dim) * 0.01
self.bh = np.zeros((hidden_dim, 1))
self.by = np.zeros((output_dim, 1))
def forward(self, xs):
H, T = len(xs), self.Whh.shape[0]
hs = np.zeros((T, H))
for t in range(T):
prev_h = hs[t-1] if t > 0 else np.zeros((H, 1))
hs[t] = np.tanh(self.Wxh @ xs[t] + self.Whh @ prev_h + self.bh)
ys = self.Why @ hs + self.by
return ys, hs7.2.2 梯度消失与梯度爆炸
RNN 在反向传播时,梯度需要沿时间步回传。由于反复乘以权重矩阵 $\mathbf{W}_{hh}$,梯度会指数级增长或衰减:
- 梯度消失:权重矩阵的特征值 < 1,梯度趋向 0,远距离信息丢失
- 梯度爆炸:权重矩阵的特征值 > 1,梯度趋向无穷大
7.2.3 LSTM(长短期记忆网络)
LSTM 通过门控机制解决梯度消失问题:
$$\mathbf{f}_t = \sigma(\mathbf{W}_f [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_f) \quad \text{(遗忘门)}$$
$$\mathbf{i}_t = \sigma(\mathbf{W}_i [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_i) \quad \text{(输入门)}$$
$$\tilde{\mathbf{c}}_t = \tanh(\mathbf{W}_c [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_c) \quad \text{(候选记忆)}$$
$$\mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t \quad \text{(更新记忆)}$$
$$\mathbf{o}_t = \sigma(\mathbf{W}_o [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_o) \quad \text{(输出门)}$$
$$\mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t)$$
7.2.4 GRU(门控循环单元)
GRU 是 LSTM 的简化版本,只有两个门(更新门和重置门),参数更少但性能相当。
7.3 注意力机制
7.3.1 Seq2Seq 与注意力
经典 Seq2Seq 模型将整个输入序列编码为固定长度的向量,然后解码输出序列。这导致信息瓶颈——长序列的信息难以压缩到一个向量中。
注意力机制允许解码器在每一步直接访问编码器的所有隐藏状态:
$$\mathbf{c}_t = \sum_{s=1}^{S} \alpha_{ts} \mathbf{h}_s$$
$$\alpha_{ts} = \frac{\exp(e_{ts})}{\sum_{s'=1}^{S} \exp(e_{ts'})}$$
$$e_{ts} = \mathbf{v}_a^T \tanh(\mathbf{W}_a [\mathbf{s}_{t-1}; \mathbf{h}_s])$$
class Attention:
def __init__(self, hidden_dim):
self.W_a = np.random.randn(hidden_dim, 2 * hidden_dim) * 0.01
self.v_a = np.random.randn(hidden_dim, 1) * 0.01
def forward(self, decoder_state, encoder_outputs):
# decoder_state: (hidden_dim,)
# encoder_outputs: (src_len, hidden_dim)
src_len = encoder_outputs.shape[0]
energies = np.zeros(src_len)
for s in range(src_len):
concat = np.concatenate([decoder_state, encoder_outputs[s]])
energy = np.tanh(self.W_a @ concat)
energies[s] = self.v_a.T @ energy
# softmax
energies = energies - np.max(energies)
attention_weights = np.exp(energies) / np.sum(np.exp(energies))
# context vector
context = np.sum(encoder_outputs * attention_weights[:, np.newaxis], axis=0)
return context, attention_weights7.3.2 自注意力(Self-Attention)
自注意力是 Transformer 的核心。序列中的每个位置都可以直接关注所有其他位置:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
其中:
- $Q = XW_Q$(查询矩阵)
- $K = XW_K$(键矩阵)
- $V = XW_V$(值矩阵)
- $d_k$ 是键向量的维度
import numpy as np
def self_attention(X, W_q, W_k, W_v):
"""
X: (seq_len, d_model)
W_q, W_k, W_v: (d_model, d_k)
"""
Q = X @ W_q # (seq_len, d_k)
K = X @ W_k # (seq_len, d_k)
V = X @ W_v # (seq_len, d_v)
d_k = W_q.shape[1]
scores = Q @ K.T / np.sqrt(d_k) # (seq_len, seq_len)
attention_weights = softmax(scores, axis=-1) # (seq_len, seq_len)
output = attention_weights @ V # (seq_len, d_v)
return output, attention_weights
def softmax(x, axis=-1):
e_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
return e_x / np.sum(e_x, axis=axis, keepdims=True)7.4 Transformer 架构
7.4.1 整体结构
Transformer 采用编码器-解码器结构:
- 编码器:由 N 个相同的层堆叠,每层包含自注意力和前馈网络
- 解码器:由 N 个相同的层堆叠,每层包含掩码自注意力、交叉注意力和前馈网络
7.4.2 多头注意力
多头注意力将 Q、K、V 分成 h 个头,分别计算注意力后拼接:
$$\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h) W_O$$
$$\text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)$$
class MultiHeadAttention:
def __init__(self, d_model=512, n_heads=8):
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads
self.W_q = np.random.randn(d_model, d_model) * 0.02
self.W_k = np.random.randn(d_model, d_model) * 0.02
self.W_v = np.random.randn(d_model, d_model) * 0.02
self.W_o = np.random.randn(d_model, d_model) * 0.02
def forward(self, Q, K, V, mask=None):
batch_size = Q.shape[0]
# 线性变换并分头
Q = Q @ self.W_q # (batch, seq, d_model)
K = K @ self.W_k
V = V @ self.W_v
Q = Q.reshape(batch_size, -1, self.n_heads, self.d_k).transpose(0, 2, 1, 3)
K = K.reshape(batch_size, -1, self.n_heads, self.d_k).transpose(0, 2, 1, 3)
V = V.reshape(batch_size, -1, self.n_heads, self.d_k).transpose(0, 2, 1, 3)
# 注意力计算
scores = Q @ K.transpose(0, 1, 3, 2) / np.sqrt(self.d_k)
if mask is not None:
scores = np.where(mask == 0, -1e9, scores)
attn_weights = softmax(scores, axis=-1)
context = attn_weights @ V
# 合并多头
context = context.transpose(0, 2, 1, 3).reshape(batch_size, -1, self.d_model)
output = context @ self.W_o
return output, attn_weights7.4.3 位置编码
Transformer 没有循环结构,需要显式注入位置信息:
$$PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$
$$PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$
def positional_encoding(max_len, d_model):
pe = np.zeros((max_len, d_model))
position = np.arange(max_len)[:, np.newaxis]
div_term = np.exp(np.arange(0, d_model, 2) * (-np.log(10000.0) / d_model))
pe[:, 0::2] = np.sin(position * div_term)
pe[:, 1::2] = np.cos(position * div_term)
return pe7.4.4 前馈网络与残差连接
每个 Transformer 层还包含:
- 位置级前馈网络:$\text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2$
- 残差连接:$\text{output} = \text{LayerNorm}(x + \text{SubLayer}(x))$
class TransformerLayer:
def __init__(self, d_model=512, d_ff=2048, n_heads=8):
self.attention = MultiHeadAttention(d_model, n_heads)
self.W1 = np.random.randn(d_model, d_ff) * 0.02
self.b1 = np.zeros(d_ff)
self.W2 = np.random.randn(d_ff, d_model) * 0.02
self.b2 = np.zeros(d_model)
def forward(self, x, mask=None):
# 自注意力 + 残差 + LayerNorm
attn_out, _ = self.attention.forward(x, x, x, mask)
x = layer_norm(x + attn_out)
# 前馈网络 + 残差 + LayerNorm
ff_out = relu(x @ self.W1 + self.b1) @ self.W2 + self.b2
x = layer_norm(x + ff_out)
return x
def layer_norm(x, eps=1e-6):
mean = np.mean(x, axis=-1, keepdims=True)
std = np.std(x, axis=-1, keepdims=True)
return (x - mean) / (std + eps)
def relu(x):
return np.maximum(0, x)7.5 Transformer 的变体
7.5.1 BERT(Bidirectional Encoder Representations)
- 只使用编码器
- 双向上下文(Masked Language Model)
- 适合理解类任务
7.5.2 GPT(Generative Pre-trained Transformer)
- 只使用解码器
- 单向(自回归)生成
- 适合生成类任务
7.5.3 T5(Text-to-Text Transfer Transformer)
- 完整的编码器-解码器
- 所有任务统一为 text-to-text 格式
三、实战案例
3.1 使用 PyTorch 实现 Transformer
import torch
import torch.nn as nn
import math
class TransformerModel(nn.Module):
def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6, dim_feedforward=2048, max_len=512):
super().__init__()
self.d_model = d_model
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = self._build_pos_encoding(max_len, d_model)
encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout=0.1, batch_first=True)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers)
self.output_layer = nn.Linear(d_model, vocab_size)
def _build_pos_encoding(self, max_len, d_model):
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() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
return pe.unsqueeze(0)
def forward(self, src, src_mask=None):
x = self.embedding(src) * math.sqrt(self.d_model)
x = x + self.pos_encoding[:, :src.size(1)].to(src.device)
x = self.transformer(x, mask=src_mask)
return self.output_layer(x)
# 使用示例
model = TransformerModel(vocab_size=10000, d_model=512, nhead=8, num_layers=6)
src = torch.randint(0, 10000, (2, 20)) # batch_size=2, seq_len=20
output = model(src)
print(f"Output shape: {output.shape}") # (2, 20, 10000)3.2 使用 Hugging Face Transformers
from transformers import BertModel, BertTokenizer
import torch
# 加载预训练 BERT
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")
# 编码文本
text = "Natural language processing is fascinating."
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
# 获取 BERT 输出
with torch.no_grad():
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state
pooler_output = outputs.pooler_output
print(f"Last hidden states shape: {last_hidden_states.shape}")
print(f"Pooler output shape: {pooler_output.shape}")四、本章小结
本章系统讲解了神经网络与 Transformer:
RNN 与梯度问题:RNN 通过隐藏状态传递序列信息,但面临梯度消失/爆炸问题。LSTM/GRU 通过门控机制缓解。
注意力机制:允许模型动态关注输入的不同部分,解决了 Seq2Seq 的信息瓶颈。
自注意力:序列中每个位置直接关注所有其他位置,计算复杂度 O(n²) 但可以并行。
Transformer 架构:多头注意力 + 位置编码 + 前馈网络 + 残差连接 + LayerNorm。
变体:BERT(双向理解)、GPT(单向生成)、T5(编码器-解码器)。
Transformer 的成功直接催生了下一章的预训练语言模型。理解 Transformer 是理解 BERT、GPT 等模型的前提。
五、思考与练习
思考题
为什么 Transformer 需要位置编码?没有位置编码会怎样?
比较自注意力和 RNN 在建模长距离依赖方面的优劣。
多头注意力中,不同的头可能学到什么不同的模式?
Transformer 的计算复杂度是 O(n²d),当序列很长时有什么问题?有哪些解决方案?
编程练习
从零实现一个简化的 Transformer 编码器,在小型文本分类任务上训练。
可视化自注意力权重矩阵,分析不同层、不同头的注意力模式。
比较 BERT 和 GPT 在同一任务(如情感分析)上的性能差异。