原文連接:www.cnblogs.com/fydeblog/p/7140974.htmlhtml
這篇notebook是關於機器學習中監督學習的k近鄰算法,將介紹2個實例,分別是使用k-近鄰算法改進約會網站的效果和手寫識別系統.
操做系統:ubuntu14.04 運行環境:anaconda-python2.7-notebook 參考書籍:機器學習實戰 notebook writer ----方陽
k-近鄰算法(kNN)的工做原理:存在一個樣本數據集合,也稱做訓練樣本集,而且樣本集中的每一個數據都存在標籤,即咱們知道樣本集中每一組數據與所屬分類的對應關係,輸入沒有標籤的新數據後,將新數據的每一個特徵與樣本集中數據對應的特徵進行比較,而後算法提取樣本集中特徵最類似的分類標籤。python
注意事項:在這裏說一句,默認環境python2.7的notebook,用python3.6的會出問題,還有個人目錄可能跟大家的不同,大家本身跑的時候記得改目錄,我會把notebook和代碼以及數據集放到結尾。git
from numpy import * import operator def createDataSet(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) labels = ['A','A','B','B'] return group, labels
先來點開胃菜,在上面的代碼中,咱們導入了兩個模塊,一個是科學計算包numpy,一個是運算符模塊,在後面都會用到,在createDataSet函數中,咱們初始化了group,labels,咱們將作這樣一件事,[1.0,1.1]和[1.0,1.0] 對應屬於labels中 A 分類,[0,0]和[0,0.1]對應屬於labels中的B分類,咱們想輸入一個新的二維座標,根據上面的座標來判斷新的座標屬於那一類,在這以前,咱們要 實現k-近鄰算法 ,下面就開始實現算法
def classify0(inX, dataSet, labels, k): dataSetSize = dataSet.shape[0] diffMat = tile(inX, (dataSetSize,1)) - dataSet sqDiffMat = diffMat**2 sqDistances = sqDiffMat.sum(axis=1) distances = sqDistances**0.5 sortedDistIndicies = distances.argsort() classCount={} for i in range(k): voteIlabel = labels[sortedDistIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0]
代碼解析:ubuntu
以上5行實現的是距離的計算 ,下面的是選出距離最小的k個點,對類別進行統計,返回所佔數目多的類別。數組
classCount定義爲存儲字典,裏面有‘A’和‘B’,它們的值是在前k個距離最小的數據集中的個數,本例最後classCount={'A':1,'B':2},函數argsort是返回array數組從小到大的排列的序號,get函數返回字典的鍵值,因爲後面加了1,因此每次出現鍵值就加1,就能夠就算出鍵值出現的次數裏。最後經過sorted函數將classCount字典分解爲列表,sorted函數的第二個參數導入了運算符模塊的itemgetter方法,按照第二個元素的次序(即數字)進行排序,因爲此處reverse=True,是逆序,因此按照從大到小的次序排列。app
這上面是k-近鄰的一個小例子,個人標題還沒介紹,如今來介紹標題,準備數據,通常都是從文本文件中解析數據,仍是從一個例子開始吧!python2.7
本次例子是改進約會網站的效果,咱們定義三個特徵來判別三種類型的人:機器學習
根據以上三個特徵:來判斷一我的是不是本身不喜歡的人,仍是魅力通常的人,仍是極具魅力的人。ide
因而,收集了1000個樣本,放在datingTestSet2.txt中,共有1000行,每一行有四列,前三列是特徵,後三列是從屬那一類人,因而問題來了,咱們這個文本文件的輸入導入到python中來處理,因而須要一個轉換函數file2matrix,函數輸入是文件名字字符串,輸出是訓練樣本矩陣(特徵矩陣)和類標籤向量。
def file2matrix(filename): fr = open(filename) numberOfLines = len(fr.readlines()) #get the number of lines in the file returnMat = zeros((numberOfLines,3)) #prepare matrix to return classLabelVector = [] #prepare labels return fr = open(filename) index = 0 for line in fr.readlines(): line = line.strip() listFromLine = line.split('\t') returnMat[index,:] = listFromLine[0:3] classLabelVector.append(int(listFromLine[-1])) index += 1 return returnMat,classLabelVector
這個函數比較簡單,就不詳細說明裏,這裏只介紹如下一些函數的功能吧!
首先,從上一步獲得訓練樣本矩陣和類標籤向量,先更換一下路徑。
cd /home/fangyang/桌面/machinelearninginaction/Ch02/
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
import matplotlib import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels)) #scatter函數是用來畫散點圖的 plt.show()
結果顯示
咱們從上圖能夠上出,橫座標的特徵值是遠大於縱座標的特徵值的,這樣再算新數據和數據集的數據的距離時,數字差值最大的屬性對計算結果的影響最大,咱們就可能會丟失掉其餘屬性,例如這個例子,每一年獲取的飛行常客里程數對計算結果的影響遠大於其他兩個特徵,這是咱們不想看到的,因此這裏採用歸一化數值處理,也叫特徵縮放,用於將特徵縮放到同一個範圍內。
本例的縮放公式 newValue = (oldValue - min) / (max - min)
其中min和max是數據集中的最小特徵值和最大特徵值。經過該公式可將特徵縮放到區間(0,1)
下面是特徵縮放的代碼
def autoNorm(dataSet): minVals = dataSet.min(0) maxVals = dataSet.max(0) ranges = maxVals - minVals normDataSet = zeros(shape(dataSet)) m = dataSet.shape[0] normDataSet = dataSet - tile(minVals, (m,1)) normDataSet = normDataSet/tile(ranges, (m,1)) #element wise divide return normDataSet, ranges, minVals
normDataSet(1000 X 3)是歸一化後的數據,range(1X3)是特徵的範圍差(即最大值減去最小值),minVals(1X3)是最小值。
原理上面已介紹,這裏不在複述。
好了,咱們已經有了k-近鄰算法、從文本解析出數據、還有歸一化處理,如今可使用以前的數據進行測試了,測試代碼以下:
def datingClassTest(): hoRatio = 0.50 datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') #load data setfrom file normMat, ranges, minVals = autoNorm(datingDataMat) m = normMat.shape[0] numTestVecs = int(m*hoRatio) errorCount = 0.0 for i in range(numTestVecs): classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3) print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]) if (classifierResult != datingLabels[i]): errorCount += 1.0 print "the total error rate is: %f" % (errorCount/float(numTestVecs)) print errorCount
這裏函數用到裏以前講的三個函數:file2matrix、autoNorm和classify0.這個函數將數據集分紅兩個部分,一部分看成分類器的訓練樣本,一部分看成測試樣本,經過hoRatio進行控制,函數hoRatio是0.5,它與樣本總數相乘,將數據集平分,若是想把訓練樣本調大一些,可增大hoRatio,但最好不要超過0.8,以避免測試樣本過少,在函數的最後,加了錯誤累加部分,預測出來的結果不等於實際結果,errorCount就加1,而後最後除以總數就獲得錯誤的機率。
說了這麼多,都尚未測試如下,下面來測試一下!先從簡單的開始(已將上面的函數放在kNN.py中了)
1 import kNN 2 group , labels = kNN.createDataSet()
group #結果在下
array([[ 1. , 1.1], [ 1. , 1. ], [ 0. , 0. ], [ 0. , 0.1]])
labels #結果在下
['A', 'A', 'B', 'B']
這個小例子最開始提過,有兩個分類A和B,經過上面的group爲訓練樣本,測試新的數據屬於那一類
1 kNN.classify0([0,0], group, labels, 3) #使用k-近鄰算法進行測試
'B' #結果是B分類
直觀地能夠看出[0,0]是與B所在的樣本更近,下面來測試一下約會網站的匹配效果。
先將文本中的數據導出來,因爲前面在分析數據畫圖的時候已經用到裏file2matrix,這裏就不重複用了。
datingDataMat #結果在下
array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01], [ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00], [ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01], ..., [ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01], [ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01], [ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]])
datingLabels #因爲過長,只截取一部分,詳細去看jupyter notebook
而後對數據進行歸一化處理。
1 normMat , ranges , minVals = kNN.autoNorm(datingDataMat) #使用歸一化函數
normMat
array([[ 0.44832535, 0.39805139, 0.56233353], [ 0.15873259, 0.34195467, 0.98724416], [ 0.28542943, 0.06892523, 0.47449629], ..., [ 0.29115949, 0.50910294, 0.51079493], [ 0.52711097, 0.43665451, 0.4290048 ], [ 0.47940793, 0.3768091 , 0.78571804]])
ranges
array([ 9.12730000e+04, 2.09193490e+01, 1.69436100e+00])
minVals
array([ 0. , 0. , 0.001156])
最後進行測試,運行以前的測試函數datingClassTest
1 kNN.datingClassTest()
因爲過長,只截取一部分,詳細去看jupyter notebook
能夠看到上面結果出現錯誤32個,錯誤率6.4%,因此這個系統還算不錯!
咱們能夠看到,測試當然不錯,但用戶交互式不好,因此結合上面,咱們要寫一個完整的系統,代碼以下:
def classifyPerson(): resultList = ['not at all', 'in small doses', 'in large doses'] percentTats = float(raw_input("percentage of time spent playing video games?")) ffMiles = float(raw_input("frequent flier miles earned per year?")) iceCream = float(raw_input("liters of ice cream consumed per year?")) datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') normMat, ranges, minVals = autoNorm(datingDataMat) inArr = array([ffMiles, percentTats, iceCream]) classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLabels,3) print "You will probably like this person" , resultList[classifierResult - 1]
運行狀況
1 kNN.classifyPerson()
percentage of time spent playing video games?10 #這裏的數字都是用戶本身輸入的 frequent flier miles earned per year?10000 liters of ice cream consumed per year?0.5 You will probably like this person in small doses
這個就是由用戶本身輸出參數,並判斷出感興趣程度,很是友好。
下面再介紹一個例子,也是用k-近鄰算法,去實現對一個數字的判斷,首先咱們是將寬高是32X32的像素的黑白圖像轉換成文本文件存儲,但咱們知道文本文件必須轉換成特徵向量,才能進入k-近鄰算法中進行處理,因此咱們須要一個img2vector函數去實現這個功能!
img2vector代碼以下:
def img2vector(filename): returnVect = zeros((1,1024)) fr = open(filename) for i in range(32): lineStr = fr.readline() for j in range(32): returnVect[0,32*i+j] = int(lineStr[j]) return returnVect
這個函數挺簡單的,先用zeros生成1024的一維array,而後用兩重循環,外循環以行遞進,內循環以列遞進,將32X32的文本數據依次賦值給returnVect。
好了,轉換函數寫好了,說一下訓練集和測試集,全部的訓練集都放在trainingDigits文件夾中,測試集放在testDigits文件夾中,訓練集有兩千個樣本,0~9各有200個,測試集大約有900個樣本,這裏注意一點,全部在文件夾裏的命名方式是有要求的,咱們是經過命名方式來解析出它的真實數字,而後與經過k-近鄰算法得出的結果相對比,例如945.txt,這裏的數字是9,鏈接符前面的數字就是這個樣本的真實數據。該系統實現的方法與前面的約會網站的相似,就很少說了。
系統測試代碼以下
def handwritingClassTest(): hwLabels = [] trainingFileList = listdir('trainingDigits') #load the training set m = len(trainingFileList) trainingMat = zeros((m,1024)) for i in range(m): fileNameStr = trainingFileList[i] fileStr = fileNameStr.split('.')[0] #take off .txt classNumStr = int(fileStr.split('_')[0]) hwLabels.append(classNumStr) trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) testFileList = listdir('testDigits') #iterate through the test set errorCount = 0.0 mTest = len(testFileList) for i in range(mTest): fileNameStr = testFileList[i] fileStr = fileNameStr.split('.')[0] #take off .txt classNumStr = int(fileStr.split('_')[0]) vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr) if (classifierResult != classNumStr): errorCount += 1.0 print "\nthe total number of errors is: %d" % errorCount print "\nthe total error rate is: %f" % (errorCount/float(mTest))
這裏的listdir是從os模塊導入的,它的功能是列出給定目錄下的全部文件名,以字符串形式存放,輸出是一個列表
這裏的split函數是要分離符號,獲得該文本的真實數據,第一個split函數是以小數點爲分隔符,例如‘1_186.txt’ ,就變成了['1_186','txt'],而後取出第一個,就截掉了.txt,第二個split函數是以鏈接符_爲分隔符,就截掉後面的序號,剩下前面的字符數據‘1’,而後轉成int型就獲得了它的真實數據,其餘的沒什麼,跟前面同樣
下面開始測試
1 kNN.handwritingClassTest()
咱們能夠看到最後結果,錯誤率1.2%, 可見效果還不錯!
這裏把整個kNN.py文件貼出來,主要是上面已經介紹的函數
''' Input: inX: vector to compare to existing dataset (1xN) dataSet: size m data set of known vectors (NxM) labels: data set labels (1xM vector) k: number of neighbors to use for comparison (should be an odd number) Output: the most popular class label ''' from numpy import * import operator from os import listdir def classify0(inX, dataSet, labels, k): dataSetSize = dataSet.shape[0] diffMat = tile(inX, (dataSetSize,1)) - dataSet sqDiffMat = diffMat**2 sqDistances = sqDiffMat.sum(axis=1) distances = sqDistances**0.5 sortedDistIndicies = distances.argsort() classCount={} for i in range(k): voteIlabel = labels[sortedDistIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0] def createDataSet(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) labels = ['A','A','B','B'] return group, labels def file2matrix(filename): fr = open(filename) numberOfLines = len(fr.readlines()) #get the number of lines in the file returnMat = zeros((numberOfLines,3)) #prepare matrix to return classLabelVector = [] #prepare labels return fr = open(filename) index = 0 for line in fr.readlines(): line = line.strip() listFromLine = line.split('\t') returnMat[index,:] = listFromLine[0:3] classLabelVector.append(int(listFromLine[-1])) index += 1 return returnMat,classLabelVector def autoNorm(dataSet): minVals = dataSet.min(0) maxVals = dataSet.max(0) ranges = maxVals - minVals normDataSet = zeros(shape(dataSet)) m = dataSet.shape[0] normDataSet = dataSet - tile(minVals, (m,1)) normDataSet = normDataSet/tile(ranges, (m,1)) #element wise divide return normDataSet, ranges, minVals def datingClassTest(): hoRatio = 0.50 #hold out 10% datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') #load data setfrom file normMat, ranges, minVals = autoNorm(datingDataMat) m = normMat.shape[0] numTestVecs = int(m*hoRatio) errorCount = 0.0 for i in range(numTestVecs): classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3) print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]) if (classifierResult != datingLabels[i]): errorCount += 1.0 print "the total error rate is: %f" % (errorCount/float(numTestVecs)) print errorCount def classifyPerson(): resultList = ['not at all', 'in small doses', 'in large doses'] percentTats = float(raw_input("percentage of time spent playing video games?")) ffMiles = float(raw_input("frequent flier miles earned per year?")) iceCream = float(raw_input("liters of ice cream consumed per year?")) datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') normMat, ranges, minVals = autoNorm(datingDataMat) inArr = array([ffMiles, percentTats, iceCream]) classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLabels,3) print "You will probably like this person" , resultList[classifierResult - 1] def img2vector(filename): returnVect = zeros((1,1024)) fr = open(filename) for i in range(32): lineStr = fr.readline() for j in range(32): returnVect[0,32*i+j] = int(lineStr[j]) return returnVect def handwritingClassTest(): hwLabels = [] trainingFileList = listdir('trainingDigits') #load the training set m = len(trainingFileList) trainingMat = zeros((m,1024)) for i in range(m): fileNameStr = trainingFileList[i] fileStr = fileNameStr.split('.')[0] #take off .txt classNumStr = int(fileStr.split('_')[0]) hwLabels.append(classNumStr) trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) testFileList = listdir('testDigits') #iterate through the test set errorCount = 0.0 mTest = len(testFileList) for i in range(mTest): fileNameStr = testFileList[i] fileStr = fileNameStr.split('.')[0] #take off .txt classNumStr = int(fileStr.split('_')[0]) vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr) if (classifierResult != classNumStr): errorCount += 1.0 print "\nthe total number of errors is: %d" % errorCount print "\nthe total error rate is: %f" % (errorCount/float(mTest))
至此,這個k-近鄰算法的介紹到這裏就結束了,但願這篇文章對你的學習有幫助!
百度雲連接: https://pan.baidu.com/s/1OuyO...
圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。
公開課地址:https://ke.qq.com/course/362788