聲明:如需轉載請先聯繫我。python
最近學習了k近鄰算法,在這裏進行了總結。git
k近鄰法(k-nearest neighbors)是由Cover和Hart於1968年提出的,它是懶惰學習(lazy learning)的著名錶明。
它的工做機制比較簡單:github
k
個訓練樣本距離衡量的標準有不少,常見的有:$L_p$距離、切比雪夫距離、馬氏距離、巴氏距離、餘弦值等。算法
什麼意思呢?先來看這張圖
機器學習
咱們對應上面的流程來講ide
這裏用歐氏距離
做爲距離的衡量標準,用鳶尾花數據集舉例說明。
鳶尾花數據集有三個類別,每一個類有150個樣本,每一個樣本有4個特徵。
先來回顧一下歐氏距離的定義(摘自維基百科):
函數
在歐幾里得空間中,點 x = (x1,...,xn) 和 y = (y1,...,yn) 之間的歐氏距離爲
$d(x,y):={\sqrt {(x_{1}-y_{1})^{2}+(x_{2}-y_{2})^{2}+\cdots +(x_{n}-y_{n})^{2}}}={\sqrt {\sum {{i=1}}^{n}(x{i}-y_{i})^{2}}}$
oop
向量 ${\displaystyle {\vec {x}}}$的天然長度,即該點到原點的距離爲
$|{\vec {x}}|{2}={\sqrt {|x{1}|^{2}+\cdots +|x_{n}|^{2}}}$
學習
它是一個純數值。在歐幾里得度量下,兩點之間線段最短。
如今給出六個訓練樣本,分爲三類,每一個樣本有4個特徵,編號爲7的名稱
是咱們要預測的。
|編號|花萼長度(cm)|花萼寬度(cm)|花瓣長度(cm)|花瓣寬度(cm)|名稱|
|----|----------|-----------|-----------|----------|----|
|1|4.9|3.1|1.5|0.1|Iris setosa|
|2|5.4|3.7|1.5|0.2|Iris setosa|
|3|5.2|2.7|3.9|1.4|Iris versicolor|
|4|5.0|2.0|3.5|1.0|Iris versicolor|
|5|6.3|2.7|4.9|1.8|Iris virginica|
|6|6.7|3.3|5.7|2.1|Iris virginica|
|7|5.5|2.5|4.0|1.3| ? |測試
按照以前說的步驟,咱們來計算測試樣本到各個訓練樣本的距離。例如到第一個樣本的距離:
$d_{1}=\sqrt{(4.9 - 5.5)^2 + (3.1 - 2.5)^2 + (1.5 - 4.0)^2 + (0.1 - 1.3)^2} = 2.9$
寫一個函數來執行這個操做吧
import numpy as np def calc_distance(iA,iB): temp = np.subtract(iA, iB) # 對應元素相減 temp = np.power(temp, 2) # 元素分別平方 distance = np.sqrt(temp.sum()) # 先求和再開方 return distance testSample = np.array([5.5, 2.5, 4.0, 1.3]) print("Distance to 1:", calc_distance(np.array([4.9, 3.1, 1.5, 0.1]), testSample)) print("Distance to 2:", calc_distance(np.array([5.4, 3.7, 1.5, 0.2]), testSample)) print("Distance to 3:", calc_distance(np.array([5.2, 2.7, 3.9, 1.4]), testSample)) print("Distance to 4:", calc_distance(np.array([5.0, 2.0, 3.5, 1.0]), testSample)) print("Distance to 5:", calc_distance(np.array([6.3, 2.7, 4.9, 1.8]), testSample)) print("Distance to 6:", calc_distance(np.array([6.7, 3.3, 5.7, 2.1]), testSample))
Distance to 1: 2.9
Distance to 2: 2.98496231132
Distance to 3: 0.387298334621
Distance to 4: 0.916515138991
Distance to 5: 1.31909059583
Distance to 6: 2.36854385647
若是咱們把k定爲3,那麼離測試樣本最近3個依次是:
編號 | 名稱 |
---|---|
3 | Iris versicolor |
4 | Iris versicolor |
5 | Iris virginica |
顯然測試樣本屬於Iris versicolor
類的「票數」多一點,事實上它的確屬於這個類。
這裏參考了CSDN蘆金宇博客上的總結
優勢
缺點
補充一點:因爲它屬於懶惰學習,所以須要大量的空間來存儲訓練實例,在預測時它還須要與已知全部實例進行比較,增大了計算量。
這裏介紹一下,當樣本不平衡時的影響。
從直觀上能夠看出X應該屬於$\omega_{1}$,這是理所應當的。對於Y看起來應該屬於$\omega_{1}$,但事實上在k範圍內,更多的點屬於$\omega_{2}$,這就形成了錯誤分類。
在周志華編著的《機器學習》中證實了最近鄰學習器的泛化錯誤率不超過貝葉斯最優分類器的錯誤率的兩倍,在原書的226頁,這裏就不摘抄了。
知道KNN的原理後,應該能夠很輕易的寫出代碼了,這裏介紹一下在距離計算上的優化,在結尾給上完整代碼(代碼比較亂,知道思想就好)。
函數的輸入:train_X
、test_X
是numpy array,假設它們的shape分別爲(n, 4)、(m, 4);要求輸出的是它們兩點間的距離矩陣,shape爲(n, m)。
不就是計算兩點之間的距離,再存起來嗎,直接暴力上啊︿( ̄︶ ̄)︿,因而就有了下面的
def euclideanDistance_two_loops(train_X, test_X): num_test = test_X.shape[0] num_train = train_X.shape[0] dists = np.zeros((num_test, num_train)) for i in range(num_test): for j in range(num_train): test_line = test_X[i] train_line = train_X[j] temp = np.subtract(test_line, train_line) temp = np.power(temp, 2) dists[i][j] = np.sqrt(temp.sum()) return dists
不知道你有沒有想過,這裏的樣本數只有100多個,因此時間上感受沒有等過久,可是當樣本量很是大的時候呢,雙循環計算的弊端就顯露出來了。解決方案是把它轉換爲兩個矩陣之間的運算,這樣就能避免使用循環了。
此處參考CSDNfrankzd的博客
記測試集矩陣P的大小爲$MD$,訓練集矩陣C的大小爲$ND$(測試集中共有M個點,每一個點爲D維特徵向量。訓練集中共有N個點,每一個點爲D維特徵向量)
記$P_{i}$是P的第i行$P_i = [ P_{i1}\quad P_{i2} \cdots P_{iD}]$,記$C_{j}$是C的第j行$C_j = [ C_{j1} C_{j2} \cdots \quad C_{jD}]$
知道距離矩陣怎麼算出來的以後,在代碼上只須要套公式帶入就能實現了。
def euclideanDistance_no_loops(train_X, test_X): num_test = test_X.shape[0] num_train = train_X.shape[0] sum_train = np.power(train_X, 2) sum_train = sum_train.sum(axis=1) sum_train = sum_train * np.ones((num_test, num_train)) sum_test = np.power(test_X, 2) sum_test = sum_test.sum(axis=1) sum_test = sum_test * np.ones((1, sum_train.shape[0])) sum_test = sum_test.T sum = sum_train + sum_test - 2 * np.dot(test_X, train_X.T) dists = np.sqrt(sum) return dists
是否是很簡單,這裏兩種方法咱們衡量兩點間距離的標準是歐氏距離
。若是想用其餘的標準呢,好比$L_{1}$距離該怎麼實現呢,這裏我參照上面推導公式的思想得出了計算$L_{1}$距離的矩陣運算。
記測試集矩陣P的大小爲$MD$,訓練集矩陣C的大小爲$ND$(測試集中共有M個點,每一個點爲D維特徵向量。訓練集中共有N個點,每一個點爲D維特徵向量)
記$P_{i}$是P的第i行$P_i = [ P_{i1}\quad P_{i2} \cdots P_{iD}]$,記$C_{j}$是C的第j行$C_j = [ C_{j1} C_{j2} \cdots \quad C_{jD}]$
首先計算Pi和Cj之間的距離dist(i,j)
$d(P_{i}, C_{j}) = |P_{i1} - C_{j1}| + |P_{i2} - C_{j2}| + \cdots + |P_{iD} - C_{jD}|$
咱們能夠推廣到距離矩陣的第i行的計算公式
$dist[i] = \left | [P_{i}\quad P_{i} \cdots P_{i}] - [C_{1}\quad C_{2}\cdots C_{N}] \right|=[|P_{i} - C_{1}|\quad |P_{i} - C_{2}| \cdots |P_{i} - C_{N}|]$
繼續將公式推廣爲整個距離矩陣
其中$P_i = [ P_{i1}\quad P_{i2} \cdots P_{iD}]$、$C_j = [ C_{j1} C_{j2} \cdots \quad C_{jD}]$
def l1_distance_no_loops(train_X, test_X): num_test = test_X.shape[0] num_train = train_X.shape[0] test = np.tile(test_X, (num_train, 1, 1)) train = np.tile(train_X, (num_test, 1, 1)) train = np.transpose(train, axes=(1, 0, 2)) sum = np.subtract(test, train) sum = np.abs(sum) sum = np.sum(sum, axis=2) dists = sum.T return dists
因爲測試集樣本數量有限,兩種距離衡量標準下的準確率分別是0.94和0.98。