训练集和测试集样本分布一致性的判断方法

(一)机器学习避坑指南:训练集/测试集分布一致性检查

https://blog.csdn.net/jpld/article/details/111659837

工业界有一个大家公认的看法,“数据和特征决定了机器学习项目的上限,而算法只是尽可能地逼近这个上限”。在实战中,特征工程几乎需要一半以上的时间,是很重要的一个部分。缺失值处理、异常值处理、数据标准化、不平衡等问题大家应该都已经手到擒来小菜一碟了,本文我们探讨一个很容易被忽视的坑:数据一致性。


众所周知,大部分机器学习算法都有一个前提假设:训练数据样本和位置的测试样本来自同一分布。如果测试数据的分布跟训练数据不一致,那么就会影响模型的效果。


在一些机器学习相关的竞赛中,给定的训练集和测试集中的部分特征本身很有可能就存在分布不一致的问题。实际应用中,随着业务的发展,训练样本分布也会发生变化,最终导致模型泛化能力不足。

下面就向大家介绍几个检查训练集和测试集特征分布一致性的方法:

KDE(核密度估计)分布图
核密度估计(kernel density estimation)是在概率论中用来估计未知的密度函数,属于非参数检验方法之一,通过核密度估计图可以比较直观的看出数据样本本身的分布特征。

seaborn中的kdeplot可用于对单变量和双变量进行核密度估计并可视化。

看一个小例子:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
train_set=pd.read_csv(r'D:\...\train_set.csv')
test_set=pd.read_csv(r'D:\...\test_set.csv')
plt.figure(figsize=(12,9))
ax1 = sns.kdeplot(train_set.balance,label='train_set')
ax2 = sns.kdeplot(test_set.balance,label='test_set')

KS检验(Kolmogorov-Smirnov)
KS检验是基于累计分布函数,用于检验一个分布是否符合某种理论分布或比较两个经验分布是否有显著差异。两样本K-S检验由于对两样本的经验分布函数的位置和形状参数的差异都敏感,所以成为比较两样本的最有用且最常用的非参数方法之一。

我们可以使用 scipy.stats 库中的ks_2samp,进行KS检验:

from scipy.stats import ks_2samp
ks_2samp(train_set.balance,test_set.balance)
ks检验一般返回两个值:第一个值表示两个分布之间的最大距离,值越小即这两个分布的差距越小,分布也就越一致。第二个值是p值,用来判定假设检验结果的一个参数,p值越大,越不能拒绝原假设(待检验的两个分布式同分布),即两个分布越是同分布。

Ks_2sampResult(statistic=0.005976590587342234, pvalue=0.9489915858135447)
最终返回的结果可以看出,balance这个特征在训练集测试集中服从相同分布。

对抗验证(Adversarial validation)
除了 KDE 和 KS检验,目前比较流行的是对抗验证,它并不是一种评估模型效果的方法,而是一种用来确认训练集和测试集的分布是否变化的方法。
具体做法:
1、将训练集、测试集合并成一个数据集,新增一个标签列,训练集的样本标记为 0 ,测试集的样本标记为 1 。
2、重新划分一个新的train_set和test_set(区别于原本的训练集和测试集)。
3、用train_set训练一个二分类模型,可以使用 LR、RF、XGBoost、 LightGBM等等,以AUC作为模型指标。
4、如果AUC在0.5左右,说明模型无法区分原训练集和测试集,也即两者分布一致。如果AUC比较大,说明原训练集和测试集差异较大,分布不一致。
5、利用第 2 步中的分类器模型,对原始的训练集进行打分预测,并将样本按照模型分从大到小排序,模型分越大,说明与测试集越接近,那么取训练集中的 TOP N 的样本作为目标任务的验证集,这样即可将原始的样本进行拆分得到训练集,验证集,测试集。

除了确定训练集和测试集特征分布一致性,对抗验证还可以用来做特征选择。大家感兴趣的话可以给个赞+在看,下一讲《特征选择》,我们用实例来看看对抗验证的具体用法和效果。
————————————————
版权声明:本文为CSDN博主「机器学习算法与Python实战」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jpld/article/details/111659837

(二)机器学习中的对抗验证

https://blog.csdn.net/qq_39783265/article/details/104848263

对抗验证
交叉验证(Cross Validation)是常用的一种用来评估模型效果的方法。

当样本分布发生变化时,交叉验证无法准确评估模型在测试集上的效果,这导致模型在测试集上的效果远低于训练集。

通过本文,你将通过一个kaggle的比赛实例了解到,样本分布变化如何影响建模,如何通过对抗验证辨别样本的分布变化,以及有哪些应对方法。

直接给链接:https://zhuanlan.zhihu.com/p/93842847

在此之前,不过不懂AUC,请学习下面链接:
https://www.zhihu.com/question/39840928/answer/241440370

贴上自己的代码,以便于理解:

##df_train:用给的训练集
##df_test:用测试机当验证集
df_train = train_lables
df_test = test_lables
# 定义新的Y
df_train['Is_Test'] = 0
df_test['Is_Test'] = 1
# 将 Train 和 Test 合成一个数据集。
df_adv = pd.concat([df_train, df_test])
# features:训练时所用到的特征
X = df_adv[features]
y = df_adv['Is_Test']
1
2
3
4
5
6
7
8
9
10
11
12
模型训练
# 定义模型参数
params = {
    'boosting_type': 'gbdt',
    'colsample_bytree': 1,
    'learning_rate': 0.1,
    'max_depth': 5,
    'min_child_samples': 100,
    'min_child_weight': 1,
    'min_split_gain': 0.0,
    'num_leaves': 20,
    'objective': 'binary',
    'random_state': 50,
    'subsample': 1.0,
    'subsample_freq': 0,
    'metric': 'auc',
    'num_threads': 8
}
cv_pred = []
best_loss = []
test_prob = 0
n_splits = 5
skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)
for index, (train_idx, test_idx) in enumerate(fold.split(X, y)):
    lgb_model = lgb.LGBMClassifier(**params)
    train_x, test_x, train_y, test_y = X.loc[train_idx], X.loc[test_idx], y.loc[train_idx], y.loc[test_idx]
    eval_set = [(test_x, test_y)]
    lgb_model.fit(train_x, train_y, eval_set = eval_set, eval_metric='auc',early_stopping_rounds=100,verbose=None)
    best_loss.append(lgb_model.best_score_['valid_0']['auc'])
    print(best_loss, np.mean(best_loss))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
AUC结果:
[0.5548410714285714] 0.5548410714285714
[0.5548410714285714, 0.5788339285714286] 0.5668375
[0.5548410714285714, 0.5788339285714286, 0.5695142857142858] 0.5677297619047619
[0.5548410714285714, 0.5788339285714286, 0.5695142857142858, 0.5460357142857143] 0.56230625
[0.5548410714285714, 0.5788339285714286, 0.5695142857142858, 0.5460357142857143, 0.5811589285714286] 0.5660767857142857

此方法用来评价训练集与测试集分布是否一致,以防止新的测试集出现,导致崩盘的现象
————————————————
版权声明:本文为CSDN博主「猫爱吃鱼the」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39783265/article/details/104848263

(三)还在用交叉验证?试试Kaggle大牛们常用的方法——对抗验证

https://zhuanlan.zhihu.com/p/93842847

交叉验证(Cross Validation)是常用的一种用来评估模型效果的方法。

当样本分布发生变化时,交叉验证无法准确评估模型在测试集上的效果,这导致模型在测试集上的效果远低于训练集。

通过本文,你将通过一个kaggle的比赛实例了解到,样本分布变化如何影响建模,如何通过对抗验证辨别样本的分布变化,以及有哪些应对方法。

本篇文章完整代码: https://github.com/Qiuyan918/Adversarial_Validation_Case_Study/blob/master/Adversarial_Validation.ipynb

目录

  • 什么是样本分布变化
  • 为什么样本分布变化的时候,交叉验证不适用?
  • 什么是对抗验证?
  • 分布变化时,有哪些优于交叉验证的方法?
  • Kaggle比赛实例(lightgbm模型;Python)

1 什么是「样本分布变化」?

在真实的业务场景中,我们经常会遇到「样本分布变化」的问题。

主要体现在训练集和测试集的分布存在的差异。比如,在化妆品或者医美市场,男性的比例越来越多。基于过去的数据构建的模型,渐渐不适用于现在。

2 为什么「样本分布变化」的时候,交叉验证不适用?

当我们要做一个模型,来预测人们在超市的消费习惯。

我们的训练样本主要是18岁至25岁的年轻人构成,而测试样本主要是70岁以上的老人组成。这时样本分布就发生了变化。

训练集和测试集样本分布一致性的判断方法_第1张图片

图1 训练样本和测试样本分布偏差

这种情况下,使用交叉验证,无法准确评估模型的效果。原因是,交叉验证的验证集和测试集不够相似。

交叉验证中,每一折的验证集都是从训练集随机抽取的。随机抽取的验证集的分布和整体的训练集是相同的,也就意味着每一折的验证集都和测试集的分布存在较大的差异。

所以在样本分布变化时,通过交叉验证的方式构建的模型,在测试集上的表现,相较于训练集,通常会打折扣。稍后我们会通过一个实例来确认这一点。

3 什么是对抗验证(Adversarial Validation)?

对抗验证(Adversarial Validation),并不是一种评估模型效果的方法,而是一种用来确认训练集和测试集的分布是否变化的方法。

它的本质是构造一个分类模型,来预测样本是训练集或测试集的概率。

如果这个模型的效果不错(通常来说AUC在0.7以上),那么可以说明我们的训练集和测试集存在较大的差异。

仍然以「预测人们在超市的消费习惯」为例。因为训练集主要是18岁-25岁的年轻人,测试集主要是70岁以上的老人,那么通过「年龄」,我们就能够很好的区分出训练集和测试集。

