返回顶部

请叫我杨先生

导航

Pytorch 5.2 参数管理

参数管理

对于深度学习,参数可以比喻成耶稣和耶稣的十字架,模型的好坏,跟我们的参数有密切的关系,据此,深入了解 \(Pytorch\) 的参数是如何运行和作用的是十分重要的。

参数访问

我们从单层隐藏层的感知机入手:

import torch 
from torch import nn  
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))
X = torch.randn(2,4) 
net(X)
tensor([[-0.1817],
        [-0.0828]], grad_fn=<AddmmBackward0>)

获取参数

上一篇文章有介绍到,每一个 \(torch.nn.Sequential()\) 都是一个块,也是一个类 \((class)\) , 每一层或者每一个块可以嵌套的形式组成新的块,所以块都是嵌套形式的结构,所以当我们当通过\(Sequential\) 类定义模型时, 我们可以通过索引来访问模型的任意层。

net[2],net[2].state_dict()
(Linear(in_features=8, out_features=1, bias=True),
 OrderedDict([('weight',
               tensor([[-0.2567,  0.3404, -0.0858,  0.1717,  0.1694, -0.0358,  0.1906, -0.3078]])),
              ('bias', tensor([-0.1932]))]))

\(torch.nn.Module.state\_dict(destination=None, prefix="", keep\_vars=False)\)
官网链接: https://pytorch.org/docs/stable/generated/torch.nn.Module.html?highlight=state_dict#torch.nn.Module.state_dict
官网描述: Returns a dictionary containing a whole state of the module.
Example:

>>> module.state_dict().keys()
['bias', 'weight']

获得目标参数

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

参数是复合的对象,包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度。 在上面这个网络中,由于我们还没有调用反向传播,所以参数的梯度处于初始状态。 还有更多访问参数的方法,具体的可以看沐神的d2l:CLICK HERE

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

从嵌套块中获得参数

X = torch.tensor([[0.8539, 0.5212, 0.9913, 0.1067],
        [0.5485, 0.7293, 0.7421, 0.8585]])
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) # 测试下
tensor([[-0.4351],
        [-0.4351]], 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] # 我们定义的块 block1 的四个组合体
rgnet[0][1] # 组合体中的 中的第二个 block1 
rgnet[0][1][0].bias # 访问其参数 
Parameter containing:
tensor([ 0.2686, -0.1795,  0.2940, -0.0979,  0.2760, -0.4608,  0.2903, -0.2612],
       requires_grad=True)

同理,我们所访问的参数是一个梯度状态和参数数值的复合体,如果我们想直接获取参数的数据直接在后面添加 \(.data\) 就可以了 ,可以查看上文中的四级标题:获得目标参数

参数初始化

默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。我们也可以自定义初始化参数的参数。

内置初始化

# 将权重参数初始化为标准差为0.01的高斯随机变量,偏置设为0。 
def init_norm(m):
    if type(m) == nn.Linear: # nn.Linear 是 nn.Module 的子类,可以和 
        nn.init.normal_(m.weight, mean=0, std=0.01) # normal 的 in-place 写法,即不创建任何其他变量 
        nn.init.zeros_(m.bias)  
net.apply(init_norm) # 这里的net还是文章顶部的net,我们在第二部分实例的是rgnet
Sequential(
  (0): Linear(in_features=4, out_features=8, bias=True)
  (1): ReLU()
  (2): Linear(in_features=8, out_features=1, bias=True)
)
net[2].weight.data ,net[2].bias.data 
'''(tensor([[-0.0032,  0.0079, -0.0093,  0.0043,  0.0216, -0.0036, -0.0067,  0.0092]]),
 tensor([0.]))'''

除此之外,我们还能够初始化权重为常数 只需要将 \(nn.init.normal\_(m.weight, mean=0, std=0.01)\) 修改成 \(nn.init.constant\_(m.weight, 1)\) 即可。官网给出了几种常见的分布:CLICK HERE,也可以讲不同的初始化作用到不同的块。如果还是满足你的要求,可以尝试下自己定义初始化函数。

自定义初始化

\[\begin{split}\begin{aligned} w \sim \begin{cases} U(5, 10) & \text{ 可能性 } \frac{1}{4} \\ 0 & \text{ 可能性 } \frac{1}{2} \\ U(-10, -5) & \text{ 可能性 } \frac{1}{4} \end{cases} \end{aligned}\end{split}\]

def my_init(m): 
    if type(m) == nn.Linear: 
        print("Init" ,*[(name,parm.shape) for name,parm in m.named_parameters()][0]) 
        nn.init.uniform_(m.weight,-10,10) # 均匀分布 Uniform Distribution  
        nn.init.zeros_(m.bias) 
net.apply(my_init)
Init weight torch.Size([8, 4])
Init weight torch.Size([1, 8])
Sequential(
  (0): Linear(in_features=4, out_features=8, bias=True)
  (1): ReLU()
  (2): Linear(in_features=8, out_features=1, bias=True)
)
net[0].weight[:2]
"""
tensor([[-8.4986,  0.0000, -0.0000, -0.0000],
        [ 6.7884,  0.0000, -9.8570, -6.8247]], grad_fn=<SliceBackward>)"""

参数共享

有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。

# 我们需要给共享层一个名称,以便可以引用它的参数
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].bias.data == net[4].bias.data) # 检查参数是否相同 
print(id(net[2]) == id(net[4])) # 最后发现地址也相同,那就说明是绑定在一块儿了 
tensor([True, True, True, True, True, True, True, True])
True

在前期我们并不需要知道绑定参数的意义何在,但是可以看看它的优点:
共享参数通常可以节省内存,并在以下方面具有特定的好处:

  • 对于图像识别中的CNN,共享参数使网络能够在图像中的任何地方而不是仅在某个区域中查找给定的功能。
  • 对于RNN,它在序列的各个时间步之间共享参数,因此可以很好地推广到不同序列长度的示例。
  • 对于自动编码器,编码器和解码器共享参数。 在具有线性激活的单层自动编码器中,共享权重会在权重矩阵的不同隐藏层之间强制正交。

trouble shooting(疑难解答)

Q1: \(torch.nn.modules.add\_module(name, module)\)
访问官网: CLICK HERE
官网描述:Adds a child module to the current module.
The module can be accessed as an attribute using the given name.
  Args:
    name (string): name of the child module. The child module can be
    accessed from this module using the given name
  module (Module): child module to be added to the module.

Q2: \(torch.nn.Module.apply(fn)\)
官网链接:CLICK HERE
官网描述: Applies \(fn\) recursively to every submodule (as returned by .children()) as well as self. Typical use includes initializing the parameters of a model (see also torch.nn.init).

Parameters: \(fn\) (Module -> None) – function to be applied to each submodule

posted on 2022-01-21 23:28  YangShusen'  阅读(166)  评论(0编辑  收藏  举报