我们用生活化比喻 + 简单代码 + 场景对比 ,向初学者彻底讲清楚 PyTorch 中 torch.nn 的这几个"容器类":
✅ Sequential
✅ ModuleList
✅ ModuleDict
✅ ParameterList
✅ ParameterDict
🎯 一句话总览:
这些都是 PyTorch 提供的"收纳盒",帮你组织神经网络中的层或参数,让代码更整洁、更灵活、更容易训练!
一、基础概念回顾
在 PyTorch 中:
- 所有网络层(如 Linear,Conv2d,ReLU)都是nn.Module的子类。
- 所有可学习参数(如权重、偏置)都是 nn.Parameter(本质是带requires_grad=True的 Tensor)。
- 如果你想把多个层或参数"打包"在一起,就要用这些"收纳盒"。
📦 1. nn.Sequential ------ "流水线式收纳盒"
🧩 比喻:
像组装流水线:输入数据从第一个模块进去,依次经过每个模块,最后输出。顺序固定,不能跳过。
✅ 适用场景:
- 简单前馈网络(如全连接、简单CNN)
- 模块顺序执行,无需条件分支或跳转
🧑💻 使用方法:
            
            
              python
              
              
            
          
          import torch
import torch.nn as nn
model = nn.Sequential(
    nn.Linear(784, 128),
    nn.ReLU(),
    nn.Linear(128, 64),
    nn.ReLU(),
    nn.Linear(64, 10),
    nn.Softmax(dim=1)
)
x = torch.randn(1, 784)
output = model(x)  # 自动依次执行每个层
print(output.shape)  # torch.Size([1, 10])⚠️ 注意:
- 不能写条件判断、循环、跳转
- 不能访问中间层输出(除非拆开写或用 hook)
- 适合"直筒型"网络
📦 2. nn.ModuleList ------ "可编程的模块列表"
🧩 比喻:
像工具箱里的扳手组:你可以按编号取出第几个扳手,也可以循环遍历、动态增减。
✅ 适用场景:
- 需要 for 循环遍历层(如多层 LSTM、ResNet 的多个残差块)
- 层数动态决定(比如根据配置文件创建 N 个层)
- 需要按索引访问特定层
🧑💻 使用方法:
            
            
              python
              
              
            
          
          class MyModel(nn.Module):
    def __init__(self, num_layers):
        super().__init__()
        self.layers = nn.ModuleList([
            nn.Linear(100, 100) for _ in range(num_layers)
        ])
        self.relu = nn.ReLU()
    def forward(self, x):
        for layer in self.layers:  # ✅ 可以用 for 循环!
            x = self.relu(layer(x))
        return x
model = MyModel(num_layers=5)
x = torch.randn(2, 100)
output = model(x)
print(output.shape)  # torch.Size([2, 100])⚠️ 重要:
- ❌ 不能直接调用 ModuleList(input)------ 它不是函数!
- ✅ 必须在 forward里手动遍历或索引调用
- ✅ 支持 .append(),.extend(),len(), 索引访问等
📦 3. nn.ModuleDict ------ "带名字的模块字典"
🧩 比喻:
像工具墙上的标签挂钩:每个工具(模块)都有名字,你可以按名字取用。
✅ 适用场景:
- 多个不同功能的模块,需要用名字区分(如不同任务的 head)
- 动态选择模块(如根据输入选择不同分支)
- 配置化模型结构(如 config['encoder'] = 'resnet')
🧑💻 使用方法:
            
            
              python
              
              
            
          
          class MultiTaskModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoders = nn.ModuleDict({
            'image': nn.Conv2d(3, 16, 3),
            'text': nn.Linear(100, 50),
            'audio': nn.LSTM(80, 64, batch_first=True)
        })
        self.classifier = nn.Linear(50, 10)
    def forward(self, x, task_type):
        if task_type == 'image':
            x = self.encoders['image'](x).mean(dim=[2,3])
        elif task_type == 'text':
            x = self.encoders['text'](x)
        elif task_type == 'audio':
            x, _ = self.encoders['audio'](x)
            x = x.mean(dim=1)
        return self.classifier(x)
