Примеры MLumina

Подробные разборы примеров кода: examples/ и ray_wrapper/examples/ — что делает каждый скрипт, как запускать, какие есть известные проблемы.

Examples Overview

Обзор примеров

В репозитории MLumina есть два независимых дерева примеров:

Статус примеров

Корневой run.sh перечисляет варианты запуска — на момент написания этой документации активна (раскомментирована) только одна строка:

python3.12 ./examples/components_wear/example_objects_simulate_ray.py

Остальные примеры не покрыты CI, но все скрипты из examples/ были развёрнуты и прогнаны на локальном Docker-стеке и приведены в рабочее состояние: docker compose -f docker-compose-local.yml up -d --build, затем каждый пример выполнен изнутри кластера (docker exec/docker run с примонтированным репозиторием). По ходу проверки нашлись и были исправлены баги в самих примерах, в модуле components_wear, в Dockerfile/.dockerignore и в конфигурации MLflow — подробности на страницах отдельных примеров (разделы «Исправлено»/«✅ Проверено»).

Как запускать

Большинство примеров в examples/ — обычные Python-скрипты, требующие установленного пакета (pip install -e .) и, для Ray-версий, поднятого кластера (docker compose up -d --build в корне репозитория, см. книгу «Документация MLumina» → страница Deployment). Скрипты в ray_wrapper/examples/ дополнительно предполагают запуск изнутри контейнера head/worker/scheduler (пути вида /home/ray/src/mlumina в runtime_env — это путь внутри контейнера).

Содержание книги

  1. XGBoost Example — полный цикл: загрузка данных, обучение, тюнинг, локально и через Ray.
  2. Image Models Example — обучение и инференс YOLO через YoloImageModel.
  3. Torch Models Example — обучение простой сети и auto_search.
  4. Interpolation Tables Examples — интерполяция таблиц и сравнение sklearn-моделей.
  5. Components Wear Examples — симуляция износа графа компонентов.
  6. Scheduler and Ray Basics — минимальные скрипты проверки связи с кластером и планировщиком.
  7. Ray Wrapper: Linear Regression and SVM — простейшие шаблоны ray.tune.
  8. Ray Wrapper: FCNN and CNN — полносвязные и свёрточные сети на MNIST/CIFAR-10.
  9. Ray Wrapper: Vision Transformer — ViT на CIFAR-10/CIFAR-100.
  10. Running Examples via Scheduler — как отправить любой из примеров в очередь планировщика вместо прямого запуска.

Модульная документация самих API-классов, которые используют эти примеры, — в книге «Документация MLumina».

XGBoost Example

XGBoost Example

Файл: examples/xgboost/example.py. Самый полный и надёжный пример в репозитории — единственный, где корневой README и код полностью совпадают.

✅ Проверено на локальном Docker-стеке: и run_example(), и run_example_ray() отрабатывают до конца, финальная модель логируется в MLflow и реально попадает в S3 на MinIO (подтверждено напрямую через mc ls).

Данные: examples/xgboost/data/train_xgboost.csv и test_xgboost.csv (оба файла присутствуют в репозитории — пример готов к запуску "из коробки"). Конфигурация берётся из configs/settings.yaml через mlumina.core.MLuminaConfig.

Что делает скрипт

if __name__ == "__main__":
    run_example()       # локальный цикл: обучение + тюнинг без Ray
    run_example_ray()    # распределённый цикл: обучение + тюнинг через Ray

run_example() — локальный путь

  1. get_config() — читает уже проинициализированный MLuminaConfig (инициализация происходит автоматически при импорте mlumina.core.config, см. страницу Getting Started в книге «Документация MLumina»).
  2. xgboost_load_dataset(train_path=config.data.train_file, eval_path=config.data.eval_file, ray_data=False) — загружает xgb.DMatrix из CSV, путь к файлам берётся из configs/settings.yaml (data.train_file/data.eval_file).
  3. XGBoostModel(mlflow_uri=config.cluster.mlflow_uri).train(dtrain, deval, **config.training.xgboost.model_dump()) — обучает модель с фиксированными параметрами из секции training.xgboost конфига (objective, tree_method, subsample, colsample_bytree, num_boost_round, error_func).
  4. XGBoostTuningModel(...).run_optimization(dtrain, deval) — подбирает гиперпараметры локальным Optuna (диапазоны — из секции tune.xgboost конфига).
  5. Переобучает модель на лучших параметрах (xgboost_tuning_model.train(...)) — отдельно от автоматического переобучения, которое run_optimization уже делает сама внутри себя при включённом MLflow.

run_example_ray() — распределённый путь

То же самое, но:

Как запустить

Внутри контейнера с доступом к Ray-кластеру (например, docker compose exec head bash, либо через сервис scheduler):

python examples/xgboost/example.py

Перед запуском убедитесь, что configs/settings.yaml указывает на существующие файлы (data.train_file/data.eval_file — тип pydantic.FilePath, при отсутствии файла на диске конфигурация не проинициализируется вовсе, упадёт при импорте mlumina.core).

На что обратить внимание

Исправленный баг: XGBoostTuningModel.__init__() missing num_boost_round

Изначально оба вызова XGBoostTuningModel(...) (в run_example() и run_example_ray()) падали с

TypeError: XGBoostTuningModel.__init__() missing 1 required positional argument: 'num_boost_round'

Причина: конструктор передавал параметры через **config.tune.xgboost.model_dump(), а config.tune.xgboost (XGBoostTuneSettings) не содержит поля num_boost_round — оно есть только в config.training.xgboost (XGBoostTrainSettings). Исправлено — в оба вызова добавлен num_boost_round=config.training.xgboost.num_boost_round.

MLflow: версия клиента и сервера

Логирование модели в run_optimization/run_optimization_ray требует, чтобы версия пакета mlflow у клиента (в этом образе) и у mlflow-server совпадала — до фикса сервер был закреплён на mlflow==2.21.1 (mlflow/requirements.txt), а клиент ставился без пина и получал 3.16.1. MLflow 3.x использует новый API логирования моделей (/api/2.0/mlflow/logged-models), которого нет на сервере 2.x — mlflow.xgboost.log_model(...) падал с 404. Сервер обновлён до 3.16.1, версии синхронизированы.

Image Models Example

Image Models Example

Файлы: examples/image_models/train.py и predict.py. Демонстрируют YoloImageModel на подвыборке COCO (4 изображения, examples/image_models/data/).

✅ Проверено на локальном Docker-стеке: оба скрипта отрабатывают до конца (train.py дообучает все 3 модели, predict.py строит предсказания и сохраняет визуализации). Датасет и Docker-образ были сломаны — оба фикса описаны ниже.

train.py — обучение всех pretrained-моделей в папке

model_files = list(pretrained_dir.glob("*.pt"))   # examples/image_models/pretrained_models/*.pt

for model_path in model_files:
    base_model = YOLO(str(model_path))
    wrapper = YoloImageModel(base_model)
    wrapper.train(data=str(dataset_yaml), epochs=5, imgsz=640)
    wrapper.save(str(trained_dir / f"{model_path.stem}_trained.pt"))

В репозитории уже лежат три готовые pretrained-модели (yolo11n.pt, yolov5nu.pt, yolov8n.pt в pretrained_models/) — скрипт дообучает каждую на локальном мини-датасете (5 эпох) и сохраняет результат в trained_models/. Там же уже лежат готовые *_trained.pt — то есть скрипт был прогнан раньше, и его можно перезапускать повторно (перезапишет результат).

predict.py — инференс всеми обученными моделями

trained_models = list(trained_dir.glob("*.pt"))
for model_path in trained_models:
    model = YOLO(str(model_path))
    wrapper = YoloImageModel(model)
    results = wrapper.predict(str(image_path), save=False)
    results[0].plot(save=True, filename=str(out_img))

Берёт одно фиксированное изображение (data/images/000000000009.jpg), прогоняет через каждую модель из trained_models/ и сохраняет визуализацию в predictions/ ({model}_prediction.jpg) — готовые примеры результатов уже есть в репозитории.

Исправлено: абсолютные пути в датасете + недостающая системная библиотека

Раньше examples/image_models/data/dataset.yaml содержал абсолютные пути конкретной машины разработчика:

train: /home/niashin/mephi/mlumina/examples/image_models/data/images
val: /home/niashin/mephi/mlumina/examples/image_models/data/images

Заменил на train: images / val: images без ключа path:. Важный нюанс, из-за которого первая попытка (path: .) не сработала: ultralytics резолвит path: относительно текущей рабочей директории процесса (если такой путь физически существует — а . существует всегда), а не относительно самого yaml-файла. Если path: в датасете вовсе не задан, ultralytics использует явный fallback — директорию самого yaml-файла, независимо от CWD запускающего процесса. Поэтому правильное решение — не указывать path:, а не указывать его как ..

