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