前言
神經網絡中的權重(weight)初始化是個常常被忽略的問題。
最近在手寫一個Python的神經網絡庫(GitHub:hamaa),剛開始為了測試代碼是否寫對,搭建了一個2->4->2
的單隱層神經網絡來擬合異或運算,擬合結果十分完美。但是在做MNIST手寫數字識別,將網絡擴展到了784->100->10
時,發現損失函數一直不下降,訓練準確率一直停留在10%左右(和隨機猜的命中概率一樣嘛)。一直以為是back propagation的代碼寫錯了,debug了整整兩天都沒發現錯誤,結果輸出中間weights的梯度dw看看,發現兩個權重矩陣的梯度都是在1e-10
左右的數量級。后來查詢了一些資料,原來是代碼缺少了權重初始化(weight initialization)這及其重要的一步。增加了權重初始化后擬合結果終于正常。
在以前看一些關于神經網絡的資料時,我也經常看到“權重初始化”這一步,但一直錯誤地以為“權重初始化”等價于“權重隨機初始化”,以為僅僅將權重初始化為很小的隨機數即可,但其實它的原因除了打破梯度更新對稱性之外,還有更深層次的原因。所以接下來文章分為兩部分,分別介紹為什么需要進行權重初始化,以及如何進行權重初始化。
權重初始化:Why
在創建了神經網絡后,通常需要對權重和偏置進行初始化,大部分的實現都是采取Gaussian distribution來生成隨機初始值。假設現在輸入層有1000個神經元,隱藏層有1個神經元,輸入數據x為一個全為1的1000維向量,采取高斯分布來初始化權重矩陣w,偏置b取0。下面的代碼計算隱藏層的輸入z:
# -*- coding:utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
def run():
x = np.ones(1000)
w = np.random.randn(1000)
b = 0
# z為加權和
z = np.sum(x * w) + b
print z
然而通過上述初始化后,因為w服從均值為0、方差為1的正太分布,x全為1,b全為0,輸入層一共1000個神經元,所以z服從的是一個均值為0、方差為1000的正太分布。修改代碼如下,生成20000萬個z并查看其均值、方差以及分布圖像:
def run():
# z的個數
t = 20000
z_lst = np.empty(t)
x = np.ones(1000)
b = 0
for i in xrange(t):
w = np.random.randn(1000)
z = np.sum(x * w) + b
z_lst[i] = z
print 'z 均值:', np.mean(z_lst)
print 'z 方差:', np.var(z_lst)
plt.hist(z_lst, bins=100)
plt.show()
輸出結果如下:
z 均值: -0.0402106463845
z 方差: 997.082082524
輸出圖像如下:
在此情況下,z有可能是一個遠小于-1或者遠大于1的數,通過激活函數(比如sigmoid)后所得到的輸出會非常接近0或者1,也就是隱藏層神經元處于飽和的狀態。所以當出現這樣的情況時,在權重中進行微小的調整僅僅會給隱藏層神經元的激活值帶來極其微弱的改變。而這種微弱的改變也會影響網絡中剩下的神經元,然后會帶來相應的代價函數的改變。結果就是,這些權重在我們進行梯度下降算法時會學習得非常緩慢[1]。
因此,我們可以通過改變權重w的分布,使|z|盡量接近于0。這就是我們為什么需要進行權重初始化的原因了。
權重初始化:How
一種簡單的做法是修改w的分布,使得z服從均值為0、方差為1的標準正態分布。根據正太分布期望與方差的特性,將w除以sqrt(1000)即可。修改后代碼如下:
def run():
# z的個數
t = 20000
z_lst = np.empty(t)
# 輸入神經元個數
m = 1000
x = np.ones(m)
b = 0
for i in xrange(t):
w = np.random.randn(m) / np.sqrt(m)
z = np.sum(x * w) + b
z_lst[i] = z
print 'z 均值:', np.mean(z_lst)
print 'z 方差:', np.var(z_lst)
# 保持與z分布(1)圖像橫坐標刻度不變,使得結果更加直觀
plt.xlim([-150, 150])
plt.hist(z_lst, bins=10)
plt.show()
輸出結果如下:
z 均值: 0.013468729222
z 方差: 1.00195898464
輸出圖像如下:
這樣的話z的分布就是一個比較接近于0的數,使得神經元處于不太飽和的狀態,讓BP過程能夠正常進行下去。
除了這種方式之外(除以前一層神經元的個數n_in的開方),還有許多針對不同激活函數的不同權重初始化方法,比如兼顧了BP過程的除以( (n_in + n_out)/2 )。具體可以參考Stanford CS231n課程中提到的各種方法。
后記
最后強烈安利Stanford CS231n的官方授權中文翻譯專欄知乎專欄:智能單元,感謝各位Zhihuer的辛勤翻譯,為后輩的學習與快速上手提供了極大的便利。
參考
[1] www.neuralnetworksanddeeplearning.com,第三章
[2] Stanford CS231n
[3] Stanford CS231n官方授權中文翻譯,知乎專欄:智能單元