より悪いモデルが本番に上がった
目標
モデルのバージョンとエイリアス(champion・challenger)を備えた小さなレジストリを立て、指標が落ちたらプロモーションを止めるゲートを作り、ゲートを無視したプロモーションをロールバックし、最後に入力の分布が動いたかを数字にします。
なぜ重要なのか
モデルをデプロイするとは、ファイルをコピーすることではなく、どのバージョンを指すかを決めることです。本番がバージョン番号を直接持っていると、ロールバックはデプロイ設定の変更になりますが、エイリアスを1層置けば、ロールバックは、名前が指す先を変えることになります。そのため、レジストリは、バージョン・エイリアス・履歴の3つを一緒に置きます。プロモーションのゲートは、ここに「自動で進めるが、いつでも進むわけではない」を加える仕組みです。再学習が終わったからとすぐに本番へ行くと、ある週には、より悪いモデルが静かに上がります。ゲートが止めたのに人が押し切ることは、実際に起こるので、止めることと同じくらい、止めたという事実と、押し切ったという事実を、一緒に残すことが重要です。
ステップ
- 今本番に出ているモデルを、バージョン1として登録します。
- championエイリアスが、バージョン1を指すようにします。
- 再学習の結果をバージョン2として登録し、challengerとして立てます。
- プロモーションのゲートをコードで作り、判定を残します。
- エイリアスが動いた履歴を、監査記録として残します。
- ゲートを無視したプロモーションを再現し、その事実を記録に残します。
- ロールバックして、どれだけ失ったかを書きます。
- 入力の分布がどれだけ移動したかを測って、再学習の判断に付けます。
参考
- 作業はすべて
/root/registryの下で行います。まずmkdir -p /root/registryをしてください。 - 材料は
/opt/fixtures/mlopsにあります。学習器の--outが、モデルファイルを書いてくれます。 - 監査記録は追記です。すでに書いた行を直すと、履歴ではなく現在の状態になります。
- よくある間違い: ロールバックするときに、バージョン2をregistry.jsonから消すことです。エイリアスだけを動かしてください。
- ラボのPodにはボリュームがないので、セッションが終わると
/rootがなくなります。60分を超えそうなら、先に+시간(日本語の画面では「+時間」ボタンです)で延長してください。
今出ているモデルをバージョン1として登録する
lr 0.1、epochs 40、seed 7で学習器を回してモデルファイルを作り、/root/registry/registry.jsonに、modelを「churn-classifier」として、versionsの最初の項目としてversion 1を登録してください。各バージョンは、version・run_id・params・metrics・model_path・model_sha256・data_sha256・created_atを持ちます。model_sha256はmodel_pathが指すファイルのハッシュで、data_sha256は/opt/fixtures/mlops/train.csvのハッシュです。
学習器の--outオプションが、モデルファイルを書いてくれます。バージョンごとにファイルを別に置いてください。上書きすると、ロールバックする対象が残りません。
デプロイの対象を番号ではなく名前で指す
/root/registry/aliases.jsonに{"champion": 1}を保存してください。このラボで使うエイリアスは、championとchallengerの2つだけです。
本番がバージョン番号を直接見るようにしておくと、ロールバックのときにデプロイ設定を直す必要があります。エイリアスを1層置けば、ロールバックは、このファイルの1行になります。
再学習の結果をチャレンジャーとして登録する
lr 0.3、epochs 30、seed 3でもう一度学習させて、バージョン2を/root/registry/registry.jsonに登録し、/root/registry/aliases.jsonのchallengerが2を指すようにしてください。バージョン番号は1から1ずつ上がる必要があり、championはまだ1である必要があります。
再学習が、そのままデプロイではありません。新しいバージョンは、まずチャレンジャーとして立てておき、プロモーションは、次のステップのゲートが判断します。
プロモーションを人ではなくルールに判断させる
/root/registry/gate.pyを作成して、/root/registry/registry.jsonと/root/registry/aliases.jsonを読み、/root/registry/gate.jsonに、champion_version・challenger_version・champion_metric・challenger_metric・margin・decision・reasonsを保存してください。marginは0.005で、チャレンジャーのvalid_accuracyが、チャンピオン+margin以上のときだけ、decisionはpromote、そうでなければblockです。reasonsは、10文字以上の文のリストです。
マージンを置く理由は、測定のノイズです。0.001の差でチャンピオンを入れ替えると、来週また入れ替えることになります。
エイリアスが動いた履歴を残す
/root/registry/audit.jsonlに、これまでエイリアスを立てたことを、1行ずつ残してください。各行は、ts・alias・from(最初ならnull)・to・actor・reason(10文字以上)を持ちます。記録を最初から再生すると、/root/registry/aliases.jsonとまったく同じ状態が出てくる必要があります。
監査記録は、「今何であるか」ではなく、「どうやってここまで来たか」を持ちます。fromを書いておけば、再生したときに、ずれた場所がすぐに表に出ます。
ゲートを無視したプロモーションが何を残すかを見る
ゲートが止めたのに、人がchampionをバージョン2に上げた状況を再現してください。/root/registry/aliases.jsonのchampionを2に変え、/root/registry/audit.jsonlに、alias champion、from 1、to 2の行を追加して、その行に、gate_decisionというキーで、gate.jsonの判定をそのまま書いてください。
止めるだけでは足りません。押し切った人が何を知っていたのかが残らないと、次に同じことを話し合えません。
ロールバックして、どれだけ失ったかを書く
championをバージョン1に戻して、その変更も/root/registry/audit.jsonlに残してください(記録は全部で4行以上になり、最後の行が、champion 2→1である必要があります)。そのあと、/root/registry/rollback.jsonに、restored_version・bad_version・detected_by(10文字以上)・metric_delta(バージョン1のvalid_accuracy−バージョン2の値、小数点以下4桁に丸める)を保存してください。
ロールバックは、エイリアスを動かすことです。モデルファイルを消したりバージョンを削除したりすると、何があったのかも一緒に消えます。
入力の分布が動いたかを数字にする
/opt/fixtures/mlops/train.csvを基準に、/opt/fixtures/mlops/week2.csvの5つの特徴量(tenure_months・monthly_fee・support_tickets・late_payments・usage_hours)がどれだけ移動したかを測って、/root/registry/drift.jsonに、baseline・current・features・max_abs_shift_sigma・alert・retrain_requiredを保存してください。featuresの各項目は、train_mean・new_mean・pstdev(基準データの母集団標準偏差)・shift_sigma((new_mean − train_mean) ÷ pstdev)を持ち、空の値や数字でない値は数えません。alertとretrain_requiredは、max_abs_shift_sigmaが1.0を超えるかどうかです。
分布の移動は、契約違反ではありません。値がすべて許容範囲の中にあっても、平均が動くことがあります。そのため、別に測る必要があります。