训练集和测试集样本分布一致性的判断方法_第2张图片

图2 分类器通过「年龄」可以轻松区分训练集和测试集

具体步骤:

  1. 定义y:样本是train还是test。
  2. 将 Train 和 Test 合成一个数据集
  3. 构造一个模型,拟合新定义的y
  4. 观察模型效果:如果模型的AUC超过0.7,说明了 Train 和 Test 的分布存在较大的差异
# 定义新的Y
df_train['Is_Test'] = 0
df_test['Is_Test'] = 1

# 将 Train 和 Test 合成一个数据集。
df_adv = pd.concat([df_train, df_test])

4 分布变化时,有哪些优于交叉验证的方法?

4.1 人工划分验证集

人工划分验证集,需要我们对数据有充分的了解。

因为这次比赛的数据是根据时间划分的,所以我的验证集同样可以根据时间划分。

如果我们不清楚训练集和测试集如何划分,可以采用后面两种方法。

# 将样本根据时间排序
df_train = df_train.sort_values('Date').reset_index(drop=True)
df_train.drop(['Date'], axis=1, inplace=True)

# 前80%的样本作为训练集,后20%的样本作为验证集
df_validation_1 = df_train.iloc[int(0.8 * len(df_train)):, ]
df_train_1 = df_train.iloc[:int(0.8 * len(df_train)), ]

4.2 和测试集最相似的样本作为验证集

如果对数据没有充分了解,如何找到训练集中,和测试集分布最相似的样本呢?

这就会用到我们做对抗验证时,模型预测样本是测试集的概率。概率越高,则说明和测试集越相似。

# 通过抗验证中的模型,得到各个样本属于测试集的概率
model_adv.fit(df_adv.drop('Is_Test', axis=1), df_adv.loc[:, 'Is_Test'])
preds_adv = model_adv.predict_proba(df_adv.drop('Is_Test', axis=1))[:, 1]

# 只需要训练样本的概率
df_train_copy = df_train.copy()
df_train_copy['is_test_prob'] = preds_adv[:len(df_train)]

# 根据概率排序
df_train_copy = df_train_copy.sort_values('is_test_prob').reset_index(drop=True)

# 将概率最大的20%作为验证集
df_validation_2 = df_train_copy.iloc[int(0.8 * len(df_train)):, ]
df_train_2 = df_train_copy.iloc[:int(0.8 * len(df_train)), ]

4.3 有权重的交叉验证

不仅可以用对抗验证中,样本是测试集的概率来划分验证集,也可以将这个概率作为样本的权重。

概率越高,和测试集就越相似,权重就越高。这样,我们就可以做有权重的交叉验证。

# 生成lightgbm的数据格式,赋予各个样本权重
train_set = lgb.Dataset(
            df_train.drop('HasDetections', axis=1),
            label=df_train.loc[:, 'HasDetections'], weight=preds_adv[:len(df_train)])

5 实例

5.1 用到的数据

训练集和测试集样本分布一致性的判断方法_第3张图片

图3 微软恶意软件比赛

这里用到的数据来自Kaggle上的微软恶意软件比赛。

因为这次比赛的 Train 和 Test 是根据时间划分的,所以Train 和 Test 的分布非常不同,很具有代表性。

通过对该数据做对抗验证,我们发现模型的AUC达到了0.99。说明本次比赛的训练集和测试集的样本分布存在较大的差异。

5.2 对比各种方法的效果

分别使用上述提到的总共4种方法,我们来对比一下四种方法的效果,如下表:

训练集和测试集样本分布一致性的判断方法_第4张图片

图4 比较各类方法训练集和测试集AUC

使用交叉验证时,验证集AUC和测试集AUC的差值是最大的,远高于其他方式。说明在样本分布发生变化时,交叉验证不能够准确评估模型在测试集上的效果。

5.3 为什么评价方式是差值,而不是测试集AUC?

有人可能会提到,哪种方法在测试集上的AUC最高,哪种方法就更好,不是吗?

需要注意的是,本文讨论的不是“提升”模型效果的方法,而是“评估”模型效果的方法。

具体来说,虽然目前看来,比如交叉验证在测试集上的AUC,略高于有权重的交叉验证。

但是,当前的模型只是一个很基础的模型(Baseline Model),没有做任何的变量筛选,特征工程,以及模型调参。

由于所有的优化模型的决定,都将基于验证集,而交叉验证无法准确评估模型在测试集上的效果,这将导致很多优化模型的决定是错误的。

只有在有一个可靠的验证集的情况下,提升模型在验证集上效果的方法,我们才有信心认为,它也可以提升在测试集上的表现。

另外,从本次比赛的结果,我们也可以发现,最终排名很好的参赛者,都没有使用交叉验证。

6 结论

在样本分布发生变化时,交叉验证不能够准确评估模型在测试集上的效果。

这里建议采用其他方式:

  • 人工划分验证集
  • 和测试集最相似的样本作为验证集
  • 有权重的交叉验证

你可能感兴趣的:(python,算法)