反向傳播算法用于經典的前饋人工神經網絡。
它仍然是訓練大型深度學習網絡的技術。
在這個教程中,你將學習如何用Python從頭開始實現神經網絡的反向傳播算法。
完成本教程后,您將了解:
- 如何將輸入前向傳播以計算輸出。
- 如何反向傳播錯誤和訓練網絡。
- 如何將反向傳播算法應用于實際的預測建模問題。
描述
本節將簡要介紹反向傳播算法和我們將在本教程中使用的 wheat seeds 數據集。
反向傳播算法
反向傳播算法是人工神經網絡領域多層前饋網絡的監督學習方法。
前饋神經網絡受到一個或多個神經細胞(稱為神經元)的信息處理的啟發。神經元通過其樹突接受輸入信號,樹突將電信號傳遞到細胞體。軸突將信號傳遞到突觸,突觸是神經元的軸突與其他細胞的樹突之間的連接。
反向傳播方法的原理是通過修改輸入信號的內部權重來模擬給定函數,以產生預期的輸出信號。該系統使用監督學習方法進行訓練,其中系統輸出與已知預期輸出之間的誤差被呈現給系統,并用于修改其內部狀態。
從技術上講,反向傳播算法是一種用于訓練多層前饋神經網絡中權重的方法。因此,它需要定義一個由一個或多個層組成的網絡結構,其中一層完全連接到下一層。標準的網絡結構是一個輸入層、一個隱藏層和一個輸出層。
反向傳播可以用于分類和回歸問題,但在本教程中我們將重點討論分類問題。
在分類問題中,當網絡的輸出層有一個神經元對應每個類值時,可以取得最佳結果。例如,一個具有類值A和B的二類或二進制分類問題。這些預期輸出需要轉換為二進制向量,每列對應一個類值。比如A對應[1, 0],B對應[0, 1]。這稱為獨熱編碼(one hot encoding)。
小麥種子數據集
種子數據集涉及根據不同小麥品種的種子測量值預測物種。
有201條記錄和7個數值輸入變量。這是一個具有3個輸出類別的分類問題。每個數值輸入值的尺度不同,因此在使用像反向傳播算法這樣的對輸入加權的算法時,可能需要對一些數據進行標準化。
以下是數據集的前5行樣本。
15.26,14.84,0.871,5.763,3.312,2.221,5.22,1
14.88,14.57,0.8811,5.554,3.333,1.018,4.956,1
14.29,14.09,0.905,5.291,3.337,2.699,4.825,1
13.84,13.94,0.8955,5.324,3.379,2.259,4.805,1
16.14,14.99,0.9034,5.658,3.562,1.355,5.175,1
使用預測最常見的類值的零規則算法,該問題的基準準確率是28.095%。
您可以從 UCI 機器學習數據倉庫了解更多并下載種子數據集。
下載種子數據集,并將其放入您當前的工作目錄中,文件名seeds_dataset.csv。
數據集是用制表符分隔的格式,因此您必須使用文本編輯器或電子表格程序將其轉換為CSV。
教程
本教程分為6個部分:
- 初始化網絡。
- 前向傳播。
- 反向傳播誤差。
- 訓練網絡。
- 預測。
- 種子數據集案例研究。
這些步驟將為你提供從頭開始實現反向傳播算法并應用于你自己的預測建模問題所需的基石。
1. 初始化網絡
讓我們從一個簡單的事情開始,創建一個用于訓練的新網絡。
每個神經元都有一個需要維護的權重集。每個輸入連接一個權重,還有一個用于偏置的額外權重。在訓練期間,我們需要為神經元存儲額外的屬性,因此我們將使用字典來表示每個神經元,并通過諸如‘weights‘這樣的名稱來存儲屬性。
一個網絡被組織成多層結構。輸入層實際上只是我們訓練數據集中的一個行。第一個真正的層是隱藏層。緊隨其后的是輸出層,該層為每個類值有一個神經元。
我們將把層組織成字典數組,并將整個網絡視為層的數組。
將網絡權重初始化為小的隨機數是良好的實踐。在這種情況下,我們將使用0到1范圍內的隨機數。
下面是一個名為initialize_network()的函數,該函數創建一個新的神經網絡,準備進行訓練。它接受三個參數,輸入的數量、隱藏層的神經元數量和輸出的數量。
你可以看到對于隱藏層我們創建了n_hidden個神經元,每個隱藏層中的神經元有n_inputs + 1個權重,一個對應于數據集中每個輸入列,并且還有一個偏置。
你還可以看到,連接到隱藏層的輸出層有n_outputs個神經元,每個神經元有n_hidden + 1個權重。這意味著輸出層中的每個神經元都連接到(對隱藏層中的每個神經元都有權重)隱藏層中的每個神經元。
# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return network
讓我們測試一下這個功能。下面是一個創建小型網絡的完整示例。
from random import seed
from random import random# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return networkseed(1)
network = initialize_network(2, 1, 2)
for layer in network:print(layer)
運行示例,你可以看到代碼逐個打印每一層。你可以看到隱藏層有一個神經元,有2個輸入權重加偏置。輸出層有2個神經元,每個神經元有1個權重加偏置。
[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}]
[{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]
既然我們已經知道如何創建和初始化一個網絡,讓我們看看如何使用它來計算輸出。
2. 前向傳播
我們可以通過將輸入信號傳播通過每層神經網絡,直到輸出層輸出其值,來計算神經網絡的輸出。
我們把這個過程稱為前向傳播。
這是我們在訓練過程中生成預測時需要的技術,這些預測需要進行修正,并且這是在訓練網絡后需要的方法,以對新數據進行預測。
我們可以將前向傳播分為三個部分:
- 神經元激活。
- 神經元轉移。
- 前向傳播。
2.1. 神經元激活
第一步是計算給定輸入時一個神經元的激活。
輸入可以是我們訓練數據集的一行,就像隱藏層的情況一樣。在輸出層的情況下,輸入也可能是隱藏層中每個神經元的輸出。
神經元激活是通過輸入的加權和計算得出的。這類似于線性回歸。
activation = sum(weight_i * input_i) + bias
其中 weight 是一個網絡權重,input 是一個輸入,i 是權重或輸入的索引,bias 是一個特殊的權重,它沒有輸入來相乘(或者你可以認為輸入總是為1.0)。
下面是一個在名為activate()函數中實現這一功能的代碼。你可以看到,該函數假定偏置是權重列表中的最后一個權重。這在這里和以后有助于使代碼更易閱讀。
# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation
現在,讓我們看看如何使用神經元激活。
2.2. 神經元轉移
一旦一個神經元被激活,我們需要將激活傳遞出去,以查看神經元的實際輸出。
可以使用不同的傳輸函數。傳統的做法是使用sigmoid激活函數,但你也可以使用tanh(雙曲正切)函數來傳輸輸出。最近,整流器傳輸函數在大型深度學習網絡中很受歡迎。
S形激活函數看起來像字母S,也稱為邏輯斯蒂克函數。它可以接受任何輸入值,并在S形曲線上產生一個0到1之間的數字。它還是一個我們可以輕松計算其導數(斜率)的函數,當我們反向傳播錯誤時,稍后會用到這個導數。
我們可以使用Sigmoid函數來傳輸激活函數,如下所示:
output = 1 / (1 + e^(-activation))
其中,e 是自然對數的底數 (歐拉數)。
下面是一個名為transfer()的函數,它實現了Sigmoid方程。
# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))
既然我們已經得到了這些碎片,讓我們來看看它們是如何使用的。
2.3. 前向傳播
將輸入前向傳播是 straightforward 的。
我們逐層計算網絡中每個神經元的輸出。一層的所有輸出將成為下一層神經元的輸入。
下面是一個名為forward_propagate()的函數,該函數使用我們的神經網絡對數據集中的某一行數據進行前向傳播。
你可以看到一個神經元的輸出值存儲在名為‘output‘的神經元中。你還可以看到我們收集了一層的輸出,并將它們存儲在名為new_inputs的數組中,這個數組變成了inputs數組,并作為下一層的輸入使用。
該函數返回最后一層(也稱為輸出層)的輸出。
# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs
讓我們將所有這些部分放在一起,測試一下我們網絡的前向傳播。
我們定義我們的網絡具有一個隱藏神經元,該神經元期望2個輸入值,并且輸出層有2個神經元。
from math import exp# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# test forward propagation
network = [[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],[{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]]
row = [1, 0, None]
output = forward_propagate(network, row)
print(output)
運行示例將輸入模式 [1, 0] 并產生一個輸出值,該值被打印出來。由于輸出層有兩個神經元,我們得到一個包含兩個數字的列表作為輸出。
目前的實際輸出值只是些無意義的數字,但接下來,我們將開始學習如何使神經元中的權重更加有用。
[0.6629970129852887, 0.7253160725279748]
3. 反向傳播誤差
反向傳播算法的命名方式是根據權重的訓練方法。
誤差是預期輸出和網絡正向傳播的輸出之間的差異。然后這些誤差從輸出層反向傳播通過網絡到隱藏層,分配錯誤責任并更新權重。
用于反向傳播錯誤的數學源于微積分,但在本節中我們將保持高級概述,重點討論計算的內容和方法,而不是為什么計算采用這種特定形式。
這一部分被分為兩個部分。
- 轉移導數。
- 誤差反向傳播。
3.1. 轉移導數
給定一個神經元的輸出值,我們需要計算它的斜率。
我們使用了S形轉移函數,其導數可以如下計算:
derivative = output * (1.0 - output)
下面是一個名為transfer_derivative()的函數,實現了這個方程。
# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)
現在,讓我們看看這將如何使用。
3.2. 誤差反向傳播
第一步是計算每個輸出神經元的誤差,這將為我們提供用于向后傳播的誤差信號(輸入)。
對于給定的神經元,其誤差可以按照以下方式計算:
1 error = (output - expected) * transfer_derivative(output)
預期輸出是神經元的預期輸出值,輸出是神經元的實際輸出值,轉移導數 計算神經元輸出值的斜率,如上所述。
這個誤差計算用于輸出層的神經元。期望值是類值本身。在隱藏層中,情況稍微復雜一些。
隱藏層中神經元的誤差信號是輸出層中每個神經元的加權誤差。可以將誤差沿著輸出層的權重反向傳播到隱藏層中的神經元。
反向傳播的誤差信號被累積,然后用于確定隱藏層神經元的誤差,如下所示:
error = (weight_k * error_j) * transfer_derivative(output)
其中 error_j 是輸出層第 j 個神經元的誤差信號,weight_k 是連接第 k 個神經元到當前神經元的權重,output 是當前神經元的輸出。
下面是一個名為backward_propagate_error()的函數,它實現了這個過程。
你可以看到,為每個神經元計算的誤差信號以‘delta’這個名字存儲。你可以看到,網絡的層是逆序迭代的,從輸出層開始向后推進。這確保了輸出層的神經元先計算出‘delta’值,隱藏層的神經元在后續迭代中可以使用這些值。我選擇‘delta’這個名字是為了反映誤差對神經元所表示的變化(例如,權重變化)。
你可以看到隱藏層神經元的誤差信號是通過輸出層神經元積累的,其中隱藏神經元編號j也是輸出層神經元權重的索引neuron[‘weights’][j]。
# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
讓我們把所有拼圖拼在一起,看看它是如何工作的。
我們定義了一個具有輸出值的固定神經網絡,并反向傳播一個期望的輸出模式。完整的示例如下所示。
# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])# test backpropagation of error
network = [[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095]}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763]}]]
expected = [0, 1]
backward_propagate_error(network, expected)
for layer in network:print(layer)
運行示例會在誤差反向傳播完成后打印網絡。你可以看到,誤差值被計算并存儲在輸出層和隱藏層的神經元中。
[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614], 'delta': 0.0005348048046610517}]
[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095], 'delta': 0.14619064683582808}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763], 'delta': -0.0771723774346327}]
現在讓我們使用誤差反向傳播來訓練網絡。
4. 訓練網絡
網絡使用隨機梯度下降進行訓練。
這涉及多次迭代:將訓練數據集暴露給網絡,對每行數據進行前向傳播輸入、反向傳播誤差并更新網絡權重。
本部分分為兩個部分:
- 更新權重。
- 訓練網絡。
4.1. 更新權重
一旦通過上述反向傳播方法計算出網絡中每個神經元的誤差,這些誤差就可以用于更新權重。
網絡權重更新如下:
weight = weight - learning_rate * error * input
其中 weight 是一個給定的權重,learning_rate 是一個你必須指定的參數,error 是通過反向傳播程序計算出的神經元的誤差,input 是導致該誤差的輸入值。
同樣的程序可以用于更新偏置權重,只是沒有輸入項,或者輸入是固定值1.0。
學習率控制了為了修正錯誤而更改權重的幅度。例如,值為0.1將使權重更新為可能更新幅度的10%。通常更喜歡較小的學習率,這會導致在大量訓練迭代中較慢的學習。這增加了網絡在所有層中找到一組良好權重的可能性,而不是最快使錯誤最小的權重(稱為過早收斂)。
下面是一個名為update_weights()的函數,該函數在給定輸入數據行、學習率的情況下更新網絡的權重,并假設已經進行了前向和反向傳播。
記住,輸出層的輸入是隱藏層的輸出集合。
# Update network weights with error
def update_weights(network, row, l_rate):for i in range(len(network)):inputs = row[:-1]if i != 0:inputs = [neuron['output'] for neuron in network[i - 1]]for neuron in network[i]:for j in range(len(inputs)):neuron['weights'][j] -= l_rate * neuron['delta'] * inputs[j]neuron['weights'][-1] -= l_rate * neuron['delta']
現在我們知道如何更新網絡權重,讓我們看看如何重復進行。
4.2. 訓練網絡
如上所述,網絡使用隨機梯度下降進行更新。
這包括首先循環固定次數的輪次,并在每個輪次中更新網絡,使其適應訓練數據集中的每一行。
由于每次訓練模式都會進行更新,因此這種學習被稱為在線學習。如果在更新權重之前將錯誤累積在一個時期內,這被稱為批量學習或批量梯度下降。
以下是用于根據給定的訓練數據集、學習率、固定輪數和期望的輸出值數量來訓練已初始化神經網絡的函數。
預期的輸出值數量用于將訓練數據中的類別值轉換為 one hot 編碼。即為每個類別值創建一個二進制向量,以匹配網絡的輸出。這是計算輸出層錯誤所必需的。
您還可以看到,每個時期之間預期輸出和網絡輸出之間的平方誤差之和被累積并打印出來。這有助于跟蹤每個時期網絡學習和改進的程度。
# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):for epoch in range(n_epoch):sum_error = 0for row in train:outputs = forward_propagate(network, row)expected = [0 for i in range(n_outputs)]expected[row[-1]] = 1sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])backward_propagate_error(network, expected)update_weights(network, row, l_rate)print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
我們現在擁有了訓練網絡的所有拼圖。我們可以組裝一個例子,包括到目前為止我們看到的所有內容,包括網絡初始化和在小數據集上訓練網絡。
以下是一個小型的構造數據集,我們可以用它來測試訓練我們的神經網絡。
X1 X2 Y
2.7810836 2.550537003 0
1.465489372 2.362125076 0
3.396561688 4.400293529 0
1.38807019 1.850220317 0
3.06407232 3.005305973 0
7.627531214 2.759262235 1
5.332441248 2.088626775 1
6.922596716 1.77106367 1
8.675418651 -0.242068655 1
7.673756466 3.508563011 1
以下是完整的示例。我們將使用2個隱藏層神經元。這是一個二元分類問題(2個類別),因此輸出層將有兩個神經元。網絡將訓練20個時期,學習率為0.5,因為我們的迭代次數很少,所以這個學習率較高。
from math import exp
from random import seed
from random import random# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return network# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])# Update network weights with error
def update_weights(network, row, l_rate):for i in range(len(network)):inputs = row[:-1]if i != 0:inputs = [neuron['output'] for neuron in network[i - 1]]for neuron in network[i]:for j in range(len(inputs)):neuron['weights'][j] -= l_rate * neuron['delta'] * inputs[j]neuron['weights'][-1] -= l_rate * neuron['delta']# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):for epoch in range(n_epoch):sum_error = 0for row in train:outputs = forward_propagate(network, row)expected = [0 for i in range(n_outputs)]expected[row[-1]] = 1sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])backward_propagate_error(network, expected)update_weights(network, row, l_rate)print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))# Test training backprop algorithm
seed(1)
dataset = [[2.7810836,2.550537003,0],[1.465489372,2.362125076,0],[3.396561688,4.400293529,0],[1.38807019,1.850220317,0],[3.06407232,3.005305973,0],[7.627531214,2.759262235,1],[5.332441248,2.088626775,1],[6.922596716,1.77106367,1],[8.675418651,-0.242068655,1],[7.673756466,3.508563011,1]]
n_inputs = len(dataset[0]) - 1
n_outputs = len(set([row[-1] for row in dataset]))
network = initialize_network(n_inputs, 2, n_outputs)
train_network(network, dataset, 0.5, 20, n_outputs)
for layer in network:print(layer)
運行示例首先會打印每個訓練周期的平方誤差。我們可以看到這個誤差在每個周期中逐漸減少。
訓練完成后,網絡將被打印出來,顯示所學習的權重。網絡中還包括輸出和增量值,這些值可以被忽略。如果我們愿意,可以更新我們的訓練函數來刪除這些數據。
>epoch=0, lrate=0.500, error=6.350
>epoch=1, lrate=0.500, error=5.531
>epoch=2, lrate=0.500, error=5.221
>epoch=3, lrate=0.500, error=4.951
>epoch=4, lrate=0.500, error=4.519
>epoch=5, lrate=0.500, error=4.173
>epoch=6, lrate=0.500, error=3.835
>epoch=7, lrate=0.500, error=3.506
>epoch=8, lrate=0.500, error=3.192
>epoch=9, lrate=0.500, error=2.898
>epoch=10, lrate=0.500, error=2.626
>epoch=11, lrate=0.500, error=2.377
>epoch=12, lrate=0.500, error=2.153
>epoch=13, lrate=0.500, error=1.953
>epoch=14, lrate=0.500, error=1.774
>epoch=15, lrate=0.500, error=1.614
>epoch=16, lrate=0.500, error=1.472
>epoch=17, lrate=0.500, error=1.346
>epoch=18, lrate=0.500, error=1.233
>epoch=19, lrate=0.500, error=1.132
[{'weights': [-1.4688375095432327, 1.850887325439514, 1.0858178629550297], 'output': 0.029980305604426185, 'delta': 0.0059546604162323625}, {'weights': [0.37711098142462157, -0.0625909894552989, 0.2765123702642716], 'output': 0.9456229000211323, 'delta': -0.0026279652850863837}]
[{'weights': [2.515394649397849, -0.3391927502445985, -0.9671565426390275], 'output': 0.23648794202357587, 'delta': 0.04270059278364587}, {'weights': [-2.5584149848484263, 1.0036422106209202, 0.42383086467582715], 'output': 0.7790535202438367, 'delta': -0.03803132596437354}]
一旦網絡訓練完成,我們需要使用它來做出預測。
5. 預測
使用訓練好的神經網絡進行預測是足夠簡單的。
我們已經看到如何將輸入模式前向傳播以獲得輸出。這就是我們進行預測所需做的全部工作。我們可以直接將輸出值用作模式屬于每個輸出類別的概率。
將這個輸出重新轉換為一個明確的類別預測可能更有用。我們可以通過選擇具有較大概率的類別值來實現這一點。這也被稱為arg max 函數。
下面是一個名為predict()的函數,該函數實現了這個過程。它返回網絡輸出中概率最大的索引。它假設類別值已經轉換為從0開始的整數。
# Make a prediction with a network
def predict(network, row):outputs = forward_propagate(network, row)return outputs.index(max(outputs))
我們可以將上面的輸入正向傳播代碼與我們的小型編造數據集結合起來,測試使用已訓練的網絡進行預測。該示例硬編碼了上一步訓練的網絡。
完整的示例如下所示。
from math import exp# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# Make a prediction with a network
def predict(network, row):outputs = forward_propagate(network, row)return outputs.index(max(outputs))# Test making predictions with the network
dataset = [[2.7810836,2.550537003,0],[1.465489372,2.362125076,0],[3.396561688,4.400293529,0],[1.38807019,1.850220317,0],[3.06407232,3.005305973,0],[7.627531214,2.759262235,1],[5.332441248,2.088626775,1],[6.922596716,1.77106367,1],[8.675418651,-0.242068655,1],[7.673756466,3.508563011,1]]
network = [[{'weights': [-1.482313569067226, 1.8308790073202204, 1.078381922048799]}, {'weights': [0.23244990332399884, 0.3621998343835864, 0.40289821191094327]}],[{'weights': [2.5001872433501404, 0.7887233511355132, -1.1026649757805829]}, {'weights': [-2.429350576245497, 0.8357651039198697, 1.0699217181280656]}]]
for row in dataset:prediction = predict(network, row)print('Expected=%d, Got=%d' % (row[-1], prediction))
運行此示例將打印訓練數據集中每個記錄的預期輸出,以及網絡做出的 crisp預測。
這表明該網絡在這個小數據集上實現了100%的準確率。
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
6. 小麥種子數據集
本節將反向傳播算法應用于小麥種子數據集。
第一步是加載數據集,并將加載的數據轉換為我們可以在神經網絡中使用的數字。為此,我們將使用助手函數load_csv()來加載文件,str_column_to_float()將字符串數字轉換為浮點數,str_column_to_int()將類別列轉換為整數值。
輸入值的尺度不同,需要歸一化到0到1的范圍內。通常將輸入值歸一化到所選傳遞函數的范圍內是良好的實踐,本例中為輸出0到1之間值的Sigmoid函數。使用了dataset_minmax()和normalize_dataset()輔助函數來歸一化輸入值。
我們將使用k折交叉驗證評估算法,分為5折。這意味著每折有201/5=40.2或40個記錄。我們將使用輔助函數evaluate_algorithm()來使用交叉驗證評估算法,并使用accuracy_metric()來計算預測的準確性。
開發了一個名為back_propagation()的新函數,用于管理反向傳播算法的應用,首先初始化一個網絡,然后在訓練數據集上訓練它,最后使用訓練好的網絡對測試數據集進行預測。
完整的示例如下所示。
# Backprop on the Seeds Dataset
from random import seed
from random import randrange
from random import random
from csv import reader
from math import exp# Load a CSV file
def load_csv(filename):dataset = list()with open(filename, 'r') as file:csv_reader = reader(file)for row in csv_reader:if not row:continuedataset.append(row)return dataset# Convert string column to float
def str_column_to_float(dataset, column):for row in dataset:row[column] = float(row[column].strip())# Convert string column to integer
def str_column_to_int(dataset, column):class_values = [row[column] for row in dataset]unique = set(class_values)lookup = dict()for i, value in enumerate(unique):lookup[value] = ifor row in dataset:row[column] = lookup[row[column]]return lookup# Find the min and max values for each column
def dataset_minmax(dataset):minmax = list()stats = [[min(column), max(column)] for column in zip(*dataset)]return stats# Rescale dataset columns to the range 0-1
def normalize_dataset(dataset, minmax):for row in dataset:for i in range(len(row)-1):row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0])# Split a dataset into k folds
def cross_validation_split(dataset, n_folds):dataset_split = list()dataset_copy = list(dataset)fold_size = int(len(dataset) / n_folds)for i in range(n_folds):fold = list()while len(fold) < fold_size:index = randrange(len(dataset_copy))fold.append(dataset_copy.pop(index))dataset_split.append(fold)return dataset_split# Calculate accuracy percentage
def accuracy_metric(actual, predicted):correct = 0for i in range(len(actual)):if actual[i] == predicted[i]:correct += 1return correct / float(len(actual)) * 100.0# Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):folds = cross_validation_split(dataset, n_folds)scores = list()for fold in folds:train_set = list(folds)train_set.remove(fold)train_set = sum(train_set, [])test_set = list()for row in fold:row_copy = list(row)test_set.append(row_copy)row_copy[-1] = Nonepredicted = algorithm(train_set, test_set, *args)actual = [row[-1] for row in fold]accuracy = accuracy_metric(actual, predicted)scores.append(accuracy)return scores# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])# Update network weights with error
def update_weights(network, row, l_rate):for i in range(len(network)):inputs = row[:-1]if i != 0:inputs = [neuron['output'] for neuron in network[i - 1]]for neuron in network[i]:for j in range(len(inputs)):neuron['weights'][j] -= l_rate * neuron['delta'] * inputs[j]neuron['weights'][-1] -= l_rate * neuron['delta']# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):for epoch in range(n_epoch):for row in train:outputs = forward_propagate(network, row)expected = [0 for i in range(n_outputs)]expected[row[-1]] = 1backward_propagate_error(network, expected)update_weights(network, row, l_rate)# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return network# Make a prediction with a network
def predict(network, row):outputs = forward_propagate(network, row)return outputs.index(max(outputs))# Backpropagation Algorithm With Stochastic Gradient Descent
def back_propagation(train, test, l_rate, n_epoch, n_hidden):n_inputs = len(train[0]) - 1n_outputs = len(set([row[-1] for row in train]))network = initialize_network(n_inputs, n_hidden, n_outputs)train_network(network, train, l_rate, n_epoch, n_outputs)predictions = list()for row in test:prediction = predict(network, row)predictions.append(prediction)return(predictions)# Test Backprop on Seeds dataset
seed(1)
# load and prepare data
filename = 'seeds_dataset.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])-1):str_column_to_float(dataset, i)
# convert class column to integers
str_column_to_int(dataset, len(dataset[0])-1)
# normalize input variables
minmax = dataset_minmax(dataset)
normalize_dataset(dataset, minmax)
# evaluate algorithm
n_folds = 5
l_rate = 0.3
n_epoch = 500
n_hidden = 5
scores = evaluate_algorithm(dataset, back_propagation, n_folds, l_rate, n_epoch, n_hidden)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))
一個隱藏層有5個神經元,輸出層有3個神經元的網絡被構建。網絡以0.3的學習率訓練了500個時期。這些參數是通過一點試錯找到的,但你可能可以做得更好。
運行示例將打印每個折疊的平均分類準確率以及所有折疊的平均性能。
你可以看到,反向傳播和所選配置達到了約93%的平均分類準確率,這比僅略高于28%準確率的零規則算法好了很多。
Scores: [92.85714285714286, 92.85714285714286, 97.61904761904762, 92.85714285714286, 90.47619047619048]
Mean Accuracy: 93.333%
擴展
本節列出了一些您可以探索的教程擴展。
- 調整算法參數。嘗試訓練更長時間或更短時間的較大或較小的網絡。看看你是否能在seeds數據集上獲得更好的性能。
- 附加方法。嘗試不同的權重初始化技術(例如小隨機數)和不同的激活函數(例如 tanh)。
- 更多層。增加對更多隱藏層的支持,這些隱藏層的訓練方式與本教程中使用的單個隱藏層相同。
- 回歸。修改網絡,使輸出層只有一個神經元,并預測一個實值。選擇一個回歸數據集進行練習。輸出層的神經元可以使用線性傳輸函數,或者將所選數據集的輸出值縮放為0到1之間的值。
- 批量梯度下降。將訓練程序從在線更改為批量梯度下降,并且僅在每個時期的末尾更新權重。