1.1 對抗驗證的簡介:
通常情況下,我們一般都會使用交叉驗證來作為評估模型的標准,來選擇我們最后的模型。但是在一些數據挖掘競賽中,數據集一般分為訓練集合測試集,國內比賽可能根據比賽階段划分多個測試集,由於數據集采樣和分布的原因導致訓練集和線上測試集可能存在分布不一致的情況,這時候CV無法准確的評估模型在測試集上的效果,導致線上線下不統一,分數上不去。而緩解這一問題的黑科技,就是對抗驗證Adversarial validation。
樣本分布變化主要體現在訓練集和測試集的數據分布存在差異。比如,在化妝品或者醫美市場,男性的比例越來越多。基於過去的數據構建的模型,漸漸不適用於現在。
比如我們現在要對淘寶用戶的購買行為進行推薦或者預測。我們的訓練數據集中用戶的年齡分布大概在18~25歲,而我們的測試集中主要是70歲以上的老人組成。這時我們的數據樣本分布就發生了變化。
訓練集與測試的不一致問題是我們經常面臨的問題,在互金領域,市場環境變化,准入策略,額度策略的調整,會導致訓練樣本在時間維度上存在差異,因此在利用大規模樣本,或者長期限樣本來訓練模型時,必然會存在訓練集與測試集(或者當前線上打分模型)產生偏差,這種現象在模型指標上的表現就是訓練集,驗證集上的KS/AUC想對較高,測試集上天然會存在decay的現象。面對這種情況,不能把原因單純的歸結為過擬合,而使勁地去改善這種表面的過擬合現象,也需要考慮上述人群變化的原因。
訓練集與測試集差異明顯的典型例子很容易發生在Kaggle比賽,或者國內高水平的比賽上,比賽前期顯示的都是在公榜上成績,最后的評判的卻是的額外的私榜上,就會產生很大喜聞樂見的地震, shake up。
Adversarial validation:用來驗證train_test 數據分布是否一致;找出影響數據分布不一致的特征;從訓練集中找出一部分跟test類似的數據。
Adversarial validation
,網上的翻譯是對抗驗證,但是當我看完內容之后其實是沒有發現太明顯的對抗痕跡,還是高攀得起的,步驟如下:
- 構建一個樣本的分類器,該二分類器的任務用於區分樣本來源於訓練集,還是測試集。因此,需要為原始數據新增一個標簽列,將訓練集中的樣本標記為0, 測試集中的樣本標記為1,樣本的特征是數據中已有的特征,或者衍生的新特征,生成新的訓練數據集;
- 將新的訓練數據集進行划分,保留部分樣本作為該樣本分類任務的測試集
, 利用分類算法(LightGBM)等對數據集進行訓練,AUC作為模型指標;
- 在測試集
中進行驗證,如果模型效果AUC在0.5左右,說明該樣本分類模型無法區分樣本來源訓練集,還是測試集,說明原始數據中訓練集,測試集分布是一致的;如果AUC較大,如0.9, 說明樣本分類器很容易區分樣本,間接說明訓練集與測試集存在很大差異;這就是用來驗證的
- 根據第3步的結論,對於分布一致的,正常對目標任務訓練即可。對於分布不一致的,可以繼續進行樣本挑選的嘗試。利用上述樣本分類器模型,對原始的訓練集進行打分預測,並將樣本按照模型分從大到小排序,模型分越大,說明與測試集越接近,那么取訓練集中的TOP N 的樣本作為目標任務的驗證集,這樣即可將原始的樣本進行拆分得到 訓練集,驗證集,測試集。那么線上模型驗證時,在這樣的數據上調參等得到模型,如果在驗證集上效果穩定,那么應用在測試集上,大概率結果是一致的
如此,完成了local CV的構建。有了上述的思路,可以做進一步的擴展(在可容忍的模型誤差范圍內), 利用樣本分類模型,挑選與測試集相近的訓練集,做樣本的反向選擇,挑選合適訓練樣本進行模型訓練。
2.1 代碼:
import pandas as pd import numpy as np from sklearn.datasets import load_irisston from sklearn.model_selection import train_test_split boston=load_boston() data=pd.DataFrame(boston.data,columns=boston.feature_names) tar=pd.DataFrame(boston.target,columns=['predict']) df=pd.concat([data,tar],axis=1) df x_train,x_test,y_train,y_test=train_test_split(data,tar,test_size=0.3,random_state=1) # 開始給訓練集和測試集打上標簽。 x_train['target']=0 x_test['target']= 1 #將訓練集和測試集合並 data=pd.concat([x_train,x_test],axis=0) #取出train_label,方便后面巡禮N train_label=data['target'] data.drop(['target'],axis=1,inplace=True) #開始驗證訓練集和測試集是否一樣,如果越接近0.5,就說明一樣 from sklearn.model_selection import StratifiedKFold import lightgbm as lgb train_data=data evals_result={} params = { 'booster': 'gbdt', 'objective': 'binary', 'metric': 'auc', 'num_leaves': 5, 'min_data_in_leaf': 91, 'max_bin': 205, 'max_depth': 8, 'num_leaves':20, 'max_bin':50, "learning_rate": 0.01, 'feature_fraction': 0.6, "bagging_fraction": 1.0, # 每次迭代時用的數據比例 'bagging_freq': 45, 'min_split_gain': 1.0, 'min_child_samples': 10, 'lambda_l1': 0.3, 'lambda_l2': 0.6, 'n_jobs': -1, 'silent': True, # 信息輸出設置成1則沒有信息輸出 'seed': 1000, } kf=StratifiedKFold(n_splits=5,shuffle=True,random_state=123) #類別交叉嚴重 x,y=pd.DataFrame(train_data),pd.DataFrame(train_label) #將數據dataframe化,后面進行iloc 等選項 for i,(train_index,valid_index) in enumerate(kf.split(x,y)): #這里需要輸入y print("第",i+1,"次") x_train,y_train=x.iloc[train_index],y.iloc[train_index] x_valid,y_valid=x.iloc[valid_index],y.iloc[valid_index] #取出數據 lgb_train = lgb.Dataset(x_train, y_train,silent=True) lgb_eval = lgb.Dataset(x_valid, y_valid, reference=lgb_train,silent=True) gbm = lgb.train(params, lgb_train, num_boost_round=10000, valid_sets=[lgb_train, lgb_eval],verbose_eval=100, early_stopping_rounds=200,evals_result=evals_result) #varbose_eval 迭代多少次打印 early_stopping_rounds:有多少次分數沒有提高就停止 #categorical_feature:lightgbm可以處理標稱型(類別)數據。通過指定'categorical_feature' 這一參數告訴它哪些feature是標稱型的。 #它不需要將數據展開成獨熱碼(one-hot),其原理是對特征的所有取值,做一個one-vs-others,從而找出最佳分割的那一個特征取值 #bagging_fraction:和bagging_freq同時使用可以更快的出結果 vaild_preds = gbm.predict(x_valid, num_iteration=gbm.best_iteration) #對測試集進行操作 import matplotlib.pyplot as plt import seaborn as sns import warnings warnings.simplefilter(action='ignore', category=FutureWarning) feature_imp = pd.DataFrame(sorted(zip(gbm.feature_importance(),data.columns), reverse=True), columns=['Value','Feature']) plt.figure(figsize=(20, 50)) sns.barplot(x="Value", y="Feature", data=feature_imp.sort_values(by="Value", ascending=False)) plt.title('LightGBM Features (avg over folds)') plt.tight_layout() plt.show() #可以看到,訓練集的AUC大概在0.5左右,測試集的AUC在0.35左右,(都沒有偏差0.2以上)訓練集和測試集的數據分布是大致一樣的. #我們還可以看到分布不一樣的幾個特征如上圖 #我們可以找出與測試集分布相似的數據進行數據驗證 Or 訓練 # # 開始給訓練集和測試集打上標簽。 x_train['target']=0 x_test['target']= 1 #將訓練集和測試集合並 data=pd.concat([x_train,x_test],axis=0) #取出train_label,方便后面巡禮N train_label=data['target'] data.drop(['target'],axis=1,inplace=True) lgb_train = lgb.Dataset(data,train_label,silent=True) gbm = lgb.train(params, lgb_train, num_boost_round=10000,verbose_eval=100) test_pre = gbm.predict(train_data, num_iteration=gbm.best_iteration) # 提取出訓練集上,樣本是測試集的概率 x_train['is_test_prob'] = test_pre[:len(x_train)] #x_train['label']=train_label # 根據概率排序 x_train= x_train.sort_values('is_test_prob',ascending=False).reset_index(drop=True) # 將概率最大的20%作為驗證集 x_train= x_train.iloc[:int(0.5 * len(x_train)), ] x_train.drop('is_test_prob', axis=1, inplace=True) #x_train.drop('target', axis=0, inplace=True) #這樣就拿到了跟測試集一樣的驗證集(訓練集)