神經網絡實現手寫識別
任務介紹
手寫數字識別是一個多分類問題,共有10個分類,每個手寫數字圖像的類別標簽是0~9中的其中一個數。例如下面這三張圖片的標簽分別是0,1,2。

任務:利用sklearn來訓練一個簡單的全連接神經網絡,即多層感知機(Multilayer perceptron,MLP)用於識別數據集DBRHD的手寫數字。
MLP的輸入
- DBRHD數據集的每個圖片是一個由0或1組成的32 X 32的文本矩陣。
- 多層感知機的輸入為圖片矩陣展開的1 X 1024個神經元。
由於數據集的標簽數字只能作為類別判別而不具有數值大小作用,即類別之間並無優先高低之分。
MLP的輸出
MLP輸出:“one-hot vectors“
- 一個one-hot向量除了某一位的數字是1以外其余各維度數字都是0。
- 圖片標簽將表示成一個只有在第n維度(從0開始)數字為1的10維向量。比如,標簽0將表示成[1,0,0,0,0,0,0,0,0,0]。即,MLP輸出層具有10個神經元。
MLP結構
-
MLP的輸入與輸出層,中間隱藏層的層數和神經元的個數設置都將影響該MLP模型的准確率。
-
在本實例中,我們只設置一層隱藏層,在后續實驗中比較該隱藏層神經元個數為50、100、200時的MLP效果。

