《機器學習實戰》kNN學習筆記

《機器學習實戰》kNN學習筆記

概述

k-近鄰算法採用測量不同特徵值之間的距離方法進行分類

優缺點

優點:精度高、對異常值不敏感、唔數據輸入假定
缺點:計算複雜度高、空間複雜度高。
適用數據範圍:數值型和標稱型
標稱型:標稱型目標變量的結果只在有限目標集中取值,如真與假(標稱型目標變量主要用於分類)標稱型:標稱型目標變量的結果只在有限目標集中取值,如真與假(標稱型目標變量主要用於分類)
數值型:數值型目標變量則可以從無限的數值集合中取值,如0.100,42.001等 (數值型目標變量主要用於迴歸分析)

k-近鄰算法的一般流程

收集數據:可以使用任何方法.

準備數據:距離計算所需要的數值,最好是結構化的數據格式.

分析數據:可以使用任何方法.

訓練算法:此步驟不適用於 k-近鄰算法.

測試算法:計算錯誤率.

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

簡單案例kNN.py

from numpy import *
import operator
from importlib import reload
# k-近鄰算法
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 classify(inX,dataSet,labels,k):
    # 計算距離
    dataSetSize = dataSet.shape[0] #讀取矩陣第一行的長度 4
    diffMat = tile(inX,(dataSetSize,1)) - dataSet #tile 重複inx的各個維度1次    相減
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort()
    classCount={}
    # 選擇距離最小的k個點
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0)+1
    # 排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

然後在python交互式開發環境下

import kNN
kNN.classify([0,0],group,labels,3)

輸出結果應該是B
可以改變輸入【0,0】的值,查看運行結果

在約會網站上使用k-近鄰算法

首先,把約會數據存放在文本文件datingTestSet中,樣本主要包括以下三個特徵

  • 每年獲得的飛行常客里程數
  • 玩視頻遊戲所耗時間百分比
  • 每週消耗的冰激凌公升數

在kNN.py中創建名爲file2matrix的函數,以此來處理輸入格式問題。該函數的輸入爲文件名字符串,輸出爲訓練樣本矩陣和類標籤向量

def file2matrix(filename):
    # 打開文件
    fr = open(filename)
    # 讀取文件所有內容
    arrayOlines = fr.readlines()
    # 得到文件行數
    numberOfLines = len(arrayOlines)
    # 返回的NumPy矩陣numberOfLines行,3列
    returnMat = np.zeros((numberOfLines, 3))
    # 創建分類標籤向量
    classLabelVector = []
    # 行的索引值
    index = 0
    # 讀取每一行
    for line in arrayOlines:
        # 去掉每一行首尾的空白符,例如'\n','\r','\t',' '
        line = line.strip()
        # 將每一行內容根據'\t'符進行切片,本例中一共有4列
        listFromLine = line.split('\t')
        # 將數據的前3列進行提取保存在returnMat矩陣中,也就是特徵矩陣
        returnMat[index,:] = listFromLine[0:3]
        # 根據文本內容進行分類1:不喜歡;2:一般;3:喜歡
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    # 返回標籤列向量以及特徵矩陣
    return returnMat, classLabelVector

在python命令提示符下輸入下內容

>>> from numpy import *
>>> import kNN
>>> from importlib import reload
>>> reload(kNN)
<module 'kNN' from 'D:\\pythonworkspace\\Machine-Learning-in-Action-Python3-master\\kNN_Project1\\kNN.py'>
>>> datingDataMat,datingLabels = kNN.file2matrix('datingTestSet.txt')
>>> import matplotlib
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>>> ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
<matplotlib.collections.PathCollection object at 0x000002B8A857A788>
>>> plt.show()

>>>

我們使用Matplotlib創建散點圖
散點圖使用大廳DataMat矩陣的第二第三列數據,分別表示特徵值玩‘視頻遊戲所耗時間百分比’和’每週消耗的冰激凌公升數‘
如圖所示
在這裏插入圖片描述
上圖很難得到一些需要的數據信息,所以用色彩來標記不同的樣本分類
Matplotlib庫提供的scatter函數支持個性化標記散點圖上的點。重新輸入上面代碼
調用scatter函數時使用下列參數

>>> ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15*array(datingLabels),15*array(datingLabels))
<matplotlib.collections.PathCollection object at 0x000002B8A857A788>
>>> plt.show()

結果如下圖所示
在這裏插入圖片描述

歸一化特徵值

在處理不同取值範圍的特徵值時,我們通常採用的方法時講數值歸一化,例如將取值範圍處理爲0-1之間。
下面的公式可以將特徵值轉化爲0-1區間內的值:
newValue = (oldValue - min)/(max - min)
其中min和max分別時數據集中最小特徵值和最大特徵值。雖然改變取值範圍增加了分類器的複雜度,但能提高準確度。
在kNN.py中添加一個函數,autoNorm(),該函數可以將數字特徵值轉化爲0-1的區間

def autoNorm(dataSet):
    # 獲取數據的最小值
    minVals = dataSet.min(0)
    # 獲取數據的最大值
    maxVals = dataSet.max(0)
    # 最大值和最小值的範圍
    ranges = maxVals - minVals
    # shape(dataSet)返回dataSet的矩陣行列數
    normDataSet = np.zeros(np.shape(dataSet))
    # numpy函數shape[0]返回dataSet的行數
    m = dataSet.shape[0]
    # 原始值減去最小值(x-xmin)
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    # 差值處以最大值和最小值的差值(x-xmin)/(xmax-xmin)
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    # 歸一化數據結果,數據範圍,最小值
    return normDataSet, ranges, minVals

然後再python命令提示符下輸入

>>> reload(kNN)
<module 'kNN' from 'D:\\pythonworkspace\\Machine-Learning-in-Action-Python3-master\\kNN_Project1\\kNN.py'>
>>> normMat,ranges,minVals = kNN.autoNorm(datingDataMat)
>>> normMat
array([[0.44832535, 0.39805139, 0.56233353],
       [0.15873259, 0.34195467, 0.98724416],
       [0.28542943, 0.06892523, 0.47449629],
       ...,
       [0.29115949, 0.50910294, 0.51079493],
       [0.52711097, 0.43665451, 0.4290048 ],
       [0.47940793, 0.3768091 , 0.78571804]])
>>> ranges
array([9.1273000e+04, 2.0919349e+01, 1.6943610e+00])
>>> minVals
array([0.      , 0.      , 0.001156])
>>>

測試代碼

