4.6 循环神经网络与 Transformer (RNNs & Transformers)


文档摘要

4.6 循环神经网络与 Transformer (RNNs & Transformers) 第四章:PyTorch 高级主题:4.6 循环神经网络与 Transformer (RNNs & Transformers) 详解与实践 4.6.1 循环神经网络 (RNNs) 循环神经网络 (Recurrent Neural Networks, RNNs) 是一类专门设计用于处理序列数据的神经网络。与传统的前馈神经网络不同,RNNs 引入了循环连接,使得网络能够维持一个内部状态 (hidden state),从而记忆先前输入的信息,并将其应用于后续的输入处理。 4.6.1.1 RNN 的基本原理 RNN 的核心思想是,对于序列中的每个元素,网络不仅接收当前的输入,还接收来自上一个时间步的隐藏状态。

4.6 循环神经网络与 Transformer (RNNs & Transformers)

第四章:PyTorch 高级主题:4.6 循环神经网络与 Transformer (RNNs & Transformers) 详解与实践

4.6.1 循环神经网络 (RNNs)

循环神经网络 (Recurrent Neural Networks, RNNs) 是一类专门设计用于处理序列数据的神经网络。与传统的前馈神经网络不同,RNNs 引入了循环连接,使得网络能够维持一个内部状态 (hidden state),从而记忆先前输入的信息,并将其应用于后续的输入处理。

4.6.1.1 RNN 的基本原理

RNN 的核心思想是,对于序列中的每个元素,网络不仅接收当前的输入,还接收来自上一个时间步的隐藏状态。这个隐藏状态就像网络的“记忆”,能够传递序列的历史信息。

我们可以使用 Mermaid 的 graph TD 图来形象地展示 RNN 的展开结构:

公式表示:

在每个时间步 t,RNN 的计算过程可以表示为:

  • 隐藏状态更新: h_t = f(W_{ih}x_t + W_{hh}h_{t-1} + b_h)

  • 输出计算: o_t = g(W_{ho}h_t + b_o)

其中:

  • x_t 是时间步 t 的输入。

  • h_t 是时间步 t 的隐藏状态。

  • h_{t-1} 是上一个时间步 t-1 的隐藏状态 (对于第一个时间步,h_0 通常初始化为零向量)。

  • o_t 是时间步 t 的输出。

  • W_{ih}W_{hh}W_{ho} 是权重矩阵。

  • b_hb_o 是偏置项。

  • fg 是激活函数,例如 ReLU 或 tanh。

关键特点:

  • 处理序列数据: RNN 的循环结构使其天然适合处理序列数据,例如文本、音频、视频等。

  • 记忆能力: 隐藏状态 h_t 充当“记忆”,能够存储和传递序列的历史信息。

  • 参数共享: 在不同的时间步,RNN 使用相同的权重矩阵 W_{ih}W_{hh}W_{ho},这大大减少了模型的参数数量,并允许模型泛化到不同长度的序列。

4.6.1.2 RNN 的类型:Simple RNN, LSTM, GRU

虽然基本的 RNN 概念简洁而强大,但在实际应用中,简单的 RNN (Simple RNN) 容易遇到梯度消失梯度爆炸的问题,尤其是在处理长序列时。为了解决这些问题,研究人员提出了更复杂的 RNN 变体,例如长短期记忆网络 (LSTM) 和门控循环单元 (GRU)。

1. Simple RNN (Vanilla RNN):

这是最基础的 RNN 结构,其隐藏状态更新公式如前文所述。Simple RNN 的结构相对简单,但由于梯度消失问题,它在处理长序列时表现不佳。

2. 长短期记忆网络 (LSTM):

LSTM (Long Short-Term Memory) 是一种特殊的 RNN 结构,旨在解决 Simple RNN 的梯度消失问题,并更好地捕捉长距离依赖关系。LSTM 引入了门控机制 (gate mechanism),包括遗忘门 (forget gate)输入门 (input gate)输出门 (output gate),以及一个细胞状态 (cell state)

