30 KiB
Executable file
Контекст проекта YOLO Train WebUI
Дата актуализации: 2026-07-18
Этот файл — основной технический контекст проекта для разработчиков и агентов.
История найденных и исправленных дефектов находится в PROJECT_ISSUES.md.
1. Назначение и границы проекта
YOLO Train WebUI — локальное веб-приложение для настройки и запуска обучения Ultralytics YOLO. Оно предоставляет форму конфигурации, профили запусков, live-логи, прогресс по эпохам, графики метрик, мягкую остановку и интеграцию с MLflow.
Поддерживаемые задачи:
detect— детекция объектов;segment— сегментация;classify— классификация;pose— оценка поз;obb— ориентированные bounding boxes.
Приложение рассчитано на локального доверенного пользователя и один активный запуск обучения. Это не многопользовательская платформа, не планировщик задач и не сервис хранения датасетов. В нём нет встроенных учётных записей, ролей или аутентификации.
2. Технологии
| Область | Технология |
|---|---|
| Backend/API | Python 3.11+, FastAPI, Uvicorn |
| Обучение | Ultralytics YOLO, PyTorch |
| Эксперименты | MLflow |
| Frontend | HTML, CSS, vanilla JavaScript |
| Графики | Chart.js из CDN |
| Real-time | WebSocket |
| Зависимости | uv, frozen-набор в uv.lock |
| Упаковка | Hatchling |
| Тесты | pytest, FastAPI TestClient/httpx, Node.js smoke-test |
| Контейнер | Docker, Docker Compose |
Основные зависимости объявлены в pyproject.toml: fastapi, uvicorn,
websockets, ultralytics, mlflow. Dev-группа содержит pytest и httpx.
Python package называется yolo-train-webui, текущая версия — 0.1.0; wheel
собирается Hatchling только из src/yolo_webui.
yolo_webui.__init__ публично экспортирует TrainingConfig, TrainingEvent и
TrainingRunner.
3. Структура репозитория
.
├── .agents/
│ ├── PROJECT_CONTEXT.md # этот технический контекст
│ └── PROJECT_ISSUES.md # аудит и история исправлений
├── src/yolo_webui/
│ ├── __init__.py # публичные Python-экспорты
│ ├── __main__.py # запуск `python -m yolo_webui`
│ ├── app.py # FastAPI, TrainingManager, REST и WebSocket
│ ├── config.py # dataclass-конфигурации и валидация
│ ├── dataset_splitter.py # detection-style train/val splitter
│ ├── subprocess_runner.py # дочерний процесс обучения и stdout-протокол
│ ├── trainer.py # Ultralytics callbacks, MLflow, cancellation
│ └── static/
│ ├── index.html # форма и панель мониторинга
│ ├── app.js # browser state, API, WebSocket, Chart.js
│ └── style.css # всё визуальное оформление
├── tests/
│ ├── test_app.py # API и TrainingManager
│ ├── test_config.py # конфигурация, безопасность, MLflow env
│ ├── test_splitter.py # классы и разбиение датасета
│ ├── test_subprocess_runner.py
│ ├── test_trainer.py # callbacks и остановка
│ ├── test_frontend.py # запуск Node-проверок из pytest
│ └── frontend_smoke.js # browser stubs, форма и графики
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
├── uv.lock
└── README.md
Рабочие каталоги не входят в Git:
datasets/— локальные датасеты;models/— локальные веса и YAML моделей;runs/— результаты Ultralytics и JSON-профили;.yolo-webui/— сгенерированные split-файлы внутри датасетов;mlflow.db,mlruns/,mlflow/— локальные данные MLflow;.venv/, кэши Python и pytest.
4. Архитектура во время выполнения
Browser
├── HTTP JSON ───────────────┐
└── WebSocket /api/ws ───────┤
v
FastAPI / TrainingManager (основной процесс Uvicorn)
├── хранит LiveState и WebSocket-клиентов
├── сохраняет профили в runs/sessions
└── запускает background thread
|
v
Python subprocess: yolo_webui.subprocess_runner
├── читает временный JSON config
├── ставит SIGTERM/SIGINT handlers
├── создаёт TrainingRunner
├── запускает Ultralytics YOLO.train()
└── пишет события, логи и результат в stdout
|
├── dataset / generated split
├── models / official model download
├── runs / training artifacts
└── MLflow storage
Изоляция обучения в subprocess нужна, чтобы тяжёлый Ultralytics/PyTorch не блокировал ASGI event loop, stdout можно было транслировать в браузер, а зависший запуск — принудительно завершить.
Важная деталь: TrainingRunner используется в двух процессах.
- В родительском
TrainingManagerон хранит ссылку на subprocess и управляет сигналами остановки. - В дочернем процессе отдельный экземпляр владеет моделью Ultralytics и выставляет
trainer.stop = True.
5. Точки входа и запуск
CLI entry point из pyproject.toml:
uv run yolo-train-webui
Альтернативный модульный запуск:
uv run -m yolo_webui
Оба варианта вызывают yolo_webui.app:main. CLI принимает:
--host, default127.0.0.1;--port, default8000.
Перед запуском Uvicorn устанавливается MPLBACKEND=Agg. В Docker приложение слушает
0.0.0.0:8000 внутри контейнера, но Compose публикует его только как
127.0.0.1:8000 на host.
6. Backend и состояние обучения
LiveState
Глобальный TrainingManager хранит единственное состояние:
status;- текущую и общую эпохи;
- список логов;
- историю метрик;
output_dir;stop_requested;last_event_kindдля классификации результата.
Состояния:
idle
└── start -> preparing
├── event started -> training
│ ├── normal exit 0 -> succeeded
│ ├── stop -> stopping -> cancelled
│ └── error -> failed
├── stop -> stopping -> cancelled/failed
└── setup error -> failed
finished больше не создаётся backend-ом; frontend понимает его только для
совместимости со старым состоянием. Новый запуск разрешён лишь когда нет активного
статуса и предыдущий background thread уже завершён.
Потоки и lock
TrainingManager._lock защищает LiveState, ссылку на thread и набор WebSocket.
Нельзя выполнять broadcast() внутри with self._lock: broadcast сам читает
защищённые данные, и повторный захват обычного threading.Lock вызовет deadlock.
WebSocket привязывается к event loop Uvicorn при подключении. Вызовы broadcast из
фонового потока передаются через asyncio.run_coroutine_threadsafe(). Отправки
сериализуются asyncio.Lock; failed-клиенты логируются и удаляются.
Запуск subprocess
TrainingManager._run_subprocess():
- сериализует
TrainingConfig.to_dict()во временный JSON; - запускает текущий interpreter с
-u -m yolo_webui.subprocess_runner; - объединяет stderr со stdout;
- читает поток посимвольно, различая
\rи\nдля progress-строк; - обновляет состояние и транслирует события;
- ждёт return code, удаляет временный JSON и очищает ссылку на процесс.
Внутренний stdout-протокол
Дочерний процесс печатает специальные маркеры:
__YOLO_WEBUI_READY__
__YOLO_WEBUI_EVENT__:{"kind":"epoch","message":"...","epoch":1,"total_epochs":100}
__YOLO_WEBUI_RESULT__:/absolute/path/to/run
READYозначает, что signal handlers уже установлены и отложенный stop можно безопасно доставить.EVENTнесётkind,message,epoch,total_epochs.RESULTпередаёт каталог результатов.- Любая другая строка считается обычным логом.
События от TrainingRunner: info, started, epoch, success, cancelled,
warning. Исключение выводится traceback-ом в stderr/stdout и даёт return code 1.
Классификация завершения
Backend различает:
succeeded— return code0без подтверждённой остановки;cancelled— был stop и subprocess завершился с0, прислалcancelledили был убит force-stop таймером;failed— ненулевой код без подтверждённой отмены, в том числе реальная ошибка, случившаяся после нажатия Stop.
7. Остановка обучения
Остановка кооперативная и двухуровневая:
POST /api/train/stopставитstop_requestedи статусstopping.- Если subprocess ещё не прислал
READY, запрос сохраняется. - После
READYродитель отправляетSIGTERM. - Signal handler дочернего процесса вызывает
TrainingRunner.request_stop(). - Runner выставляет
trainer.stop = Trueсразу либо в ближайшем callback. - Ultralytics штатно завершает callbacks и сохранение результатов.
- Если subprocess не завершился за 30 секунд, parent вызывает
kill().
prepare_run() перед каждым новым запуском очищает stop-флаги и старый таймер.
8. REST и WebSocket API
| Метод | Путь | Назначение |
|---|---|---|
| GET | / |
Возвращает static/index.html |
| GET | /static/* |
CSS и JavaScript |
| GET | /api/config/defaults |
Полный default TrainingConfig |
| GET | /api/datasets |
Верхнеуровневые каталоги и YAML из datasets/ |
| GET | /api/models |
Верхнеуровневые .pt/.pth/.yaml/.yml из models/ |
| GET | /api/sessions |
Список профилей без last_run |
| GET | /api/sessions/{name} |
Загрузить профиль; last_run читать можно |
| POST | /api/sessions/{name} |
Сохранить произвольный JSON профиля |
| DELETE | /api/sessions/{name} |
Удалить профиль |
| GET | /api/train/status |
Текущее состояние, эпохи, результат, метрики, логи |
| POST | /api/train/start |
Провалидировать config, сохранить last_run, запустить |
| POST | /api/train/stop |
Запросить остановку |
| WS | /api/ws |
Init-снимок и live-события |
FastAPI также оставляет включёнными стандартные OpenAPI endpoints: /docs,
/redoc, /openapi.json.
WebSocket server → browser сообщения:
init: полный snapshot состояния, логов и метрик при подключении;status: новое состояние и опциональныйoutput_dir;log:messageиlevel;progress: эпоха, total, извлечённые метрики и сообщение.
Browser → server сообщения не используются; endpoint только читает и отбрасывает их, поддерживая соединение открытым.
Профили хранятся в runs/sessions/{name}.json. Имя: 1–64 символа из латинских
букв, цифр, _, -. last_run зарезервирован для автосохранения при старте: его
можно прочитать, но нельзя создать или удалить через profile endpoints.
9. Конфигурация обучения
TrainingConfig
| Поле | Default | Передача в Ultralytics |
|---|---|---|
dataset |
обязательно; API default coco8.yaml |
data |
model |
обязательно; API default yolo11n.pt |
аргумент конструктора YOLO() |
task |
detect |
аргумент конструктора YOLO() |
epochs |
100 |
epochs |
image_size |
640 |
imgsz |
batch_size |
16 |
batch |
device |
пусто | device, только если задано |
workers |
8 |
workers; 0 допустим |
patience |
100 |
patience; 0 допустим |
project |
runs/train |
project |
run_name |
пусто | name, только если задано |
augmentation |
включена | набор augmentation kwargs |
mlflow |
включён | Ultralytics setting и env |
split |
выключен | preprocessing до YOLO.train() |
Основная валидация:
epochs >= 1,image_size >= 32;- batch положительный или
-1;0и значения< -1запрещены; workers >= 0,patience >= 0;- задача входит в фиксированный список;
- detection-style auto split запрещён для
classify; - dataset/model/project проходят security path validation.
Модель без / или \ считается именем и резолвится как models/{name}.
DatasetSplitConfig
enabled=False;train_ratio=0.8, допустимо0.1…0.95;classes_path="", пустое значение включает автопоиск.
MlflowConfig
enabled=True;tracking_uri="sqlite:///mlflow.db";experiment_name="yolo-webui";run_name="".
Если MLflow включён, tracking URI и experiment name не могут быть пустыми.
mlflow_environment() временно выставляет:
MLFLOW_TRACKING_URI;MLFLOW_EXPERIMENT_NAME;MLFLOW_RUN;MLFLOW_KEEP_RUN_ACTIVE=False.
После обучения предыдущие значения окружения восстанавливаются. В Ultralytics
глобальная настройка mlflow включается/выключается через settings.update().
AugmentationConfig
Default-параметры:
hsv_h=0.015 hsv_s=0.7 hsv_v=0.4
degrees=0.0 translate=0.1 scale=0.5
shear=0.0 perspective=0.0
flipud=0.0 fliplr=0.5 bgr=0.0
mosaic=1.0 mixup=0.0 cutmix=0.0
copy_paste=0.0 erasing=0.4 close_mosaic=10
copy_paste_mode=flip
auto_augment=randaugment
Вероятности и доли валидируются в диапазоне 0…1; degrees, shear и
close_mosaic не могут быть отрицательными. Режимы copy-paste: flip, mixup.
Политики AutoAugment: none (отключено), randaugment, autoaugment, augmix. Если augmentation
выключена, эти kwargs вообще не передаются в Ultralytics. При auto_augment="none" параметр транслируется в None для Ultralytics.
10. Работа с датасетами
Auto split предназначен только для detection-style структуры:
dataset/
├── images/
│ └── **/*.{jpg,jpeg,png,bmp,webp,tif,tiff}
└── labels/
└── **/*.txt
Изображения и labels могут быть вложенными. Для split нужно минимум два изображения.
Shuffle детерминирован seed-ом 42; train и val всегда получают минимум по одному
изображению.
Порядок определения классов:
- явно заданный
classes_path— авторитетный, без fallback при ошибке; - корневой
classes.txt; labels/classes.txt;- первый по имени корневой
.yaml/.ymlс полемnames; - вывод диапазона
0…max_idиз всех label-файлов с именамиclass_N.
Поддерживаются text, YAML list и YAML dict. ID должны быть целыми,
неповторяющимися и последовательными от 0; пустые имена запрещены.
Каждый split создаётся эксклюзивно:
dataset/.yolo-webui/splits/{uuid}/
├── train.txt # абсолютные пути изображений
├── val.txt # абсолютные пути изображений
└── dataset.yaml
Итоговый YAML сохраняет дополнительные ключи исходного YAML, например kpt_shape
и flip_idx, но перезаписывает path, train, val и names. Проверенный
результат read_classes() всегда авторитетен.
Для classify auto split отключён: пользователь должен предоставить готовую
структуру train/, val/ или test/ с подкаталогами классов.
11. TrainingRunner и метрики
Перед обучением Runner:
- повторно валидирует config;
- при необходимости создаёт split и заменяет
dataна generated YAML; - импортирует Ultralytics;
- включает/выключает MLflow integration;
- создаёт
YOLO(config.resolved_model, task=config.task); - подключает callbacks
on_train_start,on_train_epoch_end,on_train_end; - вызывает
model.train(**config.train_kwargs()).
Epoch callback берёт numeric metrics из trainer.metrics, форматирует максимум три
первых значения и отправляет их в текстовом сообщении. Parent разбирает пары
key=value, поэтому live chart сейчас показывает не более трёх метрик на эпоху.
Если trainer.save_dir существует, его путь передаётся parent-у как результат.
Restricted checkpoint loading принудительно включён:
ULTRALYTICS_SAFE_LOAD=1
12. Frontend
Frontend не имеет сборщика и framework: index.html, style.css и app.js
отдаются FastAPI как статические файлы. Chart.js загружается с jsDelivr CDN.
Левая панель содержит профили и вкладки:
- «Основное» — task, model, dataset, auto split;
- «Обучение» — epochs, image size, batch, device, workers, patience, output;
- «Аугментация» — все поля
AugmentationConfig; - «MLflow» — enabled, tracking URI, experiment и run name.
Правая панель содержит статус, timer, progress bar, Start/Stop, live chart и журнал.
Browser state:
- активная вкладка хранится в
localStorage.active_tab; - выбранный профиль —
localStorage.selected_profile; - черновик формы —
localStorage.draft_config; - при старте загрузки приоритет: draft →
last_run→ API defaults; - список датасетов и моделей запрашивается у backend;
- стандартные модели YOLO11 выбираются динамически по task;
- при disconnect WebSocket переподключается через 5 секунд;
initвосстанавливает status, логи, progress и историю графика.
Числа читаются через Number.parseInt/parseFloat и проверку Number.isNaN. Нельзя
заменять это на value || default: допустимые 0 для workers, patience,
close_mosaic и augmentation-параметров должны сохраняться.
Chart datasets создаются по фактически пришедшим ключам. Новая метрика может
появиться на поздней эпохе; пропущенные точки заполняются null, чтобы серии не
сдвигались относительно labels.
13. Безопасность и доверенная модель
Приложение не имеет аутентификации. Безопасность по умолчанию строится на локальной публикации и ограничении файловых путей.
Default доверенные корни:
| Назначение | Корни |
|---|---|
| Dataset и classes | ./datasets |
| Model/checkpoint | ./models, ./runs |
| Training output | ./runs |
Дополнительные корни перечисляются через системный os.pathsep:
YOLO_WEBUI_DATA_ROOTS;YOLO_WEBUI_MODEL_ROOTS;YOLO_WEBUI_RUN_ROOTS.
Проверка запрещает URL, нормализует путь через resolve(strict=False) и проверяет
принадлежность корню, включая существующие symlink-компоненты. Безопасные bare
identifiers разрешены для официальных имён, но существующий одноимённый файл вне
доверенного root отвергается. Model-файлы ограничены расширениями .pt, .pth,
.yaml, .yml.
Compose публикует только 127.0.0.1:8000:8000. Для доступа из сети обязателен
аутентифицирующий reverse proxy и явная оценка риска: API может запускать тяжёлое
обучение, останавливать его и управлять профилями.
14. Docker
Dockerfile:
- основан на
python:3.11-slim; - устанавливает системные библиотеки для OpenCV/PyTorch/Ultralytics;
- фиксирует
uv==0.10.6; - копирует
pyproject.toml,uv.lock, README; - выполняет
uv sync --locked --no-devв/opt/venv; - включает
ULTRALYTICS_SAFE_LOAD=1; - запускает
yolo-train-webui --host 0.0.0.0 --port 8000.
Compose монтирует:
./datasets -> /workspace/datasets
./runs -> /workspace/runs
./models -> /workspace/models
./models/.config -> /root/.config/Ultralytics
Порт 8000 опубликован только на loopback. Порт 5000 объявлен в image, но Compose не запускает и не публикует MLflow UI. GPU reservation оставлена как закомментированный пример для NVIDIA/Linux.
15. Тесты и проверки
Текущий regression suite содержит 49 pytest-тестов.
test_app.py: defaults/status, profiles, deadlock, background WebSocket loop, финальные состояния.test_config.py: kwargs, validation, zero-compatible параметры, MLflow env, security roots и URL.test_splitter.py: форматы классов, приоритеты, nested data, уникальные outputs, сохранение YAML metadata.test_subprocess_runner.py: return codes, READY/RESULT и traceback.test_trainer.py: Ultralytics callbacks, metrics, ранний stop, сигналы.test_frontend.py+frontend_smoke.js: syntax, сохранение нулей, динамические chart series в fake browser environment.
Основные команды:
uv sync --locked
uv run pytest -q
uv run python -m compileall -q src tests
node --check src/yolo_webui/static/app.js
node tests/frontend_smoke.js
uv lock --check
docker compose config
git diff --check
Python-команды проекта следует выполнять через uv run, чтобы использовать
зафиксированное окружение.
16. Согласованное изменение проекта
При добавлении или переименовании config-поля обычно нужно изменить вместе:
- dataclass, default, validation и
train_kwargs()вconfig.py; TrainingConfig.from_dict();- поле в
static/index.html; - чтение в
getFormConfig()и восстановление вapplyConfig()вapp.js; - backend/frontend regression tests;
- README и этот контекст, если меняется пользовательский контракт.
При добавлении нового состояния обучения нужно обновить:
- backend state machine и финальную классификацию;
- WebSocket status payload;
updateUIStatus();- CSS-селекторы
status-*; - тесты переходов и reconnect snapshot.
При изменении subprocess-протокола синхронно меняются subprocess_runner.py и parser
в TrainingManager._handle_subprocess_line(). Префиксы протокола нельзя печатать в
обычных логах.
Критические инварианты:
- не вызывать WebSocket send из нового или чужого event loop;
- не вызывать
broadcast()подTrainingManager._lock; - не объединять
succeeded,cancelled,failedв общийfinished; - не использовать JS truthiness для числовых полей;
- явно указанный classes-файл всегда авторитетен;
- не создавать split поверх пользовательских файлов;
- не снимать
--lockedс Docker/CI установки; - не расширять сетевую публикацию без аутентификации;
- сохранять traceback и ошибки доставки в наблюдаемых логах.
17. Текущие ограничения
- Только один активный training job и один глобальный in-memory
LiveState. - После перезапуска server live state теряется; сохраняются лишь JSON-профили,
last_run, training artifacts и MLflow data. - Нет очереди, scheduler, истории runs API, upload API и файлового браузера.
- Нет встроенной аутентификации и multi-user isolation.
- Discovery просматривает только верхний уровень
datasets/иmodels/. - Live chart зависит от внешнего Chart.js CDN.
- В график попадают максимум три numeric metrics, выбранные callback-ом.
- Реальное длительное YOLO-обучение и Docker image build не входят в быстрый test suite; unit-тесты подменяют Ultralytics и subprocess там, где это возможно.
- В репозитории нет
.dockerignoreи CI-конфигурации; Docker build context зависит от содержимого рабочей копии. - FastAPI TestClient выдаёт deprecation warning для текущей связки Starlette/httpx; тесты при этом проходят.
Отдельного файла лицензии проекта в репозитории нет. README напоминает, что Ultralytics распространяется по AGPL-3.0 и предлагает отдельно проверить условия Enterprise-лицензии для закрытого коммерческого использования.
Перед работой с известными дефектами сверяйтесь с PROJECT_ISSUES.md: на дату этого
контекста перечисленные там 10 проблем исправлены.