不论是打比赛、做实验还是搞工程,我们经常会遇到训练集与测试集分布不一致的情况。一般来说,我们会从训练集中划分出一个验证集,通过这个验证集来调整一些超参数,并保存在验证集上效果最好的模型。然而,如果验证集本身和测试集差别比较大,那么在验证集上表现很好的模型不一定在测试集上表现同样好,因此如何让划分出来的验证集跟测试集的分布差异更小,是一个值得研究的课题
两种情况
首先明确一点,本文所考虑的,是能拿到测试集数据、但不知到测试集标签的场景。如果是那种提交模型封闭评测的场景,我们完全看不到测试集的就没什么办法了。为什么会出现测试集跟训练集分布不一致的现象呢?主要有两种情况
- 一是标签的分布不一致。也就是说,如果只看输入x,那么分布基本上是差不多的,但是对应的y分布不一样,典型的例子就是信息抽取任务(例如关系抽取),训练集和验证集的输入x都是某一领域的文本,所以他们的分布很接近。但是训练集往往是通过"远程监督 人工粗标"的方式构建的,里面的错漏比较多,而测试集可能是通过"人工反复精标"构建的,错漏很少。这种情况下就无法通过划分数据的方式构建一个很好的验证集了
- 二是输入的分布不一致。说白了就是x的分布不一致,但y的标注情况基本上是正确的。比如分类问题中,训练集的类别分布跟测试集的类别分布可能不一样;又或者在阅读理解问题中,训练集的事实类/非事实类题型比例跟测试集不一样,等等。这种情况下我们可以适当调整采样策略,让验证集跟测试集分布更接近,从而使得验证集的结果能够更好的反应测试集的结果
Adversarial Validation
Adversarial Validation网上的翻译是对抗验证,它并不是一种评估模型的方法,而是一种用来验证训练集和测试集分布是否一致、找出影响数据分布不一致的特征、从训练集中找出一部分与测试集分布接近的数据。不过实际上有些时候我们并不需要找出影响数据分布不一致的特征,因为可能这个数据集只有一个特征,例如对于nlp的很多任务来说,就只有一个文本,因此也就只有一个特征。对抗验证的核心思想是:
训练一个判别器来区分训练/测试样本,之后将这个判别器应用到训练集中,在训练集中,选取被预测为测试样本的Top n个数据作为验证集,因为这些数据是最模型认为最像测试集的数据
判别器
我们首先让训练集的标签为0,测试集的标签为1,训练一个二分类判别器D(x):
其中p(x)代表了训练集的分布,q(x)则是测试集的分布。要注意的是,我们应该分别从训练集和测试集采样同样多的样本来组成每一个batch,也就是说需要采样到类别均衡
可能有读者担心过拟合问题,即判别器彻底地将训练集和测试集分开了,这样的话我们要找出训练集中top n个最像测试集的样本,就找不出来了。事实上,在训练判别器的时候,我们应该也要像普通的监督训练一样,划分个验证集出来,通过验证集决定训练的epoch数,这样就不会严重过拟合了;或者像网上有些案例一样,用一些简单的回归模型做判别器,这样就不太容易过拟合了
与GAN的判别器类似,不难推导D(x)的理论最优解是
也就是说,判别器训练完后,可以认为它就等于测试集分布的相对大小
代码
以下代码利用AUC指标判别两个数据集的分布是否接近,越接近0.5表示他们的分布越相似。网上对抗验证的代码,大部分是针对于numerical的数据,很少有针对于nlp文本类型数据的代码,对于nlp文本类型的数据,应该先将文本特征转为向量再进行操作。代码并不全面,例如没有实现从训练集中抽取Top n接近测试集的样本
代码语言:javascript复制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))
References
- Adversarial Validation
- 对抗验证:验证训练集和测试集的数据分布是否一致
- 你还在用交叉验证吗?大神都开始用对抗验证了
- Text Classification with Extremely Small Datasets
- Adversarial-Validation
- 如何划分一个跟测试集更接近的验证集?