- recall_macro/recall_weighted во всех скриптах с метриками (train_simple, train_gpu, autolog_demo, hyperparam_sweep, grid_search_cv, load_and_predict, compare_runs) - grid_search_cv: multi-metric scoring (accuracy + recall_macro) - register_model: выбор лучшей модели по recall (--metric) - MLflow warnings: name вместо artifact_path, сигнатура модели, numpy input_example для pytorch, подавление env-var INFO - start_ui.sh: порт 5555 + file-store backend (фикс: UI не показывал эксперименты, т.к. скрипты писали в mlruns/, а сервер читал sqlite) - setup_server.sh: развилка macOS/Linux + проверка MPS - train_gpu: get_device CUDA→MPS→CPU, num_workers=0 на macOS - AGENTS.md: гид для агента + саморегламент обновления - README/CODE_WALKTHROUGH: выровнена нумерация уроков 8/9 - .gitignore: +.DS_Store - удалён scripts/setup_git.sh
517 lines
20 KiB
Markdown
517 lines
20 KiB
Markdown
# 🔍 Экскурс по коду — разбор каждого файла
|
||
|
||
Полный разбор всех файлов проекта с пояснениями, что и почему.
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
mlflow-practice/
|
||
├── MLproject # Формат MLflow Projects (воспроизводимость)
|
||
├── python_env.yaml # Окружение для MLproject
|
||
├── requirements.txt # Зависимости
|
||
├── setup_server.sh # Установка на GPU-сервере
|
||
├── start_ui.sh # Запуск MLflow Tracking Server
|
||
├── scripts/
|
||
│ └── setup_git.sh # Настройка Git (HTTPS + токен)
|
||
└── src/
|
||
├── train_simple.py # Урок 1: sklearn + Tracking (ручное логирование)
|
||
├── train_gpu.py # Урок 2: PyTorch CNN на GPU + логирование по эпохам
|
||
├── register_model.py # Урок 3: Model Registry (версии, стадии)
|
||
├── load_and_predict.py # Урок 4: загрузка модели + предсказание
|
||
├── compare_runs.py # Урок 5: сравнение запусков через API
|
||
├── autolog_demo.py # Урок 6: autolog (автоматическое логирование)
|
||
├── hyperparam_sweep.py # Урок 7: перебор гиперпараметров (вручную)
|
||
├── serve_model.py # Урок 8: REST API сервер модели
|
||
└── grid_search_cv.py # Урок 9: GridSearchCV + autolog
|
||
```
|
||
|
||
---
|
||
|
||
## 📄 train_simple.py — основы MLflow Tracking
|
||
|
||
Это **главный файл для понимания**. Здесь показаны все базовые операции MLflow.
|
||
|
||
### Импорты
|
||
|
||
```python
|
||
import mlflow
|
||
import mlflow.sklearn
|
||
```
|
||
|
||
`mlflow` — ядро (start_run, log_param, log_metric, log_artifact).
|
||
`mlflow.sklearn` — flavor для scikit-learn моделей (save_model, load_model).
|
||
|
||
### Задание эксперимента
|
||
|
||
```python
|
||
mlflow.set_experiment("digits_classification")
|
||
```
|
||
|
||
Эксперимент — это **контейнер для связанных запусков (runs)**.
|
||
Если эксперимент не существует — MLflow создаст его автоматически.
|
||
Все runs с одинаковым именем эксперимента группируются вместе в UI.
|
||
|
||
### Контекстный менеджер run
|
||
|
||
```python
|
||
with mlflow.start_run(run_name="rf_100_8") as run:
|
||
```
|
||
|
||
`start_run()` начинает новый запуск. Контекстный менеджер (`with`)
|
||
гарантирует, что run закроется даже при ошибке — всё сохранится.
|
||
`run.info.run_id` — уникальный ID запуска, через него можно к нему вернуться.
|
||
|
||
### Логирование параметров
|
||
|
||
```python
|
||
mlflow.log_param("n_estimators", args.n_estimators)
|
||
mlflow.log_param("max_depth", args.max_depth)
|
||
```
|
||
|
||
Параметры логируются **ДО или ВО ВРЕМЯ обучения**. Это то, что вы
|
||
выбрали — гиперпараметры модели. Каждый параметр — строка/число.
|
||
В UI параметры показываются в таблице запусков как колонки.
|
||
|
||
### Логирование метрик
|
||
|
||
```python
|
||
mlflow.log_metric("accuracy", acc)
|
||
```
|
||
|
||
Метрики — числа, которые оценивают качество. Главное отличие от
|
||
параметров: метрики могут логироваться **несколько раз** с разными
|
||
шагами (`step=N`), образуя график:
|
||
|
||
```python
|
||
for i, tree in enumerate(model.estimators_):
|
||
tree_acc = accuracy_score(y_test, tree.predict(X_test))
|
||
mlflow.log_metric("per_tree_accuracy", tree_acc, step=i)
|
||
```
|
||
|
||
В UI это покажется как линейный график — как accuracy растёт
|
||
по мере добавления деревьев в RandomForest.
|
||
|
||
### Логирование артефактов
|
||
|
||
```python
|
||
mlflow.log_artifact("artifacts/confusion_matrix.png")
|
||
```
|
||
|
||
Артефакт — **любой файл**: картинка, текст, JSON, модель. MLflow
|
||
копирует файл в хранилище артефактов (папка `./artifacts/` или S3).
|
||
В UI артефакты можно просматривать прямо в браузере.
|
||
|
||
### Логирование модели
|
||
|
||
```python
|
||
mlflow.sklearn.log_model(model, artifact_path="model")
|
||
```
|
||
|
||
Модель — это особый артефакт. MLflow сохраняет её в стандартном
|
||
формате с файлом `MLmodel` (метаданные: flavor, версия, сигнатура).
|
||
Позже её можно загрузить по `run_id` или зарегистрировать в Registry.
|
||
|
||
### Теги
|
||
|
||
```python
|
||
mlflow.set_tag("model_type", "RandomForest")
|
||
mlflow.set_tag("author", "practice")
|
||
```
|
||
|
||
Теги — произвольные метки для фильтрации и поиска. В отличие от
|
||
параметров, теги можно менять после завершения run.
|
||
|
||
---
|
||
|
||
## 📄 train_gpu.py — PyTorch + GPU + логирование по эпохам
|
||
|
||
### Определение устройства
|
||
|
||
```python
|
||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||
```
|
||
|
||
Код **GPU-aware**: если есть CUDA — обучение на 3090, иначе на CPU.
|
||
Всё переносится на устройство: `model.to(device)`, `data.to(device)`.
|
||
|
||
### Логирование параметров окружения
|
||
|
||
```python
|
||
mlflow.log_param("device", str(device))
|
||
mlflow.log_param("gpu_name", gpu_name)
|
||
```
|
||
|
||
Полезно: в UI видно, на каком GPU обучалась модель. Если запускаете
|
||
на разных машинах — сразу видно различия.
|
||
|
||
### Логирование метрик по эпохам
|
||
|
||
```python
|
||
mlflow.log_metric("train_loss", avg_loss, step=epoch)
|
||
mlflow.log_metric("test_accuracy", acc, step=epoch)
|
||
```
|
||
|
||
`step=epoch` — ключевой момент. Каждая метрика логируется с номером
|
||
шага. В UI это строит **график learning curve** — как loss падает, а
|
||
accuracy растёт от эпохи к эпохе.
|
||
|
||
### Сохранение графика как артефакта
|
||
|
||
```python
|
||
fig.savefig("artifacts/training_curve.png")
|
||
mlflow.log_artifact(plot_path)
|
||
```
|
||
|
||
Matplotlib-график сохраняется в файл и логируется. В UI его можно
|
||
открыть прямо в браузере — артефакты отображаются превью.
|
||
|
||
### Логирование PyTorch-модели
|
||
|
||
```python
|
||
mlflow.pytorch.log_model(
|
||
model,
|
||
artifact_path="model",
|
||
serialization_format="pickle",
|
||
input_example=torch.randn(1, 1, 28, 28).to(device),
|
||
)
|
||
```
|
||
|
||
`serialization_format="pickle"` — формат сохранения (см. урок 2).
|
||
`input_example` — пример входа, нужен для построения сигнатуры модели.
|
||
MLflow сохраняет модель в формате, совместимом с `mlflow.pytorch.load_model()`.
|
||
|
||
---
|
||
|
||
## 📄 register_model.py — Model Registry
|
||
|
||
### Поиск лучшего запуска
|
||
|
||
```python
|
||
runs = client.search_runs(
|
||
experiment_ids=[exp.experiment_id],
|
||
order_by=["metrics.accuracy DESC"],
|
||
max_results=1,
|
||
)
|
||
```
|
||
|
||
MlflowClient — программный доступ к API Tracking Server.
|
||
`search_runs` с `order_by` — сортировка по метрике. Так можно найти
|
||
лучший run без UI — полностью программно.
|
||
|
||
### Регистрация модели
|
||
|
||
```python
|
||
model_uri = f"runs:/{run_id}/model"
|
||
result = mlflow.register_model(model_uri=model_uri, name="digits_rf_model")
|
||
```
|
||
|
||
`model_uri` — указывает на модель внутри конкретного run.
|
||
`register_model` создаёт новую версию в Model Registry.
|
||
`result.version` — номер версии (1, 2, 3...).
|
||
|
||
### Управление стадиями
|
||
|
||
```python
|
||
client.transition_model_version_stage(
|
||
name="digits_rf_model",
|
||
version=result.version,
|
||
stage="Production",
|
||
archive_existing_versions=True,
|
||
)
|
||
```
|
||
|
||
`archive_existing_versions=True` — автоматически переводит все
|
||
предыдущие Production-версии в Archived. Так в Production всегда
|
||
только одна версия.
|
||
|
||
### Жизненный цикл модели
|
||
|
||
```
|
||
None → Staging → Production → Archived
|
||
↑ ↓
|
||
└──────────────────┘
|
||
(можно вернуть обратно)
|
||
```
|
||
|
||
- **Staging** — тестирование, ещё не готова
|
||
- **Production** — используется в приложении
|
||
- **Archived** — устарела, но сохранена для истории
|
||
|
||
---
|
||
|
||
## 📄 load_and_predict.py — загрузка модели
|
||
|
||
### Загрузка по имени и стадии
|
||
|
||
```python
|
||
model = mlflow.sklearn.load_model("models:/digits_rf_model/Production")
|
||
```
|
||
|
||
`models:/` — URI для Model Registry. Формат: `models:/{name}/{stage}`
|
||
или `models:/{name}/{version}`. Загружает последнюю версию в стадии
|
||
Production. Если версию перевели в Archived — эта загрузка сломается,
|
||
что правильно — так вы узнаёте о проблеме сразу.
|
||
|
||
### Отличие от загрузки по run_id
|
||
|
||
```python
|
||
# По run_id (привязка к конкретному запуску):
|
||
model = mlflow.sklearn.load_model("runs:/abc123/model")
|
||
|
||
# По registry (привязка к имени + стадии):
|
||
model = mlflow.sklearn.load_model("models:/digits_rf_model/Production")
|
||
```
|
||
|
||
Registry — уровень абстракции выше. Код не меняется при смене версии
|
||
модели, только стадия в UI.
|
||
|
||
---
|
||
|
||
## 📄 compare_runs.py — сравнение через API
|
||
|
||
```python
|
||
runs = client.search_runs(
|
||
experiment_ids=[exp.experiment_id],
|
||
order_by=["metrics.accuracy DESC"],
|
||
max_results=10,
|
||
)
|
||
```
|
||
|
||
Программный аналог таблицы в UI. Каждый `run` содержит:
|
||
- `run.info.run_id` — ID
|
||
- `run.data.params` — словарь параметров
|
||
- `run.data.metrics` — словарь метрик
|
||
- `run.data.tags` — теги
|
||
|
||
Это позволяет автоматизировать: найти лучший, сравнить, отсеять.
|
||
|
||
---
|
||
|
||
## 📄 autolog_demo.py — автоматическое логирование
|
||
|
||
### Ключевая строка
|
||
|
||
```python
|
||
mlflow.sklearn.autolog(
|
||
log_models=True,
|
||
log_datasets=True,
|
||
max_tuning_runs=10,
|
||
)
|
||
```
|
||
|
||
После вызова `autolog()` — **больше не нужно** писать `log_param`,
|
||
`log_metric`, `log_model` вручную. MLflow перехватывает вызовы sklearn
|
||
и логирует всё автоматически:
|
||
|
||
| Что autolog перехватывает | Что логируется |
|
||
|---|---|
|
||
| `model.fit()` | все параметры конструктора модели |
|
||
| Обучение | `training_accuracy`, `training_log_loss` |
|
||
| Модель | сохраняется в `artifacts/model/` |
|
||
| Датасет | профиль (размер, типы признаков) |
|
||
| GridSearchCV | каждый под-вариант как отдельный run |
|
||
|
||
### Сравнение: ручное vs autolog
|
||
|
||
```python
|
||
# Ручное (train_simple.py):
|
||
mlflow.log_param("n_estimators", 100)
|
||
mlflow.log_param("max_depth", 8)
|
||
mlflow.log_metric("accuracy", acc)
|
||
mlflow.sklearn.log_model(model, "model")
|
||
|
||
# Autolog (autolog_demo.py):
|
||
mlflow.sklearn.autolog() # одна строка!
|
||
model.fit(X_train, y_train) # всё залогируется само
|
||
```
|
||
|
||
**Когда использовать autolog**: быстрые эксперименты, прототипы.
|
||
**Когда ручное логирование**: нужен контроль над тем, что попадает
|
||
в MLflow, кастомные метрики, специфичные артефакты.
|
||
|
||
---
|
||
|
||
## 📄 hyperparam_sweep.py — перебор гиперпараметров
|
||
|
||
### Генерация комбинаций
|
||
|
||
```python
|
||
all_combos = list(itertools.product(
|
||
param_grid["n_estimators"],
|
||
param_grid["max_depth"],
|
||
...
|
||
))
|
||
```
|
||
|
||
`itertools.product` — декартово произведение всех параметров.
|
||
5 × 6 × 3 × 3 × 3 = 810 возможных комбинаций. Берём случайные
|
||
`max_combos` из них — для скорости.
|
||
|
||
### Почему случайные, а не все 810?
|
||
|
||
Полный перебор (Grid Search) = 810 × 5 (CV) = 4050 обучений.
|
||
На 3090 это реально, но:
|
||
- Random Search часто находит хорошие параметры быстрее
|
||
- MLflow хранит каждый run — 810 runs засоряют UI
|
||
- 20-50 случайных обычно достаточно для практики
|
||
|
||
### Кросс-валидация
|
||
|
||
```python
|
||
cv_scores = cross_val_score(model, X_train, y_train, cv=5, scoring="accuracy")
|
||
mlflow.log_metric("cv_mean_accuracy", cv_scores.mean())
|
||
mlflow.log_metric("cv_std_accuracy", cv_scores.std())
|
||
```
|
||
|
||
CV делит train на 5 фолдов, обучает 5 раз, усредняет.
|
||
`cv_std` — разброс между фолдами. Если `std` высокий — модель
|
||
нестабильна (зависит от разбиения данных).
|
||
|
||
### Что искать в UI
|
||
|
||
1. Отсортировать по `test_accuracy` DESC
|
||
2. Сравнить `cv_mean_accuracy` и `test_accuracy` — если CV сильно
|
||
выше теста → переобучение (overfitting)
|
||
3. Высокий `cv_std_accuracy` → нестабильная модель
|
||
4. Scatter plot: `n_estimators` vs `accuracy` — видна ли зависимость
|
||
|
||
---
|
||
|
||
## 📄 serve_model.py — REST API
|
||
|
||
### Загрузка модели при старте
|
||
|
||
```python
|
||
model = mlflow.sklearn.load_model(f"models:/{model_name}/{stage}")
|
||
```
|
||
|
||
Загружается **один раз** при создании Flask-приложения, не на каждый
|
||
запрос. Это важно для производительности.
|
||
|
||
### Эндпоинты
|
||
|
||
| Эндпоинт | Метод | Что делает |
|
||
|---|---|---|
|
||
| `/health` | GET | Проверка состояния ("жив" ли сервер) |
|
||
| `/info` | GET | Информация о модели (признаки, классы) |
|
||
| `/predict` | POST | Предсказание для переданных данных |
|
||
| `/predict_random` | GET | Случайный образец из digits + предсказание |
|
||
|
||
### Формат запроса /predict
|
||
|
||
```json
|
||
{
|
||
"instances": [
|
||
[0, 0, 0, ..., 16, 8, 0, ...], // 64 числа — 8×8 пикселей
|
||
[0, 0, 12, ..., 0, 0, 0, ...] // можно несколько образцов
|
||
]
|
||
}
|
||
```
|
||
|
||
### predict_proba
|
||
|
||
```python
|
||
probabilities = model.predict_proba(instances)
|
||
```
|
||
|
||
RandomForest поддерживает `predict_proba` — вероятности для каждого
|
||
класса. В ответе: предсказание + вероятности + confidence.
|
||
|
||
### Альтернатива — встроенный MLflow server
|
||
|
||
```bash
|
||
mlflow models serve -m models:/digits_rf_model/Production -p 5001
|
||
```
|
||
|
||
MLflow сам поднимает сервер с стандартным API. Кастомный скрипт
|
||
(serve_model.py) даёт больше контроля — свои эндпоинты, логика,
|
||
демо-эндпоинт с случайным образом.
|
||
|
||
---
|
||
|
||
## 📄 grid_search_cv.py — GridSearchCV + autolog
|
||
|
||
```python
|
||
mlflow.sklearn.autolog(max_tuning_runs=20)
|
||
...
|
||
grid = GridSearchCV(estimator=..., param_grid=..., cv=5)
|
||
grid.fit(X_train, y_train)
|
||
```
|
||
|
||
MLflow autolog + GridSearchCV = **дерево запусков**:
|
||
- **Parent run** (`gridsearch_rf`) — главный, содержит лучшие параметры
|
||
- **Child runs** — каждая комбинация гиперпараметров отдельный run
|
||
|
||
В UI это видно как иерархия. Parent показывает итог, children — детали.
|
||
|
||
---
|
||
|
||
## 📄 MLproject — воспроизводимость
|
||
|
||
```yaml
|
||
name: mlflow-practice
|
||
entry_points:
|
||
main:
|
||
parameters:
|
||
n_estimators: {type: int, default: 100}
|
||
command: "python src/train_simple.py --n-estimators {n_estimators}"
|
||
```
|
||
|
||
`MLproject` — декларативное описание проекта. MLflow может запустить
|
||
его одной командой:
|
||
|
||
```bash
|
||
mlflow run . -P n_estimators=200 -P max_depth=12
|
||
```
|
||
|
||
При этом MLflow:
|
||
1. Читает `python_env.yaml` → создаёт виртуальное окружение
|
||
2. Устанавливает зависимости
|
||
3. Запускает команду из entry_point с параметрами
|
||
4. Логирует всё в tracking server
|
||
|
||
Это гарантирует воспроизводимость: любой человек на любой машине
|
||
может запустить ваш эксперимент с тем же результатом.
|
||
|
||
---
|
||
|
||
## 🗂️ Как файлы связаны между собой
|
||
|
||
```
|
||
train_simple.py ──→ логирует run ──→ register_model.py ──→ load_and_predict.py
|
||
│ │
|
||
│ ↓
|
||
│ Model Registry
|
||
│ │
|
||
↓ ↓
|
||
compare_runs.py serve_model.py
|
||
(ищет лучший run) (загружает из Registry)
|
||
|
||
autolog_demo.py ──→ autolog (альтернатива ручному логированию)
|
||
|
||
hyperparam_sweep.py ──→ много runs (перебор)
|
||
grid_search_cv.py ──→ parent + child runs (autolog + GridSearch)
|
||
|
||
MLproject ──→ оборачивает всё в воспроизводимый формат
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 Ключевые концепции MLflow — шпаргалка
|
||
|
||
| Концепция | Что это | Где используется |
|
||
|---|---|---|
|
||
| **Experiment** | Группа связанных runs | `set_experiment()` |
|
||
| **Run** | Один запуск обучения | `start_run()` |
|
||
| **Param** | Гиперпараметр (вход) | `log_param()` |
|
||
| **Metric** | Метрика (результат), может быть графиком | `log_metric(step=N)` |
|
||
| **Artifact** | Любой файл (картинка, модель) | `log_artifact()` |
|
||
| **Tag** | Метка для поиска/фильтрации | `set_tag()` |
|
||
| **Flavor** | Формат модели (sklearn, pytorch) | `mlflow.sklearn`, `mlflow.pytorch` |
|
||
| **Model Registry** | Реестр версий моделей | `register_model()`, `transition_stage()` |
|
||
| **Autolog** | Автоматическое логирование | `mlflow.sklearn.autolog()` |
|
||
| **MLproject** | Воспроизводимый запуск | `mlflow run .` |
|
||
| **Model URI** | Ссылка на модель | `models:/name/Production`, `runs:/id/model` |
|