【初心者向け】LightGBM (回帰分析編)【Python】【機械学習】

AI・機械学習
スポンサーリンク

【本記事の内容】LightGBMの基本的な使い方(回帰分析編)

近年Kaggleなどで主流となっているデータ分析のモデル「LightGBM」の基本的な使い方について初学者向けに丁寧に解説します。

※ とりあえず「LightGBMの使い方 (ソースコード)」を見たい人はこちら

※Pythonおよび周辺パッケージのインストール方法を知りたい方やPythonおよび基本ライブラリについて勉強したい方はこちら

LightGBMとは ?

LightGBMとは「機械学習のフレームワークで、決定木アルゴリズムに基づいた勾配ブースティング(GBDT)の一種」です(2017年 microsoft社発)。近年、Kaggle(データ分析コンテストプラットフォーム)で主流となっているモデルでもあります。

最初のうちは「現在データ分析で最も人気で有力なモデルがLightGBMという認識で大丈夫だと思います。(もちろん厳密なことを言えばまた違ってきますが)

※詳しい理論的な説明については(忘れなければ)後で上げるつもりです。

LightGBMの良い点

LightGBMが他のモデルと比較して、特に優れている点としては以下の2つが挙げられます。

  • 基本的に予測精度が良い
  • 学習時間が比較的短い

他にも良い点としては、

  • どの特徴量が重要かを算出できる(特徴量重要度の算出)」
  • 特徴量のスケーリングを揃える(標準化などを行う)必要がない
  • 欠損値をそのまま扱える
  • 不要な特徴量を入れても精度が落ちにくい

などが挙げられます。(ここらへんはLightGBMに限らずGBDT系のアルゴリズムで一般的に言えることです。)

LightGBMの文法(2種類)について

実際にLightGBMの使い方の説明に入る前に注意しておくべき点があります。

それは「LightGBMには文法が2種類ある」ということです。具体的には、「Python API (ネイティブのAPI)」と「scikit-learn API(Python APIをベースに作られたAPI)」の2種類です。(ちなみにAPIとは書き方のルールのようなものを指します。) これらは書き方が異なるだけで本質的には同じ動作をします。

※ 特に初学者はここで混乱しがちです。具体的には、「LightGBM」「LightGBM 使い方」とか検索して、複数のサイトを見比べればわかりますが、「アレ….さっきのサイトとLightGBMの書き方が違うぞ…」といったことがよくあります。これは上で述べたような「LightGBMの書き方が2種類存在すること」が原因です。

scikit-learn API  と Python API の比較

簡単にこれらの違いについて見ていきます。

scikit-learn API の特徴
  • scikit-learn likeな文法で書ける
  • Python APIに比べてカスタム性に劣る
Python API の特徴
  • カスタム性が高い
  • scikit-learnに慣れている人にとっては慣れるまでに少し時間がかかる

※基本的には「Python API」で書くことをお勧めします。

説明の準備

では、実際にLightGBMの使い方の説明をする前に「そもそも回帰分析とは何か」の説明 と 使用するデータセットの「Bostonデータ」の説明 を軽くしていきます。

※ 説明が不要でコードのみを見たい方はこちら

回帰分析とは ?

回帰分析は簡単に言うと「連続値の予測に関する分析」です。

具体例としては、「ある人の身長、筋肉量、胸囲、腹囲などから体重を予測する」モデルを作成する分析を回帰分析と言います。もっと詳しく言うと、

    体重(kg)  =   0.60×身長(cm) + 0.80×筋肉量(kg)  + 0.05×胸囲(cm) + 0.10×腹囲 (cm) – 10.00

みたいなモデルを作ることが回帰分析です。そして、LightGBMなどの分析手法は与えられたデータから上記の例の「0.60, 0.80, 0.05, 0.10, -10 」といった係数を求めるタスクを行っています。

※ちなみに、この次で出てくる「目的変数」とは「予測したい変数」、「説明変数」とは「目的変数を予測するための変数」を指します。上の例では「目的変数」は「体重」、「説明変数」は「身長」「筋肉量」「胸囲」「腹囲」となります。

Bostonデータセット

Bostonデータセットの中身を簡単に見ていきます。データの概要としてはBostonの犯罪件数とそれに関係のありそうな特徴量のデータセットとなっています。

【目的変数】

CRIM : 人口 1 人当たりの犯罪発生数

【説明変数】

ZN :   25,000 平方フィート以上の住居区画の占める割合
INDUS :   小売業以外の商業が占める面積の割合
CHAS :   チャールズ川によるダミー変数 (1: 川の周辺, 0: それ以外)
NOX :   NOx(窒素酸化物) の濃度
RM :   住居の平均部屋数
AGE :   1940 年より前に建てられた物件の割合
DIS :   5 つのボストン市の雇用施設からの距離 (重み付け済)
RAD :   環状高速道路へのアクセスしやすさ
TAX :   10,000 ドルあたりの不動産税率の総計
PTRATIO :   町ごとの児童と教師の比率
B :   町ごとの黒人 (Bk) の比率を次の式で表したもの。 1000(Bk – 0.63)^2
LSTAT :   給与の低い職業に従事する人口の割合 (%)

LightGBMの基本的な使い方(回帰分析編)

では、実際に「Boston」データセットを題材にしてLightGBMで回帰分析を行っていきます。(目的変数は「CRIM」: 人口 1 人当たりの犯罪発生数)

準備

【ライブラリのインポート、関数の定義】
# ライブラリのインポート
import pandas as pd # 基本ライブラリ
import numpy as np # 基本ライブラリ
import matplotlib.pyplot as plt # グラフ描画用
import seaborn as sns; sns.set() # グラフ描画用
import warnings # 実行に関係ない警告を無視
warnings.filterwarnings('ignore')
import lightgbm as lgb #LightGBM
from sklearn import datasets
from sklearn.model_selection import train_test_split # データセット分割用
from sklearn.metrics import mean_squared_error # モデル評価用(平均二乗誤差)
from sklearn.metrics import r2_score # モデル評価用(決定係数)

# データフレームを綺麗に出力する関数
import IPython
def display(*dfs, head=True):
    for df in dfs:
        IPython.display.display(df.head() if head else df)
【データの読み込み、トレーニングデータ・テストデータの分割】
# Boston データセットの読み込み
boston = datasets.load_boston()
df = pd.DataFrame(boston.data, columns=boston.feature_names) # データフレームへの格納

# データの確認
print(df.shape) # データサイズの確認(データ数,特徴量数(変数の個数))
display(df) # df.head()に同じ(文中に入れるときはdisplay()を使う)

# 説明変数,目的変数
X = df.drop('CRIM',axis=1).values # 説明変数(CRIM以外の特徴量)
y = df['CRIM'].values # 目的変数(CRIM)

# トレーニングデータ,テストデータの分割
X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=0.20, random_state=2)

※トレーニングデータはモデルの学習用のデータで、テストデータはモデルの性能評価用のデータを指します。わざわざ2つに分割する理由は「モデルの学習用に使用したデータでモデルの評価も行ってしまうと性能が高く評価されてしまう」ためです。このイメージとしては、テスト問題そのものを勉強してテストを受けている感じです。

【出力】
(506, 13)
CRIM ZN INDUS B LSTAT
0 0.00632 18.0 2.31 396.90 4.98
1 0.02731 0.0 7.07 396.90 9.14
2 0.02729 0.0 7.07 392.83 4.03
3 0.03237 0.0 2.18 394.63 2.94
4 0.06905 0.0 2.18 396.90 5.33

LightGBM (scikit-learn API)

【モデルの学習】
# モデルの学習
model = lgb.LGBMRegressor() # モデルのインスタンスの作成
model.fit(X_train, y_train) # モデルの学習

# テストデータの予測
y_pred = model.predict(X_test)
【予測値の確認】
# 真値と予測値の表示
df_pred = pd.DataFrame({'CRIM':y_test,'CRIM_pred':y_pred})
display(df_pred)

# 散布図を描画(真値 vs 予測値)
plt.plot(y_test, y_test, color = 'red', label = 'x=y') # 直線y = x (真値と予測値が同じ場合は直線状に点がプロットされる)
plt.scatter(y_test, y_pred) # 散布図のプロット
plt.xlabel('y') # x軸ラベル
plt.ylabel('y_test') # y軸ラベル
plt.title('y vs y_pred') # グラフタイトル
【出力】
CRIM CRIM_pred
0 5.82115 4.747133
1 1.12658 -2.650033
2 0.07886 0.742876
3 0.10008 0.362406
4 20.08490 17.476328

 

