【Python】LightGBMの実装方法

Python
Image by Gerd Altmann from Pixabay
スポンサーリンク

本記事ではLightGBMの使い方を解説します。

テーブルデータコンペでよく使われるモデルですね!

・コンペで使いやすいおススメなモデルが知りたい

・機械学習の基礎を身に着けたい

・LightGBMの使い方を知りたい

LightGBMはkaggleなどのコンペでよく使われるモデルであり、基本的にはテーブルデータで利用されます。

XGBoostの親戚ですが、LightGBMの方がCPU上での計算が早くメモリ消費も少ないです。

実際コンペでも、XGBoostではメモリエラーで学習が止まるけどLightGBMなら提出できた、なんてこともあります。

なので、迷ったらLightGBMを使ったらいいですよー。

ただしXGBoostはGPUに対応しているので、計算速度においては負けている面もあります。

それでは今回はLightGBMの使い方をマスターしましょう!

回帰モデル

Q:この住宅の価格はいくらですか
A:〇〇万円です

こんな感じで連続した数値を予測するのが回帰モデルです。

load_boston

import pandas as pd
from sklearn.datasets import load_boston
data = load_boston()
df = pd.DataFrame(data["data"], columns = data["feature_names"])
df["target"] = data["target"]
print(df.shape)
df.head()

importでライブラリを入れます。

pandas:表計算ライブラリ
load_boston:住宅価格データセット

pandasの.DataFrameでデータを表に起こしましょう。

予測したい目的変数を”target”としました。

データ分割

モデルを作成する前にデータ分割をしましょう。

from sklearn.model_selection import KFold
kf = KFold(n_splits = 5, shuffle = True)
df["fold"] = -1
for fold, (train_idx, valid_idx) in enumerate(kf.split(df)):
    df.loc[valid_idx, "fold"] = fold
print(df["fold"].value_counts())

KFoldはデータを等分に分割してくれます。n_splits = 5なので5分割です。

実行して各分割に何行データが入っているか確かめましょう。

fold = 0
train = df.loc[df["fold"] != fold].copy()
valid = df.loc[df["fold"] == fold].copy()
print(train.shape, valid.shape)

今回は0番のデータを評価用にしました。

feat_cols = train.drop(columns = ["fold", "target"]).columns.tolist()
print(feat_cols)

X_train = train[feat_cols]
X_valid = valid[feat_cols]
y_train = train["target"]
y_valid = valid["target"]

予測に使う特徴量をX、予測したい”target”をyにしました。

“fold”と”target”は特徴として使いません。

モデル作成

モデル作成で必要なことは以下の通り。

①ライブラリのインポート
②Dataset形式に変換
③パラメータ設定
④trainで学習

LightGBMをインポートします。よくlgbと省略されます。

import lightgbm as lgb

次に.DatasetでLightGBM専用の形式に変換します。

学習データ(train)と評価データ(valid)の両方で変換しましょう。

train_set = lgb.Dataset(X_train, y_train)
valid_set = lgb.Dataset(X_valid, y_valid)

最後にパラメータ(学習条件)を辞書型データで設定しましょう。

params = {
    "objective" : "regression",
    "metric" : "rmse"
}
objective:モデルの種類。“regression”は回帰のこと。
metric:モデルを評価する方法。“rmse”は平均二乗誤差の平方根。

パラメータはたくさんあります。

過学習を防ぐためには”num_leaves”などを設定することが有効です。

詳しくは公式ドキュメントを見ることをおススメします。

それでは学習していきましょう!

model = lgb.train(
    params = params,
    train_set = train_set,
    valid_sets = [train_set, valid_set],
    num_boost_round = 100
)
params:学習条件(さっき作ったやつ)
train_set:学習用データ
valid_sets:性能評価するデータ(metricで設定した指標で計算します)
num_boost_round:モデルを改善する回数

LightGBMはnum_boost_roundの数だけ評価指標が改善されるようにモデルを作り直します。

実行するとこのように各回数目でのモデル性能を出力します。

“training’s”が学習データでの、”valid_1’s”が評価データでの計算結果です。

だんだん良くなっていますね!

予測結果

predictで予測できます。評価データ(valid)を渡しましょう。

pred = model.predict(X_valid)
print(pred)

printで予測結果を出力しました。正解データと比較してみましょう。

print(pred[:10])
print("=" * 100)
print(y_valid.values[:10])

y_validが正解データです。ボチボチ近い値を予測してそうですね。

