【pytorch】模型参数的访问 与 初始化

1、模型参数的访问

可以通过Module类的 parameters() 或者 named_parameters 方法来访问所有参数(以迭代器的形式返回),后者除了返回参数Tensor外还会返回其名字。

from torch import nn

net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1))  # pytorch已进行默认的参数初始化

print(net)
# Sequential(
#   (0): Linear(in_features=4, out_features=3, bias=True)
#   (1): ReLU()
#   (2): Linear(in_features=3, out_features=1, bias=True)
# )

print(type(net.named_parameters()))
# <class 'generator'>

named_parameters 方法来访问所有参数

for name, param in net.named_parameters():
    print(name, param.size())
    
# 0.weight torch.Size([3, 4])
# 0.bias torch.Size([3])
# 2.weight torch.Size([1, 3])
# 2.bias torch.Size([1])

parameters 方法来访问所有参数

for param in net.parameters():
    print(param, param.size())
    print('*'*20)
    
# Parameter containing: tensor([[ 0.2202, -0.2954,  0.4630, -0.1012],
#                               [ 0.2209, -0.4296, -0.3343, -0.4902],
#                               [ 0.2739,  0.2316,  0.4456, -0.2660]], requires_grad=True) 
# torch.Size([3, 4])
# ********************
# Parameter containing: tensor([ 0.0928,  0.4861, -0.1114], requires_grad=True) 
# torch.Size([3])
# ********************
# Parameter containing: tensor([[ 0.2844, -0.2298,  0.2219]], requires_grad=True) 
# torch.Size([1, 3])
# ********************
# Parameter containing:tensor([0.4926], requires_grad=True) 
# torch.Size([1])
# ********************

由上可见,通过 named_parameters 方法返回的名字,自动加上了层数的索引作为前缀。
下面 通过索引来访问网络某一层, 并获取该层的参数

# 通过索引来访问网络的任一层, 并获取该层的参数
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 的子类,相关特性,下面细说。


2、模型参数 torch.nn.parameter

torch.nn.parameter.Parameter: 是 Tensor 的子类,和 Tensor 不同的是:如果一个 Tensor是Parameter,那么它会自动被添加到模型的参数列表里,下面举例:

import torch
from torch import nn

class MyModel(nn.Module):
    def __init__(self, **kwargs):
        super(MyModel, self).__init__()
        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

因为Parameter是Tensor,即Tensor拥有的属性它都有,比如可以 根据data来访问参数数值,用grad来访问参数梯度

import torch
from torch import nn

net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1))  # pytorch已进行默认的参数初始化
print(net)

print('*'*20)

weight_0 = list(net[0].parameters())[0]
print(weight_0.data)
print(weight_0.grad)  # 反向传播前梯度为None

print('*'*20)

X = torch.rand(2, 4)
Y = net(X).sum()
Y.backward()
print(weight_0.grad)   # 反向传播后,计算出了参数梯度


# Sequential(
#   (0): Linear(in_features=4, out_features=3, bias=True)
#   (1): ReLU()
#   (2): Linear(in_features=3, out_features=1, bias=True)
# )
# ********************
# tensor([[ 0.1833,  0.1013,  0.4618, -0.2482],
#         [-0.4296,  0.2273, -0.1163,  0.0901],
#         [-0.0253,  0.3190,  0.3539, -0.0818]])
# None
# ********************
# tensor([[ 0.5202,  0.3802,  0.3237,  0.0833],
#         [ 0.0155,  0.0242,  0.0096,  0.0029],
#         [-0.0048, -0.0075, -0.0030, -0.0009]])

3、模型参数的初始化

PyTorch 的 torch.nn.init 模块里提供了多种预设的初始化方法

nn.init.uniform_(tensor, a= 0., b= 1.)
nn.init.normal_(tensor, mean= 0., std= 1.)
nn.init.constant_(tensor, val)
nn.init.ones_(tensor)
nn.init.zeros_(tensor)
nn.init.eye_(tensor)
nn.init.dirac_(tensor, groups=1)

在下面的例子中,我们将权重参数初始化成均值为0、标准差为0.01的正态分布随机数,并依然将偏差参数置为0。

import torch
from torch import nn

net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1)) 

for name, param in net.named_parameters():
    
    if 'weight' in name:
        nn.init.normal_(param, mean=0, std=0.01)
        print(name, param.data)

    if 'bias' in name:
        nn.init.constant_(param, val=0)
        print(name, param.data)

# 0.weight tensor([[ 1.0030e-03,  9.9017e-03,  1.5393e-03, -1.9146e-02],
#         [-1.7850e-02, -9.5327e-03, -9.7842e-03,  2.5997e-02],
#         [ 4.6419e-03, -8.4267e-03, -4.2336e-03,  9.3962e-05]])
# 0.bias tensor([0., 0., 0.])
# 2.weight tensor([[-0.0026, -0.0046,  0.0094]])
# 2.bias tensor([0.])

有时候我们需要的初始化方法并没有在init模块中提供。=
这时,我们就需要自己实现一个初始化方法,从而能够像使用其他初始化方法那样使用它。

4、自定义参数初始化方法

在自定义参数初始化方法之前,我们先来看看 PyTorch 是怎么实现这些初始化方法的,例如 torch.nn.init.normal_:

# 可以看到这就是一个 inplace 改变 Tensor 值的函数,而且这个过程是不记录梯度的。 
def normal_(tensor, mean=0, std=1):
    with torch.no_grad():
        return tensor.normal_(mean, std)

用于初始化参数

import torch
from torch import nn


def rewrite_normal_(tensor, mean=0., std=1.):
    with torch.no_grad():
        return tensor.normal_(mean, std)


net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1))  # pytorch已进行默认的参数初始化

for name, param in net.named_parameters():
    if 'weight' in name:
        rewrite_normal_(param, mean=0, std=0.01)
        print(name, param.grad, param.requires_grad)
        print(param.data)
        print('*'*20)


# 0.weight None True
# tensor([[ 7.3672e-03,  1.9321e-02, -8.8349e-05, -1.9147e-03],
#         [ 2.8177e-02,  3.1844e-03, -1.1902e-03,  1.2436e-02],
#         [ 6.2070e-04, -3.4600e-03,  2.7835e-03, -1.2652e-02]])
# ********************
# 2.weight None True
# tensor([[-0.0160, -0.0034,  0.0134]])
# ********************

在下面的例子里,我们令权重有一半概率初始化为 0,有另一半概率初始化为 [ − 10 , − 5 ] [−10,−5] [10,5] [ 5 , 10 ] [5,10] [5,10] 两个区间里均匀分布的随机数。

import torch
from torch import nn

def init_weight_(tensor):
    with torch.no_grad():
        tensor.uniform_(-10, 10)
        tensor *= (tensor.abs() >= 5).float()


net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1))
for name, param in net.named_parameters():
    if 'weight' in name:
        init_weight_(param)
        print(name, param.data)


# 0.weight tensor([[-8.2931,  0.0000,  6.2811, -0.0000],
#         [-0.0000,  5.2139, -6.8847, -0.0000],
#         [ 0.0000, -9.9403,  0.0000, -6.1358]])
# 2.weight tensor([[-5.6402, -7.9804, -9.5935]])

5、共享模型参数

在有些情况下,我们希望在多个层之间共享模型参数。

  • 方式一: 在 forward函数 里多次调用同一个层
  • 方式二: 传入 Sequential的模块 是同一个Module实例,参数也是共享的
import torch
from torch import nn

linear = nn.Linear(1, 1, bias=False)

net = nn.Sequential(linear, linear)
print(net)
# Sequential(
#   (0): Linear(in_features=1, out_features=1, bias=False)
#   (1): Linear(in_features=1, out_features=1, bias=False)
# )


for name, param in net.named_parameters():
    nn.init.constant_(param, val=3)
    print(name, param.data)
# 0.weight tensor([[3.]])


# 在内存中,这两个线性层其实一个对象:
print(id(net[0]) == id(net[1]))   # True
print(id(net[0].weight) == id(net[1].weight))   # True


x = torch.ones(1, 1)
y = net(x).sum()
print(y)   # tensor(9., grad_fn=<SumBackward0>)
y.backward()
print(net[0].weight.grad) # tensor([[6.]])    单次梯度是3,两次所以就是6

猜你喜欢

转载自blog.csdn.net/weixin_37804469/article/details/129129331