Python程式碼搭建簡單的神經網路

  • 2019 年 10 月 6 日
  • 筆記

參考原文:

https://medium.com/technology-invention-and-more/how-to-build-a-simple-neural-network-in-9-lines-of-python-code-cc8f23647ca1

什麼是神經網路?人類有1000億個被稱為神經元的細胞,它們之間通過軸突連接。連接到某個神經元的軸突中,如果有足夠多數量被觸發,則這個神經元就會被觸發。我們把這個過程稱為「思考」。

我們可以在電腦上創建一個神經網路模型。不需要模擬分子級別的複雜生物邏輯,只需要模擬高層的邏輯。我們使用一個數學技能,成為矩陣。為了簡單,我們只用一個神經元,它有三個輸入和一個輸出。

我們將訓練這個神經元來解決下面的問題。前面4個樣本是訓練集,你能找到它的規律嗎?新樣本中的「?」應該是0還是1?

你可能會注意到,output值總是與input中最左側的值相同。所以,新樣本中的「?」應該是1. 但是,我們如何教神經元來正確回答這個問題?

我們會給每一個input一個權重,它可以是正數也可以是負數。如果權重是一個很大的正數或者很大的負數,它對神經元輸出值的影響也就很大。再開始之前,我們先給每一個權重設置一個隨機值。然後,開始訓練的過程。

1. 把訓練集中的樣本作為輸入,通過權重來調整它們。然後把它們代入一個特殊的公式來計算神經元的輸出。

2. 計算誤差(error)。誤差就是神經元的輸出和訓練集期望的輸出的差值。

3. 根據誤差的方向,輕微的調整權重。

4. 重複上面的過程10000次。

最終,神經元的權重會達到一個合適的值。如果我們讓神經元去思考一個相同模式的新條件,他會做出不錯的預測。

這個過程被稱為反向傳播。

神經元輸出公式

你可能會好奇,神經元輸出公式有什麼特別之處?首先,我們對input加權求和。

然後歸一化,使得它的最終值介於0和1之間。我們使用數學裡一個很方便的函數,叫Sigmoid函數。

它的曲線為

把第一個公式帶入第二個,得出神經元輸出的最終公式

你可能已經注意到,我沒有使用最小閾值,這樣會簡單一些。

源程式碼

from numpy import exp, array, random, dot    class NeuralNetwork():      def __init__(self):          # 隨機數發生器種子,以保證每次獲得相同結果          random.seed(1)          # 對單個神經元建模,含有3個輸入連接和一個輸出連接          # 對一個3 x 1的矩陣賦予隨機權重值。範圍-1~1,平均值為0          self.synaptic_weights = 2 * random.random((3, 1)) - 1        # Sigmoid函數,S形曲線      # 用這個函數對輸入的加權總和做正規化,使其範圍在0~1      def __sigmoid(self, x):          return 1 / (1 + exp(-x))        # Sigmoid函數的導數      # Sigmoid曲線的梯度      # 表示我們對當前權重的置信程度      def __sigmoid_derivative(self, x):          return x * (1 - x)        # 通過試錯過程訓練神經網路      # 每次都調整突觸權重      def train(self, training_set_inputs, training_set_outputs, number_of_training_iterations):          for iteration in range(number_of_training_iterations):              # 將訓練集導入神經網路              output = self.think(training_set_inputs)              # 計算誤差(實際值與期望值之差)              error = training_set_outputs - output              # 將誤差、輸入和S曲線梯度相乘              # 對於置信程度低的權重,調整程度也大              # 為0的輸入值不會影響權重              adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))              # 調整權重              self.synaptic_weights += adjustment        # 神經網路一思考      def think(self, inputs):          # 把輸入傳遞給神經網路          return self.__sigmoid(dot(inputs, self.synaptic_weights))    if __name__ == "__main__":      # 初始化神經網路      neural_network = NeuralNetwork()      print("隨機的初始突觸權重:")      print(neural_network.synaptic_weights)      # 訓練集。四個樣本,每個有3個輸入和1個輸出      training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])      training_set_outputs = array([[0, 1, 1, 0]]).T      # 用訓練集訓練神經網路      # 重複一萬次,每次做微小的調整      neural_network.train(training_set_inputs, training_set_outputs, 10000)      print("訓練後的突觸權重:")      print(neural_network.synaptic_weights)      # 用新數據測試神經網路      print("考慮新的形勢 [1, 0, 0] -> ?: ")      print(neural_network.think(array([1, 0, 0])))

運行

python3 NeuralNetwork.py

結果

隨機的初始突觸權重:  [[-0.16595599]   [ 0.44064899]   [-0.99977125]]  訓練後的突觸權重:  [[ 9.67299303]   [-0.2078435 ]   [-4.62963669]]  考慮新的形勢 [1, 0, 0] -> ?:  [0.99993704]  MacBook-Pro-5:sa

解析:

初始化方法

    def __init__(self):          # 隨機數發生器種子,以保證每次獲得相同結果          random.seed(1)          # 對單個神經元建模,含有3個輸入連接和一個輸出連接          # 對一個3 x 1的矩陣賦予隨機權重值。範圍-1~1          self.synaptic_weights = 2 * random.random((3, 1)) - 1

random.random((3, 1)) 生成一個3×1的numpy.ndarray類型的數組,每個元素的取值範圍是[0,1]

2 * random.random((3, 1)) – 1,則取值範圍變為 [-1 , 1] 。比如某次synaptic_weights得出的值為:

array([[-0.16595599],         [ 0.44064899],         [-0.99977125]])

sigmoid方法和它的導數

    # Sigmoid函數,S形曲線      # 用這個函數對輸入的加權總和做正規化,使其範圍在0~1      def __sigmoid(self, x):          return 1 / (1 + exp(-x))        # Sigmoid函數的導數      # Sigmoid曲線的梯度      # 表示我們對當前權重的置信程度      def __sigmoid_derivative(self, x):          return x * (1 - x)

他們的曲線圖如下所示:

sigmoid函數x取值範圍是(-無窮,+無窮),對應的y值為(0,1)。它是一個單調遞增函數。

sigmoid導數的圖形是一個鍾型,x取值範圍(-無窮,+無窮),當x=0時y值最大,為0.25。越往兩端,值越小。這表明,sigmoid函數在x=0這個點變化最大(確定性低),越往兩邊變化越小(確定性高)。

執行神經網路

就是將輸入賦值給神經網路的輸入層,然後根據網路的權重計算輸出。

    # 神經網路一思考      def think(self, inputs):          # 把輸入傳遞給神經網路          return self.__sigmoid(dot(inputs, self.synaptic_weights))

inputs是一個1X3的數組。synaptic_weights是一個3X1的數組。dot點乘方法。根據矩陣計算方法,1X3的矩陣點乘一個3X1的矩陣,結果是一個1X1的矩陣,即一個數字(output)。含義如下圖所示。

訓練

    # 通過試錯過程訓練神經網路      # 每次都調整突觸權重      def train(self, training_set_inputs, training_set_outputs, number_of_training_iterations):          for iteration in range(number_of_training_iterations):              # 將訓練集導入神經網路              output = self.think(training_set_inputs)              # 計算誤差(實際值與期望值之差)              error = training_set_outputs - output              # 將誤差、輸入和S曲線梯度相乘              # 對於置信程度低的權重,調整程度也大              # 為0的輸入值不會影響權重              adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))              # 調整權重              self.synaptic_weights += adjustment

training_set_inputs:訓練集的輸入,是一個NX3的數組。

training_set_outputs: 訓練集的輸出,是一個NX1的數組。當然,這兩個參數也可以合併為一個NX4的數組參數。

number_of_training_iterations:訓練中的迭代次數。一個模型往往需要很多次的訓練才能收斂。

training_set_inputs

training_set_outputs

Example1

0 0 1

0

Example2

1 1 1

1

Example3

1 0 1

1

Example4

0 1 1

0

訓練前, 程式先生成一個隨機NeuralNetwork,即[[weight1], [weight2],[weight3]],比如值為

-0.16595599

0.44064899

-0.99977125

進入迭代,不斷計算output和error,比如第1輪的值如下表所示

training_set_inputs

training_set_outputs

第i輪output

第i輪error=training_set_outputs – 第i輪output

Example1

0 0 1

0

0.2689864 …

-0.2689864 …

Example2

1 1 1

1

0.3262757 …

0.6737243 …

Example3

1 0 1

1

0.23762817 …

0.76237183 …

Example4

0 1 1

0

0.36375058 …

-0.36375058 ….

計算出來的output與訓練集中的output有差異,說明模型不夠好,需要優化。所謂優化,就是調整神經網路的權重。調整的方向是由error的符號決定的;

調整的大小是由error、output以及input的值決定的。

1.error越大,也就是誤差越大,則需要調整的幅度越大;即error值與調整幅度成正相關。

2.output絕對值越大,需要調整的幅度越小(sigmoid函數越靠近兩側,確定性越高,越不需要調整,sigmoid導數越小),用sigmoid導數代表這個因素。output絕對值與調整幅度成負相關,也即sigmoid_derivative(output)與調整幅度成正相關。

3.調整的幅度是要分攤給每一個input的,即input1、input2和input3分別承擔一部分幅度。input絕對值越小(比如接近0),越不需要關心它,也就需要調整的幅度越小。反之則越大。input值與調整幅度成正相關。

根據上面的相關性邏輯,可以用下面的公式來計算調整幅度(adjustment)

adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))

training_set_inputs.T(轉置矩陣)為3XN的矩陣;error:為NX1的矩陣;output為NX1的矩陣;

adjustment為一個3X1的矩陣,即與神經網路的權重對應。

第1輪優化,Old Network、adjustment和new Network的權重分別是:

Old Network

adjustment

new Network

-0.16595599

0.28621005

0.12025406

0.44064899

0.06391297

0.50456196

-0.99977125

0.14913351

-0.85063774

第2輪優化,Old Network、adjustment和new Network的權重分別是:

Old Network

adjustment

new Network

0.12025406

0.28537634

0.40563039

0.50456196

0.03675341

0.54131537

-0.85063774

0.12206463

-0.72857311

第99輪優化,Old Network、adjustment和new Network的權重分別是:

Old Network

adjustment

new Network

4.59630407

0.01255929

4.60886336

-0.20263703

-0.0003789

-0.20301592

2.0810197

-0.00614832

-2.08716802

第100輪優化,Old Network、adjustment和new Network的權重分別是:

Old Network

adjustment

new Network

4.60886336

0.01242401

4.62128737

-0.20301592

0.00036874

-0.20338466

-2.08716802

0.00608509

-2.09325311

可以發現:

1. 訓練到後面是,adjustment已經變得很小。也就是說,模型已經很好,不需要太多調整。

2. 最後的New Network,weight1絕對值很大。也就是說,input中,第一個值(input1)對output的影響最大。從訓練集中也可以發現這個規律(output跟input1是一樣的)。