1. 程式人生 > >Python3《機器學習實戰》學習筆記(一):k-近鄰演算法

Python3《機器學習實戰》學習筆記(一):k-近鄰演算法

**轉載:**http://blog.csdn.net/c406495762
執行平臺: Windows
Python版本: Python3.x
IDE: Sublime text3
他的個人網站:http://cuijiahua.com

 

文章目錄

    本文將從k-鄰近演算法的思想開始講起,使用python3一步一步編寫程式碼進行實戰訓練。並且,我也提供了相應的資料集,對程式碼進行了詳細的註釋。除此之外,本文也對sklearn實現k-鄰近演算法的方法進行了講解。實戰例項:電影類別分類、約會網站配對效果判定、手寫數字識別。

    本文出現的所有程式碼和資料集,均可在我的github上下載,歡迎Follow、Star:https://github.com/goldxwang/Machine-Learning/tree/master/kNN


##1.1 k-近鄰法簡介

    k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。它的工作原理是:存在一個樣本資料集合,也稱作為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一個數據與所屬分類的對應關係。輸入沒有標籤的新資料後,將新的資料的每個特徵與樣本集中資料對應的特徵進行比較,然後演算法提取樣本最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料,這就是k-近鄰演算法中k的出處,通常k是不大於20的整數。最後,選擇k個最相似資料中出現次數最多的分類,作為新資料的分類。

    舉個簡單的例子,我們可以使用k-近鄰演算法分類一個電影是愛情片還是動作片。

電影名稱 打鬥鏡頭 接吻鏡頭 電影型別
電影1 1 101 愛情片
電影2 5 89 愛情片
電影3 108 5 動作片
電影4 115 8 動作片

                                                       表1.1 每部電影的打鬥鏡頭數、接吻鏡頭數以及電影型別


    表1.1就是我們已有的資料集合,也就是訓練樣本集。這個資料集有兩個特徵,即打鬥鏡頭數和接吻鏡頭數。除此之外,我們也知道每個電影的所屬型別,即分類標籤。用肉眼粗略地觀察,接吻鏡頭多的,是愛情片。打鬥鏡頭多的,是動作片。以我們多年的看片經驗,這個分類還算合理。如果現在給我一部電影,你告訴我這個電影打鬥鏡頭數和接吻鏡頭數。不告訴我這個電影型別,我可以根據你給我的資訊進行判斷,這個電影是屬於愛情片還是動作片。而k-近鄰演算法也可以像我們人一樣做到這一點,不同的地方在於,我們的經驗更"牛逼",而k-鄰近演算法是靠已有的資料。比如,你告訴我這個電影打鬥鏡頭數為2,接吻鏡頭數為102,我的經驗會告訴你這個是愛情片,k-近鄰演算法也會告訴你這個是愛情片。你又告訴我另一個電影打鬥鏡頭數為49,接吻鏡頭數為51,我"邪惡"的經驗可能會告訴你,這有可能是個"愛情動作片",畫面太美,我不敢想象。 (如果說,你不知道"愛情動作片"是什麼?請評論留言與我聯絡,我需要你這樣像我一樣純潔的朋友。) 但是k-近鄰演算法不會告訴你這些,因為在它的眼裡,電影型別只有愛情片和動作片,它會提取樣本集中特徵最相似資料(最鄰近)的分類標籤,得到的結果可能是愛情片,也可能是動作片,但絕不會是"愛情動作片"。當然,這些取決於資料集的大小以及最近鄰的判斷標準等因素。

##1.2 距離度量

    我們已經知道k-近鄰演算法根據特徵比較,然後提取樣本集中特徵最相似資料(最鄰近)的分類標籤。那麼,如何進行比較呢?比如,我們還是以表1.1為例,怎麼判斷紅色圓點標記的電影所屬的類別呢?如圖1.1所示。

 

圖1.1 電影分類
    我們可以從散點圖大致推斷,這個紅色圓點標記的電影可能屬於動作片,因為距離已知的那兩個動作片的圓點更近。k-近鄰演算法用什麼方法進行判斷呢?沒錯,就是距離度量。這個電影分類的例子有2個特徵,也就是在2維實數向量空間,可以使用我們高中學過的兩點距離公式計算距離,如圖1.2所示。


圖1.2 兩點距離公式
    通過計算,我們可以得到如下結果:

(101,20)->動作片(108,5)的距離約為16.55
(101,20)->動作片(115,8)的距離約為18.44
(101,20)->愛情片(5,89)的距離約為118.22
(101,20)->愛情片(1,101)的距離約為128.69
    通過計算可知,紅色圓點標記的電影到動作片 (108,5)的距離最近,為16.55。如果演算法直接根據這個結果,判斷該紅色圓點標記的電影為動作片,這個演算法就是最近鄰演算法,而非k-近鄰演算法。那麼k-鄰近演算法是什麼呢?k-近鄰演算法步驟如下:

計算已知類別資料集中的點與當前點之間的距離;
按照距離遞增次序排序;
選取與當前點距離最小的k個點;
確定前k個點所在類別的出現頻率;
返回前k個點所出現頻率最高的類別作為當前點的預測分類。
    比如,現在我這個k值取3,那麼在電影例子中,按距離依次排序的三個點分別是動作片(108,5)、動作片(115,8)、愛情片(5,89)。在這三個點中,動作片出現的頻率為三分之二,愛情片出現的頻率為三分之一,所以該紅色圓點標記的電影為動作片。這個判別過程就是k-近鄰演算法。

##1.3 Python3程式碼實現

    我們已經知道了k-近鄰演算法的原理,那麼接下來就是使用Python3實現該演算法,依然以電影分類為例。

1.3.1 準備資料集
    對於表1.1中的資料,我們可以使用numpy直接建立,程式碼如下:
 

# -*- coding: UTF-8 -*-
import numpy as np

"""
函式說明:建立資料集

Parameters:
    無
Returns:
    group - 資料集
    labels - 分類標籤
Modify:
    2017-07-13
"""
def createDataSet():
    #四組二維特徵
    group = np.array([[1,101],[5,89],[108,5],[115,8]])
    #四組特徵的標籤
    labels = ['愛情片','愛情片','動作片','動作片']
    return group, labels
if __name__ == '__main__':
    #建立資料集
    group, labels = createDataSet()
    #列印資料集
    print(group)
    print(labels)

執行結果,如圖1.3所示:

 

 

                                                            圖1.3 執行結果

###1.3.2 k-近鄰演算法

    根據兩點距離公式,計算距離,選擇距離最小的前k個點,並返回分類結果。

# -*- coding: UTF-8 -*-
import numpy as np
import operator

"""
函式說明:kNN演算法,分類器

Parameters:
    inX - 用於分類的資料(測試集)
    dataSet - 用於訓練的資料(訓練集)
    labes - 分類標籤
    k - kNN演算法引數,選擇距離最小的k個點
Returns:
    sortedClassCount[0][0] - 分類結果

Modify:
    2017-07-13
"""
def classify0(inX, dataSet, labels, k):
    #numpy函式shape[0]返回dataSet的行數
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二維特徵相減後平方
    sqDiffMat = diffMat**2
    #sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #開方,計算出距離
    distances = sqDistances**0.5
    #返回distances中元素從小到大排序後的索引值
    sortedDistIndices = distances.argsort()
    #定一個記錄類別次數的字典
    classCount = {}
    for i in range(k):
        #取出前k個元素的類別
        voteIlabel = labels[sortedDistIndices[i]]
        #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回預設值。
        #計算類別次數
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #python3中用items()替換python2中的iteritems()
    #key=operator.itemgetter(1)根據字典的值進行排序
    #key=operator.itemgetter(0)根據字典的鍵進行排序
    #reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #返回次數最多的類別,即所要分類的類別
    return sortedClassCount[0][0]

###1.3.3 整體程式碼

    這裡預測紅色圓點標記的電影(101,20)的類別,K-NN的k值為3。建立kNN_test01.py檔案,編寫程式碼如下:

# -*- coding: UTF-8 -*-
import numpy as np
import operator

"""
函式說明:建立資料集

Parameters:
    無
Returns:
    group - 資料集
    labels - 分類標籤
Modify:
    2017-07-13
"""
def createDataSet():
    #四組二維特徵
    group = np.array([[1,101],[5,89],[108,5],[115,8]])
    #四組特徵的標籤
    labels = ['愛情片','愛情片','動作片','動作片']
    return group, labels

"""
函式說明:kNN演算法,分類器

Parameters:
    inX - 用於分類的資料(測試集)
    dataSet - 用於訓練的資料(訓練集)
    labes - 分類標籤
    k - kNN演算法引數,選擇距離最小的k個點
Returns:
    sortedClassCount[0][0] - 分類結果

Modify:
    2017-07-13
"""
def classify0(inX, dataSet, labels, k):
    #numpy函式shape[0]返回dataSet的行數
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二維特徵相減後平方
    sqDiffMat = diffMat**2
    #sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #開方,計算出距離
    distances = sqDistances**0.5
    #返回distances中元素從小到大排序後的索引值
    sortedDistIndices = distances.argsort()
    #定一個記錄類別次數的字典
    classCount = {}
    for i in range(k):
        #取出前k個元素的類別
        voteIlabel = labels[sortedDistIndices[i]]
        #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回預設值。
        #計算類別次數
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #python3中用items()替換python2中的iteritems()
    #key=operator.itemgetter(1)根據字典的值進行排序
    #key=operator.itemgetter(0)根據字典的鍵進行排序
    #reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #返回次數最多的類別,即所要分類的類別
    return sortedClassCount[0][0]

if __name__ == '__main__':
    #建立資料集
    group, labels = createDataSet()
    #測試集
    test = [101,20]
    #kNN分類
    test_class = classify0(test, group, labels, 3)
    #列印分類結果
    print(test_class)
--------------------- 

執行結果,如圖1.4所示:


圖1.4 執行結果
    可以看到,分類結果根據我們的"經驗",是正確的,儘管這種分類比較耗時,用時1.4s。

    到這裡,也許有人早已經發現,電影例子中的特徵是2維的,這樣的距離度量可以用兩 點距離公式計算,但是如果是更高維的呢?對,沒錯。我們可以用歐氏距離(也稱歐幾里德度量),如圖1.5所示。我們高中所學的兩點距離公式就是歐氏距離在二維空間上的公式,也就是歐氏距離的n的值為2的情況。


圖1.5 歐氏距離公式
    看到這裡,有人可能會問:“分類器何種情況下會出錯?”或者“答案是否總是正確的?”答案是否定的,分類器並不會得到百分百正確的結果,我們可以使用多種方法檢測分類器的正確率。此外分類器的效能也會受到多種因素的影響,如分類器設定和資料集等。不同的演算法在不同資料集上的表現可能完全不同。為了測試分類器的效果,我們可以使用已知答案的資料,當然答案不能告訴分類器,檢驗分類器給出的結果是否符合預期結果。通過大量的測試資料,我們可以得到分類器的錯誤率-分類器給出錯誤結果的次數除以測試執行的總數。錯誤率是常用的評估方法,主要用於評估分類器在某個資料集上的執行效果。完美分類器的錯誤率為0,最差分類器的錯誤率是1.0。同時,我們也不難發現,k-近鄰演算法沒有進行資料的訓練,直接使用未知的資料與已知的資料進行比較,得到結果。因此,可以說k-鄰近演算法不具有顯式的學習過程。
---------------------