7语音与语言处理(第3版)

神经网络与Transformer

深度学习NLP

神经网络注意力Transformer

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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 激活函数

python
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$$

python
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, hs

7.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])$$

python
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_weights

7.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$ 是键向量的维度
python
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)$$

python
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_weights

7.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)$$

python
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 pe

7.4.4 前馈网络与残差连接

每个 Transformer 层还包含:

  • 位置级前馈网络:$\text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2$
  • 残差连接:$\text{output} = \text{LayerNorm}(x + \text{SubLayer}(x))$
python
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

python
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

python
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 在同一任务(如情感分析)上的性能差异。


7/12神经网络
0%