本ページは モデル監視(Model Monitoring)を 12 のセクションで多角的に解説します。 上のチップは検索・関連語の手がかりです。 以下のリンクで各セクションに直接ジャンプできます:
🍰 まずはやさしく
モデルの健康診断のようなものです。
予測の精度が落ちていないか調べます。
スマホアプリの不具合を早めに見つける例です。
まずは結論から簡単に説明します。
🍰 まずはやさしく
AIを使い始めた後の管理のことです。
時間が経っても正しく動かすために使います。
部活の道具を定期的に手入れする感覚です。
どのような場面で使うのかを解説します。
モデル監視は MLOps の中で 「デプロイ後」の管理に相当します。 学習・評価で良い精度が出ても、 実運用では時間とともに性能が劣化(モデル腐敗 = model staleness)するのが普通。 リテール・金融・医療など、 ビジネス影響が大きい分野では 必須プロセスです。 「作って終わり」ではなく「運用しながら育てる」発想の中核技術。
🍰 まずはやさしく
変化を察知するレーダーのようなものです。
現実とAIのズレを見つけるために使います。
流行が変わっておすすめ商品が外れる例です。
具体的に何が起きるのかをイメージしましょう。
分類器を本番稼働させたあと、 放っておくと何が起きるかを思い浮かべてください:
これらは 誰も気づかないうちに 静かに進行し、 「ユーザー解約率を予測するモデルが、 半年後には勘より悪い」という事態を生みます。 監視は 「腐敗の早期検知」のレーダーです。
たとえば 2020年3月のコロナ禍では、 多くの企業の予測モデルが 一夜にして使い物にならなくなりました。 監視していた企業は数日で再学習に動けたが、 監視を怠っていた企業は 数ヶ月間誤った予測を出し続けて損失を拡大させました。
🍰 まずはやさしく
ズレを数字で測るためのルールです。
客観的に異常を判断するために使います。
テストの点数の分布が変わったか調べる例です。
計算方法や判断基準について学びます。
監視で頻出する代表指標:
モデル監視を数式 / 形式定義で表す:
PSI(Population Stability Index):学習時の特徴量ヒストグラム $p_k$ と本番時 $q_k$ のビンごと乖離度を合計。 0.1 未満は正常、 0.25 以上で再学習推奨。
PSI は本番モニタリングで最もよく使われるドリフト指標です。 基準期の分布 $p_i$ と監視期の分布 $q_i$ をビンごとに比較します。
数式を言葉で読み解く:各ビン $i$ について「監視期の比率 − 基準期の比率」と「対数比」を掛けて足し合わせる。 KL divergence を対称化した量に近く、 ビンごとの差が大きいほど PSI が増大します。
| PSI 値 | 解釈 | 推奨アクション |
|---|---|---|
| $<$ 0.10 | 分布は安定 | 通常監視を継続 |
| 0.10〜0.25 | 中程度のシフト | 調査タスク発行、 警報レベル |
| $>$ 0.25 | 顕著なシフト | 再学習・原因究明・場合により切り戻し |
この閾値は経験則で、 業界・モデル種別に応じて再校正が必要です。 金融与信では 0.10 / 0.25、 マーケでは 0.20 / 0.40 を使う例もあります。
上の数式に出てきた記号を 1 つずつ解説します。 数式が出てくる試験問題(統計検定・G 検定・基本情報)では、 各記号の意味を答えられるかが分岐点:
| 記号 | 意味 |
|---|---|
| $K$ | ビン数(10 が標準) |
| $p_k$ | 学習データのビン $k$ の構成比 |
| $q_k$ | 本番データのビン $k$ の構成比 |
| $\log$ | 自然対数 |
「モデル監視」を入門レベルで習得した次に進むべき発展テーマ:
基本概念を 確率論・情報理論・最適化理論の観点で再定式化すると、 隣接する手法との理論的な関係が見えてきます。 たとえば 正則化は事前分布の最大事後推定と等価、 クロスエントロピー損失は KL ダイバージェンスを最小化、 といった対応関係を押さえると教科書間の往復が楽になります。
scikit-learn 標準実装の外側に出ると、 GPU 対応・分散学習・低精度浮動小数点(fp16/bf16)・量子化(int8)・グラフ最適化(TorchScript・ONNX Runtime)など、 推論性能を 10–100 倍引き上げるテクニックが豊富にあります。 本番運用では モデル精度と推論コストのトレードオフを意識した実装が鍵。
予測精度だけでなく SHAP・LIME・Permutation Importance によるモデル解釈、 Calibration(確率の校正)、 Counterfactual Explanation、 Fairness 指標(demographic parity, equalized odds 等)を組合せると、 業務応用での説得力が一段増します。
医療(薬機法・GxP)・金融(モデル管理ガイドライン)・公共(個人情報保護法)など、 業界固有の規制・ガイドラインを モデル設計段階から埋め込むのが現代のスタンダード。 「モデル監視」を業務適用するときは、 ドメインの専門家・法務との早期コラボレーションが成否を分けます。
「モデルの精度が落ちる」と一口に言っても、 原因は 3 つの異なる階層で発生します。 階層ごとに使う検出指標が異なるため、 まず分類を頭に入れます。
| 階層 | 何が変化したか | 代表指標 | SSDSE での例 |
|---|---|---|---|
| ① Data Drift(共変量シフト) | 入力 X の分布が変化($P(X)$ が変わる) | PSI / KS / Wasserstein | 人口構成(A1101)が高齢化で右シフト |
| ② Concept Drift(概念シフト) | $P(Y|X)$ が変化(同じ入力でも答えが変わる) | 精度・F1 の時系列追跡 / Error rate | 同じ年齢構成でも医療費の構造が変化 |
| ③ Label Drift(ラベルシフト) | $P(Y)$ が変化(クラス比率が変化) | クラス比較 / Prior shift estimation | 過疎県の比率が増えクラスバランス崩壊 |
特に silent failure(精度劣化が顕在化しないままビジネス指標だけ悪化)は、 ground truth lag(正解ラベルが数週〜数か月遅れる)と組み合わさると致命的です。 そのため、 ラベル到着前でも検出できる ① Data Drift の常時監視がモニタリングの出発点になります。
機械学習モデルが本番環境にデプロイされた後、 時間とともに性能が劣化していくのはほぼ避けられない現象である。 この劣化を早期に検知し、 必要に応じて再学習やロールバックを判断するための活動が「モデル監視(model monitoring)」である。 監視の対象は大きく二つに分かれる: データドリフト(data drift)とコンセプトドリフト(concept drift)である。 データドリフトは、 入力特徴量 X の分布 P(X) が学習時と推論時で変化する現象を指す。 例えば SSDSE-B-2026 で学習した「都道府県人口を予測するモデル」を運用していると、 数年後には少子化の進行により全国の人口分布そのものが大きく変動し、 学習時に「平均値・分散」として扱っていた値が陳腐化してしまう。 一方コンセプトドリフトは、 入力 X と出力 Y の関係性 P(Y|X) そのものが変化する現象を指す。 例えば「平均寿命が高い県は出生率も高い」という関係性が、 社会構造の変化によって崩れていく場合がこれにあたる。 モデル監視ではこの両方を区別して捉えることが重要である。 データドリフトのみであれば再学習で対応できることが多いが、 コンセプトドリフトの場合は特徴量の再設計、 ラベルの再定義、 さらにはタスク自体の見直しが必要になることもある。
データドリフトを定量化する代表的な指標が PSI である。 学習時の特徴量分布を B 個のビンに分け、 各ビンの相対頻度を $p_i$、 推論時のサンプルの同じビンの相対頻度を $q_i$ とするとき、 PSI は次の式で定義される:
$$\mathrm{PSI} = \sum_{i=1}^{B} (q_i - p_i) \ln\left(\frac{q_i}{p_i}\right)$$
この式は KL ダイバージェンスの対称化に近い形をしており、 「分布の差」を一つの数値に集約してくれる。 経験的な閾値として、 PSI < 0.1 ならば無視できるドリフト、 0.1 ≦ PSI < 0.25 ならば中程度の注意、 PSI ≧ 0.25 ならば再学習を検討すべき、 とされることが多い(業界・組織で調整必要)。 一例として「課税対象所得」を特徴量に持つモデルを考えよう(以下は数値を仮に置いた説明用の例)。 学習時のサンプルでは中央値 320 万円、 推論時のサンプルでは中央値 360 万円に上がっていた場合、 ビン境界を学習時のクオンタイルで固定して各ビンの相対頻度を求めると、 高所得ビンの $q_i$ が増え、 低所得ビンの $q_i$ が減る。 その結果、 PSI が中程度(0.1〜0.25)のレンジに入ることがある。 こうしたとき、 機械的に再学習を走らせるのではなく、 まず「分布シフトが本物か、 サンプリングバイアスか」を確認するのが実務的な手順である。

PSI はビン分割に依存するため、 ビン数や境界の取り方で値が変わる。 連続値の分布シフトを「ビン分割を介さず」に評価したい場合は、 Kolmogorov–Smirnov 検定が有効である。 KS 統計量は累積分布関数 F_learn(x) と F_infer(x) の差の最大値として定義される:
$$D_{n,m} = \sup_{x} \left| F_{\mathrm{learn}}(x) - F_{\mathrm{infer}}(x) \right|$$
scipy.stats.ks_2samp を使えば一行で計算できる。 統計的に有意(p < 0.05)であれば「学習時と推論時で分布が同じ」という帰無仮説が棄却され、 ドリフトの存在が示唆される。 ただし注意点として、 サンプルサイズが大きい(例えば数万件)と「実務上は無視できる差」でも有意になりがちであるため、 p 値だけでなく D 統計量そのものの大きさや、 業務的なインパクトを併せて判断する必要がある。 SSDSE-B-2026 のような 47 都道府県データであればサンプルが小さく、 KS 検定が「過剰に敏感」になることは少ないが、 銀行のスコアリングモデルのように 1 日あたり数十万件の推論をするケースでは、 監視時には「直近 1 週間 vs ベースライン」のように適切なウィンドウサイズを選ぶ必要がある。
データドリフトとコンセプトドリフトに加えて、 監視の第三軸として「特徴量重要度のドリフト」がある。 同じモデルを使い続けていても、 入力分布の変化により「これまで主役だった特徴量」と「今、 効いている特徴量」が入れ替わることがある。 これを把握するには、 SHAP 値の絶対値の平均(mean |SHAP|)を時系列で追跡するのが効果的である。 例えば住宅価格予測モデルで「築年数」「面積」「最寄り駅距離」の三特徴を使っているとき、 通常は「面積」の mean |SHAP| が最大であったところ、 ある月から急に「最寄り駅距離」が首位になった、 という変化が起きたら、 これは「都市部から郊外へ顧客層がシフトしている」「新しい鉄道路線が開業した影響」など、 ビジネス上の構造変化を示唆する。 mean |SHAP| のドリフトは PSI と同様に「分布シフト」として扱うことができ、 同じ閾値(0.1 / 0.25)を準用する組織が多い。 ただし SHAP 値は計算コストが高いため、 リアルタイム監視には不向きで、 日次・週次のバッチ集計で監視するのが現実的である。

回帰タスクであれば、 予測値 ŷ と実測値 y の散布図を期間別に重ね描きして、 経時的なバイアスや分散の変化を視覚的に把握するのも有効である。 理想的には散布点は y = ŷ の対角線上に集まるはずだが、 ドリフトが進むにつれて点が対角線から離れたり、 系統的に上下に偏ったりする。 たとえば需要や売上を予測する回帰モデルでは、 運用当初は散布点が対角線にほぼ重なっていても、 環境変化が進むにつれて点が広がり、 系統的なバイアスが現れてくるのが観察できる。 監視ダッシュボードでは「直近 30 日 vs ベースライン」「四半期別」「県別」など複数の切り口で散布図を提示し、 単一の RMSE 値だけでは見えないパターン(特定セグメントだけ大きく外している、 高価格帯で過小予測している、 など)を発見できるようにするのが望ましい。

監視指標を計算するだけでは意味がなく、 「閾値を超えたら誰がいつ何をするか」というアラート運用設計が肝心である。 ありがちな失敗は、 (1) 閾値が厳しすぎて連日アラートが飛び、 担当者が「狼少年化」して無視するようになる、 (2) 閾値が緩すぎて重大な劣化を見逃す、 (3) アラートは飛ぶが対応プロセスが未定で「誰も動かない」、 の三つである。 これを防ぐには、 まず「監視 → アラート → 一次調査 → エスカレーション → 再学習判断 → 本番反映」という一連のフローを RACI(Responsible / Accountable / Consulted / Informed)でドキュメント化することが重要である。 また、 アラート閾値自体を 1〜3 ヶ月単位でレビューし、 過去のアラート履歴・対応履歴と照らし合わせて適切なレベルに調整する「メタ監視」も推奨される。 SSDSE-B-2026 のような小規模データで作った教育用モデルでは現実感が薄いかもしれないが、 実務では「PSI > 0.25 が 3 日連続したらメール、 7 日連続したら slack 通知、 14 日連続したらインシデント起票」のように、 段階的にエスカレーションする仕組みを作っておくと運用が安定する。
アラートが鳴ったからといって即座に再学習を走らせるのが正解とは限らない。 再学習にはコスト(計算資源、 検証作業、 本番反映の調整、 ロールバック準備)がかかり、 さらに「新しいモデルが古いモデルよりも本当に良い」と保証するための比較検証も必要である。 PSI が 0.25 を超えたとしても、 本番のビジネス KPI(売上、 解約率、 不良率など)に有意な悪化が見られなければ「監視は続けるが触らない」という判断もありうる。 逆に PSI は静かでも、 業務メトリクスが悪化していれば、 それはコンセプトドリフトのサインであり、 特徴量設計の見直しまで踏み込むべき場合もある。 「データの動きとビジネスの動き」を別々の軸で観察し、 両者の整合性を見るのが熟達した監視運用である。 また、 再学習を決断したときも、 シャドーデプロイ(旧モデルの隣で新モデルを走らせ、 結果は本番には使わない)、 A/B テスト、 カナリアリリース(少数のリクエストだけ新モデルに振る)といった段階的展開を組み合わせ、 リスクを抑える設計が望ましい。
📚 関連用語: データドリフト / コンセプトドリフト / PSI / KS 検定 / SHAP 値 / A/B テスト / カナリアリリース / 再学習
モデル監視で最も重要なのは「何を、 どの粒度で、 どの閾値で、 誰に通知するか」というアラート設計である。 ここを雑にすると、 誤検知だらけでチーム全員が通知を無視するようになり(いわゆる「アラート疲労」)、 本当に重要なドリフトを見落とす。 逆に厳しすぎると小さなノイズで毎日呼び出されて夜も眠れなくなる。 実務では 3 段階のアラートレベル(INFO / WARNING / CRITICAL) を設けるのが定番である。
| レベル | トリガー例 | 通知先 | 対応 SLA | エスカレーション |
|---|---|---|---|---|
| INFO | PSI 0.1〜0.15 / 推論件数 ±20% | Slack #model-info | 翌営業日に確認 | なし(ログのみ) |
| WARNING | PSI 0.15〜0.25 / AUC -3% / レイテンシ p99 ×1.5 | Slack #model-alert + email | 当日中に一次調査 | 24h 未解決で CRITICAL 昇格 |
| CRITICAL | PSI ≧ 0.25 / AUC -10% / エラー率 ≧ 5% | PagerDuty 電話 + on-call ローテ | 15 分以内に確認 | 30 分未対応で MLOps Lead 召集 |
ポイントは、 INFO の閾値を 「日常的な揺らぎの 95% カバー域」 に設定し、 WARNING で「異常の可能性が出始める領域」、 CRITICAL で「業務影響が確実に出る領域」を区切ること。 こうすると CRITICAL がほぼ「真陽性のみ」になり、 on-call エンジニアの信頼が保たれる。 また、 アラート抑制(同一原因の通知を 1 時間に 1 回に集約)と「業務時間内 / 業務時間外」での閾値の切り替えも実装しておくと、 夜間の誤呼び出しを減らせる。
| 項目 | 確認内容 | よくある失敗 |
|---|---|---|
| 閾値の根拠 | 過去 90 日の分布から 95%, 99% 分位点で設定したか | 勘で「PSI 0.2」と決めて誤検知連発 |
| 集約窓 | 1 時間移動平均 / 24 時間移動平均など複数粒度を併用したか | 1 分粒度のみで微小スパイクに反応 |
| 通知のグルーピング | 同一根本原因のアラートを 1 件に束ねるルールがあるか | 10 特徴量 × 各 PSI 超過で 10 通知が同時に来る |
| サイレンス機能 | 既知のメンテナンス時間帯はアラートをミュートできるか | DB メンテで毎週土曜 02:00 に偽 CRITICAL |
| エスカレーション | 未対応のまま N 分経過したら自動で次の人に転送されるか | 一人の on-call が寝落ちして 3 時間放置 |
| ポストモーテム | CRITICAL 発火後に必ず振り返り会を実施しているか | 対応はしたが「次に同じことが起きたら同じ対応」 |
さらに、 アラートの 「実効性指標」 自体を監視するのが上級者の流儀である。 例えば precision of alerts(発火したアラートのうち本当に対応が必要だった割合)を月次でレビューし、 70% を下回ったら閾値を緩める、 90% を超えたら細かいシグナルを逃している可能性があるので閾値を厳しくする、 といった「アラートのアラート」を回す。 この PDCA を 3 ヶ月続けると、 アラート数は半減し、 検出率は逆に上がるという報告が多い。
監視と切り離せないのが 段階的ロールアウト の設計である。 新モデルを 100% トラフィックに一気に切り替えるのは禁忌で、 通常は以下のように段階を踏む。
| フェーズ | トラフィック比率 | 監視重点 | 期間目安 |
|---|---|---|---|
| シャドー | 0%(裏で並行推論のみ) | レイテンシ / メモリ / 旧モデルとの予測差 | 3〜7 日 |
| カナリア | 1〜5% | 業務 KPI / エラー率 / ユーザー苦情 | 2〜3 日 |
| ランプアップ | 10% → 25% → 50% | セグメント別性能 / 公平性指標 | 各段階 1〜2 日 |
| 全面展開 | 100% | 通常監視(PSI / AUC / レイテンシ) | 継続 |
シャドーデプロイ段階で発見できる典型的問題は、 「推論レイテンシが想定の 3 倍」「特定セグメントで予測が NaN を返す」「旧モデルとの予測差が想定 5% を超えて 30% に達する」など。 これらを本番ユーザーに当てる前に潰せるのが価値である。 カナリア段階では業務 KPI(CVR、 解約率、 売上)の差分を慎重に監視し、 統計的に有意な悪化が見られたら自動ロールバックする仕組みを入れる。
ロールバックは「30 秒以内に旧モデルへ戻せる」状態を維持するのが鉄則で、 これができていないとカナリアでも怖くて踏み出せない。 具体的にはモデルファイルを S3 等に世代管理しておき、 推論サーバーの起動引数で参照バージョンを切り替えるか、 API ゲートウェイのルーティング比率を 0% にするだけで戻せる構成にしておく。 この「ロールバック耐性」が実は監視より上位の安全装置である。
モデル監視で扱う時系列メトリクス(AUC、 レイテンシ、 PSI など)は、 単純な閾値判定だけでなく 時系列異常検知アルゴリズム と組み合わせると検出力が大きく向上する。 ここでは実務でよく使われる 3 つの手法を、 数式と実装の両面から紹介する。
CUSUM は 小さな平均値のシフトを早期に検出 するのに向く古典的かつ強力な手法である。 監視対象のメトリクス $x_t$ について、 以下の上側統計量 $S^+_t$ と下側統計量 $S^-_t$ を逐次更新する。
$$S^+_t = \max(0,\; S^+_{t-1} + (x_t - \mu_0) - k)$$ $$S^-_t = \min(0,\; S^-_{t-1} + (x_t - \mu_0) + k)$$ここで $\mu_0$ は基準期間の平均、 $k$ は「許容するシフト量」(通常は $0.5\sigma$)、 アラート閾値 $h$ は通常 $4\sigma \sim 5\sigma$ に設定する。 $S^+_t > h$ で「上方向への持続的シフト」を検出する。 単純な「3σ ルール」と比べて、 小さなドリフトでも累積していけば必ず検出する という性質があり、 AUC が日々わずかずつ下がる「じわじわ劣化」の検出に最適。
EWMA は直近の観測値に重みを置き、 古い観測値の影響を指数的に減衰させる。 $\lambda = 0.2$ あたりがバランスが良い実務値。 管理限界は $\mu_0 \pm L \sigma \sqrt{\frac{\lambda}{2-\lambda}}$ で、 $L=3$ がよく使われる。 ノイズの多い PSI 系列を滑らかに監視し、 一方で本物のシフトには素早く反応する性質がある。 単純な移動平均よりも応答性が高い。
ストリーミングデータでメモリを使わずに変化点を検出したい場合は Page-Hinkley が便利である。
$$m_t = \sum_{i=1}^{t} (x_i - \bar{x}_t - \delta)$$ $$M_t = \min_{1 \le i \le t} m_i$$ $$\text{PH}_t = m_t - M_t$$$\text{PH}_t > \lambda$ となった瞬間にアラート発火。 $\delta$ は「許容ドリフト量」、 $\lambda$ は感度。 ストリーミング処理基盤(Kafka, Spark Streaming など)で O(1) メモリ で動かせるのが最大の利点。
| 手法 | 検出が得意な変化 | 必要なメモリ | 遅延 | 代表的な用途 |
|---|---|---|---|---|
| 単純閾値 | 突発的なスパイク | O(1) | 即時 | エラー率・レイテンシ p99 |
| CUSUM | 小さな平均シフト | O(1) | 中(数日) | AUC のじわじわ劣化 |
| EWMA | ノイズの中の傾向変化 | O(1) | 小〜中 | PSI 系列の平滑監視 |
| Page-Hinkley | オンラインの変化点 | O(1) | 小 | Kafka ストリーミング監視 |
| 変化点モデル(PELT 等) | 複数の変化点 | O(N) 〜 O(N log N) | 大(バッチ) | 事後分析・原因特定 |
このコードでやること: 仮想的な AUC 時系列に CUSUM を適用し、 平均シフトを早期検出する。 ここでは説明用に合成データではなく、 SSDSE-B 都道府県人口(実データ)の経年差分を「メトリクス時系列」として使い、 累積シフトを観察する。
📥 入力データ(SSDSE-B-2026 都道府県人口の経年変化を AUC 時系列に見立てた抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import numpy as np import pandas as pd # 実データ(SSDSE-B-2026 都道府県人口の年次比)を読み込み、 AUC 系列に見立てる df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') # ここでは説明上、 0.87 から少しずつ下降する仮想 AUC 系列を実データの長さで作る n = 30 x = np.linspace(0.872, 0.840, n) # 実運用では monitoring DB から取得 mu0, sigma = 0.870, 0.005 # 基準期間の統計 k = 0.5 * sigma # 許容シフト量 h = 5 * sigma # アラート閾値 S_pos, S_neg = 0.0, 0.0 for t, xt in enumerate(x): S_pos = max(0, S_pos + (xt - mu0) - k) S_neg = min(0, S_neg + (xt - mu0) + k) if S_pos > h or S_neg < -h: print(f"[ALERT] t={t}: S+={S_pos:.4f}, S-={S_neg:.4f}, xt={xt:.4f}") break else: print("No alert in this window.") |
📤 実行すると次の出力が得られる:
💬 単純閾値(例えば AUC < 0.85)だけでは t=20 付近で初めて検出されるが、 CUSUM は t=11 で下方シフトを検出できる。 9 ステップ早く気づける ことで、 ユーザー影響を最小化できる。 これが「累積」のパワーである。
監視ダッシュボードは「誰が、 何を、 いつ見るか」で 3 階層に分けて設計するのが定石である。 全員に全情報を見せると認知負荷で重要シグナルを見落とすため、 役割ごとに見る粒度を変える。
| 階層 | 対象者 | 表示メトリクス | 更新頻度 | 主要 KGI |
|---|---|---|---|---|
| エグゼクティブ | 事業部長 / CXO | ビジネス KPI(売上、 CVR、 NPS) | 日次 | モデル稼働率 ≧ 99.5% |
| プロダクト | PdM / ML プロダクトオーナー | AUC, F1, セグメント別性能, 公平性 | 時間〜日次 | 予測精度 SLO 遵守 |
| エンジニア | MLOps / SRE / データサイエンティスト | PSI, レイテンシ, エラー率, 特徴量分布 | 分〜時間 | MTTR < 30 分 |
SRE(Site Reliability Engineering)の概念をモデル監視に移植したのが ML-SLO である。 用語を整理する。
| カテゴリ | SLI 例 | SLO 設定例 | 違反時のアクション |
|---|---|---|---|
| 可用性 | 推論成功率 | ≧ 99.5% / 月 | 新規機能凍結 → 信頼性改善 |
| レイテンシ | 推論 p95 | ≦ 200 ms | キャッシュ追加 / モデル軽量化 |
| 精度 | 日次 AUC | ≧ 0.85 (90% の日数で) | 再学習トリガー / 特徴量見直し |
| 公平性 | グループ間 TPR 差 | ≦ 5 pp | バイアス緩和手法を導入 |
| 鮮度 | 特徴量の最終更新時刻 | ≦ 1 時間遅延 | パイプライン優先度引き上げ |
重要なのは 「エラーバジェットを使い切る前に対応する」 という運用思想で、 これが従来の「とにかくゼロエラー」というスタンスとの違いを生む。 エラーバジェットがまだ残っているうちは新機能リリース・実験を続けてよいが、 バジェットを 80% 消費したら警告、 100% 消費したら新規リリースを止めて信頼性改善に全力集中、 という運用ルールにする。 これにより「信頼性 vs 開発速度」のトレードオフが客観化される。
Grafana や Looker でダッシュボードを作る際の実務的なコツを 5 つ挙げる。
最後に、 ダッシュボード自体の鮮度監視 も忘れずに。 「ダッシュボードが過去 24 時間更新されていない」というアラートを別系統(メトリクス系とは別の単純な cron)で監視する。 そうしないと「監視システム自体が止まっていたので異常を見逃した」という最悪のシナリオが起こる。 監視の監視、 メタ監視と呼ばれる領域である。
モデル監視の真価は「異常を検出した瞬間」ではなく、 その後の 根本原因分析(RCA, Root Cause Analysis) に表れる。 ここでは、 実務でよく遭遇する 5 つのインシデント類型について、 検出パターン・調査手順・恒久対策をまとめる。 これらは MLOps チームで「インシデント・プレイブック」として整備しておくと、 新人でも対応できるようになる。
| 類型 | 検出パターン | 典型原因 | 調査手順 | 恒久対策 |
|---|---|---|---|---|
| データドリフト | PSI ≧ 0.25 が連続 3 日 | 顧客層変化 / 季節要因 / 上流データ仕様変更 | 特徴量別 PSI を降順表示、 上位 3 件を深掘り | 月次再学習 / 特徴量モニタの自動化 |
| コンセプトドリフト | AUC -5% / PSI は安定 | 行動様式変化 / 競合参入 / 法規制変更 | SHAP 重要度の変化を時系列で観察 | 特徴量再設計 / タスク再定義 |
| データパイプライン障害 | 特徴量が NaN / 鮮度遅延 | 上流 DB スキーマ変更 / ETL ジョブ失敗 | パイプライン上流から順次確認 | スキーマ契約(Data Contract)導入 |
| 推論レイテンシ悪化 | p95 が SLO 超過 | トラフィック増 / モデルサイズ膨張 / GC 多発 | プロファイラで CPU・メモリの内訳確認 | モデル蒸留 / オートスケール調整 |
| 公平性悪化 | グループ間 TPR 差 ≧ 5 pp | マイノリティ層のデータ減少 / ラベル偏り | セグメント別の混同行列を比較 | バイアス緩和 / セグメント別評価導入 |
ある EC サイトのレコメンドモデルで、 金曜 18 時〜23 時の AUC が他時間帯より 8 pp 低い という現象が観測された。 一見すると週末前の購買行動の変化(コンセプトドリフト)に見えるが、 詳細調査の結果、 原因は 「金曜夜だけログ収集パイプラインが他社のキャンペーン配信と競合して詰まり、 直近 1 時間の閲覧履歴特徴量が古いまま使われていた」 というデータパイプライン障害だった。 PSI は全体では安定していたが、 「特徴量の鮮度」を別 SLI として監視していなかったため、 検出が遅れたのである。 恒久対策として「特徴量のタイムスタンプと推論時刻の差」を新しい SLI に加え、 30 分以上の遅延でアラートを出す仕組みを導入した。
PSI 0.18 のアラートを受けて自動再学習を実行したところ、 再学習後の AUC が 逆に 2 pp 下がる という現象が起きた。 原因は、 「直近の学習データに、 短期キャンペーンによる異常購買パターンが大量に混入していた」こと。 つまり一時的なノイズを「真のシグナル」として学習してしまい、 平常時の予測が悪化した。 教訓として、 (1) 再学習データには「キャンペーン期間中フラグ」を必ず付けて期間を選別できるようにする、 (2) 再学習後は必ずホールドアウト評価を行い、 旧モデルより悪化していたら自動的に旧モデルへ戻す、 という 2 段階の防御を導入した。
バイナリ分類モデルで、 ある日突然「予測確率がほぼ 0」のレコードが急増した。 PSI も AUC も急変はなかったが、 予測値の分布の歪度 を監視していたメトリクスが反応した。 原因は、 上流の特徴量パイプラインで「カテゴリカル変数のエンコーディング辞書」が壊れ、 未知カテゴリとして扱われるレコードが急増したこと。 ここから得られる教訓は、 「入力特徴量の分布」と「予測値の分布」の両方を別々に監視せよ、 ということ。 どちらか片方では今回のような「特徴量はそれっぽく見えるが意味が壊れている」ケースを見逃す。
| 項目 | 記述内容 |
|---|---|
| 検出時刻 / 解消時刻 | UTC で記録、 MTTR 算出に必須 |
| 影響範囲 | 影響を受けたユーザー数、 失われた売上、 誤判定件数 |
| タイムライン | 最初のシグナル → アラート → 一次対応 → 恒久対応の時刻順記録 |
| 根本原因 | 「なぜ」を 5 回繰り返して掘り下げた結論 |
| 恒久対策 | 同種事象を二度と起こさないための仕組み変更(個人の注意では不可) |
| 学んだこと | プレイブックに追加すべき項目、 チーム全体への展開ポイント |
RCA の最重要原則は 「個人を責めない(blameless postmortem)」 である。 「○○さんが××を見逃したから」という結論は、 個人の注意力に依存する不安定な仕組みを温存し、 同じ事故をいつか必ず再発させる。 「そもそも個人の注意力に依存しなくても済むよう、 ガードレール(自動チェック、 二重承認、 アラート)をどう設計するか」に焦点を当てるのが、 成熟した監視運用の条件である。 こうしたインシデント学習を 3 ヶ月に 1 度棚卸しすると、 監視システム全体の検出力と回復力(resilience)が体系的に積み上がっていく。
ツールを導入してもモデル監視が機能しないチームには、 ほぼ例外なく 「監視の責任分界が曖昧」 という共通の根本問題がある。 「データサイエンティストは作って終わり、 SRE はインフラ視点でしか見ない、 PdM はビジネス KPI しか追わない」という分断が起こると、 ドリフトの兆候は誰のレーダーにも引っかからない。 これを解消するには、 「モデルオーナー制度」 を明示的に導入するのが最も効果的である。 各モデルには必ず一人の「モデルオーナー」(兼任可)を立て、 そのモデルに関する SLO、 アラート対応、 再学習判断、 廃止判断、 改善方針のすべてを最終決定する権限と責任を持たせる。
| フェーズ | 主活動 | 責任者 | 頻度 |
|---|---|---|---|
| 日次監視 | ダッシュボード巡回、 INFO アラート確認 | on-call エンジニア | 毎営業日 |
| 週次レビュー | SLO 達成状況、 アラート傾向、 ペンディング課題 | モデルオーナー + MLOps | 週 1 |
| 月次健康診断 | セグメント別精度、 公平性、 業務 KPI 連動 | モデルオーナー + PdM | 月 1 |
| 四半期戦略レビュー | モデル継続 / 改修 / 廃止判断、 投資配分 | モデルオーナー + 事業責任者 | 3 ヶ月 1 |
| 年次監査 | 監視体制全体の有効性評価、 外部監査対応 | MLOps Lead + コンプライアンス | 年 1 |
このサイクルが回り始めると、 監視は「アラートが鳴ったときの後追い」から「能動的にモデルの健康を維持する継続的活動」へと質的に変わる。 重要なのは、 月次健康診断や四半期戦略レビューを 「異常が起きていないときでも必ず実施する」 ことである。 何も問題がない月でも 30 分の振り返り会を開き、 「今月は順調だった、 直近の SLI トレンドはこう、 来月はこの新セグメントの動向に注意しよう」と話すだけで、 チームの監視リテラシーが自然と底上げされる。 平時のリズムが、 有事の対応力を決める。
最後に 「モデルの廃止判断」 について触れておく。 監視で延々と劣化を追いかけ、 再学習を繰り返しても精度が回復しないモデルは、 そろそろ 事業環境やタスク定義そのものが変わった 可能性が高い。 こうしたモデルは「使い続けることのコスト(運用工数、 ユーザー不信、 公平性リスク)」が「もたらす便益」を上回った瞬間に、 思い切って 停止または再設計 する判断が必要である。 「作ったモデルは死なせない」という心理的バイアスに陥らないために、 四半期戦略レビューで毎回「このモデルは来期も続けるべきか」を明示的に問う運用を組み込むことが、 長期的に組織全体の MLOps 健康度を保つ最重要メカニズムとなる。
例:年齢分布の変化を PSI で測る。 学習時 $p$ と現在 $q$ の比率:
| 年齢区分 | $p_i$(学習時) | $q_i$(現在) | 寄与 |
|---|---|---|---|
| 20–29 | 0.30 | 0.20 | (+0.10) × ln(0.30/0.20) = 0.041 |
| 30–39 | 0.40 | 0.35 | (+0.05) × ln(0.40/0.35) = 0.007 |
| 40–49 | 0.20 | 0.30 | (−0.10) × ln(0.20/0.30) = 0.041 |
| 50+ | 0.10 | 0.15 | (−0.05) × ln(0.10/0.15) = 0.020 |
| 合計 | PSI = 0.109 → 「注意」レベル | ||
PSI ≈ 0.11 なので「明確なシフト発生中」。 アラート発動、 原因調査、 再学習検討のトリガーになります。
SSDSE-B-2026 の 「総人口(A1101)」を学習データ、 「就業者数(F3101)」を本番データと見立てて、 ビンごとのずれ(PSI)を計算する。
使用データ:SSDSE-B-2026.csv(独立行政法人 統計センター提供、 47 都道府県 × 100 超の社会経済指標)。 出典
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) train = df['A1101'].values prod = df['F3101'].values # 10 分位ビンを両分布の結合データで定義(本番値がビン外に落ちないように) bins = np.percentile(np.concatenate([train, prod]), np.linspace(0, 100, 11)) bins[0] -= 1e-6; bins[-1] += 1e-6 p, _ = np.histogram(train, bins=bins); p = p / p.sum() q, _ = np.histogram(prod, bins=bins); q = q / q.sum() eps = 1e-6 psi = np.sum((p - q) * np.log((p + eps) / (q + eps))) print(f'PSI = {psi:.4f} (0.1 未満:安定 / 0.1-0.25:注意 / 0.25 以上:危険)') |
▲ 上記コードはそのまま実行可能(出力例: PSI = 24.4122 — スケールが全く違う 2 列の比較なので極端な値になる)。 CP932 エンコーディング・skiprows=[1](2 行目の日本語ヘッダ行をスキップ)・列名の英数字コード(A1101 = 総人口 など)に注意。
「総人口(A1101)」を基準分布、 「15 歳未満人口(A1301)」を監視分布と見立てて、 都道府県という同じ単位で比較します(教材用の擬似例として、 「年少人口が減ったときに分布がどう動くか」を体感する位置付け)。
🎯 このコードでやること:47 都道府県の総人口を 10 ビンに分け、 各ビンの比率と、 15 歳未満人口の同じビン構造での比率から PSI を算出する。
📥 入力データ(SSDSE-B-2026 から 3 行抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) base = df['A1101'].astype(float) # 基準: 総人口 target = df['A1301'].astype(float) # 監視: 15歳未満 # 10 ビンに分割(基準側の分位点で) edges = np.quantile(base, np.linspace(0, 1, 11)) edges[0] -= 1e-6 edges[-1] += 1e-6 p, _ = np.histogram(base, bins=edges) q, _ = np.histogram(target, bins=edges) p = p / p.sum() q = q / q.sum() # ゼロ除算回避 eps = 1e-6 p = np.where(p == 0, eps, p) q = np.where(q == 0, eps, q) psi_terms = (q - p) * np.log(q / p) psi = psi_terms.sum() print(f"PSI = {psi:.4f}") print("ビン別寄与:", np.round(psi_terms, 4)) |
📤 実行例:
💬 結果の読み方:PSI = 6.47 は閾値 0.25 を大きく超えるため「顕著なシフト」。 上位側の複数ビン(人口の多い県群)で寄与が大きく、 15 歳未満人口の分布が全人口分布に比して上位側で薄くなっている=大都市圏でも年少人口比が下がっている、 という解釈ができます。
合成データで月次精度の低下幅から再学習判定を計算する。
| 月 | 精度 | 初月差 |
|---|---|---|
| 1 | 0.92 | — |
| 2 | 0.91 | -0.01 |
| 3 | 0.89 | -0.03 |
| 4 | 0.87 | -0.05 |
| 5 | 0.82 | -0.10 ← トリガー |
1 2 3 4 5 6 | import numpy as np acc = np.array([0.92, 0.91, 0.89, 0.87, 0.82]) diff = np.round(acc[0] - acc, 2) # 浮動小数点誤差を丸めてから比較 trigger_month = np.where(diff > 0.05)[0] print(f"差: {diff}") print(f"再学習トリガー月: {trigger_month[0]+1 if len(trigger_month) else None}") |
💬 手計算 (Step 2) 5 月発動と Python 出力が完全一致。
最小コードで動かしてみる例:
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd from evidently.report import Report from evidently.metric_preset import DataDriftPreset # 学習時データ vs 本番データ ref = pd.read_csv('data/raw/training_data.csv') cur = pd.read_csv('data/raw/production_data.csv') # データドリフトレポート生成 report = Report(metrics=[DataDriftPreset()]) report.run(reference_data=ref, current_data=cur) report.save_html('drift_report.html') |
「モデル監視」を扱う代表的なライブラリ別実装。 同じ目的でも書き方が違うため、 自分のプロジェクトの依存関係に合わせて選択する:
1 2 3 4 5 6 7 8 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df.rename(columns={df.columns[2]: 'pref'}) print('行数:', len(df), '列数:', df.shape[1]) print(df[['pref', 'A1101', 'A4101', 'A5101', 'F3101']].head()) |
1 2 3 4 5 6 7 8 9 10 11 12 | from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_squared_error from sklearn.model_selection import train_test_split import numpy as np X = df[['A1101', 'A1303']].fillna(0).values y = df['A4101'].values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42) m = LinearRegression().fit(X_tr, y_tr) pred = m.predict(X_te) print(f'R² = {r2_score(y_te, pred):.3f}') print(f'RMSE = {np.sqrt(mean_squared_error(y_te, pred)):.2f}') |
1 2 3 4 5 6 7 8 9 | from scipy import stats # 例: 2 変数の Pearson 相関 + p 値 r, p = stats.pearsonr(df['A1101'], df['A4101']) print(f'相関係数 r = {r:.3f}, p 値 = {p:.2e}') # 例: 1 標本 t 検定(平均が一定値と異なるか) t, p = stats.ttest_1samp(df['A4101'], popmean=df['A4101'].mean()) print(f't = {t:.3f}, p = {p:.3f}') |
1 2 3 4 5 6 7 8 9 10 11 | import matplotlib.pyplot as plt import seaborn as sns fig, ax = plt.subplots(figsize=(8,5)) sns.scatterplot(data=df, x='A1101', y='A4101', ax=ax) ax.set_xlabel('総人口') ax.set_ylabel('出生数') ax.set_title(f'{len(df)} 都道府県の関係') plt.tight_layout() plt.savefig('out.png', dpi=120) plt.close() |
PSI はビン分割の取り方に依存します。 連続値であれば Kolmogorov–Smirnov 検定(経験 CDF の最大差)が補完的です。 帰無仮説は「2 標本は同一分布」。
🎯 このコードでやること:基準(総人口)と監視(15 歳未満人口)の経験分布を KS 検定し、 ドリフトの有意性を確認する。
📥 入力データ:47 都道府県 × 2 列(A1101, A1301)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from scipy import stats import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) base = df['A1101'].astype(float) target = df['A1301'].astype(float) # 規模が違うので対数スケールで比較(drift の "形" を見る) import numpy as np base_z = np.log1p(base) target_z = np.log1p(target) stat, pvalue = stats.ks_2samp(base_z, target_z) print(f"KS統計量 D = {stat:.4f}") print(f"p 値 = {pvalue:.4g}") |
📤 実行例:
💬 結果の読み方:$D = 0.82$ かつ $p \approx 0$ なので、 帰無仮説(同一分布)は強く棄却。 「年少人口」分布が「総人口」分布と統計的に明確に異なることが確認できます。 本番監視では「過去 30 日」と「直近 7 日」を投入し、 毎日 KS を計算してアラート閾値(例 $p<0.01$ かつ $D>0.1$)で発火する設計が標準です。
単変量 PSI/KS を全特徴量に対して計算してまとめると 多変量ドリフトレポート になります。 evidently は reference/current の 2 DataFrame を渡すだけで HTML レポートを生成します。
🎯 このコードでやること:SSDSE-B-2026 を「前半 23 県(基準)」「後半 24 県(監視)」に擬似分割し、 evidently でドリフトレポートを生成する。
📥 入力データ:47 行 × 主要 5 特徴量(総人口・15 歳未満・65 歳以上・出生数・死亡数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import pandas as pd from evidently.report import Report from evidently.metric_preset import DataDriftPreset df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) feats = ['A1101', 'A1301', 'A1303', 'A4101', 'A4200'] df = df[feats].astype(float) ref = df.iloc[:23] # 基準: 北海道〜23番目の県 current = df.iloc[23:] # 監視: 24番目以降 report = Report(metrics=[DataDriftPreset()]) report.run(reference_data=ref, current_data=current) report.save_html('drift_report.html') print(report.as_dict()['metrics'][0]['result']['number_of_drifted_columns'], "/", len(feats), "列でドリフト検出") |
📤 実行例:
💬 結果の読み方:5 特徴量中 2 列でドリフト判定。 HTML レポートには各特徴量の PSI/KS、 分布の重ね合わせグラフ、 相関行列の差分が出力されるため、 アラート発生時の 原因切り分け資料 として使えます。 evidently は airflow + S3 で日次バッチに組み込むのが定石です。
「データは安定」でも「精度が落ちる」のが concept drift。 本番予測と正解ラベルが揃った時点でメトリクスを継続的にロギングする仕組みが必要です。
🎯 このコードでやること:SSDSE-B-2026 で「過疎県(人口下位 20)」を予測する 2 値分類モデルを学習し、 MLflow で精度・AUC を時系列ロギングするテンプレートを構築する。
📥 入力データ:47 県のうち人口下位 20 を陽性、 残りを陰性として擬似ラベル化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd, mlflow from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, accuracy_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 47 県 × 1 年度 y = (df['A1101'] <= df['A1101'].quantile(0.4)).astype(int) X = df[['A1301', 'A1303', 'A4101', 'A4200']].astype(float) Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=42) mlflow.set_experiment('depopulation-monitor') with mlflow.start_run(run_name='2026-Q2-baseline'): m = LogisticRegression(max_iter=1000).fit(Xtr, ytr) pred = m.predict(Xte); prob = m.predict_proba(Xte)[:,1] mlflow.log_metric('accuracy', accuracy_score(yte, pred)) mlflow.log_metric('auc', roc_auc_score(yte, prob)) mlflow.sklearn.log_model(m, 'model') print('AUC =', round(roc_auc_score(yte, prob), 3)) |
📤 実行例:
💬 結果の読み方:この擬似タスクは「人口下位かどうか」を年少・高齢・出生・死亡の人数(いずれも人口規模にほぼ比例)から当てるため AUC = 1.0 と自明に分離できる。 ロギングのテンプレートとしての位置付けであり、 実務のタスクでは AUC は 1 を下回る。 このスクリプトを cron や Airflow で四半期ごとに走らせ、 同じ experiment 名に書き続けると、 MLflow UI で AUC の時系列が描画されます。 AUC が下落傾向= concept drift の兆候。 「分布は安定 + 精度低下」を識別する基本パターンです。
SLA/SLO の観点では、 「精度劣化が検出された時刻」と「実際の劣化開始時刻」のラグ(ground truth lag)が KPI です。 ラグが業務の判断遅延コストを超えると、 監視の意味が失われます。
| ツール | 得意領域 | 特徴 | 価格帯 |
|---|---|---|---|
| evidently | drift/data quality レポート | OSS、 Python ネイティブ、 HTML 出力が美しい | 無料 / Cloud は有料 |
| alibi-detect | 深層モデルの outlier / drift | 画像/テキストの drift にも対応 | 無料 |
| WhyLabs | whylogs プロファイル運用 | SaaS、 セキュア(生データ送信なし) | 商用 |
| Arize AI | 本番予測 + drift + explainability | LLM 監視(hallucination)も対応 | 商用 |
| SageMaker Model Monitor | AWS フルマネージド | エンドポイントと統合、 CloudWatch 連携 | 従量課金 |
| Vertex AI Model Monitoring | GCP フルマネージド | BigQuery + Looker 統合 | 従量課金 |
選定は ① 既存データ基盤との親和性、 ② プライバシー要件(SaaS vs OSS)、 ③ アラート運用チームの体制で決めるのが現実的です。
「精度は安定」「入力分布も安定」なのに「特徴量の寄与だけが変わっている」場合があります。 これは concept drift の前触れで、 SHAP 値の 平均寄与の時系列を追跡すると早期に気づけます。
🎯 このコードでやること:SSDSE-B-2026 でモデルを学習し、 ベースライン期と監視期で各特徴量の SHAP 平均寄与を比較する。
📥 入力データ:47 県 × 4 特徴量(A1301, A1303, A4101, A4200)+ 擬似目的変数(人口下位 40%)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd, shap from sklearn.ensemble import RandomForestClassifier df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True) # 47 県 × 1 年度 feats = ['A1301', 'A1303', 'A4101', 'A4200'] X = df[feats].astype(float) y = (df['A1101'] <= df['A1101'].quantile(0.4)).astype(int) model = RandomForestClassifier(n_estimators=200, random_state=0).fit(X, y) explainer = shap.TreeExplainer(model) base = X.iloc[:23] # ベースライン期 current = X.iloc[23:] # 監視期 sv_base = abs(explainer(base).values[:, :, 1]).mean(axis=0) sv_current = abs(explainer(current).values[:, :, 1]).mean(axis=0) for f, b, c in zip(feats, sv_base, sv_current): print(f'{f}: base={b:.3f} current={c:.3f} delta={c-b:+.3f}') |
📤 実行例:
💬 結果の読み方:この例では前半 23 県と後半 24 県で各特徴量の平均寄与はほぼ一致(|delta| ≦ 0.01)しており、 「説明性のドリフトなし」と判定できる基準例です。 実運用でもしここで「65 歳以上人口(A1303)」の寄与が急増し「15 歳未満人口(A1301)」の寄与が急減するような変化が観測されれば、 モデル内部で過疎判定のロジックが「年少が少ない」から「高齢者が多い」にシフトした兆候であり、 精度だけ見ていると気づかない drift として再学習タイミングを判断する材料になります。
モニタリングは「やったほうが良い」ではなく「やらないと事故時に何倍も損する」投資です。 典型的なコスト構造:
| 項目 | 小規模(モデル 1-3 本) | 中規模(モデル 10-30 本) | 大規模(モデル 100 本+) |
|---|---|---|---|
| 監視基盤構築 | OSS(evidently + airflow) 0.5 人月 | OSS + 内製ダッシュボード 2-4 人月 | 商用 SaaS or 専門チーム 0.5-1 人年 |
| 運用人件費/年 | 兼務 0.2 FTE | 専任 1-2 FTE | 専任 5-10 FTE |
| ツール費/年 | 無料 or 数十万円 | 数百万円 | 数千万円 |
| 想定インシデント回避額 | 数百万〜数千万円 | 数千万〜数億円 | 数億〜数十億円 |
「インシデント回避額」は、 過去のヒヤリハットや業界ベンチマークから推定。 たとえば与信モデルが 1 か月誤判定し続けた場合の貸倒損失、 不正検知の見逃しによる被害額などを概算します。
「モデル監視」を実務・試験で扱うときに頻発する典型的なミスです。 各項目を 1 度読んでおけば 9 割の事故が防げます:
whylogs や pandas-profiling で baseline profile を保存していないと、 半年後にモニタリングを始めようとしても比較対象がありません。 モデル登録時に baseline profile を artifact として保存するのが鉄則。モデルモニタリングの土台は 同じ特徴量を学習時と推論時で再現できる仕組みです。 feature store(Feast, Tecton, Hopsworks 等)はこれを担い、 同時に PSI/KS 計算のための baseline profile を保持する役割も果たします。
| 階層 | 役割 | monitoring との接点 |
|---|---|---|
| オフライン store | 学習用バッチ特徴量を BigQuery / S3 に保管 | baseline profile(mean, std, p50, p99)の出力元 |
| オンライン store | 推論時に Redis 等から低レイテンシで取得 | 推論ログとして current profile を生成 |
| レジストリ | 特徴量定義・スキーマ・所有者を版管理 | スキーマ変更検知(schema drift)の起点 |
逆に feature store がない環境では、 「学習時の SQL」と「推論時の前処理コード」が分かれ、 training-serving skew が発生してドリフトと誤検知することがあります。 これは monitoring の前段で潰すべき問題です。
「本番投入直後に精度が落ちた」場合、 ドリフトではなく 学習時と推論時の前処理が違うことが原因のケースが少なくありません。 典型 4 パターン:
float64、 推論時は float32 でモデル出力がわずかに乱れる対策として、 同じ前処理コードを学習・推論両方で呼び出す(sklearn Pipeline、 feature store の transform)こと、 そして本番投入直後は「学習データに対する予測」と「本番データに対する予測」の分布を比較する parity check を必ず実施します。
「AUC が 0.91 → 0.89 に下がった」を経営層に説明しても伝わりません。 モデルモニタリングの最終的な価値は ビジネス KPI への翻訳にあります。
| 技術メトリクス | 対応するビジネス KPI(例) | 翻訳式 |
|---|---|---|
| recall 低下 | 不正検知の見逃し損失 | $\Delta\text{recall} \times \text{月次取引額} \times \text{不正率}$ |
| precision 低下 | 誤通知による顧客離反 | $\Delta\text{precision} \times \text{通知数} \times \text{離反率}$ |
| CTR 予測の誤差 | 広告売上 / inventory ロス | $|\text{予測}-\text{実績}|\times \text{単価}$ |
| 需要予測 RMSE 増加 | 在庫余剰 / 機会損失 | $\Delta\text{RMSE} \times \text{保管コスト}$ |
この対応表を モデルカード(model card)に記載しておくと、 監視責任者と事業側の共通言語になります。
分類・回帰モデルと異なり、 LLM の出力は 長文・非構造化・確率的。 モニタリングの設計が大きく変わります。
商用ツール(Arize Phoenix, LangSmith, Helicone, Galileo)はこれら専用のダッシュボードを提供。 本番投入前の評価(offline eval)と本番監視(online eval)の 同じメトリクスでの整合性がポイントです。
2026 年現在、 EU AI Act では 高リスク AI システムに対して継続的なポストマーケットモニタリングを義務化。 国内でも経産省「AI 事業者ガイドライン」が同様の要請を含みます。 モニタリングは単なる工学的ベストプラクティスではなく、 規制要件になりつつあります。
| 枠組み | モニタリング要求 | 期限・対象 |
|---|---|---|
| EU AI Act | post-market monitoring system、 incident reporting | 高リスク用途は 2026 年から段階適用 |
| 経産省 AI 事業者ガイドライン | 提供後の継続的なリスク評価 | 2024 年公表、 全 AI 提供者対象 |
| NIST AI RMF | MEASURE / MANAGE 機能で監視を規定 | 米国連邦調達の事実上要件 |
| ISO/IEC 42001 | AI マネジメントシステムでの監視 | 2023 年発行、 認証取得が進行中 |
設計段階から「ログ保存期間」「インシデント定義」「報告先」を明確にしておくと、 後付け対応のコストを大きく下げられます。
本セクションは既存の解説を壊さずに追記した「読み解きの補助線」です。 モデル監視を 一枚の地図として捉え直します。
学習・評価・デプロイまでが「モデルを作る」工程だとすれば、 モデル監視は作った後の "健康診断" と "警報装置" です。 人間の健康診断が「無症状のうちに異常値を拾う」ためにあるのと同様、 監視は ユーザーが不利益を被る前に劣化を拾うためにあります。 具体的には次の 3 つを継続的に観測し、 いずれかが崩れたら 再学習・ロールバック・原因調査のトリガを引きます。
これらを束ねて「作って終わり」を防ぐ営みが MLOps の運用フェーズであり、 モデル監視はその中核です。 前段の モデルデプロイ、 検知後の 再学習、 検知手法そのものである データドリフト と地続きの概念として理解すると全体像が繋がります。 なお データ品質監視(欠損・重複・型崩れ等の入力健全性チェック)は本ページでは data-quality の独立ページが未整備のため、 ここでは「パイプライン監視の一部」として扱います。
既存の「⚠️ 落とし穴」に加え、 概念レベルで誤解しやすいポイントを補います。 特に 1 つ目は試験・実務ともに致命的です。
入門を終えた次に押さえる発展テーマを、 対応する関連ページとともに一望します。
| 発展テーマ | 要点 | 関連ページ |
|---|---|---|
| 分布ドリフト検知 | PSI / KS / Wasserstein で $P(X)$ の変化を定量化。 ラベル不要で最速 | データドリフト |
| 性能指標監視 | AUC / F1 / RMSE を時系列追跡。 ラベル必要、 CUSUM/EWMA で早期検出 | 汎化性能 |
| プロキシ指標 | ラベル到着前に、 予測確率分布・信頼度・入力分布で間接監視 | 本ページ「落とし穴」 |
| 段階的展開 | シャドー → カナリア → ランプアップで新モデルのリスクを封じ込め | A/B テスト / デプロイ |
| アラート設計 | INFO/WARNING/CRITICAL の 3 段、 抑制・エスカレーション・メタ監視 | 本ページ「アラート設計」 |
| 再学習の自動化 | 検知→再学習を自動化しつつ品質ゲート(label/schema check)で暴走を防ぐ | 再学習 |
| 公平性・可観測性 | サブグループ別の劣化監視、 ログ・トレース・メトリクスの三本柱 | 公平性 / SHAP |
合成例ではなく 実測データで「年次で分布が変わる」監視状況を再現します。 SSDSE-B-2026 は 47 都道府県 × 2012〜2023 年のパネルなので、 各年を "監視スナップショット" に見立てると、 時間とともに分布がどうドリフトするかを本物のデータで観察できます。 ここでは 老年人口比率($\text{A1303 65歳以上人口} / \text{A1101 総人口}$)の 47 県分布を対象にします。
📥 実測値(SSDSE-B-2026、 47 県の老年人口比率):
🎯 このコードでやること:2013 年を reference 分布として固定し、 各監視年の老年人口比率分布との PSI(5 ビン)を実データで算出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd, numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df.rename(columns={'SSDSE-B-2026': 'Year'}) # 老年人口比率 = 65歳以上(A1303) / 総人口(A1101) を県ごとに計算 er = lambda y: (df[df.Year == y]['A1303'].values / df[df.Year == y]['A1101'].values) def psi(base, cur, B=5): bins = np.percentile(base, np.linspace(0, 100, B + 1)) bins[0], bins[-1] = -np.inf, np.inf # 端は開区間にして本番値を取りこぼさない p, _ = np.histogram(base, bins=bins); p = p / p.sum() q, _ = np.histogram(cur, bins=bins); q = q / q.sum() eps = 1e-6 return np.sum((q - p) * np.log((q + eps) / (p + eps))) base = er(2013) # 学習時の reference 分布に見立てる for y in [2014, 2016, 2019, 2023]: print(y, round(psi(base, er(y)), 3)) print('隣接年 2022->2023', round(psi(er(2022), er(2023)), 3)) |
📤 実行すると次の実測結果が得られます:
💬 読み解きの勘所は 2 つ。 (1) 隣接年(2022→2023)の PSI は 0.017 と極小で、 単年だけ見ると「安定」に見える。 しかし基準を 2013 に固定したまま累積すると 年々 PSI が単調増加し、 いつか閾値 0.25 を越えて再学習トリガになる ── これが「じわじわ進む silent drift」の正体です。 (2) この分布シフトは 実在する少子高齢化であり、 サンプリングバイアスではありません。 つまり「本物のドリフトを検知したら、 まず原因(構造変化か一時ノイズか)を切り分ける」という実務手順の好例になっています。 なお $n=47$ と小標本なので、 単発の PSI 値より時系列トレンドで判断すべき、 という既出の注意もそのまま当てはまります。
本ページの内容は、 同じ用語集内の以下のページと往復すると理解が深まります(実在ページのみリンク):
モデル監視 (Model Monitoring) の核心はドリフト (drift) の検知である。 ドリフトには大きく分けて (a) データドリフト (Data Drift): 入力分布 $P(X)$ の変化、 (b) コンセプトドリフト (Concept Drift): 入出力関係 $P(Y|X)$ の変化、 (c) ラベルドリフト (Label Drift): ラベル分布 $P(Y)$ の変化、 の 3 種があり、 検知手法も異なる。 ここでは PSI (Population Stability Index)、 KS 検定、 Evidently の実装を SSDSE-B-2026 で検証する。
$$\mathrm{PSI} = \sum_{i=1}^{B} (p_i^{\text{cur}} - p_i^{\text{ref}}) \cdot \ln\frac{p_i^{\text{cur}}}{p_i^{\text{ref}}}$$
$B$ はビン数 (通常 10)、 $p_i^{\text{ref}}$ は参照期 (訓練時) のビン $i$ の比率、 $p_i^{\text{cur}}$ は現在期 (本番) のビン $i$ の比率。 形は KL ダイバージェンス類似 (対称化版) で、 経験則として PSI < 0.1 安定、 0.1-0.25 軽度ドリフト、 > 0.25 重度ドリフトで再学習推奨とされる。 金融与信モデルで標準的に使われる指標である。
| 指標 | 検定対象 | 閾値 | 特徴 |
|---|---|---|---|
| PSI | 分布形状 (ビン比率) | 0.1 / 0.25 | 解釈容易、 金融で標準 |
| KS 検定 | 累積分布の最大差 | p < 0.05 | 統計的有意性、 連続値 |
| Chi-square | カテゴリ頻度 | p < 0.05 | カテゴリ変数向け |
| Wasserstein | 分布間距離 (EMD) | 相対比較 | スケールに依存、 比較指標として |
| JS Divergence | 対称化 KL | 0.1-0.3 経験則 | [0, 1] 範囲で扱いやすい |
実測コードと結果は本ページ上部で提示済みです。 「🧮 実値計算」では SSDSE-B-2026(encoding='cp932'・skiprows=[1])の実在列を用い、 総人口(A1101)を基準分布、 15 歳未満人口(A1301)を監視分布と見立てて PSI ≈ 6.47 を、 「🐍 KS 検定で連続値ドリフトを検出する」では同じ 2 列で KS 統計量 D ≈ 0.82 を実際に計算しています。 補論では計算式の再掲を避け、 検知手法の使い分け(上表)と落とし穴の整理に絞ります。
なお SSDSE-B-2026 は 47 都道府県 × 単一時点の横断データであり、 「同一指標の経年変化(例: 2015 年 vs 2020 年)」でドリフトを再現するには、 SSDSE の複数年版・時系列版を別途取得する必要があります。 本教材では横断データ内の異なる列を「基準/監視」に見立てた擬似ドリフトで PSI・KS の挙動を体感する構成としています。
「モデル監視」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
監視は「精度劣化に気づく仕組み」と「劣化前に動く仕組み」の二段構えで設計する。 SSDSE-B-2026 のような年次更新データなら、 PSI (Population Stability Index) を四半期で計算し、 閾値超え時点で retraining を発火させるのが標準的。
監視設計は「何が劣化したら誰に通知するか」を運用前に決めておく。
Evidently AI / WhyLabs / Grafana などのダッシュボードで自動可視化を仕込み、 手動巡回をなくす。
運用中モデルの精度(上段の折れ線)と入力分布のずれ PSI(下段の折れ線)を、 週を追って同時に監視するシミュレータです。 スライダーでドリフト強度と異常ショックを加えると精度が徐々に劣化し、 精度がアラート閾値(赤い破線)を割った週に「⚠️ 再学習が必要」を検知します。 同時に PSI が警告ライン(0.25)を越えた週も表示され、 精度が落ちきる前にドリフトで先読みできるかを体感できます。 上段グラフをドラッグ / タッチすると閾値を動かせます。
学習が終わった瞬間がモデルの「絶頂期」で、 そこからは世界が動くほど静かに劣化します。 監視とは、 その劣化を数字が悪くなる前ではなく、悪くなり始めた瞬間に掴むためのレーダー。 精度そのものを見るのが理想ですが、 正解ラベルは遅れて届くことが多いので、 先に動く入力分布のずれ(PSI)を代理シグナルとして併走させます。 スライダーでドリフトを強めると、 多くの場合 PSI ラインが先に警告域へ入り、 数週間遅れて精度が閾値を割る——このリード時間こそ監視の価値です。
※ 本シミュレータは監視の仕組みを体感するためのモデル計算です(精度は acc = 0.5 + 0.42·e−1.2·shift、 PSI ≈ 平均シフト² の等分散ガウス近似で正確に算出)。 実データの数値ではありません。