Примеры MLumina
Подробные разборы примеров кода: examples/ и ray_wrapper/examples/ — что делает каждый скрипт, как запускать, какие есть известные проблемы.
- Examples Overview
- XGBoost Example
- Image Models Example
- Torch Models Example
- Interpolation Tables Examples
- Components Wear Examples
- Scheduler and Ray Basics
- Ray Wrapper: Linear Regression and SVM
- Ray Wrapper: FCNN and CNN
- Ray Wrapper: Vision Transformer
- Running Examples via Scheduler
Examples Overview
Обзор примеров
В репозитории MLumina есть два независимых дерева примеров:
examples/— самостоятельные скрипты, демонстрирующие каждый пакет фреймворка (xgboost,image_models,torch_models,interpolation_tables,components_wear), плюс пара скриптов для проверки связи с Ray и планировщиком.src/mlumina/ray_wrapper/examples/— "поваренная книга" готовых пайплайнов Ray Train/Tune (линейная регрессия, SVM, полносвязная сеть, CNN, Vision Transformer), у каждого есть параtrain.py/tune.pyдля прямого запуска иschedule_*.pyдля запуска через сервис-планировщик.
Статус примеров
Корневой 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 — это путь внутри контейнера).
Содержание книги
- XGBoost Example — полный цикл: загрузка данных, обучение, тюнинг, локально и через Ray.
- Image Models Example — обучение и инференс YOLO через
YoloImageModel. - Torch Models Example — обучение простой сети и
auto_search. - Interpolation Tables Examples — интерполяция таблиц и сравнение sklearn-моделей.
- Components Wear Examples — симуляция износа графа компонентов.
- Scheduler and Ray Basics — минимальные скрипты проверки связи с кластером и планировщиком.
- Ray Wrapper: Linear Regression and SVM — простейшие шаблоны
ray.tune. - Ray Wrapper: FCNN and CNN — полносвязные и свёрточные сети на MNIST/CIFAR-10.
- Ray Wrapper: Vision Transformer — ViT на CIFAR-10/CIFAR-100.
- 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() — локальный путь
get_config()— читает уже проинициализированныйMLuminaConfig(инициализация происходит автоматически при импортеmlumina.core.config, см. страницу Getting Started в книге «Документация MLumina»).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).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).XGBoostTuningModel(...).run_optimization(dtrain, deval)— подбирает гиперпараметры локальным Optuna (диапазоны — из секцииtune.xgboostконфига).- Переобучает модель на лучших параметрах (
xgboost_tuning_model.train(...)) — отдельно от автоматического переобучения, котороеrun_optimizationуже делает сама внутри себя при включённом MLflow.
run_example_ray() — распределённый путь
То же самое, но:
ray.init(address=f"ray://{config.cluster.head_host}")— подключение к Ray-кластеру по адресу из конфига (cluster.head_host, напримерhead:10001внутри Docker-сети).xgboost_load_dataset(..., ray_data=True)(по умолчанию) — данные загружаются какRayDMatrix.XGBoostModel.train_ray(...)иXGBoostTuningModel.run_optimization_ray(...)— распределённое обучение/тюнинг черезxgboost_rayиray.tune.
Как запустить
Внутри контейнера с доступом к 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).
На что обратить внимание
- В
run_example()xgboost_tuning_model.train(...)в конце вызывается на экземпляре, хотяXGBoostModel.train—@staticmethod(Python это допускает); работает корректно, просто нетипичный стиль вызова. - При включённом
mlflow_uri(он передаётся автоматически изconfig.cluster.mlflow_uri)run_optimization/run_optimization_rayтеперь сами логируют финальную модель в MLflow — соответственно, ручное переобучение в концеrun_example()/run_example_ray()частично дублирует эту работу (не ошибка, просто модель обучается дважды: один раз внутри тюнинга для лога в MLflow, второй раз — явно в самом скрипте).
Исправленный баг: 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:
evaluate_model(LinearRegression(), X_train, y_train, X_test, y_test)— обучение + метрики одной модели.grid_search_model(DecisionTreeRegressor, {"max_depth": (2,5,1), "max_features": (1,3,1)}, ..., optimize_metric="mse")— перебор сетки для одного класса модели.compare_multiple_models([...], ..., optimize_metric="mse")— сравнение 4 моделей (SVR,DecisionTreeRegressor,LinearRegression,XGBRegressor), каждая со своей сеткой гиперпараметров; замеряется время выполнения.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)
Исправлено: два независимых бага
- Неверный импорт. Скрипт импортировал
from src.mlumina.components_wear import ...— такого пакетаsrcв установленном окружении нет (mluminaставится как обычный пакет, без префиксаsrc), скрипт падал сразу на старте сModuleNotFoundError: No module named 'src'. Исправлено наfrom mlumina.components_wear import ..., как во всех остальных примерах. - Опечатка в самом фреймворке. После фикса импорта скрипт всё равно падал на строке
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).
На что обратить внимание
working_dir— путь внутри контейнера, а не на машине, откуда отправляется HTTP-запрос (/home/ray/src/mlumina— это то, что смонтировано в контейнерschedulerвdocker-compose.yml). Отправлять такие запросы имеет смысл из среды, где этот путь существует и виден Ray-кластеру — то есть из контейнеров стека, а не с произвольного ноутбука разработчика.- Поле
"name": "fcnn"захардкожено одинаково во всехschedule_*.py, включаяschedule_lin_reg_tune.py,schedule_svm_tune.py,schedule_cifar10_train.pyи т.д. — это copy-paste-артефакт, не ошибка запуска: имя используется только для генерацииTask.id(mlumina-{name}-priority-...-{uuid}) и отображения в очереди/дашборде, само задание при этом запускает правильныйentrypoint. Тем не менее в дашборде планировщика все такие задачи будут выглядеть как "fcnn" независимо от реальной модели — при желании стоит поправитьnameпод конкретную задачу перед отправкой. "cpu": 0, "gpu": 0, "memory": 1— это ресурсы, которые планировщик резервирует для допуска задачи в очередь (проверка_check_resources_by_allocationпротивray.cluster_resources()), а не то, что реально ограничивает Ray-задачу при выполнении — фактическое потребление ресурсов определяется тем, что делает самentrypoint-скрипт (scale_config,resources=и т.п. внутри train/tune-функций).Scheduler._add_task_to_queueотклоняет задачу только еслиcpu == 0 and gpu == 0 and memory == 0одновременно — здесьmemory=1этого не допускает, так что все такие задания успешно проходят допуск."timeout": 0— без ограничения по времени выполнения (планировщик не станет автоматически отменять задачу по таймауту).
Как отправить задачу
Изнутри контейнера, у которого есть доступ к 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/.