機器學習實戰筆記(Python實現)-01-K近鄰算法(KNN)

---------------------------------------------------------------------------------------html

本系列文章爲《機器學習實戰》學習筆記,內容整理自書本,網絡以及本身的理解,若有錯誤歡迎指正。git

源碼在Python3.5上測試均經過,代碼及數據 --> https://github.com/Wellat/MLactiongithub

---------------------------------------------------------------------------------------算法

1 算法概述

1.1 算法特色

簡單地說,k-近鄰算法採用測量不一樣特徵值之間的距離方法進行分類。api

優勢:精度高、對異常值不敏感、無數據輸入假定數組

缺點:計算複雜度高、空間複雜度高網絡

適用數據範圍:數值型和標稱型app

 

1.2 工做原理

存在一個訓練樣本集,而且每一個樣本都存在標籤(有監督學習)。輸入沒有標籤的新樣本數據後,將新數據的每一個特徵與樣本集中數據對應的特徵進行比較,而後算法提取出與樣本集中特徵最類似的數據(最近鄰)的分類標籤。通常來講,咱們只選擇樣本數據集中前k個最類似的數據,這就是k-近鄰算法中k的出處,並且k一般不大於20。最後選擇k個最類似數據中出現次數最多的分類,做爲新數據的分類。機器學習

 

1.3 實例解釋

以電影分類爲例子,使用k-近鄰算法分類愛情片和動做片。有人曾經統計過不少電影的打鬥鏡頭和接吻鏡頭,下圖顯示了6部電影的打鬥和接吻鏡頭數。 假若有一部未看過的電影,如何肯定它是愛情片仍是動做片呢? 
函數

①首先須要統計這個未知電影存在多少個打鬥鏡頭和接吻鏡頭,下圖中問號位置是該未知電影出現的鏡頭數 

②以後計算未知電影與樣本集中其餘電影的距離(類似度),具體算法先忽略,結果以下表所示:

③將類似度列表排序,選出前k個最類似的樣本。此處咱們假設k=3,將上表中的類似度進行排序後前3分別是:He’s Not Really into Dudes,Beautiful Woman,California Man。
④統計最類似樣本的分類。此處很容易知道這3個樣本均爲愛情片。
⑤將分類最多的類別做爲未知電影的分類。那麼咱們就得出結論,未知電影屬於愛情片。

 

2 代碼實現

2.1 k-近鄰簡單分類的應用

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 在約會網站上使用k-近鄰算法

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 手寫識別系統實例

2.3.1 實例數據

爲了簡單起見,這裏構造的系統只能識別數字09。須要識別的數字已經使用圖形處理軟件,處理成具備相同的色彩和大小 : 寬髙是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-近鄰算法是分類數據最簡單最有效的算法。它必須保存所有數據集,若是訓練數據集很大,必須使用大量的存儲空間。此外,因爲必須對數據集中的每一個數據計算距離值,實際使用時可能很是耗時。其另外一個缺陷是它沒法給出任何數據的基礎結構信息,所以咱們也沒法知曉平均實例樣本和典型實例樣本具備什麼特徵。

 

3 應用 scikit-learn 庫實現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

相關文章
相關標籤/搜索