机器学习实战(一)—— K-近邻算法(KNN)
本系列文章以《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》為基礎(chǔ),并結(jié)合B站的UP主shuhuai008的機(jī)器學(xué)習(xí)白板推導(dǎo)系列合集,加強(qiáng)對(duì)機(jī)器學(xué)習(xí)基礎(chǔ)算法的理解及運(yùn)用。
如果大家對(duì)計(jì)算機(jī)視覺(jué)感興趣可以參考博主的計(jì)算機(jī)視覺(jué)專欄:Python計(jì)算機(jī)視覺(jué)
? ? ? ? 近年來(lái),深度學(xué)習(xí)大火,甚至有干倒其他的機(jī)器學(xué)習(xí)方法的趨勢(shì),但基礎(chǔ)還是要打牢的,所以本篇文章用來(lái)介紹機(jī)器學(xué)習(xí)中比較基礎(chǔ)的一個(gè)算法——K-近鄰算法,希望能幫到大家。?
? ? ? ? ?
? ? ? 本文章主要參考自《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》,并加入自己的一些理解,盡量將該算法的基本原理和Python代碼實(shí)現(xiàn)講的通俗易懂。
目錄
一 基本概念
1 概念
2 實(shí)現(xiàn)步驟
二 代碼實(shí)現(xiàn)
三 約會(huì)網(wǎng)站分析
四 數(shù)據(jù)集以及《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》獲取
五 結(jié)束語(yǔ)
一 基本概念
1 概念
? ? ? ?一般來(lái)說(shuō),我們確信‘如果你想了解一個(gè)人,那么先了解他周圍的人’是一個(gè)真理,這也是K-近鄰算法的主要思想。首先我們有一個(gè)訓(xùn)練數(shù)據(jù)集,它詳細(xì)的記錄了不同類別的樣本的各種特征數(shù)據(jù),比如我們想要通過(guò)身高和體重以及腰圍等特征分別男生和女生,那么我們首先得得到大量的男生和女生的這些特征的數(shù)據(jù);對(duì)這些數(shù)據(jù)進(jìn)行處理過(guò)后,我們這時(shí)又得到一個(gè)同學(xué)的身高、體重以及其他特征的數(shù)據(jù),那么我們?cè)撛趺磁袛嘣撏瑢W(xué)的性別呢?最簡(jiǎn)單的方法就是使用K-近鄰算法了,首先計(jì)算這位同學(xué)的各項(xiàng)特征和訓(xùn)練集中的樣本的歐氏距離(就是簡(jiǎn)單的兩點(diǎn)之間的距離),選出和測(cè)試樣本距離最近的k個(gè)樣本,并計(jì)算出k個(gè)樣本中不同類別的樣本的占比,哪個(gè)種類的占比高就說(shuō)明測(cè)試樣本屬于該樣本。還是拿性別分類舉例,如果我們得到6個(gè)最近的測(cè)試樣本點(diǎn),其中有5個(gè)樣本是女生,只有一個(gè)樣本是男生,那么我們就有很大的把握確定該測(cè)試樣本是個(gè)女生。
除非我們的分類依據(jù)是頭發(fā)長(zhǎng)度、衣服等特征:
?所以說(shuō)除了k的大小,我們還要兼顧數(shù)據(jù)集的特征,盡量是獨(dú)一無(wú)二的那種,哈哈哈哈。
2 實(shí)現(xiàn)步驟
step one: 收集數(shù)據(jù)(就是得到大量的訓(xùn)練樣本)
step two: 對(duì)測(cè)試樣本集進(jìn)行結(jié)構(gòu)化并分析
step three: 計(jì)算輸入的測(cè)試樣本和訓(xùn)練樣本之間的距離
step four: 對(duì)測(cè)試樣本進(jìn)行分類
step five: 輸出結(jié)果
二 代碼實(shí)現(xiàn)
? ? ? ?假設(shè)我們需要對(duì)醫(yī)院的病人的病情等級(jí)進(jìn)行分類,為了演示方便我們指定病人的病情分為A,B兩個(gè)等級(jí),分析指標(biāo)也只有兩項(xiàng)。
比如我們之前就得到的病人的各項(xiàng)數(shù)據(jù)指標(biāo)以及病情等級(jí)如下表所示:
| 病人編號(hào) | 病情等級(jí) | 指標(biāo)一 | 指標(biāo)二 |
| 1 | A | 1.0 | 1.1 |
| 2 | A | 1.0 | 1.0 |
| 3 | A | 0.9 | 1.0 |
| 4 | A | 0.8 | 1.1 |
| 5 | A | 1.1 | 1.0 |
| 6 | B | 0.2 | 0.13 |
| 7 | B | 0.1 | 0.11 |
| 8 | B | 0.1 | 0 |
| 9 | B | 0 | 0 |
| 10 | B | 0 | 0.1 |
接下來(lái)我們要設(shè)計(jì)一個(gè)分類器來(lái)判斷我們醫(yī)院新接的病人的病情等級(jí)。
首先第一步我們要得到訓(xùn)練樣本并將樣本表示在圖中:
""" Author:XiaoMa date:2021/12/1 """ #導(dǎo)入需要的庫(kù) import numpy as np import matplotlib.pyplot as plt import operator #得到訓(xùn)練樣本集 def createDataSet():group = np.array([[1.0, 1.1], [1.0, 1.0], [0.9, 1], [0.8, 1.1], [1.1, 1], [0.2, 0.13], [0.1, 0.11], [0.1, 0], [0, 0], [0, 0.1]])labels = np.array(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'])return group, labels #返回病人的病情指標(biāo)和病情等級(jí) group, labels = createDataSet()plt.rcParams['font.family'] = 'SimHei' #將全局中文字體改為黑體 plt.rcParams['axes.unicode_minus'] = False #正常表示負(fù)號(hào) A = group[0:5] #A組是樣本集中的前五個(gè) B = group[5:] #B組是樣本集中的后五個(gè) plt.scatter(A[:, 0], A[:, 1], color = 'aquamarine', label = 'A') #將數(shù)組中的前一列作為x軸,后一列作為y軸繪制在散點(diǎn)圖中 plt.scatter(B[:, 0], B[:, 1], color = 'r', label = 'B') plt.legend(loc = 'upper left') #添加圖例 plt.show()?
接下來(lái)就是用代碼實(shí)現(xiàn)K-近鄰算法:
#K-近鄰算法 def classify0(inX, dataSet, labels, k): #四個(gè)參數(shù)分別為:用于分類的輸入向量inX,訓(xùn)練樣本集dataSet,標(biāo)簽向量labels,最近鄰居數(shù)目KdataSetSize = dataSet.shape[0] #取得訓(xùn)練樣本數(shù)目diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #將輸入的待分類樣本進(jìn)行x軸方向的四次復(fù)制并減去樣本集,如計(jì)算(x1, y1)和(x2, y2)之間的距離,首先需要計(jì)算(x2-x1, y2-y1)sqDiffMat = diffMat**2 #對(duì)得到的差值進(jìn)行平方sqDistances = sqDiffMat.sum(axis = 1) #求得(x2-x1)^2 + (y2-y1)^2distance = sqDistances**0.5 #對(duì)平方和開(kāi)根號(hào)得到兩個(gè)數(shù)據(jù)之間的歐式距離sortedDistIndicies = distance.argsort() #對(duì)距離進(jìn)行argsort()默認(rèn)的從小到大排列classCount = {} #定義一個(gè)空的字典for i in range(k): #開(kāi)始選擇距離最小的k個(gè)點(diǎn)voteIlabel = labels[sortedDistIndicies[i]] #將原先的標(biāo)簽按照從小到大的順序賦給voteIlabelclassCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1), reverse = True)#sorted()函數(shù)進(jìn)行降序排列,最后返回發(fā)生頻率最高的值return sortedClassCount[0][0] #返回頻率最高的樣本點(diǎn)的標(biāo)簽?使用上面的兩個(gè)模塊我們就可以對(duì)新來(lái)的病人的病情進(jìn)行判定了,假設(shè)新來(lái)的病人的兩項(xiàng)指標(biāo)是(0.2,0.5):
""" Author:XiaoMa date:2021/12/1 """ #導(dǎo)入需要的庫(kù) import numpy as np import matplotlib.pyplot as plt import operator #得到訓(xùn)練樣本集 def createDataSet():group = np.array([[1.0, 1.1], [1.0, 1.0], [0.9, 1], [0.8, 1.1], [1.1, 1], [0.2, 0.13], [0.1, 0.11], [0.1, 0], [0, 0], [0, 0.1]])labels = np.array(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'])return group, labels #返回病人的病情指標(biāo)和病情等級(jí) group, labels = createDataSet()plt.rcParams['font.family'] = 'SimHei' #將全局中文字體改為黑體 plt.rcParams['axes.unicode_minus'] = False #正常表示負(fù)號(hào) A = group[0:5] #A組是樣本集中的前五個(gè) B = group[5:] #B組是樣本集中的后五個(gè) plt.scatter(A[:, 0], A[:, 1], color = 'aquamarine', label = 'A') #將數(shù)組中的前一列作為x軸,后一列作為y軸繪制在散點(diǎn)圖中 plt.scatter(B[:, 0], B[:, 1], color = 'r', label = 'B') plt.legend(loc = 'upper left') #添加圖例 plt.show()#K-近鄰算法 def classify0(inX, dataSet, labels, k): #四個(gè)參數(shù)分別為:用于分類的輸入向量inX,訓(xùn)練樣本集dataSet,標(biāo)簽向量labels,最近鄰居數(shù)目KdataSetSize = dataSet.shape[0] #取得訓(xùn)練樣本數(shù)目diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #將輸入的待分類樣本進(jìn)行x軸方向的四次復(fù)制并減去樣本集,如計(jì)算(x1, y1)和(x2, y2)之間的距離,首先需要計(jì)算(x2-x1, y2-y1)sqDiffMat = diffMat**2 #對(duì)得到的差值進(jìn)行平方sqDistances = sqDiffMat.sum(axis = 1) #求得(x2-x1)^2 + (y2-y1)^2distance = sqDistances**0.5 #對(duì)平方和開(kāi)根號(hào)得到兩個(gè)數(shù)據(jù)之間的歐式距離sortedDistIndicies = distance.argsort() #對(duì)距離進(jìn)行argsort()默認(rèn)的從小到大排列classCount = {} #定義一個(gè)空的字典for i in range(k): #開(kāi)始選擇距離最小的k個(gè)點(diǎn)voteIlabel = labels[sortedDistIndicies[i]] #將原先的標(biāo)簽按照從小到大的順序賦給voteIlabelclassCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1), reverse = True)#sorted()函數(shù)進(jìn)行降序排列,最后返回發(fā)生頻率最高的值return sortedClassCount[0][0] #返回頻率最高的樣本點(diǎn)的標(biāo)簽#對(duì)新來(lái)的病人進(jìn)行判斷 ind = [0.2, 0.5]#病人指標(biāo) grade = classify0(ind, group, labels, 6)#分類器進(jìn)行判斷并返回頻率最高的標(biāo)簽 print('The patient\'s grade of illness is:', grade) #得到結(jié)果?可以得到的輸出結(jié)果為:
我們也可以將該病人的指標(biāo)繪制在散點(diǎn)圖中進(jìn)行判斷:
plt.scatter(0.2, 0.5, color = 'b', label = '待分析病人')#將待分析病人的數(shù)據(jù)繪制到散點(diǎn)圖中。此行代碼添加到前面的得到訓(xùn)練樣本集那里就行可以看出待分析病人離B類較近,所以我們的分類器判斷并沒(méi)有出錯(cuò)。?
三 約會(huì)網(wǎng)站分析
? ? ? ?海倫一直在使用約會(huì)網(wǎng)站尋找中意的約會(huì)對(duì)象,但一直沒(méi)有找到合適的約會(huì)對(duì)象,經(jīng)過(guò)對(duì)自己以往的約會(huì)的分析,她把自己的以前的約會(huì)對(duì)象分為三類人:不喜歡的人、一般魅力的人、極具魅力的人,她還有一些自己收集的數(shù)據(jù),來(lái)記錄那些約會(huì)對(duì)象的各種指標(biāo)。她把這些數(shù)據(jù)都保存在一個(gè)文件中,文件名為datingTsetSet.txt,她的觀察樣本的特征主要包括每年的飛行里程、每周的游戲時(shí)間占比以及每周消耗的冰淇淋公升數(shù)。
首先我們要做的是對(duì)讀取的數(shù)據(jù)文件進(jìn)行分析:
#分析數(shù)據(jù)文件 def file2matrix(filename): #創(chuàng)建分析函數(shù)fr = open(filename)#打開(kāi)數(shù)據(jù)集文件使用array0Lines = fr.readlines()#讀取文件的內(nèi)容number0fLines = len(array0Lines)#得到文件中數(shù)據(jù)的行數(shù)returnMat = np.zeros((number0fLines, 3))#返回的NumPy矩陣,解析完成的數(shù)據(jù):numberOfLines行,3列,兩個(gè)括號(hào),不然格式錯(cuò)誤classLabelVector = []index = 0 #行數(shù)索引for line in array0Lines:line = line.strip()#截取所有回車字符listFromLine = line.split('\t')#將整行數(shù)據(jù)分割成元素列表returnMat[index, :] = listFromLine[0:3]#選取前三列元素存儲(chǔ)到特征矩陣中# 根據(jù)文本中標(biāo)記的喜歡的程度進(jìn)行分類,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 += 1return returnMat, classLabelVector#返回特征矩陣和標(biāo)簽對(duì)數(shù)據(jù)進(jìn)行可視化,繪制到圖中:
#將數(shù)據(jù)兩兩選擇繪制到圖中,并進(jìn)行標(biāo)簽標(biāo)記 datingDataMat, datingLabels = file2matrix('E:\Python\Machine Learning\ML\datingTestSet.txt') #print(datingDataMat)#打印數(shù)據(jù)矩陣numberOfLabels = len(datingLabels) #對(duì)顏色和圖例進(jìn)行設(shè)置 LabelsColors = [] for i in datingLabels:if i == 1:LabelsColors.append('black')if i == 2:LabelsColors.append('orange')if i == 3:LabelsColors.append('red') didntLike = plt.Line2D([], [], color = 'black', marker = '.',markersize = 6, label = 'didntLike')#對(duì)圖例進(jìn)行設(shè)置 smallDoses = plt.Line2D([], [], color = 'orange', marker = '.',markersize = 6, label = 'smallDoses') largeDoses = plt.Line2D([], [], color = 'red', marker = '.',markersize = 6, label = 'largeDoses')a = plt.figure(figsize=(30, 10)) #創(chuàng)建畫(huà)布 plt.subplot(131) plt.scatter(datingDataMat[:, 1], datingDataMat[:, 2], c = LabelsColors)#第二例數(shù)據(jù)(玩游戲時(shí)間占比)和第三列數(shù)據(jù)(消耗的冰淇淋公升數(shù))繪制散點(diǎn)圖 plt.xlabel('玩游戲消耗的時(shí)間占比')#設(shè)置x軸 plt.ylabel('每周消耗的冰激凌升數(shù)')#設(shè)置y軸 plt.legend(handles = [didntLike, smallDoses, largeDoses])#添加圖例 plt.subplot(132) plt.scatter(datingDataMat[:, 0], datingDataMat[:, 1], c = LabelsColors)#第一列數(shù)據(jù)(每年的飛行里程)和第二列數(shù)據(jù)(玩游戲時(shí)間占比)繪制散點(diǎn)圖 plt.xlabel('每年的飛行里程') plt.ylabel('玩游戲消耗的時(shí)間占比') plt.legend(handles = [didntLike, smallDoses, largeDoses]) plt.subplot(133) plt.scatter(datingDataMat[:, 0], datingDataMat[:, 2], c = LabelsColors)#第一列數(shù)據(jù)(每年的飛行里程)和第三列數(shù)據(jù)(消耗的冰淇淋公升數(shù))繪制散點(diǎn)圖 plt.xlabel('每年的飛行里程') plt.ylabel('每周的冰激凌升數(shù)') plt.legend(handles = [didntLike, smallDoses, largeDoses]) plt.savefig('E:\From Zhihu\data.png', dpi = 960)#保存圖像 plt.show()?得到的圖像如下:
?對(duì)數(shù)據(jù)進(jìn)行歸一化:
? ? ? ?我們知道K-近鄰算法是計(jì)算的歐幾里得距離,如果要計(jì)算下表中中的3和4之間的距離,那么每年的飛行里程數(shù)就占了很大的權(quán)值,導(dǎo)致其他兩項(xiàng)的決定權(quán)不大,所以我們要對(duì)其進(jìn)行歸一化:
計(jì)算歐式距離,權(quán)值分配不均一般來(lái)說(shuō),原始數(shù)據(jù)和最小數(shù)據(jù)的差值比上最大數(shù)據(jù)和最小數(shù)據(jù)的差值就是對(duì)數(shù)據(jù)進(jìn)行歸一化:
def autoNorm(dataSet):minVals = dataSet.min(0) #獲得數(shù)據(jù)的最小值maxVals = dataSet.max(0) #獲得數(shù)據(jù)的最大值ranges = maxVals - minVals #最大值和最小值的差值normDataSet = np.zeros(np.shape(dataSet)) #shape(dataSet)返回dataSet的矩陣行數(shù)列數(shù)m = dataSet.shape[0] #返回dataSet的行數(shù)normDataSet = dataSet - np.tile(minVals, (m, 1)) #原始值減去最小值normDataSet = normDataSet / np.tile(ranges, (m, 1)) #除以最大和最小值的差,得到歸一化矩陣return normDataSet, ranges, minVals #返回歸一化后的特征矩陣、數(shù)據(jù)范圍、最小值進(jìn)行打印可得:?
datingDataMat, datingLabels = file2matrix('E:\Python\Machine Learning\ML\datingTestSet.txt') nor, ran, min = autoNorm(datingDataMat) print(nor) print(ran) print(min)構(gòu)建分類器幫忙分別約會(huì)對(duì)象
? ? ? ?使用該分類器海倫可以直接輸入約會(huì)對(duì)象的特征并判定這個(gè)人的魅力(如果不喜歡輸出為:you don't like this person,如果這個(gè)人魅力一般,輸出為:you will like this person,如果這個(gè)人魅力爆表,輸出為:you will be addicted to this person.)
def classifyPerson(): #定義分類器#輸出結(jié)果resultList = ['don\'t like this person', 'will like this person', 'will be addicted to this person']#三種特征用戶輸入precentTats = float(input("玩視頻游戲所耗時(shí)間百分比:"))ffMiles = float(input("每年獲得的飛行常客里程數(shù):"))iceCream = float(input("每周消費(fèi)的冰激淋公升數(shù):"))#打開(kāi)的文件名filename = "datingTestSet.txt"#打開(kāi)并處理數(shù)據(jù)datingDataMat, datingLabels = file2matrix(filename)#訓(xùn)練集歸一化normMat, ranges, minVals = autoNorm(datingDataMat)#生成NumPy數(shù)組,測(cè)試集inArr = np.array([precentTats, ffMiles, iceCream])#測(cè)試集歸一化norminArr = (inArr - minVals) / ranges#返回分類結(jié)果classifierResult = classify0(norminArr, normMat, datingLabels, 3)#打印結(jié)果print("You %s" % (resultList[classifierResult - 1]))classifyPerson()#運(yùn)行分類器?嘗試運(yùn)行并輸出結(jié)果:
可以看出這個(gè)人屬于魅力一般的人,其實(shí)也可以從前面的圖中分類出來(lái),在三幅圖中原點(diǎn)附近的點(diǎn)都是一般魅力的橙色的點(diǎn)。
四 數(shù)據(jù)集以及《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》獲取
有很多博主已經(jīng)提供了大量的資料,大家可以去進(jìn)行獲取,也可以在我的網(wǎng)盤(pán)中獲取:
鏈接:https://pan.baidu.com/s/1zrsXMEQjB18dgaAtXw-KOQ?
提取碼:jmh3
五 結(jié)束語(yǔ)
? ? ? ?本篇文章主要介紹了K-近鄰算法以及實(shí)現(xiàn)方式,并簡(jiǎn)單實(shí)現(xiàn)了約會(huì)網(wǎng)站的分析,書(shū)中還有手寫(xiě)字體的識(shí)別,如果感興趣1也可以去嘗試,有時(shí)間再更。
總結(jié)
以上是生活随笔為你收集整理的机器学习实战(一)—— K-近邻算法(KNN)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python 计算机视觉(十四)—— O
- 下一篇: Python 计算机视觉(十七)—— 基