4.2 模型参数的访问、初始化和共享
导入需要的库,init
模块包含了多种模型初始化方法。定义一个含单隐藏层的多层感知机。
1 | import torch |
输出:
1 | Sequential( |
访问模型参数
对于Sequential
实例中含模型参数的层,可以通过Module
类的parameters()
或者named_parameters
方法来访问所有参数(以迭代器的形式返回),后者除了返回参数Tensor
外还会返回其名字。下面,访问多层感知机net
的所有参数:
1 | print(type(net.named_parameters())) |
输出:
1 | <class 'generator'> |
可见返回的名字自动加上了层数的索引作为前缀。
再来访问net
中单层的参数。对于使用Sequential
类构造的神经网络,可以通过方括号[]
来访问网络的任一层。索引0表示隐藏层为Sequential
实例最先添加的层。
1 | for name, param in net[0].named_parameters(): |
输出:
1 | weight torch.Size([3, 4]) <class 'torch.nn.parameter.Parameter'> |
因为这里是单层的所以没有了层数索引的前缀。另外返回的param
的类型为torch.nn.parameter.Parameter
,这是Tensor
的子类,和Tensor
不同的是如果一个Tensor
是Parameter
,那么它会自动被添加到模型的参数列表里,来看下面这个例子。
1 | class MyModel(nn.Module): |
输出:
1 | weight1 |
上面的代码中weight1
在参数列表中但是weight2
却没在参数列表中。
因为Parameter
是Tensor
,即Tensor
拥有的属性它都有,比如可以根据data
来访问参数数值,用grad
来访问参数梯度。
1 | weight_0 = list(net[0].parameters())[0] |
输出:
1 | tensor([[ 0.2719, -0.0898, -0.2462, 0.0655], |
初始化模型参数
PyTorch的init
模块里提供了多种预设的初始化方法。在下面的例子中,将权重参数初始化成均值为0、标准差为0.01的正态分布随机数,并依然将偏差参数清零。
1 | for name, param in net.named_parameters(): |
输出:
1 | 0.weight tensor([[ 0.0030, 0.0094, 0.0070, -0.0010], |
下面使用常数来初始化权重参数。
1 | for name, param in net.named_parameters(): |
输出:
1 | 0.bias tensor([0., 0., 0.]) |
自定义初始化方法
有时候需要的初始化方法并没有在init
模块中提供。这时,可以实现一个初始化方法,从而能够像使用其他初始化方法那样使用它。在这之前先来看看PyTorch是怎么实现这些初始化方法的,例如torch.nn.init.normal_
:
1 | def normal_(tensor, mean=0, std=1): |
可以看到这就是一个inplace改变Tensor
值的函数,而且这个过程是不记录梯度的。
torch.no_grad()
是一个上下文管理器,用于禁用梯度计算。在它的作用范围内,所有对张量的操作都不会被记录到计算图中,也不会更新梯度。一旦离开 with
块,torch.no_grad()
的作用结束,后续的代码会恢复到正常的梯度记录状态。
类似的来实现一个自定义的初始化方法。在下面的例子里,令权重有一半概率初始化为0,有另一半概率初始化为\([-10,-5]\)和\([5,10]\)两个区间里均匀分布的随机数。
1 | def init_weight_(tensor): |
输出:
1 | 0.weight tensor([[ 7.0403, 0.0000, -9.4569, 7.0111], |
此外,还可以通过改变这些参数的data
来改写模型参数值同时不会影响梯度:
1 | for name, param in net.named_parameters(): |
输出:
1 | 0.bias tensor([1., 1., 1.]) |
共享模型参数
在有些情况下,希望在多个层之间共享模型参数。4.1.3节提到了如何共享模型参数: Module
类的forward
函数里多次调用同一个层。此外,如果传入Sequential
的模块是同一个Module
实例的话参数也是共享的,下面来看一个例子:
1 | linear = nn.Linear(1, 1, bias=False) |
输出:
1 | Sequential( |
在内存中,这两个线性层其实一个对象:
1 | print(id(net[0]) == id(net[1])) |
输出:
1 | True |
因为模型参数里包含了梯度,所以在反向传播计算时,这些共享的参数的梯度是累加的:
1 | x = torch.ones(1, 1) |
输出:
1 | tensor(9., grad_fn=<SumBackward0>) |