《機器學習實戰》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隨機選取訓練樣本,改變訓練樣本的數目,都會影響錯誤率