機(jī)器學(xué)習(xí)實戰(zhàn)(筆記):第 2 章 k-近鄰算法

第 2 章 k-近鄰算法

[TOC]

本章內(nèi)容:

  • k-近鄰分類算法
  • 從文本文件中解析和導(dǎo)入數(shù)據(jù)
  • 使用 Matplotlib 創(chuàng)建擴(kuò)散圖
  • 歸一化數(shù)值

1. k-近鄰算法概述

簡單地說当辐,k-近鄰算法 采用測量不同特征值之間的距離方法進(jìn)行分類懦铺。

  • 優(yōu)點:精度高、對異常值部敏感、無數(shù)據(jù)輸入假定
  • 缺點:計算復(fù)雜度高儡湾、空間復(fù)雜度高
  • 適用數(shù)據(jù)范圍:數(shù)值型和標(biāo)稱型

k-近鄰算法(kNN):它的工作原理是存在一個樣本數(shù)據(jù)集合(訓(xùn)練樣本集),并且樣本集中每個數(shù)據(jù)都存在標(biāo)簽,即我們知道樣本集中每一數(shù)據(jù)域所屬分類的對應(yīng)關(guān)系。輸入每一標(biāo)簽的新數(shù)據(jù)后谤民,將新數(shù)據(jù)的每個特征與樣本集中數(shù)據(jù)對應(yīng)的特征進(jìn)行比較,然后算法提取樣本集中特征最相似數(shù)據(jù)(最近鄰)的分類標(biāo)簽疾宏。一般來說张足,我們只選擇樣本數(shù)據(jù)集中前 k 個最相似的數(shù)據(jù),這就是 k-近鄰算法中 k 的出處坎藐,通常 k 是不大于 20 的整數(shù)为牍。最后,選擇 k 個最相似數(shù)據(jù)中出現(xiàn)次數(shù)最多的分類岩馍,作為新數(shù)據(jù)的分類碉咆。

示例:基于電影中出現(xiàn)的親吻、打斗出現(xiàn)的次數(shù)蛀恩,使用 k-近鄰算法構(gòu)造程序疫铜,自動劃分電影的題材類型。有人統(tǒng)計過很多電影的打斗鏡頭和接吻鏡頭双谆,圖2-1 顯示了 6 部電影的打斗和接吻鏡頭數(shù)块攒。假如有一部未看過的電影励稳,如何確定的它是愛情片還是動作片呢?可以使用 kNN 來解決這個問題:

2017-12-23_104833.png

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

2017-12-23_105339.png

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

2017-12-23_105737.png

現(xiàn)在得到了樣本集中所有電影與未知電影的距離庞呕,按照距離遞增排序,可以找到 k 個距離最近的電影程帕。假定 k=3 住练,則三個最近的電影依次是He...、Bea...愁拭、Cal...讲逛。k-近鄰算法按照距離最近的三部電影的類型,決定未知電影的類型岭埠,而這三部電影都是愛情片盏混,因此判定未知電影是愛情片。

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

  1. 收集數(shù)據(jù):可以使用任何方法惜论;
  2. 準(zhǔn)備數(shù)據(jù):距離計算所需要的數(shù)值许赃,最好是結(jié)構(gòu)化的數(shù)據(jù)格式;
  3. 分析數(shù)據(jù):可以使用任何方法馆类;
  4. 訓(xùn)練算法:此步驟不適用于 k-近鄰算法混聊;
  5. 測試算法:計算錯誤率;
  6. 使用算法:首先需要輸入樣本數(shù)據(jù)和結(jié)構(gòu)化的輸出結(jié)果乾巧,然后運行 k-近鄰算法判定輸入數(shù)據(jù)分別屬于哪個分類句喜,最后應(yīng)用對計算出的分類執(zhí)行后續(xù)的處理。

1.1 準(zhǔn)備:使用 Python 導(dǎo)入數(shù)據(jù)

s_1_kNN.py文件(創(chuàng)建數(shù)據(jù)集和標(biāo)簽):

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 是帶有類標(biāo)簽信息的四個數(shù)據(jù)點:

2017-12-23_112043.png

現(xiàn)在我們已經(jīng)知道 Python 如何解析數(shù)據(jù)沟于,如何加載數(shù)據(jù)咳胃,以及 kNN 算法的工作原理,接下來將使用這些方法完成分類任務(wù)社裆。

1.2 從文本文件中解析數(shù)據(jù)

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

對未知類別屬性的數(shù)據(jù)集中的每個點依次執(zhí)行以下的操作:

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

classify0() 如下:

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

classify0() 護(hù)士中有 4 個輸入?yún)?shù):用于分類的輸入向量是 inX标沪,輸入訓(xùn)練樣本集是 dataSet,標(biāo)簽向量是 labels嗜傅,最后的參數(shù) k 表示用于選擇最近鄰居的數(shù)目金句,其中標(biāo)簽向量的元素數(shù)目和矩陣 dataSet 的行數(shù)相同。通過使用歐式距離公式吕嘀,計算兩個向量點 xA 和 xB 之間的距離:

image.png

計算完所有點之間的距離后违寞,可以對數(shù)據(jù)按照從小到大的次序排序贞瞒。然后確定前 k 個距離最小元素所在的主要分類,輸入 k 總是正整數(shù)趁曼;最后军浆,將 classCount 字典分解為元組列表,然后使用工程需第二行導(dǎo)入運算符模塊的 itemgetter 方法挡闰,按照第二個元素的次序?qū)υM進(jìn)行排序乒融。此處的排序為逆序,即按照從最大到最小次序排序摄悯,最后返回發(fā)生頻率最高的元素標(biāo)簽赞季。

進(jìn)行預(yù)測:

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

1.3 如何測試分類器

通過大量的測試數(shù)據(jù),我們可以得到分類器的 錯誤率 :分類器給出錯誤結(jié)果的次數(shù)除以測試執(zhí)行的總數(shù)奢驯。錯誤率是常用的評估方法申钩,主要用于評估分類器在某個數(shù)據(jù)集上的執(zhí)行效果

2. 示例:使用 k-近鄰算法改進(jìn)約會網(wǎng)站的配對效果

將約會網(wǎng)站上的人選進(jìn)行分類:不喜歡的人、魅力一般的人瘪阁、極具魅力的人

示例:在約會網(wǎng)站上使用 k-近鄰算法:

  1. 收集數(shù)據(jù):提供文本文件
  2. 準(zhǔn)備數(shù)據(jù):使用 pyhton 解析文本文件
  3. 分析數(shù)據(jù):使用 Matplotlib 畫二維擴(kuò)散圖
  4. 訓(xùn)練算法:使用部分?jǐn)?shù)據(jù)作為測試樣本撒遣。測試樣本和非測試樣本的區(qū)別在于:測試樣本是一件完成分類的數(shù)據(jù),如果預(yù)測分類與實際類別不同罗洗,則標(biāo)記為一個錯誤
  5. 使用算法:產(chǎn)生簡單的命令行程序愉舔,然后可以輸入一些特征數(shù)據(jù)以判斷對方是否為自己喜歡的類型

2.1 準(zhǔn)備數(shù)據(jù):從文本文件中解析數(shù)據(jù)

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

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

在將特征數(shù)據(jù)輸入到分類器之前伙菜,必須將待處理數(shù)據(jù)的格式改變?yōu)榉诸惼骺山邮艿母袷健?/p>

file2matrix 函數(shù)用來處理輸入數(shù)據(jù)的格式轩缤。該函數(shù)的輸入為文本文件名字符串,輸出為訓(xùn)練樣本矩陣和類標(biāo)簽向量贩绕。

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines() # 讀取文件
    numberOfLines = len(arrayOLines) # 行數(shù)
    returnMat = np.zeros((numberOfLines,3)) # 特征矩陣
    classLabelVector = [] # 標(biāo)簽值
    index = 0
    for line in arrayOLines: # 解析文件數(shù)據(jù)到列表
        line = line.strip()
        listFromLine = line.split('\t') # 分割
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1

    return returnMat,classLabelVector

2.2 分析數(shù)據(jù):使用 Matplotlib 創(chuàng)建散點圖

scatter 函數(shù)支持個性化標(biāo)記散點圖上的點

    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 準(zhǔn)備數(shù)據(jù):歸一化數(shù)值

在處理不同取值范圍的特征值時火的,通常采用的方法是將數(shù)值歸一化,如將取值范圍處理為 0 到 1 或者 -1 到 1 之間淑倾。下面的公式可以將任意取值范圍的特征值轉(zhuǎn)化為 0 到 1 區(qū)間內(nèi)的值:

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

其中 min 和 max 分別是數(shù)據(jù)集中的最小特征值和最大特征值馏鹤。

添加 autoNorm 函數(shù),來自動將數(shù)字特征值轉(zhuǎn)化為 0 到 1 的區(qū)間:

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)) # 數(shù)據(jù) - 最小值
    normDataSet = normDataSet / np.tile(ranges, (m,1)) # 上面的值 / 最小值
    # normDataSet = np.linalg.solve(normDataSet,np.tile(ranges, (m,1)))
    return  normDataSet,ranges,minVals

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

添加 ditingClassTest 函數(shù):

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)))

可以改變函數(shù) datingClassTest 內(nèi)變量 hoRatio 和變量 k 的值娇哆,檢測錯誤率是否隨著變量值得變化而增加湃累。依賴于分類算法、數(shù)據(jù)集和程序設(shè)置碍讨,分類器的輸出結(jié)果可能有很大的不同治力。

2.5 使用算法:構(gòu)建完整可用系統(tǒng)

添加 classifyPerson 函數(shù),來輸入個人信息勃黍,判斷分類:

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. 示例:手寫識別系統(tǒng)

示例說明:構(gòu)造使用 k-近鄰分類器的手寫識別系統(tǒng)宵统,來識別數(shù)字 0 到 9.需要識別的數(shù)字已經(jīng)使用圖形處理軟件,處理成具有相同的色彩和大小的黑白圖像覆获。

示例:使用 k-近鄰算法的手寫識別系統(tǒng)

  1. 收集數(shù)據(jù):提供文本文件马澈;
  2. 準(zhǔn)備數(shù)據(jù):編寫函數(shù) classify()瓢省,將圖像格式轉(zhuǎn)換為分類器使用的 list 格式;
  3. 分析數(shù)據(jù):在 python 命令提示符中檢查數(shù)據(jù)痊班,確保它符合要求勤婚;
  4. 訓(xùn)練算法:此步驟不適用 k-近鄰算法;
  5. 測試算法:編寫函數(shù)使用提供的部分?jǐn)?shù)據(jù)集作為測試樣本辩块,測試樣本與非測試樣本的區(qū)別在于測試樣本是已經(jīng)完成分類的數(shù)據(jù)蛔六,如果預(yù)測分類與實際類別不同,則標(biāo)記為一個錯誤废亭;
  6. 使用算法:本例沒有完成此步驟。

3.1 準(zhǔn)備數(shù)據(jù):將圖像轉(zhuǎn)換為測試向量

添加 img2vector 函數(shù)具钥,將圖像轉(zhuǎn)換為向量:該函數(shù)創(chuàng)建 1 X 1024 的 numpy 數(shù)組豆村,然后打開給定的文件,循環(huán)獨處文件的前 32 行骂删,并將每行的頭 32 個字符值存儲在 numpy 數(shù)組中掌动,最后返回數(shù)組:

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-近鄰算法識別手寫數(shù)字

添加 handwritingClassTest 函數(shù),來測試分類器:

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)
    trainingMat = np.zeros((m,1024))
    
    # 訓(xùn)練
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr) # 訓(xùn)練集標(biāo)簽
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) # 訓(xùn)練集
        
    # 測試
    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]) # 實際標(biāo)簽
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) # 測試集
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 預(yù)測標(biāo)簽
        print('classifierResult:',classifierResult)
        print('classNumStr:',classNumStr)
        if(classifierResult != classNumStr):
            errorCount += 1.0
    print('errorCount:',errorCount)
    print('rate:',errorCount/float(mTest))

改變變量 k 的值宁玫、修改函數(shù) handwritingClassTest 隨機(jī)選取訓(xùn)練樣本粗恢、改變訓(xùn)練樣本的數(shù)目,都會對 k-近鄰算法的錯誤率產(chǎn)生影響欧瘪。

4. 本章小結(jié)

k-近鄰算法是分類數(shù)據(jù)最簡單最有效的算法眷射。k-近鄰算法是基于實例的學(xué)習(xí),使用算法時我們必須有接近實際數(shù)據(jù)上的訓(xùn)練樣本數(shù)據(jù)佛掖。k-近鄰算法必須保存全部數(shù)據(jù)集妖碉,如果訓(xùn)練數(shù)據(jù)集很大,必須使用大量的存儲空間芥被。此外欧宜,由于必須對數(shù)據(jù)集中的每個數(shù)據(jù)計算距離值,實際使用時可能非常耗時拴魄。

另一個缺陷是它無法給出任何數(shù)據(jù)的基礎(chǔ)結(jié)構(gòu)信息冗茸,因此我們也無法知曉平均實例樣本具有什么特征。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匹中,一起剝皮案震驚了整個濱河市夏漱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌职员,老刑警劉巖麻蹋,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異焊切,居然都是意外死亡扮授,警方通過查閱死者的電腦和手機(jī)芳室,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刹勃,“玉大人堪侯,你說我怎么就攤上這事±笕剩” “怎么了伍宦?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乏梁。 經(jīng)常有香客問我次洼,道長,這世上最難降的妖魔是什么遇骑? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任卖毁,我火速辦了婚禮,結(jié)果婚禮上落萎,老公的妹妹穿的比我還像新娘亥啦。我一直安慰自己,他們只是感情好练链,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布翔脱。 她就那樣靜靜地躺著,像睡著了一般媒鼓。 火紅的嫁衣襯著肌膚如雪届吁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天隶糕,我揣著相機(jī)與錄音瓷产,去河邊找鬼。 笑死枚驻,一個胖子當(dāng)著我的面吹牛濒旦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播再登,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼尔邓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锉矢?” 一聲冷哼從身側(cè)響起梯嗽,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沽损,沒想到半個月后灯节,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年炎疆,在試婚紗的時候發(fā)現(xiàn)自己被綠了卡骂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡形入,死狀恐怖全跨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亿遂,我是刑警寧澤浓若,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蛇数,受9級特大地震影響挪钓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耳舅,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一诵原、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挽放,春花似錦、人聲如沸蔓纠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腿倚。三九已至纯出,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敷燎,已是汗流浹背暂筝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留硬贯,地道東北人焕襟。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像饭豹,于是被迫代替她去往敵國和親鸵赖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容