Отдельно от датасета в самом Docker-образе не хватало системных библиотек libgl1/libglib2.0-0, из-за чего import cv2 (тянется через ultralytics) падал с ImportError: libGL.so.1: cannot open shared object file. Добавлено в Dockerfile.

Как запустить

python examples/image_models/train.py     # дообучение — работает из любой рабочей директории
python examples/image_models/predict.py    # инференс уже обученными моделями

Оба скрипта используют YoloImageModel в однопроцессном режиме (train/predict, не train_ray/predict_ray) — Ray не требуется и не инициализируется.

Torch Models Example

Torch Models Example

Файлы: examples/torch_models/train.py и predict.py.

✅ Проверено на локальном Docker-стеке: оба скрипта отрабатывают до конца. Недостающие данные (см. ниже) добавлены в репозиторий.

Исправлено: недостающий файл данных

Оба скрипта читают examples/torch_models/data/data.csv:

df = pd.read_csv(data_dir / "data.csv", sep=";")
X = df.drop("class", axis=1).values
y = df["class"].values

Папки examples/torch_models/data/ в репозитории не было — файл данных отсутствовал, запуск падал с FileNotFoundError. Добавлен examples/torch_models/data/data.csv — синтетический датасет из 180 строк, 3 класса (по 60 строк каждый), 4 числовых признака (f1…f4, гауссовы "блобы" вокруг разных центров) и колонка class (0/1/2), разделитель ;. Уже обученные веса (models/model.pt, models/model_automl.pt) в репозитории тоже есть и теперь соответствуют этим данным — оба файла перезаписываются при каждом запуске train.py.

train.py

class SimpleNet(nn.Module):
    def __init__(self, input_dim, num_classes):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, 512)
        self.dropout1 = nn.Dropout(0.3)
        self.fc2 = nn.Linear(512, 256)
        self.dropout2 = nn.Dropout(0.3)
        self.fc3 = nn.Linear(256, num_classes)
    # forward: fc1 -> ReLU -> Dropout -> fc2 -> ReLU -> Dropout -> fc3

wrapper = TorchModelWrapper(model)
wrapper.learn(X_train=X, y_train=y, epochs=20, batch_size=4, lr=1e-3)
wrapper.save(models_dir / "model.pt")

wrapper_aml = TorchModelWrapper.auto_search(X, y)
wrapper_aml.save(models_dir / "model_automl.pt")

Сначала обучает вручную заданную трёхслойную сеть (512→256→classes, dropout 0.3), сохраняет как model.pt. Затем отдельно прогоняет TorchModelWrapper.auto_search(X, y) — полный перебор 54 архитектур (см. страницу Torch Models в книге «Документация MLumina») — и сохраняет лучшую как model_automl.pt. Обратите внимание: batch_size=4 в ручном обучении — очень маленький батч, подходит только для небольших демонстрационных датасетов.

predict.py

Пересоздаёт точно такую же архитектуру SimpleNet (руками, не через сериализацию структуры — TorchModelWrapper.save сохраняет только state_dict, не саму архитектуру), загружает веса model.pt и делает предсказания:

model = SimpleNet(input_dim, num_classes)
state = torch.load(models_dir / "model.pt", map_location="cpu")
model.load_state_dict(state)

wrapper = TorchModelWrapper(model, device="cpu")
probs = wrapper.predict(X, return_probs=True)
labels = wrapper.predict(X, return_probs=False)

Важно: input_dim/num_classes в predict.py вычисляются заново из CSV — если структура данных при инференсе отличается от той, на которой обучалась модель (другое число признаков/классов), load_state_dict упадёт из-за несовпадения размерностей слоёв. Здесь нет сохранения/восстановления архитектуры "из коробки" — это ограничение TorchModelWrapper.save, а не только примера.

Interpolation Tables Examples

Interpolation Tables Examples

Файлы: examples/interpolation_tables/interpolation_table.py, models_evaluation.py, models_evaluation_ray.py.

✅ Проверено на локальном Docker-стеке: все три скрипта отрабатывают до конца. Недостающие данные и ошибка выбора метрики (см. ниже) исправлены.

Исправлено: недостающий файл данных

Каждый скрипт читает examples/interpolation_tables/data/table_example.csv:

csv_path = os.path.join(os.path.join(os.path.dirname(__file__), "data"), "table_example.csv")
df = pd.read_csv(csv_path, sep=";")

Папки examples/interpolation_tables/data/ в репозитории не было. Добавлен examples/interpolation_tables/data/table_example.csv — регулярная сетка 5×4 (20 строк): x ∈ {1000, 3000, 5000, 7000, 9000}, y ∈ {200, 600, 1000, 1400} (охватывает точку запроса (4723, 937) из interpolation_table.py и даёт минимум 4 точки на ось, нужные RegularGridInterpolator с method="cubic"), z — гладкая функция от x/y, плюс колонка target (для models_evaluation*.py, где X = df[["x","y","z"]], y = df["target"]) с небольшим шумом.

interpolation_table.py

interp = TableInterpolator(df, x_col="x", y_col="y", z_col="z")
value = interp.get(4723, 937)
Xg, Yg, Zg = interp.get_grid(step_x=50, step_y=50)
interp.plot_surface(step_x=50, step_y=50)

Прямое повторение примера из README/документации модуля — единственный кусок кода в проекте, который дословно совпадает с make_docs/source/introduction/what_is_mlumina.rst.

models_evaluation.py — сравнение моделей без Ray

Последовательно демонстрирует все функции модуля interpolation_tables.models_evaluation, на train/test-разбиении первых 15 / оставшихся строк CSV:

  1. evaluate_model(LinearRegression(), X_train, y_train, X_test, y_test) — обучение + метрики одной модели.
  2. grid_search_model(DecisionTreeRegressor, {"max_depth": (2,5,1), "max_features": (1,3,1)}, ..., optimize_metric="mse") — перебор сетки для одного класса модели.
  3. compare_multiple_models([...], ..., optimize_metric="mse") — сравнение 4 моделей (SVR, DecisionTreeRegressor, LinearRegression, XGBRegressor), каждая со своей сеткой гиперпараметров; замеряется время выполнения.
  4. hyperopt_search_model(DecisionTreeRegressor, {"max_depth": (2,10,1), "min_samples_split": (2,20,1)}, ..., max_evals=40, optimize_metric="mse") — TPE-поиск вместо полного перебора.

models_evaluation_ray.py — распределённая версия

То же самое, но compare_multiple_models_ray([...], ..., optimize_metric="mse") — каждая из 4 моделей оптимизируется в отдельной Ray-задаче.

Исправлено: неверная метрика оптимизации

Раньше пример использовал optimize_metric="r2". Но grid_search_model/compare_multiple_models_ray внутри всегда выбирают результат с наименьшим значением метрики (if score < best_score) — это корректно для mse/rmse/mae (меньше — лучше), но не корректно для r2 (для R² лучше — больше, в идеале ближе к 1; с "r2" выбиралась бы модель с наименьшим/наиболее отрицательным R², т.е. фактически худшая по этой метрике). Заменено на optimize_metric="mse" — с ним обе версии (без Ray и с Ray) стабильно выбирают одну и ту же (действительно лучшую) модель LinearRegression.

Ни grid_search_model, ни compare_multiple_models/compare_multiple_models_ray по-прежнему не поддерживают направление оптимизации (mode="min"/"max") — используйте только mse/rmse/mae-подобные метрики в optimize_metric, не r2.

Как запустить

python examples/interpolation_tables/interpolation_table.py
python examples/interpolation_tables/models_evaluation.py
python examples/interpolation_tables/models_evaluation_ray.py   # требует ray.init() внутри скрипта — работает и локально (без кластера)

Components Wear Examples

Components Wear Examples

Файлы: examples/components_wear/example.py, example_objects_simulate.py, example_objects_simulate_ray.py. Данные: examples/components_wear/data/graph.json (граф из двух узлов A/B, по 4 компонента в каждом, с одной зависимостью B→A) и data/object.json (конкретный объект: компонент A2 с износом 22000 и B1 с износом 63000).

example_objects_simulate_ray.py — единственный пример во всём репозитории, явно включённый в run.sh (все остальные строки там закомментированы).

✅ Проверено на локальном Docker-стеке: все три скрипта отрабатывают до конца — включая example.py, который раньше падал (см. ниже).

example.py

graph = ComponentsGraph(graph_test)
component_object = ComponentsObject(component_object_test)

print(component_object())
print(component_object.get_probabilities(graph))
print(graph.simulate_objects_bn())

component_object.update_wear(graph, 10000)
print(component_object())
print(component_object.get_probabilities(graph))

result = component_object.predict_fix(graph, target_wear=20_000, step=1_000)
for row in result[1]:
    print(row)

Исправлено: два независимых бага

  1. Неверный импорт. Скрипт импортировал from src.mlumina.components_wear import ... — такого пакета src в установленном окружении нет (mlumina ставится как обычный пакет, без префикса src), скрипт падал сразу на старте с ModuleNotFoundError: No module named 'src'. Исправлено на from mlumina.components_wear import ..., как во всех остальных примерах.
  2. Опечатка в самом фреймворке. После фикса импорта скрипт всё равно падал на строке component_object.update_wear(graph, 10000): ComponentsObject.update_wear() и .predict_fix() вызывали несуществующий метод self._get_wear_dict(), тогда как в классе был определён _get__dict() (опечатка, двойное подчёркивание). Метод переименован в _get_wear_dict — сейчас update_wear/predict_fix работают как задокументировано.

Теперь весь скрипт, включая update_wear/predict_fix, отрабатывает от начала до конца.

example_objects_simulate.py — рабочий пример, без Ray

graph = ComponentsGraph(graph_test)
brakes = graph.simulate_objects(n_objects=10000, total_wearout=10000000, step=1000, seed=42, fix_coeff=1.0)

print(f"Broken parts: {len(brakes)}")
print(brakes.head())

Симулирует 10 000 независимых объектов на графе из graph.json, каждый "проезжает" 10 000 000 условных единиц износа с шагом 1000. Возвращает pandas.DataFrame со всеми зафиксированными отказами (колонки failed_node, wear, all_parts). Объект, загруженный из object.json (component_object_test), в этом скрипте создаётся, но не используется — вся работа идёт через graph.simulate_objects(...), который сам генерирует случайные объекты внутри себя (см. страницу Components Wear в «Документация MLumina»: simulate_objects не принимает готовый ComponentsObject, а строит собственные случайные объекты на каждой итерации).

При таком объёме (10 000 объектов × 10 000 шагов) выполнение занимает заметное время на одном процессе — на локальном тесте (1 CPU в контейнере) это было около 6.5 минут, результат — 11 944 зафиксированных отказа. Отсюда мотивация для следующего примера.

example_objects_simulate_ray.py — тот же сценарий через Ray

ray.init("ray://localhost:10001")
...
brakes = graph.simulate_objects_ray(n_objects=10000, total_wearout=10000000, step=1000, seed=42, fix_coeff=1.0)

