摘要:但是在多層神經網絡,參數量非常巨大并且激活函數是非線性函數時,我們的損失函數極不可能是一個凸函數。
作者:chen_h
微信號 & QQ:862251340
微信公眾號:coderpai
簡書地址:https://www.jianshu.com/p/1fe...
這篇教程是翻譯Peter Roelants寫的神經網絡教程,作者已經授權翻譯,這是原文。
該教程將介紹如何入門神經網絡,一共包含五部分。你可以在以下鏈接找到完整內容。
(一)神經網絡入門之線性回歸
Logistic分類函數
(二)神經網絡入門之Logistic回歸(分類問題)
(三)神經網絡入門之隱藏層設計
Softmax分類函數
(四)神經網絡入門之矢量化
(五)神經網絡入門之構建多層網絡
矢量化這部分教程將介紹三部分:
矢量的反向傳播
梯度檢查
動量
在先前的教程中,我們已經使用學習了一個非常簡單的神經網絡:一個輸入數據,一個隱藏神經元和一個輸出結果。在這篇教程中,我們將描述一個稍微復雜一點的神經網絡:包括一個二維的輸入數據,三維的隱藏神經元和二維的輸出結果,并且利用softmax函數來做最后的分類。在之前的網絡中,我們都沒有添加偏差項,但在這個網絡模型中我們加入偏差項。網絡模型如下:
我們先導入教程需要使用的軟件包。
import numpy as np import sklearn.datasets import matplotlib.pyplot as plt from matplotlib.colors import colorConverter, ListedColormap from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm定義數據集
在這個教程中,我們的分類目標還是二分類:紅色類別(t=1)和藍色類別(t=0)。紅色樣本是一個環形分布,位于環形中間的是藍色樣本。我們利用scikit-learn的make_circles的方法得到數據集。
這是一個二維的數據集,而且不是線性分割。如果我們利用教程2中的方法,那么我們不能正確將它們分類,因為教程2中的方法只能學習出線性分割的樣本數據。但我們增加一個隱藏層,就能學習這個非線性分割了。
我們有N個輸入數據,每個數據有兩個特征,那么我們可以得到矩陣X如下:
其中,xij表示第i個樣本的第j個特征值。
經過softmax函數之后,該模型輸出的最終結果T為:
其中,當且僅當第i個樣本屬于類別j時,tij=1。因此,我們定義藍色樣本的標記是T = [0 1],紅色樣本的標記是T = [1 0]。
# Generate the dataset X, t = sklearn.datasets.make_circles(n_samples=100, shuffle=False, factor=0.3, noise=0.1) T = np.zeros((100,2)) # Define target matrix T[t==1,1] = 1 T[t==0,0] = 1 # Separate the red and blue points for plotting x_red = X[t==0] x_blue = X[t==1] print("shape of X: {}".format(X.shape)) print("shape of T: {}".format(T.shape))
shape of X: (100, 2)
shape of T: (100, 2)
# Plot both classes on the x1, x2 plane plt.plot(x_red[:,0], x_red[:,1], "ro", label="class red") plt.plot(x_blue[:,0], x_blue[:,1], "bo", label="class blue") plt.grid() plt.legend(loc=1) plt.xlabel("$x_1$", fontsize=15) plt.ylabel("$x_2$", fontsize=15) plt.axis([-1.5, 1.5, -1.5, 1.5]) plt.title("red vs blue classes in the input space") plt.show()矢量的反向傳播 1. 矢量的正向傳播
將二維的輸入樣本X轉換成三維的輸入層H,我們需要使用鏈接矩陣Wh(Whij表示第i個輸入特征和第j個隱藏層神經元相連的權重)和偏差向量bh:
之后計算結果如下:
其中,σ是一個Logistic函數,H是一個N*3的輸出矩陣。
整個計算流過程如下圖所示。每個輸入特征xij和權重參數whj1、whj2、whj3相乘。最后相乘的每行k(xij*whjk)結果進行累加得到zik,之后經過Logistic函數σ進行非線性激活得到hik。注意,偏差項Bh可以被整合在鏈接矩陣Wh中,只需要通過在輸入參數xi中增加一個+1項。
在代碼中,Bh和Wh使用bh和Wh表示。hidden_activations(X, Wh, bh)函數實現了隱藏層的激活輸出。
在計算輸出層的激活結果之前,我們需要先定義3*2的鏈接矩陣Wo(Woij表示隱藏層第i個神經元和輸出層第j個輸出單元的鏈接權重)和2*1的偏差項矩陣bo:
之后計算結果如下:
其中,?表示softmax函數。Y表示最后輸出的n*2的矩陣,Zod表示矩陣Zo的第d列,Wod表示矩陣Wo的第d列,bod表示向量bo的第d個元素。
在代碼中,bo和Wo用變量bo和Wo來表示。output_activations(H, Wo, bo)函數實現了輸出層的激活結果。
# Define the logistic function def logistic(z): return 1 / (1 + np.exp(-z)) # Define the softmax function def softmax(z): return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True) # Function to compute the hidden activations def hidden_activations(X, Wh, bh): return logistic(X.dot(Wh) + bh) # Define output layer feedforward def output_activations(H, Wo, bo): return softmax(H.dot(Wo) + bo) # Define the neural network function def nn(X, Wh, bh, Wo, bo): return output_activations(hidden_activations(X, Wh, bh), Wo, bo) # Define the neural network prediction function that only returns # 1 or 0 depending on the predicted class def nn_predict(X, Wh, bh, Wo, bo): return np.around(nn(X, Wh, bh, Wo, bo))2. 矢量的反向傳播
最后一層的輸出層使用的是softmax函數,該函數的交叉熵損失函數在這篇教程中已經有了很詳細的描述。如果需要對N個樣本進行C個分類,那么它的損失函數ξ是:
損失函數的誤差梯度δo可以非常方便得到:
其中,Zo(Zo=H?Wo+bo)是一個n*2的矩陣,T是一個n*2的目標矩陣,Y是一個經過模型得到的n*2的輸出矩陣。因此,δo也是一個n*2的矩陣。
在代碼中,δo用Eo表示,error_output(Y, T)函數實現了該方法。
對于N個樣本,對輸出層的梯度δwoj是通過?ξ/?woj計算的,具體計算如下:
其中,woj表示Wo的第j行,即是一個1*2的向量。因此,我們可以將上式改寫成一個矩陣操作,即:
最后梯度的結果是一個3*2的Jacobian矩陣,如下:
在代碼中,JWo表示上面的Jwo。gradient_weight_out(H, Eo)函數實現了上面的操作。
對于偏差項bo可以采用相同的方式進行更新。對于批處理的N個樣本,對輸出層的梯度?ξ/?bo的計算如下:
最后梯度的結果是一個2*1的Jacobian矩陣,如下:
在代碼中,Jbo表示上面的Jbo。gradient_bias_out(Eo)函數實現了上面的操作。
# Define the cost function def cost(Y, T): return - np.multiply(T, np.log(Y)).sum() # Define the error function at the output def error_output(Y, T): return Y - T # Define the gradient function for the weight parameters at the output layer def gradient_weight_out(H, Eo): return H.T.dot(Eo) # Define the gradient function for the bias parameters at the output layer def gradient_bias_out(Eo): return np.sum(Eo, axis=0, keepdims=True)
在隱藏層上面的誤差函數的梯度δh可以被定義如下:
其中,Zh是一個n*3的輸入到Logistic函數中的輸入矩陣,即:
其中,δh也是一個N*3的矩陣。
接下來,對于每個樣本i和每個神經元j,我們計算誤差梯度δhij的導數。通過前面一層的誤差反饋,通過BP算法我們可以計算如下:
其中,woj表示Wo的第j行,它是一個1*2的向量。δoi也是一個1*2的向量。因此,維度是N*3的誤差矩陣δh可以被如下計算:
其中,°表示逐點乘積
在代碼中,Eh表示上面的δh。error_hidden(H, Wo, Eo)函數實現了上面的函數。
在N個樣本中,隱藏層的梯度?ξ/?whj可以被如下計算:
其中,whj表示Wh的第j行,它是一個1*3的向量。我們可以將上面的式子寫成矩陣相乘的形式如下:
梯度最后的結果是一個2*3的Jacobian矩陣,如下:
在代碼中,JWh表示Jwh。gradient_weight_hidden(X, Eh)函數實現了上面的操作。
偏差項bh可以按照相同的方式進行更新操作。在N個樣本上面,梯度?ξ/?bh可以如下計算:
最后的梯度結果是一個1*3的Jacobian矩陣,如下:
在代碼中,Jbh表示Jbh。gradient_bias_hidden(Eh)函數實現了上面的方法。
# Define the error function at the hidden layer def error_hidden(H, Wo, Eo): # H * (1-H) * (E . Wo^T) return np.multiply(np.multiply(H,(1 - H)), Eo.dot(Wo.T)) # Define the gradient function for the weight parameters at the hidden layer def gradient_weight_hidden(X, Eh): return X.T.dot(Eh) # Define the gradient function for the bias parameters at the output layer def gradient_bias_hidden(Eh): return np.sum(Eh, axis=0, keepdims=True)梯度檢查
在編程計算反向傳播梯度時,很容易產生錯誤。這就是為什么一直推薦在你的模型中一定要進行梯度檢查。梯度檢查是通過對于每一個參數進行梯度數值計算進行的,即檢查這個數值與通過反向傳播的梯度進行比較計算。
假設對于參數θi,我們計算它的數值梯度?ξ/?θi如下:
其中,f是一個神經網絡的方程,X是輸入數據,θ表示所有參數的集合。?是一個很小的值,用來對參數θi進行評估。
對于每個參數的數值梯度應該接近于反向傳播梯度的參數。
# Initialize weights and biases init_var = 1 # Initialize hidden layer parameters bh = np.random.randn(1, 3) * init_var Wh = np.random.randn(2, 3) * init_var # Initialize output layer parameters bo = np.random.randn(1, 2) * init_var Wo = np.random.randn(3, 2) * init_var # Compute the gradients by backpropagation # Compute the activations of the layers H = hidden_activations(X, Wh, bh) Y = output_activations(H, Wo, bo) # Compute the gradients of the output layer Eo = error_output(Y, T) JWo = gradient_weight_out(H, Eo) Jbo = gradient_bias_out(Eo) # Compute the gradients of the hidden layer Eh = error_hidden(H, Wo, Eo) JWh = gradient_weight_hidden(X, Eh) Jbh = gradient_bias_hidden(Eh)
# Combine all parameter matrices in a list params = [Wh, bh, Wo, bo] # Combine all parameter gradients in a list grad_params = [JWh, Jbh, JWo, Jbo] # Set the small change to compute the numerical gradient eps = 0.0001 # Check each parameter matrix for p_idx in range(len(params)): # Check each parameter in each parameter matrix for row in range(params[p_idx].shape[0]): for col in range(params[p_idx].shape[1]): # Copy the parameter matrix and change the current parameter slightly p_matrix_min = params[p_idx].copy() p_matrix_min[row,col] -= eps p_matrix_plus = params[p_idx].copy() p_matrix_plus[row,col] += eps # Copy the parameter list, and change the updated parameter matrix params_min = params[:] params_min[p_idx] = p_matrix_min params_plus = params[:] params_plus[p_idx] = p_matrix_plus # Compute the numerical gradient grad_num = (cost(nn(X, *params_plus), T)-cost(nn(X, *params_min), T))/(2*eps) # Raise error if the numerical grade is not close to the backprop gradient if not np.isclose(grad_num, grad_params[p_idx][row,col]): raise ValueError("Numerical gradient of {:.6f} is not close to the backpropagation gradient of {:.6f}!".format(float(grad_num), float(grad_params[p_idx][row,col]))) print("No gradient errors found")
No gradient errors found
動量反向傳播的更新在前面幾個例子中,我們使用最簡單的梯度下降算法,根據損失函數來優化參數,因為這些損失函數都是凸函數。但是在多層神經網絡,參數量非常巨大并且激活函數是非線性函數時,我們的損失函數極不可能是一個凸函數。那么此時,簡單的梯度下降算法不是最好的方法去找到一個全局的最小值,因為這個方法是一個局部優化的方法,最后將收斂在一個局部最小值。
為了解決這個例子中的問題,我們使用一種梯度下降算法的改進版,叫做動量方法。這種動量方法,你可以想象成一只球從損失函數的表面從高處落下。在下降的過程中,這個球的速度會增加,但是當它上坡時,它的速度會下降,這一個過程可以用下面的數學公式進行描述:
其中,V(i)表示參數第i次迭代時的速度。θ(i)表示參數在第i次迭代時的值。?ξ/?θ(i)表示參數在第i次迭代時的梯度。λ表示速度根據阻力減小的值,μ表示學習率。這個數學描述公式可以被可視化為如下圖:
速度VWh,Vbh,VWo和Vbo對應于參數Wh,bh,Wo和bo,在代碼中,這些參數用VWh,Vbh,VWo和Vbo表示,并且被存儲在一個列表Vs中。update_velocity(X, T, ls_of_params, Vs, momentum_term, learning_rate)函數實現了上面的方法。update_params(ls_of_params, Vs)函數實現了速度的更新方法。
# Define the update function to update the network parameters over 1 iteration def backprop_gradients(X, T, Wh, bh, Wo, bo): # Compute the output of the network # Compute the activations of the layers H = hidden_activations(X, Wh, bh) Y = output_activations(H, Wo, bo) # Compute the gradients of the output layer Eo = error_output(Y, T) JWo = gradient_weight_out(H, Eo) Jbo = gradient_bias_out(Eo) # Compute the gradients of the hidden layer Eh = error_hidden(H, Wo, Eo) JWh = gradient_weight_hidden(X, Eh) Jbh = gradient_bias_hidden(Eh) return [JWh, Jbh, JWo, Jbo] def update_velocity(X, T, ls_of_params, Vs, momentum_term, learning_rate): # ls_of_params = [Wh, bh, Wo, bo] # Js = [JWh, Jbh, JWo, Jbo] Js = backprop_gradients(X, T, *ls_of_params) return [momentum_term * V - learning_rate * J for V,J in zip(Vs, Js)] def update_params(ls_of_params, Vs): # ls_of_params = [Wh, bh, Wo, bo] # Vs = [VWh, Vbh, VWo, Vbo] return [P + V for P,V in zip(ls_of_params, Vs)]
# Run backpropagation # Initialize weights and biases init_var = 0.1 # Initialize hidden layer parameters bh = np.random.randn(1, 3) * init_var Wh = np.random.randn(2, 3) * init_var # Initialize output layer parameters bo = np.random.randn(1, 2) * init_var Wo = np.random.randn(3, 2) * init_var # Parameters are already initilized randomly with the gradient checking # Set the learning rate learning_rate = 0.02 momentum_term = 0.9 # define the velocities Vs = [VWh, Vbh, VWo, Vbo] Vs = [np.zeros_like(M) for M in [Wh, bh, Wo, bo]] # Start the gradient descent updates and plot the iterations nb_of_iterations = 300 # number of gradient descent updates lr_update = learning_rate / nb_of_iterations # learning rate update rule ls_costs = [cost(nn(X, Wh, bh, Wo, bo), T)] # list of cost over the iterations for i in range(nb_of_iterations): # Update the velocities and the parameters Vs = update_velocity(X, T, [Wh, bh, Wo, bo], Vs, momentum_term, learning_rate) Wh, bh, Wo, bo = update_params([Wh, bh, Wo, bo], Vs) ls_costs.append(cost(nn(X, Wh, bh, Wo, bo), T))
# Plot the cost over the iterations plt.plot(ls_costs, "b-") plt.xlabel("iteration") plt.ylabel("$xi$", fontsize=15) plt.title("Decrease of cost over backprop iteration") plt.grid() plt.show()可視化訓練分類結果
在下圖中,我們利用輸入數據X和目標結果T,利用動量方法對BP算法進行更新得到的分類邊界。我們利用紅色和藍色去區分輸入數據的分類域。從圖中,我們可以看出,我們把所有的樣本都正確分類了。
# Plot the resulting decision boundary # Generate a grid over the input space to plot the color of the # classification at that grid point nb_of_xs = 200 xs1 = np.linspace(-2, 2, num=nb_of_xs) xs2 = np.linspace(-2, 2, num=nb_of_xs) xx, yy = np.meshgrid(xs1, xs2) # create the grid # Initialize and fill the classification plane classification_plane = np.zeros((nb_of_xs, nb_of_xs)) for i in range(nb_of_xs): for j in range(nb_of_xs): pred = nn_predict(np.asmatrix([xx[i,j], yy[i,j]]), Wh, bh, Wo, bo) classification_plane[i,j] = pred[0,0] # Create a color map to show the classification colors of each grid point cmap = ListedColormap([ colorConverter.to_rgba("b", alpha=0.30), colorConverter.to_rgba("r", alpha=0.30)]) # Plot the classification plane with decision boundary and input samples plt.contourf(xx, yy, classification_plane, cmap=cmap) # Plot both classes on the x1, x2 plane plt.plot(x_red[:,0], x_red[:,1], "ro", label="class red") plt.plot(x_blue[:,0], x_blue[:,1], "bo", label="class blue") plt.grid() plt.legend(loc=1) plt.xlabel("$x_1$", fontsize=15) plt.ylabel("$x_2$", fontsize=15) plt.axis([-1.5, 1.5, -1.5, 1.5]) plt.title("red vs blue classification boundary") plt.show()輸入域的轉換
存在兩個理由去解釋為什么這個神經網絡可以將這個非線性的數據進行分類。第一,因為隱藏層中使用了非線性的Logistic函數來幫助數據分類。第二,隱藏層使用了三個維度,即我們將數據從低維映射到了高維數據。下面的圖繪制除了在隱藏層中的三維數據分類。
# Plot the projection of the input onto the hidden layer # Define the projections of the blue and red classes H_blue = hidden_activations(x_blue, Wh, bh) H_red = hidden_activations(x_red, Wh, bh) # Plot the error surface fig = plt.figure() ax = Axes3D(fig) ax.plot(np.ravel(H_blue[:,0]), np.ravel(H_blue[:,1]), np.ravel(H_blue[:,2]), "bo") ax.plot(np.ravel(H_red[:,0]), np.ravel(H_red[:,1]), np.ravel(H_red[:,2]), "ro") ax.set_xlabel("$h_1$", fontsize=15) ax.set_ylabel("$h_2$", fontsize=15) ax.set_zlabel("$h_3$", fontsize=15) ax.view_init(elev=10, azim=-40) plt.title("Projection of the input X onto the hidden layer H") plt.grid() plt.show()
完整代碼,點擊這里
作者:chen_h
微信號 & QQ:862251340
簡書地址:https://www.jianshu.com/p/1fe...
CoderPai 是一個專注于算法實戰的平臺,從基礎的算法到人工智能算法都有設計。如果你對算法實戰感興趣,請快快關注我們吧。加入AI實戰微信群,AI實戰QQ群,ACM算法微信群,ACM算法QQ群。長按或者掃描如下二維碼,關注 “CoderPai” 微信號(coderpai)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/41185.html
摘要:對于多分類問題,我們使用函數來處理多項式回歸。概率方程表示輸出根據函數得到的值。最大似然估計可以寫成因為對于給定的參數,去產生和,根據聯合概率我們又能將似然函數改寫成。 作者:chen_h微信號 & QQ:862251340微信公眾號:coderpai簡書地址:https://www.jianshu.com/p/abc... 這篇教程是翻譯Peter Roelants寫的神經網絡教程...
摘要:對于多分類問題,我們可以使用多項回歸,該方法也被稱之為函數。函數的交叉熵損失函數的推導損失函數對于的導數求解如下上式已經求解了當和的兩種情況。最終的結果為,這個求導結果和函數的交叉熵損失函數求導是一樣的,再次證明函數是函數的一個擴展板。 作者:chen_h微信號 & QQ:862251340微信公眾號:coderpai簡書地址:https://www.jianshu.com/p/8eb...
摘要:神經網絡的模型結構為,其中是輸入參數,是權重,是預測結果。損失函數我們定義為對于損失函數的優化,我們采用梯度下降,這個方法是神經網絡中常見的優化方法。函數實現了神經網絡模型,函數實現了損失函數。 作者:chen_h微信號 & QQ:862251340微信公眾號:coderpai簡書地址:https://www.jianshu.com/p/0da... 這篇教程是翻譯Peter Roe...
摘要:那么,概率將是神經網絡輸出的,即。函數實現了函數,函數實現了損失函數,實現了神經網絡的輸出結果,實現了神經網絡的預測結果。 作者:chen_h微信號 & QQ:862251340微信公眾號:coderpai簡書地址:https://www.jianshu.com/p/d94... 這篇教程是翻譯Peter Roelants寫的神經網絡教程,作者已經授權翻譯,這是原文。 該教程將介紹如...
閱讀 2449·2019-08-30 15:52
閱讀 2246·2019-08-30 12:51
閱讀 2842·2019-08-29 18:41
閱讀 2826·2019-08-29 17:04
閱讀 820·2019-08-29 15:11
閱讀 1733·2019-08-28 18:02
閱讀 3610·2019-08-26 10:22
閱讀 2517·2019-08-26 10:12