1 KNN演算法
1.1 KNN演算法簡介
KNN(K-Nearest Neighbor)工作原理:存在一個樣本資料集合,也稱為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一資料與所屬分類對應的關係。輸入沒有標籤的資料後,將新資料中的每個特徵與樣本集中資料對應的特徵進行比較,提取出樣本集中特徵最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料,這就是k近鄰演算法中k的出處,通常k是不大於20的整數。最後選擇k個最相似資料中出現次數最多的分類作為新資料的分類。
說明:KNN沒有顯示的訓練過程,它是“懶惰學習”的代表,它在訓練階段只是把資料儲存下來,訓練時間開銷為0,等收到測試樣本後進行處理。
舉例:以電影分類作為例子,電影題材可分為愛情片,動作片等,那麼愛情片有哪些特徵?動作片有哪些特徵呢?也就是說給定一部電影,怎麼進行分類?這裡假定將電影分為愛情片和動作片兩類,如果一部電影中接吻鏡頭很多,打鬥鏡頭較少,顯然是屬於愛情片,反之為動作片。有人曾根據電影中打鬥動作和接吻動作數量進行評估,資料如下:
電影名稱 |
打鬥鏡頭 |
接吻鏡頭 |
電影類別 |
Califoria Man |
3 |
104 |
愛情片 |
Beautigul Woman |
1 |
81 |
愛情片 |
Kevin Longblade |
101 |
10 |
動作片 |
Amped II |
98 |
2 |
動作片 |
給定一部電影資料(18,90)打鬥鏡頭18個,接吻鏡頭90個,如何知道它是什麼型別的呢?KNN是這樣做的,首先計算未知電影與樣本集中其他電影的距離(這裡使用曼哈頓距離),資料如下:
電影名稱 |
與未知分類電影的距離 |
Califoria Man |
20.5 |
Beautigul Woman |
19.2 |
Kevin Longblade |
115.3 |
Amped II |
118.9 |
現在我們按照距離的遞增順序排序,可以找到k個距離最近的電影,加入k=3,那麼來看排序的前3個電影的類別,愛情片,愛情片,動作片,下面來進行投票,這部未知的電影愛情片2票,動作片1票,那麼我們就認為這部電影屬於愛情片。
1.2 KNN演算法優缺點
優點:精度高,對異常值不敏感、無資料輸入假定
缺點:計算複雜度高、空間複雜度高
1.3 KNN演算法python程式碼實現
實現步驟:
(1)計算距離
(2)選擇距離最小的k個點
(3)排序
Python 3程式碼:
1 import numpy as np 2 import operator 3 4 def classify(intX,dataSet,labels,k): 5 ''' 6 KNN演算法 7 ''' 8 #numpy中shape[0]返回陣列的行數,shape[1]返回列數 9 dataSetSize = dataSet.shape[0] 10 #將intX在橫向重複dataSetSize次,縱向重複1次 11 #例如intX=([1,2])--->([[1,2],[1,2],[1,2],[1,2]])便於後面計算 12 diffMat = np.tile(intX,(dataSetSize,1))-dataSet 13 #二維特徵相減後乘方 14 sqdifMax = diffMat**2 15 #計算距離 16 seqDistances = sqdifMax.sum(axis=1) 17 distances = seqDistances**0.5 18 print ("distances:",distances) 19 #返回distance中元素從小到大排序後的索引 20 sortDistance = distances.argsort() 21 print ("sortDistance:",sortDistance) 22 classCount = {} 23 for i in range(k): 24 #取出前k個元素的類別 25 voteLabel = labels[sortDistance[i]] 26 print ("第%d個voteLabel=%s",i,voteLabel) 27 classCount[voteLabel] = classCount.get(voteLabel,0)+1 28 #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回預設值。 29 #計算類別次數 30 31 #key=operator.itemgetter(1)根據字典的值進行排序 32 #key=operator.itemgetter(0)根據字典的鍵進行排序 33 #reverse降序排序字典 34 sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse = True) 35 #結果sortedClassCount = [('動作片', 2), ('愛情片', 1)] 36 print ("sortedClassCount:",sortedClassCount) 37 return sortedClassCount[0][0]
2 KNN演算法例項
2.1 KNN實現電影分類
1 import numpy as np 2 import operator 3 4 def createDataset(): 5 #四組二維特徵 6 group = np.array([[5,115],[7,106],[56,11],[66,9]]) 7 #四組對應標籤 8 labels = ('動作片','動作片','愛情片','愛情片') 9 return group,labels 10 11 def classify(intX,dataSet,labels,k): 12 ''' 13 KNN演算法 14 ''' 15 #numpy中shape[0]返回陣列的行數,shape[1]返回列數 16 dataSetSize = dataSet.shape[0] 17 #將intX在橫向重複dataSetSize次,縱向重複1次 18 #例如intX=([1,2])--->([[1,2],[1,2],[1,2],[1,2]])便於後面計算 19 diffMat = np.tile(intX,(dataSetSize,1))-dataSet 20 #二維特徵相減後乘方 21 sqdifMax = diffMat**2 22 #計算距離 23 seqDistances = sqdifMax.sum(axis=1) 24 distances = seqDistances**0.5 25 print ("distances:",distances) 26 #返回distance中元素從小到大排序後的索引 27 sortDistance = distances.argsort() 28 print ("sortDistance:",sortDistance) 29 classCount = {} 30 for i in range(k): 31 #取出前k個元素的類別 32 voteLabel = labels[sortDistance[i]] 33 print ("第%d個voteLabel=%s",i,voteLabel) 34 classCount[voteLabel] = classCount.get(voteLabel,0)+1 35 #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回預設值。 36 #計算類別次數 37 38 #key=operator.itemgetter(1)根據字典的值進行排序 39 #key=operator.itemgetter(0)根據字典的鍵進行排序 40 #reverse降序排序字典 41 sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse = True) 42 #結果sortedClassCount = [('動作片', 2), ('愛情片', 1)] 43 print ("sortedClassCount:",sortedClassCount) 44 return sortedClassCount[0][0] 45 46 47 48 if __name__ == '__main__': 49 group,labels = createDataset() 50 test = [20,101] 51 test_class = classify(test,group,labels,3) 52 print (test_class)
2.2 改進約會網站匹配
這個例子簡單說就是通過KNN找到你喜歡的人,首先資料樣本包含三個特徵,(a)每年獲得的飛行常客里程數(b)玩遊戲消耗的時間(c)每週消耗的冰激淋公升數,樣本資料放在txt中,如下,前三列為三個特徵值,最後一列為標籤
首先讀取資料,獲取資料集和標籤
1 def file2matrix(filename): 2 fr = open(filename) 3 arraylines = fr.readlines() 4 #獲取行數 5 numberoflines = len(arraylines) 6 #返回numpy的資料矩陣,目前矩陣資料為0 7 returnMat = np.zeros([numberoflines,3]) 8 #返回的分類標籤 9 classLabelVector = [] 10 #行的索引 11 index = 0 12 for line in arraylines: 13 #str.strip(rm) 刪除str頭和尾指定的字元 rm為空時,預設刪除空白符(包括'\n','\r','\t',' ') 14 line = line.strip() 15 #每行資料是\t劃分的,將每行資料按照\t進行切片劃分 16 listFromLine = line.split('\t') 17 #取出前三列資料存放到returnMat 18 returnMat[index,:] = listFromLine[0:3] 19 #根據文字中標記的喜歡程度進行分類 20 if listFromLine[-1] == "didntLike": 21 classLabelVector.append(1) 22 elif listFromLine[-1] == "smallDoses": 23 classLabelVector.append(2) 24 else: 25 classLabelVector.append(3) 26 index += 1 27 return returnMat,classLabelVector
資料和標籤我們可以列印一下:
下面用Matplotlib作圖看一下資料資訊:
1 from matplotlib.font_manager import FontProperties 2 import numpy as np 3 import matplotlib.pyplot as plt 4 from prepareData_1 import file2matrix 5 import matplotlib.lines as mlines 6 # from matplotlib.font_manage import FontProperties 7 ''' 8 函式說明:資料視覺化 9 Parameters: 10 datingDataMat - 特徵矩陣 11 datingLabels - 分類標籤向量 12 Returns: 13 無 14 ''' 15 def showDatas(datingDataMat,datingLabels): 16 #設定漢子格式 17 font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) 18 #函式返回一個figure影象和一個子圖ax的array列表。 19 fig,axs = plt.subplots(nrows=2,ncols=2,sharex=False,sharey=False,figsize=(13,8)) 20 21 numberofLabels = len(datingLabels) 22 LabelColors = [] 23 for i in datingLabels: 24 if i==1: 25 LabelColors.append('black') 26 if i ==2: 27 LabelColors.append('orange') 28 if i==3: 29 LabelColors.append("red") 30 #畫散點圖,以資料矩陣的第一列(飛行常客歷程)、第二列(玩遊戲)資料話散點圖 31 #散點大小為15 透明度為0.5 32 axs[0][0].scatter(x=datingDataMat[:,0],y=datingDataMat[:,1],color=LabelColors, 33 s=15,alpha=0.5) 34 axs0_title_text=axs[0][0].set_title(u"每年獲得的飛行里程數與玩視訊遊戲消耗時間佔比", 35 FontProperties=font) 36 axs0_xlabel_text=axs[0][0].set_xlabel("每年獲得的飛行常客里程數",FontProperties=font) 37 axs0_ylabel_text=axs[0][0].set_ylabel("玩遊戲消耗的時間",FontProperties=font) 38 plt.setp(axs0_title_text,size=9,weight='bold',color='red') 39 #畫散點圖,以資料矩陣的第一列(飛行常客歷程)、第三列(冰激淋公斤數)資料話散點圖 40 #散點大小為15 透明度為0.5 41 axs[0][1].scatter(x=datingDataMat[:,0],y=datingDataMat[:,2],color=LabelColors, 42 s=15,alpha=0.5) 43 axs0_title_text=axs[0][0].set_title("每年獲得的飛行里程數與冰激淋公斤數佔比", 44 FontProperties=font) 45 axs0_xlabel_text=axs[0][0].set_xlabel("每年獲得的飛行常客里程數",FontProperties=font) 46 axs0_ylabel_text=axs[0][0].set_ylabel("所吃冰激淋公斤數",FontProperties=font) 47 plt.setp(axs0_title_text,size=9,weight='bold',color='red') 48 #畫散點圖,以資料矩陣的第二列(玩遊戲)、第三列(冰激淋公斤數)資料話散點圖 49 #散點大小為15 透明度為0.5 50 axs[1][0].scatter(x=datingDataMat[:,1],y=datingDataMat[:,2],color=LabelColors, 51 s=15,alpha=0.5) 52 axs0_title_text=axs[0][0].set_title("玩遊戲時間與冰激淋公斤數佔比", 53 FontProperties=font) 54 axs0_xlabel_text=axs[0][0].set_xlabel("每年獲得的飛行常客里程數",FontProperties=font) 55 axs0_ylabel_text=axs[0][0].set_ylabel("所吃冰激淋公斤數",FontProperties=font) 56 plt.setp(axs0_title_text,size=9,weight='bold',color='red') 57 58 #設定圖例 59 didntLike = mlines.Line2D([],[],color='black',marker='.',markersize=6,label='didntlike') 60 smallDose = mlines.Line2D([],[],color='orange',marker='.',markersize=6,label='smallDose') 61 largeDose = mlines.Line2D([],[],color='red',marker='.',markersize=6,label='largeDose') 62 63 #新增圖例 64 axs[0][0].legend(handles=[didntLike,smallDose,largeDose]) 65 axs[0][1].legend(handles=[didntLike,smallDose,largeDose]) 66 axs[1][0].legend(handles=[didntLike,smallDose,largeDose]) 67 68 plt.show() 69 70 if __name__ == '__main__': 71 filename = "datingTestSet.txt" 72 returnMat,classLabelVector = file2matrix(filename) 73 showDatas(returnMat,classLabelVector) 74 75
這裡我把py檔案分開寫了,還要注意txt資料的路徑,高大上的圖:
樣本資料中的到底喜歡什麼樣子的人?自己去分析一下吧。下面要對資料進行歸一化,歸一化的原因就不多說了,
1 from prepareData_1 import file2matrix 2 import numpy as np 3 ''' 4 函式說明:資料歸一化 5 Parameters: 6 dataSet - 特徵矩陣 7 Returns: 8 normDataSet - 歸一化後的特徵矩陣 9 ranges - 資料範圍 10 minVals - 資料最小值 11 ''' 12 13 def autoNorm(dataSet): 14 #獲得資料的最大最小值 15 print (dataSet) 16 print ("**********************") 17 minVals = dataSet.min(0) 18 maxVals = dataSet.max(0) 19 print ("minValues:",minVals) 20 print ("maxValuse:",maxVals) 21 #計算最大最小值的差 22 ranges = maxVals - minVals 23 print () 24 #shape(dataSet)返回dataSet的矩陣行列數 25 normDataSet=np.zeros(np.shape(dataSet)) 26 #返回dataSet的行數 27 m = dataSet.shape[0] 28 #原始值減去最小值 29 normDataSet=dataSet-np.tile(minVals,(m,1)) 30 #除以最大值和最小值的差,得到的歸一化的資料 31 normDataSet = normDataSet/np.tile(ranges,(m,1)) 32 return normDataSet,ranges,minVals
歸一化後的資料如下:
有了以上步驟,下面就可以構建完整的約會分類,去找你喜歡的人了:
1 from prepareData_1 import file2matrix 2 from dataNormal_3 import autoNorm 3 import operator 4 import numpy as np 5 ''' 6 函式說明:knn演算法,分類器 7 Parameters: 8 inX - 用於分類的資料(測試集) 9 dataset - 用於訓練的資料(訓練集) 10 labes - 分類標籤 11 k - knn演算法引數,選擇距離最小的k個點 12 Returns: 13 sortedClassCount[0][0] - 分類結果 14 ''' 15 def classify0(inX,dataset,labes,k): 16 dataSetSize = dataset.shape[0] #返回行數 17 diffMat = np.tile(inX,(dataSetSize,1))-dataset 18 sqDiffMat = diffMat**2 19 sqDistances = sqDiffMat.sum(axis=1) 20 distances = sqDistances**0.5 21 sortedDistIndices =distances.argsort() 22 classCount = {} 23 for i in range(k): 24 voteLabel = labes[sortedDistIndices[i]] 25 classCount[voteLabel] = classCount.get(voteLabel,0)+1 26 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) 27 return sortedClassCount[0][0] 28 def datingClassTest(): 29 #filename="test.txt" 30 filename = "datingTestSet.txt" 31 datingDataMat,datingLabels = file2matrix(filename) 32 #取所有資料的10% 33 hoRatio = 0.1 34 #資料歸一化,返回歸一化後的矩陣,資料範圍,資料最小值 35 normMat,ranges,minVals = autoNorm(datingDataMat) 36 #獲得nornMat的行數 37 m = normMat.shape[0] 38 #百分之十的測試資料的個數 39 numTestVecs = int(m*hoRatio) 40 #分類錯誤計數 41 errorCount = 0.0 42 43 for i in range(numTestVecs): 44 #前numTestVecs個資料作為測試集,後m-numTestVecs個資料作為訓練集 45 classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:], 46 datingLabels[numTestVecs:m],10) 47 print ("分類結果:%d \t真實類別:%d"%(classifierResult,datingLabels[i])) 48 if classifierResult != datingLabels[i]: 49 errorCount += 1.0 50 print ("錯誤率:%f"%(errorCount/float(numTestVecs)*100)) 51 52 if __name__ == '__main__': 53 datingClassTest()
都是上面的步驟,這裡就不解釋了,結果如下所示:
2.3 手寫數字識別
資料可以樣例可以開啟文字檔案進行檢視,其中txt檔名的第一個數字為本txt中的數字,目錄trainingDigits中包含了大約2000個例子,每個數字大約有200個樣本,testDigits中包含900個測試資料,我們使用trainingDigits中的資料訓練分類器,testDigits中的資料作為測試,兩組資料沒有重合。
資料在這裡:https://github.com/Jenny0611/Ml_Learning01
首先我們要將影象資料處理為一個向量,將32*32的二進位制影象資訊轉化為1*1024的向量,再使用前面的分類器,程式碼如下:
1 import numpy as np 2 import operator 3 from os import listdir 4 from sklearn.neighbors import KNeighborsClassifier as kNN 5 6 ''' 7 函式說明:將32*32的二進位制圖片轉換為1*1024向量 8 Parameters: 9 filename - 檔名 10 Returns: 11 returnVect - 返回的二進位制影象的1*1024向量 12 ''' 13 def img2vector(filename): 14 #建立1*1024的0向量 15 returnVect = np.zeros((1,1024)) 16 fr = open(filename) 17 #按行讀取 18 for i in range(32): 19 #讀一行資料 20 lineStr=fr.readline() 21 #每一行的前32個資料依次新增到returnVect 22 for j in range(32): 23 returnVect[0,32*i+j]=int(lineStr[j]) 24 return returnVect 25 26 ''' 27 函式說明:手寫數字分類測試 28 Parameters: 29 filename - 無 30 Returns: 31 returnVect - 無 32 ''' 33 def handwritingClassTest(): 34 #測試集的labels 35 hwLabels=[] 36 #返回trainingDigits目錄下的檔名 37 trainingFileList=listdir('trainingDigits') 38 #返回資料夾下檔案的個數 39 m=len(trainingFileList) 40 #初始化訓練的Mat矩陣的測試集 41 trainingMat=np.zeros((m,1024)) 42 #從檔名中解析出訓練集的類別 43 for i in range(m): 44 fileNameStr=trainingFileList[i] 45 classNumber = int(fileNameStr.split('_')[0]) 46 #將獲取的類別新增到hwLabels中 47 hwLabels.append(classNumber) 48 #將每一個檔案的1*1024資料儲存到trainingMat矩陣中 49 trainingMat[i,:]=img2vector('trainingDigits/%s'%(fileNameStr)) 50 #構建KNN分類器 51 neigh = kNN(n_neighbors=3,algorithm='auto') 52 #擬合模型,trainingMat為測試矩陣,hwLabels為對應的標籤 53 neigh.fit(trainingMat,hwLabels) 54 #返回testDigits目錄下的檔案列表 55 testFileList=listdir('testDigits') 56 errorCount=0.0 57 mTest=len(testFileList) 58 #從檔案中解析出測試集的類別並進行分類測試 59 for i in range(mTest): 60 fileNameStr=testFileList[i] 61 classNumber=int(fileNameStr.split('_')[0]) 62 #獲得測試集的1*1024向量用於訓練 63 vectorUnderTest=img2vector('testDigits/%s'%(fileNameStr)) 64 #獲得預測結果 65 classifierResult=neigh.predict(vectorUnderTest) 66 print ("分類返回結果%d\t真實結果%d"%(classifierResult,classNumber)) 67 if (classNumber != classifierResult): 68 errorCount += 1.0 69 print ("總共錯了%d個\t錯誤率為%f%%"%(errorCount,errorCount/mTest*100)) 70 71 if __name__ == '__main__': 72 handwritingClassTest()
2.4 小結
KNN是簡單有效的分類資料演算法,在使用時必須有訓練樣本資料,還要計算距離,如果資料量非常大會非常消耗空間和時間。它的另一個缺陷是無法給出任何資料的基礎結構資訊,因此我們無法平均例項樣本和典型例項樣本具體特徵,而決策樹將使用概率測量方法處理分類問題,以後章節會介紹。
本文參考:http://blog.csdn.net/c406495762/article/details/75172850
《機器學習實戰》