AIDIY WikiRNN编程练习 RNN Practice

历届足球世界杯 2025-11-16 01:22:14

PyTorch RNN 实现详细指南¶

在本教程中,我们将详细讨论如何在 PyTorch 中实现循环神经网络(RNN)。内容将覆盖以下三个部分:

循环神经网络层搭建方法

前向传播定义方法

模型训练方法

每个部分后都会有相应的**习题**,帮助你通过练习加深理解。

1. 循环神经网络层搭建方法¶

在 PyTorch 中,构建循环神经网络层主要通过使用内置的 RNN 模块(如 nn.RNN、nn.LSTM、nn.GRU)。以下是一些常用的 PyTorch 循环神经网络组件及其实现方式:

1.1 标准 RNN¶

组件说明:

- nn.RNN(input_size, hidden_size, num_layers, nonlinearity, batch_first, dropout):定义一个标准的 RNN 层。

- input_size:输入特征的数量。

- hidden_size:隐藏状态的特征数量。

- num_layers:RNN 的层数。

- nonlinearity:激活函数,可以是 'tanh' 或 'relu'。

- batch_first:如果 True,输入和输出的张量形状为 (batch, seq, feature)。

- dropout:除最后一层外,在 RNN 层之间应用的 dropout 概率。

示例:

import torch.nn as nn

class BasicRNN(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=1):

super(BasicRNN, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.rnn = nn.RNN(input_size, hidden_size, num_layers, nonlinearity='tanh', batch_first=True)

self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x):

# 初始化隐藏状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 RNN

out, hn = self.rnn(x, h0)

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

1.2 长短期记忆网络 (LSTM)¶

组件说明:

- nn.LSTM(input_size, hidden_size, num_layers, batch_first, dropout):定义一个 LSTM 层。

- 参数与 nn.RNN 类似,但 LSTM 具有更复杂的内部结构,包括输入门、遗忘门和输出门。

示例:

import torch.nn as nn

class BasicLSTM(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=1):

super(BasicLSTM, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x):

# 初始化隐藏状态和细胞状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 LSTM

out, (hn, cn) = self.lstm(x, (h0, c0))

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

1.3 门控循环单元 (GRU)¶

组件说明:

- nn.GRU(input_size, hidden_size, num_layers, batch_first, dropout):定义一个 GRU 层。

- 参数与 nn.RNN 类似,但 GRU 具有更新门和重置门,结构比 LSTM 简单。

示例:

import torch.nn as nn

class BasicGRU(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=1):

super(BasicGRU, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)

self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x):

# 初始化隐藏状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 GRU

out, hn = self.gru(x, h0)

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

1.4 双向 RNN¶

双向 RNN 通过同时考虑序列的前向和后向信息,可能会提升模型性能。

组件说明:

- 在定义 RNN、LSTM 或 GRU 时,设置 bidirectional=True。

示例:

import torch.nn as nn

class BiLSTM(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=1):

super(BiLSTM, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.lstm = nn.LSTM(input_size, hidden_size, num_layers,

batch_first=True, bidirectional=True)

self.fc = nn.Linear(hidden_size * 2, output_size) # 因为是双向

def forward(self, x):

# 初始化隐藏状态和细胞状态

h0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(x.device)

c0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(x.device)

# 前向传播 LSTM

out, (hn, cn) = self.lstm(x, (h0, c0))

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

2. 前向传播定义方法¶

定义前向传播方法时,需要明确如何处理输入数据、隐藏状态以及输出。以下是一个基于 LSTM 的示例:

import torch

import torch.nn as nn

import torch.nn.functional as F

class SequenceClassifier(nn.Module):

def __init__(self, input_size, hidden_size, num_layers, num_classes):

super(SequenceClassifier, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

self.fc = nn.Linear(hidden_size, num_classes)

def forward(self, x):

# 初始化隐藏状态和细胞状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 LSTM

out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size)

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

说明:

- 输入形状:假设输入 x 的形状为 (batch_size, seq_length, input_size)。

- 隐藏状态初始化:使用全零初始化 h0 和 c0。

- 输出处理:选择序列的最后一个时间步的输出进行分类。

3. 模型训练方法¶

训练 RNN 模型的流程与其他神经网络类似,包括定义损失函数、选择优化器、前向传播、计算损失、反向传播和参数更新。

3.1 损失函数与优化器¶

常用的损失函数和优化器包括:

损失函数:

分类任务:nn.CrossEntropyLoss()

回归任务:nn.MSELoss()

优化器:

torch.optim.Adam(model.parameters(), lr=learning_rate)

torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)

