《Machine Learning In Action》學習筆記(1)-KNN(k-近鄰演算法)
阿新 • • 發佈:2019-01-09
knn演算法我在之前的部落格從零開始-Machine Learning學習筆記(20)-kNN(k-Nearset Neignbor)學習筆記中也已經提到了,大家如果感興趣可以回過頭去看看,knn原理非常簡單。不需要訓練,當有待分類樣本時,只需要從資料集中選取k個與這個樣本距離最近的樣本,將k個樣本中最多的label作為該待分類樣本的label。
我將書中所給的程式碼使用Python3編譯,添加了註釋以便於快速理解。完整程式碼在我的github上,有興趣的朋友可以自行下載。
'''
Created on Sep 16, 2010
kNN: k Nearest Neighbors
Input: inX: vector to compare to existing dataset (1xN)
dataSet: size m data set of known vectors (NxM)
labels: data set labels (1xM vector)
k: number of neighbors to use for comparison (should be an odd number)
Output: the most popular class label
@author: pbharrin
---------------------------
@modified: Kabuto_hui
@date: 2018/12/19
---------------------------
'''
from numpy import *
import operator
from os import listdir
def classify0(inX, dataSet, labels, k):
'''
knn分類器
:param inX: 待分類向量
:param dataSet: 資料集
:param labels: 資料集對應的標籤
:param k: 鄰居個數
:return: 返回分類的類別
'''
# 獲取訓練集的樣本數量
dataSetSize = dataSet.shape[0]
# 先在列方向上重複待分類向量dataSetSize次,再減去訓練集;其實就是待分類向量與訓練集中的每個向量相減
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
# 差值的平方
sqDiffMat = diffMat ** 2
# 差值的平方和
sqDistances = sqDiffMat.sum(axis=1)
# 差值平方和再開方
distances = sqDistances ** 0.5
# 將距離從小到大排序並返回index
sortedDistIndicies = distances.argsort()
classCount = {}
# 遍歷與待測樣本距離最近的k個訓練集樣本,選擇數量最多的label作為返回值
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 統計最近的k個訓練集樣本中各個label的數量
# sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 對dict按value值由大到小排序
return sortedClassCount[0][0] # 返回value值最大key作為返回,即為label
def file2matrix(filename):
'''
讀取檔案中的資料,並返回資料集及其對應的label
:param filename: 檔名稱
:return: 返回樣本集合及其label
'''
fr = open(filename)
# 獲取行數【樣本個數】
numberOfLines = len(fr.readlines()) # get the number of lines in the file
returnMat = zeros((numberOfLines, 3)) # prepare matrix to return
classLabelVector = [] # prepare labels return
index = 0
# 按行讀取
fr = open(filename)
for line in fr.readlines():
line = line.strip() # 去掉空格
listFromLine = line.split('\t') # 以\t作為分割
returnMat[index, :] = listFromLine[0:3] # 取前三列作為特徵
classLabelVector.append(int(listFromLine[-1])) # 取最後一列作為label
index += 1
return returnMat, classLabelVector
def autoNorm(dataSet):
'''
歸一化特徵值x' = (x - x_min) / (x_max - x_min)
:param dataSet: 資料集
:return: 歸一化後的資料集, 最大減最小值, 最小值
'''
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
# 獲取樣本的個數
m = dataSet.shape[0]
# 對於資料集中的每個資料進行處理: x' = (x - x_min) / (x_max - x_min)
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet / tile(ranges, (m, 1)) # element wise divide
return normDataSet, ranges, minVals
def datingClassTest():
'''
針對於約會網站的測試程式碼
'''
# 測試集的比例
hoRatio = 0.10 # hold out 10%
# 獲取資料
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # load data setfrom file
# 歸一化
normMat, ranges, minVals = autoNorm(datingDataMat)
# 獲取資料集中的樣本數量
m = normMat.shape[0]
# 獲取測試集數量
numTestVecs = int(m * hoRatio)
# 初始化誤差
errorCount = 0.0
for i in range(numTestVecs):
# 對測試集中的每一個樣本進行分類,k=3
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]))
# 如果分類錯誤,錯誤的個數+1
if (classifierResult != datingLabels[i]): errorCount += 1.0
print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
print(errorCount)
def img2vector(filename):
'''
圖片轉向量
Note: 書中所給的圖片都轉化成只有01的32*32矩陣,這個函式將矩陣轉化為向量
:param filename: 檔名
:return: 返回向量
'''
returnVect = zeros((1, 1024))
fr = open(filename)
for i in range(32):
# 讀取一行
lineStr = fr.readline()
for j in range(32):
# 對一行中的每個資料轉化為int型再存入向量中
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
def handwritingClassTest():
'''
針對於手寫識別實驗的測試程式碼
'''
hwLabels = []
# 讀取資料夾中的檔案列表
trainingFileList = listdir('trainingDigits') # load the training set
# 獲取檔案的個數
m = len(trainingFileList)
# 初始化訓練集
trainingMat = zeros((m, 1024))
# 對每個檔案進行處理
for i in range(m):
# 獲取檔名稱
fileNameStr = trainingFileList[i]
# 將檔名切割以獲取label: 如0_0.txt
fileStr = fileNameStr.split('.')[0] # take off .txt
classNumStr = int(fileStr.split('_')[0])
# 儲存該圖片的label
hwLabels.append(classNumStr)
# 圖片矩陣轉化為向量存入訓練集中
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
# 獲取測試集的檔案列表
testFileList = listdir('testDigits') # iterate through the test set
# 初始化錯分的個數
errorCount = 0.0
# 獲取測試集的數量
mTest = len(testFileList)
for i in range(mTest):
# 獲取該測試樣本的label
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0] # take off .txt
classNumStr = int(fileStr.split('_')[0])
# 將該測試樣本轉化為向量
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
# 呼叫knn進行分類
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
# 統計錯分的個數
if (classifierResult != classNumStr): errorCount += 1.0
print("\nthe total number of errors is: %d" % errorCount)
print("\nthe total error rate is: %f" % (errorCount / float(mTest)))
if __name__ == '__main__':
print('-'*15, '約會網站實驗的測試程式碼', '-'*15)
datingClassTest()
print('-' * 15, '手寫識別實驗的測試程式碼', '-' * 15)
handwritingClassTest()
相關檔案及完整程式碼:kabutohui/Machine_Learning_In_Action
總結與思考
在這一章中,使用了大量的矩陣加減操作。尤其使用了未曾用過的tile()
函式,這個函式可以將一個向量重複n次變成一個矩陣,因此在求待分類向量的與所有訓練集樣本的距離的時候,使用此函式可以代替迴圈求解。
另外值得可取的部分是檔案讀取的時候,大量的使用了str的分割,從而獲取相關的label。