背景 這是我們?yōu)閾碛?Python 基礎(chǔ)的同學(xué)推出的精進(jìn)技能的“機(jī)器學(xué)習(xí)實(shí)戰(zhàn)” 刻意練習(xí)活動(dòng),這也是我們本學(xué)期推出的第三次活動(dòng)了。
我們準(zhǔn)備利用8周時(shí)間,夯實(shí)機(jī)器學(xué)習(xí)常用算法,完成以下任務(wù):
分類問題:K鄰近算法
分類問題:決策樹
分類問題:樸素貝葉斯
分類問題:邏輯回歸
分類問題:支持向量機(jī)
分類問題:AdaBoost
回歸問題:線性回歸、嶺回歸、套索方法、逐步回歸等
回歸問題:樹回歸
聚類問題:K均值聚類
相關(guān)問題:Apriori
相關(guān)問題:FP-Growth
簡化數(shù)據(jù):PCA主成分分析
簡化數(shù)據(jù):SVD奇異值分解
本次任務(wù)的核心是熟悉K鄰近算法的原理,并實(shí)現(xiàn)《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》這本書給出的兩個(gè)案例。
算法原理 KNN 算法采用測量不同特征之間的距離方法進(jìn)行分類,通俗的講就是:給定一個(gè)樣本數(shù)據(jù)集,并且樣本集中每個(gè)數(shù)據(jù)都存在標(biāo)簽,即我們知道樣本集中每個(gè)數(shù)據(jù)與所屬分類的對(duì)應(yīng)關(guān)系。對(duì)新輸入沒有標(biāo)簽的實(shí)例,在訓(xùn)練數(shù)據(jù)集中找到與該實(shí)例最鄰近的 k 個(gè)實(shí)例,這 k 個(gè)實(shí)例的多數(shù)屬于某個(gè)類,就把該輸入實(shí)例分為這個(gè)類。
對(duì)于每一個(gè)在數(shù)據(jù)集中的數(shù)據(jù)點(diǎn): 計(jì)算目標(biāo)的數(shù)據(jù)點(diǎn)(需要分類的數(shù)據(jù)點(diǎn))與該數(shù)據(jù)點(diǎn)的距離 將距離排序:從小到大 選取前 K 個(gè)最短距離 選取這 K 個(gè)中最多的分類類別 返回該類別來作為目標(biāo)數(shù)據(jù)點(diǎn)的預(yù)測值
項(xiàng)目案例1:優(yōu)化約會(huì)網(wǎng)站的配對(duì)效果 項(xiàng)目概述
海倫使用約會(huì)網(wǎng)站尋找約會(huì)對(duì)象。經(jīng)過一段時(shí)間之后,她發(fā)現(xiàn)曾交往過三種類型的人:
她希望:
不喜歡的人則直接排除掉
工作日與魅力一般的人約會(huì)
周末與極具魅力的人約會(huì)
現(xiàn)在她收集到了一些約會(huì)網(wǎng)站未曾記錄的數(shù)據(jù)信息,這更有助于匹配對(duì)象的歸類。
開發(fā)流程
Step1:收集數(shù)據(jù)
此案例書中提供了文本文件。
海倫把這些約會(huì)對(duì)象的數(shù)據(jù)存放在文本文件 datingTestSet2.txt 中,總共有 1000 行。海倫約會(huì)的對(duì)象主要包含以下 3 種特征:
文本文件數(shù)據(jù)格式如下:
40920 8.326976 0.953952 3 14488 7.153469 1.673904 2 26052 1.441871 0.805124 1 75136 13.147394 0.428964 1 38344 1.669788 0.134296 1
Step2:準(zhǔn)備數(shù)據(jù)
使用 Python 解析文本文件。將文本記錄轉(zhuǎn)換為 NumPy 的解析程序如下所示:
import numpy as npdef file2matrix (filename) : """ Desc: 導(dǎo)入訓(xùn)練數(shù)據(jù) parameters: filename: 數(shù)據(jù)文件路徑 return: 數(shù)據(jù)矩陣 returnMat 和對(duì)應(yīng)的類別 classLabelVector """ fr = open(filename) # 獲得文件中的數(shù)據(jù)行的行數(shù) lines = fr.readlines() numberOfLines = len(lines) # type: int # 生成對(duì)應(yīng)的空矩陣 # 例如:zeros(2,3)就是生成一個(gè) 2*3的矩陣,各個(gè)位置上全是 0 returnMat = np.zeros((numberOfLines, 3 )) # prepare matrix to return classLabelVector = [] # prepare labels return index = 0 for line in lines: # str.strip([chars]) --返回已移除字符串頭尾指定字符所生成的新字符串 line = line.strip() # 以 '\t' 切割字符串 listFromLine = line.split('\t' ) # 每列的屬性數(shù)據(jù) returnMat[index, :] = listFromLine[0 :3 ] # 每列的類別數(shù)據(jù),就是 label 標(biāo)簽數(shù)據(jù) classLabelVector.append(int(listFromLine[-1 ])) index += 1 # 返回?cái)?shù)據(jù)矩陣returnMat和對(duì)應(yīng)的類別classLabelVector return returnMat, classLabelVector
Step3:分析數(shù)據(jù)
使用 Matplotlib 畫二維散點(diǎn)圖。
import matplotlib.pyplot as pltif __name__ == '__main__' : datingDataMat, datingLabels = file2matrix('datingTestSet2.txt' ) color = ['r' , 'g' , 'b' ] fig = plt.figure() ax = fig.add_subplot(311 ) for i in range(1 , 4 ): index = np.where(np.array(datingLabels) == i) ax.scatter(datingDataMat[index, 0 ], datingDataMat[index, 1 ], c=color[i - 1 ], label=i) plt.xlabel('Col.0' ) plt.ylabel('Col.1' ) plt.legend() bx = fig.add_subplot(312 ) for i in range(1 , 4 ): index = np.where(np.array(datingLabels) == i) bx.scatter(datingDataMat[index, 0 ], datingDataMat[index, 2 ], c=color[i - 1 ], label=i) plt.xlabel('Col.0' ) plt.ylabel('Col.2' ) plt.legend() cx = fig.add_subplot(313 ) for i in range(1 , 4 ): index = np.where(np.array(datingLabels) == i) cx.scatter(datingDataMat[index, 1 ], datingDataMat[index, 2 ], c=color[i - 1 ], label=i) plt.xlabel('Col.1' ) plt.ylabel('Col.2' ) plt.legend() plt.show()
圖中清晰地標(biāo)識(shí)了三個(gè)不同的樣本分類區(qū)域,具有不同愛好的人其類別區(qū)域也不同。
歸一化特征值,消除特征之間量級(jí)不同導(dǎo)致的影響。
def autoNorm (dataSet) : """ Desc: 歸一化特征值,消除特征之間量級(jí)不同導(dǎo)致的影響 parameter: dataSet: 數(shù)據(jù)集 return: 歸一化后的數(shù)據(jù)集 normDataSet.ranges和minVals即最小值與范圍,并沒有用到 歸一化公式: Y = (X-Xmin)/(Xmax-Xmin) 其中的 min 和 max 分別是數(shù)據(jù)集中的最小特征值和最大特征值。該函數(shù)可以自動(dòng)將數(shù)字特征值轉(zhuǎn)化為0到1的區(qū)間。 """ # 計(jì)算每種屬性的最大值、最小值、范圍 minVals = np.min(dataSet, axis=0 ) maxVals = np.max(dataSet, axis=0 ) # 極差 ranges = maxVals - minVals m = dataSet.shape[0 ] # 生成與最小值之差組成的矩陣 normDataSet = dataSet - np.tile(minVals, (m, 1 )) # 將最小值之差除以范圍組成矩陣 normDataSet = normDataSet / np.tile(ranges, (m, 1 )) # element wise divide return normDataSet, ranges, minVals
Step4:訓(xùn)練算法
此步驟不適用于 k-近鄰算法。因?yàn)闇y試數(shù)據(jù)每一次都要與全部的訓(xùn)練數(shù)據(jù)進(jìn)行比較,所以這個(gè)過程是沒有必要的。
import operatordef classify0 (inX, dataSet, labels, k) : dataSetSize = dataSet.shape[0 ] # 距離度量 度量公式為歐氏距離 diffMat = np.tile(inX, (dataSetSize, 1 )) - dataSet sqDiffMat = diffMat ** 2 sqDistances = np.sum(sqDiffMat, axis=1 ) distances = sqDistances ** 0.5 # 將距離排序:從小到大 sortedDistIndicies = distances.argsort() # 選取前K個(gè)最短距離, 選取這K個(gè)中最多的分類類別 classCount = {} 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 ]
Step5:測試算法
計(jì)算錯(cuò)誤率,使用海倫提供的部分?jǐn)?shù)據(jù)作為測試樣本。如果預(yù)測分類與實(shí)際類別不同,則標(biāo)記為一個(gè)錯(cuò)誤。
def datingClassTest () : """ Desc: 對(duì)約會(huì)網(wǎng)站的測試方法 parameters: none return: 錯(cuò)誤數(shù) """ # 設(shè)置測試數(shù)據(jù)的的一個(gè)比例 hoRatio = 0.1 # 測試范圍,一部分測試一部分作為樣本 # 從文件中加載數(shù)據(jù) datingDataMat, datingLabels = file2matrix('datingTestSet2.txt' ) # load data setfrom file # 歸一化數(shù)據(jù) normMat, ranges, minVals = autoNorm(datingDataMat) # m 表示數(shù)據(jù)的行數(shù),即矩陣的第一維 m = normMat.shape[0 ] # 設(shè)置測試的樣本數(shù)量, numTestVecs:m表示訓(xùn)練樣本的數(shù)量 numTestVecs = int(m * hoRatio) print('numTestVecs=' , numTestVecs) errorCount = 0.0 for i in range(numTestVecs): # 對(duì)數(shù)據(jù)測試 classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3 ) print("分類器返回結(jié)果: %d, 實(shí)際結(jié)果: %d" % (classifierResult, datingLabels[i])) if classifierResult != datingLabels[i]: errorCount += 1.0 print("錯(cuò)誤率: %f" % (errorCount / float(numTestVecs))) print(errorCount)
Step6:使用算法
產(chǎn)生簡單的命令行程序,然后海倫可以輸入一些特征數(shù)據(jù)以判斷對(duì)方是否為自己喜歡的類型。
約會(huì)網(wǎng)站預(yù)測函數(shù)如下:
def classifyPerson () : resultList = ['不喜歡的人' , '魅力一般的人' , '極具魅力的人' ] ffMiles = float(input("每年獲得的飛行常客里程數(shù)?" )) percentTats = float(input("玩視頻游戲所耗時(shí)間百分比?" )) iceCream = float(input("每周消費(fèi)的冰淇淋公升數(shù)?" )) datingDataMat, datingLabels = file2matrix('datingTestSet2.txt' ) normMat, ranges, minVals = autoNorm(datingDataMat) inArr = np.array([ffMiles, percentTats, iceCream]) intX = (inArr - minVals) / ranges classifierResult = classify0(intX, normMat, datingLabels, 3 ) print("這個(gè)人屬于: " , resultList[classifierResult - 1 ])
實(shí)際運(yùn)行效果如下:
if __name__ == '__main__' : classifyPerson()''' 每年獲得的飛行??屠锍虜?shù)? 10000 玩視頻游戲所耗時(shí)間百分比? 10 每周消費(fèi)的冰淇淋公升數(shù)? 0.5 這個(gè)人屬于: 魅力一般的人 '''
項(xiàng)目案例2:手寫識(shí)別系統(tǒng) 項(xiàng)目概述
構(gòu)造一個(gè)能識(shí)別數(shù)字 0 到 9 的基于 KNN 分類器的手寫數(shù)字識(shí)別系統(tǒng)。
需要識(shí)別的數(shù)字是存儲(chǔ)在文本文件中的具有相同的色彩和大?。簩捀呤?32 像素 * 32 像素的黑白圖像。
開發(fā)流程
Step1:收集數(shù)據(jù)
本案例書中提供了文本文件。
目錄 trainingDigits 中包含了大約 2000 個(gè)例子,每個(gè)例子內(nèi)容如下圖所示,每個(gè)數(shù)字大約有 200 個(gè)樣本;目錄 testDigits 中包含了大約 900 個(gè)測試數(shù)據(jù)。
Step2:準(zhǔn)備數(shù)據(jù)
編寫函數(shù) img2vector()
, 將圖像文本數(shù)據(jù)轉(zhuǎn)換為分類器使用的向量。
def img2vector (filename) : returnVect = np.zeros((1 , 1024 )) fr = open(filename) for i in range(32 ): # 32行 lineStr = fr.readline() for j in range(32 ): # 32列 returnVect[0 , 32 * i + j] = int(lineStr[j]) return returnVect
Step3:分析數(shù)據(jù)
在 Python 命令提示符中檢查數(shù)據(jù),確保它符合要求。
testVector = img2vector(r'./digits/trainingDigits/0_13.txt' ) print(testVector[0 , 0 :32 ])# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] print(testVector[0 , 32 :64 ])# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
Step4:訓(xùn)練算法
此步驟不適用于 k-近鄰算法。因?yàn)闇y試數(shù)據(jù)每一次都要與全部的訓(xùn)練數(shù)據(jù)進(jìn)行比較,所以這個(gè)過程是沒有必要的。
Step5:測試算法
計(jì)算錯(cuò)誤率,編寫函數(shù)使用提供的部分?jǐn)?shù)據(jù)集作為測試樣本,如果預(yù)測分類與實(shí)際類別不同,則標(biāo)記為一個(gè)錯(cuò)誤。
import osdef handwritingClassTest () : # 1. 導(dǎo)入訓(xùn)練數(shù)據(jù) hwLabels = [] trainingFileList = os.listdir(r'./digits/trainingDigits' ) # load the training set m = len(trainingFileList) trainingMat = np.zeros((m, 1024 )) # hwLabels存儲(chǔ)0~9對(duì)應(yīng)的index位置, trainingMat存放的每個(gè)位置對(duì)應(yīng)的圖片向量 for i in range(m): fileNameStr = trainingFileList[i] fileStr = fileNameStr.split('.' )[0 ] # take off .txt classNumStr = int(fileStr.split('_' )[0 ]) hwLabels.append(classNumStr) # 將 32*32的矩陣->1*1024的矩陣 trainingMat[i, :] = img2vector(r'./digits/trainingDigits/%s' % fileNameStr) # 2. 導(dǎo)入測試數(shù)據(jù) testFileList = os.listdir(r'./digits/testDigits' ) # iterate through the test set errorCount = 0.0 mTest = len(testFileList) for i in range(mTest): fileNameStr = testFileList[i] fileStr = fileNameStr.split('.' )[0 ] # take off .txt classNumStr = int(fileStr.split('_' )[0 ]) vectorUnderTest = img2vector(r'./digits/testDigits/%s' % fileNameStr) classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3 ) print("分類器返回結(jié)果: %d, 實(shí)際結(jié)果: %d" % (classifierResult, classNumStr)) if classifierResult != classNumStr: errorCount += 1.0 print("分類錯(cuò)誤數(shù)量: %d" % errorCount) print("分類錯(cuò)誤率: %f" % (errorCount / float(mTest)))
Step6:使用算法
可以構(gòu)造一個(gè)小的軟件系統(tǒng),從圖像中提取數(shù)字,并完成數(shù)字識(shí)別,我們現(xiàn)實(shí)中使用的OCR,以及車牌識(shí)別都類似于這樣的系統(tǒng)。
總結(jié) 到此為止 KNN 算法的兩個(gè)案例就全部搞定了,這個(gè)算法看起來很簡單,但參數(shù) K 的選擇,距離的選擇都需要不斷的配置和測試才能得到滿意的效果。當(dāng)然,KNN 算法的主要缺點(diǎn)是不提取訓(xùn)練數(shù)據(jù)的特征,測試實(shí)例需要與每個(gè)訓(xùn)練實(shí)例計(jì)算距離導(dǎo)致算法的執(zhí)行速度很慢。為了提升搜索 K 個(gè)最鄰近實(shí)例的速度,后面有了K-D Tree的結(jié)構(gòu),這些都不是這本書的重點(diǎn)了,我們主要是先掌握基本算法,有個(gè)武器能處理數(shù)據(jù)再說,等后面實(shí)際應(yīng)用的時(shí)候再來考慮效率問題。好了,就這樣吧!See You!
參考文獻(xiàn)
https://github.com/apachecn/AiLearning/tree/master/docs/ml
https://blog.csdn.net/c406495762/column/info/16415
https://www.bilibili.com/video/av36993857
https://space.bilibili.com/97678687/channel/detail?cid=22486
https://space.bilibili.com/97678687/channel/detail?cid=13045