k-最近鄰算法是基於實例的學習方法中最基本的,先介紹基於實例學習的相關概念。
基於實例的學習
- 已知一系列的訓練樣例,很多學習方法爲目標函數建立起明確的一般化描述;但與此不同,基於實例的學習方法只是簡單地把訓練樣例存儲起來。
從這些實例中泛化的工作被推遲到必須分類新的實例時。每當學習器遇到一個新的查詢實例,它分析這個新實例與以前存儲的實例的關係,並據此把一個目標函數值賦給新實例。 - 基於實例的方法可以爲不同的待分類查詢實例建立不同的目標函數逼近。事實上,很多技術只建立目標函數的局部逼近,將其應用於與新查詢實例鄰近的實例,而從 不建立在整個實例空間上都表現良好的逼近。當目標函數很複雜,但它可用不太複雜的局部逼近描述時,這樣做有顯著的優勢。
- 基於實例方法的不足
- 分類新實例的開銷可能很大。這是因爲幾乎所有的計算都發生在分類時,而不是在第一次遇到訓練樣例時。所以,如何有效地索引訓練樣例,以減少查詢時所需計算是一個重要的實踐問題。
- 當從存儲器中檢索相似的訓練樣例時,它們一般考慮實例的所有屬性。如果目標概念僅依賴於很多屬性中的幾個時,那麼真正最“相似”的實例之間很可能相距甚遠。
k-最近鄰法
算法概述
K最近鄰(K-Nearest Neighbor,KNN)算法,是著名的模式識別統計學方法,在機器學習分類算法中佔有相當大的地位。它是一個理論上比較成熟的方法。既是最簡單的機器學習算法之一,也是基於實例的學習方法中最基本的,又是最好的文本分類算法之一。
基本思想
如果一個實例在特徵空間中的K個最相似(即特徵空間中最近鄰)的實例中的大多數屬於某一個類別,則該實例也屬於這個類別。所選擇的鄰居都是已經正確分類的實例。
該算法假定所有的實例對應於N維歐式空間中的點。通過計算一個點與其他所有點之間的距離,取出與該點最近的K個點,然後統計這K個點裏面所屬分類比例最大的,則這個點屬於該分類。
該算法涉及3個主要因素:實例集、距離或相似的衡量、k的大小。
一個實例的最近鄰是根據標準歐氏距離定義的。更精確地講,把任意的實例表示爲下面的特徵向量:
其中表示實例x的第r個屬性值。那麼兩個實例xi和xj間的距離定義爲,其中:
有關KNN算法的幾點說明:
- 在最近鄰學習中,目標函數值可以爲離散值也可以爲實值。
- 我們先考慮學習以下形式的離散目標函數。其中V是有限集合{}。下表給出了逼近離散目標函數的k-近鄰算法。
- 正如下表中所指出的,這個算法的返回值爲對的估計,它就是距離最近的k個訓練樣例中最普遍的f值。
- 如果我們選擇k=1,那麼“1-近鄰算法”就把賦給,其中是最靠近的訓練實例。對於較大的k值,這個算法返回前k個最靠近的訓練實例中最普遍的值。
逼近離散值函數的k-近鄰算法
訓練算法:
對於每個訓練樣例,把這個樣例加入列表training_examples
分類算法:
給定一個要分類的查詢實例
在training_examples中選出最靠近的k個實例,並用表示
返回
其中如果那麼,否則
簡單來說,KNN可以看成:有那麼一堆你已經知道分類的數據,然後當一個新數據進入的時候,就開始跟訓練數據裏的每個點求距離,然後挑離這個訓練數據最近的K個點看看這幾個點屬於什麼類型,然後用少數服從多數的原則,給新數據歸類。
KNN算法的決策過程
下圖中有兩種類型的樣本數據,一類是藍色的正方形,另一類是紅色的三角形,中間那個綠色的圓形是待分類數據:
如果K=3,那麼離綠色點最近的有2個紅色的三角形和1個藍色的正方形,這三個點進行投票,於是綠色的待分類點就屬於紅色的三角形。而如果K=5,那麼離綠色點最近的有2個紅色的三角形和3個藍色的正方形,這五個點進行投票,於是綠色的待分類點就屬於藍色的正方形。
下圖則圖解了一種簡單情況下的k-最近鄰算法,在這裏實例是二維空間中的點,目標函數具有布爾值。正反訓練樣例用“+”和“-”分別表示。圖中也畫出了一個查詢點。注意在這幅圖中,1-近鄰算法把分類爲正例,然而5-近鄰算法把xq分類爲反例。
__圖解說明:__左圖畫出了一系列的正反訓練樣例和一個要分類的查詢實例。1-近鄰算法把分類爲正例,然而5-近鄰算法把分類爲反例。
右圖是對於一個典型的訓練樣例集合1-近鄰算法導致的決策面。圍繞每個訓練樣例的凸多邊形表示最靠近這個點的實例空間(即這個空間中的實例會被1-近鄰算法賦予該訓練樣例所屬的分類)。
對前面的k-近鄰算法作簡單的修改後,它就可被用於逼近連續值的目標函數。爲了實現這一點,我們讓算法計算k個最接近樣例的平均值,而不是計算其中的最普遍的值。更精確地講,爲了逼近一個實值目標函數,我們只要把算法中的公式替換爲:
針對傳統KNN算法的改進
- 快速KNN算法。參考FKNN論述文獻(實際應用中結合lucene)
- 加權歐氏距離公式。在傳統的歐氏距離中,各特徵的權重相同,也就是認定各個特徵對於分類的貢獻是相同的,顯然這是不符合實際情況的。同等的權重使得特徵向量之間相似度計算不夠準確, 進而影響分類精度。加權歐氏距離公式,特徵權重通過靈敏度方法獲得(根據業務需求調整,例如關鍵字加權、詞性加權等)
距離加權最近鄰算法
對k-最近鄰算法的一個顯而易見的改進是對k個近鄰的貢獻加權,根據它們相對查詢點xq的距離,將較大的權值賦給較近的近鄰。
例如,在上表逼近離散目標函數的算法中,我們可以根據每個近鄰與xq的距離平方的倒數加權這個近鄰的“選舉權”。
方法是通過用下式取代上表算法中的公式來實現:
其中
爲了處理查詢點恰好匹配某個訓練樣例,從而導致分母爲0的情況,我們令這種情況下的等於。如果有多個這樣的訓練樣例,我們使用它們中佔多數的分類。
我們也可以用類似的方式對實值目標函數進行距離加權,只要用下式替換上表的公式:
其中的定義與之前公式中相同。
注意這個公式中的分母是一個常量,它將不同權值的貢獻歸一化(例如,它保證如果對所有的訓練樣例,,那麼。
注意以上k-近鄰算法的所有變體都只考慮k個近鄰以分類查詢點。如果使用按距離加權,那麼允許所有的訓練樣例影響的分類事實上沒有壞處,因爲非常遠的實例對的影響很小。考慮所有樣例的惟一不足是會使分類運行得更慢。如果分類一個新的查詢實例時考慮所有的訓練樣例,我們稱此爲全局(global)法。如果僅考慮最靠近的訓練樣例,我們稱此爲局部(local)法。
四、KNN的優缺點
(1)優點
①簡單,易於理解,易於實現,無需參數估計,無需訓練;
②精度高,對異常值不敏感(個別噪音數據對結果的影響不是很大);
③適合對稀有事件進行分類;
④特別適合於多分類問題(multi-modal,對象具有多個類別標籤),KNN要比SVM表現要好.
(2)缺點
①對測試樣本分類時的計算量大,空間開銷大,因爲對每一個待分類的文本都要計算它到全體已知樣本的距離,才能求得它的K個最近鄰點。目前常用的解決方法是事先對已知樣本點進行剪輯,事先去除對分類作用不大的樣本;
②可解釋性差,無法給出決策樹那樣的規則;
③最大的缺點是當樣本不平衡時,如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數。該算法只計算“最近的”鄰居樣本,某一類的樣本數量很大,那麼或者這類樣本並不接近目標樣本,或者這類樣本很靠近目標樣本。無論怎樣,數量並不能影響運行結果。可以採用權值的方法(和該樣本距離小的鄰居權值大)來改進;
④消極學習方法。
五、對k-近鄰算法的說明
按距離加權的k-近鄰算法是一種非常有效的歸納推理方法。它對訓練數據中的噪聲有很好的魯棒性,而且當給定足夠大的訓練集合時它也非常有效。注意通過取k個近鄰的加權平均,可以消除孤立的噪聲樣例的影響。
__問題一:__近鄰間的距離會被大量的不相關屬性所支配。
應用k-近鄰算法的一個實踐問題是,實例間的距離是根據實例的所有屬性(也就是包含實例的歐氏空間的所有座標軸)計算的。這與那些只選擇全部實例屬性的一個子集的方法不同,例如決策樹學習系統。
比如這樣一個問題:每個實例由20個屬性描述,但在這些屬性中僅有2個與它的分類是有關。在這種情況下,這兩個相關屬性的值一致的實例可能在這個20維的實例空間中相距很遠。結果,依賴這20個屬性的相似性度量會誤導k-近鄰算法的分類。近鄰間的距離會被大量的不相關屬性所支配。這種由於存在很多不相關屬性所導致的難題,有時被稱爲維度災難(curse of dimensionality)。最近鄰方法對這個問題特別敏感。
__解決方法:__當計算兩個實例間的距離時對每個屬性加權。
這相當於按比例縮放歐氏空間中的座標軸,縮短對應於不太相關屬性的座標軸,拉長對應於更相關的屬性的座標軸。每個座標軸應伸展的數量可以通過交叉驗證的方法自動決定。
__問題二:__應用k-近鄰算法的另外一個實踐問題是如何建立高效的索引。因爲這個算法推遲所有的處理,直到接收到一個新的查詢,所以處理每個新查詢可能需要大量的計算。
__解決方法:__目前已經開發了很多方法用來對存儲的訓練樣例進行索引,以便在增加一定存儲開銷情況下更高效地確定最近鄰。一種索引方法是kd-tree(Bentley 1975;Friedman et al. 1977),它把實例存儲在樹的葉結點內,鄰近的實例存儲在同一個或附近的結點內。通過測試新查詢xq的選定屬性,樹的內部結點把查詢xq排列到相關的葉結點。
Python實現KNN算法
這裏實現一個手寫識別算法,這裏只簡單識別0~9數字。
輸入:每個手寫數字已經事先處理成32*32的二進制文本,存儲爲txt文件。每個數字大約有200個樣本。每個樣本保持在一個txt文件中。手寫體圖像本身的大小是32x32的二值圖,轉換到txt文件保存後,內容也是32x32個數字,如下圖所示。目錄trainingDigits存放的是大約2000個訓練數據,testDigits存放大約900個測試數據。
- __函數img2vector:__用來生成將每個樣本的txt文件轉換爲對應的一個向量
# convert image to vector
def img2vector(filename):
rows = 32
cols = 32
imgVector = zeros((1, rows * cols))
fileIn = open(filename)
for row in xrange(rows):
lineStr = fileIn.readline()
for col in xrange(cols):
imgVector[0, row * 32 + col] = int(lineStr[col])
return imgVector
- __函數loadDDataSet:__加載整個數據庫
# load dataSet
def loadDataSet():
## step 1: Getting training set
print "---Getting training set..."
dataSetDir = './'
trainingFileList = os.listdir(dataSetDir + 'trainingDigits') # load the training set
numSamples = len(trainingFileList)
train_x = zeros((numSamples, 1024))
train_y = []
for i in xrange(numSamples):
filename = trainingFileList[i]
# get train_x
train_x[i, :] = img2vector(dataSetDir + 'trainingDigits/%s' % filename)
# get label from file name such as "1_18.txt"
label = int(filename.split('_')[0]) # return 1
train_y.append(label)
## step 2: Getting testing set
print "---Getting testing set..."
testingFileList = os.listdir(dataSetDir + 'testDigits') # load the testing set
numSamples = len(testingFileList)
test_x = zeros((numSamples, 1024))
test_y = []
for i in xrange(numSamples):
filename = testingFileList[i]
# get train_x
test_x[i, :] = img2vector(dataSetDir + 'testDigits/%s' % filename)
# get label from file name such as "1_18.txt"
label = int(filename.split('_')[0]) # return 1
test_y.append(label)
return train_x, train_y, test_x, test_y
- __函數kNNClassify:__實現kNN分類算法
# classify using kNN
def kNNClassify(newInput, dataSet, labels, k):
numSamples = dataSet.shape[0] # shape[0] stands for the num of row
## step 1: calculate Euclidean distance
# tile(A, reps): Construct an array by repeating A reps times
# the following copy numSamples rows for dataSet
diff = tile(newInput, (numSamples, 1)) - dataSet # Subtract element-wise
squaredDiff = diff ** 2 # squared for the subtract
squaredDist = sum(squaredDiff, axis = 1) # sum is performed by row
distance = squaredDist ** 0.5
## step 2: sort the distance
# argsort() returns the indices that would sort an array in a ascending order
sortedDistIndices = argsort(distance)
classCount = {} # define a dictionary (can be append element)
for i in xrange(k):
## step 3: choose the min k distance
voteLabel = labels[sortedDistIndices[i]]
## step 4: count the times labels occur
# when the key voteLabel is not in dictionary classCount, get()
# will return 0
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
## step 5: the max voted class will return
maxCount = 0
for key, value in classCount.items():
if value > maxCount:
maxCount = value
maxIndex = key
return maxIndex
- __函數testHandWritingClass:__測試函數
# test hand writing class
def testHandWritingClass():
## step 1: load data
print "step 1: load data..."
train_x, train_y, test_x, test_y = loadDataSet()
## step 2: training...
print "step 2: training..."
pass
## step 3: testing
print "step 3: testing..."
numTestSamples = test_x.shape[0]
matchCount = 0
for i in xrange(numTestSamples):
predict = kNNClassify(test_x[i], train_x, train_y, 3)
if predict == test_y[i]:
matchCount += 1
accuracy = float(matchCount) / numTestSamples
## step 4: show the result
print "step 4: show the result..."
print 'The classify accuracy is: %.2f%%' % (accuracy * 100)
相似性度量
相似性一般用空間內兩個點的距離來度量。距離越大,表示兩個越不相似。
作爲相似性度量的距離函數一般滿足下列性質:
- d(X,Y)=d(Y,X);
- d(X,Y)≦d(X,Z)+d(Z,Y);
- d(X,Y)≧0;
- d(X,Y)=0,當且僅當X=Y;
這裏,X,Y和Z是對應特徵空間中的三個點。
假設X,Y分別是N維特徵空間中的一個點,其中X=,Y=,d(X,Y)表示相應的距離函數,它給出了X和Y之間的距離測度。
距離的選擇有很多種,常用的距離函數如下:
-
明考斯基(Minkowsky)距離
-
曼哈頓(Manhattan)距離
-
Cityblock距離
-
歐幾里德(Euclidean)距離(歐式距離)
-
Canberra距離
(6)Mahalanobis距離(馬式距離)
d(X,M)給出了特徵空間中的點X和M之間的一種距離測度,其中M爲某一個模式類別的均值向量,∑爲相應模式類別的協方差矩陣。
該距離測度考慮了以M爲代表的模式類別在特徵空間中的總體分佈,能夠緩解由於屬性的線性組合帶來的距離失真。易見,到M的馬式距離爲常數的點組成特徵空間中的一個超橢球面。
-
切比雪夫(Chebyshev)距離
切比雪夫距離或是度量是向量空間中的一種度量,二個點之間的距離定義爲其各座標數值差的最大值。在二維空間中。以和二點爲例,其切比雪夫距離爲
切比雪夫距離或是L∞度量是向量空間中的一種度量,二個點之間的距離定義爲其各座標數值差的最大值。在二維空間中。以(x1,y1)和(x2,y2)二點爲例,其切比雪夫距離爲
-
平均距離
消極學習與積極學習
- 積極學習(Eager Learning)
這種學習方式是指在進行某種判斷(例如,確定一個點的分類或者回歸中確定某個點對應的函數值)之前,先利用訓練數據進行訓練得到一個目標函數,待需要時就只利用訓練好的函數進行決策,顯然這是一種一勞永逸的方法,SVM就屬於這種學習方式。 - 消極學習(Lazy Learning)
這種學習方式指不是根據樣本建立一般化的目標函數並確定其參數,而是簡單地把訓練樣本存儲起來,直到需要分類新的實例時才分析其與所存儲樣例的關係,據此確定新實例的目標函數值。也就是說這種學習方式只有到了需要決策時纔會利用已有數據進行決策,而在這之前不會經歷 Eager Learning所擁有的訓練過程。KNN就屬於這種學習方式。 - 比較
- Eager Learning考慮到了所有訓練樣本,說明它是一個全局的近似,雖然它需要耗費訓練時間,但它的決策時間基本爲0.
- Lazy Learning在決策時雖然需要計算所有樣本與查詢點的距離,但是在真正做決策時卻只用了局部的幾個訓練數據,所以它是一個局部的近似,然而雖然不需要訓練,它的複雜度還是需要 O(n),n 是訓練樣本的個數。由於每次決策都需要與每一個訓練樣本求距離,這引出了Lazy Learning的缺點:(1)需要的存儲空間比較大 (2)決策過程比較慢。
- 典型算法
- 積極學習方法:SVM;Find-S算法;候選消除算法;決策樹;人工神經網絡;貝葉斯方法;
- 消極學習方法:KNN;局部加權迴歸;基於案例的推理;
文獻資料
[1] Trevor Hastie & Rolbert Tibshirani. Discriminant Adaptive Nearest Neighbor Classification. IEEE TRANSACTIONS ON PAITERN ANALYSIS AND MACHINE INTELLIGENCE,1996.
[2] R. Short & K. Fukanaga. A New Nearest Neighbor Distance Measure,Pro. Fifth IEEE Int’l Conf.Pattern Recognition,pp.81-86,1980.
[3] T.M Cover. Nearest Neighbor Pattern Classification,Pro. IEEE Trans,Infomation Theory,1967.
[4] C.J.Stone. Consistent Nonparametric Regression ,Ann.Stat.,vol.3,No.4,pp.595-645,1977.
[5] W Cleveland. Robust Locally-Weighted Regression and Smoothing Scatterplots,J.Am.Statistical.,vol.74,pp.829-836,1979.
[6] T.A.Brown & J.Koplowitz. The Weighted Nearest Neighbor Rule for Class Dependent Sample Sizes,IEEE Tran. Inform.Theory,vol.IT-25,pp.617-619,Sept.1979.
[7] J.P.Myles & D.J.Hand. The Multi-Class Metric Problem in Nearest Neighbor Discrimination Rules,Pattern Recognition,1990.
[8] N.S.Altman. An Introduction to Kernel and Nearest Neighbor Nonparametric Regression,1992.
[9]Min-Ling Zhang & Zhi-Hua Zhou. M1-KNN:A Lazy Learning Approach to Multi-Label Learning,2007.
[10]Peter Hall,Byeong U.Park & Richard J. Samworth. Choice of Neighbor Order In Nearest Neighbor Classification,2008.
[11] Jia Pan & Dinesh Manocha. Bi-Level Locality Sensitive Hashing for K-Nearest Neighbor Computation,2012.