第二章:PyTorch 模型构建


文档摘要

第二章:PyTorch 模型构建 第二章:PyTorch 模型构建 2.1 : 模型构建的基石 在 PyTorch 中,构建任何神经网络模型,都离不开 类。 是 PyTorch 中所有神经网络模块的基类,它提供了一种结构化的方式来组织和管理模型的各个组成部分。任何自定义的模型都必须继承自 。 的核心功能: 参数管理: 能够自动追踪和管理模型中所有可学习的参数(例如,权重和偏置)。 设备管理: 可以方便地将模型及其参数移动到不同的设备上,例如 CPU 或 GPU。 模块化构建: 允许我们将模型分解为更小的、可重用的模块,从而构建复杂的神经网络。 前向传播定义: 通过重写 方法,我们可以定义模型的前向传播逻辑,即输入数据如何通过模型进行计算并产生输出。 代码实践 2.1.

第二章:PyTorch 模型构建

第二章:PyTorch 模型构建

2.1 nn.Module: 模型构建的基石

在 PyTorch 中,构建任何神经网络模型,都离不开 nn.Module 类。nn.Module 是 PyTorch 中所有神经网络模块的基类,它提供了一种结构化的方式来组织和管理模型的各个组成部分。任何自定义的模型都必须继承自 nn.Module

nn.Module 的核心功能:

  • 参数管理: nn.Module 能够自动追踪和管理模型中所有可学习的参数(例如,权重和偏置)。

  • 设备管理: nn.Module 可以方便地将模型及其参数移动到不同的设备上,例如 CPU 或 GPU。

  • 模块化构建: nn.Module 允许我们将模型分解为更小的、可重用的模块,从而构建复杂的神经网络。

  • 前向传播定义: 通过重写 forward() 方法,我们可以定义模型的前向传播逻辑,即输入数据如何通过模型进行计算并产生输出。

代码实践 2.1.1: 一个简单的线性模型

为了更好地理解 nn.Module 的使用,我们从一个最简单的线性模型开始:

import torch import torch.nn as nn class LinearRegressionModel(nn.Module): def __init__(self, input_dim, output_dim): super(LinearRegressionModel, self).__init__() # 定义一个线性层 self.linear = nn.Linear(input_dim, output_dim) def forward(self, x): # 定义前向传播逻辑:输入 x 通过线性层 out = self.linear(x) return out # 创建模型实例 input_dim = 1 output_dim = 1 model = LinearRegressionModel(input_dim, output_dim) # 打印模型结构 print(model)

代码详解 2.1.1:

  1. class LinearRegressionModel(nn.Module):: 我们定义了一个名为 LinearRegressionModel 的类,并继承了 nn.Module。这是所有自定义模型的标准做法。

  2. def __init__(self, input_dim, output_dim):: __init__ 方法是类的构造函数,用于初始化模型的各个组件。

    • super(LinearRegressionModel, self).__init__(): 这行代码调用了父类 nn.Module 的构造函数,确保 nn.Module 的基本功能被正确初始化。

    • self.linear = nn.Linear(input_dim, output_dim): 我们使用 nn.Linear 定义了一个线性层,它接受 input_dim 维度的输入,并输出 output_dim 维度的输出。nn.Linear 是 PyTorch 预定义的神经网络层之一,它包含了权重矩阵和偏置项,并且会自动被 nn.Module 管理。

  3. def forward(self, x):: forward 方法定义了模型的前向传播逻辑。

    • out = self.linear(x): 输入 x 通过我们定义的线性层 self.linear 进行计算,得到输出 out

    • return out: 返回模型的输出。

模型结构可视化 (Mermaid Graph):

这个简单的例子展示了如何使用 nn.Modulenn.Linear 构建一个基本的线性回归模型。nn.Module 负责模型的结构管理,而 nn.Linear 提供了预定义的线性层功能。

2.2 PyTorch 预定义的神经网络层

PyTorch 提供了丰富的预定义神经网络层,位于 torch.nn 模块下,涵盖了构建各种类型神经网络所需的常见层。这些预定义层极大地简化了模型构建过程,开发者无需从零开始实现各种神经网络操作。

