本文へスキップ

ガイド

チュートリアル: 分類器と回帰器

コマンドラインから評価する 3 つの予測モデルを実際に動かして進む手順です。二値の解約分類器、クラスごとに 1 つずつ評価する 3 クラスのチケットルーター、宣言した範囲内の絶対誤差で採点する配達日数の回帰器です。どれもプロバイダー、キー、ネットワークなしにローカルで実行でき、どれも最後に候補の変更をベースラインに対して測定します。

このページは、すべてのフィールドを説明する分類器と回帰器の実践版です。リファレンスとしてはそちらを、一度最初から最後まで通して行うにはこちらを読んでください。区間、判断状態、リリースアクションといった用語は基本概念で定義しています。

予測モデルについて Oloproof が測定するもの、しないもの

モデル得られるもの得られないもの
二値分類器正解率、再現率、適合率、Brier スコア、対数損失、ROC-AUC、平均適合率、そして混同行列の件数、キャリブレーションの表、しきい値のスイープ推奨されるしきい値
多クラス分類器クラスごとに 1 つの再現率と 1 つの適合率。それぞれ、そのクラスを陽性クラスとする二値のメトリクス(一対他)マクロ平均やマイクロ平均のメトリクス
回帰器宣言した target_range で有界とした平均絶対誤差二乗誤差、決定係数、宣言された範囲のない誤差

モデル自体はあなたのものです。Oloproof は指定された Python 関数を呼び出し、それが返す予測を読み、特徴量、重み、内部を見ることはありません。

前提条件

  • クイックスタートと同様に、仮想環境に Python 3.11 以降と pip install oloproof。
  • パッケージに同梱されているサンプルファイル。実行のストアがそこに置かれるよう、新しいディレクトリにコピーしてください。
oloproof init --example predictive ~/oloproof-predictive
cd ~/oloproof-predictive

以下のコマンドはすべて、その 3 つのサブディレクトリのいずれかから実行します。各実行は、読み込んだ oloproof.yaml の横の .oloproof/ ディレクトリにエビデンスを書き込みます。

predictive/
  binary/        app.py  oloproof.yaml  candidate.yaml  release.yaml  comparison.yaml  data/accounts.jsonl
  multiclass/    app.py  oloproof.yaml  candidate.yaml  release.yaml  data/tickets.jsonl
  regression/    app.py  oloproof.yaml  candidate.yaml  release.yaml  data/orders.jsonl

パート 1: 二値分類器

アダプター

binary/app.py はモデルとアダプターを 1 つのファイルに持っています。モデルは churn_model サンプル(oloproof init --example churn_model)と同じ決定的な解約モデルです。アダプターは Oloproof が呼び出すデコレートされた関数です。

from oloproof import system


@system(name="churn-model", version="baseline")
def run(account):
    score = churn_score(account)
    return {"label": score >= 0.5, "score": round(score, 4)}


@system(name="churn-model", version="candidate-cutoff-0.4")
def run_candidate(account):
    score = churn_score(account)
    return {"label": score >= 0.4, "score": round(score, 4)}

自分のモデルを評価するには、形はそのままにして churn_score をそのモデルの呼び出しに置き換えます。たとえばインポート時に一度読み込む scikit-learn のモデルなら model.predict_proba([features(account)])[0][1] です。モデルやそのカットオフが変わったら必ず version を変えてください。バージョンはキャッシュキーの一部なので、同じバージョンのまま再学習したモデルは古い予測を再利用してしまいます。

入力と出力の形

data/accounts.jsonl の 1 行が 1 つのケースです。

{"expected": {"label": false}, "id": "account_000", "input": {"recent_upgrade": true, "support_contacts": 0, "tenure_months": 0}, "metadata": {"plan": "enterprise"}}
部分形読むもの
input関数が受け取るオブジェクトあなたのアダプター
expected.labeltrue または false、正解評価器
metadata.plan任意の JSONスライスのみ
返される labeltrue または false、予測評価器
返される score[0, 1] の数値、陽性クラスの確率Brier、対数損失、ランキング、キャリブレーション、スイープ

