验证数据集与交叉验证

測試數據集的意義

如果將所有數據都當做訓練數據,那麼當模型發生過擬合的情況時,我們是無法感知的.
因為在這種情況下,模型在訓練數據集上的誤差會非常小,會讓我們誤以為模型訓練效果非常得好.
但是實際上,模型很可能是泛化能力不足產生過擬合情況


所以,需要將數據分成訓練數據集測試數據集:

  • 訓練數據集用於訓練模型
  • 測試數據集用來驗證模型性能,也就是判斷模型好壞

如果模型出現過擬合(也就是在訓練數據集上表現得好,在測試數據集上表現不好),就需要調整參數(通常是超參數),重新訓練模型,重新對測試數據集進行測試
目的是得到一個在測試數據集表現好的模型
因為這樣的模型的泛化能力強,將其放入生產環境時面對未知數據時,我們更有信心它可以表現得很好


但是上面通過測試數據集判斷模型泛化能力如何的方式也會有問題:模型有可能針對測試數據集出現過擬合

原因:雖然通過訓練數據集訓練模型,但是卻是使用測試數據集來評判模型的好壞.一旦發現模型不好,就換模型重新進行訓練.這個過程在某種程度上是:我們的模型是圍繞測試數據集打轉的.
也就是說,我們在想辦法找到一組參數,這組參數使得我們在訓練數據集上獲得的模型在測試數據集上效果最好

由於訓練數據集是已知的,所以相當於是根據測試數據集進行調參,那麼這個模型就有可能出現針對測試數據集過擬合的情況


解決方式:除了訓練數據集,測試數據集,再引入一個驗證數據集
验证数据集与交叉验证

  • 訓練數據集:訓練模型
  • 驗證數據集:調整超參數使用的數據集(也就是之前的測試數據集的作用)
    將驗證數據傳給訓練好的模型,觀察相應的效果:如果效果不好,就重新換參數,重新訓練模型…直到找到一組參數,這組參數使得模型針對驗證數據來說已經達到最優
  • 測試數據集:作為衡量最終模型性能的數據集

可見,使用這種方式訓練的模型,依據的是訓練數據集和驗證數據集.
測試數據集不參與模型的訓練,也就是對模型而言,測試數據是完全不可知的
在這種情況下,使用測試數據集對模型的性能進行評判,最終的結果會更加準確


即便如此,上面引入驗證數據集對模型進行訓練的方式,還會有另一個問題:隨機

  • 一方面,由於驗證數據集每次都是隨機從原始的數據集中拆分出來的,所以訓練的模型有可能對驗證數據集過擬合
  • 另一方面,由於只有一份驗證數據集,假如驗證數據集中有極端的數值出現,就很有可能導致模型不準確

為了解決上述問題,引入交叉驗證Cross-Validation

交叉驗證

在調整參數過程中,交叉驗證是相對比較標準的評判模型性能的方式

验证数据集与交叉验证
交叉驗證過程:

  • 將訓練數據分成K份(比如3份,A,B,C)
  • 將A,B,C分別作為驗證數據集.即:A做驗證數據集時,BC共同作為訓練數據集…
  • 每一種搭配都會產生一個模型(比如分成3份,就會產生三個模型),而每一個模型在相應的驗證數據集上都會計算出一個性能指標,將這三個性能指標值的平均值作為最終衡量當前算法得到的模型的標準.即K個模型的均值作為最終的結果

由於有平均的過程,所以不會因為某份驗證數據集中有極端值出現而使模型有特別大的偏差

#導入相應包
import numpy as np
import matplotlib.pyplot as plt
#加載手寫識別數據集
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target

只使用train_test對模型進行評估

#拆分train_test
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.4,random_state=666)
#利用train_test尋找KNN算法最佳參數,並評判模型性能
%time
from sklearn.neighbors import KNeighborsClassifier

best_k,best_p,best_score = 0,0,0
for k in range(2,11):
    for p in range(1,6):
        knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)
        knn_clf.fit(X_train,y_train)
        score = knn_clf.score(X_test,y_test)
        
        if score > best_score:
            best_k,best_p,best_score = k,p,score
            
print('Best k =',best_k)
print('Best p =',best_p)
print('Best score=',best_score)

验证数据集与交叉验证

可見,當K=3,P=4時得到了最佳結果,準確率大概是98.6%
這就是使用train_test的方式對超參數進行調整


接下來使用交叉驗證cross_validation的方式進行調參

使用交叉驗證對模型進行評估

#導入交叉驗證相應包
from sklearn.model_selection import cross_val_score
#測試cross_val_score效果
#cross_val_score的參數分別是算法,X_train,y_train
#然後就是自動進行交叉驗證的過程,同時返回生成的K個模型各自對應的準確率

knn = KNeighborsClassifier() #先初始化一個K近鄰算法
cross_val_score(knn,X_train,y_train)

验证数据集与交叉验证
返回數組,且數組有三個元素
意思是cross_val_score默認將傳入的X_train,y_train分成三份進行交叉驗證
交叉驗證的結果就是得到這三個數值

#使用交叉驗證的方式尋找knn模型的最佳參數
from sklearn.neighbors import KNeighborsClassifier

