Kaggleで学ぶ機械学習(分類)-ベースライン作成-

Kaggle

黒猫さん
黒猫さん

Kaggleの「Titanic」コンペの次は何に取り組めば良いのだろう。

様々なKaggleの入門書籍でTitanicをベースにいろいろ学びますが、ほかのデータを扱ってみたいと思いませんか?

今回はKaggleで過去に開催された分類コンペのデータを用いて機械学習を学んでいきます!

今回の扱うコンペは「分類」タスクです。教師あり機械学習の一部に属しています。

つまり、Kaggleのチュートリアルで有名なTitanicコンペと同様です!

対象コンペ

今回の対象コンペは“Home Credit Default Risk”になります。

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

以下、コンペ説明訳。

クレジットヒストリーが十分でない、あるいは存在しないために、ローンを組むのに苦労している人は少なくありません。そして、残念なことに、このような人々は信頼できない金融業者に利用されることが多いのです。

ホームクレジットは、銀行口座を持たない人々に前向きで安全な借り入れ体験を提供することで、金融包摂の拡大に努めています。ホームクレジットは、この十分なサービスを受けていない人々にポジティブなローン体験を提供するために、通信事業者や取引情報を含む様々な代替データを活用して、顧客の返済能力を予測しています。

ホームクレジットは現在、様々な統計や機械学習の手法を用いてこれらの予測を行っていますが、彼らはデータの潜在能力を最大限に引き出すためにKagglersに挑戦しています。そうすることで、返済能力のある顧客が拒否されることなく、顧客が成功するための元本、満期、返済カレンダーでローンが組まれるようになります。

Translated with DeepL

https://www.kaggle.com/competitions/home-credit-default-risk
Home Credit Default Risk | Kaggle
Can you predict how capable each applicant is of repaying a loan?

提供データ

このコンペで提供されているデータはCSV形式のファイルは9種類です。

以下がそれぞれのCSVファイルの詳細です。

  • application_{train|test}.csv
    • これはメインテーブルで、Train(TARGETあり)とTest(TARGETなし)の2つのファイルに分かれています。
  • bureau.csv
    • Credit Bureauに報告された、他の金融機関から提供された顧客の過去のすべての債権(サンプルにローンがある顧客)。
    • サンプル内の各ローンについて、申込日以前にクレジットビューローに登録されていた債権数と同数の行が存在する。
  • bureau_balance.csv
    • Credit Bureauに登録された過去のクレジットの月別残高。
  • POS_CASH_balance.csv
    • 申請者がホームクレジットを利用していた過去のPOS(Point of Sales)およびキャッシングローンの月次残高スナップショット。
  • credit_card_balance.csv
    • 申請者がホームクレジットを利用している過去のクレジットカードの月次残高スナップショット。
  • previous_application.csv
    • サンプルに含まれるローンをお持ちのお客様のホームクレジットローンの過去の全申し込み。
  • installments_payments.csv
    • サンプルに含まれるローンに関連するホームクレジットの過去に払い出された債権の返済履歴。
  • HomeCredit_columns_description.csv
    • このファイルには、各種データファイルのカラムに関する記述が含まれています。

Titanicに比べると扱うファイルが増え、一気に本格的になりましたね。

扱うファイル(テーブル)が増えるとそれぞれのリレーションシップをしっかりと把握している必要があります。(把握していないとテーブル同士の結合とかできないので。。。)

テーブル同士のリレーションシップを示した図はデータ概要を説明しているページにあるため、ぜひ見てください。

一度にたくさんのテーブルを使って学習を行うと、どのデータが効果的なのかわからなくなるため、まずはapplication_{train|test}.csvを用いて学習モデルを作成していきます。

とりあえずサブミット(ベースライン作成)

今回は分析環境にKaggleのノートブックを使用します。

Import

まずは必要なライブラリ類をインポートします。

後々の特徴量エンジニアリングやパラメータチューニングに必要なライブラリもインポートします。

