当前位置: 首页 > news >正文

5.2 参数管理

目标

  • 访问参数,用于调试、诊断和可视化;
  • 参数初始化;
  • 在不同模型组件间共享参数。

模型:单隐藏层的MLP

import torch
from torch import nnnet = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
tensor([[-0.0970],[-0.0827]], grad_fn=<AddmmBackward0>)

1 参数访问

从已有模型中访问参数: 检查第二个全连接层的参数

print(net[2].state_dict())
OrderedDict([('weight', tensor([[-0.0427, -0.2939, -0.1894,  0.0220, -0.1709, -0.1522, -0.0334, -0.2263]])), ('bias', tensor([0.0887]))])

结果信息:

  • 这个全连接层包含两个参数,分别是该层的权重和偏置
  • 两者都存储为单精度浮点数(float32)

note:参数名称允许唯一标识每个参数

1.1 访问目标参数

从第二个全连接层(即第三个神经网络层,第三层)提取偏置

print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)
<class 'torch.nn.parameter.Parameter'>
Parameter containing:
tensor([0.0887], requires_grad=True)
tensor([0.0887])
net[2].weight.grad == None

1.2 一次性访问所有参数

print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])

(‘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]))

net.state_dict()['2.bias'].data

tensor([0.0887])

解读

列表推导式
  1. 列表推导式
  • 列表推导式 [(name, param.shape) for name, param in net[0].named_parameters()] 遍历该层的所有参数,提取参数的名称和形状。
    print(*… ) 中的 * 用于解包列表,将列表中的元素作为多个参数传递给 print 函数,从而将每个参数的名称和形状单独打印出来。

注:列表推导式(List Comprehension)是 Python 中一种简洁、高效创建列表的方式。它允许你使用一行代码快速生成列表,而无需使用传统的循环结构。列表推导式的基本语法结构如:[表达式 for 变量 in 可迭代对象 if 条件]
示例:

squares = [x ** 2 for x in range(10)]

输出print(squares) # 输出:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

  1. 带条件的列表推导式
    • 示例 :生成一个包含 0 到 9 之间的偶数的平方的列表。
    • 代码

squares_of_evens = [x ** 2 for x in range(10) if x % 2 == 0]
print(squares_of_evens) # 输出:[0, 4, 16, 36, 64]

  1. 嵌套循环的列表推导式
  • 示例 :生成两个列表元素的组合。
combinations = [(x, y) for x in [1, 2, 3] for y in ['a', 'b', 'c']]
print(combinations) 

输出:[(1, ‘a’), (1, ‘b’), (1, ‘c’), (2, ‘a’), (2, ‘b’), (2, ‘c’), (3, ‘a’), (3, ‘b’), (3, ‘c’)]

print(*… ) 中的 *

print(*… ) 中的 * 用于解包列表,将列表中的元素作为多个参数传递给 print 函数,从而将每个参数的名称和形状单独打印出来。

1.3 从嵌套块收集参数

1.3.1 在 PyTorch 中构建嵌套的神经网络块

#将输入数据通过两个全连接层和激活函数进行处理,最终输出维度为 4
def block1():return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),nn.Linear(8, 4), nn.ReLU())#将输入数据通过两个全连接层和激活函数进行处理,最终输出维度为 4
def block2():# 创建一个空的 `nn.Sequential` 容器 `net`net = nn.Sequential()# 使用 `for` 循环迭代 4 次,每次调用 `block1()` 函数创建一个新的 `block1` 块for i in range(4):# 在这里嵌套# `add_module` 方法将创建的块添加到 `net` 中,并指定模块的名称为 `'block i'`(其中 `i` 是循环索引)net.add_module(f'block {i}', block1())# `block2` 返回一个包含 4 个 `block1` 块的序列容器return net# 构建一个完整的神经网络模型,包含 block2 返回的块和一个最终的全连接层#`block2()` 返回一个包含 4 个 `block1` 块的序列容器。
# `nn.Linear(4, 1)` 是一个全连接层,输入维度为 4,输出维度为 1。
# `nn.Sequential` 将这两个部分按顺序组合成一个完整的模型。
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))#对输入数据 `X` 进行前向传播,通过整个网络计算输出
#输入数据首先通过 4 个 `block1` 块,每个块包含两个全连接层和激活函数,处理后的数据最终通过最后一个全连接层 `nn.Linear(4, 1)` 得到输出
rgnet(X)

tensor([[0.2596],
[0.2596]], grad_fn=)

1.3.2 执行过程

执行过程

