李沐动手学深度学习V2-模型参数和梯度

  • Post author:
  • Post category:其他




1.定义网络模型

import torch
from torch import nn
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))
X = torch.rand(size=(2,4))
print(net)
输出结果:
Sequential(
 (0): Linear(in_features=4, out_features=8, bias=True)
(1): ReLU()
(2): Linear(in_features=8, out_features=1, bias=True)
)



2.查看每一层网络权重参数:

从已有模型中访问参数。 当通过Sequential类定义模型时, 我们可以通过下标索引来访问模型的任意层。 这就像模型是一个列表一样,列表里面元素即代表一个层,每个层包含这个层的权重参数信息。 如下所示,我们可以检查第三个层的参数

#当前网络有三层,两个Linear层和一个ReLU层
net[2].state_dict()
输出结果:
OrderedDict([('weight',
          tensor([[-0.3038,  0.3059, -0.2047,  0.2368,  0.1714,  0.2864,  0.3057, -0.1771]])),
           ('bias', tensor([-0.1689]))])
注意:输出结果表明这个全连接层包含两个参数, 并且结果是一个有序的字典类型,里面包含权重参数weight和偏差bias

3.获取目标层权重weight和偏差bias

print("bias.type = ",type(net[2].bias))#输出参数类型
print("bias = ",net[2].bias)#获取第三层bias参数,类型为Parameter
print("bias.data = ",net[2].bias.data)#类型为tensor格式
print("bias.data.item() = ",net[2].bias.data.item())#获取tensor里面元素值需要调用item()函数
print("weight = ",net[2].weight)#获取第三层weight参数,类型为Parameter
输出结果:
bias.type =  <class 'torch.nn.parameter.Parameter'>
bias =  Parameter containing:
tensor([-0.1689], requires_grad=True)
bias.data =  tensor([-0.1689])
bias.data.item() =  -0.16890332102775574
weight =  Parameter containing:
tensor([[-0.3038,  0.3059, -0.2047,  0.2368,  0.1714,  0.2864,  0.3057, -0.1771]],
   requires_grad=True)



3.查看网络每一层的梯度:

参数是复合的对象,包含

值、梯度和额外信息

。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度

net[2].weight.grad #查看第三层网络的梯度
print("查看计算梯度前的grad : ")
print(net[2].weight.grad == None)#刚开始网络还没有调用反向传播,因此参数的梯度处于初始状态,为None
y = torch.rand(2,1)
loss = nn.MSELoss()
l = loss(net(X),y.reshape(2,-1))
l.backward()
print("查看计算梯度后的grad : ")
print(net[2].weight.grad)
输出结果:
查看计算梯度前的grad : 
True
查看计算梯度后的grad : 
tensor([[ 0.0091, -0.0960, -0.0732,  0.0000, -0.1485,  0.0000,  0.0010,  0.0000]])



4.一次性查看网络所有参数:

print("第一种访问所有参数的方式:")
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
print("=====另外一种访问网络所有参数:=====")
print(net.state_dict())#类型为有序键值对dict
print("======打印第三层bias:======")
print(net.state_dict()['2.bias'])
输出结果:
=====访问网络所有参数:=====
OrderedDict([('0.weight', tensor([[-0.0043, -0.4935, -0.2520, -0.2440],
    [-0.2112,  0.2671, -0.3323, -0.1353],
    [-0.0596,  0.4947, -0.3870, -0.2382],
    [ 0.2701, -0.3518,  0.1539, -0.0712],
    [ 0.2908, -0.1608,  0.4329,  0.3725],
    [ 0.4162, -0.4409, -0.4309, -0.3289],
    [ 0.3932, -0.0667, -0.4542, -0.3637],
    [-0.3167, -0.1757, -0.4067,  0.2949]])), ('0.bias', tensor([ 0.4910,  0.4605,  0.2857, -0.3043,  0.1845,  0.0351,  0.2598, -0.0989])), ('2.weight', tensor([[ 0.1251,  0.1157, -0.0849,  0.0575,  0.0276, -0.0441,  0.0468,  0.1986]])), ('2.bias', tensor([0.2353]))])
======打印第三层bias:======
tensor([0.2353])



5.从嵌套块查看参数:

