利用Python實現kNN算法


  鄰近算法(k-NearestNeighbor) 是機器學習中的一種分類(classification)算法,也是機器學習中最簡單的算法之一了。雖然很簡單,但在解決特定問題時卻能發揮很好的效果。因此,學習kNN算法是機器學習入門的一個很好的途徑。

  kNN算法的思想非常的朴素,它選取k個離測試點最近的樣本點,輸出在這k個樣本點中數量最多的標簽(label)。我們假設每一個樣本有m個特征值(property),則一個樣本的可以用一個m維向量表示: X =( x1,x2,... x),  同樣地,測試點的特征值也可表示成:Y =( y1,y2,... y)。那我們怎么定義這兩者之間的“距離”呢?

  在二維空間中,有:d2 = ( x- y)2 + ( x- y),  在三維空間中,兩點的距離被定義為:d2 = ( x- y)2 + ( x- y)2  + ( x3 - y)我們可以據此推廣到m維空間中,定義m維空間的距離:d2 = ( x- y)2 + ( x- y)2  + ...... + ( xm - y)2 。要實現kNN算法,我們只需要計算出每一個樣本點與測試點的距離,選取距離最近的k個樣本,獲取他們的標簽(label) ,然后找出k個樣本中數量最多的標簽,返回該標簽。

  在開始實現算法之前,我們要考慮一個問題,不同特征的特征值范圍可能有很大的差別,例如,我們要分辨一個人的性別,一個女生的身高是1.70m,體重是60kg,一個男生的身高是1.80m,體重是70kg,而一個未知性別的人的身高是1.81m, 體重是64kg,這個人與女生數據點的“距離”的平方 d2 = ( 1.70 - 1.81 )2 + ( 60 - 64 )= 0.0121 + 16.0 = 16.0121,而與男生數據點的“距離”的平方d2 = ( 1.80 - 1.81 )2 + ( 70 - 64 )= 0.0001 + 36.0 = 36.0001 。可見,在這種情況下,身高差的平方相對於體重差的平方基本可以忽略不計,但是身高對於辨別性別來說是十分重要的。為了解決這個問題,就需要將數據標准化(normalize),把每一個特征值除以該特征的范圍,保證標准化后每一個特征值都在0~1之間。我們寫一個normData函數來執行標准化數據集的工作:

1 def normData(dataSet): 2     maxVals = dataSet.max(axis=0) 3     minVals = dataSet.min(axis=0) 4     ranges = maxVals - minVals 5     retData = (dataSet - minVals) / ranges 6     return retData, ranges, minVals

 

然后開始實現kNN算法:

 1 def kNN(dataSet, labels, testData, k):
 2     distSquareMat = (dataSet - testData) ** 2 # 計算差值的平方
 3     distSquareSums = distSquareMat.sum(axis=1) # 求每一行的差值平方和
 4     distances = distSquareSums ** 0.5 # 開根號,得出每個樣本到測試點的距離
 5     sortedIndices = distances.argsort() # 排序,得到排序后的下標
 6     indices = sortedIndices[:k] # 取最小的k個
 7     labelCount = {} # 存儲每個label的出現次數
 8     for i in indices:
 9         label = labels[i]
10         labelCount[label] = labelCount.get(label, 0) + 1 # 次數加一
11     sortedCount = sorted(labelCount.items(), key=opt.itemgetter(1), reverse=True) 
12     # 對label出現的次數從大到小進行排序
13     return sortedCount[0][0] # 返回出現次數最大的label

 

  注意,在testData作為參數傳入kNN函數之前,需要經過標准化。

  我們用幾個小數據驗證一下kNN函數是否能正常工作:

1 if __name__ == "__main__": 2     dataSet = np.array([[2, 3], [6, 8]]) 3     normDataSet, ranges, minVals = normData(dataSet) 4     labels = ['a', 'b'] 5     testData = np.array([3.9, 5.5]) 6     normTestData = (testData - minVals) / ranges 7     result = kNN(normDataSet, labels, normTestData, 1) 8     print(result)

  結果輸出 a ,與預期結果一致。

完整代碼:

 1 import numpy as np  2 from math import sqrt  3 import operator as opt  4 
 5 def normData(dataSet):  6     maxVals = dataSet.max(axis=0)  7     minVals = dataSet.min(axis=0)  8     ranges = maxVals - minVals  9     retData = (dataSet - minVals) / ranges 10     return retData, ranges, minVals 11 
12 
13 def kNN(dataSet, labels, testData, k): 14     distSquareMat = (dataSet - testData) ** 2 # 計算差值的平方
15     distSquareSums = distSquareMat.sum(axis=1) # 求每一行的差值平方和
16     distances = distSquareSums ** 0.5 # 開根號,得出每個樣本到測試點的距離
17     sortedIndices = distances.argsort() # 排序,得到排序后的下標
18     indices = sortedIndices[:k] # 取最小的k個
19     labelCount = {} # 存儲每個label的出現次數
20     for i in indices: 21         label = labels[i] 22         labelCount[label] = labelCount.get(label, 0) + 1 # 次數加一
23     sortedCount = sorted(labelCount.items(), key=opt.itemgetter(1), reverse=True) # 對label出現的次數從大到小進行排序
24     return sortedCount[0][0] # 返回出現次數最大的label
25 
26 
27 
28 if __name__ == "__main__": 29     dataSet = np.array([[2, 3], [6, 8]]) 30     normDataSet, ranges, minVals = normData(dataSet) 31     labels = ['a', 'b'] 32     testData = np.array([3.9, 5.5]) 33     normTestData = (testData - minVals) / ranges 34     result = kNN(normDataSet, labels, normTestData, 1) 35     print(result)
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM