在上一篇博文中,我們對KNN算法思想及流程有了初步的了解,KNN是采用測量不同特征值之間的距離方法進行分類,也就是說對於每個樣本數據,需要和訓練集中的所有數據進行歐氏距離計算。這里簡述KNN算法的特點:
優點:精度高,對異常值不敏感,無數據輸入假定
缺點:計算復雜度高,空間復雜度高
適用數據范圍:數值型和標稱型(具有有窮多個不同值,值之間無序)
knn算法代碼:
#-*- coding: utf-8 -*- from numpy import * import operator
import time
from os import listdir
def classify(inputPoint,dataSet,labels,k): dataSetSize = dataSet.shape[0] #已知分類的數據集(訓練集)的行數 #先tile函數將輸入點拓展成與訓練集相同維數的矩陣,再計算歐氏距離 diffMat = tile(inputPoint,(dataSetSize,1))-dataSet #樣本與訓練集的差值矩陣 sqDiffMat = diffMat ** 2 #差值矩陣平方 sqDistances = sqDiffMat.sum(axis=1) #計算每一行上元素的和 distances = sqDistances ** 0.5 #開方得到歐拉距離矩陣 sortedDistIndicies = distances.argsort() #按distances中元素進行升序排序后得到的對應下標的列表 #選擇距離最小的k個點 classCount = {} for i in range(k): voteIlabel = labels[ sortedDistIndicies[i] ] classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 #按classCount字典的第2個元素(即類別出現的次數)從大到小排序 sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True) return sortedClassCount[0][0]
下面介紹如何使用knn算法對手寫識別數據進行分類,這里構造的分類系統只能識別數字0到9,數字經圖形處理軟件處理成具有相同的色彩和大小,寬高為32x32像素,為了便於處理,已將圖像轉換為文本格式,其效果圖如下:

數據集可在這里下載,解壓后有兩個目錄,其中目錄trainingDigits中包含了1934個例子,命名規則如 9_45.txt,表示該文件的分類是9,是數字9的第45個實例,每個數字大概有200個實例。testDigits目錄中包含946個例子。使用trainingDigits中的數據作為訓練集,使用testDigits中的數據作為測試集測試分類的效果。兩組數據沒有重疊。
算法應用步驟如下:
1. 數據准備:數字圖像文本向量化,這里將32x32的二進制圖像文本矩陣轉換成1x1024的向量。循環讀出文件的前32行,存儲在向量中。
#文本向量化 32x32 -> 1x1024
def img2vector(filename): returnVect = [] fr = open(filename) for i in range(32): lineStr = fr.readline() for j in range(32): returnVect.append(int(lineStr[j])) return returnVect
2. 構建訓練數據集:利用目錄trainingDigits中的文本數據構建訓練集向量,以及對應的分類向量
#從文件名中解析分類數字 def classnumCut(fileName): fileStr = fileName.split('.')[0] classNumStr = int(fileStr.split('_')[0]) return classNumStr #構建訓練集數據向量,及對應分類標簽向量 def trainingDataSet(): hwLabels = [] trainingFileList = listdir('trainingDigits') #獲取目錄內容 m = len(trainingFileList) trainingMat = zeros((m,1024)) #m維向量的訓練集 for i in range(m): fileNameStr = trainingFileList[i] hwLabels.append(classnumCut(fileNameStr)) trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) return hwLabels,trainingMat
3. 測試集數據測試:通過測試testDigits目錄下的樣本,來計算算法的准確率。
#測試函數 def handwritingTest(): hwLabels,trainingMat = trainingDataSet() #構建訓練集 testFileList = listdir('testDigits') #獲取測試集 errorCount = 0.0 #錯誤數 mTest = len(testFileList) #測試集總樣本數 t1 = time.time() for i in range(mTest): fileNameStr = testFileList[i] classNumStr = classnumCut(fileNameStr) vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) #調用knn算法進行測試 classifierResult = classify(vectorUnderTest, trainingMat, hwLabels, 3) print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr) if (classifierResult != classNumStr): errorCount += 1.0 print "\nthe total number of tests is: %d" % mTest #輸出測試總樣本數 print "the total number of errors is: %d" % errorCount #輸出測試錯誤樣本數 print "the total error rate is: %f" % (errorCount/float(mTest)) #輸出錯誤率 t2 = time.time() print "Cost time: %.2fmin, %.4fs."%((t2-t1)//60,(t2-t1)%60) #測試耗時 if __name__ == "__main__": handwritingTest()
運行結果如下:

利用knn算法識別手寫數字數據集,錯誤率為1.6%,算法的准確率還算可觀。也可以通過改變變量k的值,觀察錯誤率的變化,關於k值的選擇,一般取一個比較小的數值,例如采用交叉驗證法(簡單來說,就是一部分樣本做訓練集,一部分做測試集)來選擇最優的K值。
通過運行以上代碼,我們會發現knn算法的執行效率並不高,因為算法需要為每個測試向量計算約2000次歐氏距離,每個距離計算包括1024個維度浮點運算,全部樣本要執行900多次,可見算法實際耗時長,另外,knn算法必須保存全部數據集,每次需為測試向量准備2MB的存儲空間(2個1024x1024矩陣的空間)。所以如何優化算法,減少存儲空間和計算時間的開銷,需要我們進一步深入學習。
參考資料:
《機器學習實戰》
