1. 程式人生 > >資料分析與資料探勘 - 09鄰近演算法

資料分析與資料探勘 - 09鄰近演算法

## 一 鄰近演算法的基本介紹 ### 1 基本說明 鄰近演算法又叫做K臨近演算法或者KNN(K-NearestNeighbor),是機器學習中非常重要的一個演算法,but它簡單得一塌糊塗,其核心思想就是樣本的類別由距離其最近的K個鄰居投票來決定。現在假設我們已經有一個已經標記好的資料集,也就是說我們已經知道了資料集中每個樣本所屬於的類別。這個時候我們擁有一個未標記的資料樣本,我們的任務是預測出來這個資料樣本所屬於的類別。顯然鄰近演算法是屬於監督學習(Supervised Learning)的一種,它的原理是計算這個待標記的資料樣本和資料集中每個樣本的距離,取其距離最近的k個樣本,那麼待標記的資料樣本所屬於的類別,就由這距離最近的k個樣本投票產生。在這個過程中,有一個動作是標記資料集,這一點在企業中一般是有專門人來負責標記資料的。 ### 2 舉例說明 為了更加直觀的瞭解鄰近演算法,請看下面的例子。有兩種水果長得非常像,一個是菠蘿,另一個是鳳梨,很長一段時間我都以為它們是同一種水果。
![WechatIMG78.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601485366001-073aaed0-d041-4e88-b764-8b6ffd9820cc.png#align=left&display=inline&height=890&margin=%5Bobject%20Object%5D&name=WechatIMG78.png&originHeight=890&originWidth=1730&size=481534&status=done&style=none&width=1730#align=left&display=inline&height=890&margin=%5Bobject%20Object%5D&originHeight=890&originWidth=1730&status=done&style=none&width=1730)
菠蘿與鳳梨的核心區別是菠蘿的葉子有刺,而鳳梨的葉子沒有刺。菠蘿的凹槽處的顏色是黃色,而鳳梨的凹槽處的顏色是綠色。首先我們把這兩種水果抽取出其中的兩個特點(凹槽處的顏色、葉子上是否有刺)後,放入一個直角座標系中吧。如下圖:
![WechatIMG79.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601485602864-f3fa262a-6760-4ccc-b36a-4d851e5ec6f6.png#align=left&display=inline&height=938&margin=%5Bobject%20Object%5D&name=WechatIMG79.png&originHeight=938&originWidth=1830&size=248276&status=done&style=none&width=1830#align=left&display=inline&height=938&margin=%5Bobject%20Object%5D&originHeight=938&originWidth=1830&status=done&style=none&width=1830)
我們按照兩種水果的特點,已經把它們放在了直角座標系中,按照我們所說的演算法原理,此時有一個未標記的樣本,我們來預測這個樣本到底是屬於哪種水果。如下圖,這個時候來了一個未標記的樣本,也就是不知道是什麼類別的水果。
![WechatIMG80.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601485814196-60606e0c-99ed-4744-8a27-a1398ae70824.png#align=left&display=inline&height=946&margin=%5Bobject%20Object%5D&name=WechatIMG80.png&originHeight=946&originWidth=1806&size=286702&status=done&style=none&width=1806#align=left&display=inline&height=946&margin=%5Bobject%20Object%5D&originHeight=946&originWidth=1806&status=done&style=none&width=1806)
在原理中,我們說過,由其最近的K個鄰居來投票決定,這個未標記的水果到底是什麼水果,那麼這個時候,我們把K的值設定為3,如下圖:
![WechatIMG81.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601485965236-7cf2906c-273d-4454-b9d5-4ae6dfe7eef3.png#align=left&display=inline&height=944&margin=%5Bobject%20Object%5D&name=WechatIMG81.png&originHeight=944&originWidth=1800&size=294430&status=done&style=none&width=1800#align=left&display=inline&height=944&margin=%5Bobject%20Object%5D&originHeight=944&originWidth=1800&status=done&style=none&width=1800)
從圖片中,我們看到,在K的值為3的時候,與未標記樣本最近的3個鄰居其中2個為菠蘿,而1個為鳳梨,那麼這個時候我們預測這個未知的水果為菠蘿。 ### 3 虛擬碼說明 我們先來看一下如何用虛擬碼來實現這個演算法,這樣我們在後邊的學習中才能更好的寫出來這段程式碼。 - 第一步,我們設x_test為待標記的資料樣本,x_train為已標記的資料集。 - 第二步,遍歷x_train中的所有樣本,計算每個樣本與x_test的距離,並把距離儲存在distance陣列中。 - 第三步,對distance陣列進行排序,取距離最近的k個點,標記為x_knn。 - 第四步,在x_knn中統計每個類別的個數,即class0(類別0)在x_knn中有幾個樣本,class1 (類別1)在x_knn中有幾個樣本。 - 第五步,待標記樣本的類別,就是x_knn中樣本個數最多的那個類別。 ### 4 優缺點分析 - 優點:準確性高,對異常值有較高的容忍度,原因是異常值會單獨分佈在座標系的一個角落,取k個鄰居的時候大概率失去不到這個異常值的。 - 缺點:計算量大,對記憶體的需求也大,因為它每次對一個未標記的樣本進行分類的時候,都需要全部計算一下距離。 - 關鍵點:k值的選取,首先k值一定是奇數,這樣可以確保兩個類別的投票不會一樣,其次,k值越大,模型的偏差越大,對於噪聲資料(錯誤資料或異常資料)越不敏感,k值太小就會造成模型的過擬合。 ## 二 鄰近演算法的程式碼練習 ### 1 準備資料 ```python # 從sklearn庫中的資料集物件裡匯入樣本生成器中的make_blobs方法幫助我們生成資料 from sklearn.datasets.samples_generator import make_blobs # 宣告三個直角座標系中的位置 centers = [[-2, 2], [2, 2], [0, 4]] # 生成資料,其中n_samples是生成資料的個數,centers是中心點,cluster_std是標準差,指明離散程度 x, y = make_blobs(n_samples=60, centers=centers, cluster_std=0.6) # x是生成的資料,y是不同的資料對應所屬的的類別0,1,2 print(x, y) ``` ### 2 用圖形來幫助理解 ```python import matplotlib.pyplot as plt import numpy as np plt.figure(figsize=(16, 10), dpi=144) c = np.array(centers) # x軸,y軸,c顏色 s指定點的大小 plt.scatter(x[:, 0], x[:, 1], c=y, s=100, cmap="cool") # 畫出中心點 plt.scatter(c[:, 0], c[:, 1], s=100, marker="^", c="orange") plt.show() ``` 圖形顯示如下圖所示:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601533951776-9a8cbacf-15de-4c9d-9f10-7a0c2ae3ed62.png#align=left&display=inline&height=529&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1058&originWidth=1492&size=129824&status=done&style=none&width=746)
圖形中顯示的三個三角形的點就是中心點,圍繞在它們周圍的圓點就是我們隨機生成的資料的點。 ### 3 KNN演算法對資料的訓練 ```python # 從sklearn庫中匯入K鄰居分類器:KNeighbosrClassifier from sklearn.neighbors import KNeighborsClassifier # 設定K值 k = 5 # 宣告k臨近分類器物件 clf = KNeighborsClassifier(n_neighbors=k) # 訓練模型 clf.fit(x, y) ``` ### 4 預測樣本資料 ```python # 定義樣本資料 x_sample = [[0, 2]] # 使用模型進預測 neighbors = clf.kneighbors(x_sample, return_distance=False) print(neighbors) # 輸出值:[[23 39 21 47 29]] ``` x_sample變數是我們要進行預測的樣本,然後使用clf.kneighbors方法就可以對這個樣本進行預測了。關於clf.kneighbors的引數return_distance,它決定了是否返回計算後的距離,預設是True,這裡我把它修改成了False,你如果想要看一下值為True是什麼樣子,可以自己手動修改為True。到這裡你可以有一點懵,這怎麼就預測完成了呢?輸出值表示的是什麼意思呢?

輸出值表示的是5個經過計算之後的位於x訓練集中的索引值,它們並不是直接的位置。 ### 5 畫出預測的結果 為了能夠使預測的結果更加直觀,我們還需要用程式碼把他們畫出來。 ```python # 把帶預測的樣本以及和其最近的5個點標記出來 plt.figure(figsize=(8, 5), dpi=144) # dpi是畫素值 plt.scatter(x[:, 0], x[:, 1], c=y, s=100, cmap='cool') # 樣本資料 plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='k') # 中心點 # 帶預測的點 plt.scatter(x_sample[0][0], x_sample[0][1], marker='x', s=100, cmap='cool') # 把預測點與距離最近的5個樣本連成線 for i in neighbors[0]: plt.plot([x[i][0], x_sample[0][0]], [x[i][1], x_sample[0][1]], 'k--', linewidth=0.6) plt.show() ``` 顯示結果如下圖所示:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601559625920-fc5a9f38-75a0-4958-a546-47dc6c567e76.png#align=left&display=inline&height=666&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1332&originWidth=2070&size=183117&status=done&style=none&width=1035) ## 三 花卉識別專案練習 ### 1 先認識三朵花 在這一小節我們將通過一個花卉識別專案的練習來鞏固我們所講的KNN演算法,訓練資料集是非常著名的鳶尾花資料集,涉及到的花的種類一共分為三種:

第一種花是山鳶尾,長下面這個樣子
![image.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601560826570-957c7d44-3be1-4533-9735-a052d171a86c.png#align=left&display=inline&height=263&margin=%5Bobject%20Object%5D&name=image.png&originHeight=526&originWidth=670&size=817547&status=done&style=none&width=335)
第二種花是錦葵,也叫虹膜錦葵
![image.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601560900513-d7b7231e-0d12-43fc-b1a5-36332292d6ca.png#align=left&display=inline&height=300&margin=%5Bobject%20Object%5D&name=image.png&originHeight=600&originWidth=976&size=1463645&status=done&style=none&width=488)
第三種花是變色鳶尾
![image.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601560952406-e14a2b28-67d7-4ebb-8fc9-efa4d2093e18.png#align=left&display=inline&height=445&margin=%5Bobject%20Object%5D&name=image.png&originHeight=890&originWidth=706&size=1826612&status=done&style=none&width=353) ### 2 匯入資料集 我們可以通過sklearn庫的自帶資料集直接引入鳶尾花的資料集,在這個資料集中,我們可以通過花萼長度,花萼寬度,花瓣長度和花瓣寬度四個屬性來預測未標記的鳶尾花屬於哪個類別。 ```python # 1 匯入鳶尾花資料集 from sklearn.datasets import load_iris # 2 宣告一個鳶尾花的類物件 iris = load_iris() # 3 獲取鳶尾花的資料 iris_data = iris.data # 4 獲取資料對應的種類 iris_target = iris.target print(iris_data) print(iris_target) ``` 檢視資料後你會看到iris_data變數裡每一個元素一共有4個值,這四個值就是分別對應花萼長度、花萼寬度、花瓣長度、花瓣寬度4個屬性,iris_target變數對應的就是每一個花所屬的類別。一共對應的是3個類別,0的意思是山鳶尾,1是虹膜錦葵,2是變色鳶尾。 ### 3 訓練模型 ```python from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split # 分割訓練集和測試集 from sklearn.neighbors import KNeighborsClassifier iris = load_iris() iris_data = iris.data iris_target = iris.target # 把資料分為訓練集和測試集,x表示特徵值,y表示目標值,test_size=0.25表示將25%的資料用作測試集 x_train, x_test, y_train, y_test = train_test_split(iris_data, iris_target, test_size=0.25) # 建立KNN演算法例項,n_neighbors引數預設為5,後續可以通過網格搜尋獲取最優引數 knn = KNeighborsClassifier(n_neighbors=5) # 訓練測試集資料 knn.fit(x_train, y_train) # 獲取預測結果 y_predict = knn.predict(x_test) # 展示預測結果 labels = ['山鳶尾', '虹膜錦葵', '變色鳶尾'] for i in range(len(y_predict)): print('第%d次測試:預測值:%s 真實值:%s' %((i + 1), labels[y_predict[i]], labels[y_test[i]])) print('準確率:', knn.score(x_test, y_test)) ``` ### 4 獲取k值最優引數 k值選取的思路是我們先來選擇一個k值的範圍,把這個範圍中所有的誤差值都獲取到,然後我們再來選擇誤差最小的值作為k值。 ```python from sklearn.datasets import load_iris from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsClassifier import matplotlib.pyplot as plt # 中文顯示 plt.rcParams["font.family"] = 'Arial Unicode MS' # 匯入資料集 iris = load_iris() x = iris.data y = iris.target # 限制k的取值範圍 k_range = range(1, 31) # 記錄每當k值變換一次,它的錯誤值是多少 k_error = [] for k in k_range: knn = KNeighborsClassifier(n_neighbors=k) # cv引數決定資料集劃分比例,這裡按照5:1劃分訓練集和測試集 scores = cross_val_score(knn, x, y, cv=6) print(scores) k_error.append(1 / scores.mean()) # 把結果畫成圖,直觀看出k取什麼值誤差最小,x軸為k值,y軸為誤差值 plt.plot(k_range, k_error) plt.xlabel('k值') plt.ylabel('誤差值') plt.show() ``` 圖形顯示結果如下圖所示:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/281865/1601569999884-f80f4acf-319b-417a-9fb1-852b4de9c50f.png#align=left&display=inline&height=444&margin=%5Bobject%20Object%5D&name=image.png&originHeight=888&originWidth=1174&size=93530&status=done&style=none&width=587)
根據這個圖形我們就可以看得出來,k值大概是在12這個位置時,誤差是最小的。這時當我們把12重新放入到之前的程式碼中,可能你會發現他的準確率並沒有提升甚至還有可能下降了,其實是因為資料量比較小的緣故,並不影響我們解決問題的方式。 ## 四 KNeighborsClassifier引數詳解 通過前面的練習,相信你已經基本掌握了KNeighborsClassifier的使用方法了,最後,在這裡我們會對這個方法的引數進行更細緻的說明和講解。 ```python # 檢視KNeighborsClassifier原始碼 NeighborsClassifier( n_neighbors=5, weights="uniform”, algorithm=”auto“, leaf_size=30, p=2, metric="minkowski", metric_params=None, n_jobs=None, **kwargs, ) ``` - weights用於指定臨近樣本的投票權重,預設是uniform,表示所有鄰近樣本投票權重都是一樣的。如果我們把weights的值設定成distance,表示投票權重與距離成反比,也就是說鄰近樣本與未知類別樣本距離越遠,則其權重越小,反之,權重越大。 - algorithm用於指定鄰近樣本的搜尋方法,如果值為ball_tree,表示用球樹搜尋法尋找近鄰樣本,kd_tree就是KD樹搜尋法,brute是使用暴力搜尋法。algorithm預設引數是auto,表示KNN演算法會根據資料特徵自動選擇最佳搜尋方法。關於這些搜尋法的細節,我會在未來發布的機器學習的文章中做詳細的說明,現在只需要知道我們當前用的是預設的自動幫我們選擇的搜尋方法。 - leaf_size用於指定球樹或者KD樹葉子節點所包含的最小樣本量,它用於控制樹的生長條件,會影響查詢速度,預設值是30,目前我們先不關注這個點。 - metric引數是用來指定距離的度量指標,預設為閔可夫斯基距離。 - p引數是依賴metric引數生效的,當metric為minkowski距離時,p=1表示計算點之間的曼哈頓距離,p=2表示計算點之間的歐式距離,預設值為2,計算歐式距離。 - metric_params是一個字典,預設值為空,它為metric引數所對應的距離指標新增關鍵字引數。 - n_jobs設定KNN演算法平行計算時所需的CPU數量,預設值為1,表示僅使用一個CPU執行演算法,也就是不開啟並行運算。
同樣的,最後我們會有一個小的練習,請點選下方連結下載:
[chapter9-1.zip](https://www.yuque.com/attachments/yuque/0/2020/zip/281865/1601572808915-6a64e8ce-e134-47ee-8408-fecea06dd2dc.zip?_lake_card=%7B%22uid%22%3A%221601572808757-0%22%2C%22src%22%3A%22https%3A%2F%2Fwww.yuque.com%2Fattachments%2Fyuque%2F0%2F2020%2Fzip%2F281865%2F1601572808915-6a64e8ce-e134-47ee-8408-fecea06dd2dc.zip%22%2C%22name%22%3A%22chapter9-1.zip%22%2C%22size%22%3A35458%2C%22type%22%3A%22application%2Fzip%22%2C%22ext%22%3A%22zip%22%2C%22progress%22%3A%7B%22percent%22%3A99%7D%2C%22status%22%3A%22done%22%2C%22percent%22%3A0%2C%22id%22%3A%220Y5HN%22%2C%22card%22%3A%22file%22%7D)