【Ubuntu18.04】lightGBMをGPUで処理させて速度を比較した
概要
タイトルの通りでlightGBMをGPU経由で動作させる方法を自分の備忘録のためにまとめました。 ビルドしてインストールする方法は弊環境ではうまくいかなったため、pip経由でインストールする方法になります。
最後にまとめておりますが、lightGBMをGPUで動作させた場合、処理時間を25%短縮することができました。 XGBoostやcatboostなど他のGBDT系のライブラリに比べるとGPU化の恩恵は少ないですが、25%の短縮は大きいと思います。
環境構築
実行環境
環境は以下の通り。 - OS: Ubuntu 18.04.6 LTS - CPU: AMD Ryzen 9 5950X - GPU: RTX2070
NVIDIAドライバのインストール
以下のコマンドでNVIDIAのドライバをapt install経由でダウンロードできるようにします。 ppa追加時にENTERを押します。
$ sudo add-apt-repository ppa:graphics-drivers/ppa $ sudo apt update
以下のコマンドでダウンロードできるドライバの一覧を確認します。
今回は推奨(recommended)されているnvidia-driver-470
をインストールすることにしました。
$ ubuntu-drivers devices WARNING:root:_pkg_get_support nvidia-driver-515: package has invalid Support PBheader, cannot determine support level WARNING:root:_pkg_get_support nvidia-driver-510: package has invalid Support PBheader, cannot determine support level == /sys/devices/pci0000:00/0000:00:03.1/0000:08:00.0 == modalias : pci:v000010DEd00001F02sv00001462sd00003734bc03sc00i00 vendor : NVIDIA Corporation driver : nvidia-driver-515 - third-party non-free driver : nvidia-driver-470 - third-party non-free recommended driver : nvidia-driver-510 - third-party non-free driver : xserver-xorg-video-nouveau - distro free builtin
以下のコマンドでドライバをインストールし.再起動します。
$ sudo apt install nvidia-drivers-470 $ sudo reboot
lightGBM(GPU版)のインストール
再起動した後,再度ターミナルを開いて必要なパッケージをインストールしていきます。 以下のコマンドを入力します。
$ sudo apt install --no-install-recommends cmake build-essential clinfo opencl-headers libboost-all-dev
今回,python3.7&仮想環境で動作確認をしたいため,以下のコマンドを入力してpython3.7をインストールしておきます。 また,インストールした後に仮想環境を作って立ち上げておきます。 不要な方は、以下のコマンドは飛ばしてください。
$ sudo apt install python3.7 python3.7-venv $ python3.7 -m venv venv $ source ./venv/bin/activate
以下のコマンドで必要なpythonパッケージをインストールします。
$ pip install -U pip setuptools wheel
$ pip install numpy scipy scikit-learn
ここまできて,ようやくGPU版のlightGBMをインストールすることができます。 以下のコマンドを入力してインストールします。
$ pip install lightgbm --install-option=--gpu --install-option="--opencl-library=/usr/lib/x86_64-linux-gnu/libOpenCL.so.1"
以上でGPUに対応したlightGBMをインストールすることができました。
GPU認識確認
システム上からGPUが認識されているかを確認します。 GPUの認識にcatboostを使うので、追加でインストールしておきます。
$ pip install catboost
以下のコマンドを入力します。。 GPUが認識されていれば「1」が表示されます。1が表示されない場合は再起動をしてみてください。
$ python -c "from catboost.utils import get_gpu_device_count; print(get_gpu_device_count())"
動作確認
実際にCPUを利用した場合とGPUで動作させた場合で比較をしてみました。 ソースは以下のサイトのものを参考にさせていただきました。この場を借りてお礼を申し上げます。
lightGBM CPU動作
CPU版は以下のソース参照。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import time import logging from contextlib import contextmanager import lightgbm as lgb from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.metrics import log_loss LOGGER = logging.getLogger(__name__) @contextmanager def timeit(): """処理にかかった時間を計測してログに出力するコンテキストマネージャ""" start = time.time() yield end = time.time() elapsed = end - start LOGGER.info(f'Elapsed Time: {elapsed:.2f} sec') def main(): logging.basicConfig(level=logging.INFO, stream=sys.stderr, ) # 疑似的な教師信号を作るためのパラメータ dist_args = { # データ点数 'n_samples': 100_000, # 次元数 'n_features': 1_000, # その中で意味のあるもの 'n_informative': 100, # 重複や繰り返しはなし 'n_redundant': 0, 'n_repeated': 0, # タスクの難易度 'class_sep': 0.65, # 二値分類問題 'n_classes': 2, # 生成に用いる乱数 'random_state': 42, # 特徴の順序をシャッフルしない (先頭の次元が informative になる) 'shuffle': False, } # 教師データを作る train_x, train_y = make_classification(**dist_args) # データセットを学習用と検証用に分割する x_tr, x_val, y_tr, y_val = train_test_split(train_x, train_y, test_size=0.3, shuffle=True, random_state=42, stratify=train_y) # CatBoost が扱うデータセットの形式に直す train_pool = lgb.Dataset(x_tr, label=y_tr) valid_pool = lgb.Dataset(x_val, label=y_val) # 学習用のパラメータ params = { # タスク設定と損失関数 'objective': 'binary', # 学習率 'learning_rate': 0.02, # 学習ラウンド数 'num_boost_round': 5_000, # 検証用データの損失が既定ラウンド数減らなかったら学習を打ち切る # NOTE: ラウンド数を揃えたいので今回は使わない # 'early_stopping_rounds': 100, # 乱数シード 'random_state': 42, # 学習に GPU を使う場合 # 'device': 'gpu', } # モデルを学習する with timeit(): model = lgb.train(params, train_pool, valid_sets=[valid_pool], verbose_eval=100 ) # 検証用データを分類する y_pred = model.predict(x_val) # ロジスティック損失を確認する metric = log_loss(y_val, y_pred) LOGGER.info(f'Validation Metric: {metric}') if __name__ == '__main__': main()
lightGBM GPU動作
GPU版は以下の通り。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import time import logging from contextlib import contextmanager import lightgbm as lgb from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.metrics import log_loss LOGGER = logging.getLogger(__name__) @contextmanager def timeit(): """処理にかかった時間を計測してログに出力するコンテキストマネージャ""" start = time.time() yield end = time.time() elapsed = end - start LOGGER.info(f'Elapsed Time: {elapsed:.2f} sec') def main(): logging.basicConfig(level=logging.INFO, stream=sys.stderr, ) # 疑似的な教師信号を作るためのパラメータ dist_args = { # データ点数 'n_samples': 100_000, # 次元数 'n_features': 1_000, # その中で意味のあるもの 'n_informative': 100, # 重複や繰り返しはなし 'n_redundant': 0, 'n_repeated': 0, # タスクの難易度 'class_sep': 0.65, # 二値分類問題 'n_classes': 2, # 生成に用いる乱数 'random_state': 42, # 特徴の順序をシャッフルしない (先頭の次元が informative になる) 'shuffle': False, } # 教師データを作る train_x, train_y = make_classification(**dist_args) # データセットを学習用と検証用に分割する x_tr, x_val, y_tr, y_val = train_test_split(train_x, train_y, test_size=0.3, shuffle=True, random_state=42, stratify=train_y) # CatBoost が扱うデータセットの形式に直す train_pool = lgb.Dataset(x_tr, label=y_tr) valid_pool = lgb.Dataset(x_val, label=y_val) # 学習用のパラメータ params = { # タスク設定と損失関数 'objective': 'binary', # 学習率 'learning_rate': 0.02, # 学習ラウンド数 'num_boost_round': 5_000, # 検証用データの損失が既定ラウンド数減らなかったら学習を打ち切る # NOTE: ラウンド数を揃えたいので今回は使わない # 'early_stopping_rounds': 100, # 乱数シード 'random_state': 42, # 学習に GPU を使う場合 'device': 'gpu', } # モデルを学習する #model = lgb.train(params, train_pool,valid_sets=[valid_pool]) with timeit(): model = lgb.train(params, train_pool, valid_sets=[valid_pool], verbose_eval=100 ) model.save_model('model_gpu.txt' #, num_iteration=model.best_iteration ) #model.fit(train_pool, # eval_set=valid_pool, # verbose_eval=100, # use_best_model=True, # ) # 検証用データを分類する y_pred = model.predict(x_val) # ロジスティック損失を確認する metric = log_loss(y_val, y_pred) LOGGER.info(f'Validation Metric: {metric}') if __name__ == '__main__': main()
実行時間の比較
それぞれ実行すると、以下のような結果となりました。
time | log loss | |
---|---|---|
CPU | 203.67 sec | 0.114378 |
GPU | 154.56 sec | 0.113432 |
# CPU [4900] valid_0's binary_logloss: 0.116022 [5000] valid_0's binary_logloss: 0.114378 INFO:__main__:Elapsed Time: 203.67 sec INFO:__main__:Validation Metric: 0.11437848050873242
# GPU [4900] valid_0's binary_logloss: 0.115171 [5000] valid_0's binary_logloss: 0.113433 INFO:__main__:Elapsed Time: 154.56 sec INFO:__main__:Validation Metric: 0.11343276824400272
まとめ
多少調べたことがある方は解ると思いますが、lightGBMはツリー系のなかでもあまりGPU処理の恩恵が得られないというライブラリです。タスクによってはCPUとあまり差異がなかったり、他のGBDT系ライブラリのほうが高速に動作します。 (この論文やこのページを参照)
弊環境・今回のタスクではDNNのような劇的な高速化は得られませんでしたが、およそ25%の処理スピードアップとなりました。
lightGBMはあまりGPUの利用率が高くないため、高価で高性能なGPUを利用しても処理速度の向上には寄与しないと考えれられます。 一方で、弊環境のAMD Ryzen9 5950Xの16コアCPUよりGPUを利用したほうが高速で処理させることができたため、コア数の少ないCPUを利用している場合は、GPU利用の恩恵があるかもしれません。
少しでもトレーニング時間の短縮を目指す方はぜひ利用してみてください!