天天看点

权值初始化1、梯度消失与梯度爆炸2、全连接网络权值初始化总结

权值初始化

  • 1、梯度消失与梯度爆炸
  • 2、全连接网络权值初始化
    • 3. 具有激活函数时的权值初始化
    • 4. Xavier初始化
    • 5. Kaiming初始化
    • 6. Pytorch提供的初始化方法
  • 总结

1、梯度消失与梯度爆炸

梯度消失与梯度爆炸的产生是权值与激活函数相互作用的共同结果。针对一个神经网络:

权值初始化1、梯度消失与梯度爆炸2、全连接网络权值初始化总结

H 2 = H 1 ∗ W 2 H_{2} = H_{1}*W_{2} H2​=H1​∗W2​

∂ L o s s ∂ W 2 = ∂ L o s s ∂ o u t ∗ ∂ o u t ∂ H 2 ∗ ∂ H 2 ∂ W 2 = ∂ L o s s ∂ o u t ∗ ∂ o u t ∂ H 2 ∗ H 1 \frac{\partial Loss}{\partial W_{2}}= \frac{\partial Loss}{\partial out}* \frac{\partial out}{\partial H_{2}}* \frac{\partial H_{2}}{\partial W_{2}} =\frac{\partial Loss}{\partial out}* \frac{\partial out}{\partial H_{2}}*H_{1} ∂W2​∂Loss​=∂out∂Loss​∗∂H2​∂out​∗∂W2​∂H2​​=∂out∂Loss​∗∂H2​∂out​∗H1​

梯 度 消 失 : H 1 → 0 ⇒ ∂ L o s s ∂ W 2 → 0 梯度消失:H_{1}\rightarrow0 \Rightarrow\frac{\partial Loss}{\partial W_{2}}\rightarrow0 梯度消失:H1​→0⇒∂W2​∂Loss​→0

梯 度 爆 炸 : H 1 → ∞ ⇒ ∂ L o s s ∂ W 2 → 0 梯度爆炸:H_{1}\rightarrow\infty \Rightarrow\frac{\partial Loss}{\partial W_{2}}\rightarrow0 梯度爆炸:H1​→∞⇒∂W2​∂Loss​→0

公式书写参考:

https://www.cnblogs.com/lcchuguo/p/5061692.html

梯度消失或者梯度爆炸会导致模型无法训练,从公式求解角度来看,要想避免此类问题的产生,就要严格控制网络输出层输出值的尺度范围,就是让每个网络输出值不能太大也不能太小

2、全连接网络权值初始化

代码如下(示例):

class MLP(nn.Module):
    def __init__(self, neural_num, layers):
        super(MLP, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
        self.neural_num = neural_num

    def forward(self, x):
        for (i, linear) in enumerate(self.linears):
            x = linear(x)
            x = torch.relu(x)

            print("layer:{}, std:{}".format(i, x.std()))
            if torch.isnan(x.std()):
                print("output is nan in {} layers".format(i))
                break
        return x
	 def initialize(self):#权值初始化函数
        for m in self.modules():#对每个模块进行for循环
            if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
            	nn.init.normal_(m.weight.data)
layer_nums = 100
neural_nums = 256
batch_size = 16

net = MLP(neural_nums, layer_nums)
net.initialize()

inputs = torch.randn((batch_size, neural_nums))  # normal: mean=0, std=1,随机的输入,采用标准正态分布

output = net(inputs)
print(output)

#运行结果:
layer:0, std:9.352246284484863
layer:1, std:112.47123718261719
layer:2, std:1322.8056640625
layer:3, std:14569.42578125
layer:4, std:154672.765625
layer:5, std:1834038.25
layer:6, std:18807982.0
layer:7, std:209553056.0
layer:8, std:2637502976.0
layer:9, std:32415455232.0
layer:10, std:374825549824.0
layer:11, std:3912853356544.0
layer:12, std:41235926482944.0
layer:13, std:479620541448192.0
layer:14, std:5320927608832000.0
layer:15, std:5.781226125891994e+16
layer:16, std:7.022147146707108e+17
layer:17, std:6.994718592201654e+18
layer:18, std:8.473500708665033e+19
layer:19, std:9.339794309346954e+20
layer:20, std:9.56936220412742e+21
layer:21, std:1.1762745601866065e+23
layer:22, std:1.482641490484093e+24
layer:23, std:1.6921342454001848e+25
layer:24, std:1.9741449097941338e+26
layer:25, std:2.1257213262324592e+27
layer:26, std:2.191710967109107e+28
layer:27, std:2.525450570646784e+29
layer:28, std:3.221309179111329e+30
layer:29, std:3.5309529208927896e+31
layer:30, std:4.5253540317472455e+32
layer:31, std:4.7150121712384475e+33
layer:32, std:5.36959116472917e+34
layer:33, std:inf
layer:34, std:inf
layer:35, std:nan
output is nan in 35 layers
tensor([[3.2626e+36, 0.0000e+00, 7.2932e+37,  ..., 0.0000e+00, 0.0000e+00,
         2.5465e+38],
        [3.9237e+36, 0.0000e+00, 7.5033e+37,  ..., 0.0000e+00, 0.0000e+00,
         2.1274e+38],
        [0.0000e+00, 0.0000e+00, 4.4932e+37,  ..., 0.0000e+00, 0.0000e+00,
         1.7016e+38],
        ...,
        [0.0000e+00, 0.0000e+00, 2.4222e+37,  ..., 0.0000e+00, 0.0000e+00,
         2.5295e+38],
        [4.7380e+37, 0.0000e+00, 2.1580e+37,  ..., 0.0000e+00, 0.0000e+00,
         2.6028e+38],
        [0.0000e+00, 0.0000e+00, 6.0878e+37,  ..., 0.0000e+00, 0.0000e+00,
         2.1695e+38]], grad_fn=<ReluBackward0>)
           

在这里,利用标准差来表明每一层输出参数之间的变化情况。以上示例代码表明网络层输出的标准差不断增大,直到layer:33之后,超出了计算机的表示范围。这是因为:

设 E ( X ) = E ( Y ) = 0 , D ( X ) = D ( Y ) = 1 设E(X) = E(Y)=0,D(X)=D(Y)=1 设E(X)=E(Y)=0,D(X)=D(Y)=1

则: D ( X ∗ Y ) = D ( X ) ∗ D ( Y ) D(X*Y) = D(X)*D(Y) D(X∗Y)=D(X)∗D(Y)

对于图片中第一个隐藏层的第一个神经元,有:

H 11 = ∑ i = 0 n X i ∗ W 1 i H_{11} =\sum_{i=0}^n X_{i}*W_{1i} H11​=i=0∑n​Xi​∗W1i​

D ( H 11 ) = ∑ i = 0 n D ( X i ) ∗ D ( W 1 i ) = n ∗ 1 ∗ 1 = n D(H_{11} )=\sum_{i=0}^nD(X_{i})*D(W_{1i})=n*1*1=n D(H11​)=i=0∑n​D(Xi​)∗D(W1i​)=n∗1∗1=n

s t d ( H 11 ) = D ( H 11 ) = n std(H_{11} ) =\sqrt D(H_{11} )= \sqrt n std(H11​)=D

​(H11​)=n

•由此可见每层网络输出的方差相对于前一层扩大了n倍

其中n代表每一个网络神经元个数。

•输出层的方差由三个因素决定:网络层神经元个数n,输入神经元方差,网络层权值的方差。

故若想要每一个网络层输出方差尺度保持不变,就应该令方差等于1。公式上:

令 D ( H 1 ) = n ∗ D ( X ) ∗ D ( W ) = 1 令D(H_{1})= n*D(X)*D(W) = 1 令D(H1​)=n∗D(X)∗D(W)=1

D ( W ) = 1 n ⇒ s t d ( W ) = 1 n D(W) =\frac {1}{n}\Rightarrow std(W)=\sqrt {\frac{1}{n}} D(W)=n1​⇒std(W)=n1​

在代码中,只需要将上面代码的初始化代码改为:

nn.init.normal_(m.weight.data,std=np.sqrt(1/self.neural_num))

#运行结果为:
layer:0, std:0.584515392780304
layer:1, std:0.4393407702445984
layer:2, std:0.3229506015777588
layer:3, std:0.22231179475784302
layer:4, std:0.147507444024086
layer:5, std:0.10931719839572906
layer:6, std:0.07006519287824631
layer:7, std:0.04879037290811539
layer:8, std:0.03838071972131729
layer:9, std:0.029481684789061546
layer:10, std:0.021306365728378296
layer:11, std:0.013901247642934322
layer:12, std:0.009156215004622936
layer:13, std:0.006656072102487087
layer:14, std:0.004615169018507004
layer:15, std:0.003134008962661028
layer:16, std:0.002379195997491479
layer:17, std:0.0014811892760917544
layer:18, std:0.001121458481065929
layer:19, std:0.0007725696777924895
layer:20, std:0.0004947244306094944
layer:21, std:0.0003800748090725392
layer:22, std:0.000299417064525187
layer:23, std:0.00021357736841309816
layer:24, std:0.0001557325740577653
layer:25, std:0.00010480615310370922
layer:26, std:6.75373084959574e-05
layer:27, std:4.8638408770784736e-05
layer:28, std:3.877509880112484e-05
layer:29, std:2.656393735378515e-05
layer:30, std:2.1278112399159e-05
...
           

3. 具有激活函数时的权值初始化

以sigmoid和tanh函数为例,sigmoid函数及其导数图像如下:

权值初始化1、梯度消失与梯度爆炸2、全连接网络权值初始化总结

tanh函数及其导数图像如下:

权值初始化1、梯度消失与梯度爆炸2、全连接网络权值初始化总结

诸如sigmoid函数和tanh函数,由于其梯度基本都小于1,在构建模型的过程中容易产生梯度消失问题

在上面代码中的forward函数模块加入非线性激活函数:

def forward(self, x):
	for (i, linear) in enumerate(self.linears):
		x = linear(x)
		x = torch.tanh(x)#加入tanh函数作为激活函数

#运行结果(数据越来越小,并逐渐导致梯度的消失):
layer:1, std:0.48910173773765564
layer:2, std:0.4099564850330353
layer:3, std:0.35637012124061584
layer:4, std:0.32117360830307007
layer:5, std:0.2981105148792267
layer:6, std:0.27730831503868103
layer:7, std:0.2589356303215027
layer:8, std:0.2468511462211609
layer:9, std:0.23721906542778015
layer:10, std:0.22171513736248016
...
layer:90, std:0.07225216180086136
layer:91, std:0.0712454691529274
layer:92, std:0.07088855654001236
layer:93, std:0.0730612725019455
layer:94, std:0.07276969403028488
layer:95, std:0.07259569317102432
layer:96, std:0.0758652538061142
layer:97, std:0.07769152522087097
layer:98, std:0.07842093706130981
layer:99, std:0.08206242322921753
           

4. Xavier初始化

在具有饱和激活函数的网络中,Xavier初始化可以有效解决梯度消失的问题。

方差一致性:保持数据尺度维持在恰当范围,通常方差为1

针对于前向传播和反向传播,有以下公式:

n i ∗ D ( W ) = 1 n_{i}*D(W) = 1 ni​∗D(W)=1

n i + 1 ∗ D ( W ) = 1 n_{i+1}*D(W) = 1 ni+1​∗D(W)=1

⇒ D ( W ) = 2 n i + n i + 1 ( 1 ) \Rightarrow D(W) = \frac{2}{n_{i}+n_{i+1}}\qquad (1) ⇒D(W)=ni​+ni+1​2​(1)

n i 表 示 输 入 神 经 元 个 数 , n i + 1 表 示 输 出 神 经 元 个 数 n_{i}表示输入神经元个数,n_{i+1}表示输出神经元个数 ni​表示输入神经元个数,ni+1​表示输出神经元个数

通常,Xavie采用均匀分布:

W 服 从 于 U [ − a , a ] W 服从于 U[-a,a] W服从于U[−a,a]

D ( W ) = ( − a − a ) 2 12 = a 2 3 ( 2 ) D(W)=\frac{(-a-a)^2}{12}=\frac{a^2}{3}\qquad (2) D(W)=12(−a−a)2​=3a2​(2)

联立(1),(2)

⇒ a = 6 ( n i + n i + 1 ) \Rightarrow a = \frac {\sqrt 6}{\sqrt (n_{i}+n_{i+1})} ⇒a=(

​ni​+ni+1​)6

​​

⇒ W 服 从 于 U [ − 6 ( n i + n i + 1 ) , 6 ( n i + n i + 1 ) ] \Rightarrow W 服从于 U[-\frac {\sqrt 6}{\sqrt (n_{i}+n_{i+1})},\frac {\sqrt 6}{\sqrt (n_{i}+n_{i+1})}] ⇒W服从于U[−(

​ni​+ni+1​)6

​​,(

​ni​+ni+1​)6

​​]

针对于以上程序,仅对初始化部分程序进行修改:

(1)通过手动计算编写程序:

class MLP(nn.Module):
    def __init__(self, neural_num, layers):
        super(MLP, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
        self.neural_num = neural_num

    def forward(self, x):
        for (i, linear) in enumerate(self.linears):
            x = linear(x)
            #x = torch.relu(x)
            x = torch.tanh(x)

            print("layer:{}, std:{}".format(i, x.std()))
            if torch.isnan(x.std()):
                print("output is nan in {} layers".format(i))
                break

        return x

    def initialize(self):#权值初始化函数
        for m in self.modules():#对每个模块进行for循环
            if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
                # nn.init.normal_(m.weight.data, std=np.sqrt(1/self.neural_num))    # normal: mean=0, 权值的标准差std = 1/n,每个网络层输出的标准差都是1

                a = np.sqrt(6 / (self.neural_num + self.neural_num))#权值的上下限

                tanh_gain = nn.init.calculate_gain('tanh')#计算tanh的增益
                a *= tanh_gain

                nn.init.uniform_(m.weight.data, -a, a)#均匀分布,设置下限是-a,上限是a,来初始化权值


#运行结果:
layer:0, std:0.7571136355400085
layer:1, std:0.6924336552619934
layer:2, std:0.6677976846694946
layer:3, std:0.6551960110664368
layer:4, std:0.655646800994873
layer:5, std:0.6536089777946472
layer:6, std:0.6500504612922668
layer:7, std:0.6465446949005127
layer:8, std:0.645668625831604
layer:9, std:0.6414617896080017
layer:10, std:0.6423627734184265
...
layer:90, std:0.6536460518836975
layer:91, std:0.6497945785522461
layer:92, std:0.6458892226219177
layer:93, std:0.6458885669708252
layer:94, std:0.6530362963676453
layer:95, std:0.6515855193138123
layer:96, std:0.643466055393219
layer:97, std:0.6426210403442383
layer:98, std:0.6407480835914612
layer:99, std:0.6442216038703918
           

(2)调用pytorch提供的Xavier方法(l两种方法计算结果基本一致):

def initialize(self):#权值初始化函数
	for m in self.modules():#对每个模块进行for循环
		if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
			tanh_gain = nn.init.calculate_gain('tanh')#计算tanh的增益
			nn.init.xavier_uniform_(m.weight.data, gain=tanh_gain)#调用pytorch提供的Xavier方法
           

5. Kaiming初始化

后期,非饱和激活函数,ReLu函数被广泛使用,但ReLu函数同样有梯度增长快的问题。可以通过在上述代码的forward模块加入relu观察结果。

针对于ReLu函数的问题,后提出Kaiming初始化方法。

Kaiming初始化方法针对的激活函数:ReLU及其变种

遵循的原则:方差一致性,即保持数据尺度维持在恰当范围,通常方差为1

公式实现:

D ( W ) = 2 n i D(W) = \frac{2}{n_{i}} D(W)=ni​2​

针对于ReLu函数的变种:

D ( W ) = 2 ( 1 + a 2 ) ∗ n i D(W) = \frac{2}{(1+a^2)*n_{i}} D(W)=(1+a2)∗ni​2​

其 中 a 代 表 R e L u 变 种 函 数 在 负 半 轴 的 斜 率 , n i 代 表 输 入 神 经 元 个 数 其中a代表ReLu变种函数在负半轴的斜率,n_{i}代表输入神经元个数 其中a代表ReLu变种函数在负半轴的斜率,ni​代表输入神经元个数

因此权值的标准差为:

s t d ( W ) = 2 ( 1 + a 2 ) ∗ n i std(W) =\sqrt \frac{2}{(1+a^2)*n_{i}} std(W)=(1+a2)∗ni​2​

#1:采用最初全连接神经网络计算公式的方法
class MLP(nn.Module):
    def __init__(self, neural_num, layers):
        super(MLP, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
        self.neural_num = neural_num

    def forward(self, x):
        for (i, linear) in enumerate(self.linears):
            x = linear(x)
            x = torch.relu(x)         
            print("layer:{}, std:{}".format(i, x.std()))
            if torch.isnan(x.std()):
                print("output is nan in {} layers".format(i))
                break
        return x
    def initialize(self):#权值初始化函数
        for m in self.modules():#对每个模块进行for循环
            if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
                nn.init.normal_(m.weight.data, std=np.sqrt(2 / self.neural_num))#采用态分布,0均值,表准差是2/输入神经元个数
               
               
#2:采用pytorch提供的Kaiming初始化方法(不需要手动计算标准差):(只需更换以上最后一行代码)
    def initialize(self):#权值初始化函数
        for m in self.modules():#对每个模块进行for循环
            if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
                nn.init.kaiming_normal_(m.weight.data)#采用pytorch提供的Kaiming初始化方法

#运行结果:
layer:0, std:0.826629638671875
layer:1, std:0.8786815404891968
layer:2, std:0.9134422540664673
layer:3, std:0.8892471194267273
layer:4, std:0.834428071975708
layer:5, std:0.874537467956543
layer:6, std:0.7926971316337585
layer:7, std:0.7806458473205566
layer:8, std:0.8684563636779785
layer:9, std:0.9434137344360352
layer:10, std:0.964215874671936
layer:11, std:0.8896796107292175
layer:12, std:0.8287257552146912
layer:13, std:0.8519769906997681
layer:14, std:0.8354345560073853
layer:15, std:0.802306056022644
layer:16, std:0.8613607287406921
layer:17, std:0.7583686709403992
layer:18, std:0.8120225071907043
layer:19, std:0.791111171245575
layer:20, std:0.7164372801780701
...
#两种方法计算结果基本一致,网络层标准差基本维持在一个相同的尺度上,没有过大或者过小
           

6. Pytorch提供的初始化方法

  1. Xavie r均匀分布
  2. Xavie r正态分布
  3. Kaiming均匀分布
  4. Kaiming正态分布
  5. 均匀分布
  6. 正态分布
  7. 常数分布
  8. 正交矩阵初始化
  9. 单位矩阵初始化
  10. 稀疏矩阵初始化

nn.init.calculate_gain(nonlinearity, param=None)

nn.init.calculate_gain

主要功能:计算激活函数的方差变化尺度

主要参数(方差变化尺度:输入数据的方差除以经过激活函数之后输出数据的方差)

• nonlinearity: 激活函数名称

• param: 激活函数的参数,如Leaky ReLU

的negative _slop

#以下代码说明针对0均值1标准差的数据,经过tanh之后,它的标准差会减小1.6倍左右
x = torch.randn(10000)
out = torch.tanh(x)

gain = x.std() / out.std()#手动计算方差变化尺度
print('gain:{}'.format(gain))

tanh_gain = nn.init.calculate_gain('tanh')#pytorch提供的nn.init.calculate_gain计算方差变化尺度
print('tanh_gain in PyTorch:', tanh_gain)

运行结果:
gain:1.5982500314712524
tanh_gain in PyTorch: 1.6666666666666667
           

总结

不良的权值初始化会使得输出层输出值过大或者过小,从而引发梯度消失或者梯度爆炸,最终导致模型无法训练。为了避免这个现象的发生,需要控制网络输出层的尺度范围,通过公式推导可知,要使每个网络层输出的方差尽量是1,遵循方差一致性原则,保持网络层输出值的方差在1附近,使我们的输出值不能太大,也不能太小。

继续阅读