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