Цель A/B-теста - сравнить текущую модель прогнозирования дефолта model_v1 с новой моделью model_v2 и определить, дает ли новая версия статистически и практически значимое улучшение качества риск-оценки.
В контексте банка модель используется как вспомогательный инструмент для оценки вероятности дефолта клиента по кредитной карте.
Новая модель model_v2 не улучшает качество прогнозирования дефолта по сравнению с моделью model_v1.
Новая модель model_v2 улучшает качество прогнозирования дефолта по сравнению с моделью model_v1 по основной метрике и не ухудшает критичные дополнительные метрики.
| Группа | Доля трафика | Модель | Описание |
|---|---|---|---|
| Control | 50% | model_v1 |
Текущая модель, сохраненная как credit_default_model_v1.joblib |
| Treatment | 50% | model_v2 |
Демонстрационная новая версия: та же модель, но с порогом 0.50 вместо 0.47 |
Распределение клиентов выполняется случайно, но стабильно: один и тот же клиент должен попадать в одну и ту же группу в течение всего эксперимента. Для этого можно использовать hash от client_id.
Пример логики:
hash(client_id) % 100 < 50 -> control
hash(client_id) % 100 >= 50 -> treatment
Если client_id отсутствует, можно использовать случайное распределение на уровне запроса, но это хуже для реального production-сценария.
Рекомендуемая длительность: 2-4 недели.
Причины:
- необходимо накопить достаточное число наблюдений по дефолтному классу;
- дефолт является отложенным событием;
- короткий тест может дать нестабильные выводы из-за сезонности и случайных колебаний.
В учебном проекте допускается имитация A/B-теста на отложенной test-выборке.
Основная метрика:
F1-score для класса дефолта
Почему F1:
- учитывает и Precision, и Recall;
- подходит для несбалансированной бинарной классификации;
- дефолтный класс важнее, чем класс отсутствия дефолта.
| Метрика | Зачем нужна |
|---|---|
| Precision для класса дефолта | Показывает, насколько часто клиенты, отмеченные как рискованные, действительно дефолтные |
| Recall для класса дефолта | Показывает, какую долю реальных дефолтов модель смогла обнаружить |
| ROC-AUC | Оценивает ранжирующую способность модели |
| Average Precision | Более информативна при дисбалансе классов |
| Доля клиентов, попавших в high-risk | Важна для операционной нагрузки на риск-команду |
Ожидаемые финансовые потери можно оценить так:
Expected Loss = PD * LGD * EAD
Где:
PD- вероятность дефолта из модели;LGD- loss given default, доля потерь при дефолте;EAD- exposure at default, сумма задолженности или кредитный лимит.
Для сравнения моделей можно считать суммарный expected loss по клиентам, которым была бы одобрена кредитная карта.
Метрика показывает, какую долю клиентов можно одобрить при условии, что риск портфеля не превышает допустимый порог.
Пример:
Approval Rate = approved_clients / all_clients
Успешная модель должна позволять сохранить или увеличить approval rate без роста дефолтности портфеля.
Ошибки имеют разную бизнес-стоимость:
| Ошибка | Интерпретация | Последствие |
|---|---|---|
| False Negative | Модель не заметила рискованного клиента | Возможные финансовые потери |
| False Positive | Модель ошибочно посчитала надежного клиента рискованным | Потеря клиента или прибыли |
Для банка false negative обычно дороже, поэтому Recall может быть важнее Precision.
Для сравнения долей, например доли обнаруженных дефолтов или доли ошибок, можно использовать z-test для двух пропорций.
Пример:
H0: p_v1 = p_v2
H1: p_v1 != p_v2
Для сравнения средних значений expected loss можно использовать:
- t-test, если распределение достаточно стабильно;
- bootstrap confidence interval, если распределение скошенное.
Для финансовых потерь предпочтительнее bootstrap, потому что распределение убытков часто асимметрично.
Для ключевых метрик строятся 95% доверительные интервалы:
- F1-score: bootstrap по клиентам;
- Precision/Recall: bootstrap или интервалы для долей;
- expected loss: bootstrap.
Модель model_v2 считается успешной, если выполняются условия:
- F1-score для класса дефолта статистически значимо выше, чем у
model_v1; - Recall не ниже, чем у
model_v1, либо снижение Recall экономически обосновано; - Precision не падает более чем на допустимый порог, например 2-3 п.п.;
- expected loss снижается;
- нет существенного роста операционной нагрузки, например доли клиентов, отправленных на ручную проверку.
В текущей реализации API поддерживает две версии модели: model_v1 и model_v2.
Версия модели передается через поле model_version в JSON body.
models/
├── credit_default_model_v1.joblib
└── credit_default_model_v2.joblib
Поле в JSON body:
{
"model_version": "model_v1",
"data": {
"...": "..."
}
}Для второй модели:
{
"model_version": "model_v2",
"data": {
"...": "..."
}
}Также можно реализовать автоматическое распределение трафика:
POST /predict-ab
где API сам выбирает модель по hash от client_id.
Текущая архитектура уже поддерживает основные элементы A/B-теста:
- API может логировать версию модели;
- PostgreSQL может хранить результаты запросов;
- RabbitMQ может использоваться для асинхронного логирования событий;
- batch-worker может обрабатывать отложенные расчеты метрик;
- A/B-события сохраняются в отдельную таблицу
ab_test_events;
Практическая реализация A/B-логирования подробно описана в разделе 13.
Ключевая таблица - ab_test_events, где сохраняются версия модели, группа эксперимента, prediction, probability, threshold, request/response JSON и будущий фактический таргет actual_default.
После появления фактического таргета actual_default можно сравнивать качество моделей.
- Дефолт является отложенным событием, поэтому онлайн A/B-тест требует времени.
- Нельзя полностью автоматизировать кредитное решение без контроля рисков.
- Модель должна использоваться с human-in-the-loop для спорных кейсов.
- Необходимо контролировать fairness и возможные proxy-переменные, чтобы избежать дискриминации.
В проекте реализовано сохранение A/B-событий в PostgreSQL. Таблица создается при старте БД из файла:
sql/init_db.sql
Структура таблицы:
CREATE TABLE IF NOT EXISTS ab_test_events (
id BIGSERIAL PRIMARY KEY,
client_id TEXT,
group_name TEXT,
model_version TEXT NOT NULL,
prediction INTEGER,
probability DOUBLE PRECISION,
threshold DOUBLE PRECISION,
decision TEXT,
actual_default INTEGER,
request_json JSONB,
response_json JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);При вызове /predict сервис сохраняет A/B-событие сразу после инференса. При вызове /predict-batch-async событие сохраняет batch-worker после обработки сообщения из RabbitMQ.
Правила групп:
model_v1->control;model_v2->treatment.
В событие сохраняются:
- версия модели;
- группа A/B-теста;
- prediction;
- probability;
- threshold;
- business decision (
approveилиreview); - request JSON (
record+request_contextдля аудита); - response JSON;
client_id, если он передан во входном объекте;actual_default, который может быть заполнен позже после появления фактического исхода.
Пример входного запроса:
{
"model_version": "model_v2",
"data": {
"client_id": "client_001",
"LIMIT_BAL": 20000,
"SEX": 2
}
}Пример SQL-запроса для первичной проверки A/B-событий:
SELECT
group_name,
model_version,
COUNT(*) AS n_events,
AVG(prediction) AS predicted_default_rate,
AVG(probability) AS avg_probability
FROM ab_test_events
GROUP BY group_name, model_version
ORDER BY group_name, model_version;После заполнения actual_default можно считать confusion matrix по группам:
SELECT
group_name,
model_version,
COUNT(*) AS n_events,
SUM(CASE WHEN prediction = 1 AND actual_default = 1 THEN 1 ELSE 0 END) AS tp,
SUM(CASE WHEN prediction = 1 AND actual_default = 0 THEN 1 ELSE 0 END) AS fp,
SUM(CASE WHEN prediction = 0 AND actual_default = 1 THEN 1 ELSE 0 END) AS fn,
SUM(CASE WHEN prediction = 0 AND actual_default = 0 THEN 1 ELSE 0 END) AS tn
FROM ab_test_events
WHERE actual_default IS NOT NULL
GROUP BY group_name, model_version;На основе tp, fp, fn, tn можно рассчитать Precision, Recall и F1-score для каждой версии модели.
В текущей реализации model_v2 отличается от model_v1 порогом классификации. Это минимальная, но рабочая демонстрация сравнения двух версий через API и таблицу ab_test_events. В реальном развитии проекта model_v2 можно заменить на модель, обученную с другими гиперпараметрами или алгоритмом.