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,y為2個樣本,n為維度,xi,yi為x,y第i個維度上的特徵值。如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的開方。