MLP手寫識別實例構建
實現步驟:
- 步驟1:建立工程並導人sklearn包
- 步驟2:加載訓練數據
- 步驟3:訓練神經網絡
- 步驟4:測試集評價
具體步驟
步驟1:建立工程並導入sklean包
(1)創建MLP.py文件
(2)在MLP.py文件中導入sklearn相關包
步驟2:加載訓練數據
(1)定義img2vector函數,將加載的32*32的圖片矩陣展開成一列向量。
(2)定義加載訓練數據的函數readDataSet,並將樣本標簽轉化為one-hot向量。
(3)調用readDataSet和img2vector函數加載數據,將訓練的圖片存放在train_dataSet中,對應的標簽則存在train_hwLabels中。
步驟3:訓練神經網絡
(1)構建神經網絡:設置網絡的隱藏層數、各隱藏層神經元個數、激活函數、學習率、優化方法、最大迭代次數。
- 設置含100個神經元的隱藏層。
- hidden layer sizes存放的是一個元組,表示第i層隱藏層里神經元的個數。
- 使用logistic激活函數和adam優化方法,並令初始學習率為0.0001,迭代2000次。
(2)構建神經網絡:設置網絡的隱藏層數、各隱藏層神經元個數、激活函數、學習率、優化方法、最大迭代次數。
(3)使用訓練數據訓練構建好的神經網絡。
- fit函數能夠根據訓練集及對應標簽集自動設置多層感知機的輸入與輸出層的神經元個數。
- 例:train_dataSet為n X 1024的矩陣,train hwLabels為n X 10的矩陣,則fit函數將MLP的輸入層神經元個數設為1024,輸出層神經元個數為10。
步驟4:測試集評價
(1)加載測試集。
(2)使用訓練好的MLP對測試集進行預測,並計算錯誤率。
具體代碼
import numpy as np # 導入numpy工具包
from os import listdir # 使用listdir模塊,用於訪問本地文件
from sklearn.neural_network import MLPClassifier
def img2vector(fileName):
""" 將加載的32*32的圖片矩陣展開成一列向量"""
retMat = np.zeros([1024], int) # 定義返回的矩陣,大小為1*1024
fr = open(fileName) # 打開包含32*32大小的數字文件
lines = fr.readlines() # 讀取文件的所有行
for i in range(32): # 遍歷文件所有行
for j in range(32): # 並將01數字存放在retMat中
retMat[i * 32 + j] = lines[i][j]
return retMat
def readDataSet(path):
"""加載訓練數據,並將樣本標簽轉化為one-hot向量"""
fileList = listdir(path) # 獲取文件夾下的所有文件
numFiles = len(fileList) # 統計需要讀取的文件的數目
dataSet = np.zeros([numFiles, 1024], int) # 用於存放所有的數字文件
hwLabels = np.zeros([numFiles, 10]) # 用於存放對應的one-hot標簽
for i in range(numFiles): # 遍歷所有的文件
filePath = fileList[i] # 獲取文件名稱/路徑
digit = int(filePath.split('_')[0]) # 通過文件名獲取標簽,注意類型轉換
hwLabels[i][digit] = 1.0 # 將對應的one-hot標簽置1
dataSet[i] = img2vector(path + '/' + filePath) # 讀取文件內容
return dataSet, hwLabels
# 讀取訓練集
train_dataSet, train_hwLabels = readDataSet('digits/trainingDigits')
'''
構建神經網絡:設置網絡的隱藏層數、各隱藏層神經元個數、激活函數、學習率、優化方法、最大迭代次數。
設置含100個神經元的隱藏層,hidden_layer_sizes 存放的是一個元組,表示第i層隱藏層里神經元的個數
使用logistic激活函數和adam優化方法,並令初始學習率為0.0001,
后期改動數值,以及使用logistic激活函數和sgd優化方法
'''
clf = MLPClassifier(hidden_layer_sizes=(100,),
activation='logistic', solver='adam',
learning_rate_init=0.0001, max_iter=2000)
print(clf)
'''
fit函數能夠根據訓練集及對應標簽集自動設置多層感知機的輸入與輸出層的神經元個數
例如train_dataSet為n*1024的矩陣,train_hwLabels為n*10的矩陣,則fit函數將MLP的輸入層神經元個數設為1024,輸出層神經元個數為10
'''
clf.fit(train_dataSet, train_hwLabels)
# 讀取測試集
dataSet, hwLabels = readDataSet('digits/testDigits')
res = clf.predict(dataSet) # 對測試集進行預測
error_num = 0 # 統計預測錯誤的數目
num = len(dataSet) # 測試集的數目
for i in range(num): # 遍歷預測結果
# 比較長度為10的數組,返回包含01的數組,0為不同,1為相同
# 若預測結果與真實結果相同,則10個數字全為1,否則不全為1
if np.sum(res[i] == hwLabels[i]) < 10:
error_num += 1
print("Total num:", num, " Wrong num:", \
error_num, " TureRate:", 1-(error_num / float(num)))
實驗效果
實驗輸出結果:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=2000)
Total num: 946 Wrong num: 42 TureRate: 0.9556025369978858
以下是改動各個參數,各隱藏層神經元個數,最大迭代次數,學習率的實驗結果:
隱藏層神經元個數影響
運行隱藏層神經元個數為50、100、200的多層感知機,對比實驗效果。
運行隱藏層神經元個數為50:
MLPClassifier(activation='logistic', hidden_layer_sizes=(50,),
learning_rate_init=0.0001, max_iter=2000)
Total num: 946 Wrong num: 43 TureRate: 0.9545454545454546
運行隱藏層神經元個數為100:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=2000)
Total num: 946 Wrong num: 42 TureRate: 0.9556025369978858
運行隱藏層神經元個數為200:
MLPClassifier(activation='logistic', hidden_layer_sizes=(200,),
learning_rate_init=0.0001, max_iter=2000)
Total num: 946 Wrong num: 37 TureRate: 0.9608879492600423
- 隨着隱藏層神經元個數的增加,MLP的正確率持上升趨勢。
- 大量的隱藏層神經元帶來的計算負擔與對結果的提升並不對等,因此,如何選取合適的隱藏神經元個數是一個值得探討的問題。
迭代次數影響分析:
設隱藏層神經元個數為100,初始學習率為0.0001,最大迭代次數分別為500、1000、1500、2000。
最大迭代次數分別為500:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=500)
Total num: 946 Wrong num: 54 TureRate: 0.9429175475687104
最大迭代次數分別為1000:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=1000)
Total num: 946 Wrong num: 40 TureRate: 0.9577167019027484
最大迭代次數分別為1500:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=1500)
Total num: 946 Wrong num: 41 TureRate: 0.9566596194503171
最大迭代次數分別為2000:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=2000)
Total num: 946 Wrong num: 33 TureRate: 0.9651162790697675
- 過小的迭代次數可能使得MLP早停,造成較低的正確率。
- 當最大迭代次數>1000時,正確率基本保持不變,這說明MLP在第1000迭代時已收斂,剩余的迭代次數不再進行。
- —般設置較大的最大迭代次數來保證多層感知機能夠收斂,達到較高的正確率。
學習率影響分析:
改用隨機梯度下降優化算法即將MLPclassifer的參數( solver='sgd',),設隱藏層神經元個數為100,最大迭代次數為2000,學習率分別為:0.1、0.01、0.001、0.0001。
學習率為0.1:
MLPClassifier(activation='logistic', learning_rate_init=0.1, max_iter=2000,solver='sgd')
Total num: 946 Wrong num: 33 TureRate: 0.9651162790697675
學習率為0.01:
MLPClassifier(activation='logistic', learning_rate_init=0.01, max_iter=2000,solver='sgd')
Total num: 946 Wrong num: 39 TureRate: 0.9587737843551797
學習率為0.001:
MLPClassifier(activation='logistic', max_iter=2000, solver='sgd')
Total num: 946 Wrong num: 47 TureRate: 0.9503171247357294
學習率為0.0001:
MLPClassifier(activation='logistic', learning_rate_init=0.0001, max_iter=2000,solver='sgd')
Total num: 946 Wrong num: 242 TureRate: 0.7441860465116279
結論:較小的學習率帶來了更低的正確率,這是因為較小學習率無法在2000次迭代內完成收斂,而步長較大的學習率使得MLP在2000次迭代內快速收斂到最優解。因此,較小的學習率一般要配備較大的迭代次數以保證其收斂。
最后的思考
這次的代碼比較簡單,就是需要花時間理解。
這次的程序需要導入數據集digits.rar放在文件目錄下。
在看懂了代碼之后,自己修改運行了一下,發現得到的數據和視頻教學里得到的數據還是有差距的,絕知此事要躬行啊。
這兩天在轉團關系,累累的。
