一:隨機初始化
當我們使用梯度下降法或者其他高級優化算法時,我們需要對參數θ選取一些初始值。對於高級優化算法,會默認認為我們已經為變量θ設置了初始值:
同樣,對於梯度下降法,我們也需要對θ進行初始化。之后我們可以一步一步通過梯度下降來最小化代價函數J,那么如何來對θ進行初始化值呢?
(一)將θ全部設置為0---神經網絡中不適用
盡管在邏輯回歸中,可以這樣使用。但是在實際神經網絡訓練中起不到作用。
如果我們初始化所有θ全為0(全部相等):
那么對於每個隱藏單元的輸入基本都是一樣的:
所以求得的偏導也是一致的,
當我們只是設置了一個步長,那么對於每次參數的更新以后。各個隱藏單元的權重還是一致的。
這就意味着這個神經網絡計算不出很好的函數,當我們有很多的隱藏單元時,所有的隱藏單元都在計算相同的特征,都在以相同的函數作為輸入。---高度冗余。所以無論后面有幾個輸出單元,最終只能得到一個特征,這種情況阻止了神經網絡學習有趣的東西
(二)隨機初始化---解決上面所有權重都相等的問題(對稱問題)
(三)代碼實現
def rand_initiation(l_in,l_out): #對於相鄰兩層 w = np.zeros((l_out,l_in+1)) #需要加上前面一層的偏執單元權重 eps_init = 0.12 #接近0,保持在-ε到ε之間 w = np.random.rand(l_out,l_in+1)*2*eps_init-eps_init return w
w = rand_initiation(3,5) print(w.shape) print(w)
或者使用下面方法實現隨機初始化:
def debug_initialize_weights(fan_in,fan_out): w = np.zeros((fan_out,1+fan_in)) w = np.sin(np.arange(w.size)).reshape(w.shape) / 10 #使用sin保證值在(-1,1)之間,再除以10,則值在(-0.1,0.1)之間初始化 return w
w = debug_initialize_weights(3,5) print(w.shape) print(w)
二:梯度檢驗--確保我們實現的反向傳播代碼正確性
(一)反向傳播存在的問題
前面我們學習了如何使用神經網絡中的前向傳播和反向傳播算法計算導數,但是反向傳播算法含有許多細節,而且實現比較復雜,並且由一個不好的特性:
當反向傳播與梯度下降或是其他算法一同工作時,可能會存在一些不容易察覺的錯誤,意味着,雖然代價函數J看上去在不斷減小,但最終的結果可能並不是最優解。其誤差會比無Bug的情況下高出一個量級。並且我們很可能不知道我們所出現的問題是由Bug導致的。
解決方法:---梯度檢驗
幾乎可以解決所有這種問題。幾乎在所有反向傳播,或者其他類似梯度下降算法使用的同時,都會進行梯度檢驗。他將保證前向傳播已經后面的反向傳播是完全正確的。
可以用於驗證自己寫的代碼確實能夠正確的計算出代價函數J的導數
(二)定義法求解導數
當θ是一個向量時,我們則需要對偏導數進行檢驗:
(三)算法實現
n代表了θ參數維度,我們對每一個θ_i執行上面的求導方法。之后檢驗我們使用導數定義求得的導數和使用反向傳播求得的導數是否接近(幾位小數差距)或者相等。那么我們就可以確定反向傳播的實現是正確的。可以很好的優化J(θ)
注意:我們只需要使用一次梯度檢驗來檢驗我們反向傳播算法求得的導數是否正確。如果正確,那么我們后面的學習就需要關閉梯度檢驗(因為使用梯度檢驗計算量大、且慢)
注意:簡單的梯度檢驗 利用數值估計的梯度和真實的梯度二范數之差除以二范數值之和就是誤差
(四)代碼實現
def compute_numerial_gradient(cost_func,theta): #用於計算梯度 numgrad = np.zeros(theta.size) #保存梯度 perturb = np.zeros(theta.size) #保持每一次計算梯度,只有一個維度的θ_j可以減去ε e = 1e-4 for i in range(theta.size): perturb[i] = e J_1 = cost_func(theta-perturb) J_2 = cost_func(theta+perturb) numgrad[i] = (J_2-J_1)/(2*e) perturb[i] = 0 #需要將修改的位置數據還原 return numgrad
def check_gradients(lamda): #因為一個真的訓練集數據太大,所以我們這里隨機生成一些小規模數據,進行計檢驗 input_layer_size = 3 #輸入層 hidden_layer_size = 5 #隱藏層 num_labels = 3 #輸出層---分類數 m = 5 #數據集條數 #初始化權重參數 theta1 = debug_initialize_weights(input_layer_size,hidden_layer_size) theta2 = debug_initialize_weights(hidden_layer_size,num_labels) #同理:初始化一些訓練集X和標簽值y X = debug_initialize_weights(input_layer_size - 1,m) #訓練集是m行,input_layer_size列 y = 1 + np.mod(np.arange(1,m+1),num_labels) #取余分類別1->num_labels encoder = OneHotEncoder(sparse=False) #sparse=True 表示編碼的格式,默認為 True,即為稀疏的格式,指定 False 則就不用 toarray() 了 y_onehot = encoder.fit_transform(np.array([y]).T) #需要y是列向量 #展開參數 theta_param = np.concatenate([np.ravel(theta1),np.ravel(theta2)]) #1.使用導數定義求偏導 def cost_func(theta_p): return cost(theta_p,input_layer_size,hidden_layer_size,num_labels,X,y_onehot,lamda) #代價函數求偏導J(θ) numgrad = compute_numerial_gradient(cost_func,theta_param) #2.使用反向傳播求偏導 J,grad = backporp(theta_param,input_layer_size,hidden_layer_size,num_labels,X,y_onehot,lamda) print(np.c_[grad,numgrad,grad-numgrad]) #進行結果輸出,對比
check_gradients(0) #修改lamda還是很接近的
可以看出我們使用導數定義求得的導數和使用反向傳播求得的導數十分接近!!!
所以:我們的反向傳播算法是正確的!!!