1. 程式人生 > >機器學習之k-近鄰算法實踐學習

機器學習之k-近鄰算法實踐學習

ats mst 優化 slab 影響 nor min tex 存在

關於本文說明,筆者原博客地址位於http://blog.csdn.net/qq_37608890,本文來自筆者於2017年12月04日 22:54:26所撰寫內容(http://blog.csdn.net/qq_37608890/article/details/78714664)。

本文根據最近學習機器學習書籍 網絡文章的情況,特將一些學習思路做了歸納整理,詳情如下.如有不當之處,請各位大拿多多指點,在此謝過.

一、k-近鄰算法(k-Nearest Neighbor,KNN)概述

1、簡言之,k-近鄰算法采用測量不同特征值之間的距離方法進行分類。

2、工作原理

存在一個樣本數據集合,也稱為訓練樣本集,且樣本集中每個數據都存在標簽,也就是眾所周知樣本集中每一數據與所屬分類的對應關系。輸入沒有標簽的新數據以後,將新數據的每個特征與樣本集中數據對應的特征進行比較,然後算法提取樣本集中特征最相似數據(最近鄰)的分類標簽。一般情況下,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大於20的整數。最終,選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。

3、k-近鄰算法的一般流程

(1) 收集數據:可以使用任何方法。
(2) 準備數據: 距離計算所需要的數值,最好是結構化數據格式。
(3) 分析數據: 可以使用任何方法。
(4) 訓練算法: 此步驟不適用K-近鄰算法。
(5) 測試算法: 計算錯誤率。
(6) 使用算法: 首先需要輸入樣本數據和結構化的輸出結果,然後運行k-近鄰算法判定輸入數據分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。

4、相關特性

(1)優點: 精度高,對異常值不敏感、無輸入假定。

(2)缺點: 計算復雜度高、空間復雜度高。

(3)適用數據範圍: 數值型和標稱型。

kNN是non-parametric分類器(不做分布形式的假設,直接從數據估計概率密度),是memory-based learning,不適用於高維數據(curse of dimension),算法復雜度高(可用KD樹優化)。另外,k越小越容易過擬合,但是k很大會將分類精度(設想極限情況:k=1和k=N(樣本數))。

二、k-近鄰場景

我們知道,電影可以按題材分類,但題材本身是如何定義的?由誰來判定某部電影屬於哪個題材?即同一題材的電影會具有哪些公共特征?這些都是在做電影分類時一定要搞清楚的問題。這裏以動作片和愛情片為例做簡要說明。動作片具有哪些公共特征,使得動作片之間非常相似,卻明顯有別於愛情片?動作片中也可能有接吻鏡頭,愛情片中也可能存在打鬥鏡頭,所以,不能簡單地依靠是否存在打鬥或者接吻來判斷一部電影的類型。但很明顯的是,動作片中的打鬥鏡頭更多、愛情片中的接吻次數更頻繁,基於此類場景在一部電影中出現的次數可用來進行電影分類。

有人曾經統計過很多電影的打鬥鏡頭和接吻鏡頭, 下方圖1-1 給出了6部電影的打鬥和接吻鏡頭。假如現在有一部從未看過的電影,你如何判斷它屬於動作片還是愛情片呢?

技術分享圖片

圖1-1

首先,我們弄清楚這部未知電影中存在多少打鬥鏡頭、多少接吻鏡頭,圖1-1中問號位置是該未知電影出現的鏡頭數圖示,具體見下方表1-1。

表1-1每部電影的打鬥鏡頭數、接吻鏡頭數及電影評估類型

技術分享圖片

由圖1-1和表1-1,可用將未知電影在圖1-1的具體位置標出,利用歐式距離公式,計算出未知電影與樣本集中其他電影之間的距離,相見下方表2-2所示。

表2-2 已知電影與未知電影之間的距離

技術分享圖片

由表2-2所示,顯然,如果樣本集中所有電影與未知電影之間的距離按照遞增排序的話,可以得到k個距離最近的電影,這裏假設k=2的話,則未知電影與電影He’s Not Really into Dudes,Beautiful Woman影片類型最為相似,判定未知電影屬於愛情片。

三、示例:使用kNN算法優化約會網站的配對效果

特別提醒:有些教材或博客,在代碼實現過程中,由於python2.x和python3.x的不同,在實際執行過程中出現語法錯誤,這裏特做提醒,print語句將輸出內容一律加上();另外,python3.0版本後用input替換了raw_input,請讀者註意。

1、項目概述

海倫在使用約會網站尋找自己的約會對象。總結經驗之後,她發現曾交往過的人分三種類型:

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

她期待:

  • 工作日與魅力一般的人約會
  • 周末與極具魅力的人約會
  • 不喜歡的人則直接排除掉

2、 k-近鄰算法開發實現流程

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

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

目前,海倫搜集來一些約會對象的數據,並把這些數據存放在文本文件 datingTestSet2.txt 中,每個樣本數據占據一行,總共有 1000 行。這些樣本數據主要包含以下 3 種特征:

  • 每年獲得的飛行常客裏程數
  • 玩視頻遊戲所耗時間百分比
  • 每周消費的冰淇淋公升數

而文本文件datingTestSet2.txt的數據格式如下:


40999    9.161978      1.110180      3
15823    0.991725      0.730979      2
35432    7.398380      0.684218      3
53711    12.149747     1.389088      3
64371    9.149678      0.874905      1
9289    9.666576       1.370330      2

準備數據:使用 Python 解析文本文件

將文本記錄轉換為 NumPy 的解析程序

    def file2matrix(filename):  
       """ 
       Desc: 
           導入訓練數據 
       parameters: 
           filename: 數據文件路徑 
       return:  
           數據矩陣 returnMat 和對應的類別 classLabelVector 
       """  
       fr = open(filename)  
       # 獲得文件中的數據行的行數  
       numberOfLines = len(fr.readlines())  
       # 生成對應的空矩陣  
       # 例如:zeros(2,3)就是生成一個 2*3的矩陣,各個位置上全是 0   
       returnMat = zeros((numberOfLines, 3))  # prepare matrix to return  
       classLabelVector = []  # prepare labels return  
       fr = open(filename)  
       index = 0  
       for line in fr.readlines():  
           # str.strip([chars]) --返回移除字符串頭尾指定的字符生成的新字符串  
           line = line.strip()  
           # 以 ‘\t‘ 切割字符串  
           listFromLine = line.split(‘\t‘)  
           # 每列的屬性數據  
           returnMat[index, :] = listFromLine[0:3]  
           # 每列的類別數據,就是 label 標簽數據  
           classLabelVector.append(int(listFromLine[-1]))  
           index += 1  
       # 返回數據矩陣returnMat和對應的類別classLabelVector  
       return returnMat, classLabelVector  

執行如下命令
    datingDataMat,datingLabels=file2matrix(‘datingTestSet2.txt‘)  
    datingDataMat  

得到
array([[  4.09200000e+04,   8.32697600e+00,   9.53952000e-01],
       [  1.44880000e+04,   7.15346900e+00,   1.67390400e+00],
       [  2.60520000e+04,   1.44187100e+00,   8.05124000e-01],
       ..., 
       [  6.88460000e+04,   9.97471500e+00,   6.69787000e-01],
       [  2.65750000e+04,   1.06501020e+01,   8.66627000e-01],
       [  4.81110000e+04,   9.13452800e+00,   7.28045000e-01]])

執行 datingLabels[0:20],得到
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]

4、 分析數據:使用 Matplotlib 畫二維散點圖
執行如下代碼  
    import matplotlib  
    import matplotlib.pyplot as plt  
    fig = plt.figure()  
    ax = fig.add_subplot(111)  
    ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0*array(datingLabels), 15.0*array(datingLabels))  
    plt.show()  



得到如下圖

技術分享圖片

而下圖中采用矩陣的第一和第三列屬性得到很好的展示效果,清晰地標識了三個不同的樣本分類區域,具有不同愛好的人其類別區域也不同。

技術分享圖片

5、 準備數據:歸一化數值

歸一化數據 (歸一化是一個讓權重變為統一的過程)

序號玩視頻遊戲所耗時間百分比每年獲得的飛行常客裏程數每周消費的冰淇淋公升數樣本分類
1 0.8 400 0.5 1
2 12 134 000 0.9 3
3 0 20 000 1.1 2
4 67 32 000 0.1 2

樣本3和樣本4的距離:

(0?67)2+(20000?32000)2+(1.1?0.1)2?????????????????????????????????

歸一化特征值,消除特征之間量級不同導致的影響

