福岡人データサイエンティストの部屋

データサイエンスを極めるため、日々の学習を綴っています。

【LightGBM】LightGBMでクロスバリデーションをするときのサンプルプログラムの注意点【Python】



こんにちは!こーたろーです。


今回、訳あってLightGBMを使ったシミュレーションを行っています。


その中で、精度を上げるためにクロスバリデーションを行ってみたいと思い、LightGBMクロスバリデーションの情報を探していました。


ちょうどサンプルコードがあったため、そのまま貼り付けて、必要な箇所のデータを変更してみましたが、エラーが出て苦労しました。


結果として、ほぼ手組状態に陥ったため、最新の注意点を含めて、サンプルコードを見直してみました。


新旧比較しながらご覧ください。


それでは早速まいりましょう!


LightGBMにおけるクロスバリデーションサンプルプログラム(旧)



一旦、使用したサンプルプログラムの例を取り上げます。

import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

df = pd.read_csv("test.csv", index_col=0)

kf = KFold(n_splits=5, shuffle=True, random_state=123)
valid_scores = []
models = []

col = "Target_name"
X = df.drop(col, axis=1)
y = df[col]

for fold, (train_indices, valid_indices) in enumerate(kf.split(X)):
    X_train, X_valid = X[train_indices], X.[valid_indices]
    y_train, y_valid = y.[train_indices], y.[valid_indices]
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_eval = lgb.Dataset(X_valid, y_valid)

    params = {
        "objective": "regression",
        "metrics": "mae",
    }
    
    model = lgb.train(
        params,
        lgb_train,
        valid_sets = lgb_eval,
        num_boost_round = 100
    )
    
    print("now caluculating MAE values .....")

    y_valid_pred = model.predict(X_valid)
    score = mean_absolute_error(y_valid, y_valid_pred)
    print(f'fold {fold} MAE: {score}')
    valid_scores.append(score)

    models.append(model)



このサンプルに、read_csvにてファイルを読み込んだとしても、そのままではエラーになります。


一度、サンプルプログラムの内容を確認してから、解説をしていきます。

サンプルプログラムの中身

ライブラリのインポート


import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error



ライブラリのインポートです。


ここでは、データを読み込むためのpandasと学習器であるlightgbm、クロスバリデーション及び評価をするためのscikit-learnを呼び出しましょう。

データの読み込みとデータの分割



データの読み込みます。IDなどのインデックスがある場合は、index_colにてインデックスにしたい列を指定して読み込みます。

df = pd.read_csv("test.csv", index_col=0)

col = "Target_name"
X = df.drop(col, axis=1)
y = df[col]



Target_nameの列は、目的変数の列です。該当の目的変数をyへ、説明変数をXとして分割しておきます。


LightGBMでは、データフレームのまま取扱いができるため、np.arrayにわざわざしなくても大丈夫です。

K-交差クロスバリデーション



クロスバリデーションを実行して、データを作成していきます。

kf = KFold(n_splits=5, shuffle=True, random_state=123)
valid_scores = []
models = []

for fold, (train_indices, valid_indices) in enumerate(kf.split(X)):
    X_train, X_valid = X[train_indices], X.[valid_indices]
    y_train, y_valid = y.[train_indices], y.[valid_indices]
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_eval = lgb.Dataset(X_valid, y_valid)



まず、K-交差クロスバリデーションのインスタンスを発行します。分割数などを引数にいれて作成します。


また、バリデーションスコアとモデルを格納するリストも事前に作成しておきましょう。


for文では、enumerateを使って、バリデーションデータを読み込みます。


今回、分割数は5ですので、5つのリストが出てきます。


そのリストは、学習データXを分割した時のインデックスが返され(学習データのインデックス、バリデーションデータのインデックス)のリストのタプルになっています。それに加え、分割した順に番号が付与される(enumerateを使っているため)ことになります。


つまり、foldには分割したケースの何番目か(0からスタートの番号)が入り、その順番におけるバリデーションデータのインデックスのリストのタプルが(train_indeces、valid_indeces)それぞれに代入されていきます。


あとは、そのインデックスの値に該当するデータをX、yから取り出して、学習データと検証データに分ける処理をします。


その後、このデータをlgb.Datasetを使って、LightGBMに投入するデータ形式に変換すれば、データ前処理は完了です。

LightGBMのパラメータ設定とモデルの作成・学習



データの前処理が終わったら、LightGBMのインスタンス作成を行っていきます。


今回は、回帰を行うモデルで、評価関数としてはMAEを用います。


パラメータ設定を行ったら、modelにインスタンスを作成し、学習を行っていきます。

    params = {
        "objective": "regression",
        "metrics": "mae",
    }
    
    model = lgb.train(
        params,
        lgb_train,
        valid_sets = lgb_eval,
        num_boost_round = 100
    )
    
    print("now caluculating MAE values .....")

    y_valid_pred = model.predict(X_valid)
    score = mean_absolute_error(y_valid, y_valid_pred)
    print(f'fold {fold} MAE: {score}')
    valid_scores.append(score)

    models.append(model)



検証データでMAEを再度計算させて、学習済みのモデルのMAE値を出力します。


また、モデルについてもfor文の間、リストに追加保存していきます。


これがサンプルプログラムの概要です。


出来たモデルの中から、valid_scoreの値を見ながら最終的なモデルを決定し、テストデータや実データで予測させるという流れです。

サンプルプログラムを使う時の注意点


ここから、上記サンプルプログラムを使う時の注意点を述べていきます。

ファイルの読み込み時のインデックス


df = pd.read_csv("test.csv") # ※インデックスは読み込まない

col = ["Target_name", "Index_name"]
X = df.drop(col, axis=1)
y = df[col]



実は、サンプルプログラムにおけるK-交差クロスバリデーションを実施した際に「kf.split(X)」にて分割したときの出力データですが、先に述べた通りインデックスの値の配列がタプルで返ってきます。


このタプル値は、データの実際のインデックスの値ではなく、データの順番に0から割り当てられるインデックスを返します。


そのため、「X_train, X_valid = X[train_indices], X.[valid_indices]」のように、データのインデックス値が一致したデータを抽出する際にマッチするデータが見つからないことになります。


よって、インデックスがあるデータの場合は、インデックスを「index_col=0」で読み込まずに、列として読み込みその後、不要な列として削除しましょう。

データ分割時の構文エラー


for fold, (train_indices, valid_indices) in enumerate(kf.split(X)):
    X_train, X_valid = X.iloc[train_indices], X.iloc[valid_indices]
    y_train, y_valid = y.iloc[train_indices], y.iloc[valid_indices]



サンプルプログラムでは、ilocが使われていませんでした。そのため、Xのcolumnを検索する結果となり、インデックスの値とカラムの値を比較するようになっています。


複数のサイトで同じようにilocがなく、初めはどうして間違いか気づきませんでしたが、エラーを見ながらよく考えたら、おかしな構文だと気づけます。

データの型のチェック



LightGBMは、データフレームをそのまま投入できるので便利なのですが、1点注意が必要です。


それは、データの型です。


基本的に、前処理段階でint64やfloat64については、できていると思いますが、問題なのはobjectになっている文字列です。


lightGBMは、決定木を使っているため、文字列で分けられているカテゴリーも設定できるのですが、typeがobjectのままではデータ投入ができません。


従って、タイプがobjectとなっている列に関しては、astype("category")にて、カテゴリーに変換しておく必要があります。

df[col] = df[col].astype("category")




LightGBMのクロスバリデーションサンプルプログラム(新)



以上の結果から、最終的なLightGBMにおけるクロスバリデーションを使ったモデル学習のサンプルプログラムは、以下のようになりました。

import lightgbm as lgb
from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

df = pd.read_csv("test.csv") # ※インデックスは読み込まない

col = ["Target_name", "Index_name"]
X = df.drop(col, axis=1)
y = df[col]

#df[***] = df[***].astype("category")      #必要に応じて設定


kf = KFold(n_splits=6, shuffle=True, random_state=100)
valid_scores = []
models = []


for fold, (train_indices, valid_indices) in enumerate(kf.split(X)):
    X_train, X_valid = X.iloc[train_indices], X.iloc[valid_indices]
    y_train, y_valid = y.iloc[train_indices], y.iloc[valid_indices]
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_eval = lgb.Dataset(X_valid, y_valid)

    print("fold No. = ", fold)
    params = {
        "objective": "regression",
        "metrics": "mae",
    }
    
    model = lgb.train(
        params,
        lgb_train,
        valid_sets = lgb_eval,
        num_boost_round = 100,
    )
    
    print("now caluculating MAE values .....")
    y_valid_pred = model.predict(X_valid)
    score = mean_absolute_error(y_valid, y_valid_pred)
    print(f'fold {fold} MAE: {score}')
    valid_scores.append(score)

    models.append(model)



今回は、結構役に立つ情報ではないかなと思います。


エラーを検索した時に、同じよなところで詰まっていた人が結構いたので、取り纏めてみました。


皆さんの参考になれば幸いです。


ではでは。


作ってわかる! アンサンブル学習アルゴリズム入門