您的位置:首頁>正文

從零學習:從Python和R理解和編碼神經網路

編者按:當你面對一個新概念時, 你會怎麼學習和實踐它?是耗費大量時間學習整個理論, 掌握背後的演算法、數學、假設、局限再親身實踐, 還是從最簡單的基礎開始, 通過具體專案解決一個個難題來提高你對它的整體把握?在這系列文章中, 論智將採用第二種方法和讀者一起從頭理解機器學習。

“從零學習”系列第一篇從Python和R理解和編碼神經網路來自Analytics Vidhya博主、印度資深資料科學開發人員SUNIL RAY。

本文將圍繞神經網路構建的基礎知識展開, 並集中討論網路的應用方式, 用Python和R語言實戰編碼。

目錄

神經網路的基本工作原理

多層感知器及其基礎知識

神經網路構建過程詳解

神經網路構建過程的視覺化

如何用Numpy實現NN(Python)

如何用R語言實現NN

反向傳播演算法的數學原理

神經網路的基本工作原理

如果你是一名開發者, 或曾參與過程式設計專案, 你一定知道如何在代碼中找bug。 通過改變輸入和環境, 你可以用相應的各種輸出測試bug位置, 因為輸出的改變其實是一個提示, 它能告訴你應該去檢查哪個模組, 甚至是哪一行。 一旦你找到正確的那個它, 並反復調試, 你總會得到理想的結果。

神經網路其實也一樣。 它通常需要幾個輸入, 在經過多個隱藏層中神經元的處理後, 它會在輸出層返回結果, 這個過程就是神經網路的“前向傳播”。

得到輸出後, 接下來我們要做的就是用神經網路的輸出和實際結果做對比。 由於每一個神經元都可能增加最終輸出的誤差, 所以我們要盡可能減少這個損失(loss), 使輸出更接近實際值。 那該怎麼減少loss呢?

在神經網路中, 一種常用的做法是降低那些容易導致更多loss的神經元的權重/權值。 因為這個過程需要返回神經元並找出錯誤所在, 所以它也被稱為“反向傳播”。

為了在減少誤差的同時進行更少量的反覆運算, 神經網路也會使用一種名為“梯度下降”(Gradient Descent)的演算法。 這是一種基礎的優化演算法, 能説明開發者快速高效地完成各種任務。

雖然這樣的表述太過簡單粗淺, 但其實這就是神經網路的基本工作原理。 簡單的理解有助於你用簡單的方式去做一些基礎實現。

多層感知器及其基礎知識

就像原子理論中物質是由一個個離散單元原子所構成的那樣, 神經網路的最基本單位是感知器(Perceptron)。 那麼, 感知器是什麼?

對於這個問題, 我們可以這麼理解:感知器就是一種接收多個輸入並產生一個輸出的東西。 如下圖所示:

感知器

示例中的它有3個輸入, 卻只有一個輸出, 由此我們產生的下一個合乎邏輯的問題就是輸入和輸出之間的對應關係是什麼。 讓我們先從一些基本方法入手, 再慢慢上升到更複雜的方法。

以下是我列舉的3種創建輸入輸出對應關係的方法:

直接組合輸入並根據閾值計算輸出。 例如, 我們設x1=0, x2=1, x3=1, 閾值為0。 如果x1+x2+x3>0, 則輸出1;反之, 輸出0。 可以看到,

在這個情景下上圖的最終輸出是1。

接下來, 讓我們為各輸入添加權值。 例如, 我們設x1、x2、x3三個輸入的權重分別為w1、w2、w3, 其中w1=2, w2=3, w3=4。 為了計算輸出, 我們需要將輸入乘以它們各自的權值, 即2x1+3x2+4x3, 再和閾值比較。 可以發現, x3對輸出的影響比x1、x2更大。

接下來, 讓我們添加bias(偏置, 有時也稱閾值, 但和上文閾值有區別)。 每個感知器都有一個bias, 它其實也是一種加權方式, 可以反映感知器的靈活性。 bias在某種程度上相當於線性方程y=ax+b中的常數b, 可以讓函數上下移動。 如果b=0, 那分類線就要經過原點(0, 0), 這樣神經網路的fit範圍會非常受限。 例如, 如果一個感知器有兩個輸入, 它就需要3個權值, 兩個對應給輸入, 一個給bias。 在這個情景下, 上圖輸入的線性形式就是w1x1 + w2x2 + w3x3+1×b。

但是, 這樣做之後每一層的輸出還是上層輸入的線性變換,

這就有點無聊。 於是人們想到把感知器發展成一種現在稱之為神經元的東西, 它能將非線性變換(啟動函數)用於輸入和loss。

什麼是啟動函數(activation function)?

啟動函數是把加權輸入(w1x1 + w2x2 + w3x3+1×b)的和作為引數, 然後讓神經元得出輸出值。

在上式中, 我們將bias權值1表示為x0, 將b表示為w0。

輸入—加權—求和—作為實參被啟動函數計算—輸出

它主要用於進行非線性變換, 使我們能擬合非線性假設、估計複雜函數, 常用的函數有:Sigmoid、Tanh和ReLu。

前向傳播、反向傳播和Epoch

到目前為止, 我們已經由輸入計算獲得了輸出, 這個過程就是“前向傳播”(Forward Propagation)。 但是, 如果產出的估計值和實際值誤差太大怎麼辦?其實, 神經網路的工作過程可以被看作是一個試錯的過程, 我們能根據輸出值的錯誤更新之前的bias和權值,這個回溯的行為就是“反向傳播”(Back Propagation)。

反向傳播演算法(BP演算法)是一種通過權衡輸出層的loss或錯誤,將其傳回網路來發生作用的演算法。它的目的是重新調整各項權重來使每個神經元產生的loss最小化,而要實現這一點,我們要做的第一步就是基於最終輸出計算每個節點之的梯度(導數)。具體的數學過程我們會在最後一節“反向傳播演算法的數學原理”中詳細探討。

而這個由前向傳播和反向傳播構成的一輪反覆運算就是我們常說的一個訓練反覆運算,也就是Epoch。

多層感知器

現在,讓我們繼續回到例子,把注意力放到多層感知器上。截至目前,我們看到的只有一個由3個輸入節點x1、x2、x3構成的單一輸入層,以及一個隻包含單個神經元的輸出層。誠然,如果是解決線性問題,單層網路確實能做到這一步,但如果要學習非線性函數,那我們就需要一個多層感知器(MLP),即在輸入層和輸出層之間插入一個隱藏層。如下圖所示:

圖片中的綠色部分表示隱藏層,雖然上圖只有一個,但事實上,這樣一個網路可以包含多個隱藏層。同時,需要注意的一點是,MLP至少由三層節點組成,並且所有層都是完全連接的,即每一層中(除輸入層和輸出層)的每一個節點都要連接到前/後層中的每個節點。

理解了這一點,我們就能進入下一個主題,即神經網路優化演算法(誤差最小化)。在這裡,我們主要介紹最簡單的梯度下降。

批量梯度下降和隨機梯度下降

梯度下降一般有三種形式:批量梯度下降法(Batch Gradient Descent)隨機梯度下降法(Stochastic Gradient Descent)和小批量梯度下降法(Mini-Batch Gradient Descent)。由於本文為入門向,我們就先來瞭解滿批量梯度下降法(Full BGD)和隨機梯度下降法(SGD)。

這兩種梯度下降形式使用的是同一種更新演算法,它們通過更新MLP的權值來達到優化網路的目的。不同的是,滿批量梯度下降法通過反復更新權值來使誤差降低,它的每一次更新都要用到所有訓練資料,這在資料量龐大時會耗費太多時間。而隨機梯度下降法則只抽取一個或多個樣本(非所有資料)來反覆運算更新一次,較之前者,它在耗時上有不小的優勢。

讓我們來舉個例子:假設現在我們有一個包含10個資料點的資料集,它有w1、w2兩個權值。

滿批量梯度下降法:你需要用10個資料點來計算權值w1的變化情況Δw1,以及權值w2的變化情況Δw2,之後再更新w1、w2。

隨機梯度下降法:用1個資料點計算權值w1的變化情況Δw1和權值w2的變化情況Δw2,更新w1、w2並將它們用於第二個資料點的計算。

神經網路構建過程詳解

在這一節中,讓我們來看看神經網路(MLP包含隱藏層,類似上圖架構)的具體構建方法。

如上圖所示,我們的神經網路共有3層,它的思路可以被大致概括為:

第0步 確定輸入和輸出

我們定義:

x為輸入矩陣;

y為輸出矩陣。

第1步 用一個隨機值初始化網路的權值和bias(只在開始時使用,下一次反覆運算我們會用更新後的值)

我們定義:

wh為隱藏層的權值矩陣;

bh為隱藏層的bias矩陣;

wout為輸出層的權值矩陣;

bout為輸出層的bias矩陣。

第2步 將輸入矩陣和權值的矩陣點乘積分配給輸入和隱藏層之間的邊緣,再加上隱藏層的bias形成相應輸出,這個過程被稱作線性變換

hidden_layer_input = matrix_dot_product(X,wh)+bh

第3步 引入啟動函數(Sigmoid)執行非線性變換,Sigmoid輸出1/(1+exp(-x))

hiddenlayer_activations = sigmoid(hidden_layer_input)

第4步 對隱藏層進行線性變換(在原矩陣點乘積的基礎上乘以新權值矩陣並加上輸出層bias),使用啟動函數,並預測輸出

output_layer_input = matrix_dot_product (hiddenlayer_activations × wout ) + boutoutput = sigmoid(output_layer_input)

以上步驟即為“前向傳播”。

第5步 將預測值與實際值相比較,並計算誤差E(Actual-Predicted)的梯度,它是一個平方誤差函數((y-t)^2)/2(衡量期望誤差的一個常見做法是採用平方誤差測度)

E=y–output

第6步 計算隱藏層和輸出層神經元的斜率/梯度(對每個神經元每一層的非線性啟動函數x求導),Sigmoid的梯度會返回為x(1–x)

slope_output_layer = derivatives_sigmoid(output)slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)

第7步 用誤差E的梯度和輸出層啟動函數梯度計算輸出層的變化因數(delta)

d_output = E × slope_output_layer

第8步 這時,誤差E已經回到神經網路中,也就是在隱藏層中。為了計算它的梯度,我們需要用到輸出變化因數delta中的點和隱藏層輸出層之間的權重參數

Error_at_hidden_layer = matrix_dot_product(d_output,wout.Transpose)

第9步 計算隱藏層的變化因數(delta),將得到的誤差和隱藏層啟動函數導數相乘

d_hiddenlayer = Error_at_hidden_layer × slope_hidden_layer

第10步 更新輸出層和隱藏層的權值:可以用訓練樣本的誤差更新權值

wout = wout + matrix_dot_product(hiddenlayer_activations.Transpose,d_output)× learning_rate

wh = wh + matrix_dot_product(X.Transpose,d_hiddenlayer)× learning_rate

學習率(learning rate):權值更新速率由自訂超參數學習率決定。

第11步 更新輸出層和隱藏層的bias:神經網路中的bias可以由神經元中的累積誤差求導更新

bias at output_layer =bias at output_layer + sum of delta of output_layer at row-wise × learning_rate

bias at hidden_layer =bias at hidden_layer + sum of delta of output_layer at row-wise × learning_rate

即:

bh = bh + sum(d_hiddenlayer, axis=0)×learning_rate

bout = bout + sum(d_output, axis=0)×learning_rate

我們稱5—11為“反向傳播”。

我們把一個前向傳播和一個反向傳播的反覆運算成為一個訓練週期。正如我在前文中提到的,如果我們再進行訓練,那更新後的權值和bias就會用於新一次前向傳播。

神經網路構建過程的視覺化

我會在這一節中用圖表形式重新介紹上一節的內容,以説明入門者更好地瞭解神經網路(MLP)的工作方法。

注意:

為了更好的呈現效果,我只保留了2位或3位小數;

黃色儲存格表示當前活動的神經元(單元、節點);

橙色儲存格表示用於更新目前的儲存格值的輸入。

第0步 讀取輸入和輸出

第1步 用隨機值初始化權值和bias

第2步 計算隱藏層輸入

hidden_layer_input = matrix_dot_product(X,wh)+ bh

第3步 對隱藏層的線性輸入執行非線性變換(啟動函數)

hiddenlayer_activations = sigmoid(hidden_layer_input)

第4步 在輸出層對已經執行了線性、非線性變換的隱藏層使用啟動函數

output_layer_input = matrix_dot_product(hiddenlayer_activations × wout)+ boutoutput = sigmoid(output_layer_input)

第5步 計算輸出層誤差E的梯度

E = y-output

第6步 計算輸出層和隱藏層的梯度

Slope_output_layer= derivatives_sigmoid(output)Slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)

第7步 計算輸出層的delta

d_output = E × slope_output_layer*lr

第8步 計算隱藏層的誤差

Error_at_hidden_layer = matrix_dot_product(d_output, wout.Transpose)

第9步 計算隱藏層的delta

d_hiddenlayer = Error_at_hidden_layer × slope_hidden_layer

第10步 更新輸出層和隱藏層的權值

wout = wout + matrix_dot_product(hiddenlayer_activations.Transpose, d_output)×learning_rate

wh = wh+ matrix_dot_product(X.Transpose,d_hiddenlayer)×learning_rate

第11步 更新輸出層和隱藏層的bias

bh = bh + sum(d_hiddenlayer, axis=0) ×learning_ratebout = bout + sum(d_output, axis=0)×learning_rate

由表格資料可以看出,由於我們只進行了一次反覆運算,因此預測值和實際值的誤差還相對過大。如果我們多次訓練模型,那它們最終將趨於靠近。事實上,我之後已經對這個模型進行了上千次反覆運算,最後得到了和目標值很相近的結果:[[ 0.98032096] [ 0.96845624] [ 0.04532167]]。

如何用Numpy實現NN(Python)import numpy as np#Input arrayX=np.array([[1,0,1,0],[1,0,1,1],[0,1,0,1]])#Outputy=np.array([[1],[1],[0]])#Sigmoid Functiondef sigmoid (x):return 1/(1 + np.exp(-x))#Derivative of Sigmoid Functiondef derivatives_sigmoid(x):return x * (1 - x)#Variable initializationepoch=5000 #Setting training iterationslr=0.1 #Setting learning rateinputlayer_neurons = X.shape[1] #number of features in data sethiddenlayer_neurons = 3 #number of hidden layers neuronsoutput_neurons = 1 #number of neurons at output layer#weight and bias initializationwh=np.random.uniform(size=(inputlayer_neurons,hiddenlayer_neurons))bh=np.random.uniform(size=(1,hiddenlayer_neurons))wout=np.random.uniform(size=(hiddenlayer_neurons,output_neurons))bout=np.random.uniform(size=(1,output_neurons))for i in range(epoch):#Forward Propogationhidden_layer_input1=np.dot(X,wh)hidden_layer_input=hidden_layer_input1 + bhhiddenlayer_activations = sigmoid(hidden_layer_input)output_layer_input1=np.dot(hiddenlayer_activations,wout)output_layer_input= output_layer_input1+ boutoutput = sigmoid(output_layer_input)#BackpropagationE = y-outputslope_output_layer = derivatives_sigmoid(output)slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)d_output = E * slope_output_layerError_at_hidden_layer = d_output.dot(wout.T)d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layerwout += hiddenlayer_activations.T.dot(d_output) *lrbout += np.sum(d_output, axis=0,keepdims=True) *lrwh += X.T.dot(d_hiddenlayer) *lrbh += np.sum(d_hiddenlayer, axis=0,keepdims=True) *lrprint output如何用R語言實現NN# input matrixX=matrix(c(1,0,1,0,1,0,1,1,0,1,0,1),nrow = 3, ncol=4,byrow = TRUE)# output matrixY=matrix(c(1,1,0),byrow=FALSE)#sigmoid functionsigmoid<-function(x){1/(1+exp(-x))}# derivative of sigmoid functionderivatives_sigmoid<-function(x){x*(1-x)}# variable initializationepoch=5000lr=0.1in putlayer_neurons=ncol(X)hiddenlayer_neurons=3output_neurons=1#weight and bias initializationwh=matrix( rnorm(inputlayer_neurons*hiddenlayer_neurons,mean=0,sd=1), inputlayer_neurons, hiddenlayer_neurons)bias_in=runif(hiddenlayer_neurons)bias_in_temp=rep(bias_in, nrow(X))bh=matrix(bias_in_temp, nrow = nrow(X), byrow = FALSE)wout=matrix( rnorm(hiddenlayer_neurons*output_neurons,mean=0,sd=1), hiddenlayer_neurons, output_neurons)bias_out=runif(output_neurons)bias_out_temp=rep(bias_out,nrow(X))bout=matrix(bias_out_temp,nrow = nrow(X),byrow = FALSE)# forward propagationfor(i in 1:epoch){hidden_layer_input1= X%*%whhidden_layer_input=hidden_layer_input1+bhhidden_layer_activations=sigmoid(hidden_layer_input)output_layer_input1=hidden_layer_activations%*%woutoutput_layer_input=output_layer_input1+boutoutput= sigmoid(output_layer_input)# Back PropagationE=Y-outputslope_output_layer=derivatives_sigmoid(output)slope_hidden_layer=derivatives_sigmoid(hidden_layer_activations)d_output=E*slope_output_layerError_at_hidden_layer=d_output%*%t(wout)d_hiddenlayer=Error_at_hidden_layer*slope_hidden_layerwout= wout + (t(hidden_layer_activations)%*%d_output)*lrbout= bout+rowSums(d_output)*lrwh = wh +(t(X)%*%d_hiddenlayer)*lrbh = bh + rowSums(d_hiddenlayer)*lr}output反向傳播演算法的數學原理(選讀)

設Wi為輸入層與隱藏層之間的權值,Wh為隱藏層和輸出層之間的權值。

h=σ(u)= σ (WiX),其中h是u的函數,u是Wi和x的函數。這裡我們將啟動函數表示為σ。

Y=σ(u’)=σ(Whh),其中Y是u’的函數,u’是Wh和h的函數。

利用上述等式,我們可以計算偏導數。

我們需要計算∂E/∂Wi和∂E/∂Wh,其中前者是改變輸入層、隱藏層之間權值造成的誤差(E)的變化,後者是改變隱藏層、輸出層之間權值造成的誤差(E)的變化。

為了得出這兩個偏導數,我們需要用到鏈式法則,因為E是Y的函數,Y是u’的函數,而u’是Wi的函數。

讓我們結合它們的關係來計算梯度:

∂E/∂Wh = (∂E/∂Y).( ∂Y/∂u’).( ∂u’/∂Wh)……(1)

已知誤差E=((Y-t)^2)/2,可得(∂E/∂Y)= (Y-t)。

由於啟動函數σ求導後的形式是σ(1-σ),因此

