KNN 演算法其實簡單的說就是“物以類聚”,也就是將新的沒有被分類的點分類為周圍的點中大多數屬於的類。它採用測量不同特徵值之間的距離方法進行分類,思想很簡單:如果一個樣本的特徵空間中最為臨近(歐式距離進行判斷)的K個點大都屬於某一個類,那麼該樣本就屬於這個類。這就是物以類聚的思想。
當然,實際中,不同的K取值會影響到分類效果,並且在K個臨近點的選擇中,都不加意外的認為這K個點都是已經分類好的了,否則該演算法也就失去了物以類聚的意義了。
KNN演算法的不足點:
1、當樣本不平衡時,比如一個類的樣本容量很大,其他類的樣本容量很小,輸入一個樣本的時候,K個臨近值中大多數都是大樣本容量的那個類,這時可能就會導致分類錯誤。改進方法是對K臨近點進行加權,也就是距離近的點的權值大,距離遠的點權值小。
2、計算量較大,每個待分類的樣本都要計算它到全部點的距離,根據距離排序才能求得K個臨近點,改進方法是:先對已知樣本點進行剪輯,事先去除對分類作用不大的樣本。
適用性:
適用於樣本容量比較大的類域的自動分類,而樣本容量較小的類域則容易誤分
演算法描述:
1、計算已知類別資料集合彙總的點與當前點的距離
2、按照距離遞增次序排序
3、選取與當前點距離最近的K個點
4、確定距離最近的前K個點所在類別的出現頻率
5、返回距離最近的前K個點中頻率最高的類別作為當前點的預測分類
Python 實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from numpy import * import operator def createDataSet(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) lables = ['A','A','B','B'] return group,lables # KNN 分類演算法 def classify0(inx,dataSet,labels,k): dataSetSize = dataSet.shape[0] # shape[0]獲取行 shape[1] 獲取列 # 第一步,計算歐式距離 diffMat = tile(inx,(dataSetSize,1)) - dataSet #tile類似於matlab中的repmat,複製矩陣 sqDiffMat = diffMat ** 2 sqDistances = sqDiffMat.sum(axis=1) distance = sqDistances ** 0.5 sortedDistIndecies = distance.argsort() # 增序排序 classCount = {} for i in range(k): # 獲取類別 voteIlabel = labels[sortedDistIndecies[i]] #字典的get方法,查詢classCount中是否包含voteIlabel,是則返回該值,不是則返回defValue,這裡是0 # 其實這也就是計算K臨近點中出現的類別的頻率,以次數體現 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # 對字典中的類別出現次數進行排序,classCount中儲存的事 key-value,其中key就是label,value就是出現的次數 # 所以key=operator.itemgetter(1)選中的事value,也就是對次數進行排序 sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True) #sortedClassCount[0][0]也就是排序後的次數最大的那個label return sortedClassCount[0][0] |
呼叫方式:
1 2 3 4 5 6 |
import sys; sys.path.append("/home/llp/code/funcdef") import KNN group,labels = KNN.createDataSet(); relust = KNN.classify0([0,0],group,labels,3) print 'the classify relust is :' , relust |
Matlab 實現
這裡以一個完整例項呈現,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function relustLabel = KNN(inx,data,labels,k) %% % inx 為 輸入測試資料,data為樣本資料,labels為樣本標籤 %% [datarow , datacol] = size(data); diffMat = repmat(inx,[datarow,1]) - data ; distanceMat = sqrt(sum(diffMat.^2,2)); [B , IX] = sort(distanceMat,'ascend'); len = min(k,length(B)); relustLabel = mode(labels(IX(1:len))); end |
可以看到,整個KNN演算法的Matlab程式碼也就只有6行,比Python少很多,這其中要得益於Matlab成熟的矩陣計算和很多成熟的函式。
實際呼叫中,我們利用一個資料集進行測試,該資料集是由1000個樣本的3維座標組成,共分為3個類
首先視覺化我們的資料集,看看它的分佈:
第一維和第二維:可以清晰地看到資料大致上分為 3 類
第1維和第3維:從這個角度看,3類的分佈就有點重疊了,這是因為我們的視角原因
畫出3維,看看它的廬山真面目:
由於我們已經編寫了KNN程式碼,接下來我們只需呼叫就行。瞭解過機器學習的人都應該知道,很多樣本資料在代入演算法之前都應該進行歸一化,這裡我們將資料歸一化在[0,1]的區間內,歸一化方式如下:
newData = (oldData-minValue)/(maxValue-minValue)
其中,maxValue為oldData的最大值,minValue為 oldData 的最小值。
同時,我們對於1000個資料集,採取10%的資料進行測試,90%的資料進行訓練的方式,由於本測試資料之間完全獨立,可以隨機抽取10%的資料作為測試資料,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function KNNdatgingTest %% clc clear close all %% data = load('datingTestSet2.txt'); dataMat = data(:,1:3); labels = data(:,4); len = size(dataMat,1); k = 4; error = 0; % 測試資料比例 Ratio = 0.1; numTest = Ratio * len; % 歸一化處理 maxV = max(dataMat); minV = min(dataMat); range = maxV-minV; newdataMat = (dataMat-repmat(minV,[len,1]))./(repmat(range,[len,1])); % 測試 for i = 1:numTest classifyresult = KNN(newdataMat(i,:),newdataMat(numTest:len,:),labels(numTest:len,:),k); fprintf('測試結果為:%d 真實結果為:%d\n',[classifyresult labels(i)]) if(classifyresult~=labels(i)) error = error+1; end end fprintf('準確率為:%f\n',1-error/(numTest)) end |
當我們選擇K為4的時候,準確率為:97%
KNN進階
接下來我們將運用KNN演算法實現一個手寫識別系統,訓練資料集大約2000個樣本,每個數字大概有200個樣本
測試資料大概有900個樣本,由於每個樣本都是一個32×32的數字,我們將其轉換為1×1024的矩陣,方便我們利用KNN演算法
資料如下:
由於資料量比較大,載入資料的時候回花一點時間,具體程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function handWritingTest %% clc clear close all %% 獲取目錄下的所有txt檔名稱 d = dir(['digits/trainingDigits/' '*.txt']); % struct 型別 dircell = struct2cell(d); %cell 型別 trainSetLen = size(dircell,2); K = 4; dataSize = 1024; trainLabels = zeros(trainSetLen,1); trainSet = []; simpleTrainSet = zeros(1,dataSize); simpleTestSet = zeros(1,dataSize); %% 載入資料 fprintf('loading data...') for i = 1:trainSetLen trainName = dircell(1,i); trainFilename = cell2mat(trainName); trainLabels(i) = str2num(trainFilename(1)); fid = fopen(['digits/trainingDigits/' trainFilename],'r'); traindata = fscanf(fid,'%s'); for j = 1:dataSize simpleTrainSet(j) = str2num(traindata(j)); end trainSet = [trainSet ; simpleTrainSet]; fclose(fid); end d = dir(['digits/testDigits/' '*.txt']); % struct 型別 dircell = struct2cell(d); %cell 型別 testSetLen = size(dircell,2); error = 0; %% 測試資料 for k = 1:testSetLen testName = dircell(1,k); testFilename = cell2mat(testName); testLabels = str2num(testFilename(1)); fid = fopen(['digits/testDigits/' testFilename],'r'); testdata = fscanf(fid,'%s'); for j = 1:dataSize simpleTestSet(j) = str2num(testdata(j)); end classifyResult = KNN(simpleTestSet,trainSet,trainLabels,K); fprintf('識別數字為:%d 真實數字為:%d\n' , [classifyResult , testLabels]) if(classifyResult~=testLabels) error = error+1; end fclose(fid); end fprintf('識別準確率為:%f\n',1-error/testSetLen) end |
不同的K識別準確率稍有不同,當K為4的時候,準確率為 98.31%