0引言

不知道從什麼時候開始,人類開始捉摸著構造一個可以像自己一樣學習和行動的機器。一些人覺得我們可以搞仿生,模仿人腦的工作方式,來做一個機器——人工神經網路(artificial neural network)是時下最熱門的方案。BP神經網路(back propagation neural network)是神經網路的一種經典結構,因為其結構和訓練簡單(相對的啊),是我們學習神經網路時的入門演算法。BP神經網路的名字起得挺好實際上包含了特定的模型(神經網路)和特定的訓練演算法(反向傳播演算法),這也是本文將要介紹的兩個方面。

1人工神經網路的基本結構

1.1關於人腦工作方式的一點理解

現在人們經常用神經網路(neural network)來描述我們的大腦的結構形式和工作機制,如圖1-1。大腦是由以神經元(圖中的圓圈)為節點,突觸(有向邊)為邊構成的一個圖。因為我們的神經元之間的連接是稀疏的,這個圖不是完全圖,一個節點只與部分節點相連接。我們身上的觀測器官(眼睛,皮膚,耳朵等等),會實時採集環境中的數據,比如眼前這個屏幕上的像素點組成的圖像。眼睛採集到這幅圖像後,把圖像做一個簡單的變換,得到神經元可以接收和處理的信號,傳輸到神經網路中。接收到信號的神經元會對信號進行特定的處理,並將處理的結果傳輸給有突觸相連的神經元。最初的信號經過眾多神經元的處理和傳播,最後會產出若干信號(繼續看;不看了;...),表示人體應該作出的動作,進而指導身體作出合適的反應。

1-1 神經網路示意圖

1.2人工神經網路的幾個假設

人工神經網路(artificial neural network, ANN)是對神經網路的一種數學描述形式。大家都比較懶,經常用"神經網路"來稱呼"人工神經網路";有時候直接用個「網路」。為了讓這個網路足夠簡單,並且可以計算,人們做了一些必要的假設。

(1)沒有環

ANN假設信號的傳播路徑中不存在迴路,即信號經過一個神經元後,就不會再次傳送到這個神經元了(可以認為這個神經元在處理完信號後,進入了某種狀態,不在接收信號;等到本次信號接收-動作信號生成都做完後,這個神經元才會回復平時的狀態,可以接受並處理信號)。

(2)線性組合輸入、激活函數與人工神經元

ANN假設,一個神經元可以接收若干個信號,對這些信號進行"綜合處理"(比如線性組合,sigmod函數映射,softmax函數映射等都可以)後,會輸出一個新的信號,如圖1-2,是一個人工神經元的示意圖。

1-2 人工神經元示意圖

假設一個神經元接收的輸入是n個,我們用一個長度為n的一維向量表示x = (x_1, x_2, ..., x_n),神經元對這n個輸入的加權是weight = (weight_1, weight_2, ..., weight_n)

那麼,這個神經元的輸出就是

y = sigmod(weight*x)

= sigmod(weight_1*x_1 + ... + weight_n*x_n)

人工神經元的理想輸出是0或者1——0表示抑制狀態,1表示激活狀態。多個神經元的狀態,看起來就像某種編碼(比如001),可以用來表示特定的含義,比如我們可以用001、010、100來表示Iris Setosa、、Iris Versicolour和Iris Virginica三種鳶尾花。

(3)背景雜訊信號

ANN還假設,大腦中存在一個類似背景雜訊一個的信號,強度是1,每一個神經元都可以接收到這個信號(就像我們身處的環境一樣,存在很多雜訊,我們實際上一直可以聽到;我們聽到的每一句話裏都包含了這種雜訊)。這樣,一個神經元接收的輸入實際上是x = (x_1, x_2, ..., x_n, 1)。但是,各個神經元對背景雜訊信號的處理(也就是加權)是有區別的。那麼,神經元對這些信號的加權就是weight = (weight_1, weight_2, ..., weight_n, b)。為什麼要用字母b來表示這個權重呢?考慮這個假設的神經元的輸出是:

y = sigmod(weight*x)

= sigmod(weight_1*x_1 + ... + weight_n*x_n + b)

看起來是不是和邏輯回歸模型一樣,b相當於一個截距。

(4)特定的神經網路完成特定的任務

ANN還假設,針對特定外部信息-特定身體動作信號,信號在神經網路中的傳播路徑是固定的。比如,在看到屏幕裏的像素點並判斷是否要繼續看下去的這個任務中,眼睛總是會把信號傳送給固定的幾個神經元,這些神經元是接收到原始信號的一批。

1.3神經網路的結構

為了展示和觀察方便,我們通常把ANN畫成若干層神經元堆疊的樣子,如圖1-3。外界輸入首先會通過連接(每個連接都對應一個權重,就是人工神經元裏,每個輸入對應的w;w我們會通過訓練來得到,這裡假定我們已經知道了具體取值)傳送給一批神經元;第一批神經元會把他們的輸出傳送給第二批神經元;以此類推。同一批次的神經元之間沒有通信;相鄰的兩批神經元之間是全連接的(當然不是真正的全連接,訓練之後,一些神經元之間的連接權重會非常低,表示這些神經元通信強度很低)。按照圖裡的情況,我們也可以用「層」來表示一批神經元,這也是通行的做法。注意,最後一層的神經元的輸出,是一個一維向量,看起來是one hot編碼,在分類任務中,可以表示特定的類別。

1-3 一個執行特定任務的神經網路

我們的最終目的是製造機器嘛,需要首先把神經網路用數學符號表示出來。我們用這樣的向量來代表神經網路:

layer_1=(layer_1_1, layer_1_2, ..., layer_1_i, ..., layer_1_k__1)

式中,layer_1是一個以為向量,元素是k__1個神經元的唯一編號;k__1表示這一批神經元的個數。layer_1這些神經元在處理完接收並處理信號後,會將結果傳送給相鄰的特定神經元。這些神經元接收的是"二手信號"。我們用這樣的向量來代表這批神經元:

layer_2=(layer_2_1, layer_2_2, ..., layer_1_i, ..., layer_2_k__2)

以此類推,我們可以用layer_i = (layer_i_1, layer_i_2, ..., layer_i_j, ..., layer_i_k__i)來表示接收"i手信號"的神經元們。

ANN還假設,layr_i層的神經元和layer_i-1層的神經元是全連接的,即layr_i裏的任何一個神經元,都與layer_i-1裏的每一個神經元相連接。layer_i-1層的神經元在接收並處理信號後,會把信號傳送給layr_i層的每一個神經元。layer_i裏的一個神經元,會接收並處理layer_i-1裏的每一個神經元的信號,然後產生一個新的信號。

至此,我們就獲得了一個框架,描述用來做特定任務的神經網路。

1.4神經網路的使用方式

這個網路如何接收並處理信號呢?換句話說,我們怎麼使用神經網路來計算概率呢?

假設我們的大腦比較簡單,用來判斷一朵鳶尾花的種類的神經網路如圖, 用尺子測量到花的四個指標後,我們的大腦會用2層神經元對數據進行處理,最後輸出這朵花屬於各個類別的概率(得到這些概率後,還有一些神經元會把概率最大的類別挑出來作為花的類別,這裡為了簡化問題,略過不講)。

人工神經元實際上是一個邏輯回歸模型(其實還有很多其他的結構,但是這個最簡單),它將各個輸入進行線性組合(多元線性回歸)後,再用sigmod函數進行一次映射,得到一個看起來像"概率"的數值(因為取值範圍是0-1)。實際上這個數值不能按照概率來理解,它的物理含義應該是"這個神經元遇到輸入後,輸出的信號的強度"。這個信號強度有一個取值範圍是可以理解的,如果強度太大,我們的大腦可能會被"燒壞"。

記尺子測量出來一朵花的特徵向量是(x_1, x_2, x_3, x_4)。