常见的预定义层类型:

  • 线性层: nn.Linear (全连接层)

  • 卷积层: nn.Conv1d, nn.Conv2d, nn.Conv3d (一维、二维、三维卷积)

  • 池化层: nn.MaxPool1d, nn.MaxPool2d, nn.MaxPool3d, nn.AvgPool1d, nn.AvgPool2d, nn.AvgPool3d (最大池化、平均池化)

  • 循环层: nn.RNN, nn.LSTM, nn.GRU (循环神经网络层)

  • 嵌入层: nn.Embedding (用于处理离散数据的嵌入层)

  • 激活函数: nn.ReLU, nn.Sigmoid, nn.Tanh, nn.LeakyReLU, nn.Softmax (各种激活函数)

  • 归一化层: nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.LayerNorm, nn.GroupNorm (批归一化、层归一化、组归一化)

  • Dropout层: nn.Dropout, nn.Dropout2d, nn.Dropout3d (防止过拟合的 Dropout 层)

代码实践 2.2.1: 构建一个简单的多层感知机 (MLP)

下面我们使用预定义的层构建一个简单的多层感知机 (MLP):

class MLPModel(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super(MLPModel, self).__init__() self.fc1 = nn.Linear(input_dim, hidden_dim) # 第一个全连接层 self.relu = nn.ReLU() # ReLU 激活函数 self.fc2 = nn.Linear(hidden_dim, output_dim) # 第二个全连接层 def forward(self, x): out = self.fc1(x) out = self.relu(out) out = self.fc2(out) return out # 创建 MLP 模型实例 input_dim = 784 # MNIST 图片展平后的维度 hidden_dim = 500 output_dim = 10 # MNIST 数字类别数 mlp_model = MLPModel(input_dim, hidden_dim, output_dim) print(mlp_model)

代码详解 2.2.1:

  1. self.fc1 = nn.Linear(input_dim, hidden_dim): 定义第一个全连接层,将输入维度 input_dim 映射到隐藏层维度 hidden_dim

  2. self.relu = nn.ReLU(): 定义 ReLU 激活函数。ReLU 函数会对输入进行逐元素操作,将负值置为零,正值保持不变。

  3. self.fc2 = nn.Linear(hidden_dim, output_dim): 定义第二个全连接层,将隐藏层维度 hidden_dim 映射到输出维度 output_dim

  4. forward 方法: 数据首先通过 fc1 层,然后经过 relu 激活函数,最后通过 fc2 层,得到最终输出。

模型结构可视化 (Mermaid Graph):

这个例子展示了如何将多个预定义层组合起来构建一个更复杂的模型。通过堆叠线性层和激活函数,我们构建了一个简单的 MLP 模型,它可以用于分类或回归任务。

2.3 模型容器: nn.Sequentialnn.ModuleList

在构建模型时,我们经常需要将多个层按照一定的顺序或结构组织起来。PyTorch 提供了两个主要的模型容器:nn.Sequentialnn.ModuleList,用于简化模型的组织和管理。

2.3.1 nn.Sequential: 顺序容器

nn.Sequential 是一个顺序容器,它会将传入的模块按照顺序依次执行。这对于构建简单的、线性堆叠的模型非常方便。

代码实践 2.3.1: 使用 nn.Sequential 构建 MLP 模型

我们可以使用 nn.Sequential 重写之前的 MLP 模型:

class SequentialMLPModel(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super(SequentialMLPModel, self).__init__() self.mlp = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, output_dim) ) def forward(self, x): out = self.mlp(x) return out # 创建 Sequential MLP 模型实例 sequential_mlp_model = SequentialMLPModel(input_dim, hidden_dim, output_dim) print(sequential_mlp_model)

代码详解 2.3.1:

  1. self.mlp = nn.Sequential(...): 我们使用 nn.Sequential 创建了一个顺序容器 self.mlp,并将线性层和 ReLU 激活函数按照执行顺序放入容器中。

  2. forward 方法:forward 方法中,我们只需要将输入 x 传递给 self.mlpnn.Sequential 会自动按照定义的顺序执行容器中的所有层。