※ 直線 y = x を引いているのは、目的変数の値y(真値)と予測値y_predのズレ具合を見るためです。(直線に近いほど, 真値y と予測値y_predの値は近いといえます。 ) 

【モデルの評価】
# モデル評価
# rmse : 平均二乗誤差の平方根
mse = mean_squared_error(y_test, y_pred) # MSE(平均二乗誤差)の算出
rmse = np.sqrt(mse) # RSME = √MSEの算出
print('RMSE :',rmse)

# r2 : 決定係数
r2 = r2_score(y_test,y_pred)
print('R2 :',r2)
【出力】
RMSE : 5.139727770083283
R2 : 0.624707345544621

LightGBM (Python API)

【モデルの学習】
# 学習に使用するデータを設定
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) 

# LightGBM parameters
params = {
        'task': 'train',
        'boosting_type': 'gbdt',
        'objective': 'regression', # 目的 : 回帰  
        'metric': {'rmse'}, # 評価指標 : rsme(平均二乗誤差の平方根) 
}

# モデルの学習
model = lgb.train(params,
                  train_set=lgb_train, # トレーニングデータの指定
                  valid_sets=lgb_eval, # 検証データの指定
                  )

# テストデータの予測
y_pred = model.predict(X_test)
【出力】
[1]	valid_0's rmse: 7.87566
[2]	valid_0's rmse: 7.39188
[3]	valid_0's rmse: 6.95883
[4]	valid_0's rmse: 6.59709
[5]	valid_0's rmse: 6.27845
...
[96]	valid_0's rmse: 5.10767
[97]	valid_0's rmse: 5.11754
[98]	valid_0's rmse: 5.12227
[99]	valid_0's rmse: 5.11618
[100]	valid_0's rmse: 5.13973
【予測値の確認】
# 真値と予測値の表示
df_pred = pd.DataFrame({'CRIM':y_test,'CRIM_pred':y_pred})
display(df_pred)

# 散布図を描画(真値 vs 予測値)
plt.plot(y_test, y_test, color = 'red', label = 'x=y') # 直線y = x (真値と予測値が同じ場合は直線状に点がプロットされる)
plt.scatter(y_test, y_pred) # 散布図のプロット
plt.xlabel('y') # x軸ラベル
plt.ylabel('y_test') # y軸ラベル
plt.title('y vs y_pred') # グラフタイトル
【出力】
CRIM CRIM_pred
0 5.82115 4.747133
1 1.12658 -2.650033
2 0.07886 0.742876
3 0.10008 0.362406
4 20.08490 17.476328

【モデルの評価】
# モデル評価
# rmse : 平均二乗誤差の平方根
mse = mean_squared_error(y_test, y_pred) # MSE(平均二乗誤差)の算出
rmse = np.sqrt(mse) # RSME = √MSEの算出
print('RMSE :',rmse)

#r2 : 決定係数
r2 = r2_score(y_test,y_pred)
print('R2 :',r2)
【出力】
RMSE : 5.139727770083283
R2 : 0.624707345544621

LightGBM (Python API) – カスタム(パラメータを少し調整した場合)

【モデルの学習】
# 学習に使用するデータを設定
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

# LightGBM parameters
params = {
        'task': 'train',
        'boosting_type': 'gbdt',
        'objective': 'regression', # 目的 : 回帰  
        'metric': {'rmse'}, # 評価指標 : rsme(平均二乗誤差の平方根) 
        'learning_rate': 0.1,
        'num_leaves': 23,
        'min_data_in_leaf': 1,
        'num_iteration': 1000, #1000回学習
        'verbose': 0
}

# モデルの学習
model = lgb.train(params, # パラメータ
            train_set=lgb_train, # トレーニングデータの指定
            valid_sets=lgb_eval, # 検証データの指定
            early_stopping_rounds=100 # 100回ごとに検証精度の改善を検討 → 精度が改善しないなら学習を終了(過学習に陥るのを防ぐ)
               )