では回帰タスクでよく使われる平均二乗誤差の平方根(RMSE)を計算しましょう。

from sklearn.metrics import mean_squared_error
import numpy as np
mse = mean_squared_error(y_valid, pred)
rmse = np.sqrt(mse)
print(rmse)

mean_squared_error(平均二乗誤差)をインポートしました。

正解データと予測値を入れると誤差を計算してくれます。np.sqrtで平方根にしましょう。

ランダム要素が入ってくるので必ずではありませんが、3~5くらいになると思います。

“target”の分散が9くらいなので
RMSEが4くらいならソコソコいいですね。

予測結果を可視化してみます。↓

import matplotlib.pyplot as plt
plt.scatter(y_valid, pred, alpha = 0.5)
x_line = np.linspace(0, 60, 100)
plt.plot(x_line, x_line, color = "red")
plt.show()

赤線はy = xの直線なので、この線上にあれば正確に予測できています。

いい感じですね。

学習状況可視化

モデルが改善されていく過程を可視化したい

result_data = {}
model = lgb.train(
    params = params,
    train_set = train_set,
    valid_sets = [train_set, valid_set],
    num_boost_round = 100,
    evals_result = result_data
)

学習状況を保存するために新しく辞書型の”result_data”を作りました。

trainの引数“evas_result”に渡しましょう。

すると各回数目での学習結果が保存されます。

plt.plot(result_data["training"]["rmse"], color = "red", label = "train")
plt.plot(result_data["valid_1"]["rmse"], color = "blue", label = "valid")
plt.legend()
plt.show()

学習データの結果は”training”に評価データの結果は”valid_1″に入っています。

さらにその中にmetricで設定した指標である”rmse”があります。

赤が学習データでの結果で青が評価データでの結果ですね。

基本的に学習データの方がスコアは良いです。

今回は誤差が指標なので学習データ(赤)の方が小さいですね。

・評価データ(青)が改善されているか
・評価データ(青)で頭打ちになっていないか
・学習データと評価データとの差がどれくらいか(データによる)

この辺りに注目するとよいかと思います。

あまりにも学習データと評価データとの差が大きいなら、過学習の対策を検討しましょう。

EarlyStoppingRounds

評価データでのスコアが頭打ちになるとそれ以降の学習は時間のムダですよね。

そこで“early_stopping_rounds”を設定してみます。↓

result_data = {}
model = lgb.train(
    params = params,
    train_set = train_set,
    valid_sets = [train_set, valid_set],
    num_boost_round = 100,
    early_stopping_rounds = 5,
    verbose_eval = 50,
    evals_result = result_data
)

引数”early_stopping_rounds”を数値で設定しましょう。

指定した回数だけ評価データの改善が見られなかった(ほぼ同じスコアだった)とき、学習を自動で止めてくれる機能です。

5回スコアの改善がなかったらストップするようにしました。

plt.plot(result_data["training"]["rmse"], color = "red", label = "train")
plt.plot(result_data["valid_1"]["rmse"], color = "blue", label = "valid")
plt.legend()
plt.show()

このようにしばらく改善が見られないと学習が止まります。

もちろん改善が続いていればnum_boost_roundの回数まで進みますよ。

・num_boost_roundを大きめに設定
・early_stopping_roundsを設定(だいたい10くらい)

で過不足のない学習ができるかと思います。

特徴量重要度

で、どの特徴が予測に効いているの?

ということが気になりますよね。

LightGBMでは予測における特徴量重要度を計算できます。

importance = model.feature_importance()
print(importance)

“feature_importance”で特徴量を計算できます。

数値のリストが出力されますが、これが特徴量重要度です。

importance_df = pd.DataFrame({"imoportance" : importance})
importance_df.index = feat_cols
importance_df.T

データフレームとして表にしました。

“LSTAT”が大きいですね。

plt.barh(width = importance_df.values.ravel(), y = importance_df.index)
plt.show()

このように棒グラフで可視化するとわかりやすいですね。

ただし、特徴量重要度にはいくつかの種類があります。

・split:決定木の分割に使用された回数
gain:目的変数への寄与度

splitだとカテゴリ特徴よりも数値特徴が重要になりやすいみたいです。(この辺は曖昧です)

“feature_importance”ではデフォルトでsplitの重要度を計算します。

gainを確認したい場合は引数“importance_type”を設定しましょう1。

importance = model.feature_importance(importance_type = "split")
print(importance)
importance = model.feature_importance(importance_type = "gain")
print(importance)

