1. 程式人生 > >【機器學習】KNN及程式碼實戰

【機器學習】KNN及程式碼實戰

一、KNN分類思想

這裡寫圖片描述

二、例子一

1.情景

如下圖,這裡共有四個點,兩個B類,兩個A類。[1,1.1]-A 、[1,1]-A 、[0,0]-B 、[0,0.1]-B。現在我們輸入點[0,0],要求KNN分類器幫我們分類,判斷點[0,0]是A類還是B類。演算法中設定K=3,表示在該圖中,計算輸入點[0,0]到圖中已經分好類的點間的距離,然後按照距離遞增次序排序,選取與輸入點[0,0]距離最小的k個點(就是已經排好序的前k個節點),計算該k個點中哪個類別的點最多,出現頻率最高的類別作為輸入點[0,0]的預測分類。

這裡寫圖片描述

2.所用函式

''' createDataSet():
    共有四個點,兩個B類,兩個A類。[1,1.1]-A 、[1,1]-A 、[0,0]-B 、[0,0.1]-B,
    將它們有序的返回到gourp,labels
    group= 
    [[ 1.   1.1]
     [ 1.   1. ]
     [ 0.   0. ]
     [ 0.   0.1]]
    labels=  ['A', 'A', 'B', 'B']
'''
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 ''' classify0(inX, dataSet, labels, k): inX=需要進行預測分類的向量;dataSet=已經分好類的資料集;labels=對應dataSet中各個物件的分類標籤。 將向量[inX]歸類到與其距離最近的k個向量中所屬標籤出現頻率最高的那一個標籤 ''' def classify0(inX, dataSet, labels, k)
:
dataSetSize = dataSet.shape[0] #dataSet矩陣的列對應的是一個物件的屬性,行對應的是某一個物件; #這句返回dataSet矩陣第0列的長度(dataSet矩陣的行數,也是物件總數) #dataSetSize = dataSet矩陣的行數 diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile(inX, (dataSetSize,1)),創造具有
#dataSetSize行1列的矩陣,矩陣每個元素都為向量[inX]。 #diffMat=dataSet中每個物件與向量[inX]的距離 sqDiffMat = diffMat**2 sqDistances = sqDiffMat.sum(axis=1) #sqDistances=sqDiffMat的所有列相加 distances = sqDistances**0.5 sortedDistIndicies = distances.argsort() #返回distances中元素大小是遞增排序對應的下標 #sortedDistIndicies = 與[inX]距離遞增排列的點的下標 classCount={} for i in range(k): # 0<=i<k voteIlabel = labels[sortedDistIndicies[i]] #返回與[inX]距離最近的第i個點的標籤 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #遍歷完k次後,該句可以統計與[inX]距離 #最近的k個點中,各個標籤出現的次數 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0] #sortedClassCount = 將classCount={}的標籤頻率遞增排列返回的序列

3.執行結果

這裡寫圖片描述

三、例子二

1.情景

海倫收集了自己的1000條約會資訊(datingTestSet.txt),每條資訊對應的屬性是:

[ 每年獲得的飛行常客里程數,玩視訊遊戲所耗時間百分比,每週消費的冰淇淋公升數,海倫喜歡這個男生的程度 ]

這裡寫圖片描述

該例子要進行兩個實驗:

1. 取datingTestSet.txt中90%的資料作為訓練集、10%的資料作為驗證集,並計算分類的錯誤率

2.根據海倫提供的一條男生的資訊【每年獲得的飛行常客里程數,玩視訊遊戲所耗時間百分比,每週消費的冰淇淋公升數】,判斷海倫是否喜歡這個男生

2.從文字解析資料

以下函式將從datingTestSet.txt中讀取資料,並將海倫喜歡這個男生的程度進行轉換:

[ largeDoses,smallDoses,disntLike ]->[ 3,2,1 ]

程式碼:

'''
    讀取檔案filename,將原資料集拆分為兩個資料結構returnMat矩陣、classLabelVector陣列
    並返回returnMat(returnMat是由原資料集除去最後一列的矩陣)
    並返回classLabelVector(陣列中第i個元素就是原資料集中第i行的最後一列的屬性,且存入
    陣列前已將最後一列的屬性對映到3,2,1三個數字中的一個)
'''
def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())   #numberOfLines = 資料集的行數
    returnMat = zeros((numberOfLines,3))  #returnMat = numberOfLines行3列的零矩陣 
    classLabelVector = []                    
    fr = open(filename)
    index = 0
    int = {'largeDoses': 3, 'smallDoses': 2, 'didntLike': 1} #建立名為int的字典,將並將海倫喜歡這個男生的程度進行轉換:
                                                             #[ largeDoses,smallDoses,disntLike ]->[ 3,2,1 ]
    for line in fr.readlines():
        line = line.strip()       #去除當前一行字串頭尾的空格
        listFromLine = line.split('\t')  #將當前字串按照'\t'進行分割,例如第一行資料這裡會返回一個數組為:
                                         #['40920', '8.326976', '0.953952', 'largeDoses']
        returnMat[index,:] = listFromLine[0:3] #將一行資料中的前三個屬性放入零矩陣的第index行,例如這裡
                                               #將'40920', '8.326976', '0.953952'放入零矩陣的第0行
        classLabelVector.append(int[listFromLine[-1]]) #將listFromLine的最後一個元素(即'largeDoses')按照字典轉化
                                                       #為3 並新增到陣列classLabelVector
        index += 1
    return returnMat,classLabelVector

結果:
這裡寫圖片描述

這裡寫圖片描述

3.畫出散點圖

使用returnMat,classLabelVector畫出散點圖,其中橫座標x=每年獲取的飛行常客里程數,縱座標y=玩視訊遊戲所耗時間百分比。

程式碼:

'''
    returnMat,classLabelVector=file2matrix('datingTestSet.txt')
    使用returnMat,classLabelVector畫出散點圖,
    其中橫座標x=每年獲取的飛行常客里程數,縱座標y=玩視訊遊戲所耗時間百分比。
    s為datingDataMat對應第i行資料點的size,size的數值等於classLabelVector陣列中第i個
    元素([ largeDoses,smallDoses,disntLike ]->[ 3,2,1 ],3、2、1中的一個)乘以15。
    c為datingDataMat對應第i行資料點的color,color的數值等於classLabelVector陣列中第i個
    元素([ largeDoses,smallDoses,disntLike ]->[ 3,2,1 ],3、2、1中的一個)乘以15。
'''
def painting(datingDataMat, datingLabels):   #從datingDataMat,datingLabels=file2matrix('datingTestSet.txt')獲取引數,並畫出散點圖
    import matplotlib
    import matplotlib.pyplot as plt
    fig = plt.figure()
    ax = fig.add_subplot(111)
    # ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
    ax.scatter(datingDataMat[:, 0], datingDataMat[:, 1], s=15 * array(datingLabels), c=15 * array(datingLabels))
    plt.show()

結果:
這裡寫圖片描述

這裡寫圖片描述

4.資料準備:歸一化資料

這裡寫圖片描述

程式碼:

'''
    這裡的引數dataSet使用經過函式file2matrix(filename)返回的returnMat矩陣,其中
    returnMat矩陣只包含'datingTestSet.txt'的0~2共3列的資料。
    函式autoNorm(dataSet)將returnMat矩陣進行歸一化處理(即將returnMat矩陣中的所有資料轉化為0~1之間),
    並將結果返回到normDataSet,而返回的
    陣列ranges=[(第一列元素極差),(第二列元素極差),(第三列元素極差)]
'''
def autoNorm(dataSet):
    minVals = dataSet.min(0)  #返回當min()的引數為0時,返回dataSet每列的最小值
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet)) #構建與dataSet矩陣相同大小的零矩陣
    m = dataSet.shape[0]     # m=dataSet矩陣的行數
    normDataSet = dataSet - tile(minVals, (m,1)) 
    normDataSet = normDataSet/tile(ranges, (m,1))   
    return normDataSet, ranges, minVals

結果:
這裡寫圖片描述

這裡寫圖片描述

5.取’datingTestSet.txt’中的第0~10%的資料作為待分類集,取第10%~100%的資料作為已經分好類的資料集。並計算kNN分類器的錯誤率

程式碼:

