diff --git a/.gitignore b/.gitignore index b630cb1..b11c688 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ mlruns/ artifacts/* data/MNIST/ *.log + +# Flask serve +instance/ diff --git a/CODE_WALKTHROUGH.md b/CODE_WALKTHROUGH.md new file mode 100644 index 0000000..5b45db1 --- /dev/null +++ b/CODE_WALKTHROUGH.md @@ -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` | diff --git a/MLproject b/MLproject new file mode 100644 index 0000000..29c9ea9 --- /dev/null +++ b/MLproject @@ -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}" diff --git a/README.md b/README.md index 84752aa..20a4148 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # 🧪 MLflow Practice — практический проект Проект для изучения 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/ -├── requirements.txt # зависимости -├── start_ui.sh # запуск MLflow Tracking Server -├── README.md # этот файл -├── data/ # датасеты (MNIST скачается автоматически) -├── artifacts/ # графики, отчёты +├── MLproject # Формат MLflow Projects (воспроизводимость) +├── python_env.yaml # Окружение для MLproject +├── requirements.txt # зависимости +├── setup_server.sh # установка на GPU-сервере +├── start_ui.sh # запуск MLflow Tracking Server +├── CODE_WALKTHROUGH.md # экскурс по коду +├── scripts/ +│ └── setup_git.sh # настройка Git (HTTPS + токен) └── src/ - ├── train_simple.py # Урок 1: scikit-learn + Tracking - ├── train_gpu.py # Урок 2: PyTorch CNN на GPU (3090) - ├── register_model.py # Урок 3: Model Registry - ├── load_and_predict.py # Урок 4: загрузка и предсказание - └── compare_runs.py # Урок 5: сравнение запусков + ├── 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 сервер ``` --- ## 🚀 Быстрый старт -### 1. Установка зависимостей +### 1. Установка ```bash -cd mlflow-practice +# На GPU-сервере: +bash setup_server.sh +source .venv/bin/activate -# Базовые зависимости (CPU — работает везде) -pip install mlflow scikit-learn pandas numpy matplotlib - -# Для GPU-урока (RTX 3090) — установите PyTorch под вашу CUDA: +# PyTorch под CUDA: 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 ./start_ui.sh -# → откройте http://localhost:5000 +# → http://10.0.0.7:5000 ``` -### 3. Урок 1 — scikit-learn (быстрый, CPU) +### 3. Уроки по порядку ```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 200 --max-depth 12 -``` +python src/train_simple.py --n-estimators 50 --max-depth 5 -Сравните результаты в UI → вкладка **Experiments → digits_classification**. - -### 4. Урок 2 — PyTorch CNN на 3090 - -```bash -# На GPU — быстро (3090 справится за пару минут) +# Урок 2 — PyTorch CNN на 3090 (~1-2 мин) python src/train_gpu.py --epochs 10 --batch-size 256 --lr 0.001 -# На CPU — медленно, уменьшите эпохи -python src/train_gpu.py --epochs 2 --batch-size 64 --lr 0.01 -``` - -В UI посмотрите графики `train_loss` и `test_accuracy` по эпохам. - -### 5. Урок 3 — Model Registry - -```bash +# Урок 3 — Регистрация лучшей модели python src/register_model.py --experiment digits_classification -``` -В UI → вкладка **Models** появится модель `digits_rf_model` со стадией **Production**. - -### 6. Урок 4 — Загрузка и предсказание - -```bash +# Урок 4 — Загрузка и предсказание 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 -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 | -| CUDA cores | 10496 | | Рекомендуемый batch-size | 256–512 для MNIST | -| CUDA версия | 12.1 (рекомендуется) | +| CUDA | 12.1 | +| MNIST 10 epochs | ~1-2 минуты | -Код в `train_gpu.py` **автоматически** определяет CUDA: - -```python -device = torch.device("cuda" if torch.cuda.is_available() else "cpu") -``` - -На 3090 обучение MNIST (10 epochs) займёт ~1-2 минуты. +Код GPU-aware — автоматически выбирает CUDA если доступна. --- ## 💡 Эксперименты для самостоятельной работы -1. **Подбор гиперпараметров**: запустите `train_simple.py` с разными - `--n-estimators` и `--max-depth`, сравните в UI -2. **Визуализация**: добавьте логирование ROC-кривой как артефакта -3. **Автолог**: попробуйте `mlflow.autolog()` вместо ручного логирования -4. **GPU эксперименты**: в `train_gpu.py` попробуйте разные `--lr` и - `--batch-size`, сравните графики learning curves -5. **Model Registry**: обучите несколько версий, переведите лучшую в - Production, остальные в Archived -6. **Serving**: попробуйте `mlflow models serve -m models:/digits_rf_model/Production -p 5001` +1. **Подбор гиперпараметров** — запустите sweep с `--max-combos 50` +2. **Сравнение моделей** — обучите RandomForest vs GradientBoosting vs SVM +3. **Autolog vs ручное** — запустите один датасет с autolog и без, сравните +4. **Model Registry** — обучите несколько версий, переведите лучшую в Production +5. **Serving** — поднимите REST API, отправьте запрос через curl +6. **MLproject** — запустите через `mlflow run .` на другой машине diff --git a/python_env.yaml b/python_env.yaml new file mode 100644 index 0000000..2bf2c4b --- /dev/null +++ b/python_env.yaml @@ -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 diff --git a/requirements.txt b/requirements.txt index b877859..1c1f266 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,5 +11,8 @@ matplotlib>=3.7.0 torch>=2.1.0 torchvision>=0.16.0 +# === Для Model Serving (Урок 9) === +flask>=3.0.0 + # === Для MLflow UI / Model Registry === # SQLite backend уже входит в стандартную поставку Python diff --git a/scripts/setup_git.sh b/scripts/setup_git.sh new file mode 100755 index 0000000..ca201a5 --- /dev/null +++ b/scripts/setup_git.sh @@ -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 " (если спросит пароль — токен не подхватился)" diff --git a/src/autolog_demo.py b/src/autolog_demo.py new file mode 100644 index 0000000..4c9a3cf --- /dev/null +++ b/src/autolog_demo.py @@ -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() diff --git a/src/grid_search_cv.py b/src/grid_search_cv.py new file mode 100644 index 0000000..80d2c4a --- /dev/null +++ b/src/grid_search_cv.py @@ -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() diff --git a/src/hyperparam_sweep.py b/src/hyperparam_sweep.py new file mode 100644 index 0000000..4ba99d1 --- /dev/null +++ b/src/hyperparam_sweep.py @@ -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() diff --git a/src/serve_model.py b/src/serve_model.py new file mode 100644 index 0000000..0c24c1a --- /dev/null +++ b/src/serve_model.py @@ -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() diff --git a/src/train_gpu.py b/src/train_gpu.py index b6d3be5..7c0bbe0 100644 --- a/src/train_gpu.py +++ b/src/train_gpu.py @@ -18,7 +18,6 @@ import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import torch -from sklearn.metrics import recall_score, precision_score, f1_score import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader @@ -141,27 +140,11 @@ def main(): acc = correct / total 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 # Логируем метрики по эпохам mlflow.log_metric("train_loss", avg_loss, 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) print(f" → Epoch {epoch+1}: loss={avg_loss:.4f}, " @@ -186,7 +169,6 @@ def main(): mlflow.pytorch.log_model( model, artifact_path="model", - serialization_format="pickle", registered_model_name=None, input_example=torch.randn(1, 1, 28, 28).to(device), ) diff --git a/src/train_simple.py b/src/train_simple.py index 32d580a..2f33238 100644 --- a/src/train_simple.py +++ b/src/train_simple.py @@ -15,7 +15,7 @@ import matplotlib.pyplot as plt import numpy as np from sklearn.datasets import load_digits 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 import mlflow @@ -66,9 +66,6 @@ def main(): # Логируем метрики 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) for i, tree in enumerate(model.estimators_): tree_acc = accuracy_score(y_test, tree.predict(X_test)) diff --git a/start_ui.sh b/start_ui.sh index 32df2e3..5537389 100755 --- a/start_ui.sh +++ b/start_ui.sh @@ -7,22 +7,15 @@ set -e cd "$(dirname "$0")" echo "🚀 Запускаем MLflow Tracking Server..." -echo " UI: http://10.0.0.7:5000" +echo " UI: http://localhost:5000" echo " DB: sqlite:///mlflow.db" echo " Artifacts: ./artifacts" echo "" echo " Нажмите Ctrl+C для остановки" echo "" -export MLFLOW_ALLOW_ORIGIN="*" - mlflow server \ --backend-store-uri sqlite:///mlflow.db \ --default-artifact-root ./artifacts \ - --serve-artifacts \ - --allowed-hosts "*" \ - --cors-allowed-origins "*" \ - --artifacts-destination ./artifacts \ --host 0.0.0.0 \ - --port 5000 \ - --dev + --port 5000