1. 程式人生 > >《機器學習實戰》第二章:k-近鄰演算法(2)約會物件分類

《機器學習實戰》第二章:k-近鄰演算法(2)約會物件分類

這是KNN一個新例子。

在一個約會網站裡,每個約會物件有三個特徵:

(1)每年獲得的飛行常客里程數(額...這個用來判斷你是不是成功人士?)

(2)玩視訊遊戲所耗時間百分比(額...這個用來判斷你是不是肥宅?)

(3)每週消費的冰激凌公升數(額...這個是何用意我真不知道了...)

然後給貼標籤。標籤種類有三種:

(1)根本不喜歡

(2)有一點喜歡

(3)喜歡得不得了

------------------------------------------------------------------------------------------------------------------

我們首先要從資料檔案裡面把訓練集讀入記憶體。

資料檔案大概長這樣:



第一列是飛行里程數的特徵值,第二列是遊戲百分比,第三列是冰淇淋公升數。最後一列是標籤,1~3表示喜歡程度。

接下來是程式碼,file2matrix 函式將檔案中的特徵資料和標籤讀到矩陣和list裡面

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)

    returnMat = zeros((numberOfLines,3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector

值得注意的是這個 zeros 函式,構造一個n×n的零矩陣。

另外第12行,偷懶的話可以這樣寫:

returnMat[index] = listFromLine

------------------------------------------------------------------------------------------------------------------

然後作者介紹了通過散點圖來分析資料的方法,用的是metplotlib這個包。

import matplotlib.pyplot as plt

def matrix2plot(dataSet):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(dataSet[:, 0], dataSet[:, 1], 15 * array(datingLabels), array(datingLabels))
    plt.show()

add_subplot(x,y,z) 是這麼個意思:將畫布劃分為x行y列,影象畫在從左到右從上到下的第z塊。

用來畫出散點。這裡它接收了4個引數:

(1)橫軸資料。這裡是dataSet[:, 0],也就是資料集的第1個特徵(飛行里程數)

(2)縱軸資料。這裡是dataSet[:, 1],也就是資料集的第2個特徵(遊戲百分比)

(3)每個散點的標度(scalar),從實際上看是散點的半徑,或者說有多“粗”。這裡是15 * array(datingLabels)。什麼意思呢?每個散點對應一個標籤datingLabel,要麼1,要麼2,要麼3。把這個值乘以15,變成15,30,45,意思就是標籤為1的散點的“粗度”是15,以此類推。其實就是為了在圖上面好看出散點的標籤。
(4)每個散點的顏色。這裡是array(datingLabels)。意思是不同的散點,按照他的標籤(1或2或3)給他分配一種顏色。目的也是為了在圖上面好看出散點的標籤。


效果如下:


橫座標是飛行里程數,縱座標是遊戲百分比。綠色點標籤是1(根本不喜歡),黃色點標籤是2(有一點不喜歡),紫色點標籤是3( 喜歡得不得了)。


------------------------------------------------------------------------------------------------------------------
我們現在有個問題。不是要算歐氏距離嗎,那麼比如(20000, 0, 0.1)和(32000, 67, 0.1)這兩組資料,如果要算歐氏距離的話,那麼顯然第一個特徵的貢獻率是最大的,因為其他兩個特徵的數量級和他沒法比啊。

這就需要做一個預處理,叫做“數值歸一化”,把任意取值範圍的特徵值轉化為0到1區間內的值。公式如下:
newValue = (oldVlue - min) / (max - min)

程式碼很容易理解:

def autoNorm(dataSet):
    minVals = dataSet.min(0) # 每列的最小值
    maxVals = dataSet.max(0) # 每列的最大值
    ranges = maxVals - minVals #取值範圍
    normDataSet = zeros(shape(dataSet)) # 按資料的行數、列數構造0
    m = dataSet.shape[0] # 資料的條數
    normDataSet = dataSet - tile(minVals, (m,1))    # tile:構造每一行都是minVals的資料
    normDataSet = normDataSet / tile(ranges, (m,1)) # tile:構造每一行都是ranges的資料
    return normDataSet, ranges, minVals

------------------------------------------------------------------------------------------------------------------

現在來測試一下分類器的效果。大概過程是:

(1)從檔案中讀取資料,並歸一化特徵值;

(2)抽出一部分來用作分類器的訓練樣本,剩下的用於測試。

(3)開始訓練+測試,並統計錯誤率

def datingClassTest():
    hoRatio = 0.50
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    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))

normMat 是特徵值歸一化後的矩陣。m是資料的條數。hoRatio是測試樣本的比例,這裡是50%,所以有一半的樣本拿去訓練,另一半做測試。numTestVecs 是測試樣本數量。

第9行呼叫classify0(原始碼在上篇部落格),逐條地將測試樣本(前numTestVecs條資料)進行分類,然後驗證準確與否。normMat[numTestVecs:m,:] 就是訓練集(下標從numTestVecsm-1的資料)。

測試結果:


錯誤率為5.0%。還...不錯

------------------------------------------------------------------------------------------------------------------

好。現在我們將這個分類器“產品化”,就是可以接受使用者輸入,然後幫忙分類與預測。

def classifyPerson():
    resultList = ['壓根不喜歡', '有一點喜歡', '喜歡得不得了']
    ffMiles = float(raw_input("飛行里程?"))
    percentTats = float(raw_input("喜歡玩電子遊戲的時間?"))
    iceCream = float(raw_input("吃冰淇淋的量?"))
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3)
    print "你喜歡這個人的程度很可能是:", resultList[classifierResult - 1];

resultList枚舉了標籤的種類。inArr是待預測資料,在傳入classify0前進行了歸一化。

執行結果: