目录
1.torch.nn.Module--概述
1. PyTorch vs. Keras 的设计差异
- Keras(高层框架):
- 推荐通过继承
Layer
类自定义层,而非直接继承Model
(官方建议)。 - 严格区分“层”和“模型”,强调模块化组合。
- 推荐通过继承
- PyTorch(灵活统一):
- 没有严格的层/模型区分,自定义层、模块或整个模型,统一继承
nn.Module
。 - 比如
Linear
(全连接层)、Sequential
(顺序容器)都是Module
的子类。
- 没有严格的层/模型区分,自定义层、模块或整个模型,统一继承
2. 为什么只用 nn.Module
,不用 autograd.Function
?
nn.Module
的优势:- 自动管理参数(如
parameters()
)、支持 GPU/CPU 切换、内置训练/评估模式(train()
/eval()
)。 - 无需手动写反向传播,PyTorch 自动计算梯度。
- 自动管理参数(如
autograd.Function
的局限:- 需手动实现
forward
和backward
,复杂且容易出错,仅适用于特殊需求(如自定义不可导操作)。
- 需手动实现
“一切皆 Module”:PyTorch 通过 nn.Module
提供统一接口,无论是单层、多层的块,还是完整模型,都按相同方式组织。
2.torch.nn.Module--简介
class Module(object):的主要模块:
类别 | 方法 | 功能说明 |
---|---|---|
初始化与核心 | __init__(self) |
构造函数,初始化模块(需调用 super().__init__() ) |
forward(self, *input) |
必须重写,定义前向传播逻辑 | |
__call__(self, *input, **kwargs) |
使实例可调用(如 model(x) ),自动调用 forward() |
|
模块管理 | add_module(name, module) |
添加子模块(如 self.add_module('linear', nn.Linear(10, 2)) ) |
children() |
返回直接子模块的迭代器(不递归) | |
named_children() |
返回 (name, module) 对的迭代器(直接子模块) |
|
modules() |
递归返回所有子模块的迭代器 | |
named_modules() |
递归返回 (name, module) 对的迭代器 |
|
参数管理 | parameters(recurse=True) |
返回所有参数的迭代器(默认递归子模块) |
named_parameters() |
返回 (name, parameter) 对的迭代器(如 'linear.weight' ) |
|
设备移动 | cuda(device=None) |
将模块参数和缓冲区移动到 GPU |
cpu() |
将模块参数和缓冲区移动到 CPU | |
训练/评估模式 | train(mode=True) |
设置为训练模式(影响 Dropout 、BatchNorm 等) |
eval() |
设置为评估模式(等价于 train(False) ) |
|
梯度管理 | zero_grad() |
重置所有参数的梯度(默认设为 None ,PyTorch 1.7+ 推荐) |
辅助方法 | __repr__() |
返回模块的字符串表示(打印模型结构) |
__dir__() |
返回模块的属性列表(通常无需直接调用) |
注意:
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__构造函数和forward这两个方法。但有一些注意技巧:
- 一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面;
- 一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替
- forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
总结:使用pytorch定义神经网络时,
1.必须继承那nn.Module
2.必须包含__init__构造函数和forward前向传播这两个方法
3.__init__是定义网络结构,forward是前向传播
举例:
import torch
import torch.nn as nn
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, 1, 1) # 输入3通道,输出32通道
self.relu1 = nn.ReLU()
self.max_pooling1 = nn.MaxPool2d(2, 1) # kernel_size=2, stride=1
self.conv2 = nn.Conv2d(32, 32, 3, 1, 1) # 注意:输入应为32通道(修正处)
self.relu2 = nn.ReLU()
self.max_pooling2 = nn.MaxPool2d(2, 1)
# 计算全连接层输入尺寸(需根据实际输出调整)
self.dense1 = nn.Linear(32 * 222 * 222, 128) # 临时值,后面会修正
self.dense2 = nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu1(x)
x = self.max_pooling1(x)
x = self.conv2(x)
x = self.relu2(x)
x = self.max_pooling2(x)
x = x.view(x.size(0), -1) # 展平
x = self.dense1(x)
x = self.dense2(x)
return x
# 创建输入张量(假设输入为224x224的RGB图像)
x = torch.randn(4, 3, 224, 224) # batch_size=4, 通道=3, 高=224, 宽=224
# 实例化模型
model = MyNet()
# 打印网络结构
print("模型结构:")
print(model)
# 前向传播
print("\n输入形状:", x.shape)
output = model(x)
print("输出形状:", output.shape) # 应为 [4, 10]
在forward里面实现所有层的连接关系,当然这里依然是顺序连接的
总结:所有放在构造函数__init__里面的层的都是这个模型的“固有属性”.
注意:model(x)相当于model.forward(x)
3.torch.nn.Module--实现
Module类是非常灵活的,可以有很多灵活的实现方式,下面将一一介绍。
3.1.Sequential来包装层
即将几个层包装在一起作为一个大的层(块),前面的一篇文章详细介绍了Sequential类的使用,包括常见的三种方式,以及每一种方式的优缺点,参见:pytorch教程之nn.Sequential类详解——使用Sequential类来自定义顺序连接模型-CSDN博客
方式一:
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block = nn.Sequential(
nn.Conv2d(3, 32, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(2))
self.dense_block = nn.Sequential(
nn.Linear(32 * 3 * 3, 128),
nn.ReLU(),
nn.Linear(128, 10)
)
# 在这里实现层之间的连接关系,其实就是所谓的前向传播
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv_block): Sequential(
(0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(dense_block): Sequential(
(0): Linear(in_features=288, out_features=128, bias=True)
(1): ReLU()
(2): Linear(in_features=128, out_features=10, bias=True)
)
)
'''
方式二:
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block = nn.Sequential(
OrderedDict(
[
("conv1", nn.Conv2d(3, 32, 3, 1, 1)),
("relu1", nn.ReLU()),
("pool", nn.MaxPool2d(2))
]
))
self.dense_block = nn.Sequential(
OrderedDict([
("dense1", nn.Linear(32 * 3 * 3, 128)),
("relu2", nn.ReLU()),
("dense2", nn.Linear(128, 10))
])
)
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv_block): Sequential(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(dense_block): Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
)
'''
方式三:
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block=torch.nn.Sequential()
self.conv_block.add_module("conv1",torch.nn.Conv2d(3, 32, 3, 1, 1))
self.conv_block.add_module("relu1",torch.nn.ReLU())
self.conv_block.add_module("pool1",torch.nn.MaxPool2d(2))
self.dense_block = torch.nn.Sequential()
self.dense_block.add_module("dense1",torch.nn.Linear(32 * 3 * 3, 128))
self.dense_block.add_module("relu2",torch.nn.ReLU())
self.dense_block.add_module("dense2",torch.nn.Linear(128, 10))
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv_block): Sequential(
(conv1): Conv2d(3, 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)
)
(dense_block): Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
)
'''
3.2.children和modules
特别注意:Sequential类虽然继承自Module类,二者有相似部分,但是也有很多不同的部分,集中体现在:
Sequenrial类实现了整数索引,故而可以使用model[index] 这样的方式获取一个曾,但是Module类并没有实现整数索引,不能够通过整数索引来获得层
def children(self): """返回当前模块的直接子模块的迭代器(不包括子模块的子模块)。""" # 示例:若当前模块包含子模块 conv1 和 fc1,则 children() 返回 [conv1, fc1] def named_children(self): """返回当前模块的直接子模块的迭代器,并附带子模块的名称(格式:(name, module))。""" # 示例:返回 [('conv1', Conv2d(...)), ('fc1', Linear(...))] def modules(self): """递归返回当前模块及其所有子模块的迭代器(深度优先遍历)。""" # 示例:返回 [当前模块, conv1, conv1.weight, ..., fc1, ...] def named_modules(self, memo=None, prefix=''): """递归返回当前模块及其所有子模块的迭代器,并附带模块的完整路径名称(格式:(name, module))。""" # 示例:返回 [('', 当前模块), ('conv1', conv1), ('conv1.weight', conv1.weight), ...]
'''
注意:这几个方法返回的都是一个Iterator迭代器,故而通过for循环访问,当然也可以通过next
'''
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block=torch.nn.Sequential()
self.conv_block.add_module("conv1",torch.nn.Conv2d(3, 32, 3, 1, 1))
self.conv_block.add_module("relu1",torch.nn.ReLU())
self.conv_block.add_module("pool1",torch.nn.MaxPool2d(2))
self.dense_block = torch.nn.Sequential()
self.dense_block.add_module("dense1",torch.nn.Linear(32 * 3 * 3, 128))
self.dense_block.add_module("relu2",torch.nn.ReLU())
self.dense_block.add_module("dense2",torch.nn.Linear(128, 10))
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
1.model.children()方法
for i in model.children():
print(i)
print(type(i)) # 查看每一次迭代的元素到底是什么类型,实际上是 Sequential 类型,所以有可以使用下标index索引来获取每一个Sequenrial 里面的具体层
'''运行结果为:
Sequential(
(conv1): Conv2d(3, 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)
)
<class 'torch.nn.modules.container.Sequential'>
Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
<class 'torch.nn.modules.container.Sequential'>
'''
(2)model.named_children()方法
for i in model.children():
print(i)
print(type(i)) # 查看每一次迭代的元素到底是什么类型,实际上是 返回一个tuple,tuple 的第一个元素是
'''运行结果为:
('conv_block', Sequential(
(conv1): Conv2d(3, 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)
))
<class 'tuple'>
('dense_block', Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
))
<class 'tuple'>
'''
(3)model.modules()方法
for i in model.modules():
print(i)
print("==================================================")
'''运行结果为:
MyNet(
(conv_block): Sequential(
(conv1): Conv2d(3, 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)
)
(dense_block): Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
)
==================================================
Sequential(
(conv1): Conv2d(3, 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)
)
==================================================
Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
==================================================
ReLU()
==================================================
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
==================================================
Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
==================================================
Linear(in_features=288, out_features=128, bias=True)
==================================================
ReLU()
==================================================
Linear(in_features=128, out_features=10, bias=True)
==================================================
'''
(4)model.named_modules()方法
for i in model.named_modules():
print(i)
print("==================================================")
'''运行结果是:
('', MyNet(
(conv_block): Sequential(
(conv1): Conv2d(3, 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)
)
(dense_block): Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
))
==================================================
('conv_block', Sequential(
(conv1): Conv2d(3, 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)
))
==================================================
('conv_block.conv1', Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
==================================================
('conv_block.relu1', ReLU())
==================================================
('conv_block.pool1', MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False))
==================================================
('dense_block', Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
))
==================================================
('dense_block.dense1', Linear(in_features=288, out_features=128, bias=True))
==================================================
('dense_block.relu2', ReLU())
==================================================
('dense_block.dense2', Linear(in_features=128, out_features=10, bias=True))
==================================================
'''
(1)model的modules()方法和named_modules()方法都会将整个模型的所有构成(包括包装层、单独的层、自定义层等)由浅入深依次遍历出来,只不过modules()返回的每一个元素是直接返回的层对象本身,而named_modules()返回的每一个元素是一个元组,第一个元素是名称,第二个元素才是层对象本身。
(2)如何理解children和modules之间的这种差异性。注意pytorch里面不管是模型、层、激活函数、损失函数都可以当成是Module的拓展,所以modules和named_modules会层层迭代,由浅入深,将每一个自定义块block、然后block里面的每一个层都当成是module来迭代。而children就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。