評価器と、それを選ぶ理由

binary/oloproof.yaml:

version: 1
project: churn-tutorial
dataset: data/accounts.jsonl
system:
  name: churn-model
  version: baseline
  callable: app:run
evaluators:
  - {type: predictive_correct, criterion: accuracy}
  - {type: predictive_recall, criterion: recall}
  - {type: predictive_precision, criterion: precision}
  - {type: predictive_brier, criterion: brier}
  - {type: predictive_log_loss, criterion: log_loss, clip: 0.02}
  - {type: predictive_ranking, criterion: rank}
metrics:
  - {id: roc_auc, type: ranking, criterion: rank, statistic: roc_auc}
  - {id: pr_auc, type: ranking, criterion: rank, statistic: average_precision}
predictive:
  label_field: label
  score_field: score
  expected_field: label
  positive: true
  calibration_bins: 10
  thresholds: [0.3, 0.4, 0.5, 0.6, 0.7]
slices: [metadata.plan, "confidence:0.5"]
min_slice_support: 20
  • 正解率、再現率、適合率は、異なる 3 つの行の集合にわたる 3 つの率です。すべてのアカウント、解約したアカウント、モデルがフラグを立てたアカウントです。アカウントのおよそ 3 分の 1 が解約するため、解約モデルには 3 つすべてが必要です。誰も解約しないと予測するモデルは正解率 68% で、誰も見つけません。
  • Brier と対数損失は、ラベルの背後にある確率を採点します。対数損失には clip が必要です。そうしないと、1 つの自信過剰な誤りが無限大になるからです。
  • predictive_ranking と 2 つの metrics: エントリで ROC-AUC と平均適合率が得られます。これらは、カットオフがどこにあるかとは別に、モデルがアカウントをどれだけうまく順位付けるかに答えます。
  • predictive: ブロックはラベル、スコア、正解がどこにあるかを示し、メトリクスの横に混同行列の件数、キャリブレーションの表、しきい値のスイープを生成します。

ポリシー

binary/release.yaml は各率に下限を設けます。

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
  - {id: accuracy-floor, metric: accuracy, min: 0.75}
  - {id: recall-floor, metric: recall, min: 0.60}
  - {id: precision-floor, metric: precision, min: 0.60}

ルールは、区間の下限が下限値を超えれば合格、上限が下限値を下回れば不合格、それ以外は INSUFFICIENT_EVIDENCE になります。

実行する

cd binary
oloproof run

抜粋は次のとおりです。

Run run_... [DECIDED/COMPLETE]
Gate: ALLOW (exit 0)
│ accuracy-floor  │ accuracy  │ PASS  │ lower_bound_meets_minimum │
│ recall-floor    │ recall    │ PASS  │ lower_bound_meets_minimum │
│ precision-floor │ precision │ PASS  │ lower_bound_meets_minimum │

│ accuracy  │ 88.5%    │ [83.2%, 92.6%]  │ 177 / 200 observed · 0 missing · 0 excluded                                │
│ recall    │ 81.2%    │ [69.5%, 90.0%]  │ 52 / 64 observed · 0 missing · 136 excluded                                │
│ precision │ 82.5%    │ [70.9%, 91.0%]  │ 52 / 63 observed · 0 missing · 137 excluded                                │
│ brier     │ 0.120    │ [0.094, 0.154]  │ mean of 200 observed · 0 missing · 0 excluded                              │
│ log_loss  │ 0.389    │ [0.323, 0.499]  │ mean of 200 observed · 0 missing · 0 excluded                              │
│ roc_auc   │ 92.3%    │ [69.3%, 100.0%] │ roc_auc over 64 positive · 136 negative · 0 missing · 0 excluded           │
│ pr_auc    │ 86.5%    │                 │ average_precision over 64 positive · 136 negative · 0 missing · 0 excluded │

