天天看點

權值初始化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附近,使我們的輸出值不能太大,也不能太小。

繼續閱讀