如何把數據集划分成訓練集和測試集


本文主要內容來自周志華《機器學習》

本文中代碼

問題: 對於一個只包含\(m\)個樣例的數據集\(D=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\),如何適當處理,從\(D\)中產生訓練集\(S\)和測試集\(T\)?

下面介紹三種常見的做法:

  • 留出法
  • 交叉驗證法
  • 自助法

留出法(hold-out)

留出法直接將數據集\(D\)划分為兩個互斥的集合,其中一個集合作為訓練集\(S\),留下的集合作為測試集\(T\),即\(D=S \cup T, S \cap T=\emptyset\)。在\(S\)上訓練出模型后,用\(T\)來評估其測試誤差,作為對泛化誤差的估計。以二分類任務為例,假設\(D\)包含1000個樣本,我們采取7/3分樣,將其划分為\(S\)包含700個樣本,\(T\)包含300個樣本。

\(S/T\)的划分要盡可能的保持數據分布的一致性(\(S/T\)中的數據分布跟\(D\)是一樣的),才能避免因數據划分過程引入額外的偏差而對最終結果產生影響。例如,在分類任務中,至少要保持樣本的類別比例相似。保留類別比例的采樣方式,即分層采樣(stratified sampling)。例如,\(D\)中含有500個正例,500個反例,當采用分層采樣獲取70%的樣本的訓練集\(S\)和30%的贗本的測試集\(T\)時,則\(S\)包含有350個正例和350個反例,\(T\)有150個正例和150個反例。

給定樣本比例,有多種划分方式對\(D\)進行分割。如在上面的例子中,我們可以把\(D\)的樣本排序,然后把前350個正例放到\(S\)中,也可以把后350個正例放入\(S\)... 這種不同的划分將導致不同的訓練/測試集,相應的模型評估也是有差別的。因此,使用留出法時,一般要采用若干次隨機划分、重復進行實驗評估后取平均值作為留出法的評估結果。例如進行100次隨機划分,每次產生一個\(S/T\)進行實驗評估,得到100個結果,而留出法返回的則是這100個結果的平均。

常見做法將大約2/3~4/5的樣本作為\(S\),剩下的作為\(T\)

通過調用sklearn.model_selection.train_test_split按比例划分訓練集和測試集:

import numpy as np
from sklearn.model_selection import train_test_split

X, Y = np.arange(10).reshape((5, 2)), range(5)
print("X=", X)
print("Y=", Y)
X_train, X_test, Y_train, Y_test = train_test_split(
    X, Y, test_size=0.30, random_state=42)
print("X_train=", X_train)
print("X_test=", X_test)
print("Y_train=", Y_train)
print("Y_test=", Y_test)

其中test_size=0.30表示\(T\)占30%, 那么\(S\)占70%。運行結果:

X= [[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]
Y= range(0, 5)
X_train= [[4 5]
 [0 1]
 [6 7]]
X_test= [[2 3]
 [8 9]]
Y_train= [2, 0, 3]
Y_test= [1, 4]

交叉驗證法(cross validation)

交叉驗證法將\(D\)划分為\(k\)個大小相似的互斥子集,即\(D=D_1 \cup D_2 \cup \dots \cup D_k, D_i \cap D_j = \emptyset (i \not = j)\)。每個子集\(D_i\)都盡可能保持數據分布的一致性,即從\(D\)中通過分層采樣得到。然后,每次用\(k-1\)個子集的並集作為\(S\),剩下的那個子集作為\(T\),這樣可獲得\(k\)\(S/T\),從而可進行\(k\)次訓練和測試,最終返回這\(k\)個測試結果的平均。

交叉驗證法評估結果的穩定性和保真性在很大程度上取決於\(k\)的取值、為強調這一點,通常把交叉驗證法稱為“\(k\)折交叉驗證”(\(k\)-fold cross validation)。\(k\)最常用的取值是10,有時也取5和20等。

通過調用sklearn.model_selection.KFold\(k\)折交叉划分訓練集和測試集:

import numpy as np
from sklearn.model_selection import KFold

X= np.arange(10).reshape((5, 2))
print("X=", X)
kf = KFold(n_splits=2)
for train_index, test_index in kf.split(X):
    print('X_train:%s ' % X[train_index])
    print('X_test: %s ' % X[test_index])

其中n_splits=2表示\(k=2\)。運行結果:

X= [[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]
X_train:[[6 7]
 [8 9]] 
X_test: [[0 1]
 [2 3]
 [4 5]] 
X_train:[[0 1]
 [2 3]
 [4 5]] 
X_test: [[6 7]
 [8 9]]  

\(D\)包含\(m\)個樣本,令\(k=m\),則得到交叉驗證法的一個特例——留一法(Leave-One-Out,簡稱LOO)。留一法使用的\(S\)只比\(D\)少一個樣本,所以在絕大多數情況下,實際評估結果與用\(D\)訓練的模型相似。因此,留一法被認為比較准確。但留一法對於大數據集,計算開銷太大;另外也不見得永遠比其他方法准確。

通過調用sklearn.model_selection.LeaveOneOut按留一法划分訓練集和測試集:

import numpy as np
from sklearn.model_selection import LeaveOneOut

X= np.arange(10).reshape((5, 2))
print("X=", X)
loo = LeaveOneOut()
for train_index, test_index in loo.split(X):
    print('X_train:%s ' % X[train_index])
    print('X_test: %s ' % X[test_index])

運行結果:

X= [[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]
X_train:[[2 3]
 [4 5]
 [6 7]
 [8 9]] 
X_test: [[0 1]] 
X_train:[[0 1]
 [4 5]
 [6 7]
 [8 9]] 
X_test: [[2 3]] 
X_train:[[0 1]
 [2 3]
 [6 7]
 [8 9]] 
X_test: [[4 5]] 
X_train:[[0 1]
 [2 3]
 [4 5]
 [8 9]] 
X_test: [[6 7]] 
X_train:[[0 1]
 [2 3]
 [4 5]
 [6 7]] 
X_test: [[8 9]] 

自助法(bootstrapping)

在前兩種方法中都保留部分樣本作為\(T\)用於測試,因此實際評估模型使用的訓練集\(T\)總是比期望評估模型使用的訓練集\(D\)小,這樣會引入一些因訓練樣本規模不同而導致的估計偏差。

自助法,以自助采樣(bootstrap sampling)為基礎。對\(D\)進行采樣產生\(D^{\prime}\):每次隨機從\(D\)中挑選一個樣本,將其拷貝一份放入\(D^{\prime}\)中,保持\(D\)不變,重復以上過程\(m\)次。顯然,\(D\)中有部分樣本會多次出現在\(D^{\prime}\)中,而另一部分不會出現。樣本在\(m\)次采樣中的始終不被采到的概率為\((1-\frac{1}{m})^m\),當\(m \to \infty\)時,\((1-\frac{1}{m})^m \to \frac{1}{e} \approx 0.368\)

通過自助法,\(D\)中有36.8%不會出現\(D^{\prime}\)中。於是我們把\(D^{\prime}\)當作訓練集\(S\),把\(D \setminus D^{\prime}\)當作測試集\(T\),這樣實際評估模型與期望評估模型都為\(m\)個樣本,而仍有數據總量1/3的、沒有出現在訓練集中的樣本用於測試。這樣的測試結果為包外估計(out-of-bag estimate)。

自助法在數據集較小、難以划分\(S/T\)時很有用。此外,自助法能從\(D\)中產生不同的\(S\),對集成學習等方法有好吃。自助法產生的\(S\)改變了\(D\)的分布,會引入估計偏差。當數據量足夠時,留出法和交叉驗證法更常用、


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM