從零開始構(gòu)建簡(jiǎn)單人工神經(jīng)網(wǎng)絡(luò):1個(gè)隱藏層
譯文【51CTO.com快譯】在上一篇文章《從零開始構(gòu)建一個(gè)人工神經(jīng)網(wǎng)絡(luò)(上)》中,我們一開始討論了什么是人工神經(jīng)網(wǎng)絡(luò),接著介紹了如何使用Python從零開始構(gòu)建一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),只有1個(gè)輸入層和1個(gè)輸出層。這種神經(jīng)網(wǎng)絡(luò)名為Perceptron。然而,能夠執(zhí)行圖像分類和股市分析等復(fù)雜任務(wù)的實(shí)際神經(jīng)網(wǎng)絡(luò)除了輸入層和輸出層外還有多個(gè)隱藏層。
我們?cè)谏掀械贸鼋Y(jié)論,Perceptron能夠找到線性決策邊界。我們使用Perceptron借助虛擬數(shù)據(jù)集來(lái)預(yù)測(cè)某人是否患有糖尿病。然而,Perceptron無(wú)法找到非線性決策邊界。
我們?cè)诒疚闹袑?gòu)建一個(gè)有1個(gè)輸入層、1個(gè)隱藏層和1個(gè)輸出層的神經(jīng)網(wǎng)絡(luò)。我們會(huì)看到,我們構(gòu)建的神經(jīng)網(wǎng)絡(luò)能夠找到非線性邊界。
生成數(shù)據(jù)集
不妨先創(chuàng)造可供試用的數(shù)據(jù)集。幸好,scikit-learn有一些有用的數(shù)據(jù)集生成器,因此我們不需要自行編寫代碼。我們將使用make_moons函數(shù)。
- from sklearn import datasets
- np.random.seed(0)
- feature_set, labels = datasets.make_moons(300, noise=0.20)
- plt.figure(figsize=(10,7))
- plt.scatter(feature_set[:,0], feature_set[:,1], c=labels, cmap=plt.cm.Spectral)
圖1
我們生成的數(shù)據(jù)集有兩個(gè)類別,分別標(biāo)為紅點(diǎn)和藍(lán)點(diǎn)??梢詫⑺{(lán)點(diǎn)視為男性患者,將紅點(diǎn)視為女性患者,x軸和y軸是醫(yī)學(xué)度量指標(biāo)。
我們的目的是訓(xùn)練可根據(jù)x和y坐標(biāo)預(yù)測(cè)正確類別(男性或女性)的機(jī)器學(xué)習(xí)分類器。請(qǐng)注意,數(shù)據(jù)不是線性可分離的,我們無(wú)法繪制將兩個(gè)類別分開的直線。這意味著,除非你手動(dòng)設(shè)計(jì)適用于特定數(shù)據(jù)集的非線性特征(比如多項(xiàng)式),否則線性分類器(比如沒有任何隱藏層甚至沒有邏輯回歸的ANN)將無(wú)法擬合數(shù)據(jù)。
有1個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò)
這是我們的簡(jiǎn)單網(wǎng)絡(luò):
圖2
我們有兩個(gè)輸入:x1和x2。有單單一個(gè)隱藏層,它有3個(gè)單元(節(jié)點(diǎn)):h1、h2和h3。最后,有兩個(gè)輸出:y1和y2。連接它們的箭頭是權(quán)重。有兩個(gè)權(quán)重矩陣:w和u。w權(quán)重連接輸入層和隱藏層,u權(quán)重連接隱藏層和輸出層。我們使用字母w和u,那樣更容易關(guān)注要關(guān)注的計(jì)算。你還能看到我們將輸出y1和y2與目標(biāo)t1和t2進(jìn)行了比較。
進(jìn)行計(jì)算之前,我們需要介紹最后一個(gè)字母。讓a成為激活前的線性組合。因此,我們有:
由于我們無(wú)法窮盡所有激活函數(shù)和所有損失函數(shù),因此專注于兩種最常見的函數(shù)。Sigmoid激活和L2范數(shù)損失。有了該新信息和新符號(hào),輸出y等于激活的線性組合。
因此,就輸出層而言,我們有:
由于方法不同,我們將分別檢查輸出層和隱藏層的反向傳播。
我想提醒諸位:
Sigmoid函數(shù)是:
導(dǎo)數(shù)是:
輸出層的反向傳播
為了獲得更新規(guī)則:
我們必須計(jì)算
以單個(gè)權(quán)重uij為例。損失w.r.t. uij的偏導(dǎo)數(shù)等于:
其中i對(duì)應(yīng)上一層(該變換的輸入層),j對(duì)應(yīng)下一層(該變換的輸出層)。只要根據(jù)鏈?zhǔn)揭?guī)則即可計(jì)算出偏導(dǎo)數(shù)。
關(guān)注L2-范數(shù)損失導(dǎo)數(shù)。
關(guān)注Sigmoid導(dǎo)數(shù)。
最后,三階偏導(dǎo)數(shù)就是下面的導(dǎo)數(shù):
所以,
替換上面表達(dá)式中的偏導(dǎo)數(shù),我們得到:
因此,輸出層的單個(gè)權(quán)重的更新規(guī)則由下式給出:
隱藏層的反向傳播
與輸出層的反向傳播類似,wij將依賴:
關(guān)注鏈?zhǔn)揭?guī)則。利用到目前為止我們可以利用Sigmoid激活和線性模型進(jìn)行轉(zhuǎn)換的結(jié)果,我們得到:
和
反向傳播的實(shí)際問(wèn)題來(lái)自該術(shù)語(yǔ)
那是由于沒有“隱藏”目標(biāo)??梢栽谙旅婵纯礄?quán)重w11的解決方案。查看計(jì)算過(guò)程時(shí),建議先看一下上面顯示的NN圖。
從這里,我們可以計(jì)算
這就是我們想要的。最終的表達(dá)式是:
該方程的廣義形式是:
反向傳播的一般化
使用輸出層和隱藏層的反向傳播的結(jié)果,我們可以將它們放到一個(gè)公式中,在存在L2-范數(shù)損失和Sigmoid激活的情況下總結(jié)反向傳播。
其中就隱藏層而言
實(shí)現(xiàn)有1個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò)的代碼
現(xiàn)在不妨實(shí)現(xiàn)我們剛使用Pytho從零開始的神經(jīng)網(wǎng)絡(luò)。我們將再次嘗試對(duì)上面創(chuàng)建的非線性數(shù)據(jù)進(jìn)行分類。
我們先為梯度下降定義一些有用的變量和參數(shù),比如訓(xùn)練數(shù)據(jù)集大小、輸入層和輸出層的維度。
- num_examples = len(X) # training set size
- nn_input_dim = 2 # input layer dimensionality
- nn_output_dim = 2 # output layer dimensionality
還定義梯度下降參數(shù)。
- epsilon = 0.01 # learning rate for gradient descent
- reg_lambda = 0.01 # regularization strength
首先,不妨實(shí)現(xiàn)上面定義的損失函數(shù)。我們使用該函數(shù)來(lái)評(píng)估模型的表現(xiàn)有多好:
- # Helper function to evaluate the total loss on the dataset
- def calculate_loss(model, X, y):
- num_examples = len(X) # training set size
- W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
- # Forward propagation to calculate our predictions
- z1 = X.dot(W1) + b1
- a1 = np.tanh(z1)
- z2 = a1.dot(W2) + b2
- exp_scores = np.exp(z2)
- probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
- # Calculating the loss
- corect_logprobs = -np.log(probs[range(num_examples), y])
- data_loss = np.sum(corect_logprobs)
- # Add regulatization term to loss (optional)
- data_loss += Config.reg_lambda / 2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
- return 1. / num_examples * data_loss
我們還實(shí)現(xiàn)了helper函數(shù),計(jì)算網(wǎng)絡(luò)的輸出。它進(jìn)行正向傳播,返回概率最大的類別。
- def predict(model, x):
- W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
- # Forward propagation
- z1 = x.dot(W1) + b1
- a1 = np.tanh(z1)
- z2 = a1.dot(W2) + b2
- exp_scores = np.exp(z2)
- probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
- return np.argmax(probs, axis=1)
最后是訓(xùn)練神經(jīng)網(wǎng)絡(luò)的函數(shù)。它使用我們?cè)谏厦嬲业降姆聪騻鞑?dǎo)數(shù)實(shí)現(xiàn)了批梯度下降。
該函數(shù)學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)的參數(shù)后返回模型。
nn_hdim:隱藏層中節(jié)點(diǎn)的數(shù)量。
num_passes:遍歷梯度下降訓(xùn)練數(shù)據(jù)的次數(shù)。
print_loss:如果是True,每1000次迭代就打印輸出損失。
- def build_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
- # Initialize the parameters to random values. We need to learn these.
- num_examples = len(X)
- np.random.seed(0)
- W1 = np.random.randn(Config.nn_input_dim, nn_hdim) / np.sqrt(Config.nn_input_dim)
- b1 = np.zeros((1, nn_hdim))
- W2 = np.random.randn(nn_hdim, Config.nn_output_dim) / np.sqrt(nn_hdim)
- b2 = np.zeros((1, Config.nn_output_dim))# This is what we return at the end
- model = {}# Gradient descent. For each batch...
- for i in range(0, num_passes):# Forward propagation
- z1 = X.dot(W1) + b1
- a1 = np.tanh(z1)
- z2 = a1.dot(W2) + b2
- exp_scores = np.exp(z2)
- probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)# Backpropagation
- delta3 = probs
- delta3[range(num_examples), y] -= 1
- dW2 = (a1.T).dot(delta3)
- db2 = np.sum(delta3, axis=0, keepdims=True)
- delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
- dW1 = np.dot(X.T, delta2)
- db1 = np.sum(delta2, axis=0)# Add regularization terms (b1 and b2 don't have regularization terms)
- dW2 += Config.reg_lambda * W2
- dW1 += Config.reg_lambda * W1# Gradient descent parameter update
- W1 += -Config.epsilon * dW1
- b1 += -Config.epsilon * db1
- W2 += -Config.epsilon * dW2
- b2 += -Config.epsilon * db2# Assign new parameters to the model
- model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}# Optionally print the loss.
- # This is expensive because it uses the whole dataset, so we don't want to do it too often.
- if print_loss and i % 1000 == 0:
- print("Loss after iteration %i: %f" % (i, calculate_loss(model, X, y)))return model
最后是主方法:
- def main():
- X, y = generate_data()
- model = build_model(X, y, 3, print_loss=True)
- visualize(X, y, model)
每1000次迭代打印輸出損失:
圖3
隱藏層中節(jié)點(diǎn)數(shù)量是3時(shí)的分類
現(xiàn)在了解不同的隱藏層大小對(duì)結(jié)果有何影響。
- hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
- for i, nn_hdim in enumerate(hidden_layer_dimensions):
- plt.subplot(5, 2, i+1)
- plt.title('Hidden Layer size %d' % nn_hdim)
- model = build_model(X, y,nn_hdim, 20000, print_loss=False)
- plot_decision_boundary(lambda x:predict(model,x), X, y)
- plt.show()
圖4
我們可以看到,低維度的隱藏層很好地捕獲了數(shù)據(jù)的總體趨勢(shì)。較高維度易于過(guò)擬合。它們?cè)?ldquo;記憶”數(shù)據(jù),而不是擬合總體形狀。
如果我們?cè)诹硗獾臏y(cè)試集上評(píng)估模型,由于更好的泛化能力,隱藏層尺寸較小的模型可能會(huì)表現(xiàn)更好。我們可以通過(guò)更強(qiáng)的正則化來(lái)抵消過(guò)擬合,但是為隱藏層選擇正確的尺寸是一種極為“經(jīng)濟(jì)”的解決方法。
你可以在該GitHub存儲(chǔ)庫(kù)中獲取全部代碼。
nageshsinghc4 / Artificial-Neural-Network-from-scratch-python
結(jié)論
我們?cè)诒疚闹薪榻B了如何使用Numpy Python,用數(shù)學(xué)導(dǎo)出有1個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò),并創(chuàng)建了有1個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò)。
原文標(biāo)題:Build an Artificial Neural Network From Scratch: Part 2,作者:Nagesh Singh Chauhan
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】