機器學習實戰(筆記):第 2 章 k-近鄰算法

第 2 章 k-近鄰算法

[TOC]

本章內容:

  • k-近鄰分類算法
  • 從文本文件中解析和導入數據
  • 使用 Matplotlib 創建擴散圖
  • 歸一化數值

1. k-近鄰算法概述

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

  • 優點:精度高、對異常值部敏感、無數據輸入假定
  • 缺點:計算復雜度高、空間復雜度高
  • 適用數據范圍:數值型和標稱型

k-近鄰算法(kNN):它的工作原理是存在一個樣本數據集合(訓練樣本集),并且樣本集中每個數據都存在標簽,即我們知道樣本集中每一數據域所屬分類的對應關系。輸入每一標簽的新數據后,將新數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本集中特征最相似數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本數據集中前 k 個最相似的數據,這就是 k-近鄰算法中 k 的出處,通常 k 是不大于 20 的整數。最后,選擇 k 個最相似數據中出現次數最多的分類,作為新數據的分類。

示例:基于電影中出現的親吻、打斗出現的次數,使用 k-近鄰算法構造程序,自動劃分電影的題材類型。有人統計過很多電影的打斗鏡頭和接吻鏡頭,圖2-1 顯示了 6 部電影的打斗和接吻鏡頭數。假如有一部未看過的電影,如何確定的它是愛情片還是動作片呢?可以使用 kNN 來解決這個問題:

2017-12-23_104833.png

首先我們需要知道這個未知電影存在多少個打斗鏡頭和接吻鏡頭,圖2-1 問號位置是該未知電影出現的鏡頭數圖形化展示,具體數字參見表2-1:

2017-12-23_105339.png

計算未知電影與樣本集中其他電影的距離,如表2-2 所示:

2017-12-23_105737.png

現在得到了樣本集中所有電影與未知電影的距離,按照距離遞增排序,可以找到 k 個距離最近的電影。假定 k=3 ,則三個最近的電影依次是He...、Bea...、Cal...。k-近鄰算法按照距離最近的三部電影的類型,決定未知電影的類型,而這三部電影都是愛情片,因此判定未知電影是愛情片。

k-近鄰算法的一般流程:

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

1.1 準備:使用 Python 導入數據

s_1_kNN.py文件(創建數據集和標簽):

import numpy as np
import operator

def createDataSet():
    group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    print(group)
    labels = ['A','A','B','B']
    print(labels)
    return  group, labels

圖2-2 是帶有類標簽信息的四個數據點:

2017-12-23_112043.png

現在我們已經知道 Python 如何解析數據,如何加載數據,以及 kNN 算法的工作原理,接下來將使用這些方法完成分類任務。

1.2 從文本文件中解析數據

本節使用程序中的 classify0() 函數運行 kNN 算法,為每組數據分類。該函數的功能是使用 k-近鄰算法將每組數據劃分到某個類中,其偽代碼如下:

對未知類別屬性的數據集中的每個點依次執行以下的操作:

  1. 計算已知類別數據集中的點與當前點之間的距離
  2. 按照距離遞增次序排序
  3. 選取與當前點距離最小的 k 個點
  4. 確定前 k 個點所在類別的出現頻率
  5. 返回前 k 個點出現頻率最高的類別作為當前點的預測分類

classify0() 如下:

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0] # dataSet 的長度
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # 分別求出 測試數據的向量減去訓練數據的向量
    sqDiffMat = diffMat ** 2 # 求平方
    sqDistances = sqDiffMat.sum(axis=1) # 求和
    distances = sqDistances ** 0.5 # 求出距離
    sortedDistIndicies = np.argsort(distances) # 升序的索引值
    classCount = {} # 記錄近鄰標簽
    for i in range(k):
        voteilabel = labels[sortedDistIndicies[i]] # 近鄰標簽值
        classCount[voteilabel] = classCount.get(voteilabel,0) + 1 # 記錄近鄰標簽值得數量
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 按近鄰標簽數量排序
    return sortedClassCount[0][0]

classify0() 護士中有 4 個輸入參數:用于分類的輸入向量是 inX,輸入訓練樣本集是 dataSet,標簽向量是 labels,最后的參數 k 表示用于選擇最近鄰居的數目,其中標簽向量的元素數目和矩陣 dataSet 的行數相同。通過使用歐式距離公式,計算兩個向量點 xA 和 xB 之間的距離:

image.png

計算完所有點之間的距離后,可以對數據按照從小到大的次序排序。然后確定前 k 個距離最小元素所在的主要分類,輸入 k 總是正整數;最后,將 classCount 字典分解為元組列表,然后使用工程需第二行導入運算符模塊的 itemgetter 方法,按照第二個元素的次序對元組進行排序。此處的排序為逆序,即按照從最大到最小次序排序,最后返回發生頻率最高的元素標簽。

進行預測:

    group, labels = createDataSet()
    result = classify0([0.4,0.2], group, labels, 3)
    print(result) # B

1.3 如何測試分類器

