Lesson 13.5 Xavier方法与kaiming方法(HE初始化)
Lesson 13.5 Xavier方法與kaiming方法(HE初始化)
??在進行了一系列的理論推導和代碼準備工作之后,接下來,我們介紹參數初始化優化方法,也就是針對tanh和Sigmoid激活函數的Xavier方法,以及針對ReLU激活函數的Kaiming方法(HE初始化)。當然,在經過漫長的準備工作之后,實際落地應用過程并不會太過于復雜。
一、Xavier方法
1.Xavier初始化參數方法基礎理論
??回顧Glorot條件,我們要求正向傳播時候數據流經每一層前后的方差一致,并且反向傳播時候數據流經每一層,該層梯度前后方差一致。我們將前者稱為向前傳播條件,后者稱為反向傳播條件。我們先看當向前傳播條件成立時候,有如下計算過程。
首先,數據流至某一層時,該層神經元接收到的數據可以通過如下方法算得:
zj=∑i=1nwixiz_j = \sum^n_{i=1}w_ix_izj?=i=1∑n?wi?xi?
其中zjz_jzj?表示當前某層神經元接收到的數據,xix_ixi?表示上一層某神經元傳出的數據,wiw_iwi?代表連接對應兩個神經元之間的權重,當然,如果我們將zjz_jzj?、xix_ixi?、wiw_iwi?看成是隨機變量,則上式就能夠表示計算過程的一般情況。并且wiw_iwi?和xix_ixi?的方差計算過程如下:
Var(wixi)=E[wi]2Var(xi)+E[xi]2Var(wi)+Var(wi)Var(xi)Var(w_ix_i) = E[w_i]^2Var(x_i)+E[x_i]^2Var(w_i)+Var(w_i)Var(x_i)Var(wi?xi?)=E[wi?]2Var(xi?)+E[xi?]2Var(wi?)+Var(wi?)Var(xi?)
上述過程涉及基礎數學知識,設X為隨機變量,D(X)D(X)D(X)表示X方差,E(X)E(X)E(X)表示其期望,則有:D(X)=E(X2)?E(X)2D(X)=E(X^2)-E(X)^2D(X)=E(X2)?E(X)2
進一步,如果X、Y表示隨機變量并且相互獨立,則有:D(XY)=E((XY)2)?(E(XY))2=E(X2)E(Y2)?(E(X)E(Y))2D(XY)=E((XY)^2)-(E(XY))^2=E(X^2)E(Y^2)-(E(X)E(Y))^2D(XY)=E((XY)2)?(E(XY))2=E(X2)E(Y2)?(E(X)E(Y))2
并且對其進行簡單變形,可以得出:D(XY)=E(X)2D(Y)+E(Y)2D(X)+D(X)D(Y)=E(X)2[E(Y2)?E(Y)2]+E(Y)2[E(X2)?E(X)2]+[E(X2)?E(X)2][E(Y2)?E(Y)2]=E(X)2E(Y2)?E(X)2E(Y)2+E(Y)2E(X2)?E(Y)2E(X)2+E(X2)E(Y2)?E(X)2E(Y2)?E(X2)E(Y)2+E(X)2E(Y)2=E(X2)E(Y2)?E(X)2E(Y)2\begin{aligned} D(XY) &=E(X)^2D(Y)+E(Y)^2D(X)+D(X)D(Y) \\ &= E(X)^2[E(Y^2)-E(Y)^2]+E(Y)^2[E(X^2)-E(X)^2]+[E(X^2)-E(X)^2][E(Y^2)-E(Y)^2] \\ &= E(X)^2E(Y^2)-E(X)^2E(Y)^2+E(Y)^2E(X^2)-E(Y)^2E(X)^2+E(X^2)E(Y^2)-E(X)^2E(Y^2)-E(X^2)E(Y)^2+E(X)^2E(Y)^2 \\ &= E(X^2)E(Y^2)-E(X)^2E(Y)^2\\ \end{aligned}D(XY)?=E(X)2D(Y)+E(Y)2D(X)+D(X)D(Y)=E(X)2[E(Y2)?E(Y)2]+E(Y)2[E(X2)?E(X)2]+[E(X2)?E(X)2][E(Y2)?E(Y)2]=E(X)2E(Y2)?E(X)2E(Y)2+E(Y)2E(X2)?E(Y)2E(X)2+E(X2)E(Y2)?E(X)2E(Y2)?E(X2)E(Y)2+E(X)2E(Y)2=E(X2)E(Y2)?E(X)2E(Y)2?
其中Var()表示方差計算,E()表示均值計算。由于我們假設參數是以0為均值的均勻分布或者0為均值的正態分布,因此E(wi)=0E(w_i) = 0E(wi?)=0,而此前我們介紹,假設輸入數據是Zero-Centered的,因此E(xi)=0E(x_i)=0E(xi?)=0,所以上式可進一步簡化為:
Var(wixi)=Var(wi)Var(xi)Var(w_ix_i) = Var(w_i)Var(x_i)Var(wi?xi?)=Var(wi?)Var(xi?)
而對于z來說,z=∑i=1nwixiz = \sum^n_{i=1}w_ix_iz=∑i=1n?wi?xi?,其方差計算過程如下:
Var(z)=∑i=1nVar(wixi)=∑i=1nVar(wi)Var(xi)Var(z) = \sum^n_{i=1} Var(w_ix_i) = \sum^n_{i=1} Var(w_i)Var(x_i)Var(z)=i=1∑n?Var(wi?xi?)=i=1∑n?Var(wi?)Var(xi?)
此處補充數學過程,D(X+Y)=D(X)+D(Y)+COV(X,Y)D(X+Y)=D(X)+D(Y)+COV(X,Y)D(X+Y)=D(X)+D(Y)+COV(X,Y)
其中COV(X,Y)COV(X,Y)COV(X,Y)是二者的協方差,當X、YX、YX、Y相互獨立時,COV(X,Y)=0COV(X,Y)=0COV(X,Y)=0。
此時,我們可以認為x和w是獨立同分布的(一個是采集處理后的數據,一個是隨機生成的參數),因此每個Var(wi)Var(xi)Var(w_i)Var(x_i)Var(wi?)Var(xi?)也是獨立同分布的,因此所有的wiw_iwi?都可以用一個隨機變量www表示,所有的xix_ixi?也可以用一個隨機變量xxx表示,上式可進一步簡寫為:
Var(z)=∑i=1nVar(wi)Var(xi)=nVar(w)Var(x)Var(z) = \sum^n_{i=1} Var(w_i)Var(x_i) = nVar(w)Var(x)Var(z)=i=1∑n?Var(wi?)Var(xi?)=nVar(w)Var(x)
而我們希望線性變換不影響數據方差,因此有:
Var(z)=Var(x)Var(z)=Var(x)Var(z)=Var(x)
進一步可以算得:
Var(w)=1nVar(w) = \frac{1}{n}Var(w)=n1?
其中,n是上一層神經元的個數。需要注意的是,上式只考慮了正向傳播的情況,而實際在進行反向傳播時候,上述過程正好相反。反向傳播時z代表上一層神經元接收到的數據,而x則代表當前層傳出的數據,雖然計算公式不變,但n的含義卻發生了變化。為了進行區分,我們將正向傳播時的n、也就是代表上一層神經元個數的變量,命名為ninn_{in}nin?,而在進行反向傳播時的n、也就是代表當前層神經元個數的變量,命名為noutn_{out}nout?。為了同時兼顧向前傳播和反向傳播這兩種情況,我們將w的最終方差取值為:
Var(w)=1nin+nout2=2nin+noutVar(w) = \frac{1}{\frac{n_{in}+n_{out}}{2}} = \frac{2}{n_{in}+n_{out}}Var(w)=2nin?+nout??1?=nin?+nout?2?
取為均值,也就是折中的方案
而此時,如果我們設置w服從均勻在[-a, a]區間內均勻分布,則w的方差為:
Var(w)=(a+a)212=4a212=a23=2nin+noutVar(w) = \frac{(a+a)^2}{12} = \frac{4a^2}{12} = \frac{a^2}{3} = \frac{2}{n_{in}+n_{out}}Var(w)=12(a+a)2?=124a2?=3a2?=nin?+nout?2?
注:服從在[a, b]區間上均勻分布的隨機變量方差為(b?a)212\frac{(b-a)^2}{12}12(b?a)2?
進而計算可得
a=6nin+nouta = \sqrt{\frac{6}{n_{in}+n_{out}}}a=nin?+nout?6??
即w的是均勻分布在(?6nin+nout,6nin+nout)(-\sqrt{\frac{6}{n_{in}+n_{out}}}, \sqrt{\frac{6}{n_{in}+n_{out}}})(?nin?+nout?6??,nin?+nout?6??)上的隨機變量。
當然,如果我們假設w是服從正態分布的,則w服從(0,2nin+nout)(0, \sqrt{\frac{2}{n_{in}+n_{out}}})(0,nin?+nout?2??)的隨機變量。據此,我們就能設置每一層的初始參數了。當然,在PyTorch中,可以使用init方法進行初始化。
另外,一種更加嚴謹的、指代某一次傳播過程上一層神經元數量和下一層神經元數量的叫法是扇入(fan in)和扇出(fan out),由此Xavier方法中初始參數的方差也可寫為Var(w)=2fanin+fanoutVar(w) = \frac{2}{fan_{in}+fan_{out}}Var(w)=fanin?+fanout?2?。另外,Xavier在論文中所指出的,應該保持各層的激活值和梯度的方差在傳播過程中保持一致,也被稱為Glorot條件。
對于參數初始化計算過程,最重要的是確定參數的方差,如果是正態分布,由于均值是0,因此可以快速確定其分布,而如果是均勻分布,則可通過bound=3varbound = \sqrt{3var}bound=3var?來確定分布區間[?bound,bound][-bound, bound][?bound,bound]。而在Xavier初始化方法中,var=1fanavgvar = \frac{1}{fan_{avg}}var=fanavg?1? ,其中fanavg=fanin+fanout2fan_{avg} = \frac{fan_{in}+fan_{out}}{2}fanavg?=2fanin?+fanout??。
最關鍵的,我們需要知道,對于創建隨機分布的初始參數來說,只要確定其方差,就能確定其均勻分布或者正態分布的分布形式。
2.Xavier初始化參數執行方法
2.1 PyTorch實現Xavier均勻分布的參數創建
我們可以使用torch.nn.init.xavier_uniform_進行初始化參數設置。
nn.init.xavier_uniform_? # Signature: nn.init.xavier_uniform_(tensor, gain=1.0) # Docstring: # Fills the input `Tensor` with values according to the method # described in `Understanding the difficulty of training deep feedforward # neural networks` - Glorot, X. & Bengio, Y. (2010), using a uniform # distribution. The resulting tensor will have values sampled from # :math:`\mathcal{U}(-a, a)` where# .. math:: # a = \text{gain} \times \sqrt{\frac{6}{\text{fan\_in} + \text{fan\_out}}}# Also known as Glorot initialization.# Args: # tensor: an n-dimensional `torch.Tensor` # gain: an optional scaling factor# Examples: # >>> w = torch.empty(3, 5) # >>> nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu')) # File: d:\users\asus\anaconda3\lib\site-packages\torch\nn\init.py # Type: function當然,這里需要注意的是,函數處理的對象是張量,也就是說只要輸入張量,就可以自動進行處理。其中faninfan_{in}fanin?由張量的列決定,fanoutfan_{out}fanout?由張量的行數決定。(想想是為什么?)
從說明中我們能看到,此時均勻分布的邊界為a=gain×6fan_in+fan_outa = \text{gain} \times \sqrt{\frac{6}{\text{fan\_in} + \text{fan\_out}}}a=gain×fan_in+fan_out6??其中gain為增益系數,用于手動調整均勻分布的邊界,暫時不建議調整。
t = torch.arange(8).reshape(2, 4).float() t #tensor([[0., 1., 2., 3.], # [4., 5., 6., 7.]]) nn.init.xavier_uniform_(t) #tensor([[-0.2483, -0.8510, 0.0233, -0.9001], # [ 0.7834, -0.9809, 0.3314, 0.7942]]) t # 該函數會在原對象基礎上直接進行修改 #tensor([[-0.2483, -0.8510, 0.0233, -0.9001], # [ 0.7834, -0.9809, 0.3314, 0.7942]])- Sigmoid激活函數
??接下來,在建模過程中使用Xavier初始化方法測試效果。首先是數據準備與核心參數定義。
# 設置隨機數種子 torch.manual_seed(420) # 創建最高項為2的多項式回歸數據集 features, labels = tensorGenReg(w=[2, -1], bias=False, deg=2)# 進行數據集切分與加載 train_loader, test_loader = split_loader(features, labels)# 初始核心參數 lr = 0.03 num_epochs = 20然后實例化模型、調整模型初始參數,以及創建用于對比模型效果的模型容器,首先我們先創建一組相對簡單的模型,也就是包含兩個sigmoid隱藏層的神經網絡模型,對比初始化效果
# 設置隨機數種子 torch.manual_seed(420) # 實例化模型 sigmoid_model3 = Sigmoid_class3() # 保留原參數 sigmoid_model3_init = Sigmoid_class3() # 使用Xavier初始化參數# 修改init模型初始參數 for m in sigmoid_model3_init.modules():if isinstance(m, nn.Linear):nn.init.xavier_uniform_(m.weight) #不用弄截距 #<torch._C.Generator at 0x284ef4d16b0> #Parameter containing: #tensor([[ 0.7995, -0.2561], # [-0.1097, -0.0529], # [-0.8137, 0.8860], # [-0.0984, 0.9770]], requires_grad=True) #Parameter containing: #tensor([[ 0.7130, 0.3764, -0.0196, -0.0096], # [ 0.0199, -0.8417, -0.2067, -0.4007], # [-0.0515, 0.1059, -0.6787, 0.6582], # [ 0.7072, 0.5386, 0.3889, 0.1370]], requires_grad=True) #Parameter containing: #tensor([[-0.6241, 0.3488, 0.3069, 0.8371], # [-0.8167, 0.7516, 0.6801, 0.1606], # [ 0.2885, 0.1036, -0.0692, 0.1744], # [ 0.5015, 0.0987, -0.0787, -0.0286]], requires_grad=True) #Parameter containing: #tensor([[-0.8437, -0.5244, 0.8917, 0.0646]], requires_grad=True)# 創建模型容器 model_l = [sigmoid_model3, sigmoid_model3_init] name_l = ['sigmoid_model3', 'sigmoid_model3_init']train_l, test_l = model_comparison(model_l = model_l, name_l = name_l, train_data = train_loader,test_data = test_loader,num_epochs = 2, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = lr, cla = False, eva = mse_cal) weights_vp(sigmoid_model3, att="grad") weights_vp(sigmoid_model3_init, att="grad")
我們發現,在num_epochs取值為2的時候(只迭代了一輪),經過Xavier初始化的模型梯度整體更加穩定,并且沒有出現梯度消失的情況,反觀原始模型sigmoid_model2,第一層的梯度已經非常小了,已經出現了梯度消失的傾向。而我們知道,各層梯度的情況就代表著模型學習的狀態,很明顯經過初始化的模型各層都處于平穩學習狀態,此時模型收斂速度較快。我們也可以通過MSE曲線進行驗證。
??由此我們可知,Xavier初始化的作用核心在于保證各層梯度取值的平穩分布,從而確保各層模型學習的有效性,最終在模型結果的表現上,經過Xavier初始化參數的模型學習效率更高、收斂速度更快。上述結果也驗證了Xavier初始化有效性。
??當然,在一些極端情況下,Xavier初始化效果會更加明顯。我們以四層sigmoid隱藏層的神經網絡為例,觀察Xavier初始化在規避梯度消失問題時的效果。
sigmoid_model4是Lesson 13.2中出現嚴重梯度消失的模型,由于前幾層基本喪失學習能力,sigmoid_model4本身效果并不好。但加入Xavier初始化之后,我們發現,init模型能夠極大程度規避梯度消失問題,從而獲得更好的效果。
不過正如此前所說,相比于sigmoid激活函數,Xavier初始化方法更適用于tanh激活函數,核心原因在于tanh激活函數本身能夠生成Zero-centered Data,配合Xavier初始化生成的參數,能夠更好的確保各層梯度平穩、確保各層平穩學習。
- tanh激活函數
我們以三層tanh激活函數隱藏層的神經網絡為例,測試Xavier初始化效果。
# 設置隨機數種子 torch.manual_seed(420) # 創建最高項為2的多項式回歸數據集 features, labels = tensorGenReg(w=[2, -1], bias=False, deg=2)# 進行數據集切分與加載 train_loader, test_loader = split_loader(features, labels) # 設置隨機數種子 torch.manual_seed(420) # 實例化模型 tanh_model3 = tanh_class3() # 保留原參數 tanh_model3_init = tanh_class3() # 使用Xavier初始化參數 # 設置隨機數種子 torch.manual_seed(420) # 修改init模型初始參數 for m in tanh_model3_init.modules():if isinstance(m, nn.Linear):nn.init.xavier_uniform_(m.weight) #<torch._C.Generator at 0x284ef4d16b0> #Parameter containing: #tensor([[ 0.6107, -0.6019], # [ 0.9517, -0.7944], # [-0.3051, -0.6891], # [ 0.7712, 0.3751]], requires_grad=True) #Parameter containing: #tensor([[-0.4319, -0.6698, -0.5014, -0.1671], # [-0.4420, 0.6311, -0.3644, -0.5666], # [-0.2672, -0.8457, -0.4206, -0.4725], # [ 0.1864, 0.7043, 0.0935, -0.5047]], requires_grad=True) #Parameter containing: #tensor([[ 0.3565, -0.3687, -0.4099, -0.1660], # [-0.4519, 0.0952, 0.7030, 0.1181], # [ 0.5231, -0.7523, -0.6813, -0.1152], # [ 0.0008, 0.5406, -0.7617, 0.3613]], requires_grad=True) #Parameter containing: #tensor([[-0.9402, 0.1767, 0.7240, 0.1511]], requires_grad=True)# 創建模型容器 model_l = [tanh_model3, tanh_model3_init] name_l = ['tanh_model3', 'tanh_model3_init']# 核心參數 lr = 0.03 num_epochs = 20# 模型訓練 train_l, test_l = model_comparison(model_l = model_l, name_l = name_l, train_data = train_loader,test_data = test_loader,num_epochs = 2, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = lr, cla = False, eva = mse_cal) weights_vp(tanh_model3, att="grad") weights_vp(tanh_model3_init, att="grad")
同樣,能夠看出經過Xavier參數初始化后的模型梯度更加平穩,進而我們判斷,經過初始化之后的模型初始迭代時收斂速度更快
同樣我們能夠發現,模型收斂速度更快,迭代多輪之后也變得更加穩定。
2.2 PyTorch實現Xavier高斯分布的參數創建
類似的,我們可以使用torch.nn.init.xavier_normal_進行初始化參數設置。
torch.nn.init.xavier_normal_? #Signature: torch.nn.init.xavier_normal_(tensor, gain=1.0) #Docstring: #Fills the input `Tensor` with values according to the method #described in `Understanding the difficulty of training deep feedforward #neural networks` - Glorot, X. & Bengio, Y. (2010), using a normal #distribution. The resulting tensor will have values sampled from #:math:`\mathcal{N}(0, \text{std}^2)` where # #.. math:: # \text{std} = \text{gain} \times \sqrt{\frac{2}{\text{fan\_in} + \text{fan\_out}}} # #Also known as Glorot initialization. # #Args: # tensor: an n-dimensional `torch.Tensor` # gain: an optional scaling factor # #Examples: # >>> w = torch.empty(3, 5) # >>> nn.init.xavier_normal_(w) #File: d:\users\asus\anaconda3\lib\site-packages\torch\nn\init.py #Type: functionstd=gain×2fan_in+fan_out\text{std} = \text{gain} \times \sqrt{\frac{2}{\text{fan\_in} + \text{fan\_out}}}std=gain×fan_in+fan_out2??
當然,修改參數的方法也是相同的,例如:
其他測試初始化效果的相關實驗和此前操作流程一致,同學們可以自行進行嘗試。從理論上來說,均勻分布和高斯分布并沒有根本性區別,二者任選其一使用即可。不過也有一些不是很嚴謹的實驗證明了均勻分布比高斯分布能夠產生相對較大的梯度,因而模型學習效果會更好。
二、Kaiming方法(HE初始化)
1.HE初始化基礎理論
??盡管Xavier初始化能夠在Sigmoid和tanh激活函數疊加的神經網絡中起到一定的效果,但由于ReLU激活函數屬于非飽和類激活函數,并不會出現類似Sigmoid和tanh激活函數使用過程中可能存在的梯度消失或梯度爆炸問題,反而因為ReLU激活函數的不飽和特性,ReLU激活函數的疊加極有可能出現神經元活性消失的問題,很明顯,該類問題無法通過Xavier初始化解決。
??盡管如此,對參數的初始值進行合理設置,仍然是保證模型有效性的有效方法,同樣也能一定程度上解決ReLU激活函數的神經元活性消失問題。目前通用的針對ReLU激活函數的初始化參數方法,是由何凱明在2015年的《Delving Deep into Rectifiers:
Surpassing Human-Level Performance on ImageNet Classification》一文中所提出的HE初始化方法,也被稱為Kaiming方法。原文地址。
近一輪深度學習的興起也就在十年間,我們接觸到的很多算法和優化方法基本都是發表于10年內。
??當然,He初始化也遵循Glorot條件,即參數初始化結果要求正向傳播時各層接收到的數據方差保持一致、反向傳播時各層參數梯度的方差保持一致,不過由于每一層的激活值(激活函數輸出結果)均值不為0,因此Xavier的數學推導過程不再成立。關于HE初始化的數學推導此處不進行深入講解,感興趣的同學可自行參考論文中給出的推導過程。需要知道的是,經過一系列的推導和論證之后,HE初始化仍然是規定參數是滿足均值為0的隨機變量,并且仍然借助均勻分布和高斯分布進行隨機變量創建,不同的是Xavier中參數方差為:Var(w)Xavier=2fanin+fanoutVar(w)_{Xavier} = \frac{2}{fan_{in}+fan_{out}}Var(w)Xavier?=fanin?+fanout?2?而HE初始化過程中,參數方差為Var(w)HE=2fanin或Var(w)HE=2fanoutVar(w)_{HE} = \frac{2}{fan_{in}} 或 Var(w)_{HE} = \frac{2}{fan_{out}}Var(w)HE?=fanin?2?或Var(w)HE?=fanout?2?也就是分子不變,分母取某層扇入或扇出的神經元的數量,同時論文中給出論證二者沒有明顯區別,建模過程中任取其一即可。
當然,根據參數方差,我們就能確定參數滿足均勻分布時的邊界,以及滿足高斯分布時的基本形態。均勻分布時bound=3varbound = \sqrt{3var}bound=3var?,因此參數分布區間為:(?6fanin,6fanin)(-\sqrt{\frac{6}{fan_{in}}}, \sqrt{\frac{6}{fan_{in}}})(?fanin?6??,fanin?6??)
值得注意的是,HE初始化不僅針對ReLU激活函數,還可以對其變種激活函數使用,相關內容會在后續進行介紹。
后續還將介紹其他初始化方法,我們仍然可以從以下幾個角度進行理解:
(1).為了滿足或者更好的滿足Glorot條件;
(2).數學理論推導出參數方差;
(3).方差由扇入和扇出神經元個數組成。
2.HE初始化在PyTorch中實現
2.1 PyTorch實現HE初始化的均勻分布參數創建
我們可以使用torch.nn.init.kaiming_uniform_進行初始化參數設置。
nn.init.kaiming_uniform_? #Signature: #nn.init.kaiming_uniform_( # tensor, # a=0, # mode='fan_in', # nonlinearity='leaky_relu', #) #Docstring: #Fills the input `Tensor` with values according to the method #described in `Delving deep into rectifiers: Surpassing human-level #performance on ImageNet classification` - He, K. et al. (2015), using a #uniform distribution. The resulting tensor will have values sampled from #:math:`\mathcal{U}(-\text{bound}, \text{bound})` where # #.. math:: # \text{bound} = \text{gain} \times \sqrt{\frac{3}{\text{fan\_mode}}} # #Also known as He initialization. # #Args: # tensor: an n-dimensional `torch.Tensor` # a: the negative slope of the rectifier used after this layer (only # used with ``'leaky_relu'``) # mode: either ``'fan_in'`` (default) or ``'fan_out'``. Choosing ``'fan_in'`` # preserves the magnitude of the variance of the weights in the # forward pass. Choosing ``'fan_out'`` preserves the magnitudes in the # backwards pass. # nonlinearity: the non-linear function (`nn.functional` name), # recommended to use only with ``'relu'`` or ``'leaky_relu'`` (default). # #Examples: # >>> w = torch.empty(3, 5) # >>> nn.init.kaiming_uniform_(w, mode='fan_in', nonlinearity='relu') #File: d:\users\asus\anaconda3\lib\site-packages\torch\nn\init.py #Type: function相關參數解釋:
- mode:參數表示選擇帶入扇入還是扇出的神經元個數進行計算,正如前文所說,理論上二者對建模沒有明顯影響,可任選其一,但實際由于模型個體差異,在實際使用過程中還是略有差異,我們可以根據實際效果進行選擇;
- a:為使用ReLU變種激活函數時的修正系數;
- nonlinearity:表示所選用的變種ReLU激活函數類型,需要配合a參數使用,相關使用方法我們將在后續介紹ReLU變種激活函數的使用時一并介紹。
另外,值得注意的是,kaiming方法的幫助文檔中的數學公式有誤,切勿參考該公式進行建模。
幫助文檔中公式bound=gain×3fan_mode\text{bound} = \text{gain} \times \sqrt{\frac{3}{\text{fan\_mode}}}bound=gain×fan_mode3??
而實際上kaiming方法并沒有gain增益系數,只有a的一個修正系數,實際公式如下:bound=6(1+a2)faninbound = \sqrt{\frac{6}{(1+a^2)fan_{in}}}bound=(1+a2)fanin?6??和Xavier方法一樣,kaiming方法直接對張量的值進行修改,并且張量列數表示faninfan_{in}fanin?的個數,行數表示fanoutfan_{out}fanout?的個數,據此,我們簡單測試下公式正確性。
據此,驗證了公式的正確性。接下來,嘗試在建模過程中使用kaiming方法,驗證是否能夠加快迭代效率,并且規避神經元活性失效問題。
HE初始化如何幫助模型規避Dead ReLU Problem?核心在于模型初始化時如果參數完全隨機選擇,就有可能出現初始參數全部輸出0的結果,而通過HE初始化的參數不會出現上述情況。
# 設置隨機數種子 torch.manual_seed(420) # 創建最高項為2的多項式回歸數據集 features, labels = tensorGenReg(w=[2, 1], bias=False, deg=2)# 進行數據集切分與加載 train_loader, test_loader = split_loader(features, labels)# 初始核心參數 lr = 0.001 num_epochs = 20# 設置隨機數種子 torch.manual_seed(420) # 實例化模型 relu_model3 = ReLU_class3() # 保留原參數 relu_model3_init = ReLU_class3() # 使用HE初始化參數# 修改init模型初始參數 for m in relu_model3_init.modules():if isinstance(m, nn.Linear):nn.init.kaiming_uniform_(m.weight) #<torch._C.Generator at 0x225457416b0> #Parameter containing: #tensor([[ 1.3847, -0.4435], # [-0.1899, -0.0916], # [-1.4094, 1.5347], # [-0.1705, 1.6922]], requires_grad=True) #Parameter containing: #tensor([[ 1.0084, 0.5323, -0.0277, -0.0135], # [ 0.0282, -1.1904, -0.2923, -0.5667], # [-0.0729, 0.1498, -0.9598, 0.9308], # [ 1.0001, 0.7617, 0.5500, 0.1938]], requires_grad=True) #Parameter containing: #tensor([[-0.8826, 0.4933, 0.4340, 1.1838], # [-1.1550, 1.0630, 0.9618, 0.2271], # [ 0.4080, 0.1464, -0.0979, 0.2467], # [ 0.7092, 0.1396, -0.1113, -0.0405]], requires_grad=True) #Parameter containing: #tensor([[-0.9433, -0.5863, 0.9970, 0.0722]], requires_grad=True)# 創建模型容器 model_l = [relu_model3, relu_model3_init] name_l = ['relu_model3', 'relu_model3_init']train_l, test_l = model_comparison(model_l = model_l, name_l = name_l, train_data = train_loader,test_data = test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = lr, cla = False, eva = mse_cal)# 訓練誤差 for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), train_l[i], label=name) plt.legend(loc = 1) plt.title('mse_train') # 測試誤差 for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), test_l[i], label=name) plt.legend(loc = 1) plt.title('mse_test')
我們發現,使用HE初始化之后模型收斂速度明顯提升。
2.2 PyTorch實現HE初始化的正態分布參數創建
??類似的,我們可以使用torch.nn.init.kaiming_normal_來進行滿足正態分布的初始化參數設置。
nn.init.kaiming_normal_? #Signature: # nn.init.kaiming_normal_( # tensor, # a=0, # mode='fan_in', # nonlinearity='leaky_relu', # ) # Docstring: # Fills the input `Tensor` with values according to the method # described in `Delving deep into rectifiers: Surpassing human-level # performance on ImageNet classification` - He, K. et al. (2015), using a # normal distribution. The resulting tensor will have values sampled from # :math:`\mathcal{N}(0, \text{std}^2)` where# .. math:: # \text{std} = \frac{\text{gain}}{\sqrt{\text{fan\_mode}}}# Also known as He initialization.# Args: # tensor: an n-dimensional `torch.Tensor` # a: the negative slope of the rectifier used after this layer (only # used with ``'leaky_relu'``) # mode: either ``'fan_in'`` (default) or ``'fan_out'``. Choosing ``'fan_in'`` # preserves the magnitude of the variance of the weights in the # forward pass. Choosing ``'fan_out'`` preserves the magnitudes in the # backwards pass. # nonlinearity: the non-linear function (`nn.functional` name), # recommended to use only with ``'relu'`` or ``'leaky_relu'`` (default).# Examples: # >>> w = torch.empty(3, 5) # >>> nn.init.kaiming_normal_(w, mode='fan_out', nonlinearity='relu') # File: d:\users\asus\anaconda3\lib\site-packages\torch\nn\init.py # Type: function同樣,說明文檔中的公式存在錯誤。以下是說明文檔中的公式:
std=gainfan_mode\text{std} = \frac{\text{gain}}{\sqrt{\text{fan\_mode}}}std=fan_mode?gain?實際上,PyTorch中所使用的HE初始化時,初始參數的方差為:Var(w)=2(1+a2)faninVar(w) = \frac{2}{(1+a^2)fan_{in}}Var(w)=(1+a2)fanin?2?
其中a為ReLU變種激活函數的修正系數,需要配合ReLU變種激活函數共同使用,扇入神經元個數和扇出神經元個數等效,并且std(w)=2(1+a2)faninstd(w) = \sqrt{\frac{2}{(1+a^2)fan_{in}}}std(w)=(1+a2)fanin?2??接下來,簡單實踐測試效果:
同樣,經過HE初始化后的參數,模型收斂速度更快,也證明了HE初始化的有效性。
三、參數初始化的作用局限
??截止目前,我們討論了圍繞Glorot條件進行的參數初始化方法,當然,合理的設置初始參數值能夠一定程度上使得模型各層都得到有效的學習,模型訓練過程更加平穩、收斂速度也更快。但由于我們設置的是初始條件,伴隨著模型不斷訓練,由于受到激活函數導函數本身特性影響,仍然有可能在迭代過程中出現梯度不均衡的現象。
??然而模型一旦開始訓練,我們是不能手動參與修改模型參數的。那此時應該如何處理梯度不均衡的問題呢?我們知道,影響梯度計算的三個核心因素,分別是參數狀態值、激活值和輸入的數據,參數狀態值由模型迭代的數學過程決定,激活值很大程度上由我們所選取的激活函數決定,如果從Glorot條件入手,我們就只剩下一個可以人工修改的選項:每一個線性層接收到的數據。
總結
以上是生活随笔為你收集整理的Lesson 13.5 Xavier方法与kaiming方法(HE初始化)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 13.4 Dead ReL
- 下一篇: Lesson 14.1 数据归一化与Ba