見出し画像

学習時のソースコードをモデルと一緒に保存する方法。python

問題

機械学習で特徴量を色々試行錯誤しながらモデルを学習してると、学習時に使った特徴量がわからなくなる。gitのコミットハッシュとか記録しておけば、特徴量はわかるけど。本番でロードするときに、そのバージョンのコードを使わないといけないから、合わせるのが面倒。複数バージョンを同時に使うのはもっと面倒

ソリューション

cloudpickleを使う https://github.com/cloudpipe/cloudpickle

普通のpickleが関数を参照(関数名とか?)で保存するのに対して、cloudpickleは一部の関数(自動識別 + 手動で指定)を値(ソースコード?)で保存できる。

以下のようなモジュールがあったとする。PickleTestClassの中身やfunc2の中身は試行錯誤で頻繁に書き換える。register_pickle_by_valueを使うと、強制的に値で保存させられる。

# pickle_test_class.py

import cloudpickle
import sys

# 値で保存させる
cloudpickle.register_pickle_by_value(sys.modules[__name__])

class PickleTestClass:
   def __init__(self):
       pass

   def func(self, x):
       return 4 * x

# numbaも行けた
@numba.njit
def func2(x):
   return 3 * x

学習でモデルを保存したとする。

import cloudpickle
from .pickle_test_class import PickleTestClass
pt = PickleTestClass()
data = cloudpickle.dumps(pt)
with open('/tmp/data.txt', 'wb') as f:
   f.write(data)

本番環境でモデルをロード。PickleTestClassは学習時から書き換えられているとする

import cloudpickle
with open('/tmp/data.txt', 'rb') as f:
   data = f.read()
pt = cloudpickle.loads(data)
print(pt.func(1))

↑、学習時のバージョンのPickleTestClassが動く

良くいじるコード(自分で書いたコード)は値で保存(学習時のコードが使われる)。いじらないコード(sklearnとかtalibとか)は参照(ロード時のコードが使われる)で保存。とするのが良さそう。

cloudpickle.register_pickle_by_value(自分で書いてる大元のモジュール)とすれば、その配下が全て対象になるらしい https://github.com/cloudpipe/cloudpickle/blob/6099fdb1229adca6b8a013896bc8ec8daabe73b0/cloudpickle/cloudpickle.py#L181

mlflow model

試してないけど、mlflowはcloudpickle使うっぽい。

https://github.com/mlflow/mlflow/blob/9c934f5a57cc8701475eb7ed0b6b072600b66bed/mlflow/sklearn/__init__.py#L432 で保存しているらしい。そのまま呼んでるだけ。log_modelでも自前でlog_artifactでもどっちでも良い気がする。

log_modelだと圧縮されないっぽい。 https://github.com/mlflow/mlflow/issues/2545