《Machine Learning in Action》| 第1章 k-近鄰演算法
阿新 • • 發佈:2018-12-15
準備:使用 Python 匯入資料
"""
@函式說明: 建立資料集
"""
def createDataSet():
# 四組二維特徵
group = np.array([[3,104],[2,100],[101,10],[99,5]])
# 四組特徵的標籤
labels = ['愛情片','愛情片','動作片','動作片']
return group, labels
"""
實施kNN演算法
- 函式的虛擬碼如下: 對未知類別屬性的資料集中的每個點依次執行以下操作: (1) 計算已知類別資料集中的點與當前點之間的距離; (2) 按照距離遞增次序排序; (3) 選取與當前點距離最小的k個點; (4) 確定前k個點所在類別的出現頻率; (5) 返回前k個點出現頻率最高的類別作為當前點的預測分類。
程式清單 2-1 k-近鄰演算法
"""
@函式說明:kNN分類(k近鄰演算法)
Parameters:
inX - 用於分類的資料(測試集)
dataSet - 用於訓練的資料(訓練集)
labels - 類別標籤
k - 選擇最近鄰居的數目
Returns:
sortedClassCount[0][0] - 分類結果
"""
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # numpy函式shape[0]返回dataSet的行數,4行
# 歐式距離計算
# 橫向複製inX共1次,縱向複製inX共dataSize次
# print(np.tile(inX, (dataSetSize,1)))
"""
構造相同行數的測試集test
[[ 10 120]
[ 10 120]
[ 10 120]
[ 10 120]]
"""
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # 沿橫向、縱向複製多維陣列
"""
test: group: diffMat:
[[ 10 120] [[ 3 104] [[ 7 16]
[ 10 120] —— [ 2 100] = [ 8 20]
[ 10 120] [101 10] [-91 110]
[ 10 120]] [ 99 5]] [-89 115]]
"""
# print(diffMat)
# 二維特徵相減後平方
sqDiffMat = diffMat ** 2
# print(sqDiffMat)
"""
sqDiffMat(每個元素平方):
[[ 49 256]
[ 64 400]
[ 8281 12100]
[ 7921 13225]]
"""
# sum()將所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
# print("sqDistances:\n",sqDistances)
"""
sqDistances:
[ 305 464 20381 21146]
"""
# 開方,計算出距離
distances = sqDistances ** 0.5
# print(" distances:\n", distances)
"""
distances(算出來的最終的歐式距離):
[ 17.4642492 21.54065923 142.76203977 145.41664279]
"""
sortedDistIndices = distances.argsort() # 返回distances中元素從小到大排序後的索引值
# print(sortedDistIndices) # [0 1 2 3]
classCount = {} # 定義記錄類別次數的字典
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]] # 取出前K個元素的類別,前3個類別為:愛情片 愛情片 動作片
# print(voteIlabel)
#dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回預設值。
#計算類別次數
# print(classCount.get(voteIlabel,0))
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # 沒出現過的返回0再+1就是1次,愛情片(在空字典中沒出現過,+1作為1次)、愛情片(出現過1次,+1是兩次)、動作片(沒出現過,+1作為1次)
# print(classCount[voteIlabel]) # 愛情片 愛情片 動作片, 1 2 1
# classCount: {'愛情片': 2, '動作片': 1}
"""
python內建函式:sorted函式
sorted(iterable<可迭代物件>, cmp<對函式排序>, key<對元素排序>, reverse<True降序,False升序(預設)>)
sort 是應用在 list 上的方法,sorted 可以對所有可迭代的物件進行排序操作。
內建函式 sorted 方法返回的是一個新的 list,而不是在原來的基礎上進行的操作。
詳解:http://www.runoob.com/python/python-func-sorted.html
"""
#python3中用items()替換python2中的iteritems()
#key=operator.itemgetter(1)根據字典的值value進行排序
#key=operator.itemgetter(0)根據字典的鍵key進行排序
#reverse降序排序字典,reverse -- 排序規則,reverse = True 降序 , reverse = False 升序(預設)
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
# print('sortedClassCount:\n',sortedClassCount)
"""
sortedClassCount:
[('愛情片', 2), ('動作片', 1)]
"""
return sortedClassCount[0][0]
示例:使用 k-近鄰演算法改進約會網站的配對效果
準備資料:從文字檔案中解析資料
程式清單 2-2 將文字記錄轉換為NumPy的解析程式
"""
函式說明:開啟並解析檔案,對資料進行分類:1代表不喜歡、2代表魅力一般,3代表極具魅力
Parameters:
filename - 檔名
Return:
returnMat - 特徵矩陣
classLabelVector - 分類Label向量
"""
def file2matrix(filename):
loveDict = {'didntLike':1,'smallDoses':2,'largeDoses':3}
fr = open(filename) # 開啟檔案
arrayOlines = fr.readlines() # 逐行讀取
# print(arrayOlines)
'''
arrayOlines:
['40920\t8.326976\t0.953952\tlargeDoses\n', '14488\t7.153469\t1.673904\tsmallDoses\n',...,'43757\t7.882601\t1.332446\tlargeDoses\n']
'''
numberOfLines = len(arrayOlines) # 得到檔案行數,共1000行資料
# print(numberOfLines)
returnMat = np.zeros( (numberOfLines,3) ) # 初始化特徵矩陣,解析完成的資料:numberOfLines行3列
classLabelVector = [] # 初始化分類標籤向量
index = 0 # 行的索引值
for line in arrayOlines:
# s.strip(rm),當rm空時,預設刪除空白符(包括'\n','\r','\t',' ')
line = line.strip() # 刪去字串首尾部空字元
# print(line)
'''
如最後兩行line:
48111 9.134528 0.728045 largeDoses
43757 7.882601 1.332446 largeDoses
'''
# 使用s.split(str="",num=string,cout(str))將字串根據'\t'分隔符進行切片
listFromLine = line.split('\t') # 按'\t'對字串進行分割,listFromLine是列表
# print(listFromLine) # 分割成列表
'''
如最後兩行listFromLine(列表):
['48111', '9.134528', '0.728045', 'largeDoses']
['43757', '7.882601', '1.332446', 'largeDoses']
'''
returnMat[index,:] = listFromLine[0:3] # 將資料前三列(特徵)一行一行賦值給returnMat
index += 1
# 根據文字中標記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
# 讀取的listFromLine的最後一列為類別標籤
if listFromLine[-1].isdigit(): # 如果listFromLine最後一列是數字,資料集datingTestSet.txt
classLabelVector.append(int(listFromLine[-1])) # 直接賦值給classLabelVector
else: # 如果listFromLine最後一列不是數字,而是字串,資料集datingTestSet2.txt
classLabelVector.append(loveDict.get(listFromLine[-1]))
return returnMat, classLabelVector # 返回的類別標籤向量是1,2,3
'''
returnMat: classLabelVector:
[[4.0920000e+04 8.3269760e+00 9.5395200e-01] [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2,
[1.4488000e+04 7.1534690e+00 1.6739040e+00] 1, 1, 1, 1, 1, 2, 3, 2, 1, 2, 3, 2, 3,
[2.6052000e+04 1.4418710e+00 8.0512400e-01] ...
... 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2,
[2.6575000e+04 1.0650102e+01 8.6662700e-01] 2, 2, 2, 1, 3, 3, 3]
[4.8111000e+04 9.1345280e+00 7.2804500e-01]
[4.3757000e+04 7.8826010e+00 1.3324460e+00]]
'''
準備資料:歸一化數值
程式清單 2-3 歸一化特徵值
"""
函式說明:特徵歸一化函式,對資料進行歸一化
Parameters:
dataSet - 特徵矩陣
Returns:
normDataSet - 歸一化後的特徵矩陣
ranges - 資料範圍
minVals - 資料最小值
"""
def autoNorm(dataSet):
minVals = dataSet.min(0) # 獲取資料每一列的最小值和最大值,返回一維列表,min(0) axis=0每一列,axis=1每一行
maxVals = dataSet.max(0)
ranges = maxVals - minVals # 最大值和最小值的範圍
# print('minVals:\n',minVals) # [0. 0. 0.001156]
# print('maxVals:\n',maxVals) # [9.1273000e+04 2.0919349e+01 1.6955170e+00]
# print('ranges:\n',ranges) # [9.1273000e+04 2.0919349e+01 1.6943610e+00]
normDataSet = np.zeros(np.shape(dataSet)) # 初始化歸一化特徵矩陣,np.shape(dataSet)返回dataSet的矩陣行列數
# print(dataSet.shape) # (1000, 3)
m = dataSet.shape[0] # dataSet的行數,共1000行
'''
下面的公式可以將任意取值範圍的特徵值轉化為0到1區間內的值:
newValue = (oldValue - min) / (max - min)
其中min和max分別是資料集中的最小特徵值和最大特徵值。
'''
normDataSet = dataSet - np.tile(minVals, (m, 1)) # 原始值減去最小值
normDataSet = normDataSet / np.tile(ranges, (m, 1)) # 再除以最大值和最小值的差,得到歸一化資料,normDataSet值被限定在[0, 1]之間
return normDataSet, ranges, minVals # 返回歸一化資料結果,資料範圍,最小值
'''
normDataSet:
[[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:
[9.1273000e+04 2.0919349e+01 1.6943610e+00]
minVals:
[0. 0. 0.001156]
'''
測試演算法:作為完整程式驗證分類器
程式清單 2-4 分類器針對約會網站的測試程式碼
"""
函式說明:分類器測試函式
"""
def datingClassTest():
hoRatio = 0.10 # 整個資料集的10%用來測試,即拿100個樣本作為測試集
datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 匯入資料集
normMat, ranges, minVals = autoNorm(datingDataMat) # 所有特徵歸一化,返回歸一化特徵矩陣,資料範圍,最小值
# print(normMat.shape) # (1000, 3)
m = normMat.shape[0] # 樣本個數,normMat的行數,共1000個樣本
numTestVecs = int(m * hoRatio) # 測試樣本個數,100個
errorCount = 0.0 # 分類錯誤計數
# print('測試集第一條資料:\n',normMat[0,:], '------------------------------','\n訓練集:\n',normMat[numTestVecs:m,:])
'''
測試集第一條資料:
[0.44832535 0.39805139 0.56233353]
----------------------------------
訓練集:
[[0.46457331 0.53983597 0.12206372]
[0.36871802 0.31502142 0.79791792]
[0.10047878 0.2252441 0.11391374]
...
[0.29115949 0.50910294 0.51079493]
[0.52711097 0.43665451 0.4290048 ]
[0.47940793 0.3768091 0.78571804]]
'''
for i in range(numTestVecs):
# 對測試集的每行資料進行分類測試
classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 3) # 取前numTestVecs個數據作為測試集,後m-numTestVecs個數據作為訓練集,datingLabels[numTestVecs:m]為訓練集標籤
print("分類結果:%s, 真實類別:%d" % (classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("錯誤率:%g%%" % (errorCount / float(numTestVecs)*100)) # 列印錯誤率
print("錯誤個數:",errorCount)
使用演算法:構建完整可用系統
程式清單 2-5 約會網站預測函式
"""
函式說明:通過使用者輸入一個人的三維特徵,進行分類輸出
"""
def classifyPerson():
resultList = ['一點也不喜歡','有點喜歡','非常喜歡']
# 使用者輸入特徵
precentTats = float(input("玩視訊遊戲所消耗時間百分比:"))
ffMiles = float(input("每年獲得的飛行常客里程數:"))
iceCream = float(input("每週消費的冰淇淋公升數:"))
datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 開啟並處理資料集
normMat, ranges, minVals = autoNorm(datingDataMat) # 訓練集歸一化
# print('normMat:\n',normMat)
inArr = np.array([ffMiles, precentTats, iceCream]) # 測試集,生成Numpy陣列
# print('inArr:\n',inArr)
norminArr = (inArr - minVals) / ranges # 測試集歸一化
# print('norminArr:\n',norminArr)
'''
normMat:
[[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]]
inArr:
[1.34e+05 5.00e+01 9.00e-01]
norminArr:
[1.4681231 2.39013174 0.53049144]
'''
classifierResult = classify0(norminArr, normMat, datingLabels, 3) # 進行kNN分類
print("你可能%s這個人" % (resultList[classifierResult-1])) # 列印分類結果,classifierResult-1,陣列下標從0起
示例:手寫識別系統
準備資料:將影象轉換為測試向量
"""
函式說明:將32*32的二進位制影象轉換成1*1024的向量
Parameters:
filename - 檔名(路徑)
Returns:
returnVect - 返回的二進位制影象的1*1024向量
"""
def img2vector(filename):
returnVect = np.zeros((1, 1024)) # 建立1*1024的零向量,儲存圖片畫素的向量維度是1*1024
fr = open(filename) # 開啟檔案
for i in range(32): # 按行讀取
lineStr = fr.readline() # 讀一行資料
for j in range(32): # 每一行的前32個數據依次新增到returnVect中
returnVect[0, 32*i+j] = int(lineStr[j]) # 圖片尺寸是32*32,將其依次放入向量returnVect
return returnVect # 返回轉換後的1*1024向量
測試演算法:使用 k-近鄰演算法識別手寫數字
程式清單 2-6 手寫數字識別系統的測試程式碼
"""
函式說明:手寫數字分類測試
"""
def handwritingClassTest():
hwLabels = [] # 測試集Labels
trainingFileList = listdir(r'D:\dataset\inaction\kNN\trainingDigits') # 匯入訓練集,listdir() 返回指定資料夾名字的列表
m = len(trainingFileList) # 資料夾下檔案的個數,1934個檔案
# print(m)
trainingMat = np.zeros((m, 1024)) # 初始化訓練矩陣
for i in range(m):
fileNameStr = trainingFileList[i] # 獲得每個檔案的名字,如 0_0.txt,9_99.txt
# print(fileNameStr)
fileStr = fileNameStr.split('.')[0] # 去掉 .txt , 剩下0_0,9_99
# print(fileStr)
classNumStr = int(fileStr.split('_')[0]) # 按下劃線‘_’劃分‘0_0’,取第一個元素為類別標籤
hwLabels.append(classNumStr) # 將獲得的類別新增到hwLabels中
# print(hwLabels)
trainingMat[i,:] = img2vector(r'D:\dataset\inaction\kNN\trainingDigits\%s' % fileNameStr) # 將每一個檔案的1*1024資料儲存到trainingMat矩陣中
testFileList = listdir(r'D:\dataset\inaction\kNN\testDigits') # 測試樣本,返回testDigits目錄下的檔名
errorCount = 0.0 # 分類錯誤計數
mTest = len(testFileList) # 測試樣本的個數
for i in range(mTest):
fileNameStr = testFileList[i] # 獲得每個檔案的名字,如 0_0.txt
fileStr = fileNameStr.split('.')[0] # 去掉 .txt , 剩下0_0
classNumStr = int(fileStr.split('_')[0]) # 按下劃線‘_’劃分‘0_0’,取第一個元素為類別標籤
vectorUnderTest = img2vector(r'D:\dataset\inaction\kNN\testDigits\%s' % fileNameStr) # 獲得測試集的1*1024向量,用於訓練
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 呼叫kNN分類,獲得預測結果
print("分類返回結果:%d, 真實結果:%d" % (classifierResult, classNumStr)) # 列印分類結果
if classifierResult != classNumStr:
errorCount += 1.0 # 分類錯誤個數計數
print("錯誤總數:%d\n錯誤率:%f%%" % (errorCount, errorCount / float(mTest) * 100)) #列印錯誤個數及錯誤率
附所有程式碼:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 9 09:56:29 2018
@author: YAOTIANLONG
"""
# ------示例1: kNN電影分類 -------------------------------------
import numpy as np
import operator # 匯入運算子模組,sorted排序使用,key=operator.itemgetter(1)
from os import listdir # os.listdir() 方法用於返回指定的資料夾包含的檔案或資料夾的名字的列表
"""
@函式說明: 建立資料集
"""
def createDataSet():
# 四組二維特徵
group = np.array([[3,104],[2,100],[101,10],[99,5]])
# 四組特徵的標籤
labels = ['愛情片','愛情片','動作片','動作片']
return group, labels
"""
@函式說明:kNN分類(k近鄰演算法)
Parameters:
inX - 用於分類的資料(測試集)
dataSet - 用於訓練的資料(訓練集)
labels - 類別標籤
k - 選擇最近鄰居的數目
Returns:
sortedClassCount[0][0] - 分類結果
"""
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # numpy函式shape[0]返回dataSet的行數,4行
# 歐式距離計算
# 橫向複製inX共1次,縱向複製inX共dataSize次
# print(np.tile(inX, (dataSetSize,1)))
"""
構造相同行數的測試集test
[[ 10 120]
[ 10 120]
[ 10 120]
[ 10 120]]
"""
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # 沿橫向、縱向複製多維陣列
"""
test: group: diffMat:
[[ 10 120] [[ 3 104] [[ 7 16]
[ 10 120] —— [ 2 100] = [ 8 20]
[ 10 120] [101 10] [-91 110]
[ 10 120]] [ 99 5]] [-89 115]]
"""
# print(diffMat)
# 二維特徵相減後平方
sqDiffMat = diffMat ** 2
# print(sqDiffMat)
"""
sqDiffMat(每個元素平方):
[[ 49 256]
[ 64 400]
[ 8281 12100]
[ 7921 13225]]
"""
# sum()將所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
# print("sqDistances:\n",sqDistances)
"""
sqDistances:
[ 305 464 20381 21146]
"""
# 開方,計算出距離
distances = sqDistances ** 0.5
# print(" distances:\n", distances)
"""
distances(算出來的最終的歐式距離):
[ 17.4642492 21.54065923 142.76203977 145.41664279]
"""
sortedDistIndices = distances.argsort() # 返回distances中元素從小到大排序後的索引值
# print(sortedDistIndices) # [0 1 2 3]
classCount = {} # 定義記錄類別次數的字典
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]] # 取出前K個元素的類別,前3個類別為:愛情片 愛情片 動作片
# print(voteIlabel)
#dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回預設值。
#計算類別次數
# print(classCount.get(voteIlabel,0))
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # 沒出現過的返回0再+1就是1次,愛情片(在空字典中沒出現過,+1作為1次)、愛情片(出現過1次,+1是兩次)、動作片(沒出現過,+1作為1次)
# print(classCount[voteIlabel]) # 愛情片 愛情片 動作片, 1 2 1
# classCount: {'愛情片': 2, '動作片': 1}
"""
python內建函式:sorted函式
sorted(iterable<可迭代物件>, cmp<對函式排序>, key<對元素排序>, reverse<True降序,False升序(預設)>)
sort 是應用在 list 上的方法,sorted 可以對所有可迭代的物件進行排序操作。
內建函式 sorted 方法返回的是一個新的 list,而不是在原來的基礎上進行的操作。
詳解:http://www.runoob.com/python/python-func-sorted.html
"""
#python3中用items()替換python2中的iteritems()
#key=operator.itemgetter(1)根據字典的值value進行排序
#key=operator.itemgetter(0)根據字典的鍵key進行排序
#reverse降序排序字典,reverse -- 排序規則,reverse = True 降序 , reverse = False 升序(預設)
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
# print('sortedClassCount:\n',sortedClassCount)
"""
sortedClassCount:
[('愛情片', 2), ('動作片', 1)]
"""
return sortedClassCount[0][0]
"""
函式說明:測試kNN電影分類
"""
def test0():
group, labels = createDataSet() # 建立資料集
print('group:\n',group) # 列印資料集
print('labels:\n',labels)
test = [101,20] # 測試集
test_class = classify0(test, group, labels, 3) # kNN分類
print(test_class)
print('"',test,'kNN classify',test_class,'"') # 列印分類結果
# ------示例2: 使用kNN演算法改進約會網站的配對效果 -------------------------------------
"""
函式說明:開啟並解析檔案,對資料進行分類:1代表不喜歡、2代表魅力一般,3代表極具魅力
Parameters:
filename - 檔名
Return:
returnMat - 特徵矩陣
classLabelVector - 分類Label向量
"""
def file2matrix(filename):
loveDict = {'didntLike':1,'smallDoses':2,'largeDoses':3}
fr = open(filename) # 開啟檔案
arrayOlines = fr.readlines() # 逐行讀取
# print(arrayOlines)
'''
arrayOlines:
['40920\t8.326976\t0.953952\tlargeDoses\n', '14488\t7.153469\t1.673904\tsmallDoses\n',...,'43757\t7.882601\t1.332446\tlargeDoses\n']
'''
numberOfLines = len(arrayOlines) # 得到檔案行數,共1000行資料
# print(numberOfLines)
returnMat = np.zeros( (numberOfLines,3) ) # 初始化特徵矩陣,解析完成的資料:numberOfLines行3列
classLabelVector = [] # 初始化分類標籤向量
index = 0 # 行的索引值
for line in arrayOlines:
# s.strip(rm),當rm空時,預設刪除空白符(包括'\n','\r','\t',' ')
line = line.strip() # 刪去字串首尾部空字元
# print(line)
'''
如最後兩行line:
48111 9.134528 0.728045 largeDoses
43757 7.882601 1.332446 largeDoses
'''
# 使用s.split(str="",num=string,cout(str))將字串根據'\t'分隔符進行切片
listFromLine = line.split('\t') # 按'\t'對字串進行分割,listFromLine是列表
# print(listFromLine) # 分割成列表
'''
如最後兩行listFromLine(列表):
['48111', '9.134528', '0.728045', 'largeDoses']
['43757', '7.882601', '1.332446', 'largeDoses']
'''
returnMat[index,:] = listFromLine[0:3] # 將資料前三列(特徵)一行一行賦值給returnMat
index += 1
# 根據文字中標記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
# 讀取的listFromLine的最後一列為類別標籤
if listFromLine[-1]