Recall везде + фиксы окружения
- 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
This commit is contained in:
@@ -9,3 +9,6 @@ data/MNIST/
|
|||||||
|
|
||||||
# Flask serve
|
# Flask serve
|
||||||
instance/
|
instance/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# AGENTS.md — гид для агента по проекту mlflow-practice
|
||||||
|
|
||||||
|
> Этот файл — ориентир для любого AI-агента (и человека), который будет
|
||||||
|
> работать с проектом. Читай его **первым** при входе в репозиторий.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Назначение проекта
|
||||||
|
|
||||||
|
Учебный проект для изучения **MLflow** на практике: от логирования
|
||||||
|
экспериментов до Model Registry и serving моделей. Девять последовательных
|
||||||
|
уроков, каждый — отдельный скрипт в `src/`. Поддерживает **GPU (RTX 3090, CUDA)**
|
||||||
|
и **macOS (Intel CPU / Apple Silicon MPS)**.
|
||||||
|
|
||||||
|
Не production-система. Цель — показать полный цикл MLOps на маленьких
|
||||||
|
встроенных датасетах (digits, wine, MNIST).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Карта проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
mlflow-practice/
|
||||||
|
├── AGENTS.md # ← этот файл (поддерживай актуальным!)
|
||||||
|
├── README.md # пользовательская документация (как запускать)
|
||||||
|
├── CODE_WALKTHROUGH.md # подробный разбор каждого файла с пояснениями
|
||||||
|
├── MLproject # MLflow Projects: entry points main/gpu/sweep
|
||||||
|
├── python_env.yaml # окружение для MLproject (python 3.10)
|
||||||
|
├── requirements.txt # pip-зависимости
|
||||||
|
├── setup_server.sh # установка окружения (РАЗВИЛКА macOS/Linux)
|
||||||
|
├── start_ui.sh # запуск MLflow Tracking Server (UI на :5555, file-store)
|
||||||
|
├── .gitignore
|
||||||
|
├── scripts/
|
||||||
|
│ └── setup_git.sh # настройка Git (HTTPS + токен)
|
||||||
|
├── src/ # ← весь код уроков здесь
|
||||||
|
│ ├── train_simple.py # Урок 1: sklearn + Tracking (ручное логирование)
|
||||||
|
│ ├── train_gpu.py # Урок 2: PyTorch CNN, CUDA→MPS→CPU
|
||||||
|
│ ├── register_model.py # Урок 3: Model Registry (версии, стадии)
|
||||||
|
│ ├── load_and_predict.py # Урок 4: загрузка модели + предсказание
|
||||||
|
│ ├── compare_runs.py # Урок 5: сравнение запусков через API
|
||||||
|
│ ├── autolog_demo.py # Урок 6: mlflow.autolog()
|
||||||
|
│ ├── hyperparam_sweep.py # Урок 7: перебор гиперпараметров вручную
|
||||||
|
│ ├── serve_model.py # Урок 8: REST API сервер модели (Flask)
|
||||||
|
│ └── grid_search_cv.py # Урок 9: GridSearchCV + autolog
|
||||||
|
├── data/ # датасеты (MNIST скачается автоматически)
|
||||||
|
├── artifacts/ # графики, отчёты (логируются в MLflow)
|
||||||
|
└── mlruns/ # локальный file store MLflow (в .gitignore)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Роль каждого файла (кратко)
|
||||||
|
|
||||||
|
| Файл | Что делает | Датасет | Фреймворк |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `train_simple.py` | RandomForest, ручное логирование params/metrics/model/artifacts | digits | sklearn |
|
||||||
|
| `train_gpu.py` | CNN, логирование по эпохам, выбор устройства `get_device()` | MNIST | PyTorch |
|
||||||
|
| `register_model.py` | ищет лучший run по метрике, регистрирует модель, стадия Production | — | MLflow API |
|
||||||
|
| `load_and_predict.py` | грузит `models:/digits_rf_model/Production`, предсказывает | digits | MLflow |
|
||||||
|
| `compare_runs.py` | `MlflowClient.search_runs`, топ-N по метрике | — | MLflow API |
|
||||||
|
| `autolog_demo.py` | `mlflow.autolog()` для GradientBoosting | wine | sklearn |
|
||||||
|
| `hyperparam_sweep.py` | ручной перебор комбинаций, `--max-combos` | digits | sklearn |
|
||||||
|
| `serve_model.py` | Flask-сервер `/predict`, грузит модель из реестра | digits | Flask + MLflow |
|
||||||
|
| `grid_search_cv.py` | `GridSearchCV`, autolog залогит каждую попытку | digits | sklearn |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ Платформенная специфика
|
||||||
|
|
||||||
|
### Выбор устройства в `train_gpu.py`
|
||||||
|
Функция `get_device()` определяет устройство в порядке:
|
||||||
|
**CUDA → MPS (Apple Silicon) → CPU**. Тег MLflow `device_type` хранит выбор
|
||||||
|
(`cuda` / `mps` / `cpu`) — по нему удобно фильтровать запуски в UI.
|
||||||
|
|
||||||
|
### `num_workers` в DataLoader
|
||||||
|
На **macOS с MPS** многопроцессная загрузка нестабильна (fork + MPS),
|
||||||
|
поэтому `num_workers=0` на Darwin и `4` на Linux. `pin_memory=True`
|
||||||
|
только для CUDA. Логика — в `train_gpu.py` рядом с `DataLoader`.
|
||||||
|
|
||||||
|
### `setup_server.sh` — развивка по ОС
|
||||||
|
- `Darwin` → macOS: проверка Homebrew, `python@3.12` через brew, PyTorch
|
||||||
|
из PyPI (без CUDA), проверка `torch.backends.mps.is_available()`.
|
||||||
|
- `Linux` → GPU-сервер: `nvidia-smi`, PyTorch из
|
||||||
|
`--index-url https://download.pytorch.org/whl/cu121`, проверка VRAM.
|
||||||
|
|
||||||
|
При добавлении новой платформы — расширяй `case "$OS"` в начале скрипта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Стандартные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Окружение
|
||||||
|
bash setup_server.sh # установка (macOS или Linux)
|
||||||
|
source .venv/bin/activate
|
||||||
|
./start_ui.sh # MLflow UI → http://localhost:5555
|
||||||
|
|
||||||
|
# Уроки (в отдельном терминале, с активированным venv)
|
||||||
|
python src/train_simple.py --n-estimators 100 --max-depth 8
|
||||||
|
python src/train_gpu.py --epochs 5 --batch-size 128 --lr 0.001 # macOS/MPS
|
||||||
|
python src/register_model.py --experiment digits_classification
|
||||||
|
python src/load_and_predict.py
|
||||||
|
python src/compare_runs.py --experiment digits_classification --top 10
|
||||||
|
python src/autolog_demo.py
|
||||||
|
python src/hyperparam_sweep.py --max-combos 20
|
||||||
|
python src/serve_model.py --port 5001
|
||||||
|
python src/grid_search_cv.py
|
||||||
|
|
||||||
|
# Через MLflow Projects (изолированное окружение по MLproject)
|
||||||
|
mlflow run . -P n_estimators=100 -P max_depth=8
|
||||||
|
mlflow run . -P epochs=10 -P batch_size=256 -P lr=0.001 --entry-point gpu
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Конвенции кода
|
||||||
|
|
||||||
|
- **Датасеты** — только встроенные (`load_digits`, `load_wine`, MNIST через
|
||||||
|
`torchvision`). Никаких внешних файлов в `data/`, кроме скачанного MNIST.
|
||||||
|
- **Логирование** — параметры/метрики/модель/артефакты. Графики сохраняются
|
||||||
|
в `artifacts/` и логируются через `mlflow.log_artifact`.
|
||||||
|
- **Имена экспериментов**: `digits_classification`, `mnist_cnn_gpu`,
|
||||||
|
`wine_autolog` и т.п. Модель в реестре — `digits_rf_model`.
|
||||||
|
- **`matplotlib.use("Agg")`** обязателен перед импортом pyplot — скрипты
|
||||||
|
работают без GUI (на сервере).
|
||||||
|
- **Python ≥ 3.10** (см. `python_env.yaml`). type hints опциональны.
|
||||||
|
- **CLI через `argparse`** — все скрипты с параметрами запускаются из CLI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Известные шероховатости / TODO
|
||||||
|
|
||||||
|
- ~~Нумерация уроков 8/9 расходилась между докстрингами и README/CODE_WALKTHROUGH~~ —
|
||||||
|
**исправлено**: канон `serve_model.py`=Урок 8 / `grid_search_cv.py`=Урок 9,
|
||||||
|
README.md и CODE_WALKTHROUGH.md выровнены под докстринги (порядок секций тоже).
|
||||||
|
- `requirements.txt` включает `torch`/`torchvision` из PyPI (CPU/MPS).
|
||||||
|
Для CUDA их нужно ставить отдельно через `--index-url .../whl/cu121`
|
||||||
|
(см. `setup_server.sh`, ветка Linux).
|
||||||
|
- **Порт MLflow UI = 5555** (не 5000): на macOS порт 5000 занят системным сервисом AirPlay Receiver (процесс `ControlCenter`), поэтому `start_ui.sh` использует 5555. На Linux можно вернуть 5000, но 5555 работает везде.
|
||||||
|
- **MLflow 3.x: `artifact_path` устарел** — во всех `log_model` используется `name=...` (был `artifact_path=...`). При обновлении/добавлении log_model не используй `artifact_path`.
|
||||||
|
- **Сигнатура модели**: `train_simple.py` логирует модель с явной `signature=` и `input_example=` (через `mlflow.models.signature.infer_signature`). Без этого MLflow пишет warning «Model logged without a signature and input example».
|
||||||
|
- `train_gpu.py` передаёт `input_example` → сигнатура авто-infer'ится.
|
||||||
|
- `autolog_demo.py` намеренно отключает сигнатуры (`log_model_signatures=False`) как демо опций autolog — там warning ожидаем.
|
||||||
|
- **INFO про переменные окружения** (`OPENAI_API_KEY` и т.п.) при логировании подавлено через `MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING=false` в скриптах, логирующих модели.
|
||||||
|
- **`input_example` для `mlflow.pytorch.log_model` — только numpy**, не `torch.Tensor`: MLflow 3.x валидирует пример входа и падает с `MlflowException ... but got '<class 'torch.Tensor'>'`. В `train_gpu.py` передаётся `np.random.rand(1,1,28,28).astype(np.float32)`. Аналогично для других flavor'ов — пример должен быть DataFrame/ndarray/dict/list/скаляр.
|
||||||
|
- **urllib3 `NotOpenSSLWarning`** на macOS (системный `ssl` собран с LibreSSL 2.8.3, urllib3 v2 просит OpenSSL 1.1.1+) — стороннее шумовое предупреждение, к проекту отношения не имеет; не подавляем (чтобы не прятать реальные предупреждения).
|
||||||
|
- **«Эксперимент не виден в UI» (главная причина)**: скрипты по умолчанию пишут в локальный file store `./mlruns`, а `start_ui.sh` раньше поднимал сервер на **SQLite** (`mlflow.db`) — два разных хранилища, UI ничего не показывал. Теперь `start_ui.sh` использует `--backend-store-uri file://${PWD}/mlruns` — **то же хранилище, что и скрипты**, поэтому всё видно без настройки tracking URI. Альтернатива (SQLite-бэкенд) оставлена закомментированной в `start_ui.sh`; при её включении в скриптах нужно задать `mlflow.set_tracking_uri('http://localhost:5555')` (или `MLFLOW_TRACKING_URI`).
|
||||||
|
- `mlruns/`, `artifacts/*`, `data/MNIST/`, `mlflow.db` — в `.gitignore`,
|
||||||
|
в репозиторий не попадают.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Когда обновлять этот файл
|
||||||
|
|
||||||
|
**AGENTS.md — живой документ. Обновляй его при:**
|
||||||
|
|
||||||
|
1. **Добавлении/удалении файла в `src/`** — обнови карту проекта и таблицу ролей.
|
||||||
|
2. **Изменении платформенной логики** (новое устройство, новая ОС в
|
||||||
|
`setup_server.sh`, изменение `get_device()` или `num_workers`) — обнови
|
||||||
|
раздел «Платформенная специфика».
|
||||||
|
3. **Изменении имён экспериментов/моделей** в реестре — обнови «Конвенции».
|
||||||
|
4. **Изменении зависимостей или версии Python** (`requirements.txt`,
|
||||||
|
`python_env.yaml`) — проверь раздел команд и конвенции.
|
||||||
|
5. **Появлении новых entry points в `MLproject`** — добавь в карту и команды.
|
||||||
|
6. **Обнаружении gotcha/шероховатости** — занеси в «Известные шероховатости»,
|
||||||
|
чтобы следующий агент не наступал повторно.
|
||||||
|
|
||||||
|
Правило простое: **если ты что-то изменил в структуре/поведении проекта,
|
||||||
|
отрази это в AGENTS.md в том же коммите.** Если файл устарел относительно
|
||||||
|
кода — это баг, который нужно исправить.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 Шпаргалка по MLflow API (часто используемое в проекте)
|
||||||
|
|
||||||
|
```python
|
||||||
|
mlflow.set_experiment("name")
|
||||||
|
with mlflow.start_run(run_name="...") as run:
|
||||||
|
mlflow.log_param("k", v)
|
||||||
|
mlflow.log_metric("acc", 0.95, step=epoch)
|
||||||
|
mlflow.log_artifact("artifacts/curve.png")
|
||||||
|
mlflow.sklearn.log_model(model, "model") # sklearn flavor
|
||||||
|
mlflow.pytorch.log_model(model, "model") # pytorch flavor
|
||||||
|
mlflow.set_tag("device_type", "mps")
|
||||||
|
mlflow.autolog() # авто-логирование
|
||||||
|
|
||||||
|
# Model Registry
|
||||||
|
client = MlflowClient()
|
||||||
|
client.search_runs([experiment_id], order_by=["metrics.accuracy DESC"])
|
||||||
|
mlflow.register_model("runs:/<run_id>/model", "digits_rf_model")
|
||||||
|
client.transition_model_version_stage("digits_rf_model", version=1, stage="Production")
|
||||||
|
mlflow.pyfunc.load_model("models:/digits_rf_model/Production")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧭 С чего начать новому агенту
|
||||||
|
|
||||||
|
1. Прочитай этот файл целиком.
|
||||||
|
2. Прочитай `README.md` (пользовательский взгляд) и `CODE_WALKTHROUGH.md`
|
||||||
|
(детальный разбор кода).
|
||||||
|
3. Прогони `bash setup_server.sh` на целевой платформе, чтобы понять
|
||||||
|
состояние окружения.
|
||||||
|
4. Перед правками проверь `git status` — в этом окружении `.git` может быть
|
||||||
|
read-only для агента (тогда коммит делает пользователь вручную).
|
||||||
|
5. Любое структурное изменение → коммит + правка AGENTS.md.
|
||||||
+19
-19
@@ -23,8 +23,8 @@ mlflow-practice/
|
|||||||
├── compare_runs.py # Урок 5: сравнение запусков через API
|
├── compare_runs.py # Урок 5: сравнение запусков через API
|
||||||
├── autolog_demo.py # Урок 6: autolog (автоматическое логирование)
|
├── autolog_demo.py # Урок 6: autolog (автоматическое логирование)
|
||||||
├── hyperparam_sweep.py # Урок 7: перебор гиперпараметров (вручную)
|
├── hyperparam_sweep.py # Урок 7: перебор гиперпараметров (вручную)
|
||||||
├── grid_search_cv.py # Урок 8: GridSearchCV + autolog
|
├── serve_model.py # Урок 8: REST API сервер модели
|
||||||
└── serve_model.py # Урок 9: REST API сервер модели
|
└── grid_search_cv.py # Урок 9: GridSearchCV + autolog
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -379,23 +379,6 @@ CV делит train на 5 фолдов, обучает 5 раз, усредня
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 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
|
## 📄 serve_model.py — REST API
|
||||||
|
|
||||||
### Загрузка модели при старте
|
### Загрузка модели при старте
|
||||||
@@ -448,6 +431,23 @@ MLflow сам поднимает сервер с стандартным API. К
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 📄 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 — воспроизводимость
|
## 📄 MLproject — воспроизводимость
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ mlflow-practice/
|
|||||||
├── compare_runs.py # Урок 5: сравнение через API
|
├── compare_runs.py # Урок 5: сравнение через API
|
||||||
├── autolog_demo.py # Урок 6: autolog (автоматическое)
|
├── autolog_demo.py # Урок 6: autolog (автоматическое)
|
||||||
├── hyperparam_sweep.py # Урок 7: перебор гиперпараметров
|
├── hyperparam_sweep.py # Урок 7: перебор гиперпараметров
|
||||||
├── grid_search_cv.py # Урок 8: GridSearchCV + autolog
|
├── serve_model.py # Урок 8: REST API сервер
|
||||||
└── serve_model.py # Урок 9: REST API сервер
|
└── grid_search_cv.py # Урок 9: GridSearchCV + autolog
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -52,7 +52,7 @@ pip install flask # для serve_model.py
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start_ui.sh
|
./start_ui.sh
|
||||||
# → http://10.0.0.7:5000
|
# → http://10.0.0.7:5555
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Уроки по порядку
|
### 3. Уроки по порядку
|
||||||
@@ -81,12 +81,12 @@ python src/autolog_demo.py
|
|||||||
# Урок 7 — Hyperparameter sweep (20 комбинаций)
|
# Урок 7 — Hyperparameter sweep (20 комбинаций)
|
||||||
python src/hyperparam_sweep.py --max-combos 20
|
python src/hyperparam_sweep.py --max-combos 20
|
||||||
|
|
||||||
# Урок 8 — GridSearchCV + autolog (parent/child runs)
|
# Урок 8 — Model serving (REST API)
|
||||||
python src/grid_search_cv.py
|
|
||||||
|
|
||||||
# Урок 9 — Model serving (REST API)
|
|
||||||
python src/serve_model.py --port 5001
|
python src/serve_model.py --port 5001
|
||||||
# → curl http://localhost:5001/predict_random
|
# → curl http://localhost:5001/predict_random
|
||||||
|
|
||||||
|
# Урок 9 — GridSearchCV + autolog (parent/child runs)
|
||||||
|
python src/grid_search_cv.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. MLflow Projects (воспроизводимый запуск)
|
### 4. MLflow Projects (воспроизводимый запуск)
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
#!/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 " (если спросит пароль — токен не подхватился)"
|
|
||||||
+96
-10
@@ -1,14 +1,92 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Установка окружения MLflow на GPU-сервере (RTX 3090)
|
# Установка окружения MLflow Practice
|
||||||
# Запускать НА СЕРВЕРЕ: bash setup_server.sh
|
# Поддержка: Linux (GPU-сервер, RTX 3090) и macOS (Intel/Apple Silicon)
|
||||||
|
# Запускать: bash setup_server.sh
|
||||||
# ============================================================
|
# ============================================================
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo " MLflow Practice — установка на сервере"
|
echo " MLflow Practice — установка окружения"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
|
|
||||||
|
# --- Определение ОС ---
|
||||||
|
OS="$(uname -s)"
|
||||||
|
case "$OS" in
|
||||||
|
Darwin*) PLATFORM="macos" ;;
|
||||||
|
Linux*) PLATFORM="linux" ;;
|
||||||
|
*) echo "❌ Неподдерживаемая ОС: $OS"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
echo "📍 Платформа: $PLATFORM ($ARCH)"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# macOS — установка без CUDA (есть MPS на Apple Silicon)
|
||||||
|
# ============================================
|
||||||
|
if [ "$PLATFORM" = "macos" ]; then
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🍎 macOS: подготовка системы..."
|
||||||
|
|
||||||
|
# --- Homebrew ---
|
||||||
|
if ! command -v brew &> /dev/null; then
|
||||||
|
echo "⚠️ Homebrew не найден. Установите его вручную:"
|
||||||
|
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
||||||
|
echo " и запустите скрипт снова."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Homebrew: $(brew --version | head -1)"
|
||||||
|
|
||||||
|
# --- Python через brew (если нет) ---
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "📥 Устанавливаю python@3.13 через brew..."
|
||||||
|
brew install python@3.13
|
||||||
|
fi
|
||||||
|
echo "🐍 Python: $(python3.13 --version)"
|
||||||
|
|
||||||
|
# --- Создание venv ---
|
||||||
|
echo ""
|
||||||
|
echo "📦 Создание виртуального окружения venv..."
|
||||||
|
if [ ! -d ".venv" ]; then
|
||||||
|
python3.13 -m venv .venv
|
||||||
|
fi
|
||||||
|
source .venv/bin/activate
|
||||||
|
echo " Активировано: $(which python)"
|
||||||
|
|
||||||
|
# --- pip ---
|
||||||
|
echo ""
|
||||||
|
echo "⬆️ Обновление pip..."
|
||||||
|
pip install --upgrade pip wheel setuptools
|
||||||
|
|
||||||
|
# --- MLflow + базовые библиотеки ---
|
||||||
|
echo ""
|
||||||
|
echo "📥 Установка MLflow + scikit-learn + matplotlib..."
|
||||||
|
pip install mlflow scikit-learn pandas numpy matplotlib
|
||||||
|
|
||||||
|
# --- PyTorch (CPU на Intel, MPS на Apple Silicon) ---
|
||||||
|
echo ""
|
||||||
|
echo "🔥 Установка PyTorch (PyPI — без CUDA)..."
|
||||||
|
pip install torch torchvision
|
||||||
|
|
||||||
|
# --- Проверка PyTorch + устройства ---
|
||||||
|
echo ""
|
||||||
|
echo "✅ Проверка PyTorch..."
|
||||||
|
python3 -c "
|
||||||
|
import torch, platform
|
||||||
|
print(f'PyTorch: {torch.__version__}')
|
||||||
|
print(f'MPS available: {torch.backends.mps.is_available()}')
|
||||||
|
if torch.backends.mps.is_available():
|
||||||
|
print('Устройство: Apple Silicon GPU (MPS)')
|
||||||
|
else:
|
||||||
|
print('Устройство: CPU (Intel Mac)')
|
||||||
|
print('ℹ️ На macOS CUDA недоступна — train_gpu.py будет использовать CPU/MPS')
|
||||||
|
"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Linux — GPU-сервер с CUDA (RTX 3090)
|
||||||
|
# ============================================
|
||||||
|
else
|
||||||
|
|
||||||
# --- Проверка GPU ---
|
# --- Проверка GPU ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "📡 Проверка GPU..."
|
echo "📡 Проверка GPU..."
|
||||||
@@ -23,7 +101,7 @@ echo ""
|
|||||||
echo "🐍 Проверка Python..."
|
echo "🐍 Проверка Python..."
|
||||||
python3 --version
|
python3 --version
|
||||||
|
|
||||||
# --- Создание виртуального окружения ---
|
# --- Создание venv ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "📦 Создание виртуального окружения venv..."
|
echo "📦 Создание виртуального окружения venv..."
|
||||||
if [ ! -d ".venv" ]; then
|
if [ ! -d ".venv" ]; then
|
||||||
@@ -32,22 +110,22 @@ fi
|
|||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
echo " Активировано: $(which python)"
|
echo " Активировано: $(which python)"
|
||||||
|
|
||||||
# --- Обновление pip ---
|
# --- pip ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "⬆️ Обновление pip..."
|
echo "⬆️ Обновление pip..."
|
||||||
pip install --upgrade pip wheel setuptools
|
pip install --upgrade pip wheel setuptools
|
||||||
|
|
||||||
# --- Установка MLflow и базовых библиотек ---
|
# --- MLflow + базовые библиотеки ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "📥 Установка MLflow + scikit-learn + matplotlib..."
|
echo "📥 Установка MLflow + scikit-learn + matplotlib..."
|
||||||
pip install mlflow scikit-learn pandas numpy matplotlib
|
pip install mlflow scikit-learn pandas numpy matplotlib
|
||||||
|
|
||||||
# --- Установка PyTorch под CUDA ---
|
# --- PyTorch под CUDA 12.1 ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "🔥 Установка PyTorch (CUDA 12.1)..."
|
echo "🔥 Установка PyTorch (CUDA 12.1)..."
|
||||||
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
||||||
|
|
||||||
# --- Проверка CUDA ---
|
# --- Проверка PyTorch + CUDA ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Проверка PyTorch + CUDA..."
|
echo "✅ Проверка PyTorch + CUDA..."
|
||||||
python3 -c "
|
python3 -c "
|
||||||
@@ -58,15 +136,18 @@ if torch.cuda.is_available():
|
|||||||
print(f'GPU: {torch.cuda.get_device_name(0)}')
|
print(f'GPU: {torch.cuda.get_device_name(0)}')
|
||||||
print(f'VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB')
|
print(f'VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB')
|
||||||
"
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Проверка MLflow ---
|
# ============================================
|
||||||
|
# Общая проверка MLflow (обе платформы)
|
||||||
|
# ============================================
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Проверка MLflow..."
|
echo "✅ Проверка MLflow..."
|
||||||
python3 -c "import mlflow; print(f'MLflow: {mlflow.__version__}')"
|
python3 -c "import mlflow; print(f'MLflow: {mlflow.__version__}')"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo " ✅ Установка завершена!"
|
echo " ✅ Установка завершена! ($PLATFORM)"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Следующие шаги:"
|
echo "Следующие шаги:"
|
||||||
@@ -74,6 +155,11 @@ echo " 1. Активируйте окружение: source .venv/bin/activate
|
|||||||
echo " 2. Запустите MLflow UI: ./start_ui.sh"
|
echo " 2. Запустите MLflow UI: ./start_ui.sh"
|
||||||
echo " 3. В другом терминале:"
|
echo " 3. В другом терминале:"
|
||||||
echo " source .venv/bin/activate"
|
echo " source .venv/bin/activate"
|
||||||
|
if [ "$PLATFORM" = "macos" ]; then
|
||||||
|
echo " python src/train_simple.py --n-estimators 100 --max-depth 8"
|
||||||
|
echo " python src/train_gpu.py --epochs 3 --batch-size 64 --lr 0.01 # CPU/MPS, меньше эпох"
|
||||||
|
else
|
||||||
echo " python src/train_simple.py --n-estimators 100 --max-depth 8"
|
echo " python src/train_simple.py --n-estimators 100 --max-depth 8"
|
||||||
echo " python src/train_gpu.py --epochs 10 --batch-size 256 --lr 0.001"
|
echo " python src/train_gpu.py --epochs 10 --batch-size 256 --lr 0.001"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
+11
-1
@@ -13,11 +13,15 @@
|
|||||||
from sklearn.datasets import load_wine
|
from sklearn.datasets import load_wine
|
||||||
from sklearn.ensemble import GradientBoostingClassifier
|
from sklearn.ensemble import GradientBoostingClassifier
|
||||||
from sklearn.model_selection import train_test_split
|
from sklearn.model_selection import train_test_split
|
||||||
from sklearn.metrics import accuracy_score
|
from sklearn.metrics import accuracy_score, recall_score
|
||||||
|
|
||||||
import mlflow
|
import mlflow
|
||||||
import mlflow.sklearn
|
import mlflow.sklearn
|
||||||
|
|
||||||
|
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
|
||||||
|
import os
|
||||||
|
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# ─── КЛЮЧЕВАЯ СТРОКА ──────────────────────────────────────
|
# ─── КЛЮЧЕВАЯ СТРОКА ──────────────────────────────────────
|
||||||
@@ -60,12 +64,18 @@ def main():
|
|||||||
# Можно добавить и ручные метрики — они дополнят автолог
|
# Можно добавить и ручные метрики — они дополнят автолог
|
||||||
y_pred = model.predict(X_test)
|
y_pred = model.predict(X_test)
|
||||||
acc = accuracy_score(y_test, y_pred)
|
acc = accuracy_score(y_test, y_pred)
|
||||||
|
recall_macro = recall_score(y_test, y_pred, average="macro")
|
||||||
|
recall_weighted = recall_score(y_test, y_pred, average="weighted")
|
||||||
mlflow.log_metric("manual_accuracy", acc)
|
mlflow.log_metric("manual_accuracy", acc)
|
||||||
|
mlflow.log_metric("manual_recall_macro", recall_macro)
|
||||||
|
mlflow.log_metric("manual_recall_weighted", recall_weighted)
|
||||||
|
|
||||||
print(f"\n✅ Готово! Откройте MLflow UI:")
|
print(f"\n✅ Готово! Откройте MLflow UI:")
|
||||||
print(f" Эксперимент: autolog_wine")
|
print(f" Эксперимент: autolog_wine")
|
||||||
print(f" Run: {run.info.run_id}")
|
print(f" Run: {run.info.run_id}")
|
||||||
print(f" Accuracy: {acc:.4f}")
|
print(f" Accuracy: {acc:.4f}")
|
||||||
|
print(f" Recall (macro): {recall_macro:.4f}")
|
||||||
|
print(f" Recall (weighted):{recall_weighted:.4f}")
|
||||||
print(f"\n🔍 Что autolog залогировал автоматически:")
|
print(f"\n🔍 Что autolog залогировал автоматически:")
|
||||||
print(f" • Параметры: n_estimators, learning_rate, max_depth, ...")
|
print(f" • Параметры: n_estimators, learning_rate, max_depth, ...")
|
||||||
print(f" • Метрики: training_accuracy, training_log_loss, ...")
|
print(f" • Метрики: training_accuracy, training_log_loss, ...")
|
||||||
|
|||||||
+21
-5
@@ -35,19 +35,35 @@ def main():
|
|||||||
print("❌ Нет запусков!")
|
print("❌ Нет запусков!")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Какие метрики показывать (показываем recall везде, где он есть)
|
||||||
|
metric_cols = [args.metric, "recall_macro", "recall_weighted",
|
||||||
|
"test_recall_macro", "test_recall_weighted",
|
||||||
|
"manual_recall_macro"]
|
||||||
|
|
||||||
print(f"📊 Топ-{len(runs)} запусков в '{args.experiment}' по {args.metric}:")
|
print(f"📊 Топ-{len(runs)} запусков в '{args.experiment}' по {args.metric}:")
|
||||||
print(f"{'#':>3} | {'Run ID':>36} | {'accuracy':>9} | {'n_est':>6} | {'depth':>5}")
|
header = f"{'#':>3} | {'Run ID':>20} | {'n_est':>6} | {'depth':>5}"
|
||||||
print("-" * 75)
|
for m in metric_cols:
|
||||||
|
header += f" | {m[:14]:>14}"
|
||||||
|
print(header)
|
||||||
|
print("-" * len(header))
|
||||||
|
|
||||||
for i, run in enumerate(runs):
|
for i, run in enumerate(runs):
|
||||||
run_id = run.info.run_id
|
run_id = run.info.run_id
|
||||||
acc = run.data.metrics.get(args.metric, 0)
|
|
||||||
n_est = run.data.params.get("n_estimators", "—")
|
n_est = run.data.params.get("n_estimators", "—")
|
||||||
depth = run.data.params.get("max_depth", "—")
|
depth = run.data.params.get("max_depth", "—")
|
||||||
print(f"{i+1:>3} | {run_id:>36} | {acc:>9.4f} | {n_est:>6} | {depth:>5}")
|
row = f"{i+1:>3} | {run_id[:20]:>20} | {str(n_est):>6} | {str(depth):>5}"
|
||||||
|
for m in metric_cols:
|
||||||
|
v = run.data.metrics.get(m)
|
||||||
|
row += f" | {v:>14.4f}" if v is not None else f" | {'—':>14}"
|
||||||
|
print(row)
|
||||||
|
|
||||||
best = runs[0]
|
best = runs[0]
|
||||||
print(f"\n🏆 Лучший: accuracy={best.data.metrics.get(args.metric, 0):.4f}")
|
print(f"\n🏆 Лучший по {args.metric}: {best.data.metrics.get(args.metric, 0):.4f}")
|
||||||
|
# Покажем recall лучшего, если он залогирован
|
||||||
|
for rm in ("recall_macro", "test_recall_macro", "manual_recall_macro"):
|
||||||
|
rv = best.data.metrics.get(rm)
|
||||||
|
if rv is not None:
|
||||||
|
print(f" {rm} = {rv:.4f}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+18
-4
@@ -10,11 +10,15 @@ MLflow autolog автоматически залогирует КАЖУЮ поп
|
|||||||
from sklearn.datasets import load_digits
|
from sklearn.datasets import load_digits
|
||||||
from sklearn.ensemble import RandomForestClassifier
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
from sklearn.model_selection import GridSearchCV, train_test_split
|
from sklearn.model_selection import GridSearchCV, train_test_split
|
||||||
from sklearn.metrics import accuracy_score
|
from sklearn.metrics import accuracy_score, recall_score
|
||||||
|
|
||||||
import mlflow
|
import mlflow
|
||||||
import mlflow.sklearn
|
import mlflow.sklearn
|
||||||
|
|
||||||
|
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
|
||||||
|
import os
|
||||||
|
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# autolog для sklearn — залогирует все промежуточные попытки GridSearch
|
# autolog для sklearn — залогирует все промежуточные попытки GridSearch
|
||||||
@@ -56,7 +60,8 @@ def main():
|
|||||||
estimator=RandomForestClassifier(random_state=42, n_jobs=-1),
|
estimator=RandomForestClassifier(random_state=42, n_jobs=-1),
|
||||||
param_grid=param_grid,
|
param_grid=param_grid,
|
||||||
cv=5,
|
cv=5,
|
||||||
scoring="accuracy",
|
scoring=["accuracy", "recall_macro"], # мульти-метрика: accuracy + recall
|
||||||
|
refit="accuracy", # лучший выбираем по accuracy
|
||||||
n_jobs=-1,
|
n_jobs=-1,
|
||||||
verbose=1,
|
verbose=1,
|
||||||
)
|
)
|
||||||
@@ -66,14 +71,23 @@ def main():
|
|||||||
best = grid.best_estimator_
|
best = grid.best_estimator_
|
||||||
y_pred = best.predict(X_test)
|
y_pred = best.predict(X_test)
|
||||||
test_acc = accuracy_score(y_test, y_pred)
|
test_acc = accuracy_score(y_test, y_pred)
|
||||||
|
test_recall_macro = recall_score(y_test, y_pred, average="macro")
|
||||||
|
test_recall_weighted = recall_score(y_test, y_pred, average="weighted")
|
||||||
|
best_cv_recall = grid.cv_results_["mean_test_recall_macro"][grid.best_index_]
|
||||||
|
|
||||||
mlflow.log_param("best_params", str(grid.best_params_))
|
mlflow.log_param("best_params", str(grid.best_params_))
|
||||||
mlflow.log_metric("best_cv_score", grid.best_score_)
|
mlflow.log_metric("best_cv_score", grid.best_score_) # accuracy (refit)
|
||||||
|
mlflow.log_metric("best_cv_recall_macro", best_cv_recall)
|
||||||
mlflow.log_metric("test_accuracy", test_acc)
|
mlflow.log_metric("test_accuracy", test_acc)
|
||||||
|
mlflow.log_metric("test_recall_macro", test_recall_macro)
|
||||||
|
mlflow.log_metric("test_recall_weighted", test_recall_weighted)
|
||||||
|
|
||||||
print(f"\n🏆 Лучшие параметры: {grid.best_params_}")
|
print(f"\n🏆 Лучшие параметры: {grid.best_params_}")
|
||||||
print(f" CV score: {grid.best_score_:.4f}")
|
print(f" CV accuracy: {grid.best_score_:.4f}")
|
||||||
|
print(f" CV recall_macro: {best_cv_recall:.4f}")
|
||||||
print(f" Test accuracy: {test_acc:.4f}")
|
print(f" Test accuracy: {test_acc:.4f}")
|
||||||
|
print(f" Test recall_macro: {test_recall_macro:.4f}")
|
||||||
|
print(f" Test recall_weighted: {test_recall_weighted:.4f}")
|
||||||
print(f"\n📊 В MLflow UI:")
|
print(f"\n📊 В MLflow UI:")
|
||||||
print(f" Эксперимент: gridsearch_digits")
|
print(f" Эксперимент: gridsearch_digits")
|
||||||
print(f" Parent run: {run.info.run_id}")
|
print(f" Parent run: {run.info.run_id}")
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ def main():
|
|||||||
y_pred = model.predict(X_test)
|
y_pred = model.predict(X_test)
|
||||||
test_acc = accuracy_score(y_test, y_pred)
|
test_acc = accuracy_score(y_test, y_pred)
|
||||||
test_recall = recall_score(y_test, y_pred, average="macro")
|
test_recall = recall_score(y_test, y_pred, average="macro")
|
||||||
|
test_recall_weighted = recall_score(y_test, y_pred, average="weighted")
|
||||||
test_precision = precision_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")
|
test_f1 = f1_score(y_test, y_pred, average="macro")
|
||||||
|
|
||||||
@@ -112,6 +113,7 @@ def main():
|
|||||||
mlflow.log_metric("cv_std_accuracy", cv_scores.std())
|
mlflow.log_metric("cv_std_accuracy", cv_scores.std())
|
||||||
mlflow.log_metric("test_accuracy", test_acc)
|
mlflow.log_metric("test_accuracy", test_acc)
|
||||||
mlflow.log_metric("test_recall_macro", test_recall)
|
mlflow.log_metric("test_recall_macro", test_recall)
|
||||||
|
mlflow.log_metric("test_recall_weighted", test_recall_weighted)
|
||||||
mlflow.log_metric("test_precision_macro", test_precision)
|
mlflow.log_metric("test_precision_macro", test_precision)
|
||||||
mlflow.log_metric("test_f1_macro", test_f1)
|
mlflow.log_metric("test_f1_macro", test_f1)
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ def main():
|
|||||||
filled = int(bar_len * (i + 1) / len(combos))
|
filled = int(bar_len * (i + 1) / len(combos))
|
||||||
bar = "█" * filled + "░" * (bar_len - filled)
|
bar = "█" * filled + "░" * (bar_len - filled)
|
||||||
print(f"\r [{bar}] {i+1}/{len(combos)} | "
|
print(f"\r [{bar}] {i+1}/{len(combos)} | "
|
||||||
f"acc={test_acc:.4f} | {run_name[:40]:<40}", end="")
|
f"acc={test_acc:.4f} rec={test_recall:.4f} | {run_name[:38]:<38}", end="")
|
||||||
|
|
||||||
print(f"\n\n{'='*60}")
|
print(f"\n\n{'='*60}")
|
||||||
print(f"🏆 Лучший результат:")
|
print(f"🏆 Лучший результат:")
|
||||||
|
|||||||
+18
-3
@@ -29,10 +29,25 @@ def main():
|
|||||||
print(" Сначала запустите train_simple.py и register_model.py")
|
print(" Сначала запустите train_simple.py и register_model.py")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Делаем предсказание на нескольких образцах
|
# --- Качество на отложенной тестовой выборке (как в train_simple.py) ---
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.metrics import accuracy_score, recall_score
|
||||||
|
|
||||||
digits = load_digits()
|
digits = load_digits()
|
||||||
X, y = digits.data, digits.target
|
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
|
||||||
|
)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
acc = accuracy_score(y_test, y_pred)
|
||||||
|
recall_macro = recall_score(y_test, y_pred, average="macro")
|
||||||
|
recall_weighted = recall_score(y_test, y_pred, average="weighted")
|
||||||
|
print(f"\n📈 Качество загруженной модели на тесте ({len(y_test)} образов):")
|
||||||
|
print(f" Accuracy: {acc:.4f}")
|
||||||
|
print(f" Recall (macro): {recall_macro:.4f}")
|
||||||
|
print(f" Recall (weighted): {recall_weighted:.4f}")
|
||||||
|
|
||||||
|
# --- Демо: предсказание на нескольких случайных образцах ---
|
||||||
n_samples = 5
|
n_samples = 5
|
||||||
indices = np.random.choice(len(X), n_samples, replace=False)
|
indices = np.random.choice(len(X), n_samples, replace=False)
|
||||||
samples = X[indices]
|
samples = X[indices]
|
||||||
@@ -46,8 +61,8 @@ def main():
|
|||||||
ok = "✅" if predictions[i] == true_labels[i] else "❌"
|
ok = "✅" if predictions[i] == true_labels[i] else "❌"
|
||||||
print(f"{i+1:>6} | {true_labels[i]:>8} | {predictions[i]:>14} | {ok}")
|
print(f"{i+1:>6} | {true_labels[i]:>8} | {predictions[i]:>14} | {ok}")
|
||||||
|
|
||||||
acc = np.mean(predictions == true_labels)
|
demo_acc = np.mean(predictions == true_labels)
|
||||||
print(f"\nТочность на {n_samples} образцах: {acc:.2%}")
|
print(f"\nТочность на {n_samples} образцах: {demo_acc:.2%}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
Урок 3: Model Registry — управление версиями моделей
|
Урок 3: Model Registry — управление версиями моделей
|
||||||
=====================================================
|
=====================================================
|
||||||
Ищет лучший run по метрике accuracy, регистрирует модель
|
Ищет лучший run по заданной метрике (по умолчанию accuracy),
|
||||||
в Model Registry и переводит её в стадию Production.
|
регистрирует модель в Model Registry и переводит её в стадию Production.
|
||||||
|
|
||||||
|
Можно выбирать лучшую модель по recall:
|
||||||
|
python src/register_model.py --experiment digits_classification --metric recall_macro
|
||||||
|
python src/register_model.py --experiment sweep_digits --metric test_recall_macro
|
||||||
|
|
||||||
Запуск:
|
Запуск:
|
||||||
python src/register_model.py --experiment digits_classification
|
python src/register_model.py --experiment digits_classification
|
||||||
|
|||||||
+59
-10
@@ -2,11 +2,15 @@
|
|||||||
Урок 2: MLflow + PyTorch на GPU (RTX 3090)
|
Урок 2: MLflow + PyTorch на GPU (RTX 3090)
|
||||||
=============================================
|
=============================================
|
||||||
Обучаем CNN на MNIST. Автоматически использует CUDA если доступна,
|
Обучаем CNN на MNIST. Автоматически использует CUDA если доступна,
|
||||||
иначе — CPU. Все параметры, метрики и модель логируются в MLflow.
|
иначе — CPU. На Apple Silicon используется MPS.
|
||||||
|
Все параметры, метрики и модель логируются в MLflow.
|
||||||
|
|
||||||
Запуск на 3090:
|
Запуск на 3090 (CUDA):
|
||||||
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
|
||||||
|
|
||||||
|
Запуск на Apple Silicon (MPS):
|
||||||
|
python src/train_gpu.py --epochs 5 --batch-size 128 --lr 0.001
|
||||||
|
|
||||||
Запуск на CPU (для теста):
|
Запуск на CPU (для теста):
|
||||||
python src/train_gpu.py --epochs 2 --batch-size 64 --lr 0.01
|
python src/train_gpu.py --epochs 2 --batch-size 64 --lr 0.01
|
||||||
"""
|
"""
|
||||||
@@ -17,15 +21,21 @@ import time
|
|||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use("Agg")
|
matplotlib.use("Agg")
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
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
|
||||||
from torchvision import datasets, transforms
|
from torchvision import datasets, transforms
|
||||||
|
from sklearn.metrics import recall_score
|
||||||
|
|
||||||
import mlflow
|
import mlflow
|
||||||
import mlflow.pytorch
|
import mlflow.pytorch
|
||||||
|
|
||||||
|
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
|
||||||
|
import os
|
||||||
|
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
|
||||||
|
|
||||||
|
|
||||||
# ─── Модель: простая CNN ───
|
# ─── Модель: простая CNN ───
|
||||||
class SimpleCNN(nn.Module):
|
class SimpleCNN(nn.Module):
|
||||||
@@ -52,6 +62,15 @@ class SimpleCNN(nn.Module):
|
|||||||
return self.classifier(x)
|
return self.classifier(x)
|
||||||
|
|
||||||
|
|
||||||
|
def get_device() -> torch.device:
|
||||||
|
"""Выбор устройства: CUDA -> MPS (Apple Silicon) -> CPU."""
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
return torch.device("cuda")
|
||||||
|
if getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available():
|
||||||
|
return torch.device("mps")
|
||||||
|
return torch.device("cpu")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="MLflow + PyTorch GPU demo")
|
parser = argparse.ArgumentParser(description="MLflow + PyTorch GPU demo")
|
||||||
parser.add_argument("--epochs", type=int, default=5)
|
parser.add_argument("--epochs", type=int, default=5)
|
||||||
@@ -60,13 +79,16 @@ def main():
|
|||||||
parser.add_argument("--experiment-name", type=str, default="mnist_cnn_gpu")
|
parser.add_argument("--experiment-name", type=str, default="mnist_cnn_gpu")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# ─── Устройство ───
|
# ─── Устройство: CUDA -> MPS -> CPU ───
|
||||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
device = get_device()
|
||||||
print(f"🖥️ Устройство: {device}")
|
print(f"🖥️ Устройство: {device}")
|
||||||
|
gpu_name = None
|
||||||
if device.type == "cuda":
|
if device.type == "cuda":
|
||||||
gpu_name = torch.cuda.get_device_name(0)
|
gpu_name = torch.cuda.get_device_name(0)
|
||||||
gpu_mem = torch.cuda.get_device_properties(0).total_memory / 1e9
|
gpu_mem = torch.cuda.get_device_properties(0).total_memory / 1e9
|
||||||
print(f" GPU: {gpu_name} ({gpu_mem:.1f} GB)")
|
print(f" GPU: {gpu_name} ({gpu_mem:.1f} GB)")
|
||||||
|
elif device.type == "mps":
|
||||||
|
print(" Apple Silicon GPU (MPS)")
|
||||||
|
|
||||||
# ─── MLflow эксперимент ───
|
# ─── MLflow эксперимент ───
|
||||||
mlflow.set_experiment(args.experiment_name)
|
mlflow.set_experiment(args.experiment_name)
|
||||||
@@ -80,9 +102,22 @@ def main():
|
|||||||
os.makedirs(data_dir, exist_ok=True)
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
train_ds = datasets.MNIST(data_dir, train=True, download=True, transform=transform)
|
train_ds = datasets.MNIST(data_dir, train=True, download=True, transform=transform)
|
||||||
test_ds = datasets.MNIST(data_dir, train=False, download=True, transform=transform)
|
test_ds = datasets.MNIST(data_dir, train=False, download=True, transform=transform)
|
||||||
train_loader = DataLoader(train_ds, batch_size=args.batch_size, shuffle=True, num_workers=4)
|
|
||||||
test_loader = DataLoader(test_ds, batch_size=args.batch_size, shuffle=False, num_workers=4)
|
# num_workers: на macOS с MPS многопроцессная загрузка нестабильна
|
||||||
print(f"📊 Train: {len(train_ds)}, Test: {len(test_ds)}")
|
# (fork + MPS) → используем 0 воркеров на mac, 4 на Linux/CUDA.
|
||||||
|
import platform
|
||||||
|
num_workers = 0 if platform.system() == "Darwin" else 4
|
||||||
|
pin_memory = device.type == "cuda"
|
||||||
|
train_loader = DataLoader(
|
||||||
|
train_ds, batch_size=args.batch_size, shuffle=True,
|
||||||
|
num_workers=num_workers, pin_memory=pin_memory,
|
||||||
|
)
|
||||||
|
test_loader = DataLoader(
|
||||||
|
test_ds, batch_size=args.batch_size, shuffle=False,
|
||||||
|
num_workers=num_workers, pin_memory=pin_memory,
|
||||||
|
)
|
||||||
|
print(f"📊 Train: {len(train_ds)}, Test: {len(test_ds)} "
|
||||||
|
f"(num_workers={num_workers}, pin_memory={pin_memory})")
|
||||||
|
|
||||||
# ─── MLflow run ───
|
# ─── MLflow run ───
|
||||||
with mlflow.start_run(run_name=f"cnn_e{args.epochs}_bs{args.batch_size}") as run:
|
with mlflow.start_run(run_name=f"cnn_e{args.epochs}_bs{args.batch_size}") as run:
|
||||||
@@ -130,6 +165,7 @@ def main():
|
|||||||
# ─── Валидация ───
|
# ─── Валидация ───
|
||||||
model.eval()
|
model.eval()
|
||||||
correct, total = 0, 0
|
correct, total = 0, 0
|
||||||
|
all_preds, all_targets = [], []
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
for data, target in test_loader:
|
for data, target in test_loader:
|
||||||
data, target = data.to(device), target.to(device)
|
data, target = data.to(device), target.to(device)
|
||||||
@@ -137,18 +173,28 @@ def main():
|
|||||||
pred = output.argmax(dim=1)
|
pred = output.argmax(dim=1)
|
||||||
correct += (pred == target).sum().item()
|
correct += (pred == target).sum().item()
|
||||||
total += target.size(0)
|
total += target.size(0)
|
||||||
|
all_preds.append(pred.cpu())
|
||||||
|
all_targets.append(target.cpu())
|
||||||
|
|
||||||
acc = correct / total
|
acc = correct / total
|
||||||
|
# Recall (macro) — полнота по всем классам MNIST
|
||||||
|
recall_macro = recall_score(
|
||||||
|
torch.cat(all_targets).numpy(),
|
||||||
|
torch.cat(all_preds).numpy(),
|
||||||
|
average="macro",
|
||||||
|
zero_division=0,
|
||||||
|
)
|
||||||
test_accs.append(acc)
|
test_accs.append(acc)
|
||||||
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("test_recall_macro", recall_macro, 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}, "
|
||||||
f"acc={acc:.4f}, time={elapsed:.1f}s")
|
f"acc={acc:.4f}, recall={recall_macro:.4f}, time={elapsed:.1f}s")
|
||||||
|
|
||||||
# ─── График обучения ───
|
# ─── График обучения ───
|
||||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
|
||||||
@@ -166,17 +212,20 @@ def main():
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
# ─── Логируем модель ───
|
# ─── Логируем модель ───
|
||||||
|
# input_example — numpy ndarray (MLflow 3.x не принимает torch.Tensor).
|
||||||
|
# Форма как у входа модели: (batch, channels, H, W) = (1, 1, 28, 28).
|
||||||
mlflow.pytorch.log_model(
|
mlflow.pytorch.log_model(
|
||||||
model,
|
model,
|
||||||
artifact_path="model",
|
name="model", # name вместо устаревшего artifact_path
|
||||||
registered_model_name=None,
|
registered_model_name=None,
|
||||||
input_example=torch.randn(1, 1, 28, 28).to(device),
|
input_example=np.random.rand(1, 1, 28, 28).astype(np.float32),
|
||||||
)
|
)
|
||||||
|
|
||||||
# ─── Теги ───
|
# ─── Теги ───
|
||||||
mlflow.set_tag("model_type", "SimpleCNN")
|
mlflow.set_tag("model_type", "SimpleCNN")
|
||||||
mlflow.set_tag("framework", "PyTorch")
|
mlflow.set_tag("framework", "PyTorch")
|
||||||
mlflow.set_tag("dataset", "MNIST")
|
mlflow.set_tag("dataset", "MNIST")
|
||||||
|
mlflow.set_tag("device_type", device.type)
|
||||||
|
|
||||||
final_acc = test_accs[-1]
|
final_acc = test_accs[-1]
|
||||||
print(f"\n✅ Обучение завершено!")
|
print(f"\n✅ Обучение завершено!")
|
||||||
|
|||||||
+17
-3
@@ -15,12 +15,17 @@ 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
|
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, recall_score
|
||||||
from sklearn.model_selection import train_test_split
|
from sklearn.model_selection import train_test_split
|
||||||
|
|
||||||
import mlflow
|
import mlflow
|
||||||
import mlflow.sklearn
|
import mlflow.sklearn
|
||||||
|
|
||||||
|
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
|
||||||
|
import os
|
||||||
|
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
|
||||||
|
from mlflow.models.signature import infer_signature
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="MLflow + scikit-learn demo")
|
parser = argparse.ArgumentParser(description="MLflow + scikit-learn demo")
|
||||||
@@ -62,10 +67,16 @@ def main():
|
|||||||
# --- Предсказание и метрики ---
|
# --- Предсказание и метрики ---
|
||||||
y_pred = model.predict(X_test)
|
y_pred = model.predict(X_test)
|
||||||
acc = accuracy_score(y_test, y_pred)
|
acc = accuracy_score(y_test, y_pred)
|
||||||
|
recall_macro = recall_score(y_test, y_pred, average="macro")
|
||||||
|
recall_weighted = recall_score(y_test, y_pred, average="weighted")
|
||||||
print(f"Accuracy: {acc:.4f}")
|
print(f"Accuracy: {acc:.4f}")
|
||||||
|
print(f"Recall (macro): {recall_macro:.4f}")
|
||||||
|
print(f"Recall (weighted): {recall_weighted:.4f}")
|
||||||
|
|
||||||
# Логируем метрики
|
# Логируем метрики
|
||||||
mlflow.log_metric("accuracy", acc)
|
mlflow.log_metric("accuracy", acc)
|
||||||
|
mlflow.log_metric("recall_macro", recall_macro)
|
||||||
|
mlflow.log_metric("recall_weighted", recall_weighted)
|
||||||
# можно логировать несколько шагов (для графиков в 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))
|
||||||
@@ -92,10 +103,13 @@ def main():
|
|||||||
f.write(report)
|
f.write(report)
|
||||||
mlflow.log_artifact(report_path)
|
mlflow.log_artifact(report_path)
|
||||||
|
|
||||||
# --- Логируем саму модель ---
|
# --- Логируем саму модель (с сигнатурой и примером входа) ---
|
||||||
|
signature = infer_signature(X_test[:5], model.predict(X_test[:5]))
|
||||||
mlflow.sklearn.log_model(
|
mlflow.sklearn.log_model(
|
||||||
model,
|
model,
|
||||||
artifact_path="model",
|
name="model", # name вместо устаревшего artifact_path
|
||||||
|
signature=signature, # явная сигнатура модели
|
||||||
|
input_example=X_test[:5], # пример входа → убирает warning
|
||||||
registered_model_name=None, # регистрация — в отдельном скрипте
|
registered_model_name=None, # регистрация — в отдельном скрипте
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+27
-8
@@ -1,21 +1,40 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Запуск MLflow Tracking Server с SQLite backend
|
# Запуск MLflow Tracking Server
|
||||||
# Откройте в браузере: http://localhost:5000
|
# Откройте в браузере: http://localhost:5555
|
||||||
|
#
|
||||||
|
# Backend store = file://$(pwd)/mlruns (тот же каталог, куда скрипты
|
||||||
|
# пишут по умолчанию). Поэтому UI видит все эксперименты/запуски
|
||||||
|
# без доп. настройки tracking URI в скриптах.
|
||||||
|
#
|
||||||
|
# Альтернатива — SQLite-бэкенд (нужен, только если хотите SQL-запросы
|
||||||
|
# к запускам). Тогда раскомментируйте блок SQLITE ниже и закомментируйте
|
||||||
|
# FILE, а в скриптах задайте mlflow.set_tracking_uri("http://localhost:5555")
|
||||||
|
# (или export MLFLOW_TRACKING_URI=http://localhost:5555).
|
||||||
# ============================================================
|
# ============================================================
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
PORT=5555
|
||||||
|
|
||||||
echo "🚀 Запускаем MLflow Tracking Server..."
|
echo "🚀 Запускаем MLflow Tracking Server..."
|
||||||
echo " UI: http://localhost:5000"
|
echo " UI: http://localhost:${PORT}"
|
||||||
echo " DB: sqlite:///mlflow.db"
|
echo " Store: file://${PWD}/mlruns"
|
||||||
echo " Artifacts: ./artifacts"
|
echo " Artifacts: file://${PWD}/artifacts"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Нажмите Ctrl+C для остановки"
|
echo " Нажмите Ctrl+C для остановки"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# ─── FILE backend (по умолчанию — единое хранилище со скриптами) ───
|
||||||
mlflow server \
|
mlflow server \
|
||||||
--backend-store-uri sqlite:///mlflow.db \
|
--backend-store-uri "file://${PWD}/mlruns" \
|
||||||
--default-artifact-root ./artifacts \
|
--default-artifact-root "file://${PWD}/artifacts" \
|
||||||
--host 0.0.0.0 \
|
--host 0.0.0.0 \
|
||||||
--port 5000
|
--port "${PORT}"
|
||||||
|
|
||||||
|
# ─── SQLITE backend (альтернатива — см. комментарий в шапке) ──────
|
||||||
|
# mlflow server \
|
||||||
|
# --backend-store-uri sqlite:///mlflow.db \
|
||||||
|
# --default-artifact-root ./artifacts \
|
||||||
|
# --host 0.0.0.0 \
|
||||||
|
# --port "${PORT}"
|
||||||
|
|||||||
Reference in New Issue
Block a user