我有4类的数据,我正试图建立一个分类器。一类有~1000个向量,另一类有~10^4,第三类有~10^5,第四类有~10^6。我希望使用交叉验证,所以我查看了scikit-learn文档。
我的第一次尝试是使用StratifiedShuffleSplit,但这为每个类提供了相同的百分比,使类仍然严重不平衡。
是否有一种方法来做交叉验证,但与类平衡训练和测试集?
作为旁注,我无法计算出StratifiedShuffleSplit和StratifiedKFold之间的区别。这些描述在我看来很相似。
我的第一次尝试是使用StratifiedShuffleSplit,但这为每个类提供了相同的百分比,使类仍然严重不平衡。
我觉得你对分层策略的作用感到困惑,但你需要展示你的代码和结果来确定发生了什么(与原始集合中的百分比相同,还是与返回的训练/测试集合中的百分比相同?)第一个是它应该是怎样的)。
作为旁注,我无法计算出StratifiedShuffleSplit和StratifiedKFold之间的区别。这些描述在我看来很相似。
其中一个肯定可以工作。第一个的描述肯定有点令人困惑,但以下是它们的功能。
StratifiedShuffleSplit
提供训练/测试索引来分割训练测试集中的数据。
这意味着它将你的数据分成训练集和测试集。分层的部分意味着百分比将在这个分割中保持。因此,如果您的数据的10%
在类1和90%
在类2,这将确保您的训练集的10%
将在类1和90%
将在类2。测试集也一样。
你的帖子听起来像是你想要测试集中每个类的50%
。这不是分层的作用,分层维持了原始的百分比。你应该维护它们,否则你会给自己一个与分类器性能无关的想法:谁会关心它对50/50
分割的分类有多好,当在实践中你会看到10/90
分割的时候?
StratifiedKFold
这个交叉验证对象是返回分层折叠的KFold的一个变体。通过保留每个类别的样本百分比来进行折叠。
见k-fold交叉验证。没有分层,它只是将您的数据分成k
折叠。然后,每个fold 1 <= i <= k
使用一次作为测试集,其他的用于训练。最后对结果求平均值。这类似于运行ShuffleSplit
k
次。
分层将确保每个类别在整个数据中的百分比在每个单独的折叠中相同(或非常接近)。
有很多文献都是关于阶级不平衡的。一些简单易用的方法包括使用类权重和分析ROC曲线。我建议使用以下资源作为起点:
- 一个scikit-learn使用类权值的例子。
- 关于实现神经网络处理不平衡数据的quora问题。
- 这个统计数据。
K-Fold CV
K-Fold CV通过将数据随机划分为k
(相当)相等的分区来工作。如果你的数据在像[0,1,0,1,0,1,0,1,0,1]
这样的类之间是均衡的,那么随机抽样(或不进行替换)将给你0
和1
大约相等的样本量。
但是,如果您的数据更像[0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0]
当一个类超过代表数据时,没有加权抽样的k倍CV会给你错误的结果。
如果你使用普通的k-fold CV而不调整均匀抽样的抽样权值,那么你会得到类似
的结果## k-fold CV
k = 5
splits = np.array_split(y, k)
for i in range(k):
print(np.mean(splits[i]))
[array([0, 0, 0, 0, 0, 0, 0]),
array([0, 0, 0, 0, 0, 0, 0]),
array([0, 0, 0, 0, 0, 0]),
array([0, 0, 0, 0, 0, 0]),
array([0, 1, 1, 1, 1, 1])]
,其中有明显的分裂,没有两个类的有用表示。
k-fold CV的要点是在所有数据子集上训练/测试模型,而在每次试验中留下1个子集并在k-1个子集上训练。
在这个场景中,您希望使用分层分割。在上述数据集中,0s
有27个,1s
有5个。如果你想计算k=5 CV,将1
的分层分成5个子集是不合理的。一个更好的解决方案是把它分成k <5个子集,如2。0s
的地层可以保持k=5的分裂,因为它更大。然后,在训练时,您将从数据集中获得一个简单的2 x 5
产品。下面是一些代码来说明
from itertools import product
for strata, iterable in groupby(y):
data = np.array(list(iterable))
if strata == 0:
zeros = np.array_split(data, 5)
else:
ones = np.array_split(data, 2)
cv_splits = list(product(zeros, ones))
print(cv_splits)
m = len(cv_splits)
for i in range(2):
for j in range(5):
data = np.concatenate((ones[-i+1], zeros[-j+1]))
print("Leave out ONES split {}, and Leave out ZEROS split {}".format(i,j))
print("train on: ", data)
print("test on: ", np.concatenate((ones[i], zeros[j])))
Leave out ONES split 0, and Leave out ZEROS split 0
train on: [1 1 0 0 0 0 0 0]
test on: [1 1 1 0 0 0 0 0 0]
Leave out ONES split 0, and Leave out ZEROS split 1
train on: [1 1 0 0 0 0 0 0]
...
Leave out ONES split 1, and Leave out ZEROS split 4
train on: [1 1 1 0 0 0 0 0]
test on: [1 1 0 0 0 0 0]
该方法可以完成将数据分割成分区,所有分区最终都被留出来进行测试。值得注意的是,并非所有的统计学习方法都允许加权,因此调整CV等方法对于考虑抽样比例至关重要。
- 参考文献:James, G., Witten, D., Hastie, T., &;Tibshirani, R.(2013)。统计学习简介:在r中的应用。