“特徴量エンジニアリング”ってなにかしらね。
機械学習の入門書籍等を拝見すると特徴量エンジニアリングはモデルの精度に大きな影響を及ぼす重要な作業という記載をされていることが多々あります。
今回はそんな特徴量エンジニアリングをKaggleのコンペを通じて学んでいきましょう!
この記事は続編です。前編は以下のURLからご覧ください。
対象コンペ
前回に引き続き“Home Credit Default Risk”コンペになります。
簡単に説明すると…
お金をその人に貸したとき、
その人がしっかりと返せる人なのか貸し倒れする人なのかを判別するコンペになります。
特徴量エンジニアリング
そもそも特徴量エンジニアリングとはモデルの精度向上のため有効な変数(特徴量)を新たに生成したり、逆に精度に悪影響を及ぼす変数(特徴量)を減らしたりする作業です。
モデルの学習には良質なインプットデータが必要不可欠です。そのため、特徴量エンジニアリングを行って学習により使いやすいデータにするんですね。
それでは、実際に特徴量エンジニアリングを行っていきましょう。
特徴量エンジニアリングの種類
ここでは特徴量エンジニアリングを大きく2種類に分けます。
1つ目は、ドメイン知識を使った特徴量エンジニアリング。
(仮説に基づく特徴量生成 etc..)
2つ目は、ドメイン知識を使わずに行う特徴量エンジニアリング。
(対数変換、one-hot-encoding etc..)
今回の記事では1つ目のドメイン知識を使った特徴量エンジニアリングを行っていきます。
取り扱うデータ
前回に引き続き“application_train.csv”を使用していきます。
“application_train.csv”に含まれるカラムの数はかなり多いため、どのようなデータがあるのか把握するのはとても大変ですが1つずつ調べてみましょう。
それでは実際に分析対象のCSVファイルを読み込んでみましょう。
# ファイルの読み込み・データ確認
application_train = pd.read_csv("../input/home-credit-default-risk/application_train.csv")
print(application_train.shape)
application_train.head()
仮説設定
仮説に基づく特徴量生成を実施するため、まずは仮説を設定しましょう。
以下が用意した仮説になります。
- 所得金額が同じでも就労期間が短い方がハイステータスな人材だと予想できるため、貸し倒れリスクが低いと考える。
- 外部機関によるスコアが平均的に高い方が貸し倒れリスクが低いと考えられる。
- 所得金額が同じでも家族人数が多いほうが経済的な負担が大きいため貸し倒れリスクが高いと考えられる。
このように与えられたデータから背景(今回の場合はお金を貸す人)を考察して仮説を設定します。
今回のコンペでは一般的な知識から特徴量生成まで行けそうですが、より専門的なコンペの内容だとそれは難しいです。その場合、ドメイン知識の学習から始める必要があるため手間が掛かってしまいますね。
状況に応じて、ドメイン知識から仮説を設定するか否かが決まりそうです。
特徴量生成
それでは上記の仮説より、特徴量生成を行っていきましょう。
“application_train.csv”のデータを取り込んだデータフレームに新たなカラムとして生成した特徴量を追加しましょう。また、外部機関スコアについては平均値以外にも最大値等の統計量を追加しておきましょう。
'''仮説に基づく特徴量生成'''
# [仮説] 所得金額が同じでも就労期間が短い方がハイステータスな人材だと予想できるため、貸し倒れリスクが低いと考える。
application_train['INCOME_div_EMPLOYED'] = application_train['AMT_INCOME_TOTAL'] / application_train['DAYS_EMPLOYED']
# [仮説] 外部機関によるスコアが平均的に高い方が貸し倒れリスクが低いと考えられる。
application_train["EXT_SOURCE_mean"] = application_train[["EXT_SOURCE_1", "EXT_SOURCE_2", "EXT_SOURCE_3"]].mean(axis=1)
application_train["EXT_SOURCE_max"] = application_train[["EXT_SOURCE_1", "EXT_SOURCE_2", "EXT_SOURCE_3"]].max(axis=1)
application_train["EXT_SOURCE_min"] = application_train[["EXT_SOURCE_1", "EXT_SOURCE_2", "EXT_SOURCE_3"]].min(axis=1)
application_train["EXT_SOURCE_std"] = application_train[["EXT_SOURCE_1", "EXT_SOURCE_2", "EXT_SOURCE_3"]].std(axis=1)
application_train["EXT_SOURCE_count"] = application_train[["EXT_SOURCE_1", "EXT_SOURCE_2", "EXT_SOURCE_3"]].notnull().sum(axis=1)
# [仮説] 所得金額が同じでも家族人数が多いほうが経済的な負担が大きいため貸し倒れリスクが高いと考えられる。
application_train['INCOME_div_PERSON'] = application_train['AMT_INCOME_TOTAL'] / application_train['CNT_FAM_MEMBERS']
学習
追加したデータを学習に用いてみましょう。
データセットの生成や学習の仕方は前回の内容と同様です。
#データセットの作成
x_train = application_train.drop(columns=["TARGET", "SK_ID_CURR"])
y_train = application_train["TARGET"]
id_train = application_train[["SK_ID_CURR"]]
for col in x_train.columns:
if x_train[col].dtype=="O":
x_train[col] = x_train[col].astype("category")
# ハイパーパラメータの設定
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'learning_rate': 0.05,
'num_leaves': 32,
'n_estimators': 100000,
"random_state": 123,
"importance_type": "gain",
}
list_nfold=[0,1,2,3,4]
n_splits=5
train_oof = np.zeros(len(x_train))
metrics = []
imp = pd.DataFrame()
# cross-validation
cv = list(StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=123).split(x_train, y_train))
for nfold in list_nfold:
print("-"*20, nfold, "-"*20)
# make dataset
idx_tr, idx_va = cv[nfold][0], cv[nfold][1]
x_tr, y_tr, id_tr = x_train.loc[idx_tr, :], y_train[idx_tr], id_train.loc[idx_tr, :]
x_va, y_va, id_va = x_train.loc[idx_va, :], y_train[idx_va], id_train.loc[idx_va, :]
print(x_tr.shape, x_va.shape)
# train
model = lgb.LGBMClassifier(**params)
model.fit(x_tr,
y_tr,
eval_set=[(x_tr, y_tr), (x_va, y_va)],
early_stopping_rounds=100,
verbose=100
)
fname_lgb = "model_lgb_fold{}.pickle".format(nfold)
with open(fname_lgb, "wb") as f:
pickle.dump(model, f, protocol=4)
#evaluate
y_tr_pred = model.predict_proba(x_tr)[:,1]
y_va_pred = model.predict_proba(x_va)[:,1]
metric_tr = roc_auc_score(y_tr, y_tr_pred)
metric_va = roc_auc_score(y_va, y_va_pred)
metrics.append([nfold, metric_tr, metric_va])
print("[auc] tr:{:.4f}, va:{:.4f}".format(metric_tr, metric_va))
# oof
train_oof[idx_va] = y_va_pred
# imp
_imp = pd.DataFrame({"col":x_train.columns, "imp":model.feature_importances_, "nfold":nfold})
imp = pd.concat([imp, _imp])
print("-"*20, "result", "-"*20)
# metric
metrics = np.array(metrics)
print(metrics)
print("[cv] tr:{:.4f}+-{:.4f}, va:{:.4f}+-{:.4f}".format(
metrics[:,1].mean(), metrics[:,1].std(),
metrics[:,2].mean(), metrics[:,2].std(),
))
print("[oof] {:.4f}".format(
roc_auc_score(y_train, train_oof)
))
# oof
train_oof = pd.concat([
id_train,
pd.DataFrame({"pred":train_oof})
], axis=1)
# importance
imp = imp.groupby("col")["imp"].agg(["mean", "std"]).reset_index(drop=False)
imp.columns = ["col", "imp", "imp_std"]
説明変数の影響度
追加した特徴量が学習モデルにどれほど影響を及ぼしているのか確認してみましょう。
追加した特徴量が有効か否かをこれで判別することができます。
#説明変数の重要度の確認
imp.sort_values("imp", ascending=False)[:10]
説明変数の重要度を見ると、特徴量エンジニアリングで追加した“EXT_SOURCE_mean”が強い影響を及ぼしているということがわかりますね。
col imp imp_std 44 EXT_SOURCE_mean 114005.214702 1381.645644 10 ANNUITY_div_CREDIT 23720.301550 805.397477 112 ORGANIZATION_TYPE 22660.210567 1372.230448 41 EXT_SOURCE_3 12046.854638 886.653726 24 DAYS_BIRTH 8108.684084 578.972393 45 EXT_SOURCE_min 7727.391587 314.203161 39 EXT_SOURCE_1 7155.619219 472.422492 2 AMT_GOODS_PRICE 6148.167858 364.159044 0 AMT_ANNUITY 6091.805210 581.987900 46 EXT_SOURCE_std 5830.390690 679.963947
おわりに
今回は特徴量エンジニアリング(仮説)の仕方を学んでみました。
ドメイン知識がある場合は非常に有効な特徴量生成を行えますが、ドメイン知識が足りていないとそもそもの仮説設定が困難に感じますね。
ドメイン知識が足りていない場合は、探査的データ分析(EDA)を行うことで知識を深めることができそうですが、どこまで通用するのでしょう。
まだまだ勉強不足なのでKaggleのノートブックを見ながら引き続き学びます‼