1. 概念
測量不同特征值之間的距離來進行分類
優點:精度高、對異常值不敏感、無數據輸入假定
缺點:計算復雜度高、空間復雜度高。
適用范圍:數值型和標稱型
工作原理:
存在一個樣本數據合計,也稱作訓練樣本集,並且樣本集中每個數據都存在標簽,即我們知道樣本集中每一數據與所屬分類的對應關系。輸入沒有標簽的新數據后,將新數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本集中特征最相似(最近鄰)數據的分類標簽。一般會選擇樣本數據集中前K個最相似的數據,K通常不大於20的整數,選擇K個最相似數據中出現次數最多的分類,作為新數據的分類。
一般流程:
- 收集數據:任何方法
- 准備數據:距離計算所需要的數值,最好是結構化數據格式
- 分析數據:任何方法
- 訓練算法:不適用
- 測試算法:計算錯誤率
- 使用算法:輸入樣本數據和結構化的輸出結果,運行K近鄰算法判斷輸入數據分別屬於哪個分類,最后應用對計算出的分類執行后續的處理
2. Python 實現
1. 數據輸入
建立一個KNN.py文件
from numpy import * import operator def createDataSet(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) labels = ['A','A','B','B'] return group,labels
operator:運算符模塊
createDataSet函數用於創建simple數據集和標簽
在Console中
import KNN group,labels = KNN.createDataSet() group Out[69]: array([[ 1. , 1.1], [ 1. , 1. ], [ 0. , 0. ], [ 0. , 0.1]]) labels Out[70]: ['A', 'A', 'B', 'B']
通過KNN中的createDataSet函數創建變量group和labels
4個樣本集在坐標軸上的位置:

2. 實施KNN分類算法
實現步驟:
- 計算已知類別數據集中的點與當前點之間的距離;
- 按照距離遞增次序排序;
- 選取與當前點距離最小的k個點;
- 確定前k個點所在類別的出現頻率;
- 返回前k個點出現頻率最高的類別作為當前點的預測分類;
在KNN.py文件中創建classify0函數
1 def classify0(inX,dataSet,labels,k): 2 dataSetSize = dataSet.shape[0] 3 #距離計算 4 diffMat = tile(inX,(dataSetSize,1)) - dataSet 5 sqDiffMat = diffMat ** 2 6 sqDistances = sqDiffMat.sum(axis = 1) 7 distances = sqDistances ** 0.5 8 sortedDistIndicies = distances.argsort() 9 classCount = {} 10 for i in range(k): 11 voteIlabel = labels[sortedDistIndicies[i]] 12 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 13 sortedClassCount = sorted(classCount.iteritems(), 14 key=operator.itemgetter(1), 15 reverse=True) 16 return sortedClassCount[0][0]
參數:
- inX: 輸入樣本
- dataSet: 訓練樣本集
- labels:訓練樣本集標簽
- k: 選取k個點
代碼中使用的python方法:
- dataSet.shape():返回數據集維度(幾行幾列),這里需要返回有幾條數據(幾行) 可以用len(dataSet)替代?
- tile(inX,(dataSetSize,1)):將輸入樣本放入(並復制)到一個與數據集相同大小的矩陣
疑問:為什么不直接減?
tile(inX,(dataSetSize,1))- dataSet 與 inX - dataSet 結果一樣
sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True):排序,reverse=True降序排列
計算距離公式
勾股定律:
重新裝載KNN模塊,python提示符中使用classify0函數來預測數據
1 reload(KNN) 2 Out[101]: <module 'KNN' from 'KNN.py'> 3 4 KNN.classify0([0,0],group,labels,3) 5 Out[102]: 'B'
3. 示例:使用K近鄰算法改進約會網站的配對效果
示例說明:
樣本集中包含三個特征值:
- 每年獲得的飛行常客里程數
- 玩視頻游戲所耗時間百分比
- 每周消費的冰激凌公升數
標簽種類:
- 不喜歡的人
- 魅力一般的人
- 極具魅力的人
1. 從文本文件中解析數據
文本文件datingTestSet.txt
在KNN.py中增加文本轉換為Numpy的解析函數
1 def file2matrix(filename): 2 fr = open(filename) 3 array0lines = fr.readlines() 4 numberOfLines = len(array0lines) 5 returnMat = np.zeros((numberOfLines,3)) 6 classLabelVector = [] 7 index = 0 8 for line in array0lines: 9 line = line.strip() 10 linstFromLine = line.split('\t') 11 returnMat[index,:] = linstFromLine[0:3] 12 classLabelVector.append(int(linstFromLine[-1])) 13 index +=1 14 return returnMat,classLabelVector
參數:
- filename:文件名
代碼中使用的python方法:
- np.zeros((numberOfLines,3)) :創建以零填充的矩陣
- line.strip():去除回車字符
- line.split('\t'):以\t為分隔符分割字符
python命令提示符下輸入命令:
1 reload(KNN) 2 Out[111]: <module 'KNN' from 'KNN.pyc'> 3 4 datingDataMat,datingLabels = KNN.file2matrix('datingTestSet2.txt') 5 6 datingDataMat 7 Out[113]: 8 array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01], 9 [ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00], 10 [ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01], 11 ..., 12 [ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01], 13 [ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01], 14 [ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]]) 15 16 datingLabels[:5] 17 Out[114]: [3, 2, 1, 1, 1]
2. 分析數據:使用Matplotlib創建散點圖
1 import matplotlib 2 import matplotlib.pyplot as plt 3 fig = plt.figure() 4 ax = fig.add_subplot(111) 5 ax.scatter(datingDataMat[:,1],datingDataMat[:,2]) 6 fig

1 ax.scatter(datingDataMat[:,1],datingDataMat[:,2], 2 15.0*array(datingLabels),15.0*array(datingLabels)) 3 fig

1 ax.scatter(datingDataMat[:,0],datingDataMat[:,1], 2 15.0*array(datingLabels),15.0*array(datingLabels)) 3 fig

3. 准備數據:歸一化數值
三個特征值計算樣本距離:


每年獲得飛行常客里程數遠大於其他特征值需要轉化為0-1區間內的值
newValue = (oldValue-min)/(max-min)
在KNN.py中增加歸一化特征值函數
1 def autoNorm(dataSet): 2 minVals = dataSet.min(0) 3 maxVals = dataSet.max(0) 4 ranges = maxVals - minVals 5 normDataSet = zeros(shape(dataSet)) 6 m = dataSet.shape[0] 7 normDataSet = dataSet - tile(minVals,(m,1)) 8 normDataSet = normDataSet/tile(ranges,(m,1)) 9 return normDataSet,ranges,minVals
執行歸一化函數
1 reload(KNN) 2 Out[145]: <module 'KNN' from 'KNN.py'> 3 4 normMat,ranges,minVals = KNN.autoNorm(datingDataMat) 5 6 normMat 7 Out[147]: 8 array([[ 0.44832535, 0.39805139, 0.56233353], 9 [ 0.15873259, 0.34195467, 0.98724416], 10 [ 0.28542943, 0.06892523, 0.47449629], 11 ..., 12 [ 0.29115949, 0.50910294, 0.51079493], 13 [ 0.52711097, 0.43665451, 0.4290048 ], 14 [ 0.47940793, 0.3768091 , 0.78571804]]) 15 16 ranges 17 Out[148]: array([ 9.12730000e+04, 2.09193490e+01, 1.69436100e+00]) 18 19 minVals 20 Out[149]: array([ 0. , 0. , 0.001156])
4. 測試算法:作為完整程序驗證分類器
在KNN.py文件中添加測試函數datingClassTest
1 def datingClassTest(): 2 hoRatio = 0.1 3 datingDataMat,datingLabels = file2matrix('datingTestSet.txt') 4 normMat,ranges,minVals = autoNorm(datingDataMat) 5 m = normMat.shape[0] 6 numTestVecs = int(m * hoRatio) 7 errorCount = 0.0 8 for i in range(numTestVecs): 9 classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:], 10 datingLabels[numTestVecs:m],3) 11 print "the classifier came back with: %d, the real answer is: %d" % (classifierResult,datingLabels[i]) 12 if (classifierResult != datingLabels[i]): 13 errorCount += 1.0 14 print "the total error rate is: %f" % (errorCount/float(numTestVecs))
在python命令提示符中運行datingClassTest()
1 reload(KNN) 2 Out[153]: <module 'KNN' from 'KNN.py'> 3 4 KNN.datingClassTest() 5 the classifier came back with: 3, the real answer is: 3 6 the classifier came back with: 2, the real answer is: 2 7 the classifier came back with: 1, the real answer is: 1 8 the classifier came back with: 3, the real answer is: 1 9 the total error rate is: 0.050000
錯誤率為5%
5. 使用算法:構建完整可用系統
在KNN.py中加入classifyPerson()函數:
1 def classifyPerson(): 2 resultList = ['not at all','insmall doses','in large doses'] 3 percentTats = float(raw_input("percentage of time spent playing video games?")) 4 ffMiles = float(raw_input("frequent flier miles earned per year?")) 5 iceCream = float(raw_input("liters of ice cream consumed per year?")) 6 datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') 7 normMat , ranges, minVals = autoNorm(datingDataMat) 8 inArr = array([ffMiles,percentTats,iceCream]) 9 classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels,3) 10 print "You will probably like this person:",resultList[classifierResult - 1]
在python命令提示行執行classifyPerson函數
1 reload(KNN) 2 Out[155]: <module 'KNN' from 'KNN.py'> 3 4 KNN.classifyPerson() 5 6 percentage of time spent playing video games?10 7 8 frequent flier miles earned per year?10000 9 10 liters of ice cream consumed per year?0.5 11 You will probably like this person: insmall doses
4. 示例:手寫數字識別系統
將圖像轉換為測試向量,將32*32的二進制圖像矩陣轉換為1*1024的向量
創建函數img2vector
1 def img2vector(filename): 2 returnVect = zeros(1,1024) #創建1*1024的矩陣 3 fr = open(filename) 4 for i in range(32): 5 lineStr = fr.readline() 6 for j in range(32): 7 returnVect[0,32*i+j] = int(lineStr[j])
測試算法:使用k近鄰算法識別手寫數字
1 def handwritingClassTest(): 2 hwLabels = [] 3 trainingFileList = listdir('trainingDigits') #獲取目錄內容 4 m = len(trainingFileList) 5 trainingMat = zeros((m,1024)) 6 #獲取訓練集向量和標簽 7 for i in range(m): 8 #解析文件名 9 fileNameStr = trainingFileList[i] 10 fileStr = fileNameStr.split('.')[0] 11 classNumStr = int(fileStr.split('_')[0]) 12 hwLabels.append(classNumStr) 13 trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) 14 testFileList = listdir('testDigits') 15 errorCount = 0 16 mTest = len(testFileList) 17 #獲取測試集向量進行測試並將測試結果與正式結果比對 18 for i in range(mTest): 19 fileNameStr = testFileList[i] 20 fileStr = fileNameStr.split('.')[0] 21 classNumStr = int(fileStr.split('_')[0]) 22 vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) 23 classifierResult = KNN.classify0(vectorUnderTest,trainingMat,hwLabels,3) 24 print "the classifier came back with %d,the real answer is:%d" % (classifierResult,classNumStr) 25 if classifierResult != classNumStr: 26 errorCount += 1.0 27 print '\n the total number of errors is:%d' % errorCount 28 print '\n the total error rate is:%f' % (errorCount/float(mTest))
在python命令行運行handwritingClassTest()函數
handwritingClassTest() the classifier came back with 9,the real answer is:9 the classifier came back with 9,the real answer is:9 the classifier came back with 9,the real answer is:9 the total number of errors is:11 the total error rate is:0.011628
錯誤率1.1628%
