train和test的列分布差异(covariate shift)观察
代碼來自:
https://www.kaggle.com/nroman/eda-for-cis-fraud-detection/comments
?
下面每一個代碼塊都表示一個jupyter notebook上面的一個單元格。
原代碼有一些bug,只能繪制沒有NaN的特征,本文已經修復這些bug
Python:3.6
?
代碼:
import pandas as pd import numpy as np import multiprocessing import warnings import matplotlib.pyplot as plt import seaborn as sns import lightgbm as lgb import gc from time import time import datetime import matplotlib.pyplot as plt from tqdm import tqdm_notebook from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import StratifiedKFold, KFold, TimeSeriesSplit, train_test_split from sklearn.metrics import roc_auc_score from sklearn.tree import DecisionTreeClassifier from sklearn import tree import graphviz import pandas as pd import datatable as dt warnings.simplefilter('ignore') sns.set()def reduce_mem_usage(df, verbose=True):numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']start_mem = df.memory_usage(deep=True).sum() / 1024**2for col in df.columns:col_type = df[col].dtypesif col_type in numerics:c_min = df[col].min()c_max = df[col].max()if str(col_type)[:3] == 'int':if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:df[col] = df[col].astype(np.int8)elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:df[col] = df[col].astype(np.int16)elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:df[col] = df[col].astype(np.int32)elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:df[col] = df[col].astype(np.int64)else:c_prec = df[col].apply(lambda x: np.finfo(x).precision).max()if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max and c_prec == np.finfo(np.float32).precision:df[col] = df[col].astype(np.float32)else:df[col] = df[col].astype(np.float64)end_mem = df.memory_usage().sum() / 1024**2if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))return dfdef plot_numerical(feature):"""Plot some information about a numerical feature for both train and test set.Args:feature (str): name of the column in DataFrame"""#因為有NaN數值,繪制前需要扔掉NaN,但是不能破壞原有數據,所以在備份上操作train_feature=train[feature].copy()train_feature=train_feature.dropna()test_feature=test[feature].copy()test_feature=test_feature.dropna()fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(16, 18))sns.kdeplot(train_feature, ax=axes[0][0], label='Train');sns.kdeplot(test_feature, ax=axes[0][0], label='Test');del train_featuredel test_feature#因為有NaN數值,繪制前需要扔掉NaN,但是不能破壞原有數據,所以在備份上操作train_train_isFraud_0=train[train['isFraud']==0][feature]train_train_isFraud_0=train_train_isFraud_0.dropna()train_train_isFraud_1=train[train['isFraud']==1][feature]train_train_isFraud_1=train_train_isFraud_1.dropna()sns.kdeplot(train_train_isFraud_0, ax=axes[0][1], label='isFraud 0')sns.kdeplot(train_train_isFraud_1, ax=axes[0][1], label='isFraud 1')test[feature].index += len(train)axes[1][0].plot(train[feature], '.', label='Train');axes[1][0].plot(test[feature], '.', label='Test');axes[1][0].set_xlabel('row index');axes[1][0].legend()test[feature].index -= len(train)axes[1][1].plot(train_train_isFraud_0, '.', label='isFraud 0');axes[1][1].plot(train_train_isFraud_1, '.', label='isFraud 1');axes[1][1].set_xlabel('row index');axes[1][1].legend()pd.DataFrame({'train': [train[feature].isnull().sum()], 'test': [test[feature].isnull().sum()]}).plot(kind='bar', rot=0, ax=axes[2][0]);pd.DataFrame({'isFraud 0': [train[(train['isFraud']==0) & (train[feature].isnull())][feature].shape[0]],'isFraud 1': [train[(train['isFraud']==1) & (train[feature].isnull())][feature].shape[0]]}).plot(kind='bar', rot=0, ax=axes[2][1]);del train_train_isFraud_0del train_train_isFraud_1#----------------------下面是為了設置子圖的名字---------------------------------------------------#第1行的兩個子圖axes[0][0].set_title('Train/Test KDE distribution(Between Train and Test)');axes[0][1].set_title('Target value KDE distribution(Train Only)');#第2行的兩個子圖axes[1][0].set_title('Index versus value: Train/Test distribution');axes[1][1].set_title('Index versus value: Target distribution');#第3行的兩個子圖axes[2][0].set_title('Number of NaNs');axes[2][1].set_title('Target value distribution among NaN values');plt.show()#相當于對單列數據使用對抗驗證 def covariate_shift(feature):#取一列特征,然后各自標記上是否是測試集df_card1_train = pd.DataFrame(data={feature: train[feature], 'isTest': 0})df_card1_test = pd.DataFrame(data={feature: test[feature], 'isTest': 1})# 縱向拼接df = pd.concat([df_card1_train, df_card1_test], ignore_index=True)#這個只是編碼為了圖形上顯示得緊湊些,并沒有實質的作用# Encoding if feature is categoricalif str(df[feature].dtype) in ['object', 'category']:df[feature] = LabelEncoder().fit_transform(df[feature].astype(str))# Splitting it to a training and testing setX_train, X_test, y_train, y_test = train_test_split(df[feature], df['isTest'], test_size=0.33, random_state=47, stratify=df['isTest'])clf = lgb.LGBMClassifier(**params, num_boost_round=500)clf.fit(X_train.values.reshape(-1, 1), y_train)roc_auc = roc_auc_score(y_test, clf.predict_proba(X_test.values.reshape(-1, 1))[:, 1])del df, X_train, y_train, X_test, y_testgc.collect();return roc_auc以處理card1為例,下面代碼有兩個部分,其中一部分用來處理數值特征(可能是離散ID類特征,也可能是連續特征)
另外一部分用來處理字符特征以及取值極少的特征(例如V1)
不要盲目調用,調用前要看下特征的取值情況再做決定
#這里其實是對抗驗證 #這段代碼的意圖是,同在train中,isFraud=1以及isFraud=0是否具備對于card1的相同分布 # 作用是: # ①判斷訓練集內部分布是否統一,如果分布統一AUC=0.5,我們就留用,否則不用 # ②另外還能說明,該特征作為決策樹的split feature的時候,一定處于決策樹的下方. #X_train和y_train配對 #X_test和y_test配對#---------------處理card1-------------------------------------- #---------------下面代碼用來處理數值特征(注意數值特征不一定是離散或者連續特征)-------------------------------------- y = train['isFraud'] X = pd.DataFrame()#初始化 Feature='card1' X[Feature] = train[Feature] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=47, stratify=y)# #填充NaN值,因為后面的訓練需要 X_train.fillna(-999, inplace = True) X_test.fillna(-999, inplace = True)# 這個是不理會test的,僅僅是train,下面是建模 clf = DecisionTreeClassifier(max_leaf_nodes=4)#用一個弱分類器進行 print(pd.DataFrame(y_test).columns.values.tolist()) print(pd.DataFrame(y_train).columns.values.tolist())#獲取series的列名 clf.fit(X_train, y_train) print("---------------------------train內部該特征分布是否統一(0.5:統一;0.99:不統一)-------------------------") print(X_test.columns.values.tolist())#獲取列名 print('ROC AUC score:',roc_auc_score(y_test, clf.predict_proba(X_test)[:, 1])) print('ROC AUC score:',roc_auc_score(y_test, clf.predict_proba(X_test.values.reshape(-1, 1))[:, 1]))params = {'objective': 'binary', "boosting_type": "gbdt", "subsample": 1, "bagging_seed": 11, "metric": 'auc', 'random_state': 47} print("-------------------train和test之間的列對抗驗證(0.5:統一且留下;0.99:不統一再處理或者刪除)-------------------------") roc_auc=covariate_shift(Feature) print("roc_auc=",roc_auc) print("---------------------繪圖:train和test之間以及train內部-------------------------------------------------") plot_numerical(Feature)# --------------------處理card1(數值特征)------------------------------------------------- # 下面的代碼和上面的函數重復,但是多了個繪制光標的功能,所以還是繼續放在這里。 plt.figure(figsize=(14, 6)) #繪制核密度曲線 sns.kdeplot(X[y==1]['card1'], label='isFraud 1');#傳入數據 sns.kdeplot(X[y==0]['card1'], label='isFraud 0');#傳入數據 #下面兩個句子就是在曲線圖上繪制兩個光標,不重要。 plt.plot([10881.5, 10881.5], [0.0000, 0.0001], sns.xkcd_rgb["black"], lw=2);#lw:linewidth plt.plot([8750.0, 8750.0], [0.0000, 0.0001], sns.xkcd_rgb["red"], lw=2); plt.show() # #上面第二個參數的意思是縱坐標可以用來讓游標線(紅和黑)斜著畫. pd.DataFrame(X[y==1]['card1']).columns.values.tolist()#這里繪圖只用了一列數據#---------------處理字符取值的特征(離散特征)--------------------------------------Feature='R_emaildomain' df_train = pd.DataFrame(data={Feature: train[Feature], 'isTest': 0}) df_test = pd.DataFrame(data={Feature: test[Feature], 'isTest': 1}) df = pd.concat([df_train, df_test], ignore_index=True) fig, axes = plt.subplots(1, 2, figsize=(14, 6)) sns.countplot(data=df.fillna('NaN'), x=Feature, hue='isTest', ax=axes[0]); sns.countplot(data=train[[Feature, 'isFraud']].fillna('NaN'), x=Feature, hue='isFraud', ax=axes[1]); axes[0].set_title('Train / Test distibution'); axes[1].set_title('Train distibution by isFraud'); axes[0].legend(['Train', 'Test']); print("----------------單列特征在train和test之間的對抗驗證,AUC=0.5保留-----------------------------------------------") print('Covariate shift ROC AUC:', covariate_shift('ProductCD'))處理TransactionAMT
train=pd.read_csv('./ieee-fraud-detection/train1.csv') train=reduce_mem_usage(train) test =pd.read_csv('./ieee-fraud-detection/test1.csv') test=reduce_mem_usage(test) gc.collect();#--------------------------下面處理TransactionAMT------------------------------------------- plot_numerical('TransactionAmt') fig, axes = plt.subplots(1,1,figsize=(16, 6)) axes.set_title('Moving average of TransactionAmt', fontsize=16); train[['TransactionDT', 'TransactionAmt']].set_index('TransactionDT').rolling(10000).mean().plot(ax=axes);#rolling是時間窗函數 test[['TransactionDT', 'TransactionAmt']].set_index('TransactionDT').rolling(10000).mean().plot(ax=axes); axes.legend(['Train', 'Test']); plt.show() params = {'objective': 'binary', "boosting_type": "gbdt", "subsample": 1, "bagging_seed": 11, "metric": 'auc', 'random_state': 47} print('Covariant shift ROC AUC:', covariate_shift('TransactionAmt'))#需要保留處理TransactionAmt_decimal
train=pd.read_csv('./ieee-fraud-detection/train1.csv') train=reduce_mem_usage(train) test =pd.read_csv('./ieee-fraud-detection/test1.csv') test=reduce_mem_usage(test) gc.collect();#--------------------------下面生成并處理新特征TransactionAmt_decimal------------------------------------------- # # #交易額的小數點后面的數值做成新的特征☆ train['TransactionAmt_decimal'] = ((train['TransactionAmt'] - train['TransactionAmt'].astype(int)) * 1000).astype(int) test['TransactionAmt_decimal'] = ((test['TransactionAmt'] - test['TransactionAmt'].astype(int)) * 1000).astype(int) plot_numerical('TransactionAmt_decimal') print('Covariant shift ROC AUC:', covariate_shift('TransactionAmt_decimal'))#需要保留處理TransactionAmt_decimal_length
#--------------------------下面生成并處理新特征TransactionAmt_decimal_length☆-------------------------------------------train=pd.read_csv('./ieee-fraud-detection/train1.csv') train=reduce_mem_usage(train) test =pd.read_csv('./ieee-fraud-detection/test1.csv') test=reduce_mem_usage(test) gc.collect();train['TransactionAmt_decimal_length'] = train['TransactionAmt'].astype(str).str.split('.', expand=True)[1].str.len() test['TransactionAmt_decimal_length'] = test['TransactionAmt'].astype(str).str.split('.', expand=True)[1].str.len()# 觀察新特征TransactionAmt_decimal_length的分布 df_train = pd.DataFrame(data={'TransactionAmt_decimal_length': train['TransactionAmt_decimal_length'], 'isTest': 0}) df_test = pd.DataFrame(data={'TransactionAmt_decimal_length': test['TransactionAmt_decimal_length'], 'isTest': 1}) df = pd.concat([df_train, df_test], ignore_index=True) fig, axes = plt.subplots(1, 2, figsize=(14, 6)) sns.countplot(data=df.fillna('NaN'), x='TransactionAmt_decimal_length', hue='isTest', ax=axes[0]); sns.countplot(data=train[['TransactionAmt_decimal_length', 'isFraud']].fillna('NaN'), x='TransactionAmt_decimal_length', hue='isFraud', ax=axes[1]); axes[0].set_title('Train / Test distibution'); axes[1].set_title('Train distibution by isFraud'); axes[0].legend(['Train', 'Test']); plt.show() plot_numerical('TransactionAmt_decimal_length') params = {'objective': 'binary', "boosting_type": "gbdt", "subsample": 1, "bagging_seed": 11, "metric": 'auc', 'random_state': 47} print('Covariant shift ROC AUC:', covariate_shift('TransactionAmt_decimal_length'))#需要保留?
其他不需要特征工程的列的分析可以是:
#V1 plot_numerical('V1') print('Covariate shift:', covariate_shift('V1'))注意:
網上目前過分鼓吹對抗驗證。
ieee-fraud-detection的V37可以得出auc=0.5左右,但是train和test關于V37的核密度是不重合的。
也就是說:
對抗驗證得到的AUC=0.5不能推出V37在train和test中的核密度分布一致。
如果V37在train和test中的核密度分布一致,那么對抗驗證的AUC=0.5
?
所以看完AUC還是要再看一眼核密度曲線確認下保險。
?
?
舉個例子:
?
train有8條數據的Att1取值為0,2條數據的取值為1,所有10條數據新增特征isTest=1
? test有6條數據的Att1取值為0,4條數據的取值為0,所有10條數據新增特征isTest=0
共Att1與isTest兩個特征。
?
現在進行holdout驗證,ratio=0.5
從train中隨機得到5條數據,其中4條的Att=0,1條的Att=1
從? test中隨機得到5條數據,其中3條的Att=0,2條的Att=1
?
因為只有兩種取值,所以cart樹的分割值=0.5,建模后規則如下:
Att<0.5時,isTest=1,TP=4,FP=3
Att>0.5時,isTest=0,TN=2,FN=1
所以最后得到(TPR,FPR=)=(4/(1+4),3/(3+2))=(0.8,0.6),
也就是說在FPR=TPR這條線的附近,
預示著模型很差,
進一步推導出該數據集的特征Att1在test和train中的分布一致。
但是看看開頭我們的設定,頻率一致嗎?并不一致。
所以AUC=0.5能嚴謹地說明某特征的分布在train和test中一致嗎?
并不能,最好是,看下train和test中這個特征的密度曲線是否重合。
?
?
?
Reference:
[1]https://www.kaggle.com/nroman/eda-for-cis-fraud-detection
[2]https://www.analyticsvidhya.com/blog/2017/07/covariate-shift-the-hidden-problem-of-real-world-data-science/
[3]https://www.analyticsvidhya.com/blog/2018/05/24-ultimate-data-science-projects-to-boost-your-knowledge-and-skills/
總結
以上是生活随笔為你收集整理的train和test的列分布差异(covariate shift)观察的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DataFrame纵向合并
- 下一篇: 数据松弛Data Relaxation