SHAPをGPUで加速できるか試してみました

SHAPは特徴量が増えたら、すごく時間がかかりそうな手法に見えます。

特徴量を一つずつ検証しているから、そうなるな

早くする方法はないんですか

GPUで高速化する手法があるぞ

参考にした記事は下記です。

https://medium.com/rapids-ai/gpu-accelerated-shap-values-with-xgboost-1-3-and-rapids-587fad6822

環境構築

NVIDIA GPU CLOUDからRAPIDSのコンテナを取得して実行します。

docker run --gpus all --rm -it -p 8888:8888 nvcr.io/nvidia/rapids_ml_workshop:20.08

下記記事でNVIDIA GPU CLOUDの設定方法を記述しました。

SHAPのCPUパフォーマンス確認

SHAPに関しては下記記事で記述しました。

手法としては特徴量が増えると時間がかかる手法になっています。

では下記のコードで動作速度を検証してみます。

検証環境は下記になります。

  • Ubuntu 18.04
  • メモリ:64GB
  • GPU: Geforce 1080
  • CPU : Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz

カリフォルニアの住宅価格のデータを取得します。

データ数は20640、特徴量は8なのでデータ数は十分ですが、特徴量は少ないデータ・セットになります。

https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_california_housing.html

%matplotlib inline

import xgboost as xgb
import numpy as np
import sklearn.datasets
import matplotlib.pyplot as plt
import time

data = sklearn.datasets.fetch_california_housing()
feature_names = ["MedInc", "HouseAge", "AveRooms", "AveBedrms", "Population", "AveOccup",
                 "Latitude", "Longitude"]
X = data.data
y = data.target

まずはCPUに対して動作確認をします。XGBoostでモデルを学習して、各値の分布を出してみます。

dmat = xgb.DMatrix(X, y)
bst = xgb.train({"tree_method": "hist"}, dmat, 500)

plt.hist(bst.predict(dmat), bins=40, density=True)
plt.title("House price prediction distribution")
plt.xlabel("$100k")
plt.tight_layout()
plt.show()

SHAP値を導出します。

%%time
# Make sure GPU prediction is enabled
# Set "cpu_predictor" to use CPU
bst.set_param({"predictor": "cpu_predictor"})

# Compute our shap values
start = time.time()
shap_values = bst.predict(dmat, pred_contribs=True)
print("SHAP time {}".format(time.time() - start))

時間は下記でした。

SHAP time 8.007731437683105

SHAP値の分布を見てみます。住宅価格に影響があるのは緯度と経度なので場所の影響が大きいことが分かります。

def plot_feature_importance(feature_names, shap_values):
 # Get the mean absolute contribution for each feature
 aggregate = np.mean(np.abs(shap_values[:, 0:-1]), axis=0)
 # sort by magnitude
 z = [(x, y) for y, x in sorted(zip(aggregate, feature_names), reverse=True)]
 z = list(zip(*z))
 plt.bar(z[0], z[1])
 plt.xticks(rotation=90)
 plt.tight_layout()
 plt.show()


plot_feature_importance(feature_names, shap_values)

各特徴量のペアのSHAP値を導出します。時間は116秒でした。

start = time.time()
shap_interactions = bst.predict(dmat, pred_interactions=True)
print("SHAP interactions time {}".format(time.time() - start))
SHAP interactions time 116.02994728088379

結果を可視化してみます。緯度、経度のペアが最もSHAP値が高いことが分かります。

def plot_top_k_interactions(feature_names, shap_interactions, k):
 # Get the mean absolute contribution for each feature interaction
 aggregate_interactions = np.mean(np.abs(shap_interactions[:, :-1, :-1]), axis=0)
 interactions = []
 for i in range(aggregate_interactions.shape[0]):
     for j in range(aggregate_interactions.shape[1]):
         if j < i:
             interactions.append((feature_names[i] + "-" + feature_names[j], aggregate_interactions[i][j] * 2))
 # sort by magnitude
 interactions.sort(key=lambda x: x[1], reverse=True)
 interaction_features, interaction_values = map(tuple, zip(*interactions))
 plt.bar(interaction_features[:k], interaction_values[:k])
 plt.xticks(rotation=90)
 plt.tight_layout()
 plt.show()


plot_top_k_interactions(feature_names, shap_interactions, 10)

SHAPのGPUパフォーマンス確認

GPUで同様の処理を行います。まずGPUで学習します。`hist`が`gpu_hist`に変更されているだけです。

dmat = xgb.DMatrix(X, y)
bst = xgb.train({"tree_method": "gpu_hist"}, dmat, 500)

plt.hist(bst.predict(dmat), bins=40, density=True)
plt.title("House price prediction distribution")
plt.xlabel("$100k")
plt.tight_layout()
plt.show()

SHAP値をGPUで導出します。`cpu_predictor`が`gpu_predictor`に変更されているだけです。

%%time
# Make sure GPU prediction is enabled
# Set "cpu_predictor" to use CPU
bst.set_param({"predictor": "gpu_predictor"})

# Compute our shap values
start = time.time()
shap_values = bst.predict(dmat, pred_contribs=True)
print("SHAP time {}".format(time.time() - start))

時間は下記のような結果でした。

少し早くなりましたが、思ったより早くなっていません。


SHAP time 7.866410493850708

参考にした記事では15倍程度、早くなっているらしいのでGPUによってはパフォーマンス向上率が低い可能性があります。

Computing all interactions takes only ~1.33s on our V100 where 40 CPU Computing all SHAP values takes only ~0.17s using a V100 GPU compared to 2.64s using 40 CPU cores on 2x Xeon E5–2698, a speedup of 15x even for this small dataset.

各特徴量のペアのSHAP値を導出します。結果としては115秒で思ったより早くなっていません。

start = time.time()
shap_interactions = bst.predict(dmat, pred_interactions=True)
print("SHAP interactions time {}".format(time.time() - start))
SHAP interactions time 115.78668594360352

参考にした記事では30倍程度、早くなっているらしいのでGPUによってはパフォーマンス向上率が低い可能性があります。

Computing all interactions takes only ~1.33s on our V100 where 40 CPU cores take ~40.12s, a speedup of 30x. This is a relatively small dataset with only 8×8 possible feature interactions. For larger datasets, as shown in our paper, GPUTreeShap can reduce feature interaction computations from days to a matter of minutes.

思ったより早くなりませんでした・・・・

まだ開発段階の面もあるからだろうな。特徴量が増えたら効果があがるかもしれんぞ

試すコストは低いので、とりあえずやってみても良いですね

Close Bitnami banner
Bitnami