scikit-learn 交叉驗證:評估模型表現(xiàn)

2023-02-20 14:09 更新

? 通過對預(yù)測函數(shù)參數(shù)的訓(xùn)練,并在相同的數(shù)據(jù)集上進(jìn)行測試,是一個錯誤的方法論:如果一個模型對相同樣本標(biāo)簽進(jìn)行訓(xùn)練和測試,模型的得分會很高,但在實驗數(shù)據(jù)上的表現(xiàn)會很差,這種情況被稱為過擬合。為了避免過擬合,在執(zhí)行有監(jiān)督的機(jī)器學(xué)習(xí)“實驗”時,常見的方法是將已有數(shù)據(jù)集中保留一部分?jǐn)?shù)據(jù)作為測試集,即Xtest, Ytest。需要注意的是:“實驗”并不僅用于學(xué)術(shù)領(lǐng)域,因為在商業(yè)領(lǐng)域中,機(jī)器學(xué)習(xí)通常也是以實驗為開端。如下是一個交叉驗證流程圖。最優(yōu)參數(shù)選擇可以參考grid search。


? 在scikit-learn中,隨機(jī)切分?jǐn)?shù)據(jù)集為訓(xùn)練集和測試集可以使用train_test_split幫助函數(shù)。

? 如:使用鶯尾花數(shù)據(jù)集(iris data)擬合一個線性支持向量機(jī)模型。

>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import datasets
>>> from sklearn import svm

>>> X, y = datasets.load_iris(return_X_y=True)
>>> X.shape, y.shape
((150, 4), (150,)) 

使用部分?jǐn)?shù)據(jù)來訓(xùn)練模型,并用剩余40%的數(shù)據(jù)來對模型分類效果進(jìn)行評估:

>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, test_size=0.4, random_state=0)

>>> X_train.shape, y_train.shape
((90, 4), (90,))
>>> X_test.shape, y_test.shape
((60, 4), (60,))

>>> clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
>>> clf.score(X_test, y_test)
0.96...
 當(dāng)評估器對不同的模型參數(shù)(“超參數(shù)”)進(jìn)行評估時,如參數(shù)C是支持向量機(jī)的一個超參數(shù),在模型未調(diào)整前,仍有過擬合的風(fēng)險,因為參數(shù)的選擇決定模型的最佳表現(xiàn)。然而,通過這種方式,測試集中的信息仍有可能會“泄露”到模型中,導(dǎo)致評估指標(biāo)不能概括模型的評估能力。通過將已知數(shù)據(jù)集劃分一部分為“驗證集”可以解決上述問題:在訓(xùn)練數(shù)據(jù)上對模型進(jìn)行訓(xùn)練,利用驗證集對模型進(jìn)行評估。當(dāng)“實驗”得到一個較好的成績時,在測試集上完成模型的最終評估。 

? 但是,當(dāng)把整個數(shù)據(jù)集分為上述三個集合時,大大降低可用于建模的數(shù)據(jù)量。從而,模型的評估結(jié)果取決于對訓(xùn)練集和驗證集的某種隨機(jī)劃分。

? 解決上述問題的一個方法是交叉驗證(corss-validation,簡稱CV)。當(dāng)應(yīng)用交叉驗證的方法時,不再需要劃分驗證集,而測試集始終應(yīng)用于模型的最終評估。最基本的交叉驗證方法是K折交叉驗證(k-fold CV),它是指將訓(xùn)練集劃分為k個最小的子集(其它的方法將在下文介紹,當(dāng)均具有相同的原理)。如下的過程應(yīng)用k“折疊”中的一個:

  • 將k-1個子集用于模型訓(xùn)練;

  • 用剩余數(shù)據(jù)驗證上一步中建立的模型(類似于利用測試集計算模型的準(zhǔn)確率)。

    模型表現(xiàn)是由k折交叉驗證的結(jié)果表示的,它是上述步驟循環(huán)結(jié)果的平均值。這個方法計算成本高,但不會浪費(fèi)很多數(shù)據(jù)(如下圖每一個例子中,固定一個隨機(jī)驗證集),上述方法在如下問題中,譬如樣本數(shù)據(jù)非常小的反向推演,具有顯著優(yōu)勢。


3.1.1 計算交叉驗證的指標(biāo)

? 在數(shù)據(jù)集和估計器上應(yīng)用交叉驗證最簡單的方法是調(diào)用cross_val_score幫助函數(shù)。

? 隨后的例子將會說明在鶯尾花(iris)數(shù)據(jù)集上,如何通過劃分?jǐn)?shù)據(jù)集、訓(xùn)練模型和計算5次交叉驗證的得分,從而估計一個線性核支持向量機(jī)的準(zhǔn)確率(每次使用不同的拆分)。

>>> from sklearn.model_selection import cross_val_score
>>> clf = svm.SVC(kernel='linear', C=1)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ]) 

? 如下是在95%置信區(qū)間上的平均得分為:

>>> print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
Accuracy: 0.98 (+/- 0.03)  

? 默認(rèn)的交叉驗證迭代的輸出結(jié)果是參數(shù)為score的估計器,通過修改scoring的參數(shù)可以修改估計器:

>>> from sklearn import metrics
>>> scores = cross_val_score(
...     clf, X, y, cv=5, scoring='f1_macro')
>>> scores
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ]) 

? 參見 The scoring parameter: defining model evaluation。在上述鶯尾花數(shù)據(jù)集(Iris dataset)的例子中,樣本標(biāo)簽是均衡的,因此計算的準(zhǔn)確率和F1-score幾乎相等。

? 當(dāng)參數(shù)cv是一個整數(shù)時,cross_val_score的初始參數(shù)為KFoldStratifiedKFold,當(dāng)模型的估計器來自于ClassifierMixin時,使用后一種參數(shù)。

? 也可以通過傳入交叉驗證迭代器,來使用交叉驗證的策略對模型進(jìn)行評估,如:

>>> from sklearn.model_selection import ShuffleSplit
>>> n_samples = X.shape[0]
>>> cv = ShuffleSplit(n_splits=5, test_size=0.3, random_state=0)
>>> cross_val_score(clf, X, y, cv=cv)
array([0.977..., 0.977..., 1.  ..., 0.955..., 1.        ]) 

? 另一種選擇時使用可迭代的數(shù)據(jù)集拆分(訓(xùn)練集,測試集)方式作為數(shù)組的索引,例如:

>>> def custom_cv_2folds(X):
...     n = X.shape[0]
...     i = 1
...     while i <= 2:
...         idx = np.arange(n * (i - 1) / 2, n * i / 2, dtype=int)
...         yield idx, idx
...         i += 1
...
>>> custom_cv = custom_cv_2folds(X)
>>> cross_val_score(clf, X, y, cv=custom_cv)
array([1.        , 0.973...]) 

使用保留的數(shù)據(jù)進(jìn)行數(shù)據(jù)轉(zhuǎn)化

? 正如對從訓(xùn)練數(shù)據(jù)中拆分的數(shù)據(jù)進(jìn)行預(yù)測評估是重要的,預(yù)處理(如標(biāo)準(zhǔn)化、特征選擇等)和類似的數(shù)據(jù)轉(zhuǎn)換同樣應(yīng)從訓(xùn)練集中學(xué)習(xí),并應(yīng)用于剩余的數(shù)據(jù)以進(jìn)行預(yù)測評估:

>>> from sklearn import preprocessing
>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, test_size=0.4, random_state=0)
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> X_train_transformed = scaler.transform(X_train)
>>> clf = svm.SVC(C=1).fit(X_train_transformed, y_train)
>>> X_test_transformed = scaler.transform(X_test)
>>> clf.score(X_test_transformed, y_test)
0.9333... 

? Pipeline庫使編寫估計器更加容易,在交叉驗證下提供這種方法:

>>> from sklearn.pipeline import make_pipeline
>>> clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(C=1))
>>> cross_val_score(clf, X, y, cv=cv)
array([0.977..., 0.933..., 0.955..., 0.933..., 0.977...]) 

參見 管道和復(fù)合估計器

3.1.1.1 交叉驗證函數(shù)和多指標(biāo)評估

cross_validatecross_val_score存在兩方面不同:

  • 它允許制定多個評估指標(biāo),

  • 它返回一個字典,包含擬合時間,得分時間(以及可選輸出項訓(xùn)練分?jǐn)?shù)和評估器),另外還會輸出測試集得分。

    對于單一的評估指標(biāo),scoring參數(shù)是一個字符串類型,可調(diào)用的對象或者空值,關(guān)鍵詞可能如下:

    ['test_score', 'fit_time', 'score_time']

    而對于多參數(shù)評估指標(biāo),輸入如下關(guān)鍵詞將會返回值是一個字典類型:

    ['test<score1_name>', 'test<score2_name>', 'test_<scorer...>', 'fit_time', 'score_time']

    return_train_score的初始值是False,以便節(jié)省計算時間。如果需要輸出訓(xùn)練集的得分,則需要將參數(shù)修改為True。

    如果想獲得模型在每個訓(xùn)練集上擬合的估計器,則需要設(shè)置return_estimator = True。

    多個指標(biāo)的傳入可以是列表,元組或預(yù)定義的評分器名稱:

    >>> from sklearn.model_selection import cross_validate
    >>> from sklearn.metrics import recall_score
    >>> scoring = ['precision_macro', 'recall_macro']
    >>> clf = svm.SVC(kernel='linear', C=1, random_state=0)
    >>> scores = cross_validate(clf, X, y, scoring=scoring)
    >>> sorted(scores.keys())
    ['fit_time', 'score_time', 'test_precision_macro', 'test_recall_macro']
    >>> scores['test_recall_macro']
    array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ]) 

    也可以用字典映射一個預(yù)定義的評分器或者自定義評分器函數(shù):

    >>> from sklearn.metrics import make_scorer
    >>> scoring = {'prec_macro': 'precision_macro',
    ...            'rec_macro': make_scorer(recall_score, average='macro')}
    >>> scores = cross_validate(clf, X, y, scoring=scoring,
    ...                         cv=5, return_train_score=True)
    >>> sorted(scores.keys())
    ['fit_time', 'score_time', 'test_prec_macro', 'test_rec_macro',
     'train_prec_macro', 'train_rec_macro']
    >>> scores['train_rec_macro']
    array([0.97..., 0.97..., 0.99..., 0.98..., 0.98...]) 

    如下是使用單一參數(shù)的cross_validate的例子:

    >>> scores = cross_validate(clf, X, y,
    ...                         scoring='precision_macro', cv=5,
    ...                         return_estimator=True)
    >>> sorted(scores.keys())
    ['estimator', 'fit_time', 'score_time', 'test_score'] 

3.1.1.2 獲得交叉驗證的預(yù)測值

? cross_val_predict函數(shù)與cross_val_score具有類似的界面,返回的是每一個屬于測試集數(shù)據(jù)的預(yù)測值。只有交叉驗證的策略允許測試集一次使用所有數(shù)據(jù)(否則,將會報錯)。

警告:不正確使用cross_val_predict的報警

