1. 程式人生 > >K-近鄰演算法—基本原理與實戰

K-近鄰演算法—基本原理與實戰

概述

    k-近鄰演算法(k-Nearest Neighbor, KNN),是一個理論上比較成熟的方法,也是最簡單的機器學習演算法之一,用於預測資料的類別,以及對資料進行分類。該方法的簡要思路就是採用測量不同特徵值之間的距離來進行分類。

特點

工作原理

    存在一個樣本資料集合,也稱作訓練樣本集,並且樣本集中的每個資料都存在標籤,即我們知道樣本集中每一個數據與所屬分類的對應關係。當輸入一個沒有包含標籤(也就是對應的類別)的新資料後,將新資料的每個特徵與樣本集中資料對應的特徵進行比較,然後用演算法提取樣本集中特徵與新資料最相似的資料(最近鄰)的分類標籤。一般來說,只取樣本資料集中前k個最相似的資料,這就是k-近鄰演算法中k的出處,通常k是不大於20的整數。最後選擇k個最相似資料

出現次數最多的分類,作為新資料的分類(標籤)。

使用歐式距離公式,計算兩個向量點xA和xB的距離:

kNN演算法的一般流程

(1)收集資料:可以使用任何方法。

(2)準備資料:距離計算所需要的數值,最好是結構化的資料格式。

(3)分析資料:可以使用任何方法。

(4)訓練演算法:k-近鄰演算法不需要訓練演算法。

(5)測試演算法:計算錯誤率。

(6)使用演算法:首先需要輸入樣本資料和結構化的輸出結果,然後執行k-近鄰演算法判定輸入資料分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。

實施kNN分類演算法

#kNN演算法的虛擬碼
#功能:使用k-近鄰演算法將資料劃分到某個類中
#對未知類別屬性的資料集中的每個點依次執行以下操作:
(1)計算已知類別資料集中的點與當前點之間的距離;
(2)按照距離遞增次序對資料集中的點進行排序;
(3)選取與當前點距離最小的k個點;
(4)確定前k個點所在類別的出現頻率;
(5)返回前k個點出現頻率最高的類別作為當前點的預測分類;

簡單分類示例

from numpy import *
import operator

#createDataSet():產生樣本集資料和樣本集中每個資料的型別資訊
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

函式createDataSet()初始化了四個樣本資料,每個樣本資料含有兩個特徵值,並且還初始化了標籤資訊,其中第一個資料的標籤是A,第二個是A,第三個是B,第四個是B。產生的樣本資料用於對新資料進行分類預測。

#classify0:k-近鄰演算法的基本實現,返回資料的類別預測資訊
#inX:用於分類的輸入向量
#dataSet:訓練樣本集
#labels:訓練樣本集對應的標籤向量
#k:選擇最近鄰居的數目
from numpy import *

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]                      #獲取樣本資料的行數
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet     #將輸入向量擴充套件到和樣本資料同樣大小,然後與樣本資料相減
    sqDiffMat = diffMat ** 2;                           #對相減後的陣列平方(每個元素平方);如果是矩陣,表示矩陣平方(矩陣乘法)
    sqDistances = sqDiffMat.sum(axis=1)                 #axis=0表示將矩陣或陣列的每個元素相加;
                                                        #axis=1表示將矩陣或陣列的每行的每個元素相加,相加的結果(數量和行數相同)組成一個一維的陣列
    distances = sqDistances ** 0.5                      #對陣列中的每個元素求平方根
    sortedDistIndicies = distances.argsort()            #對distances陣列中的元素進行排序,返回從小到大的索引值
    classCount = {}                                     #建立一個空字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]      #根據距離從小到大或取樣本資料的類別
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
                                                        #將類別作為key,出現次數作為value,類別每出現一次,key值加1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
                                                        #根據字典中value值的大小進行排序,預設從小到大,新增reverse=True表示從大到小
    return sortedClassCount[0][0]                       #返回出現次數最高的類別

執行classify0(),傳入上面生成的資料,返回B。

分類器的測試

    為了檢驗分類器給出的答案是否符合預期,就需要對分類器進行測試。通過大量的測試資料,我們可以得到分類器的錯誤率——分類器給出錯誤結果的次數/測試執行總次數。完美的分類器錯誤率為0,最差的分類器錯誤率為1.0。

歸一化特徵值

    對於大多數資料來說,其每個特徵的特徵值可能數值差距很大。例如某個人,身高屬性1.7m,而體重屬性70KG,這兩個屬性數值差距比較大,如果直接用尤拉距離公式計算,體重對分類結果的影響會大大提升,而真正意願則是體重和身高對分類結果的影響相同,因此需要對這些資料進行歸一化。

使用下列公式可以將任意取值範圍的特徵值轉化為0到1區間內的值:

其中oldValue是需要進行歸一化的特徵值,min是所有同類特徵值最小的特徵值,max是所有同類特徵值最大的特徵值。

程式碼示例:

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet)) #建立一個和dataSet大小相同的陣列
    m = dataSet.shape[0]                #dataSet的行數
    normDataSet = dataSet - tile(minVals, (m, 1))   #minVals擴充套件:行*m,列*n
    normDataSet = normDataSet/tile(ranges, (m, 1))  #特徵值相除
    return normDataSet, ranges, minVals

示例一:使用kNN演算法改進約會網站的配對效果

這裡使用的示例都來自書籍《機器學習實戰》,原始碼下載

(1)收集資料:提供文字檔案。

(2)準備資料:使用Python解析文字檔案。

(3)分析資料:使用Matplotlib畫二維擴散圖。

(4)訓練演算法:此步驟不適用於k-近鄰演算法。

(5)測試演算法:使用訓練資料中10%的資料進行測試。

(6)使用演算法:產生簡單的命令列程式,然後輸入特徵值進行判斷。

準備資料:將文字記錄轉換成NumPy的解析程式:

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #獲取列數
    cols = len(arrayOLines[0].split('\t'))
    returnMat = zeros((numberOfLines, cols - 1))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:cols-1]
        classLabelVector.append(int(listFromLine[-1]))  #特徵值為int型
        index += 1
    return returnMat, classLabelVector

分析資料:使用Matplotlib建立散點圖

import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])
plt.show()

準備資料:歸一化數值

採用上述方法進行歸一化

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

#測試分類器的效果
def datingClassTest():
    hoRatio = 0.10
    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], 10)
        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))

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

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent palying video games?"))
    ffMiles = float(input("frequent flier miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))

    datingDataMat, datingLables = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLables, 3)
    print("You will like the person: ", resultList[classifierResult - 1])

示例二:手寫識別系統

    原理:將影象經過圖形處理軟體轉為為二進位制的形式,然後將二進位制圖片轉化為一個向量,每一位二進位制都是向量的一個元素,將大量向量再加上對應的類別作為訓練資料集。使用演算法時,使用沒有類別的向量,計算其與訓練資料集向量的距離,通過kNN演算法預測出類別資訊。

準備資料:將影象(已經是二進位制格式)轉換為測試向量

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

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

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')    #listdir需要使用    from os import listdir
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]           #每個檔案的檔名,如:0_1.txt
        fileStr = fileNameStr.split('.')[0]         #獲取除字尾的檔名,如:0_1
        classNumStr = int(fileStr.split('_')[0])    #獲取檔案的標籤,如0_1表示數字0的第一個訓練資料,則該資料的的標籤是1
        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("The classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if classifierResult != classNumStr:
            errorCount += 1.0;

    print("The total number of errors is: %d" % errorCount)
    print("The total error rate is: %f" % (errorCount / float(mTest)))