因为层是分层嵌套的,所以我们也可以像通过嵌套列表索引一样访问它们。 例如下面,我们访问第一个主要的块中、第二个子块的第一层的偏置项。

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())#block{i}是对添加的模块的名字标识,如果名字相同则会进行覆盖
    return net
rgnet = nn.Sequential(block2(),block1())
print("打印rgnet网络结构:")
print(rgnet)
print(rgnet(X))
print("查看第一个主块中第二个子块中第一个层的bias值:")
print(rgnet[0][1][0].bias.data)#跟嵌套列表访问下标方式一样
输出结果:
打印rgnet网络结构:
Sequential(
(0): Sequential(
(block0): 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()
)
(block1): 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()
)
(block2): 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()
)
(block3): 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): 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()
  )
)
tensor([[0.0000, 0.3053, 0.0000, 0.0000],
    [0.0000, 0.3054, 0.0000, 0.0000]], grad_fn=<ReluBackward0>)
查看第一个主块中第二个子块中第一个层的bias值:
tensor([ 0.0419,  0.0318, -0.3740, -0.1464,  0.0328, -0.4305,  0.4983, 	-0.1561])



6.模型参数初始化:

深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。

默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。 PyTorch的nn.init模块提供了多种预置初始化方法。



6.1内置初始化:

#内置初始化
print("网络手动初始化前的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias
def init_normal(m):
    if type(m) ==nn.Linear:
        nn.init.normal_(m.weight,mean=0,std=1)#权重参数初始化为均值为0,方差为1的高斯随机变量,正态分布
        nn.init.zeros_(m.bias)#将网络bias初始化为0
net.apply(init_normal)#将网络所有层递归调用init_normal()函数,如果当前层为线性层,则将其weight,bias参数进行初始化
print("网络手动初始化后的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias
输出结果:
网络手动初始化前的参数:
tensor([[ 0.1578, -0.1795,  0.4302,  0.3081],
    [-0.2809, -0.2261, -0.1772, -0.3692],
    [ 0.3474,  0.1296, -0.1798, -0.2431],
    [-0.3672, -0.1137, -0.4514, -0.4026],
    [ 0.4464, -0.4416, -0.0856, -0.1792],
    [-0.2553, -0.2837, -0.1460, -0.3735],
    [-0.3935, -0.4341, -0.3780, -0.4967],
    [-0.1182, -0.0212,  0.4872,  0.4944]])
tensor([-0.4315,  0.2152,  0.3420, -0.3812, -0.3065,  0.2562,  0.3562,  0.4229])
网络手动初始化后的参数:
tensor([[-0.2072, -0.1545,  0.3582,  0.6092],
    [ 1.6172,  0.0604, -0.0331, -1.7334],
    [-0.7343,  1.5221, -0.3575, -1.4513],
    [-1.2600, -0.5106,  1.2613,  0.3267],
    [ 2.0954, -0.5164,  0.5844, -0.1147],
    [-1.4974, -0.3811, -1.6760,  0.9083],
    [-0.2349, -2.1486,  0.1563, -0.9675],
    [ 0.8971,  1.3023,  1.2888,  2.3419]])
tensor([0., 0., 0., 0., 0., 0., 0., 0.])
#将线性网络所有参数初始化为给定的常数,比如初始化为1
print("网络手动初始化前的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias
def init_constant(m):
    if type(m) ==nn.Linear:
        nn.init.constant_(m.weight,1)#权重参数初始化为1
        nn.init.zeros_(m.bias)#将网络bias初始化为0
net.apply(init_normal)#将网络所有层递归调用init_normal()函数,如果当前层为线性层,则将其weight,bias参数进行初始化
print("网络手动初始化后的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias
输出结果:
网络手动初始化前的参数:
tensor([[ 1.8708e-01,  1.5800e-01, -2.1730e-01, -4.2157e-01],
    [ 3.1356e-01, -2.7407e-01,  3.4003e-01, -1.7509e-02],
    [ 4.1263e-01,  4.7126e-02, -9.5766e-02,  2.4069e-01],
    [-4.1355e-01,  4.9330e-01,  2.8744e-01, -5.1064e-02],
    [-1.9057e-01,  6.0020e-02, -3.1255e-02,  3.4372e-01],
    [ 6.1105e-02,  2.5677e-01,  7.3035e-02,  3.0843e-01],
    [-7.8338e-02, -3.6490e-04, -4.3265e-02,  9.1942e-02],
    [ 3.4140e-01, -6.1874e-03,  3.3147e-01,  3.8987e-02]])
tensor([ 0.0695, -0.2131,  0.2754,  0.3682,  0.3665, -0.2256,  0.4950,  0.4537])
网络手动初始化后的参数:
tensor([[1., 1., 1., 1.],
    [1., 1., 1., 1.],
    [1., 1., 1., 1.],
    [1., 1., 1., 1.],
    [1., 1., 1., 1.],
    [1., 1., 1., 1.],
    [1., 1., 1., 1.],
    [1., 1., 1., 1.]])
tensor([0., 0., 0., 0., 0., 0., 0., 0.])
#对不同层应用不同的初始化方法,比如:使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42
def init_xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
def init_constant_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight,42)   
net[0].apply(init_xavier)#对第一层使用xavier初始化参数
net[2].apply(init_constant_42)#对第三层使用常量初始化参数
print(net[0].weight.data)
print(net[2].weight.data)
输出结果:
tensor([[-0.1172, -0.0877, -0.2024, -0.6203],
    [-0.1364, -0.0685, -0.1863, -0.5243],
    [-0.5010,  0.2677,  0.1483,  0.0910],
    [ 0.2132,  0.1636,  0.4678, -0.0672],
    [ 0.0275, -0.6788,  0.1568,  0.1017],
    [ 0.6513, -0.5893,  0.4852, -0.4098],
    [ 0.3675, -0.3404,  0.1597,  0.4320],
    [-0.4341,  0.4389,  0.1168, -0.1841]])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])



6.2 自定义初始化参数:

有时,深度学习框架没有提供我们需要的初始化方法。 在下面的例子中,使用以下的分布为任意权重参数 𝑤 定义初始化方法,使用均匀分布初始后再判断初始化的参数绝对值是否大于5,是则保留,不是则变为0:





w

{

U

(

5

,

10

)

 可能性 

1

4

0

 可能性 

1

2

U

(

10

,

5

)

 可能性 

1

4

\begin{aligned} w \sim \begin{cases} U(5, 10) & \text{ 可能性 } \frac{1}{4} \\ 0 & \text{ 可能性 } \frac{1}{2} \\ U(-10, -5) & \text{ 可能性 } \frac{1}{4} \end{cases} \end{aligned}
















w




















































































U


(


5


,




1


0


)








0








U


(





1


0


,







5


)






























可能性

















4
















1






























可能性

















2
















1






























可能性

















4
















1





























































def my_init(m):
    if type(m) == nn.Linear:
        print("init",*[(name,parameters.shape) for name,parameters in m.named_parameters()][0]) 
        nn.init.uniform(m.weight,-10,10)
        m.weight.data *= m.weight.data.abs()>=5
net.apply(my_init)
net[0].weight[:2]
输出结果:
init weight torch.Size([8, 4])
init weight torch.Size([1, 8])
tensor([[-9.0073,  0.0000, -0.0000, -0.0000],
    [-5.1906, -0.0000, -0.0000, -6.4784]], grad_fn=<SliceBackward0>)

注意:我们可以直接设置参数:

net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]
#输出结果:tensor([42.0000,  9.0813,  1.0000, -5.7370])



7.共享(绑定)网络层参数:在多个层共享参数(绑定参数)

#共享层
shared_layer = nn.Linear(8,8)
#模型第三层和第五层都是同一个层shared_layer,都共享shared_layer层参数,指向(引用)shared_layer,当shared_layer参数发生改变时,该模型第三层和第五层参数也会跟着改变
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),shared_layer,nn.ReLU(),shared_layer,nn.ReLU(),nn.Linear(8,1))
net(X)
print(net[2].weight.data == net[4].weight.data)
net[2].weight.data[0,0] = 100 #当改变第三层参数时,第五层参数也会跟着改变
print(net[2].weight.data == net[4].weight.data)
输出结果:
tensor([[True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True]])
tensor([[True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True],
    [True, True, True, True, True, True, True, True]])


表明第三个和第五个神经网络层的参数是绑定的,即使改变第三层参数,第五层参数也会相应改变

。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。


思考:当参数绑定时,梯度会发生什么情况?



答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。



8.全部源代码:

import torch
from torch import nn

