网络能完成 forward,不代表每个层都进入了父模块的注册结构。准备把小网络改成可配置多层结构时,应同时检查计算图、父模块参数集合和优化器参数集合。
有梯度与由优化器管理是两回事
把层只放在普通 Python list 内,且没有另行注册时,父模块不会将这些层发现为子模块。将 nn.ModuleList 赋给父模块属性,会注册其中的模块,使它们能够被父模块的接口发现;ModuleList 提供列表式访问,但不会替整个网络自动定义 forward。ModuleList API
父模块 parameters() 默认递归遍历注册子模块,通常用来构造优化器。因此一个层即使参与前向计算、在反向时获得梯度,也可能没有进入该优化器的参数集合。保存的状态、父模块模式切换和浮点类型转换也要核对覆盖范围。Module 参数与状态接口
练习一 同权重网络的注册与更新对照
构造两个 CPU 小网络:隐藏层分别放入普通 list 与 ModuleList,两者都另有一个直接注册的输出层,避免普通 list 版本因完全没有注册参数而在创建优化器时提前失败。
将无偏置、单输入线性隐藏层和输出层的权重都设为 1;输入为 1,目标为 0,使用平方误差和非零学习率,执行一次普通 SGD 更新。两份网络的优化器都只由各自父模块的 parameters() 构造。
更新前后分别记录:
named_parameters()的名称与state_dict()的键。- 优化器参数对象是否包含隐藏层权重,按对象身份检查。
- 反向传播后隐藏层的梯度。
- 隐藏层权重更新前、更新后的值。
按上述条件推导,普通 list 中隐藏层虽然得到梯度,却不在优化器参数集合中,权重不变;ModuleList 对照组的隐藏层被发现并更新。需提交参数、梯度和数值记录,不能只用 loss 曲线判断。
练习二 检查模式与类型转换的递归覆盖
另建两份模型,在相应容器中放 Linear 与 Dropout,不执行优化步骤。
先调用父模块 eval(),直接检查各子层的 training 标志;再调用父模块 double(),检查 Linear 权重 dtype。注册版本应随父模块变化;没有另行注册的普通 list 子层不受父模块的递归操作覆盖。Module 模式与类型接口
使用属性断言,不以随机输出恰好一致为依据。这一项只验收注册覆盖,不重新解释 eval 与 no_grad 的差别。
不能省略的对照条件
普通 list 并非一定无法训练:显式注册其中的模块,或显式将其参数交给优化器,都会改变本实验的条件。ModuleList 也不意味着已创建的优化器会自动接纳随后新增的层;本练习在构造优化器前固定网络结构。
两项为未运行的验收设计,未测量训练效果。已有模型快照与重载检查保存是否独立,本节在更早阶段检查应保存的子模块是否已经被发现,两个问题需要分别确认。
来源与版本
PyTorch 2.14 ModuleList 和 Module,原核查为 2026-10-27 12:10–12:12 JST;本次于 2026-10-30 定向复核注册、递归参数、模式、类型与状态接口。独立发布日期未标明,实际项目依赖版本需另行核对。