一、前言

在上一节“模型构造”中,我们已经知道了如何用 PyTorch 定义一个神经网络,比如:

  • nn.Sequential 搭建顺序模型

  • 继承 nn.Module 自定义网络

  • __init__() 中定义层

  • forward() 中定义前向传播过程

但当我们真正开始训练模型时,很快就会遇到一个更具体的问题:

模型里的参数到底存在哪里?
我们该怎么查看、访问、初始化、共享和修改这些参数?

这就是“参数管理”要解决的核心问题。

因为神经网络训练的本质,说到底就是:

不断更新模型参数,使模型输出越来越接近真实目标。

所以如果不会管理参数,那么:

  • 你看不懂模型内部到底学了什么

  • 你不会初始化参数

  • 你不会冻结某些层

  • 你不会做参数共享

  • 你也很难真正理解训练过程

因此,这一节虽然表面看起来像是在学 PyTorch 的接口,但实际上它非常关键。
它会帮助我们真正走近神经网络内部。


二、什么是参数管理

所谓参数管理,本质上就是:

对模型中的可学习参数进行查看、访问、修改和组织。

在神经网络中,最常见的参数包括:

  • 权重 weight

  • 偏置 bias

例如一个全连接层:

这里的:

  • (W) 就是权重参数

  • (b) 就是偏置参数

训练时优化器不断更新的,就是这些东西。

所以参数管理关注的是:

  • 怎么拿到这些参数

  • 怎么看它们的形状

  • 怎么单独访问某一层的参数

  • 怎么对参数做初始化

  • 怎么让不同层共享同一组参数


三、为什么参数管理很重要

1. 训练本质上是在更新参数

你如果不知道参数在哪里、长什么样,就等于不知道模型真正学的是什么。

2. 很多高级操作都依赖参数管理

例如:

  • 自定义初始化

  • 冻结某些层

  • 迁移学习

  • 参数共享

  • 模型保存与加载

这些都离不开参数管理。

3. 它能帮助你理解模型内部结构

一个模型表面看是很多层,往里看其实就是很多参数张量。
学会参数管理之后,你对网络的理解会更“落地”。


四、先看一个简单模型

下面先定义一个最基础的多层感知机:

import torch
from torch import nn

net = nn.Sequential(
    nn.Linear(4, 8),
    nn.ReLU(),
    nn.Linear(8, 1)
)

print(net)

这个模型里有两层线性层:

  • 第一层:Linear(4, 8)

  • 第二层:Linear(8, 1)

而真正有参数的是这两层 Linear,中间的 ReLU 没有可学习参数。


五、如何查看某一层的参数

例如我们想查看第二个线性层,也就是:

net[2]

可以直接打印它的参数:

print(net[2].state_dict())

输出通常会类似于:

OrderedDict([
    ('weight', tensor(...)),
    ('bias', tensor(...))
])

这说明这个层中包含:

  • weight

  • bias

也就是说,state_dict() 可以把该层当前保存的参数状态展示出来。


六、state_dict() 到底是什么

state_dict() 是 PyTorch 里非常重要的一个方法。

它的作用可以理解为:

返回模型当前所有参数和缓冲区的状态字典。

对初学阶段来说,你可以简单把它理解成:

模型参数的“总账本”

比如:

print(net.state_dict())

你会看到整个模型中所有层的参数都被组织在一个字典中,例如:

  • 0.weight

  • 0.bias

  • 2.weight

  • 2.bias

这里的 02 表示 Sequential 中对应层的编号。


七、如何直接访问权重和偏置

如果你只想访问某一层的权重,可以这样写:

print(net[0].weight)
print(net[0].bias)

例如第一层 Linear(4, 8)

  • 权重形状通常是 [8, 4]

  • 偏置形状通常是 [8]

因为这层表示:

[
y = Wx + b
]

输入 4 维,输出 8 维,所以权重矩阵大小就是 8 × 4


八、param.data 和 param 本身有什么区别

当你访问参数时,比如:

print(net[0].weight)

你拿到的是一个 Parameter 对象,它本质上是特殊的张量。

如果你只想看里面实际的数据值,可以写:

print(net[0].weight.data)

简单理解:

  • weight:是参数对象,包含梯度等训练信息

  • weight.data:更偏向于参数内部的纯数值张量

平时查看数值时,经常会看到 .data


九、named_parameters():更系统地查看参数

如果你想遍历一个层或整个模型中的参数,可以使用:

for name, param in net.named_parameters():
    print(name, param.shape)

输出一般类似:

0.weight torch.Size([8, 4])
0.bias torch.Size([8])
2.weight torch.Size([1, 8])
2.bias torch.Size([1])

这个方法非常实用,因为它可以同时告诉你:

  • 参数名字

  • 参数形状

这对于调试模型特别方便。


十、parameters() 和 named_parameters() 的区别

这两个方法很像,但有一点差别。

1. parameters()

只返回参数本身:

for param in net.parameters():
    print(param.shape)

2. named_parameters()

同时返回参数名称和参数本身:

for name, param in net.named_parameters():
    print(name, param.shape)

所以一般来说:

  • 只想把参数交给优化器,用 parameters()

  • 想调试、查看名字,用 named_parameters()


十一、为什么优化器只更新参数层

例如:

optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

这里传进去的是 net.parameters()

这意味着优化器会自动找到模型中所有可学习参数。
而像 ReLU 这种没有参数的层,不会出现在里面。

所以你会发现:

不是所有层都有参数,只有可学习层才有参数。

比如:

  • Linear 有参数

  • Conv2d 有参数

  • BatchNorm 有参数

  • ReLU 没有参数

  • Sigmoid 没有参数


十二、如何从嵌套块中访问参数

很多模型不是简单一层层平铺,而是嵌套结构。
例如:

def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                         nn.Linear(8, 4), nn.ReLU())

def block2():
    net = nn.Sequential()
    for i in range(4):
        net.add_module(f'block {i}', block1())
    return net

rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
print(rgnet)

这个模型会比较复杂,里面套了很多 Sequential

如果你想访问某个深层参数,可以一层层索引:

print(rgnet[0][1][0].bias.data)

也就是说:

  • rgnet[0]:第一个大块

  • [1]:里面第二个子块

  • [0]:这个子块里的第一层线性层

虽然写起来有点长,但这说明:

PyTorch 的模型结构本质上是可以层层访问的。


十三、如何一次性初始化所有参数

参数管理中最常见的需求之一就是:
自定义初始化。

例如我们想把所有线性层的权重初始化为均值 0、标准差 0.01 的正态分布:

def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)
        nn.init.zeros_(m.bias)

net.apply(init_normal)
print(net[0].weight.data[0], net[0].bias.data[0])

这里的核心是:

net.apply(init_normal)

它会把 init_normal 函数递归地应用到模型中的每个子模块。


十四、常见初始化方式有哪些

1. 正态分布初始化

nn.init.normal_(m.weight, mean=0, std=0.01)

2. 常数初始化

nn.init.constant_(m.weight, 1)
nn.init.zeros_(m.bias)

3. Xavier 初始化

nn.init.xavier_uniform_(m.weight)

4. He 初始化

nn.init.kaiming_uniform_(m.weight, nonlinearity='relu')

这些初始化方式你在前面的“模型初始化”博客里已经接触过了。
现在到了参数管理这一节,你会发现它们终于能真正和代码对上了。


十五、如何只初始化某些参数

有时候我们不想把所有层都一样初始化,而是只改某一层。

比如只初始化第一层:

nn.init.normal_(net[0].weight, mean=0, std=0.01)
nn.init.zeros_(net[0].bias)

或者只对名字里包含 weight 的参数做处理:

for name, param in net.named_parameters():
    if "weight" in name:
        nn.init.normal_(param, mean=0, std=0.01)

这样就更灵活。


十六、可以直接修改参数值吗

可以。

比如:

net[0].weight.data[:] = 42
net[0].bias.data[:] = 0

这表示:

  • 第一层所有权重全改成 42

  • 第一层所有偏置全改成 0

当然,实际训练中一般不会这么干,但这个例子能说明:

参数本质上就是张量,所以是可以直接操作的。

也正因为如此,PyTorch 在调试和实验时非常灵活。


十七、参数共享是什么意思

参数共享是这一节非常有代表性的内容。

所谓参数共享,就是:

多个地方使用同一组参数,而不是各自拥有独立参数。

例如我们先定义一个共享层:

shared = nn.Linear(8, 8)

然后把它放进网络中多次使用:

net = nn.Sequential(
    nn.Linear(4, 8), nn.ReLU(),
    shared, nn.ReLU(),
    shared, nn.ReLU(),
    nn.Linear(8, 1)
)

这里 shared 被用了两次。


十八、为什么说这是“共享”而不是“复制”

因为这里不是重新创建了两个 Linear(8,8),而是:

把同一个层对象用了两次。

所以这两个位置实际指向的是同一组参数。

你可以验证:

print(net[2].weight.data[0] == net[4].weight.data[0])

一般会发现它们完全一样。

进一步,如果你修改其中一个:

net[2].weight.data[0, 0] = 100
print(net[4].weight.data[0, 0])

另一个对应位置也会变化。
这就说明它们确实共享同一份参数。


十九、参数共享有什么用

参数共享在深度学习中非常重要,不只是一个“技巧”。

1. 减少参数量

共享参数后,模型参数更少,内存占用更低。

2. 强制不同位置学习相同模式

例如卷积神经网络本质上就带有参数共享思想。
同一个卷积核在不同位置滑动,其实就是在共享参数。

3. 在某些结构设计中很自然

比如:

  • Siamese Network

  • 某些递归结构

  • 特定残差或重复模块

