1. 程式人生 > >Python實現神經網路Part 1: 實現forward和BP演算法的神經元

Python實現神經網路Part 1: 實現forward和BP演算法的神經元

主旨和本系列目錄

《Python實現神經網路》是一個文章系列,目的在於:通過實際編寫程式,加深對神經網路所涉及的各類演算法的理解。動機描述詳見“本系列動機”部分。

截止目前本系列已完成部分目錄如下

本系列動機

在學習深度學習理論過程中,由神經元(節點)組成的多層全連線神經網路是最基礎的概念,如下圖所示[1]。
這裡寫圖片描述

這樣一個神經網路要發揮作用,必須有演算法支援,這其中包括前向(forward)結果計算,也包括誤差反向傳播(Backward Propagation, BP),還有基於梯度的引數訓練等。

在我自己在學習和應用這些演算法的過程中,面臨這樣一種狀況:一方面,理論教材(例如周志華教授的

西瓜書以及網路上浩如煙海的資料)從單個神經元開始詳細講解了上述演算法的數學原理;另一方面,在目前流行的深度學習框架,例如TensorFlow中,這些功能已經被良好封裝,只要呼叫正確的API就可以實現。這樣,學到的數學原理在框架呼叫中並不能直觀的感覺到,雖然已經能用神經網路解決一些問題,但是對於演算法基礎原理的理解仍然不夠深入。

源於此,我開始嘗試用程式碼實現神經網路,希望通過這樣的方式加深對神經網路原理性的理解,在今後的工作中更清楚框架(例如TensorFlow)中API的特性與侷限。

原始碼位置

神經元的軟體設計

在設計中,存在以下假設

  1. 神經元與前一級iDims個節點有連線,iDims >=0且iDims為整數
  2. 神經元的輸入為1*iDims的向量,記為x

從軟體的角度,神經元可以抽象為一個物件,此物件包含以下成員

  1. 權重向量weight: shape =1*iDims,用於儲存與前一級iDims個連線中每個連線的權重
  2. 偏置量bias:這是一個標量,用於記錄偏置值
  3. 輸入向量x⃗ 
  4. 啟用函式的輸入值dotValuedotValue=x⃗ weight+bias=i=1iDimsweightixi+bias之所以要儲存這個成員,是為了在誤差反向傳播時避免重複計算

程式碼實現

建構函式:在構造時要指定與前一級的連線數目inputDim,同時weight的所有元素和bias均初始化為1

  def __init__(self, inputDim):
    #當前結點與前一級的連線數目
    self.iDims = inputDim
    #權重向量,Shape = (iDims, )
    self.weight = np.random.rand(self.iDims)
    #self.weight = np.ones(self.iDims)
    #偏置 
    self.bias = 1
    #啟用函式的輸入
    self.z = 1
    #當前層的殘差
    self.delta = 1
    #前級節點的輸入向量,必須與iDims匹配,Shape = (iDims, )的向量
    self.x = []

前向計算和儲存計算結果

  #forward: 輸入1*iDims向量,計算前向結果
  def forward(self, ix):
    if (ix.shape <> (self.iDims,)):
      print ("Wrong input shape: x.shape = " + str(ix.shape))
      return
    self.x = ix
    self.dotValue = np.dot(self.x,self.weight) + self.bias
    return sigmoid(self.dotValue)

反向梯度計算:這裡的演算法學習自文獻[2]

  #backward: 輸入前一級計算出的梯度,輸出為兩個陣列
  #第一個陣列: dx,iDims*1向量,即當前節點對於前一級每個輸入的梯度
  #第二個陣列:dw,iDims*1向量,當前節點對於每個權重的梯度
  #第三個陣列:dbias, 1*1向量,當前節點對於偏置量的梯度
  def backward(self, gradient):
    ddot =  (1-self.dotValue) * self.dotValue #Sigmoid函式的求導
    dx = self.weight*ddot*gradient # 回傳到x
    dw = self.x*ddot*gradient # 回傳到w
    dbias = ddot*gradient # 回傳到bias
    return [dx, dw, dbias]

權值調整(訓練):需要給定學習率LearnRate

  #根據學習率和梯度調整weight和bias引數
  def adjustWeightAndBias(self, learnRate, dw, dbias):
    self.weight = self.weight - learnRate*dw
    self.bias = self.bias - learnRate*dbias

單元測試設計與實現

單元測試例設計為:
前級輸入向量維度iDims = 2,輸入向量x⃗ =(2,2),目標輸出值為target=11+e
損失函式定義為

loss=(predictValuetarget)2

測試程式碼如下

  n1 = NeuralNode(2)
  n1.printParam();

  x = np.ones(2)
  x[0] = 2
  x[1] = 2

  target = 1/(1+np.exp(1))

  for i in range(10000):
    print "Round",i
    fowardResult = n1.forward(x)
    print "Forward Result:",fowardResult
    loss = (fowardResult-target)*(fowardResult-target)
    print "Loss=",loss
    dLossdvalue = 2*(target-fowardResult)
    grad = n1.backward(dLossdvalue)
    print "grad=",grad
    n1.adjustWeightAndBias(0.0001, grad[1], grad[2])
    n1.printParam()
    print ""

測試結果:

  1. 目前的神經元實現可以調整權重,使得損失函式向loss降低的方向調整
  2. 在足夠多次迭代後,引數總是收斂到如下結果。在這個位置,梯度已經接近於0,無法進一步調整。但我們明確的知道這個位置不是最優解

Round 999
Forward Result: 0.73105857863
Loss= 0.213552267034
grad= [array([ 2.28023604e-17, 2.28023604e-17]), array([ 4.10442486e-16, 4.10442486e-16]), 2.0522124322575492e-16]
Weight = [ 0.11111111 0.11111111]
Bias = 0.555555555556

待解決的問題

  1. 單元測試中發現的沒有訓練到最優解的問題:這有兩種可能,一種是隻有一條訓練資料,訓練樣本數量不足;另一種,根據梯度下降調整引數的演算法只實現了最基本的功能,當搜尋到區域性極值點之後由於梯度消失無法跳出來找到更好的極值點。
  2. 還沒有實現由多層神經元組成的網路
  3. 軟體方面:還沒有實現batch訓練和調整。在TensorFlow中,一次訓練的是一個batch的資料,即輸入X={x⃗ i,i=1,2,...N},批量計算結果、梯度並調整權值。目前的程式碼只能一次處理一個輸入向量。

參考文獻