Kaggleで学ぶ機械学習(分類)-特徴量エンジニアリング-

Kaggle
黒猫さん
黒猫さん

“特徴量エンジニアリング”ってなにかしらね。

機械学習の入門書籍等を拝見すると特徴量エンジニアリングはモデルの精度に大きな影響を及ぼす重要な作業という記載をされていることが多々あります。

今回はそんな特徴量エンジニアリングをKaggleのコンペを通じて学んでいきましょう!

この記事は続編です。前編は以下のURLからご覧ください。

対象コンペ

前回に引き続き“Home Credit Default Risk”コンペになります。

Home Credit Default Risk | Kaggle
Can you predict how capable each applicant is of repaying a loan?

簡単に説明すると…

お金をその人に貸したとき、
  その人がしっかりと返せる人なのか貸し倒れする人なのかを判別するコンペになります。

特徴量エンジニアリング

 そもそも特徴量エンジニアリングとはモデルの精度向上のため有効な変数(特徴量)を新たに生成したり、逆に精度に悪影響を及ぼす変数(特徴量)を減らしたりする作業です。

 モデルの学習には良質なインプットデータが必要不可欠です。そのため、特徴量エンジニアリングを行って学習により使いやすいデータにするんですね。

 それでは、実際に特徴量エンジニアリングを行っていきましょう。

特徴量エンジニアリングの種類

ここでは特徴量エンジニアリングを大きく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のノートブックを見ながら引き続き学びます‼

タイトルとURLをコピーしました