? cross_val_predict的結(jié)果可能與cross_val_score的結(jié)果不同,因為數(shù)據(jù)會以不同的方式進(jìn)行分組。cross_val_score函數(shù)是交叉驗證的平均值,而cross_val_predict僅返回標(biāo)簽(或者標(biāo)簽的概率)來源于不同的輸入學(xué)習(xí)器**(這里的翻譯看的知乎https://zhuanlan.zhihu.com/p/90451347)**。 因此,cross_val_predict不是一個適宜的度量泛化誤差的方法。

? cross_val_predict函數(shù)適合的場景如下:

3.1.2 交叉驗證迭代器

? 隨后的部分列出了一些用于生成索引標(biāo)號,用于在不同的交叉驗證策略中生成數(shù)據(jù)劃分的工具。

3.1.2.1 針對獨(dú)立同分布數(shù)據(jù)的交叉驗證迭代器

? 假設(shè)一些數(shù)據(jù)是獨(dú)立同分布的,假定所有的樣本來自于相同的生成過程,并且生成過程假設(shè)不基于對過去樣本的記憶。

? 隨后的交叉驗證會被用于如下的例子。

注意:

? 盡管獨(dú)立同分布的數(shù)據(jù)在機(jī)器學(xué)習(xí)理論中是一個普遍的假設(shè),但它很少存在于實際生活中。假設(shè)某樣本的生成是一個的過程,那么使用時間序列感知交叉驗證方案會更安全。同樣的,如果已知樣本生成過程具有g(shù)roup structure(團(tuán)體結(jié)構(gòu))性質(zhì)(樣本來自于不同的主體,實驗和測量工具),就可以更安全的使用group-wise cross-validation。

3.1.2.1.1 K-fold

? KFold將所有的樣本分為k組,被稱為折疊(如果k=n,等價于Leave One Out(留一)策略),每個子樣本組的數(shù)據(jù)量相同(如果可能)。預(yù)測函數(shù)將會使用k-1個折疊中的數(shù)據(jù)進(jìn)行學(xué)習(xí),剩余的折疊將會被用于測試。

例如四個樣本集的2折交叉驗證:

>>> import numpy as np
>>> from sklearn.model_selection import KFold

>>> X = ["a", "b", "c", "d"]
>>> kf = KFold(n_splits=2)
>>> for train, test in kf.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1] 
[0 1] [2 3]

? 下圖是交叉驗證的可視化。注意KFold并不受類別或者組別的影響。


? 每一個折疊都有兩個數(shù)組構(gòu)成:第一個是訓(xùn)練集,第二個是測試集。因此,可以使用numpy索引建立訓(xùn)練/測試集合:

>>> X = np.array([[0., 0.], [1., 1.], [-1., -1.], [2., 2.]])
>>> y = np.array([0, 1, 0, 1])
>>> X_train, X_test, y_train, y_test = X[train], X[test], y[train], y[test] 

3.1.2.1.2 重復(fù)的K-Fold

? RepeatedKFold重復(fù)K-Fold n次。該方法可以用于需要執(zhí)行n次KFold的情形,在每個循環(huán)中會生成不同的分組。

2折K-Fold重復(fù)2次示例:

>>> import numpy as np
>>> from sklearn.model_selection import RepeatedKFold
>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
>>> random_state = 12883823
>>> rkf = RepeatedKFold(n_splits=2, n_repeats=2, random_state=random_state)
>>> for train, test in rkf.split(X):
...     print("%s %s" % (train, test))
...
[2 3] [0 1]
[0 1] [2 3]
[0 2] [1 3]
[1 3] [0 2] 

? 類似的,RepeatedStratifiedKFold是在每個重復(fù)中以不同的隨機(jī)化重復(fù)n次分層的K-Fold。

3.1.2.1.3 Leave One Out(LOO)

? LeaveOneOut(或LOO)是一個簡單的交叉驗證。每個學(xué)習(xí)集都是去除一個樣本后的剩余樣本,測試集是余下樣本。因此,對于n個樣本而言,就會有n個不同的訓(xùn)練集和n個不同的測試集。這個交叉驗證的過程不會浪費(fèi)很多數(shù)據(jù),因為只有一個數(shù)據(jù)從訓(xùn)練集中移出。

>>> from sklearn.model_selection import LeaveOneOut

>>> X = [1, 2, 3, 4]
>>> loo = LeaveOneOut()
>>> for train, test in loo.split(X):
...     print("%s %s" % (train, test))
[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3] 

