KNN近鄰算法 詳解

前言

通過本文,你將瞭解並深刻理解什麼是 KNN算法。
當然,閱讀本文前,你最好會點python,這樣閱讀起來纔會沒有障礙噢

春節後的第一篇文章,在這裏祝大家新的一年工作順心!心想事成!新年又有新高度!

什麼是 KNN近鄰算法?

通常我們都知道這麼一句話 “近朱者赤近墨者黑” ,KNN算法就是這句話的完美詮釋了。

我們想要判斷某個東西屬於哪個分類,那麼我們只需要找到最接近該東西的 K 個鄰居,這些鄰居中哪種分類佔比最大,那麼我們就認爲該東西就屬於這個分類!

KNN近鄰算法 實踐

這裏我們會使用到 sklearnnumpy 兩個庫,當然就算你不熟悉也沒關係,這裏主要就是爲了直觀的感受一下 KNN 算法

  • 導入數據
import numpy as np
import matplotlib 
import matplotlib.pyplot as plt
from sklearn import datasets

#這裏我們採集的是 sklearn 包裏面自帶的 鳶尾花 的數據
digits = datasets.load_iris()

# 鳶尾花的種類 用 0,1,2 標識
y = digits.target

# 鳶尾花的 特徵,爲了可視化的需求,我們這裏只取前兩個特徵
x = digits.data[:,:2]

# 在2d平面上畫出鳶尾花的分佈情況
#爲了方便顯示,我們這裏只取標識爲 0 和 1 兩種鳶尾花的數據
plt.scatter(x[y==0,0],x[y==0,1],color='r')
plt.scatter(x[y==1,0],x[y==1,1],color='b')
plt.show()
  • 拆分數據
# 將數據拆分爲 測試數據 和 訓練數據
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2)
# 顯示如下圖
plt.scatter(x_train[y_train==0,0],x_train[y_train==0,1],color='r')
plt.scatter(x_train[y_train==1,0],x_train[y_train==1,1],color='b')

# 測試數據我們用 黃色顯示
plt.scatter(x_test[y_test==0,0],x_test[y_test==0,1],color='y')
plt.scatter(x_test[y_test==1,0],x_test[y_test==1,1],color='y')

plt.show()

  • 預測 和 校準 數據
from sklearn.neighbors import KNeighborsClassifier

# 使用 sklearn knn算法模型進行訓練和預測
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(x_train,y_train)
y_predict = knn.predict(x_test)

# 真實數據分佈情況
plt.scatter(x[y==0,0],x[y==0,1],color='r')
plt.scatter(x[y==1,0],x[y==1,1],color='b')
plt.show()

# 預測數據分佈情況
plt.scatter(x_train[y_train==0,0],x_train[y_train==0,1],color='r')
plt.scatter(x_train[y_train==1,0],x_train[y_train==1,1],color='b')

plt.scatter(x_test[y_predict==0,0],x_test[y_predict==0,1],color='r')
plt.scatter(x_test[y_predict==1,0],x_test[y_predict==1,1],color='b')

plt.show()


從圖中我們很明顯的看到 左下角的一個點預測錯誤,其餘都正確 ,這裏我們很直觀的就可以感受到 KNN 算法的整個流程,其中最關鍵的還是在 預測數據那塊,那麼接下來我們就來剖析下 KNN 的原理吧

KNN算法 手寫實現

  • 思路
    首先我們理一下,knn的幾個關鍵因素:
    ① neighbors,我們該選取幾個鄰居作爲目標分類的依據。
    ② 和鄰居之間的距離怎麼計算

    1. neighbors 很簡單,我們讓調用者自己傳入就好了
    2. 和鄰居之間的距離,我們採用簡單的 歐拉距離 公式計算, 如下:
      distance = \sqrt{ \sum_{i=1}^n (X_i^a-X_i^b)^2}

當然,真正要寫好 KNN算法 肯定不是我們考慮的這麼簡單,但是主要思路是這樣,所以我們根據這個思路先來把簡單的 KNN 實現一下吧。

  • 實現
    有了上面的思路,我們直接來看代碼吧!

from math import sqrt
from collections import Counter

class MyKNN:
    # 初始化
    def __init__(self,n_neighbors=5):
        self.n_neighbors = n_neighbors
        self.X = None
        self.Y = None

    def fit(self,x,y):
        """
        KNN 算是一個比較特殊的算法,其實它是沒有一個訓練過程的,
        這裏簡單的將訓練數據保存起來就好了  
        """
        self.X = x
        self.Y = y
    
    def _predict(self,x):
        """
        預測單個樣本的所屬分類
        """
        # 歐拉距離的計算
        distances = [sqrt(np.sum((i-x)**2)) for i in self.X]
        # 排序
        sort_distances_index = np.argsort(distances)
        # 找出最近的 n_neighbors 個鄰居
        neighbors_index = sort_distances_index[:self.n_neighbors]
        neighbors = self.Y[neighbors_index]
        # 返回最近鄰居中分類佔比最大的那個分類標識
        return Counter(neighbors).most_common(1)[0][0]
    
    def predict(self, X_predict):
        """
        預測多個樣本的分類
        """
        # 通過單個樣本分類直接 預測就 ok了
        y_predict = [self._predict(x) for x in X_predict]
        
        return np.array(y_predict)

上面這個代碼應該是相當簡單了,如果你有興趣,可以把 KNN近鄰算法 實踐 這一節的 預測代碼用我們手寫的跑一遍,這裏就不重複了,實現的效果大同小異,但是從上面的代碼我們也可以看出來,咱們是採用遍歷的方式來求所有距離的,如果你和 sklearn 中的算法做下對比,會發現我們寫的運行效率,那真不是一個速度級的。

KNN 調參

實踐了,手寫了,不知道現在你對knn是不是有了一個比較深入的瞭解,嗯,只想說一句,不愧是最簡單的算法之一,是真的很簡單,完全沒有什麼高深的東西嘛。。。話雖如此,但是如果你覺得這樣就可以用好knn那就有點太想當然了。

這裏我們就引入了 KNN的調參,其實在機器學習過程中,調參那也是很大一部分的工作內容了。

  • 超參數 K
    這個k就是我們上面的選取的鄰居的個數,選取數目的不一樣,對於我們預測的結果肯定也是有差異的,那麼下面我們來尋找一下最優的 K 把
    1. 首先我們得有一個評價標準,這裏我們簡單的就用正確率來評價這個 k 的好壞把,當然在實際工作中肯定不能是這麼簡單了.
    def score(y_predict,y_true):
      """
      傳入 預測的 y  和 真實的 y ,判斷一下他們的正確率
      """
      return np.sum(y_predict == y_true)/len(y_true)
    
    1. 尋找最佳的 K 值
    best_score = 0
    best_k = 0
    
    # 循環 10 以內的 k值進行預測,並且求出最佳的 k 值
    for i in range(1,10):
        knn = MyKNN(n_neighbors=i)
        knn.fit(x_train,y_train)
        y_predict = knn.predict(x_test)
    
        s = score(y_predict,y_test)
      
        if(s>best_score):
            best_score = s
            best_k = i
    
    print("best_score = ",best_score)
    print("best_k = ",best_k)
    
    
  • 和距離有關的超參數
    1. 權重
      什麼是權重呢?舉個簡單的栗子,我有三個朋ABC,其中A喜歡吃蘋果,BC喜歡吃梨,我和A的關係非常好,和BC只是普通朋友,那麼我是喜歡吃蘋果還是梨呢?如果不考慮權重,我肯定是喜歡吃梨,因爲我的三個朋友有兩個喜歡吃梨,但是如果考慮權重的話,就不一定了,鑑於我和A的關係非常好,那麼我喜歡吃蘋果的概率可能更大。

      通過上面的栗子,我們應該知道,關於距離的遠近對於預測的結果應該是可以考慮不同權重的,距離越近,那麼權重越高,我們上面寫的算法那肯定是沒有考慮,不管遠近權重都是1。但是在 sklearn 中你是可以找到 weight 這個超參數的

    2. 距離的計算方式
      上面我們採用的是歐拉距離作爲距離的計算方式,實際上,在 sklearn 中,採用的是另外一種方式,叫做明可夫斯基距離,公式如下:
      distance = ( \sum_{i=1}^n |X_i^a-X_i^b|^p)^\frac{1}{p}

      其實仔細觀察我們會發現,當 P = 1/2 就是我們用到的 歐拉距離 了,所以這裏我們又得到一個可以調節的超參數 P,在sklearn 中你是可以找到 P 這個超參數的

      當然還有很多距離的計算方式,比如:餘弦相似度,皮爾森相似度等

這一小節我們主要介紹了另外兩個超參數,我們其實可以想一下,當有多個超參數需要調節,我們還採用上面的那個循環遍歷一個個去試的話,可能就會有大問題,因爲存在的可能性太多了,會導致尋找非常困難,所以這裏我們需要另外一個叫做 網格搜索 的方式來尋找,這裏就不介紹了,感興趣的可以自行搜索一下噢。

KNN是否可以用於迴歸算法?

前面我們說了,KNN算法是一個分類算法,但事實上其同樣可以用來處理迴歸問題,思路也很簡單,找到相應的鄰居,然後根據鄰居的打分來求自己的打分,將分類問題就轉換成了迴歸問題了。

最後,我們在總結下 KNN 的優缺點

  • 優點
    簡單,並且效果還不錯
    天然適合多分類問題

  • 缺點
    效率低, 樣本越多,維度越多,其執行時間複雜度呈線性增長
    高度數據相關性
    結果不具有可解釋性

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章