Skip to content

Repository files navigation

Credit Card Default Prediction Service

Проект по дисциплине «Внедрение моделей машинного обучения».

Цель проекта - разработать production-like сервис машинного обучения для прогнозирования дефолта клиента по кредитной карте. Проект покрывает полный цикл: обучение модели, сохранение артефакта, API-инференс, контейнеризация, асинхронная batch-обработка, логирование, хранение результатов в БД и практическую демонстрацию A/B-тестирования.

1. Бизнес-контекст

Домен: финансы / кредитный скоринг.

Задача: бинарная классификация клиента:

  • 0 - дефолт в следующем месяце не ожидается;
  • 1 - ожидается дефолт в следующем месяце.

Датасет: Default of Credit Card Clients Dataset.
Таргет: default.payment.next.month.

Модель может использоваться как вспомогательный компонент risk-based decision making: например, для приоритизации ручной проверки заявок, настройки лимитов или выделения клиентов с повышенным риском.

2. Структура проекта

SF_MLOps/
├── app/
│   ├── __init__.py
│   ├── api.py                 # Flask API
│   ├── model_handler.py       # загрузка модели и инференс
│   ├── model_registry.py      # выбор версии модели
│   ├── queue_client.py        # публикация сообщений в RabbitMQ
│   ├── batch_worker.py        # асинхронная обработка batch-задач
│   ├── log_worker.py          # запись логов в PostgreSQL
│   └── db.py                  # функции работы с PostgreSQL
├── docker/
│   └── Dockerfile
├── models/
│   ├── credit_default_model_v1.joblib
│   └── credit_default_model_v2.joblib
├── notebooks/
│   └── credit_card_default_ml.ipynb
├── screenshots/
│   ├── health_model_info.png
│   ├── model_v1.png
│   ├── model_v2.png
│   └── batch_async.png
├── sql/
│   └── init_db.sql
├── src/
│   └── train_model.py
├── tests/
│   └── test_api.py
├── ARCHITECTURE.md
├── ab_test_plan.md
├── docker-compose.yml
├── pytest.ini
├── requirements.txt
├── README.md
└── .gitignore

3. Архитектура сервиса

В проекте используется расширенная монолитная архитектура с worker-компонентами:

flowchart LR
    client[Client / curl / Postman]

    subgraph api_layer[API layer]
        api[Flask API<br/>credit-default-api]
        handler[ModelHandler]
        registry[Model Registry<br/>model_v1 / model_v2]
    end

    subgraph model_layer[Model artifacts]
        model_v1[credit_default_model_v1.joblib]
        model_v2[credit_default_model_v2.joblib]
    end

    subgraph queue_layer[Message broker]
        rabbit[RabbitMQ]
        batch_queue[batch_prediction_queue]
        log_queue[api_log_queue]
    end

    subgraph workers[Worker services]
        batch_worker[batch-worker]
        log_worker[log-worker]
    end

    subgraph storage[PostgreSQL]
        batch_table[(batch_predictions)]
        logs_table[(api_logs)]
        ab_table[(ab_test_events)]
    end

    client -->|GET /health| api
    client -->|GET /model-info| api
    client -->|POST /predict| api
    client -->|POST /predict-batch-async| api
    client -->|GET /batch-result/task_id| api

    api --> registry
    registry --> handler
    handler --> model_v1
    handler --> model_v2

    api -->|sync prediction result| client
    api -->|save A/B event| ab_table

    api -->|publish batch task| rabbit
    rabbit --> batch_queue
    batch_queue --> batch_worker

    batch_worker --> registry
    batch_worker --> handler
    batch_worker -->|save batch result| batch_table
    batch_worker -->|save A/B event| ab_table

    api -->|publish log event| rabbit
    batch_worker -->|publish log event| rabbit
    rabbit --> log_queue
    log_queue --> log_worker
    log_worker -->|save logs| logs_table

    api -->|read batch result| batch_table
Loading

Компоненты:

  • credit-default-api - Flask API, принимает HTTP-запросы;
  • rabbitmq - брокер сообщений;
  • batch-worker - читает batch-задачи из очереди и выполняет инференс;
  • log-worker - сохраняет события логирования в PostgreSQL;
  • postgres - хранит batch-результаты, логи и A/B-события;
  • models/credit_default_model_v1.joblib и models/credit_default_model_v2.joblib - сохраненные joblib-артефакты моделей.

Подробное описание архитектурных решений вынесено в файл:

ARCHITECTURE.md

4. Почему выбран Flask

В задании требуется реализовать сервис на Flask. Flask подходит для данного учебного проекта, потому что API имеет небольшое число эндпоинтов, а бизнес-логика вынесена в отдельные модули:

  • model_handler.py;
  • model_registry.py;
  • db.py;
  • queue_client.py.

Для production-like запуска используется gunicorn, так как встроенный Flask-сервер предназначен для разработки.

5. Монолит vs микросервисы

Для текущего проекта выбран монолитный API-контейнер + отдельные worker-сервисы, потому что:

  • модельная логика компактная;
  • проект проще воспроизвести локально;
  • API, workers и БД можно запустить одной командой через Docker Compose;
  • RabbitMQ уже отделяет прием запроса от фоновой обработки.

