[応用] 否決された人はどこへ行ったのか:リジェクト推論とrejectkit
信用モデルは承認された人の結果だけを見て学習しますが、評価は否決者を含む申込者全体に対して行います。この標本選択バイアスを補正するリジェクト推論の手法八つを一つのAPIにまとめ、その補正が自分のデータで実際に役立つのかまで測るPythonライブラリrejectkitを作って公開しました。
Part 4でリジェクト推論(reject inference)を短く扱いました。承認した顧客だけを見て作ったモデルを申込者全体に適用すると偏る、という話でしたね。この記事は、そのリジェクト推論を実際にコードで書いた記録です。古典的な手法をまとめ、何より「この補正が自分のデータで実際に役立つのか」まで評価してくれるライブラリrejectkitを作って、PyPIとGitHubに公開しました。
否決者の結果は永遠に分かりません
ローン審査を思い浮かべてください。申込者が来ると情報を見て承認するか否決します。承認した人は数か月後に結果が分かります。きちんと返せば正常、延滞すれば貸し倒れです。ところが否決した人は融資しなかったので、返すことも返さないこともありません。結果を永遠に知ることができません。
問題はここから始まります。来年入ってくる申込者が延滞するかを予測するモデルを作るには正解のあるデータが必要ですが、それは承認者だけです。ところが承認者は、そもそも大丈夫そうに見えて通った、一方に偏ったサンプルです。これらだけで学習したモデルは、実際に窓口に来る申込者全体と分布がずれます。
再び訪れた患者だけを見て「私の治療法は効果が高い」と結論づける医者と同じです。効果がなくて二度と来なかった患者はデータに表れないからです。Part 0で見た選択バイアス、そしてPart 4で見たリジェクト推論が、まさにこの話です。
ところがPythonのツールが空いていました
リジェクト推論は信用リスクの世界で数十年来の標準的なテーマです。ところがいざPythonのツールとなると空いていました。
RにはscoringToolsというパッケージがありますが、CRANにもなくGitHubにあるだけです。Pythonのスコアカードライブラリ(scorecardpy、optbinning、toad)は、Part 4で見たWOE/IVのビニングやロジスティックのスコアカードはうまくこなしますが、リジェクト推論はまったく扱いません。残るのは論文用の使い捨て研究コードだけでした。
そこでrejectkitを作りました。目標は二つでした。一つは古典的な手法八つをscikit-learn風の一つのAPIにまとめることです。もう一つは、こちらのほうが重要ですが、「この補正が自分のデータで実際に役立つのか」を測るベンチマークを提供することです。実はリジェクト推論は「効果が疑わしい、どの手法も常に優れているわけではない」というのが学界の長年の結論です。1993年にはすでに「リジェクト推論は果たして機能しうるのか(Can reject inference ever work?)」という題の論文が出たほどです。だから信じて使うのではなく、まず評価してみようというのが、このライブラリの本当のメッセージです。
八つの手法、そして核心の仮定
否決者を再び学習に含める方法は、大きく三つに分かれます。
一つ目は、否決者にラベルを作って埋める方法です(augmentation系)。承認モデルで否決者にスコアを付け、カットオフで切ってラベルを貼ったり(simple)、一人をgood版とbad版の二行に分けて重みと一緒に入れたり(fuzzy)、スコア区間ごとの貸し倒れ率に重みを掛けて「同じスコアでも否決者はもっと悪いはず」という実務の仮定を数字で入れたり(parcelling)、特徴が似た承認者の近傍の貸し倒れ率を借りてきたりします(extrapolation)。
二つ目は、ラベルを作らずに補正する方法です。承認と否決を分ける選択モデルを学習して承認者に逆重みを付けたり(IPW reweighting、Part 2で見たバイアス補正と同じ論理です)、計量経済学のHeckmanの制御関数を分類問題に合わせて追加の特徴量として入れたりします。
三つ目は、半教師あり学習です。否決者をラベルなしのデータとして置き、確信の高いものだけに擬似ラベルを貼って再学習する過程を繰り返します(self-training)。
どの手法が通用するかは、結局一つの仮定にかかっています。否決が何に依存するか、です。
- 否決が観測された特徴だけに依存するなら(MAR)、よく作ったモデルは実は偏りが大きくありません。
- 否決が観測されない結果にも依存するなら(MNAR)、たとえば過去の審査者がデータにない情報で悪い人を弾いていたなら、承認者だけを見たモデルが最も大きく偏ります。
ここに重要な限界があります。augmentation系は偏った承認モデルに頼って否決者のラベルを推測するので、強いMNARの状況を自力で抜け出せません。だから「どの手法を使うか」より「今の自分の状況でリジェクト推論が役立ちはするのか」を先に問うのが正しいです。
役立つかをどう測るか
リジェクト推論の根本的な難しさはこれです。否決者は正解がないので、補正がうまくいったかを直接採点できません。
rejectkitはこれを回避します。正解を全部知っているデータを持ってきて、一部をわざと否決者に選んで正解を隠します(mask)。そして各手法がその隠した正解をどれだけうまく復元するかを、触っていないテストセットで採点します。名前に入っているMaskedが、この正解隠しです。
核心の指標はauc_recoveryです。0なら承認者だけを使ったnaiveモデルと同等、1なら全部の正解を使ったoracleと同じだけ回復、マイナスならむしろ悪化です。
否決の作り方は三つから選びます。特徴だけに依存するmar、隠れた結果まで依存するmnar(最も過酷)、予測リスクが低い順に承認するcutoff(実際の信用ポリシーに最も近い)です。
では役立つのか:合成データ vs 実データ
きれいな合成データにMNARで回してみました。oracleが0.820、naiveが0.749ですが、リジェクト推論の手法はnaiveをほとんど超えられず、いくつかはむしろ損を出します。fuzzyが+0.001でかろうじてトントン、parcellingは-0.116で悪化、Heckmanだけがかろうじてnaiveの線を守ります。選択が結果に依存するMNARでは、理論が予測するそのままです。リジェクト推論はタダ飯ではありません。
では実データで確かめてみましょう。Kaggle Home Creditのデータ(約30万件、貸し倒れ率8%)にそのまま適用しました。
| モデル | AUC | auc_recovery |
|---|---|---|
| oracle (全部の正解) | 0.741 | 1.00 |
| naive (承認者だけ) | 0.568 | 0.00 |
| parcelling | 0.582 | +0.084 |
| extrapolation | 0.582 | +0.079 |
| heckman | 0.580 | +0.071 |
ここでは正反対です。否決者を無視すると0.74から0.57へ大きく崩れ、リジェクト推論がその損失の7〜8%を実際に回復します。合成データではほとんど無用だったのに、実データでは役に立ちます。そして同じデータでもmarやcutoffに変えると、naiveがすでにoracle級なので回復する余地そのものがなくなります(auc_recoveryがNaNで出ます)。
結論は一つに集まります。リジェクト推論が役立つかはデータ次第で、だから使う前にベンチマークで確認しなければなりません。
使い方
一つのモデルにリジェクト推論をかぶせるのは、こうします。
from sklearn.linear_model import LogisticRegression
from rejectkit import RejectInferenceClassifier
# X_accept, y_accept: 承認者と結果(1=貸し倒れ) / X_reject: 否決者(特徴量のみ)
clf = RejectInferenceClassifier(
estimator=LogisticRegression(max_iter=1000),
method="parcelling",
method_params={"uplift": 1.3}, # 否決者は同じスコア帯より約30%悪いと仮定
)
clf.fit(X_accept, y_accept, X_reject)
pd_bad = clf.predict_proba(X_new)[:, 1]
役立つかをまず測るのは一行です。
from rejectkit import MaskedRejectBenchmark
bench = MaskedRejectBenchmark(selection="mnar", accept_rate=0.6, random_state=0)
print(bench.compare(["fuzzy", "parcelling", "reweighting", "heckman"], X, y).round(4))
入力はpandas、polars、numpyをすべて受け付けます。承認と否決の分布がどれだけ違うかを見る診断(変数ごとのPSI)や可視化も入っています。
まとめ
- 信用モデルを承認者のデータだけで学習すると、標本選択バイアスが生じます。Part 0とPart 4で見たあの問題です。
- rejectkitは、これを補正する古典的な手法八つを一つのAPIにまとめました。
- 本当の差別化点は、その補正が自分のデータで役立つかを測るベンチマークです。リジェクト推論は万能ではなく、時に有害ですらあるので、信じて使うのではなくまず測ろう、というのが核心です。
- 信用だけでなく、合格者だけ結果が見えるあらゆる選択バイアスの状況に、同じ原理が当てはまります。採用の成果や、フラグが付いた件だけラベリングされる不正検知のようなものです。
そしてリジェクト推論の最も信頼できる答えは、実は別にあります。少数をあえてランダムに承認して本当の結果を確保する実験です。推定ではなく事実をくれるからです。その話はPart 6で因果推論と実験へと続きます。
インストールは一行です。
pip install rejectkit
- GitHub: github.com/HangilKim11/rejectkit
- PyPI: pypi.org/project/rejectkit
フィードバック、Issue、PR歓迎します。