「並列処理」「関数の戻り値のキャッシュ」「データのシリアライズ・デシリアライズ」を使いやすくするモジュール。
個々の機能はPythonの標準ライブラリでも実現でき、実装もそこまで大変ではないのだが、 よく使いたくなる機能を、多すぎず少なすぎないパラメータ数で簡潔に書ける、という点で価値がある。
標準ライブラリのmultiprocessingに相当する機能を、より簡潔に記述できる。
親プロセスを殺すと子も終了させるなどの、「必ずしもやらなくていいけど大体はやるし、そのために毎回同じ記述をしなきゃいけない」処理がデフォルトで為されるようになっている。
ソースを辿ると、当然と言うか、内部ではmultiprocessing使ってるっぽい。
関数が、同じ引数で2度目以降に呼ばれたとき、1度目の結果を保存しておいて再計算を防ぐ機能。
memory = joblib.Memory()
で設定を行った後、@memory.cache
をデコレータとすることでその関数の戻り値がキャッシュされる。
標準ライブラリのキャッシュ機能には itertools.lru_cache
などがあるが、これはオンメモリであり、一旦プロセスが終了すると再計算は一からになる。
joblib.Memory
はそれとは違い、キャッシュはファイルとして保存される。
従って「$10^6~10^7$ 種類以上の引数を取り、返値も整数1個などシンプルな値」というような関数には正直向かない。
却ってメチャ遅くなるしキャッシュディレクトリが大変なことになる。
どちらかというと、機械学習でパラメータを変更してのデータセットやモデルなど、引数の種類は高が知れ1)、結果が重たいような処理を保存するのに向くか。
この戻り値の保存はpickleで行われているが、pickleではファイルオブジェクトやlambda式などを含むオブジェクトは保存できない。
そのため、(何も表示が出てくれないのだが)そのようなオブジェクトを戻り値に持つ関数は、結果はキャッシュされず、毎回計算されるようになっているっぽい。
memory = joblib.Memory(location='./cache_dir') @memory.cache def func(n): print('Calc', n) d = defaultdict(lambda: 0) d[0] = n return d print(func(5)) print(func(5)) print(func(6)) print(func(5)) # => 毎回 'Calc 5' などが表示され、関数内部の処理が実行されていることがわかる
標準ライブラリの pickle
に置き換わる機能。joblib.dump
で保存、joblib.load
で読込。
pickle
はファイルを一旦バイナリで開いてファイルハンドラを渡さないといけないが、joblib
はファイルパスでOK。
2行が1行になるのは地味にありがたい。
# pickle with open('path/to/pickle', 'rb') as f: data = pickle.load(f) # joblib data = joblib.load('path/to/joblib')
また、保存時に圧縮するよう指定することもでき、重たいデータに向く。
裏で使っているのはやはりpickleっぽいので、pickleと同様の注意が必要。
たとえばファイルオブジェクトやlambda式が含まれるオブジェクトはエラーになるし、独自クラスはloadする前に定義されている必要がある。