3.2 训练循环示例¶

以下是一个训练 RNN 模型的完整示例:

import torch

import torch.nn as nn

import torch.optim as optim

from torch.utils.data import DataLoader, TensorDataset

# 假设我们有一些序列数据

# 输入数据的形状:(num_samples, seq_length, input_size)

# 标签的形状:(num_samples,)

num_samples = 1000

seq_length = 50

input_size = 10

hidden_size = 128

num_layers = 2

num_classes = 5

batch_size = 64

num_epochs = 20

learning_rate = 0.001

# 生成随机数据

X = torch.randn(num_samples, seq_length, input_size)

y = torch.randint(0, num_classes, (num_samples,))

# 创建数据加载器

dataset = TensorDataset(X, y)

train_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

# 定义模型、损失函数和优化器

model = SequenceClassifier(input_size, hidden_size, num_layers, num_classes).to('cuda' if torch.cuda.is_available() else 'cpu')

criterion = nn.CrossEntropyLoss()

optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 训练过程

for epoch in range(num_epochs):

for batch_X, batch_y in train_loader:

batch_X, batch_y = batch_X.to(model.fc.weight.device), batch_y.to(model.fc.weight.device)

# 前向传播

outputs = model(batch_X)

loss = criterion(outputs, batch_y)

# 反向传播和优化

optimizer.zero_grad()

loss.backward()

optimizer.step()

print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

说明:

- 数据准备:这里使用随机生成的数据作为示例,实际应用中需要使用真实的数据集。

- 模型移动:将模型和数据移动到 GPU(如果可用)以加速训练。

- 训练循环:

1. 前向传播:计算模型的输出。

2. 损失计算:使用交叉熵损失函数。

3. 反向传播:计算梯度。

4. 参数更新:优化器更新模型参数。

- 打印损失:每个 epoch 打印一次损失以监控训练过程。

3.3 防止梯度消失和爆炸¶

在训练 RNN 时,梯度消失和爆炸是常见的问题。以下是一些应对方法:

梯度裁剪:限制梯度的最大范数,防止梯度爆炸。

示例:

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

使用改进的 RNN 结构:如 LSTM 和 GRU,它们通过门控机制缓解梯度消失问题。

权重初始化:合理初始化模型的权重可以帮助稳定训练过程。

正则化:如 Dropout,防止模型过拟合。

3.4 正则化方法¶

Dropout:在 RNN 中应用 Dropout 通常只在非循环连接处使用。PyTorch 的 RNN 模块支持在层之间应用 Dropout。

示例:

self.lstm = nn.LSTM(input_size, hidden_size, num_layers,

batch_first=True, dropout=0.5)

L2 正则化:通过在优化器中添加权重衰减参数实现。

示例:

optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=1e-5)

习题与解答¶

习题 1:构建一个双层 LSTM 模型¶

构建一个包含两层 LSTM 的模型,每层的隐藏单元数量为256,并在最后连接一个输出层进行二分类。

提示: 在 nn.LSTM 中设置 num_layers=2,并在 forward 方法中处理双层 LSTM 的输出。

查看答案

参考答案:

import torch.nn as nn

import torch.nn.functional as F

class TwoLayerLSTM(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=2):