読み方は次のとおりです。

  • Gate: ALLOW (exit 0) は、ポリシーがブロックする状態に達したルールがなかったことを意味します。書いた 3 つの下限を超えてモデルが良いという主張ではありません。
  • excluded の件数は分母が働いている様子です。再現率は解約した 64 のアカウントにわたって測定されるため、解約しなかった 136 は失敗として数えられるのではなく、そこから除外されます。
  • pr_auc には区間がありません。200 行では、エンジンは支えられない境界を出しません。それに対するルールは interval_unavailable で INSUFFICIENT_EVIDENCE になります。

メトリクスの下に、同じ出力が混同行列の件数、キャリブレーションの表、しきい値のスイープを出力します。

│ actually positive │ 52                 │ 12                 │
│ actually negative │ 11                 │ 125                │

│ 0.2-0.3 │ 26.7%   │ 0.0%     │ 34 rows │
│ 0.5-0.6 │ 53.4%   │ 81.0%    │ 21 rows │

│ 0.3     │ 57.4%     │ 96.9%  │ 62/108 predicted positive · 62/64 actual positive │
│ 0.4     │ 62.6%     │ 89.1%  │ 57/91 predicted positive · 57/64 actual positive  │
│ 0.5     │ 82.5%     │ 81.2%  │ 52/63 predicted positive · 52/64 actual positive  │
│ 0.6     │ 83.3%     │ 54.7%  │ 35/42 predicted positive · 35/64 actual positive  │
│ 0.7     │ 100.0%    │ 37.5%  │ 24/24 predicted positive · 24/64 actual positive  │

件数は率ではなく、どのルールもそれを指定できません。キャリブレーションの行は、モデルの確率がずれていることを示しています。0.2 から 0.3 の帯では、おおよそ 4 件に 1 件が解約すると主張していますが、34 件のうち解約したものはありません。スイープの表題は Thresholds (exploratory; recommends nothing) です。各カットオフで何が測定されたはずかを示し、選択はあなたに任せます。見逃した解約者のコストと無駄な引き留めの電話のコストを比べられるのはあなただけだからです。

失敗を調べる

oloproof inspect RUN_ID --failures --limit 5
23 of 200 cases failed, errored or did not finish

account_032
  output: {"label": false, "score": 0.07}
  accuracy: failed
  recall: failed

account_037
  output: {"label": false, "score": 0.37}
  accuracy: failed
  recall: failed
...

RUN_ID は、実行の出力の 1 行目にある id です。oloproof inspect RUN_ID --case account_037 は、1 つのケースの入力、期待値、出力、すべての判定を示します。見逃した解約者のいくつかは 0.5 のカットオフのすぐ下(0.37、0.43)にあり、これはスイープの 0.4 の行がすでに示唆していたことです。

意味のある次の行動は、ゲートからではなく、目にしたものから導かれます。ここでは見逃しがカットオフの下に集まっているので、候補はより低いカットオフを試します。それが自信を持った見逃し(0.07)であれば、次の行動はモデルの特徴量であり、どのカットオフも役に立たなかったでしょう。

候補の変更と比較

binary/candidate.yaml は、oloproof.yaml の 2 行を変えたものです。

system:
  name: churn-model
  version: candidate-cutoff-0.4
  callable: app:run_candidate
oloproof run --config candidate.yaml
Gate: BLOCK (exit 3)
│ accuracy-floor  │ accuracy  │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ recall-floor    │ recall    │ PASS                  │ lower_bound_meets_minimum   │
│ precision-floor │ precision │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │

│ accuracy  │ 79.5%    │ [73.2%, 84.9%]  │ 159 / 200 observed · 0 missing · 0 excluded                                │
│ recall    │ 89.1%    │ [78.7%, 95.5%]  │ 57 / 64 observed · 0 missing · 136 excluded                                │
│ precision │ 62.6%    │ [51.8%, 72.6%]  │ 57 / 91 observed · 0 missing · 109 excluded                                │

