MLflow Model Signatures and Input Examples Guide — MLflow 2.15.1 documentation [2024/8/28時点]の翻訳です。
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
モデルシグネチャと入力サンプルのご紹介
MLflowにおいて機械学習モデルを効果的に取り扱うためには、モデルシグネチャとモデル入力サンプルのコンセプトが重要となります。これらのコンポーネントはメタデータを提供する以上のことをします。モデルとのやりとりに関する重要なガイドラインを構成し、MLflowエコシステムにおけるインテグレーションと使いやすさを強化します。
モデルシグネチャ
MLflowのモデルシグネチャは、モデルの明確で正確なオペレーション出不可欠です。推論に必要なすべての追加パラメータを含むモデルで期待される入出力フォーマットを定義します。この仕様は完全なガイドとして動作し、MLflowのツールや外部サービスとのシームレスなモデルのインテグレーションを確実にします。
モデル入力サンプル
モデルのシグネチャを補完するモデル入力サンプルは、適切なモデル入力がどのようなものであるのかの具体的な例を提供します。
これらがなぜ重要なのか?
モデルシグネチャと入力サンプルは、堅牢なMLワークフローの基盤であり、一貫性、精度、使いやすさを確実にするモデルインタラクションの設計図を提供します。これらは、モデルとユーザーの間の契約書として動作し、期待されるデータフォーマットに対する完全なガイドを提供するので、誤解や不適切だったり期待されない入力によって生じるエラーを防ぎます。
モデルシグネチャ
MLflowにおいては、モデルのシグネチャは効果的なモデルオペレーションに必要なモデルの入出力、すべての追加パラメータのスキーマを正確に定義します。この定義は、モデルの適切かつ正確な使用のためにユーザーをガイドする統一的なインタフェースとして動作します。モデルシグネチャはMLflowエコシステムにおいて不可欠であり、MLflowトラッキングUIやモデルレジストリのUIでモデルの入出力やパラメータを表示できるようになります。さらに、MLflowのモデルのデプロイメントツールでは、推論で用いるデータがモデルで規定されている仕様に準拠することを確実にするためにこれらのシグネチャを活用しており、モデルの一貫性とパフォーマンスを維持しています。これらのシグネチャがデータの正確性をどのように強制しているのかに関しての更なる洞察については、シグネチャの強制セクションをご覧ください。
モデルにシグネチャに埋め込むことは、MLflowにおいてはわかりやすいプロセスとなっています。モデルを記録、保存するためのsklearn.log_model()
のような関数を用いる際には、シンプルにモデル入力サンプルを含めるだけです。このアクションによって、MLflowが自動でモデルのシグネチャを推定できるようになります。このプロセスにおける詳細な手順は、シグネチャを持つモデルの記録方法セクションで確認することができます。推定されたシグネチャと他の重要なモデルのメタデータは、あなたのモデルアーティファクトのMLmodelファイル内にJSONフォーマットとして格納されます。すでに記録、保存されているモデルにシグネチャを追加する必要がある場合には、この目的で使用できるset_signature()
APIを利用できます。この機能を実装するための詳細なガイドに関しては、モデルへのシグネチャの設定方法をご覧ください。
モデルシグネチャのコンポーネント
MLflowにおけるモデルシグネチャの構造は、3つの異なるスキーマタイプから構成されています: (1) 入力 、(2) 出力 、(3)パラメーター(params) です。inputsとoutputsのスキーマでは、モデルが受け取り、生成することを期待されるデータの構造を指定します。これらは、列型のデータやtensor、ArrayやObjectタイプの入力を含む様々なデータフォーマットに対応できるように調整することができるので、様々なモデルの多岐にわたる要件に応えることができます。
一方、パラメーター(params) では、モデル推論ステージにおいて重要となる多くの場合においてオプションの追加パラメーターを参照します。これらのパラメーターによって更なる柔軟性を提供し、推論プロセスにおけるファインチューニングとカスタマイゼーションを可能にします。
モデルシグネチャでObjectやArrayを取り扱える機能は、MLflow version 2.10.0で導入されました。2.10.0以前のバージョンでは、カラムベースのシグネチャでは、スカラーの入力タイプと、リストとディクショナリーの入力に固有の特定の条件付きタイプ、主にtransformersフレーバーに対する主要なサポートに制限されています。以降のバージョンにおけるこのエンハンスによって、MLflowがシームレスに対応できるデータ型と構造のスコープを大きく拡大しました。
シグネチャのプレイグラウンド
どのようにしてデータ構造が適切なシグネチャとして自動的に推定されるのかを理解する助けとなるように、適切なシグネチャに関する網羅的なサンプルを提供するために、様々なサンプルと推定されたシグネチャを示している、参照可能なノートブックを作成しました。こちらでノートブックを参照することができます。
あるいは、ローカルにノートブックをダウンロードし、ご自身のデータ構造でテストしたいのであれば、以下からノートブックをダウンロードできます。
必須 vs. オプションの入力フィールド
モデルシグネチャを定義する際に検討することが重要な、シグネチャの強制を決断する特定の条件があります。特筆すべきことの一つは、入力データにおける必須 vs. オプションのコンセプトです。
必須フィールドは、モデルが予測を行えるようにするためには必須となる入力データとなります。必須フィールドが無い場合には、シグネチャ強制の検証処理で必須入力フィールドがないということを告げる例外を出します。
フィールドをオプションに設定するためには、mlflow.models.infer_signature()
関数を使う際にそのフィールドにNoneやnp.nanの値を引き渡す必要があります。あるいは、手動でシグネチャを定義し、当該フィールドのrequiredフィールドをfalseに設定することができます。
モデルシグネチャのタイプ
MLflowでは、2つの主要なタイプをサポートしています: 表ベースデータのためのカラムベースのシグネチャとtensorデータ向けのtensorベースのシグネチャです。
カラムベースのシグネチャ
カラムベースのシグネチャは、Pandasデータフレームのような表形式のデータ入力を必要とする従来の機械学習モデルで主に使用されます。これらのシグネチャは、カラムのシリーズとして構成されており、それぞれには名前と特定のデータタイプを含めることができます。入出力両方のそれぞれのカラムの型は、サポートされるデータタイプのいずれかに対応し、オプションで列に名前をつけることができます。さらに、入力スキーマのカラムはoptional
として指定することで、モデル入力に含めることが必須ではなく、必要に応じて指定を省くことができることを指定できます(詳細はオプションのカラムをご覧ください)。
サポートされるデータタイプ
カラムベースのシグネチャでは、MLflow DataTypeの仕様で定義されているデータプリミティブをサポートしています:
- string
- integer (1)
- long
- float
- double
- boolean
- datetime
入力(Python)
from mlflow.models import infer_signature
infer_signature(model_input={
"long_col": 1,
"str_col": "a",
"bool_col": True
})
推定されたシグネチャ
signature:
input: '[
{"name": "long_col", "type": "long", "required": "true"},
{"name": "str_col", "type": "string", "required": "true"},
{"name": "bool_col", "type": "boolean", "required": "true"}
]'
output: null
params: null
(1) Pythonは多くの場合integerデータの欠損値をfloatとして表現し、integerカラムにおける型の変化を引き起こし、MLflowにおけるスキーマ強制エラーにつながる場合があります。このような問題を回避するには、モデルサービングやSparkのデプロイメントで、MLlowでPythonを使う際は特に、欠損値を持つintegerカラムをdoubles(float64)を定義します。
カラムベースのシグネチャでは、これらのプリミティブの複合データ型もサポートしています。
- Array (list, numpy arrays)
- Spark ML vector (
Array[double]
タイプを継承) - Object (dictionary)
- ArrayとObjectタイプのサポートはMLflowバージョン2.10.0で導入されました。これらのタイプは、以前のバージョンのMLflowではこれらのタイプは認識されません。これらのシグネチャタイプを使うモデルを保存する際には、これらのモデルをロードしようとするすべての環境でインストールされているMLflowのバージョンが少なくとも2.10.0であることを確認するようにしてください。
- Spark ML vectorタイプはMLflowバージョン2.15.0で導入されており、以前のバージョンのMLflowではこれらのタイプは認識されません。
複合データタイプの他のサンプルは、シグネチャサンプルノートブックで確認することができます。
入力(Python)
from mlflow.models import infer_signature
infer_signature(model_input={
# Python list
"list_col": ["a", "b", "c"],
# Numpy array
"numpy_col": np.array([[1, 2], [3, 4]]),
# Dictionary
"obj_col": {
"long_prop": 1,
"array_prop": ["a", "b", "c"],
},
})
推定されたシグネチャ
signature:
input: '[
{"list_col": Array(string) (required)},
{"numpy_col": Array(Array(long)) (required)},
{"obj_col": {array_prop: Array(string) (required), long_prop: long (required)} (required)}
]'
output: null
params: null
オプションのカラム
入力データにNoneやnp.nanの値を含むカラムは、optional(required=False)として推定されます。
入力(Python)
from mlflow.models import infer_signature
infer_signature(model_input=
pd.DataFrame({
"col": [1.0, 2.0, None]
})
)
推定されたシグネチャ
signature:
input: '[
{"name": "col", "type": "double", "required": false}
]'
output: null
params: null
ネストされた配列に空のリストを含めることができますが、からのセット(∅)を表現することになるので、カラムをoptionalにしません。このようなケースでは、同質のタイプを持つことを前提として、リストの他の要素からスキーマが推定されます。カラムをオプションにしたい場合には、代わりにNoneを指定してください。
入力(Python)
infer_signature(model_input={
"list_with_empty": [["a", "b"], []],
"list_with_none": [["a", "b"], None],
})
推定されたシグネチャ
signature:
input: '[
{"name": "list_with_empty", "type": "Array(str)", "required": "true" },
{"name": "list_with_none" , "type": "Array(str)", "required": "false"},
]'
output: null
params: null
Tensorベースのシグネチャ
Tensorベースのシグネチャは、画像、音声データのようなフォーマットを含むディープラーニングアプリケーションでよく見られるtensor入力を処理するモデルで主に活用されます。これらのスキーマは、tensorのシーケンスから構成され、それぞれはnumpyデータタイプによって定義され、名前を指定することもできます。
Tensorベースのシグネチャでは、入出力tensorのそれぞれは3つの属性で特徴づけられます: dtype(numpyデータタイプとアラインするデータタイプ)、shape、オプションのnameです。Tensorベースのシグネチャでは、オプションの入力に対応しないことに注意することが重要です。shape属性では、バッチ処理でよく起こり得るサイズの変更可能性を踏まえて次元に-1を使うことが多くあります。
MNIST datasetでトレーニングされる分類モデルを考えてみます。モデルシグネチャの例は、画像をfloat32の数値の28 × 28 × 1の配列として表現する入力tensorとして特徴づけるかもしれません。モデルの出力は、10のターゲットクラスのそれぞれの確率を示すtensorかもしれません。このような場合、バッチサイズを表現する最初の次元は多くの場合-1に設定され、サイズが変化するバッチにモデルが対応できるようにします。
signature:
inputs: '[{"name": "images", "type": "tensor", "tensor-spec": {"dtype": "uint8", "shape": [-1, 28, 28, 1]}}]'
outputs: '[{"type": "tensor", "tensor-spec": {"shape": [-1, 10], "dtype": "float32"}}]'
params: null
サポートされるデータタイプ
Tensorベースのスキーマでは、numpy data typesをサポートしています。
入力(Python)
from mlflow.models import infer_signature
infer_signature(model_input=np.array([
[[1, 2, 3], [4, 5, 6]],
[[7, 8, 9], [1, 2, 3]],
]))
推定されるシグネチャ
signature:
input: '[{"type": "tensor", "tensor-spec": {"dtype": "int64", "shape": [-1, 2, 3]}}]'
output: None
params: None
Tensorベースのスキーマではオプションの入力はサポートしていません。Noneやnp.nanの値を持つ配列を渡すことはできますが、スキーマではそれらをオプションとしては認識しません。
推論パラメータを持つモデルシグネチャ
推論パラメータ(あるいはparams
)は、推論ステージでモデルに渡される追加の設定です。一般的な例には、大規模言語モデル(LLM)におけるtemperature
やmax_length
のようなパラメータがあります。通常、これらのパラメータはトレーニング時には不要であり、推論の際にモデルの挙動を調整する際の重要な役割を担います。同じモデルが様々な推論シナリオのために様々なパラメータを必要とすることがあるため、基盤モデルにおいてこの種の設定が重要になってきています。
MLflow 2.6.0のリリースでは、モデル推論の際に推論パラメーターのディクショナリーの仕様が導入されました。この機能は、推論結果に対する柔軟性とコントロールを強化し、より微妙な違いのあるモデルの挙動の調整を可能にしています。
推論時にパラメータを活用するには、モデルシグネチャに組み込む必要があります。パラメータのスキーマは以下を構成するParamSpec
要素のシーケンスとして定義されます:
-
name:
temperature
のようなパラメータの識別子。 - type: サポートされるデータタイプのいずれかに準拠するパラメータのデータタイプ。
- default: パラメータのデフォルト値、これによって仕様の値が指定されていない場合のフォールバックを確実にします。
-
shape: パラメータのシェイプ、多くの場合スカラー値なら
None
、リストなら(-1,)
となります。
この機能は、モデルのパラメータ化に対してさらに動的で適応型のアプローチを提供することで、MLflowにおけるモデル推論の取り扱いを大きく前進させました。
signature:
inputs: '[{"name": "input", "type": "string"}]'
outputs: '[{"name": "output", "type": "string"}]'
params: '[
{
"name": "temperature",
"type": "float",
"default": 0.5,
"shape": null
},
{
"name": "suppress_tokens",
"type": "integer",
"default": [101, 102],
"shape": [-1]
}
]'
推論ステージで、モデルに対して推論パラメータがディクショナリーの形態で提供されます。それぞれのパラメータ値は、モデルのシグネチャで指定されるタイプにマッチしているかどうかの検証対象となります。以下の例では、モデルシグネチャにおけるパラメータの定義のプロセスを説明し、モデル推論における適用形態をデモンストレーションしています。
import mlflow
from mlflow.models import infer_signature
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, ctx, model_input, params):
return list(params.values())
params = {"temperature": 0.5, "suppress_tokens": [101, 102]}
# params' default values are saved with ModelSignature
signature = infer_signature(["input"], params=params)
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
python_model=MyModel(), artifact_path="my_model", signature=signature
)
loaded_model = mlflow.pyfunc.load_model(model_info.model_uri)
# Not passing params -- predict with default values
loaded_predict = loaded_model.predict(["input"])
assert loaded_predict == [0.5, [101, 102]]
# Passing some params -- override passed-in params
loaded_predict = loaded_model.predict(["input"], params={"temperature": 0.1})
assert loaded_predict == [0.1, [101, 102]]
# Passing all params -- override all params
loaded_predict = loaded_model.predict(
["input"], params={"temperature": 0.5, "suppress_tokens": [103]}
)
assert loaded_predict == [0.5, [103]]
パラメータでサポートされるデータタイプ
MLflowのパラメータは、MLflow DataTypeを受け付けるように定義され、これには、これらのデータタイプの1次元のリストも含まれます。現時点では、MLflowはパラメータの1Dのリストのみをサポートしています。
パラメータの値を検証する際、値はPythonのネイティブ型に変換されます。例えば、np.float32(0.1)
はfloat(0.1)
に変換されます。
シグネチャの強制
MLflowのスキーマ強制は、指定された入力とパラメータをモデルのシグネチャに対して厳密に検証します。入力に互換性がない場合には例外が発生し、互換性のないパラメータに関しては警告を出力するか例外を発生させます。この強制は、背後のモデル実装を呼び出す前に適用され、モデル推論プロセスでも適用されます。しかし、この強制は、MLflowのモデルデプロイメントツールを活用する際、あるいはモデルがpython_function
としてロードされる際に固有であることに注意してください。mlflow.sklearn.load_model()
を通じてのように、ネイティブフォーマットでモデルがロードされる際には適用されません。
名前順の強制
MLflowでは、入力の名前はモデルシグネチャで検証されます。必須の入力の欠落は例外を発生させますが、オプションの入力の欠如では例外は発生しません。シグネチャで宣言されていない入力は無視されます。シグネチャで入力のスキーマが入力の名前を指定している場合、名前ベースでマッチングが行われ、入力はそれに応じて並び替えられます。スキーマで名前が使用されていない場合には、MLflowは入力の個数のみをチェックし、入力の順序ベースでマッチングが行われます。
入力のタイプの強制
MLflowは、モデルシグネチャで定義されている入力タイプを強制します。(DataFrameの入力を用いるような)カラムベースシグネチャのモデルでは、MLflowは必要に応じて安全な型変換を行い、ロスレスの変換のみを可能とします。例えば、intをlongへの変換、intからdoubleへの変換は許可されますが、longからdoubleへの変換は許可されません。タイプに互換性を持てない場合にはMLflowは例外を起こします。
PySparkデータフレームの入力に対しては、MLflowはPySparkのサンプルをPandasデータフレームにキャストします。MLflowはデータ行のサブセットに対してのみスキーマを強制します。
Tensorベースシグネチャのモデルでは、タイプのチェックはより厳格です。入力タイプがスキーマで指定されたタイプに準拠しない場合には例外が発生します。
パラメータタイプとシェイプの強制
MLflowでは、パラメータのタイプとシェイプはモデルシグネチャで念入りにチェックされます。推論の際には、シグネチャにおける仕様に準拠していることを確実にするために、それぞれのパラメータのタイプとシェイプが検証されます。スカラー値のシェイプはNone
であることが期待されますが、リストの値では(-1,)
のシェイプである必要があります。パラメータのタイプやシェイプで互換性がない場合、MLflowは例外を発生させます。さらに、パラメータの値はシグネチャで割り当てられたタイプに対して検証チェックを受けることになります。特定のタイプへの変換に失敗した場合、MlflowExceptionが引き起こされます。適切なパラメータの包括的なリストについては、モデル推論パラメータのセクションをご覧ください。
推論の際、宣言されていないパラメータを受けとったシグネチャあるのモデルは、リクエストごとに警告を発生させ、不適切なパラメータの全ては無視されます。
欠損値を持つIntegerの取り扱い
Pythonでは、欠損値を持つintegerのデータは、多くの場合floatとして表現されます。これは、integerカラムにおけるデータ型の変化につながり、integerとfloatは本質的に互換性がないので実行時のスキーマ強制エラーを引き起こすことがあります。例えば、トレーニングデータのカラムc
がすべてintegerである場合、それはそのように認識されます。しかし、c
に欠損値がある場合、floatとして表現されることになります。モデルシグネチャで、c
がintegerであることを期待している場合、MLflowはfloatをintに変換できないためエラーを発生させます。特にMLflowではモデルサービングとSparkのデプロイメントでPythonを使っているので、この問題に対策するためには、欠損値を持つintegerカラムをdouble(float64)として定義することをお勧めします。
日付とタイムスタンプの取り扱い
Pythonのdatetimeタイプでは、日の精度ならdatetime64[D]
、ナノ秒の精度ならdatetime64[ns]
のようなビルトインの精度を提供しています。この精度の詳細はカラムベースのモデルシグネチャでは無視されますが、tensorベースのシグネチャでは強制されます。
ラグのある配列の取り扱い
(-1,)のシェイプで表現され、objectのdypeであるnumpyのラグありの配列は、infer_signature
を使う際に自動で管理されます。これによって、Tensor('object', (-1,))
のようなシグネチャになります。より詳細な表現においては、Tensor('float64', (-1, -1, -1, 3))
のようなラグ配列の固有の性質を反映させるために、手動でシグネチャを作成することができます。ラグありの入力配列に合わせて、シグネチャの詳細に合わせて強制が適用されます。
シグネチャを持つモデルの記録方法
MLflowでモデルにシグネチャを含めるのは分かりやすいものとなっています。sklearn.log_model()
のようにlog_modelやsave_model関数を呼び出す際に、シンプルにモデル入力サンプルを提供します。MLflowは、この入力サンプルと指定されたサンプルに対してモデルが予測した出力に基づいて、モデルのシグネチャを自動で推定します。
あるいは、あなたのモデルに明示的にシグネチャオブジェクトをアタッチすることができます。これは、お使いのlog_modelやsave_model関数にsignature objectを渡すことで行うことができます。モデルシグネチャオブジェクトを手動で作成するか、適正なモデル入力(例えば、ターゲットカラムを除外したトレーニングデータセット)を持つデータセット、(トレーニングデータセットから生成した予測結果)適切なモデル出力、(transformers設定の生成でよく見られる、モデル推論時に使用するパラメータのディ区書なりのような)適切なモデルパラメータからシグネチャを生成するために、infer_signature
関数を使うことができます。
モデルシグネチャは、Python関数(PyFunc)フレーバーでモデルをサービングする際には特に、MLflowモデルデプロイメントツールで重要な役割を担います。このため、log_modelやsave_modelの呼び出しでシグネチャを指定する際には、モデルのPyFunc表現で期待される入出力をシグネチャが正確に反映しているようにすることが重要です。この検討は、PyFuncとしてロードされた際にモデルの入力スキーマがテストで用いられたデータセットのスキーマと異なる際には、特に重要となります(このシナリオの例はpmdarimaモデルフレーバーを用いた際のものです)。
カラムベースのシグネチャの例
以下の例では、Iris dataset
でシンプルな分類器に対するモデルシグネチャの保存方法を示しています:
import pandas as pd
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
import mlflow
iris = datasets.load_iris()
iris_train = pd.DataFrame(iris.data, columns=iris.feature_names)
clf = RandomForestClassifier(max_depth=7, random_state=0)
with mlflow.start_run():
clf.fit(iris_train, iris.target)
# Take the first row of the training dataset as the model input example.
input_example = iris_train.iloc[[0]]
# The signature is automatically inferred from the input example and its predicted output.
mlflow.sklearn.log_model(clf, "iris_rf", input_example=input_example)
以下のように、同じシグネチャを明示的に作成することができます:
from mlflow.models import ModelSignature, infer_signature
from mlflow.types.schema import Schema, ColSpec
# Option 1: Manually construct the signature object
input_schema = Schema(
[
ColSpec("double", "sepal length (cm)"),
ColSpec("double", "sepal width (cm)"),
ColSpec("double", "petal length (cm)"),
ColSpec("double", "petal width (cm)"),
]
)
output_schema = Schema([ColSpec("long")])
signature = ModelSignature(inputs=input_schema, outputs=output_schema)
# Option 2: Infer the signature
signature = infer_signature(iris_train, clf.predict(iris_train))
with mlflow.start_run():
mlflow.sklearn.log_model(clf, "iris_rf", signature=signature)
Tensorベースのシグネチャの例
以下の例では、MNIST dataset
に対してトレーニングしたシンプルな分類器に対するモデルシグネチャの保存方法を示しています:
import tensorflow as tf
import mlflow
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
model = tf.keras.models.Sequential(
[
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation="relu"),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10),
]
)
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
model.compile(optimizer="adam", loss=loss_fn, metrics=["accuracy"])
with mlflow.start_run():
model.fit(x_train, y_train, epochs=5)
# Take the first three training examples as the model input example.
input_example = x_train[:3, :]
mlflow.tensorflow.log_model(model, "mnist_cnn", input_example=input_example)
以下のように、同じシグネチャを明示的に作成することができます:
import numpy as np
from mlflow.models import ModelSignature, infer_signature
from mlflow.types.schema import Schema, TensorSpec
# Option 1: Manually construct the signature object
input_schema = Schema(
[
TensorSpec(np.dtype(np.float64), (-1, 28, 28, 1)),
]
)
output_schema = Schema([TensorSpec(np.dtype(np.float32), (-1, 10))])
signature = ModelSignature(inputs=input_schema, outputs=output_schema)
# Option 2: Infer the signature
signature = infer_signature(testX, model.predict(testX))
with mlflow.start_run():
mlflow.tensorflow.log_model(model, "mnist_cnn", signature=signature)
パラメータを持つシグネチャの例
以下の例では、シンプルなtransformersモデルのシグネチャの保存を方法を示しています:
import mlflow
from mlflow.models import infer_signature
import transformers
architecture = "mrm8488/t5-base-finetuned-common_gen"
model = transformers.pipeline(
task="text2text-generation",
tokenizer=transformers.T5TokenizerFast.from_pretrained(architecture),
model=transformers.T5ForConditionalGeneration.from_pretrained(architecture),
)
data = "pencil draw paper"
params = {
"top_k": 2,
"num_beams": 5,
"max_length": 30,
"temperature": 0.62,
"top_p": 0.85,
"repetition_penalty": 1.15,
"begin_suppress_tokens": [1, 2, 3],
}
# infer signature with params
signature = infer_signature(
data,
mlflow.transformers.generate_signature_output(model, data),
params,
)
# save model with signature
mlflow.transformers.save_model(
model,
"text2text",
signature=signature,
)
pyfunc_loaded = mlflow.pyfunc.load_model("text2text")
# predict with params
result = pyfunc_loaded.predict(data, params=params)
以下のように、同じシグネチャを明示的に作成することができます:
from mlflow.models import ModelSignature
from mlflow.types.schema import ColSpec, ParamSchema, ParamSpec, Schema
input_schema = Schema([ColSpec(type="string")])
output_schema = Schema([ColSpec(type="string")])
params_schema = ParamSchema(
[
ParamSpec("top_k", "long", 2),
ParamSpec("num_beams", "long", 5),
ParamSpec("max_length", "long", 30),
ParamSpec("temperature", "double", 0.62),
ParamSpec("top_p", "double", 0.85),
ParamSpec("repetition_penalty", "double", 1.15),
ParamSpec("begin_suppress_tokens", "long", [1, 2, 3], (-1,)),
]
)
signature = ModelSignature(
inputs=input_schema, outputs=output_schema, params=params_schema
)
モデルへのシグネチャの設定方法
モデルシグネチャなしに、あるいは誤ったシグネチャでモデルが保存されることがあります。既存の記録モデルに対してシグネチャを追加、更新するにはmlflow.models.set_signature()
APIを使います。以下では、使い方の例をいくつか示しています。
記録されたモデルへのシグネチャの設定
以下の例では、すでに記録されているsklearnモデルに対するモデルシグネチャの設定方法を示しています。以下のようにシグネチャなしでsklearnモデルを記録したものとします:
import pandas as pd
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
import mlflow
X, y = datasets.load_iris(return_X_y=True, as_frame=True)
clf = RandomForestClassifier(max_depth=7, random_state=0)
with mlflow.start_run() as run:
clf.fit(X, y)
mlflow.sklearn.log_model(clf, "iris_rf")
以下のように記録したモデルにシグネチャを設定できます:
import pandas as pd
from sklearn import datasets
import mlflow
from mlflow.models.model import get_model_info
from mlflow.models import infer_signature, set_signature
# load the logged model
model_uri = f"runs:/{run.info.run_id}/iris_rf"
model = mlflow.pyfunc.load_model(model_uri)
# construct the model signature from test dataset
X_test, _ = datasets.load_iris(return_X_y=True, as_frame=True)
signature = infer_signature(X_test, model.predict(X_test))
# set the signature for the logged model
set_signature(model_uri, signature)
# now when you load the model again, it will have the desired signature
assert get_model_info(model_uri).signature == signature
MLflowトラッキングの外で保存されたモデルアーティファクトにモデルシグネチャを設定できることにも注意してください。例えば、上のコードスニペットでモデルのローカルディレクトリをポイントするようにmodel_uri変数を変更することで、ローカルに保存したirisモデルにシグネチャを容易に設定することができます。
登録されたモデルへのシグネチャの設定
MLflowモデルレジストリのアーティファクトは読み取り専用を意図しているので、モデルバージョンやmodels:/
のURIスキーマで表現されるモデルアーティファクトに直接シグネチャを設定することはできません。代わりに、ソースのモデルアーティファクトにシグネチャを最初に設定し、更新されたモデルアーティファクトを用いて、新規のモデルバージョンを生成します。以下の例では、どのように行うのかを説明しています。
以下のようにシグネチャなしのモデルバージョンを作成しているものとします:
from sklearn.ensemble import RandomForestClassifier
import mlflow
from mlflow.client import MlflowClient
model_name = "add_signature_model"
with mlflow.start_run() as run:
mlflow.sklearn.log_model(RandomForestClassifier(), "sklearn-model")
model_uri = f"runs:/{run.info.run_id}/sklearn-model"
mlflow.register_model(model_uri=model_uri, name=model_name)
モデルバージョンにシグネチャを設定するには、以下のように新たなシグネチャを持つモデルバージョンを作成します:
from sklearn.ensemble import RandomForestClassifier
import mlflow
from mlflow.store.artifact.models_artifact_repo import ModelsArtifactRepository
client = mlflow.client.MlflowClient()
model_name = "add_signature_model"
model_version = 1
mv = client.get_model_version(name=model_name, version=model_version)
# set a dummy signature on the model version source
signature = infer_signature(np.array([1]))
set_signature(mv.source, signature)
# create a new model version with the updated source
client.create_model_version(name=model_name, source=mv.source, run_id=mv.run_id)
このプロセスは、モデルバージョン1のソースランを新たなモデルシグネチャで上書きすることに注意してください。
モデル入力サンプル
モデル入力サンプルは、適切なモデル入力の例を指定します。入力サンプルは、個別のアーティファクトとしてモデルとともに保存され、MLmodel fileで参照されます。モデルに入力サンプルを含めるには、sklearn.log_model()
のような適切なlog_modelの呼び出しに追加します。入力サンプルは、シグネチャが指定されていない際のlog_modelの呼び出しでモデルシグネチャを推定する際にも使用されます。
モデルを記録する際に入力サンプルを含めることで2つのメリットが生じます。初めに、モデルシグネチャの推定の助けとなります。次に、同様に重要なことですが、これはモデルの要件を検証します。 この入力サンプルは、記録しようとしているモデルを用いて予測を実行するために利用されるので、モデルの要件の依存関係の特定における精度を高めます。モデルを記録する際には常に入力サンプルを含めることを強くお勧めします。
デフォルトでは、入力サンプルがディクショナリーの場合、保存の際にはそれをPandasデータフレームフォーマットに変換します。langchain、openai、pyfunc、transformersフレーバーでは、example_no_conversion
をFalse
に設定することで、変換なしに入力サンプルを保存することができることに注意してください。
モデルシグネチャと同様に、モデル入力はカラムベース(データフレーム)、tensorベース(numpy.ndarray)、jsonオブジェクト(Pythonディクショナリー)にすることができます。モデル入力とパラメータを組み合わせるためにタプルを用いることで、パラメータを持つ入力サンプルをサポートしています。以下の例をご覧ください:
カラムベースサンプルを持つモデルの記録方法
カラムベースの入力を受け付けるモデルにおいては、サンプルは単一のレコードあるいはレコードのバッチにすることができます。入力サンプルは以下のフォーマットにすることができます:
- Pandasデータフレーム
- (スカラー、文字列、スカラー値のリストの)
dict
list
str
bytes
指定されたサンプルはPandasデータフレームに変換され、Pandasのsplit-orientedフォーマットを用いてjsonにシリアライズされます。Bytesはbase64エンコードされます。以下の例では、モデルとともにカラムベースの入力サンプルの記録方法を示しています:
input_example = {
"sepal length (cm)": 5.1,
"sepal width (cm)": 3.5,
"petal length (cm)": 1.4,
"petal width (cm)": 0.2,
}
mlflow.sklearn.log_model(..., input_example=input_example)
Tensorベースサンプルを持つモデルの記録方法
Tensorベースの入力を受け付けるモデルにおいては、サンプルは入力のバッチである必要があります。デフォルトでは、モデルシグネチャで他のものが指定されない限り、軸0はバッチ軸となります。入力サンプルは、以下のいずれのフォーマットで指定することができます:
- numpy ndarray
- 文字列をnumpy arrayにマッピングするPythonの
dict
- Scipy
csr_matrix
(sparse matrix) - Scipy
csc_matrix
(sparse matrix)
以下の例では、モデルとともにどのようにtensorベースの入力サンプルを保存できるのかを示しています:
# each input has shape (4, 4)
input_example = np.array(
[
[[0, 0, 0, 0], [0, 134, 25, 56], [253, 242, 195, 6], [0, 93, 82, 82]],
[[0, 23, 46, 0], [33, 13, 36, 166], [76, 75, 0, 255], [33, 44, 11, 82]],
],
dtype=np.uint8,
)
mlflow.tensorflow.log_model(..., input_example=input_example)
JSONオブジェクトサンプルを用いたモデルの記録方法
Pandasデータフレームではなく、Pythonディクショナリーを受け付けるモデルにおいては、そのままの状態でのサンプルの直接保存をサポートしています。この機能は、推論やモデルサービングにおいてサンプルの直接保存が有用であるlangchain、openai、pyfunc、transformersフレーバーでのみサポートされています。デフォルトでは、後方互換性のためにexample_no_conversion
はFalse
に設定されています。
以下の例では、モデルとともにjsonオブジェクトの入力サンプルの保存方法を示しています。
input_example = {
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "assistant", "content": "What would you like to ask?"},
{"role": "user", "content": "Who owns MLflow?"},
]
}
mlflow.langchain.log_model(..., input_example=input_example, example_no_conversion=True)
パラメータを含むサンプルを持つモデルの記録方法
推論の際に追加のパラメータを必要とするモデルにおいては、モデルの保存、記録時にパラメータを含む入力サンプルを含めることができます。これを達成するには、入力サンプルをtuple
として指定する必要があります。タプルの最初の要素は入力データサンプルであり、二つ目の要素はパラメータのdict
となります。適切なパラメータの包括的なリストは、推論パラメータを持つモデルシグネチャをご覧ください。
- Pythonの
tuple
: (input_data, params)
以下の例では、パラメータを含むサンプルとともにモデルを記録する方法を示しています:
# input_example could be column-based or tensor-based example as shown above
# params must be a valid dictionary of params
input_data = "Hello, Dolly!"
params = {"temperature": 0.5, "top_k": 1}
input_example = (input_data, params)
mlflow.transformers.log_model(..., input_example=input_example)