機器學習sklearn(二十三): 模型評估(三)交叉驗證:評估估算器的表現(三) 交叉驗證迭代器
接下來的部分列出了一些用於生成索引標號,用於在不同的交叉驗證策略中生成資料劃分的工具。
1. 交叉驗證迭代器–迴圈遍歷資料
假設一些資料是獨立的和相同分佈的 (i.i.d) 假定所有的樣本來源於相同的生成過程,並假設生成過程沒有記憶過去生成的樣本。
在這種情況下可以使用下面的交叉驗證器。
注意
儘管 i.i.d 資料是機器學習理論中的一個常見假設,但在實踐中很少成立。如果知道樣本是使用時間相關的過程生成的,則使用time-series aware cross-validation scheme更安全。 同樣,如果我們知道生成過程具有 group structure (群體結構)(從不同 subjects(主體) , experiments(實驗), measurement devices (測量裝置)收集的樣本),則使用
1.1. K 折
KFold
將所有的樣例劃分為個組,稱為摺疊 (fold) (如果, 這等價於Leave One Out(留一)策略),都具有相同的大小(如果可能)。預測函式學習時使用個摺疊中的資料,最後一個剩下的摺疊會用於測試。
在 4 個樣例的資料集上使用 2-fold 交叉驗證的示例:
>>> 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不被分類所影響.
每個摺疊由兩個 arrays 組成,第一個作為training set,另一個作為test set。 由此,可以通過使用 numpy 的索引建立訓練/測試集合:
>>> 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]
1.2. 重複 K-折交叉驗證
RepeatedKFold
重複 K-Fold n 次。當需要執行時可以使用它KFold
n 次,在每次重複中產生不同的分割。
2折 K-Fold 重複 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
在每個重複中以不同的隨機化重複 n 次分層的 K-Fold 。
1.3. 留一交叉驗證 (LOO)
LeaveOneOut
(或 LOO) 是一個簡單的交叉驗證。每個學習集都是通過除了一個樣本以外的所有樣本建立的,測試集是被留下的樣本。 因此,對於個樣本,我們有個不同的訓練集和個不同的測試集。這種交叉驗證程式不會浪費太多資料,因為只有一個樣本是從訓練集中刪除掉的:
>>> 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 潛在的使用者選擇模型應該權衡一些已知的警告。 當與折交叉驗證進行比較時,可以從樣本中構建模型,而不是模型,其中。 此外,每個在個樣本而不是在上進行訓練。在兩種方式中,假設不是太大,並且, LOO 比折交叉驗證計算開銷更加昂貴。
就精度而言, LOO 經常導致較高的方差作為測試誤差的估計器。直觀地說,因為個樣本中的被用來構建每個模型,摺疊構建的模型實際上是相同的,並且是從整個訓練集建立的模型。
但是,如果學習曲線對於所討論的訓練大小是陡峭的,那麼 5- 或 10- 折交叉驗證可以泛化誤差增高。
作為一般規則,大多數作者和經驗證據表明, 5- 或者 10- 交叉驗證應該優於 LOO 。
參考資料:
- http://www.faqs.org/faqs/ai-faq/neural-nets/part3/section-12.html;
- T. Hastie, R. Tibshirani, J. Friedman,The Elements of Statistical Learning, Springer 2009
- L. Breiman, P. SpectorSubmodel selection and evaluation in regression: The X-random case, International Statistical Review 1992;
- R. Kohavi,A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection, Intl. Jnt. Conf. AI
- R. Bharat Rao, G. Fung, R. Rosales,On the Dangers of Cross-Validation. An Experimental Evaluation, SIAM 2008;
- G. James, D. Witten, T. Hastie, R Tibshirani,An Introduction to Statistical Learning, Springer 2013.
1.4. 留 P 交叉驗證 (LPO)
LeavePOut
與LeaveOneOut
非常相似,因為它通過從整個集合中刪除個樣本來建立所有可能的 訓練/測試集。對於個樣本,這產生了個 訓練-測試 對。與LeaveOneOut
和KFold
不同,當時,測試集會重疊。
在有 4 個樣例的資料集上使用 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]
1.5. 隨機排列交叉驗證 a.k.a. Shuffle & Split
ShuffleSplit
迭代器 將會生成一個使用者給定數量的獨立的訓練/測試資料劃分。樣例首先被打散然後劃分為一對訓練測試集合。
可以通過設定明確的random_state
,使得偽隨機生成器的結果可以重複。
這是一個使用的小示例:
>>> from sklearn.model_selection import ShuffleSplit >>> X = np.arange(5) >>> ss = ShuffleSplit(n_splits=3, test_size=0.25, ... random_state=0) >>> for train_index, test_index in ss.split(X): ... print("%s %s" % (train_index, test_index)) ... [1 3 4] [2 0] [1 4 3] [0 2] [4 0 2] [1 3]
下面是交叉驗證行為的視覺化。注意ShuffleSplit不受分類的影響。
ShuffleSplit
可以替代KFold
交叉驗證,因為其提供了細緻的訓練 / 測試劃分的 數量和樣例所佔的比例等的控制。
2. 基於類標籤、具有分層的交叉驗證迭代器
一些分類問題在目標類別的分佈上可能表現出很大的不平衡性:例如,可能會出現比正樣本多數倍的負樣本。在這種情況下,建議採用如StratifiedKFold
和StratifiedShuffleSplit
中實現的分層抽樣方法,確保相對的類別頻率在每個訓練和驗證 摺疊 中大致保留。
2.1. 分層 k 折
StratifiedKFold
是k-fold的變種,會返回stratified(分層)的摺疊:每個小集合中, 各個類別的樣例比例大致和完整資料集中相同。
在有 10 個樣例的,有兩個略不均衡類別的資料集上進行分層 3-fold 交叉驗證的示例:
>>> from sklearn.model_selection import StratifiedKFold >>> X = np.ones(10) >>> y = [0, 0, 0, 0, 1, 1, 1, 1, 1, 1] >>> skf = StratifiedKFold(n_splits=3) >>> for train, test in skf.split(X, y): ... print("%s %s" % (train, test)) [2 3 6 7 8 9] [0 1 4 5] [0 1 3 4 5 8 9] [2 6 7] [0 1 2 4 5 6 7] [3 8 9]
下面是該交叉驗證方法的視覺化。
RepeatedStratifiedKFold
可用於在每次重複中用不同的隨機化重複分層 K-Fold n 次。
2.2. 分層隨機 Split
下面是該交叉驗證方法的視覺化。
StratifiedShuffleSplit
是ShuffleSplit的一個變種,會返回直接的劃分,比如: 建立一個劃分,但是劃分中每個類的比例和完整資料集中的相同。
2.3. 用於分組資料的交叉驗證迭代器
如果潛在的生成過程產生依賴樣本的 groups ,那麼 i.i.d. 假設將會被打破。
這樣的資料分組是特定於域的。一個示例是從多個患者收集醫學資料,從每個患者身上採集多個樣本。而這樣的資料很可能取決於個人群體。 在我們的示例中,每個樣本的患者 ID 將是其 group identifier (組識別符號)。
在這種情況下,我們想知道在一組特定的 groups 上訓練的模型是否能很好地適用於看不見的 group 。為了衡量這一點,我們需要確保驗證物件中的所有樣本來自配對訓練摺疊中完全沒有表示的組。
下面的交叉驗證分離器可以用來做到這一點。 樣本的 grouping identifier (分組識別符號) 通過groups
引數指定。
2.3.1. 組 k-fold
GroupKFold
是 k-fold 的變體,它確保同一個 group 在測試和訓練集中都不被表示。 例如,如果資料是從不同的 subjects 獲得的,每個 subject 有多個樣本,並且如果模型足夠靈活以高度人物指定的特徵中學習,則可能無法推廣到新的 subject 。GroupKFold
可以檢測到這種過擬合的情況。 Imagine you have three subjects, each with an associated number from 1 to 3:
>>> 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]
每個 subject 都處於不同的測試階段,同一個科目從來沒有在測試和訓練過程中。請注意,由於資料不平衡,摺疊的大小並不完全相同。
下面是該交叉驗證方法的視覺化。
2.3.2. 留一組交叉驗證
LeaveOneGroupOut
是一個交叉驗證方案,它根據第三方提供的 array of integer groups (整陣列的陣列)來提供樣本。這個組資訊可以用來編碼任意域特定的預定義交叉驗證摺疊。
每個訓練集都是由除特定組別以外的所有樣本構成的。
例如,在多個實驗的情況下,LeaveOneGroupOut
可以用來根據不同的實驗建立一個交叉驗證:我們使用除去一個實驗的所有實驗的樣本建立一個訓練集:
>>> 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]
另一個常見的應用是使用時間資訊:例如,組可以是收集樣本的年份,從而允許與基於時間的分割進行交叉驗證。
2.3.3. 留 P 組交叉驗證
LeavePGroupsOut
類似於LeaveOneGroupOut
,但為每個訓練/測試集刪除與組有關的樣本。
Leave-2-Group Out 的示例:
>>> 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]
2.3.4. Group Shuffle Split
GroupShuffleSplit
迭代器是ShuffleSplit
和LeavePGroupsOut
的組合,它生成一個隨機劃分分割槽的序列,其中為每個分組提供了一個組子集。
這是使用的示例:
>>> 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]
下面是該交叉驗證方法的視覺化。
當需要LeavePGroupsOut
的操作時,這個類的資訊是很有必要的,但是 組 的數目足夠大,以至於用組生成所有可能的分割槽將會花費很大的代價。在這種情況下,GroupShuffleSplit
通過LeavePGroupsOut
提供了一個隨機(可重複)的訓練 / 測試劃分取樣。
2.4. 預定義的摺疊 / 驗證集
對一些資料集,一個預定義的,將資料劃分為訓練和驗證集合或者劃分為幾個交叉驗證集合的劃分已經存在。 可以使用PredefinedSplit
來使用這些集合來搜尋超引數。
比如,當使用驗證集合時,設定所有驗證集合中的樣例的test_fold
為 0,而將其他樣例設定為 -1 。
2.5. 交叉驗證在時間序列資料中應用
時間序列資料的特點是時間 (autocorrelation(自相關性)) 附近的觀測之間的相關性。 然而,傳統的交叉驗證技術,例如KFold
和ShuffleSplit
假設樣本是獨立的且分佈相同的,並且在時間序列資料上會導致訓練和測試例項之間不合理的相關性(產生廣義誤差的估計較差)。 因此,對 “future(未來)” 觀測的時間序列資料模型的評估至少與用於訓練模型的觀測模型非常重要。為了達到這個目的,一個解決方案是由TimeSeriesSplit
提供的。
2.5.1. 時間序列分割
TimeSeriesSplit
是k-fold的一個變體,它首先返回折作為訓練資料集,並且折作為測試資料集。 請注意,與標準的交叉驗證方法不同,連續的訓練集是超越前者的超集。 另外,它將所有的剩餘資料新增到第一個訓練分割槽,它總是用來訓練模型。
這個類可以用來交叉驗證以固定時間間隔觀察到的時間序列資料樣本。
對具有 6 個樣本的資料集進行 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. A note on shuffling
如果資料的順序不是任意的(比如說,相同標籤的樣例連續出現),為了獲得有意義的交叉驗證結果,首先對其進行 打散是很有必要的。然而,當樣例不是獨立同分布時打散則是不可行的。例如:樣例是相關的文章,以他們發表的時間 進行排序,這時候如果對資料進行打散,將會導致模型過擬合,得到一個過高的驗證分數:因為驗證樣例更加相似(在時間上更接近) 於訓練資料。
一些交叉驗證迭代器, 比如KFold
,有一個內建的在劃分資料前進行資料索引打散的選項。注意:
- 這種方式僅需要很少的記憶體就可以打散資料。
- 預設不會進行打散,包括設定
cv=some_integer
(直接)k 摺疊交叉驗證的cross_val_score
, 表格搜尋等。注意train_test_split
會返回一個隨機的劃分。 - 引數
random_state
預設設定為None
,這意為著每次進行KFold(..., shuffle=True)
時,打散都是不同的。 然而,GridSearchCV
通過呼叫fit
方法驗證時,將會使用相同的打散來訓練每一組引數。 - 為了保證結果的可重複性(在相同的平臺上),應該給
random_state
設定一個固定的值。
4. 交叉驗證和模型選擇
交叉驗證迭代器可以通過網格搜尋得到最優的模型超引數,從而直接用於模型的選擇。 這是另一部分調整估計器的超引數的主要內容。