深度学习-如何把数据集划分成训练集和测试集

问题: 对于一个只包含mm个样例的数据集D={(x1,y1),(x2,y2),⋯,(xm,ym)D={(x1,y1),(x2,y2),⋯,(xm,ym),如何适当处理,从DD中产生训练集SS和测试集TT?

下面介绍三种常见的做法:

  • 留出法
  • 交叉验证法
  • 自助法

留出法(hold-out)

留出法直接将数据集DD划分为两个互斥的集合,其中一个集合作为训练集SS,留下的集合作为测试集TT,即D=S∪T,S∩T=∅D=S∪T,S∩T=∅。在SS上训练出模型后,用TT来评估其测试误差,作为对泛化误差的估计。以二分类任务为例,假设DD包含1000个样本,我们采取7/3分样,将其划分为SS包含700个样本,TT包含300个样本。

S/TS/T的划分要尽可能的保持数据分布的一致性(S/TS/T中的数据分布跟DD是一样的),才能避免因数据划分过程引入额外的偏差而对最终结果产生影响。例如,在分类任务中,至少要保持样本的类别比例相似。保留类别比例的采样方式,即分层采样(stratified sampling)。例如,DD中含有500个正例,500个反例,当采用分层采样获取70%的样本的训练集SS和30%的赝本的测试集TT时,则SS包含有350个正例和350个反例,TT有150个正例和150个反例。

给定样本比例,有多种划分方式对DD进行分割。如在上面的例子中,我们可以把DD的样本排序,然后把前350个正例放到SS中,也可以把后350个正例放入SS... 这种不同的划分将导致不同的训练/测试集,相应的模型评估也是有差别的。因此,使用留出法时,一般要采用若干次随机划分、重复进行实验评估后取平均值作为留出法的评估结果。例如进行100次随机划分,每次产生一个S/TS/T进行实验评估,得到100个结果,而留出法返回的则是这100个结果的平均。

常见做法将大约2/3~4/5的样本作为SS,剩下的作为TT。

通过调用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表示TT占30%, 那么SS占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)

交叉验证法将DD划分为kk个大小相似的互斥子集,即D=D1∪D2∪⋯∪Dk,Di∩Dj=∅(i≠j)D=D1∪D2∪⋯∪Dk,Di∩Dj=∅(i≠j)。每个子集DiDi都尽可能保持数据分布的一致性,即从DD中通过分层采样得到。然后,每次用k−1k−1个子集的并集作为SS,剩下的那个子集作为TT,这样可获得kk组S/TS/T,从而可进行kk次训练和测试,最终返回这kk个测试结果的平均。

交叉验证法评估结果的稳定性和保真性在很大程度上取决于kk的取值、为强调这一点,通常把交叉验证法称为“kk折交叉验证”(kk-fold cross validation)。kk最常用的取值是10,有时也取5和20等。

通过调用sklearn.model_selection.KFold按kk折交叉划分训练集和测试集:

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=2k=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]]  

当DD包含mm个样本,令k=mk=m,则得到交叉验证法的一个特例——留一法(Leave-One-Out,简称LOO)。留一法使用的SS只比DD少一个样本,所以在绝大多数情况下,实际评估结果与用DD训练的模型相似。因此,留一法被认为比较准确。但留一法对于大数据集,计算开销太大;另外也不见得永远比其他方法准确。

通过调用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)

在前两种方法中都保留部分样本作为TT用于测试,因此实际评估模型使用的训练集TT总是比期望评估模型使用的训练集DD小,这样会引入一些因训练样本规模不同而导致的估计偏差。

自助法,以自助采样(bootstrap sampling)为基础。对DD进行采样产生D′D′:每次随机从DD中挑选一个样本,将其拷贝一份放入D′D′中,保持DD不变,重复以上过程mm次。显然,DD中有部分样本会多次出现在D′D′中,而另一部分不会出现。样本在mm次采样中的始终不被采到的概率为(1−1m)m(1−1m)m,当m→∞m→∞时,(1−1m)m→1e≈0.368(1−1m)m→1e≈0.368。

通过自助法,DD中有36.8%不会出现D′D′中。于是我们把D′D′当作训练集SS,把D∖D′D∖D′当作测试集TT,这样实际评估模型与期望评估模型都为mm个样本,而仍有数据总量1/3的、没有出现在训练集中的样本用于测试。这样的测试结果为包外估计(out-of-bag estimate)。

自助法在数据集较小、难以划分S/TS/T时很有用。此外,自助法能从DD中产生不同的SS,对集成学习等方法有好吃。自助法产生的SS改变了DD的分布,会引入估计偏差。当数据量足够时,留出法和交叉验证法更常用、

你可能感兴趣的:(深度学习-如何把数据集划分成训练集和测试集)