k鄰近算法理解及代碼實現


github:代碼實現
本文算法均使用python3實現


1 KNN

  KNN(k-nearest neighbor, k近鄰法),故名思議,是根據最近的 $ k $ 個鄰居來判斷未知點屬於哪個類別。《統計學習方法》中對其定義為:

給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最鄰近的 $ k $ 個實例,這 $ k $ 個實例的多數屬於某個類,就把該輸入實例分為這個類。

  我們對該定義進行直觀地分析,已知實例點為如下圖中帶有顏色的點,不同顏色代表不同類別,未知點為綠色點,要想知道未知點屬於哪個類別,則需要找到未知點最近的 $ k $ 個鄰居。

![](https://images2018.cnblogs.com/blog/1238724/201805/1238724-20180503203407053-209855295.png)

  當 $ k=3 $ 時,結果見第一個圓環內,此時紅色實例點為2個,藍色實例點為1個,因此將未知點判定為紅色類別。
  當 $ k=5 $ 時,結果見第二個圓環內,此時紅色實例點為2個,藍色實例點為3個,因此將未知點判定為藍色類別。

  此時想必大家對KNN算法的思想有了初步的理解。接下來我們將根據定義,來探討KNN算法的相關細節以及具體的實現。

1.1 KNN算法要點:

  回顧一下剛剛的定義,“給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最近鄰的 $ k $ 個實例,這 $ k $ 個實例的多數屬於某個類,就把該輸入實例分為這個類”
  由定義我們可以找到KNN算法的三個要點:
  - 距離度量
  - $ k $ 值的選擇
  - 分類決策規則

(1)距離的度量

  特征空間中我們通常使用兩個實例點的距離來衡量兩個實例點的相似程度。對於KNN模型,其特征空間一般是 $ n $ 維實數向量空間 $ R^n $ ,即對於每一個實例點 $ x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(n)}) $ 都有 $ n $ 維,一般使用的距離是歐氏距離:$$ d=\sqrt{\sum_{l=1}^n {(x_i^{(l)}-x_j^{(l)})^2}} $$
  當然也可以使用其它距離公式進行度量。

(2)$ k $ 值的選擇

  $ k $ 值的選擇會對KNN的結果產生很大的影響,正如上面所示,當 $ k=3 $ 時與 $ k=5 $時,對未知點的類別判定是不同的。 $ k $ 值的減小意味着整體模型變得復雜,會產生過擬合;但 $ k $ 值過大,模型過於簡單,也並不可取。
  在應用中, $ k $ 值一般取較小的值,通常采用交叉驗證法來選取最優的 $ k $ 值。

(3)分類決策選擇

  在得到離未知點最近的 $ k $ 個點之后,如何判定未知點屬於哪個類?通常采用多數表決法,即對最近的 $ k $ 個點中每個類別及個數進行統計,個數最多的那個類別即為未知點的類別。(該方法存在一定缺陷,我們將在后面內容進行講述)

1.2 KNN算法步驟:

  輸入:訓練數據集:$$ T={ (x_1,y_1),(x_2,y_2),...,(x_N,y_N) } $$
  其中,$ x_i \in R^N $ 為實例的特征向量,$ y_i \in {c_1,c_2,...,c_k} $ 為實例的類別, $ i=1,2,..,N $ ;
  輸出:實例 $ x $ 所屬的類別 $ y $
  (1)根據給定的距離度量,在訓練集 $ T $ 中找出與 $ x $ 最近鄰的 $ k $ 個點,涵蓋這 $ k $ 個點的 $ x $ 的鄰域記作 $ N_k(x) $

  (2)在 $ N_k(x) $ 中根據分類決策規則(如多數表決)決定 $ x $ 的類別 $ y $ :$$ y={arg max}{c_g}{\sum{x_i \in N_k(x)}I(y_i=c_g)}, i=1,2,..,N; g=1,2,..,k $$
  上式中,$ I $ 為指示函數,即當 $ y_i=c_g $ 時 $ I $ 為1,否則 $ I $ 為0

1.3 KNN算法分析

  適用數據范圍:數值型標稱型
  優點:精度高、對異常值不敏感、無數據輸入假定
  缺點:
  (1)計算復雜度高:由於對於每一次未知實例的輸入,都需要將其與已知訓練實例依次進行距離的計算,再找出 $ top-k $ 最后進行類別的判斷。在進行距離計算時相當於線性掃描,當訓練集很大時,計算將非常耗時。

  (2)空間復雜度高:由於KNN算法必須保存全部數據集,如果訓練數據集很大,必須使用大量的存儲空間。

  (3)當訓練樣本不平衡時(見下圖),按照KNN中的多數表決法,在 $ k $ 個最近點中,明顯藍色點個數較多,因此會被判定為藍色點對應的類別。但據觀察可知未知點的類別可能更接近於紅色實例部分。也就是說KNN中的多數表決法只考慮了樣本數量的多少,而忽略了距離的遠近。因此,我們可以采用均值或者權值的方法來改進。(均值:$ k $ 近鄰中某一類別所有實例點距未知點的平均距離作為判決標准,選擇距離最短的作為結果。權值:和該樣本距離小的鄰居權值大,和該樣本距離大的權值相對較小)

