一、kNN算法分析
K最近鄰(k-Nearest Neighbor,KNN)分類算法可以說是最簡單的機器學習算法了。它採用測量不同特徵值之間的距離方法進行分類。它的思想很簡單:如果一個樣本在特徵空間中的k個最相似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。
比如上面這個圖,我們有兩類數據,分別是藍色方塊和紅色三角形,他們分佈在一個上圖的二維中間中。那麼假如我們有一個綠色圓圈這個數據,需要判斷這個數據是屬於藍色方塊這一類,還是與紅色三角形同類。怎麼做呢?我們先把離這個綠色圓圈最近的幾個點找到,因爲我們覺得離綠色圓圈最近的纔對它的類別有判斷的幫助。那到底要用多少個來判斷呢?這個個數就是k了。如果k=3,就表示我們選擇離綠色圓圈最近的3個點來判斷,由於紅色三角形所佔比例爲2/3,所以我們認爲綠色圓是和紅色三角形同類。如果k=5,由於藍色四方形比例爲3/5,因此綠色圓被賦予藍色四方形類。從這裏可以看到,k的值還是很重要的。
KNN算法中,所選擇的鄰居都是已經正確分類的對象。該方法在定類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。由於KNN方法主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來確定所屬類別的,因此對於類域的交叉或重疊較多的待分樣本集來說,KNN方法較其他方法更爲適合。
該算法在分類時有個主要的不足是,當樣本不平衡時,如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數。因此可以採用權值的方法(和該樣本距離小的鄰居權值大)來改進。該方法的另一個不足之處是計算量較大,因爲對每一個待分類的文本都要計算它到全體已知樣本的距離,才能求得它的K個最近鄰點。目前常用的解決方法是事先對已知樣本點進行剪輯,事先去除對分類作用不大的樣本。該算法比較適用於樣本容量比較大的類域的自動分類,而那些樣本容量較小的類域採用這種算法比較容易產生誤分[參考機器學習十大算法]。
總的來說就是我們已經存在了一個帶標籤的數據庫,然後輸入沒有標籤的新數據後,將新數據的每個特徵與樣本集中數據對應的特徵進行比較,然後算法提取樣本集中特徵最相似(最近鄰)的分類標籤。一般來說,只選擇樣本數據庫中前k個最相似的數據。最後,選擇k個最相似數據中出現次數最多的分類。其算法描述如下:
1)計算已知類別數據集中的點與當前點之間的距離;
2)按照距離遞增次序排序;
3)選取與當前點距離最小的k個點;
4)確定前k個點所在類別的出現頻率;
5)返回前k個點出現頻率最高的類別作爲當前點的預測分類。
二、Python實現
對於機器學習而已,Python需要額外安裝三件寶,分別是Numpy,scipy和Matplotlib。前兩者用於數值計算,後者用於畫圖。安裝很簡單,直接到各自的官網下載回來安裝即可。安裝程序會自動搜索我們的python版本和目錄,然後安裝到python支持的搜索路徑下。反正就python和這三個插件都默認安裝就沒問題了。
2.1、kNN基礎實踐
一般實現一個算法後,我們需要先用一個很小的數據庫來測試它的正確性,否則一下子給個大數據給它,它也很難消化,而且還不利於我們分析代碼的有效性。
首先,我們新建一個kNN.py腳本文件,文件裏面包含兩個函數,一個用來生成小數據庫,一個實現kNN分類算法。代碼如下:
from numpy import *
"""
構造數據集
dataSet:數據集 多維數組表示(矩陣)
label:數據相應列標
"""
def createDataSet():
dataSet=array([[1.0, 0.9], [1.0, 1.0], [0.1, 0.2], [0.0, 0.1]])
label=['A','A','B','B']
return dataSet,label
"""
kNN算法實現
傳入參數:
testData:需要計算出列標的數據
dataSet:訓練集
labels:相應列標
k:指定k值
"""
def kNNClassify(testData,dataSet,labels,k):
samplesNum=shape(dataSet)[0]
"""
首先創建與訓練集同樣規模的 測試機矩陣 便於後面計算歐式距離
rows:samplesNum
"""
diff=tile(testData,(samplesNum,1))-dataSet
squareSum=sum(diff**2,axis=1)
sqrt=squareSum**0.5
"""
將得到的距離進行排序
利用argsort()排序得到 相應順序的下標
"""
indices=argsort(sqrt)
"""
取前k個數據 進行統計
"""
classCount={}
for i in range(k):
voteLabel=labels[indices[i]]
classCount[voteLabel]=classCount.get(voteLabel,0)+1
maxNum=0
for label,num in classCount.items():
if(num>maxNum):
maxNum=num
maxLabel=label
return maxLabel
輸出爲:
Your input is: [ 1.2 1. ] and classified to class: A
Your input is: [ 0.1 0.3] and classified to class: B
2.2、kNN進階
這裏我們用kNN來分類一個大點的數據庫,包括數據維度比較大和樣本數比較多的數據庫。這裏我們用到一個手寫數字的數據庫,可以到這裏下載。這個數據庫包括數字0-9的手寫體。每個數字大約有200個樣本。每個樣本保持在一個txt文件中。手寫體圖像本身的大小是32x32的二值圖,轉換到txt文件保存後,內容也是32x32個數字,0或者1,如下:
數據庫解壓後有兩個目錄:目錄trainingDigits存放的是大約2000個訓練數據,testDigits存放大約900個測試數據。
這裏我們還是新建一個kNN.py腳本文件,文件裏面包含四個函數,一個用來生成將每個樣本的txt文件轉換爲對應的一個向量,一個用來加載整個數據庫,一個實現kNN分類算法。最後就是實現這個加載,測試的函數。
具體代碼:
from numpy import *
import kNN
import os
"""
將每張圖片轉換成多維數組的形式
即讀取每個文件--> vector
"""
def img2vec(filename):
rows=32
cols=32
imgvec=zeros((1,1024))
fp=open(filename)
for row in xrange(rows):
lines=fp.readline()
for col in xrange(cols):
imgvec[0,row*32+col]=int(lines[col])
return imgvec
"""
加載訓練集和測試集
traingDataDir:訓練集目錄
testDataDir:測試機目錄
"""
def loadDataSet(traingDataDir,testDataDir):
print 'loading traing files...'
trainfiles=os.listdir(traingDataDir)
traingNum=len(trainfiles)
print 'the num of traing files is: ',traingNum
print 'loading test files...'
testfiles=os.listdir(testDataDir)
testNum=len(testfiles)
print 'the num of test files is: ',testNum
traingData=zeros((traingNum,1024))
traingLabels=[]
testData=zeros((testNum,1024))
testLabels=[]
for i in xrange(traingNum):
filename=trainfiles[i]
traingData[i,:]=img2vec(traingDataDir+'\\'+filename)
traingLabels.append(filename.split('_')[0])
for i in xrange(testNum):
filename=testfiles[i]
testData[i,:]=img2vec(testDataDir+'\\'+filename)
testLabels.append(filename.split('_')[0])
return traingData,traingLabels,testData,testLabels
"""
計算預測準確率
"""
def predict(traingDataDir,testDataDir):
traingData,traingLabels,testData,testLabels=loadDataSet(traingDataDir,testDataDir)
k=10
print 'start predict...'
testNum=testData.shape[0]
corNum=0
for i in xrange(testNum):
voteLabel=kNN.kNNClassify(testData[i],traingData,traingLabels,k)
if voteLabel==testLabels[i]:
corNum+=1
precison=float(corNum)/testNum
print 'the testNum is: %d, the correctNum is: %d ' %(testNum,corNum)
print 'the precision is: %.2f %%' %(precison*100)
測試:
handWritingClassifier.predict(‘D:\python_study\KNN\trainingDigits’,’D:\python_study\KNN\testDigits’)
輸出:
loading traing files…
the num of traing files is: 1934
loading test files…
the num of test files is: 946
start predict…
the testNum is: 946, the correctNum is: 926
the precision is: 97.89 %