歸一化定義: 我是這樣認為的,歸一化就是要把你需要處理的數據經過處理後(通過某種算法)限制在你需要的一定範圍內。首先歸一化是為了後面數據處理的方便,其次是保正程序運行時收斂加快。 方法有如下:

  • 線性函數轉換,表達式如下:  

    y=(x-MinValue)/(MaxValue-MinValue)  

    說明:x、y分別為轉換前、後的值,MaxValue、MinValue分別為樣本的最大值和最小值。  

  • 對數函數轉換,表達式如下:  

    y=log10(x)  

    說明:以10為底的對數函數轉換。

    如圖:技術分享圖片

  • 反余切函數轉換,表達式如下:

    y=atan(x)*2/PI 

    如圖:技術分享圖片

  • 式(1)將輸入值換算為[-1,1]區間的值,在輸出層用式(2)換算回初始值,其中和分別表示訓練樣本集中負荷的最大值和最小值。  

在統計學中,歸一化的具體作用是歸納統一樣本的統計分布性。歸一化在0-1之間是統計的概率分布,歸一化在-1--+1之間是統計的坐標分布。

    def autoNorm(dataSet):  
        """ 
        Desc: 
            歸一化特征值,消除特征之間量級不同導致的影響 
        parameter: 
            dataSet: 數據集 
        return: 
            歸一化後的數據集 normDataSet. ranges和minVals即最小值與範圍,並沒有用到 
     
        歸一化公式: 
            Y = (X-Xmin)/(Xmax-Xmin) 
            其中的 min 和 max 分別是數據集中的最小特征值和最大特征值。該函數可以自動將數字特征值轉化為0到1的區間。 
        """  
        # 計算每種屬性的最大值、最小值、範圍  
        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  

訓練算法:此步驟不適用於 k-近鄰算法 因為測試數據每一次都要與全量的訓練數據進行比較,所以這個過程是沒有必要的。

6 、 測試算法

作為完整程序驗證分類器

使用海倫提供的部分數據作為測試樣本。如果預測分類與實際類別不同,則標記為一個錯誤。

kNN 分類器針對約會網站的測試代碼

    def datingClassTest():  
        """ 
        Desc: 
            對約會網站的測試方法 
        parameters: 
            none 
        return: 
            錯誤數 
        """  
        # 設置測試數據的的一個比例(訓練數據集比例=1-hoRatio)  
        hoRatio = 0.1  # 測試範圍,一部分測試一部分作為樣本  
        # 從文件中加載數據  
        datingDataMat, datingLabels = file2matrix(‘datingTestSet2.txt‘)  # load data setfrom file  
        # 歸一化數據  
        normMat, ranges, minVals = autoNorm(datingDataMat)  
        # m 表示數據的行數,即矩陣的第一維  
        m = normMat.shape[0]  
        # 設置測試的樣本數量, numTestVecs:m表示訓練樣本的數量  
        numTestVecs = int(m * hoRatio)  
        print ‘numTestVecs=‘, numTestVecs  
        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)  

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