При дальнейшем масштабировании систему можно разделить на полноценные микросервисы: API Gateway, отдельный inference service, сервис batch-обработки, сервис логирования, мониторинг и feature store.

6. Обучение модели

Скрипт обучения:

python src/train_model.py \
  --data-path data/UCI_Credit_Card.csv \
  --model-output models/credit_default_model_v1.joblib \
  --metrics-output reports/train_metrics.json

Если датасет отсутствует, можно использовать загрузку:

python src/train_model.py --download-data

Скрипт выполняет:

  1. загрузку данных;
  2. базовую очистку;
  3. train/test split со стратификацией;
  4. построение sklearn Pipeline;
  5. подбор гиперпараметров RandomForestClassifier;
  6. оценку метрик;
  7. сохранение модели через joblib.

7. Метрики модели

Сохраненный артефакт модели содержит метрики на test-выборке.

Метрика Значение
ROC-AUC 0.775
Average Precision 0.549
F1 при пороге 0.50 0.543
Precision при пороге 0.50 0.499
Recall при пороге 0.50 0.595
Подобранный порог 0.47

Порог 0.47 повышает recall, но немного снижает test F1 относительно порога 0.50. В кредитном скоринге выбор порога должен учитывать бизнес-стоимость ошибок: false negative может означать выдачу кредита клиенту с высоким риском, а false positive - отказ потенциально надежному клиенту.

В текущей демонстрации model_v1 использует порог 0.47, а model_v2 использует порог 0.50. Это минимальная, но рабочая демонстрация маршрутизации разных версий модели через API и A/B-логирования в PostgreSQL.

8. Локальный запуск API без Docker

Установить зависимости:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Запустить API:

python -m app.api

Проверить:

curl http://localhost:5000/health

9. Запуск через Docker Compose

Запуск всей системы:

docker compose up --build

Остановить:

docker compose down

Полная очистка вместе с volume PostgreSQL:

docker compose down -v

Проверить контейнеры:

docker compose ps

10. API endpoints

10.1 Выбор версии модели через model_version

Версия модели передается в JSON body:

{
  "model_version": "model_v1",
  "data": {
    "client_id": "client_001",
    "LIMIT_BAL": 20000
  }
}

Если model_version не передан, используется версия по умолчанию:

model_v1

GET /health

Проверка работоспособности сервиса:

curl http://localhost:5000/health

Пример ответа:

{
  "default_model_version": "model_v1",
  "models": {
    "model_v1": true,
    "model_v2": true
  },
  "status": "healthy"
}

GET /model-info

Информация о модели по умолчанию:

curl http://localhost:5000/model-info

POST /predict

Синхронный инференс для одного объекта или небольшого batch:

curl -X POST http://localhost:5000/predict \
  -H "Content-Type: application/json" \
  -d '{
    "model_version": "model_v1",
    "data": {
      "client_id": "client_001",
      "LIMIT_BAL": 20000,
      "SEX": 2,
      "EDUCATION": 2,
      "MARRIAGE": 1,
      "AGE": 24,
      "PAY_0": 2,
      "PAY_2": 2,
      "PAY_3": -1,
      "PAY_4": -1,
      "PAY_5": -2,
      "PAY_6": -2,
      "BILL_AMT1": 3913,
      "BILL_AMT2": 3102,
      "BILL_AMT3": 689,
      "BILL_AMT4": 0,
      "BILL_AMT5": 0,
      "BILL_AMT6": 0,
      "PAY_AMT1": 0,
      "PAY_AMT2": 689,
      "PAY_AMT3": 0,
      "PAY_AMT4": 0,
      "PAY_AMT5": 0,
      "PAY_AMT6": 0
    }
  }'

Пример ответа:

{
  "model_version": "model_v1",
  "requested_model_version": "model_v1",
  "n_objects": 1,
  "results": [
    {
      "prediction": 1,
      "probability": 0.63,
      "threshold": 0.47,
      "label": "default"
    }
  ]
}

Для проверки второй версии модели достаточно заменить:

"model_version": "model_v2"

POST /predict-batch-async

Асинхронная batch-обработка через RabbitMQ:

curl -X POST http://localhost:5000/predict-batch-async \
  -H "Content-Type: application/json" \
  -d '{
    "model_version": "model_v2",
    "data": [
      {
        "client_id": "batch_client_001",
        "LIMIT_BAL": 20000,
        "SEX": 2,
        "EDUCATION": 2,
        "MARRIAGE": 1,
        "AGE": 24,
        "PAY_0": 2,
        "PAY_2": 2,
        "PAY_3": -1,
        "PAY_4": -1,
        "PAY_5": -2,
        "PAY_6": -2,
        "BILL_AMT1": 3913,
        "BILL_AMT2": 3102,
        "BILL_AMT3": 689,
        "BILL_AMT4": 0,
        "BILL_AMT5": 0,
        "BILL_AMT6": 0,
        "PAY_AMT1": 0,
        "PAY_AMT2": 689,
        "PAY_AMT3": 0,
        "PAY_AMT4": 0,
        "PAY_AMT5": 0,
        "PAY_AMT6": 0
      }
    ]
  }'