Идентичные параметры симуляции, но каждый из 10 000 объектов считается в отдельной Ray-задаче (@ray.remote) — распараллеливается на все воркеры кластера. Подключается напрямую к Ray Client Server (ray://localhost:10001 — порт HEADNODEPORT из .env, при запуске не из Docker-сети нужно, чтобы этот порт был проброшен на хост, что и происходит в docker-compose.yml). На локальном тесте отработал за ~5 минут, 12 030 отказов; в середине прогона несколько Ray-воркеров были убиты out-of-memory killer'ом (конкурировал за память с параллельно запущенным тяжёлым тестом на том же урезанном локальном стенде) — Ray автоматически повторил упавшие задачи, и скрипт всё равно успешно завершился без вмешательства.

Как запустить

# без Ray (медленнее, но не требует кластера)
python examples/components_wear/example_objects_simulate.py

# с Ray (нужен поднятый кластер, порт HEADNODEPORT=10001 должен быть доступен)
python examples/components_wear/example_objects_simulate_ray.py

# example.py — теперь тоже полностью рабочий, включая update_wear/predict_fix
python examples/components_wear/example.py

Scheduler and Ray Basics

Scheduler and Ray Basics

Два минимальных скрипта для проверки, что инфраструктура (Ray-кластер, планировщик) вообще работает — без них имеет смысл начинать отладку любого другого примера, если что-то не запускается.

examples/ray_example.py — проверка связи с Ray-кластером

ray.init(address="ray://localhost:10001")

print(f"{len(ray.nodes())} nodes, {ray.cluster_resources()['CPU']} CPU total")

@ray.remote
def f():
    time.sleep(0.001)
    return socket.gethostbyname(socket.gethostname())

object_ids = [f.remote() for _ in range(10000)]
ip_addresses = ray.get(object_ids)

for ip_address, num_tasks in Counter(ip_addresses).items():
    print(f"{num_tasks} tasks on {ip_address}")

Подключается к Ray Client Server по адресу ray://localhost:10001 (порт HEADNODEPORT), печатает число узлов и суммарные CPU-ресурсы кластера, затем запускает 10 000 тривиальных удалённых задач и показывает, сколько из них выполнилось на каждом IP-адресе — наглядно демонстрирует, что worker'ы реально участвуют в распределении нагрузки, а не всё выполняется на одном узле.

Запуск: python examples/ray_example.py (с хоста, где проброшен порт 10001 — по умолчанию так и есть в docker-compose.yml).

examples/scheduler/test.py — тестовая задача для планировщика

import time
for i in range(30):
    print(i)
    time.sleep(1)

Ничего специфичного для mlumina — просто скрипт на ~30 секунд, который печатает числа. Предназначен не для прямого запуска, а как entrypoint для сервиса scheduler (mlumina.scheduler, порт 8264, см. книгу «Документация MLumina»): удобная минимальная нагрузка, чтобы проверить весь путь постановки задачи в очередь, её запуска планировщиком и завершения, не тратя время на реальное обучение модели.

Пример отправки этой задачи в планировщик (аналогично ray_wrapper/examples/*/schedule_*.py, см. следующие страницы книги):

import requests

requests.post(
    "http://127.0.0.1:8264/add_task",
    json={
        "name": "scheduler_smoke_test",
        "entrypoint": "python examples/scheduler/test.py",
        "runtime_env": {"working_dir": "/home/ray/src/mlumina"},
        "priority": 1,
        "cpu": 1,
        "gpu": 0,
        "memory": 1,
        "timeout": 60,
    },
    timeout=60,
)

После постановки в очередь прогресс можно отслеживать через GET /get_running_tasks_list или веб-дашборд планировщика (http://<host>:8264/).

Ray Wrapper: Linear Regression and SVM

Ray Wrapper: Linear Regression and SVM

Самые простые примеры из ray_wrapper/examples/ — хорошая отправная точка, чтобы понять паттерн ray.tune.Tuner, используемый во всех остальных моделях пакета mlumina.ray_wrapper. У обеих моделей есть только tune.py (готовой функции train_* без подбора гиперпараметров для них не предусмотрено).

Linear Regression — ray_wrapper/examples/lin_reg/tune.py

ray.init(address="auto")

search_space = {
    "fit_intercept": tune.choice([True, False]),
    "positive": tune.choice([True, False]),
    "test_size": 0.2,
    "random_state": 42,
}

tune_lin_reg(search_space=search_space, num_samples=4, resources={"CPU": 1}, max_concurrent_trials=4)

Внутри (ray_wrapper/models/lin_reg/lin_reg.py) каждый trial строит sklearn-датасет diabetes (build_sklearn_ray_datasets, регрессия), обучает sklearn.linear_model.LinearRegression с параметрами из search_space, репортит rmse/r2. Отбор — ASHAScheduler(metric="rmse", mode="min"). test_size/random_state передаются как обычные (не варьируемые) значения конфига — tune.choice/tune.loguniform применяются только к fit_intercept/positive.

SVM — ray_wrapper/examples/svm/tune.py

ray.init(address="auto")

search_space = {
    "kernel": tune.choice(["rbf", "linear", "poly"]),
    "C": tune.loguniform(1e-2, 1e2),
    "gamma": tune.choice(["scale", "auto"]),
    "degree": tune.choice([2, 3, 4]),
    "class_weight": tune.choice([None, "balanced"]),
    "test_size": 0.2,
    "random_state": 42,
}

tune_svm(search_space=search_space, num_samples=8, resources={"CPU": 1}, max_concurrent_trials=4)

В отличие от lin_reg, это классификация: внутри (ray_wrapper/models/svm/svm.py) каждый trial строит датасет breast_cancer (бинарная классификация), обучает Pipeline(StandardScaler → SVC(kernel, C, gamma, degree, class_weight)), репортит accuracy. Отбор — ASHAScheduler(metric="accuracy", mode="max").

Как запустить

Оба скрипта вызывают ray.init(address="auto") — рассчитаны на запуск изнутри кластера (в контейнере head/worker, где переменная окружения RAY_ADDRESS/сокет автообнаружения кластера уже настроены), либо нужно подключаться явно через ray://<head>:10001, поправив скрипт:

# из контейнера head/worker
python ray_wrapper/examples/lin_reg/tune.py
python ray_wrapper/examples/svm/tune.py

Результаты (tune.Tuner.fit() → ResultGrid) сохраняются локально в /tmp/ray_results/tune_lin_reg_<uuid> / tune_svm_<uuid> на том узле, где физически исполнялся driver-процесс trial'а — при работе на многих worker'ах это может быть не тот узел, с которого был запущен скрипт.

Ray Wrapper: FCNN and CNN

Ray Wrapper: FCNN and CNN

Полносвязная сеть (MNIST) и свёрточная сеть (CIFAR-10), обе — через ray.train (распределённое обучение) и ray.tune (подбор гиперпараметров поверх распределённого обучения).

Датасеты (MNIST/CIFAR-10) скачиваются автоматически через torchvision в data_config["data_root"] (/tmp/mlumina_data/mnist и /tmp/mlumina_data/cifar10 в примерах) — при первом запуске узлам кластера нужен доступ в интернет; при повторных запусках данные переиспользуются из этой директории на каждом узле.

FCNN — ray_wrapper/examples/fcnn/train.py

train_config = {"hidden1": 256, "hidden2": 128, "dropout": 0.2, "lr": 1e-3, "epochs": 1, "batch_size_per_worker": 32}
scale_config = {"num_workers": 2, "use_gpu": False}
data_config = {"data_root": "/tmp/mlumina_data/mnist"}

train_fcnn(train_config=train_config, scale_config=scale_config, data_config=data_config)

scale_config управляет количеством Ray Train воркеров и GPU; train_config — гиперпараметрами архитектуры/обучения. Один запуск = одна конфигурация, без перебора.

ray_wrapper/examples/fcnn/tune.py

search_space = {
    "hidden1": tune.choice([128, 256, 512]),
    "hidden2": tune.choice([64, 128, 256]),
    "dropout": tune.choice([0.1, 0.2, 0.3]),
    "lr": tune.loguniform(1e-4, 1e-2),
    "epochs": 1,
    "batch_size_per_worker": tune.choice([16, 32, 64]),
}

tune_fcnn(
    search_space=search_space, num_samples=1,
    scale_config={"num_workers": 2, "use_gpu": False},
    data_config={"data_root": "/tmp/mlumina_data/mnist"},
    driver_resources={"CPU": 1}, max_concurrent_trials=2,
    search_algorithm="hyperopt",
)

search_algorithm="hyperopt" — использует mlumina.ray_wrapper.build_search_algorithm (см. книгу «Документация MLumina», страница Ray Wrapper) вместо дефолтного семплера Ray Tune. num_samples=1 в примере — по сути демонстрация механики, а не реальный подбор (для реального тюнинга это значение обычно увеличивают на порядки).

CNN (CIFAR-10) — ray_wrapper/examples/cnn/train.py

scale_config = {"num_workers": 2, "use_gpu": False}
train_config = {"c1": 32, "c2": 64, "c3": 128, "lr": 1e-3, "epochs": 3, "batch_size_per_worker": 16}
data_config = {"data_root": "/tmp/mlumina_data/cifar10"}

train_cnn(
    scale_config=scale_config, train_config=train_config, data_config=data_config,
    mlflow_tracking_uri="http://mlflow-server:5001",
    mlflow_experiment_name="mlumina-cnn-cifar10-train",
    mlflow_run_name="cnn_cifar10_train_example",
)

В отличие от FCNN, CNN-пример сразу подключает MLflow-логирование (метрики, не модель — см. страницу Deployment/XGBoost в «Документация MLumina» про то, какие пути в фреймворке реально сохраняют артефакты в MLflow/S3, а какие только метрики). c1/c2/c3 — число каналов в трёх свёрточных блоках.

ray_wrapper/examples/cnn/tune.py устроен аналогично fcnn/tune.py — варьирует c1/c2/c3/lr/batch_size_per_worker через tune.choice/tune.loguniform.

Как запустить

Изнутри кластера (head/worker), с рабочей директорией src/mlumina:

python ray_wrapper/examples/fcnn/train.py
python ray_wrapper/examples/fcnn/tune.py
python ray_wrapper/examples/cnn/train.py
python ray_wrapper/examples/cnn/tune.py

Дольше всего в первый раз — скачивание датасета; сама тренировка при epochs=1/3 и num_workers=2 рассчитана на быстрый прогон-демонстрацию, а не на получение хорошей модели.

Ray Wrapper: Vision Transformer

Ray Wrapper: Vision Transformer

Vision Transformer (ViT) на CIFAR-10 и CIFAR-100 — самые тяжёлые примеры пакета ray_wrapper, обучаются "с нуля" (без предобученных весов).

CIFAR-10 — ray_wrapper/examples/vit/cifar_10/train.py

scale_config = {"num_workers": 2, "use_gpu": False}
train_config = {
    "num_layers": 6, "num_heads": 4, "hidden_dim": 192, "mlp_dim": 384,
    "lr": 1e-3, "epochs": 1, "batch_size_per_worker": 16,
}
data_config = {"data_root": "/tmp/mlumina_data/cifar10"}

train_vit_cifar10(scale_config=scale_config, train_config=train_config, data_config=data_config)

Компактная конфигурация трансформера: 6 слоёв, 4 головы внимания, скрытая размерность 192, MLP-размерность 384 — заметно меньше "классического" ViT-Base (12 слоёв, 768/3072), рассчитана на демонстрацию на CPU/малом кластере, а не на конкурентоспособное качество.

ray_wrapper/examples/vit/cifar_10/tune.py подбирает гиперпараметры поверх той же архитектуры аналогично примерам FCNN/CNN (см. предыдущую страницу).

CIFAR-100 — ray_wrapper/examples/vit/cifar_100/train.py

Идентичная конфигурация модели (num_layers=6, num_heads=4, hidden_dim=192, mlp_dim=384), но data_config={"data_root": "/tmp/mlumina_data/cifar100"} и, соответственно, train_vit_cifar100 — 100 классов вместо 10. Учитывая ту же компактную архитектуру, что и для CIFAR-10, стоит ожидать заметно более низкое качество на CIFAR-100 (задача сложнее, ёмкость модели та же) — это нормально для демонстрационного примера, не повод считать код сломанным.

Как запустить

Изнутри кластера (head/worker):

python ray_wrapper/examples/vit/cifar_10/train.py
python ray_wrapper/examples/vit/cifar_10/tune.py
python ray_wrapper/examples/vit/cifar_100/train.py
python ray_wrapper/examples/vit/cifar_100/tune.py

Как и в FCNN/CNN-примерах, при use_gpu: False обучение на CPU при epochs=1 — это в первую очередь проверка того, что пайплайн работает end-to-end, а не полноценное обучение модели.

Running Examples via Scheduler

Running Examples via Scheduler

Для каждой модели в ray_wrapper/examples/ рядом с train.py/tune.py лежит schedule_*.py — тот же пример, но запускаемый не напрямую, а через постановку задачи в очередь сервиса scheduler (порт 8264, подробнее — книга «Документация MLumina», страница Scheduler).

Общий паттерн

import requests

SCHEDULER_URL = "http://127.0.0.1:8264/add_task"

task_json = {
    "name": "fcnn",
    "entrypoint": "python ray_wrapper/examples/fcnn/tune.py",
    "runtime_env": {
        "working_dir": "/home/ray/src/mlumina",
        "pip": ["ray", "torch", "tqdm"],
    },
    "priority": 0,
    "cpu": 0,
    "gpu": 0,
    "memory": 1,
    "timeout": 0,
}

response = requests.post(SCHEDULER_URL, json=task_json, timeout=60)
print(response.status_code)
print(response.json())

Существующие файлы: lin_reg/schedule_lin_reg_tune.py, svm/schedule_svm_tune.py, fcnn/schedule_fcnn_train.py, fcnn/schedule_fcnn_tune.py, cnn/schedule_cnn_train.py, cnn/schedule_cnn_tune.py, vit/cifar_10/schedule_cifar10_train.py, vit/cifar_10/schedule_cifar10_tune.py, vit/cifar_100/schedule_cifar100_train.py, vit/cifar_100/schedule_cifar100_tune.py — по одному на каждую пару train/tune, кроме lin_reg и svm (у них есть только tune).

На что обратить внимание

Как отправить задачу

Изнутри контейнера, у которого есть доступ к scheduler по сети (например, head/worker, либо curl/python с той же Docker-сети):

python ray_wrapper/examples/fcnn/schedule_fcnn_tune.py

Прогресс — через GET http://<host>:8264/get_task_queue, GET /get_running_tasks_list или веб-дашборд по адресу http://<host>:8264/.