Проект по дисциплине «Внедрение моделей машинного обучения».
Цель проекта - разработать production-like сервис машинного обучения для прогнозирования дефолта клиента по кредитной карте. Проект покрывает полный цикл: обучение модели, сохранение артефакта, API-инференс, контейнеризация, асинхронная batch-обработка, логирование, хранение результатов в БД и практическую демонстрацию A/B-тестирования.
Домен: финансы / кредитный скоринг.
Задача: бинарная классификация клиента:
0- дефолт в следующем месяце не ожидается;1- ожидается дефолт в следующем месяце.
Датасет: Default of Credit Card Clients Dataset.
Таргет: default.payment.next.month.
Модель может использоваться как вспомогательный компонент risk-based decision making: например, для приоритизации ручной проверки заявок, настройки лимитов или выделения клиентов с повышенным риском.
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
В проекте используется расширенная монолитная архитектура с 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
Компоненты:
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
В задании требуется реализовать сервис на Flask. Flask подходит для данного учебного проекта, потому что API имеет небольшое число эндпоинтов, а бизнес-логика вынесена в отдельные модули:
model_handler.py;model_registry.py;db.py;queue_client.py.
Для production-like запуска используется gunicorn, так как встроенный Flask-сервер предназначен для разработки.
Для текущего проекта выбран монолитный API-контейнер + отдельные worker-сервисы, потому что:
- модельная логика компактная;
- проект проще воспроизвести локально;
- API, workers и БД можно запустить одной командой через Docker Compose;
- RabbitMQ уже отделяет прием запроса от фоновой обработки.
При дальнейшем масштабировании систему можно разделить на полноценные микросервисы: API Gateway, отдельный inference service, сервис batch-обработки, сервис логирования, мониторинг и feature store.
Скрипт обучения:
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Скрипт выполняет:
- загрузку данных;
- базовую очистку;
- train/test split со стратификацией;
- построение
sklearn Pipeline; - подбор гиперпараметров
RandomForestClassifier; - оценку метрик;
- сохранение модели через
joblib.
Сохраненный артефакт модели содержит метрики на 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.
Установить зависимости:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtЗапустить API:
python -m app.apiПроверить:
curl http://localhost:5000/healthЗапуск всей системы:
docker compose up --buildОстановить:
docker compose downПолная очистка вместе с volume PostgreSQL:
docker compose down -vПроверить контейнеры:
docker compose psВерсия модели передается в JSON body:
{
"model_version": "model_v1",
"data": {
"client_id": "client_001",
"LIMIT_BAL": 20000
}
}Если model_version не передан, используется версия по умолчанию:
model_v1
Проверка работоспособности сервиса:
curl http://localhost:5000/healthПример ответа:
{
"default_model_version": "model_v1",
"models": {
"model_v1": true,
"model_v2": true
},
"status": "healthy"
}Информация о модели по умолчанию:
curl http://localhost:5000/model-infoСинхронный инференс для одного объекта или небольшого 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"Асинхронная 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"
}Получение результата batch-задачи:
curl http://localhost:5000/batch-result/<task_id>Замените <task_id> на значение, полученное из ответа /predict-batch-async.
RabbitMQ Management UI:
http://localhost:15672
Логин и пароль:
guest / guest
Очереди:
batch_prediction_queue- задачи batch-предсказаний;api_log_queue- события логирования.
RabbitMQ нужен для отделения приема HTTP-запроса от тяжелой фоновой обработки. API быстро возвращает task_id, а worker обрабатывает задачу независимо.
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События 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 или облачный мониторинг.
Запуск тестов:
pytestТесты проверяют:
/health;/model-info;/predict;/predict-batch-async;/batch-result/<task_id>;- сохранение A/B-событий через мок PostgreSQL.
RabbitMQ и PostgreSQL в unit-тестах мокируются.
Скриншоты проверки API находятся в папке screenshots/.
DVC может использоваться для версионирования датасетов и артефактов модели.
MLflow может использоваться для отслеживания экспериментов: параметры модели, метрики, версии моделей и сравнение model_v1 / model_v2.
ONNX-ML может использоваться для конвертации sklearn-модели в переносимый и более быстрый формат инференса.
Gunicorn + NGINX в production-среде используются для надежного запуска WSGI-приложения, reverse proxy, TLS, ограничения размера запросов и балансировки нагрузки.
Бизнес-метрики для заказчика:
- снижение ожидаемых финансовых потерь;
- approval rate при заданном уровне риска;
- доля обнаруженных дефолтов.
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План 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 и бизнес-метрики.



