1. 程式人生 > 其它 >CNN-權重引數初始化

CNN-權重引數初始化

原地址
https://www.bilibili.com/video/BV1ba411m72B

1.為什麼需要對權重初始化精心設計

  • 1.容易出現梯度消失(梯度特別的接近0)和梯度爆炸(梯度特別的大)的情況,導致大部分反向傳播得到的梯度不起作用或者起反作用

2.設計思路

神經網路的每層的資料的傳送是要有意義的
意義體現在我原本表達的意思,不能經過傳遞之後就曲解了,比如某句話傳著傳著變謠言
例如小學一個班男孩的身高是一個分佈,有一個方差
過了若干年後聚會
這批男孩的身高是一個新分佈,有一個新方差
方差描述的是這批資料分佈的緊密程度
如果是同一批男孩,那麼他們身高的分佈大致不會變
所以我們用方差來衡量神經網路的輸入到底和輸出是不是同一分佈

3.是否可以全零初始化,小值初始化,大值初始化?

全零初始化
每層輸出都一樣,因此無論網路訓練多少輪
對於每一層中的各個神經元,weights都是相同的,無法學習(提取)到不同的特徵

點選檢視程式碼
import numpy as np
from matplotlib import pyplot as plt

def init_weights(u, a0, a1=None, a2=None, a3=None, a4=None):
    a_1 = a1 or a0
    a_2 = a2 or a0
    a_3 = a3 or a0
    a_4 = a4 or a0
    W0 = np.random.normal(u, a0, 400).reshape(2, 200)
    W1 = np.random.normal(u, a_1, 60000).reshape(200, 300)
    W2 = np.random.normal(u, a_2, 120000).reshape(300, 400)
    W3 = np.random.normal(u, a_3, 120000).reshape(400, 300)
    W4 = np.random.normal(u, a_4, 600).reshape(300, 2)
    return W0, W1, W2, W3, W4
def sigmoid(x):
    return 1 / (1 + np.exp(-x))                 # 定義sigmoid函式
def derivative_sigmoid(x):
    return x * (1 - x)   
def relu(x):
    return np.maximum(x, 0)
def leaky_relu(x, p=0.1):
    return np.maximum(x, p*x)
def tanh(x):
    return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
def derivative_tanh(x):
    return 1-tanh(x)**2
def model(X, W0, W1, W2, W3, W4, act='tanh'):                       # 定義模型的前向傳播過程
    if act == 'tanh':
        output_0 = tanh(X @ W0)                  # [n,  2] @ [ 2, 200] = [n, 200]
#         print(X.shape, W0.shape, output_0.shape)
        output_1 = tanh(output_0 @ W1)           # [n, 200] @ [200, 300] = [n, 300]
#         print(output_0.shape, W1.shape, output_1.shape)
        output_2 = tanh(output_1 @ W2)           # [n, 300] @ [300,  400] = [n,  400]
#         print(output_1.shape, W2.shape, output_2.shape)
        output_3 = tanh(output_2 @ W3)           # [n, 400] @ [400,  300] = [n,  300]
#         print(output_2.shape, W3.shape, output_3.shape)
        output_4 = tanh(output_3 @ W4)           # [n, 300] @ [300,  2] = [n,  2]
#         print(output_3.shape, W4.shape, output_4.shape)
    elif act == 'relu':
        output_0 = relu(X @ W0)                 
        output_1 = relu(output_0 @ W1)          
        output_2 = relu(output_1 @ W2)           
        output_3 = relu(output_2 @ W3)         
        output_4 = relu(output_3 @ W4)          
    elif act == 'leaky_relu':
        output_0 = leaky_relu(X @ W0)                 
        output_1 = leaky_relu(output_0 @ W1)           
        output_2 = leaky_relu(output_1 @ W2)        
        output_3 = leaky_relu(output_2 @ W3)          
        output_4 = leaky_relu(output_3 @ W4)         
    else:
        output_0 = sigmoid(X @ W0)              
        output_1 = sigmoid(output_0 @ W1)          
        output_2 = sigmoid(output_1 @ W2)          
        output_3 = sigmoid(output_2 @ W3)         
        output_4 = sigmoid(output_3 @ W4)         
    return [output_0, output_1, output_2, output_3, output_4]
def plot_hist(outputs, xlim=(-1, 1), ylim=(0, 1)):
    n = len(outputs)
    fig, axes=plt.subplots(1, n, figsize=(3*n, 3), sharex=True, sharey=True)
    for i in range(n):
        axes[i].hist(outputs[i].flatten(),bins=50,histtype="stepfilled",density=True,alpha=0.6)
    plt.xlim(*xlim)
    plt.ylim(*ylim)
    plt.show()