第一層的節點的輸入向量是x_0 = (x_0_1, x_0_2, x_0_3, x_0_4, 1)。"x_0_1"的腳標裏,第一個數字表示網路的層數,x_i表示第i層網路節點的輸出組成的一維向量(第i層的輸出,就是第i+1層的輸入,很多教材用兩種符號表示同一份數據,初學者有時候會被弄迷糊;這裡為了簡單,一份數據只使用一個符號)。

第一層的第一個節點layer_1_1與5個(4 + 1)輸入節點相連的邊的權重是:

weight_1_1 = (weight_1_1_1, weight_1_1_2, weight_1_1_3, weight_1_1_4, b_1_1)

式中,"weight_1_1_1"的腳標裏,第一個數字表示節點所在的網路的層序號,第二個數字表示邊的結束節點在那一層的序號,第三個數字表示邊的出發點在那一層的序號。b_1_1是第1層第1個神經元對背景雜訊信號的加權。制定這些序號的目的,是為了唯一確定一個節點或者邊,也為了我們計算或者編程時方便。

那麼,這個節點的輸出值就是:

x_1_1 = sigmod(weight_1_1*x_0)

= sigmod( weight_1_1_1*x_0_1 + weight_1_1_2*x_0_2 + weight_1_1_3*x_0_3 + weight_1_1_4*x_0_4 + b_1_1)

我們從輸入層開始從左到右,按照這個方式,就可以求出神經網路的輸出,也就是一朵花屬於3個類別的權重。

x_1_2 = sigmod(weight_1_2*x_0)

= sigmod( weight_1_2_1*x_0_1 + weight_1_2_2*x_0_2 + weight_1_2_3*x_0_3 + weight_1_2_4*x_0_4 + b_1_2)

第1層神經元的輸出向量是x_1 = (x_1_1, x_1_2)。

第2層神經元的輸出是:

x_2_1 = sigmod(weight_2_1*x_1)

= sigmod( weight_2_1_1*x_1_1 + weight_2_1_2*x_1_2 + b_2_1)

x_2_2 = sigmod(weight_2_2*x_1)

= sigmod( weight_2_2_1*x_1_1 + weight_2_2_2*x_1_2 + b_2_2)

x_2_3 = sigmod(weight_2_3*x_1)

= sigmod( weight_2_3_1*x_1_1 + weight_2_3_2*x_1_2 + b_2_3)

為了後面求導的時候方便,我們記z_1_1 = weight_1_1*x_0,表示第1層的第1個神經元的激活函數的輸入.這樣,各個神經元的輸出就可以寫為

x_1_1 = sigmod(z_1_1)

x_1_2 = sigmod(z_1_2)

x_2_1 = sigmod(z_2_1)

x_2_2 = sigmod(z_2_2)

x_2_3 = sigmod(z_2_3)

sigmod函數的導數比較有趣:d(x_1_1)/d(z_1_1) = x_1_1*(1-x_1_1),後面我們會用到這個結論。

第2層神經元的輸出向量是x_2 = (x_2_1, x_2_2, x_2_3)

那麼,怎麼獲得類標籤呢?我們只需要將x_2中最大的那個元素賦值為1,其他的元素賦值為0,就獲得了類標籤(我們的網路最後輸出的結果,實際上,是對0或者1的逼近)。

至此,神經網路模型就構建完成了。

2 用BP演算法訓練神經網路

這個模型的參數怎麼求呢?在訓練過程中,對於第一層神經元,他們的真實輸入是已知的,但是真實輸出是未知的;對於第2層神經元,他們的真實輸出是已知的,但是真實輸入是未知的。簡單來說,就是每一個神經元的訓練,似乎無法使用常見的有監督訓練。

有認為,在訓練開始之前,神經網路的參數存在較大的誤差,第一層神經元由於參數的誤差,輸出的信號裏會帶有誤差;第二層神經元,由於參數的誤差,輸出信號裡帶有誤差,再考慮到第一層神經元輸出的誤差,第二層神經元實際輸出的誤差會更大。也就是說,神經網路最後輸出的信號,包含了所有神經元輸出的誤差,體現了所有參數的錯誤程度;誤差會在網路中傳遞。

