神经网络 参数初始化方法

参数初始化方法

文章目录


参数初始化对模型具有较大的影响,不同的初始化方式可能会导致截然不同的结果。

PyTorch 的初始化方式并没有那么显然,如果你使用最原始的方式创建模型,那么你需要定义模型中的所有参数,当然这样你可以非常方便地定义每个变量的初始化方式,但是对于复杂的模型,这并不容易,而且我们推崇使用 Sequential 和 Module 来定义模型,所以这个时候我们就需要知道如何来自定义初始化方式。

1. 使用NumPy来初始化

因为 PyTorch 是一个非常灵活的框架,理论上能够对所有的 Tensor 进行操作,所以我们能够通过定义新的 Tensor 来初始化。

import numpy as np
import torch
from torch import nn


# 定义一个 Sequential 模型
net1 = nn.Sequential(
    nn.Linear(30, 40),
    nn.ReLU(),
    nn.Linear(40, 50),
    nn.ReLU(),
    nn.Linear(50, 10)
)
# 访问第一层的参数 用weight 和bias 来访问
w1 = net1[0].weight
b1 = net1[0].bias
print(type(w1)) # Parameter
<class 'torch.nn.parameter.Parameter'>
w1.data
tensor([[-0.0415, -0.0698, -0.0271,  ...,  0.1236,  0.1427, -0.0937],
        [ 0.1146, -0.1277,  0.0939,  ..., -0.1292,  0.1174,  0.0545],
        [-0.1797,  0.1133,  0.0326,  ...,  0.1709, -0.0763, -0.1533],
        ...,
        [-0.1759, -0.1023, -0.1474,  ..., -0.1568, -0.0180,  0.0122],
        [-0.0129, -0.1814,  0.0708,  ...,  0.0646, -0.1447,  0.0313],
        [ 0.0918, -0.0959, -0.1383,  ..., -0.1123,  0.0753,  0.1391]])
# 定义一个 Tensor 直接对其进行替换
net1[0].weight.data = torch.from_numpy(np.random.uniform(3, 5, size=(40, 30)))
print(net1[0].weight)
Parameter containing:
tensor([[4.7496, 3.6950, 4.2277,  ..., 4.5078, 4.5376, 4.3453],
        [3.9391, 3.9205, 3.2906,  ..., 3.6608, 3.1979, 3.6794],
        [4.7851, 4.5606, 3.0872,  ..., 4.9677, 4.7213, 4.1803],
        ...,
        [4.6278, 3.5423, 4.3622,  ..., 4.9560, 4.7099, 3.6241],
        [4.5235, 4.8335, 3.1930,  ..., 4.3652, 3.9845, 4.2987],
        [3.7621, 4.9450, 3.1637,  ..., 4.4203, 4.2376, 3.7132]],
       dtype=torch.float64, requires_grad=True)

可以看到这个参数的值已经被改变了,也就是说已经被定义成了我们需要的初始化方式,如果模型中某一层需要我们手动去修改,那么我们可以直接用这种方式去访问,但是更多的时候是模型中相同类型的层都需要初始化成相同的方式,这个时候一种更高效的方式是使用循环去访问,比如

for layer in net1:
    if isinstance(layer, nn.Linear): # 判断是否是线性层
        param_shape = layer.weight.shape
        layer.weight.data = torch.from_numpy(np.random.normal(0, 0.5, size=param_shape)) 
        # 定义为均值为 0,方差为 0.5 的正态分布

2. Xavier 初始化方法

我们给出这种初始化的公式

w   ∼   U n i f o r m [ − 6 n j + n j + 1 , 6 n j + n j + 1 ] w\ \sim \ Uniform[- \frac{\sqrt{6}}{\sqrt{n_j + n_{j+1}}}, \frac{\sqrt{6}}{\sqrt{n_j + n_{j+1}}}] w ∼ Uniform[−nj​+nj+1​ ​6 ​​,nj​+nj+1​ ​6 ​​]

其中 n j n_j nj​ 和 n j + 1 n_{j+1} nj+1​ 表示该层的输入和输出数目,所以请尝试实现以下这种初始化方式

对于 Module 的参数初始化,其实也非常简单,如果想对其中的某层进行初始化,可以直接像 Sequential 一样对其 Tensor 进行重新定义,其唯一不同的地方在于,如果要用循环的方式访问,需要介绍两个属性,children 和 modules

class sim_net(nn.Module):    
	def __init__(self):        
		super(sim_net, self).__init__()        
		self.l1 = nn.Sequential(            
		nn.Linear(30, 40),            
		nn.ReLU()        
			)        
		self.l1[0].weight.data = torch.randn(40, 30) # 直接对某一层初始化        
		self.l2 = nn.Sequential(            
		nn.Linear(40, 50),            
		nn.ReLU()        
			)        
		self.l3 = nn.Sequential(            
		nn.Linear(50, 10),            
		nn.ReLU()        
		)    
	def forward(self, x):        
		x = self.l1(x)        
		x =self.l2(x)       
	 	x = self.l3(x)        
	 	return x
net2 = sim_net()
# 访问 children
for i in net2.children():    
	print(i)
