フリーランチ食べたい

No Free Lunch in ML and Life. Pythonや機械学習のことを書きます。

Vaex入門 / 可視化もXGBoostも

f:id:ikedaosushi:20190414133051p:plain

はじめに

  • 昨日Vaexの性能評価の記事を書きました。
  • その記事では長くなってしまうので基本的な使い方などを省略しました。
  • なので今回は入門記事として使い方を紹介します。

blog.ikedaosushi.com

Vaexとは

昨日の記事でも書きましたが、遅延評価/Pandasライク/アウトオブコアという特徴を持ったデータフレームライブラリです。 github.com

リンク集

インストール・読み込み

インストールはpipやcondaを使って通常通り行えます。

pip install vaex

読み込みもライブラリ名のままimportすればOKです。

import vaex

読み込み

それでは早速使っていきましょう。

df = vaex.example()

vaex.example() を呼ぶと、テストデータ(http://vaex.astro.rug.nl/data/helmi-dezeeuw-2000-10p.hdf5)をダウンロードして読み込んでくれます。

遅延評価なライブラリなので、データを読み込んだといってもメモリに乗せているわけではなく、一瞬で読み込みは終わります。

表示

そのままdataframeをそのまま打ち込むと一部表示できます。

df

f:id:ikedaosushi:20190414134030p:plain

これはデータをすべてメモリに乗せているわけではないので、一瞬で表示されます。 同じアウトオブコアライブラリのDaskだと一部表示するために dask.dataframe.head() などで呼び出して表示までにだいたい数秒かかるのでそれに比べるとUXとしてかなり良いです。

カラムはPandasと異なりOrderedDict型になっているので次のような形で確認できます。

df.columns.keys()
# => odict_keys(['E', 'FeH', 'L', 'Lz', 'random_index', 'vx', 'vy', 'vz', 'x', 'y', 'z'])

カラムを選択して表示する場合はPandasと同じようにできます。

df.x # df['x']も同じ
# Expression = x
# Length: 330,000 dtype: float64 (column)
# ---------------------------------------
#      0  -0.777471
#      1    3.77427
#      2    1.37576
#      3   -7.06738
#      4   0.243441
#        ...       
# 329995    3.76884
# 329996    9.17409
# 329997   -1.14041
# 329998   -14.2986
# 329999    10.5451

ただこれも遅延評価なので全てがメモリに乗っているわけではありません。

評価・仮想関数

もし評価を行いたい場合は evaluate()を使います。次のような感じです。

df.evaluate(df.x)
# array([ -0.77747077,   3.77427316,   1.3757627 , ...,  -1.14041007, -14.2985935 ,  10.5450506 ])

evaluate() はカラムだけでなくnumpyの多くの関数でも使えるらしいです。

import numpy as np
exp = np.sqrt(df.x**2 + df.y**2 + df.z**2)
df.evaluate(exp)
# array([ 2.96554504,  5.77829281,  6.99079604, ...,  8.86425027, 17.60104719, 14.54018152])

np.sqrt(df.x**2 + df.y**2 + df.z**2) はこの時点では評価されないので、Vaexでは「仮想関数」と呼びます。次のようにカラムに仮想関数を入れることもできます。

df['v'] = np.sqrt(df.x**2 + df.y**2 + df.z**2)
df[['x', 'y', 'z', 'v']]

f:id:ikedaosushi:20190414155155p:plain

選択

データの選択は select() を使って行います。 select() はcopyを行わないのでメモリを増やすことなくデータを選択できます。

df.select(df.x < 0) # xが負なものだけ選択
df.evaluate(df.x, selection=True)
# array([ -0.77747077,  -7.06737804,  -5.17174435, ...,  -1.87310386, -1.14041007, -14.2985935 ])

Pandasライクな書き方もできるらしいです。 こちらもcopyが発生しないので、これで十分な気もしますが、 select() の方が嬉しいケースもあるのかもしれません。

df_negative = df[df.x < 0]
df_negative[['x', 'y', 'z', 'r']]  # xが負なものだけ表示

※追記 ライブラリ開発者のMaarten A. Breddelsから直接DMもらいました。許可をもらったのでここに掲載します。(Thank you so much!)

f:id:ikedaosushi:20190415005401p:plain

ざっくり言うと、性能面では差がないのですが、dfが大量にできるのを防げたりプロットするときにselectionありとなしで簡単に比較できるとのことです。

統計処理

統計処理も並列化処理を使って高速に行えます。大体Pandasと同じAPIのようです。

df.count()
# array(330000.)
df.mean(df.x)
# -0.06713149126400597
df.mean(df.x, selection=True)
# -5.211037972111967

さらに細かいAPIはドキュメントを参照してください。

docs.vaex.io

可視化

Vaexの素晴らしいところは集約関数を伴う可視化メソッドも並列化処理が行われ、単純にmatplotlibを使うより高速に描画できる点です。APIはPandasに似ていますが若干違うので注意です。

ax, = df.plot1d(df.x, linewidth=5) # 返ってくるのがline2Dの配列なので注意
# ↓はグラフの見た目調整
ax.axes.tick_params(axis='both', labelsize=20)
ax.axes.xaxis.label.set_size(20)
ax.axes.yaxis.label.set_size(20)

f:id:ikedaosushi:20190414162107p:plain

これはxでgroupbyしてカウントしたもののヒストグラムです。

もし、ヒストグラムでなく別の集約関数を扱いたい場合は、 plot1d(df.x, what='method_name(col_name)') のように what 引数を使います。例えばxの平均を出したい場合は次のようになります。

# xでgroupbyしてEの平均をプロット
ax, = df.plot1d(df.x, what='mean(E)', linewidth=5)
# ↓はグラフの見た目調整
ax.axes.tick_params(axis='both', labelsize=20)
ax.axes.xaxis.label.set_size(20)
ax.axes.yaxis.label.set_size(20)

f:id:ikedaosushi:20190414162334p:plain

2次元においても同様に行えます。

# x, yでgroupbyしてEの平均をプロット
ax = df.plot(df.x, df.y, what=vaex.stat.mean(df.E))
# ↓はグラフの見た目調整
ax.axes.tick_params(axis='both', labelsize=20)
ax.axes.xaxis.label.set_size(20)
ax.axes.yaxis.label.set_size(20)

f:id:ikedaosushi:20190414163153p:plain

ポイントは集約関数を伴うものに限られるということです。例えばScatterを書きたい場合は普通に evaluate してからプロットするみたいです。

x = df.evaluate("x")
y = df.evaluate("y")
plt.scatter(x, y)

Join

JoinもPandasではcopyが発生しますが、copyなしで行うことができます。 次のようにPandasと同じようなAPIです。

a = np.array(['a', 'b', 'c'])
x = np.arange(1,4)
df1 = vaex.from_arrays(a=a, x=x)

b = np.array(['a', 'b', 'd'])
y = x**2
df2 = vaex.from_arrays(b=b, y=y)

df1.join(df2, left_on='a', right_on='b')

f:id:ikedaosushi:20190414164051p:plain

注意点としては、Inner, Outer Joinが現状使えません。 ドキュメントの書き方だと今後はサポートされるかもですね。

JIT

なんとJust-In-Timeコンパイルもサポートしています。 使い方も簡単で evaluate() する前に jit_numba() を呼び出すだけです。

内部で使うJITコンパイラは別途インストールが必要なので適宜インストールしましょう。

pip install numba
exp = np.sqrt(df.x**2 + df.y**2 + df.z**2)
df.evaluate(exp)
df.evaluate(exp.jit_numba()) # JITコンパイルしたのち評価

機械学習

機械学習もアルゴリズムは限られていますが、サポートされています。 まず機械学習を行うためには vaex-ml が必要なのでインストールしましょう。

pip install vaex-ml

サポートされているのは、KMeans/PCA/XGBoostですね。 あと前処理のOne-Hot-Encodingもサポートされています。

ここでは一番気になるXGBoostを使ってみます。

import vaex.ml.xgboost
import vaex.ml.datasets

# テストデータのIrisを使います。
df = vaex.ml.datasets.load_iris()

# Train/Test 分割
df_train, df_test = df.ml.train_test_split()

# 使う特徴量を設定します
expressions = [
    df.col.petal_width, 
    df.col.petal_length, 
    df.col.sepal_width, 
    df.col.sepal_length
]
features = [k.expression for k in expressions]

# パラメータを設定します
param = {
    'max_depth': 3,  # the maximum depth of each tree
    'eta': 0.3,  # the training step for each iteration
    'silent': 1,  # logging mode - quiet
    'objective': 'multi:softmax',  # error evaluation for multiclass training
    'num_class': 3  # the number of classes that exist in this datset
}
num_round = 10 # 学習回数(おそらく) ※デフォルトが0なので注意

# モデルを作成します
model = vaex.ml.xgboost.XGBModel(features=features, num_round=10, param=param)

# 学習
model.fit(df_train, df_train.class_, copy=True)

# 予測
df_predict = model.transform(df_test)
df_predict

予測結果がこちらです。 f:id:ikedaosushi:20190414172410p:plain

ところどころAPIが違うので少々混乱しましたができました。 ドキュメントやAPIの感じからまだUnder Developmentな様子ですが、今後精微されてきたら実用できるかもですね。

まとめ

  • Vaexの入門記事でした。
  • Joinや機械学習までサポートされているのには驚きました。
  • まだProductionで使う状態ではないと思いますが、小規模プロジェクトなどで使ってみたいですね。
  • 自分もVaexについて知らないことばかりなので、皆さんと情報共有できたら嬉しいです!
  • 今回使ったコードは↓のGitHubにあげています。

github.com