前面的話:
最近被安利了風犬少年的天空這部劇,美好而又接地氣,永遠也無法拒絕溫暖的情感。順便平安夜大家記得吃蘋果喲,平平安安,順順遂遂。
這裡先給出阿里雲機器學習訓練營地址:
https://tianchi.aliyun.com/s/5f7fc113c530028add3c2b9fc80e7aee
大家可以進行一個代碼的學習,可以將其代碼下載進行學習或者參加最後一個任務的比賽。
本文章同見於csdn博客:(歡迎關注csdn)
https://blog.csdn.net/handsomeandge/article/details/111648990
歡迎一起討論代碼,一起進步呀!
1 KNN的介紹和應用1.1 KNN的介紹kNN(k-nearest neighbors),中文翻譯K近鄰。我們常常聽到一個故事:如果要了解一個人的經濟水平,只需要知道他最好的5個朋友的經濟能力, 對他的這五個人的經濟水平求平均就是這個人的經濟水平。這句話裡面就包含著kNN的算法思想。
示例 :如上圖,綠色圓要被決定賦予哪個類,是紅色三角形還是藍色四方形?如果K=3,由於紅色三角形所佔比例為2/3,綠色圓將被賦予紅色三角形那個類,如果K=5,由於藍色四方形比例為3/5,因此綠色圓被賦予藍色四方形類。
1) KNN建立過程
1 給定測試樣本,計算它與訓練集中的每一個樣本的距離。
2 找出距離近期的K個訓練樣本。作為測試樣本的近鄰。
3 依據這K個近鄰歸屬的類別來確定樣本的類別。
2) 類別的判定
①投票決定,少數服從多數。取類別最多的為測試樣本類別。
②加權投票法,依據計算得出距離的遠近,對近鄰的投票進行加權,距離越近則權重越大,設定權重為距離平方的倒數。
1.2 KNN的應用KNN雖然很簡單,但是人們常說"大道至簡",一句"物以類聚,人以群分"就能揭開其面紗,看似簡單的KNN即能做分類又能做回歸, 還能用來做數據預處理的缺失值填充。由於KNN模型具有很好的解釋性,一般情況下對於簡單的機器學習問題,我們可以使用KNN作為 Baseline,對於每一個預測結果,我們可以很好的進行解釋。推薦系統的中,也有著KNN的影子。例如文章推薦系統中, 對於一個用戶A,我們可以把和A最相近的k個用戶,瀏覽過的文章推送給A。
機器學習領域中,數據往往很重要,有句話叫做:"數據決定任務的上限, 模型的目標是無限接近這個上限"。可以看到好的數據非常重要,但是由於各種原因,我們得到的數據是有缺失的,如果我們能夠很好的填充這些缺失值, 就能夠得到更好的數據,以至於訓練出來更魯棒的模型。接下來我們就來看看KNN如果做分類,怎麼做回歸以及怎麼填充空值。
補充:
KNN可以說是最簡單的分類算法之一,同時,它也是最常用的分類算法之一,注意KNN算法是有監督學習中的分類算法,它看起來和另一個機器學習算法Kmeans有點像(Kmeans是無監督學習算法),但卻是有本質區別的。KNN的全稱是K Nearest Neighbors,意思是K個最近的鄰居,從這個名字我們就能看出一些KNN算法的蛛絲馬跡了。K個最近鄰居,毫無疑問,K的取值肯定是至關重要的。KNN的原理就是當預測一個新的值x的時候,根據它距離最近的K個點是什麼類別來判斷x屬於哪個類別。
KNN算法優點
簡單易用,相比其他算法,KNN算是比較簡潔明了的算法。即使沒有很高的數學基礎也能搞清楚它的原理。
模型訓練時間快,上面說到KNN算法是惰性的,這裡也就不再過多講述。
預測效果好。
對異常值不敏感
KNN算法缺點
對內存要求較高,因為該算法存儲了所有訓練數據
預測階段可能很慢
對不相關的功能和數據規模敏感
2 實驗室手冊2.1 實驗環境1. python3.7
2. numpy >= '1.16.4'
3. sklearn >= '0.23.1'
了解KNN怎麼做分類問題
了解KNN如何做回歸
了解KNN怎麼做空值填充, 如何使用knn構建帶有空值的pipeline
2.3 代碼流程二維數據集--knn分類
Step1: 庫函數導入
Step2: 數據導入
Step3: 模型訓練&可視化
Step4: 原理簡析
鶯尾花數據集--kNN分類
Step1: 庫函數導入
Step2: 數據導入&分析
Step3: 模型訓練
Step4: 模型預測&可視化
模擬數據集--kNN回歸
Step1: 庫函數導入
Step2: 數據導入&分析
Step3: 模型訓練&可視化
馬絞痛數據--kNN數據預處理+kNN分類pipeline
Step1: 庫函數導入
Step2: 數據導入&分析
Step3: KNNImputer空值填充--使用和原理介紹
Step4: KNNImputer空值填充--歐式距離的計算
Step5: 基於pipeline模型預測&可視化
2.4 算法實戰2.4.1 Demo數據集--kNN分類Step1: 庫函數導入
import numpy as npimport matplotlib.pyplot as pltfrom matplotlib.colors import ListedColormap
from sklearn.neighbors import KNeighborsClassifierfrom sklearn import datasetsStep2: 數據導入
iris = datasets.load_iris()X = iris.data[:, :2]y = iris.targetStep3: 模型訓練&可視化
k_list = [1, 3, 5, 8, 10, 15]h = .02cmap_light = ListedColormap(['orange', 'cyan', 'cornflowerblue'])cmap_bold = ListedColormap(['darkorange', 'c', 'darkblue'])
plt.figure(figsize=(15,14))for ind,k in enumerate(k_list): clf = KNeighborsClassifier(k) clf.fit(X, y) x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape)
plt.subplot(321+ind) plt.pcolormesh(xx, yy, Z, cmap=cmap_light) plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold, edgecolor='k', s=20) plt.xlim(xx.min(), xx.max()) plt.ylim(yy.min(), yy.max()) plt.title("3-Class classification (k = %i)"% k)
plt.show()Step4: 原理簡析
如果選擇較小的K值,就相當於用較小的領域中的訓練實例進行預測,例如當k=1的時候,在分界點位置的數據很容易受到局部的影響,圖中藍色的部分中還有部分綠色塊,主要是數據太局部敏感。當k=15的時候,不同的數據基本根據顏色分開,當時進行預測的時候,會直接落到對應的區域,模型相對更加魯棒。
2.4.2 鶯尾花數據集--kNN分類Step1: 庫函數導入
import numpy as npfrom sklearn import datasetsfrom sklearn.neighbors import KNeighborsClassifierfrom sklearn.model_selection import train_test_splitStep2: 數據導入&分析
iris = datasets.load_iris()
X = iris.datay = iris.targetX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)Step3: 模型訓練
這裡我們設置參數k(n_neighbors)=5, 使用歐式距離(metric=minkowski & p=2)
clf = KNeighborsClassifier(n_neighbors=5, p=2, metric="minkowski")clf.fit(X_train, y_train)Step4:模型預測&可視化
X_pred = clf.predict(X_test)acc = sum(X_pred == y_test) / X_pred.shape[0]print("預測的準確率ACC: %.3f" % acc)2.4.3 模擬數據集--kNN回歸Step1: 庫函數導入
import numpy as npimport matplotlib.pyplot as pltfrom sklearn.neighbors import KNeighborsRegressorStep2: 數據導入&分析
np.random.seed(0)
X = np.sort(5 * np.random.rand(40, 1), axis=0)
T = np.linspace(0, 5, 500)[:, np.newaxis]
y = np.sin(X).ravel()
y[::5] += 1 * (0.5 - np.random.rand(8))Step3: 模型訓練&預測可視化
n_neighbors = [1, 3, 5, 8, 10, 40]plt.figure(figsize=(10,20))for i, k in enumerate(n_neighbors): clf = KNeighborsRegressor(n_neighbors=k, p=2, metric="minkowski") clf.fit(X, y) y_ = clf.predict(T) plt.subplot(6, 1, i + 1) plt.scatter(X, y, color='red', label='data') plt.plot(T, y_, color='navy', label='prediction') plt.axis('tight') plt.legend() plt.title("KNeighborsRegressor (k = %i)" % (k))
plt.tight_layout()plt.show()Step4:模型分析
當k=1時,預測的結果只和最近的一個訓練樣本相關,從預測曲線中可以看出當k很小時候很容易發生過擬合。
當k=40時,預測的結果和最近的40個樣本相關,因為我們只有40個樣本,此時是所有樣本的平均值,此時所有預測值都是均值,很容易發生欠擬合。
一般情況下,使用knn的時候,根據數據規模我們會從[3, 20]之間進行嘗試,選擇最好的k,例如上圖中的[3, 10]相對1和40都是還不錯的選擇。
2.4.4 馬絞痛數據--kNN數據預處理+kNN分類pipelineStep1: 庫函數導入
import numpy as npimport pandas as pd
from sklearn.neighbors import KNeighborsClassifier
from sklearn.impute import KNNImputer
from sklearn.metrics.pairwise import nan_euclidean_distances
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFoldfrom sklearn.pipeline import Pipelineimport matplotlib.pyplot as pltfrom sklearn.model_selection import train_test_splitStep2: 數據導入&分析
數據中的'?'表示空值,如果我們使用KNN分類器,'?'不能數值,不能進行計算,因此我們需要進行數據預處理對空值進行填充。
這裡我們使用KNNImputer進行空值填充,KNNImputer填充的原來很簡單,計算每個樣本最近的k個樣本,進行空值填充。
我們先來看下KNNImputer的運行原理:
Step3: KNNImputer空值填充--使用和原理介紹
X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]]
imputer = KNNImputer(n_neighbors=2, metric='nan_euclidean')imputer.fit_transform(X)帶有空值的歐式距離計算公式
nan_euclidean_distances([[np.nan, 6, 5], [3, 4, 3]], [[3, 4, 3], [1, 2, np.nan], [8, 8, 7]])input_file = './horse-colic.csv'df_data = pd.read_csv(input_file, header=None, na_values='?')
data = df_data.valuesix = [i for i in range(data.shape[1]) if i != 23]X, y = data[:, ix], data[:, 23]
for i in range(df_data.shape[1]): n_miss = df_data[[i]].isnull().sum()
perc = n_miss / df_data.shape[0] * 100 if n_miss.values[0] > 0: print('>Feat: %d, Missing: %d, Missing ratio: (%.2f%%)' % (i, n_miss, perc))
print('KNNImputer before Missing: %d' % sum(np.isnan(X).flatten()))
imputer = KNNImputer()
imputer.fit(X)
Xtrans = imputer.transform(X)
print('KNNImputer after Missing: %d' % sum(np.isnan(Xtrans).flatten()))Step5: 基於pipeline模型訓練&可視化
什麼是Pipeline, 我這裡直接翻譯成數據管道。任何有序的操作有可以看做pipeline,例如工廠流水線,對於機器學習模型來說,這就是數據流水線。是指數據通過管道中的每一個節點,結果除了之後,繼續流向下遊。對於我們這個例子,數據是有空值,我們會有一個KNNImputer節點用來填充空值, 之後繼續流向下一個kNN分類節點,最後輸出模型。
results = list()strategies = [str(i) for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 18, 20, 21]]for s in strategies: pipe = Pipeline(steps=[('imputer', KNNImputer(n_neighbors=int(s))), ('model', KNeighborsClassifier())]) scores = [] for k in range(20): X_train, X_test, y_train, y_test = train_test_split(Xtrans, y, test_size=0.2) pipe.fit(X_train, y_train) score = pipe.score(X_test, y_test) scores.append(score) results.append(np.array(scores)) print('>k: %s, Acc Mean: %.3f, Std: %.3f' % (s, np.mean(scores), np.std(scores)))plt.boxplot(results, labels=strategies, showmeans=True)plt.show()Step 6: 結果分析
我們的實驗是每個k值下,隨機切分20次數據, 從上述的圖片中, 根據k值的增加,我們的測試準確率會有先上升再下降再上升的過程。[3, 5]之間是一個很好的取值,上文我們提到,k很小的時候會發生過擬合,k很大時候會發生欠擬合,當遇到第一下降節點,此時我們可以 簡單認為不在發生過擬合,取當前的k值即可。
2.5 KNN原理介紹k近鄰方法是一種惰性學習算法,可以用於回歸和分類,它的主要思想是投票機制,對於一個測試實例x, 我們在有標籤的訓練數據集上找到和最相近的k個數據,用他們的label進行投票,分類問題則進行表決投票,回歸問題使用加權平均或者直接平均的方法。knn算法中我們最需要關注兩個問題:k值的選擇和距離的計算。kNN中的k是一個超參數,需要我們進行指定,一般情況下這個k和數據有很大關係,都是交叉驗證進行選擇,但是建議使用交叉驗證的時候,k∈[2,20],使用交叉驗證得到一個很好的k值。
k值還可以表示我們的模型複雜度,當k值越小意味著模型複雜度變大,更容易過擬合,(用極少數的樣例來絕對這個預測的結果,很容易產生偏見,這就是過擬合)。我們有這樣一句話,k值越多學習的估計誤差越小,但是學習的近似誤差就會增大。