FA_CNN卷积神经网络二十四---层\块\参数管理PyTorch

FA :Formula and Algorithm,CNN:Convolution Netural Network

1. 引言

在 PyTorch 中,深度学习模型本质上是由一个个「层(Layer)」和「块(Block)」组合而成的。理解层与块的组织方式、参数的管理机制,以及如何自定义层和保存模型,是掌握 PyTorch 建模能力的关键一步。

本文将从最基础的 nn.Sequential 出发,逐步深入到自定义块、参数管理、参数绑定、自定义层以及模型读写,帮助你建立对 PyTorch 模型结构的完整认知。

2. 层和块

nn.Sequential 定义了一种特殊的 Module,它把多个层按顺序串联起来,前一个层的输出直接作为后一个层的输入。

2.1 回顾多层感知机

python 复制代码
import torch
from torch import nn
from torch.nn import functional as F

net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)

输出结果:

text 复制代码
tensor([[-0.0214, -0.1789, -0.0700, -0.0238, -0.2697,  0.0381,  0.3078, -0.2082,
         -0.1502,  0.0433],
        [ 0.0200, -0.1466, -0.0633,  0.0031, -0.2042,  0.0993,  0.3137, -0.1206,
         -0.1057,  0.0434]], grad_fn=<AddmmBackward0>)

3. 自定义块

除了使用内置的 Sequential,我们还可以通过继承 nn.Module 来定义自己的块。自定义块需要实现 __init__ 和 forward 两个方法。

python 复制代码
class MLP(nn.Module):
    def __init__(self):
        super().__init__()  # 调用父类的 __init__ 函数
        self.hidden = nn.Linear(20, 256)
        self.out = nn.Linear(256, 10)

    def forward(self, X):
        return self.out(F.relu(self.hidden(X)))

实例化多层感知机的层,然后在每次调用正向传播函数时调用这些层:

python 复制代码
net = MLP()
X = torch.rand(2, 20)
net(X)

输出结果:

text 复制代码
tensor([[-0.1600,  0.0363,  0.0851,  0.0364,  0.0189,  0.1590,  0.1519,  0.1299,
         -0.1382, -0.2075],
        [-0.1956,  0.0779, -0.0385, -0.0741,  0.0229,  0.0116,  0.1271,  0.0273,
         -0.0867, -0.0511]], grad_fn=<AddmmBackward0>)

4. 顺序块

我们可以自己实现一个类似 nn.Sequential 的顺序块,把每个子块以字典的形式存入 _modules 中。

python 复制代码
class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for block in args:
            # block 本身作为它的 key,存在 _modules 里面的为层,以字典的形式
            self._modules[block] = block

    def forward(self, X):
        for block in self._modules.values():
            print(block)
            X = block(X)
        return X
python 复制代码
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)

输出结果:

text 复制代码
Linear(in_features=20, out_features=256, bias=True)
ReLU()
Linear(in_features=256, out_features=10, bias=True)
tensor([[-0.0651,  0.0377, -0.0348, -0.0377,  0.1602,  0.0022, -0.0904,  0.1742,
         -0.0520,  0.0189],
        [-0.0192,  0.1056, -0.0497,  0.0301,  0.2464,  0.0126, -0.1700,  0.4147,
          0.0703, -0.0013]], grad_fn=<AddmmBackward0>)

5. 正向传播

在 forward 函数中,我们不仅可以调用层,还可以执行任意 Python 代码,例如控制流、张量运算等。

python 复制代码
class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        X = F.relu(torch.mm(X, self.rand_weight + 1))
        X = self.linear(X)
        while X.abs().sum() > 1:
            X /= 2
        return X.sum()
python 复制代码
net = FixedHiddenMLP()
X = torch.rand(2, 20)
net(X)

输出结果:

text 复制代码
tensor(0.3770, grad_fn=<SumBackward0>)

6. 混合组合块

各种块可以自由混合组合,构建出更复杂的模型结构。

python 复制代码
class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU())
        self.linear = nn.Linear(32, 16)

    def forward(self, X):
        return self.linear(self.net(X))


chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
X = torch.rand(2, 20)
chimera(X)

输出结果:

text 复制代码
tensor(-0.1488, grad_fn=<SumBackward0>)

7. 参数管理

7.1 访问参数

首先关注具有单隐藏层的多层感知机:

python 复制代码
import torch
from torch import nn

net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
print(net(X))
text 复制代码
tensor([[0.3941],
        [0.4224]], grad_fn=<AddmmBackward0>)

访问参数,net[2] 就是最后一个输出层:

