动手学深度学习(pytorch)学习记录19-参数管理[学习记录]

本节内容:
访问参数,用于调试、诊断和可视化;
参数初始化;
在不同模型组件间共享参数;
延后初始化。

# 单隐藏层的多层感知机
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))
net(X)

在这里插入图片描述

参数访问

通过Sequential类定义的模型可以通过索引来访问模型的任意层。 模型就像一个列表一样,每层的参数都在其属性中。 如下所示,检查第二个全连接层的参数。

print(net[2].state_dict())
OrderedDict([('weight', tensor([[ 0.0143, -0.1299, -0.0290,  0.2700,  0.2086, -0.3533,  0.0657,  0.2856]])), ('bias', tensor([-0.1287]))])

通过输出可知:全连接层包含两个参数,分别是该层的权重和偏置。
每个参数都表示为参数类的一个实例。 要对参数执行任何操作,首先我们需要访问底层的数值。

目标参数

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

参数是复合的对象,包含值、梯度和额外信息。
在上面这个网络中,由于我没有调用反向传播,所以参数的梯度处于初始状态。

net[2].weight.grad == None
True

一次性访问所有参数

当我们需要对所有参数执行操作时,逐个访问它们可能会很麻烦。 当我们处理更复杂的块(例如,嵌套块)时,情况可能会变得特别复杂, 因为我们需要递归整个树来提取每个子块的参数。 下面,演示来访问第一个全连接层的参数和访问所有层。

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

从嵌套块收集参数

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())
# 用 add_module 方法将 block1 函数返回的 nn.Sequential 对象添加到 net 容器中。每次迭代都会添加一个新的模块,并且每个模块都有一个唯一的名称(例如 'block 0', 'block 1', ...)
    return net

rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)
tensor([[0.1559],
        [0.1559]], grad_fn=<AddmmBackward0>)

查看一下网络结构

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

因为层是分层嵌套的,所以也可以像通过嵌套列表索引一样访问它们。 下面,访问第一个主要的块中、第二个子块的第一层的偏置项。

rgnet[0][1][0].bias.data
tensor([-0.4003,  0.3388,  0.2142,  0.3416, -0.0377,  0.3460, -0.1539,  0.0325])

参数初始化

PyTorch的nn.init模块提供了多种预置初始化方法。

内置初始化

首先调用内置的初始化器。 下面的代码将所有权重参数初始化为标准差为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([0.0106, 0.0016, 0.0035, 0.0076]), tensor(0.))

还可以将所有参数初始化为给定的常数,比如初始化为6

def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 6)
        nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]
(tensor([6., 6., 6., 6.]), 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.5471,  0.4637, -0.2951,  0.1913])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])

自定义初始化

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)
        m.weight.data *= m.weight.data.abs() >= 5
        # 将所有权重的绝对值小于5的权重设置为0,而保持权重的绝对值大于或等于5的权重不变

net.apply(my_init)
net[0].weight[:2]
Init weight torch.Size([8, 4])
Init weight torch.Size([1, 8])

tensor([[ 5.1561, -0.0000,  5.4036,  5.2387],
        [-7.6918,  9.9444,  0.0000, -9.6561]], 
        grad_fn=<SliceBackward0>)

参数绑定

个层间共享参数: 可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。

# 我们需要给共享层一个名称,以便可以引用它的参数
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])

这个例子表明第三个和第五个神经网络层的参数是绑定的。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。

延后初始化

我们定义了网络架构,但没有指定输入维度。
我们添加层时没有指定前一层的输出维度。
我们在初始化参数时,甚至没有足够的信息来确定模型应该包含多少参数。
这里的诀窍是框架的延后初始化(defers initialization), 即直到数据第一次通过模型传递时,框架才会动态地推断出每个层的大小。

import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))
C:\anaconda3\envs\pytorch\lib\site-packages\torch\nn\modules\lazy.py:181: UserWarning: Lazy modules are a new feature under heavy development so changes to the API or functionality can happen at any moment.
  warnings.warn('Lazy modules are a new feature under heavy development '

网络络还不知道输入层权重的维度,因为输入维度仍然未知。

net[0].weight
<UninitializedParameter>

通过网络传递数据后,框架最终初始化参数。
一旦知道输入维度20,框架就可以通过插入20的值来识别第一层权重矩阵的形状。识别出第一层的形状后,框架继续到第二层,通过计算图依次进行,直到所有形状都已知。请注意,在这种情况下,只有第一层需要延迟初始化,但框架会顺序初始化。一旦已知所有参数形状,框架最终就可以初始化参数。

X = torch.rand(2, 20)
net(X)

net[0].weight.shape
torch.Size([256, 20])

以下方法通过网络传递伪输入进行试运行,以推断所有参数形状并随后初始化参数。稍后当不需要默认随机初始化时将使用它。

@d2l.add_to_class(d2l.Module)  #@save
def apply_init(self, inputs, init=None):
    self.forward(*inputs)
    if init is not None:
        self.net.apply(init)
AttributeError: module 'd2l.torch' has no attribute 'add_to_class'

封面图片来源

欢迎点击我的主页查看更多文章。
本人学习地址https://zh-v2.d2l.ai/
恳请大佬批评指正。

  • 34
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

walfar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值