def datingClassTest():
    # 打開文件名
    filename = "datingTestSet.txt"
    # 將返回的特徵矩陣和分類向量分別存儲到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix(filename)
    # 取所有數據的10% hoRatio越小,錯誤率越低
    hoRatio = 0.10
    # 數據歸一化,返回歸一化數據結果,數據範圍,最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 獲取normMat的行數
    m = normMat.shape[0]
    # 10%的測試數據的個數
    numTestVecs = int(m * hoRatio)
    # 分類錯誤計數
    errorCount = 0.0
    for i in range(numTestVecs):
        # 前numTestVecs個數據作爲測試集,後m-numTestVecs個數據作爲訓練集
        # k選擇label數+1(結果比較好)
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],\
                                     datingLabels[numTestVecs:m], 4)
        print("分類結果:%d\t真實類別:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("錯誤率:%f%%" % (errorCount/float(numTestVecs)*100))
        

它首先使用了file2matrix和autoNorm()函數從文件讀取數據並把它轉換爲歸一化特徵值。接着計算測試向量的數量,決定normMat向量中哪些爲測試集,哪些爲訓練集,然後將這兩部分數據輸入到classify()函數中,最後計算錯誤率並輸出結果

預測函數

def classifyPerson():
    # 輸出結果
    resultList = ['討厭', '有些喜歡', '非常喜歡']
    # 三維特徵用戶輸入
    percentTats = float(input("玩視頻遊戲所消耗時間百分比:"))
    ffMiles = float(input("每年獲得的飛行常客里程數:"))
    iceCream = float(input("每週消費的冰淇淋公升數:"))
    # 打開的文件名
    filename = "datingTestSet.txt"
    # 打開並處理數據
    datingDataMat, datingLabels = file2matrix(filename)
    # 訓練集歸一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 生成NumPy數組,測試集
    inArr = np.array([percentTats, ffMiles, iceCream])
    # 測試集歸一化
    norminArr = (inArr - minVals) / ranges
    # 返回分類結果
    classifierResult = classify0(norminArr, normMat, datingLabels, 4)
    # 打印結果
    print("你可能%s這個人" % (resultList[classifierResult - 1]))

新加入函數raw_input(),該函數允許用戶輸入文本行命令並返回用戶所輸入的命令

使用k-近鄰算法識別手寫數字

首先,我們必須把圖像格式化處理爲一個向量。我們把一個3232的二進制圖像矩陣轉化爲11024的向量,這樣之前的分類棋就可以處理數字圖像信息了

import numpy as np
import operator
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN

"""
函數說明:將32*32的二進制圖像轉換爲1*1024向量

Parameters:
    filename - 文件名
    
Returns:
    returnVect - 返回二進制圖像的1*1024向量

def img2vector(filename):
    # 創建1*1024零向量
    returnVect = np.zeros((1, 1024))
    # 打開文件
    fr = open(filename)
    # 按行讀取
    for i in range(32):
        # 讀取一行數據
        lineStr = fr.readline()
        # 每一行的前32個數據依次存儲到returnVect中
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])
    # 返回轉換後的1*1024向量
    return returnVect

該函數創建一個1*1024的numpy數組,然後打開指定的文件,讀出文件的前32行,並將每行的前32個字符儲存在numpy數組中,然後返回數組

"""
函數說明:手寫數字分類測試

def handwritingClassTest():
    # 測試集的Labels
    hwLabels = []
    # 返回trainingDigits目錄下的文件名
    trainingFilesList = listdir('trainingDigits')
    # 返回文件夾下文件的個數
    m = len(trainingFilesList)
    # 初始化訓練的Mat矩陣(全零陣),測試集
    trainingMat = np.zeros((m, 1024))
    # 從文件名中解析出訓練集的類別
    for i in range(m):
        # 獲得文件的名字
        fileNameStr = trainingFilesList[i]
        # 獲得分類的數字
        classNumber = int(fileNameStr.split('_')[0])
        # 將獲得的類別添加到hwLabels中
        hwLabels.append(classNumber)
        # 將每一個文件的1x1024數據存儲到trainingMat矩陣中
        trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr))
    # 構造kNN分類器
    neigh = kNN(n_neighbors=3, algorithm='auto')
    # 擬合模型,trainingMat爲測試矩陣,hwLabels爲對應標籤
    neigh.fit(trainingMat, hwLabels)
    # 返回testDigits目錄下的文件列表
    testFileList = listdir('testDigits')
    # 錯誤檢測計數
    errorCount = 0.0
    # 測試數據的數量
    mTest = len(testFileList)
    # 從文件中解析出測試集的類別並進行分類測試
    for i in range(mTest):
        # 獲得文件名字
        fileNameStr = testFileList[i]
        # 獲得分類的數字
        classNumber = int(fileNameStr.split('_')[0])
        # 獲得測試集的1*1024向量,用於訓練
        vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr))
        # 獲得預測結果
        classifierResult = neigh.predict(vectorUnderTest)
        print("分類返回結果爲%d\t真實結果爲%d" % (classifierResult, classNumber))
        if(classifierResult != classNumber):
            errorCount += 1.0
    print("總共錯了%d個數據\n錯誤率爲%f%%" % (errorCount, errorCount/mTest * 100))
        
def main():
    handwritingClassTest()
    
    
if __name__ == '__main__':
    main()
    

在python輸入kNN.handwritingClassTest()

分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
分類返回結果爲9 真實結果爲9
總共錯了12個數據
錯誤率爲1.268499%

通過改變k的值,修改函數handwritingClassTest隨機選取訓練樣本,改變訓練樣本的數目,都會影響錯誤率

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