python 复制代码
print(net[2].state_dict())
text 复制代码
OrderedDict([('weight', tensor([[ 4.7564e-02, -5.3226e-02,  1.4919e-04, -2.8679e-01,  1.7408e-01,
          3.0859e-01, -1.2281e-01,  5.6171e-02]])), ('bias', tensor([0.3129]))])

查看目标参数的类型与数值:

python 复制代码
print(type(net[2].bias))       # 目标参数
print(net[2].bias)
print(net[2].bias.data)
print(net[2].weight.grad == None)  # 还没进行反向计算,所以 grad 为 None
text 复制代码
<class 'torch.nn.parameter.Parameter'>
Parameter containing:
tensor([0.3129], requires_grad=True)
tensor([0.3129])
True

7.2 一次性访问所有参数

python 复制代码
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
text 复制代码
('weight', torch.Size([8, 4])) ('bias', torch.Size([8]))
('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))

说明:0 是第一层名字,1 是 ReLU,它没有参数。

通过名字获取参数:

python 复制代码
print(net.state_dict()['2.bias'].data)
text 复制代码
tensor([0.3129])

7.3 从嵌套块收集参数

python 复制代码
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):
        # f'block{i}' 可以传一个字符串名字过来,block2 可以嵌套四个 block1
        net.add_module(f'block{i}', block1())
    return net


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

输出结果:

text 复制代码
tensor([[-0.1750],
        [-0.1750]], grad_fn=<AddmmBackward0>)
Sequential(
  (0): Sequential(
    (block0): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block1): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block2): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block3): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
  )
  (1): Linear(in_features=4, out_features=1, bias=True)
)

8. 内置初始化

8.1 正态分布初始化

python 复制代码
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))


def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)  # 下划线表示把 m.weight 的值替换掉
        nn.init.zeros_(m.bias)


net.apply(init_normal)  # 会递归调用,直到所有层都初始化
print(net[0].weight.data[0])
print(net[0].bias.data[0])
text 复制代码
tensor([ 0.0012, -0.0112, -0.0153,  0.0218])
tensor(0.)

8.2 常数初始化

python 复制代码
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))


def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)


net.apply(init_constant)
print(net[0].weight.data[0])
print(net[0].bias.data[0])
text 复制代码
tensor([1., 1., 1., 1.])
tensor(0.)

8.3 对某些块应用不同的初始化

python 复制代码
def xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)


def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)


net[0].apply(xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)
text 复制代码
tensor([ 0.0479, -0.1771,  0.5267, -0.0020])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])

9. 参数替换

自定义初始化,只保留绝对值大于 5 的权重,其余置零:

python 复制代码
def my_init(m):
    if type(m) == nn.Linear:
        print("Init", *[(name, param.shape) for name, param in m.named_parameters()][0])
        nn.init.uniform_(m.weight, -10, 10)
        # 先计算布尔矩阵(判断 >= 5),再用布尔矩阵对应元素乘以原始矩阵
        # 保留绝对值大于 5 的权重,否则设为 0
        m.weight.data *= m.weight.data.abs() >= 5


net.apply(my_init)
print(net[0].weight[:2])
text 复制代码
Init weight torch.Size([8, 4])
Init weight torch.Size([1, 8])
tensor([[ 0.0000,  7.1240,  0.0000,  5.1135],
        [-8.6745, -7.3974,  0.0000, -0.0000]], grad_fn=<SliceBackward0>)

直接替换参数:

python 复制代码
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
print(net[0].weight.data[0])
text 复制代码
tensor([42.0000,  8.1240,  1.0000,  6.1135])

10. 参数绑定

多个层可以共享同一组参数。下面的例子中,第 2 个隐藏层和第 3 个隐藏层共享权重。

python 复制代码
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(8, 1))
net(X)

print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
print(net[2].weight.data[0] == net[4].weight.data[0])
text 复制代码
tensor([True, True, True, True, True, True, True, True])
tensor([True, True, True, True, True, True, True, True])

可以看到,修改其中一个层的权重,另一个共享层的权重也会同步变化。

11. 自定义层

11.1 无参数的自定义层

python 复制代码
import torch
import torch.nn.functional as F
from torch import nn


class CenteredLayer(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, X):
        return X - X.mean()


layer = CenteredLayer()
print(layer(torch.FloatTensor([1, 2, 3, 4, 5])))
text 复制代码
tensor([-2., -1.,  0.,  1.,  2.])

将层作为组件合并到更复杂的模型中:

python 复制代码
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
Y = net(torch.rand(4, 8))
print(Y.mean())
text 复制代码
tensor(-6.2864e-09, grad_fn=<MeanBackward0>)

11.2 带参数的自定义层

