1. 程式人生 > 實用技巧 >KNN+交叉驗證

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