对抗验证:验证训练集和测试集的数据分布是否一致

1.1 对抗验证的简介:

  通常情况下,我们一般都会使用交叉验证来作为评估模型的标准,来选择我们最后的模型。但是在一些数据挖掘竞赛中,数据集一般分为训练集合测试集,国内比赛可能根据比赛阶段划分多个测试集,由于数据集采样和分布的原因导致训练集和线上测试集可能存在分布不一致的情况,这时候CV无法准确的评估模型在测试集上的效果,导致线上线下不统一,分数上不去。而缓解这一问题的黑科技,就是对抗验证Adversarial validation。

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

比如我们现在要对淘宝用户的购买行为进行推荐或者预测。我们的训练数据集中用户的年龄分布大概在18~25岁,而我们的测试集中主要是70岁以上的老人组成。这时我们的数据样本分布就发生了变化。

 

训练集与测试的不一致问题是我们经常面临的问题,在互金领域,市场环境变化,准入策略,额度策略的调整,会导致训练样本在时间维度上存在差异,因此在利用大规模样本,或者长期限样本来训练模型时,必然会存在训练集与测试集(或者当前线上打分模型)产生偏差,这种现象在模型指标上的表现就是训练集,验证集上的KS/AUC想对较高,测试集上天然会存在decay的现象。面对这种情况,不能把原因单纯的归结为过拟合,而使劲地去改善这种表面的过拟合现象,也需要考虑上述人群变化的原因。

训练集与测试集差异明显的典型例子很容易发生在Kaggle比赛,或者国内高水平的比赛上,比赛前期显示的都是在公榜上成绩,最后的评判的却是的额外的私榜上,就会产生很大喜闻乐见的地震, shake up。

 

Adversarial validation:用来验证train_test 数据分布是否一致;找出影响数据分布不一致的特征;从训练集中找出一部分跟test类似的数据。

Adversarial validation,网上的翻译是对抗验证,但是当我看完内容之后其实是没有发现太明显的对抗痕迹,还是高攀得起的,步骤如下:

  1. 构建一个样本的分类器,该二分类器的任务用于区分样本来源于训练集,还是测试集。因此,需要为原始数据新增一个标签列,将训练集中的样本标记为0, 测试集中的样本标记为1,样本的特征是数据中已有的特征,或者衍生的新特征,生成新的训练数据集;
  2. 将新的训练数据集进行划分,保留部分样本作为该样本分类任务的测试集[公式], 利用分类算法(LightGBM)等对数据集进行训练,AUC作为模型指标;
  3. 在测试集[公式]中进行验证,如果模型效果AUC在0.5左右,说明该样本分类模型无法区分样本来源训练集,还是测试集,说明原始数据中训练集,测试集分布是一致的;如果AUC较大,如0.9, 说明样本分类器很容易区分样本,间接说明训练集与测试集存在很大差异;这就是用来验证的
  4. 根据第3步的结论,对于分布一致的,正常对目标任务训练即可。对于分布不一致的,可以继续进行样本挑选的尝试。利用上述样本分类器模型,对原始的训练集进行打分预测,并将样本按照模型分从大到小排序,模型分越大,说明与测试集越接近,那么取训练集中的TOP N 的样本作为目标任务的验证集,这样即可将原始的样本进行拆分得到 训练集,验证集,测试集。那么线上模型验证时,在这样的数据上调参等得到模型,如果在验证集上效果稳定,那么应用在测试集上,大概率结果是一致的

如此,完成了local CV的构建。有了上述的思路,可以做进一步的扩展(在可容忍的模型误差范围内), 利用样本分类模型,挑选与测试集相近的训练集,做样本的反向选择,挑选合适训练样本进行模型训练。

 

2.1 代码:

import pandas as pd 
import numpy as np
from sklearn.datasets import load_irisston
from sklearn.model_selection import train_test_split

boston=load_boston()
data=pd.DataFrame(boston.data,columns=boston.feature_names)
tar=pd.DataFrame(boston.target,columns=['predict'])
df=pd.concat([data,tar],axis=1)
df

x_train,x_test,y_train,y_test=train_test_split(data,tar,test_size=0.3,random_state=1)

# 开始给训练集和测试集打上标签。
x_train['target']=0
x_test['target']= 1
#将训练集和测试集合并
data=pd.concat([x_train,x_test],axis=0)
#取出train_label,方便后面巡礼N
train_label=data['target']
data.drop(['target'],axis=1,inplace=True)

#开始验证训练集和测试集是否一样,如果越接近0.5,就说明一样
from sklearn.model_selection import StratifiedKFold
import lightgbm as lgb
train_data=data
evals_result={}
params = {
        'booster': 'gbdt',
        'objective': 'binary',
        'metric': 'auc',
        'num_leaves': 5,
        'min_data_in_leaf': 91,
        'max_bin': 205,
        'max_depth': 8,
        'num_leaves':20,
        'max_bin':50,
        "learning_rate": 0.01,
        'feature_fraction': 0.6,
        "bagging_fraction": 1.0,  # 每次迭代时用的数据比例
         'bagging_freq': 45,
        'min_split_gain': 1.0,
        'min_child_samples': 10,
        'lambda_l1': 0.3, 'lambda_l2': 0.6,
        'n_jobs': -1,
        'silent': True,  # 信息输出设置成1则没有信息输出
        'seed': 1000,
    } 
kf=StratifiedKFold(n_splits=5,shuffle=True,random_state=123)  #类别交叉严重
x,y=pd.DataFrame(train_data),pd.DataFrame(train_label)  #将数据dataframe化,后面进行iloc 等选项

for i,(train_index,valid_index) in enumerate(kf.split(x,y)): #这里需要输入y
    print("",i+1,"")
    x_train,y_train=x.iloc[train_index],y.iloc[train_index]
    x_valid,y_valid=x.iloc[valid_index],y.iloc[valid_index]  #取出数据
    lgb_train = lgb.Dataset(x_train, y_train,silent=True)
    lgb_eval  = lgb.Dataset(x_valid, y_valid, reference=lgb_train,silent=True)
    gbm = lgb.train(params, lgb_train, num_boost_round=10000, valid_sets=[lgb_train, lgb_eval],verbose_eval=100,
                        early_stopping_rounds=200,evals_result=evals_result)
    #varbose_eval 迭代多少次打印  early_stopping_rounds:有多少次分数没有提高就停止
    #categorical_feature:lightgbm可以处理标称型(类别)数据。通过指定'categorical_feature' 这一参数告诉它哪些feature是标称型的。
    #它不需要将数据展开成独热码(one-hot),其原理是对特征的所有取值,做一个one-vs-others,从而找出最佳分割的那一个特征取值
    #bagging_fraction:和bagging_freq同时使用可以更快的出结果
    vaild_preds = gbm.predict(x_valid, num_iteration=gbm.best_iteration)
    #对测试集进行操作


import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

feature_imp = pd.DataFrame(sorted(zip(gbm.feature_importance(),data.columns), reverse=True), columns=['Value','Feature'])

plt.figure(figsize=(20, 50))
sns.barplot(x="Value", y="Feature", data=feature_imp.sort_values(by="Value", ascending=False))
plt.title('LightGBM Features (avg over folds)')
plt.tight_layout()
plt.show()


#可以看到,训练集的AUC大概在0.5左右,测试集的AUC在0.35左右,(都没有偏差0.2以上)训练集和测试集的数据分布是大致一样的.
#我们还可以看到分布不一样的几个特征如上图

#我们可以找出与测试集分布相似的数据进行数据验证 Or 训练

#
# 开始给训练集和测试集打上标签。
x_train['target']=0
x_test['target']= 1
#将训练集和测试集合并
data=pd.concat([x_train,x_test],axis=0)
#取出train_label,方便后面巡礼N
train_label=data['target']
data.drop(['target'],axis=1,inplace=True)


lgb_train = lgb.Dataset(data,train_label,silent=True)

gbm = lgb.train(params, lgb_train, num_boost_round=10000,verbose_eval=100)
test_pre = gbm.predict(train_data, num_iteration=gbm.best_iteration)


# 提取出训练集上,样本是测试集的概率

x_train['is_test_prob'] = test_pre[:len(x_train)]
#x_train['label']=train_label
# 根据概率排序
x_train= x_train.sort_values('is_test_prob',ascending=False).reset_index(drop=True)

# 将概率最大的20%作为验证集
x_train= x_train.iloc[:int(0.5 * len(x_train)), ]

x_train.drop('is_test_prob', axis=1, inplace=True)
#x_train.drop('target', axis=0, inplace=True)
#这样就拿到了跟测试集一样的验证集(训练集)

 

posted @ 2020-07-29 15:46  MiQing4in  阅读(3865)  评论(0编辑  收藏  举报