KNN(K近鄰法)算法原理


 

一、K近鄰概述

k近鄰法(k-nearest neighbor, kNN)是一種基本分類與回歸方法(有監督學習的一種),KNN(k-nearest neighbor algorithm)算法的核心思想是如果一個樣本在特征空間中的k(k一般不超過20)個最相鄰的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別,並具有這個類別上樣本的特性。簡單地說,K-近鄰算法采用測量不同特征值之間的距離方法進行分類。

通常,在分類任務中可使用“投票法”,即選擇這k個實例中出現最多的標記類別作為預測結果;在回歸任務中可使用“平均法”,即將這k個實例的實值輸出標記的平均值作為預測結果;還可基於距離遠近進行加權平均或加權投票,距離越近的實例權重越大。
k近鄰法不具有顯式的學習過程,事實上,它是懶惰學習(lazy learning)的著名代表,此類學習技術在訓練階段僅僅是把樣本保存起來,訓練時間開銷為零,待收到測試樣本后再進行處理

K近鄰算法的優缺點:

  • 優點:精度高、對異常值不敏感、無數據輸入假定
  • 缺點:計算復雜度高、空間復雜度高
  • 適用數據范圍:數值型和標稱型

二、K近鄰法的三要素

距離度量k值的選擇分類決策規則是k近鄰法的三個基本要素。根據選擇的距離度量(如曼哈頓距離或歐氏距離),可計算測試實例與訓練集中的每個實例點的距離,根據k值選擇k個最近鄰點,最后根據分類決策規則將測試實例分類。

根據歐氏距離,選擇k=4個離測試實例最近的訓練實例(紅圈處),再根據多數表決的分類決策規則,即這4個實例多數屬於“-類”,可推斷測試實例為“-類”。
k近鄰法1968年由Cover和Hart提出

 

 

 1.距離度量

特征空間中的兩個實例點的距離是兩個實例點相似程度的反映。K近鄰法的特征空間一般是n維實數向量空間Rn。使用的距離是歐氏距離,但也可以是其他距離,如更一般的Lp距離或Minkowski距離

Minkowski距離(也叫閔氏距離):

  • p=1時,得到絕對值距離,也稱曼哈頓距離(Manhattan distance),在二維空間中可以看出,這種距離是計算兩點之間的直角邊距離,相當於城市中出租汽車沿城市街道拐直角前進而不能走兩點連接間的最短距離,絕對值距離的特點是各特征參數以等權參與進來,所以也稱等混合距離
  • p=2時,得到歐幾里德距離(Euclidean distance),就是兩點之間的直線距離(以下簡稱歐氏距離)。歐氏距離中各特征參數是等權的
  • 當p趨向無窮∞,得到切比雪夫距離

設特征空間X是n維實數向量空間Lp 距離定義為

這里p≥1。
當p=1時,稱為曼哈頓距離(Manhattan distance),即

 當p=2時,稱為歐氏距離(Euclidean distance),即

 當p=∞時,它是各個坐標距離的最大值,即

 證明:以二維實數向量空間(n=2)為例說明曼哈頓距離和歐氏距離的物理意義

 (1)曼哈頓距離

 

 (2)歐氏距離

2.K值的選擇

k值的選擇會對k近鄰法的結果產生重大影響。在應用中,k值一般取一個比較小的數值,通常采用交叉驗證法來選取最優的k值

說明一下:我舉這個例子,雖說不是k近鄰,但是我們可以操作這種方式去找到比較適合的參數,當然也可以使用網格搜索法

from sklearn.ensemble import GradientBoostingClassifier 
depth_ = [1, 2, 3, 4, 5, 6, 7, 8]
scores = []
for depth in depth_:
    clf = GradientBoostingClassifier(n_estimators=100, max_depth=depth, random_state=0)
    test_score = cross_val_score(clf, train_df, y_train, cv=10, scoring="precision")
    scores.append(np.mean(test_score))
plt.plot(depth_, scores)
3.分類決策規則

k近鄰法中的分類決策規則往往是多數表決,即由輸入實例的k個鄰近的訓練實例中的多數類,決定輸入實例的類

我自己編寫了一下自定義的knn :

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 導入KNN分類器
from sklearn.neighbors import KNeighborsClassifier
from sklearn import datasets
from sklearn.model_selection import train_test_split
from collections import Counter

def knn(test_data,train_data,train_y,k):
    '''
    目的就是knn
    里面的距離計算使用的是歐氏距離

    '''
    data_size=train_data.shape[0]
    test_y=[]
    for i in test_data:
        diffmat=np.tile(i,(data_size,1))-train_data
        sqdiffmat=diffmat**2
        sqdistance=sqdiffmat.sum(axis=1)
        distance=sqdistance**0.5  #計算測試和每個訓練集的距離
        sort_index=distance.argsort()
        for_label=[]
        for j in range(k):
            voteilabel=train_y[sort_index[j]]  #
            for_label.append(voteilabel)
        t_label=Counter(for_label).most_common(1)[0][0]
        test_y.append(t_label)
    return test_y

def knn_acuuary(test_y,test_data,train_data,train_y,k):
    df=pd.DataFrame()
    df['y']=knn(test_data,train_data,train_y,k)
    df['test_y']=test_y
    l=len(test_y)
    acuuary=len(df[df['y']==df['test_y']])/l
    return acuuary,df




if __name__=='__main__':

    # 載入鳶尾花數據集
    # iris是一個對象類型的數據,其中包括了data(鳶尾花的特征)和target(也就是分類標簽)
    iris = datasets.load_iris()
    x=pd.DataFrame(iris.data)
    y=iris.target    
    print(x.shape, y.shape)  # (150, 4) (150,)    

    # 划分數據集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.2)  # 8:2
    print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)
    acuuary,df=knn_acuuary(y_test,x_test.values,x_train.values,y_train,5)
    #print(acuuary,df)
    print(acuuary)

 

三、K近鄰法的實現:KD樹

       實現k近鄰法時,主要考慮的問題是如何對訓練數據進行快速k近鄰搜索,這點在特征空間的維數大及訓練數據容量大時尤其必要。
    k近鄰法最簡單的實現方法是線性掃描(linear scan),這時要計算輸入實例與每一個訓練實例的距離,當訓練集很大時,計算非常耗時。為了提高k近鄰法搜索的效率,可以考慮使用特殊的結構存儲訓練數據,以減少計算距離的次數。具體方法很多,下面介紹其中的kd樹方法(kd樹是存儲k維空間數據的樹結構,這里的k與k近鄰法的k意義不同)。

 1.kd樹算法原理

       k-d tree是每個節點均為k維數值點(即是說每個節點都是一個坐標(x1,x2...xn))的二叉樹,其上的每個節點代表一個超平面,該超平面垂直於當前划分維度的坐標軸,並在該維度上將空間划分為兩部分,一部分在其左子樹,另一部分在其右子樹。即若當前節點的划分維度為d,其左子樹上所有點在d維的坐標值均小於當前值,右子樹上所有點在d維的坐標值均大於等於當前值,本定義對其任意子節點均成立
    通常,依次選擇坐標軸對空間切分,選擇訓練實例點在選定坐標軸上的中位數(一組數據按大小順序排列起來,處於中間的一個數或最中間兩個數的平均值本文在最中間有兩個數時選擇最大值作中位數)為切分點,這樣得到的kd樹是平衡的。注意,平衡的kd樹搜索時未必是最優的

 2.KD樹構建

一個平衡的k-d tree,其所有葉子節點到根節點的距離近似相等。但一個平衡的k-d tree對最近鄰搜索、空間搜索等應用場景並非是最優的。
常規的k-d tree的構建過程為:循環依序取數據點的各維度來作為切分維度,取數據點在該維度的中值作為切分超平面,將中值左側的數據點掛在其左子樹,將中值右側的數據點掛在其右子樹。遞歸處理其子樹,直至所有數據點掛載完畢

切分維度選擇優化

構建開始前,對比數據點在各維度的分布情況,數據點在某一維度坐標值的方差越大分布越分散,方差越小分布越集中。從方差大的維度開始切分可以取得很好的切分效果及平衡性

 

 

中值選擇優化

第一種,算法開始前,對原始數據點在所有維度進行一次排序,存儲下來,然后在后續的中值選擇中,無須每次都對其子集進行排序,提升了性能。
第二種,從原始數據點中隨機選擇固定數目的點,然后對其進行排序,每次從這些樣本點中取中值,來作為分割超平面。該方式在實踐中被證明可以取得很好性能及很好的平衡性

以二維平面點(x,y)的集合(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)為例結合下圖來說明k-d tree的構建過程

a)構建根節點時,此時的切分維度為x,如上點集合在x維從小到大排序為(2,3),(4,7),(5,4),(7,2),(8,1),(9,6);其中值為(7,2)。(注:2,4,5,7,8,9在數學中的中值為(5 + 7)/2=6,但因該算法的中值需在點集合之內,所以本文中值計算用的是len(points)//2=3, points[3]=(7,2))
b)(2,3),(4,7),(5,4)掛在(7,2)節點的左子樹,(8,1),(9,6)掛在(7,2)節點的右子樹。
c)構建(7,2)節點的左子樹時,點集合(2,3),(4,7),(5,4)此時的切分維度為y,中值為(5,4)作為分割平面,(2,3)掛在其左子樹,(4,7)掛在其右子樹。
d)構建(7,2)節點的右子樹時,點集合(8,1),(9,6)此時的切分維度也為y,中值為(9,6)作為分割平面,(8,1)掛在其左子樹。至此k-d tree構建完成

樹的深度應該等於數據集的維度(也就是特征個數),也就是k=特征個數=樹的深度

 

 

 

 

 

 


免責聲明!

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



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