cumlを用いてGPUでxgboostの推論が高速化するか試す

cumlはGPUを使ってxgboostも高速化できるぞ

Deep Learningでないのに高速化できるんですか!!

可能だ。しかも簡単に使えるぞ

XGboostについて

Deep LearningはGPUを高速化するので有名ですが、機械学習の分野で有名なxgboostも高速化します。詳細は下記をご覧ください。

https://rapids.ai/xgboost.html

XGboostは勾配ブーストを用いた決定木によるクラス分類や下記を行うモデルです。

数式をなるべく除いてイメージだけ説明してみます。下記のようなデータ・セットを用意します。

このデータを多数用意した木構造のモデルに入力します。葉の数はTで一定の木構造のモデルを用意します。

データを各木構造のモデルに入力すると葉まで処理がされます。葉の赤く囲まれたものが木構造のモデルで処理された結果です。

木構造のモデルをfとしてモデルの重みをWとすると下記の図のようになります。

この複数の木で構成されたモデルに入力データを入れた際の出力と実際のラベルを比較して学習を行います。

ここで学習の際に正解と予測の差分を微分して勾配を取り、これを最小化していくのですが、徐々に木を追加して勾配を更新していくため、勾配ブースティングと呼ばれる所以です。

下記のように勾配(ロス)が更新されていきます。

木を追加する際に構造変化して変化前後のロスを計算。ロスが減るかどうかを判断してロスが減るような構造を採用します。

XGboostをGPUで高速化するための工夫として、木の構造を決定するにはLeftのノードとRightのノードの全パターンのロスの導出が必要なのですがそれをGPUで並列計算することによって高速化しています。より詳細が気になる方は下記をご覧ください。

環境設定

NGCからdockerコンテナを取得して動作確認をしました。NGCの設定方法は下記をご覧ください。

構築環境は下記です。

ハードウェア

  • CPU: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
  • Memory: 64GB
  • GPU: Geforce 2080 Ti

ソフトウェア

  • OS: Ubuntu 18.04
  • cuda: バージョン11.0
  • Graphics Driver: バージョン450.51.06
  • docker: 19.03.12

動作確認の際に参考にしたコードは下記になります。

https://github.com/rapidsai/cuml/blob/branch-0.16/notebooks/forest_inference_demo.ipynb

docker コンテナにアクセスします。

docker run --gpus all --rm -it -p 8889:8889 -p 8787:8787 -p 8786:8786          rapidsai/rapidsai:cuda10.1-runtime-ubuntu18.04

“cuml”のディレクトリがすでにあって名前が重なるので名前変更してダウンロードします。

“cugraph”のディレクトリがすでにあって名前が重なるので名前変更してダウンロードします。

git clone https://github.com/rapidsai/cuml.git cuml_test

対象のnotebookがあるディレクトリに移動します。

cd cuml_test/notebooks/

Jupyter-labを起動します。

jupyter-lab --allow-root --ip=0.0.0.0 --NotebookApp.token=""

推論速度の比較

必要なライブラリをインポートします。

import numpy as np
import os

from cuml.test.utils import array_equal
from cuml.common.import_utils import has_xgboost

from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
    
from cuml import ForestInference

xgboostがインストールされているかどうかチェックします。

if has_xgboost():
    import xgboost as xgb
else:
    raise ImportError("Please install xgboost using the conda package,"
                      " Use conda install -c conda-forge xgboost "
                      "command to install xgboost")

xgboostのモデル学習用の関数です。

  • params: Xgboostのパラメータを設定します。
  • xgb.DMatrix(X_train, label=y_train):学習データの設定をします。詳細はこちら
  • xgb.train(params, dtrain, num_rounds):xgbを学習します。
  • bst.save_model(model_path):モデルを保存します。
def train_xgboost_model(X_train, y_train,
                        num_rounds, model_path):
    # set the xgboost model parameters
    params = {'silent': 1, 'eval_metric':'error',
              'objective':'binary:logistic',
              'max_depth': 25}
    dtrain = xgb.DMatrix(X_train, label=y_train)
    # train the xgboost model
    bst = xgb.train(params, dtrain, num_rounds)

    # save the trained xgboost model
    bst.save_model(model_path)

    return bst

Xgboostで推論する関数です。

  • xgb.DMatrix(X_validation, label=y_validation):バリデーションデータを設定します。
  • xgb_model.predict(dvalidation):Xgboostで推論します。
  • np.around(xgb_preds):推論した結果をクラスラベルに変換しています。