まさにスイープの 0.4 の行です。再現率は上がり、適合率は下がりました。終了コード 3 は FAIL ではなく INSUFFICIENT_EVIDENCE です。下限値が区間の内側にあるため、200 のアカウントでは候補がどちら側にあるかを言えません。スコアは変わっていないので、Brier、対数損失、ROC-AUC は同一です。

次に、2 つの実行をケースごとに比較します。binary/comparison.yaml は比較ルールを保持しています。

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
  - {id: recall-no-worse, kind: non_inferiority, metric: recall, margin: 0.05}
  - {id: accuracy-no-worse, kind: non_inferiority, metric: accuracy, margin: 0.05}
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy comparison.yaml
Comparison sha256:... of run_... against run_... · 200 paired cases
accuracy: -9.0 points [-17.9, -1.4] · 200 paired · 0 missing · 0 excluded
recall: +7.8 points [-2.8, +21.9] · 64 paired · 0 missing · 136 excluded
  excluded 136: not_a_positive_case
precision: +0.0 points [-8.3, +8.3] · 63 paired · 0 missing · 137 excluded
  excluded 137: not_predicted_positive
...
Decisions
  recall-no-worse  recall  non-inferiority, margin 5.0 points  PASS  lower_bound_above_margin
  accuracy-no-worse  accuracy  non-inferiority, margin 5.0 points  INSUFFICIENT_EVIDENCE  interval_overlaps_margin
    no sample size would make this PASS: the difference itself (-9.0 points) is outside the margin, so more cases would move it toward FAIL
Gate: BLOCK (exit 3)

適合率の行は注意して読んでください。候補自身の適合率は 82.5% から 62.6% に下がりましたが、対応のある差は 63 組で +0.0 です。比較は行を対にします。適合率の差は両方のモデルがフラグを立てたアカウントだけで測定され、その 63 件では両方とも正しかったのです。候補が追加でフラグを立てた 28 のアカウントは、うち 23 件が誤警報ですが、その集合の外にあります。だからこそ comparison.yaml は、カットオフの変更について適合率ではなく正解率を守っています。ルールの種類は比較ルールにあります。

判断こそが有用な部分です。再現率は悪くないことが示され、正解率は 5 ポイント以内であることを示せません。助言の行は、データを増やすと FAIL のほうへ動くと示しています。正解率 9 ポイントを再現率 8 ポイントと引き換えにする価値があるかは事業上の判断であり、ゲートはそれを見えるようにしました。

パート 2: 多クラス分類器をクラスごとに

multiclass/app.py はサポートチケットを 3 つのキューのいずれかに振り分け、意図的な欠陥を 1 つ持っています。モバイルアプリから送られたチケットはすべて technical に行きます。

@system(name="ticket-router", version="baseline")
def route(ticket):
    if ticket["channel"] == "app":
        return {"queue": "technical"}
    return {"queue": classify(str(ticket["subject"]))}

ケースの例です。

{"expected": {"queue": "billing"}, "id": "ticket_000", "input": {"channel": "email", "subject": "update the card on file"}, "metadata": {"channel": "email"}}

有効にできる多クラスのメトリクスはありません。各クラスはそれぞれ二値のブロックを持ちます。billing の再現率は、billing を陽性クラスとする二値の再現率です。multiclass/oloproof.yaml:

evaluators:
  - {type: predictive_correct, criterion: accuracy, field: queue, expected_field: queue}
  - {type: predictive_recall, criterion: recall_billing, field: queue, expected_field: queue, positive: billing}
  - {type: predictive_precision, criterion: precision_billing, field: queue, expected_field: queue, positive: billing}
  - {type: predictive_recall, criterion: recall_technical, field: queue, expected_field: queue, positive: technical}
  - {type: predictive_precision, criterion: precision_technical, field: queue, expected_field: queue, positive: technical}
  - {type: predictive_recall, criterion: recall_account, field: queue, expected_field: queue, positive: account}
  - {type: predictive_precision, criterion: precision_account, field: queue, expected_field: queue, positive: account}
slices: [metadata.channel]

Oloproof はこれらのマクロ平均やマイクロ平均を計算しません。必要なら、それはクラスごとの件数から自分で導く数値であり、どのルールもそれでゲートできません。ルーターは全体では正確でも 1 つのキューを失うことがあるため、ポリシーは重要なクラスに下限を設けます。

rules:
  - {id: billing-recall-floor, metric: recall_billing, min: 0.80}
  - {id: account-recall-floor, metric: recall_account, min: 0.80}
  - {id: technical-precision-floor, metric: precision_technical, min: 0.80}
cd ../multiclass
oloproof run
Gate: BLOCK (exit 1)
│ billing-recall-floor      │ recall_billing      │ FAIL                  │ upper_bound_below_minimum   │
│ account-recall-floor      │ recall_account      │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ technical-precision-floor │ precision_technical │ FAIL                  │ upper_bound_below_minimum   │

│ accuracy            │ 81.3%    │ [74.1%, 87.3%]  │ 122 / 150 observed · 0 missing · 0 excluded │
│ recall_billing      │ 66.0%    │ [51.2%, 78.8%]  │ 33 / 50 observed · 0 missing · 100 excluded │
│ precision_billing   │ 100.0%   │ [89.4%, 100.0%] │ 33 / 33 observed · 0 missing · 117 excluded │
│ recall_technical    │ 100.0%   │ [92.8%, 100.0%] │ 50 / 50 observed · 0 missing · 100 excluded │
│ precision_technical │ 64.1%    │ [52.4%, 74.7%]  │ 50 / 78 observed · 0 missing · 72 excluded  │
│ recall_account      │ 78.0%    │ [64.0%, 88.5%]  │ 39 / 50 observed · 0 missing · 100 excluded │
│ precision_account   │ 100.0%   │ [90.9%, 100.0%] │ 39 / 39 observed · 0 missing · 111 excluded │

終了コード 1 は、少なくとも 1 つのルールが FAIL であることを意味します。各クラスは独自の分母を持ちます。78 のチケットが technical と判定されたので、それが technical の適合率の分母です。スライスの表は原因を指し示しています。スライスは探索的でゲートには使われませんが、印の付いたスライスは読む価値のある手がかりです。

│ metadata.channel=app   │ accuracy            │ 42.9%    │ [28.8%, 57.8%]  │ 21 / 49 observed · ... │ marked (p 0.0001)    │
oloproof inspect RUN_ID --failures --limit 2
28 of 150 cases failed, errored or did not finish

ticket_011
  output: {"queue": "technical"}
  accuracy: failed
  precision_technical: failed
  recall_account: failed
...

候補(route_candidate、oloproof run --config candidate.yaml で実行)はチャネルによる近道を取り除きます。この合成データでは、すべてのチケットを正しく振り分け、ゲートは許可します。

Gate: ALLOW (exit 0)
│ billing-recall-floor      │ recall_billing      │ PASS  │ lower_bound_meets_minimum │
│ account-recall-floor      │ recall_account      │ PASS  │ lower_bound_meets_minimum │
│ technical-precision-floor │ precision_technical │ PASS  │ lower_bound_meets_minimum │

oloproof compare はここでもパート 1 とまったく同じように、クラスのメトリクスごとに 1 つの差として動作します。

パート 3: 絶対誤差で採点する回帰器

regression/app.py は配達日数を見積もり、商品の在庫があるかどうかを無視します。

@system(name="delivery-estimator", version="baseline")
def estimate(order):
    return {"days": round(base_days(order), 1)}

正解を数値として持つケースです。

{"expected": {"days": 11}, "id": "order_000", "input": {"distance_km": 468, "express": false, "in_stock": false}, "metadata": {"in_stock": false}}

回帰の評価器は絶対誤差だけで、すべての目標値が収まる範囲を必要とします。絶対誤差は有界平均であり、その区間はその範囲内でのみ成り立つため、範囲は宣言するものであり、デフォルトはありません。ここでの配達は 0 日から 20 日です。

evaluators:
  - type: predictive_absolute_error
    criterion: days_error
    field: days
    expected_field: days
    target_range: [0, 20]
slices: [metadata.in_stock]

ポリシーは予算、つまり max: ルールです。区間の上限がそれ以下のときに合格します。

rules:
  - {id: error-budget, metric: days_error, max: 2.0}
cd ../regression
oloproof run
Gate: BLOCK (exit 1)
│ error-budget │ days_error │ FAIL  │ lower_bound_above_maximum │

│ days_error │ 2.66     │ [2.01, 3.57] │ mean of 120 observed · 0 missing · 0 excluded │

│ metadata.in_stock=false │ days_error │ 5.18     │ [4.60, 6.65] │ mean of 53 observed · ... │
│ metadata.in_stock=true  │ days_error │ 0.67     │ [0.51, 2.19] │ mean of 67 observed · ... │

区間の下限でさえ 2 日の予算を上回っているため FAIL です。スコアの評価器にはケースごとの合格や不合格がないため、oloproof inspect RUN_ID --failures は何も列挙しません。代わりにケースを読んでください。

oloproof inspect RUN_ID --case order_000
output: {
  "days": 6.1
}
judgments:
  days_error: score 4.9

スライスがどこを見るべきかを示しています。在庫切れの注文は 5 日ずれています。候補(estimate_candidate)は在庫切れの商品に 5 日を加えます。

oloproof run --config candidate.yaml
Gate: ALLOW (exit 0)
│ error-budget │ days_error │ PASS  │ upper_bound_meets_maximum │
│ days_error │ 0.70     │ [0.56, 1.57] │ mean of 120 observed · 0 missing · 0 excluded │

トラブルシューティング

症状原因と対処
Configuration error: evaluator 'recall' counts 'churned' as the positive class, and no case's 'label' is 'churned'positive: がどのケースにもない値を指定しています。true と "true" の違いも含め、expected の下に現れるとおりの値を使ってください。
release rule ... refers to unknown metric 'false_positives'混同行列の件数はメトリクスではありません。再現率か適合率でゲートしてください。
回帰の評価器が実行前に拒否されるtarget_range がないか空です。目標値が実際にとりうる範囲を宣言してください。範囲が広いほど区間も広くなります。
ランキングのルールが INSUFFICIENT_EVIDENCE (interval_unavailable) になるその統計量の区間にはスイートが小さすぎます。典型的には pr_auc です。roc_auc でゲートするか、ケースを増やしてください。
候補がベースラインの数値を報告する両方の実行が同じ version を共有しているため、キャッシュされた予測が再利用されました。変更ごとに独自のバージョンを付けてください。
ケースが誤りではなく missing になる関数が例外を出したか、評価器が読むフィールドがないか数値ではありません。oloproof inspect RUN_ID --case ID がエラーを示します。

制限事項

  • 推奨されるしきい値はありません。スイープは、宣言した各カットオフで何が測定されたはずかを報告します。
  • 多クラスのマクロ平均やマイクロ平均のメトリクスはなく、マルチラベルはラベルごとに 1 ブロックという以上のサポートはありません。
  • 回帰は宣言した target_range 内の絶対誤差だけです。二乗誤差、決定係数、有界でない誤差はありません。
  • キャリブレーションは表として示されるだけで、ゲートにも補正にも使われません。
  • パート 1 が示すように、対応のある適合率の差は、両方のモデルがフラグを立てた行だけを対象とします。
  • 例は決定的で合成されたものです。その判断は仕組みを示すものであり、実際のモデルが実際のデータでどう振る舞うかを示すものではありません。

次に読むもの