【Pytorch使用】Sequential、ModuleList 与 ModuleDict 的设计与取舍

​ 在 PyTorch 中,所有神经网络模型都基于 nn.Module 构建。

​ 在此基础上,我们通常通过以下三种方式来组织网络结构:

理解这三种容器的设计目的与使用边界,是写出清晰、可维护、可扩展模型代码的关键。

一、nn.Module 与模型的基本结构

  • nn.Moduletorch.nn 中提供的模型构造基类,所有神经网络层和模型都继承自它;
  • 一个标准的 PyTorch 模型通常包含两个核心部分:
    • 结构定义 :在 __init__ 中声明和注册子模块;
    • 数据流定义 :在 forward 中明确数据的前向传播路径。

在此基础上,PyTorch 提供了多种"模块容器",用来帮助我们更优雅地组织这些子模块。

二、三种组织方式的对比与适用场景

方式 是否自动定义 forward 是否有顺序 是否灵活
Sequential ✅ 是 ✅ 固定顺序 ❌ 低
ModuleList ❌ 否 ⚠️ 仅存储顺序 ✅ 高
ModuleDict ❌ 否 ❌ 无隐含顺序 ✅ 高
  • Sequential
    适合用于 结构已确定、仅做快速验证或标准模块封装 的场景;
  • ModuleList / ModuleDict
    适合:
    • 同一结构重复多次
    • 层数可变
    • 需要在 forward 中显式控制数据流(如残差、分支、多尺度)

三、Sequential

nn.Sequential 是一个 有序的模块容器,用于将多个子模块按顺序串联起来,形成一个整体模块。

​ 当模型的前向传播逻辑是严格的线性串联 时,Sequential 可以显著简化代码结构。

  • 核心特性Sequential 中模块的前向计算顺序,与其被添加到容器中的顺序完全一致。

3.1 Sequential 的内部实现逻辑(简化版)

python 复制代码
class MySequential(nn.Module):
    def __init__(self, *args):
        super(MySequential, self).__init__()
        if len(args) == 1 and isinstance(args[0], OrderedDict):
            for key, module in args[0].items():
                self.add_module(key, module)
        else:
            for idx, module in enumerate(args):
                self.add_module(str(idx), module)

    def forward(self, input):
        for module in self._modules.values():
            input = module(input)
        return input
  • add_module 会将子模块注册到 self._modules(一个 OrderedDict)中;
  • forward 只是按顺序遍历并调用这些模块
  • 因此,Sequential 本质上是一个**"自动写好 forward 的 Module"**。

3.2 Sequential 的两种常见定义方式

(1) 直接按顺序排列
python 复制代码
import torch.nn as nn

net = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Linear(256, 10),
)
print(net)
bash 复制代码
Sequential(
(0): Linear(in_features=784, out_features=256, bias=True)
(1): ReLU()
(2): Linear(in_features=256, out_features=10, bias=True)
)
(2) 使用 OrderedDict 显式命名
python 复制代码
import collections
import torch.nn as nn

net = nn.Sequential(collections.OrderedDict([
    ('fc1', nn.Linear(784, 256)),
    ('relu1', nn.ReLU()),
    ('fc2', nn.Linear(256, 10))
]))
print(net)
Sequential(
  (fc1): Linear(in_features=784, out_features=256, bias=True)
  (relu1): ReLU()
  (fc2): Linear(in_features=256, out_features=10, bias=True)
)
  • 为什么这里要使用 OrderedDict

    OrderedDict 是一种 显式保持插入顺序的字典结构 。 在 nn.Sequential 中,这一点非常重要。

    从内部实现可以看到,Sequential 的前向传播本质上是:

    python 复制代码
    for module in self._modules.values():
        x = module(x)

    ​ 也就是说,Sequential 并不依赖模块的名字来决定计算逻辑 ;它真正依赖的是, 模块在容器中的遍历顺序

    ​ 当使用 OrderedDict 时,我们同时获得了两点好处:

    • 前向执行顺序是确定且可依赖的
    • 每一层都有明确的语义名称,提升可读性和可维护性