上面已經在數據上對分類器進行了測試,現在則可以使用這個分類器幫助海倫對於約會對象進行分類,即海倫在網站上找到某人並輸入他的信息,該程序會給出她對對方喜歡程度的預測值.

    def clasdifyPerson():  
        resultList = [‘not at all‘, ‘in small doses‘, ‘in large doses‘]  
        percentTats = float(raw_input("percentage of time spent playing video games ?"))  
        ffMiles = float(raw_input("frequent filer 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([ffMils, percentTats, iceCream])  
        classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels, 3)  
        print ("You will probably like this person: ", resultList[classifierResult - 1])  

實際運行效果如下:
>>> kNN.classifyPerson()
percentage of time spent playing video games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You will probably like this person: in small doses

四、 項目案例2: 手寫數字識別系統

1、 項目概述

構造一個能識別數字 0 到 9 的基於 KNN 分類器的手寫數字識別系統。

需要識別的數字是存儲在文本文件中的具有相同的色彩和大小:寬高是 32 像素 * 32 像素的黑白圖像。

2、 開發流程

收集數據:提供文本文件。 準備數據:編寫函數 img2vector(), 將圖像格式轉換為分類器使用的向量格式 分析數據:在 Python 命令提示符中檢查數據,確保它符合要求 訓練算法:此步驟不適用於 KNN 測試算法:編寫函數使用提供的部分數據集作為測試樣本,測試樣本與非測試樣本的          區別在於測試樣本是已經完成分類的數據,如果預測分類與實際類別不同,          則標記為一個錯誤 使用算法:本例沒有完成此步驟,若你感興趣可以構建完整的應用程序,從圖像中提取          數字,並完成數字識別,美國的郵件分揀系統就是一個實際運行的類似系統 

收集數據: 提供文本文件

目錄 trainingDigits中包含了大約 2000 個例子,每個例子內容如下圖所示,每個數字大約有 200 個樣本;目錄 testDigits中包含了大約 900 個測試數據。

技術分享圖片

準備數據: 編寫函數 img2vector(), 將圖像文本數據轉換為分類器使用的向量

將圖像文本數據轉換為向量

    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  

分析數據:在 Python 命令提示符中檢查數據,確保它符合要求

在 Python 命令行中輸入下列命令測試 img2vector 函數,然後與文本編輯器打開的文件進行比較:

>>> testVector = kNN.img2vector(‘testDigits/0_13.txt‘)
>>> testVector[0,0:31]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
>>> testVector[0,31:63]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

訓練算法:此步驟不適用於 KNN

因為測試數據每一次都要與全量的訓練數據進行比較,所以這個過程是沒有必要的。

測試算法:編寫函數使用提供的部分數據集作為測試樣本,如果預測分類與實際類別不同,則標記為一個錯誤
def handwritingClassTest():  
    # 1. 導入訓練數據  
    hwLabels = []  
    trainingFileList = listdir(‘trainingDigits‘)  # load the training set  
    m = len(trainingFileList)  
    trainingMat = zeros((m, 1024))  
    # hwLabels存儲0~9對應的index位置, trainingMat存放的每個位置對應的圖片向量  
    for i in range(m):  
        fileNameStr = trainingFileList[i]  
        fileStr = fileNameStr.split(‘.‘)[0]  # take off .txt  
        classNumStr = int(fileStr.split(‘_‘)[0])  
        hwLabels.append(classNumStr)  
        # 將 32*32的矩陣->1*1024的矩陣  
        trainingMat[i, :] = img2vector(‘trainingDigits/%s‘ % fileNameStr)  
  
    # 2. 導入測試數據  
    testFileList = listdir(‘testDigits‘)  # iterate through the test set  
    errorCount = 0.0  
    mTest = len(testFileList)  
    for i in range(mTest):  
        fileNameStr = testFileList[i]  
        fileStr = fileNameStr.split(‘.‘)[0]  # take off .txt  
        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))) 

使用算法:本例沒有完成此步驟,若你感興趣可以構建完整的應用程序,從圖像中提取數字,並完成數字識別,美國的郵件分揀系統就是一個實際運行的類似系統

五、 K-近鄰算法小結

截至目前,我們做一總結,k-近鄰算法必須保存全部數據集,如果訓練數據集很大,必須使用大量的存儲空間.由於必須對數據集中的每個數據計算距離值,實際使用時可能非常耗時.此外,k-近鄰還有以缺陷是它沒辦法給出任何數據的基礎結構信息,也就無法得知平均實例樣本和典型實例樣本有哪些特征.而要解決這個問題,需要使用概率測量方法來處理.

k 近鄰算法有 三個基本的要素:

  • k 值的選擇

    • k 值的選擇會對 k 近鄰算法的結果產生重大的影響。
    • 如果選擇較小的 k 值,就相當於用較小的鄰域中的訓練實例進行預測,“學習”的近似誤差(approximation error)會減小,只有與輸入實例較近的(相似的)訓練實例才會對預測結果起作用。但缺點是“學習”的估計誤差(estimation error)會增大,預測結果會對近鄰的實例點非常敏感。如果鄰近的實例點恰巧是噪聲,預測就會出錯。換句話說,k 值的減小就意味著整體模型變得復雜,容易發生過擬合。
    • 如果選擇較大的 k 值,就相當於用較大的鄰域中的訓練實例進行預測。其優點是可以減少學習的估計誤差。但缺點是學習的近似誤差會增大。這時與輸入實例較遠的(不相似的)訓練實例也會對預測起作用,使預測發生錯誤。 k 值的增大就意味著整體的模型變得簡單。
    • 近似誤差和估計誤差,請看這裏:https://www.zhihu.com/question/60793482
  • 距離度量

    • 特征空間中兩個實例點的距離是兩個實例點相似程度的反映。
    • k 近鄰模型的特征空間一般是 n 維實數向量空間 技術分享圖片 。使用的距離是歐氏距離,但也可以是其他距離,如更一般的技術分享圖片 距離,或者 Minkowski 距離。
  • 分類決策規則

    • k 近鄰算法中的分類決策規則往往是多數表決,即由輸入實例的 k 個鄰近的訓練實例中的多數類決定輸入實例的類。

機器學習之k-近鄰算法實踐學習