(∂Y/∂u’)= ∂( σ(u’)/ ∂u’= σ(u’)(1-σ(u’))

但是因為σ(u’)=Y,所以

(∂Y/∂u’)=Y(1-Y)

現在我們就能得到( ∂u’/∂Wh)= ∂( Whh)/ ∂Wh = h

把這個等式帶入式(1)可得

∂E/∂Wh = (Y-t). Y(1-Y).h

現在我們已經得出了隱藏層和輸出層之間的梯度,是時候該計算輸入層和隱藏層之間的梯度了。

∂E/∂Wi =(∂E/∂h). (∂h/∂u).( ∂u/∂Wi)

但是,(∂E/∂h) = (∂E/∂Y).( ∂Y/∂u’).( ∂u’/∂h)也同樣成立,將這個式子帶入上式後,

∂E/∂Wi =[(∂E/∂Y).( ∂Y/∂u’).( ∂u’/∂h)]. (∂h/∂u).( ∂u/∂Wi)……(2)

可能會有人有一位,為什麼我們要先計算隱藏層和輸出層之間的梯度呢?這個問題的解答就在式(2)中。我們可以發現,由於之前我們已經先計算了∂E/∂Y和∂Y/∂u’,在進行之後的計算時,我們無需進行大量重複計算,這大大節約了資源佔用和計算時間,提高了整體效率。這也是這個演算法被稱為反向傳播演算法的原因。

解下來讓我們計算式(2)中的未知導數。

∂u’/∂h = ∂(Whh)/ ∂h = Wh

∂h/∂u = ∂( σ(u)/ ∂u= σ(u)(1- σ(u))

因為σ(u)=h,所以

(∂Y/∂u)=h(1-h)

我們可以得到∂u/∂Wi = ∂(WiX)/ ∂Wi = X

將它代入式(2)我們可得梯度

∂E/∂Wi = [(Y-t). Y(1-Y).Wh].h(1-h).X

既然我們已經計算得到兩個梯度,那神經網路的權值就可以更新為

Wh = Wh + η . ∂E/∂Wh

Wi = Wi + η . ∂E/∂Wi

其中η表示學習率。

這裡我們可以再一次回到這個演算法的命名上。通過對比∂E/∂Wh和∂E/∂Wi的最終形式我們可以發現,預測值和實際值的誤差(Y-t)在權值更新的過程中被作為了輸入層。

那這些數學計算是怎麼和代碼對應的呢?

hiddenlayer_activations = HE = YtSlope_output_layer = Y(1-Y)lr =ηslope_hidden_layer = h(1-h)wout = Wh論智補充

本文以神經網路中最基礎的兩層神經網路作為講解物件,雖然內容已經很翔實,但為了更形象地解釋輸入、輸出、權值、bias等概念在圖中的位置,論智君在文末為讀者做一些基礎知識補充

上圖是一個簡單的兩層神經網路,它由一些圓形和向量組成,其中的圓圈就是我們說的神經元,有時也稱單元(unit)和節點(node),它主要起到計算和儲存作用,因此其實發揮重要作用的是圖中的各條帶箭頭的線。

文章中涉及大量“輸入層隱藏層之間的權值”“隱藏層輸出層之間的權值”的表述,這些都可以從圖片中獲得直觀感受。而我們反復提起的偏置項bias在上圖中也有所展示。理論上,bias是個儲存值永遠為1的單元,它出現在除了輸入層之外的所有層,與後一層的所有節點都有連接。神經網路結構圖一般不會把它明確畫出來,但我們應該知道它是存在的。

本文把多處神經網路都注釋為MLP,並詳細介紹了多層感知器的設計演變。需要注意的是,MLP曾經就指代神經網路,但是它表示的是兩層神經網路,即包含一個輸入層、隱藏層、輸出層的神經網路,一般不能被用來展示層數非常深的神經網路。

論智也沿用了損失的英文原型loss,區別於error誤差。另外會有人把loss譯為殘差、代價,尤其是在談及loss function的時候,它們其實是一種東西。

最後再提一點閾值和參數。文章在介紹bias時用了它的英文單詞,沒有用它的譯名偏置項、閾值,這是為了防止誤讀,尤其是閾值,它容易與案例中的約束條件混淆。事實上,bias作為閾值最生動的體現是在圖像上,它能限制曲線的峰值區間,具體可以Google相關圖片。而本文儘量避免了“參數”這個詞的使用,因為一般而言,神經網路參數就是指訓練得到的權值和bias,把這個概念用於任何一方都是不合理的,它和其他機器學習方法中的參數也不是一個概念,在閱讀其他原創、翻譯內容時,希望讀者能擦亮雙眼。

我們能根據輸出值的錯誤更新之前的bias和權值,這個回溯的行為就是“反向傳播”(Back Propagation)。

反向傳播演算法(BP演算法)是一種通過權衡輸出層的loss或錯誤,將其傳回網路來發生作用的演算法。它的目的是重新調整各項權重來使每個神經元產生的loss最小化,而要實現這一點,我們要做的第一步就是基於最終輸出計算每個節點之的梯度(導數)。具體的數學過程我們會在最後一節“反向傳播演算法的數學原理”中詳細探討。

而這個由前向傳播和反向傳播構成的一輪反覆運算就是我們常說的一個訓練反覆運算,也就是Epoch。

多層感知器

現在,讓我們繼續回到例子,把注意力放到多層感知器上。截至目前,我們看到的只有一個由3個輸入節點x1、x2、x3構成的單一輸入層,以及一個隻包含單個神經元的輸出層。誠然,如果是解決線性問題,單層網路確實能做到這一步,但如果要學習非線性函數,那我們就需要一個多層感知器(MLP),即在輸入層和輸出層之間插入一個隱藏層。如下圖所示:

圖片中的綠色部分表示隱藏層,雖然上圖只有一個,但事實上,這樣一個網路可以包含多個隱藏層。同時,需要注意的一點是,MLP至少由三層節點組成,並且所有層都是完全連接的,即每一層中(除輸入層和輸出層)的每一個節點都要連接到前/後層中的每個節點。

理解了這一點,我們就能進入下一個主題,即神經網路優化演算法(誤差最小化)。在這裡,我們主要介紹最簡單的梯度下降。

批量梯度下降和隨機梯度下降

梯度下降一般有三種形式:批量梯度下降法(Batch Gradient Descent)隨機梯度下降法(Stochastic Gradient Descent)和小批量梯度下降法(Mini-Batch Gradient Descent)。由於本文為入門向,我們就先來瞭解滿批量梯度下降法(Full BGD)和隨機梯度下降法(SGD)。

這兩種梯度下降形式使用的是同一種更新演算法,它們通過更新MLP的權值來達到優化網路的目的。不同的是,滿批量梯度下降法通過反復更新權值來使誤差降低,它的每一次更新都要用到所有訓練資料,這在資料量龐大時會耗費太多時間。而隨機梯度下降法則只抽取一個或多個樣本(非所有資料)來反覆運算更新一次,較之前者,它在耗時上有不小的優勢。

讓我們來舉個例子:假設現在我們有一個包含10個資料點的資料集,它有w1、w2兩個權值。

滿批量梯度下降法:你需要用10個資料點來計算權值w1的變化情況Δw1,以及權值w2的變化情況Δw2,之後再更新w1、w2。

隨機梯度下降法:用1個資料點計算權值w1的變化情況Δw1和權值w2的變化情況Δw2,更新w1、w2並將它們用於第二個資料點的計算。

神經網路構建過程詳解

在這一節中,讓我們來看看神經網路(MLP包含隱藏層,類似上圖架構)的具體構建方法。

如上圖所示,我們的神經網路共有3層,它的思路可以被大致概括為:

第0步 確定輸入和輸出

我們定義:

x為輸入矩陣;

y為輸出矩陣。

第1步 用一個隨機值初始化網路的權值和bias(只在開始時使用,下一次反覆運算我們會用更新後的值)

我們定義:

wh為隱藏層的權值矩陣;

bh為隱藏層的bias矩陣;

wout為輸出層的權值矩陣;

bout為輸出層的bias矩陣。

第2步 將輸入矩陣和權值的矩陣點乘積分配給輸入和隱藏層之間的邊緣,再加上隱藏層的bias形成相應輸出,這個過程被稱作線性變換

hidden_layer_input = matrix_dot_product(X,wh)+bh

第3步 引入啟動函數(Sigmoid)執行非線性變換,Sigmoid輸出1/(1+exp(-x))

hiddenlayer_activations = sigmoid(hidden_layer_input)

第4步 對隱藏層進行線性變換(在原矩陣點乘積的基礎上乘以新權值矩陣並加上輸出層bias),使用啟動函數,並預測輸出

output_layer_input = matrix_dot_product (hiddenlayer_activations × wout ) + boutoutput = sigmoid(output_layer_input)

以上步驟即為“前向傳播”。

第5步 將預測值與實際值相比較,並計算誤差E(Actual-Predicted)的梯度,它是一個平方誤差函數((y-t)^2)/2(衡量期望誤差的一個常見做法是採用平方誤差測度)

E=y–output

第6步 計算隱藏層和輸出層神經元的斜率/梯度(對每個神經元每一層的非線性啟動函數x求導),Sigmoid的梯度會返回為x(1–x)

slope_output_layer = derivatives_sigmoid(output)slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)

第7步 用誤差E的梯度和輸出層啟動函數梯度計算輸出層的變化因數(delta)

d_output = E × slope_output_layer

第8步 這時,誤差E已經回到神經網路中,也就是在隱藏層中。為了計算它的梯度,我們需要用到輸出變化因數delta中的點和隱藏層輸出層之間的權重參數

Error_at_hidden_layer = matrix_dot_product(d_output,wout.Transpose)

第9步 計算隱藏層的變化因數(delta),將得到的誤差和隱藏層啟動函數導數相乘

d_hiddenlayer = Error_at_hidden_layer × slope_hidden_layer

第10步 更新輸出層和隱藏層的權值:可以用訓練樣本的誤差更新權值

wout = wout + matrix_dot_product(hiddenlayer_activations.Transpose,d_output)× learning_rate

wh = wh + matrix_dot_product(X.Transpose,d_hiddenlayer)× learning_rate

學習率(learning rate):權值更新速率由自訂超參數學習率決定。

第11步 更新輸出層和隱藏層的bias:神經網路中的bias可以由神經元中的累積誤差求導更新

bias at output_layer =bias at output_layer + sum of delta of output_layer at row-wise × learning_rate

bias at hidden_layer =bias at hidden_layer + sum of delta of output_layer at row-wise × learning_rate

即:

bh = bh + sum(d_hiddenlayer, axis=0)×learning_rate

bout = bout + sum(d_output, axis=0)×learning_rate

我們稱5—11為“反向傳播”。

我們把一個前向傳播和一個反向傳播的反覆運算成為一個訓練週期。正如我在前文中提到的,如果我們再進行訓練,那更新後的權值和bias就會用於新一次前向傳播。

神經網路構建過程的視覺化

我會在這一節中用圖表形式重新介紹上一節的內容,以説明入門者更好地瞭解神經網路(MLP)的工作方法。

注意:

為了更好的呈現效果,我只保留了2位或3位小數;

黃色儲存格表示當前活動的神經元(單元、節點);

橙色儲存格表示用於更新目前的儲存格值的輸入。

第0步 讀取輸入和輸出

第1步 用隨機值初始化權值和bias

第2步 計算隱藏層輸入

hidden_layer_input = matrix_dot_product(X,wh)+ bh

第3步 對隱藏層的線性輸入執行非線性變換(啟動函數)

hiddenlayer_activations = sigmoid(hidden_layer_input)

第4步 在輸出層對已經執行了線性、非線性變換的隱藏層使用啟動函數

output_layer_input = matrix_dot_product(hiddenlayer_activations × wout)+ boutoutput = sigmoid(output_layer_input)

第5步 計算輸出層誤差E的梯度

E = y-output

第6步 計算輸出層和隱藏層的梯度

Slope_output_layer= derivatives_sigmoid(output)Slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)

第7步 計算輸出層的delta

d_output = E × slope_output_layer*lr

第8步 計算隱藏層的誤差

Error_at_hidden_layer = matrix_dot_product(d_output, wout.Transpose)

第9步 計算隱藏層的delta

d_hiddenlayer = Error_at_hidden_layer × slope_hidden_layer

第10步 更新輸出層和隱藏層的權值

wout = wout + matrix_dot_product(hiddenlayer_activations.Transpose, d_output)×learning_rate

wh = wh+ matrix_dot_product(X.Transpose,d_hiddenlayer)×learning_rate

第11步 更新輸出層和隱藏層的bias

bh = bh + sum(d_hiddenlayer, axis=0) ×learning_ratebout = bout + sum(d_output, axis=0)×learning_rate

由表格資料可以看出,由於我們只進行了一次反覆運算,因此預測值和實際值的誤差還相對過大。如果我們多次訓練模型,那它們最終將趨於靠近。事實上,我之後已經對這個模型進行了上千次反覆運算,最後得到了和目標值很相近的結果:[[ 0.98032096] [ 0.96845624] [ 0.04532167]]。

如何用Numpy實現NN(Python)import numpy as np#Input arrayX=np.array([[1,0,1,0],[1,0,1,1],[0,1,0,1]])#Outputy=np.array([[1],[1],[0]])#Sigmoid Functiondef sigmoid (x):return 1/(1 + np.exp(-x))#Derivative of Sigmoid Functiondef derivatives_sigmoid(x):return x * (1 - x)#Variable initializationepoch=5000 #Setting training iterationslr=0.1 #Setting learning rateinputlayer_neurons = X.shape[1] #number of features in data sethiddenlayer_neurons = 3 #number of hidden layers neuronsoutput_neurons = 1 #number of neurons at output layer#weight and bias initializationwh=np.random.uniform(size=(inputlayer_neurons,hiddenlayer_neurons))bh=np.random.uniform(size=(1,hiddenlayer_neurons))wout=np.random.uniform(size=(hiddenlayer_neurons,output_neurons))bout=np.random.uniform(size=(1,output_neurons))for i in range(epoch):#Forward Propogationhidden_layer_input1=np.dot(X,wh)hidden_layer_input=hidden_layer_input1 + bhhiddenlayer_activations = sigmoid(hidden_layer_input)output_layer_input1=np.dot(hiddenlayer_activations,wout)output_layer_input= output_layer_input1+ boutoutput = sigmoid(output_layer_input)#BackpropagationE = y-outputslope_output_layer = derivatives_sigmoid(output)slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)d_output = E * slope_output_layerError_at_hidden_layer = d_output.dot(wout.T)d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layerwout += hiddenlayer_activations.T.dot(d_output) *lrbout += np.sum(d_output, axis=0,keepdims=True) *lrwh += X.T.dot(d_hiddenlayer) *lrbh += np.sum(d_hiddenlayer, axis=0,keepdims=True) *lrprint output如何用R語言實現NN# input matrixX=matrix(c(1,0,1,0,1,0,1,1,0,1,0,1),nrow = 3, ncol=4,byrow = TRUE)# output matrixY=matrix(c(1,1,0),byrow=FALSE)#sigmoid functionsigmoid<-function(x){1/(1+exp(-x))}# derivative of sigmoid functionderivatives_sigmoid<-function(x){x*(1-x)}# variable initializationepoch=5000lr=0.1in putlayer_neurons=ncol(X)hiddenlayer_neurons=3output_neurons=1#weight and bias initializationwh=matrix( rnorm(inputlayer_neurons*hiddenlayer_neurons,mean=0,sd=1), inputlayer_neurons, hiddenlayer_neurons)bias_in=runif(hiddenlayer_neurons)bias_in_temp=rep(bias_in, nrow(X))bh=matrix(bias_in_temp, nrow = nrow(X), byrow = FALSE)wout=matrix( rnorm(hiddenlayer_neurons*output_neurons,mean=0,sd=1), hiddenlayer_neurons, output_neurons)bias_out=runif(output_neurons)bias_out_temp=rep(bias_out,nrow(X))bout=matrix(bias_out_temp,nrow = nrow(X),byrow = FALSE)# forward propagationfor(i in 1:epoch){hidden_layer_input1= X%*%whhidden_layer_input=hidden_layer_input1+bhhidden_layer_activations=sigmoid(hidden_layer_input)output_layer_input1=hidden_layer_activations%*%woutoutput_layer_input=output_layer_input1+boutoutput= sigmoid(output_layer_input)# Back PropagationE=Y-outputslope_output_layer=derivatives_sigmoid(output)slope_hidden_layer=derivatives_sigmoid(hidden_layer_activations)d_output=E*slope_output_layerError_at_hidden_layer=d_output%*%t(wout)d_hiddenlayer=Error_at_hidden_layer*slope_hidden_layerwout= wout + (t(hidden_layer_activations)%*%d_output)*lrbout= bout+rowSums(d_output)*lrwh = wh +(t(X)%*%d_hiddenlayer)*lrbh = bh + rowSums(d_hiddenlayer)*lr}output反向傳播演算法的數學原理(選讀)

