之前参加了kaggle的Toxic比赛,名次为Top1%(瞎打,忙于项目——提不上去的理由了,安慰自己)。回头看别人分享的kernel时,发现了Adversarial validation,本文也是直接copy fastml以及来自kaggle中一个kernel。

在比赛中,可能会遇到测试数据集与训练数据集分布明显不同。这时候,我们常用的k-折交叉验证可能达不到想要的效果,即训练集上的交叉验证结果可能与测试集上的结果相差甚远。

可能是由以下原因造成:

  • 1.过细的学习训练数据集,即可能也把噪声数据学习到了。
  • 2.训练数据集和测试数据集存在显著差异,即分布不同。

问题

对于第一个问题,我们可以使用一些正则化手段解决过拟合问题,那么对于第二个问题,我们该如何解决?如果你参加过像Kaggle这样的竞赛,你就会知道这些竞赛的模式。在比赛中,你可以下载到一个train数据集和test数据集。你需要在train数据上训练你的模型,在test数据上预测并且在上传到Kaggle平台上来得到你排名。

我们通常做法是将完整的train数据集划分为train和valid数据集。valid数据用于评估与模型的性能,理想状态下,我们希望valid数据集可以代表test数据集,即同分布,这样我们通过对valid数据集的评估,可以渐近地认识到模型在test数据上的结果。如果train数据集和test数据的分布不相同。那么,模型在valid数据集和test数据集就会得到不同的结果。

解决方法

解决方法是Adversarial validation。在fastml上看到了这种技术,具体的做法如下:

  • 1.建立一个分类器来区分train数据集和test数据集

将train数据和test数据合并成一个数据中。并新增一个类别变量isTest,其中train数据中isTest为0,test数据中isTest为1。然后需要构建一个二元分类器(可以是任意分类器,比如RF,xgboost或lr等)来区分train数据和test数据。

  • 2.将train数据的预测概率按递减顺序排序

一旦训练完模型之后,接下来使用训练好的模型对train数据进行预测。得到一个拟合概率,对概率按递减顺序排序,那么,top的数据可以理解为很大的概率预测为test数据。

  • 3.取train数据的top n%数据作为valid数据集

比如取30%作为验证集,剩下的数据作为train数据来训练模型。

通过上述方法,得到valid数据集上的准确性度量应该与test数据上的相似。如果模型能够很好地预测valid数据,那么它应该也能够很好地预测test数据。

案例

当然理想的情况是,train数据和test数据来自一个相同的分布,这样模型在valid数据上的结果就可以很好的映射到test数据上。在这种情况下,如果我们尝试训练一个分类器来区分train和test,那么模型的性能不会比随机猜测好——相当于ROC_AUC为0.5。

该例子来自于fastml

设置train数据集合test数据集的类别:

train = pd.read_csv( 'data/train.csv' )
test = pd.read_csv( 'data/test.csv' )
train['TARGET'] = 1
test['TARGET'] = 0

将train数据和test数据合并成一个数据:

data = pd.concat(( train, test ))
data = data.iloc[ np.random.permutation(len( data )) ]
data.reset_index( drop = True, inplace = True )
x = data.drop( [ 'TARGET', 'ID' ], axis = 1 )
y = data.TARGET

重新划分一个新的train和test数据:

from sklearn.cross_validation import train_test_splitx_train, x_test, y_train, y_test = train_test_split( x, y, train_size = train_examples)

训练一个lr和rf分类器,训练结果如下:

# logistic regression / AUC: 49.82%
# random forest, 10 trees / AUC: 50.05%
# random forest, 100 trees / AUC: 49.95%

在valid数据集上的结果如下:

# logistic regression / AUC: 58.30%
# random forest / AUC: 75.32%

在test数据集上的结果如下:

# logistic regression / AUC: 61.47%
# random forest / AUC: 74.37%

看样子,valid数据跟test数据结果类似。

接下来,我们来看看valid数据与test数据不同的一个例子,使用来自金融时间序列的Numerai数据时看到这种效果。

首先尝试了logistic回归,得到以下验证分数:

LR
AUC: 52.67%, accuracy: 52.74%MinMaxScaler + LR
AUC: 53.52%, accuracy: 52.48%

排行榜得分(使用AUC):

# AUC 0.51706 / LR
# AUC 0.52781 / MinMaxScaler + LR
# AUC 0.51784 / PolynomialFeatures + MinMaxScaler + LR

从结果中,valid数据结果和test结果存在一定差距。

我们希望有一个验证集来代表Numerai测试集。为此,我们从train数据集提取出与test数据最相似的数据。

具体来说,我们将使用交叉验证训练一个分类器,从而获得所有train数据的预测概率。然后我们将看到哪些train数据被错误地预测为test数据,并将它们当做valid数据。这意味着它们看起来像test数据,但实际上是trian数据。

首先,让我们尝试训练一个分类器来区分train和test,就像我们对Santander数据所做的那样。但是我们得到的不是0.5,而是0.87 AUC,这意味着模型能够很好地区分了train和test。因此,随机地进行构建valid数据集明显是不可靠的,因此,我们通过交叉验证对所有trian数据进行预测,并对预测概率进行排序。

i = predictions.argsort()
train['p'] = predictions
train_sorted = train.iloc[i]

valid和test

按照概率排序结果,选取前后位置一定样本量进行测试。

val_size = 5000
train = data.iloc[:-val_size]
val = data.iloc[-val_size:]

结果如下:

LR
AUC: 52.54%, accuracy: 51.96%, log loss: 69.22%Pipeline(steps=[('poly', PolynomialFeatures(degree=2, include_bias=True, interaction_only=False)), ('scaler', MinMaxScaler(copy=True, feature_range=(0, 1)))])
AUC: 52.57%, accuracy: 51.76%, log loss: 69.58%

可以看到,模型在valid数据之间的差异非常小。接下来我们与test数据结果进行比较。

valid数据结果:

# 0.6922 / LR
# 0.6958 / PolynomialFeatures + MinMaxScaler + LR

poblic排行榜结果:

# 0.6910 / LR
# 0.6923 / PolynomialFeatures + MinMaxScaler + LR

private排行榜结果:

# 0.6916 / LR
# 0.6954 / PolynomialFeatures + MinMaxScaler + LR

可以看到新的valid数据结果与test数据结果相近。

Toxic

Adversarial validation是检查训练和测试数据集是否有显著差异的一种方法。其思想是使用数据集特性来尝试分离训练和测试样本。

因此,对trrain和test数据集创建一个二分类模型,其中train的target为1,test的target为0,并基于给定的特征训练一个分类器,以预测给定的样本是否在train或test数据集中!

在这里,我们将使用logistic回归模型和TF-IDF特征来检查文本特征分布是否不同,看看是否可以分离样本。

参考地址:kernel

读取数据集:

import numpy as np
import pandas as pd
trn = pd.read_csv("../input/train.csv", encoding="utf-8")
sub = pd.read_csv("../input/test.csv", encoding="utf-8")

构建tfidf特征:

from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
import regex
vectorizer = TfidfVectorizer(sublinear_tf=True,strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 1), max_features=20000
)
trn_idf = vectorizer.fit_transform(trn.comment_text)
trn_vocab = vectorizer.vocabulary_
sub_idf = vectorizer.fit_transform(sub.comment_text)
sub_vocab = vectorizer.vocabulary_
all_idf = vectorizer.fit_transform(pd.concat([trn.comment_text, sub.comment_text], axis=0))
all_vocab = vectorizer.vocabulary_

将词汇字典转化为词列表:

trn_words = [word for word in trn_vocab.keys()]
sub_words = [word for word in sub_vocab.keys()]
all_words = [word for word in all_vocab.keys()]

查看词的分布:

number of words in both train and test : 16182
number of words in all_words not in train : 1931
number of words in all_words not in test : 1896

这意味着在train数据和test数据的词汇存在很大的差异,让我们看看线性回归是否能在train和test之间发挥作用。对trian数据和test数据构建tfidf特征。

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score
# Create target where all train samples are ones and all test samples are zeros
target = np.hstack((np.ones(trn.shape[0]), np.zeros(sub.shape[0])))
# Shuffle samples to mix zeros and ones
idx = np.arange(all_idf.shape[0])
np.random.seed(1)
np.random.shuffle(idx)
all_idf = all_idf[idx]
target = target[idx]
# Train a Logistic Regression
folds = StratifiedKFold(5, True, 1)
for trn_idx, val_idx in folds.split(all_idf, target):lr = LogisticRegression()lr.fit(all_idf[trn_idx], target[trn_idx])print(roc_auc_score(target[val_idx], lr.predict_proba(all_idf[val_idx])[:, 1]))

结果如下:

0.6847478442985545
0.6838642860252299
0.6862951075131871
0.6883996519299357
0.6882208658576139

从结果中,我们发现只在trian和test数据集上构建tfidf特征,train和test数据之间也存在显著的差异。或许我们可以构建一个与test数据即渐近分布的valid。

接下来,尝试以trian数据和test数据共有的词作为特征(可以降低不同词分布带来的影响),设置最小频数为3:

vectorizer = TfidfVectorizer(sublinear_tf=True,strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 1), max_features=20000,min_df=3
)
trn_idf = vectorizer.fit_transform(trn.comment_text)
trn_vocab = vectorizer.vocabulary_
sub_idf = vectorizer.fit_transform(sub.comment_text)
sub_vocab = vectorizer.vocabulary_

构建tfidf特征

trn_words = [word for word in trn_vocab.keys()]
sub_words = [word for word in sub_vocab.keys()]
print("Number of words in common : ", len(set(trn_words).intersection(set(sub_words))))
vectorizer = TfidfVectorizer(sublinear_tf=True,strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 1), vocabulary=list(set(trn_words).intersection(set(sub_words)))
)
all_idf = vectorizer.fit_transform(pd.concat([trn.comment_text, sub.comment_text], axis=0))
# Create target where all train samples are ones and all test samples are zeros
target = np.hstack((np.ones(trn.shape[0]), np.zeros(sub.shape[0])))
# Shuffle samples to mix zeros and ones
idx = np.arange(all_idf.shape[0])
np.random.seed(1)
np.random.shuffle(idx)
all_idf = all_idf[idx]
target = target[idx]
# Train a Logistic Regression
folds = StratifiedKFold(5, True, 1)
for trn_idx, val_idx in folds.split(all_idf, target):lr = LogisticRegression()lr.fit(all_idf[trn_idx], target[trn_idx])print(roc_auc_score(target[val_idx], lr.predict_proba(all_idf[val_idx])[:, 1]))

结果如下所示

Number of words in common :  16416
0.6758812171438944
0.6755599693902824
0.6787566884700114
0.6796040649316202
0.6789255076072573

尝试更少的词汇:

vectorizer = TfidfVectorizer(sublinear_tf=True,strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 1), max_features=500,min_df=3
)
trn_idf = vectorizer.fit_transform(trn.comment_text)
trn_vocab = vectorizer.vocabulary_
sub_idf = vectorizer.fit_transform(sub.comment_text)
sub_vocab = vectorizer.vocabulary_trn_words = [word for word in trn_vocab.keys()]
sub_words = [word for word in sub_vocab.keys()]
print("Number of words in common : ", len(set(trn_words).intersection(set(sub_words))))
vectorizer = TfidfVectorizer(sublinear_tf=True,strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 1), vocabulary=list(set(trn_words).intersection(set(sub_words)))
)
all_idf = vectorizer.fit_transform(pd.concat([trn.comment_text, sub.comment_text], axis=0))
# Create target where all train samples are ones and all test samples are zeros
target = np.hstack((np.ones(trn.shape[0]), np.zeros(sub.shape[0])))
# Shuffle samples to mix zeros and ones
idx = np.arange(all_idf.shape[0])
np.random.seed(1)
np.random.shuffle(idx)
all_idf = all_idf[idx]
target = target[idx]
# Train a Logistic Regression
folds = StratifiedKFold(5, True, 1)
for trn_idx, val_idx in folds.split(all_idf, target):lr = LogisticRegression()lr.fit(all_idf[trn_idx], target[trn_idx])print(roc_auc_score(target[val_idx], lr.predict_proba(all_idf[val_idx])[:, 1]))

结果如下:

Number of words in common :  444
0.6295718202729551
0.6268219112785893
0.6270581079920985
0.6280726585488302
0.6244650004722636

即使词汇量大大减少,train数据和test数据之间仍然有显著的差异。我认为这是由于数据集涵盖的话题范围太广,总共只有30万个样本。

你需要意识到分词非常重要。为了演示我将使用CountVectorizer和两种不同的分词记方法:一个非常简单的分词和来自nltk包的TweetTokenizer。

simple Tokenization

from nltk.tokenize import TweetTokenizer
vectorizer = CountVectorizer(strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 2), max_features=500,min_df=3
)
trn_idf = vectorizer.fit_transform(trn.comment_text)
trn_vocab = vectorizer.vocabulary_
sub_idf = vectorizer.fit_transform(sub.comment_text)
sub_vocab = vectorizer.vocabulary_trn_words = [word for word in trn_vocab.keys()]
sub_words = [word for word in sub_vocab.keys()]
print("Number of words in common : ", len(set(trn_words).intersection(set(sub_words))))
vectorizer = CountVectorizer(strip_accents='unicode',tokenizer=lambda x: regex.findall(r'[^\p{P}\W]+', x),analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 2), vocabulary=list(set(trn_words).intersection(set(sub_words)))
)
all_idf = vectorizer.fit_transform(pd.concat([trn.comment_text, sub.comment_text], axis=0))
# Create target where all train samples are ones and all test samples are zeros
target = np.hstack((np.ones(trn.shape[0]), np.zeros(sub.shape[0])))
# Shuffle samples to mix zeros and ones
idx = np.arange(all_idf.shape[0])
np.random.seed(1)
np.random.shuffle(idx)
all_idf = all_idf[idx]
target = target[idx]
# Train a Logistic Regression
folds = StratifiedKFold(5, True, 1)
for trn_idx, val_idx in folds.split(all_idf, target):lr = LogisticRegression()lr.fit(all_idf[trn_idx], target[trn_idx])print(roc_auc_score(target[val_idx], lr.predict_proba(all_idf[val_idx])[:, 1]))

结果如下:

Number of words in common :  440
0.6063327137520516
0.5999916796025004
0.6011318222132256
0.5996101413728843
0.5993641245063593

TweetTokenizer

from nltk.tokenize import TweetTokenizer
vectorizer = CountVectorizer(strip_accents='unicode',tokenizer=TweetTokenizer().tokenize,analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 2), max_features=500,min_df=3
)
trn_idf = vectorizer.fit_transform(trn.comment_text)
trn_vocab = vectorizer.vocabulary_
sub_idf = vectorizer.fit_transform(sub.comment_text)
sub_vocab = vectorizer.vocabulary_trn_words = [word for word in trn_vocab.keys()]
sub_words = [word for word in sub_vocab.keys()]
print("Number of words in common : ", len(set(trn_words).intersection(set(sub_words))))
vectorizer = CountVectorizer(strip_accents='unicode',tokenizer=TweetTokenizer().tokenize,analyzer='word',token_pattern=None,stop_words='english',ngram_range=(1, 2), vocabulary=list(set(trn_words).intersection(set(sub_words)))
)
all_idf = vectorizer.fit_transform(pd.concat([trn.comment_text, sub.comment_text], axis=0))
# Create target where all train samples are ones and all test samples are zeros
target = np.hstack((np.ones(trn.shape[0]), np.zeros(sub.shape[0])))
# Shuffle samples to mix zeros and ones
idx = np.arange(all_idf.shape[0])
np.random.seed(1)
np.random.shuffle(idx)
all_idf = all_idf[idx]
target = target[idx]
# Train a Logistic Regression
folds = StratifiedKFold(5, True, 1)
for trn_idx, val_idx in folds.split(all_idf, target):lr = LogisticRegression()lr.fit(all_idf[trn_idx], target[trn_idx])print(roc_auc_score(target[val_idx], lr.predict_proba(all_idf[val_idx])[:, 1]))

结果如下:

Number of words in common :  425
0.808150062507659
0.8092440866192762
/opt/conda/lib/python3.6/site-packages/sklearn/linear_model/base.py:340: RuntimeWarning: overflow encountered in expnp.exp(prob, prob)
0.8100851554254078
0.8080836017812789
0.8085904163543269

可以尝试构造一份valid数据集

#seperate train rows which have been misclassified as test and use them as validation
train["predictions"] = predictions
predictions_argsort = predictions.argsort()
train_sorted = train.iloc[predictions_argsort]#select only trains set because we need to find train rows which have been misclassified as test set and use them for validation
train_sorted = train_sorted.loc[train_sorted.is_test == 0]#Why did I chose 0.7 as thereshold? just a hunch, but you should try different thresholds i.e 0.6, 0.8 and see the difference in validation score and please report back. :)
train_as_test = train_sorted.loc[train_sorted.predictions > 0.7]
#save the indices of the misclassified train rows to use as validation set
adversarial_set_ids = train_as_test.index.values
adversarial_set = pd.DataFrame(adversarial_set_ids, columns=['adversial_set_ids'])
#save adversarial set index
adversarial_set.to_csv('adversarial_set_ids.csv', index=False)

Adversarial validation相关推荐

  1. 数据挖掘竞赛黑科技——对抗验证(Adversarial validation)

    通常情况下,我们一般都会使用交叉验证来作为评估模型的标准,来选择我们最后的模型.但是在一些数据挖掘竞赛中,数据集一般分为训练集合测试集,国内比赛可能根据比赛阶段划分多个测试集,由于数据集采样和分布的原 ...

  2. Adversarial Validation 微软恶意代码比赛的一个kenel的解析

    英文文档链接

  3. EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES 论文笔记

    EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES 论文笔记 EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES 原 ...

  4. 5种数据同分布的检测方法!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:小雨姑娘,康涅狄格大学,Datawhale成员 在数据挖掘比赛中, ...

  5. 【机器学习基础】5种数据同分布的检测方法!

    作者:小雨姑娘,康涅狄格大学,Datawhale成员 在数据挖掘比赛中,很重要的一个技巧就是要确定训练集与测试集特征是否同分布,这也是机器学习的一个很重要的假设.但很多时候我们知道这个道理,却很难有方 ...

  6. Kaggle知识点:数据分布不一致的验证

    Adversarial Validation 在Kaggle比赛中数据分布一致是阻碍大家上分的点,本篇文章将介绍Adversarial Validation原理和使用方法,并对数据分布不一致的情况给出 ...

  7. label y 训练集测试集x_Adversarial validation-对抗验证| 一种解决训练集与测试集分布不一致的方法...

    导语: 马上就要五一了,祝全世界人民五一快乐!在这之前,想过好几个准备这些天可以完成的专题,比如Boosting系列在搞点最近几年的新玩意,或者开一个新专题,如心心念念的GNN/GCN(主要是又可以去 ...

  8. ai与虚拟现实_将AI推向现实世界

    ai与虚拟现实 If you fit one of these profiles, this article is for you: 如果您适合这些配置文件之一,那么本文适合您: ● You are ...

  9. kaggle比赛数据_表格数据二进制分类:来自5个Kaggle比赛的所有技巧和窍门

    kaggle比赛数据 This article was originally written by Shahul ES and posted on the Neptune blog. 本文最初由 Sh ...

最新文章

  1. Django中配置自定义日志系统
  2. android网络请求流程图,Android OKHttp系列1-流程总结
  3. 计算机的桌面图片还原,电脑桌面图标重启后自动复原的解决方法。
  4. 怎么样得到平台相关的换行符?
  5. 通讯可以并联吗_工业控制知识:吃透RS485通讯的连接方式,接485总线就简单多了...
  6. 按120分计算成绩 mysql_Mysql实用教程试卷B.doc
  7. 成绩查询数据结构c语言,学生成绩管理系统(数据结构c语言版源代码)09169.pdf
  8. 组策略彻底解决windows 2003 终端数
  9. 2018-12-28
  10. Hive(二)——数据类型与文件格式
  11. Mac新手必备技巧-如何使用 macOS 帮助菜单?
  12. 软件商业模式的发展与SaaS
  13. Linux 安装PostgreSql
  14. 用R语言看武汉市武昌区房租出租情况
  15. api存在csrf攻击吗_使用rest api防止单页应用上的csrf攻击
  16. 科普:快速了解第三代半导体及什么是宽禁带半导体
  17. docker搭建kong、konga步骤
  18. 网站制作网站制作如何实现图文并茂
  19. 没有经验该如何转行做产品经理?
  20. Python qrcode制作二维码

热门文章

  1. SQLServer2019安装(Windows)
  2. Ajax基础教程电子版
  3. 序:何为高级java开发工程师?
  4. Mysql数据库和navicat
  5. ORACLE一次大量数据删除导致问题的处理
  6. 成为钢铁侠!只需一块RTX3090,微软开源贾维斯(J.A.R.V.I.S.)人工智能AI助理系统
  7. Jarvis-OJ-Web writeup
  8. LUN、ThickLUN与Thin LUN的联系和区别
  9. 浙大 | FcaNet:频域通道注意力机制(keras实现)
  10. 【个人博客】Astro 快速搭建个人博客