super(TwoLayerLSTM, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x):

# 初始化隐藏状态和细胞状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 LSTM

out, (hn, cn) = self.lstm(x, (h0, c0))

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

实现一个带有 Dropout 的 GRU 模型,其中 Dropout 概率为0.3,仅应用于非循环层之间。

提示: 在定义 nn.GRU 时设置 dropout=0.3,并确保 num_layers > 1 以启用 Dropout。

查看答案

参考答案:

import torch.nn as nn

import torch.nn.functional as F

class DropoutGRU(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=2, dropout=0.3):

super(DropoutGRU, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout)

self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x):

# 初始化隐藏状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 GRU

out, hn = self.gru(x, h0)

# 选择最后一个时间步的输出

out = self.fc(out[:, -1, :])

return out

扩展 BasicRNN 类,添加一个双向 RNN,并在输出层之前添加批归一化。

提示: 设置 bidirectional=True,并调整输出层的输入维度为 hidden_size * 2。使用 nn.BatchNorm1d 进行批归一化。

查看答案

参考答案:

import torch.nn as nn

import torch.nn.functional as F

class BiRNNWithBN(nn.Module):

def __init__(self, input_size, hidden_size, output_size, num_layers=1):

super(BiRNNWithBN, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.rnn = nn.RNN(input_size, hidden_size, num_layers,

nonlinearity='tanh', batch_first=True, bidirectional=True)

self.bn = nn.BatchNorm1d(hidden_size * 2)

self.fc = nn.Linear(hidden_size * 2, output_size)

def forward(self, x):

# 初始化隐藏状态

h0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(x.device)

# 前向传播 RNN

out, hn = self.rnn(x, h0)

# 选择最后一个时间步的输出

out = out[:, -1, :] # (batch_size, hidden_size * 2)

# 批归一化

out = self.bn(out)

# 输出层

out = self.fc(out)

return out

习题 2:实现一个带有梯度裁剪的训练循环¶

在训练循环中添加梯度裁剪,限制梯度的最大范数为5.0。

提示: 使用 torch.nn.utils.clip_grad_norm_ 在 loss.backward() 和 optimizer.step() 之间裁剪梯度。

查看答案

参考答案:

import torch

import torch.nn as nn

import torch.optim as optim

from torch.utils.data import DataLoader, TensorDataset

# 假设已有模型、数据加载器等

model = TwoLayerLSTM(input_size=10, hidden_size=256, output_size=2, num_layers=2).to('cuda' if torch.cuda.is_available() else 'cpu')

criterion = nn.CrossEntropyLoss()

optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练过程

for epoch in range(num_epochs):

for batch_X, batch_y in train_loader:

batch_X, batch_y = batch_X.to(model.fc.weight.device), batch_y.to(model.fc.weight.device)

# 前向传播

outputs = model(batch_X)

loss = criterion(outputs, batch_y)

# 反向传播和优化

optimizer.zero_grad()

loss.backward()

# 梯度裁剪

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)

optimizer.step()

print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

修改 SequenceClassifier 模型,添加 Dropout 层以防止过拟合。

提示: 在 LSTM 的输出和全连接层之间添加 nn.Dropout。

查看答案

参考答案:

import torch

import torch.nn as nn

import torch.nn.functional as F

class DropoutSequenceClassifier(nn.Module):

def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout=0.5):

super(DropoutSequenceClassifier, self).__init__()

self.hidden_size = hidden_size

self.num_layers = num_layers

self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

self.dropout = nn.Dropout(dropout)

self.fc = nn.Linear(hidden_size, num_classes)

def forward(self, x):

# 初始化隐藏状态和细胞状态

h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

# 前向传播 LSTM

out, (hn, cn) = self.lstm(x, (h0, c0))

# 选择最后一个时间步的输出

out = out[:, -1, :]

out = self.dropout(out) # 应用 Dropout

out = self.fc(out)

return out