Уроки 6-9: autolog, hyperparam sweep, grid search, serving + MLproject, walkthrough
This commit is contained in:
@@ -0,0 +1,516 @@
|
||||
# 🔍 Экскурс по коду — разбор каждого файла
|
||||
|
||||
Полный разбор всех файлов проекта с пояснениями, что и почему.
|
||||
|
||||
---
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
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: перебор гиперпараметров (вручную)
|
||||
├── grid_search_cv.py # Урок 8: GridSearchCV + autolog
|
||||
└── serve_model.py # Урок 9: REST API сервер модели
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 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` — видна ли зависимость
|
||||
|
||||
---
|
||||
|
||||
## 📄 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 — детали.
|
||||
|
||||
---
|
||||
|
||||
## 📄 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) даёт больше контроля — свои эндпоинты, логика,
|
||||
демо-эндпоинт с случайным образом.
|
||||
|
||||
---
|
||||
|
||||
## 📄 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` |
|
||||
Reference in New Issue
Block a user