文章目录
- [1、torch.nn 和 nn.Module](#1、torch.nn 和 nn.Module)
- 2、summary
- [3、summary 使用](#3、summary 使用)
- 4、nn.Module.named_parameters
- [5、nn.Module.named_parameters() 和 nn.Module.parameters()](#5、nn.Module.named_parameters() 和 nn.Module.parameters())
- [6、通常不需要手动 初始化输出层 参数](#6、通常不需要手动 初始化输出层 参数)
- [7、构建自己的神经网络模型 - 代码](#7、构建自己的神经网络模型 - 代码)
1、torch.nn 和 nn.Module
一、torch.nn 是什么?
torch.nn(通常简写为 nn)是 PyTorch 中用于构建神经网络的核心模块。
- 它提供了:
- 网络层(Layers) :如
nn.Linear(全连接层)、nn.Conv2d(卷积层)、nn.BatchNorm1d(批归一化)等; - 激活函数 :虽然常用激活函数在
torch中(如torch.relu),但nn也提供类形式(如nn.ReLU()); - 损失函数 :如
nn.CrossEntropyLoss; - 初始化方法 :通过
nn.init子模块; - 容器类 :如
nn.Sequential,用于快速搭建网络。
- 网络层(Layers) :如
✅ 简单说:
nn就是 PyTorch 的"神经网络工具箱"。
二、nn.Module 是什么?
nn.Module 是 所有神经网络模块的基类(父类) 。
你写的每一个自定义模型(比如 ModelDemo),都必须继承自 nn.Module。
🔑 nn.Module 的核心作用:
-
自动管理参数(Parameters)
- 当你在
__init__中创建nn.Linear等层时,这些层内部的weight和bias会自动注册为模型的可学习参数。 - 调用
model.parameters()就能获取所有待优化的参数,供优化器(如Adam)使用。
- 当你在
-
支持前向传播(Forward Pass)
- 你只需实现
forward()方法,PyTorch 会自动处理反向传播(通过自动微分机制)。
- 你只需实现
-
支持 GPU 加速、保存/加载、嵌套结构等高级功能
- 比如调用
model.to('cuda')可将整个模型移到 GPU; - 模型可以像搭积木一样嵌套(一个
Module可包含多个子Module)。
- 比如调用
三、结合代码逐部分解析
python
class ModelDemo(nn.Module): # ← 继承 nn.Module,这是必须的!
✅ 这行表示:ModelDemo 是一个神经网络模型,具备 nn.Module 的所有能力。
__init__构造方法:搭建网络结构
python
def __init__(self):
super().__init__() # ← 必须调用父类初始化!否则 nn.Module 功能失效
# 或者
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # ← 必须调用父类初始化!否则 nn.Module 功能失效
📌
super().__init__()会初始化nn.Module内部的状态(如参数注册器、子模块容器等),绝对不能省略!
python
self.linear1 = nn.Linear(in_features=3, out_features=3)
self.linear2 = nn.Linear(in_features=3, out_features=2)
self.output = nn.Linear(in_features=2, out_features=2)
nn.Linear(a, b)表示一个全连接层 (也叫仿射变换):- 输入维度:
a - 输出维度:
b - 内部自动创建权重矩阵(形状
[b, a])和偏置向量(长度b)
- 输入维度:
💡 注意:
self.linear1被赋值为self的属性,这样 PyTorch 才能"看到"它,并把它当作子模块管理。
- 参数初始化
python
nn.init.xavier_normal_(tensor=self.linear1.weight)
nn.init.zeros_(tensor=self.linear1.bias)
...
- 这些是对刚创建的层的权重和偏置进行手动初始化。
- 因为
nn.Linear默认已有初始化(Kaiming),但老师想演示如何自定义。
✅ 初始化只在模型创建时执行一次,训练过程中权重会被优化器更新。
forward方法:定义数据流向
python
def forward(self, x):
x = torch.sigmoid(self.linear1(x))
x = torch.relu(self.linear2(x))
x = torch.softmax(self.output(x), dim=-1)
return x
forward是模型的计算逻辑 ,描述输入x如何一步步变成输出。- 每次调用
model(input)时,PyTorch 会自动调用forward()。 - 使用了三种激活函数:
sigmoid:常用于二分类输出或门控机制;relu:现代网络主流激活函数;softmax:多分类问题的输出层,将 logits 转为概率分布。
📌
dim=-1表示对最后一个维度(即每个样本的类别维度)做 softmax,确保每行概率和为 1。
四、如何使用这个模型?
python
# 1. 创建模型实例
model = ModelDemo()
# 2. 准备输入数据(假设 batch_size=4,每个样本3个特征)
x = torch.randn(4, 3)
# 3. 前向传播,得到预测
y_pred = model(x) # 自动调用 forward()
print(y_pred.shape) # → torch.Size([4, 2]),4个样本,2类概率
五、关键总结
| 概念 | 说明 |
|---|---|
nn |
PyTorch 神经网络模块库,提供层、损失、初始化等工具 |
nn.Module |
所有模型的基类,负责参数管理、设备迁移、嵌套等 |
super().__init__() |
必须调用,初始化父类状态 |
self.xxx = nn.Layer(...) |
将层作为属性保存,PyTorch 才能识别并管理它 |
forward() |
定义前向计算流程,方法名固定,不能改 |
model(input) |
实际调用的是 forward(input) |
💡 记住一句话 : "在
__init__里搭积木,在forward里走流程。"
2、summary
📘 PyTorch 模型结构可视化工具:summary(来自 torchsummary)
- 是什么?
summary 是 Python 第三方库 torchsummary 提供的一个函数,用于打印 PyTorch 神经网络的结构摘要(model summary) ,功能类似于 Keras 中的 model.summary()。
✅ 主要用途:快速查看模型每层的输入/输出形状、参数数量、总参数量等信息。
- 为什么需要它?
在 PyTorch 中,模型结构不像 Keras 那样自动显示。当你构建复杂网络时,容易出现:
- 维度不匹配
- 参数量估算错误
- 层连接逻辑不清
summary 能帮你:
- 🔍 调试网络结构
- 📊 检查张量形状传递是否正确
- 🧮 统计可训练参数总数
- 📝 教学或文档展示模型架构
- 安装方法
bash
pip install torchsummary
⚠️ 注意:不要与
torchinfo混淆。torchsummary轻量简单,适合全连接、基础 CNN;torchinfo功能更强,支持更复杂的模块。
- 基本用法
✅ 正确语法:
python
from torchsummary import summary
summary(model, input_size) # input_size 是元组类型, 不能直接是int
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
model |
nn.Module |
PyTorch 模型实例 |
input_size |
tuple |
单个样本的输入形状(不含 batch 维度) |
示例:
python
# 全连接网络:输入是 3 维特征向量
summary(my_model, input_size=(3,))
# CNN:输入是 (通道, 高, 宽)
summary(cnn_model, input_size=(3, 32, 32))
❗ 关键:
input_size不能包含 batch_size!
非常好的问题!你观察得很仔细 👏。
torchsummary.summary 的函数签名确实是:
python
def summary(model, input_size, batch_size=-1, device="cuda"):
✅ summary 四个参数详解
| 参数 | 默认值 | 作用 | 是否常用 | 说明 |
|---|---|---|---|---|
model |
--- | 要分析的 PyTorch 模型(nn.Module 实例) |
✅ 必填 | 核心参数 |
input_size |
--- | 单个样本的输入形状(不含 batch 维度) | ✅ 必填 | 如 全连接(3,) 如 CNN(3, 224, 224) |
batch_size |
-1 |
用于构建 dummy input 的 batch 大小 | ⚠️ 可选 | 默认 -1 表示自动推断或设为 2 |
device |
"cuda" |
模型和 dummy input 放在哪种设备上运行 | ⚠️ 可选 | 若无 GPU 需改为 "cpu" |
model和input_size:必填,核心
- 这两个是你必须提供的。
input_size永远不包含 batch 维度 ,这是torchsummary的设计约定。
input_size:
| 写法 | 含义 | 适用模型 |
|---|---|---|
(3,) |
每个样本是 3 维向量 | 全连接网络(MLP) |
(3, 224, 224) |
每个样本是 3 通道、224×224 的图像 | CNN(如 ResNet, VGG) |
(1, 28, 28) |
每个样本是 1 通道、28×28 的灰度图 | CNN(MNIST) |
(50, 128) |
每个样本是 50 个 token,每个 128 维 | RNN / Transformer |
✅ 正确示例:
python
summary(model, (3,)) # 全连接
summary(model, (3, 32, 32)) # CNN
batch_size:通常不用管,但有时需指定
默认行为:
- 当
batch_size=-1(默认),torchsummary会内部使用batch_size=2构造 dummy input。 - 为什么是 2?因为有些层(如 BatchNorm)在
batch_size=1时会报错,所以用 2 更安全。
什么时候需要手动设置?
- 你想看特定 batch size 下的输出形状 (虽然 Output Shape 中 batch 维度显示为
-1,但内部计算依赖实际 batch) - 调试与 batch 相关的层(如 BatchNorm、某些自定义层)
✅ 示例:
python
summary(model, (3,), batch_size=8) # 明确使用 batch_size=8
💡 但对全连接网络,
batch_size几乎不影响结构,可忽略。
device:非常重要!尤其在没有 GPU 时
默认值陷阱:
device="cuda"是默认值,如果你的机器没有 GPU 或 PyTorch 未启用 CUDA,会直接报错!
常见错误:
python
# 在 CPU 环境下运行
summary(model, (3,))
# 报错: RuntimeError: Attempting to deserialize object on a CUDA device ...
✅ 正确做法:
- 始终根据你的环境显式指定
device:
python
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
summary(model, (3,), device=device)
或者简单点:
python
summary(model, (3,), device="cpu") # 如果你确定用 CPU
🌟 强烈建议 :除非你明确使用 GPU,否则一律写
device="cpu",避免莫名其妙的 CUDA 错误。
🔍 实际源码逻辑简析(简化版)
python
def summary(model, input_size, batch_size=-1, device="cuda"):
if batch_size == -1:
batch_size = 2 # 安全起见用 2
# 构造 dummy input: shape = (batch_size, *input_size)
x = torch.rand((batch_size, *input_size), device=device)
# 将模型移到对应设备
model.to(device)
# 前向传播并记录每层输出
...
所以:
batch_size决定了 dummy input 的第一维device决定了模型和数据放在哪里运行
✅ 最佳实践总结
| 场景 | 推荐写法 |
|---|---|
| 普通 CPU 调试(最常见) | summary(model, (3,), device="cpu") |
| GPU 环境且模型已在 GPU | summary(model, (3,), device="cuda") |
| 不确定是否有 GPU |
python
device = "cuda" if torch.cuda.is_available() else "cpu"
summary(model, (3,), device=device)
其他参数不是"不重要",而是"有合理默认值,在基础使用中可省略"。但在实际项目中,尤其是跨设备调试时,`device` 参数至关重要!
# 推荐写法(安全、清晰)
summary(model=my_model, input_size=(3,), device="cpu")
- 输出解读
运行后会打印类似如下内容:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 64] 192
ReLU-2 [-1, 64] 0
Linear-3 [-1, 10] 650
================================================================
Total params: 842 # 总参数个数
Trainable params: 842 # 可训练参数
Non-trainable params: 0 # 不可训练参数
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
----------------------------------------------------------------
各字段含义:
| 字段 | 说明 |
|---|---|
| Layer (type) | 层的名称和类型(如 Linear, Conv2d) |
| Output Shape | 该层输出的张量形状,-1 表示 batch 维度(可变) |
| Param # | 该层的可训练参数数量(权重 + 偏置) |
| Total params | 模型总参数量 |
| Trainable params | 可训练参数数量(通常等于总参数) |
| MB 估算 | 输入、中间激活、参数占用的内存(粗略估计) |
- 常见错误与注意事项
❌ 错误1:input_size 包含 batch 维度
python
# 错误!
summary(model, input_size=(5, 3)) # 报错或解析错误
✅ 正确:
python
summary(model, input_size=(3,)) # 只写特征维度
❌ 错误2:未实现 forward 方法
- 如果模型没有正确实现
forward,summary无法追踪计算图,会报错。
❌ 错误3:使用了不支持的层(如自定义复杂模块)
torchsummary对标准层(Linear,Conv2d,ReLU等)支持良好,但对某些动态结构(如循环、条件分支)可能失效。
- 替代方案:
torchinfo(推荐进阶使用)
如果你需要更强大的功能(如支持 RNN、Transformer、自定义模块),推荐使用 torchinfo:
python
# 安装
pip install torchinfo
# 使用(注意:这里 input_size 可以带 batch)
from torchinfo import summary
summary(model, input_size=(16, 3)) # batch_size=16, features=3
优势:
- 支持更多层类型
- 可指定
batch_size - 输出更美观,支持 Colab/Jupyter
- 显示每层的 MACs(计算量)
- 总结对比表
| 特性 | torchsummary |
torchinfo |
|---|---|---|
| 安装简单 | ✅ 是 | ✅ 是 |
| 支持标准层 | ✅ 是 | ✅ 是 |
| 支持复杂结构 | ❌ 有限 | ✅ 强大 |
input_size 是否含 batch |
❌ 不含 | ✅ 可含 |
| 内存/MACs 估算 | ⚠️ 粗略 | ✅ 精细 |
| 推荐场景 | 初学者、全连接/CNN 基础模型 | 进阶用户、复杂模型 |
-
最佳实践建议
-
初学阶段 :用
torchsummary快速验证网络维度。 -
input_size永远只写单样本形状 ,如(3,)、(3, 224, 224)。 -
配合
print(model)使用:print(model):看层定义summary(model, ...):看数据流和参数
-
遇到报错先检查
forward是否正确实现。
🌟 一句话记住:
summary是 PyTorch 的"Keras-style model summary",让你一眼看清模型长什么样!
3、summary 使用
只要你调用:
python
summary(model=my_model, input_size=(3,)) # input_size 是元组类型, 不能直接是int
它就会自动打印出模型的结构摘要(summary)到控制台 ,不需要你手动 print,也不需要额外操作。
太好了!你已经掌握了 summary(model, input_size=(3,)) 的正确写法 👏。
现在我们来聚焦问题:
"我知道怎么设置了,但到底该怎么使用它?在什么场景下用?输出怎么看?有什么实际价值?"
✅ 一、什么时候用 summary?
| 场景 | 说明 |
|---|---|
| 🧪 刚写完模型结构 | 快速验证每层维度是否连得上(避免 forward 报错) |
| 🔍 调试维度错误 | 比如 mat1 and mat2 shapes cannot be multiplied 时,先看 summary 确认形状传递 |
| 📊 汇报/教学展示 | 向别人展示你的模型有多少层、多少参数 |
| 🧮 估算模型大小 | 查看 Total params,判断是否能在手机/嵌入式设备部署 |
| 📝 写论文或文档 | 把 summary 输出贴到附录里,说明模型复杂度 |
💡 最佳时机 :定义完
__init__和forward后,立刻调用一次summary!
✅ 二、怎么正确使用?(完整代码模板)
python
import torch
import torch.nn as nn
from torchsummary import summary
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.linear1 = nn.Linear(3, 64)
self.linear2 = nn.Linear(64, 10)
def forward(self, x):
x = torch.relu(self.linear1(x))
x = self.linear2(x)
return x
# === 使用 summary 的标准流程 ===
if __name__ == "__main__":
model = MyModel()
# 推荐:显式指定 device,避免 CUDA 错误
device = "cpu" # 或 "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
# 调用 summary:input_size 是单个样本的形状(不含 batch)
summary(model, input_size=(3,), device=device)
✅ 输出示例:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 64] 256
Linear-2 [-1, 10] 650
================================================================
Total params: 906
Trainable params: 906
Non-trainable params: 0
...
✅ 三、如何解读输出?(重点!)
以全连接网络为例:
python
nn.Linear(in_features=3, out_features=3) # 权重: 3×3=9, 偏置(每个输出对应一个偏置): 3 → 共12
nn.Linear(3, 2) # 权重: 2×3=6, 偏置: 2 → 共8
nn.Linear(2, 2) # 权重: 2×2=4, 偏置: 2 → 共6
summary 输出会显示:
| Layer | Output Shape | Param # |
|---|---|---|
| Linear-1 | [-1, 3] |
12 |
| Linear-2 | [-1, 2] |
8 |
| Linear-3 | [-1, 2] |
6 |
[-1, 3]:-1表示任意 batch size,3是该层输出特征数Param # = in_features × out_features + out_features(权重 + 偏置)
Linear-1 为什么参数是12个:
输入3个,输出3个,3 x 3 = 9,每个输出都有一个偏置,所以 9 + 3 = 12
✅ 你可以快速核对:
- 第一层输出是不是 3?→ 是
- 总参数是不是 12+8+6=26?→ 是
如果数字对不上,说明模型定义有误!
✅ 四、实际价值举例
例1:发现维度错误
你误写成:
python
self.linear2 = nn.Linear(4, 2) # 应该是 3→2,但写了 4→2
summary 会报错:
RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x3 and 4x2)
→ 你立刻知道:上一层输出是 3,但这一层期望输入是 4,不匹配!
例2:控制模型大小
你想部署到手机,要求参数 < 1000。
summary 显示 Total params: 906 → ✅ 符合要求。
例3:教学演示
学生问:"这个网络中间层输出是多少维?"
你直接展示 summary → 一目了然。
✅ 五、注意事项(避坑指南)
| 问题 | 解决方案 |
|---|---|
| 报 CUDA 错误 | 加 device="cpu" |
| 报维度不匹配 | 检查 input_size 是否和第一层 in_features 一致 |
| 自定义层不显示 | torchsummary 对非标准层支持有限,可改用 torchinfo |
| BatchNorm 报错 | 默认 batch_size=2 通常能避免,若仍报错可设 batch_size=8 |
✅ 六、一句话总结使用流程
定义模型 → 移到设备 → 调用
summary(model, input_size=(特征维度,), device="cpu")→ 核对输出形状和参数量
🌟 最终建议
把下面这行代码当作模型开发的"标配",每次写完模型就跑一次:
python
summary(model, input_size=(你的输入特征维度,), device="cpu")
它花不了 1 秒钟,却能帮你省下 10 分钟 debug 时间!
4、nn.Module.named_parameters
📘 PyTorch 模型参数遍历利器:named_parameters()
- 是什么?
named_parameters() 是 torch.nn.Module 类提供的一个生成器方法(generator) ,用于遍历模型中所有可学习(可训练)参数(parameters)及其名称。
✅ 返回形式:
(name: str, param: torch.Tensor)的迭代器✅ 仅包含
requires_grad=True的参数(即参与反向传播和优化的权重与偏置)
- 为什么需要它?
在深度学习中,我们经常需要:
- 查看模型有哪些参数
- 检查参数是否正确初始化
- 冻结某些层(迁移学习)
- 自定义参数更新逻辑
- 调试梯度是否流动
而 named_parameters() 正是实现这些操作的标准入口。
- 基本语法
python
for name, param in model.named_parameters():
print(f"参数名: {name}")
print(f"参数值:\n{param}")
print(f"形状: {param.shape}")
print(f"是否参与梯度计算: {param.requires_grad}\n")
返回内容说明:
| 变量 | 类型 | 含义 |
|---|---|---|
name |
str |
参数的层级命名路径(由模块结构自动生成) |
param |
torch.Tensor |
实际的参数张量(如权重矩阵、偏置向量) |
- 命名规则(
name从哪来?)
PyTorch 根据你在 __init__ 中定义的子模块变量名自动构建参数名:
示例模型:
python
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(4, 8) # 变量名:fc1
self.classifier = nn.Linear(8, 2) # 变量名:classifier
对应的 named_parameters() 输出:
fc1.weight
fc1.bias
classifier.weight
classifier.bias
🔍 规则:
<模块变量名>.weight或<模块变量名>.bias
嵌套模块示例:
python
self.backbone = nn.Sequential(
nn.Linear(10, 5),
nn.ReLU(),
nn.Linear(5, 2)
)
→ 参数名会变成:
backbone.0.weight
backbone.0.bias
backbone.2.weight
backbone.2.bias
(因为 Sequential 中第 0 层和第 2 层是 Linear)
- 完整代码示例
python
import torch
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.linear1 = nn.Linear(3, 4)
self.linear2 = nn.Linear(4, 2)
def forward(self, x):
return self.linear2(torch.relu(self.linear1(x)))
model = MyModel()
# 遍历所有可训练参数
for name, param in model.named_parameters():
print(f"name -> {name}")
print(f"shape -> {param.shape}")
print(f"param ->\n{param}\n")
输出片段:
name -> linear1.weight
shape -> torch.Size([4, 3])
param ->
tensor([[ 0.123, -0.456, 0.789],
[ ... ],
[ ... ],
[ ... ]], requires_grad=True)
name -> linear1.bias
shape -> torch.Size([4])
...
name -> linear2.weight
...
- 核心特点
| 特性 | 说明 |
|---|---|
| ✅ 只包含可训练参数 | 不包括 BatchNorm 的 running_mean、num_batches_tracked 等 buffer |
| ✅ 自动递归遍历所有子模块 | 无论多深的嵌套结构都能覆盖 |
| ✅ 保留参数梯度信息 | param.requires_grad 默认为 True |
| ❌ 不包含非 Parameter 的张量 | 如普通 nn.Parameter 以外的属性不会被包含 |
💡 如果你需要所有状态(包括 buffer) ,请使用
model.state_dict()。
- 典型应用场景
✅ 场景 1:调试参数初始化
python
# 检查权重是否真的被 Xavier 初始化了
for name, param in model.named_parameters():
if 'weight' in name:
print(f"{name} std: {param.std().item():.4f}")
✅ 场景 2:冻结部分层(迁移学习)
python
for name, param in model.named_parameters():
if 'linear1' in name:
param.requires_grad = False # 冻结第一层
✅ 场景 3:自定义参数初始化
python
for name, param in model.named_parameters():
if 'bias' in name:
nn.init.zeros_(param)
elif 'weight' in name and 'linear2' in name:
nn.init.kaiming_normal_(param)
✅ 场景 4:查看哪些参数会被优化器更新
python
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
print("优化器将更新以下参数:")
for name, _ in model.named_parameters():
if any(param is _ for param in optimizer.param_groups[0]['params']):
print(name)
✅ 场景 5:统计总参数量
python
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"总参数: {total_params}, 可训练: {trainable_params}")
- 与其他方法对比
| 方法 | 是否含名字 | 是否含 buffer | 是否可训练 | 用途 |
|---|---|---|---|---|
model.parameters() |
❌ | ❌ | ✅ | 传给优化器 |
model.named_parameters() |
✅ | ❌ | ✅ | 调试、操作特定参数 |
model.state_dict() |
✅(dict key) | ✅ | ✅ + ❌ | 保存/加载模型 |
model.named_modules() |
✅ | --- | --- | 遍历所有子模块 |
📌 记住:
- 用
parameters()给优化器- 用
named_parameters()调试和精细控制- 用
state_dict()保存模型
- 常见误区
❌ 误区 1:认为它包含所有模型内部张量
→ 错!只包含 nn.Parameter 类型的可训练参数。
❌ 误区 2:修改 param 就能改变模型行为
→ 要小心!直接赋值 param = new_tensor 无效 ,应使用 param.data = new_tensor 或 nn.init。
✅ 正确修改方式:
python
# ❌ 无效(只是局部变量重新绑定)
param = torch.zeros_like(param)
# ✅ 有效(原地修改张量数据)
param.data = torch.zeros_like(param)
# 或
nn.init.zeros_(param)
- 总结
named_parameters()是 PyTorch 中访问模型可训练参数的"黄金标准"。
- 🔹 返回
(name, param)对,名字反映模块结构 - 🔹 仅包含
weight、bias等可学习参数 - 🔹 广泛用于:初始化检查、层冻结、参数分析、自定义训练逻辑
- 🔹 与
parameters()、state_dict()互补,各司其职
🌟 一句话口诀:
"想看模型里哪些数字会变?
named_parameters()帮你全看见!"
5、nn.Module.named_parameters() 和 nn.Module.parameters()
🧠 核心区别:返回值结构
| 方法 | 返回类型 | 返回内容 | 用途 |
|---|---|---|---|
model.parameters() |
生成器(Generator) | 仅包含参数值(torch.nn.Parameter对象) |
简单获取所有参数 |
model.named_parameters() |
生成器(Generator) | 每个元素是(参数名, 参数值)元组 |
获取带名称的参数,用于精细控制 |
简单来说:
parameters():只给你参数"值",就像给你一堆书,但没有标签named_parameters():给你参数"值"和"标签",就像给你每本书都贴上了标签
🔍 详细对比分析
- 返回值的结构
parameters() 返回什么:
python
for param in model.parameters():
print(param.shape) # 仅显示参数形状,没有名称
输出示例:
torch.Size([64, 128])
torch.Size([64])
torch.Size([10, 64])
torch.Size([10])
named_parameters() 返回什么:
python
for name, param in model.named_parameters():
print(f"{name}: {param.shape}")
输出示例:
fc1.weight: torch.Size([64, 128])
fc1.bias: torch.Size([64])
fc2.weight: torch.Size([10, 64])
fc2.bias: torch.Size([10])
- 深入理解:PyTorch内部机制
在PyTorch中,nn.Module类内部维护了几个关键数据结构:
_parameters: 一个字典,保存模型中所有参数,键是参数名,值是参数_modules: 保存所有子模块_buffers: 保存模型中的缓冲区(如BatchNorm中的running_mean)
当你调用model.parameters()时,它会遍历_parameters字典并返回所有参数值。
当你调用model.named_parameters()时,它会遍历_parameters字典并返回(name, param)元组。
- 为什么需要两个方法?
parameters():当不需要知道参数名称时,可以快速获取所有参数。例如,如果你只是想统计参数数量:
python
# 统计模型总参数数量
total_params = sum(p.numel() for p in model.parameters())
print(f"总参数数量: {total_params}")
named_parameters():当需要基于参数名称进行操作时,比如为不同层设置不同学习率、冻结特定层等。这是深度学习中非常常见的需求。
🛠️ 实际应用场景详解
- 为不同层设置不同学习率(分层优化)
这是named_parameters()最常用的应用场景之一,特别适合在迁移学习和复杂模型中:
python
# 为不同层设置不同学习率
optimizer = torch.optim.Adam([
{"params": [p for n, p in model.named_parameters() if "conv" in n], "lr": 0.0001},
{"params": [p for n, p in model.named_parameters() if "fc" in n], "lr": 0.001}
])
为什么这很重要?
- 卷积层(conv)通常学习率设置得低一些,因为它们是特征提取层,需要稳定
- 全连接层(fc)可以学习得快一些,因为它们是分类层,需要更快适应新任务
- 冻结特定层(迁移学习)
在迁移学习中,我们经常想冻结预训练模型的底层,只训练顶层:
python
# 冻结卷积层,只训练全连接层
for name, param in model.named_parameters():
if "conv" in name: # 假设conv是卷积层
param.requires_grad = False
为什么这很重要?
- 预训练模型的底层已经学习了通用特征(如边缘、纹理)
- 我们不需要重新学习这些特征,只需微调顶层以适应新任务
- 可以大大减少计算量和训练时间
- 参数检查和调试
在训练过程中,检查是否有参数未更新,这有助于排查梯度消失/爆炸问题:
python
# 检查未更新的参数
unupdated = []
for name, param in model.named_parameters():
if param.grad is None:
unupdated.append(name)
print(f"未更新参数: {unupdated}")
为什么这很重要?
- 如果某些参数的梯度是None,说明它们没有被更新
- 可能是学习率太低、参数被冻结或模型结构有问题
- 有助于快速定位和解决问题
- 参数可视化和分析
在模型分析中,我们经常需要知道特定层的参数分布:
python
# 打印特定层的参数
for name, param in model.named_parameters():
if "fc1" in name:
print(f"fc1参数: {param.data}")
print(f"fc1梯度: {param.grad}")
为什么这很重要?
- 了解模型内部参数的变化
- 分析模型学习过程
- 为模型优化提供依据
🧪 实际案例:ResNet模型中的应用
以ResNet18为例,看看named_parameters()如何工作:
python
import torchvision.models as models
model = models.resnet18()
# 打印部分参数名
for name, param in model.named_parameters():
print(f"参数名: {name}")
if "layer1" in name:
print(" 层1的参数...")
if "layer4" in name:
print(" 层4的参数...")
if "fc" in name:
print(" 全连接层参数...")
if "bn" in name:
print(" 批归一化层参数...")
if "weight" in name:
print(" 权重参数...")
print("-" * 50)
输出片段:
参数名: conv1.weight
权重参数...
--------------------------------------------------
参数名: conv1.bias
权重参数...
--------------------------------------------------
参数名: bn1.weight
批归一化层参数...
--------------------------------------------------
参数名: bn1.bias
批归一化层参数...
--------------------------------------------------
参数名: layer1.0.conv1.weight
层1的参数...
权重参数...
--------------------------------------------------
参数名: layer1.0.conv1.bias
层1的参数...
权重参数...
--------------------------------------------------
...
参数名: fc.weight
全连接层参数...
权重参数...
--------------------------------------------------
⚠️ 常见错误与注意事项
- 混淆参数名和参数值
python
# 错误示例:试图从parameters()中获取名称
for name, param in model.parameters():
print(name, param) # 这会报错,因为parameters()不返回名称
# 正确做法:使用named_parameters()
for name, param in model.named_parameters():
print(name, param)
- 忘记使用
list()转换生成器
python
# 错误:直接打印生成器
print(model.parameters()) # 输出:<generator object Module.parameters at 0x7f8c0c2b0d60>
# 正确:转换为列表
print(list(model.parameters())) # 显示所有参数
- 冻结参数后忘记重置优化器
python
# 冻结参数
for name, param in model.named_parameters():
if "conv" in name:
param.requires_grad = False
# 重要:需要在训练前重置优化器
optimizer.zero_grad() # 重置梯度
- 误以为
named_parameters()返回所有参数
named_parameters()只返回可训练参数(requires_grad=True的参数),不包括冻结的参数。
🔗 与其他相关方法的对比
| 方法 | 返回内容 | 用途 | 与named_parameters()/parameters()的关系 |
|---|---|---|---|
state_dict() |
字典,包含模型所有参数和缓冲区 | 保存和加载模型 | 与参数方法不同,返回的是字典 |
modules() |
模型中所有模块的生成器 | 遍历所有模块 | 不返回参数,返回模块 |
named_modules() |
(name, module)元组的生成器 |
遍历所有模块,带名称 | 不返回参数,返回模块 |
children() |
模型直接子模块的生成器 | 遍历直接子模块 | 不返回参数,返回子模块 |
named_children() |
(name, module)元组的生成器 |
遍历直接子模块,带名称 | 不返回参数,返回子模块 |
💡 为什么PyTorch要设计这两个方法?
想象一下,你有一个巨大的书架(模型),里面有很多书(参数):
parameters():就像你把所有书都拿了出来,但没有标签,你得一个一个翻才能知道哪本是《深度学习入门》named_parameters():就像你给每本书都贴了标签,你可以直接说"我要《深度学习入门》",立刻找到
在深度学习中,模型通常由多个层组成,每个层都有自己的参数。在训练过程中,我们可能需要:
- 为不同层设置不同的学习率
- 冻结某些层(如预训练模型的底层)
- 检查特定层的梯度是否正常
没有named_parameters(),我们就无法区分这些参数,也就无法进行这些精细控制。
🌟 实用技巧
- 为不同层设置学习率的高级用法
python
# 为不同层设置不同学习率,同时指定权重衰减
optimizer = torch.optim.Adam([
{"params": [p for n, p in model.named_parameters() if "conv" in n], "lr": 0.0001, "weight_decay": 1e-5},
{"params": [p for n, p in model.named_parameters() if "fc" in n], "lr": 0.001, "weight_decay": 1e-4}
])
- 冻结特定层并保留梯度
python
# 冻结特定层,但保留梯度用于其他操作
for name, param in model.named_parameters():
if "conv" in name:
param.requires_grad = False
# 但可以为这些参数保留梯度,以便在需要时重新启用
# 例如,用于梯度裁剪或检查
- 打印模型参数的统计信息
python
# 打印模型参数的统计信息
for name, param in model.named_parameters():
if param.requires_grad:
print(f"{name}:")
print(f" 形状: {param.shape}")
print(f" 均值: {param.data.mean().item()}")
print(f" 标准差: {param.data.std().item()}")
print(f" 最小值: {param.data.min().item()}")
print(f" 最大值: {param.data.max().item()}")
print("-" * 50)
📚 为什么这个知识点如此重要?
作为深度学习实践者,你经常会遇到以下场景:
-
迁移学习 :你需要冻结预训练模型的底层,只训练顶层。没有
named_parameters(),这几乎不可能实现。 -
模型调试 :当模型不收敛时,你需要检查哪些参数没有更新。
named_parameters()是定位问题的关键。 -
模型优化:为不同层设置不同学习率可以显著提高训练效率和模型性能。
-
模型压缩 :在模型压缩中,我们需要对特定层进行特殊处理,
named_parameters()提供了必要的接口。
💬 最后总结
model.parameters():简单获取所有参数,适合需要快速获取参数的场景。model.named_parameters():获取带名称的参数,适合需要基于参数名称进行精细控制的场景。
核心区别 :named_parameters()提供参数名称,让你能区分不同层的参数,而parameters()只提供参数值,无法区分。
在实际应用中,named_parameters()比parameters()更常用,特别是在需要对不同层进行不同处理时。如果你只做简单的模型训练,可能用不到named_parameters();但如果你要进行迁移学习、模型微调或深度调试,named_parameters()几乎是必备的工具。
6、通常不需要手动 初始化输出层 参数
为什么前两层(
linear1,linear2)都手动初始化了权重和偏置,而输出层self.output = nn.Linear(2, 2)却没有初始化?是不是"不需要设置权重"?
答案是:
❌ 不是"不需要",而是"你没写,但它其实已经被自动初始化了" 。
✅ PyTorch 的
nn.Linear在创建时会自动进行默认初始化。
下面我来为你彻底讲清楚。
✅ 一、nn.Linear 默认会初始化!
当你写:
python
self.output = nn.Linear(in_features=2, out_features=2)
PyTorch 内部会自动调用默认的初始化方法:
- 权重(weight) :从均匀分布 \\mathcal{U}(-\\sqrt{k}, \\sqrt{k}) 中采样
其中 k = 1 / \\text{in_features} - 偏置(bias) :初始化为全 0(如果
bias=True,默认就是)
这是 PyTorch 的标准行为,源码中定义在 Linear.reset_parameters() 方法里。
所以 output 层并不是"没初始化" ,而是用了 PyTorch 默认初始化。
🔍 二、对比:你手动初始化 vs 默认初始化
| 层 | 初始化方式 | 权重分布 | 偏置 |
|---|---|---|---|
linear1 |
手动:xavier_normal_ |
正态分布,适合 Sigmoid/Tanh | 全 0 |
linear2 |
手动:kaiming_normal_(ReLU) |
正态分布,适合 ReLU | 全 0 |
output |
默认初始化 | 均匀分布(范围小) | 全 0 |
✅ 所以 output 层是有初始值的,只是你没显式打印出来。
🧪 三、验证:打印 output 层的参数
在代码末尾加上:
python
print("输出层权重:\n", my_model.output.weight)
print("输出层偏置:\n", my_model.output.bias)
你会看到类似:
输出层权重:
Parameter containing:
tensor([[ 0.4231, -0.5123],
[-0.3312, 0.6789]], requires_grad=True)
输出层偏置:
Parameter containing:
tensor([0., 0.], requires_grad=True)
→ 权重不是零,也不是 NaN,说明已经初始化了!
❓ 四、那要不要手动初始化输出层?
这取决于你的任务和激活函数!
情况 1:输出层后接 softmax(多分类)
- 通常不需要特殊初始化
- 默认初始化已经足够好
- 因为 softmax 对输入尺度不敏感(会归一化)
✅ 所以代码是合理的:可以不手动初始化输出层
情况 2:输出层后接其他激活(如 sigmoid 用于二分类)
- 有时为了避免初始输出集中在 0 或 1,会微调偏置(比如设为 small negative value)
- 但一般也不需要改权重
情况 3:你追求极致控制 or 复现实验
- 可以统一初始化所有层,保持一致性
例如:
python
# 统一初始化所有 Linear 层
for layer in [self.linear1, self.linear2, self.output]:
nn.init.xavier_normal_(layer.weight)
nn.init.zeros_(layer.bias)
⚠️ 五、常见误区
❌ 误区:"不初始化 = 权重为 0"
→ 错!不手动初始化 ≠ 权重为 0,而是用 PyTorch 默认初始化(非零、有随机性)。
❌ 误区:"输出层不用管,随便初始化"
→ 虽然默认初始化通常够用,但在某些任务中(如不平衡分类),调整输出层偏置能加速收敛。
✅ 六、最佳实践建议
| 场景 | 建议 |
|---|---|
| 教学/简单实验 | 可以只初始化隐藏层,输出层用默认(如你当前代码)✅ |
| 复现论文/严格控制实验 | 所有层统一初始化(包括输出层) |
| 多分类任务(softmax) | 输出层通常无需特殊处理 |
| 二分类(sigmoid)且正样本极少 | 可设 output.bias = torch.tensor([-2.0]) 避免初始预测全为 1 |
🌟 总结
self.output = nn.Linear(2, 2)是会被自动初始化的!你没写初始化代码 ≠ 没初始化,而是用了 PyTorch 的默认初始化策略。
- ✅ 默认初始化对输出层通常是合适的
- ✅ 代码没有问题,可以正常训练
- ✅ 如果你想更严谨,也可以手动初始化输出层(非必须)
7、构建自己的神经网络模型 - 代码
python
import torch
import torch.nn as nn
from torchsummary import summary
class MyModel(nn.Module):
# 定义 __init__ 方法, 构建神经网络
def __init__(self, *args, **kwargs):
# 必须调用父类的 __init__ 方法
super().__init__(*args, **kwargs)
# 创建隐藏层1
self.linear1 = nn.Linear(in_features=3, out_features=3) # 3个输入, 3个输出, 权重矩阵是 (输出, 输入), 即 [out_features, in_features]
# 隐藏层1参数初始化
nn.init.xavier_normal_(tensor=self.linear1.weight) # 正态化的Xavier初始化
nn.init.zeros_(tensor=self.linear1.bias)
print(f'隐藏层1的权重: \n{self.linear1.weight}')
print(f'隐藏层1的偏置: \n{self.linear1.bias}')
# 隐藏层1的权重:(每次运行结果都不一样)
# Parameter containing:
# tensor([[ 0.5180, 0.0963, 0.1452],
# [-0.1293, 0.2209, 0.3645],
# [-1.1118, 0.3499, -0.6002]], requires_grad=True)
# 隐藏层1的偏置:
# Parameter containing:
# tensor([0., 0., 0.], requires_grad=True)
# 创建隐藏层2
# 全连接神经网络: 第N层的每个神经元和第N-1层的所有神经元相连(这就是Fully Connected的含义)
self.linear2 = nn.Linear(in_features=3, out_features=2) # 3个输入(需要和上一层的输出相同), 权重矩阵 (2, 3)
# 隐藏层2参数初始化 【 nonlinearity n.非线性 】
nn.init.kaiming_normal_(tensor=self.linear2.weight, nonlinearity='relu') # 正态分布的he(Kaiming He et al. 2015)初始化
nn.init.zeros_(tensor=self.linear2.bias)
print(f'隐藏层2的权重: \n{self.linear2.weight}')
print(f'隐藏层2的偏置: \n{self.linear2.bias}')
# 隐藏层2的权重:
# Parameter containing:
# tensor([[-0.3379, -0.4480, -0.9356],
# [ 0.1274, 0.7152, -0.4393]], requires_grad=True)
# 隐藏层2的偏置:
# Parameter containing:
# tensor([0., 0.], requires_grad=True)
# 创建输出层(使用默认初始化即可)
self.output = nn.Linear(in_features=2, out_features=2)
# 重写 forward, 定义数据流向: 定义前向传播方法, 得到预测值 y
def forward(self, x): # x: 输入样本
z = self.linear1(x) # 第1层计算: 加权求和, z 表示线性输出(加权和)
x = torch.sigmoid(input=z) # 第1层计算: 使用激活函数, 得到非线性
z = self.linear2(x) # 第2层计算: 加权求和
x = torch.relu(input=z) # 第2层计算: 使用激活函数, 得到非线性
z = self.output(x) # 第3层计算: 加权求和
y_predict = torch.softmax(z, dim=1) # 第3层计算: 输出层计算, 假设为多分类问题,
return y_predict
if __name__ == '__main__':
my_model = MyModel() # 创建神经网络对象
print(f'神经网络对象: \n{my_model}')
# MyModel(
# (linear1): Linear(in_features=3, out_features=3, bias=True)
# (linear2): Linear(in_features=3, out_features=2, bias=True)
# (output): Linear(in_features=2, out_features=2, bias=True)
# )
# 生成随机样本: 第1层的输入是3, 所以必须是3列
data = torch.randn(size=(5, 3)) # 从 标准正态分布 N(0,1) (均值为0,方差为1)中生成张量。
print(f'data.shape = {data.shape}')
print(f'data.requires_grad = {data.requires_grad}')
print(f'data: \n{data}')
# data.shape = torch.Size([5, 3])
# data.requires_grad = False
# data:
# tensor([[ 0.6058, -0.8497, -1.7114],
# [-0.0886, 1.7651, 0.4772],
# [ 1.2658, 0.9205, -1.2388],
# [ 1.9676, -0.1317, 1.5285],
# [-0.3718, 0.8589, -1.4751]])
# 调用神经网络模型对象进行模型训练、预测
y_predict = my_model(data) # 自动调用 forward 函数(因为实现了 __call__ 函数)
print(f'y_predict: \n{y_predict}')
# tensor([[0.7149, 0.2851],
# [0.7452, 0.2548],
# [0.7055, 0.2945],
# [0.7025, 0.2975],
# [0.7185, 0.2815]], grad_fn=<SoftmaxBackward0>)
print('*' * 22)
# input_size: 单个样本的输入形状(不含 batch 维度)
# 调用 summary 会自动打印, 不需要额外的 print
summary(model=my_model, input_size=(3, ))
# ----------------------------------------------------------------
# Layer (type) Output Shape Param #
# ================================================================
# Linear-1 [-1, 3] 12
# Linear-2 [-1, 2] 8
# Linear-3 [-1, 2] 6
# ================================================================
# Total params: 26
# Trainable params: 26
# Non-trainable params: 0
# ----------------------------------------------------------------
# Input size (MB): 0.00
# Forward/backward pass size (MB): 0.00
# Params size (MB): 0.00
# Estimated Total Size (MB): 0.00
# ----------------------------------------------------------------
print('*' * 22)
# 查看模型参数
for name, param in my_model.named_parameters():
print('name->', name)
print('param->', param)