一文讲清 nn.Sequential 等容器类

我们用生活化比喻 + 简单代码 + 场景对比 ,向初学者彻底讲清楚 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 ❌ 否 一组有名参数(如任务特定参数)

🧠 给初学者的黄金建议:

  1. 90% 情况用 Sequential 或普通 Module 属性就够了
  2. ✅ 当你需要 for 循环层 → 用 ModuleList
  3. ✅ 当你需要 按名字选模块 → 用 ModuleDict
  4. ✅ 自定义参数时,优先考虑是否能封装成 nn.Linear 等标准层
  5. ✅ 如果必须用自定义参数 → 用 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() 找不到参数,优化器不会更新它们!

相关推荐
阿里云云原生2 小时前
如何快速看懂「祖传项目」?Qoder 强势推出新利器
人工智能
美团技术团队2 小时前
LongCat-Flash:如何使用 SGLang 部署美团 Agentic 模型
人工智能·算法
程序员小袁4 小时前
基于C-MTEB/CMedQAv2-rerankingv的Qwen3-1.7b模型微调-demo
人工智能
飞哥数智坊5 小时前
AI 编程一年多,我终于明白:比技巧更重要的,是熟练度
人工智能·ai编程
新智元5 小时前
收手吧 GPT-5-Codex,外面全是 AI 编程智能体!
人工智能·openai
IT_陈寒5 小时前
Java 性能优化:5个被低估的JVM参数让你的应用吞吐量提升50%
前端·人工智能·后端
阿里云云原生6 小时前
阿里云基础设施 AI Tech Day AI 原生,智构未来——AI 原生架构与企业实践专场
人工智能
Memene摸鱼日报7 小时前
「Memene 摸鱼日报 2025.9.16」OpenAI 推出 GPT-5-Codex 编程模型,xAI 发布 Grok 4 Fast
人工智能·aigc
xiaohouzi1122337 小时前
OpenCV的cv2.VideoCapture如何加GStreamer后端
人工智能·opencv·计算机视觉