XGBoost/LightGBM のGPUサポート状況調査とベンチマーク

**（Microsoft Blog記事に関して，[追記](http://qiita.com/TomokIshii/items/95c49d5e86f2eae61720#%E8%BF%BD%E8%A8%98lightgbm%E3%81%AE%E3%83%99%E3%83%B3%E3%83%81%E3%83%9E%E3%83%BC%E3%82%AF%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8Bblog%E8%A8%98%E4%BA%8B) しました．）**


Deep Learning計算では大活躍するGPU (Graphics Processing Unit) ですが，勾配ブースティングライブラリのXGBoostやLightGBMでは，少し前までGPUは使われていませんでした．理由としては，GPU演算が性能を発揮する線形代数的な要素がXGBoost/LightGBMの処理では少ないことがあります．しかしながら最近になって，XGBoost, LightGBM双方で，主に決定木を成長させるプロセスでGPUを活用し，処理速度を上げる機能がサポートされるようになりました．本記事では，XGBoostとLightGBMのGPUサポート状況，使い方，及びベンチマークの結果を紹介したいと思います．

（用いたプログラミング環境は，次の通りです：  Ubuntu 16.04LTS. Python 3.5.2, XGBoost 0.6, LightGBM 2.0.3, CUDA 8.0, gcc 5.4.0　)

## XGBoost (+GPU support) のインストール
GPUサポート機能を入れるために，XGBoost，LightGBM両方ともソースコードからビルドする必要があります．XGBoostのインストール関連ドキュメンは以下になります．

CUDA Accelerated Tree Construction Algorithms
https://github.com/dmlc/xgboost/tree/master/plugin/updater_gpu


XGBoost (全体パートの) Installation
https://xgboost.readthedocs.io/en/latest/build.html

プログラムの前提ライブラリとして，次が必要となります．

- CUDA Toolkit 7.5 or later
- CUB (CUB is a header only cuda library)
- (for Windows) NVIDIA NCCL

CUDA Toolkitのインストール方法は，別記事を参照ください．"CUB"については，xgboost 本体を git clone する過程で，submoduleの一つとしてcloneされました．

（ドキュメントより．）

```bash
# git 2.9.0+ (assumes only HEAD used for all submodules, but not true currently for dmlc-core and rabbit)
$ git clone --recursive --shallow-submodules https://github.com/dmlc/xgboost.git

# git 2.9.0-: (only cub is shallow, as largest repo)
$ git clone https://github.com/dmlc/xgboost.git cd bash plugin/updater/gpu/gitshallow_submodules.sh
```

私の`git`は，"shallow submodules" がサポートされていない ver.2.7.4 でしたが，`gitshallow_submodules.sh` で問題なく "CUB" が入りました．

ビルドは，以下の流れになります．

```bash
$ mkdir build
$ cd build
$ cmake .. -DPLUGIN_UPDATER_GPU=ON
$ make -j4  
# -j4 の4は，適当な数字に置き換えて下さい．
```

ビルドにて，CLI(Command Line Interface) の実行ファイル `xgboost` 及びライブラリ "libxgboost.so" が作られますので，その後，必要に応じて Python-package, R-package の
インストールを実行してください．以上の通り，XGBoost(+GPU) のインストールはそれほど難しくはないと思います．

## LightGBM (+GPU support) のインストール

LightGBMでは，ビルド時に問題多発で，インストールに苦労させられました．まず，関連ドキュメントをおさえておきます．

LightGBM GPU Tutorial
https://github.com/Microsoft/LightGBM/blob/master/docs/GPU-Tutorial.md

LightGBM (全体パートの) Installation
https://github.com/Microsoft/LightGBM/wiki/Installation-Guide

(ref.) GPU Tuning Guide and Performance Comparison
https://github.com/Microsoft/LightGBM/blob/master/docs/GPU-Performance.md

LightGBM +GPU の特徴として，OpenCLライブラリにより，AMD GPUがサポートされる点があります．現状，あまりAMD GPUでGPGPUを実施する人は多くないと思いますが，さすが Microsoft，AMD GPUユーザにも配慮した形となっています．またNVIDIAユーザーにとっては，前提ライブラリを用意するために以下の処理が必要とドキュメントにありました．

```bash
sudo apt-get update
sudo apt-get install --no-install-recommends nvidia-375
sudo apt-get install --no-install-recommends nvidia-opencl-icd-375 nvidia-opencl-dev opencl-headers
```

NVIDIAドライバはインストール済かもしれませんが，"nvidia-opencl-xxxx" 関係のパッケージは入れる必要があります．また，C++のBOOSTライブラリもビルドに必要です．

```bash
sudo apt-get install --no-install-recommends git cmake build-essential libboost-dev libboost-system-dev libboost-filesystem-dev
```

ドキュメントの説明が上記の通りでしたので，私の環境で足りない BOOSTライブラリを Ubuntu aptで入れてビルドを実施しましたが，全くビルド(CMake)が成功しませんでした．因みに，Ubuntu 16.04LTSのパッケージでインストールする場合，BOOSTのバージョンは，1.58.0 (2017-07時点) になります．

当初，発生したCMakeのエラーメッセージは，（入れたはずの）特定ライブラリが見つからないというものでした．エラーメッセージに従い，環境変数をセットしてもダメ．さらに調べると以下の説明がドキュメントにありました．

> libboost 1.56 or later (1.61 or later recommended). We use Boost.Compute as the interface to GPU, which is part of the Boost library since version 1.61.

libboost 1.58 は，一応，上の条件をクリアしているはずですが，BOOSTのバージョンを上げることにしました．リコメンドより新しい BOOST 1.62 を http://www.boost.org/ より入手し，HOME以下に展開の上，環境変数で場所の指示をしたところ，問題なくビルドすることができました．

```bash
export BOOST_ROOT = /home/username/boost_1_62_0
```

さらにCMakeのfindOpenCLモジュール(FindOpenCL)が，正しいOpenCLバージョンを検出しない問題もありました．これは，以下のようにすることで，問題が回避できました．

```bash
cmake -DUSE_GPU=1 -DOpenCL_LIBRARY=/usr/local/cuda-8.0/lib64/libOpenCL.so -DOpenCL_INCLUDE_DIR=/usr/local/cuda-8.0/include/ ..

```

（参考：　https://github.com/Microsoft/LightGBM/issues/458 ）

## テストとベンチマーク結果

2つの分類(classification)タスクでテストを行いました．一つは，"Kaggle Otto" の商品分類問題と，もう一つが "MNIST" 手書き文字分類問題です．それぞれ，XGBoostのGPUサポート無し／有り，LightGBMのサポート無し／有りで計算を実施しました．

XGBoostのClassifierオプション指定は，次のコードで行いました．(Scikit-learn APIを使っています．）

```py
def xgb_set_param(args):
    '''
      XGBoost (classifier) parameters
    '''
    params = {}
    if args.cpu:
        params['device'] = 'cpu'
        params['updater'] = 'grow_histmaker,prune'
    else:   # gpu
        params['device'] = 'gpu'
        params['gpu_id'] = 0
        params['updater'] = 'grow_gpu_hist'

    params['max_depth'] = 6
    params['learning_rate'] = 0.1   # alias of 'eta'
    params['objective'] = 'multi:softmax'
    params['n_estimators'] = 500
    params['n_jobs'] = -1

    return params
```

コマンドライン・オプションを"argparse"で取得した`args`の内容に従い，cpu/gpuをスイッチします．"cpu"オプションでは，'device'に'cpu'をセットし，'updater'に'grou_histmaker,prune'を選択．"gpu"オプションでは，'device', 'gpu_id'をセットし，'updater'に'grow_gpu_hist'をセットしました．分類器の性能に関わるパラメータは，cpu/gpuで共通なので，上記のように選びました．
（一応，狭い範囲の探索ですが， Grid Searchを行った結果を反映させています．）このパラメータを用いて，分類器のインスタンスを生成し，分類の計算を行いました．

一方，LightGBMのClassifierオプション指定は，次の通りです．


```py
def lgb_set_param(args):
    '''
      LightGBM (classifier) parameters
    '''
    params = {}
    if args.cpu:
        params['device'] = 'cpu'
    else:   # gpu
        params['device'] = 'gpu'
        params['max_bin'] = 15
        params['gpu_use_dp'] = False

    params['num_leaves'] = 64 
    params['learning_rate'] = 0.1
    params['objective'] = 'multiclass'
    params['n_estimators'] = 500
    params['nthread'] = 8

    return params
```

XGBoostのパラメータ設定と似ていますが，"gpu"オプションでは，'max-bin'オプションを設定しています．これはドキュメントのアドバイス「小さい値がSpeed-upに貢献する」を参考にしています．'gpu_use_dp'は「倍精度(double precision)を使うか」のオプションですが，GPUのパフォーマンスを出せる'False'を選びました．分類器の性能に関するオプションについては，XGBoostと整合性がとれる数値を選択しています．

このオプション設定後，分類器インスタンスを生成，fit()（モデル訓練）を行いました．処理時間比較のため，タイマーをセットし，5-folds の cross-validation処理を実施し，それの所要時間を計測しています．

（プログラムは，[こちら(GitHub)](https://github.com/tomokishii/Qiita-posts/tree/master/XGB_LGB_GPUsupport) にuploadしました．）

ベンチマークの結果は，次の通りです．

### Kaggle Otto Classification Results

| dataset  |  classifier lib. |  GPU support  | time *1)  | accurarcy  | mlogloss  |
|:--------:|:----------------:|:-----------:|------:|:----------:|:---------:|
| kaggle otto | xgboost       |  off (CPU)   | 1299.49 s | 0.816   | 0.475     |
| kaggle otto | xgboost       |  on (GPU)  | 340.03 s | 0.814   | 0.475    |
| kaggle otto | lightgbm      | off (CPU)    | 120.10 s | 0.825   | 0.482    |
| kaggle otto | lightgbm      | on (GPU)   | 116.42 s | 0.824   | 0.486   |

*1) Elapse time in 5-folds cross validation process


