K最近鄰(k-Nearest Neighbor,KNN)分類算法

概述

K最近鄰(k-Nearest Neighbor,KNN)分類算法是最簡單的機器學習算法。

它沒有訓練的過程,它的學習階段僅僅是把樣本保存起來,等收到測試集之後再進行處理,屬於“懶惰學習”。反之,在訓練階段就對樣本進行學習的算法屬於“急切學習”。

它本質上是衡量樣本之間的相似度。

口頭描述

給定測試集裏某個點,基於某種距離度量計算它與訓練集中每個點的距離,按照距離遞增依次排序,選取與當前點距離最小的K個點,確定K個點的所在類別的出現頻率,頻率最高的類別作爲當前點的label

計算步驟

計算步驟如下:

  • 算距離:給定測試對象,計算它與訓練集中的每個對象的距離
  • 找鄰居:對訓練集的每個對象根據距離排序,選取最近的K個
  • 做分類:根據這k個近鄰歸屬的主要類別進行投票,以確定測試對象的分類

相似度的衡量

距離越近應該意味着這兩個點屬於一個分類的可能性越大。
但,距離不能代表一切,有些數據的相似度衡量並不適合用距離
相似度衡量方法:包括歐式距離、夾角餘弦等。

(簡單應用中,一般使用歐氏距離,但對於文本分類來說,使用餘弦(cosine)來計算相似度就比歐式(Euclidean)距離更合適)

類別的判定

  • 簡單投票法:少數服從多數,近鄰中哪個類別的點最多就分爲該類。
  • 加權投票法:根據距離的遠近,對近鄰的投票進行加權,距離越近則權重越大(權重爲距離平方的倒數)

算法不足之處

  • 樣本不平衡容易導致結果錯誤
    如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數。

    改善方法:對此可以採用權值的方法(和該樣本距離小的鄰居權值大)來改進。

  • 計算量較大
    因爲對每一個待分類的文本都要計算它到全體已知樣本的距離,才能求得它的K個最近鄰點。

    改善方法:事先對已知樣本點進行剪輯,事先去除對分類作用不大的樣本。
    該方法比較適用於樣本容量比較大的類域的分類,而那些樣本容量較小的類域採用這種算法比較容易產生誤分。

k值設定爲多大?

k太小,分類結果易受噪聲點影響;k太大,近鄰中又可能包含太多的其它類別的點。
(對距離加權,可以降低k值設定的影響)
k值通常是採用交叉檢驗來確定(以k=1爲基準)
經驗規則:k一般低於訓練樣本數的平方根

類別如何判定最合適?

投票法沒有考慮近鄰的距離的遠近,距離更近的近鄰也許更應該決定最終的分類,所以加權投票法更恰當一些。而具體如何加權,需要根據具體的業務和數據特性來探索

如何選擇合適的距離衡量?

高維度對距離衡量的影響:衆所周知當變量數越多,歐式距離的區分能力就越差。
變量值域對距離的影響:值域越大的變量常常會在距離計算中佔據主導作用,因此應先對變量進行標準化。

訓練樣本是否要一視同仁?

在訓練集中,有些樣本可能是更值得依賴的。
也可以說是樣本數據質量的問題
可以給不同的樣本施加不同的權重,加強依賴樣本的權重,降低不可信賴樣本的影響。

性能問題?

kNN是一種懶惰算法,平時不好好學習,考試(對測試樣本分類)時才臨陣磨槍(臨時去找k個近鄰)。

懶惰的後果:構造模型很簡單,但在對測試樣本分類地的系統開銷大,因爲要掃描全部訓練樣本並計算距離。
已經有一些方法提高計算的效率,例如壓縮訓練樣本量等。

距離計算方法

假設使用歐氏距離,有兩個向量 A(a1,a2,...,an),  B(b1,b2,...,bn)A(a_1,a_2,...,a_n),\; B(b_1,b_2,...,b_n) ,則他們之間的歐氏距離爲:
DA,B=i=1n(aibi)2 D_{A,B}=\sqrt{\sum_{i=1}^{n}(a_i-b_i)^2}

Python代碼

"""
測試集 inX
訓練樣本集 dataSet
標籤向量 labels
最近鄰數目 k
"""
def KNN(inX,dataSet,labels,k):
    dataSetSize=dataSet.shape[0]
    diffMat=tile(inX,(dataSetSize,1))-dataSet
    sqDiffMat=diffMat**2
    sqDistance=sqDiffMat.sum(axis=1)
    distances=sqDistance**0.5
    sortedDistIndicies=distances.argsort()
    classCount={}
    for i in range(k):
        voteIlabel=labels[sortedDistIndicies[i]]
        classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章