所以理解参数共享,对后面学习 CNN 等结构很有帮助。


二十、如何判断参数是不是同一个

有时我们想确认两层参数到底是不是共享的,可以直接比较对象标识:

print(net[2].weight is net[4].weight)

如果结果是 True,就说明它们真的是同一个参数对象,而不是值恰好相等。

这一点比只比较数值更本质。


二十一、这一节的核心思想到底是什么

如果把“参数管理”这一节压缩成一句话,我觉得就是:

模型不是神秘黑箱,本质上就是一组可以被组织、访问、修改和共享的参数。

前面“模型构造”让我们知道了网络怎么搭;
这一节“参数管理”则让我们开始真正接触网络内部。

从这时候开始,你对神经网络的理解就不只是“层和层”,而是:

  • 每层里有什么参数

  • 参数长什么样

  • 参数如何初始化

  • 参数如何被优化器更新

  • 参数如何被共享和复用

这会让你对模型的认识更深入一层。


二十二、初学者最容易混淆的几个点

1. 不是所有层都有参数

例如 ReLU 没有参数,Linear 才有。

2. state_dict() 看到的是参数状态,不只是权重

它是整个模型参数的总表。

3. parameters()named_parameters() 不一样

前者只有参数,后者还有名字。

4. self.xxx = 某层 才会注册参数

临时变量不会被模型自动管理。

5. 参数共享不是“数值一样”,而是“对象本身就是同一个”

这一点很关键。


二十三、一个适合 CSDN 的完整示例

下面给你一份适合放博客里的完整演示代码:

import torch
from torch import nn

# 1. 定义模型
net = nn.Sequential(
    nn.Linear(4, 8),
    nn.ReLU(),
    nn.Linear(8, 1)
)

# 2. 查看整个模型参数
print("=== state_dict ===")
print(net.state_dict())

# 3. 查看每个参数名字和形状
print("\n=== named_parameters ===")
for name, param in net.named_parameters():
    print(name, param.shape)

# 4. 自定义初始化
def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)
        nn.init.zeros_(m.bias)

net.apply(init_normal)

print("\n=== 初始化后的第一层权重 ===")
print(net[0].weight.data)

# 5. 参数共享
shared = nn.Linear(8, 8)
net2 = nn.Sequential(
    nn.Linear(4, 8), nn.ReLU(),
    shared, nn.ReLU(),
    shared, nn.ReLU(),
    nn.Linear(8, 1)
)

print("\n=== 参数共享验证 ===")
print(net2[2].weight is net2[4].weight)

这段代码基本把本节最核心的内容都串起来了。


二十四、我对这一节的理解

学完“参数管理”之后,我最大的感受是:

以前写神经网络时,更多只是把层堆起来,感觉模型像个整体黑箱;
但这一节让我第一次开始真正把模型拆开来看。

我发现:

  • 每一层其实就是若干参数张量

  • 参数是可以查看的

  • 参数是可以单独初始化的

  • 参数甚至还可以共享

这让我对神经网络的理解更具体了。
原来训练模型,不是某种神秘过程,而是在不断调整这些权重和偏置。

所以这一节虽然不像卷积层那样“看起来很炫”,但它非常基础,也非常关键。


二十五、结语

“参数管理”是 PyTorch 神经网络基础中非常重要的一节。
如果说“模型构造”解决的是“怎么搭网络”,那么“参数管理”解决的就是:

怎么真正看懂网络里面的参数,并对它们进行控制。

这一节学扎实之后,后面你再去学:

  • 自定义层

  • 模型初始化

  • 参数冻结

  • 卷积网络

  • 模型保存与加载

都会轻松很多。

所以建议这一节一定不要只停留在“看懂”,最好自己亲手试一下:

  • state_dict()

  • named_parameters()

  • apply()

  • 参数共享

这些操作。


二十六、重点速记版

1. 什么是参数管理

对模型中的权重和偏置进行查看、访问、修改和组织。

2. 查看某层参数用什么

state_dict()

3. 遍历模型参数用什么

parameters()named_parameters()

4. 初始化整个模型常用什么

net.apply(初始化函数)

5. 参数能不能直接改

可以,本质上它们是张量。

6. 什么是参数共享

多个位置使用同一组参数对象。

7. 为什么参数共享重要

可以减少参数量,并让不同位置学习相同模式。

 

以上就是我对《动手学深度学习》中 PyTorch 神经网络基础——参数管理 这一节的学习整理。
这一节让我对神经网络的理解进一步从“搭结构”深入到了“看参数”。也让我真正明白,模型训练的本质其实就是对这些参数不断进行更新和优化。

对于刚开始学 PyTorch 的同学来说,这一节非常值得自己动手敲一遍。因为只有真正看过参数、改过参数、初始化过参数,后面学习更复杂的网络结构时,才不会一直停留在“黑箱使用”的阶段。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