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是一樣的)。