Sequential(  (0): Linear(in_features=30, out_features=40, bias=True)  (1): ReLU())Sequential(  (0): Linear(in_features=40, out_features=50, bias=True)  (1): ReLU())Sequential(  (0): Linear(in_features=50, out_features=10, bias=True)  (1): ReLU())
# 访问 modules
for i in net2.modules():    
	print(i)
sim_net(  (l1): Sequential(    (0): Linear(in_features=30, out_features=40, bias=True)    (1): ReLU()  )  (l2): Sequential(    (0): Linear(in_features=40, out_features=50, bias=True)    (1): ReLU()  )  (l3): Sequential(    (0): Linear(in_features=50, out_features=10, bias=True)    (1): ReLU()  ))Sequential(  (0): Linear(in_features=30, out_features=40, bias=True)  (1): ReLU())Linear(in_features=30, out_features=40, bias=True)ReLU()Sequential(  (0): Linear(in_features=40, out_features=50, bias=True)  (1): ReLU())Linear(in_features=40, out_features=50, bias=True)ReLU()Sequential(  (0): Linear(in_features=50, out_features=10, bias=True)  (1): ReLU())Linear(in_features=50, out_features=10, bias=True)ReLU()

children 只会访问到模型定义中的第一层,因为上面的模型中定义了三个 Sequential,所以只会访问到三个 Sequential,而 modules 会访问到最后的结构,比如上面的例子,modules 不仅访问到了 Sequential,也访问到了 Sequential 里面,这就对我们做初始化非常方便,比如

for layer in net2.modules():    
	if isinstance(layer, nn.Linear): #如果要判断两个类型是否相同推荐使用 isinstance()。        																
	param_shape = layer.weight.shape        
	layer.weight.data = torch.from_numpy(np.random.normal(0, 0.5, size=param_shape))

用列表举例:a=[1,2,[3,4]]

children返回:

1,2,[3,4]

modules返回:

[1,2,[3,4]], 1, 2, [3,4], 3, 4

3.torch.nn.init

因为 PyTorch 灵活的特性,我们可以直接对 Tensor 进行操作从而初始化,PyTorch 也提供了初始化的函数帮助我们快速初始化,就是 torch.nn.init,其操作层面仍然在 Tensor 上,

from torch.nn import initprint(net1[0].weight)
Parameter containing:tensor([[ 0.3859,  0.4535,  0.0696,  ..., -1.0227,  0.9353, -0.4104],        [-0.4560,  0.1488, -0.1437,  ...,  0.9651,  0.3467,  0.4422],        [ 0.0016,  0.0452, -0.5707,  ..., -1.0326,  0.1063,  0.2163],        ...,        [ 0.5122, -0.2351, -0.2402,  ..., -0.0511,  0.1905,  0.1106],        [-0.8786, -1.0855,  0.0846,  ..., -0.6484, -0.2868, -0.7436],        [-0.6684, -0.1849, -0.1377,  ..., -0.0652, -0.0663,  0.1641]],       dtype=torch.float64, requires_grad=True)
init.xavier_uniform_(net1[0].weight) # 这就是上面我们讲过的 Xavier 初始化方法,PyTorch 直接内置了其实现
Parameter containing:tensor([[ 0.1515, -0.2174,  0.1019,  ...,  0.1673, -0.1855,  0.1930],        [-0.1304,  0.0215, -0.1496,  ..., -0.2263,  0.0526,  0.1186],        [-0.2530,  0.2620,  0.1042,  ..., -0.2545, -0.0648, -0.1097],        ...,        [ 0.0597,  0.2584,  0.1990,  ..., -0.2716,  0.0210, -0.0741],        [ 0.1171, -0.1044, -0.2067,  ..., -0.0768,  0.1825, -0.2877],        [ 0.2399,  0.0216,  0.2085,  ..., -0.1675,  0.2450, -0.2347]],       dtype=torch.float64, requires_grad=True)
print(net1[0].weight)
Parameter containing:tensor([[ 0.1515, -0.2174,  0.1019,  ...,  0.1673, -0.1855,  0.1930],        [-0.1304,  0.0215, -0.1496,  ..., -0.2263,  0.0526,  0.1186],        [-0.2530,  0.2620,  0.1042,  ..., -0.2545, -0.0648, -0.1097],        ...,        [ 0.0597,  0.2584,  0.1990,  ..., -0.2716,  0.0210, -0.0741],        [ 0.1171, -0.1044, -0.2067,  ..., -0.0768,  0.1825, -0.2877],        [ 0.2399,  0.0216,  0.2085,  ..., -0.1675,  0.2450, -0.2347]],       dtype=torch.float64, requires_grad=True)

可以看到参数已经被修改了

torch.nn.init 为我们提供了更多的内置初始化方式,避免了我们重复去实现一些相同的操作

上面讲了两种初始化方式(后两种结合),其实它们的本质都是一样的,就是去修改某一层参数的实际值,而 torch.nn.init 提供了更多成熟的深度学习相关的初始化方式,非常方便

参考:参数初始化方法

上一篇:echarts - 柱状图


下一篇:【RTS笔记8】Discrete system