設Wi為輸入層與隱藏層之間的權值,Wh為隱藏層和輸出層之間的權值。

h=σ(u)= σ (WiX),其中h是u的函數,u是Wi和x的函數。這裡我們將啟動函數表示為σ。

Y=σ(u’)=σ(Whh),其中Y是u’的函數,u’是Wh和h的函數。

利用上述等式,我們可以計算偏導數。

我們需要計算∂E/∂Wi和∂E/∂Wh,其中前者是改變輸入層、隱藏層之間權值造成的誤差(E)的變化,後者是改變隱藏層、輸出層之間權值造成的誤差(E)的變化。

為了得出這兩個偏導數,我們需要用到鏈式法則,因為E是Y的函數,Y是u’的函數,而u’是Wi的函數。

讓我們結合它們的關係來計算梯度:

∂E/∂Wh = (∂E/∂Y).( ∂Y/∂u’).( ∂u’/∂Wh)……(1)

已知誤差E=((Y-t)^2)/2,可得(∂E/∂Y)= (Y-t)。

由於啟動函數σ求導後的形式是σ(1-σ),因此

(∂Y/∂u’)= ∂( σ(u’)/ ∂u’= σ(u’)(1-σ(u’))

但是因為σ(u’)=Y,所以

(∂Y/∂u’)=Y(1-Y)

現在我們就能得到( ∂u’/∂Wh)= ∂( Whh)/ ∂Wh = h

把這個等式帶入式(1)可得

∂E/∂Wh = (Y-t). Y(1-Y).h

現在我們已經得出了隱藏層和輸出層之間的梯度,是時候該計算輸入層和隱藏層之間的梯度了。

∂E/∂Wi =(∂E/∂h). (∂h/∂u).( ∂u/∂Wi)

但是,(∂E/∂h) = (∂E/∂Y).( ∂Y/∂u’).( ∂u’/∂h)也同樣成立,將這個式子帶入上式後,

∂E/∂Wi =[(∂E/∂Y).( ∂Y/∂u’).( ∂u’/∂h)]. (∂h/∂u).( ∂u/∂Wi)……(2)

可能會有人有一位,為什麼我們要先計算隱藏層和輸出層之間的梯度呢?這個問題的解答就在式(2)中。我們可以發現,由於之前我們已經先計算了∂E/∂Y和∂Y/∂u’,在進行之後的計算時,我們無需進行大量重複計算,這大大節約了資源佔用和計算時間,提高了整體效率。這也是這個演算法被稱為反向傳播演算法的原因。

解下來讓我們計算式(2)中的未知導數。

∂u’/∂h = ∂(Whh)/ ∂h = Wh

∂h/∂u = ∂( σ(u)/ ∂u= σ(u)(1- σ(u))

因為σ(u)=h,所以

(∂Y/∂u)=h(1-h)

我們可以得到∂u/∂Wi = ∂(WiX)/ ∂Wi = X

將它代入式(2)我們可得梯度

∂E/∂Wi = [(Y-t). Y(1-Y).Wh].h(1-h).X

既然我們已經計算得到兩個梯度,那神經網路的權值就可以更新為

Wh = Wh + η . ∂E/∂Wh

Wi = Wi + η . ∂E/∂Wi

其中η表示學習率。

這裡我們可以再一次回到這個演算法的命名上。通過對比∂E/∂Wh和∂E/∂Wi的最終形式我們可以發現,預測值和實際值的誤差(Y-t)在權值更新的過程中被作為了輸入層。

那這些數學計算是怎麼和代碼對應的呢?

hiddenlayer_activations = HE = YtSlope_output_layer = Y(1-Y)lr =ηslope_hidden_layer = h(1-h)wout = Wh論智補充

本文以神經網路中最基礎的兩層神經網路作為講解物件,雖然內容已經很翔實,但為了更形象地解釋輸入、輸出、權值、bias等概念在圖中的位置,論智君在文末為讀者做一些基礎知識補充

上圖是一個簡單的兩層神經網路,它由一些圓形和向量組成,其中的圓圈就是我們說的神經元,有時也稱單元(unit)和節點(node),它主要起到計算和儲存作用,因此其實發揮重要作用的是圖中的各條帶箭頭的線。

文章中涉及大量“輸入層隱藏層之間的權值”“隱藏層輸出層之間的權值”的表述,這些都可以從圖片中獲得直觀感受。而我們反復提起的偏置項bias在上圖中也有所展示。理論上,bias是個儲存值永遠為1的單元,它出現在除了輸入層之外的所有層,與後一層的所有節點都有連接。神經網路結構圖一般不會把它明確畫出來,但我們應該知道它是存在的。

本文把多處神經網路都注釋為MLP,並詳細介紹了多層感知器的設計演變。需要注意的是,MLP曾經就指代神經網路,但是它表示的是兩層神經網路,即包含一個輸入層、隱藏層、輸出層的神經網路,一般不能被用來展示層數非常深的神經網路。

論智也沿用了損失的英文原型loss,區別於error誤差。另外會有人把loss譯為殘差、代價,尤其是在談及loss function的時候,它們其實是一種東西。

最後再提一點閾值和參數。文章在介紹bias時用了它的英文單詞,沒有用它的譯名偏置項、閾值,這是為了防止誤讀,尤其是閾值,它容易與案例中的約束條件混淆。事實上,bias作為閾值最生動的體現是在圖像上,它能限制曲線的峰值區間,具體可以Google相關圖片。而本文儘量避免了“參數”這個詞的使用,因為一般而言,神經網路參數就是指訓練得到的權值和bias,把這個概念用於任何一方都是不合理的,它和其他機器學習方法中的參數也不是一個概念,在閱讀其他原創、翻譯內容時,希望讀者能擦亮雙眼。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示