def predict_xgboost_model(X_validation, y_validation, xgb_model):

    # predict using the xgboost model
    dvalidation = xgb.DMatrix(X_validation, label=y_validation)
    xgb_preds = xgb_model.predict(dvalidation)

    # convert the predicted values from xgboost into class labels
    xgb_preds = np.around(xgb_preds)
    return xgb_preds

Xgboostのパラメータ、保存するモデルのパスを設定します。

n_rows = 10000
n_columns = 100
n_categories = 2
random_state = np.random.RandomState(43210)

# enter path to the directory where the trained model will be saved
model_path = 'xgb.model'

# num of iterations for which the model is trained
num_rounds = 15

動作確認のため、sklearnの関数を使用してクラス識別用の簡単なサンプルデータを作成します。

学習データを検証データを分割しています。80%が学習データ、20%が検証データになります。

# create the dataset
X, y = make_classification(n_samples=n_rows,
                           n_features=n_columns,
                           n_informative=int(n_columns/5),
                           n_classes=n_categories,
                           random_state=random_state)
train_size = 0.8

# convert the dataset to np.float32
X = X.astype(np.float32)
y = y.astype(np.float32)

# split the dataset into training and validation splits
X_train, X_validation, y_train, y_validation = train_test_split(
    X, y, train_size=train_size)

先程、設定した関数を用いてxgboostのモデルを学習します。

# train the xgboost model
xgboost_model = train_xgboost_model(X_train, y_train,
                                    num_rounds, model_path)

今回はcumlのXgboostと推論速度比較を行いたいので通常のXgboostの推論速度を計測します。結果は12.4 msでした

%%time
# test the xgboost model
trained_model_preds = predict_xgboost_model(X_validation,
                                            y_validation,
                                            xgboost_model)

cumlのForestInferenceでモデルをロードして実行します。ForestInference.load関数の詳細は下記です。

https://docs.rapids.ai/api/cuml/stable/api.html?highlight=forestinference#cuml.ForestInference.load

  • model_path:モデルが保存されているパスです。train_xgboost_modelで学習されたモデルを利用しています。
  • algo:設定できるアルゴリズムの情報はこちらです。”BATCH_TREE_REORG”は複数の行をスレッドブロックごとに推論しています。このアルゴリズムによりGeforce RTX2080 tiはスレッドブロック(Streaming Multiple processor)が72あるため高速に推論できます。
  • output_class:クラス分類かどうか判断するためのパラメータです。
  • threshold:クラス分類の場合は0と1で分類するため、この値以下の場合は0、それ以上であれば1に設定するための値です。
  • model_type:木構造のモデルの分類は複数あるため、そのモデル名を設定しています。今回は”xgboost”を使用しているため、その値を設定しています。ドキュメントによると‘xgboost’, ‘lightgbm’が使用できるようです。
fm = ForestInference.load(filename=model_path,
                          algo='BATCH_TREE_REORG',
                          output_class=True,
                          threshold=0.50,
                          model_type='xgboost')

推論速度を計測します。私の環境では1.73 msでした。

推論速は約7倍ほど早くなっています。

%%time
# perform prediction on the model loaded from path
fil_preds = fm.predict(X_validation)

推論結果が正しくできているか確認します。Trueになったので結果は等しいようです。

print("The shape of predictions obtained from xgboost : ",(trained_model_preds).shape)
print("The shape of predictions obtained from FIL : ",(fil_preds).shape)
print("Are the predictions for xgboost and FIL the same : " ,   array_equal(trained_model_preds, fil_preds))

array_equal関数はcumlの内部関数なのでどのように等しい結果になっているか確認するためコードを見てみましょう。

https://github.com/rapidsai/cuml/blob/cdb14e7de6a40d8d707d29b2889a89aa553125ee/python/cuml/test/utils.py#L34

下記のような関数になっています。絶対値の差がunit_tol以上の値の平均がtotal_tol以下であればTrueを返す関数のようです。この2つの値を調整すればより精度の高い判断ができるようです。

def array_equal(a, b, unit_tol=1e-4, total_tol=1e-4, with_sign=True):
    """
    Utility function to compare 2 numpy arrays. Two individual elements
    are assumed equal if they are within `unit_tol` of each other, and two
    arrays are considered equal if less than `total_tol` percentage of
    elements are different.
    """

    a = to_nparray(a)
    b = to_nparray(b)

    if len(a) == 0 and len(b) == 0:
        return True

    if not with_sign:
        a, b = np.abs(a), np.abs(b)
    res = (np.sum(np.abs(a-b) > unit_tol)) / len(a) < total_tol
    return res

今回はXgboostの推論のみだが高速化を確認できたぞ

学習速度も気になりますね

それは今度、確認しよう!

Close Bitnami banner
Bitnami