K近鄰法(k-nearest neighbors,KNN)是一種很基本的機器學習方法了,在咱們日常的生活中也會不自主的應用。好比,咱們判斷一我的的人品,只須要觀察他來往最密切的幾我的的人品好壞就能夠得出了。這裏就運用了KNN的思想。KNN方法既能夠作分類,也能夠作迴歸,這點和決策樹算法相同。算法
KNN作迴歸和分類的主要區別在於最後作預測時候的決策方式不一樣。KNN作分類預測時,通常是選擇多數表決法,即訓練集裏和預測的樣本特徵最近的K個樣本,預測爲裏面有最多類別數的類別。而KNN作迴歸時,通常是選擇平均法,即最近的K個樣本的樣本輸出的平均值做爲迴歸預測值。因爲二者區別不大,雖然本文主要是講解KNN的分類方法,但思想對KNN的迴歸方法也適用。因爲scikit-learn裏只使用了蠻力實現(brute-force),KD樹實現(KDTree)和球樹(BallTree)實現,本文只討論這幾種算法的實現原理。其他的實現方法好比BBF樹,MVP樹等,在這裏不作討論。微信
KNN算法咱們主要要考慮三個重要的要素,對於固定的訓練集,只要這三點肯定了,算法的預測方式也就決定了。這三個最終的要素是k值的選取,距離度量的方式和分類決策規則。機器學習
對於分類決策規則,通常都是使用前面提到的多數表決法。因此咱們重點是關注與k值的選擇和距離的度量方式。學習
對於k值的選擇,沒有一個固定的經驗,通常根據樣本的分佈,選擇一個較小的值,能夠經過交叉驗證選擇一個合適的k值。測試
選擇較小的k值,就至關於用較小的領域中的訓練實例進行預測,訓練偏差會減少,只有與輸入實例較近或類似的訓練實例纔會對預測結果起做用,與此同時帶來的問題是泛化偏差會增大,換句話說,K值的減少就意味着總體模型變得複雜,容易發生過擬合;
選擇較大的k值,就至關於用較大領域中的訓練實例進行預測,其優勢是能夠減小泛化偏差,但缺點是訓練偏差會增大。這時候,與輸入實例較遠(不類似的)訓練實例也會對預測器做用,使預測發生錯誤,且K值的增大就意味着總體的模型變得簡單。
一個極端是k等於樣本數m,則徹底沒有分類,此時不管輸入實例是什麼,都只是簡單的預測它屬於在訓練實例中最多的類,模型過於簡單。優化
對於距離的度量,咱們有不少的距離度量方式,可是最經常使用的是歐式距離,即對於兩個n維向量x和y,二者的歐式距離定義爲:
\[ D(x,y) = \sqrt{(x_1-y_1)^2 + (x_2-y_2)^2 + ... + (x_n-y_n)^2} = \sqrt{\sum\limits_{i=1}^{n}(x_i-y_i)^2} \]spa
大多數狀況下,歐式距離能夠知足咱們的需求,咱們不須要再去操心距離的度量。rest
固然咱們也能夠用他的距離度量方式。好比曼哈頓距離,定義爲:
\[ D(x,y) =|x_1-y_1| + |x_2-y_2| + ... + |x_n-y_n| =\sum\limits_{i=1}^{n}|x_i-y_i| \]code
更加通用點,好比閔可夫斯基距離(Minkowski Distance),定義爲:
\[ D(x,y) =\sqrt[p]{(|x_1-y_1|)^p + (|x_2-y_2|)^p + ... + (|x_n-y_n|)^p} =\sqrt[p]{\sum\limits_{i=1}^{n}(|x_i-y_i|)^p} \]blog
能夠看出,歐式距離是閔可夫斯基距離距離在p=2時的特例,而曼哈頓距離是p=1時的特例。
從本節起,咱們開始討論KNN算法的實現方式。首先咱們看看最想固然的方式。
既然咱們要找到k個最近的鄰居來作預測,那麼咱們只須要計算預測樣本和全部訓練集中的樣本的距離,而後計算出最小的k個距離便可,接着多數表決,很容易作出預測。這個方法的確簡單直接,在樣本量少,樣本特徵少的時候有效。可是在實際運用中不少時候用不上,爲何呢?由於咱們常常碰到樣本的特徵數有上千以上,樣本量有幾十萬以上,若是咱們這要去預測少許的測試集樣本,算法的時間效率很成問題。所以,這個方法咱們通常稱之爲蠻力實現。比較適合於少許樣本的簡單模型的時候用。
既然蠻力實如今特徵多,樣本多的時候頗有侷限性,那麼咱們有沒有其餘的好辦法呢?有!這裏咱們講解兩種辦法,一個是KD樹實現,一個是球樹實現。
KD樹算法沒有一開始就嘗試對測試樣本分類,而是先對訓練集建模,創建的模型就是KD樹,建好了模型再對測試集作預測。所謂的KD樹就是K個特徵維度的樹,注意這裏的K和KNN中的K的意思不一樣。KNN中的K表明最近的K個樣本,KD樹中的K表明樣本特徵的維數。爲了防止混淆,後面咱們稱特徵維數爲n。
KD樹算法包括三步,第一步是建樹,第二部是搜索最近鄰,最後一步是預測。
咱們首先來看建樹的方法。KD樹建樹採用的是從m個樣本的n維特徵中,分別計算n個特徵的取值的方差,用方差最大的第k維特徵\(n_k\)來做爲根節點。對於這個特徵,咱們選擇特徵\(n_k\)的取值的中位數\(n_{kv}\)對應的樣本做爲劃分點,對於全部第k維特徵的取值小於\(n_{kv}\)的樣本,咱們劃入左子樹,對於第k維特徵的取值大於等於\(n_{kv}\)的樣本,咱們劃入右子樹,對於左子樹和右子樹,咱們採用和剛纔一樣的辦法來找方差最大的特徵來作更節點,遞歸的生成KD樹。
具體流程以下圖:
好比咱們有二維樣本6個,{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},構建kd樹的具體步驟爲:
1)找到劃分的特徵。6個數據點在x,y維度上的數據方差分別爲6.97,5.37,因此在x軸上方差更大,用第1維特徵建樹。
2)肯定劃分點(7,2)。根據x維上的值將數據排序,6個數據的中值(所謂中值,即中間大小的值)爲7,因此劃分點的數據是(7,2)。這樣,該節點的分割超平面就是經過(7,2)並垂直於:劃分點維度的直線x=7;
3)肯定左子空間和右子空間。 分割超平面x=7將整個空間分爲兩部分:x<;=7的部分爲左子空間,包含3個節點={(2,3),(5,4),(4,7)};另外一部分爲右子空間,包含2個節點={(9,6),(8,1)}。
4)用一樣的辦法劃分左子樹的節點{(2,3),(5,4),(4,7)}和右子樹的節點{(9,6),(8,1)}。最終獲得KD樹。
最後獲得的KD樹以下:
當咱們生成KD樹之後,就能夠去預測測試集裏面的樣本目標點了。對於一個目標點,咱們首先在KD樹裏面找到包含目標點的葉子節點。以目標點爲圓心,以目標點到葉子節點樣本實例的距離爲半徑,獲得一個超球體,最近鄰的點必定在這個超球體內部。而後返回葉子節點的父節點,檢查另外一個子節點包含的超矩形體是否和超球體相交,若是相交就到這個子節點尋找是否有更加近的近鄰,有的話就更新最近鄰。若是不相交那就簡單了,咱們直接返回父節點的父節點,在另外一個子樹繼續搜索最近鄰。當回溯到根節點時,算法結束,此時保存的最近鄰節點就是最終的最近鄰。
從上面的描述能夠看出,KD樹劃分後能夠大大減小無效的最近鄰搜索,不少樣本點因爲所在的超矩形體和超球體不相交,根本不須要計算距離。大大節省了計算時間。
咱們用3.1創建的KD樹,來看對點(2,4.5)找最近鄰的過程。
先進行二叉查找,先從(7,2)查找到(5,4)節點,在進行查找時是由y = 4爲分割超平面的,因爲查找點爲y值爲4.5,所以進入右子空間查找到(4,7),造成搜索路徑<;(7,2),(5,4),(4,7)>;,但 (4,7)與目標查找點的距離爲3.202,而(5,4)與查找點之間的距離爲3.041,因此(5,4)爲查詢點的最近點; 以(2,4.5)爲圓心,以3.041爲半徑做圓,以下圖所示。可見該圓和y = 4超平面交割,因此須要進入(5,4)左子空間進行查找,也就是將(2,3)節點加入搜索路徑中得<;(7,2),(2,3)>;;因而接着搜索至(2,3)葉子節點,(2,3)距離(2,4.5)比(5,4)要近,因此最近鄰點更新爲(2,3),最近距離更新爲1.5;回溯查找至(5,4),直到最後回溯到根結點(7,2)的時候,以(2,4.5)爲圓心1.5爲半徑做圓,並不和x = 7分割超平面交割,以下圖所示。至此,搜索路徑回溯完,返回最近鄰點(2,3),最近距離1.5。
對應的圖以下:
有了KD樹搜索最近鄰的辦法,KD樹的預測就很簡單了,在KD樹搜索最近鄰的基礎上,咱們選擇到了第一個最近鄰樣本,就把它置爲已選。在第二輪中,咱們忽略置爲已選的樣本,從新選擇最近鄰,這樣跑k次,就獲得了目標的K個最近鄰,而後根據多數表決法,若是是KNN分類,預測爲K個最近鄰里面有最多類別數的類別。若是是KNN迴歸,用K個最近鄰樣本輸出的平均值做爲迴歸預測值。
KD樹算法雖然提升了KNN搜索的效率,可是在某些時候效率並不高,好比當處理不均勻分佈的數據集時,無論是近似方形,仍是矩形,甚至正方形,都不是最好的使用形狀,由於他們都有角。一個例子以下圖:
若是黑色的實例點離目標點星點再遠一點,那麼虛線圓會如紅線所示那樣擴大,致使與左上方矩形的右下角相交,既然相 交了,那麼就要檢查這個左上方矩形,而實際上,最近的點離星點的距離很近,檢查左上方矩形區域已經是多餘。於此咱們看見,KD樹把二維平面劃分紅一個一個矩形,但矩形區域的角倒是個難以處理的問題。
爲了優化超矩形體致使的搜索效率的問題,牛人們引入了球樹,這種結構能夠優化上面的這種問題。
咱們如今來看看球樹建樹和搜索最近鄰的算法。
球樹,顧名思義,就是每一個分割塊都是超球體,而不是KD樹裏面的超矩形體。
咱們看看具體的建樹流程:
1) 先構建一個超球體,這個超球體是能夠包含全部樣本的最小球體。
2) 從球中選擇一個離球的中心最遠的點,而後選擇第二個點離第一個點最遠,將球中全部的點分配到離這兩個聚類中心最近的一個上,而後計算每一個聚類的中心,以及聚類可以包含它全部數據點所需的最小半徑。這樣咱們獲得了兩個子超球體,和KD樹裏面的左右子樹對應。
3)對於這兩個子超球體,遞歸執行步驟2). 最終獲得了一個球樹。
能夠看出KD樹和球樹相似,主要區別在於球樹獲得的是節點樣本組成的最小超球體,而KD獲得的是節點樣本組成的超矩形體,這個超球體要與對應的KD樹的超矩形體小,這樣在作最近鄰搜索的時候,能夠避免一些無謂的搜索。
使用球樹找出給定目標點的最近鄰方法是首先自上而下貫穿整棵樹找出包含目標點所在的葉子,並在這個球裏找出與目標點最鄰近的點,這將肯定出目標點距離它的最近鄰點的一個上限值,而後跟KD樹查找同樣,檢查兄弟結點,若是目標點到兄弟結點中心的距離超過兄弟結點的半徑與當前的上限值之和,那麼兄弟結點裏不可能存在一個更近的點;不然的話,必須進一步檢查位於兄弟結點如下的子樹。
檢查完兄弟節點後,咱們向父節點回溯,繼續搜索最小鄰近值。當回溯到根節點時,此時的最小鄰近值就是最終的搜索結果。
從上面的描述能夠看出,KD樹在搜索路徑優化時使用的是兩點之間的距離來判斷,而球樹使用的是兩邊之和大於第三邊來判斷,相對來講球樹的判斷更加複雜,可是卻避免了更多的搜索,這是一個權衡。
這裏咱們再討論下KNN算法的擴展,限定半徑最近鄰算法。
有時候咱們會遇到這樣的問題,即樣本中某系類別的樣本很是的少,甚至少於K,這致使稀有類別樣本在找K個最近鄰的時候,會把距離其實較遠的其餘樣本考慮進來,而致使預測不許確。爲了解決這個問題,咱們限定最近鄰的一個最大距離,也就是說,咱們只在一個距離範圍內搜索全部的最近鄰,這避免了上述問題。這個距離咱們通常稱爲限定半徑。
接着咱們再討論下另外一種擴展,最近質心算法。這個算法比KNN還簡單。它首先把樣本按輸出類別歸類。對於第 L類的\(C_l\)個樣本。它會對這\(C_l\)個樣本的n維特徵中每一維特徵求平均值,最終該類別全部維度的n個平均值造成所謂的質心點。對於樣本中的全部出現的類別,每一個類別會最終獲得一個質心點。當咱們作預測時,僅僅須要比較預測樣本和這些質心的距離,最小的距離對於的質心類別即爲預測的類別。這個算法一般用在文本分類處理上。
KNN算法是很基本的機器學習算法了,它很是容易學習,在維度很高的時候也有很好的分類效率,所以運用也很普遍,這裏總結下KNN的優缺點。
KNN的主要優勢有:
1) 理論成熟,思想簡單,既能夠用來作分類也能夠用來作迴歸
2) 可用於非線性分類
3) 訓練時間複雜度比支持向量機之類的算法低,僅爲O(n)
4) 和樸素貝葉斯之類的算法比,對數據沒有假設,準確度高,對異常點不敏感
5) 因爲KNN方法主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來肯定所屬類別的,所以對於類域的交叉或重疊較多的待分樣本集來講,KNN方法較其餘方法更爲適合 6)該算法比較適用於樣本容量比較大的類域的自動分類,而那些樣本容量較小的類域採用這種算法比較容易產生誤分
KNN的主要缺點有:
1)計算量大,尤爲是特徵數很是多的時候
2)樣本不平衡的時候,對稀有類別的預測準確率低
3)KD樹,球樹之類的模型創建須要大量的內存
4)使用懶散學習方法,基本上不學習,致使預測時速度比起邏輯迴歸之類的算法慢
5)相比決策樹模型,KNN模型可解釋性不強```
以上就是KNN算法原理的一個總結,但願能夠幫到朋友們,尤爲是在用scikit-learn學習KNN的朋友們。
(歡迎轉載,轉載請註明出處。歡迎溝通交流: 微信:nickchen121)