model = MultiTaskModel()
x_img = torch.randn(1, 3, 32, 32)
output = model(x_img, 'image')
print(output.shape)  # torch.Size([1, 10])⚠️ 注意:
- ✅ 支持字典操作:keys(),values(),items(),['key'],.get()
- ❌ 不能直接调用 ModuleDict(input)
📦 4. nn.ParameterList ------ "参数列表收纳盒"
🧩 比喻:
像一排可调电阻:每个都是独立可学习参数,你可以编号访问、循环调整。
✅ 适用场景:
- 一组自定义可学习参数,数量动态或较多
- 不想写 self.param1, self.param2, ...
- 需要注册到模型中,让 optimizer能更新它们
🧑💻 使用方法:
            
            
              python
              
              
            
          
          class LearnableBiases(nn.Module):
    def __init__(self, num_biases):
        super().__init__()
        # 创建多个可学习偏置参数
        self.biases = nn.ParameterList([
            nn.Parameter(torch.randn(1)) for _ in range(num_biases)
        ])
    def forward(self, x):
        for bias in self.biases:
            x = x + bias  # 每个样本加上所有偏置
        return x
model = LearnableBiases(3)
x = torch.tensor([1.0, 2.0, 3.0])
output = model(x)
print(output)        # tensor([4.xxx, 5.xxx, 6.xxx])
print(model.biases)  # 包含3个 Parameter⚠️ 注意:
- 里面的元素必须是 nn.Parameter
- 会自动被 model.parameters()收录 → 优化器能更新
- 支持 .append(), 索引等
📦 5. nn.ParameterDict ------ "带名字的参数字典"
🧩 比喻:
像控制面板上的旋钮组:每个旋钮都有名字(如"亮度"、"音量"),按名字调节。
✅ 适用场景:
- 多个有语义名称的可学习参数
- 根据配置动态创建参数
- 需要按名字访问/更新参数
🧑💻 使用方法:
            
            
              python
              
              
            
          
          class TaskSpecificParams(nn.Module):
    def __init__(self):
        super().__init__()
        self.task_params = nn.ParameterDict({
            'task_a_weight': nn.Parameter(torch.ones(10)),
            'task_b_bias': nn.Parameter(torch.zeros(1)),
            'global_scale': nn.Parameter(torch.tensor(1.0))
        })
    def forward(self, x, task_name):
        if task_name == 'task_a':
            return x * self.task_params['task_a_weight']
        elif task_name == 'task_b':
            return x + self.task_params['task_b_bias']
        else:
            return x * self.task_params['global_scale']
model = TaskSpecificParams()
x = torch.randn(5, 10)
output = model(x, 'task_a')
print(output.shape)  # torch.Size([5, 10])⚠️ 注意:
- 会自动注册参数 → model.parameters()包含它们
- 支持字典操作
🆚 对比总结表:
| 容器 | 存什么 | 是否可调用 | 是否支持索引/循环 | 是否支持命名访问 | 典型场景 | 
|---|---|---|---|---|---|
| Sequential | Module | ✅ 是 | ✅ | ❌ | 简单直筒网络 | 
| ModuleList | Module | ❌ 否 | ✅ | ❌ | 循环层、动态层数 | 
| ModuleDict | Module | ❌ 否 | ✅ | ✅ | 多分支、按名选模块 | 
| ParameterList | Parameter | ❌ 否 | ✅ | ❌ | 一组无名参数 | 
| ParameterDict | Parameter | ❌ 否 | ✅ | ✅ | 一组有名参数(如任务特定参数) | 
🧠 给初学者的黄金建议:
- ✅ 90% 情况用 Sequential或普通Module属性就够了
- ✅ 当你需要 for 循环层 → 用 ModuleList
- ✅ 当你需要 按名字选模块 → 用 ModuleDict
- ✅ 自定义参数时,优先考虑是否能封装成 nn.Linear等标准层
- ✅ 如果必须用自定义参数 → 用 ParameterList/Dict,别忘了它们是nn.Parameter!
🎁 小测验(巩固理解):
下面哪个写法是错误的?
            
            
              python
              
              
            
          
          # A
model = nn.Sequential(nn.Linear(10,5), nn.ReLU())
out = model(x)
# B
layers = [nn.Linear(10,5), nn.ReLU()]  # ❌ 普通 list,不会被注册!
out = x
for layer in layers:
    out = layer(out)
# C
layers = nn.ModuleList([nn.Linear(10,5), nn.ReLU()])
out = x
for layer in layers:  # ✅ 正确!
    out = layer(out)👉 答案:B 是错的!
因为普通 Python list 里的 Module 不会被 PyTorch 识别为子模块 → model.parameters() 找不到参数,优化器不会更新它们!