---------------------------------------------------------------------------------------html
本系列文章爲《機器學習實戰》學習筆記,內容整理自書本,網絡以及本身的理解,若有錯誤歡迎指正。git
源碼在Python3.5上測試均經過,代碼及數據 --> https://github.com/Wellat/MLactiongithub
---------------------------------------------------------------------------------------算法
簡單地說,k-近鄰算法採用測量不一樣特徵值之間的距離方法進行分類。api
優勢:精度高、對異常值不敏感、無數據輸入假定數組
缺點:計算複雜度高、空間複雜度高網絡
適用數據範圍:數值型和標稱型app
存在一個訓練樣本集,而且每一個樣本都存在標籤(有監督學習)。輸入沒有標籤的新樣本數據後,將新數據的每一個特徵與樣本集中數據對應的特徵進行比較,而後算法提取出與樣本集中特徵最類似的數據(最近鄰)的分類標籤。通常來講,咱們只選擇樣本數據集中前k個最類似的數據,這就是k-近鄰算法中k的出處,並且k一般不大於20。最後選擇k個最類似數據中出現次數最多的分類,做爲新數據的分類。機器學習
以電影分類爲例子,使用k-近鄰算法分類愛情片和動做片。有人曾經統計過不少電影的打鬥鏡頭和接吻鏡頭,下圖顯示了6部電影的打鬥和接吻鏡頭數。 假若有一部未看過的電影,如何肯定它是愛情片仍是動做片呢?
函數
①首先須要統計這個未知電影存在多少個打鬥鏡頭和接吻鏡頭,下圖中問號位置是該未知電影出現的鏡頭數
②以後計算未知電影與樣本集中其餘電影的距離(類似度),具體算法先忽略,結果以下表所示:
③將類似度列表排序,選出前k個最類似的樣本。此處咱們假設k=3,將上表中的類似度進行排序後前3分別是:He’s Not Really into Dudes,Beautiful Woman,California Man。
④統計最類似樣本的分類。此處很容易知道這3個樣本均爲愛情片。
⑤將分類最多的類別做爲未知電影的分類。那麼咱們就得出結論,未知電影屬於愛情片。
2.1.1 算法通常流程
2.1.2 Python實現代碼及註釋
1 #coding=UTF8 2 from numpy import * 3 import operator 4 5 def createDataSet(): 6 """ 7 函數做用:構建一組訓練數據(訓練樣本),共4個樣本 8 同時給出了這4個樣本的標籤,及labels 9 """ 10 group = array([ 11 [1.0, 1.1], 12 [1.0, 1.0], 13 [0. , 0. ], 14 [0. , 0.1] 15 ]) 16 labels = ['A', 'A', 'B', 'B'] 17 return group, labels 18 19 def classify0(inX, dataset, labels, k): 20 """ 21 inX 是輸入的測試樣本,是一個[x, y]樣式的 22 dataset 是訓練樣本集 23 labels 是訓練樣本標籤 24 k 是top k最相近的 25 """ 26 # shape返回矩陣的[行數,列數], 27 # 那麼shape[0]獲取數據集的行數, 28 # 行數就是樣本的數量 29 dataSetSize = dataset.shape[0] 30 31 """ 32 下面的求距離過程就是按照歐氏距離的公式計算的。 33 即 根號(x^2+y^2) 34 """ 35 # tile屬於numpy模塊下邊的函數 36 # tile(A, reps)返回一個shape=reps的矩陣,矩陣的每一個元素是A 37 # 好比 A=[0,1,2] 那麼,tile(A, 2)= [0, 1, 2, 0, 1, 2] 38 # tile(A,(2,2)) = [[0, 1, 2, 0, 1, 2], 39 # [0, 1, 2, 0, 1, 2]] 40 # tile(A,(2,1,2)) = [[[0, 1, 2, 0, 1, 2]], 41 # [[0, 1, 2, 0, 1, 2]]] 42 # 上邊那個結果的分開理解就是: 43 # 最外層是2個元素,即最外邊的[]中包含2個元素,相似於[C,D],而此處的C=D,由於是複製出來的 44 # 而後C包含1個元素,即C=[E],同理D=[E] 45 # 最後E包含2個元素,即E=[F,G],此處F=G,由於是複製出來的 46 # F就是A了,基礎元素 47 # 綜合起來就是(2,1,2)= [C, C] = [[E], [E]] = [[[F, F]], [[F, F]]] = [[[A, A]], [[A, A]]] 48 # 這個地方就是爲了把輸入的測試樣本擴展爲和dataset的shape同樣,而後就能夠直接作矩陣減法了。 49 # 好比,dataset有4個樣本,就是4*2的矩陣,輸入測試樣本確定是一個了,就是1*2,爲了計算輸入樣本與訓練樣本的距離 50 # 那麼,須要對這個數據進行做差。這是一次比較,由於訓練樣本有n個,那麼就要進行n次比較; 51 # 爲了方便計算,把輸入樣本複製n次,而後直接與訓練樣本做矩陣差運算,就能夠一次性比較了n個樣本。 52 # 好比inX = [0,1],dataset就用函數返回的結果,那麼 53 # tile(inX, (4,1))= [[ 0.0, 1.0], 54 # [ 0.0, 1.0], 55 # [ 0.0, 1.0], 56 # [ 0.0, 1.0]] 57 # 做差以後 58 # diffMat = [[-1.0,-0.1], 59 # [-1.0, 0.0], 60 # [ 0.0, 1.0], 61 # [ 0.0, 0.9]] 62 diffMat = tile(inX, (dataSetSize, 1)) - dataset 63 64 # diffMat就是輸入樣本與每一個訓練樣本的差值,而後對其每一個x和y的差值進行平方運算。 65 # diffMat是一個矩陣,矩陣**2表示對矩陣中的每一個元素進行**2操做,即平方。 66 # sqDiffMat = [[1.0, 0.01], 67 # [1.0, 0.0 ], 68 # [0.0, 1.0 ], 69 # [0.0, 0.81]] 70 sqDiffMat = diffMat ** 2 71 72 # axis=1表示按照橫軸,sum表示累加,即按照行進行累加。 73 # sqDistance = [[1.01], 74 # [1.0 ], 75 # [1.0 ], 76 # [0.81]] 77 sqDistance = sqDiffMat.sum(axis=1) 78 79 # 對平方和進行開根號 80 distance = sqDistance ** 0.5 81 82 # 按照升序進行快速排序,返回的是原數組的下標。 83 # 好比,x = [30, 10, 20, 40] 84 # 升序排序後應該是[10,20,30,40],他們的原下標是[1,2,0,3] 85 # 那麼,numpy.argsort(x) = [1, 2, 0, 3] 86 sortedDistIndicies = distance.argsort() 87 88 # 存放最終的分類結果及相應的結果投票數 89 classCount = {} 90 91 # 投票過程,就是統計前k個最近的樣本所屬類別包含的樣本個數 92 for i in range(k): 93 # index = sortedDistIndicies[i]是第i個最相近的樣本下標 94 # voteIlabel = labels[index]是樣本index對應的分類結果('A' or 'B') 95 voteIlabel = labels[sortedDistIndicies[i]] 96 # classCount.get(voteIlabel, 0)返回voteIlabel的值,若是不存在,則返回0 97 # 而後將票數增1 98 classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 99 100 # 把分類結果進行排序,而後返回得票數最多的分類結果 101 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) 102 return sortedClassCount[0][0] 103 104 if __name__== "__main__": 105 # 導入數據 106 dataset, labels = createDataSet() 107 inX = [0.1, 0.1] 108 # 簡單分類 109 className = classify0(inX, dataset, labels, 3) 110 print('the class of test sample is %s' %className)
2.2.1 算法通常流程
2.2.2 Python實現代碼
datingTestSet.txt 文件中有1000行的約會數據,樣本主要包括如下3種特徵:
將上述特徵數據輸人到分類器以前,必須將待處理數據的格式改變爲分類器能夠接受的格式 。在kNN.py中建立名爲 file2matrix 的函數,以此來處理輸人格式問題。該函數的輸人爲文件名字符串,輸出爲訓練樣本矩陣和類標籤向量。autoNorm 爲數值歸一化函數,將任意取值範圍的特徵值轉化爲0到1區間內的值。最後,datingClassTest 函數是測試代碼。
將下面的代碼增長到 kNN.py 中。
1 def file2matrix(filename): 2 """ 3 從文件中讀入訓練數據,並存儲爲矩陣 4 """ 5 fr = open(filename) 6 arrayOlines = fr.readlines() 7 numberOfLines = len(arrayOlines) #獲取 n=樣本的行數 8 returnMat = zeros((numberOfLines,3)) #建立一個2維矩陣用於存放訓練樣本數據,一共有n行,每一行存放3個數據 9 classLabelVector = [] #建立一個1維數組用於存放訓練樣本標籤。 10 index = 0 11 for line in arrayOlines: 12 # 把回車符號給去掉 13 line = line.strip() 14 # 把每一行數據用\t分割 15 listFromLine = line.split('\t') 16 # 把分割好的數據放至數據集,其中index是該樣本數據的下標,就是放到第幾行 17 returnMat[index,:] = listFromLine[0:3] 18 # 把該樣本對應的標籤放至標籤集,順序與樣本集對應。 19 classLabelVector.append(int(listFromLine[-1])) 20 index += 1 21 return returnMat,classLabelVector 22 23 def autoNorm(dataSet): 24 """ 25 訓練數據歸一化 26 """ 27 # 獲取數據集中每一列的最小數值 28 # 以createDataSet()中的數據爲例,group.min(0)=[0,0] 29 minVals = dataSet.min(0) 30 # 獲取數據集中每一列的最大數值 31 # group.max(0)=[1, 1.1] 32 maxVals = dataSet.max(0) 33 # 最大值與最小的差值 34 ranges = maxVals - minVals 35 # 建立一個與dataSet同shape的全0矩陣,用於存放歸一化後的數據 36 normDataSet = zeros(shape(dataSet)) 37 m = dataSet.shape[0] 38 # 把最小值擴充爲與dataSet同shape,而後做差,具體tile請翻看 第三節 代碼中的tile 39 normDataSet = dataSet - tile(minVals, (m,1)) 40 # 把最大最小差值擴充爲dataSet同shape,而後做商,是指對應元素進行除法運算,而不是矩陣除法。 41 # 矩陣除法在numpy中要用linalg.solve(A,B) 42 normDataSet = normDataSet/tile(ranges, (m,1)) 43 return normDataSet, ranges, minVals 44 45 def datingClassTest(): 46 # 將數據集中10%的數據留做測試用,其他的90%用於訓練 47 hoRatio = 0.10 48 datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') #load data setfrom file 49 normMat, ranges, minVals = autoNorm(datingDataMat) 50 m = normMat.shape[0] 51 numTestVecs = int(m*hoRatio) 52 errorCount = 0.0 53 for i in range(numTestVecs): 54 classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3) 55 print("the classifier came back with: %d, the real answer is: %d, result is :%s" % (classifierResult, datingLabels[i],classifierResult==datingLabels[i])) 56 if (classifierResult != datingLabels[i]): errorCount += 1.0 57 print("the total error rate is: %f" % (errorCount/float(numTestVecs))) 58 print(errorCount)
2.3.1 實例數據
爲了簡單起見,這裏構造的系統只能識別數字0到9。須要識別的數字已經使用圖形處理軟件,處理成具備相同的色彩和大小 : 寬髙是32像素x 32像素的黑白圖像。儘管採用文本格式存儲圖像不能有效地利用內存空間,可是爲了方便理解,咱們仍是將圖像轉換爲文本格式。
trainingDigits是2000個訓練樣本,testDigits是900個測試樣本。
2.3.2 算法的流程
2.3.3 Python實現代碼
將下面的代碼增長到 kNN.py 中,img2vector 爲圖片轉換成向量的方法,handwritingClassTest 爲測試方法:
1 from os import listdir 2 def img2vector(filename): 3 """ 4 將圖片數據轉換爲01矩陣。 5 每張圖片是32*32像素,也就是一共1024個字節。 6 所以轉換的時候,每行表示一個樣本,每一個樣本含1024個字節。 7 """ 8 # 每一個樣本數據是1024=32*32個字節 9 returnVect = zeros((1,1024)) 10 fr = open(filename) 11 # 循環讀取32行,32列。 12 for i in range(32): 13 lineStr = fr.readline() 14 for j in range(32): 15 returnVect[0,32*i+j] = int(lineStr[j]) 16 return returnVect 17 18 def handwritingClassTest(): 19 hwLabels = [] 20 # 加載訓練數據 21 trainingFileList = listdir('trainingDigits') 22 m = len(trainingFileList) 23 trainingMat = zeros((m,1024)) 24 for i in range(m): 25 # 從文件名中解析出當前圖像的標籤,也就是數字是幾 26 # 文件名格式爲 0_3.txt 表示圖片數字是 0 27 fileNameStr = trainingFileList[i] 28 fileStr = fileNameStr.split('.')[0] #take off .txt 29 classNumStr = int(fileStr.split('_')[0]) 30 hwLabels.append(classNumStr) 31 trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) 32 # 加載測試數據 33 testFileList = listdir('testDigits') #iterate through the test set 34 errorCount = 0.0 35 mTest = len(testFileList) 36 for i in range(mTest): 37 fileNameStr = testFileList[i] 38 fileStr = fileNameStr.split('.')[0] #take off .txt 39 classNumStr = int(fileStr.split('_')[0]) 40 vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) 41 classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) 42 print("the classifier came back with: %d, the real answer is: %d, The predict result is: %s" % (classifierResult, classNumStr, classifierResult==classNumStr)) 43 if (classifierResult != classNumStr): errorCount += 1.0 44 print("\nthe total number of errors is: %d / %d" %(errorCount, mTest)) 45 print("\nthe total error rate is: %f" % (errorCount/float(mTest)))
k-近鄰算法識別手寫數字數據集,錯誤率爲1. 2%。改變變量k的值、修改函數 handwritingClassTest 隨機選取訓練樣本、改變訓練樣本的數目,都會對k-近鄰算法的錯誤率產生影響,感興趣的話能夠改變這些變量值,觀察錯誤率的變化。
k-近鄰算法是分類數據最簡單最有效的算法。它必須保存所有數據集,若是訓練數據集很大,必須使用大量的存儲空間。此外,因爲必須對數據集中的每一個數據計算距離值,實際使用時可能很是耗時。其另外一個缺陷是它沒法給出任何數據的基礎結構信息,所以咱們也沒法知曉平均實例樣本和典型實例樣本具備什麼特徵。
1 """ 2 scikit-learn 庫對knn的支持 3 數據集是iris虹膜數據集 4 """ 5 6 from sklearn.datasets import load_iris 7 from sklearn import neighbors 8 import sklearn 9 10 #查看iris數據集 11 iris = load_iris() 12 print(iris) 13 14 ''' 15 KNeighborsClassifier(n_neighbors=5, weights='uniform', 16 algorithm='auto', leaf_size=30, 17 p=2, metric='minkowski', 18 metric_params=None, n_jobs=1, **kwargs) 19 n_neighbors: 默認值爲5,表示查詢k個最近鄰的數目 20 algorithm: {‘auto’, ‘ball_tree’, ‘kd_tree’, ‘brute’},指定用於計算最近鄰的算法,auto表示試圖採用最適合的算法計算最近鄰 21 leaf_size: 傳遞給‘ball_tree’或‘kd_tree’的葉子大小 22 metric: 用於樹的距離度量。默認'minkowski與P = 2(即歐氏度量) 23 n_jobs: 並行工做的數量,若是設爲-1,則做業的數量被設置爲CPU內核的數量 24 查看官方api:http://scikit-learn.org/dev/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier 25 ''' 26 knn = neighbors.KNeighborsClassifier() 27 #訓練數據集 28 knn.fit(iris.data, iris.target) 29 #訓練準確率 30 score = knn.score(iris.data, iris.target) 31 32 #預測 33 predict = knn.predict([[0.1,0.2,0.3,0.4]]) 34 #預測,返回機率數組 35 predict2 = knn.predict_proba([[0.1,0.2,0.3,0.4]]) 36 37 print(predict) 38 print(iris.target_names[predict])
代碼解釋參考原貼:http://blog.csdn.net/niuwei22007/article/details/49703719