Files
mlflow-practice/src/hyperparam_sweep.py
T
second_constantine 9c3321b3a7 Перевод tracking на SQLite + HTTP-клиенты (file-store запрещён в MLflow 3.x)
- start_ui.sh: SQLite backend (sqlite:///mlflow.db) вместо file-store
- все скрипты src/: mlflow.set_tracking_uri(http://localhost:5555) с env-override
- README: сервер должен быть запущен до запуска скриптов
- AGENTS.md: обновлена архитектура хранения и gotcha
2026-07-21 00:10:02 +03:00

153 lines
6.3 KiB
Python

"""
Урок 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
import os
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
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_recall_weighted = recall_score(y_test, y_pred, average="weighted")
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_recall_weighted", test_recall_weighted)
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} rec={test_recall:.4f} | {run_name[:38]:<38}", 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()