こんにちは!
18年10月にデータマイニング推進部に中途入社したサワイと申します。
これまでは主にシステムの運用やSQLを使用してシステムテストなどを経験してきました。
エム・フィールド入社後も社内研修だけでなく、社外でもLT(Lightning talks、5~10分程度の短い発表)を行ったり、勉強会に参加しています。
今回の記事では18年10月に参加したPyData.tokyo One-day Conference 2018という勉強会で紹介されていたNVIDIAが開発したRAPIDSというライブラリ群の1つ、cuDFというライブラリについて紹介します。
コードを使った実例に入る前にNVIDIAとGPUについてすこし紹介します。
NVIDIAはGPU(Graphics Processing Unit)を開発して販売しているアメリカの企業です。
GPUは大量の単純な計算を高速に行うことが求められる場面で使われる演算装置で、使い方によっては、GPUはCPUよりも高速に演算できるなど優れた性能を発揮します。
機械学習・ディープラーニングなどデータ分析においてこれまでもGPUは使われてきましたが、一部の工程に過ぎませんでした。
今回紹介するcuDFは、エンドツーエンドでGPUを使用できるRAPIDSというプラットフォームの中でデータの読み込みを担うライブラリです。
NVIDIAはGPUメーカーなので、GPUの仕様場面を増やすことを狙っているのだと思います。
PyData.tokyo One-day Conference 2018に参加した際にGPUを使用したプラットフォームとしてRAPIDSが紹介されていました。
RAPIDSのサイトはコチラ→https://rapids.ai/
RAPIDSの中でデータを読み込んだ後のDataFrameの機能を担うのがcuDFになります。
cuDFのGithubはコチラ→https://github.com/rapidsai/cudf
環境についてはAWS上にEC2インスタンスを建て、その中にNVIDIADocker2環境を構築し、JupyterLab経由でローカルPCから接続しています。
その際AWSのコンソール上のセキュリティグループ設定でJupyterLabを使用するのに必要なポート(8888、8787、8786など)の通信を許可してください。
サーバ AWS EC2インスタンス:RAPIDSの使用にはパスカル世代以降のGPUを使用する必要があり、Tesla V100を積んだp3.2xlargeを選択しました。
なおデフォルトではインスタンスの申請の制限がかかっているためサポートセンター経由で制限を解除してください。
OS:Ubuntu16.04
CUDA:9.2
使用したライブラリのバージョン(NVIDIADocker)
pandas 0.20.3
cudf 0.4.0
scikit-learn 0.20.0
xgboost 0.80 ※rapids対応版のXGBoostがプリインストールされていましたが、エラーで動作しなかったためconda経由でCPU版XGBoostを再インストールして使用
公式の環境構築については以下に記載があります。
https://rapids.ai/documentation.html
cuDFは現在開発中のライブラリになりますので、読者の皆様におかれましては、環境構築手順は公式のページを参照されるのが一番かと思います。
それでは実際のコードを見ていきたいと思います。
以下では
1.データ読み込みをCPUを使用したPandasとGPUを使用したcuDFで速度の比較を行います。
2.XGBoostを用いて訓練データの学習を行います。
3.結果考察
4.まとめ
といった構成で紹介します。
import cudf
import pandas as pd
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
import os
import gc
import xgboost as xgb
SEED = 2019
cuDFは巨大なデータセットでもGPUを使用して素早く読み込める点が売りかと思い、
Kaggleなどで大きなサイズのcsvを探しましたが、手ごろなデータがありませでした。
そこでsklearnのmake_classificationを利用してデータセットを生成することにします。
なお、巨大なデータセットを作成しているため環境によってはメモリエラーなどスペックが足りない場合があります。
# サンプルデータ生成
#カラム数
N_FEATURES = 1000
"""
n_samples:行数
n_features:カラムの数
n_classes:2クラス分類なので2を指定
n_clusters_per_class
random_state:乱数。固定しないと毎回結果が変わる
"""
X, y = make_classification(n_samples=100000, n_features=N_FEATURES, n_informative=50, n_redundant=0, n_repeated=0,n_classes=2,
n_clusters_per_class=8, random_state=SEED)
cols = ["col_" + str(i) for i in range(1,N_FEATURES+1)]
X = pd.DataFrame(X,columns=cols)
y = pd.DataFrame(y,columns=['target'])
data = pd.merge(X,y,left_index=True,right_index=True)
分割前後のデータでyの割合が変わらないようにtrain_test_splitを使用して層化サンプリングを行います。
%%time
train, test = train_test_split(data, stratify=data['target'],test_size=0.3,random_state = SEED)
#trainとtestに分割して保存する。
train.to_csv('train.csv',index=False)
test.to_csv('test.csv',index=False)
分割後のサイズを確認します。
print('trainの行数と列数:{}'.format(train.shape))
print('testの行数と列数:{}'.format(test.shape))
分割の割合を確認します。
for df in [train,test,y]:
name =[x for x in globals() if globals()[x] is df and x != 'df'][0]
print('----'+name+'----')
print(df['target'].value_counts()/df.shape[0] * 100)
ほぼ50対50で分割できていることがわかります。
次にファイルサイズを確認します。trainとtestが560MBと1.3GB程度のcsvファイルとして生成できていることが確認できます。
!ls -alh
PandasとcuDFでそれぞれcsvを読み込んで比較したいと思います。
%%time
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
cuDFはcsvを読み込む際、カラム名とデータ型を指定する必要があるため、Pandasのカラムと型を使用します。
%%time
train_cu = cudf.read_csv('train.csv', names=train.columns.tolist(), dtype=train.dtypes.to_dict(),skiprows=1)
test_cu = cudf.read_csv('test.csv', names=test.columns.tolist(), dtype=train.dtypes.to_dict(),skiprows=1)
大雑把な比較ですがcsv読み込みに関してはPandasよりcuDFを使用した方が遥かに速いことがわかります。
Pandasのdfを読み込んでXGBoostでtrainのセットを学習してtestセットで評価してみます。
冒頭にも書いたのですが、残念ながらRAPIDS版XGBoostはエラーとなってしまったため、通常のXGBoostをインストールして使用しています。
評価指標はROC AUCです。
X_train = train.drop(['target'],axis=1)
y_train = train['target']
X_test = test.drop(['target'],axis=1)
y_test = test['target']
%%time
d_train =xgb.DMatrix(X_train.as_matrix(),label=y_train.as_matrix().ravel())
d_test =xgb.DMatrix(X_test.as_matrix())
param = {
'max_depth':8,
'objective':'binary:logistic',
'verbose':True,
'seed':SEED
}
xgb_model = xgb.train(params=param,dtrain=d_train)
preds =np.where(pd.DataFrame(xgb_model.predict(d_test))<0.5,0,1)
roc_auc_score(preds,y_test)
先ほどと同様にcuDFのdfを読み込んでXGBoostでtrainのセットを学習してtestセットで評価してみます。
通常のXGBoostではcuDFをそのまま読み込めないため、numpyに変換した上で読み込みます。
評価指標は先ほど同様にROC AUCです。
len(train_cu),len(test_cu)
y_train_cu = train_cu[['target']]
#cuDFはカラムを削除すると大元のDataFrameが変更されるようです。
train_cu.drop_column('target')
X_train_cu = train_cu
y_test_cu = test_cu[['target']]
test_cu.drop_column('target')
X_test_cu = test_cu
%%time
#numpyに変換してxgboostで学習します。
d_train_cu = xgb.DMatrix(X_train_cu.as_matrix(),label=y_train_cu.as_matrix().ravel())
d_test_cu = xgb.DMatrix(X_test_cu.as_matrix())
param = {
'max_depth':8,
'objective':'binary:logistic',
'verbose':True,
'seed':SEED
}
xgb_model_cu = xgb.train(params=param,dtrain=d_train_cu)
preds_cu =np.where(pd.DataFrame(xgb_model_cu.predict(d_test_cu))<0.5,0,1)
roc_auc_score(preds_cu,y_test_cu.as_matrix())
PandasとcuDFを使用した場合でROC AUCの評価結果が異なることがわかります。
この原因を探ります。
同じ変形をしているつもりですが、有効桁数の問題などで、PandasとcuDFでは変換結果が異なるようです。
X_train.as_matrix() == X_train_cu.as_matrix()
#From Pandas
X_train.as_matrix()[0][0]
#From cuDF
X_train_cu.as_matrix()[0][0]
数値の末尾まで見ていくと数値が異なることがわかります。
cuDFの良い点は、GPUを使用しているのでCPUを使用したPandasよりもcsvの読み込みが速いことです。
いまいちな点は、
・DataFrameを読み込む際に、型とカラム名を自動的に判定してくれないので、事前に定義する必要があるのは少し面倒だと感じました。
・nvidia-dockerにインストールされたrapidsai版のXGBoostが動作しなかったため、一旦アンインストールして、conda経由で通常のXGBoostを再インストールする必要があったこと
・現在開発中のようですがrapidsaiのライブラリに教師あり学習のライブラリがないこと
などが挙げられます。
rapidsが、
・現在のスタンダードであるscikit-learn、PandasなどのライブラリとAPI形式を似せる
・Anaconda経由ですべて環境構築できるなど、難易度が下がる
ようになると広く使われるようになると思います。
今後に期待を込めて本記事を結びたいと思います。