一、算法介紹
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*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(logD*N*N)。D是維度數,N是樣本數。但是這樣維度很多的話那么時間復雜度還是非常的高,所以可以利用類似哈希算法解決高維空間問題,只不過該算法得到的解是近似解,不是完全解。會損失精確率。
3、怎么處理樣本的重要性
利用權重值。我們在計算距離的時候可以針對不同的鄰居使用不同的權重值,比如距離越近的鄰居我們使用的權重值偏大,這個可以指定算法的weights參數來設置。
五、總結
KNN是一個比較簡單的算法,它適合在低維度空間中使用,數據量太大預測時間高,所以需要對大數據量進行一定的處理。
下面給出兩篇KD-Tree的文章
https://www.cs.cmu.edu/~ckingsf/bioinfo-lectures/kdtrees.pdf
https://zhuanlan.zhihu.com/p/23966698