训练集和测试集的分布差距太大有好的处理方法吗?
机器学习常见步骤
三种数据集的含义
常见的划分方法
神经网络在网络结构确定的情况下,有两部分影响模型最终的性能,一是普通参数(比如权重w和偏置b),另一个是超参数(例如学习率,网络层数)。普通参数我们在训练集上进行训练,超参数我们一般人工指定(比较不同超参数的模型在验证集上的性能)。那为什么我们不像普通参数一样在训练集上训练超参数呢?(花书给出了解答)一是超参数一般难以优化(无法像普通参数一样通过梯度下降的方式进行优化)。二是超参数很多时候不适合在训练集上进行训练,例如:如果在训练集上训练能控制模型容量的超参数,这些超参数总会被训练成使得模型容量最大的参数(因为模型容量越大,训练误差越小),所以训练集上训练超参数的结果就是模型绝对过拟合。
正因为超参数无法在训练集上进行训练,因此我们单独设立了一个验证集,用于选择(人工训练)最优的超参数。因为验证集是用于选择超参数的,因此验证集和训练集是独立不重叠的。
测试集是用于在完成神经网络训练过程后,为了客观评价模型在其未见过(未曾影响普通参数和超参数选择)的数据上的性能,因此测试与验证集和训练集之间也是独立不重叠的,而且测试集不能提出对参数或者超参数的修改意见,只能作为评价网络性能的一个指标。
从训练集中划分出一部分作为验证集,该部分不用于训练,作为评价模型generalization error,而训练集与验证集之间的误差作为data mismatch error,表示数据分布不同引起的误差。
这种划分方式有利于保证:数据具有相同的分布
如果训练集和测试集的数据分布可能不相同,那么必定会导致一个问题,模型在训练集上的表现会非常的好,而在测试集上表现可能不会那么理想。
通过训练数据来训练模型,就是希望模型能够从训练集中学习到数据的分布,如果训练集和测试集数据不在同一个分布中,那么模型在测试集上的表现肯定是不会理想的。
训练集高分,测试集预测提交后发现分数很低,为什么?有可能是训练集和测试集分布不一致,导致模型过拟合训练集,个人很不喜欢碰到这种线下不错但线上抖动过大的比赛,有种让你感觉好像在“碰运气”,看谁“碰”对了测试集的分布。但实际是有方法可循的,而不是说纯碰运气。本文我将从“训练/测试集分布不一致问题”的发生原因讲起,然后罗列判断该问题的方法和可能的解决手段。
一、发生原因
训练集和测试集分布不一致也被称作数据集偏移(Dataset Shift)。西班牙格拉纳达大学Francisco Herrera教授在他PPT[1]里提到数据集偏移有三种类型:
协变量偏移(Covariate Shift): 独立变量的偏移,指训练集和测试集的输入服从不同分布,但背后是服从同一个函数关系,如图1所示。
先验概率偏移(Prior Probability Shift): 目标变量的偏移。
概念偏移(Concept Shift): 独立变量和目标变量之间关系的偏移。
图1:协变量偏移
最常见的有两种原因[1]:
样本选择偏差(Sample Selection Bias): 训练集是通过有偏方法得到的,例如非均匀选择(Non-uniform Selection),导致训练集无法很好表征的真实样本空间。
环境不平稳(Non-stationary Environments): 当训练集数据的采集环境跟测试集不一致时会出现该问题,一般是由于时间或空间的改变引起的。
在分类任务上,有时候官方随机划分数据集,没有考虑类别平衡问题,例如: 训练集类别A数据量远多于类别B,而测试集相反,这类样本选择偏差问题会导致训练好的模型在测试集上鲁棒性很差,因为训练集没有很好覆盖整个样本空间。此外,除了目标变量,输入特征也可能出现样本选择偏差问题,比如要预测泰坦尼克号乘客存活率,而训练集输入特征里“性别”下更多是男性,而测试集里“性别”更多是女性,这样也会导致模型在测试集上表现差。
样本选择偏差也有些特殊的例子,之前我参加阿里天池2021“AI Earth”人工智能创新挑战赛[2],官方提供两类数据集作为训练集,分别是CMIP模拟数据和SODA真实数据,然后测试集又是SODA真实数据,CMIP模拟数据是通过系列气象模型仿真模拟得到的,即有偏方法,但选手都会选择将模拟数据加入训练,因为训练集真实数据太少了,可模拟数据的加入也无可避免的引入了样本选择偏差。
聊完样本选择偏移,我们聊下环境不平稳带来的数据偏移,我想最常见是在时序比赛里了吧,用历史时序数据预测未来时序,未来突发事件很可能带来时序的不稳定表现,这便带来了分布差异。环境因素不仅限于时间和空间,还有数据采集设备、标注人员等。
二、判断方法
1. KDE (核密度估计)分布图
当我们一想到要对比训练集和测试集的分布,便是画概率密度函数直方图,但直方图看分布有两点缺陷: 受bin宽度影响大和不平滑,因此多数人会偏向于使用核密度估计图(Kernel Density Estimation, KDE),KDE是非参数检验,用于估计分布未知的密度函数,相比于直方图,它受bin影响更小,绘图呈现更平滑,易于对比数据分布。我研究生的有一门课的小作业有要去对比直方图和KDE图,相信这个能帮助大家更直观了解到它们的差异:
图2:心脏疾病患者最大心率的概率密度函数分布图,数据源自UCI ML开放数据集
这里在略微细讲下KDE,我们先看KDE函数:
图3:生成KDE的过程呈现[3]
言归正传,对比训练集和测试集特征分布时,我们可以用seaborn.kdeplot()[4]进行绘图可视化,样例图和代码如下:
图4:不同数据集下的KDE对比
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# 创建样例特征
train_mean, train_cov = [0, 2], [(1, .5), (.5, 1)]
test_mean, test_cov = [0, .5], [(1, 1), (.6, 1)]
train_feat, _ = np.random.multivariate_normal(train_mean, train_cov, size=50).T
test_feat, _ = np.random.multivariate_normal(test_mean, test_cov, size=50).T
# 绘KDE对比分布
sns.kdeplot(train_feat, shade = True, color='r', label = 'train')
sns.kdeplot(test_feat, shade = True, color='b', label = 'test')
plt.xlabel('Feature')
plt.legend()
plt.show()
2.KS检验
KDE是PDF来对比,而KS检验是基于CDF(累计分布函数Cumulative Distribution Function)来检验两个数据分布是否一致,它也是非参数检验方法(即不知道数据分布情况)。两条不同数据集下的CDF曲线,它们最大垂直差值可用作描述分布差异(见下图5中的D)。
图5:不同数据集下的CDF对比[5]
调用scipy.stats.ks_2samp()[6]可轻松得到KS的统计值(最大垂直差)和假设检验下的p值:
from scipy import statsstats.ks_2samp(train_feat, test_feat)输出:KstestResult(statistic=0.2, pvalue=0.2719135601522248)
若KS统计值小且p值大,则我们可以接受KS检验的原假设H0,即两个数据分布一致。上面样例数据的统计值较低,p值大于10%但不是很高,因此反映分布略微不一致。注意: p值<0.01,强烈建议拒绝原假设H0,p值越大,越倾向于原假设H0成立。
3. 对抗验证
对抗验证是个很有趣的方法,它的思路是:我们构建一个分类器去分类训练集和测试集,如果模型能清楚分类,说明训练集和测试集存在明显区别(即分布不一致),否则反之。具体步骤如下:
训练集和测试集合并,同时新增标签‘Is_Test’去标记训练集样本为0,测试集样本为1。
构建分类器(例如LGB, XGB等)去训练混合后的数据集(可采用交叉验证的方式),拟合目标标签‘Is_Test’。
输出交叉验证中最优的AUC分数。AUC越大(越接近1),越说明训练集和测试集分布不一致。
相关代码可参考Qiuyan918在Kaggle的Microsoft Malware Prediction比赛中使用实例代码[7]。
图6:对抗验证示意图
三、解决方法
1. 构造合适的验证集
当出现训练集和测试集分布不一致的,我们可以试图去构建跟测试集分布近似相同的验证集,保证线下验证跟线上测试分数不会抖动,这样我们就能得到稳定的benchmark。Qiuyan918在基于对抗验证的基础上,提出了三种构造合适的验证集的办法:
人工划分验证集
选择和测试集最相似的样本作为验证集
有权重的交叉验证
接下来,我将依次细讲上述方法。
(1) 人工划分验证集
以时间序列举例,因为一般测试集也会是未来数据,所以我们也要保证训练集是历史数据,而划分出的验证集是未来数据,不然会发生“时间穿越”的数据泄露问题,导致模型过拟合(例如用未来预测历史数据),这个时候就有两种验证划分方式可参考使用:
TimeSeriesSplit:Sklearn提供的TimeSeriesSplit。
固定窗口滑动划分法:固定时间窗口,不断在数据集上滑动,获得训练集和验证集。(个人推荐这种)
图7:划分时序数据的两种方法
除了时间序列数据,其它数据集的验证集划分都要遵循一个原则,即尽可能符合测试集的数据模式。像前面提到的2021“AI Earth”人工智能创新挑战赛中气象数据,由于测试集是真实气象数据,那么我们划分验证集时,更倾向于使用真实气象数据去评估线下模型的表现,而不是使用模拟气象数据作为验证集。
(2) 选择和测试集最相似的样本作为验证集
前面在讲对抗验证时,我们有训练出一个分类器去分类训练集和测试集,那么自然我们也能预测出训练集属于测试集的概率(即训练集在‘Is_Test’标签下预测概率),我们对训练集的预测概率进行降序排列,选择概率最大的前20%样本划分作为验证集,这样我们就能从原始数据集中,得到分布跟测试集接近的一个验证集了,具体样例代码详见[7]。之后,我们还可以评估划分好的验证集跟测试集的分布状况,评估方法:将验证集和测试集做对抗验证,若AUC越小,说明划分出的验证集和测试集分布越接近(即分类器越分不清验证集和测试集)。
图8:选择和测试集最相似的样本作为验证集
(3) 有权重的交叉验证
如果我们对训练集里分布更偏向于测试集分布的样本更大的样本权重,给与测试集分布不太一致的训练集样本更小权重,也能一定程度上,帮助我们线下得到不易抖动的评估分数。在lightgbm库的Dataset初始化参数中,便提供了样本加权的参数weight,详见文档[8]。图7中,对抗验证的分类器预测训练集的Is_Test概率作为权重即可。
2. 删除分布不一致特征
如果我们遇到分布不一致且不太重要的特征,我们可以选择直接删去这种特征。该方法在各大比赛中十分常见。例如: 在2018年蚂蚁金服风险大脑-支付风险识别比赛中,亚军团队根据特征在训练集和测试集上的表现,去除分布差异较大的特征,如图9[9]。
图9:蚂蚁金服支付风险识别比赛中删除分布不一致特征[9]
虽然个人建议的是删除分布不一致但不太重要的特征,但有时避免不了碰到分布不一致但又很重要的特征,这时候其实就需要自行trade off特征分布和特征重要性的关系了,比如在第四届工业大数据创新竞赛-注塑成型工艺的虚拟量测中,第5名团队保留了sensor1_mean特征而删除了pack_press_2特征,尽管他们发现pack_press_2从实际生产角度和相关性角度都非常重要,可为了提升模型在测试集的泛化能力和分数,他们没用pack_press_2特征,如图10[10]。
图10:注塑成型工艺的虚拟量测比赛中删除分布不一致特征[10]
3. 伪标签
伪标签是半监督方法,利用未标注数据加入训练,我们先看看伪标签的思路,再讨论为什么它可能在一定程度上对分布不一致的数据集有帮助。伪标签最常见的方法是:
使用有标注的训练集训练模型M;
然后用模型M预测未标注的测试集;
选取测试集中预测置信度高的样本加入训练集中;
使用标注样本和高置信度的预测样本训练模型M';
预测测试集,输出预测结果。
TripleLift知乎主提供的入门版伪标签思路图如下所示,建议有兴趣的朋友阅读他原文[12],他还提供了进阶版和创新版的伪标签技术,值得借鉴学习。
图12:入门版伪标签思路图
由上图我们可以看到,模型的训练引入了部分测试集的样本,这样相当于引入了部分测试集的分布。但需要注意:
(1) 相比于前面的方法,伪标签通常没有表现的很好,因为它引入的是置信度高的测试集样本,这些样本很可能跟训练集分布接近一致,所以才会预测概率高。因此引入的测试集分布也没有很不同,所以使用时常发生过拟合的情况。
(2) 注意引入的是高置信度样本,如果引入低置信度样本,会带来很大的噪声。另外,高置信度样本也不建议选取过多加入训练集,这也是为了避免模型过拟合。
(3) 伪标签适用于图像领域更多些,表格型比赛建议最后没办法再考虑该方法,因为本人使用过该方法,涨分的可能性都不是很高(也可能是我没用好)。
文章来源机器学习AI算法工程