不论是打比赛、做实验还是搞工程,我们经常会遇到训练集与测试集分布不一致的情况。一般来说,我们会从训练集中划分出一个验证集,通过这个验证集来调整一些超参数,并保存在验证集上效果最好的模型。然而,如果验证集本身和测试集差别比较大,那么在验证集上表现很好的模型不一定在测试集上表现同样好,因此如何让划分出来的验证集跟测试集的分布差异更小,是一个值得研究的课题
首先明确一点,本文所考虑的,是能拿到测试集数据、但不知到测试集标签的场景。如果是那种提交模型封闭评测的场景,我们完全看不到测试集的就没什么办法了。为什么会出现测试集跟训练集分布不一致的现象呢?主要有两种情况
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接近测试集的样本
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))