假設一朵花的真實類別是y=(y_1, y_2, y_3),y_1,y_2,y_3中只有一個取值為1,其他都是0。

那麼,模型計算出來的一朵花的類別權重分佈誤差是多少呢?

我們先來看一個二層神經元的輸出的誤差大小。我們用交叉熵來表示這個誤差(殘差平方也是可以的,不過效果會差一些):

crossEntropy_2_1 = -(1-y_1)*log(1-x_2_1) - y_1*log(x_2_1)

第二層的另外兩個神經元的輸出的誤差就是:

crossEntropy_2_2 = -(1-y_2)*log(1-x_2_2) - y_1*log(x_2_2)

crossEntropy_2_3 = -(1-y_3)*log(1-x_2_3) - y_1*log(x_2_3)

我們以這3個交叉熵表達式來作為3個「邏輯回歸模型」的代價函數。

這樣,整個模型的代價函數就是:

cost = crossEntropy_2_1 + crossEntropy_2_2 + crossEntropy_2_3

我們可以用梯度下降的方式來求神經網路的參數。

第2層第1個神經元,與第1層第1個神經元的連接權重weight_2_1_1對應的梯度是:

d(cost)/d(weight_2_1_1)

= d(crossEntropy_2_1)/d(weight_2_1_1) + d(crossEntropy_2_2)/d(weight_2_1_1) + d(crossEntropy_2_3)/d(weight_2_1_1)

= d(crossEntropy_2_1)/d(z_2_1) * d(z_2_1)/d(weight_2_1_1)

= x_2_1 * (x_2_1 - y_1)

注意,代價函數的後兩項不包含weight_2_1_1,對它的導數就是0。第二層的神經元的其他權重對應的梯度是類似的。

那麼,第0層神經元與第1層神經元連接權重對應的梯度是多少呢?

d(cost)/d(weight_1_1_1) = d(cost)/d(x_1_1) * d(x_1_1) / d(weight_1_1_1)

d(x_1_1) / d(weight_1_1_1) = x_0_1 * x_1_1 * (1 - x_1_1)

d(cost)/d(x_1_1)

= d(crossEntropy_2_1)/d(x_1_1) + d(crossEntropy_2_2)/d(x_1_1) + d(crossEntropy_2_3)/d(x_1_1)

= weight_2_1_1 * ( - y_1 + x_2_1) + weight_2_2_1 * ( - y_2 + x_2_2) + weight_2_3_1 * ( - y_3 + x_2_3)

所以,它的梯度是:

d(cost)/d(weight_1_1_1) = x_0_1 * x_1_1 * (1 - x_1_1) * [weight_2_1_1 * ( - y_1 + x_2_1) + weight_2_2_1 * ( - y_2 + x_2_2) + weight_2_3_1 * ( - y_3 + x_2_3)]

我們根據第2層的輸出誤差,來計算第0層與第1層的連接權重。接下來,我們把計算某個權重對應梯度的方式推廣到一般情況。假設我們要計算第i層的第j個神經元,與第i-1層的第k個神經元的連接權重,對應的梯度。

d(cost)/d(weight_i_j_k) = d(cost)/d(x_i_j) * d(x_i_j)/d(weight_i_j_k)

式中,x_i_j是這個神經元的輸出。

d(x_i_j)/d(weight_i_j_k) = x_i-1_j * x_i_j * (1 - x_i_j)

d(cost)/d(x_i_j) = d(cost)/d(x_i+1_1) * d(x_i+1_1)/d(x_i_j) + d(cost)/d(x_i+1_2) * d(x_i+1_2)/d(x_i_j) + ... +

所以:

d(cost)/d(weight_i_j_k) =

x_i-1_j * x_i_j * (1 - x_i_j) * [d(cost)/d(x_i+1_1) * d(x_i+1_1)/d(x_i_j) + d(cost)/d(x_i+1_2) * d(x_i+1_2)/d(x_i_j) + ... + ]