下段がgainでの重要度です。

for t in ["split", "gain"]:
    importance = model.feature_importance(importance_type = t)
    importance_df = pd.DataFrame({"imoportance" : importance})
    importance_df.index = feat_cols
    plt.barh(width = importance_df.values.ravel(), y = importance_df.index)
    plt.title(t)
    plt.show()

全然違いますね。どちらにせよ”LSTAT”は重要そうです。

個人的には困ったらgainでいいと思います。

二値分類モデル

Q:明日雨が降る?
A:降る or 降らない

こんな感じで2択の分類をするのが二値分類です。

パラメータ設定まではほとんど回帰モデルと同じなので割愛して紹介しますね。

load_breast_cancer

import pandas as pd
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
df = pd.DataFrame(data["data"], columns = data["feature_names"])
df["target"] = data["target"]
print(df.shape)
df.head()

load_breast_cancerをインポートしました。

病院での検査結果を予測するデータセットで、0が陰性を1が陽性を示します。

30列の特徴量から“target”が0か1かを予測するわけですね。

データ分割

from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits = 5, shuffle = True)
df["fold"] = -1
for fold, (train_idx, valid_idx) in enumerate(skf.split(X = df, y = df["target"])):
    df.loc[valid_idx, "fold"] = fold
print(df["fold"].value_counts())

学習データと評価データに分割しました。

分類問題ではラベル(今回でいう”target”)の分布に偏りがないように分割するといいです。

そこでStratifiedKFoldが便利。

引数yに分布を保ちたいデータを指定して分割しましょう。

df.groupby("fold").mean()["target"]

このコードで分割した番号ごとでの”target”の割合が見れます。だいたい6割くらい。

fold = 0
train = df.loc[df["fold"] != fold].copy()
valid = df.loc[df["fold"] == fold].copy()
print(train.shape, valid.shape)

feat_cols = train.drop(columns = ["fold", "target"]).columns.tolist()
print(feat_cols)

X_train = train[feat_cols]
X_valid = valid[feat_cols]
y_train = train["target"]
y_valid = valid["target"]

今回も番号0を評価データとして使いましょう。

モデル作成

回帰との大きな違いはパラメータ設定です!

import lightgbm as lgb
train_set = lgb.Dataset(X_train, y_train)
valid_set = lgb.Dataset(X_valid, y_valid)

params = {
    "objective" : "binary",
    "metric" : "binary_logloss"
}

result_data = {}
model = lgb.train(
    params = params,
    train_set = train_set,
    valid_sets = [train_set, valid_set],
    num_boost_round = 100,
    early_stopping_rounds = 5,
    verbose_eval = 50,
    evals_result = result_data
)
objective:binary
metric:binary_logloss

“binary”は二値を意味します。バイナリーオプションとかありますよね。

“binary_logloss”は損失関数の1つです。

メチャクチャざっくり言うと、

自信満々に予測を外したら損失が大きくなります。

学習過程を見てみましょう。↓

import matplotlib.pyplot as plt
plt.plot(result_data["training"]["binary_logloss"], color = "red", label = "train")
plt.plot(result_data["valid_1"]["binary_logloss"], color = "blue", label = "valid")
plt.legend()
plt.show()

いい感じですねー。

予測結果

predictで予測できます。

pred = model.predict(X_valid)
print(pred)

予測結果はラベルの0or1ではなく、”ラベル1である確率”を出力します。

例えば1個目のデータはラベル1(陽性)である確率が0.2%です。

3つ目のデータは98.9%でラベル1ですね。

これを0or1のラベルに変換しましょう。↓

import numpy as np
pred = np.where(pred > 0.5, 1, 0)
print(pred)
print("=" * 100)
print(y_valid.values)

numpyのwhereで0.5以上ならラベル1としました。

y_validが正解データです。だいたい合っていそうですね。

では正解率を計算してみましょう!

from sklearn.metrics import accuracy_score
acc = accuracy_score(y_valid, pred)
print(acc)

“accuracy_score”で正解率を計算できます。だいたい95%くらいになるはず。

AUCも計算してみましょう。

from sklearn.metrics import roc_auc_score
auc = roc_auc_score(y_valid, pred)
print(auc)

AUCは“roc_auc_score”で計算できます。これも95%くらいで高いはずです。

AUCって何?

という方はすみませんが調べてください。

