轉自http://blog.csdn.net/c406495762/article/details/75172850 html
版權聲明:本文爲博主原創文章,未經博主容許不得轉載。python
轉載請註明做者和出處:http://blog.csdn.net/c406495762
運行平臺: Windows
Python版本: Python3.x
IDE: Sublime text3github
本文將從k-鄰近算法的思想開始講起,使用python3一步一步編寫代碼進行實戰訓練。而且,我也提供了相應的數據集,對代碼進行了詳細的註釋。除此以外,本文也對sklearn實現k-鄰近算法的方法進行了講解。實戰實例:電影類別分類、約會網站配對效果斷定、手寫數字識別。算法
本文出現的全部代碼和數據集,都可在個人github上下載,歡迎Follow、Star:https://github.com/Jack-Cherish/Machine-Learning/tree/master/kNNwindows
若是對於代碼理解不夠的,能夠結合本文,觀看由南京航空航天大學碩士:深度眸,爲你們免費錄製的視頻:數組
k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。它的工做原理是:存在一個樣本數據集合,也稱做爲訓練樣本集,而且樣本集中每一個數據都存在標籤,即咱們知道樣本集中每個數據與所屬分類的對應關係。輸入沒有標籤的新數據後,將新的數據的每一個特徵與樣本集中數據對應的特徵進行比較,而後算法提取樣本最類似數據(最近鄰)的分類標籤。通常來講,咱們只選擇樣本數據集中前k個最類似的數據,這就是k-近鄰算法中k的出處,一般k是不大於20的整數。最後,選擇k個最類似數據中出現次數最多的分類,做爲新數據的分類。markdown
舉個簡單的例子,咱們可使用k-近鄰算法分類一個電影是愛情片仍是動做片。數據結構
電影名稱 | 打鬥鏡頭 | 接吻鏡頭 | 電影類型 |
---|---|---|---|
電影1 | 1 | 101 | 愛情片 |
電影2 | 5 | 89 | 愛情片 |
電影3 | 108 | 5 | 動做片 |
電影4 | 115 | 8 | 動做片 |
表1.1就是咱們已有的數據集合,也就是訓練樣本集。這個數據集有兩個特徵,即打鬥鏡頭數和接吻鏡頭數。除此以外,咱們也知道每一個電影的所屬類型,即分類標籤。用肉眼粗略地觀察,接吻鏡頭多的,是愛情片。打鬥鏡頭多的,是動做片。以咱們多年的看片經驗,這個分類還算合理。若是如今給我一部電影,你告訴我這個電影打鬥鏡頭數和接吻鏡頭數。不告訴我這個電影類型,我能夠根據你給個人信息進行判斷,這個電影是屬於愛情片仍是動做片。而k-近鄰算法也能夠像咱們人同樣作到這一點,不一樣的地方在於,咱們的經驗更」牛逼」,而k-鄰近算法是靠已有的數據。好比,你告訴我這個電影打鬥鏡頭數爲2,接吻鏡頭數爲102,個人經驗會告訴你這個是愛情片,k-近鄰算法也會告訴你這個是愛情片。你又告訴我另外一個電影打鬥鏡頭數爲49,接吻鏡頭數爲51,我」邪惡」的經驗可能會告訴你,這有多是個」愛情動做片」,畫面太美,我不敢想象。 (若是說,你不知道」愛情動做片」是什麼?請評論留言與我聯繫,我須要你這樣像我同樣純潔的朋友。) 可是k-近鄰算法不會告訴你這些,由於在它的眼裏,電影類型只有愛情片和動做片,它會提取樣本集中特徵最類似數據(最鄰近)的分類標籤,獲得的結果多是愛情片,也多是動做片,但毫不會是」愛情動做片」。固然,這些取決於數據集的大小以及最近鄰的判斷標準等因素。app
咱們已經知道k-近鄰算法根據特徵比較,而後提取樣本集中特徵最類似數據(最鄰近)的分類標籤。那麼,如何進行比較呢?好比,咱們仍是以表1.1爲例,怎麼判斷紅色圓點標記的電影所屬的類別呢?如圖1.1所示。
咱們能夠從散點圖大體推斷,這個紅色圓點標記的電影可能屬於動做片,由於距離已知的那兩個動做片的圓點更近。k-近鄰算法用什麼方法進行判斷呢?沒錯,就是距離度量。這個電影分類的例子有2個特徵,也就是在2維實數向量空間,可使用咱們高中學過的兩點距離公式計算距離,如圖1.2所示。
經過計算,咱們能夠獲得以下結果:
經過計算可知,紅色圓點標記的電影到動做片 (108,5)的距離最近,爲16.55。若是算法直接根據這個結果,判斷該紅色圓點標記的電影爲動做片,這個算法就是最近鄰算法,而非k-近鄰算法。那麼k-鄰近算法是什麼呢?k-近鄰算法步驟以下:
好比,如今我這個k值取3,那麼在電影例子中,按距離依次排序的三個點分別是動做片(108,5)、動做片(115,8)、愛情片(5,89)。在這三個點中,動做片出現的頻率爲三分之二,愛情片出現的頻率爲三分之一,因此該紅色圓點標記的電影爲動做片。這個判別過程就是k-近鄰算法。
咱們已經知道了k-近鄰算法的原理,那麼接下來就是使用Python3實現該算法,依然以電影分類爲例。
對於表1.1中的數據,咱們可使用numpy直接建立,代碼以下:
# -*- coding: UTF-8 -*- import numpy as np """ 函數說明:建立數據集 Parameters: 無 Returns: group - 數據集 labels - 分類標籤 Modify: 2017-07-13 """ def createDataSet(): #四組二維特徵 group = np.array([[1,101],[5,89],[108,5],[115,8]]) #四組特徵的標籤 labels = ['愛情片','愛情片','動做片','動做片'] return group, labels if __name__ == '__main__': #建立數據集 group, labels = createDataSet() #打印數據集 print(group) print(labels)
運行結果,如圖1.3所示:
根據兩點距離公式,計算距離,選擇距離最小的前k個點,並返回分類結果。
# -*- coding: UTF-8 -*- import numpy as np import operator """ 函數說明:kNN算法,分類器 Parameters: inX - 用於分類的數據(測試集) dataSet - 用於訓練的數據(訓練集) labes - 分類標籤 k - kNN算法參數,選擇距離最小的k個點 Returns: sortedClassCount[0][0] - 分類結果 Modify: 2017-07-13 """ def classify0(inX, dataSet, labels, k): #numpy函數shape[0]返回dataSet的行數 dataSetSize = dataSet.shape[0] #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向) diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #二維特徵相減後平方 sqDiffMat = diffMat**2 #sum()全部元素相加,sum(0)列相加,sum(1)行相加 sqDistances = sqDiffMat.sum(axis=1) #開方,計算出距離 distances = sqDistances**0.5 #返回distances中元素從小到大排序後的索引值 sortedDistIndices = distances.argsort() #定一個記錄類別次數的字典 classCount = {} for i in range(k): #取出前k個元素的類別 voteIlabel = labels[sortedDistIndices[i]] #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,若是值不在字典中返回默認值。 #計算類別次數 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #python3中用items()替換python2中的iteritems() #key=operator.itemgetter(1)根據字典的值進行排序 #key=operator.itemgetter(0)根據字典的鍵進行排序 #reverse降序排序字典 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #返回次數最多的類別,即所要分類的類別 return sortedClassCount[0][0]
這裏預測紅色圓點標記的電影(101,20)的類別,K-NN的k值爲3。建立kNN_test01.py文件,編寫代碼以下:
# -*- coding: UTF-8 -*- import numpy as np import operator """ 函數說明:建立數據集 Parameters: 無 Returns: group - 數據集 labels - 分類標籤 Modify: 2017-07-13 """ def createDataSet(): #四組二維特徵 group = np.array([[1,101],[5,89],[108,5],[115,8]]) #四組特徵的標籤 labels = ['愛情片','愛情片','動做片','動做片'] return group, labels """ 函數說明:kNN算法,分類器 Parameters: inX - 用於分類的數據(測試集) dataSet - 用於訓練的數據(訓練集) labes - 分類標籤 k - kNN算法參數,選擇距離最小的k個點 Returns: sortedClassCount[0][0] - 分類結果 Modify: 2017-07-13 """ def classify0(inX, dataSet, labels, k): #numpy函數shape[0]返回dataSet的行數 dataSetSize = dataSet.shape[0] #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向) diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #二維特徵相減後平方 sqDiffMat = diffMat**2 #sum()全部元素相加,sum(0)列相加,sum(1)行相加 sqDistances = sqDiffMat.sum(axis=1) #開方,計算出距離 distances = sqDistances**0.5 #返回distances中元素從小到大排序後的索引值 sortedDistIndices = distances.argsort() #定一個記錄類別次數的字典 classCount = {} for i in range(k): #取出前k個元素的類別 voteIlabel = labels[sortedDistIndices[i]] #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,若是值不在字典中返回默認值。 #計算類別次數 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #python3中用items()替換python2中的iteritems() #key=operator.itemgetter(1)根據字典的值進行排序 #key=operator.itemgetter(0)根據字典的鍵進行排序 #reverse降序排序字典 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #返回次數最多的類別,即所要分類的類別 return sortedClassCount[0][0] if __name__ == '__main__': #建立數據集 group, labels = createDataSet() #測試集 test = [101,20] #kNN分類 test_class = classify0(test, group, labels, 3) #打印分類結果 print(test_class)
運行結果,如圖1.4所示:
能夠看到,分類結果根據咱們的」經驗」,是正確的,儘管這種分類比較耗時,用時1.4s。
到這裏,也許有人早已經發現,電影例子中的特徵是2維的,這樣的距離度量能夠用兩 點距離公式計算,可是若是是更高維的呢?對,沒錯。咱們能夠用歐氏距離(也稱歐幾里德度量),如圖1.5所示。咱們高中所學的兩點距離公式就是歐氏距離在二維空間上的公式,也就是歐氏距離的n的值爲2的狀況。
看到這裏,有人可能會問:「分類器何種狀況下會出錯?」或者「答案是否老是正確的?」答案是否認的,分類器並不會獲得百分百正確的結果,咱們可使用多種方法檢測分類器的正確率。此外分類器的性能也會受到多種因素的影響,如分類器設置和數據集等。不一樣的算法在不一樣數據集上的表現可能徹底不一樣。爲了測試分類器的效果,咱們可使用已知答案的數據,固然答案不能告訴分類器,檢驗分類器給出的結果是否符合預期結果。經過大量的測試數據,咱們能夠獲得分類器的錯誤率-分類器給出錯誤結果的次數除以測試執行的總數。錯誤率是經常使用的評估方法,主要用於評估分類器在某個數據集上的執行效果。完美分類器的錯誤率爲0,最差分類器的錯誤率是1.0。同時,咱們也不難發現,k-近鄰算法沒有進行數據的訓練,直接使用未知的數據與已知的數據進行比較,獲得結果。所以,能夠說k-鄰近算法不具備顯式的學習過程。
上一小結學習了簡單的k-近鄰算法的實現方法,可是這並非完整的k-近鄰算法流程,k-近鄰算法的通常流程:
已經瞭解了k-近鄰算法的通常流程,下面開始進入實戰內容。
海倫女士一直使用在線約會網站尋找適合本身的約會對象。儘管約會網站會推薦不一樣的任選,但她並非喜歡每個人。通過一番總結,她發現本身交往過的人能夠進行以下分類:
海倫收集約會數據已經有了一段時間,她把這些數據存放在文本文件datingTestSet.txt中,每一個樣本數據佔據一行,總共有1000行。
海倫收集的樣本數據主要包含如下3種特徵:
這裏不得不吐槽一句,海倫是個小吃貨啊,冰淇淋公斤數都影響本身擇偶標準。打開txt文本文件,數據格式如圖2.1所示。
在將上述特徵數據輸入到分類器前,必須將待處理的數據的格式改變爲分類器能夠接收的格式。分類器接收的數據是什麼格式的?從上小結已經知道,要將數據分類兩部分,即特徵矩陣和對應的分類標籤向量。在kNN_test02.py文件中建立名爲file2matrix的函數,以此來處理輸入格式問題。 將datingTestSet.txt放到與kNN_test02.py相同目錄下,編寫代碼以下:
# -*- coding: UTF-8 -*- import numpy as np """ 函數說明:打開並解析文件,對數據進行分類:1表明不喜歡,2表明魅力通常,3表明極具魅力 Parameters: filename - 文件名 Returns: returnMat - 特徵矩陣 classLabelVector - 分類Label向量 Modify: 2017-03-24 """ def file2matrix(filename): #打開文件 fr = open(filename) #讀取文件全部內容 arrayOLines = fr.readlines() #獲得文件行數 numberOfLines = len(arrayOLines) #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 returnMat = np.zeros((numberOfLines,3)) #返回的分類標籤向量 classLabelVector = [] #行的索引值 index = 0 for line in arrayOLines: #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') line = line.strip() #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 listFromLine = line.split('\t') #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特徵矩陣 returnMat[index,:] = listFromLine[0:3] #根據文本中標記的喜歡的程度進行分類,1表明不喜歡,2表明魅力通常,3表明極具魅力 if listFromLine[-1] == 'didntLike': classLabelVector.append(1) elif listFromLine[-1] == 'smallDoses': classLabelVector.append(2) elif listFromLine[-1] == 'largeDoses': classLabelVector.append(3) index += 1 return returnMat, classLabelVector """ 函數說明:main函數 Parameters: 無 Returns: 無 Modify: 2017-03-24 """ if __name__ == '__main__': #打開的文件名 filename = "datingTestSet.txt" #打開並處理數據 datingDataMat, datingLabels = file2matrix(filename) print(datingDataMat) print(datingLabels)
運行上述代碼,獲得的數據解析結果如圖2.2所示。
能夠看到,咱們已經順利導入數據,並對數據進行解析,格式化爲分類器須要的數據格式。接着咱們須要瞭解數據的真正含義。能夠經過友好、直觀的圖形化的方式觀察數據。
在kNN_test02.py文件中編寫名爲showdatas的函數,用來將數據可視化。編寫代碼以下:
# -*- coding: UTF-8 -*- from matplotlib.font_manager import FontProperties import matplotlib.lines as mlines import matplotlib.pyplot as plt import numpy as np """ 函數說明:打開並解析文件,對數據進行分類:1表明不喜歡,2表明魅力通常,3表明極具魅力 Parameters: filename - 文件名 Returns: returnMat - 特徵矩陣 classLabelVector - 分類Label向量 Modify: 2017-03-24 """ def file2matrix(filename): #打開文件 fr = open(filename) #讀取文件全部內容 arrayOLines = fr.readlines() #獲得文件行數 numberOfLines = len(arrayOLines) #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 returnMat = np.zeros((numberOfLines,3)) #返回的分類標籤向量 classLabelVector = [] #行的索引值 index = 0 for line in arrayOLines: #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') line = line.strip() #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 listFromLine = line.split('\t') #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特徵矩陣 returnMat[index,:] = listFromLine[0:3] #根據文本中標記的喜歡的程度進行分類,1表明不喜歡,2表明魅力通常,3表明極具魅力 if listFromLine[-1] == 'didntLike': classLabelVector.append(1) elif listFromLine[-1] == 'smallDoses': classLabelVector.append(2) elif listFromLine[-1] == 'largeDoses': classLabelVector.append(3) index += 1 return returnMat, classLabelVector """ 函數說明:可視化數據 Parameters: datingDataMat - 特徵矩陣 datingLabels - 分類Label Returns: 無 Modify: 2017-03-24 """ def showdatas(datingDataMat, datingLabels): #設置漢字格式 font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) #將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小爲(13,8) #當nrow=2,nclos=2時,表明fig畫布被分爲四個區域,axs[0][0]表示第一行第一個區域 fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8)) numberOfLabels = len(datingLabels) LabelsColors = [] for i in datingLabels: if i == 1: LabelsColors.append('black') if i == 2: LabelsColors.append('orange') if i == 3: LabelsColors.append('red') #畫出散點圖,以datingDataMat矩陣的第一(飛行常客例程)、第二列(玩遊戲)數據畫散點數據,散點大小爲15,透明度爲0.5 axs[0][0].scatter(x=datingDataMat[:,0], y=datingDataMat[:,1], color=LabelsColors,s=15, alpha=.5) #設置標題,x軸label,y軸label axs0_title_text = axs[0][0].set_title(u'每一年得到的飛行常客里程數與玩視頻遊戲所消耗時間佔比',FontProperties=font) axs0_xlabel_text = axs[0][0].set_xlabel(u'每一年得到的飛行常客里程數',FontProperties=font) axs0_ylabel_text = axs[0][0].set_ylabel(u'玩視頻遊戲所消耗時間佔',FontProperties=font) plt.setp(axs0_title_text, size=9, weight='bold', color='red') plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black') plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black') #畫出散點圖,以datingDataMat矩陣的第一(飛行常客例程)、第三列(冰激凌)數據畫散點數據,散點大小爲15,透明度爲0.5 axs[0][1].scatter(x=datingDataMat[:,0], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5) #設置標題,x軸label,y軸label axs1_title_text = axs[0][1].set_title(u'每一年得到的飛行常客里程數與每週消費的冰激淋公升數',FontProperties=font) axs1_xlabel_text = axs[0][1].set_xlabel(u'每一年得到的飛行常客里程數',FontProperties=font) axs1_ylabel_text = axs[0][1].set_ylabel(u'每週消費的冰激淋公升數',FontProperties=font) plt.setp(axs1_title_text, size=9, weight='bold', color='red') plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black') plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black') #畫出散點圖,以datingDataMat矩陣的第二(玩遊戲)、第三列(冰激凌)數據畫散點數據,散點大小爲15,透明度爲0.5 axs[1][0].scatter(x=datingDataMat[:,1], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5) #設置標題,x軸label,y軸label axs2_title_text = axs[1][0].set_title(u'玩視頻遊戲所消耗時間佔比與每週消費的冰激淋公升數',FontProperties=font) axs2_xlabel_text = axs[1][0].set_xlabel(u'玩視頻遊戲所消耗時間佔比',FontProperties=font) axs2_ylabel_text = axs[1][0].set_ylabel(u'每週消費的冰激淋公升數',FontProperties=font) plt.setp(axs2_title_text, size=9, weight='bold', color='red') plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black') plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black') #設置圖例 didntLike = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='didntLike') smallDoses = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='smallDoses') largeDoses = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='largeDoses') #添加圖例 axs[0][0].legend(handles=[didntLike,smallDoses,largeDoses]) axs[0][1].legend(handles=[didntLike,smallDoses,largeDoses]) axs[1][0].legend(handles=[didntLike,smallDoses,largeDoses]) #顯示圖片 plt.show() """ 函數說明:main函數 Parameters: 無 Returns: 無 Modify: 2017-03-24 """ if __name__ == '__main__': #打開的文件名 filename = "datingTestSet.txt" #打開並處理數據 datingDataMat, datingLabels = file2matrix(filename) showdatas(datingDataMat, datingLabels)
運行上述代碼,能夠看到可視化結果如圖2.3所示。
經過數據能夠很直觀的發現數據的規律,好比以玩遊戲所消耗時間佔比與每一年得到的飛行常客里程數,只考慮這二維的特徵信息,給個人感受就是海倫喜歡有生活質量的男人。爲何這麼說呢?每一年得到的飛行常客里程數代表,海倫喜歡能享受飛行常客獎勵計劃的男人,可是不能常常坐飛機,疲於奔波,滿世界飛。同時,這個男人也要玩視頻遊戲,而且佔必定時間比例。能處處飛,又能常常玩遊戲的男人是什麼樣的男人?很顯然,有生活質量,而且生活清閒的人。個人分析,僅僅是經過可視化的數據總結的我的見解。我想,每一個人的感覺應該也是不盡相同。
表2.1給出了四組樣本,若是想要計算樣本3和樣本4之間的距離,可使用歐拉公式計算。
樣本 | 玩遊戲所耗時間百分比 | 每一年得到的飛行經常使用里程數 | 每週消費的冰淇淋公升數 | 樣本分類 |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 0.1 | 2 |
計算方法如圖2.4所示。
咱們很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大,也就是說,每一年獲取的飛行常客里程數對於計算結果的影響將遠遠大於表2.1中其餘兩個特徵-玩視頻遊戲所耗時間佔比和每週消費冰淇淋公斤數的影響。而產生這種現象的惟一緣由,僅僅是由於飛行常客里程數遠大於其餘特徵值。但海倫認爲這三種特徵是同等重要的,所以做爲三個等權重的特徵之一,飛行常客里程數並不該該如此嚴重地影響到計算結果。
在處理這種不一樣取值範圍的特徵值時,咱們一般採用的方法是將數值歸一化,如將取值範圍處理爲0到1或者-1到1之間。下面的公式能夠將任意取值範圍的特徵值轉化爲0到1區間內的值:
newValue = (oldValue - min) / (max - min)
其中min和max分別是數據集中的最小特徵值和最大特徵值。雖然改變數值取值範圍增長了分類器的複雜度,但爲了獲得準確結果,咱們必須這樣作。在kNN_test02.py文件中編寫名爲autoNorm的函數,用該函數自動將數據歸一化。代碼以下:
# -*- coding: UTF-8 -*- import numpy as np """ 函數說明:打開並解析文件,對數據進行分類:1表明不喜歡,2表明魅力通常,3表明極具魅力 Parameters: filename - 文件名 Returns: returnMat - 特徵矩陣 classLabelVector - 分類Label向量 Modify: 2017-03-24 """ def file2matrix(filename): #打開文件 fr = open(filename) #讀取文件全部內容 arrayOLines = fr.readlines() #獲得文件行數 numberOfLines = len(arrayOLines) #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 returnMat = np.zeros((numberOfLines,3)) #返回的分類標籤向量 classLabelVector = [] #行的索引值 index = 0 for line in arrayOLines: #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') line = line.strip() #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 listFromLine = line.split('\t') #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特徵矩陣 returnMat[index,:] = listFromLine[0:3] #根據文本中標記的喜歡的程度進行分類,1表明不喜歡,2表明魅力通常,3表明極具魅力 if listFromLine[-1] == 'didntLike': classLabelVector.append(1) elif listFromLine[-1] == 'smallDoses': classLabelVector.append(2) elif listFromLine[-1] == 'largeDoses': classLabelVector.append(3) index += 1 return returnMat, classLabelVector """ 函數說明:對數據進行歸一化 Parameters: dataSet - 特徵矩陣 Returns: normDataSet - 歸一化後的特徵矩陣 ranges - 數據範圍 minVals - 數據最小值 Modify: 2017-03-24 """ def autoNorm(dataSet): #得到數據的最小值 minVals = dataSet.min(0) maxVals = dataSet.max(0) #最大值和最小值的範圍 ranges = maxVals - minVals #shape(dataSet)返回dataSet的矩陣行列數 normDataSet = np.zeros(np.shape(dataSet)) #返回dataSet的行數 m = dataSet.shape[0] #原始值減去最小值 normDataSet = dataSet - np.tile(minVals, (m, 1)) #除以最大和最小值的差,獲得歸一化數據 normDataSet = normDataSet / np.tile(ranges, (m, 1)) #返回歸一化數據結果,數據範圍,最小值 return normDataSet, ranges, minVals """ 函數說明:main函數 Parameters: 無 Returns: 無 Modify: 2017-03-24 """ if __name__ == '__main__': #打開的文件名 filename = "datingTestSet.txt" #打開並處理數據 datingDataMat, datingLabels = file2matrix(filename) normDataSet, ranges, minVals = autoNorm(datingDataMat) print(normDataSet) print(ranges) print(minVals)
運行上述代碼,獲得結果如圖2.5所示。
從圖2.5的運行結果能夠看到,咱們已經順利將數據歸一化了,而且求出了數據的取值範圍和數據的最小值,這兩個值是在分類的時候須要用到的,直接先求解出來,也算是對數據預處理了。
機器學習算法一個很重要的工做就是評估算法的正確率,一般咱們只提供已有數據的90%做爲訓練樣原本訓練分類器,而使用其他的10%數據去測試分類器,檢測分類器的正確率。須要注意的是,10%的測試數據應該是隨機選擇的,因爲海倫提供的數據並無按照特定目的來排序,因此我麼你能夠隨意選擇10%數據而不影響其隨機性。
爲了測試分類器效果,在kNN_test02.py文件中建立函數datingClassTest,編寫代碼以下:
# -*- coding: UTF-8 -*- import numpy as np import operator """ 函數說明:kNN算法,分類器 Parameters: inX - 用於分類的數據(測試集) dataSet - 用於訓練的數據(訓練集) labes - 分類標籤 k - kNN算法參數,選擇距離最小的k個點 Returns: sortedClassCount[0][0] - 分類結果 Modify: 2017-03-24 """ def classify0(inX, dataSet, labels, k): #numpy函數shape[0]返回dataSet的行數 dataSetSize = dataSet.shape[0] #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向) diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #二維特徵相減後平方 sqDiffMat = diffMat**2 #sum()全部元素相加,sum(0)列相加,sum(1)行相加 sqDistances = sqDiffMat.sum(axis=1) #開方,計算出距離 distances = sqDistances**0.5 #返回distances中元素從小到大排序後的索引值 sortedDistIndices = distances.argsort() #定一個記錄類別次數的字典 classCount = {} for i in range(k): #取出前k個元素的類別 voteIlabel = labels[sortedDistIndices[i]] #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,若是值不在字典中返回默認值。 #計算類別次數 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #python3中用items()替換python2中的iteritems() #key=operator.itemgetter(1)根據字典的值進行排序 #key=operator.itemgetter(0)根據字典的鍵進行排序 #reverse降序排序字典 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #返回次數最多的類別,即所要分類的類別 return sortedClassCount[0][0] """ 函數說明:打開並解析文件,對數據進行分類:1表明不喜歡,2表明魅力通常,3表明極具魅力 Parameters: filename - 文件名 Returns: returnMat - 特徵矩陣 classLabelVector - 分類Label向量 Modify: 2017-03-24 """ def file2matrix(filename): #打開文件 fr = open(filename) #讀取文件全部內容 arrayOLines = fr.readlines() #獲得文件行數 numberOfLines = len(arrayOLines) #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 returnMat = np.zeros((numberOfLines,3)) #返回的分類標籤向量 classLabelVector = [] #行的索引值 index = 0 for line in arrayOLines: #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') line = line.strip() #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 listFromLine = line.split('\t') #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特徵矩陣 returnMat[index,:] = listFromLine[0:3] #根據文本中標記的喜歡的程度進行分類,1表明不喜歡,2表明魅力通常,3表明極具魅力 if listFromLine[-1] == 'didntLike': classLabelVector.append(1) elif listFromLine[-1] == 'smallDoses': classLabelVector.append(2) elif listFromLine[-1] == 'largeDoses': classLabelVector.append(3) index += 1 return returnMat, classLabelVector """ 函數說明:對數據進行歸一化 Parameters: dataSet - 特徵矩陣 Returns: normDataSet - 歸一化後的特徵矩陣 ranges - 數據範圍 minVals - 數據最小值 Modify: 2017-03-24 """ def autoNorm(dataSet): #得到數據的最小值 minVals = dataSet.min(0) maxVals = dataSet.max(0) #最大值和最小值的範圍 ranges = maxVals - minVals #shape(dataSet)返回dataSet的矩陣行列數 normDataSet = np.zeros(np.shape(dataSet)) #返回dataSet的行數 m = dataSet.shape[0] #原始值減去最小值 normDataSet = dataSet - np.tile(minVals, (m, 1)) #除以最大和最小值的差,獲得歸一化數據 normDataSet = normDataSet / np.tile(ranges, (m, 1)) #返回歸一化數據結果,數據範圍,最小值 return normDataSet, ranges, minVals """ 函數說明:分類器測試函數 Parameters: 無 Returns: normDataSet - 歸一化後的特徵矩陣 ranges - 數據範圍 minVals - 數據最小值 Modify: 2017-03-24 """ def datingClassTest(): #打開的文件名 filename = "datingTestSet.txt" #將返回的特徵矩陣和分類向量分別存儲到datingDataMat和datingLabels中 datingDataMat, datingLabels = file2matrix(filename) #取全部數據的百分之十 hoRatio = 0.10 #數據歸一化,返回歸一化後的矩陣,數據範圍,數據最小值 normMat, ranges, minVals = autoNorm(datingDataMat) #得到normMat的行數 m = normMat.shape[0] #百分之十的測試數據的個數 numTestVecs = int(m * hoRatio) #分類錯誤計數 errorCount = 0.0 for i in range(numTestVecs): #前numTestVecs個數據做爲測試集,後m-numTestVecs個數據做爲訓練集 classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 4) print("分類結果:%d\t真實類別:%d" % (classifierResult, datingLabels[i])) if classifierResult != datingLabels[i]: errorCount += 1.0 print("錯誤率:%f%%" %(errorCount/float(numTestVecs)*100)) """ 函數說明:main函數 Parameters: 無 Returns: 無 Modify: 2017-03-24 """ if __name__ == '__main__': datingClassTest()
運行上述代碼,獲得結果如圖2.6所示。
從圖2.6驗證分類器結果中能夠看出,錯誤率是3%,這是一個想當不錯的結果。咱們能夠改變函數datingClassTest內變量hoRatio和分類器k的值,檢測錯誤率是否隨着變量值的變化而增長。依賴於分類算法、數據集和程序設置,分類器的輸出結果可能有很大的不一樣。
咱們能夠給海倫一個小段程序,經過該程序海倫會在約會網站上找到某我的並輸入他的信息。程序會給出她對男方喜歡程度的預測值。
在kNN_test02.py文件中建立函數classifyPerson,代碼以下:
# -*- coding: UTF-8 -*- import numpy as np import operator """ 函數說明:kNN算法,分類器 Parameters: inX - 用於分類的數據(測試集) dataSet - 用於訓練的數據(訓練集) labes - 分類標籤 k - kNN算法參數,選擇距離最小的k個點 Returns: sortedClassCount[0][0] - 分類結果 Modify: 2017-03-24 """ def classify0(inX, dataSet, labels, k): #numpy函數shape[0]返回dataSet的行數 dataSetSize = dataSet.shape[0] #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向) diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #二維特徵相減後平方 sqDiffMat = diffMat**2 #sum()全部元素相加,sum(0)列相加,sum(1)行相加 sqDistances = sqDiffMat.sum(axis=1) #開方,計算出距離 distances = sqDistances**0.5 #返回distances中元素從小到大排序後的索引值 sortedDistIndices = distances.argsort() #定一個記錄類別次數的字典 classCount = {} for i in range(k): #取出前k個元素的類別 voteIlabel = labels[sortedDistIndices[i]] #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,若是值不在字典中返回默認值。 #計算類別次數 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #python3中用items()替換python2中的iteritems() #key=operator.itemgetter(1)根據字典的值進行排序 #key=operator.itemgetter(0)根據字典的鍵進行排序 #reverse降序排序字典 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #返回次數最多的類別,即所要分類的類別 return sortedClassCount[0][0] """ 函數說明:打開並解析文件,對數據進行分類:1表明不喜歡,2表明魅力通常,3表明極具魅力 Parameters: filename - 文件名 Returns: returnMat - 特徵矩陣 classLabelVector - 分類Label向量 Modify: 2017-03-24 """ def file2matrix(filename): #打開文件 fr = open(filename) #讀取文件全部內容 arrayOLines = fr.readlines() #獲得文件行數 numberOfLines = len(arrayOLines) #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列 returnMat = np.zeros((numberOfLines,3)) #返回的分類標籤向量 classLabelVector = [] #行的索引值 index = 0 for line in arrayOLines: #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ') line = line.strip() #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。 listFromLine = line.split('\t') #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特徵矩陣 returnMat[index,:] = listFromLine[0:3] #根據文本中標記的喜歡的程度進行分類,1表明不喜歡,2表明魅力通常,3表明極具魅力 if listFromLine[-1] == 'didntLike': classLabelVector.append(1) elif listFromLine[-1] == 'smallDoses': classLabelVector.append(2) elif listFromLine[-1] == 'largeDoses': classLabelVector.append(3) index += 1 return returnMat, classLabelVector """ 函數說明:對數據進行歸一化 Parameters: dataSet - 特徵矩陣 Returns: normDataSet - 歸一化後的特徵矩陣 ranges - 數據範圍 minVals - 數據最小值 Modify: 2017-03-24 """ def autoNorm(dataSet): #得到數據的最小值 minVals = dataSet.min(0) maxVals = dataSet.max(0) #最大值和最小值的範圍 ranges = maxVals - minVals #shape(dataSet)返回dataSet的矩陣行列數 normDataSet = np.zeros(np.shape(dataSet)) #返回dataSet的行數 m = dataSet.shape[0] #原始值減去最小值 normDataSet = dataSet - np.tile(minVals, (m, 1)) #除以最大和最小值的差,獲得歸一化數據 normDataSet = normDataSet / np.tile(ranges, (m, 1)) #返回歸一化數據結果,數據範圍,最小值 return normDataSet, ranges, minVals """ 函數說明:經過輸入一我的的三維特徵,進行分類輸出 Parameters: 無 Returns: 無 Modify: 2017-03-24 """ def classifyPerson(): #輸出結果 resultList = ['討厭','有些喜歡','很是喜歡'] #三維特徵用戶輸入 precentTats = float(input("玩視頻遊戲所耗時間百分比:")) ffMiles = float(input("每一年得到的飛行常客里程數:")) iceCream = float(input("每週消費的冰激淋公升數:")) #打開的文件名 filename = "datingTestSet.txt" #打開並處理數據 datingDataMat, datingLabels = file2matrix(filename) #訓練集歸一化 normMat, ranges, minVals = autoNorm(datingDataMat) #生成NumPy數組,測試集 inArr = np.array([precentTats, ffMiles, iceCream]) #測試集歸一化 norminArr = (inArr - minVals) / ranges #返回分類結果 classifierResult = classify0(norminArr, normMat, datingLabels, 3) #打印結果 print("你可能%s這我的" % (resultList[classifierResult-1])) """ 函數說明:main函數 Parameters: 無 Returns: 無 Modify: 2017-03-24 """ if __name__ == '__main__': classifyPerson()
在cmd中,運行程序,並輸入數據(12,44000,0.5),預測結果是」你可能有些喜歡這我的」,也就是這我的魅力通常。一共有三個檔次:討厭、有些喜歡、很是喜歡,對應着不喜歡的人、魅力通常的人、極具魅力的人。結果如圖2.7所示。
對於須要識別的數字已經使用圖形處理軟件,處理成具備相同的色彩和大小:寬高是32像素x32像素。儘管採用本文格式存儲圖像不能有效地利用內存空間,可是爲了方便理解,咱們將圖片轉換爲文本格式,數字的文本格式如圖3.1所示。
與此同時,這些文本格式存儲的數字的文件命名也頗有特色,格式爲:數字的值_該數字的樣本序號,如圖3.2所示。
對於這樣已經整理好的文本,咱們能夠直接使用Python處理,進行數字預測。數據集分爲訓練集和測試集,使用上小結的方法,本身設計k-近鄰算法分類器,能夠實現分類。
這裏再也不講解本身用Python寫的k-鄰域分類器的方法,由於這不是本小節的重點。接下來,咱們將使用強大的第三方Python科學計算庫Sklearn構建手寫數字系統。
Scikit learn 也簡稱sklearn,是機器學習領域當中最知名的python模塊之一。sklearn包含了不少機器學習的方式:
使用sklearn能夠很方便地讓咱們實現一個機器學習算法。一個複雜度算法的實現,使用sklearn可能只須要調用幾行API便可。因此學習sklearn,能夠有效減小咱們特定任務的實現週期。
在安裝sklearn以前,須要安裝兩個庫,即numpy+mkl和scipy。不要使用pip3直接進行安裝,由於pip3默安裝的是numpy,而不是numpy+mkl。第三方庫下載地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/
這個網站的使用方法,我在以前的文章裏有講過:http://blog.csdn.net/c406495762/article/details/60156205
找到對應python版本的numpy+mkl和scipy,下載安裝便可,如圖3.1和圖3.2所示。
使用pip3安裝好這兩個whl文件後,使用以下指令安裝sklearn。
pip3 install -U scikit-learn
sklearn.neighbors模塊實現了k-近鄰算法,內容如圖3.3所示。
咱們使用sklearn.neighbors.KNeighborsClassifier就能夠是實現上小結,咱們實現的k-近鄰算法。KNeighborsClassifier函數一共有8個參數,如圖3.4所示。
KNneighborsClassifier參數說明:
KNeighborsClassifier提供了以一些方法供咱們使用,如圖3.5所示。
因爲篇幅緣由,每一個函數的怎麼用,就不具體講解了。官方手冊已經講解的很詳細了,各位能夠查看這個手冊進行學習,咱們直接講手寫數字識別系統的實現。
咱們知道數字圖片是32x32的二進制圖像,爲了方便計算,咱們能夠將32x32的二進制圖像轉換爲1x1024的向量。對於sklearn的KNeighborsClassifier輸入能夠是矩陣,不用必定轉換爲向量,不過爲了跟本身寫的k-近鄰算法分類器對應上,這裏也作了向量化處理。而後構建kNN分類器,利用分類器作預測。建立kNN_test04.py文件,編寫代碼以下:
# -*- coding: UTF-8 -*- import numpy as np import operator from os import listdir from sklearn.neighbors import KNeighborsClassifier as kNN """ 函數說明:將32x32的二進制圖像轉換爲1x1024向量。 Parameters: filename - 文件名 Returns: returnVect - 返回的二進制圖像的1x1024向量 Modify: 2017-07-15 """ def img2vector(filename): #建立1x1024零向量 returnVect = np.zeros((1, 1024)) #打開文件 fr = open(filename) #按行讀取 for i in range(32): #讀一行數據 lineStr = fr.readline() #每一行的前32個元素依次添加到returnVect中 for j in range(32): returnVect[0, 32*i+j] = int(lineStr[j]) #返回轉換後的1x1024向量 return returnVect """ 函數說明:手寫數字分類測試 Parameters: 無 Returns: 無 Modify: 2017-07-15 """ def handwritingClassTest(): #測試集的Labels hwLabels = [] #返回trainingDigits目錄下的文件名 trainingFileList = listdir('trainingDigits') #返回文件夾下文件的個數 m = len(trainingFileList) #初始化訓練的Mat矩陣,測試集 trainingMat = np.zeros((m, 1024)) #從文件名中解析出訓練集的類別 for i in range(m): #得到文件的名字 fileNameStr = trainingFileList[i] #得到分類的數字 classNumber = int(fileNameStr.split('_')[0]) #將得到的類別添加到hwLabels中 hwLabels.append(classNumber) #將每個文件的1x1024數據存儲到trainingMat矩陣中 trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr)) #構建kNN分類器 neigh = kNN(n_neighbors = 3, algorithm = 'auto') #擬合模型, trainingMat爲測試矩陣,hwLabels爲對應的標籤 neigh.fit(trainingMat, hwLabels) #返回testDigits目錄下的文件列表 testFileList = listdir('testDigits') #錯誤檢測計數 errorCount = 0.0 #測試數據的數量 mTest = len(testFileList) #從文件中解析出測試集的類別並進行分類測試 for i in range(mTest): #得到文件的名字 fileNameStr = testFileList[i] #得到分類的數字 classNumber = int(fileNameStr.split('_')[0]) #得到測試集的1x1024向量,用於訓練 vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr)) #得到預測結果 # classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) classifierResult = neigh.predict(vectorUnderTest) print("分類返回結果爲%d\t真實結果爲%d" % (classifierResult, classNumber)) if(classifierResult != classNumber): errorCount += 1.0 print("總共錯了%d個數據\n錯誤率爲%f%%" % (errorCount, errorCount/mTest * 100)) """ 函數說明:main函數 Parameters: 無 Returns: 無 Modify: 2017-07-15 """ if __name__ == '__main__': handwritingClassTest()
運行上述代碼,獲得如圖3.6所示的結果。
上述代碼使用的algorithm參數是auto,更改algorithm參數爲brute,使用暴力搜索,你會發現,運行時間變長了,變爲10s+。更改n_neighbors參數,你會發現,不一樣的值,檢測精度也是不一樣的。本身能夠嘗試更改這些參數的設置,加深對其函數的理解。
優勢
缺點:
PS: 若是以爲本篇本章對您有所幫助,歡迎關注、評論、頂!