本科生畢業設計想出來一個題目,用CNN來手寫體識別。出了題目之後,發現對NN的知識都忘光了,現在重新手寫NN來再學一次。
什麼是神經網絡(Neural Network)
計算機處理數字計算十分簡單,然而處理圖像數據卻十分困難。通過利用生物學的設計構建一個使計算機能處理高維數據的模型,從而產生了神經網絡這樣的技術。
假設有這樣一臺機器,接受一個問題進行思考,然後得出答案。對於人類而言就是這樣一個過程,對於計算機來說。這樣的過程變成了:輸入->計算->輸出。但是從流程上看起來他們是相似的。
{% asset_img 1.png 預測機 %}
{% asset_img 2.png 神經元 %}
生物大腦的基本單元是神經元,雖然有各種形式,但是他們的本質就是將電信號從一端傳遞到另一端,從一個神經元傳遞到另一個神經元。然而,每個神經元的輸入信號可能是單個,也可能是多個。有時候單個信號的輸入並不能很好的解決某些問題(例如異或情況的分類)。
{% asset_img 3.png 多個信號的輸入 %}
{% asset_img 4.png 異或XOR的分類 %}
但是與傳統函數的輸入輸出不同,神經元對於微小的噪聲信號直接忽略,只有在輸入信號高於**閾值(threshold)纔會產生輸出。因此多個信號輸入之後進行累加求和,當求和總量達到某個閾值後,神經元被激活,產生輸出結果。這就是神經元。
{% asset_img 5.png 激活神經元 %}
關於閾值的判斷,需要一個名爲激活函數(activtion function)**的模型來對信號進行處理。激活函數的種類十分多,普遍比較多的是Sigmoid函數。
這個函數相比較於階躍函數比較平滑。
通過多個神經元之間的連接,構造一種名爲神經網絡的模型方法,對於計算機識別高維度的數據而言具有良好的適用性。
{% asset_img 6.png 生物神經網絡 %}
神經網絡的工作原理
{% asset_img 7.png 神經網絡模型 %}
傳統的神經網絡分爲三層,輸入層(input layer),隱藏層(hidden layer),輸出層(output layer)。隱藏層可以有多層網絡構建,每層之間依靠連接權值進行連接。
正向傳值
現在開始考慮正向傳值計算,即從輸入->輸入層->隱藏層->輸出層的過程。從單個神經元開始分析,從輸入層開始,當輸入值進入輸入層時,輸入值就只是單純賦值給輸入層節點,即輸入值=輸入單元,無需進行計算。
從輸入層進入隱藏層開始,引入了權值的概念。即輸入*權值的過程。
{% asset_img 8.png 隱藏層神經元 %}
再將得到的值經過激活函數進行調節,這樣就能得到隱藏層每個節點的輸入結果。將得到的結果放入輸出層再次進行計算,這樣子我們就得到了一個完整的正向傳播過程。
爲了計算方便,引入矩陣來進行計算。令輸入層數據爲,輸入層權值矩陣爲。則進入隱藏層的和值爲:
再將這個和值通過對應的激活函數進行激活得到隱藏層的輸出,在這裏我們使用Sigmoid函數作爲激活函數(此時計算出來的爲值向量,長度爲隱藏層神經元的個數),輸出值爲:
然後將隱藏層輸出值放入到輸出層重複以上步驟輸出,得到輸出矩陣。
反向傳播(BackPropagation)
單次的正向傳播得出的結果和實際結果相差可能會很大,現在需要做的就是使用這個誤差來調整神經網絡本身,進而改進輸出值。
誤差值的修正,是通過權值來進行實現的。當我們得到正向計算矩陣之後,可以求得輸出誤差,通過來自多個節點的權重進行反饋傳播,這裏還是要用到矩陣的計算,首先將隱藏輸出係數矩陣進行轉置,然後和輸出誤差點乘,公式如下:
使用這樣的方式是將誤差分割,按照權值的比例進行分配給不同的神經元。雖然也有其他的分配方式(如均勻分配),但是按權分配是比較合理的做法。
{% asset_img 9.png 誤差反饋 %}
更新權重
雖然得到了每個神經元的誤差值,但是還是沒有解決如何更新權值的問題。在這裏我們使用最簡明且有效的方法:梯度下降(Gradient Descent)。關於梯度下降的過程,在手寫線性迴歸的實現那章當中我有寫,這裏就不再描述了。
我們的目標是求得的極小值。這裏使用鏈式法則並將每個項展開。
sigmoid函數求導過程十分複雜,這裏不贅述。將上式化簡得:
這裏將公式化爲了輸入-隱藏層的下標,隱藏-輸出層權值更新同理。
{% asset_img 10.png 權重更新表示 %}
得到了修正值之後,設定學習率,然後得到更新權值如下:
雖然一次更新的量十分微小,但是訓練集量足夠預測效果將十分優秀。
以上基本就是BP神經網絡的工作原理,接下來我們使用python手寫一個BP神經網絡,並使用mnist手寫體訓練集來測試效果。
編碼實現
class NeuralN:
def __init__(self,input_nodes,hidden_nodes,output_nodes,learning_rate):
self.inodes = input_nodes
self.hnodes = hidden_nodes
self.onodes = output_nodes
self.lr = learning_rate
self.wih = np.random.normal(0.0,pow(self.hnodes,-0.5),(self.hnodes,self.inodes))
self.who = np.random.normal(0.0,pow(self.onodes,-0.5),(self.onodes,self.hnodes))
self.activtion_fun = lambda x: special.expit(x)
def train(self,inputs_list,targets_list):
inputs = np.array(inputs_list, ndmin=2).T
targets = np.array(targets_list,ndmin=2).T
hidden_inputs = np.dot(self.wih,inputs)
hidden_outputs = self.activtion_fun(hidden_inputs)
# print(hidden_outputs.shape)
final_inputs = np.dot(self.who,hidden_outputs)
final_outputs = self.activtion_fun(final_inputs)
outputs_error = targets - final_outputs
hidden_error = np.dot(self.who.T,outputs_error)
# print(hidden_error.shape)
print((hidden_error * hidden_outputs * (1 - hidden_outputs)).shape)
# print('+++++++++++++++++')
print(np.transpose(inputs).shape)
print('+++++++++++++++++')
# print(np.dot((outputs_error * final_outputs * (1 - final_outputs)),np.transpose(hidden_outputs)))
self.who += self.lr * np.dot((outputs_error * final_outputs * (1.0 - final_outputs)), \
np.transpose(hidden_outputs))
self.wih += self.lr * np.dot((hidden_error * hidden_outputs * (1.0 - hidden_outputs)), \
np.transpose(inputs))
pass
def query(self,inputs_list):
inputs = np.array(inputs_list, ndmin=2).T
hidden_inputs = np.dot(self.wih,inputs)
hidden_outputs = self.activtion_fun(hidden_inputs)
final_inputs = np.dot(self.who,hidden_outputs)
final_outputs = self.activtion_fun(final_inputs)
return final_outputs
這裏是BP神經網絡的基本框架。讀取的數據是手寫數據集,每個圖像都是28x28的矩陣,訓練集包含了70000個手寫體數據。這裏的不能使用有序的數據進行訓練,不然就會取值趨於最後一個特定值。(之前使用了sklearn自帶的數據集,調了一下午QAQ)下面我們開始加載數據,調用框架。
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.3
n = NeuralN(input_nodes,hidden_nodes,output_nodes,learning_rate)
training_data_file = open("./mnist_train.csv",'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
for record in training_data_list:
all_values = record.split(',')
inputs = (np.asfarray(all_values[1:])/255*0.99)+0.01
targets = np.zeros(output_nodes) + 0.01
targets[int(all_values[0])] = 0.99
n.train(inputs,targets)
# train_list = X[:10000,:]
# target_list = Y[:10000]
# i = 0
# for record in train_list:
# # print(record.shape)
# inputs_list = (np.asfarray(record[:]/255*0.99)+0.01)
# # print(inputs_list.shape)
# targets = np.zeros(output_nodes) + 0.01
# print(target_list[i])
# targets[int(target_list[i])] = 0.99
# i += 1
# print(i)
# print(targets)
# n.train(inputs_list,targets)
# print()
訓練完成之後我們開始預測。讀取測試集,使用計分表進行積分,輸出正確率。
test_data_file = open("./mnist_test.csv",'r')
test_data_list = test_data_file.readlines()
test_data_file.close()
scorecard =[]
for record in test_data_list:
all_values = record.split(',')
correct_label = int(all_values[0])
print(correct_label,'correct label')
inputs = (np.asfarray(all_values[1:])/255*0.99)+0.01
outputs = n.query(inputs)
label = np.argmax(outputs)
print(label,'net answer')
if label == correct_label:
scorecard.append(1)
else:
scorecard.append(0)
scorecard_arr = np.array(scorecard)
print(scorecard_arr.sum()/scorecard_arr.size)
最後正確率在93左右。
總結
整個手寫基本參考《python神經網絡編程》。這本書基本上詳細講明瞭BP神經網絡和python基本開發方法,這篇博客是在這本書的基礎上加上自身的學習經驗所作的一個簡單總結。因爲時間有限,書中很多內容都沒有好好總結(公式推導,預測問題,矩陣計算,權重初始隨機等)。還有很多可以優化的地方,如學習率優化,隱藏層節點數優化等等。在這之後,卷積神經網絡(Convolutional Neural Networks, CNN)是對於BP神經網絡而言,在手寫體識別具有更好的效果。畢業設計的題目也是使用CNN來進行手寫體識別,所以說下一步就開始手寫CNN試試。