PyTorch nn.Module 与模型结构
nn.Module 是 PyTorch 模型的基本组织单元:它负责注册参数、子模块和缓冲区,并提供 .to()、.train()、.eval()、state_dict() 等工程能力。模型结构页面只关注“怎样声明和组合计算”,损失、优化器和训练循环见下一页。
学习目标
- 用
nn.Module和forward定义可训练模型。 - 选择
Sequential、显式forward、ModuleList和ModuleDict。 - 理解常用层的 shape、训练/推理差异和参数注册规则。
核心知识点
- 自定义模型继承
nn.Module,在__init__中声明层,在forward中描述计算。 - 赋给 Module 属性的子模块会被注册,普通 Python list 里的层不会自动注册。
nn.Linear处理最后一维;Conv2d常用[N, C, H, W];BatchNorm与Dropout依赖训练/推理状态。model.parameters()交给优化器;named_parameters()和state_dict()适合检查和保存。torch.nn.functional是无状态函数,带可学习参数的运算优先使用模块实例。
常用用法
nn.Module:定义最小模型
import torch
from torch import nn
class Regressor(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(2, 1)
def forward(self, features):
return self.linear(features)
model = Regressor()
prediction = model(torch.ones(3, 2))
print(prediction.shape)
# 输出:torch.Size([3, 1])调用 model(x) 会进入 forward,不要手动直接调用 model.forward(x),这样才能保留 Module 的钩子和封装行为。
nn.Sequential:按顺序组合层
import torch
from torch import nn
model = nn.Sequential(
nn.Linear(4, 8),
nn.ReLU(),
nn.Linear(8, 2),
)
output = model(torch.ones(5, 4))
print(output.shape)
# 输出:torch.Size([5, 2])Sequential 适合单一路径、层间只传一个 Tensor 的网络;有残差连接、多个输入或分支时写显式 forward 更清晰。
nn.Linear:全连接层
import torch
from torch import nn
layer = nn.Linear(3, 2)
features = torch.ones(4, 3)
output = layer(features)
print(output.shape, sum(parameter.numel() for parameter in layer.parameters()))
# 输出:torch.Size([4, 2]) 8参数量为 in_features * out_features + out_features(包含 bias);输入最后一维必须是 in_features。
nn.ReLU:激活函数
import torch
from torch import nn
activation = nn.ReLU()
values = activation(torch.tensor([[-1.0, 2.0]]))
print(values.tolist())
# 输出:[[0.0, 2.0]]nn.ReLU() 是无可学习参数的模块;需要函数式写法时可用 torch.nn.functional.relu,但两者都不会自动改变训练状态。
nn.Conv2d:图像卷积层
import torch
from torch import nn
layer = nn.Conv2d(in_channels=3, out_channels=8, kernel_size=3, padding=1)
images = torch.ones(2, 3, 16, 16)
feature_map = layer(images)
print(feature_map.shape)
# 输出:torch.Size([2, 8, 16, 16])卷积输入通常是 NCHW;padding=1 配合 3×3 卷积保持空间尺寸,具体输出仍要结合 stride、dilation 和 kernel 计算。
nn.BatchNorm1d:批归一化
import torch
from torch import nn
features = torch.randn(4, 6)
layer = nn.BatchNorm1d(6)
layer.train()
normalized = layer(features)
print(normalized.shape)
# 输出:torch.Size([4, 6])BatchNorm1d 常用于 [N, C] 或序列特征,BatchNorm2d 常用于 [N, C, H, W];训练态更新 running statistics,推理态使用已保存的统计量。
nn.Dropout、train() 与 eval():切换模块状态
import torch
from torch import nn
dropout = nn.Dropout(p=0.5)
dropout.train()
training_state = dropout.training
dropout.eval()
evaluation_state = dropout.training
print(training_state, evaluation_state)
# 输出:True False状态切换会递归影响子模块,但不会自动开启或关闭 autograd;推理循环还应配合 torch.inference_mode()。
named_parameters 与 state_dict:检查已注册参数
from torch import nn
model = nn.Sequential(nn.Linear(2, 3), nn.Linear(3, 1))
names = [name for name, _ in model.named_parameters()]
keys = list(model.state_dict().keys())
print(names[:2], keys[:2])
# 输出:参数名字列表前两项(例如 ['0.weight', '0.bias'])和 state_dict 键state_dict 还包含 registered buffer;保存和加载模型时优先围绕它设计,具体见保存专题。
不常用但需要知道
nn.ModuleList:注册动态层列表
import torch
from torch import nn
layers = nn.ModuleList([nn.Linear(2, 2), nn.Linear(2, 2)])
value = torch.ones(1, 2)
for layer in layers:
value = layer(value)
print(len(list(layers.parameters())), value.shape)
# 输出:4 torch.Size([1, 2])普通 list 不会自动注册参数;需要遍历的一组动态层使用 ModuleList,否则优化器和 state_dict 都看不到它们。
nn.ModuleDict:按名字注册分支
import torch
from torch import nn
heads = nn.ModuleDict({"regression": nn.Linear(4, 1), "classification": nn.Linear(4, 2)})
features = torch.ones(3, 4)
result = heads["classification"](features)
print(sorted(heads.keys()), result.shape)
# 输出:['classification', 'regression'] torch.Size([3, 2])ModuleDict 让配置驱动的多头模型保持参数注册和可读的命名,分支选择仍应在 forward 中集中处理。
register_buffer:注册不训练的状态
import torch
from torch import nn
class WithMean(nn.Module):
def __init__(self):
super().__init__()
self.register_buffer("mean", torch.tensor(0.5))
def forward(self, value):
return value - self.mean
model = WithMean()
print(list(model.state_dict().keys()), model(torch.tensor(1.0)).item())
# 输出:['mean'] 0.5Buffer 会跟随 model.to(device) 并进入 state_dict,但不会出现在 parameters() 中;running mean、固定 mask 等状态适合这样注册。
forward 中的残差连接:显式组合张量
import torch
from torch import nn
class ResidualBlock(nn.Module):
def __init__(self, width):
super().__init__()
self.layers = nn.Sequential(nn.Linear(width, width), nn.ReLU(), nn.Linear(width, width))
def forward(self, value):
return value + self.layers(value)
block = ResidualBlock(4)
print(block(torch.ones(2, 4)).shape)
# 输出:torch.Size([2, 4])残差相加要求两条路径 shape 兼容;维度变化时需要额外的投影层,而不能直接相加。
易混点
nn.Module会递归注册子模块,普通 Python list、dict 不会;动态结构使用ModuleList、ModuleDict。F.relu(x)没有参数;nn.ReLU()是模块。需要可学习参数或持久化状态时使用模块。train()/eval()是状态切换,不是“开始训练/开始推理”的完整命令。Linear只要求最后一维匹配;卷积层则要求通道维和布局匹配。state_dict包含参数和 buffer,不等于整个模型对象;加载前必须有对应结构。
课后小问
为什么把层放进普通 Python 列表会导致优化器更新不到它们?
答案: 普通列表不会触发
nn.Module的子模块注册,model.parameters()和state_dict()看不到这些层。解析: 使用
nn.ModuleList或nn.Sequential注册后,参数才会被.to()、优化器和保存逻辑统一管理。为什么验证前既要
model.eval()又常常要inference_mode()?答案: 前者切换 Dropout/BatchNorm 的行为,后者关闭 autograd 并减少推理开销。
解析: 一个是模块状态,一个是梯度上下文,职责不同;只调用其中一个都可能得到错误的验证行为或不必要的显存占用。
本节小结
nn.Module 解决的是模型结构与参数管理:简单串联用 Sequential,有分支或残差时写 forward,动态层用 ModuleList/ModuleDict,固定状态用 register_buffer。训练和保存都依赖正确的注册。
快速回顾
Module:__init__ 注册层,forward 描述计算
串联:Sequential
动态层:ModuleList / ModuleDict
参数:parameters / named_parameters
状态:train / eval / register_buffer
持久化:state_dict
阿源的知识库