《动手学深度学习 Pytorch版》 5.2 参数管理

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)

tensor([[-0.3771],
[-0.3822]], grad_fn=)

5.2.1 参数访问

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

print(net[2].state_dict())

OrderedDict([('weight', tensor([[ 0.0867, 0.1007, 0.2371, 0.1944, -0.2581, -0.2854, -0.0813, -0.1310]])), ('bias', tensor([-0.2911]))])

  1. 目标参数

    参数表示为参数类的实例

print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)

net[2].weight.grad == None

<class 'torch.nn.parameter.Parameter'>
Parameter containing:
tensor([-0.2911], requires_grad=True)
tensor([-0.2911])

True

  1. 一次性访问所有参数

    当块很复杂的时候则需要递归整棵树来提取每个子块的参数。

print(*[(name, param.shape) for name, param in net[0].named_parameters()])  # 太优雅了!列表推导式+封包
print(*[(name, param.shape) for name, param in net.named_parameters()])

net.state_dict()['2.bias'].data  # 也可通过键值访问(这什么奇妙的语法)

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

tensor([-0.2911])

  1. 从嵌套块收集参数
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))
rgnet(X)

print(rgnet)  # 查看网络结构

rgnet[0][1][0].bias.data  # 可嵌套索引访问

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

tensor([-0.2230, 0.3445, -0.0584, -0.4562, 0.3161, -0.4832, 0.2733, 0.1244])

5.2.2 参数初始化

  1. 内置初始化

    框架提供默认的随机初始化,也允许我们创建自定义的初始化方法。

    默认情况下,PyTorch 会根据一个范围均匀地初始化权重和偏置矩阵。PyTorch 的 nn.init 模块提供了很多内置初始化的方法。

def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)  # 提供均值为0,标准差为0.01的初始化
        nn.init.zeros_(m.bias)  # 偏置设为0
net.apply(init_normal)  # 应用参数
net[0].weight.data[0], net[0].bias.data[0]

(tensor([-0.0041, 0.0007, 0.0194, 0.0155]), tensor(0.))

def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)  # 初始化为 1
        nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]

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

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)  # 使用 Xavier 初始化第一层
net[2].apply(init_42)  # 第三层初始化为常数值42
print(net[0].weight.data[0])
print(net[2].weight.data)

tensor([ 0.2770, -0.0892, 0.1333, 0.0069])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])

  1. 自定义初始化

以下示例实现一个自定义分布初始化

\[\begin{equation} w\sim\left\{ \begin{aligned} & U(5,10), && 可能性为\frac{1}{4}\\ & 0, && 可能性为\frac{1}{2}\\ & U(-10,-5), && 可能性为\frac{1}{4} \end{aligned} \right. \end{equation} \]

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

net.apply(my_init)
net[0].weight[:2]

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

tensor([[-5.8878, -5.7943, 8.7435, -7.0228],
[-0.0000, -0.0000, 5.7496, -8.9288]], grad_fn=)

# 我们始终可以直接设置参数

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

tensor([42.0000, -4.7943, 9.7435, -6.0228])

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

练习

(1)使用 FancyMLP 模型访问各个层的参数。

FancyMLP是啥?


(2)查看初始化模块文档以了解不同的初始方法。

dir(nn.init)

['Tensor',
'builtins',
'cached',
'doc',
'file',
'loader',
'name',
'package',
'spec',
'calculate_correct_fan',
'calculate_fan_in_and_fan_out',
'make_deprecate',
'no_grad_fill',
'no_grad_normal',
'no_grad_trunc_normal',
'no_grad_uniform',
'no_grad_zero',
'calculate_gain',
'constant',
'constant
',
'dirac',
'dirac
',
'eye',
'eye
',
'kaiming_normal',
'kaiming_normal_',
'kaiming_uniform',
'kaiming_uniform_',
'math',
'normal',
'normal_',
'ones_',
'orthogonal',
'orthogonal_',
'sparse',
'sparse_',
'torch',
'trunc_normal_',
'uniform',
'uniform_',
'warnings',
'xavier_normal',
'xavier_normal_',
'xavier_uniform',
'xavier_uniform_',
'zeros_']


(3)构建包含共享参数层的多元感知机并对其进行训练。在训练过程中,观察模型各层的参数和梯度。


(4)为什么共享参数是个好方式?

共享参数可以实现平移不变性,有时效果会更好,比如卷积神经网络。

posted @ 2023-09-11 08:43  AncilunKiang  阅读(279)  评论(0)    收藏  举报