1. 程式人生 > >K最近鄰演算法(KNN)

K最近鄰演算法(KNN)

       K最近鄰 (k-Nearest Neighbors,KNN) 演算法是一種分類演算法,也是最簡單易懂的機器學習演算法,沒有之一。1968年由 Cover 和 Hart 提出,應用場景有字元識別、文字分類、影象識別等領域。該演算法的思想是:一個樣本與資料集中的k個樣本最相似,如果這k個樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。還是直接講例子最好懂,一直沒找到好的例子,就改造了下Peter Harrington的《機器學習實戰》中電影分類的例子,當然實際情況不可能這麼簡單,這裡只是為了說明該演算法的用法。

      先準備下電影分類資料集(電影名稱與分類來自於優酷網;鏡頭數量則純屬虛構):

序號

電影名稱

搞笑鏡頭

擁抱鏡頭

打鬥鏡頭

電影型別

1. 

寶貝當家

45

2

9

喜劇片

2. 

美人魚

21

17

5

喜劇片

3. 

澳門風雲3

54

9

11

喜劇片

4. 

功夫熊貓3

39

0

31

喜劇片

5. 

諜影重重

5

2

57

動作片

6. 

葉問3

3

2

65

動作片

7. 

倫敦陷落

2

3

55

動作片

8. 

我的特工爺爺

6

4

21

動作片

9. 

奔愛

7

46

4

愛情片

10. 

夜孔雀

9

39

8

愛情片

11. 

代理情人

9

38

2

愛情片

12. 

新步步驚心

8

34

17

愛情片

13. 

唐人街探案

23

3

17

上面資料集中序號1-12為已知的電影分類,分為喜劇片、動作片、愛情片三個種類,使用的特徵值分別為搞笑鏡頭、打鬥鏡頭、擁抱鏡頭的數量。那麼來了一部新電影《唐人街探案》,它屬於上述3個電影分類中的哪個型別?用KNN是怎麼做的呢?

       首先,我們構建一個已分好類的資料集。

對於一個規模巨大的資料集,顯然資料庫是更好的選擇。這裡為了方便驗證,使用Python的字典dict構造資料集。

movie_data = {"寶貝當家": [45, 2, 9, "喜劇片"],
              "美人魚": [21, 17, 5, "喜劇片"],
              "澳門風雲3": [54, 9, 11, "喜劇片"],
              "功夫熊貓3": [39, 0, 31, "喜劇片"],
              "諜影重重": [5, 2, 57, "動作片"],
              "葉問3": [3, 2, 65, "動作片"],
              "倫敦陷落": [2, 3, 55, "動作片"],
              "我的特工爺爺": [6, 4, 21, "動作片"],
              "奔愛": [7, 46, 4, "愛情片"],
              "夜孔雀": [9, 39, 8, "愛情片"],
              "代理情人": [9, 38, 2, "愛情片"],
              "新步步驚心": [8, 34, 17, "愛情片"]}

       第二步:計算一個新樣本與資料集中所有資料的距離。

       這裡的新樣本就是:"唐人街探案": [23, 3, 17, "?片"]。歐式距離是一個非常簡單又最常用的距離計算方法。

       

       其中x,y2個樣本,n為維度,xiyixyi個維度上的特徵值。如x為:"唐人街探案": [23, 3, 17, "?片"],y為:"倫敦陷落": [2, 3, 55, "動作片"],則兩者之間的距離為:=43.42

       下面為求與資料集中所有資料的距離程式碼:

x = [23, 3, 17]
KNN = []
for key, v in movie_data.items():
    d = math.sqrt((x[0] - v[0]) ** 2 + (x[1] - v[1]) ** 2 + (x[2] - v[2]) ** 2)
    KNN.append([key, round(d, 2)])
print(KNN)

輸出結果:

[['諜影重重', 43.87], ['倫敦陷落', 43.42], ['澳門風雲3', 32.14], ['葉問3', 52.01], ['我的特工爺爺', 17.49], ['新步步驚心', 34.44], ['寶貝當家', 23.43], ['功夫熊貓3', 21.47], ['奔愛', 47.69], ['美人魚', 18.55], ['夜孔雀', 39.66], ['代理情人', 40.57]]

       第三步:按照距離大小進行遞增排序。

KNN.sort(key=lambda dis: dis[1])

輸出結果:

[['我的特工爺爺', 17.49], ['美人魚', 18.55], ['功夫熊貓3', 21.47], ['寶貝當家', 23.43], ['澳門風雲3', 32.14], ['新步步驚心', 34.44], ['夜孔雀', 39.66], ['代理情人', 40.57], ['倫敦陷落', 43.42], ['諜影重重', 43.87], ['奔愛', 47.69], ['葉問3', 52.01]]

    第四步:選取距離最小的k個樣本。

這裡取k=5

KNN=KNN[:5]

輸出:[['我的特工爺爺', 17.49], ['美人魚', 18.55], ['功夫熊貓3', 21.47], ['寶貝當家', 23.43], ['澳門風雲3', 32.14]]

       第五步:確定前k個樣本所在類別出現的頻率,並輸出出現頻率最高的類別。

labels = {"喜劇片":0,"動作片":0,"愛情片":0}
for sin KNN:
    label = movie_data[s[0]]
    labels[label[3]] += 1
labels =sorted(labels.items(),key=lambdal: l[1],reverse=True)
print(labels,labels[0][0],sep='\n')
輸出結果:

[('喜劇片', 4), ('動作片', 1), ('愛情片', 0)]

喜劇片

KNN有幾個特點:

(1)KNN屬於惰性學習(lazy-learning

這是與急切學習(eager learning)相對應的,因為KNN沒有顯式的學習過程!也就是說沒有訓練階段,從上面的例子就可以看出,資料集事先已有了分類和特徵值,待收到新樣本後直接進行處理。

(2)KNN的計算複雜度較高

我們從上面的例子可以看到,新樣本需要與資料集中每個資料進行距離計算,計算複雜度和資料集中的資料數目n成正比,也就是說,KNN的時間複雜度為O(n),因此KNN一般適用於樣本數較少的資料集。

(3)k取不同值時,分類結果可能會有顯著不同。

上例中,如果k取值為k=1,那麼分類就是動作片,而不是喜劇片。一般k的取值不超過20,上限是n的開方。