論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
モデル監視
Model Monitoring
MLOps

🔖 キーワード索引

モデル監視MonitoringDriftPSIKLアラートダッシュボードEvidentlyArizeWhyLabs

本ページは モデル監視(Model Monitoring)を 12 のセクションで多角的に解説します。 上のチップは検索・関連語の手がかりです。 以下のリンクで各セクションに直接ジャンプできます:

💡 30秒結論📍 文脈🎨 直感📐 数式🔬 数式を言葉で読み解く🧮 実値計算🐍 Python 実装⚠️ 落とし穴🌐 関連手法🔗 関連用語📚 グループ教材

💡 30秒で分かる結論

🍰 まずはやさしく

モデルの健康診断のようなものです。

予測の精度が落ちていないか調べます。

スマホアプリの不具合を早めに見つける例です。

まずは結論から簡単に説明します。

💡 30秒で分かる結論

📍 文脈 — どこで使う概念か

🍰 まずはやさしく

AIを使い始めた後の管理のことです。

時間が経っても正しく動かすために使います。

部活の道具を定期的に手入れする感覚です。

どのような場面で使うのかを解説します。

モデル監視は MLOps の中で 「デプロイ後」の管理に相当します。 学習・評価で良い精度が出ても、 実運用では時間とともに性能が劣化(モデル腐敗 = model staleness)するのが普通。 リテール・金融・医療など、 ビジネス影響が大きい分野では 必須プロセスです。 「作って終わり」ではなく「運用しながら育てる」発想の中核技術。

🎨 直感で掴む — 具体例で理解する

🍰 まずはやさしく

変化を察知するレーダーのようなものです。

現実とAIのズレを見つけるために使います。

流行が変わっておすすめ商品が外れる例です。

具体的に何が起きるのかをイメージしましょう。

分類器を本番稼働させたあと、 放っておくと何が起きるかを思い浮かべてください:

これらは 誰も気づかないうちに 静かに進行し、 「ユーザー解約率を予測するモデルが、 半年後には勘より悪い」という事態を生みます。 監視は 「腐敗の早期検知」のレーダーです。

たとえば 2020年3月のコロナ禍では、 多くの企業の予測モデルが 一夜にして使い物にならなくなりました。 監視していた企業は数日で再学習に動けたが、 監視を怠っていた企業は 数ヶ月間誤った予測を出し続けて損失を拡大させました。

📐 定義・数式

🍰 まずはやさしく

ズレを数字で測るためのルールです。

客観的に異常を判断するために使います。

テストの点数の分布が変わったか調べる例です。

計算方法や判断基準について学びます。

監視で頻出する代表指標:

【Population Stability Index (PSI)】
$$\mathrm{PSI} = \sum_{i=1}^{k} (p_i - q_i) \ln\!\left( \frac{p_i}{q_i} \right)$$
$p_i$ = 学習時のビン $i$ の比率、 $q_i$ = 本番でのビン $i$ の比率。
PSI < 0.1: 安定、 0.1〜0.25: 注意、 > 0.25: 警告
【Kolmogorov-Smirnov 統計量】
$$D = \sup_x |F_{\mathrm{ref}}(x) - F_{\mathrm{cur}}(x)|$$
2 つの累積分布の最大差。 D が大きいほど分布が違う

📐 数式・定義

モデル監視を数式 / 形式定義で表す:

$$\text{PSI} = \sum_{k=1}^K (p_k - q_k) \log\!\frac{p_k}{q_k}$$

PSI(Population Stability Index):学習時の特徴量ヒストグラム $p_k$ と本番時 $q_k$ のビンごと乖離度を合計。 0.1 未満は正常、 0.25 以上で再学習推奨。

📐 PSI(Population Stability Index)— 数式を言葉で読み解く

PSI は本番モニタリングで最もよく使われるドリフト指標です。 基準期の分布 $p_i$ と監視期の分布 $q_i$ をビンごとに比較します。

$$\mathrm{PSI} = \sum_{i=1}^{B} (q_i - p_i) \cdot \ln \frac{q_i}{p_i}$$

数式を言葉で読み解く:各ビン $i$ について「監視期の比率 − 基準期の比率」と「対数比」を掛けて足し合わせる。 KL divergence を対称化した量に近く、 ビンごとの差が大きいほど PSI が増大します。

PSI 値解釈推奨アクション
$<$ 0.10分布は安定通常監視を継続
0.10〜0.25中程度のシフト調査タスク発行、 警報レベル
$>$ 0.25顕著なシフト再学習・原因究明・場合により切り戻し

この閾値は経験則で、 業界・モデル種別に応じて再校正が必要です。 金融与信では 0.10 / 0.25、 マーケでは 0.20 / 0.40 を使う例もあります。

🔬 記号・要素の読み解き

$p_i$(ベースライン)
学習時データの各ビンの比率(例:年齢 20代= 0.3, 30代=0.4, …)
$q_i$(現在)
本番運用中データの各ビンの比率
$\ln(p_i/q_i)$
2 分布の比の対数。 同じなら 0、 違うほど絶対値が大きい
$(p_i - q_i)$ で重み付け
大きく変動したビンほど強く寄与する設計
合計
すべてのビンを足して 1 つのスコアに(対称 KL 発散)
$D$(KS 統計量)
累積分布関数の最大差。 連続値の分布シフト検知に強い

🔬 数式を言葉で読み解く

上の数式に出てきた記号を 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 ExplanationFairness 指標(demographic parity, equalized odds 等)を組合せると、 業務応用での説得力が一段増します。

④ 業界応用

医療(薬機法・GxP)・金融(モデル管理ガイドライン)・公共(個人情報保護法)など、 業界固有の規制・ガイドラインを モデル設計段階から埋め込むのが現代のスタンダード。 「モデル監視」を業務適用するときは、 ドメインの専門家・法務との早期コラボレーションが成否を分けます。

🔬 ドリフトの 3 階層分類と検出指標

「モデルの精度が落ちる」と一口に言っても、 原因は 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 の常時監視がモニタリングの出発点になります。

1. モデル監視の二つの軸 — データドリフトとコンセプトドリフト

機械学習モデルが本番環境にデプロイされた後、 時間とともに性能が劣化していくのはほぼ避けられない現象である。 この劣化を早期に検知し、 必要に応じて再学習やロールバックを判断するための活動が「モデル監視(model monitoring)」である。 監視の対象は大きく二つに分かれる: データドリフト(data drift)コンセプトドリフト(concept drift)である。 データドリフトは、 入力特徴量 X の分布 P(X) が学習時と推論時で変化する現象を指す。 例えば SSDSE-B-2026 で学習した「都道府県人口を予測するモデル」を運用していると、 数年後には少子化の進行により全国の人口分布そのものが大きく変動し、 学習時に「平均値・分散」として扱っていた値が陳腐化してしまう。 一方コンセプトドリフトは、 入力 X と出力 Y の関係性 P(Y|X) そのものが変化する現象を指す。 例えば「平均寿命が高い県は出生率も高い」という関係性が、 社会構造の変化によって崩れていく場合がこれにあたる。 モデル監視ではこの両方を区別して捉えることが重要である。 データドリフトのみであれば再学習で対応できることが多いが、 コンセプトドリフトの場合は特徴量の再設計、 ラベルの再定義、 さらにはタスク自体の見直しが必要になることもある。

2. PSI(Population Stability Index)の数式と直感

データドリフトを定量化する代表的な指標が 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)のレンジに入ることがある。 こうしたとき、 機械的に再学習を走らせるのではなく、 まず「分布シフトが本物か、 サンプリングバイアスか」を確認するのが実務的な手順である。

ヒストグラム
図 1: 学習時(青)vs 推論時(橙)の分布シフト検出。 ビンごとの頻度差が PSI 値の主な構成要素となる。

3. Kolmogorov–Smirnov(KS)統計量による連続値ドリフト検定

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 ベースライン」のように適切なウィンドウサイズを選ぶ必要がある。

4. SHAP 値の監視 — モデルの「考え方」が変わっていないかをチェックする

データドリフトとコンセプトドリフトに加えて、 監視の第三軸として「特徴量重要度のドリフト」がある。 同じモデルを使い続けていても、 入力分布の変化により「これまで主役だった特徴量」と「今、 効いている特徴量」が入れ替わることがある。 これを把握するには、 SHAP 値の絶対値の平均(mean |SHAP|)を時系列で追跡するのが効果的である。 例えば住宅価格予測モデルで「築年数」「面積」「最寄り駅距離」の三特徴を使っているとき、 通常は「面積」の mean |SHAP| が最大であったところ、 ある月から急に「最寄り駅距離」が首位になった、 という変化が起きたら、 これは「都市部から郊外へ顧客層がシフトしている」「新しい鉄道路線が開業した影響」など、 ビジネス上の構造変化を示唆する。 mean |SHAP| のドリフトは PSI と同様に「分布シフト」として扱うことができ、 同じ閾値(0.1 / 0.25)を準用する組織が多い。 ただし SHAP 値は計算コストが高いため、 リアルタイム監視には不向きで、 日次・週次のバッチ集計で監視するのが現実的である。

箱ひげ図
図 2: 期間別の予測誤差分布(箱ひげ図)。 中央値・四分位範囲・外れ値の経時変化からモデルの劣化傾向を読み取る。

5. 予測 vs 実測の散布図モニタリング

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

散布図
図 3: 予測 vs 実測の散布図の経時変化。 対角線からの乖離パターンでバイアス・分散の変化を診断する。

6. アラート設計の落とし穴と組織運用の勘所

監視指標を計算するだけでは意味がなく、 「閾値を超えたら誰がいつ何をするか」というアラート運用設計が肝心である。 ありがちな失敗は、 (1) 閾値が厳しすぎて連日アラートが飛び、 担当者が「狼少年化」して無視するようになる、 (2) 閾値が緩すぎて重大な劣化を見逃す、 (3) アラートは飛ぶが対応プロセスが未定で「誰も動かない」、 の三つである。 これを防ぐには、 まず「監視 → アラート → 一次調査 → エスカレーション → 再学習判断 → 本番反映」という一連のフローを RACI(Responsible / Accountable / Consulted / Informed)でドキュメント化することが重要である。 また、 アラート閾値自体を 1〜3 ヶ月単位でレビューし、 過去のアラート履歴・対応履歴と照らし合わせて適切なレベルに調整する「メタ監視」も推奨される。 SSDSE-B-2026 のような小規模データで作った教育用モデルでは現実感が薄いかもしれないが、 実務では「PSI > 0.25 が 3 日連続したらメール、 7 日連続したら slack 通知、 14 日連続したらインシデント起票」のように、 段階的にエスカレーションする仕組みを作っておくと運用が安定する。

7. 再学習の判断基準と「触らない勇気」

アラートが鳴ったからといって即座に再学習を走らせるのが正解とは限らない。 再学習にはコスト(計算資源、 検証作業、 本番反映の調整、 ロールバック準備)がかかり、 さらに「新しいモデルが古いモデルよりも本当に良い」と保証するための比較検証も必要である。 PSI が 0.25 を超えたとしても、 本番のビジネス KPI(売上、 解約率、 不良率など)に有意な悪化が見られなければ「監視は続けるが触らない」という判断もありうる。 逆に PSI は静かでも、 業務メトリクスが悪化していれば、 それはコンセプトドリフトのサインであり、 特徴量設計の見直しまで踏み込むべき場合もある。 「データの動きとビジネスの動き」を別々の軸で観察し、 両者の整合性を見るのが熟達した監視運用である。 また、 再学習を決断したときも、 シャドーデプロイ(旧モデルの隣で新モデルを走らせ、 結果は本番には使わない)、 A/B テスト、 カナリアリリース(少数のリクエストだけ新モデルに振る)といった段階的展開を組み合わせ、 リスクを抑える設計が望ましい。

✅ 理解度チェック

  1. 問 1: PSI = 0.18 と算出された。 ドリフトのレベル判定と、 取るべき次のアクションを 1 つ挙げよ。
    解答0.1 ≦ PSI < 0.25 のレンジに入るため「中程度の注意」レベル。 即座に再学習するのではなく、 まずは分布シフトが本物(顧客層の変化など)か、 サンプリングバイアス(特定セグメントの抽出比率の問題)かを切り分ける一次調査を行う。 業務 KPI の同時推移も確認する。
  2. 問 2: データドリフトとコンセプトドリフトの違いを、 それぞれ確率分布の記号で表せ。
    解答データドリフトは入力分布 P(X) の変化、 コンセプトドリフトは条件付き分布 P(Y|X) の変化である。 前者は再学習で対応できることが多いが、 後者は特徴量設計・タスク定義の再検討が必要になりうる。
  3. 問 3: SHAP 値の monthly mean |SHAP| を監視していたところ、 ある特徴量の重要度が急上昇した。 これは何のサインで、 どう確認すべきか。
    解答顧客層やデータ生成プロセスの構造変化のサインの可能性が高い(新規セグメントの流入、 外部環境の変化など)。 PSI や KS 検定で当該特徴量の分布シフトを確認し、 同時にビジネス KPI の動向と突き合わせて、 「データの変化」「モデルの反応」「業績インパクト」の三者を整合的に説明できるかを検証する。

📚 関連用語: データドリフト / コンセプトドリフト / 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 つの手法を、 数式と実装の両面から紹介する。

1. CUSUM(累積和管理図)— 平均シフトの早期検出

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 が日々わずかずつ下がる「じわじわ劣化」の検出に最適。

2. EWMA(指数加重移動平均)— 直近重視の滑らかな監視

$$z_t = \lambda x_t + (1-\lambda) z_{t-1}, \quad 0 < \lambda \le 1$$

EWMA は直近の観測値に重みを置き、 古い観測値の影響を指数的に減衰させる。 $\lambda = 0.2$ あたりがバランスが良い実務値。 管理限界は $\mu_0 \pm L \sigma \sqrt{\frac{\lambda}{2-\lambda}}$ で、 $L=3$ がよく使われる。 ノイズの多い PSI 系列を滑らかに監視し、 一方で本物のシフトには素早く反応する性質がある。 単純な移動平均よりも応答性が高い。

3. Page-Hinkley テスト — オンライン変化点検出

ストリーミングデータでメモリを使わずに変化点を検出したい場合は 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) 大(バッチ) 事後分析・原因特定

🧮 Python による CUSUM 実装例

このコードでやること: 仮想的な AUC 時系列に CUSUM を適用し、 平均シフトを早期検出する。 ここでは説明用に合成データではなく、 SSDSE-B 都道府県人口(実データ)の経年差分を「メトリクス時系列」として使い、 累積シフトを観察する。

📥 入力データ(SSDSE-B-2026 都道府県人口の経年変化を AUC 時系列に見立てた抜粋):

t metric (AUC 相当) 0 0.872 1 0.870 2 0.871 3 0.866 4 0.862 5 0.858 6 0.855 ...(じわじわと下降している例)
 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.")

📤 実行すると次の出力が得られる:

[ALERT] t=11: S+=0.0000, S-=-0.0303, xt=0.8599

💬 単純閾値(例えば AUC < 0.85)だけでは t=20 付近で初めて検出されるが、 CUSUM は t=11 で下方シフトを検出できる。 9 ステップ早く気づける ことで、 ユーザー影響を最小化できる。 これが「累積」のパワーである。

📋 監視ダッシュボード設計と SLO の決め方

監視ダッシュボードは「誰が、 何を、 いつ見るか」で 3 階層に分けて設計するのが定石である。 全員に全情報を見せると認知負荷で重要シグナルを見落とすため、 役割ごとに見る粒度を変える。

階層 対象者 表示メトリクス 更新頻度 主要 KGI
エグゼクティブ 事業部長 / CXO ビジネス KPI(売上、 CVR、 NPS) 日次 モデル稼働率 ≧ 99.5%
プロダクト PdM / ML プロダクトオーナー AUC, F1, セグメント別性能, 公平性 時間〜日次 予測精度 SLO 遵守
エンジニア MLOps / SRE / データサイエンティスト PSI, レイテンシ, エラー率, 特徴量分布 分〜時間 MTTR < 30 分

SLI / SLO / SLA の定義と実例

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 つ挙げる。

  1. 「正常時の見た目」を共有する: 異常検知の前に、 まず「正常時にどう見えるか」のスナップショットをチーム全員で見て頭に焼き付ける。 これがないと異常が起きてもパターンの違いに気づけない。
  2. 移動平均と生値を重ねる: 7 日移動平均を太線、 日次生値を細線で重ねる。 ノイズの中の傾向と、 個別日の特異点の両方が見える。
  3. 注釈マーカーを入れる: モデル再学習、 メジャーリリース、 マーケティングキャンペーン等のイベントを縦線で表示。 KPI 変化と原因を即座に紐付けられる。
  4. セグメント別の小型グラフを並べる: 「全体 AUC」だけでなく「年代別 AUC」「地域別 AUC」を 4×4 のスモールマルチプルで表示。 平均では隠れる部分的悪化を発見できる。
  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 マイノリティ層のデータ減少 / ラベル偏り セグメント別の混同行列を比較 バイアス緩和 / セグメント別評価導入

事例 1: 「金曜の夜だけ AUC が下がる」

ある EC サイトのレコメンドモデルで、 金曜 18 時〜23 時の AUC が他時間帯より 8 pp 低い という現象が観測された。 一見すると週末前の購買行動の変化(コンセプトドリフト)に見えるが、 詳細調査の結果、 原因は 「金曜夜だけログ収集パイプラインが他社のキャンペーン配信と競合して詰まり、 直近 1 時間の閲覧履歴特徴量が古いまま使われていた」 というデータパイプライン障害だった。 PSI は全体では安定していたが、 「特徴量の鮮度」を別 SLI として監視していなかったため、 検出が遅れたのである。 恒久対策として「特徴量のタイムスタンプと推論時刻の差」を新しい SLI に加え、 30 分以上の遅延でアラートを出す仕組みを導入した。

事例 2: 「再学習したら精度が下がった」

PSI 0.18 のアラートを受けて自動再学習を実行したところ、 再学習後の AUC が 逆に 2 pp 下がる という現象が起きた。 原因は、 「直近の学習データに、 短期キャンペーンによる異常購買パターンが大量に混入していた」こと。 つまり一時的なノイズを「真のシグナル」として学習してしまい、 平常時の予測が悪化した。 教訓として、 (1) 再学習データには「キャンペーン期間中フラグ」を必ず付けて期間を選別できるようにする、 (2) 再学習後は必ずホールドアウト評価を行い、 旧モデルより悪化していたら自動的に旧モデルへ戻す、 という 2 段階の防御を導入した。

事例 3: 「予測値が突然 0 ばかりになる」

バイナリ分類モデルで、 ある日突然「予測確率がほぼ 0」のレコードが急増した。 PSI も AUC も急変はなかったが、 予測値の分布の歪度 を監視していたメトリクスが反応した。 原因は、 上流の特徴量パイプラインで「カテゴリカル変数のエンコーディング辞書」が壊れ、 未知カテゴリとして扱われるレコードが急増したこと。 ここから得られる教訓は、 「入力特徴量の分布」と「予測値の分布」の両方を別々に監視せよ、 ということ。 どちらか片方では今回のような「特徴量はそれっぽく見えるが意味が壊れている」ケースを見逃す。

RCA テンプレート(インシデント振り返り用)

項目 記述内容
検出時刻 / 解消時刻 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–290.300.20(+0.10) × ln(0.30/0.20) = 0.041
30–390.400.35(+0.05) × ln(0.40/0.35) = 0.007
40–490.200.30(−0.10) × ln(0.20/0.30) = 0.041
50+0.100.15(−0.05) × ln(0.10/0.15) = 0.020
合計PSI = 0.109 → 「注意」レベル

PSI ≈ 0.11 なので「明確なシフト発生中」。 アラート発動、 原因調査、 再学習検討のトリガーになります。

🧮 SSDSE-B 実値計算 — 都道府県データで手を動かす

SSDSE-B-2026 の 「総人口(A1101)」を学習データ、 「就業者数(F3101)」を本番データと見立てて、 ビンごとのずれ(PSI)を計算する。

使用データ:SSDSE-B-2026.csv(独立行政法人 統計センター提供、 47 都道府県 × 100 超の社会経済指標)。 出典

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) F3101(新規求職申込件数(一般)) 北海道 5,092,000 156,458 東京都 14,086,000 270,954 沖縄県 1,468,000 43,877 …(全 47 行)
 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 = 総人口 など)に注意。

🧮 SSDSE-B-2026 で PSI を実計算する

「総人口(A1101)」を基準分布、 「15 歳未満人口(A1301)」を監視分布と見立てて、 都道府県という同じ単位で比較します(教材用の擬似例として、 「年少人口が減ったときに分布がどう動くか」を体感する位置付け)。

🎯 このコードでやること:47 都道府県の総人口を 10 ビンに分け、 各ビンの比率と、 15 歳未満人口の同じビン構造での比率から PSI を算出する。

📥 入力データ(SSDSE-B-2026 から 3 行抜粋)

都道府県 A1101(総人口) A1301(15歳未満) 北海道 5,092,000 514,000 東京都 14,086,000 1,513,000 鳥取県 537,000 65,000
 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.4692 ビン別寄与: [0.4642 0.0378 0.1464 0.0624 0.0018 1.1424 1.1646 1.1424 1.1424 1.1646]

💬 結果の読み方:PSI = 6.47 は閾値 0.25 を大きく超えるため「顕著なシフト」。 上位側の複数ビン(人口の多い県群)で寄与が大きく、 15 歳未満人口の分布が全人口分布に比して上位側で薄くなっている=大都市圏でも年少人口比が下がっている、 という解釈ができます。

🧮 数式に値を入れて手で計算する: モデル精度低下の検知

合成データで月次精度の低下幅から再学習判定を計算する。

Step 1: 月次精度

精度初月差
10.92
20.91-0.01
30.89-0.03
40.87-0.05
50.82-0.10 ← トリガー

Step 2: 再学習トリガー (閾値 0.05)

5 月 0.82 → 初月との差 -0.10 > 0.05 → 再学習発動 ドリフト速度 = -0.10/4 = -0.025 /月

🐍 Python で再現

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}")

📤 実行結果

差: [0. 0.01 0.03 0.05 0.1 ] 再学習トリガー月: 5

💬 手計算 (Step 2) 5 月発動と Python 出力が完全一致。

🐍 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')

🐍 Python 実装バリエーション

「モデル監視」を扱う代表的なライブラリ別実装。 同じ目的でも書き方が違うため、 自分のプロジェクトの依存関係に合わせて選択する:

① pandas + numpy(最小依存)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) A5101(転入者数(日本人移動者)) F3101(新規求職申込件数(一般)) 北海道 5,092,000 24,430 47,388 156,458 東京都 14,086,000 86,348 406,749 270,954 沖縄県 1,468,000 12,549 26,410 43,877 …(全 47 行)
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())

② scikit-learn(学習・評価)

 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}')

③ scipy.stats(統計検定・分布)

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}')

④ 可視化(matplotlib + seaborn)

 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()

🐍 KS 検定で連続値ドリフトを検出する

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}")

📤 実行例

KS統計量 D = 0.8227 p 値 = 4.57e-193

💬 結果の読み方:$D = 0.82$ かつ $p \approx 0$ なので、 帰無仮説(同一分布)は強く棄却。 「年少人口」分布が「総人口」分布と統計的に明確に異なることが確認できます。 本番監視では「過去 30 日」と「直近 7 日」を投入し、 毎日 KS を計算してアラート閾値(例 $p<0.01$ かつ $D>0.1$)で発火する設計が標準です。

🐍 evidently による多変量モニタリングレポート

単変量 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), "列でドリフト検出")

📤 実行例

2 / 5 列でドリフト検出 (drift_report.html を生成)

💬 結果の読み方:5 特徴量中 2 列でドリフト判定。 HTML レポートには各特徴量の PSI/KS、 分布の重ね合わせグラフ、 相関行列の差分が出力されるため、 アラート発生時の 原因切り分け資料 として使えます。 evidently は airflow + S3 で日次バッチに組み込むのが定石です。

🐍 MLflow で本番モデルの精度を時系列追跡する

「データは安定」でも「精度が落ちる」のが 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 (mlruns/ に accuracy, auc, model artifact が記録される)

💬 結果の読み方:この擬似タスクは「人口下位かどうか」を年少・高齢・出生・死亡の人数(いずれも人口規模にほぼ比例)から当てるため AUC = 1.0 と自明に分離できる。 ロギングのテンプレートとしての位置付けであり、 実務のタスクでは AUC は 1 を下回る。 このスクリプトを cron や Airflow で四半期ごとに走らせ、 同じ experiment 名に書き続けると、 MLflow UI で AUC の時系列が描画されます。 AUC が下落傾向= concept drift の兆候。 「分布は安定 + 精度低下」を識別する基本パターンです。

🚨 監視アラート発火後のインシデント対応 5 ステップ

  1. 影響範囲の確認:どの予測 API・どのセグメントに影響しているか。 推論ログから user_id / region 別の予測分布を即可視化。
  2. シャドーモデルへの切り替え:直近の安定 model_version に即時切り戻し、 新モデルはシャドー実行(推論結果は記録のみ)に降格。
  3. 原因切り分け:① 上流データソース変更(ETL の列定義変更等)、 ② 季節要因、 ③ 外部環境変化(規制/世論/競合)の順に確認。
  4. 再学習トリガー:最新ラベルが揃い次第、 期間スライド窓で再学習。 評価セットには「ドリフト後の最新データ」を含めて bias を防ぐ。
  5. ポストモーテム:再発防止策・新規モニタリング指標・SLO 見直しまで含めた振り返り。 設計の「死角」を可視化するのが目的。

SLA/SLO の観点では、 「精度劣化が検出された時刻」と「実際の劣化開始時刻」のラグ(ground truth lag)が KPI です。 ラグが業務の判断遅延コストを超えると、 監視の意味が失われます。

🛠 ツール比較(2025 年時点)

ツール得意領域特徴価格帯
evidentlydrift/data quality レポートOSS、 Python ネイティブ、 HTML 出力が美しい無料 / Cloud は有料
alibi-detect深層モデルの outlier / drift画像/テキストの drift にも対応無料
WhyLabswhylogs プロファイル運用SaaS、 セキュア(生データ送信なし)商用
Arize AI本番予測 + drift + explainabilityLLM 監視(hallucination)も対応商用
SageMaker Model MonitorAWS フルマネージドエンドポイントと統合、 CloudWatch 連携従量課金
Vertex AI Model MonitoringGCP フルマネージドBigQuery + Looker 統合従量課金

選定は ① 既存データ基盤との親和性、 ② プライバシー要件(SaaS vs OSS)、 ③ アラート運用チームの体制で決めるのが現実的です。

🐍 SHAP 寄与の時系列ドリフトで「説明の崩れ」を捉える

「精度は安定」「入力分布も安定」なのに「特徴量の寄与だけが変わっている」場合があります。 これは 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}')

📤 実行例

A1301: base=0.180 current=0.178 delta=-0.001 A1303: base=0.080 current=0.081 delta=+0.001 A4101: base=0.159 current=0.152 delta=-0.007 A4200: base=0.046 current=0.047 delta=+0.002

💬 結果の読み方:この例では前半 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 か月誤判定し続けた場合の貸倒損失、 不正検知の見逃しによる被害額などを概算します。

✅ モデルモニタリング導入チェックリスト

📌 本ページの要点 7 行サマリ

  1. ドリフトは Data / Concept / Label の 3 階層 に分けて検出する
  2. PSI(< 0.10 安定 / 0.10〜0.25 中 / > 0.25 顕著)と KS 検定が基本
  3. ground truth lag があるためラベル前でも出力分布を監視する
  4. evidently / alibi-detect / WhyLabs / Arize / クラウドフルマネージドから選ぶ
  5. training-serving skew はモデル更新直後の「ドリフト誤検知」の主犯
  6. SHAP の時系列で 説明性ドリフトまで監視するのが上級者の標準
  7. EU AI Act / 国内ガイドライン により監視は規制要件にも近づいている

⚠️ よくある落とし穴

❌ ラベル取得の遅延
実ラベル(解約したか否か等)は数ヶ月後にしか分からないことが多い。 ラベルなしでドリフトを検知する仕組み(入力分布監視)が必須。
❌ アラート疲れ
閾値を厳しくしすぎて毎日アラートが出ると、 開発者が無視するようになる。 重要度を分けて運用する。
❌ 再学習のループ
「ドリフト検知 → 再学習」を機械的に回すと、 異常値や攻撃データを学習してしまう。 必ず人間の確認を挟む。
❌ 監視の偏り
全体精度だけ見て、 サブグループ(性別・地域別)での劣化を見逃す。 公平性指標も監視対象に。
❌ コストの過小評価
毎日 TB 級のログを保存・分析するには 相応のインフラ費がかかる。 サンプリングと階層化で抑える。

⚠️ よくある落とし穴(5 件)

「モデル監視」を実務・試験で扱うときに頻発する典型的なミスです。 各項目を 1 度読んでおけば 9 割の事故が防げます:

❌ ラベル遅延の無視
本番ラベルが何日も遅れて取得される場合、 リアルタイム精度監視は不可能。 代替プロキシ指標を立てる。
❌ 単一指標で判断
Accuracy だけ見ると、 公平性・レイテンシ・コストの劣化を見逃す。 複数指標を並列監視。
❌ 閾値が固定
曜日効果・季節性で正常範囲が変動。 動的閾値(移動平均±2σ)を採用する。
❌ アラート過多
アラートが頻発すると無視されるようになる。 重要度別に通知を絞る運用設計。
❌ 再現性のないログ
監視ログの形式が頻繁に変わると過去比較不可。 スキーマバージョン管理を仕込む。

⚠️ さらに踏み込んだ落とし穴 5 件

❌ シャドーモード期間が短すぎる
新モデルを 1 日だけシャドー実行して切替えると、 週次/月次の周期性によるドリフトを見逃します。 最低 1 周期(多くの場合 4〜6 週)はシャドー比較を継続するのが安全。
❌ アラートが多すぎて誰も見ない(alert fatigue)
「特徴量 200 個 × 毎日 PSI」を全部 Slack に流すと、 オオカミ少年化して本物の異常を見落とします。 SLO ベースで「ビジネス影響が出る精度低下」だけ発火する設計が肝。
❌ retraining の自動化が暴走する
「PSI > 0.25 で自動再学習」を組むと、 上流データバグでドリフトしている状況で誤ったラベルを学習し、 モデルが悪化します。 自動 retrain には品質ゲート(label sanity check, schema check)が必須。
❌ ground truth lag の存在を無視
「与信判定」「医療診断」「離反予測」のように正解ラベルが 30〜180 日遅れる用途では、 ラベル到着前の proxy 指標(出力分布、 信頼度)でも監視する必要があります。
❌ 学習データの統計を保存し忘れる
PSI は「reference 分布」が必要です。 学習時に whylogspandas-profiling で baseline profile を保存していないと、 半年後にモニタリングを始めようとしても比較対象がありません。 モデル登録時に baseline profile を artifact として保存するのが鉄則。

❓ よくある質問

Q. PSI と KL divergence は何が違う?
PSI は「対称化された KL」で、 基準と監視が入れ替わっても同じ値になります($(q-p)\ln(q/p) = (p-q)\ln(p/q)$)。 KL divergence は非対称で、 reference を入れ替えると値が変わります。
Q. ドリフト検出は train-test split のときの検証と何が違う?
学習時の split は「同じ分布から取った 2 標本」を比較するため、 通常はドリフトが検出されません。 本番モニタリングは「時間的に離れた 2 標本」を比較し、 統計的に差が出ます。
Q. データ量が少ないと PSI/KS は不安定では?
そのとおり。 SSDSE のように $n=47$ では信頼区間が広く、 単発の値より 時系列トレンド を見る方が実務的。 サンプル数が少ないときは bootstrapping で信頼区間を付ける運用もよく使われます。
Q. LLM の出力監視はどうする?
embedding 空間で PSI/KS を取る、 出力長/perplexity を時系列追跡、 LLM-as-a-judge による品質スコア、 などが組み合わされます。 hallucination や jailbreak の検出は別レイヤで実装。

🏛 feature store とモニタリングの関係

モデルモニタリングの土台は 同じ特徴量を学習時と推論時で再現できる仕組みです。 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 の前段で潰すべき問題です。

🔄 training-serving skew の検出パターン

「本番投入直後に精度が落ちた」場合、 ドリフトではなく 学習時と推論時の前処理が違うことが原因のケースが少なくありません。 典型 4 パターン:

  1. 型変換の差:学習時は float64、 推論時は float32 でモデル出力がわずかに乱れる
  2. 欠損補完の差:学習時は中央値、 推論時はゼロ埋め
  3. カテゴリエンコーディングの差:学習時には存在したカテゴリが推論時には未知(unseen category)
  4. 時刻処理の差:UTC と JST の取り違え、 タイムゾーン違いで「曜日」が 1 日ずれる

対策として、 同じ前処理コードを学習・推論両方で呼び出す(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 モニタリングの追加項目

分類・回帰モデルと異なり、 LLM の出力は 長文・非構造化・確率的。 モニタリングの設計が大きく変わります。

商用ツール(Arize Phoenix, LangSmith, Helicone, Galileo)はこれら専用のダッシュボードを提供。 本番投入前の評価(offline eval)と本番監視(online eval)の 同じメトリクスでの整合性がポイントです。

📜 EU AI Act / 国内 AI ガイドラインとの整合

2026 年現在、 EU AI Act では 高リスク AI システムに対して継続的なポストマーケットモニタリングを義務化。 国内でも経産省「AI 事業者ガイドライン」が同様の要請を含みます。 モニタリングは単なる工学的ベストプラクティスではなく、 規制要件になりつつあります。

枠組みモニタリング要求期限・対象
EU AI Actpost-market monitoring system、 incident reporting高リスク用途は 2026 年から段階適用
経産省 AI 事業者ガイドライン提供後の継続的なリスク評価2024 年公表、 全 AI 提供者対象
NIST AI RMFMEASURE / MANAGE 機能で監視を規定米国連邦調達の事実上要件
ISO/IEC 42001AI マネジメントシステムでの監視2023 年発行、 認証取得が進行中

設計段階から「ログ保存期間」「インシデント定義」「報告先」を明確にしておくと、 後付け対応のコストを大きく下げられます。

🧭 深掘り補足 — 直感・落とし穴・発展を一気通貫で

本セクションは既存の解説を壊さずに追記した「読み解きの補助線」です。 モデル監視を 一枚の地図として捉え直します。

🎨 直感 — 監視は「デプロイ後の健康診断」

学習・評価・デプロイまでが「モデルを作る」工程だとすれば、 モデル監視は作った後の "健康診断" と "警報装置" です。 人間の健康診断が「無症状のうちに異常値を拾う」ためにあるのと同様、 監視は ユーザーが不利益を被る前に劣化を拾うためにあります。 具体的には次の 3 つを継続的に観測し、 いずれかが崩れたら 再学習・ロールバック・原因調査のトリガを引きます。

これらを束ねて「作って終わり」を防ぐ営みが MLOps の運用フェーズであり、 モデル監視はその中核です。 前段の モデルデプロイ、 検知後の 再学習、 検知手法そのものである データドリフト と地続きの概念として理解すると全体像が繋がります。 なお データ品質監視(欠損・重複・型崩れ等の入力健全性チェック)は本ページでは data-quality の独立ページが未整備のため、 ここでは「パイプライン監視の一部」として扱います。

⚠️ 落とし穴(重要)— 監視で最も事故る 5 つの誤解

既存の「⚠️ 落とし穴」に加え、 概念レベルで誤解しやすいポイントを補います。 特に 1 つ目は試験・実務ともに致命的です。

❌【最重要】ドリフト検知 ≠ 性能劣化
入力分布が動いても(データドリフト)、 モデルの答えが正しいまま = 性能は無劣化、 ということは頻繁にあります。 逆に $P(X)$ が安定でも $P(Y\mid X)$ が変わるコンセプトドリフトでは、 ドリフト指標は静かなまま精度だけ落ちます。 「PSI が上がった → すぐ再学習」は誤り。 分布の動きと性能の動きは別の軸として観測し、 両者を突き合わせて初めて判断できます。
❌ ラベル遅延で「精度監視だけ」に頼る
解約・与信・診断の正解は数週〜数か月遅れます(ground truth lag)。 精度監視は本質的に後追いになるため、 ラベル到着前でも動く プロキシ指標(入力分布 PSI/KS、 予測確率の分布、 信頼度スコア)を主軸に据えるのが定石です。
❌ パイプライン監視の欠如
モデルは正常でも、 上流 ETL の失敗・スキーマ変更・特徴量の鮮度遅延で予測が壊れます。 「特徴量の最終更新時刻」「NaN 率」「行数」を モデル指標とは別系統で監視しないと、 分布は正常に見えるのに中身が壊れているケースを見逃します。
❌ フィードバックループの見落とし
モデルの予測が現実そのものを変える(レコメンドが購買を誘導、 与信否決が信用履歴を作る)と、 次の学習データが自分の予測に汚染されます。 「監視で見ている分布」がモデル自身の作用の結果である可能性を常に疑い、 介入前後のログを分けて記録します。
❌ 閾値・指標選択の思考停止と「監視自体のコスト」
PSI 0.1/0.25 は経験則であり、 業界・モデルで再校正が要ります。 偽アラートを恐れて全特徴量を毎日監視すると、 ストレージ・計算・人的レビューのコストが膨らみます。 監視は無料ではないため、 重要特徴量に絞る・サンプリングする・アラートの precision 自体を月次で見直す(メタ監視)といった「監視の設計」が必要です。

🚀 発展 — 監視技術の見取り図

入門を終えた次に押さえる発展テーマを、 対応する関連ページとともに一望します。

発展テーマ要点関連ページ
分布ドリフト検知PSI / KS / Wasserstein で $P(X)$ の変化を定量化。 ラベル不要で最速データドリフト
性能指標監視AUC / F1 / RMSE を時系列追跡。 ラベル必要、 CUSUM/EWMA で早期検出汎化性能
プロキシ指標ラベル到着前に、 予測確率分布・信頼度・入力分布で間接監視本ページ「落とし穴」
段階的展開シャドー → カナリア → ランプアップで新モデルのリスクを封じ込めA/B テスト / デプロイ
アラート設計INFO/WARNING/CRITICAL の 3 段、 抑制・エスカレーション・メタ監視本ページ「アラート設計」
再学習の自動化検知→再学習を自動化しつつ品質ゲート(label/schema check)で暴走を防ぐ再学習
公平性・可観測性サブグループ別の劣化監視、 ログ・トレース・メトリクスの三本柱公平性 / SHAP

🧮 実データで体感する — SSDSE-B-2026 の「加齢ドリフト」を PSI で追う

合成例ではなく 実測データで「年次で分布が変わる」監視状況を再現します。 SSDSE-B-2026 は 47 都道府県 × 2012〜2023 年のパネルなので、 各年を "監視スナップショット" に見立てると、 時間とともに分布がどうドリフトするかを本物のデータで観察できます。 ここでは 老年人口比率($\text{A1303 65歳以上人口} / \text{A1101 総人口}$)の 47 県分布を対象にします。

📥 実測値(SSDSE-B-2026、 47 県の老年人口比率)

基準年 2013: 平均 0.2655 最小 0.1832 最大 0.3152 監視年 2023: 平均 0.3159 最小 0.2275 最大 0.3906 (10 年で全県が右シフト = 少子高齢化による実データのドリフト)

🎯 このコードでやること: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))

📤 実行すると次の実測結果が得られます:

2014 0.220 ← 中程度の注意(1 年でも基準固定なら閾値超え) 2016 0.707 ← 顕著なシフト 2019 1.547 2023 1.658 ← 10 年で強いドリフト(要再学習) 隣接年 2022->2023 0.017 ← 単年比較ではほぼ安定(silent drift) (参考: KS 検定 D=0.702, p=2.1e-11 で 2013 vs 2023 の分布差は明確に有意)

💬 読み解きの勘所は 2 つ。 (1) 隣接年(2022→2023)の PSI は 0.017 と極小で、 単年だけ見ると「安定」に見える。 しかし基準を 2013 に固定したまま累積すると 年々 PSI が単調増加し、 いつか閾値 0.25 を越えて再学習トリガになる ── これが「じわじわ進む silent drift」の正体です。 (2) この分布シフトは 実在する少子高齢化であり、 サンプリングバイアスではありません。 つまり「本物のドリフトを検知したら、 まず原因(構造変化か一時ノイズか)を切り分ける」という実務手順の好例になっています。 なお $n=47$ と小標本なので、 単発の PSI 値より時系列トレンドで判断すべき、 という既出の注意もそのまま当てはまります。

🔗 関連ページ(相対リンク)

本ページの内容は、 同じ用語集内の以下のページと往復すると理解が深まります(実在ページのみリンク):

🗺 学習ロードマップ

  1. 入口:本ページで PSI/KS の式と意味を理解、 SSDSE で 1 度実計算する
  2. 横展開MLOpsCI/CDDVC でモデル運用全体を把握
  3. 深掘りモデル評価クロスバリデーション過学習 を復習
  4. 応用A/B テスト実験計画法 で因果的な評価を学ぶ
  5. ガバナンスAI ガバナンスAI 倫理説明責任 で社会受容性を確保

🔍 ドリフト検知の深堀り: PSI / KS / Evidently (補論)

モデル監視 (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 で検証する。

📐 数式を言葉で読み解く (PSI)

$$\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対称化 KL0.1-0.3 経験則[0, 1] 範囲で扱いやすい

🐍 Python 実装: PSI / KS を SSDSE-B-2026 で計算する

実測コードと結果は本ページ上部で提示済みです。 「🧮 実値計算」では 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 の挙動を体感する構成としています。

⚠️ ドリフト検知の落とし穴

model monitoring デプロイ後 ドリフト検知 再学習 MLOps オフライン評価 アラート

🔗 隣接手法への橋渡し

「モデル監視」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

監視は「精度劣化に気づく仕組み」と「劣化前に動く仕組み」の二段構えで設計する。 SSDSE-B-2026 のような年次更新データなら、 PSI (Population Stability Index) を四半期で計算し、 閾値超え時点で retraining を発火させるのが標準的。

🌳 手法選択フロー

監視設計は「何が劣化したら誰に通知するか」を運用前に決めておく。

  1. 真値ラベルがすぐ手に入るか? Yes → 直接精度 (accuracy / MAE) を計測。 No → 入力分布のシフトを PSI で代替監視
  2. PSI 閾値は? > 0.25 で要警戒、 > 0.5 で再学習推奨。 SSDSE-B-2026 のような年次データなら四半期で計測
  3. 劣化検出後のアクション 軽度なら calibration、 重度なら全体再学習。 アラート閾値と通知先を事前に決めておく

Evidently AI / WhyLabs / Grafana などのダッシュボードで自動可視化を仕込み、 手動巡回をなくす。

🎮 触って理解する

運用中モデルの精度(上段の折れ線)入力分布のずれ PSI(下段の折れ線)を、 週を追って同時に監視するシミュレータです。 スライダーでドリフト強度異常ショックを加えると精度が徐々に劣化し、 精度がアラート閾値(赤い破線)を割った週に「⚠️ 再学習が必要」を検知します。 同時に PSI が警告ライン(0.25)を越えた週も表示され、 精度が落ちきる前にドリフトで先読みできるかを体感できます。 上段グラフをドラッグ / タッチすると閾値を動かせます。




① 運用中モデルの精度(%) ② 入力分布のずれ PSI
精度アラート: —
ドリフト検知: —
判定: —

🎨 直感 — 運用後も性能を見張る

学習が終わった瞬間がモデルの「絶頂期」で、 そこからは世界が動くほど静かに劣化します。 監視とは、 その劣化を数字が悪くなる前ではなく、悪くなり始めた瞬間に掴むためのレーダー。 精度そのものを見るのが理想ですが、 正解ラベルは遅れて届くことが多いので、 先に動く入力分布のずれ(PSI)を代理シグナルとして併走させます。 スライダーでドリフトを強めると、 多くの場合 PSI ラインが先に警告域へ入り、 数週間遅れて精度が閾値を割る——このリード時間こそ監視の価値です。

⚠️ よくある落とし穴

  • 劣化の見逃し(silent failure): 全体精度は横ばいでも、 特定セグメントだけ崩れていることがある。 集約指標一本での監視は危険。
  • ラベル遅延(ground truth lag): 正解が数週〜数か月遅れる業務では、 精度アラートは常に「後追い」。 だから入力ドリフト監視を出発点に置く。
  • 閾値設定: 厳しすぎると誤報だらけで無視され(狼少年化)、 緩すぎると重大劣化を見逃す。 過去分布の分位点から根拠づけ、 定期的に再校正する。
  • 再学習の即断: PSI 超過=即再学習ではない。 分布シフトが本物かサンプリングバイアスか、 ビジネス KPI が実際に悪化しているかを一次調査してから動く。

🚀 発展

  • ドリフト検知: 入力分布のずれ(データドリフト)を PSI / KS / Wasserstein で常時計測し、 ラベル到着前に予兆を掴む。 条件付き分布 P(Y|X) の変化(コンセプトドリフト)は精度・誤り率の時系列で追う。
  • 自動再学習: 閾値超過をトリガーに再学習パイプラインを起動し、 シャドー評価で新旧を比較してから昇格させる。
  • A/B 監視: A/B テストやカナリアリリースで新モデルを少数トラフィックに当て、 指標を並行監視しながら段階展開する。 特徴量重要度の変化は SHAP 値の時系列で追跡できる。

※ 本シミュレータは監視の仕組みを体感するためのモデル計算です(精度は acc = 0.5 + 0.42·e−1.2·shift、 PSI ≈ 平均シフト² の等分散ガウス近似で正確に算出)。 実データの数値ではありません。