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

2023-02-20 14:09 更新

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


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

? 如:使用鶯尾花數(shù)據(jù)集(iris data)擬合一個(gè)線性支持向量機(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ù)來(lái)訓(xùn)練模型,并用剩余40%的數(shù)據(jù)來(lái)對(duì)模型分類效果進(jìn)行評(píng)估:

>>> 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)評(píng)估器對(duì)不同的模型參數(shù)(“超參數(shù)”)進(jìn)行評(píng)估時(shí),如參數(shù)C是支持向量機(jī)的一個(gè)超參數(shù),在模型未調(diào)整前,仍有過(guò)擬合的風(fēng)險(xiǎn),因?yàn)閰?shù)的選擇決定模型的最佳表現(xiàn)。然而,通過(guò)這種方式,測(cè)試集中的信息仍有可能會(huì)“泄露”到模型中,導(dǎo)致評(píng)估指標(biāo)不能概括模型的評(píng)估能力。通過(guò)將已知數(shù)據(jù)集劃分一部分為“驗(yàn)證集”可以解決上述問(wèn)題:在訓(xùn)練數(shù)據(jù)上對(duì)模型進(jìn)行訓(xùn)練,利用驗(yàn)證集對(duì)模型進(jìn)行評(píng)估。當(dāng)“實(shí)驗(yàn)”得到一個(gè)較好的成績(jī)時(shí),在測(cè)試集上完成模型的最終評(píng)估。 

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

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

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

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

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


3.1.1 計(jì)算交叉驗(yàn)證的指標(biāo)

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

? 隨后的例子將會(huì)說(shuō)明在鶯尾花(iris)數(shù)據(jù)集上,如何通過(guò)劃分?jǐn)?shù)據(jù)集、訓(xùn)練模型和計(jì)算5次交叉驗(yàn)證的得分,從而估計(jì)一個(gè)線性核支持向量機(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)的交叉驗(yàn)證迭代的輸出結(jié)果是參數(shù)為score的估計(jì)器,通過(guò)修改scoring的參數(shù)可以修改估計(jì)器:

>>> 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)簽是均衡的,因此計(jì)算的準(zhǔn)確率和F1-score幾乎相等。

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

? 也可以通過(guò)傳入交叉驗(yàn)證迭代器,來(lái)使用交叉驗(yàn)證的策略對(duì)模型進(jìn)行評(píng)估,如:

>>> 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í)使用可迭代的數(shù)據(jù)集拆分(訓(xùn)練集,測(cè)試集)方式作為數(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)化

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

>>> 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庫(kù)使編寫估計(jì)器更加容易,在交叉驗(yàn)證下提供這種方法:

>>> 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ù)合估計(jì)器。

3.1.1.1 交叉驗(yàn)證函數(shù)和多指標(biāo)評(píng)估

cross_validatecross_val_score存在兩方面不同:

  • 它允許制定多個(gè)評(píng)估指標(biāo),

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

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

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

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

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

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

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

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

    >>> 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.        ]) 

    也可以用字典映射一個(gè)預(yù)定義的評(píng)分器或者自定義評(píng)分器函數(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àn)證的預(yù)測(cè)值

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

警告:不正確使用cross_val_predict的報(bào)警

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

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

3.1.2 交叉驗(yàn)證迭代器

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

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

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

? 隨后的交叉驗(yàn)證會(huì)被用于如下的例子。

注意:

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

3.1.2.1.1 K-fold

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

例如四個(gè)樣本集的2折交叉驗(yàn)證:

>>> 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]

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


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

>>> 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的情形,在每個(gè)循環(huán)中會(huì)生成不同的分組。

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是在每個(gè)重復(fù)中以不同的隨機(jī)化重復(fù)n次分層的K-Fold。

3.1.2.1.3 Leave One Out(LOO)

? LeaveOneOut(或LOO)是一個(gè)簡(jiǎn)單的交叉驗(yàn)證。每個(gè)學(xué)習(xí)集都是去除一個(gè)樣本后的剩余樣本,測(cè)試集是余下樣本。因此,對(duì)于n個(gè)樣本而言,就會(huì)有n個(gè)不同的訓(xùn)練集和n個(gè)不同的測(cè)試集。這個(gè)交叉驗(yàn)證的過(guò)程不會(huì)浪費(fèi)很多數(shù)據(jù),因?yàn)橹挥幸粋€(gè)數(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交叉驗(yàn)證做比較時(shí),當(dāng)n>k時(shí),留一法可以從n個(gè)樣本中構(gòu)建n個(gè)模型,而不是k個(gè)模型。進(jìn)一步,每一個(gè)模型都是在n-1個(gè)樣本上進(jìn)行訓(xùn)練,而不是在(k-1)n/k。兩種方法都假設(shè)k不是很大,并且k<n,LOO比k-fold交叉驗(yàn)證的計(jì)算成本高。

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

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

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

參考文獻(xiàn):

3.1.2.1.4 Leave P Out(LPO)

? LeavePOutLeaveOneOut相類似,因?yàn)樗ㄟ^(guò)從整個(gè)數(shù)據(jù)集中,刪除p個(gè)樣本點(diǎn)來(lái)創(chuàng)建所有可能的訓(xùn)練集/測(cè)試集。對(duì)于n個(gè)樣本量,它產(chǎn)生了

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

? 在只有四個(gè)樣本的數(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ī)排列交叉驗(yàn)證a.k.a. Shuffle & Split:

ShuffleSplit

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

通過(guò)設(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]

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


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

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

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

3.1.2.2.1 分層k折

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

? 如下是一個(gè)在包含50個(gè)樣本,分為兩類別的不平衡數(shù)據(jù)集上,進(jìn)行3折交叉驗(yàn)證的例子。該例子將展示每個(gè)類別的樣本量,并與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)練集和測(cè)試集中保存了各類別的相同比例(大約1/10)。

? 下圖是交叉驗(yàn)證原理的可視化。


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

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

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

? 下圖為交叉驗(yàn)證原理的可視化。


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

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

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

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

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

3.1.2.3.1 組k折

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

? 假設(shè)有三個(gè)受試者,每個(gè)受試者都有一個(gè)從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]  

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

? 下圖是交叉驗(yàn)證原理的可視化。


3.1.2.3.2 留一組交叉驗(yàn)證

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

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

? 在多次實(shí)驗(yàn)的例子中,LeaveOneGroupOut可以建立一個(gè)基于不同實(shí)驗(yàn)的交叉驗(yàn)證:創(chuàng)建一個(gè)除某一實(shí)驗(yàn)樣本外的所有實(shí)驗(yàn)樣本的訓(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] 

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

3.1.2.3.3 留P組交叉驗(yàn)證

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

? 留2組交叉驗(yàn)證的例子:

>>> 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é)合,并生成一個(gè)隨機(jī)分區(qū)的序列,并為每一個(gè)分組提供一個(gè)組子集。

? 如下示例:

>>> 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] 

? 交叉驗(yàn)證原理的可視化:


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

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

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

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

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

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

3.1.2.5.1 時(shí)間序列劃分

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

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

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

>>> 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] 

? 交叉驗(yàn)證原理可視化如下所示:

 

3.1.3 shuffling的注意事項(xiàng)

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

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

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

3.1.4 交叉驗(yàn)證和模型選擇

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


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)