概要
タイトルの通りでlightGBMをGPU経由で動作させる方法を自分の備忘録のためにまとめました。
ビルドしてインストールする方法は弊環境ではうまくいかなったため、pip経由でインストールする方法になります。
最後にまとめておりますが、lightGBMをGPUで動作させた場合、弊環境では処理時間を25%短縮することができました。
CPUはAMD Ryzen 9 5950Xを利用しているので、CPUはあまり高性能じゃないけどGPUは高性能なものを利用している、という方は更に高速化の恩恵があるかもしれません。
環境構築
実行環境
環境は以下の通り。
- 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利用の恩恵があるかもしれません。
少しでもトレーニング時間の短縮を目指す方はぜひ利用してみてください!