模型结构可视化 (Mermaid Graph):

nn.Sequential 的优点是代码简洁,模型结构清晰。但它的局限性在于,它只能处理线性堆叠的模型结构,对于更复杂的模型,例如具有分支或跳跃连接的模型,nn.Sequential 就显得力不从心。

2.3.2 nn.ModuleList: 模块列表

nn.ModuleList 也是一个容器,但它仅仅是简单地存储模块列表,并不会自动按照顺序执行它们。nn.ModuleList 主要用于管理模块集合,例如在循环神经网络中,可能需要重复使用某些模块多次。

代码实践 2.3.2: 使用 nn.ModuleList 构建动态层数的 MLP

假设我们需要构建一个 MLP 模型,其隐藏层层数可以动态调整。我们可以使用 nn.ModuleList 来实现:

class DynamicLayerMLPModel(nn.Module): def __init__(self, input_dim, hidden_dims, output_dim): super(DynamicLayerMLPModel, self).__init__() self.layers = nn.ModuleList() # 使用 nn.ModuleList 存储层 self.layers.append(nn.Linear(input_dim, hidden_dims[0])) # 添加第一层 # 动态添加隐藏层 for i in range(len(hidden_dims) - 1): self.layers.append(nn.Linear(hidden_dims[i], hidden_dims[i+1])) self.layers.append(nn.ReLU()) # 每一层后添加 ReLU 激活函数 self.layers.append(nn.Linear(hidden_dims[-1], output_dim)) # 添加输出层 def forward(self, x): out = x for layer in self.layers: # 手动遍历 ModuleList 中的层并执行 out = layer(out) return out # 创建动态层数 MLP 模型实例 input_dim = 784 hidden_dims = [500, 250, 100] # 定义隐藏层维度列表 output_dim = 10 dynamic_mlp_model = DynamicLayerMLPModel(input_dim, hidden_dims, output_dim) print(dynamic_mlp_model)

代码详解 2.3.2:

  1. self.layers = nn.ModuleList(): 我们使用 nn.ModuleList 创建一个模块列表 self.layers

  2. 动态添加层: 我们根据 hidden_dims 列表动态地向 self.layers 中添加线性层和 ReLU 激活函数。

  3. forward 方法:forward 方法中,我们手动遍历 self.layers 中的每个模块,并依次执行。

模型结构可视化 (Mermaid Graph - 简化版,仅展示结构):

nn.ModuleList 提供了更大的灵活性,可以用于构建更复杂的模型结构,例如循环神经网络、动态神经网络等。它允许开发者自定义模块的执行顺序和方式。

2.4 自定义神经网络层

虽然 PyTorch 提供了丰富的预定义层,但在某些情况下,我们可能需要实现自定义的神经网络层,以满足特定的需求。自定义层可以让我们更灵活地控制模型的行为,并实现特定的算法或操作。

实现自定义层的步骤:

  1. 继承 nn.Module 类。

  2. __init__ 方法中初始化层的参数 (例如,权重和偏置),可以使用 nn.Parameter 将 Tensor 注册为模型参数。

  3. 重写 forward 方法,定义层的前向传播逻辑。

代码实践 2.4.1: 实现一个自定义的线性层

为了演示自定义层的实现,我们重新实现一个与 nn.Linear 功能类似的线性层:

import torch.nn.functional as F # 导入函数式 API class CustomLinearLayer(nn.Module): def __init__(self, input_dim, output_dim): super(CustomLinearLayer, self).__init__() # 使用 nn.Parameter 定义可学习的权重和偏置 self.weight = nn.Parameter(torch.randn(output_dim, input_dim)) self.bias = nn.Parameter(torch.randn(output_dim)) def forward(self, x): # 使用函数式 API F.linear 实现线性运算 return F.linear(x, self.weight, self.bias) # 创建自定义线性层实例 custom_linear = CustomLinearLayer(input_dim=10, output_dim=5) print(custom_linear)

