k近鄰算法的Python實現
0. 寫在前面
這篇小教程適合對Python與NumPy有一定了解的朋友閱讀,如果在閱讀本文的源代碼時感到吃力,請及時參照相關的教程或者文檔。
1. 算法原理
k近鄰算法(k Nearest Neighbor)可以簡稱為kNN。kNN是一個簡單直觀的算法,也是機器學習從業者入門首選的算法。先看一個簡單的應用場景。
小例子
設有下表,命名為為表1
| 電影名稱 | 打斗鏡頭數量 | 接吻鏡頭數量 | 電影類型 |
|---|---|---|---|
| foo1 | 3 | 104 | 愛情片 |
| foo2 | 2 | 100 | 愛情片 |
| foo3 | 1 | 81 | 愛情片 |
| foo4 | 101 | 10 | 動作片 |
| foo5 | 99 | 5 | 動作片 |
| foo6 | 98 | 2 | 動作片 |
一個朴素的願望是,能夠根據打斗鏡頭與接吻鏡頭的數量來推測一部電影是屬於愛情片還是動作片。具體而言,如果有一部電影的相關信息如下,命名為表2:
| 電影名稱 | 打斗鏡頭數量 | 接吻鏡頭數量 |
|---|---|---|
| foo7 | 18 | 90 |
我們能否給出這部電影的類型?
解決方案
表1可以抽象為一個矩陣A與一個列向量x如下:
矩陣A
foo1 3 104
foo2 2 100
foo3 1 81
foo4 101 10
foo5 99 5
foo6 98 2
列向量x
愛情片
愛情片
愛情片
動作片
動作片
動作片
表2可以抽象為一個行向量a如下:
行向量a
foo7 18 90
顯然,可以求矩陣A中每一個行向量與行向量a的歐式距離(本例中計算歐式距離時只考慮打斗鏡頭數量與接吻鏡頭數量兩個分量),並按照距離由小到大排序,結果如下表,命名為表3:
| 電影名稱 | 與未知電影之的距離 |
|---|---|
| foo2 | 18.7 |
| foo3 | 19.2 |
| foo1 | 20.5 |
| foo4 | 115.3 |
| foo5 | 117.4 |
| foo6 | 118.9 |
此時選擇前k個距離最小的電影及其所屬的類型,結果如下表,命名為表4:
| 電影名稱 | 類型 |
|---|---|
| foo2 | 愛情片 |
| foo3 | 愛情片 |
| foo4 | 愛情片 |
找出表4中出現次數最多的類型——“愛情片”,即kNN認為行向量a所屬的類型為愛情片。
2. Python實現
代碼的核心部分是如下函數,將其保存在文件中mykNN.py中。
import numpy as np
import operator as op
from collections import defaultdict
def classify(vec, dataSet, labels, k):
"""
要求dataSet為NumPy的array類型
vec: 參照行向量a
dataSet: 參照矩陣A
labels: 參照列向量x
k: kNN中選擇前k小的行
"""
size = dataSet.shape[0]
assert size == len(labels) #斷言,確保輸入正確
tmp = (dataSet - vec)**2 #使用了NumPy的廣播機制
tmp = tmp.sum(axis=1)
tmp = tmp.argsort()
tmpDict = defaultdict(int) #簡化用於分組的代碼
for i in range(k):
tmpDict[labels[tmp[i]]] += 1
return max(tmpDict.items(),key=op.itemgetter(1))[0]
3. 練手案例
我們使用第2小節的代碼解決第1小節的問題。下面的代碼文件保存為test.py,請確保test.py與mykNN.py文件位於同一個路徑下。
import numpy as np
import mykNN as knn
if __name__ == "__main__":
dataSet = np.array([
[3, 104],
[2, 100],
[1, 81],
[101, 10],
[99, 5],
[98, 2]
])
labels = ["愛情片", "愛情片", "愛情片",
"動作片", "動作片", "動作片"]
k = 3
vec = [18, 90]
res = knn.classify(vec,dataSet,labels,k)
print(res)
4. 補充說明
真實的分類任務不會像我們的案例那樣簡單。
-
一般來說,第3小節中的dataSet與labels都會放在文件或者數據庫中,並且未必是NumPy可以處理的數據類型。這時需要增加讀文件或者讀數據庫並解析轉換數據的一系列代碼。
-
有時需要考慮對表格的不同字段歸一化的問題。
-
以數據驅動的應用的開發需要關注kNN算法的正確率,這時需要增加判斷正確率或者錯誤率的代碼。