通過大量的測試數據,我們可以得到分類器的 錯誤率 :分類器給出錯誤結果的次數除以測試執行的總數。錯誤率是常用的評估方法,主要用于評估分類器在某個數據集上的執行效果

2. 示例:使用 k-近鄰算法改進約會網站的配對效果

將約會網站上的人選進行分類:不喜歡的人、魅力一般的人、極具魅力的人

示例:在約會網站上使用 k-近鄰算法:

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

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

樣本主要包含以下3種特征:

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

在將特征數據輸入到分類器之前,必須將待處理數據的格式改變為分類器可接受的格式。

file2matrix 函數用來處理輸入數據的格式。該函數的輸入為文本文件名字符串,輸出為訓練樣本矩陣和類標簽向量。

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines() # 讀取文件
    numberOfLines = len(arrayOLines) # 行數
    returnMat = np.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

2.2 分析數據:使用 Matplotlib 創建散點圖

scatter 函數支持個性化標記散點圖上的點

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

2.3 準備數據:歸一化數值

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

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

其中 min 和 max 分別是數據集中的最小特征值和最大特征值。

添加 autoNorm 函數,來自動將數字特征值轉化為 0 到 1 的區間:

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals # 差值
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m,1)) # 數據 - 最小值
    normDataSet = normDataSet / np.tile(ranges, (m,1)) # 上面的值 / 最小值
    # normDataSet = np.linalg.solve(normDataSet,np.tile(ranges, (m,1)))
    return  normDataSet,ranges,minVals

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

添加 ditingClassTest 函數:

def datingClassTest():
    hoRatio = 0.10
    filename = 'datingTestSet2.txt'
    datingDataMat, datingLabels = file2matrix(filename)
    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("classifierResult:" , classifierResult)
        print('datingLabels[i]:',datingLabels[i])
        if(classifierResult != datingLabels[i]):
            errorCount += 1.0
    print('the total error rate is: %f' % (errorCount / float(numTestVecs)))

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

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

添加 classifyPerson 函數,來輸入個人信息,判斷分類:

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percenTats = float(input('percentage of time spent playing video games?'))
    ffMiles = float(input('freguent flier miles earned per year?'))
    iceCream = float(input('liters of ice cream consumed per year?'))
    filename = 'datingTestSet2.txt'
    datingDataMat, datingLabels = file2matrix(filename)
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = np.array([ffMiles, percenTats, iceCream])
    classifierResult = classify0((inArr - minVals) / ranges,normMat, datingLabels, 3)
    print('you will probably like this person:',resultList[classifierResult - 1])

3. 示例:手寫識別系統

示例說明:構造使用 k-近鄰分類器的手寫識別系統,來識別數字 0 到 9.需要識別的數字已經使用圖形處理軟件,處理成具有相同的色彩和大小的黑白圖像。

示例:使用 k-近鄰算法的手寫識別系統

  1. 收集數據:提供文本文件;
  2. 準備數據:編寫函數 classify(),將圖像格式轉換為分類器使用的 list 格式;
  3. 分析數據:在 python 命令提示符中檢查數據,確保它符合要求;
  4. 訓練算法:此步驟不適用 k-近鄰算法;
  5. 測試算法:編寫函數使用提供的部分數據集作為測試樣本,測試樣本與非測試樣本的區別在于測試樣本是已經完成分類的數據,如果預測分類與實際類別不同,則標記為一個錯誤;
  6. 使用算法:本例沒有完成此步驟。

3.1 準備數據:將圖像轉換為測試向量

添加 img2vector 函數,將圖像轉換為向量:該函數創建 1 X 1024 的 numpy 數組,然后打開給定的文件,循環獨處文件的前 32 行,并將每行的頭 32 個字符值存儲在 numpy 數組中,最后返回數組:

def img2vector(filename):
    returnVect = np.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

3.2 測試算法:使用 k-近鄰算法識別手寫數字

添加 handwritingClassTest 函數,來測試分類器:

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)
    trainingMat = np.zeros((m,1024))
    
    # 訓練
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr) # 訓練集標簽
        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('classifierResult:',classifierResult)
        print('classNumStr:',classNumStr)
        if(classifierResult != classNumStr):
            errorCount += 1.0
    print('errorCount:',errorCount)
    print('rate:',errorCount/float(mTest))

改變變量 k 的值、修改函數 handwritingClassTest 隨機選取訓練樣本、改變訓練樣本的數目,都會對 k-近鄰算法的錯誤率產生影響。

4. 本章小結

k-近鄰算法是分類數據最簡單最有效的算法。k-近鄰算法是基于實例的學習,使用算法時我們必須有接近實際數據上的訓練樣本數據。k-近鄰算法必須保存全部數據集,如果訓練數據集很大,必須使用大量的存儲空間。此外,由于必須對數據集中的每個數據計算距離值,實際使用時可能非常耗時。

另一個缺陷是它無法給出任何數據的基礎結構信息,因此我們也無法知曉平均實例樣本具有什么特征。

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

推薦閱讀更多精彩內容