#1.定义网络模型
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))
X = torch.rand(size=(2,4))
y = torch.rand(2,1)
print("y = ",y)
print(net)
print(net[2].state_dict())

# 2.查看每一层网络权重参数:
print("bias.type = ",type(net[2].bias))#输出参数类型
print("bias = ",net[2].bias)#类型为Parameter
print("bias.data = ",net[2].bias.data)#类型为tensor格式
print("bias.data.item() = ",net[2].bias.data.item())#获取tensor里面元素值需要调用item()函数
print("weight = ",net[2].weight)

# 3.查看网络每一层的梯度:
print("查看计算梯度前的grad : ")
print(net[2].weight.grad == None)
y = torch.rand(2,1)
loss = nn.MSELoss()
l = loss(net(X),y.reshape(2,-1))
l.backward()
print("查看计算梯度后的grad : ")
print(net[2].weight.grad)

# 4.一次性查看网络所有参数:
print(*[(name,parameters.shape) for name,parameters in net[0].named_parameters()])
print(*[(name,parameters.shape) for name,parameters in net.named_parameters()])

print("=====访问网络所有参数:=====")
print(net.state_dict())#类型为有序键值对dict
print("======打印第三层bias:======")
print(net.state_dict()['2.bias'])


# 5.从嵌套块查看参数:
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())#block{i}是对添加的模块的名字标识,如果名字相同则会进行覆盖
    return net
rgnet = nn.Sequential(block2(),block1())
print("打印rgnet网络结构:")
print(rgnet)
print(rgnet(X))
print("查看第一个主块中第二个子块中第一个层的bias值:")
print(rgnet[0][1][0].bias.data)

# 6.模型参数初始化:
## 6.1内置初始化:
print("网络手动初始化前的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias
def init_normal(m):
    if type(m) ==nn.Linear:
        nn.init.normal_(m.weight,mean=0,std=1)#权重参数初始化为均值为0,方差为1的高斯随机变量,正态分布
        nn.init.zeros_(m.bias)#将网络bias初始化为0
net.apply(init_normal)#将网络所有层递归调用init_normal()函数,如果当前层为线性层,则将其weight,bias参数进行初始化
print("网络手动初始化后的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias

#将线性网络所有参数初始化为给定的常数,比如初始化为1
print("网络手动初始化前的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias
def init_constant(m):
    if type(m) ==nn.Linear:
        nn.init.constant_(m.weight,1)#权重参数初始化为1
        nn.init.zeros_(m.bias)#将网络bias初始化为0
net.apply(init_normal)#将网络所有层递归调用init_normal()函数,如果当前层为线性层,则将其weight,bias参数进行初始化
print("网络手动初始化后的参数:")
print(net[0].weight.data)#第一层的weight
print(net[0].bias.data)#第一层的bias


#对不同层应用不同的初始化方法,比如:使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42
def init_xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
def init_constant_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight,42)
net[0].apply(init_xavier)#对第一层使用xavier初始化参数
net[2].apply(init_constant_42)#对第三层使用常量初始化参数
print(net[0].weight.data)
print(net[2].weight.data)

## 6.2 自定义初始化参数:
def my_init(m):
    if type(m) == nn.Linear:
        print("init",*[(name,parameters.shape) for name,parameters in m.named_parameters()][0])
        nn.init.uniform(m.weight,-10,10)
        m.weight.data *= m.weight.data.abs()>=5
net.apply(my_init)
#直接对参数进行初始化
net[0].weight[:2]
net[0].weight.data[:]+=1
net[0].weight.data[0,0]=42
net[0].weight.data[0]


# 7.共享(绑定)网络层参数:在多个层共享参数(绑定参数)
shared_layer = nn.Linear(8,8)
#模型第三层和第五层都是同一个层shared_layer,都共享shared_layer层参数,指向(引用)shared_layer,当shared_layer参数发生改变时,该模型第三层和第五层参数也会跟着改变
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),shared_layer,nn.ReLU(),shared_layer,nn.ReLU(),nn.Linear(8,1))
net(X)
print(net[2].weight.data == net[4].weight.data)
net[2].weight.data[0,0] = 100 #当改变第三层参数时,第五层参数也会跟着改变
print(net[2].weight.data == net[4].weight.data)



版权声明:本文为flyingluohaipeng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。