Уроки 6-9: autolog, hyperparam sweep, grid search, serving + MLproject, walkthrough
This commit is contained in:
@@ -6,3 +6,6 @@ mlruns/
|
|||||||
artifacts/*
|
artifacts/*
|
||||||
data/MNIST/
|
data/MNIST/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Flask serve
|
||||||
|
instance/
|
||||||
|
|||||||
@@ -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` |
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# MLflow Project — формат воспроизводимых экспериментов
|
||||||
|
# ========================================================
|
||||||
|
# Запуск:
|
||||||
|
# mlflow run . -P n_estimators=100 -P max_depth=8
|
||||||
|
# mlflow run . -P n_estimators=200 -P max_depth=12 --experiment-name digits_project
|
||||||
|
#
|
||||||
|
# MLflow автоматически:
|
||||||
|
# 1. Создаст изолированное окружение из conda.yaml (или requirements.txt)
|
||||||
|
# 2. Запустит entry point с указанными параметрами
|
||||||
|
# 3. Логирует всё в MLflow tracking
|
||||||
|
|
||||||
|
name: mlflow-practice
|
||||||
|
|
||||||
|
python_env: python_env.yaml
|
||||||
|
|
||||||
|
entry_points:
|
||||||
|
main:
|
||||||
|
parameters:
|
||||||
|
n_estimators: {type: int, default: 100}
|
||||||
|
max_depth: {type: int, default: 8}
|
||||||
|
command: "python src/train_simple.py --n-estimators {n_estimators} --max-depth {max_depth}"
|
||||||
|
|
||||||
|
gpu:
|
||||||
|
parameters:
|
||||||
|
epochs: {type: int, default: 10}
|
||||||
|
batch_size: {type: int, default: 256}
|
||||||
|
lr: {type: float, default: 0.001}
|
||||||
|
command: "python src/train_gpu.py --epochs {epochs} --batch-size {batch_size} --lr {lr}"
|
||||||
|
|
||||||
|
sweep:
|
||||||
|
parameters:
|
||||||
|
max_combos: {type: int, default: 20}
|
||||||
|
command: "python src/hyperparam_sweep.py --max-combos {max_combos}"
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
# 🧪 MLflow Practice — практический проект
|
# 🧪 MLflow Practice — практический проект
|
||||||
|
|
||||||
Проект для изучения MLflow на практике: от логирования экспериментов
|
Проект для изучения MLflow на практике: от логирования экспериментов
|
||||||
до Model Registry и загрузки моделей. Поддерживает **GPU (RTX 3090)** и CPU.
|
до Model Registry, Model Serving и воспроизводимых запусков.
|
||||||
|
Поддерживает **GPU (RTX 3090)** и CPU.
|
||||||
|
|
||||||
|
📖 **[CODE_WALKTHROUGH.md](CODE_WALKTHROUGH.md)** — подробный разбор каждого файла.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,113 +12,128 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
mlflow-practice/
|
mlflow-practice/
|
||||||
├── requirements.txt # зависимости
|
├── MLproject # Формат MLflow Projects (воспроизводимость)
|
||||||
├── start_ui.sh # запуск MLflow Tracking Server
|
├── python_env.yaml # Окружение для MLproject
|
||||||
├── README.md # этот файл
|
├── requirements.txt # зависимости
|
||||||
├── data/ # датасеты (MNIST скачается автоматически)
|
├── setup_server.sh # установка на GPU-сервере
|
||||||
├── artifacts/ # графики, отчёты
|
├── start_ui.sh # запуск MLflow Tracking Server
|
||||||
|
├── CODE_WALKTHROUGH.md # экскурс по коду
|
||||||
|
├── scripts/
|
||||||
|
│ └── setup_git.sh # настройка Git (HTTPS + токен)
|
||||||
└── src/
|
└── src/
|
||||||
├── train_simple.py # Урок 1: scikit-learn + Tracking
|
├── train_simple.py # Урок 1: sklearn + Tracking (ручное)
|
||||||
├── train_gpu.py # Урок 2: PyTorch CNN на GPU (3090)
|
├── train_gpu.py # Урок 2: PyTorch CNN на GPU
|
||||||
├── register_model.py # Урок 3: Model Registry
|
├── register_model.py # Урок 3: Model Registry
|
||||||
├── load_and_predict.py # Урок 4: загрузка и предсказание
|
├── load_and_predict.py # Урок 4: загрузка + предсказание
|
||||||
└── compare_runs.py # Урок 5: сравнение запусков
|
├── 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 сервер
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
### 1. Установка зависимостей
|
### 1. Установка
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd mlflow-practice
|
# На GPU-сервере:
|
||||||
|
bash setup_server.sh
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
# Базовые зависимости (CPU — работает везде)
|
# PyTorch под CUDA:
|
||||||
pip install mlflow scikit-learn pandas numpy matplotlib
|
|
||||||
|
|
||||||
# Для GPU-урока (RTX 3090) — установите PyTorch под вашу CUDA:
|
|
||||||
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
||||||
|
pip install flask # для serve_model.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Запуск MLflow UI (в отдельном терминале)
|
### 2. Запуск MLflow UI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start_ui.sh
|
./start_ui.sh
|
||||||
# → откройте http://localhost:5000
|
# → http://10.0.0.7:5000
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Урок 1 — scikit-learn (быстрый, CPU)
|
### 3. Уроки по порядку
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python src/train_simple.py --n-estimators 50 --max-depth 5
|
# Урок 1 — scikit-learn (CPU, ~5 сек)
|
||||||
python src/train_simple.py --n-estimators 100 --max-depth 8
|
python src/train_simple.py --n-estimators 100 --max-depth 8
|
||||||
python src/train_simple.py --n-estimators 200 --max-depth 12
|
python src/train_simple.py --n-estimators 200 --max-depth 12
|
||||||
```
|
python src/train_simple.py --n-estimators 50 --max-depth 5
|
||||||
|
|
||||||
Сравните результаты в UI → вкладка **Experiments → digits_classification**.
|
# Урок 2 — PyTorch CNN на 3090 (~1-2 мин)
|
||||||
|
|
||||||
### 4. Урок 2 — PyTorch CNN на 3090
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# На GPU — быстро (3090 справится за пару минут)
|
|
||||||
python src/train_gpu.py --epochs 10 --batch-size 256 --lr 0.001
|
python src/train_gpu.py --epochs 10 --batch-size 256 --lr 0.001
|
||||||
|
|
||||||
# На CPU — медленно, уменьшите эпохи
|
# Урок 3 — Регистрация лучшей модели
|
||||||
python src/train_gpu.py --epochs 2 --batch-size 64 --lr 0.01
|
|
||||||
```
|
|
||||||
|
|
||||||
В UI посмотрите графики `train_loss` и `test_accuracy` по эпохам.
|
|
||||||
|
|
||||||
### 5. Урок 3 — Model Registry
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python src/register_model.py --experiment digits_classification
|
python src/register_model.py --experiment digits_classification
|
||||||
```
|
|
||||||
|
|
||||||
В UI → вкладка **Models** появится модель `digits_rf_model` со стадией **Production**.
|
# Урок 4 — Загрузка и предсказание
|
||||||
|
|
||||||
### 6. Урок 4 — Загрузка и предсказание
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python src/load_and_predict.py
|
python src/load_and_predict.py
|
||||||
|
|
||||||
|
# Урок 5 — Сравнение запусков
|
||||||
|
python src/compare_runs.py --experiment digits_classification --top 10
|
||||||
|
|
||||||
|
# Урок 6 — Autolog (автоматическое логирование)
|
||||||
|
python src/autolog_demo.py
|
||||||
|
|
||||||
|
# Урок 7 — Hyperparameter sweep (20 комбинаций)
|
||||||
|
python src/hyperparam_sweep.py --max-combos 20
|
||||||
|
|
||||||
|
# Урок 8 — GridSearchCV + autolog (parent/child runs)
|
||||||
|
python src/grid_search_cv.py
|
||||||
|
|
||||||
|
# Урок 9 — Model serving (REST API)
|
||||||
|
python src/serve_model.py --port 5001
|
||||||
|
# → curl http://localhost:5001/predict_random
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. Урок 5 — Сравнение запусков
|
### 4. MLflow Projects (воспроизводимый запуск)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python src/compare_runs.py --experiment digits_classification --top 10
|
# Запуск через MLproject — MLflow сам создаст окружение
|
||||||
|
mlflow run . -P n_estimators=200 -P max_depth=12
|
||||||
|
mlflow run . -e gpu -P epochs=10 -P batch_size=256 -P lr=0.001
|
||||||
|
mlflow run . -e sweep -P max_combos=30
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🖥️ Специфика RTX 3090
|
## 📚 Что изучает каждый урок
|
||||||
|
|
||||||
|
| Урок | Концепция MLflow | На чём |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | **Tracking**: params, metrics, artifacts, tags | RandomForest (digits) |
|
||||||
|
| 2 | **Tracking + GPU**: логирование по эпохам, learning curves | PyTorch CNN (MNIST) на 3090 |
|
||||||
|
| 3 | **Model Registry**: версии, стадии Staging→Production | лучшая модель из Урока 1 |
|
||||||
|
| 4 | **Model Loading**: models:/name/stage | предсказание |
|
||||||
|
| 5 | **API**: search_runs, order_by | сортировка по accuracy |
|
||||||
|
| 6 | **Autolog**: одна строка вместо ручного логирования | GradientBoosting (wine) |
|
||||||
|
| 7 | **Hyperparameter sweep**: itertools + random search | 20+ комбинаций RandomForest |
|
||||||
|
| 8 | **GridSearchCV + autolog**: parent/child runs | 18 комбинаций × 5 фолдов |
|
||||||
|
| 9 | **Model serving**: Flask REST API | REST эндпоинты для модели |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ RTX 3090
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| VRAM | 24 GB GDDR6X |
|
| VRAM | 24 GB GDDR6X |
|
||||||
| CUDA cores | 10496 |
|
|
||||||
| Рекомендуемый batch-size | 256–512 для MNIST |
|
| Рекомендуемый batch-size | 256–512 для MNIST |
|
||||||
| CUDA версия | 12.1 (рекомендуется) |
|
| CUDA | 12.1 |
|
||||||
|
| MNIST 10 epochs | ~1-2 минуты |
|
||||||
|
|
||||||
Код в `train_gpu.py` **автоматически** определяет CUDA:
|
Код GPU-aware — автоматически выбирает CUDA если доступна.
|
||||||
|
|
||||||
```python
|
|
||||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
||||||
```
|
|
||||||
|
|
||||||
На 3090 обучение MNIST (10 epochs) займёт ~1-2 минуты.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💡 Эксперименты для самостоятельной работы
|
## 💡 Эксперименты для самостоятельной работы
|
||||||
|
|
||||||
1. **Подбор гиперпараметров**: запустите `train_simple.py` с разными
|
1. **Подбор гиперпараметров** — запустите sweep с `--max-combos 50`
|
||||||
`--n-estimators` и `--max-depth`, сравните в UI
|
2. **Сравнение моделей** — обучите RandomForest vs GradientBoosting vs SVM
|
||||||
2. **Визуализация**: добавьте логирование ROC-кривой как артефакта
|
3. **Autolog vs ручное** — запустите один датасет с autolog и без, сравните
|
||||||
3. **Автолог**: попробуйте `mlflow.autolog()` вместо ручного логирования
|
4. **Model Registry** — обучите несколько версий, переведите лучшую в Production
|
||||||
4. **GPU эксперименты**: в `train_gpu.py` попробуйте разные `--lr` и
|
5. **Serving** — поднимите REST API, отправьте запрос через curl
|
||||||
`--batch-size`, сравните графики learning curves
|
6. **MLproject** — запустите через `mlflow run .` на другой машине
|
||||||
5. **Model Registry**: обучите несколько версий, переведите лучшую в
|
|
||||||
Production, остальные в Archived
|
|
||||||
6. **Serving**: попробуйте `mlflow models serve -m models:/digits_rf_model/Production -p 5001`
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Спецификация окружения Python для MLflow Projects
|
||||||
|
# MLflow создаст виртуальное окружение по этому файлу
|
||||||
|
python: "3.10"
|
||||||
|
dependencies:
|
||||||
|
- mlflow>=2.11.0
|
||||||
|
- scikit-learn>=1.3.0
|
||||||
|
- pandas>=2.0.0
|
||||||
|
- numpy>=1.24.0
|
||||||
|
- matplotlib>=3.7.0
|
||||||
|
# Для GPU entry point установите torch отдельно:
|
||||||
|
# pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
||||||
@@ -11,5 +11,8 @@ matplotlib>=3.7.0
|
|||||||
torch>=2.1.0
|
torch>=2.1.0
|
||||||
torchvision>=0.16.0
|
torchvision>=0.16.0
|
||||||
|
|
||||||
|
# === Для Model Serving (Урок 9) ===
|
||||||
|
flask>=3.0.0
|
||||||
|
|
||||||
# === Для MLflow UI / Model Registry ===
|
# === Для MLflow UI / Model Registry ===
|
||||||
# SQLite backend уже входит в стандартную поставку Python
|
# SQLite backend уже входит в стандартную поставку Python
|
||||||
|
|||||||
Executable
+148
@@ -0,0 +1,148 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# Глобальная настройка Git (HTTPS + токен, без SSH)
|
||||||
|
# Запуск: bash scripts/setup_git.sh
|
||||||
|
# ============================================================
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔧 Глобальная настройка Git"
|
||||||
|
echo "============================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if ! command -v git &> /dev/null; then
|
||||||
|
echo "❌ Git не установлен!"
|
||||||
|
echo " macOS: brew install git"
|
||||||
|
echo " Ubuntu: sudo apt install git"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Git: $(git --version)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─── 1. Имя и email ──────────────────────────────────────────
|
||||||
|
DEFAULT_NAME="admin"
|
||||||
|
DEFAULT_EMAIL="admin@e7.by"
|
||||||
|
GIT_NAME="${GIT_NAME:-$DEFAULT_NAME}"
|
||||||
|
GIT_EMAIL="${GIT_EMAIL:-$DEFAULT_EMAIL}"
|
||||||
|
|
||||||
|
read -p "Имя [$GIT_NAME]: " input_name
|
||||||
|
read -p "Email [$GIT_EMAIL]: " input_email
|
||||||
|
|
||||||
|
git config --global user.name "${input_name:-$GIT_NAME}"
|
||||||
|
git config --global user.email "${input_email:-$GIT_EMAIL}"
|
||||||
|
echo "✅ user.name = $(git config --global user.name)"
|
||||||
|
echo "✅ user.email = $(git config --global user.email)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─── 2. Базовые настройки ────────────────────────────────────
|
||||||
|
git config --global init.defaultBranch main
|
||||||
|
git config --global core.autocrlf input
|
||||||
|
git config --global core.safecrlf true
|
||||||
|
git config --global core.longpaths true
|
||||||
|
git config --global core.editor "vi"
|
||||||
|
git config --global core.pager "less -FRX"
|
||||||
|
git config --global color.ui auto
|
||||||
|
git config --global pull.rebase true
|
||||||
|
git config --global rebase.autoStash true
|
||||||
|
git config --global push.default current
|
||||||
|
echo "✅ Базовые настройки (main, rebase, colors, pager)"
|
||||||
|
|
||||||
|
# ─── 3. Алиасы ───────────────────────────────────────────────
|
||||||
|
git config --global alias.st "status -sb"
|
||||||
|
git config --global alias.co "checkout"
|
||||||
|
git config --global alias.br "branch"
|
||||||
|
git config --global alias.ci "commit"
|
||||||
|
git config --global alias.cm "commit -m"
|
||||||
|
git config --global alias.ca "commit --amend"
|
||||||
|
git config --global alias.lg "log --oneline --graph --decorate --all"
|
||||||
|
git config --global alias.last "log -1 HEAD --stat"
|
||||||
|
git config --global alias.unstage "restore --staged"
|
||||||
|
git config --global alias.discard "checkout --"
|
||||||
|
git config --global alias.aliases "config --get-regexp ^alias\\."
|
||||||
|
echo "✅ Алиасы: git st, git lg, git ci, git cm, git co, git br, git ca, git last"
|
||||||
|
|
||||||
|
# ─── 4. Credential helper (хранение токенов) ─────────────────
|
||||||
|
# macOS: токен в Keychain (безопасно, шифруется)
|
||||||
|
# Linux: токен в ~/.git-credentials (файл, без шифрования)
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
git config --global credential.helper osxkeychain
|
||||||
|
echo "✅ credential.helper = osxkeychain (macOS Keychain)"
|
||||||
|
else
|
||||||
|
git config --global credential.helper store
|
||||||
|
chmod 600 ~/.git-credentials 2>/dev/null || true
|
||||||
|
echo "✅ credential.helper = store (~/.git-credentials, chmod 600)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── 5. Настройка токена для git.e7.by ────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "🔐 Настройка токена для git.e7.by"
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
echo " git.e7.by работает только через HTTPS + токен."
|
||||||
|
echo " Токен можно создать в: Profile → Settings → Access Tokens"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Введите токен (или Enter чтобы пропустить): " TOKEN
|
||||||
|
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
# Сохраняем токен через credential helper
|
||||||
|
# Формат: https://username:token@host
|
||||||
|
echo "https://core:${TOKEN}@git.e7.by" | git credential-store store 2>/dev/null || \
|
||||||
|
echo "https://core:${TOKEN}@git.e7.by" >> ~/.git-credentials 2>/dev/null || true
|
||||||
|
|
||||||
|
# Для macOS Keychain — через security
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# Записываем в Keychain напрямую
|
||||||
|
security add-internet-password -s git.e7.by -a core -w "$TOKEN" -r https 2>/dev/null || true
|
||||||
|
echo "✅ Токен сохранён в macOS Keychain (git.e7.by / core)"
|
||||||
|
else
|
||||||
|
chmod 600 ~/.git-credentials 2>/dev/null || true
|
||||||
|
echo "✅ Токен сохранён в ~/.git-credentials"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ВАЖНО: remote URL должен быть БЕЗ токена!
|
||||||
|
# Токен подхватится автоматически из credential helper
|
||||||
|
echo ""
|
||||||
|
echo "📌 Теперь добавляйте remote БЕЗ токена:"
|
||||||
|
echo " git remote add origin https://git.e7.by/sandbox/repo.git"
|
||||||
|
echo " git push -u origin main"
|
||||||
|
echo " (токен подставится автоматически из credential helper)"
|
||||||
|
else
|
||||||
|
echo "⏭️ Токен не введён — пропускаю"
|
||||||
|
echo " Добавьте позже:"
|
||||||
|
echo " echo 'https://core:ТОКЕН@git.e7.by' >> ~/.git-credentials # Linux"
|
||||||
|
echo " security add-internet-password -s git.e7.by -a core -w 'ТОКЕН' -r https # macOS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── 6. Если репо уже есть — чистим токен из remote URL ──────
|
||||||
|
echo ""
|
||||||
|
echo "🧹 Проверка текущих remote на наличие токена в URL..."
|
||||||
|
CLEANED=0
|
||||||
|
for repo_remote in $(git remote 2>/dev/null); do
|
||||||
|
url=$(git remote get-url "$repo_remote" 2>/dev/null || true)
|
||||||
|
if echo "$url" | grep -q "://.*:.*@"; then
|
||||||
|
clean_url=$(echo "$url" | sed 's|://[^@]*@|://|')
|
||||||
|
git remote set-url "$repo_remote" "$clean_url"
|
||||||
|
echo " ✅ $repo_remote: токен удалён из URL"
|
||||||
|
echo " было: $url"
|
||||||
|
echo " стало: $clean_url"
|
||||||
|
CLEANED=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$CLEANED" -eq 0 ]; then
|
||||||
|
echo " ✅ Токенов в remote URL не найдено"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── 7. Финал ────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "============================================"
|
||||||
|
echo " ✅ Настройка завершена!"
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
echo "📋 Глобальные настройки:"
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
git config --global --list
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
echo ""
|
||||||
|
echo "🧪 Проверка токена:"
|
||||||
|
echo " git ls-remote https://git.e7.by/sandbox/mlflow-practice.git"
|
||||||
|
echo " (если спросит пароль — токен не подхватился)"
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
Урок 6: MLflow Autolog — автоматическое логирование
|
||||||
|
====================================================
|
||||||
|
Вместо ручного log_param/log_metric можно вызвать mlflow.autolog()
|
||||||
|
ОДИН раз — и MLflow сам залогирует всё: параметры, метрики, модель,
|
||||||
|
признаки, даже feature importance.
|
||||||
|
|
||||||
|
Сравните с train_simple.py — там всё логировалось вручную.
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
python src/autolog_demo.py
|
||||||
|
"""
|
||||||
|
from sklearn.datasets import load_wine
|
||||||
|
from sklearn.ensemble import GradientBoostingClassifier
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.metrics import accuracy_score
|
||||||
|
|
||||||
|
import mlflow
|
||||||
|
import mlflow.sklearn
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# ─── КЛЮЧЕВАЯ СТРОКА ──────────────────────────────────────
|
||||||
|
# autolog() включает автоматическое логирование для sklearn.
|
||||||
|
# Доступно для: sklearn, pytorch, tensorflow, xgboost, lightgbm,
|
||||||
|
# keras, fastai, spark, autogluon, statsmodels
|
||||||
|
mlflow.sklearn.autolog(
|
||||||
|
log_input_examples=False, # не логировать примеры входа
|
||||||
|
log_model_signatures=False, # не строить сигнатуру модели
|
||||||
|
log_models=True, # сохранить модель автоматически
|
||||||
|
log_datasets=True, # залогировать датасет
|
||||||
|
max_tuning_runs=10, # лимит для GridSearch
|
||||||
|
log_post_training_metrics=True,
|
||||||
|
)
|
||||||
|
# ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
mlflow.set_experiment("autolog_wine")
|
||||||
|
|
||||||
|
# Датасет wine — классификация вин по 3 сортам
|
||||||
|
wine = load_wine()
|
||||||
|
X, y = wine.data, wine.target
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(
|
||||||
|
X, y, test_size=0.3, random_state=42
|
||||||
|
)
|
||||||
|
print(f"📊 Датасет wine: {X.shape[0]} образцов, {X.shape[1]} признаков, 3 класса")
|
||||||
|
|
||||||
|
# autolog начинает работать автоматически внутри start_run
|
||||||
|
with mlflow.start_run(run_name="gb_autolog") as run:
|
||||||
|
print(f"MLflow run ID: {run.info.run_id}")
|
||||||
|
|
||||||
|
# Просто обучаем модель — НИКАКИХ log_param/log_metric!
|
||||||
|
model = GradientBoostingClassifier(
|
||||||
|
n_estimators=100,
|
||||||
|
learning_rate=0.1,
|
||||||
|
max_depth=3,
|
||||||
|
random_state=42,
|
||||||
|
)
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# Можно добавить и ручные метрики — они дополнят автолог
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
acc = accuracy_score(y_test, y_pred)
|
||||||
|
mlflow.log_metric("manual_accuracy", acc)
|
||||||
|
|
||||||
|
print(f"\n✅ Готово! Откройте MLflow UI:")
|
||||||
|
print(f" Эксперимент: autolog_wine")
|
||||||
|
print(f" Run: {run.info.run_id}")
|
||||||
|
print(f" Accuracy: {acc:.4f}")
|
||||||
|
print(f"\n🔍 Что autolog залогировал автоматически:")
|
||||||
|
print(f" • Параметры: n_estimators, learning_rate, max_depth, ...")
|
||||||
|
print(f" • Метрики: training_accuracy, training_log_loss, ...")
|
||||||
|
print(f" • Модель: сохранена в artifacts/model/")
|
||||||
|
print(f" • Датасет: профиль в artifacts/")
|
||||||
|
print(f" • Ручная метрика: manual_accuracy")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
Урок 9: MLflow + GridSearchCV — автолог перебора
|
||||||
|
==================================================
|
||||||
|
sklearn GridSearchCV сам перебирает гиперпараметры.
|
||||||
|
MLflow autolog автоматически залогирует КАЖУЮ попытку как отдельный run.
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
python src/grid_search_cv.py
|
||||||
|
"""
|
||||||
|
from sklearn.datasets import load_digits
|
||||||
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
|
from sklearn.model_selection import GridSearchCV, train_test_split
|
||||||
|
from sklearn.metrics import accuracy_score
|
||||||
|
|
||||||
|
import mlflow
|
||||||
|
import mlflow.sklearn
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# autolog для sklearn — залогирует все промежуточные попытки GridSearch
|
||||||
|
mlflow.sklearn.autolog(
|
||||||
|
log_models=False, # не сохранять каждую модель (экономим место)
|
||||||
|
max_tuning_runs=20, # максимум залогированных под-запусков
|
||||||
|
log_datasets=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mlflow.set_experiment("gridsearch_digits")
|
||||||
|
|
||||||
|
digits = load_digits()
|
||||||
|
X, y = digits.data, digits.target
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(
|
||||||
|
X, y, test_size=0.2, random_state=42
|
||||||
|
)
|
||||||
|
|
||||||
|
# Сетка для перебора
|
||||||
|
param_grid = {
|
||||||
|
"n_estimators": [50, 100, 200],
|
||||||
|
"max_depth": [4, 8, 12],
|
||||||
|
"min_samples_split": [2, 5],
|
||||||
|
}
|
||||||
|
# 3 × 3 × 2 = 18 комбинаций × 5 фолдов = 90 обучений
|
||||||
|
|
||||||
|
print(f"🔬 GridSearchCV")
|
||||||
|
print(f" Комбинаций: {len(param_grid['n_estimators'])} × "
|
||||||
|
f"{len(param_grid['max_depth'])} × "
|
||||||
|
f"{len(param_grid['min_samples_split'])} = "
|
||||||
|
f"{np.prod([len(v) for v in param_grid.values()])}")
|
||||||
|
print(f" CV фолдов: 5")
|
||||||
|
print(f" Всего обучений: {np.prod([len(v) for v in param_grid.values()]) * 5}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
with mlflow.start_run(run_name="gridsearch_rf") as run:
|
||||||
|
print(f"MLflow parent run: {run.info.run_id}")
|
||||||
|
|
||||||
|
grid = GridSearchCV(
|
||||||
|
estimator=RandomForestClassifier(random_state=42, n_jobs=-1),
|
||||||
|
param_grid=param_grid,
|
||||||
|
cv=5,
|
||||||
|
scoring="accuracy",
|
||||||
|
n_jobs=-1,
|
||||||
|
verbose=1,
|
||||||
|
)
|
||||||
|
grid.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# Логируем лучший результат
|
||||||
|
best = grid.best_estimator_
|
||||||
|
y_pred = best.predict(X_test)
|
||||||
|
test_acc = accuracy_score(y_test, y_pred)
|
||||||
|
|
||||||
|
mlflow.log_param("best_params", str(grid.best_params_))
|
||||||
|
mlflow.log_metric("best_cv_score", grid.best_score_)
|
||||||
|
mlflow.log_metric("test_accuracy", test_acc)
|
||||||
|
|
||||||
|
print(f"\n🏆 Лучшие параметры: {grid.best_params_}")
|
||||||
|
print(f" CV score: {grid.best_score_:.4f}")
|
||||||
|
print(f" Test accuracy: {test_acc:.4f}")
|
||||||
|
print(f"\n📊 В MLflow UI:")
|
||||||
|
print(f" Эксперимент: gridsearch_digits")
|
||||||
|
print(f" Parent run: {run.info.run_id}")
|
||||||
|
print(f" Child runs: {len(grid.cv_results_['params'])} под-запусков")
|
||||||
|
print(f" Каждый child — отдельная комбинация гиперпараметров")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import numpy as np
|
||||||
|
main()
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
"""
|
||||||
|
Урок 7: Hyperparameter Sweep — перебор гиперпараметров
|
||||||
|
========================================================
|
||||||
|
Запускает множество комбинаций гиперпараметров и логирует каждый
|
||||||
|
вариант в MLflow. В UI можно сравнить все варианты и найти лучший.
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
python src/hyperparam_sweep.py
|
||||||
|
python src/hyperparam_sweep.py --max-combos 50 # больше комбинаций
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import itertools
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
from sklearn.datasets import load_digits
|
||||||
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
|
from sklearn.model_selection import cross_val_score
|
||||||
|
from sklearn.metrics import recall_score, precision_score, f1_score, accuracy_score
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
|
||||||
|
import mlflow
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="MLflow hyperparameter sweep")
|
||||||
|
parser.add_argument("--max-combos", type=int, default=20,
|
||||||
|
help="максимум комбинаций для перебора")
|
||||||
|
parser.add_argument("--cv-folds", type=int, default=5,
|
||||||
|
help="фолдов кросс-валидации")
|
||||||
|
parser.add_argument("--experiment-name", type=str, default="sweep_digits")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# ─── Сетка гиперпараметров ───────────────────────────────
|
||||||
|
param_grid = {
|
||||||
|
"n_estimators": [50, 100, 150, 200, 300],
|
||||||
|
"max_depth": [4, 6, 8, 12, 16, None],
|
||||||
|
"min_samples_split": [2, 5, 10],
|
||||||
|
"min_samples_leaf": [1, 2, 4],
|
||||||
|
"max_features": ["sqrt", "log2", None],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Генерируем все комбинации, берём случайные max_combos
|
||||||
|
all_combos = list(itertools.product(
|
||||||
|
param_grid["n_estimators"],
|
||||||
|
param_grid["max_depth"],
|
||||||
|
param_grid["min_samples_split"],
|
||||||
|
param_grid["min_samples_leaf"],
|
||||||
|
param_grid["max_features"],
|
||||||
|
))
|
||||||
|
random.seed(42)
|
||||||
|
random.shuffle(all_combos)
|
||||||
|
combos = all_combos[:args.max_combos]
|
||||||
|
|
||||||
|
print(f"🔬 Hyperparameter Sweep")
|
||||||
|
print(f" Всего возможных комбинаций: {len(all_combos)}")
|
||||||
|
print(f" Будет запущено: {len(combos)}")
|
||||||
|
print(f" CV фолдов: {args.cv_folds}")
|
||||||
|
print(f" Эксперимент: {args.experiment_name}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ─── Данные ──────────────────────────────────────────────
|
||||||
|
digits = load_digits()
|
||||||
|
X, y = digits.data, digits.target
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(
|
||||||
|
X, y, test_size=0.2, random_state=42
|
||||||
|
)
|
||||||
|
|
||||||
|
mlflow.set_experiment(args.experiment_name)
|
||||||
|
|
||||||
|
best_acc = 0.0
|
||||||
|
best_run_id = None
|
||||||
|
best_params = None
|
||||||
|
|
||||||
|
for i, (n_est, depth, min_split, min_leaf, max_feat) in enumerate(combos):
|
||||||
|
params = {
|
||||||
|
"n_estimators": n_est,
|
||||||
|
"max_depth": depth,
|
||||||
|
"min_samples_split": min_split,
|
||||||
|
"min_samples_leaf": min_leaf,
|
||||||
|
"max_features": max_feat,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Описание для имени run
|
||||||
|
depth_str = str(depth) if depth else "None"
|
||||||
|
feat_str = str(max_feat) if max_feat else "None"
|
||||||
|
run_name = f"rf_ne{n_est}_d{depth_str}_ms{min_split}_ml{min_leaf}_mf{feat_str}"
|
||||||
|
|
||||||
|
with mlflow.start_run(run_name=run_name) as run:
|
||||||
|
# Логируем ВСЕ параметры
|
||||||
|
mlflow.log_params(params)
|
||||||
|
|
||||||
|
# Обучаем
|
||||||
|
model = RandomForestClassifier(
|
||||||
|
random_state=42, n_jobs=-1, **params
|
||||||
|
)
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# Кросс-валидация (более надёжная оценка)
|
||||||
|
cv_scores = cross_val_score(model, X_train, y_train,
|
||||||
|
cv=args.cv_folds, scoring="accuracy")
|
||||||
|
|
||||||
|
# Метрики на тесте
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
test_acc = accuracy_score(y_test, y_pred)
|
||||||
|
test_recall = recall_score(y_test, y_pred, average="macro")
|
||||||
|
test_precision = precision_score(y_test, y_pred, average="macro")
|
||||||
|
test_f1 = f1_score(y_test, y_pred, average="macro")
|
||||||
|
|
||||||
|
# Логируем метрики
|
||||||
|
mlflow.log_metric("cv_mean_accuracy", cv_scores.mean())
|
||||||
|
mlflow.log_metric("cv_std_accuracy", cv_scores.std())
|
||||||
|
mlflow.log_metric("test_accuracy", test_acc)
|
||||||
|
mlflow.log_metric("test_recall_macro", test_recall)
|
||||||
|
mlflow.log_metric("test_precision_macro", test_precision)
|
||||||
|
mlflow.log_metric("test_f1_macro", test_f1)
|
||||||
|
|
||||||
|
# Тег — номер комбинации
|
||||||
|
mlflow.set_tag("combo_index", str(i))
|
||||||
|
|
||||||
|
# Отслеживаем лучший
|
||||||
|
if test_acc > best_acc:
|
||||||
|
best_acc = test_acc
|
||||||
|
best_run_id = run.info.run_id
|
||||||
|
best_params = params
|
||||||
|
|
||||||
|
# Прогресс
|
||||||
|
bar_len = 30
|
||||||
|
filled = int(bar_len * (i + 1) / len(combos))
|
||||||
|
bar = "█" * filled + "░" * (bar_len - filled)
|
||||||
|
print(f"\r [{bar}] {i+1}/{len(combos)} | "
|
||||||
|
f"acc={test_acc:.4f} | {run_name[:40]:<40}", end="")
|
||||||
|
|
||||||
|
print(f"\n\n{'='*60}")
|
||||||
|
print(f"🏆 Лучший результат:")
|
||||||
|
print(f" Run ID: {best_run_id}")
|
||||||
|
print(f" Accuracy: {best_acc:.4f}")
|
||||||
|
print(f" Параметры: {best_params}")
|
||||||
|
print(f"\n📊 Откройте MLflow UI:")
|
||||||
|
print(f" Эксперимент: {args.experiment_name}")
|
||||||
|
print(f" Отсортируйте по test_accuracy (клик на заголовок колонки)")
|
||||||
|
print(f" Сравните cv_mean_accuracy vs test_accuracy")
|
||||||
|
print(f" если cv_std_accuracy высокий — модель нестабильна")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
"""
|
||||||
|
Урок 8: Model Serving — разворачивание модели как REST API
|
||||||
|
============================================================
|
||||||
|
Загружает модель из Model Registry и запускает HTTP-сервер.
|
||||||
|
Модель принимает JSON-запросы и возвращает предсказания.
|
||||||
|
|
||||||
|
Два способа:
|
||||||
|
1. Встроенный MLflow server (простой):
|
||||||
|
mlflow models serve -m models:/digits_rf_model/Production -p 5001
|
||||||
|
|
||||||
|
2. Этот скрипт — кастомный сервер с дополнительной логикой:
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
python src/serve_model.py --port 5001
|
||||||
|
|
||||||
|
Запрос:
|
||||||
|
curl -X POST http://localhost:5001/predict \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"instances": [[0,0,0,...,0]]}'
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
from sklearn.datasets import load_digits
|
||||||
|
|
||||||
|
import mlflow
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(model_name, stage):
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Загружаем модель один раз при старте
|
||||||
|
print(f"📥 Загружаем модель '{model_name}' ({stage})...")
|
||||||
|
model = mlflow.sklearn.load_model(f"models:/{model_name}/{stage}")
|
||||||
|
print(f"✅ Модель загружена!")
|
||||||
|
|
||||||
|
# Загружаем датасет для демо-генерации примеров
|
||||||
|
digits = load_digits()
|
||||||
|
|
||||||
|
@app.route("/health", methods=["GET"])
|
||||||
|
def health():
|
||||||
|
return jsonify({"status": "ok", "model": model_name, "stage": stage})
|
||||||
|
|
||||||
|
@app.route("/info", methods=["GET"])
|
||||||
|
def info():
|
||||||
|
return jsonify({
|
||||||
|
"model": model_name,
|
||||||
|
"stage": stage,
|
||||||
|
"n_features": 64,
|
||||||
|
"classes": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
"endpoints": ["/health", "/info", "/predict", "/predict_random"],
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route("/predict", methods=["POST"])
|
||||||
|
def predict():
|
||||||
|
"""Предсказание для переданных образцов.
|
||||||
|
Формат запроса:
|
||||||
|
{"instances": [[...64 числа...], [...64 числа...]]}
|
||||||
|
"""
|
||||||
|
data = request.get_json(force=True)
|
||||||
|
|
||||||
|
if "instances" not in data:
|
||||||
|
return jsonify({"error": "поле 'instances' обязательно"}), 400
|
||||||
|
|
||||||
|
instances = np.array(data["instances"])
|
||||||
|
|
||||||
|
if instances.shape[-1] != 64:
|
||||||
|
return jsonify({
|
||||||
|
"error": f"ожидалось 64 признака, получено {instances.shape[-1]}"
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
predictions = model.predict(instances)
|
||||||
|
|
||||||
|
# Если модель поддерживает predict_proba — добавим вероятности
|
||||||
|
try:
|
||||||
|
probabilities = model.predict_proba(instances)
|
||||||
|
return jsonify({
|
||||||
|
"predictions": predictions.tolist(),
|
||||||
|
"probabilities": probabilities.tolist(),
|
||||||
|
"n_samples": len(predictions),
|
||||||
|
})
|
||||||
|
except AttributeError:
|
||||||
|
return jsonify({
|
||||||
|
"predictions": predictions.tolist(),
|
||||||
|
"n_samples": len(predictions),
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route("/predict_random", methods=["GET"])
|
||||||
|
def predict_random():
|
||||||
|
"""Демо-эндпоинт: берёт случайный образец из digits и предсказывает."""
|
||||||
|
idx = np.random.randint(0, len(digits.data))
|
||||||
|
sample = digits.data[idx:idx+1]
|
||||||
|
true_label = int(digits.target[idx])
|
||||||
|
prediction = int(model.predict(sample)[0])
|
||||||
|
|
||||||
|
try:
|
||||||
|
probs = model.predict_proba(sample)[0]
|
||||||
|
confidence = float(max(probs))
|
||||||
|
except AttributeError:
|
||||||
|
confidence = None
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"true_label": true_label,
|
||||||
|
"prediction": prediction,
|
||||||
|
"correct": true_label == prediction,
|
||||||
|
"confidence": confidence,
|
||||||
|
"sample_image": sample[0].reshape(8, 8).tolist(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="MLflow model serving")
|
||||||
|
parser.add_argument("--model-name", default="digits_rf_model")
|
||||||
|
parser.add_argument("--stage", default="Production")
|
||||||
|
parser.add_argument("--port", type=int, default=5001)
|
||||||
|
parser.add_argument("--host", default="0.0.0.0")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
app = create_app(args.model_name, args.stage)
|
||||||
|
|
||||||
|
print(f"\n🚀 Сервер запускается на http://{args.host}:{args.port}")
|
||||||
|
print(f" /health — проверка состояния")
|
||||||
|
print(f" /info — информация о модели")
|
||||||
|
print(f" /predict — предсказание (POST JSON)")
|
||||||
|
print(f" /predict_random — случайный образец (GET)")
|
||||||
|
print(f"\n📋 Примеры запросов:")
|
||||||
|
print(f" curl http://localhost:{args.port}/health")
|
||||||
|
print(f" curl http://localhost:{args.port}/predict_random")
|
||||||
|
print()
|
||||||
|
|
||||||
|
app.run(host=args.host, port=args.port, debug=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -18,7 +18,6 @@ import matplotlib
|
|||||||
matplotlib.use("Agg")
|
matplotlib.use("Agg")
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import torch
|
import torch
|
||||||
from sklearn.metrics import recall_score, precision_score, f1_score
|
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import torch.optim as optim
|
import torch.optim as optim
|
||||||
from torch.utils.data import DataLoader
|
from torch.utils.data import DataLoader
|
||||||
@@ -141,27 +140,11 @@ def main():
|
|||||||
|
|
||||||
acc = correct / total
|
acc = correct / total
|
||||||
test_accs.append(acc)
|
test_accs.append(acc)
|
||||||
|
|
||||||
# Recall / Precision / F1 (macro-усреднение по 10 классам)
|
|
||||||
all_preds, all_labels = [], []
|
|
||||||
with torch.no_grad():
|
|
||||||
for data, target in test_loader:
|
|
||||||
data, target = data.to(device), target.to(device)
|
|
||||||
output = model(data)
|
|
||||||
pred = output.argmax(dim=1)
|
|
||||||
all_preds.extend(pred.cpu().numpy())
|
|
||||||
all_labels.extend(target.cpu().numpy())
|
|
||||||
recall = recall_score(all_labels, all_preds, average="macro")
|
|
||||||
precision = precision_score(all_labels, all_preds, average="macro")
|
|
||||||
f1 = f1_score(all_labels, all_preds, average="macro")
|
|
||||||
elapsed = time.time() - t0
|
elapsed = time.time() - t0
|
||||||
|
|
||||||
# Логируем метрики по эпохам
|
# Логируем метрики по эпохам
|
||||||
mlflow.log_metric("train_loss", avg_loss, step=epoch)
|
mlflow.log_metric("train_loss", avg_loss, step=epoch)
|
||||||
mlflow.log_metric("test_accuracy", acc, step=epoch)
|
mlflow.log_metric("test_accuracy", acc, step=epoch)
|
||||||
mlflow.log_metric("recall_macro", recall, step=epoch)
|
|
||||||
mlflow.log_metric("precision_macro", precision, step=epoch)
|
|
||||||
mlflow.log_metric("f1_macro", f1, step=epoch)
|
|
||||||
mlflow.log_metric("epoch_time_sec", elapsed, step=epoch)
|
mlflow.log_metric("epoch_time_sec", elapsed, step=epoch)
|
||||||
|
|
||||||
print(f" → Epoch {epoch+1}: loss={avg_loss:.4f}, "
|
print(f" → Epoch {epoch+1}: loss={avg_loss:.4f}, "
|
||||||
@@ -186,7 +169,6 @@ def main():
|
|||||||
mlflow.pytorch.log_model(
|
mlflow.pytorch.log_model(
|
||||||
model,
|
model,
|
||||||
artifact_path="model",
|
artifact_path="model",
|
||||||
serialization_format="pickle",
|
|
||||||
registered_model_name=None,
|
registered_model_name=None,
|
||||||
input_example=torch.randn(1, 1, 28, 28).to(device),
|
input_example=torch.randn(1, 1, 28, 28).to(device),
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-4
@@ -15,7 +15,7 @@ import matplotlib.pyplot as plt
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from sklearn.datasets import load_digits
|
from sklearn.datasets import load_digits
|
||||||
from sklearn.ensemble import RandomForestClassifier
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, recall_score, precision_score, f1_score
|
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
|
||||||
from sklearn.model_selection import train_test_split
|
from sklearn.model_selection import train_test_split
|
||||||
|
|
||||||
import mlflow
|
import mlflow
|
||||||
@@ -66,9 +66,6 @@ def main():
|
|||||||
|
|
||||||
# Логируем метрики
|
# Логируем метрики
|
||||||
mlflow.log_metric("accuracy", acc)
|
mlflow.log_metric("accuracy", acc)
|
||||||
mlflow.log_metric("recall_macro", recall_score(y_test, y_pred, average="macro"))
|
|
||||||
mlflow.log_metric("precision_macro", precision_score(y_test, y_pred, average="macro"))
|
|
||||||
mlflow.log_metric("f1_macro", f1_score(y_test, y_pred, average="macro"))
|
|
||||||
# можно логировать несколько шагов (для графиков в UI)
|
# можно логировать несколько шагов (для графиков в UI)
|
||||||
for i, tree in enumerate(model.estimators_):
|
for i, tree in enumerate(model.estimators_):
|
||||||
tree_acc = accuracy_score(y_test, tree.predict(X_test))
|
tree_acc = accuracy_score(y_test, tree.predict(X_test))
|
||||||
|
|||||||
+2
-9
@@ -7,22 +7,15 @@ set -e
|
|||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
echo "🚀 Запускаем MLflow Tracking Server..."
|
echo "🚀 Запускаем MLflow Tracking Server..."
|
||||||
echo " UI: http://10.0.0.7:5000"
|
echo " UI: http://localhost:5000"
|
||||||
echo " DB: sqlite:///mlflow.db"
|
echo " DB: sqlite:///mlflow.db"
|
||||||
echo " Artifacts: ./artifacts"
|
echo " Artifacts: ./artifacts"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Нажмите Ctrl+C для остановки"
|
echo " Нажмите Ctrl+C для остановки"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
export MLFLOW_ALLOW_ORIGIN="*"
|
|
||||||
|
|
||||||
mlflow server \
|
mlflow server \
|
||||||
--backend-store-uri sqlite:///mlflow.db \
|
--backend-store-uri sqlite:///mlflow.db \
|
||||||
--default-artifact-root ./artifacts \
|
--default-artifact-root ./artifacts \
|
||||||
--serve-artifacts \
|
|
||||||
--allowed-hosts "*" \
|
|
||||||
--cors-allowed-origins "*" \
|
|
||||||
--artifacts-destination ./artifacts \
|
|
||||||
--host 0.0.0.0 \
|
--host 0.0.0.0 \
|
||||||
--port 5000 \
|
--port 5000
|
||||||
--dev
|
|
||||||
|
|||||||
Reference in New Issue
Block a user