'''
    取'datingTestSet.txt'中的第0~10%的資料作為待分類集,取第10%~100%的資料作為已經分好類的資料集。
    並計算kNN分類器的錯誤率
'''
def datingClassTest():
    hoRatio = 0.10  # hold out 10%
    datingDataMat, datingLabels = file2matrix('datingTestSet.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): #這個for迴圈中取normMat的第0~10%的資料作為待分類集,取第10%~100%的資料作
                                 #為已經分好類的資料集。
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        #classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)中,例如i=0是,則
        #取normMat矩陣的第0行在已經分好類的normMat矩陣的第numTestVecs到m行的資料中進行分類,計算前3個最接近【normMat矩陣第0行資料的特徵】的
        #資料所對應的標籤出現次數,將normMat矩陣的第0行資料歸類為出現頻率最高的標籤
        if classifierResult == datingLabels[i]:
            print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
        else:
            errorCount += 1.0
            print("\33[1;35m the classifier came back with: %d, the real answer is: %d  \33[0m" % (
            classifierResult, datingLabels[i]))
    print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
    print("errorCount= ",errorCount)

結果:
這裡寫圖片描述

6.通過海倫輸入某男生的資訊【每年獲得的飛行常客里程數,玩視訊遊戲所耗時間百分比,每週消費的冰淇淋公升數】,使用該程式判斷海倫是否喜歡這個男生

程式碼:


'''
    通過海倫輸入某男生的資訊【每年獲得的飛行常客里程數,玩視訊遊戲所耗時間百分比,每週消費的冰淇淋公升數】,使用
    該程式判斷海倫是否喜歡這個男生
'''
def classifyPerson():
    resultList = ['disntLike' ,'smallDoses','largeDoses']
    percentTats = float(input("percentage of time spent playing video games?"))
    ffMiles = float(input("frequent flier miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))
    datingDataMat , datingLabels = file2matrix('datingTestSet.txt')
    normMat , ranges , minVals = autoNorm(datingDataMat)
    inArr=array([ffMiles,percentTats,iceCream])
    classfierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)
    print("You will probably like this person:", resultList[classfierResult-1])

結果:
這裡寫圖片描述

這裡寫圖片描述

四、例子三

1.情景

  現在我們要構建一個手寫識別系統。

  (訓練集的格式與測試集相同)

  目前我們有一堆訓練資料集(手寫數字及其標籤,例如檔名0_14.txt表示這個第14個手寫數字’0’的樣本,0同時也是該樣本的標籤。),具體如下圖:

這裡寫圖片描述

這裡寫圖片描述

2.程式碼

'''
  影象用txt檔案表示,是一個32*32的矩陣。該函式將這個矩陣放入一個1*1024的向量裡面。
  例如,矩陣中第一行放入向量的0~31號位置,矩陣中的第二行放入向量的32~63號位置。
'''
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


'''
  將目錄“trainingDigits”的全部檔案放進一個矩陣trainingMat中,矩陣trainingMat的行數等於檔案數,列數等於每個檔案的長度;
  函式handwritingClassTest()將目錄“testDigits”中的全部測試集進行kNN分類,並與測試集原本自帶的標籤進行比較,得出分類的錯誤率
'''
def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')           #listdir函式將返回目錄中的所有檔名
    m = len(trainingFileList)
    trainingMat = zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]     #以“.”進行劃分,獲取第0號劃分元素
        classNumStr = int(fileStr.split('_')[0]) #以“_”進行劃分,獲取第0號劃分元素
        hwLabels.append(classNumStr) #將當前樣本的標籤存放到陣列hwLabels的尾部
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        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)))


3.執行結果

這裡寫圖片描述

這裡寫圖片描述

五、使用kNN挖掘一個真實的資料集

(1)MNIST資料集

MNIST 資料集來自美國國家標準與技術研究所, National Institute of Standards and Technology (NIST). 訓練集 (training set) 由來自 250 個不同人手寫的數字構成, 其中 50% 是高中學生, 50% 來自人口普查局 (the Census Bureau) 的工作人員. 測試集(test set) 也是同樣比例的手寫數字資料.

MNIST 資料集包含了四個部分:
● Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解壓後 47 MB, 包含 60,000 個樣本)
● Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解壓後 60 KB, 包含 60,000 個標籤)
● Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解壓後 7.8 MB, 包含 10,000 個樣本)
● Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解壓後 10 KB, 包含 10,000 個標籤)

(2)MNIST資料集的儲存格式

1.TRAINING SET LABEL FILE (train-labels-idx1-ubyte):

[offset] [type] [value] [description]
0000 32 bit integer 0x00000801(2049) magic number (MSB first)
0004 32 bit integer 60000 number of items
0008 unsigned byte ?? label
0009 unsigned byte ?? label
……..
xxxx unsigned byte ?? label
The labels values are 0 to 9.

2.TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[offset] [type] [value] [description]
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 60000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
……..
xxxx unsigned byte ?? pixel
Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

3.TEST SET LABEL FILE (t10k-labels-idx1-ubyte):

[offset] [type] [value] [description]
0000 32 bit integer 0x00000801(2049) magic number (MSB first)
0004 32 bit integer 10000 number of items
0008 unsigned byte ?? label
0009 unsigned byte ?? label
……..
xxxx unsigned byte ?? label
The labels values are 0 to 9.

4.TEST SET IMAGE FILE (t10k-images-idx3-ubyte):