例えばデータの99%がラベル0だったら、”全部0でぇ~す”というアホなモデルでも正解率99%です。

AUCなら偽陽性や偽陰性なども考慮して評価できます。

多クラス分類モデル

Q:明日の天気は?
A:晴れ or 曇り or 雨

こんな感じで複数選択肢があるのが他クラス分類。

これもパラメータ設定以外は共通です。

load_iris

import pandas as pd
from sklearn.datasets import load_iris
data = load_iris()
df = pd.DataFrame(data["data"], columns = data["feature_names"])
df["target"] = data["target"]
print(df.shape)
df.head()

loasd_irisは花の種類を0,1,2の3つのラベルで分類するデータセットです。

“target”の数値が0~2の3種類になっています。これを予測しましょう。

データ分割

from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits = 3, shuffle = True)
df["fold"] = -1
for fold, (train_idx, valid_idx) in enumerate(skf.split(X = df, y = df["target"])):
    df.loc[valid_idx, "fold"] = fold
print(df["fold"].value_counts())

データ分割をします。やってることはこれまでと同じです。

今回もStratifiedKFoldにしてラベル分布を保ちましょう。

データ数(行数)が少ないのでn_splits = 3にしときました。

fold = 0
train = df.loc[df["fold"] != fold].copy()
valid = df.loc[df["fold"] == fold].copy()
print(train.shape, valid.shape)
print(train["target"].value_counts())

feat_cols = train.drop(columns = ["fold", "target"]).columns.tolist()
print(feat_cols)

X_train = train[feat_cols]
X_valid = valid[feat_cols]
y_train = train["target"]
y_valid = valid["target"]

特徴量と目的変数への分割です。

“target”に入っているラベルの分布を確認しておきましょう。

モデル作成

import lightgbm as lgb
train_set = lgb.Dataset(X_train, y_train)
valid_set = lgb.Dataset(X_valid, y_valid)

params = {
    "objective" : "multiclass",
    "metric" : "multi_logloss",
    "num_class" : 3
}

result_data = {}
model = lgb.train(
    params = params,
    train_set = train_set,
    valid_sets = [train_set, valid_set],
    num_boost_round = 100,
    early_stopping_rounds = 5,
    verbose_eval = 50,
    evals_result = result_data
)
objective:multiclass
metric:multi_logloss
num_class:ラベルの種類数(今回は3)

“num_class”でラベル数を指定することだけ注意しましょう!

学習結果はこんな感じ。↓

import matplotlib.pyplot as plt
plt.plot(result_data["training"]["multi_logloss"], color = "red", label = "train")
plt.plot(result_data["valid_1"]["multi_logloss"], color = "blue", label = "valid")
plt.legend()
plt.show()

評価データ(青)も小さいですね。

予測結果

predictで予測します。

pred = model.predict(X_valid)
print(pred.shape)
print(pred)

今回は3種のラベルなので、各行につき3つの確率が出力されます。

確率が高い列をラベルとして変換しましょう。

pred = pred.argmax(axis = 1)
print(pred)
print("=" * 100)
print(y_valid.values)

argmaxは数値が最も大きい位置を返してくれます。

次に正解率を見ましょう。

from sklearn.metrics import accuracy_score
acc = accuracy_score(y_valid, pred)
print(acc)

98%くらいになるかと思います。

多クラス分類ではAUCが計算できません。

適合率(precision)や再現率(recall)を計算しておきましょう。

from sklearn.metrics import precision_score, recall_score
precision = precision_score(y_valid, pred, average = "micro")
recall = recall_score(y_valid, pred, average = "micro")
print(precision, recall)
適合率:ラベル0と予測したうち、実際に0だった割合
再現率:実際にラベル0だったデータのうち、0と予測した割合

これをラベル0,1,2において計算し、平均をとります。

だいたい98%くらいなのでかなり優秀です。

まとめ

今回はLightGBMの使い方を解説しました。

パラメータ設定以外はどのモデルでも使い方は同じです。

おススメ書籍

モデル作成と前処理の基礎を学びたい人向けの本です。

Pythonの文法については解説していないので注意してください。

個人的にはさっさとkaggleに登録して簡単なコンペに参加することをおススメします。

この本を持っておけば最低限の知識を補填しながら挑戦できるはずです。

他のユーザーが上げているLightGBMのコードを写経すればメキメキ上達しますよ。

TabulerPlaygroundSeriesというコンペがあります。
メダルはもらえませんが初心者向けなのでおススメ。

コメント

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