# テストデータの予測
y_pred = model.predict(X_test)
【出力】
[1]	valid_0's rmse: 7.71522
Training until validation scores don't improve for 100 rounds.
[2]	valid_0's rmse: 7.11206
[3]	valid_0's rmse: 6.58775
[4]	valid_0's rmse: 6.10861
[5]	valid_0's rmse: 5.7183
...
[120] valid_0's rmse: 3.90496 
[121] valid_0's rmse: 3.90481 
[122] valid_0's rmse: 3.90497 
[123] valid_0's rmse: 3.90494 
[124] valid_0's rmse: 3.90495 
Early stopping, best iteration is: 
[24] valid_0's rmse: 3.83994
【予測値の確認】
# 真値と予測値の表示
df_pred = pd.DataFrame({'CRIM':y_test,'CRIM_pred':y_pred})
display(df_pred)

# 散布図を描画(真値 vs 予測値)
plt.plot(y_test, y_test, color = 'red', label = 'x=y') # 直線y = x (真値と予測値が同じ場合は直線状に点がプロットされる)
plt.scatter(y_test, y_pred) # 散布図のプロット
plt.xlabel('y') # x軸ラベル
plt.ylabel('y_test') # y軸ラベル
plt.title('y vs y_pred') # グラフタイトル
【出力】
CRIM CRIM_pred
0 5.82115 4.615314
1 1.12658 2.033706
2 0.07886 0.448928
3 0.10008 0.448928
4 20.08490 24.277344

【モデルの評価】
# モデル評価
# rmse : 平均二乗誤差の平方根
mse = mean_squared_error(y_test, y_pred) # MSE(平均二乗誤差)の算出
rmse = np.sqrt(mse) # RSME = √MSEの算出
print('RMSE :',rmse)

#r2 : 決定係数
r2 = r2_score(y_test,y_pred)
print('R2 :',r2)
【出力】
RMSE : 3.8399389842757987
R2 : 0.7905219716354266

簡単な分析結果・実行結果の解説

評価指標

・RMSE : 平均二乗誤差の平方根。予測誤差を表す。値が低いほうがモデルの予測精度が良いとされる。

・R2 : 決定係数。モデルの当てはまりの良さを表す。0以上1以下の値をとり, 値が大きいほど(1に近いほど)モデルの当てはまりが良いとされる。

結果まとめ
  • scikit-learn APIのモデル(デフォルト)による予測結果とPython APIのモデル(デフォルト)による予測結果は全く同じであった。
  • Python APIのモデル(カスタム)による予測結果 は scikit-learn APIのモデル(デフォルト)および Python APIのモデルの予測結果より精度が良かった。
  • scikit-learn API, PythonAPI(デフォルト) :   RMSE(予測誤差) : 5.140,  R2(決定係数) : 0.625 → モデルの精度はまずまず良いといえる
  • Python API (カスタム)   :    RMSE(予測誤差) : 3.840,  R2(決定係数) : 0.791 → モデルの精度は良いといえる

おわりに

このblogでは、今後「数学 (主に統計学,微積分,線形代数)・AI機械学習(特にデータ解析系)」の「データサイエンス」に関するトピックを自分の学習の振り返りもかねて投稿していきます。

初学者にもわかりやすいようになるべく丁寧に書いていくのでよろしくお願いします。

何かあればコメントをお願いします。

参考文献

【今回の記事に関する参考文献】

※特に②の参考文献はわかりやすかく、かつ内容もしっかりしていた(LightGBMの理論まで触れていた)ので、興味がある方はぜひそちらもご覧ください。

① Python API — LightGBM 2.3.2 documentation (公式ドキュメント)
  https://lightgbm.readthedocs.io/en/latest/Python-API.html
②【機械学習実践】LightGBMで回帰してみる【Python】

 https://rin-effort.com/2019/12/29/machine-learning-6/
③ Python: LightGBM を使ってみる
  https://blog.amedama.jp/entry/2018/05/01/081842

    【環境セットアップについての参考文献】

    ① Anaconda インストール方法 (AnacondaをインストールすることでPythonおよび機械学習ライブラリ一式が使用できるようになります)
    https://www.python.jp/install/anaconda/windows/install.html

    LightGBMをインストール (LightGBMとAnacondaには含まれていないので別途インストールする必要があります)
    https://qiita.com/m__k/items/5f905cf5d20e875961b5

    ③Python と 基本ライブラリ(Pandas,Numpyなど)の講座 : かめ@米国データサイエンティストのブログ (網羅性も高く、説明も丁寧で、さらに実務よりの講座なのでPythonの入門に最適だと思います。)
    https://datawokagaku.com/category/%e8%ac%9b%e5%ba%a7%e4%b8%80%e8%a6%a7/

     

    コメント

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