W0, W1, W2, W3, W4 = init_weights(0, 0)
X = np.random.normal(0, 1, 1000).reshape(-1, 2) # 初始化X,用於下面所有測試
outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-1, 1), ylim=(0, 1))

![](https://img2022.cnblogs.com/blog/2682749/202205/2682749-20220516083854776-1693978353.png)

小一點的隨機權值初始化
保證了更新不是相同的
但幾乎聚集0,資訊仍然傳不過去,區域性梯度也為0,梯度太小

點選檢視程式碼

W0, W1, W2, W3, W4 = init_weights(0, 0.01)

outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-1, 1), ylim=(0, 1))

outputs = model(X, W0, W1, W2, W3, W4, 'sigmoid')
plot_hist(outputs, xlim=(0, 1), ylim=(0, 1))


大一點的值初始化
資訊傳過去了部分,但由於tanh函式的作用,值太大就飽和了,梯度太小

點選檢視程式碼
W0, W1, W2, W3, W4 = init_weights(0, 1)

outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-1, 1), ylim=(0, 1))

outputs = model(X, W0, W1, W2, W3, W4, 'sigmoid')
plot_hist(outputs, xlim=(0, 1), ylim=(0, 1))


4.Xavier初始化


這個公式有點問題,累加符號應該再套一個大括號,把裡面的三項包起來

sigmoid/tanh啟用函式驗證

點選檢視程式碼
# 這裡傳入的引數是輸入資料維度的標準差的倒數,因為numpy建立正態分佈是靠標準差來建立的
W0, W1, W2, W3, W4 = init_weights(0, (1/2)**0.5, (1/200)**0.5, (1/300)**0.5, (1/400)**0.5, (1/300)**0.5)

outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-1, 1), ylim=(0, 1))

outputs = model(X, W0, W1, W2, W3, W4, 'sigmoid')
plot_hist(outputs, xlim=(0, 1), ylim=(0, 1))
# 明顯看到分佈基本上是屬於正太分佈,資料正向傳播通暢


光考慮正向傳播是不夠的,我們還要考慮反向傳播更新梯度
如果效果比較理想,那麼應當是輸入是什麼樣的就能傳過去什麼樣,從右往左反向傳播
注意此時是從右往左看

點選檢視程式碼
W0, W1, W2, W3, W4 = init_weights(0, (1/2)**0.5, (1/200)**0.5, (1/300)**0.5, (1/400)**0.5, (1/300)**0.5)
lr = 0.01
epochs = 1
Y_onehot = []  # 隨機生成一個y標籤的獨熱編碼,因為要用於計算損失反向傳播更新梯度
for i in range(X.shape[0]):
    temp = np.random.randint(0, 2)
    Y_onehot.append([temp, abs(1-temp)])
Y_onehot = np.array(Y_onehot)  

for epoch in range(epochs):
    for j in range(X.shape[0]):
        [output_0, output_1, output_2, output_3, output_4] = model(X[j], W0, W1, W2, W3, W4, 'tanh')
        # 反向傳播計算梯度, 梯度的維度和權重維度相同,因為等會要更新權重
        loss_4 = derivative_tanh(output_4) * (Y_onehot[j] - output_4)    
        grad_4 = output_3.reshape(-1,1) @ loss_4.reshape(1,-1)                  
        loss_3 = derivative_tanh(output_3) * (W4 @ loss_4)                       
        grad_3 = output_2.reshape(-1,1) @ loss_3.reshape(1,-1)  
        loss_2 = derivative_tanh(output_2) * (W3 @ loss_3)                       
        grad_2 = output_1.reshape(-1,1) @ loss_2.reshape(1,-1) 
        loss_1 = derivative_tanh(output_1) * (W2 @ loss_2)                       
        grad_1 = output_0.reshape(-1,1) @ loss_1.reshape(1,-1)                     
        loss_0 = derivative_tanh(output_0) * (W1 @ loss_1)                      
        grad_0 = X[j].reshape(-1,1) @ loss_0.reshape(1,-1)                   

        # 梯度更新
        W4 += lr*grad_4
        W3 += lr*grad_3 
        W2 += lr*grad_2
        W1 += lr*grad_1 
        W0 += lr*grad_0
    
outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-0.5, 0.5), ylim=(0, 4))

