最近kaggleは放置して機械学習を使った競馬の予想をやってるんですけど、少しパラメータを変えただけのコードを実行するだけなのに、毎回一から前処理をしていると時間がかかりすぎて死ぬという問題が発生していました。 そんな悩みを解決するために、実行するときに前処理をキャッシュしておいて、次に実行するときには以前と変化した部分だけを計算するPreprepというライブラリを作りました。
よく何言ってるかわからないと思うので実際に例を見てもらったほうが早い気がします。
import preprep
import pandas as pd
def func1(df):
return df*2
def func2(df):
return df*3
def func3(df):
return df*4
def func4(df):
return df*5
def func5(df,n = 1):
return df*n
df = pd.DataFrame([[1,2,3],[4,5,6]])
#キャッシュするディレクトリを指定してインスタンスを作成
p = preprep.Preprep("./cache\_files")
#前処理の関数を登録 func1からfunc3を順番に実行していく
p = p.add(func1,name = "func1")
p = p.add(func2,name = "func2")
p = p.add(func3,name = "func3")
#fit\_geneで実際に前処理を実行
df = p.fit_gene(df,verbose = True)
print(df.head())
1回目の出力
[*] start running graph
[*] no cache exists for func1, calculate
[*] no cache exists for func2, calculate
[*] no cache exists for func3, calculate
0 1 2
0 24 48 72
1 96 120 144
2回目の出力
[*] start running graph
[*] available cache for func1 exists, skip calculation
[*] available cache for func2 exists, skip calculation
[*] available cache for func3 exists, skip calculation
0 1 2
0 24 48 72
1 96 120 144
1回目に実行したときはキャッシュが存在していないので全ての前処理を計算していますが、2回目に実行するときにはキャッシュが存在しているので計算をスキップしています。 キャッシュが存在している状態で、fit_geneに渡すDataFrameが変化すると次のようになります。
#DataFrameの値を変更
#df = pd.DataFrame([[1,2,3],[4,5,6]])
df = pd.DataFrame([[1,2,3],[4,5,7]])
#キャッシュするディレクトリを指定してインスタンスを作成
p = preprep.Preprep("./cache\_files")
#前処理の関数を登録
p = p.add(func1,name = "func1")
p = p.add(func2,name = "func2")
p = p.add(func3,name = "func3")
#fit\_geneで実際に前処理を実行
df = p.fit_gene(df,verbose = True)
print(df.head())
[*] start running graph
[*] saved cache for func1 exists, but dataset hash value has changed, calculate
[*] saved cache for func2 exists, but dataset hash value has changed, calculate
[*] saved cache for func3 exists, but dataset hash value has changed, calculate
0 1 2
0 24 48 72
1 96 120 168
ちゃんと入力されたDataFrameが変化していることを検出して計算をやり直しています。 またfunc3中身を書き換えたり、他の関数に変えたりすると、func3以前はキャッシュから読み込んで、func3以降だけ計算をやり直してくれます。 ちゃんと内部で依存グラフを作ってそれを解決しているので、今回のような関数を順番に実行していくだけでなく、枝分かれしたり、結合したりみたいな複雑なものでもちゃんと必要なとこだけ計算をやり直してくれます。
df = pd.DataFrame([[1,2,3],[4,5,7]])
p = preprep.Preprep("./cache\_files")
#func3をfunc4に変更
p = p.add(func1,name = "func1")
p = p.add(func2,name = "func2")
#p = p.add(func3,name = "func3")
p = p.add(func4,name = "func3")
#fit\_geneで実際に前処理を実行
df = p.fit_gene(df,verbose = True)
print(df.head())
出力
[*] start running graph
[*] available cache for func1 exists, skip calculation
[*] available cache for func2 exists, skip calculation
[*] saved cache for func3 exists, but op hash value has changed, calculate
0 1 2
0 30 60 90
1 120 150 210
実際に自分で使っていますが、以前に比べて飛躍的に実験を回すスピードが上がったと思います。 これまで前処理に毎回30分くらいかかっていましたが、preprepのおかげで変化したとこだけ実行するので今ではほとんど前処理に時間を取られません(入力するDataFrameが変わったりすると、1から計算しなおさないといけないのでやっぱり時間がかかりますが)。 インストールの方法ですがpipで配布しているので下記のコマンドでインストールできる(はず)です。
$ pip install preprep
まだ生まれたばっかのライブラリなのでバグが多かったりドキュメントが存在してなかったりするので今後その辺はこつこつ直していこうかなと思います。バグとか見つけたらここかgithubでぜひ教えていください。