Пример ответа:

{
  "task_id": "uuid",
  "status": "queued",
  "requested_model_version": "model_v2",
  "n_objects": 1,
  "result_url": "/batch-result/uuid"
}

GET /batch-result/<task_id>

Получение результата batch-задачи:

curl http://localhost:5000/batch-result/<task_id>

Замените <task_id> на значение, полученное из ответа /predict-batch-async.

11. RabbitMQ

RabbitMQ Management UI:

http://localhost:15672

Логин и пароль:

guest / guest

Очереди:

  • batch_prediction_queue - задачи batch-предсказаний;
  • api_log_queue - события логирования.

RabbitMQ нужен для отделения приема HTTP-запроса от тяжелой фоновой обработки. API быстро возвращает task_id, а worker обрабатывает задачу независимо.

12. PostgreSQL

PostgreSQL хранит:

  • batch_predictions - статусы, входные данные и результаты batch-задач;
  • api_logs - события API и worker-процессов;
  • ab_test_events - события A/B-теста.

Подключение к БД:

docker exec -it postgres psql -U credit_user -d credit_default_db

Проверить batch-задачи:

SELECT task_id, status, n_objects, created_at, finished_at
FROM batch_predictions
ORDER BY created_at DESC
LIMIT 5;

Проверить логи:

SELECT event_type, created_at, payload
FROM api_logs
ORDER BY created_at DESC
LIMIT 10;

Проверить A/B-события:

SELECT
    id,
    client_id,
    group_name,
    model_version,
    prediction,
    probability,
    threshold,
    decision,
    created_at
FROM ab_test_events
ORDER BY created_at DESC
LIMIT 10;

Сравнить события по группам:

SELECT
    group_name,
    model_version,
    COUNT(*) AS n_events,
    AVG(probability) AS avg_probability,
    AVG(prediction) AS predicted_default_rate
FROM ab_test_events
GROUP BY group_name, model_version
ORDER BY group_name, model_version;

Если PostgreSQL уже запускался раньше, init_db.sql не выполнится повторно для существующего volume. Для пересоздания схемы:

docker compose down -v
docker compose up --build

13. Логирование и мониторинг

События API и worker-процессов отправляются в RabbitMQ и сохраняются в таблицу api_logs.

Примеры событий:

  • sync_prediction;
  • sync_prediction_failed;
  • batch_prediction_enqueued;
  • batch_prediction_started;
  • batch_prediction_finished;
  • batch_prediction_failed.

В production эти логи можно дополнительно отправлять в ELK/Opensearch, Grafana Loki или облачный мониторинг.

14. Тесты

Запуск тестов:

pytest

Тесты проверяют:

  • /health;
  • /model-info;
  • /predict;
  • /predict-batch-async;
  • /batch-result/<task_id>;
  • сохранение A/B-событий через мок PostgreSQL.

RabbitMQ и PostgreSQL в unit-тестах мокируются.

15. Демонстрация работы API

Скриншоты проверки API находятся в папке screenshots/.

Health check и информация о модели

Health check and model info

Prediction для model_v1

Prediction with model_v1

Prediction для model_v2

Prediction with model_v2

Асинхронная batch-обработка

Async batch prediction

16. MLOps-концепты и бизнес-метрики

DVC может использоваться для версионирования датасетов и артефактов модели.

MLflow может использоваться для отслеживания экспериментов: параметры модели, метрики, версии моделей и сравнение model_v1 / model_v2.

ONNX-ML может использоваться для конвертации sklearn-модели в переносимый и более быстрый формат инференса.

Gunicorn + NGINX в production-среде используются для надежного запуска WSGI-приложения, reverse proxy, TLS, ограничения размера запросов и балансировки нагрузки.

Бизнес-метрики для заказчика:

  • снижение ожидаемых финансовых потерь;
  • approval rate при заданном уровне риска;
  • доля обнаруженных дефолтов.

17. Docker Hub

Docker-образ API опубликован в Docker Hub:

docker.io/attractorset/credit-default-api:latest

Скачать образ:

docker pull docker.io/attractorset/credit-default-api:latest

Запустить только API-контейнер:

docker run -p 5000:5000 docker.io/attractorset/credit-default-api:latest

Однако для полной работы проекта рекомендуется использовать Docker Compose, потому что сервис состоит не только из API, но и из дополнительных компонентов:

  • rabbitmq;
  • postgres;
  • batch-worker;
  • log-worker.

Полный запуск проекта:

docker compose up --build

18. A/B-тестирование

План A/B-тестирования находится в файле:

ab_test_plan.md

Кратко:

  • model_v1 - контрольная группа control;
  • model_v2 - тестовая группа treatment;
  • версия модели передается через JSON-поле model_version;
  • события сохраняются в PostgreSQL в таблицу ab_test_events;
  • после появления фактического таргета actual_default можно сравнить Precision, Recall, F1-score и бизнес-метрики.

About

Production-like сервис машинного обучения для прогнозирования дефолта по кредитным картам, который охватывает полный цикл от сохранения модели до организации A/B-тестирования

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages