1. 程式人生 > >機器學習-KNN演算法

機器學習-KNN演算法

一、演算法介紹

  KNN演算法中文名稱叫做K近鄰演算法,是眾多機器學習演算法裡面最基礎入門的演算法。它是一個有監督的機器學習演算法,既可以用來做分類任務也可以用來做迴歸任務。KNN演算法的核心思想是未標記的樣本的類別,由距離他最近的K個鄰居投票來決定。下面我們來看個例子加深理解一下:

  如上圖所描述張三要參加一家公司的面試,他通過各種渠道瞭解到了一些工作年限和工資之間對應的關係以及在這種條件下他們是否獲取到了offer的情況。讓我們來預測一下張三是否能夠拿到他這家公司的offer吧?當K-近鄰中的K選擇為1的時候我們看下結果。張三不可以拿到offer。

  當我們選擇K的值為3的時候,張三拿到了offer。

  同理當我們選擇K的值為5的時候呢?張三被分類到了拿不到offer的類別上了。

  看了上面的例子是不是感覺KNN演算法超級的easy。下面我們來稍微深入瞭解一下演算法的實現步驟。

二、演算法的實現步驟

  假設X_test為待標記的樣本,X_train為已標記的樣本資料集:

  1、遍歷X_train中的所有樣本,計算每個樣本與X_test的之間的距離(一般為歐式距離)。並且把距離儲存在一個distince 的陣列中。

  2、對distince陣列進行排序,取距離最近的K個點。記作X_knn。

  3、在X_knn中統計每個類別的個數,既class_0在X_knn中有幾個樣本,class_1在X_knn中有幾個樣本等。

  4、待標記樣本的類別就是X_knn中樣本個數最多的那個類別。

  好了,說完了虛擬碼以後,我們嘗試手寫一個KNN的演算法吧。

from sklearn import datasets
from collections import Counter  # 為了做投票
from sklearn.model_selection import train_test_split
import numpy as np

# 匯入iris資料
iris = datasets.load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=20)

def euc_dis(instance1, instance2):
    """
    計算兩個樣本instance1和instance2之間的歐式距離
    instance1: 第一個樣本, array型
    instance2: 第二個樣本, array型
    """
    dist = np.sqrt(sum((instance1 - instance2)**2))
    return dist
    
 
def knn_classify(X, y, testInstance, k):
    """
    給定一個測試資料testInstance, 通過KNN演算法來預測它的標籤。 
    X: 訓練資料的特徵
    y: 訓練資料的標籤
    testInstance: 測試資料,這裡假定一個測試資料 array型
    k: 選擇多少個neighbors? 
    """
    # TODO  返回testInstance的預測標籤 = {0,1,2}
    distances = [euc_dis(x, testInstance) for x in X]
    kneighbors = np.argsort(distances)[:k]
    count = Counter(y[kneighbors])
    return count.most_common()[0][0]

# 預測結果。    
predictions = [knn_classify(X_train, y_train, data, 3) for data in X_test]
correct = np.count_nonzero((predictions==y_test)==True)
#accuracy_score(y_test, clf.predict(X_test))
print ("Accuracy is: %.3f" %(correct/len(X_test)))

  看完了程式碼的實現以後。我們來思考一下演算法的時間複雜度是多少呢?很明顯KNN演算法的時間複雜度為O(D*N)。其中D為維度數,N為樣本總數。從時間複雜度上我們可以很清楚的就知道KNN非常不適合高維度的資料集,容易發生維度爆炸的情況。同時我們也發現了一個問題在關於K的選擇上面,我們一般也要選擇K的值應該儘量選擇為奇數,並且不要是分類結果的偶數倍,否則會出現同票的情況。那麼說到這裡,關於K的選擇?我們到底應該怎麼去選擇K的大小比較合適呢?答案是交叉驗證。交叉驗證指的是將訓練資料集進一步分成訓練資料和驗證資料,選擇在驗證資料裡面最好的超引數組合。交叉驗證或者通俗一點的說法就是說調參。我們不是經常說機器學習或者深度學習工程師為調參工程師嘛。哈哈。調參。引數一般分為模型引數和超級引數。模型引數是需要我們通過不斷的調整模型和超引數訓練得到的最佳引數。而超引數則是我們人為手動設定的值。像在KNN中超引數就是K的值。我們可以通過交叉驗證的方式,選擇一組最好的K值作為模型最終的K值。下圖是五折交叉驗證:

三、實戰KNN演算法

  這裡我們使用KNN演算法來做最普通的水仙花分類。下面請看程式碼。超級簡單。

# 讀取相應的庫
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

# 讀取資料 X, y
iris = datasets.load_iris()
X = iris.data
y = iris.target

# 把資料分成訓練資料和測試資料
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=20)

# 構建KNN模型, K值為3、 並做訓練
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)

# 計算準確率
from sklearn.metrics import accuracy_score
correct = np.count_nonzero((clf.predict(X_test)==y_test)==True)
#accuracy_score(y_test, clf.predict(X_test))
print ("Accuracy is: %.3f" %(correct/len(X_test)))
結果也是達到了 Accuracy is: 0.921
上面說完了KNN用作分類演算法以後,我們來看一下KNN演算法在做迴歸演算法的時候的表現。KNN用於做迴歸演算法的原理是挑選最近的K個點的值,然後計算這K個點的均值作為迴歸預測值。下面我們用實戰演示一下回歸演算法。
下面是利用KNN的迴歸演算法來做二手車的價格迴歸預測的一個小需求例子。
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

#讀取資料
df = pd.read_csv('data.csv')
df  # data frame

#清洗資料
# 把顏色獨熱編碼
df_colors = df['Color'].str.get_dummies().add_prefix('Color: ')
# 把型別獨熱編碼
df_type = df['Type'].apply(str).str.get_dummies().add_prefix('Type: ')
# 新增獨熱編碼資料列
df = pd.concat([df, df_colors, df_type], axis=1)
# 去除獨熱編碼對應的原始列
df = df.drop(['Brand', 'Type', 'Color'], axis=1)

df

from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.preprocessing import StandardScaler
import numpy as np

X = df[['Construction Year', 'Days Until MOT', 'Odometer']]
y = df['Ask Price'].values.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=41)

X_normalizer = StandardScaler() # N(0,1)
X_train = X_normalizer.fit_transform(X_train)
X_test = X_normalizer.transform(X_test)

y_normalizer = StandardScaler()
y_train = y_normalizer.fit_transform(y_train)
y_test = y_normalizer.transform(y_test)

knn = KNeighborsRegressor(n_neighbors=2)
knn.fit(X_train, y_train.ravel())

#Now we can predict prices:
y_pred = knn.predict(X_test)
y_pred_inv = y_normalizer.inverse_transform(y_pred)
y_test_inv = y_normalizer.inverse_transform(y_test)

# Build a plot
plt.scatter(y_pred_inv, y_test_inv)
plt.xlabel('Prediction')
plt.ylabel('Real value')

# Now add the perfect prediction line
diagonal = np.linspace(500, 1500, 100)
plt.plot(diagonal, diagonal, '-r')
plt.xlabel('Predicted ask price')
plt.ylabel('Ask price')
plt.show()

print(y_pred_inv)

四、KNN需要注意的幾個問題

  1、大數吞小數

  在進行距離計算的時候,有時候某個特徵的數值會特別的大,那麼計算歐式距離的時候,其他的特徵的值的影響就會非常的小被大數給覆蓋掉了。所以我們很有必要進行特徵的標準化或者叫做特徵的歸一化。

  2、如何處理大資料量

  一旦特徵或者樣本的數目特別的多,KNN的時間複雜度將會非常的高。解決方法是利用KD-Tree這種方式解決時間複雜度的問題,利用KD樹可以將時間複雜度降到O(logDN)。D是維度數,N是樣本數。但是這樣維度很多的話那麼時間複雜度還是非常的高,所以可以利用類似雜湊演算法解決高維空間問題,只不過該演算法得到的解是近似解,不是完全解。會損失精確率。

  3、怎麼處理樣本的重要性

  利用權重值。我們在計算距離的時候可以針對不同的鄰居使用不同的權重值,比如距離越近的鄰居我們使用的權重值偏大,這個可以指定演算法的weights引數來設定。

五、總結

  KNN是一個比較簡單的演算法,它適合在低維度空間中使用,資料量太大預測時間高,所以需要對大資料量進行一定的處理。

  下面給出兩篇KD-Tree的文章

  https://www.cs.cmu.edu/~ckingsf/bioinfo-lectures/kdtrees.pdf

  https://zhuanlan.zhihu.com/p/23966698

  

 



      &nbs