[機器學習實戰]k近鄰算法

從一個最基本的算法示例來入門機器學習

第一個分類算法 --- k-近鄰算法(簡單地說,k近鄰算法采用測量不同特征值之間的距離方法進行分類。)


前言:

  • 工具, 我用的 Pycharm ,Python V3.X和V2.x 隨意切換,建議初學者還是下個編輯工具方便些. Pycharm環境配置
  • 本篇基于 Python 2.X 環境,Mac OS 系統
  • 模塊導入 numpy,operator,matplotlib,不知道從哪導入?
  • 這里用的比較的多的NumPy數組,建議先簡單了解下,NumPy使用手冊
  • 建議第一遍先過一下算法大概的思想,之后再細看代碼
  • 源碼在這里

需求分析,使用k-近鄰算法改進約會網站的配對效果

海倫一直使用在線約會網站尋找適合自己的約會對象。盡管約會網站會推薦不同的 人選,但她沒有從中找到喜歡的人。經過一番總結,她發現曾交往過三種類型的人:

  • 不喜歡的人
  • 魅力一般的人
  • 極具魅力的人

這次的分類軟件可以更好地幫助她將匹配對象劃分到確切的分類中


k-近鄰算法流程

  1. 收集數據:提供文本文件
  2. 準備數據: 使用 Python 解析文本文件
  3. 分析數據:使用 Matplotlib 畫二維擴散圖
  4. 訓練算法:此步驟不適用于卜近鄰算法
  5. 使用海倫提供的部分數據作為測試樣本,測試樣本和非測試樣本的區別在于:測試樣本是已經完成分類的數據,如果預測分類與實際類別不同,則標記為一個錯誤
  6. 使用算法:產生簡單的命令行程序,然后海倫可以輸入一些特征數據以判斷對方是否
    為自己喜歡的類型

準備數據:從文本文件中解析數據

本次數據來源海倫的約會數據,放在文本文件 datingTestSet2.txt 中,每 個樣本數據占據一行,總共有1000行。樣本主要包含以下3種特征:

  • 每年獲得的飛行??屠锍虜?/li>
  • 玩視頻游戲所耗時間百分比
  • 每周消費的冰淇淋公升數

在將上述特征數據輸人到分類器之前,必須將待處理數據的格式改變為分類器可以接受的格式 。在kNN.py中創建名為file2matrix的函數,以此來處理輸人格式問題。該函數的輸人為文件名字符串,輸出為訓練樣本矩陣和類標簽向量。

def file2matrix(filename):
    love_dictionary = {'非常喜歡': 3, '一般喜歡': 2, '討厭': 1}
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)  # get the number of lines in the file
    returnMat = zeros((numberOfLines, 3))  # prepare matrix to return
    classLabelVector = []  # prepare labels return
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:3]
        if (listFromLine[-1].isdigit()):
            classLabelVector.append(int(listFromLine[-1]))
        else:
            classLabelVector.append(love_dictionary.get(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector


首先我們需要知道文本文件包含多 少行。打開文件,得到文件的行數。然后創建以零填充的矩陣NumPy(實際上,NumPy是一 個二維數組,這里暫時不用考慮其用途)。為了簡化處理,我們將該矩陣的另一維度設置為固定 值3 , 你可以按照自己的實際需求增加相應的代碼以適應變化的輸人值。循環處理文件中的每行 數據 , 首先使用函數line.strip()截取掉所有的回車字符,然后使用tab字符\t將上一步得 到的整行數據分割成一個元素列表。接著,我們選取前3個元素,將它們存儲到特征矩陣中。Python 語言可以使用索引值-1表示列表中的最后一列元素,利用這種負索引,我們可以很方便地將列表 的最后一列存儲到向量classLabelVector中。需要注意的是,我們必須明確地通知解釋器,告 訴它列表中存儲的元素值為整型,否則Python語言會將這些元素當作字符串處理。以前我們必須自己處理這些變量值類型問題.

測試一下,讀出來的數據是這樣

屏幕快照 2017-10-09 下午3.46.43.png

散點圖,觀察數據的樣本分布

只是為了能比較直觀的看到樣本之間的模式,不在算法內

一般來說,圖形化的方式能更直觀地展示數據。接下來下面用 Matplotlib 來圖形化展示數據內容,看一下數據之間的模式,測試代碼。

def matplotTest():
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
    plt.show()

圖二.png

這是玩視頻游戲所耗時間百分比(X)與每周消費的冰激凌公升數的散點圖(Y),可以看到三個樣本的分布情況,不過區分度不是很清晰

接下來取矩陣列的0和1列

datingDataMat[:,0],datingDataMat[:,1]
圖三.png

這是每年飛行??屠锍虜?X)與玩視頻游戲所占百分比的約會數據散點圖(Y)

清晰地標識了三個不同的樣本分類區域,具有不同愛好的人其類別區域也不同。

準備數據:歸一化數值

計算倆點間的距離公式

圖四.png

很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大,也就是說,每年 獲取的飛行??屠锍虜祵τ谟嬎憬Y果的影響將遠遠大于表2-3中其他兩個特征— 玩視頻游戲的 和每周消費冰洪淋公升數— 的影響。而產生這種現象的唯一原因,僅僅是因為飛行??屠锍虜?遠大于其他特征值。但海倫認為這三種特征是同等重要的,因此作為三個等權重的特征之一,飛 行??屠锍虜挡⒉粦撊绱藝乐氐赜绊懙接嬎憬Y果。

在處理這種不同取值范圍的特征值時,我們通常采用的方法是將數值歸一化,如將取值范圍 處理為0到1或者-1到1之間。下面的公式可以將任意取值范圍的特征值轉化為0到1區間內的值:

newValue = {oldValue-min)/(max-min)

其中min和max分別是數據集中的最小特征值和最大特征值。雖然改變數值取值范圍增加了 分類器的復雜度,但為了得到準確結果,必須這樣做。定義一個函數,將數字特征值轉化為0到1的區間。

為了歸一化特征值,我們必須使用當前值減去最小值,然后除以取值范圍. 需要注意的是,特征 值矩陣有1000 * 3個值,而minVals和range的值都為1 * 3。為了解決這個冋題,我們使用NumPY函數將變量內容復制成輸入矩陣同樣大小的矩陣,注意這是具體特征值相除 ,而 對于某些數值處理軟件包,/可能意味著矩陣除法,但在NumPy庫中,矩陣除法需要使用函數 linalg .solve (matA,matB) 。

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
    
#將每列的最小值放在變量minVals中,將最大值放在變量 maxVals中,其中dataSet.min(0)中的參數0使得函數可以從列中選取最小值,而不是選取當前行的最小值。然后,函數計算可能的取值范圍,并創建新的返回矩陣    

這里可以打印出歸一之后的結果,測試代碼

def solveTest():
    datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
    print autoNorm(datingDataMat)

輸出

圖五.png

測試算法:作為完整程序驗證分類器

機器學習算法一個很重要的工作就是評估算法的正確率,通常我們只提供已有數據的90%作為訓練樣本來訓練分類 器 ,而使用其余的10%數據去測試分類器,檢測分類器的正確率,這里隨意選擇10%數據而不影響其隨機性。

使用錯誤率來檢測分類器的性能。對于分類器來說,錯誤率就是分類 器給出錯誤結果的次數除以測試數據的總數,完美分類器的錯誤率為0,而錯誤率為1.0的分類器 不會給出任何正確的分類結果。代碼里我們定義一個計數器變量,每次分類器錯誤地分類數據, 計數器就加1, 程序執行完成之后計數器的結果除以數據點總數即是錯誤率

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): #表示提取前k個值
        voteIlabel = labels[sortedDistIndicies[i]] #排在第i位置的類別
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 #對 classCount[voteIlabel] 的值進行加一,如果不存在則初始化為1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) #出現頻次列表classCount 進行倒序排序
    return sortedClassCount[0][0]
#分類器

classify0分類算法流程,對未知類別屬性的數據集中的每個點依次執行以下操作:
(1)計算已知類別數據集中的點與當前點之間的距離;
(2)按照距離遞增次序排序;
(3)選取與當前點距離最小k幾個點;
(4)確定前k個點所在類別的出現頻率;
(5)返回前k個點出現頻率最高的類別作為當前點的預測分類。


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

從文件中讀取數據并將其轉換為歸一化特征值。接著計算測試向量的數量,此步決定了 normMat向量中哪些數據用于測試,哪些數據用于分類器的訓練樣本;然后將這兩部分數據輸人到原始kNN分類器函數classifyO。最后,函數計算錯誤率并輸出結果

調用測試函數 datingClassTest(),輸出

圖六.png

可以看到,約會數據集的錯誤率5%,還算是一個可以接受的結果,可以改變函數 datingClassTest內變量 hoRatio 和變量 k 的值,檢測錯誤率是否隨著變量值的變化而增加。依 賴于分類算法、數據集和程序設置,分類器的輸出結果可能有很大的不同

這個例子表明我們可以正確地預測分類,錯誤率是5%。海倫完全可以輸人未知對象的 屬性信息’由分類軟件來幫助她判定某一對象的可交往程度:討厭、一般喜歡、非常喜歡。

使用算法:構建完整可用系統

現在可以做出一個小程序,通過該程序海倫會在約會網站上找到某個人并輸入他的信息。 程序會給出她對對方喜歡程度的預測值


def classifyPerson():
    resultList = ['討厭', '一般喜歡', '非常喜歡']
    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: %s" % resultList[classifierResult - 1]

測試下,運行函數,依次輸入

玩視頻游戲的百分比?  12
每年的飛行公里數  30354
每周消費的冰淇淋公升數  2

然后可以得到 你對他的喜歡程度 非常喜歡 的結論


到此為止,算是初步摸到了機器學習的門檻,后續會有包括決策樹,樸素貝葉斯等進階算法的學習總結篇,共同進步.

WechatIMG1.jpeg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容