Press "Enter" to skip to content

对抗验证:划分一个跟测试集更接近的验证集

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

不论是打比赛、做实验还是搞工程,我们经常会遇到训练集与测试集分布不一致的情况。一般来说,我们会从训练集中划分出一个验证集,通过这个验证集来调整一些超参数,并保存在验证集上效果最好的模型。然而,如果验证集本身和测试集差别比较大,那幺在验证集上表现很好的模型不一定在测试集上表现同样好,因此如何让划分出来的验证集跟测试集的分布差异更小,是一个值得研究的课题

 

两种情况

 

首先明确一点,本文所考虑的,是能拿到测试集数据、但不知到测试集标签的场景。如果是那种提交模型封闭评测的场景,我们完全看不到测试集的就没什幺办法了。为什幺会出现测试集跟训练集分布不一致的现象呢?主要有两种情况

一是标签的分布不一致
。也就是说,如果只看输入$x$,那幺分布基本上是差不多的,但是对应的$y$分布不一样,典型的例子就是信息抽取任务(例如关系抽取),训练集和验证集的输入$x$都是某一领域的文本,所以他们的分布很接近。但是训练集往往是通过”远程监督+人工粗标”的方式构建的,里面的错漏比较多,而测试集可能是通过”人工反复精标”构建的,错漏很少。这种情况下就无法通过划分数据的方式构建一个很好的验证集了
二是输入的分布不一致
。说白了就是$x$的分布不一致,但$y$的标注情况基本上是正确的。比如分类问题中,训练集的类别分布跟测试集的类别分布可能不一样;又或者在阅读理解问题中,训练集的事实类/非事实类题型比例跟测试集不一样,等等。这种情况下我们可以适当调整采样策略,让验证集跟测试集分布更接近,从而使得验证集的结果能够更好的反应测试集的结果

Adversarial Validation

 

Adversarial Validation网上的翻译是对抗验证,它并不是一种评估模型的方法,而是一种用来验证训练集和测试集分布是否一致、找出影响数据分布不一致的特征、从训练集中找出一部分与测试集分布接近的数据。不过实际上有些时候我们并不需要找出影响数据分布不一致的特征,因为可能这个数据集只有一个特征,例如对于nlp的很多任务来说,就只有一个文本,因此也就只有一个特征。对抗验证的核心思想是:

 

训练一个判别器来区分训练/测试样本,之后将这个判别器应用到训练集中,在训练集中,选取被预测为测试样本的Top n个数据作为验证集,因为这些数据是最模型认为最像测试集的数据

 

判别器

 

我们首先让训练集的标签为0,测试集的标签为1
,训练一个二分类判别器$D(x)$:

 

$$
-\mathbb{E}_{x\sim p(x)}[\log (1-D(x))]-\mathbb{E}_{x\sim q(x)}[\log D(x)]\tag{1}
$$

 

其中$p(x)$代表了训练集的分布,$q(x)$则是测试集的分布。要注意的是,我们应该分别从训练集和测试集采样同样多的样本来组成每一个batch,也就是说需要采样到类别均衡

 

可能有读者担心过拟合问题,即判别器彻底地将训练集和测试集分开了,这样的话我们要找出训练集中top n个最像测试集的样本,就找不出来了。事实上,在训练判别器的时候,我们应该也要像普通的监督训练一样,划分个验证集出来,通过验证集决定训练的epoch数,这样就不会严重过拟合了;或者像网上有些案例一样,用一些简单的回归模型做判别器,这样就不太容易过拟合了

 

与GAN的判别器类似,不难推导$D(x)$的理论最优解是

 

$$
D(x)=\frac{q(x)}{p(x)+q(x)}\tag{2}
$$

 

也就是说,判别器训练完后,可以认为它就等于测试集分布的相对大小

 

代码

 

以下代码利用AUC指标判别两个数据集的分布是否接近,越接近0.5表示他们的分布越相似。网上对抗验证的代码,大部分是针对于numerical的数据,很少有针对于nlp文本类型数据的代码,对于nlp文本类型的数据,应该先将文本特征转为向量再进行操作。代码并不全面,例如没有实现从训练集中抽取Top n接近测试集的样本

 

import sklearn
import numpy as np
import pandas as pd
import lightgbm as lgb
from sklearn.feature_extraction.text import TfidfVectorizer
df = pd.read_csv('data.csv')
df = df.sample(frac=1).reset_index(drop=True)
df_train = df[:int(len(df) * 0.7)]
df_test = df[int(len(df) * 0.7):]
col = 'text'
tfidf = TfidfVectorizer(ngram_range=(1, 2), max_features=50).fit(df_train[col].iloc[:].values)
train_tfidf = tfidf.transform(df_train[col].iloc[:].values)
test_tfidf = tfidf.transform(df_test[col].iloc[:].values)
train_test = np.vstack([train_tfidf.toarray(), test_tfidf.toarray()]) # new training data
lgb_data = lgb.Dataset(train_test, label=np.array([0]*len(df_train)+[1]*len(df_test)))
params = {
    'max_bin': 10,
    'learning_rate': 0.01,
    'boosting_type': 'gbdt',
    'metric': 'auc',
}
result = lgb.cv(params, lgb_data, num_boost_round=100, nfold=3, verbose_eval=20)
print(pd.DataFrame(result))

Be First to Comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注