### MNIST Classification Results

| dataset  |  classifier lib. |  GPU support  | time *1) | accurarcy  | mlogloss  |
|:--------:|:----------------:|:-----------:|------:|:----------:|:---------:|
| MNIST | xgboost       |  off (CPU)   | 3882.01 s | 0.978  | 0.068 |
| MNIST | xgboost       |  on (GPU)  | 1027.71 s | 0.977   | 0.069   |
| MNIST | lightgbm      | off (CPU)    | 255.18 s | 0.976  | 0.078   |
| MNIST | lightgbm      | on (GPU)   |  87.80 s | 0.976  | 0.078  |

*1) Elapse time in 5-folds cross validation process


## 考察
CPU vs. GPU に関して，XGBoost で約3 ~ 4倍の高速化，LightGBM で（ばらつきが大きく）1倍（103%）~ 3倍の高速化が達成できることが分かりました．上の表の通り，GPUオプションを選んでも，精度(accuracy)に関わる性能低下はほとんど見られませんでした．

CPU vs. GPU の比較より，もっと意外だったのが，XGBoost vs. LightGBM の速度パフォーマンスの差です．

いままでも XGBoost より LightGBM の方が処理が軽い印象があったのですが，今回の結果は「軽い」どころではなく，大きな差がついてしまいました．Kaggle Otto問題では，CPUにて約10倍，GPUでも約3倍，LightGBM が高速に処理できています．XGBoostが「遅すぎる」ので，ビルドのオプションで何か間違ったことをしていないか調べてみましたが，ビルドオプションの自由度はあまり大きくないので，差の原因については現段階で不明のままです．
（コメント，ご指摘等，ありましたらお願いします．）

今回の結果を受けて，（XGBoostのパフォーマンスが改善するまでは）LightGBM を優先的に使用し，必要に応じて XGBoost も使う，といった運用も検討したいと思います．もちろん，分類器のパフォーマンスは，データセットの内容に大きく依存しますので，継続的に両者(XGBoost vs. LightGBM)の比較は行っていく必要もありそうです．

## （追記）LightGBMのベンチマークに関するblog記事
その後，以下のblog記事を見つけました．(Microsoft Server & Tools Blogs)

Lessons Learned From Benchmarking Fast Machine Learning Algorithms
https://blogs.technet.microsoft.com/machinelearning/2017/07/25/lessons-learned-benchmarking-fast-machine-learning-algorithms/

Summaryを引用させていただきます．
> 1. XGBoost and LightGBM achieve similar accuracy metrics.  
> XGBoost と LightGBM は同等の精度を出せる．
1. LightGBM has lower training time than XGBoost and its histogram-based variant, XGBoost hist, for all test datasets, on both CPU and GPU implementations. The training time difference between the two libraries depends on the dataset, and can be as big as 25 times.  
LightGBM は，XGBoostおよびXGBoostのhistgramベースオプションより訓練時間が少なく，テストしたデータセットにおいて，CPUおよびGPUのインプリメンテーションの両方で時間短縮できた．
1. XGBoost GPU implementation does not scale well to large datasets and ran out of memory in half of the tests.  
XGBoostのGPUインプリメンテーションは，あまり上手にスケールできていないし，（テストした内の）半分のデータセットでメモリ不足が発生した．
1. XGBoost hist may be significantly slower than the original XGBoost when feature dimensionality is high.  
XGBoost histは，おそらくオリジナルのXGBoostより速度が低下する．特に特徴量の規模が大きい場合にそうなる．

Microsoftのブログですので，少し割り引いて受け取る必要がありますが，概ね私の行ったベンチマークの結果と整合性がある内容かと思います．（引用元の数値...training time をご確認ください．）


## 参考文献，web site
- XGBoost Documentation  
https://github.com/dmlc/xgboost/tree/master/plugin/updater_gpu  
https://xgboost.readthedocs.io/en/latest/build.html
- LightGBM Documenation  
https://github.com/Microsoft/LightGBM/blob/master/docs/GPU-Tutorial.md  
https://github.com/Microsoft/LightGBM/wiki/Installation-Guide
- LightGBM GPU Tuning Guide and Performance Comparison  
https://github.com/Microsoft/LightGBM/blob/master/docs/GPU-Performance.md
- LightGBM GitHub repo, issues #458  
https://github.com/Microsoft/LightGBM/issues/458
- CMake documentation  
https://cmake.org/documentation/
- Boost C++ Library - Getting Started  
http://www.boost.org/doc/libs/1_64_0/more/getting_started/index.html