之前参加了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数据集的类别:
1 | train = pd.read_csv( 'data/train.csv' ) |
将train数据和test数据合并成一个数据:
1 | data = pd.concat(( train, test )) |
重新划分一个新的train和test数据:
1 | from sklearn.cross_validation import train_test_split |
训练一个lr和rf分类器,训练结果如下:
1 | # logistic regression / AUC: 49.82% |
在valid数据集上的结果如下:
1 | # logistic regression / AUC: 58.30% |
在test数据集上的结果如下:
1 | # logistic regression / AUC: 61.47% |
看样子,valid数据跟test数据结果类似。
接下来,我们来看看valid数据与test数据不同的一个例子,使用来自金融时间序列的Numerai数据时看到这种效果。
首先尝试了logistic回归,得到以下验证分数:
1 | LR |
排行榜得分(使用AUC):
1 | # AUC 0.51706 / LR |
从结果中,valid数据结果和test结果存在一定差距。
我们希望有一个验证集来代表Numerai测试集。为此,我们从train数据集提取出与test数据最相似的数据。
具体来说,我们将使用交叉验证训练一个分类器,从而获得所有train数据的预测概率。然后我们将看到哪些train数据被错误地预测为test数据,并将它们当做valid数据。这意味着它们看起来像test数据,但实际上是trian数据。
首先,让我们尝试训练一个分类器来区分train和test,就像我们对Santander数据所做的那样。但是我们得到的不是0.5,而是0.87 AUC,这意味着模型能够很好地区分了train和test。因此,随机地进行构建valid数据集明显是不可靠的,因此,我们通过交叉验证对所有trian数据进行预测,并对预测概率进行排序。
1 | i = predictions.argsort() |
valid和test
按照概率排序结果,选取前后位置一定样本量进行测试。
1 | val_size = 5000 |
结果如下:
1 | LR |
可以看到,模型在valid数据之间的差异非常小。接下来我们与test数据结果进行比较。
valid数据结果:
1 | # 0.6922 / LR |
poblic排行榜结果:
1 | # 0.6910 / LR |
private排行榜结果:
1 | # 0.6916 / LR |
可以看到新的valid数据结果与test数据结果相近。
Toxic
Adversarial validation是检查训练和测试数据集是否有显著差异的一种方法。其思想是使用数据集特性来尝试分离训练和测试样本。
因此,对trrain和test数据集创建一个二分类模型,其中train的target为1,test的target为0,并基于给定的特征训练一个分类器,以预测给定的样本是否在train或test数据集中!
在这里,我们将使用logistic回归模型和TF-IDF特征来检查文本特征分布是否不同,看看是否可以分离样本。
参考地址:kernel
读取数据集:
1 | import numpy as np |
构建tfidf特征:
1 | from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer |
将词汇字典转化为词列表:
1 | trn_words = [word for word in trn_vocab.keys()] |
查看词的分布:
1 | number of words in both train and test : 16182 |
这意味着在train数据和test数据的词汇存在很大的差异,让我们看看线性回归是否能在train和test之间发挥作用。对trian数据和test数据构建tfidf特征。
1 | from sklearn.linear_model import LogisticRegression |
结果如下:
1 | 0.6847478442985545 |
从结果中,我们发现只在trian和test数据集上构建tfidf特征,train和test数据之间也存在显著的差异。或许我们可以构建一个与test数据即渐近分布的valid。
接下来,尝试以trian数据和test数据共有的词作为特征(可以降低不同词分布带来的影响),设置最小频数为3:
1 | vectorizer = TfidfVectorizer( |
构建tfidf特征
1 | trn_words = [word for word in trn_vocab.keys()] |
结果如下所示
1 | Number of words in common : 16416 |
尝试更少的词汇:
1 | vectorizer = TfidfVectorizer( |
结果如下:
1 | Number of words in common : 444 |
即使词汇量大大减少,train数据和test数据之间仍然有显著的差异。我认为这是由于数据集涵盖的话题范围太广,总共只有30万个样本。
你需要意识到分词非常重要。为了演示我将使用CountVectorizer和两种不同的分词记方法:一个非常简单的分词和来自nltk包的TweetTokenizer。
simple Tokenization
1 | from nltk.tokenize import TweetTokenizer |
结果如下:
1 | Number of words in common : 440 |
TweetTokenizer
1 | from nltk.tokenize import TweetTokenizer |
结果如下:
1 | Number of words in common : 425 |
可以尝试构造一份valid数据集
1 | #seperate train rows which have been misclassified as test and use them as validation |