​ 虽然在 Python 3.7+ 中,普通 dict 也保证插入顺序,但在 PyTorch 的设计中,凡是**"顺序本身具有语义"的地方**,都会显式使用 OrderedDict,以避免歧义。换句话说:使用 OrderedDict 并不是为了"让 Sequential 有顺序",而是为了 让顺序成为一种明确、可阅读、可依赖的设计约定

3.3 优缺点总结

  • 优点
    • 代码简洁、可读性高
    • 适合标准模块(Conv + BN + ReLU 等)
    • 无需手写 forward
  • 缺点
    • 灵活性受限
    • 无法方便地插入分支、跳连或外部输入
    • 不适合复杂拓扑结构(ResNet、UNet 等)

四、 ModuleList

对应容器:nn.ModuleList

nn.ModuleList 是一个 模块的列表容器 ,用于保存多个子模块,但不会自动定义前向传播逻辑。这也是它与 Sequential 的本质区别。

4.1 ModuleList 的核心定位

ModuleList 是"参数级别的容器",而不是"计算图级别的容器"。

  • 具体来说,它会正确注册子模块,使参数出现在 model.parameters() / state_dict() 中。但它不会决定模块之间的执行顺序,自动参与前向传播。

  • 它的主要作用是:

    • 注册参数

    • 方便重复结构的声明

    • 将控制权完全交给 forward

4.2 基本用法示例

python 复制代码
import torch.nn as nn

net = nn.ModuleList([
    nn.Linear(784, 256),
    nn.ReLU()
])
net.append(nn.Linear(256, 10))

print(net[-1])
print(net)
复制代码
Linear(in_features=256, out_features=10, bias=True)
ModuleList(
(0): Linear(in_features=784, out_features=256, bias=True)
(1): ReLU()
(2): Linear(in_features=256, out_features=10, bias=True)
)

​ 此时需要特别注意:ModuleList 中模块的排列顺序,本身并不等价于它们在网络中的计算顺序。

4.3 为什么 ModuleList 不自动 forward?

​ 这是很多人第一次用 ModuleList 时最容易困惑的地方。

​ 原因并不复杂,因为Sequential 的设计前提是:网络结构是严格线性的 ;而 ModuleList 的设计目标是:
允许前向传播逻辑是动态的、可编程的

  • 例如:
    • 跳跃连接(ResNet)
    • 多分支结构
    • 条件执行
    • 不同层之间的特征融合
  • 这些结构无法用"固定顺序"描述,因此PyTorch 干脆不替你定义 forward,而是把控制权完全交还给用户。

4.4 一个完整示例

python 复制代码
class MyModel(nn.Module):
    def __init__(self, input_size, hidden_sizes, output_size):
        super().__init__()

        layers = []
        layers.append(nn.Linear(input_size, hidden_sizes[0]))
        layers.append(nn.ReLU())

        for i in range(1, len(hidden_sizes)):
            layers.append(nn.Linear(hidden_sizes[i-1], hidden_sizes[i]))
            layers.append(nn.ReLU())

        layers.append(nn.Linear(hidden_sizes[-1], output_size))
        self.layers = nn.ModuleList(layers)

    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return x

​ 从这里可以清楚地看到,ModuleList 负责 "模块存在"forward 负责 "模块怎么连"

​ 因此,当"层的数量或连接方式"需要在 forward 中被显式控制时,ModuleList 几乎是唯一合理的选择。

五、 ModuleDict

对应模块:nn.ModuleDict

​ 如果说 ModuleList 解决的是 "同类模块的重复组织" ,那么 ModuleDict 解决的则是:"按语义而不是按顺序组织模块"

5.1 ModuleDict 的核心思想

nn.ModuleDict 是一个 以名称为键的模块容器

python 复制代码
import torch.nn as nn

net = nn.ModuleDict({
    'linear': nn.Linear(784, 256),
    'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10)

​ 从形式上看,它很像一个普通的 dict,但二者在语义上有着本质区别。

ModuleDict 相比普通 dict,额外承担了 "模块注册" 的职责:

  • 子模块会被正确注册为模型的一部分

  • 对应参数会自动出现在 state_dict

  • 能被 PyTorch 的训练、保存、加载、优化器等机制完整识别

    换句话说,ModuleDict 不是为了"存对象",而是为了"存模型结构"。

5.2 基于名称访问模块

​ 使用 ModuleDict 的一个直接好处是:可以通过语义明确的名称来访问网络中的子模块。

python 复制代码
print(net['linear']) # 访问
bash 复制代码
Linear(in_features=784, out_features=256, bias=True)

​ 这种访问方式在以下场景中特别有价值:

  • 网络中存在多个功能不同的分支

  • 模块的"角色"比"位置"更重要

  • forward 中需要根据条件选择不同子模块

5.3 ModuleDict 并不定义"顺序"

​ 这一点非常重要,也非常容易被误解。

python 复制代码
print(net)
python 复制代码
ModuleDict(
(linear): Linear(in_features=784, out_features=256, bias=True)
(act): ReLU()
(output): Linear(in_features=256, out_features=10, bias=True)
)

ModuleDict 中模块的"排列"不代表任何计算顺序语义 。真正的执行逻辑,依然必须在 forward 中明确指定。

  • Sequential 不同:

    • ModuleDict 不会自动决定前向传播的执行顺序
    • 模块之间的调用关系,必须在 forward 中显式给出
    python 复制代码
    def forward(self, x):
        x = self.layers['linear'](x)
        x = self.layers['act'](x)
        x = self.layers['output'](x)
        return x

5.4 为什么要用 ModuleDict,而不是 ModuleList?

​ 两者的核心差异不在"能不能存模块",而在建模视角

  • ModuleList: 关注 "第 0 层 / 第 1 层 / 第 2 层"

  • ModuleDict: 关注 "backbone / head / classifier / branch_A"

    当模块的语义角色本身就是模型设计的一部分 时, ModuleDict 会明显优于 ModuleList

六、结论:容器的选择,本质是 forward 控制权的选择

​ 在 PyTorch 中,模型结构的定义并不是通过"层的声明顺序"完成的, 而是通过 forward 中的数据流向 最终确定的。

SequentialModuleListModuleDict 的区别,并不在于"能不能装模块",而在于:谁来决定 forward 的执行逻辑?

  • nn.Sequential
    • forward 的控制权交给框架
    • 适合结构固定、线性可描述的网络模块
  • nn.ModuleList
    • 保留模块的注册与管理
    • forward 的控制权完全交还给用户
    • 适合层数可变、结构动态的模型
  • nn.ModuleDict
    • 进一步强调模块的语义角色
    • forward 不再依赖"位置",而依赖"名称"
    • 适合多分支、多功能组件组成的复杂模型

​ 从这个角度看,PyTorch 的设计并不是在提供三种"不同的写法",而是在刻意引导一种建模思想:模型结构 = 模块注册 + forward 中的显式连接。理解这一点,不仅能帮助我们写出更清晰的模型代码,也能在面对复杂网络结构时,做出更合理的设计选择。

​ 但是真正复杂的模型,从来不是"层堆出来的",而是 forward 里流动的数据决定的

相关推荐
owlion11 小时前
如何将视频文案整理成学习笔记
人工智能·python·机器学习·语言模型·自然语言处理
癫狂的兔子11 小时前
【Python】【NumPy】random.rand和random.uniform的异同点
开发语言·python·numpy
Lupino11 小时前
aio_periodic 重构与优化实战:构建高性能 Python 定时任务客户端
python·haskell
自然语11 小时前
人工智能之数字生命-特征类升级20260106
人工智能·算法
AC赳赳老秦11 小时前
前端可视化组件开发:DeepSeek辅助Vue/React图表组件编写实战
前端·vue.js·人工智能·react.js·信息可视化·数据分析·deepseek
先做个垃圾出来………11 小时前
Python整数存储与位运算
开发语言·python
RAY_010411 小时前
Python—面向对象
python
IT_陈寒11 小时前
React 18实战:这5个新特性让我的开发效率提升了40%
前端·人工智能·后端
zhengfei61111 小时前
AI渗透工具——AI驱动的BAS网络安全平台
人工智能·安全·web安全
imbackneverdie11 小时前
研究生如何高效完成文献综述并提炼创新点?
人工智能·ai·语言模型·自然语言处理·aigc·ai写作