本篇為此系列文中第三篇,若還沒有讀過前文的讀者,建議從" Kaggle Case - Santander Customer Transaction Prediction (1) "開始閱讀唷 !
Feature Correlation
雖說Feature Correlation 以及下一部分的 Duplicate values 在Kernel作者的觀點都還算是 Data Exploration的一部分,但是畢竟它已經跨到特徵處理了,我就把這兩部分併到這邊一起來看。
1 2 3 4 5
| %%time correlations = train_df[features].corr().abs().unstack().sort_values(kind="quicksort").reset_index()
correlations = correlations[correlations['level_0'] != correlations['level_1']] correlations.head(10)
|
在這裡,我嘗試過中間的.unstack()
替換成.stack()
似乎不會影響這部分特徵相關係數的排序,但是不管何者,都不能省略,它是將train_df[features].corr().abs()
這個DataFrame變成可排序的一個重要的媒介。
上面輸出的結果(略),可以發現到除了特徵自己本身的相關係數為1外,其他不同的特徵配對的相關性都非常低 ( 最高也差不多千分之9.9左右 )
一樣的,我還是想問,如若有一些特徵的配對呈現高度相關性,那麼這些特徵代表的意義是什麼?
Duplicate values
1 2 3 4 5 6 7 8 9 10 11
| %%time features = train_df.columns.values[2:202] unique_max_train = [] unique_max_test = [] for feature in features: values = train_df[feature].value_counts() unique_max_train.append([feature, values.max(), values.idxmax()]) values = test_df[feature].value_counts() unique_max_test.append([feature, values.max(), values.idxmax()])
|
1 2
| np.transpose((pd.DataFrame(unique_max_train, columns=['Feature', 'Max duplicates', 'Value'])).\ sort_values(by = 'Max duplicates', ascending=False).head(15))
|
1 2
| np.transpose((pd.DataFrame(unique_max_test, columns=['Feature', 'Max duplicates', 'Value'])).\ sort_values(by = 'Max duplicates', ascending=False).head(15))
|
從這裡結果我們可以觀察到,在訓練資料及測試資料中,在許多特徵中,某些特定 ( 相近 ) 的數值 重複的次數也非常接近,這是個蠻有趣的現象,也或許在後面的特徵工程中會有所用 ( ? )
Feature Engineering
1 2 3 4 5 6 7 8 9 10 11
| %%time idx = features = train_df.columns.values[2:202] for df in [test_df, train_df]: df['sum'] = df[idx].sum(axis=1) df['min'] = df[idx].min(axis=1) df['max'] = df[idx].max(axis=1) df['mean'] = df[idx].mean(axis=1) df['std'] = df[idx].std(axis=1) df['skew'] = df[idx].skew(axis=1) df['kurt'] = df[idx].kurtosis(axis=1) df['med'] = df[idx].median(axis=1)
|
1
| train_df[train_df.columns[202:]].head()
|
1
| test_df[test_df.columns[201:]].head()
|
Gabriel Preda在特徵的處理上面,選擇的是加進統計量當作新的特徵,而不對就有特徵進行處理,有關於 Feature Engineering 的處理選擇,往往來自於前面對於資料的探索加上後面要用的模型來決定,此處,Gabriel Preda並沒有對於為何要這樣處理特徵多做解釋,或許有一部分也是取決 lightGBM 的特性 (有待查證) ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def plot_new_feature_distribution(df1, df2, label1, label2, features): i = 0 sns.set_style('whitegrid') plt.figure() fig, ax = plt.subplots(2,4,figsize=(18,8))
for feature in features: i += 1 plt.subplot(2,4,i) sns.kdeplot(df1[feature], bw=0.5,label=label1) sns.kdeplot(df2[feature], bw=0.5,label=label2) plt.xlabel(feature, fontsize=11) locs, labels = plt.xticks() plt.tick_params(axis='x', which='major', labelsize=8) plt.tick_params(axis='y', which='major', labelsize=8) plt.show();
|
1 2 3 4
| t0 = train_df.loc[train_df['target'] == 0] t1 = train_df.loc[train_df['target'] == 1] features = train_df.columns.values[202:] plot_new_feature_distribution(t0, t1, 'target: 0', 'target: 1', features)
|
1 2
| features = train_df.columns.values[202:] plot_new_feature_distribution(train_df, test_df, 'train', 'test', features)
|
1
| print('Train and test columns: {} {}'.format(len(train_df.columns), len(test_df.columns)))
|
上面這些圖看到不用太驚恐,其實只是把上一篇的概念用在新的特徵上面,看看這些新特徵的分布是否有其一致性。而最後我們的測試資料及訓練資料都各增加了八個新特徵。
Model
首先,將原本的資料做一個修改 : 拿掉ID,並將 target獨立出來
1 2
| features = [c for c in train_df.columns if c not in ['ID_code', 'target']] target = train_df['target']
|
設置模型參數 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| param = { 'bagging_freq': 5, 'bagging_fraction': 0.4, 'boost_from_average':'false', 'boost': 'gbdt', 'feature_fraction': 0.05, 'learning_rate': 0.01, 'max_depth': -1, 'metric':'auc', 'min_data_in_leaf': 80, 'min_sum_hessian_in_leaf': 10.0, 'num_leaves': 13, 'num_threads': 8, 'tree_learner': 'serial', 'objective': 'binary', 'verbosity': 1 }
|
最重要的就是 Run Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| %%time folds = StratifiedKFold(n_splits=10, shuffle=False, random_state=44000) oof = np.zeros(len(train_df)) predictions = np.zeros(len(test_df)) feature_importance_df = pd.DataFrame()
for fold_, (trn_idx, val_idx) in enumerate(folds.split(train_df.values, target.values)): print("Fold {}".format(fold_)) trn_data = lgb.Dataset(train_df.iloc[trn_idx][features], label=target.iloc[trn_idx]) val_data = lgb.Dataset(train_df.iloc[val_idx][features], label=target.iloc[val_idx])
num_round = 1000000 clf = lgb.train(param, trn_data, num_round, valid_sets = [trn_data, val_data], verbose_eval=1000, early_stopping_rounds = 3000) oof[val_idx] = clf.predict(train_df.iloc[val_idx][features], num_iteration=clf.best_iteration) fold_importance_df = pd.DataFrame() fold_importance_df["Feature"] = features fold_importance_df["importance"] = clf.feature_importance() fold_importance_df["fold"] = fold_ + 1 feature_importance_df = pd.concat([feature_importance_df, fold_importance_df], axis=0) predictions += clf.predict(test_df[features], num_iteration=clf.best_iteration) / folds.n_splits
print("CV score: {:<8.5f}".format(roc_auc_score(target, oof)))
|
真的不誇張,我也是花了30幾分鐘才跑完這10個folds的驗證過程,經過這一系列的處理,這一次的CV Score可以達到90%,著實不低。
不過其實說真的,這個Case其實並沒有太多的處理,一方面是資料本身還算蠻乾淨,二來我想也是模型本身的原因。
最後,把特徵的重要性視覺化
1 2 3 4 5 6 7 8 9 10 11
| cols = (feature_importance_df[["Feature", "importance"]] .groupby("Feature") .mean() .sort_values(by="importance", ascending=False)[:150].index) best_features = feature_importance_df.loc[feature_importance_df.Feature.isin(cols)]
plt.figure(figsize=(14,28)) sns.barplot(x="importance", y="Feature", data=best_features.sort_values(by="importance",ascending=False)) plt.title('Features importance (averaged/folds)') plt.tight_layout() plt.savefig('FI.png')
|
最後,將結果輸出成CSV檔上傳就可以即時獲得名次
1 2 3
| sub_df = pd.DataFrame({"ID_code":test_df["ID_code"].values}) sub_df["target"] = predictions sub_df.to_csv("submission.csv", index=False)
|
後記
這一篇 kernel 在kaggle上面獲得了500多的按讚,應該會是在這題所有分享kernel裡面的最高分,說真的,的確實至名歸,在講解以及整個處理的流程都非常的順暢。
如果要拿這一篇當作第一篇kaggle Study的題材也算是蠻適合的,可以了解到流程,也可以知道Kaggler到底都在幹些什麼事情。
不過,簡單易懂也有相對來說的可惜,可惜並沒有遇到比較接近現實的資料狀況 ( EX: 缺失值、異常值、特徵的選擇及縮放.... ),不過對於第一次接觸的新手來說,如果一開始就這麼刺激,可能看完就放棄了XDDD
註釋
這樣的表格其實無法排序,所以.unstack()
可以說是將整個表格換做可以排序的方式