机器学习算法系列之K近邻算法
本系列機器學習的文章打算從機器學習算法的一些理論知識、python實現該算法和調一些該算法的相應包來實現。
目錄
K近鄰算法
一、K近鄰算法原理
k近鄰算法
通俗解釋
近鄰距離的度量
k值的選擇
KNN最近鄰分類算法的過程
?總結
二、利用K近鄰算法實現約會網站的匹配預測
三、sklearn實現鳶尾花的種類預測
K近鄰算法
一、K近鄰算法原理
k近鄰算法
k近鄰法(k-nearest neighbor)是一種基于回歸和分類的算法。k近鄰法的輸入為實例的特征向量,對應于特征空間中的點;輸出為實例的類別,可以取多類。
通俗解釋
可以簡單粗暴的認為是:K個最近的鄰居,當K=1時,算法便成了最近鄰算法,即尋找最近的那個鄰居。
用官方的話來說,所謂K近鄰算法,即是給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最鄰近的K個實例(也就是上面所說的K個鄰居),這K個實例的多數屬于某個類,就把該輸入實例分類到這個類中。
?
?
?
如上圖所示,有兩類不同的樣本數據,分別用藍色的小正方形和紅色的小三角形表示,而圖正中間的那個綠色的圓所標示的數據則是待分類的數據。也就是說,現在,我們不知道中間那個綠色的數據是從屬于哪一類(藍色小正方形or紅色小三角形),KNN就是解決這個問題的。
如果K=3,綠色圓點的最近的3個鄰居是2個紅色小三角形和1個藍色小正方形,少數從屬于多數,基于統計的方法,判定綠色的這個待分類點屬于紅色的三角形一類。
如果K=5,綠色圓點的最近的5個鄰居是2個紅色三角形和3個藍色的正方形,還是少數從屬于多數,基于統計的方法,判定綠色的這個待分類點屬于藍色的正方形一類。
?于此我們看到,當無法判定當前待分類點是從屬于已知分類中的哪一類時,我們可以依據統計學的理論看它所處的位置特征,衡量它周圍鄰居的權重,而把它歸為(或分配)到權重更大的那一類。這就是K近鄰算法的核心思想。
?
近鄰距離的度量
K近鄰算法的核心在于找到實例點的鄰居,這個時候,問題就接踵而至了,如何找到鄰居,鄰居的判定標準是什么,用什么來度量。這一系列問題便是下面要講的一些距離度量表示法。
1.歐式距離,最常見的兩點之間的距離表示方法,又稱為歐幾里得度量,
它定義于歐幾里得空間中,如點 x = (x1,...,xn) 和 y = (y1,...,yn) 之間的距離為:
二維平面上兩點a(x1,y1)與b(x2,y2)間的歐氏距離:
三維空間兩點a(x1,y1,z1)與b(x2,y2,z2)間的歐氏距離:
推廣到n維:也可以表示成向量的形式:
?
在k近鄰算法中通常用到的距離度量方視為歐式距離。
2.曼哈頓距離:在歐幾里得空間的固定直角坐標系上兩點所形成的線段對軸產生的投影的距離總和。例如在平面上,坐標(x1, y1)的點P1與坐標(x2, y2)的點P2的曼哈頓距離為: ,要注意的是,曼哈頓距離依賴座標系統的轉度,而非系統在座標軸上的平移或映射。
二維平面兩點a(x1,y1)與b(x2,y2)間的曼哈頓距離:
兩個n維向量a(x11,x12,…,x1n)與 b(x21,x22,…,x2n)間的曼哈頓距離:
?
3.切比雪夫距離:
在平面幾何中,若二點p及q的直角坐標系坐標為(x1,y1)及(x2,y2),則切比雪夫距離為:
兩個n維向量a(x11,x12,…,x1n)與 b(x21,x22,…,x2n)間的切比雪夫距離:
這里就介紹這三種度量巨距離的方法,其他度量距離的方法還有閔可夫斯基距離、標準化歐式距離、馬氏距離等。
k值的選擇
在上面那幅圖中我們可以看到,當k值選擇為3時,帶歸類的那個樣本就被歸類為紅色三角形一類;當k值選擇為5時就被歸類為藍色正方形類。所以不同的k值對模型的歸類影響也會不一樣,這里需要選擇一個最恰當的k值使得模型能夠更好的預測未知樣本。
一般而言,如果選擇較小的k值,就相當于用較小的領域中的訓練實例進行預測,“學習”近似誤差會減小,只有與輸入實例較近或者相似的訓練實例才會對預測結果起作用,與此同時帶來的問題是“學習”的誤差估計會增大,換句話說:k值的減小就意味著整體模型會變得復雜,容易發生過擬合。
如果選擇較大的k值,就相當于用較大的領域中的訓練實例進行預測,其優點是可以減少學習的估計誤差,但缺點是學習的近似誤差會變大。這時候,與輸入實例較遠(不相似)的訓練實例也會起預測作用,使預測發生錯誤,且k值的增大也意味著整體的模型變得簡單。例如,當k=n時,無論輸入什么實例,其預測結果都會是訓練實例中最多的那一類了。完全失去了預測作用。
在實際應用中,K值一般取一個比較小的數值,例如采用交叉驗證法(簡單來說,就是一部分樣本做訓練集,一部分做測試集)來選擇最優的K值。
?
KNN最近鄰分類算法的過程
K近鄰法的實現:kd樹
實現k近鄰法時,主要考慮的問題是如何對訓練數據進行快速k近鄰搜索
Kd-樹是K-dimension tree的縮寫,是對數據點在k維空間(如二維(x,y),三維(x,y,z),k維(x1,y,z..))中劃分的一種數據結構,主要應用于多維空間關鍵數據的搜索(如:范圍搜索和最近鄰搜索)。。
首先必須搞清楚的是,k-d樹是一種空間劃分樹,說白了,就是把整個空間劃分為特定的幾個部分,然后在特定空間的部分內進行相關搜索操作。想像一個三維空間,kd樹按照一定的劃分規則把這個三維空間劃分了多個空間,如下圖所示:
?kd樹算法構建流程:
舉例:
6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}構建kd樹的具體步驟為:
與此同時,經過對上面所示的空間劃分之后,我們可以看出,點(7,2)可以為根結點,從根結點出發的兩條紅粗斜線指向的(5,4)和(9,6)則為根結點的左右子結點,而(2,3),(4,7)則為(5,4)的左右孩子(通過兩條細紅斜線相連),最后,(8,1)為(9,6)的左孩子(通過細紅斜線相連)。如此,便形成了下面這樣一棵k-d樹:
?
?總結
1、k近鄰法三要素:距離度量、k值的選擇和分類決策規則.常用的距離度量是歐氏距離及更一般的Lp距離. k值小時,k近鄰模型更復雜; k值大時,k近鄰模型更簡單.k值的選擇反映了對近似誤差與估計誤差之間的權衡,通常由交叉驗證選擇最優的k.常用的分類決策規則是多數表決,對應于經驗風險最小化。
2、k近鄰模型對應于基于訓練數據集對特征空間的-一個劃分,k近鄰法中,當訓練集、距離度量、k值及分類決策規則確定后,其結果唯一-確定。
3、k近鄰法的實現需要考慮如何快速搜索k個最近鄰點,kd樹是一種便于對k維空間中的數據進行快速檢索的數據結構。kd 樹是二叉樹,表示對k維空間的一個劃分,其每個結點對應于k維空間劃分中的一個超矩形區域。利用kd樹可以省去對大部分數據點的搜索,從而減少搜索的計算量。
二、利用K近鄰算法實現約會網站的匹配預測
背景:一個經常使用約會網站尋找適合自己的對象的海倫,但是依舊沒有從中找到自己喜歡的人。經過一番總結,她發現自己曾經交往過三種類型的人:不喜歡的人、魅力一般的人、極具魅力的人。此外海倫還收集了一些約會網站未曾記錄的數據信息。
數據信息:第一列:每年獲得的飛行常客里程數;第二列:玩游戲耗時的百分比;第三列:每周消耗的奶茶升數。
第四列:喜歡程度(didntLike、smallDoses、largeDoses)
飛行常客里程數:飛行常客計劃(也稱:飛行常客獎勵計劃)是航空公司給忠實乘客的一種獎勵,普遍的形式是:乘客們通過這個計劃累計自己的飛行里程,并使用這些里程來兌換免費的機票、商品和服務以及其他類似貴賓休息室或優先值機之類的特權。
40920 8.326976 0.953952 largeDoses 14488 7.153469 1.673904 smallDoses 26052 1.441871 0.805124 didntLike 75136 13.147394 0.428964 didntLike 38344 1.669788 0.134296 didntLike 72993 10.141740 1.032955 didntLike 35948 6.830792 1.213192 largeDoses 42666 13.276369 0.543880 largeDoses 67497 8.631577 0.749278 didntLike 35483 12.273169 1.508053 largeDoses 50242 3.723498 0.831917 didntLike 63275 8.385879 1.669485 didntLike 5569 4.875435 0.728658 smallDoses 51052 4.680098 0.625224 didntLike 77372 15.299570 0.331351 didntLike 43673 1.889461 0.191283 didntLike 61364 7.516754 1.269164 didntLike 69673 14.239195 0.261333 didntLike 15669 0.000000 1.250185 smallDoses 28488 10.528555 1.304844 largeDoses 6487 3.540265 0.822483 smallDoses代碼:
# -*- coding: utf-8 -*-from matplotlib.font_manager import FontProperties import matplotlib.lines as mlines import matplotlib.pyplot as plt import time import numpy as np import operator""" 函數說明:kNN算法,分類器Parameters:inX - 用于分類的數據(測試集)dataSet - 用于訓練的數據(訓練集)(n*1維列向量)labels - 分類標準(n*1維列向量)k - kNN算法參數,選擇距離最小的k個點Returns:sortedClassCount[0][0] - 分類結果"""def classify0(inX, dataSet, labels, k):# numpy函數shape[0]返回dataSet的行數dataSetSize = dataSet.shape[0]# 將inX重復dataSetSize次并排成一列diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet# 二維特征相減后平方(用diffMat的轉置乘diffMat)sqDiffMat = diffMat ** 2# sum()所有元素相加,sum(0)列相加,sum(1)行相加sqDistances = sqDiffMat.sum(axis=1)# 開方,計算出距離distances = sqDistances ** 0.5# argsort函數返回的是distances值從小到大的--索引值sortedDistIndicies = distances.argsort()# 定義一個記錄類別次數的字典classCount = {}# 選擇距離最小的k個點for i in range(k):# 取出前k個元素的類別voteIlabel = labels[sortedDistIndicies[i]]# 字典的get()方法,返回指定鍵的值,如果值不在字典中返回0# 計算類別次數classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1# python3中用items()替換python2中的iteritems()# key = operator.itemgetter(1)根據字典的值進行排序# key = operator.itemgetter(0)根據字典的鍵進行排序# reverse降序排序字典sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True)# 返回次數最多的類別,即所要分類的類別return sortedClassCount[0][0]""" 函數說明:打開解析文件,對數據進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力Parameters:filename - 文件名Returns:returnMat - 特征矩陣classLabelVector - 分類label向量"""def file2matrix(filename):# 打開文件fr = open(filename)# 讀取文件所有內容arrayOlines = fr.readlines()# 得到文件行數numberOfLines = len(arrayOlines)# 返回的NumPy矩陣numberOfLines行,3列returnMat = np.zeros((numberOfLines, 3))# 創建分類標簽向量classLabelVector = []# 行的索引值index = 0# 讀取每一行for line in arrayOlines:# 去掉每一行首尾的空白符,例如'\n','\r','\t',' 'line = line.strip()# 將每一行內容根據'\t'符進行切片,本例中一共有4列listFromLine = line.split('\t')# 將數據的前3列進行提取保存在returnMat矩陣中,也就是特征矩陣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 += 1# 返回標簽列向量以及特征矩陣return returnMat, classLabelVector""" 函數說明:可視化數據Parameters:datingDataMat - 特征矩陣datingLabels - 分類LabelReturns:None"""def showdatas(datingDataMat, datingLabels):# 設置漢字格式為14號簡體字font = FontProperties(fname=r"C:\Windows\Fonts\simsun.ttc", size=14)# 將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8)# 當nrows=2,ncols=2時,代表fig畫布被分為4個區域,axs[0][0]代表第一行第一個區域fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))# 獲取datingLabels的行數作為label的個數# numberOfLabels = len(datingLabels)# label的顏色配置矩陣LabelsColors = []for i in datingLabels:# didntLikeif i == 1:LabelsColors.append('black')# smallDosesif i == 2:LabelsColors.append('orange')# largeDosesif i == 3:LabelsColors.append('red')# 畫出散點圖,以datingDataMat矩陣第一列為x,第二列為y,散點大小為15, 透明度為0.5axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)# 設置標題,x軸label, y軸labelaxs0_title_text = axs[0][0].set_title(u'每年獲得的飛行常客里程數與玩視頻游戲所消耗時間占比', FontProperties=font)axs0_xlabel_text = axs[0][0].set_xlabel(u'每年獲得的飛行常客里程數', FontProperties=font)axs0_ylabel_text = axs[0][0].set_ylabel(u'玩視頻游戲所消耗時間占比', FontProperties=font)plt.setp(axs0_title_text, size=9, weight='bold', color='red')plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')# 畫出散點圖,以datingDataMat矩陣第一列為x,第三列為y,散點大小為15, 透明度為0.5axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)# 設置標題,x軸label, y軸labelaxs1_title_text = axs[0][1].set_title(u'每年獲得的飛行常客里程數與每周消費的冰淇淋公升數', FontProperties=font)axs1_xlabel_text = axs[0][1].set_xlabel(u'每年獲得的飛行常客里程數', FontProperties=font)axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消費的冰淇淋公升數', FontProperties=font)plt.setp(axs1_title_text, size=9, weight='bold', color='red')plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')# 畫出散點圖,以datingDataMat矩陣第二列為x,第三列為y,散點大小為15, 透明度為0.5axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)# 設置標題,x軸label, y軸labelaxs2_title_text = axs[1][0].set_title(u'玩視頻游戲所消耗時間占比與每周消費的冰淇淋公升數', FontProperties=font)axs2_xlabel_text = axs[1][0].set_xlabel(u'玩視頻游戲所消耗時間占比', FontProperties=font)axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消費的冰淇淋公升數', FontProperties=font)plt.setp(axs2_title_text, size=9, weight='bold', color='red')plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')# 設置圖例didntLike = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='didntLike')smallDoses = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='smallDoses')largeDoses = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='largeDoses')# 添加圖例axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])# 顯示圖片plt.show()""" 函數說明:對數據進行歸一化Parameters:dataSet - 特征矩陣Returns:normDataSet - 歸一化后的特征矩陣ranges - 數據范圍minVals - 數據最小值"""def autoNorm(dataSet):# 獲取數據的最小值minVals = dataSet.min(0)# 獲取數據的最大值maxVals = dataSet.max(0)# 最大值和最小值的范圍ranges = maxVals - minVals# shape(dataSet)返回dataSet的矩陣行列數normDataSet = np.zeros(np.shape(dataSet))# numpy函數shape[0]返回dataSet的行數m = dataSet.shape[0]# 原始值減去最小值(x-xmin)normDataSet = dataSet - np.tile(minVals, (m, 1))# 差值處以最大值和最小值的差值(x-xmin)/(xmax-xmin)normDataSet = normDataSet / np.tile(ranges, (m, 1))# 歸一化數據結果,數據范圍,最小值return normDataSet, ranges, minVals""" 函數說明:分類器測試函數Parameters:NoneReturns:normDataSet - 歸一化后的特征矩陣ranges - 數據范圍minVals - 數據最小值"""def datingClassTest():# 打開文件名filename = "datingTestSet.txt"# 將返回的特征矩陣和分類向量分別存儲到datingDataMat和datingLabels中datingDataMat, datingLabels = file2matrix(filename)# 取所有數據的10% hoRatio越小,錯誤率越低hoRatio = 0.10# 數據歸一化,返回歸一化數據結果,數據范圍,最小值normMat, ranges, minVals = autoNorm(datingDataMat)# 獲取normMat的行數m = normMat.shape[0]# 10%的測試數據的個數numTestVecs = int(m * hoRatio)# 分類錯誤計數errorCount = 0.0for i in range(numTestVecs):# 前numTestVecs個數據作為測試集,后m-numTestVecs個數據作為訓練集# k選擇label數+1(結果比較好)classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], \datingLabels[numTestVecs:m], 4)print("分類結果:%d\t真實類別:%d" % (classifierResult, datingLabels[i]))if classifierResult != datingLabels[i]:errorCount += 1.0print("錯誤率:%f%%" % (errorCount / float(numTestVecs) * 100))""" 函數說明:通過輸入一個人的三圍特征,進行分類輸出Parameters:NoneReturns:None"""def classifyPerson():# 輸出結果resultList = ['不喜歡', '有些喜歡', '非常喜歡']# 三維特征用戶輸入income = float(input("每年獲得的飛行常客里程數:"))percentTats = float(input("玩視頻游戲所消耗時間百分比:"))milkTea = float(input("每周消費的奶茶公升數:"))# 打開的文件名filename = "datingTestSet.txt"# 打開并處理數據datingDataMat, datingLabels = file2matrix(filename)# 訓練集歸一化normMat, ranges, minVals = autoNorm(datingDataMat)# 生成NumPy數組,測試集inArr = np.array([income,percentTats , milkTea])# 測試集歸一化norminArr = (inArr - minVals) / ranges# 返回分類結果classifierResult = classify0(norminArr, normMat, datingLabels, 4)# 打印結果print("你可能%s這個人" % (resultList[classifierResult - 1]))""" 函數說明:main函數Parameters:NoneReturns:None"""def main():# 獲取程序運行時間start = time.clock()# 打開文件的名稱filename = "datingTestSet.txt"# 打開并處理數據datingDataMat, datingLabels = file2matrix(filename)# 訓練集歸一化normDataset, ranges, minVals = autoNorm(datingDataMat)datingClassTest()# print(normDataset)# print(ranges)# print(minVals)showdatas(datingDataMat, datingLabels)classifyPerson()# print(datingDataMat)# print(datingLabels)# 創建數據集# group, labels = createDataSet()# 測試集# test = [100,100]# kNN分類# test_class = classify0(test, group, labels, 3)# 打印分類結果# print(test_class)end = time.clock()# 打印程序運行時間print('Running time: %f Seconds' % (end - start))if __name__ == '__main__':main()程序運行結果:
可以看到當預測出這個對象可能海倫將會非常喜歡(畢竟條件確實比較好,hhhh)?
三、sklearn實現鳶尾花的種類預測
?
數據集介紹
在Sklearn機器學習包中,集成了各種各樣的數據集,包括前面的糖尿病數據集,這里引入的是鳶尾花卉(Iris)數據集,它是很常用的一個數據集。鳶尾花有三個亞屬,分別是山鳶尾(Iris-setosa)、變色鳶尾(Iris-versicolor)和維吉尼亞鳶尾(Iris-virginica)。
該數據集一共包含4個特征變量,1個類別變量。共有150個樣本,iris是鳶尾植物,這里存儲了其萼片和花瓣的長寬,共4個屬性,鳶尾植物分三類。如表所示:
#!/usr/bin/env python # -*- coding:utf-8 -*- from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split import pandas as pd import numpy as np from pandas.plotting import scatter_matrix import mglearn import matplotlib.pyplot as plt from sklearn.neighbors import KNeighborsClassifier iris_dataset = load_iris() X_train, X_test, y_train, y_test = train_test_split(iris_dataset['data'], iris_dataset['target'], random_state=0) iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset.feature_names) # 利用DataFrame創建散點圖矩陣,按照y_train著色 grr = scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3) plt.show() knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) X_new = np.array([[5, 2.9, 1, 0.2]]) print("X_new: {}".format(X_new)) prediction = knn.predict(X_new) print("Prediction: {}".format(prediction)) print("Predicted target name: {}".format(iris_dataset['target_names'][prediction])) # 模型評價 print("Test set score: {:.2f}".format(knn.score(X_test, y_test))) 輸出結果 X_new: [[5. 2.9 1. 0.2]] Prediction: [0] Predicted target name: ['setosa'] Test set score: 0.97可以看出預測的花的結果為'setosa'(山鳶尾),且在測試集上的準確率為97%。
?
總結
以上是生活随笔為你收集整理的机器学习算法系列之K近邻算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: foxit pdf editor lin
- 下一篇: PPT插入计时器