best_k,best_p,best_score = 0,0,0
for k in range(2,11):
    for p in range(1,6):
        knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)
        scores = cross_val_score(knn_clf,X_train,y_train)
        score = np.mean(scores)
        
        if score > best_score:
            best_k,best_p,best_score = k,p,score
            
print('Best k=',best_k)
print('Best p=',best_p)
print('Best score=',best_score)

验证数据集与交叉验证


這個結果與不使用交叉驗證得到的結果對比:

  • Best_k,Best_p均不相同
    通常情況下傾向於相信通過交叉驗證得到的參數
    因為train_test_split中得到的參數很有可能是過擬合了在train_test_split中分離的這組測試數據集
  • 在交叉驗證中得到的Best_score低於在train_test_split中得到的Best_score
    因為在交叉驗證中,通常不會過擬合某一組數據,所以平均來講分數會稍微低一點

現在,通過交叉驗證的方式得到了最好的k和p,但是這個模型最終的準確率並不是得到的Best_score(0.982)

交叉驗證的過程只是為了拿到最好的k和p而已

拿到最好的參數後,接下來就是使用這組參數

  • 重新實例化算法
  • 重新訓練模型
  • 重新對測試數據集進行預測

使用交叉驗證得到的參數重新訓練模型並預測

#使用最佳參數重新實例化模型
best_knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=2,p=2)
#使用得到的最佳模型訓練
best_knn_clf.fit(X_train,y_train)
#使用測試數據集對訓練好的模型進行評估
best_knn_clf.score(X_test,y_test)

验证数据集与交叉验证
以上就是使用三交叉驗證(在交叉驗證的過程中將訓練數據集分成三份)的方式找到了KNN算法最佳的參數組合(k=2,p=2),此時模型的分類的準確度是98%

這裏得到的準確度是通過X_test,y_test計算出來的.
而X_test,y_test這組數據在尋找模型最佳參數的過程中是完全沒有的,對於模型來說是完全陌生的. 也就是說,是使用了一組模型完全沒有見過的數據來測量模型最終的準確率.
這個準確率相對來說是值得相信的


以上即是使用交叉驗證的過程尋找最佳超參數的過程

在scikit-learn中,上述過程被封裝在GridSearchCV

回顧網格搜索

#導入GridSearchCV包
from sklearn.model_selection import GridSearchCV
#實例化網格搜索要針對的算法
knn_clf = KNeighborsClassifier()
#設定網絡搜索要搜索的參數列表
#也就是要調整的上面所實例化的算法的超參數
params_grid = [
    {
        'weights':['distance'],
        'n_neighbors':[i for i in range(2,11)],
        'p':[i for i in range(1,6)]
     }
    ]
#實例化GridSearchCV
grid_search = GridSearchCV(knn_clf,params_grid,verbose=1)

#開始GridSearchCV
%time grid_search.fit(X_train,y_train)

验证数据集与交叉验证

  • 3 folds:網格搜索的過程中使用交叉驗證的方式.此次交叉驗證將訓練數據集分成三份
  • 45 candidates:n_neighbors有9種可能(2-11,不包括11),p有5種可能(1-6,不包括6).9*5=45種組合
  • 135 fits:對45組參數進行搜索,每組參數又要生成3種模型進行計算平均值,所以要進行45*3=135次訓練

#獲得該最佳模型的評分
best_knn_clf.best_score_

验证数据集与交叉验证
結果與上面的best_score相吻合


#查看最佳模型對應的超參數分別是多少
best_knn_clf.best_params_

验证数据集与交叉验证
可以將這組參數在算法實例化時傳入
knn = KNeighborsClassifier(weights='distanct',n_neighbors=2,p=2)

也可以直接使用GridSearchCV訓練後生成的分類器
(此時的分類器的參數即是上面的最佳參數)

#獲得最加參數對應的最佳分類器(模型)
best_knn_clf = grid_search.best_estimator_

#使用這個分類器對測試數據集的預測結果進行評估
best_knn_clf.score(X_test,y_test)

验证数据集与交叉验证


以上即是使用網格搜索-交叉驗證GridSearchCV的方式尋找最佳參數

上面無論是cross_val_score還是GridSearchCV,都是默認將訓練數據集分成三份,如果想要修改分成的份數,使用參數cv

from sklearn.model_selection import cross_val_score
cross_val_score(knn,X_train,y_train,cv=5)

验证数据集与交叉验证

from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(knn,params_grid,verbose=1,cv=5)

验证数据集与交叉验证


  • k-folds交叉驗證
    將數據集分成k份,稱為k-flods cross validation

    • 優點:這種方式找出的參數整體來講可以更加信賴
    • 缺點:每次訓練k個模型,相當於調參過程慢了k倍
  • LOO-CV留一法
    訓練數據集有m個樣本,就將訓練數據集分成m份,每次使用m-1份樣本用於訓練,剩下的一個樣本用於測試預測的準不準
    將這些結果進行平均作為當前參數下對應的模型預測的準確度

    • 優點:完全不受隨機的影響,最接近模型真正的性能指標
    • 缺點:計算量巨大.比如有上千個樣本,每驗證一個參數,就要訓練上千個模型