python 复制代码
class MyLinear(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        # nn.Parameter 使得这些参数加上了梯度
        self.weight = nn.Parameter(torch.randn(in_units, units))
        self.bias = nn.Parameter(torch.randn(units,))

    def forward(self, X):
        linear = torch.matmul(X, self.weight.data) + self.bias.data
        return F.relu(linear)


dense = MyLinear(5, 3)
print(dense.weight)
text 复制代码
Parameter containing:
tensor([[-2.8449,  0.1887,  0.7945],
        [ 0.4226,  1.6180, -0.5880],
        [-0.4794, -0.0817, -0.3648],
        [-0.1979,  0.8702, -0.3515],
        [-1.4943,  0.3618,  0.2969]], requires_grad=True)

使用自定义层直接执行正向传播计算:

python 复制代码
print(dense(torch.rand(2, 5)))
text 复制代码
tensor([[0.0000, 0.0000, 1.3957],
        [0.8225, 0.0000, 0.9089]])

使用自定义层构建模型:

python 复制代码
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
print(net(torch.rand(2, 64)))
text 复制代码
tensor([[0.],
        [0.]])

12. 读写文件

12.1 加载和保存张量

python 复制代码
import torch
from torch import nn
from torch.nn import functional as F

x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load("x-file")
print(x2)
text 复制代码
tensor([0, 1, 2, 3])

存储一个张量列表,然后把它们读回内存:

python 复制代码
y = torch.zeros(4)
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files')
print(x2)
print(y2)
text 复制代码
tensor([0, 1, 2, 3])
tensor([0., 0., 0., 0.])

写入或读取从字符串映射到张量的字典:

python 复制代码
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
print(mydict2)
text 复制代码
{'x': tensor([0, 1, 2, 3]), 'y': tensor([0., 0., 0., 0.])}

12.2 加载和保存模型参数

python 复制代码
class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(20, 256)
        self.output = nn.Linear(256, 10)

    def forward(self, x):
        return self.output(F.relu(self.hidden(x)))


net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)

# 将模型的参数存储为一个叫做 "mlp.params" 的文件
torch.save(net.state_dict(), 'mlp.params')

# 实例化了原始多层感知机模型的一个备份,直接读取文件中存储的参数
clone = MLP()  # 必须要先声明一下,才能导入参数
clone.load_state_dict(torch.load("mlp.params"))
print(clone.eval())  # eval() 是进入测试模式

Y_clone = clone(X)
print(Y_clone == Y)
text 复制代码
MLP(
  (hidden): Linear(in_features=20, out_features=256, bias=True)
  (output): Linear(in_features=256, out_features=10, bias=True)
)
tensor([[True, True, True, True, True, True, True, True, True, True],
        [True, True, True, True, True, True, True, True, True, True]])

13. 总结

本文系统梳理了 PyTorch 中模型构建的核心机制:

  • 层与块 :nn.Sequential 是最简单的块,自定义块需继承 nn.Module 并实现 forward。
  • 正向传播 :forward 中可以执行任意 Python 代码,实现灵活的控制流。
  • 参数管理 :通过 state_dict()、named_parameters() 访问参数,通过 apply() 递归初始化。
  • 参数绑定:多个层共享同一组参数,修改一处即同步生效。
  • 自定义层 :无参数层与带参数层(使用 nn.Parameter)均可灵活构建。
  • 读写文件 :torch.save / torch.load 支持张量、字典与模型参数的持久化。

掌握这些基础能力后,就可以更从容地搭建和调试复杂的深度学习模型了。

相关推荐
AiNightVision42 分钟前
无人机夜视模块如何选择?——从参数指标到系统集成的完整选型指南
人工智能·算法·计算机视觉·车载系统·无人机·视频·智能硬件
Java后端的Ai之路1 小时前
【AIAgent开发】-Agent工具封装成CLI(纯干货!!!)
java·javascript·人工智能·agent·cli
Flynn Liang1 小时前
Spring AI 上生产:限流熔断、密钥与成本治理、私有化/混合部署、降级与 Prompt 灰度架构全解
人工智能·spring·架构·高可用·springai·成本治理·生产架构
飞猫的边缘AI1 小时前
边缘AI芯片的规则变了:从跑CNN到跑LLM和VLM
人工智能·语言模型·cnn·transformer·边缘ai
code2cat1 小时前
【随笔】连续批处理:长短请求怎样在迭代之间补位
java·人工智能·后端·大语言模型·vllm
牧羊人.3332 小时前
卷积神经网络之VGG
人工智能·深度学习·神经网络·算法·cnn
Flynn Liang2 小时前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
Martina_03212 小时前
AI生成3D模型一合并网格,材质就串色?用6步检查材质槽、UV与图集重映射
图像处理·人工智能·3d·材质·uv·游戏策划·1024程序员节
蓝鲨硬科技2 小时前
Token之后,物理AI需要什么样的度量衡?
大数据·人工智能