式中, x_i+1_1是第i+1層的第1個神經元的輸出。

d(x_i+1_1)/d(x_i_j) = weight_i+1_1 * x_i+1_j * (1 - x_i+1_j)

d(cost)/d(x_i+1_1) = d(cost)/d(x_i+2_1) * d(x_i+2_1)/d(x_i+1_1) + d(cost)/d(x_i+2_2) * d(x_i+2_2)/d(x_i+1_2) + ... +

我們可以一直推導下去,直到最後一層(假設網路中有M層神經元)。

d(cost)/d(x_M-1_1) = weight_M_1_1 * ( - y_1 + x_M_1) + weight_M_1_2 * ( x_M_2 - y_2) + ...

我們再反過來算一遍。

最後一層(M層)的神經元與M-1層的神經元的連接權重,對應的梯度是:

d(cost)/d(weight_M_1_1) = x_M-1_1 * (x_M_1 - y_1)

d(cost)/d(weight_M_1_2) = x_M-1_2 * (x_M_1 - y_1)

...

第M-1層的神經元與第M-2層的神經元的連接權重,對應的梯度是:

d(cost)/d(weight_M-1_1_1)

= d(cost)/d(x_M-1_1) * d(x_M-1_1)/d(weight_M-1_1_1)

= [weight_M_1_1*(x_M_1 - y_1) + weight_M_2_1*(x_M_2 - y_2) + ...] * x_M-2_1 * x_M-1_1(1-x_M-1_1)

...

d(cost)/d(weight_M-2_1_1)

= d(cost)/d(x_M-2_1) * d(x_M-2_1)/d(weight_M-2_1_1)

= [d(cost)/d(x_M-1_1)* d(x_M-1_1)/d(x_M-2_1) + d(cost)/d(x_M-1_2)* d(x_M-1_2)/d(x_M-2_1) + ....] * x_M-3_1 * x_M-2_1(1-x_M-2_1)

式中:

d(x_M-1_1)/d(x_M-2_1) = wieght_M-1_1_1 * x_M-1_1 * (1 - x_M-1_1)

...

最終得到的表達式,實際上就是誤差從輸出節點出發,反向傳播到這裡,得到的一個量。這也是反向傳播這個叫法的由來。在編程的時候,我們可以用後向演算法來實現,,就是我們在計算完某個節點的誤差後,保存起來,然後在計算這個節點所在層的前一層的梯度的時候使用——這會節省大量的耗時。注意,本文的神經網路是從左到右排列的,這裡的「前」指的是左邊,「後」指的是右邊。不同的人的提法不一樣,大家要注意區分。

假設我們給每一個權重以相同的學習率(實際應用的時候,我們可以給各層的神經元不同的學習率;可以讓學習率在訓練過程中變化;可以隨便設置學習率),那麼某個權重的更新規則就是:

weight_i_j_k_new = weight_i_j_k - learningRate * d(cost)/d(weight_i_j_k)

在訓練的時候,我們從最後一層開始向前,計算每一個權重對應的梯度;接著更新所有的權重,然後重複前面兩步,直到我們滿意。

到這裡,神經網路的訓練方法就介紹完畢了。

3結束語

BP神經網路是有若干個演算法結合起來形成的:邏輯回歸,梯度下降,前向、後向演算法。另外還需要一些函數求導的基礎知識(函數求導基本公式),以及圖論裏的(最)基礎知識(圖的基本概念,簡單的圖的結構)。大家可以先學習這裡提到的知識碎片,然後再來看神經網路,學習曲線就會平緩很多。

通常來說,寫教材或者教程的人都是想讓自己的表述足夠易懂的——只不過有的人目的是做個筆記,自己動就行;有的人目的是傳播知識,盡量讓別人也能看懂。我們在學習的過程中,如果看不懂一篇文章的講法,可能是因為作者是寫給自己的,也可能是作者寫的不夠通俗,也可能是作者的思維方式和我們不一樣。可以看一看另外一篇,總能遇到適合自己的文章。


推薦閱讀:
相關文章