Pytorch神经网络参数管理方法详细讲解
作者:ting_qifengl
这里记录一下pytorch神经网络参数管理方法(参数访问、参数初始化、参数绑定),方便自己和需要的朋友学习、查阅。
一、参数访问
1.1 访问指定层的指定参数
首先构建一个多层感知机。
import torch from torch import nn net = nn.Sequential(nn.Linear(2, 4), nn.ReLU(), nn.Linear(4, 1)) X = torch.rand(size=(2, 2))
当通过nn.Sequential定义模型时, 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样,每层的参数都在其属性中。 如下所示,我们可以检查任意一个全连接层的参数。
# 1 查看网络第一层(即第一个全连接层)的参数 print(net[0].state_dict()) # 2 查看网络第三层(即第二个全连接层)偏置参数的类型 print(type(net[2].bias)) # 3 查看网络第三层(即第二个全连接层)偏置参数 print(net[2].bias) # 4 查看网络第三层(即第二个全连接层)偏置参数的值 print(net[2].bias.data) # 5 查看网络第一层(即第一个全连接层)权重参数 print(net[0].weight) # 6 查看网络第二层 print(net[1])
结果分别如下所示:
# 1 OrderedDict([('weight', tensor([[ 0.0854, 0.1861], [ 0.5421, 0.2435], [ 0.5745, 0.2469], [ 0.4120, -0.4345]])), ('bias', tensor([ 0.3356, 0.4215, 0.2181, -0.2548]))]) # 2 <class 'torch.nn.parameter.Parameter'> # 3 Parameter containing: tensor([-0.1606], requires_grad=True) # 4 tensor([-0.1606]) # 5 Parameter containing: tensor([[-0.4710, 0.0820], [-0.5563, 0.0728], [ 0.1691, 0.2211], [ 0.4279, -0.5597]], requires_grad=True) # 6 ReLU()
可以看出,每个参数都表示为参数类的一个实例,要对参数执行任何操作,首先需要访问底层的数值。网络层数从0开始,即net[0]表示网络第一层,激活函数也是网络中的一层。
访问偏置使用basis属性,访问权重使用weight属性。参数是复合的对象,包含值、梯度和额外信息。若只想获取参数的值,要在basis或weight后加data属性。除了值之外,我们还可以访问每个参数的梯度。
print(net[2].weight.grad == None) # 结果为 True # 原因:由于还没有调用反向传播,所以参数的梯度处于初始状态
1.2 访问某一层或整个网络的所有参数
当需要对所有参数执行操作时,逐个访问它们可能会很麻烦,此时我们可以通过递归整个树来提取每个子块的参数。
# 1 访问第一层的所有参数 print(*[(name, param.shape) for name, param in net[0].named_parameters()]) # 2 访问网络所有层的全部参数 print(*[(name, param.shape) for name, param in net.named_parameters()])
结果如下:
# 1
('weight', torch.Size([4, 2])) ('bias', torch.Size([4]))
# 2
('0.weight', torch.Size([4, 2])) ('0.bias', torch.Size([4])) ('2.weight', torch.Size([1, 4])) ('2.bias', torch.Size([1]))
注意:激活函数没有参数,所以打印出来的网络的所有参数只有两个全连接层的参数。
此外,我们还可以通过下述方式访问网络参数。
# 访问第网络第三层的偏置参数的值 print(net.state_dict()['2.bias'].data)
结果如下:
tensor([-0.1089])
如果不使用nn.Sequential定义模型,而是自己定义一个类实现网络,则不能使用索引访问指定层的参数。如下所示:
class mlp(nn.Module): def __init__(self, input_size, output_size): super().__init__() self.linear1 = nn.Linear(input_size, 4) # 全连接层 self.relu = nn.ReLU() self.linear2 = nn.Linear(4, output_size) # 全连接层 def forward(self, x): x = self.linear1(x) return self.relu(self.linear2(x)) mlp_net= mlp(2, 1) X = torch.rand(size=(2, 2)) # 如果使用索引访问会报错 print(mlp_net[0].state_dict())
此时会输出如下结果:
Traceback (most recent call last):
File "E:/SoftwareLearning/Python/Code/d2l-Train/ParameterManagement.py", line 46, in <module>
print(mlp_net[0].state_dict())
TypeError: 'mlp' object is not subscriptable
但可以通过下述方式进行访问:
# linear2为定义网络模型时自己起的某一全连接层的名称 print(mlp_net.state_dict()['linear2.bias'].data)
输出结果如下:
tensor([0.3709])
1.3 访问嵌套块的指定参数
首先构建一个嵌套块的网络。
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)) # 查看网络结构 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)
)
因为是分层嵌套的,所以我们也可以像通过嵌套列表索引一样访问它们。下面,我们访问第一个主要的块中第二个子块的第一层的权重项。
print(rgnet[0][1][0].weight.data)
输出结果如下:
tensor([[ 0.3175, 0.0233, 0.3233, -0.0627],
[-0.0835, -0.3371, -0.4527, 0.0141],
[ 0.1070, 0.3952, 0.4051, 0.3921],
[ 0.1958, -0.3643, 0.4481, -0.3448],
[ 0.0446, -0.0256, 0.1490, 0.4568],
[-0.1352, -0.2099, -0.1225, -0.0413],
[ 0.3027, 0.2114, -0.4063, -0.0288],
[-0.4594, 0.0076, -0.2671, 0.2669]])
二、参数初始化
初始化对神经网络来说十分重要,良好的初始化能帮助模型快速收敛,对保持网络的数值稳定性至关重要。默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵,这个范围是根据输入和输出维度计算出的。PyTorch的nn.init
模块提供了多种预置初始化方法。
2.1 内置初始化
首先调用内置的初始化器对网络参数进行初始化。下面的代码将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0。
# 将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0 def init_normal(m): if type(m) == nn.Linear: # 将权重参数初始化为标准差为0.01的高斯随机变量 nn.init.normal_(m.weight, mean=0, std=0.01) # 将偏置参数初始化为0 nn.init.zeros_(m.bias) net.apply(init_normal) print(net[0].weight.data[0], '\n', net[0].bias.data[0])
输出结果如下:
tensor([-0.0142, -0.0054])
tensor(0.)
我们还可以将所有参数初始化为给定的常数,比如初始化为1。
# 将所有参数初始化为给定的常数 def init_constant(m): if type(m) == nn.Linear: nn.init.constant_(m.weight, 1) nn.init.zeros_(m.bias) net.apply(init_constant) print(net[0].weight.data[0], net[0].bias.data[0])
输出结果如下:
tensor([1., 1.])
tensor(0.)
我们还可以对不同层应用不同的初始化方法。 例如,下面我们使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42。
def 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) # 使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42 net[0].apply(xavier) net[2].apply(init_42) print(net[0].weight.data[0]) print(net[2].weight.data)
输出结果如下:
tensor([ 0.9265, -0.1521])
tensor([[42., 42., 42., 42.]])
2.2 自定义初始化
有时,深度学习框架没有提供我们需要的初始化方法,此时就需要我们自定义初始化方法实现参数初始化。
# 自定义初始化 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) # 均匀分布 # 如果绝对值大于5则参数不变,如果绝对值小于5则将参数置0 m.weight.data *= m.weight.data.abs() >= 5 net.apply(my_init) print(net[0].weight[:2])
输出结果如下:
Init weight torch.Size([4, 2])
Init weight torch.Size([1, 4])
tensor([[ 6.6987, -5.3545],
[ 6.6684, -6.3039]], grad_fn=<SliceBackward0>)
也可以根据需要直接对指定层的参数进行设置。
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
print(net[0].weight.data[0])
输出结果如下:
Init weight torch.Size([4, 2])
Init weight torch.Size([1, 4])
tensor([42.0000, 8.5777])
三、参数绑定
有时我们希望在多个层间共享参数,此时,我们可以定义一个全连接层,然后使用它的参数来设置另一个层的参数,实现参数共享。
shared = nn.Linear(8, 8) net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(8, 1)) print(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([[0.4362],
[0.4562]], grad_fn=<AddmmBackward0>)
tensor([True, True, True, True])
tensor([True, True, True, True])
这个例子表明第三个和第五个神经网络层的参数是绑定的。它们不仅值相等,而且由相同的张量表示。因此,如果我们改变其中一个参数,另一个参数也会改变。你可能会思考:当参数绑定时,梯度会发生什么情况? 答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。
四、全部测试代码
全部测试代码如下。
import torch from torch import nn class mlp(nn.Module): def __init__(self, input_size, output_size): super().__init__() self.linear1 = nn.Linear(input_size, 4) # 全连接层 self.relu = nn.ReLU() self.linear2 = nn.Linear(4, output_size) # 全连接层 def forward(self, x): x = self.linear1(x) return self.relu(self.linear2(x)) 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 net = nn.Sequential(nn.Linear(2, 4), nn.ReLU(), nn.Linear(4, 1)) X = torch.rand(size=(2, 2)) print(net(X)) # 当通过Sequential类定义模型时,可以通过索引来访问模型的任意层。这就像模型是一个列表一样,每层的参数都在其属性中。 print(net[0].state_dict()) print(type(net[2].bias)) print(net[2].bias) print(net[2].bias.data) print(net[0].weight) print(net[1]) print(net[2].weight.grad == None) # 一次性访问所有参数 print(*[(name, param.shape) for name, param in net[0].named_parameters()]) print(*[(name, param.shape) for name, param in net.named_parameters()]) # 访问网络参数的另一种方式 print(net.state_dict()['2.bias'].data) # 不使用nn.Sequential定义模型 mlp_net = mlp(2, 1) X = torch.rand(size=(2, 2)) print(mlp_net(X)) # 不能使用索引访问某一层的参数,会报错 # print(mlp_net[0].state_dict()) print(mlp_net.state_dict()['linear2.bias'].data) # 从嵌套块收集参数 rgnet = nn.Sequential(block2(), nn.Linear(4, 1)) print(rgnet) # 访问第一个主要的块中、第二个子块的第一层的权重 print(rgnet[0][1][0].weight.data) # 参数初始化 # 将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0 def init_normal(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, mean=0, std=0.01) nn.init.zeros_(m.bias) # 将所有参数初始化为给定的常数 def init_constant(m): if type(m) == nn.Linear: nn.init.constant_(m.weight, 1) nn.init.zeros_(m.bias) net.apply(init_normal) print(net[0].weight.data[0], '\n', net[0].bias.data[0]) net.apply(init_constant) print(net[0].weight.data[0], '\n', net[0].bias.data[0]) def 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) # 使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42 net[0].apply(xavier) net[2].apply(init_42) print(net[0].weight.data[0]) print(net[2].weight.data) # 自定义初始化 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) print(net[0].weight[:2]) # 也可以直接设置参数 net[0].weight.data[:] += 1 net[0].weight.data[0, 0] = 42 print(net[0].weight.data[0]) # 参数绑定 # 有时我们希望在多个层间共享参数:我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数 # 我们需要给共享层一个名称,以便可以引用它的参数 shared = nn.Linear(4, 4) net = nn.Sequential(nn.Linear(2, 4), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(4, 1)) print(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])
到此这篇关于Pytorch神经网络参数管理方法详细讲解的文章就介绍到这了,更多相关Pytorch神经网络参数管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!