假设输入数据 X 的形状为 (batch_size, 4),前向传播的具体执行过程如下:

  1. 第一个 block1

    • 输入:X(形状 (batch_size, 4)
    • 第一层全连接层:nn.Linear(4, 8),输出形状为 (batch_size, 8)
    • ReLU 激活函数:对输出应用 ReLU 激活,输出形状仍为 (batch_size, 8)
    • 第二层全连接层:nn.Linear(8, 4),输出形状为 (batch_size, 4)
    • ReLU 激活函数:对输出应用 ReLU 激活,输出形状仍为 (batch_size, 4)
  2. 第二个 block1

    • 输入:上一个块的输出(形状 (batch_size, 4)
    • 重复第一个块的处理过程,输出形状仍为 (batch_size, 4)
  3. 第三个 block1

    • 输入:上一个块的输出(形状 (batch_size, 4)
    • 重复第一个块的处理过程,输出形状仍为 (batch_size, 4)
  4. 第四个 block1

    • 输入:上一个块的输出(形状 (batch_size, 4)
    • 重复第一个块的处理过程,输出形状仍为 (batch_size, 4)
  5. 最终全连接层

    • 输入:最后一个 block1 块的输出(形状 (batch_size, 4)
    • 全连接层:nn.Linear(4, 1),输出形状为 (batch_size, 1)
向前传播流程
rgnet((0): block2((block 0): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU())(block 1): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU())(block 2): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU())(block 3): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU()))(1): Linear(4 -> 1)
)

最终构建的网络 rgnet 是一个由多个嵌套块组成的复杂神经网络。以下是其结构的详细说明:

网络结构
  1. block2

    • 包含 4 个 block1 块,每个 block1 块的结构如下:
      • nn.Linear(4, 8):输入维度 4,输出维度 8 的全连接层。
      • nn.ReLU():ReLU 激活函数。
      • nn.Linear(8, 4):输入维度 8,输出维度 4 的全连接层。
      • nn.ReLU():ReLU 激活函数。
  2. 最终的全连接层

    • nn.Linear(4, 1):输入维度 4,输出维度 1 的全连接层。
层级关系
  • rgnet 是一个 nn.Sequential 容器,包含两个主要部分:block2 和一个最终的全连接层。
  • block2 本身也是一个 nn.Sequential 容器,包含 4 个 block1 块。
  • 每个 block1 块是一个 nn.Sequential 容器,包含两个全连接层和两个 ReLU 激活函数。
具体结构

以下是 rgnet 的具体结构示意图:

rgnet((0): block2((block 0): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU())(block 1): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU())(block 2): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU())(block 3): block1((0): Linear(4 -> 8)(1): ReLU()(2): Linear(8 -> 4)(3): ReLU()))(1): Linear(4 -> 1)
)
前向传播流程

假设输入数据 X 的形状为 (batch_size, 4),以下是前向传播的具体流程:

  1. 第一个 block1

    • 输入:X(形状 (batch_size, 4)
    • 第一层全连接层:nn.Linear(4, 8),输出形状为 (batch_size, 8)
    • ReLU 激活函数:对输出应用 ReLU 激活,输出形状仍为 (batch_size, 8)
    • 第二层全连接层:nn.Linear(8, 4),输出形状为 (batch_size, 4)
    • ReLU 激活函数:对输出应用 ReLU 激活,输出形状仍为 (batch_size, 4)
  2. 第二个 block1

    • 输入:上一个块的输出(形状 (batch_size, 4)
    • 重复第一个块的处理过程,输出形状仍为 (batch_size, 4)
  3. 第三个 block1

    • 输入:上一个块的输出(形状 (batch_size, 4)
    • 重复第一个块的处理过程,输出形状仍为 (batch_size, 4)
  4. 第四个 block1

    • 输入:上一个块的输出(形状 (batch_size, 4)
    • 重复第一个块的处理过程,输出形状仍为 (batch_size, 4)
  5. 最终的全连接层

    • 输入:最后一个 block1 块的输出(形状 (batch_size, 4)
    • 全连接层:nn.Linear(4, 1),输出形状为 (batch_size, 1)

1.3.3 输出

print(rgnet)

输出

Sequential((0): Sequential((block 0): 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())(block 1): 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())(block 2): 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())(block 3): 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)
)

1.3.4 参数访问

rgnet[0][1][0].bias.data

tensor([ 0.1999, -0.4073, -0.1200, -0.2033, -0.1573, 0.3546, -0.2141, -0.2483])

2. 参数初始化

深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。

2.1 内置初始化

将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0。

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)
net[0].weight.data[0], net[0].bias.data[0]

(tensor([1., 1., 1., 1.]), tensor(0.))

Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42

def init_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(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)

tensor([ 0.5236, 0.0516, -0.3236, 0.3794])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])

2.2 自定义初始化

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_将张量的值填充为从均匀分布中采样的随机数nn.init.uniform_(m.weight, -10, 10)# 对权重参数进行筛选和调整。具体来说,它会将权重参数矩阵 m.weight.data 中绝对值大于等于 5 的元素保留,而将绝对值小于 5 的元素设置为 0 m.weight.data *= m.weight.data.abs() >= 5# 由于 weight 为均匀分布,所以数值在[-5,5]的概率为1/2,[-10,-5]的概率为1/4,[5,10]的概率为1/4;net.apply(my_init)
net[0].weight[:2]

Init weight torch.Size([8, 4])
Init weight torch.Size([1, 8])

tensor([[5.4079, 9.3334, 5.0616, 8.3095],
[0.0000, 7.2788, -0.0000, -0.0000]], grad_fn=)

net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]

tensor([42.0000, 10.3334, 6.0616, 9.3095])

实现特定的初始化分布 :在前面的例子中,定义的初始化分布要求权重值以 1/4 的概率从 U(5,10) 中取值,以 1/2 的概率取 0,以 1/4 的概率从 U(−10,−5) 中取值。通过将权重初始化为 U(−10,10) 内的随机数,然后再执行 m.weight.data *= m.weight.data.abs() >=5,可以筛选出绝对值大于等于 5 的权重值(来自 U(5,10) 或 U(−10,−5) 的分布),而将绝对值小于 5 的权重值设置为 0,从而近似实现目标的初始化分布。
这种方法并不能完全精确地实现目标分布,因为从 U(−10,10) 中采样的权重值经过筛选后,可能会导致最终的权重值分布与目标分布存在一定差异。

3 参数绑定

定义一个稠密层,然后使用它的参数来设置另一个层的参数


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])

tensor([True, True, True, True, True, True, True, True])
tensor([True, True, True, True, True, True, True, True])

3.1 定义共享层

shared = nn.Linear(8, 8)

首先定义了一个稠密层 shared = nn.Linear(8, 8),这个层的参数将被其他层共享。注意要给共享层一个名称,以便后续可以引用它的参数。

3.2 构建网络结构

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

通过 nn.Sequential 构建了一个包含多个层的神经网络 net,其中包括两个 Linear 层和多个 ReLU 激活函数层。其中,第三个、第五个神经网络层(即第二个和第三个隐藏层)使用了 shared 层的参数。

3.3 检查参数是否相同

通过比较 net[2].weight.data[0] 和 net[4].weight.data[0],发现它们的值是相同的,这表明这两个层的权重参数是绑定在一起的

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

3.4 修改参数并验证绑定关系

net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])

将 net[2].weight.data[0, 0] 设置为 100 后,再次比较 net[2].weight.data[0] 和 net[4].weight.data[0],发现它们的值仍然相同,这说明它们实际上是同一个对象,而不是仅仅有相同的值。
第一次比较的结果 tensor([True, True, True, True, True, True, True, True, True]) 表示第三个和第五个层的权重参数在初始化后是相同的。
第二次比较的结果同样显示为 tensor([True, True, True, True, True, True, True, True, True]),这进一步验证了它们共享的是同一个参数对象,因为当修改其中一个参数时,另一个参数也随之改变。

3.5 梯度累加

当参数被绑定时,在反向传播过程中,梯度会累加。由于模型参数包含梯度,所以在反向传播期间,第二个隐藏层(即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起,共同影响参数的更新。
总结来说,参数绑定是一种在深度学习中用于共享参数的技术,可以在多个层之间实现参数的共享,减少模型参数量,提高参数的利用率。在 PyTorch 中,通过定义共享层并将其用于多个位置,可以轻松实现参数绑定。同时,需要注意的是,绑定参数在反向传播时梯度会累加。

http://www.xdnf.cn/news/370513.html

相关文章:

  • Vue 两种导航方式
  • API 网关核心功能解析:负载均衡、容灾、削峰降级原理与实战摘要
  • Linux笔记---System V共享内存
  • uniapp+vue3+firstUI时间轴 提现进度样式
  • 比 Mac 便笺更好用更好看的便利贴
  • 源码示例:使用SpringBoot+Vue+ElementUI+UniAPP技术组合开发一套小微企业ERP系统
  • CentOS7.9部署FunASR实时语音识别接口 | 部署商用级别实时语音识别接口FunASR
  • milvus+flask山寨复刻《从零构建向量数据库》第7章
  • LeetCode 2918.数组的最小相等和:if-else
  • OpenCv实战笔记(4)基于opencv实现ORB特征匹配检测
  • Web3 初学者的第一个实战项目:留言上链 DApp
  • 协议路由与路由协议
  • 【图书管理系统】深度讲解:图书列表展示的后端实现、高内聚低耦合的应用、前端代码讲解
  • PXE_Kickstart_无人值守自动化安装系统
  • 物业企业绩效考核制度与考核体系
  • 前端弹性布局:用Flexbox构建现代网页的魔法指南
  • vue2 上传pdf,拖拽盖章,下载图片
  • 前端开发实战:用React Hooks优化你的组件性能
  • [C] 第10章 预处理命令
  • LeetCode热题100--240.搜索二维矩阵--中等
  • 达索MODSIM实施成本高吗?哪家服务商靠谱?
  • 思考:(linux) tmux 超级终端快速入门的宏观思维
  • Java—— 集合 List
  • 程序代码篇---Python视频流
  • JSON|cJSON 介绍以及具体项目编写
  • STM32CUBEIDE开发实战:ADC与UART应用
  • 网络原理(Java)
  • 使用python脚本连接SQL Server数据库导出表结构
  • 解决虚拟机挂起之后的网络问题
  • 鸿蒙系统使用ArkTS开发语言支持身份证阅读器、社保卡读卡器等调用二次开发SDK