import numpy as np
import pandas as pd
import re
import pickle
import gc
import optuna

# scikit-learn
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score

# LightGBM
import lightgbm as lgb

import warnings
warnings.filterwarnings("ignore")

ファイル読み込み

今回は“application_train.csv”を使用していきます。

読み込んだデータが正しいかデータの中身も確認しておきましょう。

# ファイルの読み込み・データ確認
application_train = pd.read_csv("../input/home-credit-default-risk/application_train.csv")
print(application_train.shape)
application_train.head()

データセット作成

application_trainのテーブルから”TARGET”, “SK_ID_CURR”カラムを削除したX_trainと”TARGET”のみしたY_train、”SK_ID_CURR”のみにしたid_trainの三種類のデータセットを作成します。

また、lightGBMで学習できるようにカテゴリ変数をcategory型へ変換しておきます。

#データセットの作成
x_train = application_train.drop(columns=["TARGET", "SK_ID_CURR"])
y_train = application_train["TARGET"]
id_train = application_train[["SK_ID_CURR"]]
#カテゴリ変数をcategory型へ変換(lightGBMに使うため)
for col in x_train.columns:
    if x_train[col].dtype=="O":
        x_train[col] = x_train[col].astype("category")

バリデーション設計

クロスバリデーションによるデータの分割を行います。

分割の際にデータの割合が不均衡な場合、foldごとに割合が同じになるように層化分割を用います。

'''
バリデーションのindexeリスト作成
分割の割合が一定になるように層化分割を行う(StratifidKFold)
'''
# 層化分割したバリデーションのindexのリスト作成
cv = list(StratifiedKFold(n_splits=5, shuffle=True, random_state=123).split(x_train, y_train))

# indexの確認:fold=0のtrainデータ
print("index(train):", cv[0][0])

# indexの確認:fold=0のvalidデータ
print("index(valid):", cv[0][1])

モデルの学習

モデルの学習をしていきます。まずは学習データと検証データを用意します。

先ほど、層化分割して作成したindexリストを用います。

'''学習データと検証データに分類'''
# 0fold目のindexのリスト取得
nfold = 0
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, y_tr.shape, id_tr.shape)
print(x_va.shape, y_va.shape, id_va.shape)

それではデータの準備が完了したので、実際にモデルの学習を行っていきます。

前にもチラッと出てきましたが、今回使用するモデルはlightGBM(分類)です。

大体のKaggle入門本に出てくるとても万能なモデルですね。

パラメータのチューニングは後程行いますので、今回の学習では以下の内容で行います。

# モデルの学習
params = {
    'boosting_type': 'gbdt',
    'objective': 'binary', 
    'metric': 'auc',
    'learning_rate': 0.05,
    'num_leaves': 32,
    'n_estimators': 100000,
    "random_state": 123,
    "importance_type": "gain",
}

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
         )

# モデルの保存
with open("model_lgb_fold0.pickle", "wb") as f:
    pickle.dump(model, f, protocol=4)

学習済みモデルの評価

次に学習したモデルを評価しましょう。学習したモデルを用いて推論値を算出することで精度を確認することができます。

今回の場合は下図のようにして評価指標の計算を行うことができます。

'''モデル評価'''
# 学習データの推論値取得とROC計算
y_tr_pred = model.predict_proba(x_tr)[:,1]
metric_tr = roc_auc_score(y_tr, y_tr_pred)

# 検証データの推論値取得とROC計算
y_va_pred = model.predict_proba(x_va)[:,1]
metric_va = roc_auc_score(y_va, y_va_pred)

# 評価値を入れる変数の作成(最初のfoldのときのみ)
metrics = []

# 評価値を格納
metrics.append([nfold, metric_tr, metric_va])

# 結果の表示
print("[auc] tr:{:.4f}, va:{:.4f}".format(metric_tr, metric_va))

左が学習データでの推論値、右が検証データでの推論値です。学習データの推論値は過学習確認用です。

[auc] tr:0.8126, va:0.7586

説明変数の重要度

学習したモデルから説明変数の重要度を取得してみましょう。どの説明変数が重要なのかを確認することで、データの取捨選択に役立ちます。

'''説明変数の重要度取得(学習したモデルから説明変数の重要度を取得)'''
# 重要度の取得
imp_fold = pd.DataFrame({"col":x_train.columns, "imp":model.feature_importances_, "nfold":nfold})
# 確認(重要度の上位10個)
display(imp_fold.sort_values("imp", ascending=False)[:10])

# 重要度を格納する5fold用データフレームの作成
imp = pd.DataFrame()
# imp_foldを5fold用データフレームに結合
imp = pd.concat([imp, imp_fold])

“imp”が重要度を示します。“EXT_SOURCE_3”は外部データソースからのスコアみたいですね。

    col	        imp	    nfold
41	EXT_SOURCE_3	    66225.020483	0
40	EXT_SOURCE_2	    52568.833805	0
38	ORGANIZATION_TYPE	20218.523523	0
39	EXT_SOURCE_1	    19776.252288	0
6	AMT_CREDIT	    8111.321247	0
8	AMT_GOODS_PRICE	    7120.960365	0
15	DAYS_BIRTH	    7042.223005	0
7	AMT_ANNUITY	    6992.551795	0
16	DAYS_EMPLOYED	    5236.514120	0
26	OCCUPATION_TYPE	    4376.651746	0

OOFデータの推論値取得

OOFデータというのは学習データのうち学習に使わなかったデータのことです。要するに検証に用いたデータですね。

集めたOOFを結合することで、学習用データセット全体の推論値を取得することができたり、分布の確認やアンサンブル時の重みづけに有効なようです。

折角なので取得しておきましょう。

#OOFデータの推論値取得
train_oof = pd.concat([
    id_train,
    pd.DataFrame({"true": y_train, "pred": train_oof}),
], axis=1)
train_oof.head()

5-foldクロスバリデーション

お気づきかと思いますが、ここまでのコードは1fold分です。

ループ処理で5fold分の学習を行うようにしましょう。

# ハイパーパラメータの設定
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"]
    

推論

学習済みモデルを用いてテストデータから推論してみましょう。

トレーニングデータと同様の読み込み処理を行います。

'''推論用データセットの作成'''
# ファイルの読み込み
application_test = pd.read_csv("../input/home-credit-default-risk/application_test.csv")

# データセットの作成
x_test = application_test.drop(columns=["SK_ID_CURR" ])
id_test = application_test[["SK_ID_CURR"]]

# カテゴリ変数をcategory型に変換
for col in x_test.columns:
    if x_test[col].dtype=="O":
        x_test[col] = x_test[col].astype("category")

それでは実際に推論を行って、結果を提出用CSVファイルに保存してみましょう。

保存した学習モデルを読み込み、モデルごとに推論を行います。推論で得た値の平均値を取得し、それを最終的な結果としてファイル出力をします。

pred = np.zeros((len(x_test), len(list_nfold)))
for nfold in list_nfold:
    print("-"*20, nfold, "-"*20)
    fname_lgb = "model_lgb_fold{}.pickle".format(nfold)
    with open(fname_lgb, "rb") as f:
        model = pickle.load(f)
    pred[:, nfold] = model.predict_proba(x_test)[:,1]
    
pred = pd.concat([
    id_test,
    pd.DataFrame({"pred": pred.mean(axis=1)}),
], axis=1)

#提出ファイルの作成
df_submit = pred.rename(columns={"pred":"TARGET"})
print(df_submit.shape)
display(df_submit.head())

# ファイル出力
df_submit.to_csv("submission_baseline.csv", index=None)

出力されたCSVファイルを“Home Credit Default Risk”にCSVファイルをサブミットしてみましょう!

提出されたフォーマットが正しければScoreは表示されます。

おわりに

とりあえずベースライン作成の仕方を学びました。

次は特徴量エンジニアリング!

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