2.2 构建模型 (Model Construction) 第二章:PyTorch 模型构建领域 - 2.2 构建模型 (Model Construction) 详解 2.2.1 模型构建的基石: 类 在 PyTorch 中,构建模型的基石是 类。它是一个抽象类,所有神经网络模块都应继承自它。 不仅封装了网络的结构,还管理了模型的参数,提供了模型训练和推理所需的各种方法。 的核心作用可以概括为: 模块化封装: 将神经网络分解为更小的、可复用的模块(如层、块),并允许将这些模块组合成更复杂的网络结构。 参数管理: 自动追踪和管理模型中所有可学习的参数(例如,权重和偏置),方便进行优化和更新。 前向传播定义: 强制用户定义 方法,明确指定输入数据如何通过网络进行计算,最终得到输出。
nn.Module 类在 PyTorch 中,构建模型的基石是 torch.nn.Module 类。它是一个抽象类,所有神经网络模块都应继承自它。nn.Module 不仅封装了网络的结构,还管理了模型的参数,提供了模型训练和推理所需的各种方法。
nn.Module 的核心作用可以概括为:
模块化封装: 将神经网络分解为更小的、可复用的模块(如层、块),并允许将这些模块组合成更复杂的网络结构。
参数管理: 自动追踪和管理模型中所有可学习的参数(例如,权重和偏置),方便进行优化和更新。
前向传播定义: 强制用户定义 forward() 方法,明确指定输入数据如何通过网络进行计算,最终得到输出。
模型组件的注册: 通过子模块的赋值,自动注册子模块及其参数,使得模型结构清晰可维护。
提供模型操作的便利函数: 例如 parameters() 用于获取所有参数, to() 用于设备迁移, train() 和 eval() 用于设置训练和评估模式等。
基本模型构建流程:
继承 nn.Module: 创建一个新的类,并继承自 nn.Module。
__init__ 方法: 在 __init__ 方法中定义模型的各个层和子模块。常见的层类型包括线性层 (nn.Linear)、卷积层 (nn.Conv2d)、循环层 (nn.RNN, nn.LSTM)、激活函数 (nn.ReLU, nn.Sigmoid)、池化层 (nn.MaxPool2d) 等。
forward 方法: 实现 forward 方法,定义数据在模型中的前向传播过程。在这个方法中,你需要明确数据如何流经你在 __init__ 中定义的各个层,最终得到模型的输出。
为了更好地理解模型构建的过程,我们从一个最简单的线性模型开始。线性模型是神经网络的基础,它只包含一个线性层。
import torch import torch.nn as nn class LinearModel(nn.Module): def __init__(self, input_size, output_size): super(LinearModel, self).__init__() # 必须调用父类的 __init__ 方法 self.linear = nn.Linear(input_size, output_size) # 定义一个线性层 def forward(self, x): out = self.linear(x) # 前向传播:输入 x 通过线性层 return out # 模型实例化 input_dim = 10 output_dim = 1 model = LinearModel(input_dim, output_dim) # 打印模型结构 print(model) # 测试模型 input_tensor = torch.randn(1, input_dim) # 随机生成一个输入张量 output_tensor = model(input_tensor) # 输入张量通过模型 print("Input shape:", input_tensor.shape) print("Output shape:", output_tensor.shape)
代码详解:
import torch 和 import torch.nn as nn: 导入 PyTorch 库和 nn 模块,nn 模块包含了构建神经网络所需的各种类和函数。
class LinearModel(nn.Module):: 定义一个名为 LinearModel 的类,并继承自 nn.Module,表明这是一个 PyTorch 模型。
__init__(self, input_size, output_size):: 构造函数,接收输入特征维度 input_size 和输出特征维度 output_size 作为参数。
super(LinearModel, self).__init__(): 必须调用父类 nn.Module 的 __init__ 方法。 这是初始化 nn.Module 内部状态的必要步骤,例如注册参数列表等。
self.linear = nn.Linear(input_size, output_size): 定义一个线性层,并将其赋值给 self.linear。 nn.Linear 是 PyTorch 提供的线性层类,它接受 input_size 和 output_size 作为参数,表示输入和输出的特征维度。这个线性层会被自动注册为 LinearModel 的子模块。
forward(self, x):: 定义前向传播方法,接收输入张量 x 作为参数。
out = self.linear(x): 将输入 x 传入线性层 self.linear 进行计算,得到输出 out。 PyTorch 的 nn.Module 对象可以像函数一样调用,实际上会调用对象的 forward 方法。
return out: 返回模型的输出 out。
model = LinearModel(input_dim, output_dim): 实例化 LinearModel 类,创建一个模型对象 model。 传入 input_dim 和 output_dim 参数来指定线性层的输入和输出维度。
print(model): 打印模型结构。 PyTorch 会自动打印出模型的层结构和参数信息,方便我们查看模型组成。
input_tensor = torch.randn(1, input_dim): 随机生成一个输入张量 input_tensor。 torch.randn(1, input_dim) 创建一个形状为 (1, input_dim) 的张量,其中元素服从标准正态分布。
output_tensor = model(input_tensor): 将 input_tensor 输入模型 model,得到输出张量 output_tensor。 这里再次体现了 nn.Module 对象可以像函数一样调用。
print("Input shape:", input_tensor.shape) 和 print("Output shape:", output_tensor.shape): 打印输入和输出张量的形状,验证模型的输入输出维度是否符合预期。
模型结构打印结果:
LinearModel( (linear): Linear(in_features=10, out_features=1, bias=True) )
这清晰地展示了 LinearModel 的结构:它包含一个名为 linear 的子模块,类型为 Linear,输入特征维度为 10,输出特征维度为 1,并且包含偏置项 (bias=True)。
线性模型虽然简单,但表达能力有限。为了构建更强大的模型,我们需要引入非线性激活函数和多层结构。多层感知机 (MLP) 就是一种常见的深度神经网络,它由多个线性层和非线性激活层堆叠而成。
import torch import torch.nn as nn class MLPModel(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(MLPModel, self).__init__() self.fc1 = nn.Linear(input_size, hidden_size) # 第一个全连接层 self.relu = nn.ReLU() # ReLU 激活函数 self.fc2 = nn.Linear(hidden_size, output_size) # 第二个全连接层 def forward(self, x): out = self.fc1(x) # 输入 x 通过第一个全连接层 out = self.relu(out) # 通过 ReLU 激活函数 out = self.fc2(out) # 通过第二个全连接层 return out # 模型实例化 input_dim = 784 # 例如,MNIST 图像的像素数量 hidden_dim = 500 output_dim = 10 # 例如,MNIST 数字的类别数量 model = MLPModel(input_dim, hidden_dim, output_dim) # 打印模型结构 print(model) # 使用 Mermaid 绘制模型结构图 print(""" ```mermaid graph TD Input[Input] --> FC1[FC1]; FC1 --> ReLU[ReLU]; ReLU --> FC2[FC2]; FC2 --> Output[Output]; classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef func fill:#ccf,stroke:#333,stroke-width:2px class FC1,FC2 layer class ReLU func
""")
**代码详解:** 1. **`class MLPModel(nn.Module):`**: 定义一个名为 `MLPModel` 的类,继承自 `nn.Module`。 2. **`__init__(self, input_size, hidden_size, output_size):`**: 构造函数,接收输入维度 `input_size`、隐藏层维度 `hidden_size` 和输出维度 `output_size` 作为参数。 - `self.fc1 = nn.Linear(input_size, hidden_size)`: **定义第一个全连接层 `fc1`,将输入维度 `input_size` 映射到隐藏层维度 `hidden_size`。** - `self.relu = nn.ReLU()`: **定义 ReLU 激活函数,并赋值给 `self.relu`。** `nn.ReLU` 是 PyTorch 提供的 ReLU 激活函数层。 - `self.fc2 = nn.Linear(hidden_size, output_size)`: **定义第二个全连接层 `fc2`,将隐藏层维度 `hidden_size` 映射到输出维度 `output_size`。** 3. **`forward(self, x):`**: 定义前向传播方法。 - `out = self.fc1(x)`: **输入 `x` 通过第一个全连接层 `fc1`。** - `out = self.relu(out)`: **`fc1` 的输出通过 ReLU 激活函数 `relu`。** - `out = self.fc2(out)`: **ReLU 的输出通过第二个全连接层 `fc2`。** - `return out`: 返回模型的最终输出。 **模型结构打印结果:**
MLPModel(
(fc1): Linear(in_features=784, out_features=500, bias=True)
(relu): ReLU()
(fc2): Linear(in_features=500, out_features=10, bias=True)
)
这展示了 `MLPModel` 的结构,包含两个线性层 `fc1` 和 `fc2`,以及一个 ReLU 激活函数层 `relu`。 **Mermaid 模型结构图:** ```mermaid graph TD Input[Input] --> FC1[FC1]; FC1 --> ReLU[ReLU]; ReLU --> FC2[FC2]; FC2 --> Output[Output]; classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef func fill:#ccf,stroke:#333,stroke-width:2px class FC1,FC2 layer class ReLU func
这个 Mermaid 图清晰地可视化了 MLP 模型的结构,展示了数据流动的路径:输入 (Input) -> 全连接层 1 (FC1) -> ReLU 激活函数 (ReLU) -> 全连接层 2 (FC2) -> 输出 (Output)。
nn.Sequential 构建模型当模型结构比较简单,例如层与层之间是顺序连接时,可以使用 nn.Sequential 容器来简化模型构建过程。nn.Sequential 允许我们将多个层按顺序组合成一个模块,使得代码更加简洁易读。
import torch.nn as nn model_sequential = nn.Sequential( nn.Linear(784, 500), nn.ReLU(), nn.Linear(500, 10) ) print(model_sequential) # 使用 Mermaid 绘制模型结构图 print(""" ```mermaid graph TD Input[Input] --> Linear1[Linear_1]; Linear1 --> ReLU[ReLU]; ReLU --> Linear2[Linear_2]; Linear2 --> Output[Output]; classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef func fill:#ccf,stroke:#333,stroke-width:2px class Linear1,Linear2 layer class ReLU func
""")
**代码详解:** 1. **`model_sequential = nn.Sequential(...)`**: **使用 `nn.Sequential` 容器构建模型。** 在 `nn.Sequential` 的构造函数中,我们按顺序传入模型的各个层,PyTorch 会自动将这些层连接起来。 - `nn.Linear(784, 500)`: 第一个线性层。 - `nn.ReLU()`: ReLU 激活函数。 - `nn.Linear(500, 10)`: 第二个线性层。 2. **`print(model_sequential)`**: **打印 `nn.Sequential` 模型结构。** PyTorch 会自动打印出容器中包含的层及其顺序。 **模型结构打印结果:**
Sequential(
(0): Linear(in_features=784, out_features=500, bias=True)
(1): ReLU()
(2): Linear(in_features=500, out_features=10, bias=True)
)
`nn.Sequential` 将模型结构以有序列表的形式展示,并自动为每个层分配索引 (0, 1, 2...)。 **Mermaid 模型结构图:** ```mermaid graph TD Input[Input] --> Linear1[Linear_1]; Linear1 --> ReLU[ReLU]; ReLU --> Linear2[Linear_2]; Linear2 --> Output[Output]; classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef func fill:#ccf,stroke:#333,stroke-width:2px class Linear1,Linear2 layer class ReLU func
Mermaid 图与之前 MLP 模型类似,但更加简洁地表示了模型的顺序结构。
除了线性层和全连接层,卷积神经网络 (CNN) 在图像处理领域也至关重要。PyTorch 提供了丰富的卷积层、池化层等组件,可以灵活构建各种 CNN 模型。
import torch import torch.nn as nn class CNNModel(nn.Module): def __init__(self, num_classes=10): super(CNNModel, self).__init__() self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1, padding=1) # 第一个卷积层 self.relu1 = nn.ReLU() self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 第一个最大池化层 self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1) # 第二个卷积层 self.relu2 = nn.ReLU() self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) # 第二个最大池化层 self.fc = nn.Linear(64 * 7 * 7, num_classes) # 全连接层 (假设输入图像大小为 28x28,经过两次池化后特征图大小变为 7x7) def forward(self, x): out = self.pool1(self.relu1(self.conv1(x))) # 卷积 -> ReLU -> 池化 out = self.pool2(self.relu2(self.conv2(out))) # 卷积 -> ReLU -> 池化 out = out.view(-1, 64 * 7 * 7) # 展平特征图 out = self.fc(out) # 通过全连接层 return out # 模型实例化 model_cnn = CNNModel(num_classes=10) print(model_cnn) # 使用 Mermaid 绘制模型结构图 print(""" ```mermaid graph TD Input[Input Image] --> Conv1[Conv2d_1]; Conv1 --> ReLU1[ReLU_1]; ReLU1 --> Pool1[MaxPool2d_1]; Pool1 --> Conv2[Conv2d_2]; Conv2 --> ReLU2[ReLU_2]; ReLU2 --> Pool2[MaxPool2d_2]; Pool2 --> Flatten[Flatten]; Flatten --> FC[Linear]; FC --> Output[Output]; classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef func fill:#ccf,stroke:#333,stroke-width:2px class Conv1,Conv2,FC layer class ReLU1,ReLU2 func class Pool1,Pool2 layer class Flatten func
""")
**代码详解:** 1. **`class CNNModel(nn.Module):`**: 定义 CNN 模型类。 2. **`__init__(self, num_classes=10):`**: 构造函数,接收类别数量 `num_classes` 作为参数。 - `self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1, padding=1)`: **定义第一个卷积层 `conv1`。** - `in_channels=1`: 输入通道数,假设输入是灰度图像(单通道)。 - `out_channels=32`: 输出通道数,卷积核数量为 32。 - `kernel_size=3`: 卷积核大小为 3x3。 - `stride=1`: 步长为 1。 - `padding=1`: 填充为 1,保持特征图大小基本不变。 - `self.relu1 = nn.ReLU()`: 第一个 ReLU 激活函数。 - `self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)`: 第一个最大池化层,池化窗口大小和步长都为 2,将特征图尺寸减半。 - `self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1)`: 第二个卷积层,输入通道数为 32(与 `conv1` 的输出通道数一致),输出通道数为 64。 - `self.relu2 = nn.ReLU()`: 第二个 ReLU 激活函数。 - `self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)`: 第二个最大池化层。 - `self.fc = nn.Linear(64 * 7 * 7, num_classes)`: **全连接层 `fc`。** `64 * 7 * 7` 是假设输入图像大小为 28x28,经过两次池化后特征图大小为 7x7,通道数为 64,展平后的特征维度。`num_classes` 是输出类别数量。 3. **`forward(self, x):`**: 定义前向传播方法。 - `out = self.pool1(self.relu1(self.conv1(x)))`: **输入 `x` 依次经过卷积层 `conv1`、ReLU 激活函数 `relu1` 和最大池化层 `pool1`。** PyTorch 支持链式调用,代码更加简洁。 - `out = self.pool2(self.relu2(self.conv2(out)))`: **`pool1` 的输出继续经过卷积层 `conv2`、ReLU 激活函数 `relu2` 和最大池化层 `pool2`。** - `out = out.view(-1, 64 * 7 * 7)`: **将特征图展平为二维张量,以便输入全连接层。** `out.view(-1, 64 * 7 * 7)` 将 `out` 张量 reshape 成形状为 `(batch_size, 64 * 7 * 7)` 的张量,其中 `-1` 表示批次大小由 PyTorch 自动推断。 - `out = self.fc(out)`: **展平后的特征通过全连接层 `fc`。** - `return out`: 返回模型的最终输出。 **模型结构打印结果:**
CNNModel(
(conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU()
(pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(fc): Linear(in_features=3136, out_features=10, bias=True)
)
**Mermaid 模型结构图:** ```mermaid graph TD Input[Input Image] --> Conv1[Conv2d_1]; Conv1 --> ReLU1[ReLU_1]; ReLU1 --> Pool1[MaxPool2d_1]; Pool1 --> Conv2[Conv2d_2]; Conv2 --> ReLU2[ReLU_2]; ReLU2 --> Pool2[MaxPool2d_2]; Pool2 --> Flatten[Flatten]; Flatten --> FC[Linear]; FC --> Output[Output]; classDef layer fill:#f9f,stroke:#333,stroke-width:2px classDef func fill:#ccf,stroke:#333,stroke-width:2px class Conv1,Conv2,FC layer class ReLU1,ReLU2 func class Pool1,Pool2 layer class Flatten func
Mermaid 图清晰地展示了 CNN 模型的结构,包括卷积层、ReLU 激活函数、池化层和全连接层,以及数据在模型中的流动路径。
PyTorch 的强大之处在于其高度的灵活性,允许用户自定义各种层来满足特定的需求。自定义层需要继承 nn.Module 类,并实现 __init__ 和 forward 方法。
import torch import torch.nn as nn class MyLinear(nn.Module): def __init__(self, in_features, out_features): super(MyLinear, self).__init__() self.weight = nn.Parameter(torch.randn(out_features, in_features)) # 定义权重参数 self.bias = nn.Parameter(torch.randn(out_features)) # 定义偏置参数 def forward(self, input): return torch.matmul(input, self.weight.t()) + self.bias # 手动实现线性运算 class CustomModel(nn.Module): def __init__(self): super(CustomModel, self).__init__() self.my_linear = MyLinear(10, 5) # 使用自定义线性层 self.relu = nn.ReLU() self.linear = nn.Linear(5, 2) # 使用 PyTorch 内置线性层 def forward(self, x): out = self.my_linear(x) out = self.relu(out) out = self.linear(out) return out model_custom = CustomModel() print(model_custom)
代码详解:
class MyLinear(nn.Module):: 定义自定义线性层 MyLinear,继承自 nn.Module。
__init__(self, in_features, out_features):: 构造函数。
self.weight = nn.Parameter(torch.randn(out_features, in_features)): 定义权重参数 weight。 nn.Parameter 将 torch.randn 生成的张量注册为模型的参数,PyTorch 会自动追踪和管理这些参数。
self.bias = nn.Parameter(torch.randn(out_features)): 定义偏置参数 bias。
forward(self, input):: 定义前向传播方法。
return torch.matmul(input, self.weight.t()) + self.bias: 手动实现线性运算。 torch.matmul 执行矩阵乘法,self.weight.t() 对权重矩阵进行转置, + self.bias 加上偏置项。class CustomModel(nn.Module):: 定义使用自定义层的模型 CustomModel。
__init__(self):: 构造函数。
self.my_linear = MyLinear(10, 5): 实例化自定义线性层 MyLinear。
self.relu = nn.ReLU(): ReLU 激活函数。
self.linear = nn.Linear(5, 2): PyTorch 内置线性层。
forward(self, x):: 定义前向传播方法,依次通过自定义线性层、ReLU 激活函数和内置线性层。
模型结构打印结果:
CustomModel( (my_linear): MyLinear() (relu): ReLU() (linear): Linear(in_features=5, out_features=2, bias=True) )
这表明我们成功构建了一个包含自定义线性层 MyLinear 的模型。
模块化设计: 将模型分解为小的、可复用的模块,提高代码的可读性和可维护性。
清晰的 __init__ 和 forward 方法: __init__ 负责定义模型结构,forward 负责定义数据流向,保持逻辑清晰。
使用 nn.Sequential 简化顺序模型: 对于层与层之间顺序连接的模型,使用 nn.Sequential 可以提高代码简洁性。
合理命名层和模块: 使用有意义的名称,例如 conv1, fc2, relu 等,方便理解模型结构。
注释代码: 为代码添加必要的注释,解释代码的功能和逻辑,提高代码的可读性。
可视化模型结构: 使用工具 (例如 Mermaid, Netron) 可视化模型结构,方便理解和调试。
模型构建是 PyTorch 中至关重要的一环。nn.Module 类是构建模型的基石,通过继承 nn.Module 和定义 __init__ 和 forward 方法,我们可以灵活构建各种复杂的神经网络模型。PyTorch 提供了丰富的内置层和功能,同时允许用户自定义层,使得模型构建既强大又灵活。掌握模型构建的技巧和最佳实践,能够帮助我们更好地利用 PyTorch 构建高效、可维护的深度学习模型,并应用于各种实际问题中。通过代码实践和Mermaid图的可视化,我们深入理解了 PyTorch 模型构建的核心概念和方法,为进一步探索深度学习的奥秘奠定了坚实的基础。