KNN+交叉驗證
KNN分類模型
- 分類:將一個未知歸類的樣本歸屬到某一個已知的類群中
- 預測:可以根據資料的規律計算出一個未知的資料
- 概念:
- 簡單地說,K-近鄰演算法採用測量不同特徵值之間的距離方法進行分類(k-Nearest Neighbor,KNN)
#兩點間距離 A(x1,y1) B(x2,y2) dist(A,B) = ((x1-x2)**2 + (y1-y2)**2)**0.5
k值的作用
歐幾里得距離(Euclidean Distance) :
如何進行電影分類
眾所周知,電影可以按照題材分類,然而題材本身是如何定義的?由誰來判定某部電影屬於哪 個題材?也就是說同一題材的電影具有哪些公共特徵?這些都是在進行電影分類時必須要考慮的問 題。沒有哪個電影人會說自己製作的電影和以前的某部電影類似,但我們確實知道每部電影在風格 上的確有可能會和同題材的電影相近。那麼動作片具有哪些共有特徵,使得動作片之間非常類似, 而與愛情片存在著明顯的差別呢?動作片中也會存在接吻鏡頭,愛情片中也會存在打鬥場景,我們 不能單純依靠是否存在打鬥或者親吻來判斷影片的型別。但是愛情片中的親吻鏡頭更多,動作片中 的打鬥場景也更頻繁,基於此類場景在某部電影中出現的次數可以用來進行電影分類。
工作原理
存在一個樣本資料集合,也稱作訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一資料 與所屬分類的對應關係。輸人沒有標籤的新資料後,將新資料的每個特徵與樣本集中資料對應的 特徵進行比較,然後演算法提取樣本集中特徵最相似資料(最近鄰)的分類標籤。一般來說,我們 只選擇樣本資料集中前K個最相似的資料,這就是K-近鄰演算法中K的出處,通常K是不大於20的整數。 最後 ,選擇K個最相似資料中出現次數最多的分類,作為新資料的分類。
回到前面電影分類的例子,使用K-近鄰演算法分類愛情片和動作片。有人曾經統計過很多電影的打鬥鏡頭和接吻鏡頭,下圖顯示了6部電影的打鬥和接吻次數。假如有一部未看過的電影,如何確定它是愛情片還是動作片呢?我們可以使用K-近鄰演算法來解決這個問題。
首先我們需要知道這個未知電影存在多少個打鬥鏡頭和接吻鏡頭,上圖中問號位置是該未知電影出現的鏡頭數圖形化展示,具體數字參見下表。
即使不知道未知電影屬於哪種型別,我們也可以通過某種方法計算出來。首先計算未知電影與樣本集中其他電影的距離,如圖所示。
現在我們得到了樣本集中所有電影與未知電影的距離,按照距離遞增排序,可以找到K個距 離最近的電影。假定k=3,則三個最靠近的電影依次是California Man、He's Not Really into Dudes、Beautiful Woman。K-近鄰演算法按照距離最近的三部電影的型別,決定未知電影的型別,而這三部電影全是愛情片,因此我們判定未知電影是愛情片。
import pandas as pd df = pd.read_excel('./datasets/my_films.xlsx') feature = df[['Action Lens','Love Lens']] target = df['target'] from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=3) knn.fit(feature,target) #使用模型做分類 knn.predict([[30,55]]) array(['Love'], dtype=object)
在scikit-learn庫中使用k-近鄰演算法
- 分類問題:from sklearn.neighbors import KNeighborsClassifier
- 鳶尾花分類的實現
from sklearn.neighbors import KNeighborsClassifier import pandas as pd import sklearn.datasets as datasets from sklearn.model_selection import train_test_split #1.捕獲鳶尾花資料 iris = datasets.load_iris() #2.提取樣本資料 feature = iris.data target = iris.target feature.shape (150, 4) target.shape (150,) #3.資料集進行拆分 x_train,x_test,y_train,y_test = train_test_split(feature,target,test_size=0.2,random_state=2020) x_train.shape y_train.shape (120,) #4.觀察資料集:看是否需要進行特徵工程的處理 x_train
#模型的超引數 #如果模型類中的相關引數的不同,會導致分類或者回歸效果的不同,則這些引數叫#做模型的超引數。 #5.例項化模型物件 knn = KNeighborsClassifier(n_neighbors=5) #6.使用訓練集資料訓練模型 #X:特徵(特徵資料的維度必須是二維(表格型資料)) #y:標籤 knn.fit(x_train,y_train) #訓練集 KNeighborsClassifier() #7.測試模型:使用測試資料 knn.score(x_test,y_test) 0.9 #8.使用模型進行分類 print('真實的分類結果:',y_test) print('模型的分類結果:',knn.predict(x_test)) 真實的分類結果: [2 0 1 1 1 2 2 1 0 0 2 2 0 2 2 0 1 1 2 0 0 2 1 0 2 1 1 1 0 0] 模型的分類結果: [2 0 1 1 1 1 2 1 0 0 2 1 0 2 2 0 1 1 2 0 0 2 2 0 2 1 1 1 0 0]
預測年收入是否大於50K美元
from sklearn.preprocessing import StandardScaler,MinMaxScaler df = pd.read_csv('./datasets/adults.txt') df.head() #1.提取樣本資料 feature = df[['age','education_num','occupation','hours_per_week']] target = df['salary'] #2.特徵工程-特徵值化 one_hot_feature = pd.concat((feature[['age','education_num','hours_per_week']],pd.get_dummies(feature['occupation'])),axis=1) #特徵的預處理 s = StandardScaler() s_feature = s.fit_transform(one_hot_feature) #3.切分資料集 x_train,x_test,y_train,y_test = train_test_split(s_feature,target,test_size=0.2,random_state=20) knn = KNeighborsClassifier(30) knn.fit(x_train,y_train) knn.score(x_test,y_test) 0.7982496545370796
不用one-hot的形式
#1.提取樣本資料 feature = df[['age','education_num','occupation','hours_per_week']] target = df['salary'] count = 1 dic = {} for occ in feature['occupation'].unique().tolist(): dic[occ] = count count += 1 feature['occupation'] = feature['occupation'].map(dic) #資料集切分 x_train,x_test,y_train,y_test = train_test_split(feature,target,test_size=0.2,random_state=20) knn = KNeighborsClassifier(n_neighbors=30) knn.fit(x_train,y_train) knn.score(x_test,y_test) #使用模型對未知資料分類 print('真實分類結果:',y_test[0:10]) print('模型分類結果:',knn.predict(x_test)[0:10]) 真實分類結果: 13376 <=50K 7676 >50K 32188 <=50K 30550 <=50K 18873 >50K 21652 >50K 29911 <=50K 27398 <=50K 5757 >50K 4303 <=50K Name: salary, dtype: object 模型分類結果: ['>50K' '<=50K' '<=50K' '<=50K' '<=50K' '<=50K' '<=50K' '<=50K' '>50K' '<=50K']
k-近鄰演算法之約會網站配對效果判定(datingTestSet.txt)
df = pd.read_csv('./datasets/datingTestSet.txt',header=None,sep='\t') df.head() #樣本資料提取 feature_col = [col for col in df.columns if col != 3] feature = df[feature_col] target = df[3] #特徵工程 mm = MinMaxScaler() m_feature = mm.fit_transform(feature) #資料集切分 x_train,x_test,y_train,y_test = train_test_split(m_feature,target,test_size=0.2,random_state=2020) knn = KNeighborsClassifier(n_neighbors=10) knn.fit(x_train,y_train) knn.score(x_test,y_test) 0.95
學習曲線尋找最優的k值
- 窮舉不同的k值
ks = [5,7,9,12,15,20,25,30,35,40,45,50,60,70,80,90,100] scores = [] for k in ks: knn = KNeighborsClassifier(n_neighbors=k).fit(x_train,y_train) score = knn.score(x_test,y_test) scores.append(score) import matplotlib.pyplot as plt plt.plot(ks,scores) plt.xlabel('k') plt.ylabel('score') #找到了分值最大的元素下標 import numpy as np arr_scores = np.array(scores) np.argmax(arr_scores) 4 ks[4] #最高分值對應的k為15 15 #基於最優的k值建模 knn = KNeighborsClassifier(n_neighbors=15) knn.fit(x_train,y_train) knn.score(x_test,y_test) 0.965
- 問題:約會資料中發現標籤資料為非數值型資料,可行嗎?
- 可行!因為在knn中樣本的標籤資料是不需要參與運算。
k的取值問題:學習曲線&交叉驗證選取K值
- K值較小,則模型複雜度較高,容易發生過擬合,學習的估計誤差會增大,預測結果對近鄰的例項點非常敏感。
- K值較大可以減少學習的估計誤差,但是學習的近似誤差會增大,與輸入例項較遠的訓練例項也會對預測起作用,使預測發生錯誤,k值增大模型的複雜度會下降。
- 在應用中,k值一般取一個比較小的值,通常採用交叉驗證法來來選取最優的K值。
- 適用場景
- 小資料場景,樣本為幾千,幾萬的
K折交叉驗證
- 目的:
- 選出最為適合的模型超引數的取值,然後將超引數的值作用到模型的建立中。
- 思想:
- 將樣本的訓練資料交叉的拆分出不同的訓練集和驗證集,使用交叉拆分出不同的訓練集和驗證集測分別試模型的精準度,然就求出的精準度的均值就是此次交叉驗證的結果。將交叉驗證作用到不同的超引數中,選取出精準度最高的超引數作為模型建立的超引數即可!
- 實現思路:
- 將資料集平均分割成K個等份
- 使用1份資料作為測試資料,其餘作為訓練資料
- 計算測試準確率
- 使用不同的測試集,重複2、3步驟
- 對準確率做平均,作為對未知資料預測準確率的估計
- API
- from sklearn.model_selection import cross_val_score
- cross_val_score(estimator,X,y,cv):
- estimator:模型物件
- X,y:訓練集資料
- cv:折數
- 交叉驗證在KNN中的基本使用
- 使用交叉驗證&學習曲線找尋最優的超引數
- 交叉驗證也可以幫助我們進行模型選擇,以下是一組例子,分別使用iris資料,KNN和logistic迴歸模型進行模型的比較和選擇。
from sklearn.linear_model import LogisticRegression knn = KNeighborsClassifier(n_neighbors=5) print (cross_val_score(knn, x_train, y_train, cv=10).mean()) lr = LogisticRegression() print(cross_val_score(lr,x_train,y_train,cv=10).mean())
0.9833333333333332 0.9416666666666667
K-Fold&cross_val_score
- Scikit中指供了K-Fold的API
- n-split就是折數
- shuffle指是否對資料洗牌
- random_state為隨機種子,固定隨機性
from numpy import array from sklearn.model_selection import KFold # data sample data = array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) kfold = KFold(n_splits=3, shuffle = True, random_state= 1) for train, test in kfold.split(data): print('train: %s, test: %s' % (data[train], data[test]))
train: [0.1 0.4 0.5 0.6], test: [0.2 0.3] train: [0.2 0.3 0.4 0.6], test: [0.1 0.5] train: [0.1 0.2 0.3 0.5], test: [0.4 0.6]
- Scikit中提取帶K-Fold介面的交叉驗證介面sklearn.model_selection.cross_validate,但是該介面沒有資料shuffle功能,所以一般結合Kfold一起使用。如果Train資料在分組前已經經過了shuffle處理,比如使用train_test_split分組,那就可以直接使用cross_val_score介面
from sklearn.model_selection import cross_val_score iris = datasets.load_iris() X, y = iris.data, iris.target knn = KNeighborsClassifier(n_neighbors=5) n_folds = 5 kf = KFold(n_folds, shuffle=True, random_state=42).get_n_splits(X) scores = cross_val_score(knn, X, y, cv = kf) scores.mean()
0.9733333333333334