LSTM 的核心组件和公式:

  • 遗忘门 (forget gate) f_t: 决定从细胞状态 c_{t-1} 中丢弃哪些信息。

    f_t = σ(W_f [h_{t-1}, x_t] + b_f)

  • 输入门 (input gate) i_t: 决定将哪些新的信息添加到细胞状态。

    i_t = σ(W_i [h_{t-1}, x_t] + b_i)

    g_t = tanh(W_g [h_{t-1}, x_t] + b_g) (候选细胞状态)

  • 细胞状态更新 c_t: 结合遗忘门和输入门,更新细胞状态。

    c_t = f_t * c_{t-1} + i_t * g_t

  • 输出门 (output gate) o_t: 决定从细胞状态中输出哪些信息作为当前时间步的隐藏状态 h_t

    o_t = σ(W_o [h_{t-1}, x_t] + b_o)

    h_t = o_t * tanh(c_t)

其中,σ 是 sigmoid 函数,tanh 是双曲正切函数,[h_{t-1}, x_t] 表示将 h_{t-1}x_t 向量拼接。

LSTM 的优势:

  • 缓解梯度消失问题: 细胞状态 c_t 通过线性相加的方式更新,信息更容易在时间步之间传递,从而缓解梯度消失问题。

  • 长期依赖性建模: 门控机制使 LSTM 能够选择性地记住和遗忘信息,更好地捕捉长序列中的长期依赖关系。

3. 门控循环单元 (GRU):

GRU (Gated Recurrent Unit) 是 LSTM 的简化版本,它将 LSTM 的遗忘门和输入门合并为一个更新门 (update gate),并去除了细胞状态,只保留了隐藏状态。GRU 在保持 LSTM 性能的同时,结构更加简单,参数更少,训练速度更快。

GRU 的核心组件和公式:

  • 更新门 (update gate) z_t: 控制前一时间步隐藏状态 h_{t-1} 对当前隐藏状态 h_t 的影响程度。

    z_t = σ(W_z [h_{t-1}, x_t] + b_z)

  • 重置门 (reset gate) r_t: 控制前一时间步隐藏状态 h_{t-1} 对计算候选隐藏状态 h̃_t 的影响程度。

    r_t = σ(W_r [h_{t-1}, x_t] + b_r)

  • 候选隐藏状态 h̃_t:

    h̃_t = tanh(W_h [r_t * h_{t-1}, x_t] + b_h)

  • 隐藏状态更新 h_t:

    h_t = (1 - z_t) * h̃_t + z_t * h_{t-1}

GRU 的优势:

  • 结构更简单,参数更少: 相较于 LSTM,GRU 的门控机制更少,结构更简洁,参数量更少,训练速度更快。

  • 性能接近 LSTM: 在许多任务中,GRU 的性能与 LSTM 相当甚至更好。

4.6.1.3 PyTorch 中的 RNN 实现

PyTorch 提供了 torch.nn 模块,其中包含了 RNNLSTMGRU 类,方便用户构建循环神经网络。

代码实践:使用 PyTorch 构建一个简单的文本分类 RNN 模型

我们以文本情感分类为例,演示如何使用 PyTorch 构建 RNN 模型。

import torch import torch.nn as nn import torch.optim as optim # 1. 数据准备 (简化示例) # 假设我们有简单的句子和对应的情感标签 (0: 负面, 1: 正面) sentences = [ "This movie is terrible.", "I really enjoyed this film.", "The acting was awful.", "It was a fantastic experience.", "Not worth watching.", "Highly recommended!" ] labels = [0, 1, 0, 1, 0, 1] # 构建词汇表 vocab = set() for sentence in sentences: for word in sentence.split(): vocab.add(word) vocab_size = len(vocab) word_to_index = {word: index for index, word in enumerate(vocab)} # 将句子转换为索引序列 def sentence_to_indices(sentence, word_to_index): return [word_to_index[word] for word in sentence.split()] indexed_sentences = [sentence_to_indices(sentence, word_to_index) for sentence in sentences] # 转换为 PyTorch Tensor def to_tensor(batch_sentences, batch_labels): max_len = max(len(sentence) for sentence in batch_sentences) padded_sentences = [] for sentence in batch_sentences: padded_sentence = sentence + [0] * (max_len - len(sentence)) # 使用 0 padding padded_sentences.append(padded_sentence) return torch.LongTensor(padded_sentences), torch.LongTensor(batch_labels) inputs, targets = to_tensor(indexed_sentences, labels) # 2. 定义 RNN 模型 class SentimentRNN(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim): super(SentimentRNN, self).__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) self.rnn = nn.RNN(embedding_dim, hidden_dim, batch_first=True) # batch_first=True 表示输入张量的第一个维度是 batch size self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, text): # text: [batch_size, seq_len] embedded = self.embedding(text) # embedded: [batch_size, seq_len, embedding_dim] output, hidden = self.rnn(embedded) # output: [batch_size, seq_len, hidden_dim], hidden: [1, batch_size, hidden_dim] # 取最后一个时间步的 hidden state 作为句子表示 # output[:, -1, :] 等价于 output[range(output.size(0)), [output.size(1)-1]*output.size(0), :] last_hidden = output[:, -1, :] # last_hidden: [batch_size, hidden_dim] prediction = self.fc(last_hidden) # prediction: [batch_size, output_dim] return prediction # 3. 模型参数设置 embedding_dim = 100 hidden_dim = 128 output_dim = 2 # 二分类 (正面/负面) learning_rate = 0.01 epochs = 100 model = SentimentRNN(vocab_size, embedding_dim, hidden_dim, output_dim) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) # 4. 训练模型 for epoch in range(epochs): optimizer.zero_grad() predictions = model(inputs) loss = criterion(predictions, targets) loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: _, predicted_classes = torch.max(predictions, 1) accuracy = (predicted_classes == targets).float().mean() print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, Accuracy: {accuracy:.4f}') # 5. 模型评估 (简化示例) _, predicted_classes = torch.max(predictions, 1) accuracy = (predicted_classes == targets).float().mean() print(f'Final Accuracy: {accuracy:.4f}')

