双通网络网站建设价格,新楼盘网站模板,wordpress 所属分类,相册 wordpress注#xff1a;本文为《动手学深度学习》开源内容#xff0c;部分标注了个人理解#xff0c;仅为个人学习记录#xff0c;无抄袭搬运意图 4.2 模型参数的访问、初始化和共享
在3.3节#xff08;线性回归的简洁实现#xff09;中#xff0c;我们通过init模块来初始化模型… 注本文为《动手学深度学习》开源内容部分标注了个人理解仅为个人学习记录无抄袭搬运意图 4.2 模型参数的访问、初始化和共享
在3.3节线性回归的简洁实现中我们通过init模块来初始化模型的参数。我们也介绍了访问模型参数的简单方法。本节将深入讲解如何访问和初始化模型参数以及如何在多个层之间共享同一份模型参数。
我们先定义一个与上一节中相同的含单隐藏层的多层感知机。我们依然使用默认方式初始化它的参数并做一次前向计算。与之前不同的是在这里我们从nn中导入了init模块它包含了多种模型初始化方法。
import torch
from torch import nn
from torch.nn import initnet 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_features4, out_features3, biasTrue)(1): ReLU()(2): Linear(in_features3, out_features1, biasTrue)
)4.2.1 访问模型参数
回忆一下上一节中提到的Sequential类与Module类的继承关系。对于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):passn 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) # 反向传播前梯度为None
Y.backward()
print(weight_0.grad)输出
tensor([[ 0.2719, -0.0898, -0.2462, 0.0655],[-0.4669, -0.2703, 0.3230, 0.2067],[-0.2708, 0.1171, -0.0995, 0.3913]])
None
tensor([[-0.2281, -0.0653, -0.1646, -0.2569],[-0.1916, -0.0549, -0.1382, -0.2158],[ 0.0000, 0.0000, 0.0000, 0.0000]])4.2.2 初始化模型参数
我们在3.15节数值稳定性和模型初始化中提到了PyTorch中nn.Module的模块参数都采取了较为合理的初始化策略不同类型的layer具体采样的哪一种初始化方法的可参考源代码。但我们经常需要使用其他方法来初始化权重。PyTorch的init模块里提供了多种预设的初始化方法。在下面的例子中我们将权重参数初始化成均值为0、标准差为0.01的正态分布随机数并依然将偏差参数清零。
for name, param in net.named_parameters():if weight in name:init.normal_(param, mean0, std0.01)print(name, param.data)输出
0.weight tensor([[ 0.0030, 0.0094, 0.0070, -0.0010],[ 0.0001, 0.0039, 0.0105, -0.0126],[ 0.0105, -0.0135, -0.0047, -0.0006]])
2.weight tensor([[-0.0074, 0.0051, 0.0066]])下面使用常数来初始化权重参数。
for name, param in net.named_parameters():if bias in name:init.constant_(param, val0)print(name, param.data)输出
0.bias tensor([0., 0., 0.])
2.bias tensor([0.])4.2.3 自定义初始化方法
有时候我们需要的初始化方法并没有在init模块中提供。这时可以实现一个初始化方法从而能够像使用其他初始化方法那样使用它。在这之前我们先来看看PyTorch是怎么实现这些初始化方法的例如torch.nn.init.normal_
def normal_(tensor, mean0, std1):with torch.no_grad():return tensor.normal_(mean, std)可以看到这就是一个inplace改变Tensor值的函数而且这个过程是不记录梯度的。 类似的我们来实现一个自定义的初始化方法。在下面的例子里我们令权重有一半概率初始化为0有另一半概率初始化为 [ − 10 , − 5 ] [-10,-5] [−10,−5]和 [ 5 , 10 ] [5,10] [5,10]两个区间里均匀分布的随机数。
def init_weight_(tensor):with torch.no_grad():tensor.uniform_(-10, 10)tensor * (tensor.abs() 5).float()for name, param in net.named_parameters():if weight in name:init_weight_(param)print(name, param.data)输出
0.weight tensor([[ 7.0403, 0.0000, -9.4569, 7.0111],[-0.0000, -0.0000, 0.0000, 0.0000],[ 9.8063, -0.0000, 0.0000, -9.7993]])
2.weight tensor([[-5.8198, 7.7558, -5.0293]])此外参考2.3.2节我们还可以通过改变这些参数的data来改写模型参数值同时不会影响梯度:
for name, param in net.named_parameters():if bias in name:param.data 1print(name, param.data)输出
0.bias tensor([1., 1., 1.])
2.bias tensor([1.])4.2.4 共享模型参数
在有些情况下我们希望在多个层之间共享模型参数。4.1.3节提到了如何共享模型参数: Module类的forward函数里多次调用同一个层。此外如果我们传入Sequential的模块是同一个Module实例的话参数也是共享的下面来看一个例子:
linear nn.Linear(1, 1, biasFalse)
net nn.Sequential(linear, linear)
print(net)
for name, param in net.named_parameters():init.constant_(param, val3)print(name, param.data)输出
Sequential((0): Linear(in_features1, out_features1, biasFalse)(1): Linear(in_features1, out_features1, biasFalse)
)
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) # 单次梯度是3两次所以就是6输出:
tensor(9., grad_fnSumBackward0)
tensor([[6.]])小结
有多种方法来访问、初始化和共享模型参数。可以自定义初始化方法。 注本节与原书此节有一些不同原书传送门 4.3 模型参数的延后初始化
由于使用Gluon创建的全连接层的时候不需要指定输入个数。所以当调用initialize函数时由于隐藏层输入个数依然未知系统也无法得知该层权重参数的形状。只有在当形状已知的输入X传进网络做前向计算net(X)时系统才推断出该层的权重参数形状为多少此时才进行真正的初始化操作。但是使用PyTorch在定义模型的时候就要指定输入的形状所以也就不存在这个问题了所以本节略。有兴趣的可以去看看原文传送门。