137

機械学習関連の技術記事を投稿します。137と言えば微細構造定数

【Python】cuMLの性能を評価してみた

KaggleでRAPIDSを使う方法 が公開されていたので、RAPIDSに含まれている機械学習ライブラリcuMLについて、簡単な性能評価を行ってみた。
cuMLを利用することで、GPU上でランダムフォレストなどの機械学習アルゴリズムを利用することができ、学習の高速化が期待できる。

準備

今回はKaggle上でcuMLの性能評価を行うため、Kaggleで新しいNotebookを作成した上で次の手順に従い、RAPIDSを利用できる環境を整える必要がある。

  • RAPIDSのDataset をNotebookに追加
  • NotebookのAcceleratorをGPUに変更
  • Notebookの最初に次のコマンド一式をコピペ
import sys
!cp ../input/rapids/rapids.0.14.0 /opt/conda/envs/rapids.tar.gz
!cd /opt/conda/envs/ && tar -xzvf rapids.tar.gz > /dev/null
sys.path = ["/opt/conda/envs/rapids/lib/python3.7/site-packages"] + sys.path
sys.path = ["/opt/conda/envs/rapids/lib/python3.7"] + sys.path
sys.path = ["/opt/conda/envs/rapids/lib"] + sys.path 
!cp /opt/conda/envs/rapids/lib/libxgboost.so /opt/conda/lib/

※ Kaggleのコンテナに搭載されているPythonのバージョン等が異なる場合は上記のコマンドがエラーとなるため、適宜修正が必要

今回はお試し利用であるため、TitanicのDatasetを利用する。 ベンチマークに使用したソースコード一式は、Kaggle上で公開している

cuMLのベンチマークプログラム

今回は、RAPIDSに含まれているDataFrameライブラリ cuDF機械学習ライブラリ cuML を利用し、scikit-learn と性能比較した。

cuDFとcuMLを利用するためには、以下のパッケージをインポートする必要がある。

import cudf
import cuml

pandasのDataFrameからcuDFのDataFrameへは、cudf.from_pandas で変換できる。

train_df = pd.read_csv("../input/titanic/train.csv")
train_df_gpu = cudf.from_pandas(train_df)

cuMLはscikit-learnと似たようなインターフェースを提供しているため、過去にscikit-learnを使ったことがある人なら比較的簡単にcuMLを使うことができる。 しかし一部のAPIの引数が異なっていることと、入力データとして受け付けるデータ型に制約があることに注意しなければならない。 特にデータ型については、scikit-learnに入力したデータ型と同じデータ型でcuMLのAPIを利用しようとして何度もエラーで悩まされたため、結局全てのデータ型をfloat32に型変換した。

今回は、以下の3つのアルゴリズムを性能評価対象とした。

  • k近傍法
  • サポートベクタ―マシン
  • ランダムフォレスト

scikit-learnとcuMLを用いて性能評価で使用したベンチマークプログラムを次に示す。

import time

import pandas as pd

import sklearn.neighbors
import sklearn.svm
import sklearn.ensemble
from sklearn.model_selection import KFold

import cudf
import cuml

import matplotlib.pyplot as plt
import numpy as np

NFOLDS = 5
ITERATION = 300

# Load data.
train_orig_df = pd.read_csv("../input/titanic/train.csv")
test_orig_df = pd.read_csv("../input/titanic/test.csv")

# Pre-process
train_df = train_orig_df.copy()
train_df.drop(["Cabin", "Ticket", "Name"], axis=1, inplace=True)
train_df = pd.get_dummies(train_df.iloc[:, 1:], columns=["Pclass", "Sex", "Embarked"])
train_df.dropna(inplace=True)

X_all = train_df.drop(["Survived"], axis=1).astype("float32")
y_all = train_df["Survived"].astype("int32")

X_all_gpu = cudf.from_pandas(X_all)
y_all_gpu = cudf.from_pandas(y_all)

# Benchmark code.
def bench(X, y, classifiers, params):
    elapsed = {}
    for name, clf_class in classifiers.items():
        elapsed_list = []

        for _ in range(ITERATION):
            kf = KFold(n_splits=NFOLDS)
            clf = clf_class()
            clf.set_params(**params[name])

            elapsed_sum = 0
            for i, (train_idx, val_idx) in enumerate(kf.split(X, y)):
                X_train = X_all.iloc[train_idx]
                y_train = y_all.iloc[train_idx]
                X_val = X_all.iloc[val_idx]
                y_val = y_all.iloc[val_idx]

                start = time.time()
                clf.fit(X_train, y_train)
                elapsed_sum += time.time() - start

            elapsed_list.append(elapsed_sum)

        elapsed[name] = pd.Series(elapsed_list).mean()
    return elapsed

# scikit-learn
classifiers = {
    "KNN": sklearn.neighbors.KNeighborsClassifier,
    "SVM": sklearn.svm.SVC,
    "RandomForest": sklearn.ensemble.RandomForestClassifier
}

params = {
    "KNN": {},
    "SVM": {
        "random_state": 47
    },
    "RandomForest": {
        "n_estimators": 100,
        "random_state": 47
    }
}

elapsed_sklearn = bench(X_all, y_all, classifiers, params)

# cuML
classifiers = {
    "KNN": cuml.neighbors.KNeighborsClassifier,
    "SVM": cuml.svm.SVC,
    "RandomForest": cuml.ensemble.RandomForestClassifier
}

params = {
    "KNN": {},
    "SVM": {},
    "RandomForest": {
        "n_estimators": 100
    }
}

elapsed_cuml = bench(X_all_gpu, y_all_gpu, classifiers, params)

# Results.
left = np.arange(len(elapsed_sklearn.keys()))
width = 0.3

fig = plt.figure(figsize=(6, 6))
fig.patch.set_alpha(1)

plt.subplot(1, 1, 1)

plt.bar(left, elapsed_sklearn.values(), color='b', width=width, label="Scikit Learn", align="center")
plt.bar(left + width, elapsed_cuml.values(), color="g", width=width, label="cuML", align="center")

plt.xticks(left + width / 2, elapsed_sklearn.keys())
plt.legend(loc=2)
plt.ylabel("sec / iter")
plt.title("fit() performance")
plt.show()

性能評価

ベンチマークプログラムを実行すると、次のような結果が得られた。

f:id:nuka137:20200914132442p:plain

いずれのアルゴリズムについてもcuMLの方が優れているが、今回性能評価に利用したTitanicのデータ量が小さいこともあり、ランダムフォレスト除くとその性能差はわずかしかない。 より大きなデータを用いた学習など、計算量が多いものではcuMLのほうが優勢になると考えられる。

データ型の制約によりcuMLの使い勝手はscikit-learnと比較して劣るものの、計算量の多いアルゴリズムや非常に大きなデータを扱うときにはcuMLによる高速化が活きてくると考える。 cuMLなどを開発しているRAPIDSには KaggleのGrandmasterの人も在席している ため、使い勝手の面でも性能面でも改善が期待できるcuMLの発展が楽しみである。

【2020.12.11 追記】

Home Credit Default Risk のデータセットを利用し、KNNとランダムフォレストについて性能評価した。

f:id:nuka137:20201211082109p:plain

Titanicと比較してデータサイズが大きいこともあり、scikit-learnと比較してよりcuMLのほうが性能が高い結果が得られた。 このことから、データサイズが大きい場合はcuMLを利用したときの効果が非常に高くなると言える。 一方で、データサイズが小さいとGPUの処理量に対してCPUの処理量の割合が多くなるため、cuMLがあまり活きてこないと考えられる。

なお今回評価に使用したソースコードは、KaggleのNotebook として公開している。