代码详解:

  1. 数据准备:

    • 构建词汇表 vocab,将句子中的单词映射到唯一的索引。

    • sentence_to_indices 函数将句子转换为索引序列。

    • to_tensor 函数将索引序列 padding 到相同长度,并转换为 PyTorch Tensor。

  2. 定义 SentimentRNN 模型:

    • nn.Embedding: 将单词索引转换为低维的词向量 (embedding)。

    • nn.RNN: 定义 RNN 层,batch_first=True 指定输入张量的第一个维度是 batch size。

    • nn.Linear: 全连接层,将 RNN 的输出映射到最终的分类输出。

    • forward 函数:

      • 输入 text (单词索引序列)。

      • 通过 embedding 层得到词向量表示 embedded

      • embedded 输入到 rnn 层,得到 output (每个时间步的隐藏状态) 和 hidden (最后一个时间步的隐藏状态)。

      • 取最后一个时间步的隐藏状态 last_hidden 作为句子的表示。

      • last_hidden 输入到 fc 层,得到最终的分类预测 prediction

  3. 模型参数设置:

    • 定义 embedding 维度 embedding_dim、隐藏层维度 hidden_dim、输出维度 output_dim、学习率 learning_rate 和训练轮数 epochs

    • 初始化 SentimentRNN 模型、损失函数 nn.CrossEntropyLoss 和优化器 optim.Adam

  4. 训练模型:

    • 循环 epochs 次进行训练。

    • optimizer.zero_grad(): 清空梯度。

    • model(inputs): 前向传播,计算模型输出 predictions

    • criterion(predictions, targets): 计算损失。

    • loss.backward(): 反向传播,计算梯度。

    • optimizer.step(): 更新模型参数。

    • 每 10 个 epoch 打印训练 loss 和 accuracy。

  5. 模型评估:

    • 计算最终的训练集 accuracy。

其他 RNN 类型 (LSTM, GRU) 的使用:

将上述代码中的 nn.RNN 替换为 nn.LSTMnn.GRU 即可使用 LSTM 或 GRU 模型。例如,将模型定义部分修改为:

class SentimentLSTM(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim): super(SentimentLSTM, self).__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) self.rnn = nn.LSTM(embedding_dim, hidden_dim, batch_first=True) # 使用 LSTM self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, text): embedded = self.embedding(text) output, (hidden, cell) = self.rnn(embedded) # LSTM 返回 hidden state 和 cell state last_hidden = hidden[-1, :, :] # 取最后一个 LSTM 层的 hidden state prediction = self.fc(last_hidden) return prediction

注意:

  • 对于 LSTM,self.rnnforward 函数返回 output, (hidden, cell),其中 hiddencell 分别是最后一个时间步的 hidden state 和 cell state。我们需要取 hidden 的最后一个 LSTM 层的 hidden state (通常是 hidden[-1, :, :]) 作为句子的表示。

  • GRU 的使用方式与 LSTM 类似,但 self.rnnforward 函数只返回 output, hidden,没有 cell state。

4.6.2 Transformer

Transformer 模型是由 Google 提出的,最初用于机器翻译任务,但很快在 NLP 领域取得了革命性的突破,并逐渐扩展到其他领域,例如计算机视觉和语音识别。Transformer 摒弃了 RNN 的循环结构,完全基于自注意力机制 (self-attention mechanism),实现了并行计算,并能够有效地捕捉长距离依赖关系。

4.6.2.1 Transformer 的核心:自注意力机制

自注意力机制是 Transformer 的核心,它允许模型在处理序列中的每个位置时,关注序列中所有位置的信息,并根据它们之间的相关性进行加权聚合。

自注意力机制的计算步骤:

  1. 线性变换得到 Queries (Q), Keys (K), Values (V):

    对于输入序列 X,通过三个不同的线性变换 (权重矩阵 W_Q, W_K, W_V) 得到 Queries (Q), Keys (K), Values (V)。

    Q = XW_Q, K = XW_K, V = XW_V

  2. 计算注意力分数 (Attention Scores):

    计算 Queries 和 Keys 之间的点积,得到注意力分数。

    Attention Scores = QK^T

  3. 缩放 (Scale) 和 Mask (可选):

    • 缩放: 将注意力分数除以 √d_k (Keys 的维度),以防止点积值过大导致 softmax 函数梯度过小。

    • Mask: 可选的 mask 操作,例如在自回归模型中,mask 掉未来位置的信息,或者在 padding 序列中 mask 掉 padding 部分。

  4. Softmax 归一化:

    对缩放后的注意力分数进行 softmax 归一化,得到注意力权重 (Attention Weights)。

    Attention Weights = softmax(Scale(QK^T) + Mask)

  5. 加权求和得到输出:

    将注意力权重与 Values (V) 相乘,并求和,得到自注意力机制的输出。

    Output = Attention Weights * V

多头注意力 (Multi-Head Attention):

Transformer 模型使用了多头注意力机制,即并行地进行多次自注意力计算 (每个头使用不同的线性变换),并将多个头的输出拼接在一起,再通过一个线性变换得到最终的输出。多头注意力可以使模型从不同的角度关注输入序列的信息,提高模型的表达能力。

4.6.2.2 Transformer 的架构

Transformer 模型通常由编码器 (Encoder) 和解码器 (Decoder) 组成,用于序列到序列 (sequence-to-sequence) 任务,例如机器翻译。对于某些任务,例如文本分类或序列标注,可以只使用编码器部分。

Transformer 编码器 (Encoder):

编码器由多层相同的编码器层堆叠而成。每个编码器层包含两个子层:

  1. 多头自注意力 (Multi-Head Self-Attention): 计算输入序列的自注意力,捕捉序列内部的依赖关系。

  2. 前馈神经网络 (Feed Forward Network): 对每个位置的表示进行独立的非线性变换。

每个子层之后都接一个 Add & Norm 操作,即残差连接 (Add) 和层归一化 (Layer Normalization)。

Transformer 解码器 (Decoder):

解码器也由多层相同的解码器层堆叠而成。每个解码器层包含三个子层:

  1. Masked 多头自注意力 (Masked Multi-Head Self-Attention): 与编码器中的自注意力类似,但需要进行 masking,防止解码器在预测时看到未来位置的信息 (用于自回归生成)。

  2. 编码器-解码器注意力 (Encoder-Decoder Attention): 解码器在这个子层中关注编码器的输出,以获取输入序列的信息。

  3. 前馈神经网络 (Feed Forward Network): 与编码器中的前馈网络相同。

每个子层之后同样接 Add & Norm 操作。

位置编码 (Positional Encoding):

由于 Transformer 模型没有循环结构,无法直接感知序列中单词的顺序信息。为了解决这个问题,Transformer 引入了位置编码 (Positional Encoding),将位置信息添加到输入词向量中。位置编码通常使用正弦和余弦函数生成。

4.6.2.3 PyTorch 中的 Transformer 实现

PyTorch 提供了 torch.nn.Transformer 类,以及 torch.nn.TransformerEncodertorch.nn.TransformerDecoder 类,方便用户构建 Transformer 模型。

代码实践:使用 PyTorch 构建一个简单的机器翻译 Transformer 模型 (简化示例)

我们以一个简化的英法翻译任务为例,演示如何使用 PyTorch 构建 Transformer 模型。


发布者: 作者: 转发
评论区 (0)
U