1. 程式人生 > >機器學習實戰k-鄰近演算法(kNN)簡單實施程式碼解讀

機器學習實戰k-鄰近演算法(kNN)簡單實施程式碼解讀

一.概念

k-鄰近演算法是最簡單的機器學習演算法之一。

k-鄰近演算法採用測量不同特徵值之間的距離(具體說是歐氏距離)的方法進行分類。

輸入待分類的資料後,計算輸入特徵與樣本集資料對應特徵的距離,選擇樣本集中與輸入特徵距離最小的前k個樣本,統計這k個樣本資料中出現次數最多的類別作為新資料的分類。

二.kNN的簡單實施程式碼及註釋

from numpy import *
import operator

def creatDataSet():
    dataSet = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A'
,'B','B'] return dataSet,labels def classify0(inX,dataSet,labels,k): #求出樣本集的行數,也就是labels標籤的數目 dataSetSize = dataSet.shape[0] #構造輸入值和樣本集的差值矩陣 diffMat = tile(inX,(dataSetSize,1)) - dataSet #計算歐式距離 sqDiffMat = diffMat**2 sqDistances = sqDiffMat.sum(axis=1) distances = sqDistances**0.5
#求距離從小到大排序的序號 sortedDistIndicies = distances.argsort() #對距離最小的k個點統計對應的樣本標籤 classCount = {} for i in range(k): #取第i+1鄰近的樣本對應的類別標籤 voteIlabel = labels[sortedDistIndicies[i]] #以標籤為key,標籤出現的次數為value將統計到的標籤及出現次數寫進字典 classCount[voteIlabel] = classCount.get(voteIlabel,0
) + 1 #對字典按value從大到小排序 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #返回排序後字典中最大value對應的key return sortedClassCount[0][0]

三.詳細解讀

這裡建立的是一個名為kNN.py的模組。

首先匯入了兩個模組,一個是科學計算包numpy,另一個是運算子模組,書中有提到。

接著是一個建立資料集的無參函式creatDataSet(),一共4個樣本,每個樣本有2個特徵和1個分類標籤。特徵集以4*2的陣列形式表示,類別標籤集以列表的形式表示。

接下來是一個有4個引數的分類函式classify0(inX,dataSet,labels,k):
inX表示待分類的輸入特徵向量,
dataSet為樣本集的特徵,
labels為樣本集對應每一個樣本的分類標籤,
k為選擇最近距離的樣本的數目。
其中dataSet和labels由creatDataSet()函式返回。

dataSetSize = dataSet.shape[0]
求出樣本集的行數,即樣本個數,也是分類標籤labels列表裡元素的個數。

shape用於返回一個矩陣或陣列的大小,返回的是一個元組,即(行數,列數)。如下:

>>> import kNN
>>> dataSet,labels=kNN.creatDataSet()
>>> dataSet.shape
(4, 2)
>>> dataSet
array([[ 1. ,  1.1],
       [ 1. ,  1. ],
       [ 0. ,  0. ],
       [ 0. ,  0.1]])
>>> dataSet.shape
(4, 2)
>>> dataSet.shape[0]
4
>>> dataSet.shape[1]
2
>>> type(dataSet.shape)
<class 'tuple'>

故這裡,
shape[0]即得到shape元組的第一個元素,dataSet的行數;
shape[1]即得到shape元組的第二個元素,dataSet的列數;

當有n個特徵時,歐式距離

d=(A0B0)2+(A1B1)2+(A2B2)2++(AnBn)2由於這裡只有兩個特徵,故簡化為d=(A0B0)2+(A1B1)2

diffMat = tile(inX,(dataSetSize,1)) - dataSet
用於構造輸入特徵值和樣本集的差值矩陣,即每一行有兩個元素,[(A0Bi0),(A1Bi1)],即輸入樣本的特徵和第i個樣本對應特徵的差。

因為dataSet有多個樣本,但inX只有一個,矩陣相減要求維數相同,故使用tile()函式,這裡是將inX變為dataSetSize*1維的矩陣,每一行都是inX。

關於numpy庫中tile()函式的用法,可參考


sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
用於計算歐氏距離,先將差值矩陣的每一個元素平方,再按行求和,最後開方。

關於sum()函式,表示普通求和,sum(axis=1)表示每一行向量相加,sum(axis=0)表示每一列向量相加,如下:

>>> dataSet
array([[ 1. ,  1.1],
       [ 1. ,  1. ],
       [ 0. ,  0. ],
       [ 0. ,  0.1]])
>>> dataSet.sum()
4.1999999999999993
>>> dataSet.sum(axis=1)
array([ 2.1,  2. ,  0. ,  0.1])
>>> dataSet.sum(axis=0)
array([ 2. ,  2.2])

sortedDistIndicies = distances.argsort()
將輸入特徵與每個樣本的歐式距離從小到大排序,返回的是樣本在原歐式距離集中的序號。

接著初始化字典,用for迴圈處理最鄰近的前k個樣本,統計各類別出現的次數。

classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
以第i+1鄰近的樣本的類別標籤為key,該類別標籤出現的次數為value將統計到的類別標籤及出現次數寫進字典,將該類別出現的次數加1。

關於字典的get()方法,因為voteIlabel是key,get(voteIlabel,0)表示字典按key查詢,如果存在這個key,則返回這個key的value;如果當前沒有這個key,則返回0。如下:

>>> dic1 = {'color':'red','size':18,3:'good'}
>>> dic1
{'color': 'red', 3: 'good', 'size': 18}
>>> dic1.get('color')
'red'
>>> dic1.get(3)
'good'
>>> dic1.get('size',0)
18
>>> dic1.get(4,0)
0

第一次統計到一個類別標籤時,由於字典中無對應的key,就返回0,表示當前沒有這個類別,之後加1;
不是第一次統計到這個類別標籤時,則返回這個標籤之前出現的次數,並在此基礎上加1。

sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
表示按字典的value進行從大到小排序。
第一個引數指定要排序的列表或者iterable,如果一個物件是iterable的,表示它可以被遍歷;

《機器學習實戰》中這一塊的第一個引數使用的是classCount.iteritems(),用python3.0以後的版本會出現
錯誤:AttributeError: ‘dict’ object has no attribute ‘iteritems’

python3.0不再支援dict.iteritems(),具體可參考官方更新文件

這裡寫圖片描述

第二個引數是一個函式,operator.itemgetter(1)表示按字典的第二項即value排序,而不是按key排序;
第三個引數為True表示從大到小排序。

關於Python中的sorted()函式以及operator.itemgetter()函式,可參考

return sortedClassCount[0][0]
最後返回排序後字典中最大的value對應的key,即對新資料分類的類別。

四.執行結果

因為上述程式碼為一個模組,故首先要F5 run module,然後在command window中匯入該模組,再呼叫creatDataSet()建立樣本集,此時可以檢視一下樣本是否建立成功,之後就可以呼叫classify0對新的輸入進行分類了。

>>> import kNN
>>> dataSet,labels=kNN.creatDataSet()
>>> dataSet
array([[ 1. ,  1.1],
       [ 1. ,  1. ],
       [ 0. ,  0. ],
       [ 0. ,  0.1]])
>>> labels
['A', 'A', 'B', 'B']
>>> kNN.classify0([0,0],dataSet,labels,3)
'B'