Taste of Tech Topics

Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

超爆速なcuDFとPandasを比較した

皆さんこんにちは。
@tereka114です。
今年末はKaggleで開催される面白いコンペも多くて日々、エンジョイしています。

最近は巨大なデータを扱うことが増えており、Pandasだと時間がかかりすぎて効率が悪いと感じています。
そのため、データを高速に処理できるcuDFを利用することも多くなってきました。
この記事ではcuDFの魅力と扱う際の注意点を説明していきます。

※この記事は「Pythonその2 アドベントカレンダー」10日目の記事です。
qiita.com

cuDFとは

cuDFはNVIDIAさんが開発している、Pandasの代わりに利用することができるGPUのライブラリです。
最も大きな特徴はGPUで計算するため、高速であることです。
主に、カテゴリ変数ごとの平均計算や、テーブル同士の結合といった、時間のかかるテーブル処理で、効果を発揮します。

github.com

cuDFは特徴量を簡単に作れるライブラリであるxfeatでもサポートされています。

github.com

cuDFとPandasの性能比較

cuDFがどの程度強力なライブラリなのかを評価します。
今回は利用頻度の多い、次の2点に関して性能を計測しました。

  • カテゴリごとの平均値の計算
  • テーブルの結合

前準備

Colaboratory上での準備

cuDFは手元にGPU環境がなくとも、Google Colaboratoryを利用すれば、簡単に試せます。
今回の性能計測もGoogle Colaboratory上で実施しました。
Google ColaboratoryでcuDFを動かすためには、次のコマンドを最初に実行してください。

!git clone https://github.com/rapidsai/rapidsai-csp-utils.git
!bash rapidsai-csp-utils/colab/rapids-colab.sh stable

import sys, os

dist_package_index = sys.path.index('/usr/local/lib/python3.6/dist-packages')
sys.path = sys.path[:dist_package_index] + ['/usr/local/lib/python3.6/site-packages'] + sys.path[dist_package_index:]
sys.path
exec(open('rapidsai-csp-utils/colab/update_modules.py').read(), globals())
データ作成

本検証では擬似データとして2つのテーブルを作成します。
作成条件は次の通りです。

  • メインテーブル:2000万レコード、カテゴリを2つ100000種類
  • 結合用テーブル:100万レコード、カテゴリを100000種類
import numpy as np
import pandas as pd
import cudf

N = 20000000
MERGE_N = 1000000

df = pd.DataFrame([{"Category": np.random.randint(0,100000), "Category2":np.random.randint(0,100000), "Value": np.random.rand()} for _ in range(N)])
right_df = pd.DataFrame([{"Category": np.random.randint(0,100000), "Value2": np.random.rand()} for _ in range(MERGE_N)])
cudf_table = cudf.from_pandas(df)
cudf_right_table = cudf.from_pandas(right_df)

試したこと

カテゴリごとの平均値の計算

巨大データからカテゴリごとの平均を計算する(df.groupby.mean)ケースで試してみます。
この場合Pandasだと非常に長時間の計算が必要です。
先程作成した「メインテーブル」で平均を計算します。実装は次の通りです。(時間計算部は省略しています)

df.groupby(["Category", "Category2"]).mean() # Pandas
cudf_table.groupby(["Category", "Category2"]).mean() # cuDF

結果はcuDFが50倍高速でした。(驚異的なスピード)

  • Pandas:50.92s
  • cuDF:1.02s
テーブルの結合

次に巨大ファイルの結合を試します。
作成した「メインテーブル」と「結合用テーブル」を"Category"列で結合します。実装は次の通りです。

df.merge(right_df, on="Category", how="left") # Pandas
cudf_table.merge(cudf_right_table, on="Category", how="left") # cuDF

結果、cuDFが37.5倍高速です。(早い!)

  • Pandas:20.65s
  • cuDF:0.55s

cuDFを扱う上での注意点

Pandasと比較して爆速なcuDFですが、3点注意点があります。

1. mergeの並び順が異なる。

Pandasのmergeは元のテーブルと順番が変わりません。
しかし、cuDFの出力結果は並列計算の都合で元のテーブルと順番が異なります。
そのため、順番に依存する処理(ex:インデックス番号でデータを分ける)を記載している場合に期待しない動作になるので注意が必要です。

2. 平均の結果がPandasと異なる。

巨大なデータを扱うときにはPandasと平均値の結果が異なる場合があります。
現在Kaggleで開催されている「Riiid! Answer Correctness Prediction」では、平均値を取ったときに計算結果がpandasとcuDFで異なる結果が返ってきます。
本件についてはオープンに議論しており、議論している場所は次の箇所です。

www.kaggle.com

原因は不明ですが、色々と検証した結果、cuDFの値が正しそうです。
何らかの事情で計算環境が異なりPandasとcuDFの実装が混ざる状況の場合はテストコードを書いて検知するなど、対策が必要だと思っています。

3. 未実装/IFが異なる関数がある

cuDFは全てのPandasの関数をサポートできておらず、未実装関数やIFがPandasと合わないものがいくつかあります。
df.unstackのIFが若干異なることでプログラムで例外が発生したり、df.applyを対応していなかったりします。
その場合は一時的にPandasに変換(df.to_pandas())を行い、計算するなどで回避可能です。

最後に

ここまでcuDFが爆速であることをお伝えしてきました。
しかし、挙動を把握しながら実装していかないと思わぬ罠にハマり、苦労することがあります。
個人的にcuDFの性能が最高なので、挙動のクセを正しく理解、評価した上で、使っていきたいと思います!

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

【データ分析】
Kaggle Masterと働きたい尖ったエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの求人 - Wantedlywww.wantedly.com