LoginSignup
11
7

More than 1 year has passed since last update.

lightGBMでGPUを利用した高速化【Ubuntu18.04】

Last updated at Posted at 2022-07-18

概要

タイトルの通りで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利用の恩恵があるかもしれません。

少しでもトレーニング時間の短縮を目指す方はぜひ利用してみてください!

参考文献

  1. LightGBM ~GPU Tuning Guide and Performance Comparison

  2. Python: CatBoost を GPU で学習させる

11
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7