機器學習之KNN演算法

知其然,知其所以然。發表於2017-09-24

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]
View Code

 

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)
View Code

 

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
View Code

資料和標籤我們可以列印一下:

下面

下面用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             
View Code

這裡我把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
View Code

歸一化後的資料如下:

有了以上步驟,下面就可以構建完整的約會分類,去找你喜歡的人了:

 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()
View Code

都是上面的步驟,這裡就不解釋了,結果如下所示:

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()
View Code

 

2.4 小結

  KNN是簡單有效的分類資料演算法,在使用時必須有訓練樣本資料,還要計算距離,如果資料量非常大會非常消耗空間和時間。它的另一個缺陷是無法給出任何資料的基礎結構資訊,因此我們無法平均例項樣本和典型例項樣本具體特徵,而決策樹將使用概率測量方法處理分類問題,以後章節會介紹。

 

 

 

 

本文參考:http://blog.csdn.net/c406495762/article/details/75172850

     《機器學習實戰》

 

相關文章