本次将分享模型参数如何访问、初始化以及共享,首先我们再来定义一个MLP
1、导入库
import torch
from torch import nn
from torch.nn import init
print(torch.__version__)
1.0.1.post2
2、定义MLP模型
net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1)) # pytorch已进行默认初始化
print(net)
X = torch.rand(2, 4)
Y = net(X).sum()
Sequential(
(0): Linear(in_features=4, out_features=3, bias=True)
(1): ReLU()
(2): Linear(in_features=3, out_features=1, bias=True)
)
3、访问模型参数
对于 Sequential 实例中含模型参数的层,我们可以通过 Module 类的 parameters() 或者 named\_parameters ⽅法来访问所有参数 (以迭代器的形式返回),后者除了返回参数 Tensor 外还会返回其名字。下面,访问多层感知机 net 的所有参数:
print(type(net.named_parameters()))
for name, param in net.named_parameters():
print(name, param.size())
<class 'generator'>
0.weight torch.Size([3, 4])
0.bias torch.Size([3])
2.weight torch.Size([1, 3])
2.bias torch.Size([1])
可见返回的名字⾃动加上了层数的索引作为前缀。我们再来访问 net 中单层的参数。对于使用 Sequential 类构造的神经⽹络,我们可以通过方括号 [] 来访问⽹络的任一层。索引0表示隐藏层 为 Sequential 实例最先添加的层。
for name, param in net[0].named_parameters():
print(name, param.size(), type(param))
weight torch.Size([3, 4]) <class 'torch.nn.parameter.Parameter'>
bias torch.Size([3]) <class 'torch.nn.parameter.Parameter'>
因为这里是单层的所以没有了层数索引的前缀 。另外返回的 param 的类 型为 torch.nn.parameter.Parameter ,其实这是 Tensor 的⼦类,和 Tensor 不同的是如果一 个 Tensor 是 Parameter ,那么它会⾃动被添加到模型的参数列表里,来看下⾯这个例子。
class MyModel(nn.Module):
def __init__(self, **kwargs):
super(MyModel, self).__init__(**kwargs)
self.weight1 = nn.Parameter(torch.rand(20, 20))
self.weight2 = torch.rand(20, 20)
def forward(self, x):
pass
n = MyModel()
for name, param in n.named_parameters():
print(name)
weight1
上⾯的代码中 weight1 在参数列表中但是 weight2 却没在参数列表中。
因为 Parameter 是 Tensor ,即 Tensor 拥有的属性它都有,⽐如可以根据 data 来访问参数值, ⽤grad 来访问参数梯度
weight_0 = list(net[0].parameters())[0]
print(weight_0.data)
print(weight_0.grad)
Y.backward()
print(weight_0.grad)
tensor([[ 0.1113, -0.3047, -0.4044, -0.0759],
[ 0.3811, -0.1577, -0.1052, -0.4404],
[-0.3102, -0.1009, 0.3620, 0.2666]])
None
tensor([[0.0000, 0.0000, 0.0000, 0.0000],
[0.0069, 0.0008, 0.0123, 0.0061],
[0.0300, 0.0500, 0.0778, 0.0806]])
4、初始化模型参数
PyTorch的 init 模块⾥提供了多种预设的初始化方法。在下面的例子中, 我们将权重参数初始化成均值为0、标准差为0.01的正态分布随机数,并依然将偏差参数清零。
for name, param in net.named_parameters():
if 'weight' in name:
init.normal_(param, mean=0, std=0.01)
print(name, param.data)
0.weight tensor([[ 0.0217, -0.0044, -0.0019, -0.0016],
[-0.0080, -0.0026, 0.0184, 0.0134],
[ 0.0004, 0.0022, -0.0319, 0.0015]])
2.weight tensor([[0.0111, 0.0065, 0.0038]])
下⾯使⽤常数来初始化权重参数。
for name, param in net.named_parameters():
if 'bias' in name:
init.constant_(param, val=0)
print(name, param.data)
0.bias tensor([0., 0., 0.])
2.bias tensor([0.])
如果只想对某个特定参数进⾏初始化,我们可以调用 Parameter 类的 initialize 函数,它与 Block 类提供的 initialize 函数的使⽤方法⼀致
5、自定义模型参数
有时候我们需要的初始化⽅法并没有在 init 模块中提供,这就需要我们自定义初始化函数,在这之前我们先来看看PyTorch是怎么实现这些初始化⽅法的,例如 torch.nn.init.normal\_
def init_weight_(tensor):
with torch.no_grad():
tensor.uniform_(-10, 10)
tensor *= (tensor.abs() >= 5).float()
可以看到这就是一个inplace改变 Tensor 值的函数,⽽且这个过程是不记录梯度的。类似的我们来实现⼀个⾃定义的初始化方法。在下面的例子里,我们令权重有⼀半概率初始化为0,有另一半概率初始化为[-10,-5] 和[5,10]两个区间⾥均匀分布的随机数
for name, param in net.named_parameters():
if 'weight' in name:
init_weight_(param)
print(name, param.data)
0.weight tensor([[-8.3413, -6.6506, 0.0000, 0.0000],
[-0.0000, -0.0000, 0.0000, -7.8356],
[-0.0000, -9.9840, 8.4777, -9.9372]])
2.weight tensor([[-0.0000, 7.0348, -0.0000]])
6、参数共享
在有些情况下,我们希望在多个层之间共享模型参数。Module 类的 forward 函数⾥多次调⽤同⼀个层。此外,如果我们传入 Sequential 的模块是同一个 Module 实例的话参数也是共享的,下⾯来看⼀个例子:
linear = nn.Linear(1, 1, bias=False)
net = nn.Sequential(linear, linear)
print(net)
for name, param in net.named_parameters():
init.constant_(param, val=3)
print(name, param.data)
Sequential(
(0): Linear(in_features=1, out_features=1, bias=False)
(1): Linear(in_features=1, out_features=1, bias=False)
)
0.weight tensor([[3.]])
在内存中,这两个线性层其实⼀一个对象:
print(id(net[0]) == id(net[1]))
print(id(net[0].weight) == id(net[1].weight))
True
True
因为模型参数⾥包含了梯度,所以在反向传播计算时,这些共享的参数的梯度是累加的:
x = torch.ones(1, 1)
y = net(x).sum()
print(y)
y.backward()
print(net[0].weight.grad)
tensor(9., grad_fn=)
tensor([[6.]])