顯然從右往左看,反向傳播的資訊是不能夠很好的傳遞的,所以Xavier重新考慮反向傳播

結論,折中正向傳播和反向傳播

首先驗證權重初始化的方差是按照輸入維度和輸出維度之和的倒數的2倍 2/(Nin+Nout)
用tanh和sigmoid驗證
如果效果比較理想,那麼應當是輸入是什麼樣的就能傳過去什麼樣

點選檢視程式碼
# 這裡傳入的引數是輸入資料維度的標準差的倒數,因為numpy建立正態分佈是靠標準差來建立的
W0, W1, W2, W3, W4 = init_weights(0, (2/(2+200))**0.5, (2/(200+300))**0.5, (2/(300+400))**0.5, (2/(400+300))**0.5, (2/(300+2))**0.5)
outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-0.75, 0.75), ylim=(0, 1))
# 明顯看到分佈基本上是屬於正太分佈,資料正向傳播通暢

lr = 0.01
epochs = 1
for epoch in range(epochs):
    for j in range(X.shape[0]):
        [output_0, output_1, output_2, output_3, output_4] = model(X[j], W0, W1, W2, W3, W4, 'tanh')
        # 反向傳播計算梯度, 梯度的維度和權重維度相同,因為等會要更新權重
        loss_4 = derivative_tanh(output_4) * (Y_onehot[j] - output_4)    
        grad_4 = output_3.reshape(-1,1) @ loss_4.reshape(1,-1)              
        loss_3 = derivative_tanh(output_3) * (W4 @ loss_4)                       
        grad_3 = output_2.reshape(-1,1) @ loss_3.reshape(1,-1) 
        loss_2 = derivative_tanh(output_2) * (W3 @ loss_3)                       
        grad_2 = output_1.reshape(-1,1) @ loss_2.reshape(1,-1) 
        loss_1 = derivative_tanh(output_1) * (W2 @ loss_2)                       
        grad_1 = output_0.reshape(-1,1) @ loss_1.reshape(1,-1) 
        loss_0 = derivative_tanh(output_0) * (W1 @ loss_1)                      
        grad_0 = X[j].reshape(-1,1) @ loss_0.reshape(1,-1)                   

        # 梯度更新
        W4 += lr*grad_4
        W3 += lr*grad_3 
        W2 += lr*grad_2
        W1 += lr*grad_1 
        W0 += lr*grad_0
    
outputs = model(X, W0, W1, W2, W3, W4, 'tanh')
plot_hist(outputs, xlim=(-0.75, 0.75), ylim=(0, 1))


用tanh先進行一次正向傳播為上圖
學習率0.01,訓練一個epoch的反向傳播為下圖
第一幅圖正向傳播,從左往右看,資訊基本傳遞
第二幅圖反向傳播,從右往左看,資訊基本傳遞¶

點選檢視程式碼
# 這裡傳入的引數是輸入資料維度的標準差的倒數,因為numpy建立正態分佈是靠標準差來建立的
W0, W1, W2, W3, W4 = init_weights(0, (2/(2+200))**0.5, (2/(200+300))**0.5, (2/(300+400))**0.5, (2/(400+300))**0.5, (2/(300+2))**0.5)

outputs = model(X, W0, W1, W2, W3, W4, 'sigmoid')
n = len(outputs)
fig, axes=plt.subplots(1, n, figsize=(3*n, 3), sharex=True, sharey=True)
for i in range(n):
    axes[i].hist(outputs[i].flatten(),bins=25,histtype="stepfilled",density=True,alpha=0.6)
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.show()
# 明顯看到分佈基本上是屬於正太分佈,資料正向傳播通暢

lr = 0.01
epochs = 1
# Y_onehot = []
# for i in range(X.shape[0]):
#     temp = np.random.randint(0, 1)
#     Y_onehot.append([temp, abs(1-temp)])
# Y_onehot = np.array(Y_onehot)  
for epoch in range(epochs):
    for j in range(X.shape[0]):
        [output_0, output_1, output_2, output_3, output_4] = model(X[j], W0, W1, W2, W3, W4, 'sigmoid')
        # 反向傳播計算梯度, 梯度的維度和權重維度相同,因為等會要更新權重
        # 最後一層“損失” = 最後一層輸出帶入sigmoid偏導 * (真實值 - 最後一層輸出)
        loss_4 = derivative_sigmoid(output_4) * (Y_onehot[j] - output_4)    
        # 該層梯度 = 上層權重 @ 該層“損失”
        grad_4 = output_3.reshape(-1,1) @ loss_4.reshape(1,-1)                      

        # 非最後一層的該層“損失” = 該層輸出帶入sigmoid偏導 * (下一層權重 - 下一層“損失”)
        loss_3 = derivative_sigmoid(output_3) * (W4 @ loss_4)                       
        grad_3 = output_2.reshape(-1,1) @ loss_3.reshape(1,-1)  

        # 非最後一層的該層“損失” = 該層輸出帶入sigmoid偏導 * (下一層權重 - 下一層“損失”)
        loss_2 = derivative_sigmoid(output_2) * (W3 @ loss_3)                       
        grad_2 = output_1.reshape(-1,1) @ loss_2.reshape(1,-1) 

        # 非最後一層的該層“損失” = 該層輸出帶入sigmoid偏導 * (下一層權重 - 下一層“損失”)
        loss_1 = derivative_sigmoid(output_1) * (W2 @ loss_2)                       
        grad_1 = output_0.reshape(-1,1) @ loss_1.reshape(1,-1)                     

        loss_0 = derivative_sigmoid(output_0) * (W1 @ loss_1)                      
        grad_0 = X[j].reshape(-1,1) @ loss_0.reshape(1,-1)                   

        # 梯度更新
        W4 += lr*grad_4
        W3 += lr*grad_3 
        W2 += lr*grad_2
        W1 += lr*grad_1 
        W0 += lr*grad_0
    
outputs = model(X, W0, W1, W2, W3, W4, 'sigmoid')
plot_hist(outputs, xlim=(0, 1), ylim=(0, 1))


用sigmoid先進行一次正向傳播為上圖
學習率0.01,訓練一個epoch的反向傳播為下圖
第一幅圖正向傳播,從左往右看,資訊基本傳遞
第二幅圖反向傳播,從右往左看,資訊基本傳遞


Relu函式不適用Xavier初始化權重方法
因為隨機殺死一半,破壞了資料的分佈
Relu不會有小於0的數,所以只畫了大於0的部分
基本上在第4層時候就沒什麼資訊了¶

點選檢視程式碼
W0, W1, W2, W3, W4 = init_weights(0, (2/(2+200))**0.5, (2/(200+300))**0.5, (2/(300+400))**0.5, (2/(400+300))**0.5, (2/(300+2))**0.5)

outputs = model(X, W0, W1, W2, W3, W4, 'relu')
plot_hist(outputs, xlim=(0, 0.5), ylim=(0, 2))

5.何愷明的初始化 He初始化

如果使用Relu函式,則採用何凱明大神的初始化權重方法
僅僅只是將Xavier中方差改為2倍,就能很好的緩和使用Relu的情況

點選檢視程式碼
W0, W1, W2, W3, W4 = init_weights(0, (2*2/(2+200))**0.5, (2*2/(200+300))**0.5, (2*2/(300+400))**0.5, (2*2/(400+300))**0.5, (2*2/(300+2))**0.5)

outputs = model(X, W0, W1, W2, W3, W4, 'relu')
plot_hist(outputs, xlim=(0, 0.5), ylim=(0, 2))

但是這樣並不是適用於relu的變體,於是何凱明大神做了一個推廣
這裡分母的理解是,如果把全體實數R看作2份,那麼relu相當於殺死了其中1份,所以a=0
那麼leaky relu(a)相當於抑制了一半,抑制的係數a¶

點選檢視程式碼
a = 0.3
W0, W1, W2, W3, W4 = init_weights(0, (2*2/((1+a**2)*(2+200)))**0.5, (2*2/((1+a**2)*(200+300)))**0.5, 
                                  (2*2/((1+a**2)*(300+400)))**0.5, (2*2/((1+a**2)*(400+300)))**0.5, (2*2/((1+a**2)*(300+2)))**0.5)

outputs = model(X, W0, W1, W2, W3, W4, 'leaky_relu')
plot_hist(outputs, xlim=(-0.1, 0.5), ylim=(0, 2))

sigmoid和tanh函式用Xavier比較合適,relu和leaky relu用何凱明方法比較合適

點選檢視程式碼
import torch
def cal(x):
    return (x.mean(), x.var())