代码详解 2.4.1:

  1. self.weight = nn.Parameter(torch.randn(output_dim, input_dim)): 我们使用 nn.Parameter 将一个随机初始化的 Tensor self.weight 注册为模型参数。nn.Parameter 是 Tensor 的子类,它告诉 nn.Module 这个 Tensor 是模型的可学习参数,需要被优化器更新。

  2. self.bias = nn.Parameter(torch.randn(output_dim)): 同样,我们使用 nn.Parameter 定义了偏置项 self.bias

  3. return F.linear(x, self.weight, self.bias): 在 forward 方法中,我们使用 torch.nn.functional.linear (通常简写为 F.linear) 函数实现了线性运算。F.linear 是线性运算的函数式 API,它接受输入 x、权重 self.weight 和偏置 self.bias,并返回线性变换的结果。

模型结构可视化 (Mermaid Graph):

这个例子展示了如何使用 nn.Parameter 和函数式 API torch.nn.functional 来实现自定义的神经网络层。自定义层提供了极大的灵活性,开发者可以根据需求定制各种复杂的层结构和操作。

2.5 模型的参数访问与管理

构建模型后,我们经常需要访问和管理模型的参数,例如查看参数值、初始化参数、冻结某些参数等。nn.Module 提供了方便的方法来访问和管理模型的参数。

常用的参数访问与管理方法:

  • model.parameters(): 返回一个迭代器,遍历模型的所有参数 (nn.Parameter)。

  • model.named_parameters(): 返回一个迭代器,遍历模型的所有参数,并返回参数的名称 (模块名.参数名)。

  • model.state_dict(): 返回一个有序字典,包含模型的所有状态 (参数和缓冲区)。

  • model.load_state_dict(state_dict): 加载 state_dict 到模型中,用于模型参数的加载和恢复。

代码实践 2.5.1: 访问和打印模型参数

# 使用之前定义的 MLPModel model = MLPModel(input_dim=784, hidden_dim=500, output_dim=10) # 遍历模型参数并打印 for name, param in model.named_parameters(): print(name, param.shape) # 打印模型的状态字典 state_dict = model.state_dict() print("\nModel State Dict Keys:", state_dict.keys())

代码详解 2.5.1:

  1. model.named_parameters(): 我们使用 model.named_parameters() 迭代器遍历 MLPModel 的所有参数。

  2. print(name, param.shape): 对于每个参数,我们打印参数的名称和形状。参数名称包含了参数所属的模块和参数名 (例如,fc1.weight 表示第一个全连接层的权重)。

  3. state_dict = model.state_dict(): 我们使用 model.state_dict() 获取模型的状态字典。状态字典包含了模型的所有参数,以有序字典的形式存储。

  4. print("\nModel State Dict Keys:", state_dict.keys()): 我们打印状态字典的键,可以看到键与 named_parameters() 迭代器返回的参数名称一致。

通过这些方法,我们可以方便地访问和管理模型的参数,进行参数的初始化、修改、保存和加载等操作。

2.6 模型的设备放置

在 PyTorch 中,模型和数据都需要放置在相同的设备上 (例如,CPU 或 GPU) 才能进行计算。nn.Module 提供了简单的方法来将模型移动到指定的设备。

常用的设备放置方法:

  • model.to(device): 将模型及其参数移动到指定的设备 device (例如,torch.device('cpu')torch.device('cuda'))。

  • tensor.to(device): 将 Tensor 数据移动到指定的设备 device

代码实践 2.6.1: 将模型和数据移动到 GPU (如果可用)

# 检测 GPU 是否可用 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print("Using device:", device) # 创建 MLP 模型实例 model = MLPModel(input_dim=784, hidden_dim=500, output_dim=10) # 将模型移动到指定设备 model.to(device) # 创建随机输入数据并移动到相同设备 dummy_input = torch.randn(1, 784).to(device) # 进行前向传播 output = model(dummy_input) print("Output shape:", output.shape)

代码详解 2.6.1:

  1. device = torch.device("cuda" if torch.cuda.is_available() else "cpu"): 我们检测 GPU 是否可用,如果可用则使用 CUDA 设备 ("cuda"), 否则使用 CPU 设备 ("cpu").

  2. model.to(device): 我们使用 model.to(device)MLPModel 实例移动到检测到的设备上。这会将模型的所有参数都移动到指定的设备。

  3. dummy_input = torch.randn(1, 784).to(device): 我们创建了一个随机输入 Tensor dummy_input,并使用 tensor.to(device) 将其移动到与模型相同的设备上。

  4. output = model(dummy_input): 进行前向传播计算。由于模型和数据都位于同一设备上,计算可以正常进行。

通过 model.to(device)tensor.to(device) 方法,我们可以方便地将模型和数据移动到 GPU 上进行加速计算,或者在 CPU 上进行调试和测试。

2.7 模型保存与加载

训练好的模型需要被保存下来,以便后续使用或部署。PyTorch 提供了简单的方法来保存和加载模型的状态。

常用的模型保存与加载方法:

  • torch.save(model.state_dict(), filepath): 保存模型的 state_dict 到指定的文件路径 filepath。推荐只保存 state_dict,因为它更轻量级且更安全。

  • model.load_state_dict(torch.load(filepath)): 从文件路径 filepath 加载 state_dict,并加载到模型中。

代码实践 2.7.1: 保存和加载模型

# 使用之前定义的 MLPModel model = MLPModel(input_dim=784, hidden_dim=500, output_dim=10) # 假设模型已经训练好了... # 保存模型状态字典到文件 filepath = "mlp_model.pth" torch.save(model.state_dict(), filepath) print(f"Model saved to {filepath}") # 创建一个新的模型实例 (结构必须与保存的模型一致) loaded_model = MLPModel(input_dim=784, hidden_dim=500, output_dim=10) # 加载模型状态字典 loaded_model.load_state_dict(torch.load(filepath)) print("Model loaded from file") # 验证加载的模型 (例如,进行前向传播) loaded_model.eval() # 设置为 evaluation 模式 with torch.no_grad(): # 关闭梯度计算 dummy_input = torch.randn(1, 784) output = loaded_model(dummy_input) print("Output from loaded model:", output.shape)

代码详解 2.7.1:

  1. torch.save(model.state_dict(), filepath): 我们使用 torch.save() 函数将 model.state_dict() 保存到文件 mlp_model.pth 中。

  2. loaded_model = MLPModel(...): 我们创建了一个新的 MLPModel 实例 loaded_model注意:加载模型的结构必须与保存的模型结构完全一致。

  3. loaded_model.load_state_dict(torch.load(filepath)): 我们使用 torch.load() 函数从文件 mlp_model.pth 中加载 state_dict,并使用 loaded_model.load_state_dict() 将加载的 state_dict 加载到 loaded_model 中。

  4. loaded_model.eval(): 在加载模型后,通常需要将模型设置为 evaluation 模式 (model.eval()),这会影响某些层的行为,例如 Dropout 和 BatchNorm。

  5. with torch.no_grad(): ...: 在 evaluation 阶段,通常不需要计算梯度,可以使用 torch.no_grad() 上下文管理器关闭梯度计算,以提高效率。

通过 torch.save()torch.load() 方法,我们可以方便地保存和加载模型的参数,实现模型的持久化和复用。

2.8 总结

本章深入探讨了 PyTorch 中模型构建的核心概念和实践技巧。我们从 nn.Module 这个基石开始,介绍了预定义的神经网络层、模型容器 nn.Sequentialnn.ModuleList,以及自定义神经网络层的方法。同时,我们还学习了如何访问和管理模型参数、将模型放置在不同的设备上,以及模型的保存与加载。

掌握这些模型构建的基础知识,是深入学习和应用 PyTorch 的关键。在后续的章节中,我们将基于这些基础,进一步学习模型的训练、评估和优化,以及构建更复杂的神经网络模型。希望本章的内容能够帮助读者扎实地掌握 PyTorch 模型构建的技能,为未来的深度学习之旅打下坚实的基础。


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