運行平台: Windows
Python版本: Python3.x
1.1 k-近鄰法簡介
k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與回歸方法。它的工作原理是:存在一個樣本數據集合,也稱作為訓練樣本集,並且樣本集中每個數據都存在標簽,即我們知道樣本集中每一個數據與所屬分類的對應關系。輸入沒有標簽的新數據后,將新的數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本最相似數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大於20的整數。最后,選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。
舉個簡單的例子,我們可以使用k-近鄰算法分類一個電影是愛情片還是動作片。
電影名稱 | 打斗鏡頭 | 接吻鏡頭 | 電影類型 |
電影1 | 1 | 101 | 愛情片 |
電影2 | 5 | 89 | 愛情片 |
電影3 | 108 | 5 | 動作片 |
電影4 | 115 | 8 | 動作片 |
這個數據集有兩個特征,即打斗鏡頭數和接吻鏡頭數。除此之外,我們也知道每個電影的所屬類型,即分類標簽。用肉眼粗略地觀察,接吻鏡頭多的,是愛情片。打斗鏡頭多的,是動作片。以我們多年的看片經驗,這個分類還算合理。如果現在給我一部電影,你告訴我這個電影打斗鏡頭數和接吻鏡頭數。不告訴我這個電影類型,我可以根據你給我的信息進行判斷,這個電影是屬於愛情片還是動作片。而k-近鄰算法也可以像我們人一樣做到這一點,不同的地方在於,我們的經驗更”牛逼”,而k-鄰近算法是靠已有的數據。比如,你告訴我這個電影打斗鏡頭數為2,接吻鏡頭數為102,我的經驗會告訴你這個是愛情片,k-近鄰算法也會告訴你這個是愛情片。你又告訴我另一個電影打斗鏡頭數為49,接吻鏡頭數為51,我”邪惡”的經驗可能會告訴你,這有可能是個”愛情動作片”,畫面太美,我不敢想象。 (如果說,你不知道”愛情動作片”是什么?請評論留言與我聯系,我需要你這樣像我一樣純潔的朋友。) 但是k-近鄰算法不會告訴你這些,因為在它的眼里,電影類型只有愛情片和動作片,它會提取樣本集中特征最相似數據(最鄰近)的分類標簽,得到的結果可能是愛情片,也可能是動作片,但絕不會是”愛情動作片”。當然,這些取決於數據集的大小以及最近鄰的判斷標准等因素。
那么k-鄰近算法是什么呢?k-近鄰算法步驟如下:
- 計算已知類別數據集中的點與當前點之間的距離;
- 按照距離遞增次序排序;
- 選取與當前點距離最小的k個點;
- 確定前k個點所在類別的出現頻率;
- 返回前k個點所出現頻率最高的類別作為當前點的預測分類。
比如,現在我這個k值取3,那么在電影例子中,按距離依次排序的三個點分別是動作片(108,5)、動作片(115,8)、愛情片(5,89)。在這三個點中,動作片出現的頻率為三分之二,愛情片出現的頻率為三分之一,所以該紅色圓點標記的電影為動作片。這個判別過程就是k-近鄰算法。
1.2 創建kNN_test01.py文件,K-NN的k值為3,編寫代碼如下:
1 # -*- coding: UTF-8 -*- 2 import numpy as np 3 import operator 4 5 def createDataSet(): 6 #四組二維特征 7 group = np.array([[1,101],[5,89],[108,5],[115,8]]) 8 #四組特征的標簽 9 labels = ['愛情片','愛情片','動作片','動作片'] 10 return group, labels 11 12 """ 13 函數說明:kNN算法,分類器 14 15 Parameters: 16 inX - 用於分類的數據(測試集) 17 dataSet - 用於訓練的數據(訓練集) 18 labes - 分類標簽 19 k - kNN算法參數,選擇距離最小的k個點 20 Returns: 21 sortedClassCount[0][0] - 分類結果 22 23 Modify: 24 2017-07-13 25 """ 26 def classify0(inX, dataSet, labels, k): 27 #numpy函數shape[0]返回dataSet的行數 28 dataSetSize = dataSet.shape[0] 29 #在列向量方向上重復inX共1次(橫向),行向量方向上重復inX共dataSetSize次(縱向) 30 diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet 31 #二維特征相減后平方 32 sqDiffMat = diffMat**2 33 #sum()所有元素相加,sum(0)列相加,sum(1)行相加 34 sqDistances = sqDiffMat.sum(axis=1) 35 #開方,計算出距離 36 distances = sqDistances**0.5 37 #返回distances中元素從小到大排序后的索引值 38 sortedDistIndices = distances.argsort() 39 #定一個記錄類別次數的字典 40 classCount = {} 41 for i in range(k): 42 #取出前k個元素的類別 43 voteIlabel = labels[sortedDistIndices[i]] 44 #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。 45 #計算類別次數 46 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 47 #python3中用items()替換python2中的iteritems() 48 #key=operator.itemgetter(1)根據字典的值進行排序 49 #key=operator.itemgetter(0)根據字典的鍵進行排序 50 #reverse降序排序字典 51 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) 52 #返回次數最多的類別,即所要分類的類別 53 return sortedClassCount[0][0] 54 55 if __name__ == '__main__': 56 #創建數據集 57 group, labels = createDataSet() 58 #測試集 59 test = [101,20] 60 #kNN分類 61 test_class = classify0(test, group, labels, 3) 62 #打印分類結果 63 print(test_class)
測試結果:
print(test_class)為動作片。
2.1 k-近鄰算法實戰之約會網站配對效果判定
上一小結學習了簡單的k-近鄰算法的實現方法,但是這並不是完整的k-近鄰算法流程,k-近鄰算法的一般流程:
- 收集數據:可以使用爬蟲進行數據的收集,也可以使用第三方提供的免費或收費的數據。一般來講,數據放在txt文本文件中,按照一定的格式進行存儲,便於解析及處理。
- 准備數據:使用Python解析、預處理數據。
- 分析數據:可以使用很多方法對數據進行分析,例如使用Matplotlib將數據可視化。
- 測試算法:計算錯誤率。
- 使用算法:錯誤率在可接受范圍內,就可以運行k-近鄰算法進行分類。
已經了解了k-近鄰算法的一般流程,下面開始進入實戰內容。
2.2 實戰背景
海倫女士一直使用在線約會網站尋找適合自己的約會對象。盡管約會網站會推薦不同的任選,但她並不是喜歡每一個人。經過一番總結,她發現自己交往過的人可以進行如下分類:
- 不喜歡的人
- 魅力一般的人
- 極具魅力的人
海倫收集約會數據已經有了一段時間,她把這些數據存放在文本文件datingTestSet.txt中,每個樣本數據占據一行,總共有1000行。
海倫收集的樣本數據主要包含以下3種特征:
- 每年獲得的飛行常客里程數
- 玩視頻游戲所消耗時間百分比
- 每周消費的冰淇淋公升數
didntLike:不喜歡 largeDoses:極具魅力 smallDOses:魅力一般
數據下載地址:https://github.com/Jack-Cherish/Machine-Learning/tree/master/kNN/2.%E6%B5%B7%E4%BC%A6%E7%BA%A6%E4%BC%9A
2.3 分析數據:數據可視化
在kNN_test02.py文件中編寫名為showdatas的函數,用來將數據可視化
1 import matplotlib.lines as mlines 2 import matplotlib.pyplot as plt 3 import numpy as np 4 5 6 """ 7 函數說明:打開並解析文件,對數據進行分類:1代表不喜歡,2代表魅力一般,3代表極具魅力 8 9 Parameters: 10 filename - 文件名 11 Returns: 12 returnMat - 特征矩陣 13 classLabelVector - 分類Label向量 14 15 Modify: 16 2017-12-28 17 """ 18 def file2matrix(filename): 19 #打開文件 20 fr = open(filename) 21 #讀取文件所有內容 22 arrayOLines = fr.readlines() 23 #得到文件行數 24 numberOfLines = len(arrayOLines) 25 #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 26 returnMat = np.zeros((numberOfLines,3)) 27 #返回的分類標簽向量 28 classLabelVector = [] 29 #行的索引值 30 index = 0 31 for line in arrayOLines: 32 #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') 33 line = line.strip() 34 #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 35 listFromLine = line.split('\t') 36 #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特征矩陣 37 returnMat[index,:] = listFromLine[0:3] 38 #根據文本中標記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力 39 if listFromLine[-1] == 'didntLike': 40 classLabelVector.append(1) 41 elif listFromLine[-1] == 'smallDoses': 42 classLabelVector.append(2) 43 elif listFromLine[-1] == 'largeDoses': 44 classLabelVector.append(3) 45 index += 1 46 #print(returnMat) 47 return returnMat, classLabelVector 48 49 def showdatas(returnMat, classLabelVector): 50 #設置漢字格式 51 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標簽 52 plt.rcParams['font.serif'] = ['SimHei'] 53 plt.rcParams['axes.unicode_minus'] = False # 用控制中文亂碼 54 #將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8) 55 #當nrow=2,nclos=2時,代表fig畫布被分為四個區域,axs[0][0]表示第一行第一個區域 56 fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8)) 57 numberOfLabels = len(classLabelVector) 58 LabelsColors = [] 59 for i in classLabelVector: 60 if i == 1: 61 LabelsColors.append('black') 62 if i == 2: 63 LabelsColors.append('orange') 64 if i == 3: 65 LabelsColors.append('red') 66 #畫出散點圖,以returnMat矩陣的第一(飛行常客例程)、第二列(玩游戲)數據畫散點數據,散點大小為15,透明度為0.5 67 axs[0][0].scatter(x=returnMat[:,0], y=returnMat[:,1], color=LabelsColors,s=30, alpha=.5) 68 #設置標題,x軸label,y軸label 69 axs0_title_text = axs[0][0].set_title('每年獲得的飛行常客里程數與玩視頻游戲所消耗時間占比') 70 axs0_xlabel_text = axs[0][0].set_xlabel('每年獲得的飛行常客里程數') 71 axs0_ylabel_text = axs[0][0].set_ylabel('玩視頻游戲所消耗時間占') 72 plt.setp(axs0_title_text, size=12, weight='bold', color='red') 73 plt.setp(axs0_xlabel_text, size=9, weight='bold', color='black') 74 plt.setp(axs0_ylabel_text, size=9, weight='bold', color='black') 75 76 #畫出散點圖,以returnMat矩陣的第一(飛行常客例程)、第三列(冰激凌)數據畫散點數據,散點大小為15,透明度為0.5 77 axs[0][1].scatter(x=returnMat[:,0], y=returnMat[:,2], color=LabelsColors,s=30, alpha=.5) 78 #設置標題,x軸label,y軸label 79 axs1_title_text = axs[0][1].set_title('每年獲得的飛行常客里程數與每周消費的冰激淋公升數') 80 axs1_xlabel_text = axs[0][1].set_xlabel('每年獲得的飛行常客里程數') 81 axs1_ylabel_text = axs[0][1].set_ylabel('每周消費的冰激淋公升數') 82 plt.setp(axs1_title_text, size=12, weight='bold', color='red') 83 plt.setp(axs1_xlabel_text, size=9, weight='bold', color='black') 84 plt.setp(axs1_ylabel_text, size=9, weight='bold', color='black') 85 86 #畫出散點圖,以returnMat矩陣的第二(玩游戲)、第三列(冰激凌)數據畫散點數據,散點大小為15,透明度為0.5 87 axs[1][0].scatter(x=returnMat[:,1], y=returnMat[:,2], color=LabelsColors,s=30, alpha=.5) 88 #設置標題,x軸label,y軸label 89 axs2_title_text = axs[1][0].set_title('玩視頻游戲所消耗時間占比與每周消費的冰激淋公升數') 90 axs2_xlabel_text = axs[1][0].set_xlabel('玩視頻游戲所消耗時間占比') 91 axs2_ylabel_text = axs[1][0].set_ylabel('每周消費的冰激淋公升數') 92 plt.setp(axs2_title_text, size=12, weight='bold', color='red') 93 plt.setp(axs2_xlabel_text, size=9, weight='bold', color='black') 94 plt.setp(axs2_ylabel_text, size=9, weight='bold', color='black') 95 #設置圖例 96 didntLike = mlines.Line2D([], [], color='black', marker='.', 97 markersize=6, label='didntLike') 98 smallDoses = mlines.Line2D([], [], color='orange', marker='.', 99 markersize=6, label='smallDoses') 100 largeDoses = mlines.Line2D([], [], color='red', marker='.', 101 markersize=6, label='largeDoses') 102 #添加圖例 103 axs[0][0].legend(handles=[didntLike,smallDoses,largeDoses], loc='upper right') 104 axs[0][1].legend(handles=[didntLike,smallDoses,largeDoses], loc='upper right') 105 axs[1][0].legend(handles=[didntLike,smallDoses,largeDoses], loc='upper right') 106 #顯示圖片 107 plt.show() 108 109 """ 110 函數說明:main函數 111 112 Parameters: 113 無 114 Returns: 115 無 116 117 Modify: 118 2017-12-28 119 """ 120 if __name__ == '__main__': 121 #打開的文件名 122 filename = "datingTestSet.txt" 123 #打開並處理數據 124 returnMat, classLabelVector = file2matrix(filename) 125 showdatas(returnMat, classLabelVector)
運行結果:
通過數據可以很直觀的發現數據的規律,比如以玩游戲所消耗時間占比與每年獲得的飛行常客里程數,只考慮這二維的特征信息,給我的感覺就是海倫喜歡有生活質量的男人。為什么這么說呢?每年獲得的飛行常客里程數表明,海倫喜歡能享受飛行常客獎勵計划的男人,但是不能經常坐飛機,疲於奔波,滿世界飛。同時,這個男人也要玩視頻游戲,並且占一定時間比例。能到處飛,又能經常玩游戲的男人是什么樣的男人?很顯然,有生活質量,並且生活悠閑的人。我的分析,僅僅是通過可視化的數據總結的個人看法。我想,每個人的感受應該也是不盡相同。
2.4 使用算法:構建完整可用系統
我們可以給海倫一個小段程序,通過該程序海倫會在約會網站上找到某個人並輸入他的信息。程序會給出她對男方喜歡程度的預測值。
代碼如下:
1 #!/usr/bin/env python 2 # _*_ coding:utf-8 _*_ 3 4 import numpy as np 5 import operator 6 7 """ 8 函數說明:kNN算法,分類器 9 10 Parameters: 11 inX - 用於分類的數據(測試集) 12 dataSet - 用於訓練的數據(訓練集) 13 labes - 分類標簽 14 k - kNN算法參數,選擇距離最小的k個點 15 Returns: 16 sortedClassCount[0][0] - 分類結果 17 18 Modify: 19 2017-12-28 20 """ 21 def classify0(inX, dataSet, labels, k): 22 #numpy函數shape[0]返回dataSet的行數 23 dataSetSize = dataSet.shape[0] 24 #在列向量方向上重復inX共1次(橫向),行向量方向上重復inX共dataSetSize次(縱向) 25 diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet 26 #二維特征相減后平方 27 sqDiffMat = diffMat**2 28 #sum()所有元素相加,sum(0)列相加,sum(1)行相加 29 sqDistances = sqDiffMat.sum(axis=1) 30 #開方,計算出距離 31 distances = sqDistances**0.5 32 #返回distances中元素從小到大排序后的索引值 33 sortedDistIndices = distances.argsort() 34 #定一個記錄類別次數的字典 35 classCount = {} 36 for i in range(k): 37 #取出前k個元素的類別 38 voteIlabel = labels[sortedDistIndices[i]] 39 #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。 40 #計算類別次數 41 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 42 #python3中用items()替換python2中的iteritems() 43 #key=operator.itemgetter(1)根據字典的值進行排序 44 #key=operator.itemgetter(0)根據字典的鍵進行排序 45 #reverse降序排序字典 46 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) 47 #返回次數最多的類別,即所要分類的類別 48 return sortedClassCount[0][0] 49 50 51 """ 52 函數說明:打開並解析文件,對數據進行分類:1代表不喜歡,2代表魅力一般,3代表極具魅力 53 54 Parameters: 55 filename - 文件名 56 Returns: 57 returnMat - 特征矩陣 58 classLabelVector - 分類Label向量 59 60 Modify: 61 2017-12-28 62 """ 63 def file2matrix(filename): 64 #打開文件 65 fr = open(filename) 66 #讀取文件所有內容 67 arrayOLines = fr.readlines() 68 #得到文件行數 69 numberOfLines = len(arrayOLines) 70 #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 71 returnMat = np.zeros((numberOfLines,3)) 72 #返回的分類標簽向量 73 classLabelVector = [] 74 #行的索引值 75 index = 0 76 for line in arrayOLines: 77 #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') 78 line = line.strip() 79 #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 80 listFromLine = line.split('\t') 81 #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特征矩陣 82 returnMat[index,:] = listFromLine[0:3] 83 #根據文本中標記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力 84 if listFromLine[-1] == 'didntLike': 85 classLabelVector.append(1) 86 elif listFromLine[-1] == 'smallDoses': 87 classLabelVector.append(2) 88 elif listFromLine[-1] == 'largeDoses': 89 classLabelVector.append(3) 90 index += 1 91 return returnMat, classLabelVector 92 93 """ 94 函數說明:對數據進行歸一化 95 96 Parameters: 97 dataSet - 特征矩陣 98 Returns: 99 normDataSet - 歸一化后的特征矩陣 100 ranges - 數據范圍 101 minVals - 數據最小值 102 103 Modify: 104 2017-12-28 105 """ 106 def autoNorm(dataSet): 107 #獲得數據的最小值 108 minVals = dataSet.min(0) 109 maxVals = dataSet.max(0) 110 #最大值和最小值的范圍 111 ranges = maxVals - minVals 112 #shape(dataSet)返回dataSet的矩陣行列數 113 normDataSet = np.zeros(np.shape(dataSet)) 114 #返回dataSet的行數 115 m = dataSet.shape[0] 116 #原始值減去最小值 117 normDataSet = dataSet - np.tile(minVals, (m, 1)) 118 #除以最大和最小值的差,得到歸一化數據 119 normDataSet = normDataSet / np.tile(ranges, (m, 1)) 120 #返回歸一化數據結果,數據范圍,最小值 121 return normDataSet, ranges, minVals 122 123 """ 124 函數說明:通過輸入一個人的三維特征,進行分類輸出 125 126 Parameters: 127 無 128 Returns: 129 無 130 131 Modify: 132 2017-12-28 133 """ 134 def classifyPerson(): 135 #輸出結果 136 resultList = ['討厭','有些喜歡','非常喜歡'] 137 #三維特征用戶輸入 138 precentTats = float(input("每年獲得的飛行常客里程數:")) 139 ffMiles = float(input("玩視頻游戲所耗時間百分比:")) 140 iceCream = float(input("每周消費的冰激淋公升數:")) 141 #打開的文件名 142 filename = "datingTestSet.txt" 143 #打開並處理數據 144 datingDataMat, datingLabels = file2matrix(filename) 145 #訓練集歸一化 146 normMat, ranges, minVals = autoNorm(datingDataMat) 147 #生成NumPy數組,測試集 148 inArr = np.array([precentTats, ffMiles, iceCream]) 149 150 #測試集歸一化 151 norminArr = (inArr - minVals) / ranges 152 153 #返回分類結果 154 classifierResult = classify0(norminArr, normMat, datingLabels, 3) 155 156 #打印結果 157 print("你可能%s這個人" % (resultList[classifierResult-1])) 158 159 """ 160 函數說明:main函數 161 162 Parameters: 163 無 164 Returns: 165 無 166 167 Modify: 168 2017-12-28 169 """ 170 if __name__ == '__main__': 171 classifyPerson()
並輸入數據(44000,12,,0.5),預測結果是”你可能有些喜歡這個人”,也就是這個人魅力一般。一共有三個檔次:討厭、有些喜歡、非常喜歡,對應着不喜歡的人、魅力一般的人、極具魅力的人
3.1 kNN算法的優缺點
優點
- 簡單好用,容易理解,精度高,理論成熟,既可以用來做分類也可以用來做回歸;
- 可用於數值型數據和離散型數據;
- 訓練時間復雜度為O(n);無數據輸入假定;
- 對異常值不敏感。
缺點:
- 計算復雜性高;空間復雜性高;
- 樣本不平衡問題(即有些類別的樣本數量很多,而其它樣本的數量很少);
- 一般數值很大的時候不用這個,計算量太大。但是單個樣本又不能太少,否則容易發生誤分。
- 最大的缺點是無法給出數據的內在含義。
博客轉至:http://blog.csdn.net/c406495762/article/details/75172850