uniform_w = torch.nn.init.uniform_(torch.empty(300, 500), a=0.0, b=1.0)
normal_w = torch.nn.init.normal_(torch.empty(300, 500), mean=0.0, std=1.0)
xavier_uniform_w = torch.nn.init.xavier_uniform_(torch.empty(300, 500), gain=1.0)
kaiming_uniform_w = torch.nn.init.kaiming_uniform_(torch.empty(300, 500), a=0,mode='fan_in',nonlinearity='relu')
xavier_normal_w = torch.nn.init.xavier_normal_(torch.empty(300, 500), gain=1.0)
kaiming_normal_w = torch.nn.init.kaiming_normal_(torch.empty(300, 500), a=0,mode='fan_in',nonlinearity='relu')
kaiming_uniform_w_l = torch.nn.init.kaiming_uniform_(torch.empty(300, 500), a=0.3,mode='fan_in',nonlinearity='leaky_relu')
kaiming_normal_w_l = torch.nn.init.kaiming_normal_(torch.empty(300, 500), a=0.3,mode='fan_in',nonlinearity='leaky_relu')


點選檢視程式碼

print('uniform_w:', cal(uniform_w))
print('normal_w:', cal(normal_w))
print('xavier均勻分佈:', cal(xavier_uniform_w))
print('kaiming均勻分佈by torch:', cal(kaiming_uniform_w))
print('xavier正態分佈:', cal(xavier_normal_w))
print('kaiming正態分佈by torch:', cal(kaiming_normal_w))
print('kaiming均勻分佈leakyrelu:', cal(kaiming_uniform_w_l))
print('kaiming正態分佈leakyrelu by torch:', cal(kaiming_normal_w_l))

uniform_w: (tensor(0.4999), tensor(0.0833))
normal_w: (tensor(-0.0016), tensor(0.9989))
xavier均勻分佈: (tensor(0.0001), tensor(0.0025))
kaiming均勻分佈by torch: (tensor(7.6385e-05), tensor(0.0040))
xavier正態分佈: (tensor(6.1033e-05), tensor(0.0025))
kaiming正態分佈by torch: (tensor(7.5544e-05), tensor(0.0040))
kaiming均勻分佈leakyrelu: (tensor(-1.6571e-05), tensor(0.0037))
kaiming正態分佈leakyrelu by torch: (tensor(-0.0001), tensor(0.0037))

點選檢視程式碼

# 均勻分佈的方差是區間長度的平方再除以12
# 何凱明就是在xavier基礎上乘了2,但最後有個近似處理,假設輸入輸出維度一樣,便可以約掉2,但可以設定你想要保證正向還是反向。
print('[0, 1]均勻分佈的均值與方差', 1/2, (1-0)**2/12)
print('xavier均勻分佈的方差', (2*((6/(800))**0.5))**2/12)
print('kaiming均勻分佈的方差', (2*((2*6/(800))**0.5))**2/12)  # 2*6/(400+400) -> 6/400
print('kaiming均勻分佈的方差近似', (2*((6/(500))**0.5))**2/12)
print('xavier正態分佈的方差', 2/(800))
print('kaiming正態分佈的方差', 2*2/(800))     # 2*2/(400+400) -> 2/400
print('kaiming正態分佈的方差近似', 2/(500))
print('kaiming均勻分佈的方差leakyrelu', (2*((2*6/((1+0.3**2)*800))**0.5))**2/12)
print('kaiming均勻分佈的方差近似leakyrelu', (2*((6/((1+0.3**2)*500))**0.5))**2/12)
print('kaiming正態分佈的方差leakyrelu', 2*2/((1+0.3**2)*800))
print('kaiming正態分佈的方差近似leakyrelu', 2/((1+0.3**2)*500))

[0, 1]均勻分佈的均值與方差 0.5 0.08333333333333333
xavier均勻分佈的方差 0.0025
kaiming均勻分佈的方差 0.005
kaiming均勻分佈的方差近似 0.004
xavier正態分佈的方差 0.0025
kaiming正態分佈的方差 0.005
kaiming正態分佈的方差近似 0.004
kaiming均勻分佈的方差leakyrelu 0.0045871559633027525
kaiming均勻分佈的方差近似leakyrelu 0.0036697247706422016
kaiming正態分佈的方差leakyrelu 0.004587155963302752
kaiming正態分佈的方差近似leakyrelu 0.003669724770642202

Kaiming 中的 fan_in ,fan_out模式

kaiming_uniform_w = torch.nn.init.kaiming_uniform_(torch.empty(300, 500), a=0,mode='fan_in',nonlinearity='relu')
注意這一行,他的分母不是300,500的均值400,而是由一個引數mode控制,如果是fan_in 就是保證輸入 分母是500,可以看一下這條
print('kaiming均勻分佈的方差近似', (2*((6/(500))0.5))2/12)