[offset] [type] [value] [description]
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 10000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
……..
xxxx unsigned byte ?? pixel
Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

(3)MNIST資料集的讀取方法

1.函式load_mnist(path, kind=’train’)

  從指定路徑path讀取mnist資料集,返回矩陣images存放所有影象,每行有28*28個畫素點(代表一個圖片), 共有60,0000行。返回一維陣列labels,存放每張圖片對應的標籤

2.函式print_First_10_Row(images,labels)

  從load_mnist(path, kind=’train’)中返回images, labels,將images的前10行(共10張圖片)顯示出來

'''
   def load_mnist(path, kind='train'):從指定路徑path讀取mnist資料集,返回矩陣images存放所有影象,每行有28*28個畫素點(代表一個圖片),
   共有60,0000行。返回一維陣列labels,存放每張圖片對應的標籤
'''
def load_mnist(path, kind='train'):
    """Load MNIST data from `path`"""
    labels_path = os.path.join(path,'%s-labels.idx1-ubyte'% kind)
    images_path = os.path.join(path,'%s-images.idx3-ubyte'% kind)
    with open(labels_path, 'rb') as lbpath:
        magic, n = struct.unpack('>II',lbpath.read(8))
        labels = fromfile(lbpath,dtype=uint8)
    with open(images_path, 'rb') as imgpath:
        magic, num, rows, cols = struct.unpack('>IIII',imgpath.read(16))
        images = fromfile(imgpath,dtype=uint8).reshape(len(labels), 784)
    return images, labels

'''
    def print_First_10_Row(images,labels):從load_mnist(path, kind='train')中返回images, labels,
    將images的前10行(共10張圖片)顯示出來,,並在控制檯輸出這10張圖片真實的標籤
'''
def print_First_10_Row(images,labels):
    print(labels[0:10])
    fig, ax = plt.subplots(nrows=2,ncols=5,sharex=True,sharey=True, )
    ax = ax.flatten()
    for i in range(10):
        img = images[i].reshape(28, 28)
        ax[i].imshow(img, cmap='Greys', interpolation='nearest')
    ax[0].set_xticks([])
    ax[0].set_yticks([])
    plt.tight_layout()
    plt.show()

(4)對測試集中的前100個樣本進行kNN(k=3)分類測試

所用函式:

''' classify0(inX, dataSet, labels, k):
    inX=需要進行預測分類的向量;dataSet=已經分好類的資料集;labels=對應dataSet中各個物件的分類標籤。
    將向量[inX]歸類到與其距離最近的k個向量中所屬標籤出現頻率最高的那一個標籤
'''
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]   #dataSet矩陣的列對應的是一個物件的屬性,行對應的是某一個物件;
                                     #這句返回dataSet矩陣第0列的長度(dataSet矩陣的行數,也是物件總數)
                                     #dataSetSize = dataSet矩陣的行數
    diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile(inX, (dataSetSize,1)),創造具有
                                                   #dataSetSize行1列的矩陣,矩陣每個元素都為向量[inX]。
                                                   #diffMat=dataSet中每個物件與向量[inX]的距離
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1) #sqDistances=sqDiffMat的所有列相加
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort() #返回distances中元素大小是遞增排序對應的下標
                                             #sortedDistIndicies = 與[inX]距離遞增排列的點的下標
    classCount={}          
    for i in range(k): # 0<=i<k
        voteIlabel = labels[sortedDistIndicies[i]] #返回與[inX]距離最近的第i個點的標籤 
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #遍歷完k次後,該句可以統計與[inX]距離
                                                                  #最近的k個點中,各個標籤出現的次數
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]   #sortedClassCount = 將classCount={}的標籤頻率降序返回的序列


'''
    使用kNN進行測試(MNIST的訓練集,MNIST的測試集),path是MNIST資料集在硬碟中的存放路徑,k是kNN使用的k值
'''
def kNN_MNIST(path,k):
    train_x, train_y = load_mnist(path,'train')
    test_x, test_y = load_mnist(path,'t10k')
    test_x_size=test_x.shape[0]
    print(test_x_size)
    errorCount=0.0
    for i in range(test_x_size):
        classifierResult=classify0(test_x[i], train_x, train_y, k)
        if classifierResult!=test_y[i]:
            errorCount+=1.0
        print("樣本%d,kNN分類結果:%d,真實結果:%d" % (i,classifierResult,test_y[i]))
    print("errorCount= ", errorCount)
    print("the total error rate is: %f" % (errorCount / float(test_x_size)))

結果:

這裡寫圖片描述