使用kNN算法實現手寫字體的簡單識別

看完一節《機器學習實戰》,算是踏入ML的大門了吧!這裏就詳細講一下一個demo:使用kNN算法實現手寫字體的簡單識別

kNN

 先簡單介紹一下kNN,就是所謂的K-近鄰算法:

  【作用原理】:存在一個樣本數據集合、每個樣本數據都存在標籤。輸入沒有標籤的新數據後,將新數據的每個特徵與樣本集數據的對應特徵進行比較,然後算法提取樣本集中最相似的分類標籤。一般說來,我們只選擇樣本數據集中前k個最相似的數據,最後,選擇這k個相似數據中出現次數最多的分類,作爲新數據的分類。

  通俗的說,舉例說明:有一羣明確國籍的人(樣本集合,比如1000個):中國人、韓國人、日本人、美國人、埃及人,現在有一個不知國籍的人,想要通過比較特徵來猜測他的國籍(當然,特徵具有可比較性和有效性),通過比較特徵,得出特徵與該人最相近的樣本集中的9個人(k),其中,1個是韓國人、2個是日本人,6個是中國人,那麼這個人是中國人的可能性就很大。

  這就是kNN的基本思想。

手寫體識別數據準備

  kNN輸入需要特徵矩陣,一般是固定大小的二值圖像,這裏我們使用書上提供的數據集:這個數據集使用32X32文本文件存儲數值圖像。例如下圖的'9'

 

  這裏每個文本文件存儲一個手寫體數據,並且文件名寫成"number_num.txt"這樣的形式,例如9_1.txt,方便後期提取標籤

  我們將樣本數據放在trainingDigits文件夾中,測試樣例存儲在testDigits文件夾中

  我們在處理時將每個手寫體數據(32x32)轉換成1X1024維的向量。

  另外,kNN涉及到相似度計算。這裏我們使用的是歐氏距離,由於手寫體數據向量是規則的二值數據,因此不需要進行歸一化。

手寫體識別算法運行流程

  (一)讀取手寫體txt文件,轉化爲1X1024向量

    我們創建一個kNN.py,添加模塊img2vector

 

1 #識別手寫字體模塊-圖像轉向量32x32 to 1x1024
2 def img2vector(filename):
3     returnVect = zeros((1,1024))
4     fr = open(filename)
5     for i in range(32):
6         lineStr = fr.readline()
7         for j in range(32):
8             returnVect[0,32*i+j] = int(lineStr[j])
9     return returnVect

 

    我們的樣本數據和測試數據都需要用到該函數

  (二)比較測試數據和樣本數據集的距離,返回k近鄰中最相似的標籤

    在kNN.py中添加classify0模塊,附上代碼註釋  

 

 1 #---------------------------------------------
 2 #分類模塊
 3 #@params
 4 #   inX:輸入向量、手寫體識別的測試向量
 5 #    dataSet:訓練集樣本、手寫體識別的訓練集向量
 6 #    labels:訓練集對應的標籤向量
 7 #    k:最近鄰居數目、本實驗爲3
 8 #---------------------------------------------
 9 def classifiy0(inX, dataSet, labels, k):
10     dataSetSize = dataSet.shape[0]     #手寫體樣本集容量
11     #(以下三行)距離計算
12     diffMat = tile(inX, (dataSetSize,1)) - dataSet
13     sqDiffMat = diffMat**2    
14     sqDistances = sqDiffMat.sum(axis=1)
15     distances = sqDistances**0.5   #歐氏距離開平方
16     sortedDistIndicies = distances.argsort()  #距離排序的索引排序
17     classCount = {}    
18     #(以下兩行)選擇距離最小的k個點
19     for i in range(k):
20         voteIlabel = labels[sortedDistIndicies[i]]
21         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
22     sortedClassCount = sorted(classCount.items(),
23     #排序
24     key = operator.itemgetter(1), reverse = True)
25     return sortedClassCount[0][0]            

 

    注意,這裏使用了numpy的接口,在kNN.py的開頭要加上:from numpy import* 

  (三)比較標籤與測試結果,計算正確率

    同樣,在kNN.py中添加handwritingClassTest模塊,綜合以上的兩個模塊,獲得識別正確率

 

 1 #手寫識別的測試代碼
 2 def handwritingClassTest():
 3     hwLabels = []
 4     trainingFileList = listdir(path='trainingDigits')  #獲取目錄內容
 5     m = len(trainingFileList)
 6     trainingMat = zeros((m,1024))
 7     for i in range(m):
 8         #一下三行,從文件名解析分類數字
 9         fileNameStr = trainingFileList[i]
10         fileStr = fileNameStr.split('.')[0]
11         classNumStr = int(fileStr.split('_')[0])
12 
13         hwLabels.append(classNumStr)
14         trainingMat[i,:] = img2vector('trainingDigits/%s'%fileNameStr)
15     testFileList = listdir(path='testDigits')
16     
17     errorCount = 0.0  #錯誤個數計數器
18     mTest = len(testFileList)
19 
20     #從測試數據中提取數據
21     for i in range(mTest):
22         fileNameStr = testFileList[i]
23         fileStr = fileNameStr.split('.')[0]
24         
25         classNumStr = int(fileStr.split('_')[0])
26         vectorUnderTest = img2vector('testDigits/%s'% fileNameStr)
27         classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
28         
29         print("the classifier came back with:%d,the real answer is:%d"%(classifierResult,classNumStr))
30         if(classifierResult != classNumStr):
31             errorCount += 1.0
32    #輸出結果
33     print("\nthe total number of errors is:%d"%errorCount)
34     print("\nthe total error rate is: %f"%(errorCount/float(mTest)))

    注意,這裏使用到了os模塊listdir,在kNN開頭加入:from numpy import listdir

  測試結果如下:

  錯誤率爲1.16%,可以看到,識別效果挺不錯。

後記

  通過實驗我們可以看到,使用kNN要將訓練樣本一次性加載入內存、如果訓練集的規模很大,勢必對機器有很大的要求。另外,kNN不需要訓練算法、對異常值不敏感、在後期使用的時候要慎重選擇吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章