![](https://images2018.cnblogs.com/blog/1238724/201805/1238724-20180503203540871-1067844371.jpg)

為了提高KNN算法的效率,可以考慮使用特殊的結構存儲訓練數據,以減少計算距離的次數,下面將介紹其中一種方法---kd樹(kd tree)


2 Kd樹

  我們將該部分分為兩部分進行講解:(1)構造kd樹(2)搜索kd樹。前者是對訓練數據集使用特殊結構進行存儲,后者是對實例使用kd數結構進行搜索找到其類別。(該方法類似於搜索二叉樹,在下面講述中可將其與搜索二叉樹對比着理解會比較容易接受)

2.1 構造kd樹

  kd樹是一種對 $ k $ 維空間中的實例點進行存儲以便對其進行快速檢索的樹形數據結構。kd樹是二叉樹,表示對 $ k $ 維空間的一個划分(partition)。構造kd樹相當於不斷地用垂直於坐標軸的超平面將 $ k $ 維空間進行切分,構成一系列的 $ k $ 維矩形區域。kd樹的每個節點對應於一個 $ k $ 維超矩形區域。
  本文講述的切分方法是按照中位數(基於經驗風險最小化)進行切分,也可使用其它方法進行切分,例如按照某維上的數據分散程度進行划分,該方法將使用方差作為划分標准。
  下面將按照個人理解,對二維空間的划分以及kd樹進行講解:
  給定一個二維空間的數據集:$$ T={ (2,3),(5,4),(9,6),(4,7),(8,1),(7,2) } $$
  根節點對應包含數據集 $ T $ 的矩形,首先選擇 $ x^{(1)} $ 維,按照 $ x^{(1)} $ 維對數據集進行排序: $ { (2,3),(4,7),(5,4),(7,2),(8,1),(9,6) } $ ,對於 $ x^{(1)} $ 維,其中位數為:7(按照排好序的數據集,其中位數的所在位置對應下標的中位數,即可使用 $ median = dataset[len(dataset)//2] $ 來確定),選擇 $ (7,2) $ 作為根節點,接着按照 $ x^{(1)} $ 維進行划分,將小於7的划分到根節點的左子樹中,大於7的划分到根節點的右子樹中(該過程類似搜索二叉樹)。
此時對於左右子樹,需要選取相同的維進行划分(此處選取 $ x^{(2)} $ 維)。左子樹:對於節點 $ { (2,3),(4,7),(5,4) } $ ,按照 $ x^{(2)} $ 維進行排序 $ { (2,3),(5,4),(4,7) } $ ,選擇中位數 $ (5,4) $ 作為子樹根節點,同理將小於4的放到 $ (5,4) $ 節點的左子樹中,大於4的放到 $ (5,4) $ 節點的右子樹中。對於根節點 $ (7,2) $ 的右子樹,對節點 $ { (8,1),(9,6) } $ 進行排序,並選擇中位數6,由於 $ 1 < 6 $ ,將 $ (8,1) $ 作為 $ (9,6) $ 的左孩子。到此,kd樹構造完成。(對於kd樹的每一層,都是對相同維進行划分,而每一次划分都相當於一次構造一次搜索二叉樹)
  如下圖是一個二維空間的划分

  其對應的kd樹為:

![](https://images2018.cnblogs.com/blog/1238724/201805/1238724-20180503205731427-1562056086.jpg)

  起初對於二維空間的划分圖,我也是想了很久才理解,在此處以圖的方式,給大家講一下我的理解:
  結合二維空間划分圖與kd樹二叉圖,可以將下圖中垂直綠色線看做是根節點,整個圖的左半部分為根節點的左子樹空間,右半部分為根節點的右子樹空間:

  接着我們來看根節點的左右子樹。左半部分的綠色橫線可以看做左子樹的根節點,下面部分在 $ x^{(2)} $ 維上比根節點小,因此看做是該根節點的左子樹空間,上面部分是該根節點的右子樹空間。同理整個圖的右半部分,中間綠色橫線看作是右子樹根節點,下面部分是相對於該根節點的左子樹空間,上面部分是相對於該根節點的右子樹空間。

  構造平衡kd樹算法步驟:
  輸入:$ k $ 維空間數據集:$$ T={ x_1,x_2,...,x_N } $$ ,
其中 $ x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(k)}), i=1,2,...,N $
  輸出:kd樹
  (1)開始:構造根結點,根結點對應於包含T的 $ k $ 維空間的超矩形區域。選擇 $ x^{(1)} $ 為坐標軸,以 $ T $ 中所有實例的 $ x^{(1)} $ 坐標的中位數為切分點,將根結點對應的超矩形區域切分為兩個子區域。切分由通過切分點並與坐標軸 $ x^{(1)} $ 垂直的超平面實現。由根結點生成深度為1的左、右子結點:左子結點對應坐標 $ x^{(1)} $ 小於切分點的子區域,右子結點對應於坐標 $ x^{(1)} $ 大於切分點的子區域。將落在切分超平面上的實例點保存在根結點。

  (2)重復。對深度為 $ j $ 的結點,選擇 $ x^{(l)} $ 為切分的坐標軸, $ l=j%k+1 $,以該結點的區域中所有實例的 $ x^{(l)} $ 坐標的中位數為切分點,將該結點對應的超矩形區域切分為兩個子區域。切分由通過切分點並與坐標軸 $ x^{(l)} $ 垂直的超平面實現。由該結點生成深度為 $ j+1 $ 的左、右子結點:左子結點對應坐標 $ x^{(l)} $ 小於切分點的子區域,右子結點對應坐標 $ x^{(l)} $ 大於切分點的子區域。將落在切分超平面上的實例點保存在該結點。

2.2 搜索kd樹

  利用kd樹可以省去對大部分數據點的搜索,從而減少搜索的計算量。下面以搜索最近鄰點為例加以敘述:給定一個目標點,搜索其最近鄰,首先找到包含目標點的葉節點;然后從該葉結點出發,依次回退到父結點;不斷查找與目標點最近鄰的結點,當確定不可能存在更近的結點時終止。這樣搜索就被限制在空間的局部區域上,效率大為提高。
  從空間划分來看:以先前構建好的kd樹為例,查找目標點 $ (3,4.5) $ 的最近鄰點。同樣先進行二叉查找,先從 $ (7,2) $ 查找到 $ (5,4) $ 節點,在進行查找時是由 $ y = 4 $ 為分割超平面的,由於查找點為 $ y $ 值為4.5,因此進入右子空間查找到 $ (4,7) $ ,形成搜索路徑:$ (7,2) \rightarrow (5,4) \rightarrow (4,7) $,取 $ (4,7) $ 為當前最近鄰點。以目標查找點為圓心,目標查找點到當前最近點的距離2.69為半徑確定一個紅色的圓。然后回溯到 $ (5,4) $ ,計算其與查找點之間的距離為2.06,則該結點比當前最近點距目標點更近,以 $ (5,4) $ 為當前最近點。用同樣的方法再次確定一個綠色的圓,可見該圓和 $ y = 4 $ 超平面相交(相交即可認為可能存在某一個點在相交區域內,而相交區域內的所有點的距離都比 $ (5.4) $ 點的距離要短,因此只要判斷相交,就需要進入另一個子空間進行查找),所以需要進入 $ (5,4) $ 結點的另一個子空間進行查找。$ (2,3) $ 結點與目標點距離為1.8,比當前最近點要更近,所以最近鄰點更新為 $ ( 2,3) $ ,最近距離更新為1.8,同樣可以確定一個藍色的圓。接着根據規則回退到根結點 $ (7,2) $ ,藍色圓與 $ x=7 $ 的超平面不相交,因此不用進入 $ (7,2) $ 的右子空間進行查找。至此,搜索路徑回溯完,返回最近鄰點 $ (2,3) $,最近距離1.8。

  用kd樹的最近鄰搜索算法:
  輸入: 已構造的kd樹;目標點 $ x $ ;
  輸出: $ x $ 的最近鄰。

  (1) 在kd樹中找出包含目標點 $ x $ 的葉結點:從根結點出發,遞歸的向下訪問kd樹。若目標點當前維的坐標值小於切分點的坐標值,則移動到左子結點,否則移動到右子結點。直到子結點為葉結點為止;

  (2) 以此葉結點為“當前最近點”;

  (3) 遞歸的向上回退,在每個結點進行以下操作:

    (a) 如果該結點保存的實例點比當前最近點距目標點更近,則以該實例點為“當前最近點”;

    (b) 當前最近點一定存在於該結點一個子結點對應的區域。檢查該子結點的父結點的另一個子結點對應的區域是否有更近的點。具體的,檢查另一個子結點對應的區域是否與以目標點為球心、以目標點與“當前最近點”間的距離為半徑的超球體相交。如果相交,可能在另一個子結點對應的區域內存在距離目標更近的點,移動到另一個子結點。接着,遞歸的進行最近鄰搜索。如果不相交,向上回退。

  (4) 當回退到根結點時,搜索結束。最后的“當前最近點”即為x的最近鄰點。

2.3 kd樹算法分析

  如果實例點是隨機分布的,kd樹搜索的平均計算復雜度是 $ O(\log N) $ ,$ N $ 為訓練實例數,kd樹更適用與訓練實例數遠大於空間維數的$ k $ 近鄰搜索,當空間維數接近訓練實例數時,它的效率將迅速下降,幾乎接近線性掃描。


引用及參考:
[1] https://www.cnblogs.com/21207-iHome/p/6084670.html
[2]《統計學習方法》李航著
[3]《機器學習實戰》Peter Harrington著

寫在最后:本文參考以上資料進行整合與總結,屬於原創,文章中可能出現理解不當的地方,若有所見解或異議可在下方評論,謝謝!
若需轉載請注明http://www.cnblogs.com/lliuye/p/8981586.html


免責聲明!

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



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