? LOO潛在的用戶選擇模型應(yīng)該考慮一些已知警告。當(dāng)與k-fold交叉驗證做比較時,當(dāng)n>k時,留一法可以從n個樣本中構(gòu)建n個模型,而不是k個模型。進(jìn)一步,每一個模型都是在n-1個樣本上進(jìn)行訓(xùn)練,而不是在(k-1)n/k。兩種方法都假設(shè)k不是很大,并且k<n,LOO比k-fold交叉驗證的計算成本高。

? 就正確率而言,LOO經(jīng)常導(dǎo)致高方差(測試誤差的估計器)。直觀地說,因為用n個樣本的n-1個樣本構(gòu)建模型,由折疊構(gòu)建的模型實際上是相同的,且相同于由整個數(shù)據(jù)集構(gòu)建的模型。

? 但是,如果學(xué)習(xí)曲線相對于訓(xùn)練集的大小是陡峭的,那么5-或者10-折交叉驗證會高估模型的泛化誤差。

? 作為通用規(guī)則,大多數(shù)作者,及實際經(jīng)驗表明,5-或者10-交叉驗證會優(yōu)于LOO。

參考文獻(xiàn):

3.1.2.1.4 Leave P Out(LPO)

? LeavePOutLeaveOneOut相類似,因為它通過從整個數(shù)據(jù)集中,刪除p個樣本點(diǎn)來創(chuàng)建所有可能的訓(xùn)練集/測試集。對于n個樣本量,它產(chǎn)生了

種訓(xùn)練-測試組合。與LeaveOneOutKFold不同的是,當(dāng)p>1時,測試集將會重疊。

? 在只有四個樣本的數(shù)據(jù)集上使用Leave-2-Out示例:

>>> from sklearn.model_selection import LeavePOut

>>> X = np.ones(4)
>>> lpo = LeavePOut(p=2)
>>> for train, test in lpo.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1]
[1 3] [0 2]
[1 2] [0 3]
[0 3] [1 2]
[0 2] [1 3]
[0 1] [2 3] 

3.1.2.1.5 隨機(jī)排列交叉驗證a.k.a. Shuffle & Split:

ShuffleSplit

ShuffleSplit迭代器會產(chǎn)生一個由用戶定義數(shù)值,獨(dú)立的訓(xùn)練集/測試集劃分。樣本首先被打亂,然后劃分為訓(xùn)練集和測試集的組合。

通過設(shè)置種子random_state偽隨機(jī)數(shù)發(fā)生器,可以再現(xiàn)隨機(jī)切分?jǐn)?shù)據(jù)集的結(jié)果。

以下是使用示例:

>>> from sklearn.model_selection import ShuffleSplit
>>> X = np.arange(10)
>>> ss = ShuffleSplit(n_splits=5, test_size=0.25, random_state=0)
>>> for train_index, test_index in ss.split(X):
...     print("%s %s" % (train_index, test_index))
[9 1 6 7 3 0 5] [2 8 4]
[2 9 8 0 6 7 4] [3 5 1]
[4 5 1 0 6 9 7] [2 3 8]
[2 7 5 8 0 3 4] [6 1 9]
[4 1 0 6 8 9 3] [5 2 7]

下圖是交叉驗證原理的可視化。注意ShuffleSplit不受類別或組的影響。


ShuffleSplitKFold交叉驗證好的替代選擇,它允許數(shù)量或比例控制訓(xùn)練/測試集的劃分方式。

3.1.2.2 基于類標(biāo)簽的層化交叉驗證迭代器

? 一些分類問題會表現(xiàn)出極大不均衡的樣本類別分布:例如正樣本是負(fù)樣本的許多倍。在一些案例中,推薦使用分層抽樣的方法,以確保相關(guān)類別的頻率在訓(xùn)練集和驗證折疊中大致一致,可以用StratifiedKFoldStratifiedShuffleSplit實現(xiàn)。

3.1.2.2.1 分層k折

? StratifiedKFold是k-fold的變種,它返回分層的折疊:每個集合的標(biāo)簽分布比例與整個數(shù)據(jù)集幾乎相同。

? 如下是一個在包含50個樣本,分為兩類別的不平衡數(shù)據(jù)集上,進(jìn)行3折交叉驗證的例子。該例子將展示每個類別的樣本量,并與KFold進(jìn)行比較。

>>> from sklearn.model_selection import StratifiedKFold, KFold
>>> import numpy as np
>>> X, y = np.ones((50, 1)), np.hstack(([0] * 45, [1] * 5))
>>> skf = StratifiedKFold(n_splits=3)
>>> for train, test in skf.split(X, y):
...     print('train -  {}   |   test -  {}'.format(
...         np.bincount(y[train]), np.bincount(y[test])))
train -  [30  3]   |   test -  [15  2]
train -  [30  3]   |   test -  [15  2]
train -  [30  4]   |   test -  [15  1]
>>> kf = KFold(n_splits=3)
>>> for train, test in kf.split(X, y):
...     print('train -  {}   |   test -  {}'.format(
...         np.bincount(y[train]), np.bincount(y[test])))
train -  [28  5]   |   test -  [17]
train -  [28  5]   |   test -  [17]
train -  [34]   |   test -  [11  5] 

? StratifiedKFold在訓(xùn)練集和測試集中保存了各類別的相同比例(大約1/10)。

? 下圖是交叉驗證原理的可視化。


? RepeatedStratifiedKFold可以在每一次循環(huán)中,重復(fù)n次不同隨機(jī)選擇的分層k折。

3.1.2.2.2 分層的隨機(jī)拆分

? StratifiedShuffleSplit是shuffleSplit的變種,它返回分層的樣本拆分,例如在拆分的各數(shù)據(jù)集中,保留與整個樣本數(shù)據(jù)集相同的樣本比例。

? 下圖為交叉驗證原理的可視化。


3.1.2.3 分組數(shù)據(jù)的交叉驗證迭代器

? 如潛在的樣本生成過程生成非獨(dú)立的樣本組,則獨(dú)立同分布的假設(shè)將不適用。

? 這樣的樣本分組是應(yīng)用于特定領(lǐng)域的。如下是一個從不同病人處收集的醫(yī)學(xué)數(shù)據(jù),每一位患者提供多個樣本。并且這些數(shù)據(jù)很可能依賴于各自的組。在這個例子中,每個樣本的患者id是其組標(biāo)識符。

? 上述案例中,在特定組上訓(xùn)練的模型,是否能很好的概括看不見的組將是關(guān)注重點(diǎn)。為了衡量它,需要確保驗證集中的所有樣本不會在與之配對的訓(xùn)練折疊中出現(xiàn)。

? 在隨后的交叉驗證數(shù)據(jù)劃分可以實現(xiàn)上述功能。樣本中的組標(biāo)識將通過groups參數(shù)進(jìn)行定義。

3.1.2.3.1 組k折

? GroupKFold是k折的一個變種,它確保相同的組不會同時出現(xiàn)在測試集和訓(xùn)練集中。例如,如果數(shù)據(jù)來自不同的受試者,且每個受試者提供多個樣本,同時,如果模型可以靈活學(xué)習(xí)高度具體化的特征,它就不能概括其它受試者的特征。GroupKFold方法使模型可以察覺到上述過擬合情形。

? 假設(shè)有三個受試者,每個受試者都有一個從1到3的關(guān)聯(lián)數(shù)字:

>>> from sklearn.model_selection import GroupKFold

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 8.8, 9, 10]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "d", "d", "d"]
>>> groups = [1, 1, 1, 2, 2, 2, 3, 3, 3, 3]

>>> gkf = GroupKFold(n_splits=3)
>>> for train, test in gkf.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[0 1 2 3 4 5] [6 7 8 9]
[0 1 2 6 7 8 9] [3 4 5]
[3 4 5 6 7 8 9] [0 1 2]  

? 每個受試者在不同的測試折中,相同的受試者不會同時出現(xiàn)在測試集和訓(xùn)練集中。需要注意的是,由于數(shù)據(jù)的不均衡問題,每個折疊含有不同的數(shù)據(jù)量。

? 下圖是交叉驗證原理的可視化。


3.1.2.3.2 留一組交叉驗證

? LeaveOneGroupOut是一個交叉驗證方案,它根據(jù)第三方提供的整數(shù)組的數(shù)組來提供樣本。這個組信息可用于編碼任意域特定的預(yù)定義交叉驗證折疊。

? 每一個訓(xùn)練集包含除特定組以外的所有樣本。

? 在多次實驗的例子中,LeaveOneGroupOut可以建立一個基于不同實驗的交叉驗證:創(chuàng)建一個除某一實驗樣本外的所有實驗樣本的訓(xùn)練集:

>>> from sklearn.model_selection import LeaveOneGroupOut

>>> X = [1, 5, 10, 50, 60, 70, 80]
>>> y = [0, 1, 1, 2, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3, 3]
>>> logo = LeaveOneGroupOut()
>>> for train, test in logo.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[2 3 4 5 6] [0 1]
[0 1 4 5 6] [2 3]
[0 1 2 3] [4 5 6] 

? 另外一個常見應(yīng)用是使用時間信息:例如,組可以是收集樣本的年份,從而允許對基于時間的拆分進(jìn)行交叉驗證。

3.1.2.3.3 留P組交叉驗證

? 對于訓(xùn)練/測試集而言,LeaveGroupOutLeaveOneGroupOut相類似,但移除與P組相關(guān)的數(shù)據(jù)。

? 留2組交叉驗證的例子:

>>> from sklearn.model_selection import LeavePGroupsOut

>>> X = np.arange(6)
>>> y = [1, 1, 1, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3]
>>> lpgo = LeavePGroupsOut(n_groups=2)
>>> for train, test in lpgo.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[4 5] [0 1 2 3]
[2 3] [0 1 4 5]
[0 1] [2 3 4 5] 

3.1.2.3.4 組亂序分割(Group Shuffle Split)

? GroupShuffleSplit迭代器表現(xiàn)為ShuffleSplit和LeavePGroupsOut的結(jié)合,并生成一個隨機(jī)分區(qū)的序列,并為每一個分組提供一個組子集。

? 如下示例:

>>> from sklearn.model_selection import GroupShuffleSplit

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 0.001]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "a"]
>>> groups = [1, 1, 2, 2, 3, 3, 4, 4]
>>> gss = GroupShuffleSplit(n_splits=4, test_size=0.5, random_state=0)
>>> for train, test in gss.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
...
[0 1 2 3] [4 5 6 7]
[2 3 6 7] [0 1 4 5]
[2 3 4 5] [0 1 6 7]
[4 5 6 7] [0 1 2 3] 

? 交叉驗證原理的可視化:


? 當(dāng)需要LeavePGroupsOut的功能時,這個分類非常有用,但是當(dāng)組的個數(shù)足夠大時,保留P組的分區(qū)成本將很高。在這種情形中,GroupShuffleSplit通過LeavePGroupsOut提供一個隨機(jī)(可重復(fù))的訓(xùn)練/測試集劃分。

3.1.2.4 預(yù)定義的折疊/驗證集

? 對于一些數(shù)據(jù)集,一個預(yù)定義的數(shù)據(jù)集劃分訓(xùn)練-和驗證折疊或一些交叉驗證折疊已經(jīng)存在。例如當(dāng)需要搜索超參數(shù)時,可以使用PredefinedSplit來使用這些折疊。

