第二章 K-近鄰算法 及 約會網站配對

2.1 K-近鄰算法概述

K-近鄰算法應該就是一個分類算法。採用測量不同特徵值之間的距離方法進行分類。


優點:精度高、對異常值不敏感、無數據輸入假定。

缺點:計算複雜度高、空間複雜度高。

適用範圍:數值型和標稱型。

書中舉了一個電影分類的例子,通過一些鏡頭來判斷這是愛情片還是動作片愛情動作片


K-近鄰算法的一般流程

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

================================================================================


接下來是準備工作,使用 Python 導入數據。推薦手打,不要複製粘貼。

kNN.py

注意我用的文件可以從 http://www.ituring.com.cn/book/1021 下載,而且根據勘誤應該用 datingTestSet2.txt

#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊

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


在 PowerShell 裏面先用 cd 命令定位到 kNN.py 的位置,然後 輸入 python 進入開發環境。

>>> import kNN
>>> group,labels = kNN.createDataSet()
>>> group
>>> labels

驗證下與書一樣就可以了。結果如下圖:


==========================================================================


接下來正式的 kNN 算法,需要說明的是,幾個輸入參數是什麼。

inX 是用於分類的輸入向量,dataSet 是輸入的訓練樣本,標籤向量爲 labels , 參數 k 表示的是用於選擇的最近鄰居的數目,其中標籤向量的元素數目和矩陣 dataSet 的行數相同 。

代碼如下:

#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊

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

def classify0(inX, dataSet, labels, k):
    
    # 距離計算
    dataSetSize = dataSet.shape[0] # .shape 讀取矩陣的長度
    diffMat = tile(inX,(dataSetSize,1)) - dataSet 
    # tile(A,n),功能是將數組A重複n次,構成一個新的數組
    sqDiffMat = diffMat ** 2 # **就是乘方
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort() # argsort函數返回的是數組值從小到大的索引值
    classCount = {}
    
    # 選擇距離最小的 K 個點
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
        #dict.get(key, default=None) key在字典中查找,在key不存在的情況下返回值None。
    
    # 排序 sorted 函數詳見 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
    sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器對象,返回鍵值對
    key = operator.itemgetter(1), reverse = True) 
    # itemgetter(1) 使用元組的第二個元素對列表排序
    # itemgetter(0) 使用元組的第一個元素對列表排序
    return sortedClassCount[0][0]


  第一部分的距離計算就是歐氏距離,沒什麼好說的。

然後,確定前 k 個距離最小元素所在的主要分類,輸入 k 總是正整數。

最後,把 classCount 字典分解爲元組列表,然後用 itemgetter 方法,按照第二個元素的次序對元組排序。


==========================================================================================

跑一遍試試:



可以看出 [0,0] 分類結果是 B

==========================================================================================

2.2 示例:使用K-近鄰算法改進約會網站配對效果

2.2.1準備數據

樣本主要包含以下三種特徵:

1.每年獲得的飛行常客里程數

2.玩視頻遊戲所花費時間百分比

3.每週消費冰激凌公升數


在 kNN.py 中創建一個 file2matrix 函數處理輸入格式問題。

增加一部分代碼,並且在本文一開始的地方下載所需要的數據文件。

#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊

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

def classify0(inX, dataSet, labels, k):
    
    # 距離計算
    dataSetSize = dataSet.shape[0] # .shape 讀取矩陣的長度
    diffMat = tile(inX,(dataSetSize,1)) - dataSet 
    # tile(A,n),功能是將數組A重複n次,構成一個新的數組
    sqDiffMat = diffMat ** 2 # **就是乘方
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort() # argsort函數返回的是數組值從小到大的索引值
    classCount = {}
    
    # 選擇距離最小的 K 個點
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
        #dict.get(key, default=None) key在字典中查找,在key不存在的情況下返回值None。
    
    # 排序 sorted 函數詳見 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
    sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器對象,返回鍵值對
    key = operator.itemgetter(1), reverse = True) 
    # itemgetter(1) 使用元組的第二個元素對列表排序
    # itemgetter(0) 使用元組的第一個元素對列表排序
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines) # 文件的行數
    returnMat = zeros((numberOfLines, 3)) # 創建返回的 NumPy 矩陣
    # 創建給定類型的矩陣,並初始化爲0。zeros((A,B)),創建一個A行,B列的0矩陣
    classLabelVector = []
    index = 0
    for line in arrayOLines: # 解析文件數據列表
        line = line.strip() # strip() 方法用於移除字符串頭尾指定的字符(這裏是截掉回車字符)
        listFromLine = line.split('\t') # tab 字符
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector


爲了方便起見,我再寫了一個 use-kNN.py 文件,不用在命令行裏面一個一個輸入了,文件如下:

import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]

結果如下:



以上就是從文本文件導入數據並且轉化爲想要的格式。不過結果和書上例子不太一樣。

========================================================================================================


2.2.2 分析數據 

我先建立一個 plot.py 用於畫圖,代碼如下:

import matplotlib
import matplotlib.pyplot as plt
import kNN

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

那麼就可以畫出圖了:



上圖存在的問題是,沒有用顏色來標記不同樣本分類。於是在執行的時候可以加點代碼。


# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 沒導入會報錯

fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 應該指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.show()


結果如下:



稍微改動一下,完善一下,加個xy標籤,改動一下讀取的數據列:

# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 沒導入會報錯

fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 應該指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))

plt.xlabel('Air Miles')
plt.ylabel('Video Games')

plt.show()

得到如下圖:


============================================================================

2.2.3 歸一化數值

原始數據文件裏面,飛行常客里程數遠大於其他兩者,這導致其影響程度也遠大於其他兩者,而我們希望三者是具有同樣權重的,因此需要歸一化處理。

通常我們將其取值範圍處理爲0到1或者-1到1之間,使用的公式如下:


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

我們要在 kNN.py  程序裏面加上一個歸一化的函數,爲了簡短起見,就只寫這一段。

def autoNorm(dataSet):
    minVals = dataSet.min(0) # 每列最小變量,參數 0 使得函數可以從列中選取最小值
    maxVals = dataSet.max(0) # 每列最大變量
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet)) # 照着 dataSet 的樣子做一個 0 矩陣
    m= dataSet.shape[0] # shape[0] 指的是矩陣第一維長度
    normDataSet = dataSet - tile(minVals, (m,1)) # tile(A,n),功能是將數組A重複n次,構成一個新的數組
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals

特徵值矩陣有 1000 *3 個值,而 minVals 和 range 的值都爲 1*3 ,爲此我們使用 tile() 函數將變量內容複製成輸入舉證同樣大小的矩陣。另外這裏的 / 是具體特徵值相除,而 NumPy 的矩陣除法是 linalg.solve(matA,matB) 。

現在執行 autoNorm 試一試,我們在 usekNN.py 裏面加一點

 

# usekNN.py
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
print '**********'
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print normMat
print '**********'
print ranges
print '**********'
print minVals

運行結果就不發了,反正正常...

=====================================================================================

2.2.4 測試算法

這裏主要是測試分類器效果,因此要寫測試代碼:

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], 3)
        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)) # 寫出正確率

接下來在 usekNN.py 裏面加上

reload(kNN)
kNN.datingClassTest()

這樣運行就知道錯誤率了,我運行出來的錯誤率是 5% ,並不算高。


=====================================================================================

2.2.5 使用算法

通過該程序可以在約會網站上面找到某個人輸入他的信息,程序會給出預測。

kNN.py 添加代碼如下:

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

然後 usekNN.py 添加執行:

print '**********'
reload(kNN)
kNN.classifyPerson()

結果:



===============================================================================================================

最後給一遍完整代碼,總共三個文件。

kNN.py:

#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊

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

def classify0(inX, dataSet, labels, k):
    
    # 距離計算
    dataSetSize = dataSet.shape[0] # .shape 讀取矩陣的長度
    diffMat = tile(inX,(dataSetSize,1)) - dataSet 
    # tile(A,n),功能是將數組A重複n次,構成一個新的數組
    sqDiffMat = diffMat ** 2 # **就是乘方
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort() # argsort函數返回的是數組值從小到大的索引值
    classCount = {}
    
    # 選擇距離最小的 K 個點
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
        #dict.get(key, default=None) key在字典中查找,在key不存在的情況下返回值None。
    
    # 排序 sorted 函數詳見 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
    sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器對象,返回鍵值對
    key = operator.itemgetter(1), reverse = True) 
    # itemgetter(1) 使用元組的第二個元素對列表排序
    # itemgetter(0) 使用元組的第一個元素對列表排序
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines) # 文件的行數
    returnMat = zeros((numberOfLines, 3)) # 創建返回的 NumPy 矩陣
    # 創建給定類型的矩陣,並初始化爲0。zeros((A,B)),創建一個A行,B列的0矩陣
    classLabelVector = []
    index = 0
    for line in arrayOLines: # 解析文件數據列表
        line = line.strip() # strip() 方法用於移除字符串頭尾指定的字符(這裏是截掉回車字符)
        listFromLine = line.split('\t') # tab 字符
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

def autoNorm(dataSet):
    minVals = dataSet.min(0) # 每列最小變量,參數 0 使得函數可以從列中選取最小值
    maxVals = dataSet.max(0) # 每列最大變量
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet)) # 照着 dataSet 的樣子做一個 0 矩陣
    m= dataSet.shape[0] # shape[0] 指的是矩陣第一維長度
    normDataSet = dataSet - tile(minVals, (m,1)) # tile(A,n),功能是將數組A重複n次,構成一個新的數組
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals

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], 3)
        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(raw_input(\
                    "percentage of time spent playing video games?"))
    ffMiles = float(raw_input("frequent flier miles earned per year?"))
    iceCream = float(raw_input("liters of ice cream consumed per year?"))
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr-\
                    minVals)/ranges, normMat, datingLabels, 3)
    print "You will probably like this person: ",\
                    resultList[classifierResult - 1]


usekNN.py:

# usekNN.py
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
print '**********'
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print normMat
print '**********'
print ranges
print '**********'
print minVals
print '**********'
reload(kNN)
kNN.datingClassTest()
print '**********'
reload(kNN)
kNN.classifyPerson()

plot.py:

# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 沒導入會報錯

fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 應該指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))

plt.xlabel('Air Miles')
plt.ylabel('Video Games')

plt.show()


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章