系列文章:
《PyTorch 基础学习》文章索引

介绍

PyTorch 提供了一套强大的工具来构建和训练神经网络。其中的核心组件之一是 torch.nn,它提供了模块和类以帮助您创建和定制神经网络。

参数和模块

torch.nn.Parameter

  • torch.nn.Parameter() 是一种特殊的 Variable,常用于模块参数。
  • Parameter 被赋值给模块的属性时,它会自动添加到模块的参数列表中,成为模型可学习的参数。
  • VariableParameter 的区别:
    • Parameter 不能是 volatile,并且默认 requires_grad=True,而 Variable 默认 requires_grad=False

torch.nn.Module

  • 所有神经网络模块的基类。
  • 您的模型应继承此类。
  • 模块可以包含其他模块,形成树形结构。将子模块赋值为属性会自动注册它们。
示例
import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 20, 5)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        return F.relu(self.conv2(x))

模块方法

  • add_module(name, module): 向当前模块添加子模块。
  • children(): 返回当前模块的子模块迭代器。
  • modules(): 返回网络中所有模块的迭代器,包括自身和所有子模块。

移动模块

  • cpu(): 将模块参数和缓冲区移动到 CPU。
  • cuda(device_id=None): 将模块参数和缓冲区移动到 GPU。
  • double(): 将参数和缓冲区的数据类型转换为 double
  • float(): 将参数和缓冲区的数据类型转换为 float
  • half(): 将参数和缓冲区的数据类型转换为 half

评估和训练模式

  • eval(): 将模块设置为评估模式,影响诸如 Dropout 和 BatchNorm 等模块。
  • train(mode=True): 将模块设置为训练模式。

保存和加载模型

  • load_state_dict(state_dict): 从状态字典中加载参数和缓冲区。
  • state_dict(): 返回包含模块状态的字典。

线性层

torch.nn.Linear

  • 对输入数据进行线性变换:( y = Ax + b )。
示例
import torch.nn as nn
m = nn.Linear(20, 30)

卷积层

torch.nn.Conv2d

  • 进行 2D 卷积操作。
示例
import torch.nn as nn
m = nn.Conv2d(16, 33, 3, stride=2)

池化层

torch.nn.MaxPool2d

  • 进行 2D 最大池化操作。
示例
import torch.nn as nn
m = nn.MaxPool2d(3, stride=2)

torch.nn.AvgPool2d

  • 进行 2D 平均池化操作。
示例
import torch.nn as nn
m = nn.AvgPool2d(3, stride=2)

激活函数

常用激活函数

  • ReLU: 修正线性单元, R e L U ( x ) = m a x ( 0 , x ) ReLU(x)=max(0,x) ReLU(x)=max(0,x)
  • Sigmoid: S i g m o i d ( x ) = 1 / 1 + e − x Sigmoid(x)=1/1 + e^{-x} Sigmoid(x)=1/1+ex
  • Tanh: 双曲正切函数, t a n h ( x ) tanh(x) tanh(x)
示例
import torch.nn as nn
m = nn.ReLU()

循环神经网络层

循环神经网络(RNN)是一类用于处理序列数据的神经网络。PyTorch 提供了多种循环层,包括 RNNLSTMGRU,用于构建复杂的序列模型。下面我们详细介绍这些循环层及其使用方法。

torch.nn.RNN

torch.nn.RNN 实现了多层 Elman RNN,适用于输入序列的处理。它通过循环连接来保持序列中每个时间步的信息。可以选择使用 tanhrelu 作为激活函数。

示例
import torch
import torch.nn as nn
from torch.autograd import Variable

# 创建一个 RNN 层,输入维度为 10,隐状态维度为 20,使用两层堆叠
rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=2)

# 输入数据,形状为 (序列长度, 批量大小, 特征维度)
input = Variable(torch.randn(5, 3, 10))

# 初始隐状态,形状为 (层数, 批量大小, 隐状态维度)
h0 = Variable(torch.randn(2, 3, 20))

# 前向传播,计算输出和新的隐状态
output, hn = rnn(input, h0)

# 输出是最后一层的输出,hn 是最后一个时间步的隐状态

torch.nn.LSTM

torch.nn.LSTM 实现了长短时记忆网络(LSTM),用于处理更复杂的序列模式,特别是长序列。LSTM 使用门控机制(包括输入门、遗忘门和输出门)来控制信息的流动,从而有效地捕捉序列中的长期依赖关系。

示例
import torch
import torch.nn as nn
from torch.autograd import Variable

# 创建一个 LSTM 层,输入维度为 10,隐状态和细胞状态维度为 20,使用两层堆叠
lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)

# 输入数据,形状为 (序列长度, 批量大小, 特征维度)
input = Variable(torch.randn(5, 3, 10))

# 初始隐状态和细胞状态,形状为 (层数, 批量大小, 隐状态维度)
h0 = Variable(torch.randn(2, 3, 20))
c0 = Variable(torch.randn(2, 3, 20))

# 前向传播,计算输出、最后的隐状态和细胞状态
output, (hn, cn) = lstm(input, (h0, c0))

# 输出是最后一层的输出,hn 和 cn 分别是最后一个时间步的隐状态和细胞状态

torch.nn.GRU

torch.nn.GRU 实现了门控循环单元(GRU)网络,是一种比 LSTM 更简单的结构,常用于处理序列数据。GRU 通过合并输入门和遗忘门,简化了门控机制,同时保持了捕捉长期依赖的能力。

示例
import torch
import torch.nn as nn
from torch.autograd import Variable

# 创建一个 GRU 层,输入维度为 10,隐状态维度为 20,使用两层堆叠
gru = nn.GRU(input_size=10, hidden_size=20, num_layers=2)

# 输入数据,形状为 (序列长度, 批量大小, 特征维度)
input = Variable(torch.randn(5, 3, 10))

# 初始隐状态,形状为 (层数, 批量大小, 隐状态维度)
h0 = Variable(torch.randn(2, 3, 20))

# 前向传播,计算输出和新的隐状态
output, hn = gru(input, h0)

# 输出是最后一层的输出,hn 是最后一个时间步的隐状态

以上这些循环层可以用于处理序列数据,如时间序列预测、自然语言处理等。选择合适的循环层和参数设置可以帮助您构建出性能优异的序列模型。

Dropout 层

torch.nn.Dropout

  • 随机将输入张量中的部分元素置零(详见附录1)。
示例
import torch.nn as nn
m = nn.Dropout(p=0.5)

损失函数

常用损失函数

  • L1Loss: 平均绝对误差损失。
  • MSELoss: 均方误差损失。
  • CrossEntropyLoss: 将 LogSoftMax 和 NLLLoss 集成在一个类中。
示例
import torch.nn as nn
criterion = nn.MSELoss()

工具

torch.nn.utils.clip_grad_norm

  • 裁剪参数梯度的范数。

torch.nn.utils.rnn

  • 用于处理变长序列的 RNN 的函数。
序列的打包和填充
  • **pack_padded_sequence

应用实例:多项式回归

以下是一个使用 PyTorch 构建和训练循环神经网络(RNN)进行简单时间序列预测的完整示例。该脚本展示了如何使用 LSTM 层来处理序列数据,包括数据准备、模型定义、训练和评估。

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from torch.autograd import Variable
from sklearn.preprocessing import MinMaxScaler

# 生成示例数据:一个正弦波
# 设置随机种子以确保可重复性
np.random.seed(0)
torch.manual_seed(0)

# 生成一个正弦波序列
def generate_data(seq_length=50, num_samples=1000):
    x = np.linspace(0, 100, num_samples)
    y = np.sin(x) + 0.1 * np.random.randn(num_samples)  # 添加一些噪声
    return y

# 数据预处理:将数据归一化到 [0, 1] 区间,并构造序列样本
def create_dataset(data, seq_length):
    scaler = MinMaxScaler(feature_range=(0, 1))
    data_normalized = scaler.fit_transform(data.reshape(-1, 1)).flatten()

    sequences = []
    targets = []

    for i in range(len(data_normalized) - seq_length):
        sequences.append(data_normalized[i:i+seq_length])
        targets.append(data_normalized[i+seq_length])

    return np.array(sequences), np.array(targets), scaler

# 定义 LSTM 模型
class LSTMModel(nn.Module):
    def __init__(self, input_size=1, hidden_size=50, num_layers=1):
        super(LSTMModel, 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, 1)

    def forward(self, x):
        # 初始化隐藏状态和细胞状态
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).requires_grad_()
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).requires_grad_()

        # 前向传播 LSTM
        out, _ = self.lstm(x, (h0.detach(), c0.detach()))

        # 从最后一个时间步提取输出
        out = self.fc(out[:, -1, :])
        return out

# 参数设置
seq_length = 50
num_samples = 1000
batch_size = 16
num_epochs = 200
learning_rate = 0.01

# 生成和处理数据
data = generate_data(seq_length, num_samples)
sequences, targets, scaler = create_dataset(data, seq_length)

# 转换为 PyTorch 的张量格式
sequences = torch.from_numpy(sequences).float().unsqueeze(2)  # (样本数, 序列长度, 特征数)
targets = torch.from_numpy(targets).float().unsqueeze(1)  # (样本数, 1)

# 构造数据集和数据加载器
dataset = torch.utils.data.TensorDataset(sequences, targets)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)

# 创建模型、定义损失函数和优化器
model = LSTMModel(input_size=1, hidden_size=50, num_layers=1)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# 训练模型
for epoch in range(num_epochs):
    for batch_seqs, batch_targets in dataloader:
        # 前向传播
        outputs = model(batch_seqs)
        loss = criterion(outputs, batch_targets)

        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

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

# 评估模型
model.eval()
with torch.no_grad():
    # 使用训练数据进行预测
    train_pred = model(sequences).detach().numpy()
    train_pred_rescaled = scaler.inverse_transform(train_pred)

    # 原始数据逆归一化
    targets_rescaled = scaler.inverse_transform(targets.numpy())

# 绘制结果
plt.figure(figsize=(10, 6))
plt.plot(data, label='Original Data')
plt.plot(range(seq_length, seq_length + len(train_pred_rescaled)), train_pred_rescaled, label='LSTM Prediction')
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend()
plt.show()

输出结果:
在这里插入图片描述

代码说明

  1. 生成数据:

    • 生成一个正弦波,并添加噪声以模拟真实数据。
    • 使用 np.linspace 创建一个线性间隔的数组来表示时间。
  2. 数据预处理:

    • 使用 MinMaxScaler 将数据归一化到 [0, 1] 区间,以帮助模型更快地收敛。
    • 将数据转换为固定长度的序列样本,每个样本的长度为 seq_length
  3. LSTM 模型定义:

    • 定义 LSTMModel 类,继承自 nn.Module
    • 使用 LSTM 层和全连接层来实现序列到序列的映射。
  4. 训练过程:

    • 使用 MSELoss 作为损失函数,Adam 作为优化器。
    • 在每个 epoch 内,迭代数据加载器进行批次训练,并更新模型参数。
  5. 评估和可视化:

    • 在训练结束后,用训练数据进行预测,并将结果与原始数据对比。
    • 使用 matplotlib 绘制原始数据和预测结果。

该示例展示了如何使用 PyTorch 实现基本的时间序列预测任务,您可以根据需要对数据和模型进行调整以适应不同的应用场景,如:股票预测。

附录1:Dropout 层

Dropout 层是一种在训练神经网络时常用的正则化技术,其目的是防止模型过拟合。过拟合是指模型在训练数据上表现很好,但在未见过的数据上表现不佳的情况。Dropout 层通过在训练过程中随机“丢弃”(即暂时移除)网络中的一部分神经元(及其连接),来减少神经元之间复杂的共适应关系。

具体来说,Dropout 层的工作原理如下:

  1. 随机丢弃:在每次训练迭代中,Dropout 层会按照一定的概率(通常用参数 p 表示)随机将一部分神经元的输出置为零。这意味着这些神经元在这次迭代中不会对网络的输出产生影响。

  2. 减少过拟合:由于每次迭代中被丢弃的神经元是随机的,这迫使网络中的其他神经元不能过分依赖于任何一个神经元的输出,从而鼓励网络学习到更加鲁棒的特征。

  3. 保持激活稀疏性:Dropout 还有助于保持网络激活的稀疏性,即在任何给定时间只有一部分神经元是活跃的。

  4. 训练与测试的区别:在测试或评估模型时,Dropout 层通常不生效,或者以不同的方式工作(例如,每个神经元的输出乘以 1-p,以保持整体激活水平与训练时相同)。

Dropout 是一种简单而有效的正则化方法,它在各种深度学习框架中都有实现,包括 PyTorch。在 PyTorch 中,torch.nn.Dropout 是实现 Dropout 功能的类。通过设置 p 参数(丢弃概率),可以控制 Dropout 的强度。例如,nn.Dropout(p=0.5) 表示在训练过程中有 50% 的概率丢弃任何给定的神经元。

附录2:模块(Module)API和函数(functional)API

PyTorch提供了两种方式来使用激活函数:模块(Module)API和函数(functional)API。这两种方式的主要区别在于它们的使用上下文和灵活性。

  1. 模块(Module)API

    • 模块API中的激活函数是作为nn.Module的子类实现的,这意味着它们可以像其他层一样被集成到神经网络模型中,并且可以作为计算图的一部分来自动计算梯度。
    • 模块API通常用于定义静态的网络结构,其中激活函数的参数是固定的,不会在每次前向传播时改变。
    • 模块API中的激活函数可以很容易地与PyTorch的其他模块(如层和损失函数)一起使用,并且可以利用PyTorch的模块化和继承特性来构建复杂的网络结构。
    • 示例:m = nn.ReLU() 创建了一个ReLU激活函数的模块,可以在模型的forward方法中像其他层一样调用m(x)
  2. 函数(functional)API

    • 函数API提供的激活函数是作为普通Python函数实现的,它们可以直接在代码中调用,不需要创建模块实例。
    • 函数API更加灵活,允许在每次前向传播时动态地改变参数,或者在不同的上下文中以不同的方式使用激活函数。
    • 函数API通常用于快速实验或者在需要动态决定使用哪种激活函数时使用。
    • 示例:import torch.nn.functional as F 然后使用F.relu(x)来应用ReLU激活函数。

总结来说,模块API更适合于构建静态的、参数固定的网络结构,而函数API提供了更大的灵活性,适用于需要动态决策的场景。在实际应用中,选择哪种API取决于具体的任务需求和个人偏好。在构建复杂的神经网络模型时,模块API由于其模块化和易于集成的特性,通常是首选。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