? 例如,當(dāng)使用驗證集時,設(shè)置所有樣本的test_fold為0,以將其歸為驗證集。并設(shè)置剩余樣本得到該參數(shù)為-1。

3.1.2.5 時間序列數(shù)據(jù)的交叉驗證

? 時間序列數(shù)據(jù)的特點(diǎn)是觀測值間的相關(guān)性與時間趨勢相關(guān)(自相關(guān))。但是,傳統(tǒng)的交叉驗證技術(shù)例如KFoldShuffleSplit均假定樣本的獨(dú)立同分布,并且會造成訓(xùn)練集和測試集間難以解釋的相關(guān)性(估計出較差的泛化誤差)在時間序列的數(shù)據(jù)集中。因此,評估模型對觀測“未來”值的時間序列數(shù)據(jù)至關(guān)重要,而不僅是用于訓(xùn)練模型。為了完成這個目標(biāo),可以使用TimeSeriesSplit

3.1.2.5.1 時間序列劃分

? TimeSeriesSplit是一個k-fold變種,它返回前k折作為訓(xùn)練集,同時第(k+1)折作為測試集。需要注意的是,與標(biāo)準(zhǔn)的交叉驗證法不同,連續(xù)的訓(xùn)練集是前面數(shù)據(jù)集的超集。并且,會將多余的數(shù)據(jù)放到用于訓(xùn)練模型的第一個訓(xùn)練分區(qū)。

? 這些分類可被用于固定時間被觀測的交叉驗證時間序列數(shù)據(jù)樣本。

? 例如在6個樣本上進(jìn)行3-split 時間序列交叉驗證:

>>> from sklearn.model_selection import TimeSeriesSplit

>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]])
>>> y = np.array([1, 2, 3, 4, 5, 6])
>>> tscv = TimeSeriesSplit(n_splits=3)
>>> print(tscv)
TimeSeriesSplit(max_train_size=None, n_splits=3)
>>> for train, test in tscv.split(X):
...     print("%s %s" % (train, test))
[0 1 2] [3]
[0 1 2 3] [4]
[0 1 2 3 4] [5] 

? 交叉驗證原理可視化如下所示:

 

3.1.3 shuffling的注意事項

? 如果數(shù)據(jù)的順序不是隨機(jī)的(例如具有相同類別標(biāo)簽的樣本連在一起),先把數(shù)據(jù)打亂對于獲得有價值的交叉驗證結(jié)果是有意義的。但是,如果樣本不是獨(dú)立同分布的,則結(jié)果可能相反。例如,如果樣本數(shù)據(jù)是關(guān)于“文章發(fā)表”的,并且發(fā)表時間符合時間序列,則打亂數(shù)據(jù)會導(dǎo)致模型過擬合及過高的驗證集分?jǐn)?shù):這是人為導(dǎo)致訓(xùn)練集與驗證集相類似(時間趨勢相似)的結(jié)果。

? 一些交叉驗證迭代器具有內(nèi)置選項在拆分?jǐn)?shù)據(jù)集前打亂數(shù)據(jù)索引。注意:

  • 相比直接打亂數(shù)據(jù),它消耗更少內(nèi)存。
  • 初始化是不打亂數(shù)據(jù),包括:通過給cross_val_score,網(wǎng)格搜索等定義cv=some_integer來執(zhí)行(分層的)K折交叉驗證。但train_test_split會返回一個隨機(jī)劃分。
  • random_state參數(shù)的初始值是None,意味著KFold(..., shuffle = True)每次迭代的結(jié)果不同。但是GridSearchCV調(diào)用fit方法,對同一組參數(shù)使用相同的打亂方式。
  • 如想獲取相同的拆分結(jié)果,需設(shè)置random_state為整數(shù)。

3.1.4 交叉驗證和模型選擇

? 交叉驗證迭代器也可以直接用于模型選擇,如獲得模型最佳超參數(shù)的網(wǎng)格搜索。下一節(jié)的話題是:調(diào)整估計器的超參數(shù)。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號