KNN算法和KD樹

KNN算法和KD樹

KNN算法的思路非常簡單,對於新的樣本,找出距其最近的k個樣本,再根據這k個樣本的類別,通過多數投票的方式預測新樣本的類別。k近鄰算法沒有學習或訓練過程。但k近鄰算法仍有很多值得關注的地方,比如超參數k值的選擇、距離的度量方式、決策規則以及快速檢索k近鄰的算法(kd樹等)。

1. KNN算法的三要素

KNN算法的流程非常簡單,確定一個KNN算法,明確下來三個基本要素即可。即k值的選擇、距離的度量方式和決策規則。

1.1 k值的選擇

在KNN算法中k值即表示對於一個新的樣本,從訓練集中選擇和新樣本距離最近的k個樣本,用這k個樣本決定新樣本的類別。k值作爲一個超參數,很慢明確給出一個合適的值,k取的過大或過小都會導致算法誤差增大。一般可以採用留取驗證集的方式確定k值的大小。即選擇使得驗證集準確率最高的k值。

1.2 距離度量方式

計算樣本之間的距離時,有不同的距離計算方式,常用是歐式距離,也可以採用更一般的Lp 距離。

Lp 距離: Lp(xi,xj)=(kn|xikxjk|p)1p

歐式距離是Lp 距離的特殊情況,即p=2 時:

歐式距離:L2(xi,xj)=kn|xikxjk|2

另外,還有曼哈頓距離(也稱之爲街區距離),也是Lp 距離的特殊情況,即p=1 時的情況。

曼哈頓距離:L1(xi,xj)=kn|xikxjk|

p=

Lp(xi,xj)=(kn|xikxjk|)1=maxk|xikxjk|

選擇不同的距離度量方式,最終結果也是不一樣的,一般情況下都是選擇歐式距離。

1.3 決策規則

決策規則就是指如何利用訓練集中離新樣本最近的k個樣本決定。默認一般都是利用多數投票規則(即選擇k個樣本所屬類別最多的類),因此很容易忽略決策規則的重要性。如果你願意,你也可以選擇其它的決策規則。

1.4 KNN算法求解

通過前面的解釋,將KNN算法用代碼解釋如下:

問題描述

訓練數據集(n個樣本):D={(x1,y1),(x2,y2),...,(xn,yn))}
新的樣本:x

需要明確新樣本x 的類別

train_data # 訓練樣本
train_label #訓練樣本的標籤類別,標籤類別和樣本一一對應,即trian_data[i]的標籤爲train_label[i]
distances = []
for i in range(0, len(train_data)):
    distances.append(distance(x,train_data[i])) #計算樣本x和每個訓練樣本的距離
nn = np.argsort(distances) #對distances進行排序,並返回排序後的索引值
y = decision_rule(nn[0:k-1]) #用k近鄰和響應的決策規則確定樣本x的類別

根據上面的算法解釋,如果訓練樣本數量很少時,是可行的,線性掃描一次計算消耗的計算量不算太大。但如果訓練樣本的數據量很大時呢?每個新樣本,都需要和所有訓練樣本計算距離,計算距離後還需要排序,無論是時間複雜度還是空間複雜度都是很高,樣本數量很大時,不能夠接受這麼高的複雜度。那麼有沒有更加高效的方式呢?這裏我有一些個人的見解,比如最小堆(減少排序時間和排序所需要的空間)、訓練數據排序等。

最小堆

在前面的算法流程中,保存了樣本x 和所有訓練樣本的距離,然後再排序,再取前k 個訓練樣本做決策。但其實可以維護一個大小爲k 的最小堆。對於每次距離的計算,只需要更新該最小堆就好了。即可已節約內存空間(不需要保存所有的距離),也可以減少計算量。

對訓練數據排序

最初步的想法是,對先對訓練數據做排序,對於排好序的訓練數據,每次檢索時,就可以用二分查找法,就可以找到最近鄰的樣本,而k近鄰的,就在最近鄰的附近。但其實仔細一想,這是沒法操作的,依據什麼來排序呢?難道根據訓練數據到新樣本的距離排序?Are you kidding me? 這不就是最原始的算法嗎?naive了。

那麼實際情況下,應該怎麼做呢?常見的是KD樹。

2. KD樹

前面提了一些我自己的想法改進檢索策略的思路,其實KNN算法有更加詳細好用的搜索算法,即kd樹,kd樹表示k-dimensional tree。需要注意的是,kd樹中的k和knn中的k含義完全不一樣,kd樹中的k如英文名中的含義,表示k維。

kd樹的想法是先用訓練數據構建一顆查找樹,我前面對數據進行排序的想法其實也是想幹這個,但是我自己想不到這麼深。構建了樹之後,再對樹進行搜索,可以大大節約搜索的時間。用kd樹解決KNN問題的主要兩點就是:1)構建kd樹;2)如何利用kd樹搜索。

2.1 構建kd樹

kd樹的構建比較簡單,從1~k維(樣本xi 的維度爲k)依次將數據集劃分爲左右兩個節點,不斷的遞歸進行劃分,直到所有訓練樣本都被劃分了爲止。

構建kd樹的流程
  1. 選擇x(1) 作爲初始的劃分座標軸,計算所有訓練樣本在座標軸x(1) 下的中位點(奇數個樣本,則對應中間位置的數,偶數個樣本,則對應中間兩個數的平均值),通過該中位點,可以將所有訓練樣本劃分爲兩部分,該中位點作爲根節點,而這兩部分則作爲左右子節點的劃分數據樣本
  2. 對於深度爲j 的節點,選擇j(mod)k+1 (根節點的深度爲0)作爲劃分的座標軸,同樣以中位點將數據劃分爲兩部分。
  3. 以此往復,直到沒有樣本可以劃分了爲止。

kd樹的會保證每個樣本都會佔一個節點,即使兩個點x1(3,5),x2(3,10) 會被同一個切分點劃分?

2.2在kd樹上搜索

kd樹的構建是比較簡單的,關鍵在於如何在kd樹上搜索,找出k近鄰的樣本和最近鄰的樣本。

kd樹搜索k近鄰
  1. 對於新樣本x ,首先找到樣本對應的葉節點(即使在kd樹上搜索的過程,找了完全匹配的點,也要遞歸到葉節點)。若樣本x 當前維的座標小於結點的座標,則移動到左子結點,否則移動到右子結點;
  2. 將葉結點作爲最近鄰;
  3. 從葉結點開始回退,對回退路上的結點,判斷結點和樣本x 的距離(如果還未湊齊k個近鄰點,則加入;如果湊齊了,但比k近鄰的點更近,那麼更新k近鄰結果)。
    另外對於每個回退結點,還需要判斷是否要進入該結點的另外一個子結點搜索,如何判斷呢?在k近鄰的k個點中,必然存在一個離樣本點x 最遠的點,那麼以它們之間的距離作爲判斷準則,如果回退結點的切分座標軸到樣本x 的距離大於這個最遠距離,那麼就沒有必要再探索了。
  4. 一直回退到根節點爲止。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章