背景 這是我們?yōu)閾碛?Python 基礎(chǔ)的同學推出的精進技能的“機器學習實戰(zhàn)” 刻意練習活動,這也是我們本學期推出的第三次活動了。
我們準備利用8周時間,夯實機器學習常用算法,完成以下任務(wù):
分類問題:K鄰近算法
分類問題:決策樹
分類問題:樸素貝葉斯
分類問題:邏輯回歸
分類問題:支持向量機
分類問題:AdaBoost
回歸問題:線性回歸、嶺回歸、套索方法、逐步回歸等
回歸問題:樹回歸
聚類問題:K均值聚類
相關(guān)問題:Apriori
相關(guān)問題:FP-Growth
簡化數(shù)據(jù):PCA主成分分析
簡化數(shù)據(jù):SVD奇異值分解
本次任務(wù)的核心是熟悉K鄰近算法的原理,并實現(xiàn)《機器學習實戰(zhàn)》這本書給出的兩個案例。
算法原理 KNN 算法采用測量不同特征之間的距離方法進行分類,通俗的講就是:給定一個樣本數(shù)據(jù)集,并且樣本集中每個數(shù)據(jù)都存在標簽,即我們知道樣本集中每個數(shù)據(jù)與所屬分類的對應(yīng)關(guān)系。對新輸入沒有標簽的實例,在訓練數(shù)據(jù)集中找到與該實例最鄰近的 k 個實例,這 k 個實例的多數(shù)屬于某個類,就把該輸入實例分為這個類。
對于每一個在數(shù)據(jù)集中的數(shù)據(jù)點: 計算目標的數(shù)據(jù)點(需要分類的數(shù)據(jù)點)與該數(shù)據(jù)點的距離 將距離排序:從小到大 選取前 K 個最短距離 選取這 K 個中最多的分類類別 返回該類別來作為目標數(shù)據(jù)點的預(yù)測值
項目案例1:優(yōu)化約會網(wǎng)站的配對效果 項目概述
海倫使用約會網(wǎng)站尋找約會對象。經(jīng)過一段時間之后,她發(fā)現(xiàn)曾交往過三種類型的人:
她希望:
不喜歡的人則直接排除掉
工作日與魅力一般的人約會
周末與極具魅力的人約會
現(xiàn)在她收集到了一些約會網(wǎng)站未曾記錄的數(shù)據(jù)信息,這更有助于匹配對象的歸類。
開發(fā)流程
Step1:收集數(shù)據(jù)
此案例書中提供了文本文件。
海倫把這些約會對象的數(shù)據(jù)存放在文本文件 datingTestSet2.txt 中,總共有 1000 行。海倫約會的對象主要包含以下 3 種特征:
Col1
:每年獲得的飛行??屠锍虜?shù)
Col2
:玩視頻游戲所耗時間百分比
Col3
:每周消費的冰淇淋公升數(shù)
文本文件數(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:準備數(shù)據(jù)
使用 Python 解析文本文件。將文本記錄轉(zhuǎn)換為 NumPy 的解析程序如下所示:
import numpy as npdef file2matrix (filename) : """ Desc: 導入訓練數(shù)據(jù) parameters: filename: 數(shù)據(jù)文件路徑 return: 數(shù)據(jù)矩陣 returnMat 和對應(yīng)的類別 classLabelVector """ fr = open(filename) # 獲得文件中的數(shù)據(jù)行的行數(shù) lines = fr.readlines() numberOfLines = len(lines) # type: int # 生成對應(yīng)的空矩陣 # 例如:zeros(2,3)就是生成一個 2*3的矩陣,各個位置上全是 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 標簽數(shù)據(jù) classLabelVector.append(int(listFromLine[-1 ])) index += 1 # 返回數(shù)據(jù)矩陣returnMat和對應(yīng)的類別classLabelVector return returnMat, classLabelVector
Step3:分析數(shù)據(jù)
使用 Matplotlib 畫二維散點圖。
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()
圖中清晰地標識了三個不同的樣本分類區(qū)域,具有不同愛好的人其類別區(qū)域也不同。
歸一化特征值,消除特征之間量級不同導致的影響。
def autoNorm (dataSet) : """ Desc: 歸一化特征值,消除特征之間量級不同導致的影響 parameter: dataSet: 數(shù)據(jù)集 return: 歸一化后的數(shù)據(jù)集 normDataSet.ranges和minVals即最小值與范圍,并沒有用到 歸一化公式: Y = (X-Xmin)/(Xmax-Xmin) 其中的 min 和 max 分別是數(shù)據(jù)集中的最小特征值和最大特征值。該函數(shù)可以自動將數(shù)字特征值轉(zhuǎn)化為0到1的區(qū)間。 """ # 計算每種屬性的最大值、最小值、范圍 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:訓練算法
此步驟不適用于 k-近鄰算法。因為測試數(shù)據(jù)每一次都要與全部的訓練數(shù)據(jù)進行比較,所以這個過程是沒有必要的。
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個最短距離, 選取這K個中最多的分類類別 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:測試算法
計算錯誤率,使用海倫提供的部分數(shù)據(jù)作為測試樣本。如果預(yù)測分類與實際類別不同,則標記為一個錯誤。
def datingClassTest () : """ Desc: 對約會網(wǎng)站的測試方法 parameters: none return: 錯誤數(shù) """ # 設(shè)置測試數(shù)據(jù)的的一個比例 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表示訓練樣本的數(shù)量 numTestVecs = int(m * hoRatio) print('numTestVecs=' , numTestVecs) errorCount = 0.0 for i in range(numTestVecs): # 對數(shù)據(jù)測試 classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3 ) print("分類器返回結(jié)果: %d, 實際結(jié)果: %d" % (classifierResult, datingLabels[i])) if classifierResult != datingLabels[i]: errorCount += 1.0 print("錯誤率: %f" % (errorCount / float(numTestVecs))) print(errorCount)
Step6:使用算法
產(chǎn)生簡單的命令行程序,然后海倫可以輸入一些特征數(shù)據(jù)以判斷對方是否為自己喜歡的類型。
約會網(wǎng)站預(yù)測函數(shù)如下:
def classifyPerson () : resultList = ['不喜歡的人' , '魅力一般的人' , '極具魅力的人' ] ffMiles = float(input("每年獲得的飛行??屠锍虜?shù)?" )) percentTats = float(input("玩視頻游戲所耗時間百分比?" )) iceCream = float(input("每周消費的冰淇淋公升數(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("這個人屬于: " , resultList[classifierResult - 1 ])
實際運行效果如下:
if __name__ == '__main__' : classifyPerson()''' 每年獲得的飛行常客里程數(shù)? 10000 玩視頻游戲所耗時間百分比? 10 每周消費的冰淇淋公升數(shù)? 0.5 這個人屬于: 魅力一般的人 '''
項目案例2:手寫識別系統(tǒng) 項目概述
構(gòu)造一個能識別數(shù)字 0 到 9 的基于 KNN 分類器的手寫數(shù)字識別系統(tǒng)。
需要識別的數(shù)字是存儲在文本文件中的具有相同的色彩和大小:寬高是 32 像素 * 32 像素的黑白圖像。
開發(fā)流程
Step1:收集數(shù)據(jù)
本案例書中提供了文本文件。
目錄 trainingDigits 中包含了大約 2000 個例子,每個例子內(nèi)容如下圖所示,每個數(shù)字大約有 200 個樣本;目錄 testDigits 中包含了大約 900 個測試數(shù)據(jù)。
Step2:準備數(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:訓練算法
此步驟不適用于 k-近鄰算法。因為測試數(shù)據(jù)每一次都要與全部的訓練數(shù)據(jù)進行比較,所以這個過程是沒有必要的。
Step5:測試算法
計算錯誤率,編寫函數(shù)使用提供的部分數(shù)據(jù)集作為測試樣本,如果預(yù)測分類與實際類別不同,則標記為一個錯誤。
import osdef handwritingClassTest () : # 1. 導入訓練數(shù)據(jù) hwLabels = [] trainingFileList = os.listdir(r'./digits/trainingDigits' ) # load the training set m = len(trainingFileList) trainingMat = np.zeros((m, 1024 )) # hwLabels存儲0~9對應(yīng)的index位置, trainingMat存放的每個位置對應(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. 導入測試數(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, 實際結(jié)果: %d" % (classifierResult, classNumStr)) if classifierResult != classNumStr: errorCount += 1.0 print("分類錯誤數(shù)量: %d" % errorCount) print("分類錯誤率: %f" % (errorCount / float(mTest)))
Step6:使用算法
可以構(gòu)造一個小的軟件系統(tǒng),從圖像中提取數(shù)字,并完成數(shù)字識別,我們現(xiàn)實中使用的OCR,以及車牌識別都類似于這樣的系統(tǒng)。
總結(jié) 到此為止 KNN 算法的兩個案例就全部搞定了,這個算法看起來很簡單,但參數(shù) K 的選擇,距離的選擇都需要不斷的配置和測試才能得到滿意的效果。當然,KNN 算法的主要缺點是不提取訓練數(shù)據(jù)的特征,測試實例需要與每個訓練實例計算距離導致算法的執(zhí)行速度很慢。為了提升搜索 K 個最鄰近實例的速度,后面有了K-D Tree的結(jié)構(gòu),這些都不是這本書的重點了,我們主要是先掌握基本算法,有個武器能處理數(shù)據(jù)再說,等后面實際應(yīng)用的時候再來考慮效率問題。好了,就這樣吧!See You!
參考文獻
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