BJTU深度学习实验五:循环神经网络(RNN)从零实现与调优指南

1次阅读
没有评论

共计 2454 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

1. 循环神经网络的核心价值

在处理时序数据(如文本、语音、时间序列)时,传统全连接网络和卷积神经网络(CNN/ 卷积神经网络)存在明显局限:

BJTU 深度学习实验五:循环神经网络 (RNN) 从零实现与调优指南

  • 全连接网络 需要固定长度的输入,且无法捕捉序列的时序依赖关系
  • CNN虽能通过滑动窗口处理局部序列,但受限于感受野大小(Receptive Field),难以建模长距离依赖

循环神经网络(RNN/Recurrent Neural Network)通过引入隐状态(Hidden State)$h_t$ 实现时序信息的传递,其数学表达为:

$$h_t = \sigma(W_{hh}h_{t-1} + W_{xh}x_t + b_h)$$
$$y_t = W_{hy}h_t + b_y$$

其中 $\sigma$ 通常为 tanh 激活函数,这种结构使得 RNN 理论上可以处理任意长度的序列数据。

2. PyTorch 实现详解

2.1 网络结构实现

import torch
import torch.nn as nn

class VanillaRNN(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers=1):
        super().__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers

        # 嵌入层(处理离散输入)self.embedding = nn.Embedding(num_embeddings=10000, embedding_dim=input_size)

        # RNN 核心层
        self.rnn = nn.RNN(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True  # 输入格式为(batch, seq, feature)
        )

        # 输出层
        self.fc = nn.Linear(hidden_size, num_classes)

    def forward(self, x, hidden=None):
        # 初始化隐状态
        if hidden is None:
            hidden = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

        # 前向传播
        embedded = self.embedding(x)
        output, hidden = self.rnn(embedded, hidden)
        logits = self.fc(output[:, -1, :])  # 只取序列最后时刻输出

        return logits, hidden

关键参数说明

  • hidden_size:隐状态维度,影响模型记忆容量。文本任务通常设为 128-512
  • num_layers:RNN 堆叠层数,增加层数可提升模型复杂度但会加剧梯度消失

2.2 训练流程实现

# 数据预处理示例
from torch.utils.data import Dataset, DataLoader

train_loader = DataLoader(dataset=TextDataset(),
    batch_size=32,
    shuffle=True,
    collate_fn=lambda batch: pad_sequence(batch, padding_value=0)
)

# 训练循环
def train(model, loader, criterion, optimizer):
    model.train()
    total_loss = 0

    for inputs, targets in loader:
        optimizer.zero_grad()

        # 梯度裁剪(防止爆炸)torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)

        outputs, _ = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    return total_loss / len(loader)

3. 关键优化技术

3.1 梯度问题解决方案

  • 梯度裁剪:限制梯度范数(代码见 2.2 节)
  • 激活函数改进:将 tanh 替换为 ReLU 变体(如 LeakyReLU)可缓解梯度消失

$$\text{LeakyReLU}(x) = \begin{cases}
x & \text{if} x \geq 0 \
0.01x & \text{otherwise}
\end{cases}$$

3.2 序列处理技巧

# 使用 pack_padded_sequence 处理变长序列
from torch.nn.utils.rnn import pack_padded_sequence

def forward(self, x, lengths):
    embedded = self.embedding(x)
    packed = pack_padded_sequence(embedded, lengths, batch_first=True, enforce_sorted=False)
    output, hidden = self.rnn(packed)
    ...

4. 进阶实验建议

  1. 对比实验设计
  2. 在相同数据集上比较 RNN/LSTM/GRU 的验证集准确率
  3. 可视化不同模型在长序列上的记忆能力差异

  4. 常见问题排查

  5. CUDA 内存不足时:减小 batch_size 或使用梯度累积
  6. 训练 loss 震荡:尝试学习率动态调整(如 ReduceLROnPlateau)

  7. 推荐阅读

  8. 原始论文:《Learning representations by back-propagating errors》(Werbos, 1990)
  9. 开源项目:PyTorch 官方 RNN 教程(含字符级语言模型实现)

5. 实验总结

通过本实验可以直观理解 RNN 的时序建模机制,其核心价值在于隐状态的递归更新方式。实际应用中需特别注意梯度问题的处理,后续可扩展至 LSTM 等更复杂的循环结构。建议在掌握基础 RNN 后,进一步研究其变体在长序列任务中的改进思路。

正文完
 0
评论(没有评论)