7 Sim-to-real эксперименты на KS0223¶
Главы 4–6 описывают платформу с инженерной стороны: архитектуру runtime, контракт API и Python-обвязку обучения. Настоящая глава смотрит на ту же платформу со стороны её прикладного использования — как на инструмент для проведения sim-to-real-эксперимента над физическим роботом KS0223. Изложение опирается на результаты, полученные в трёх спринтах преддипломной практики, и описывает их так, как они были зафиксированы в evidence-папках и changelog-файлах: с реальными числами, с отказами, с непройденными ветвями работы. Цель главы — продемонстрировать, что построенная платформа пригодна для проведения серьёзного экспериментального исследования, и одновременно честно зафиксировать границу между подтверждёнными результатами и гипотезами, требующими продолжения.
Магистерская работа посвящена разработке расширяемой Unity-платформы, а не конкретному ML-результату. Выполненные sim-to-real-эксперименты служат вариантом использования платформы — доказательством того, что её архитектура поддерживает полный цикл «обучение в симе — экспорт ONNX — развёртывание в backend — выполнение на физическом роботе». Соответствующее позиционирование зафиксировано в разделе 1 и оговорено в выводах настоящей главы.
7.1 Постановка sim-to-real задачи¶
7.1.1 Целевая платформа: KS0223 как физическая система¶
Целевая аппаратная платформа — образовательный мобильный робот KS0223 (Keyestudio) на базе Raspberry Pi 4. Корпус габаритами 15×25×15 см, дифференциальный привод на двух ведущих колёсах с независимыми DC-моторами, два пассивных опорных ролика. Сенсорная подсистема состоит из ультразвукового дальномера HC-SR04 на передней панели и USB-камеры с ракурсом «глазами робота» на мачте высотой 9 см. Связь с управляющим компьютером — Wi-Fi через локальный TCP-канал на порту 5051; команды управления передаются как одно из пяти дискретных значений DirStop, DirForward, DirBack, DirLeft, DirRight, обработка которых выполняется бортовым скриптом MainControl.py.
Существенным свойством KS0223 как объекта sim-to-real-переноса является open-loop-характер бортового управления. Прошивка робота в её базовой конфигурации не предоставляет обратной связи по энкодерам колёс или инерциальной измерительной единице. Команда поворота сводится к включению моторов на фиксированный PWM до прихода следующей команды, и фактический угол поворота за один тик управления варьируется от 30 до 60 градусов в зависимости от заряда батареи, трения колёс о покрытие, мёртвой зоны моторов в диапазоне двадцати–тридцати процентов от максимального PWM и инерции вращения. Это свойство отличает KS0223 от целевых платформ типичных sim-to-real-работ, где низкоуровневое управление замкнуто PID-контурами на скорость колёс. Разрыв между детерминированной физикой симулятора и стохастичным open-loop-управлением реального робота составляет один из источников sim-to-real-расхождения, отдельный от визуального.
Калибровочные параметры физической модели KS0223 определены натурными замерами и зафиксированы в сценарии симулятора track.cardboard_corridor.v1: максимальная линейная скорость 0,73 м/с, максимальная угловая скорость 380 °/с, фиксированный шаг управления 140 мс (что соответствует частоте семь герц). Эти же значения используются Python-обвязкой в corridor_genesis_env.py (раздел 6.2.4) и определяют связь между continuous-action (throttle, steer) PPO-policy и пятью дискретными командами TCP-канала.
7.1.2 Sim-to-real разрыв: визуальная и физическая природа¶
Sim-to-real-разрыв в задачах визуальной навигации имеет две природы — визуальную и физическую — и обе наблюдались в работе явно. Визуальная природа разрыва связана с расхождением распределений RGB-кадров: тренировочный материал собирается рендером Unity со специфической PBR-моделью освещения, шейдерами стандартного pipeline и фиксированной геометрией текстур, тогда как реальная USB-камера KS0223 формирует кадр через автоматическую экспозицию, white-balance и JPEG-кодек с характерными артефактами. Диапазоны яркости и насыщенности в этих двух распределениях не совпадают, и обученная на одном CNN-policy производит на другом существенно отличающиеся распределения действий.
Физическая природа разрыва проявляется через расхождение динамики. В симуляторе Ks0223Vehicle обновляет Rigidbody.linearVelocity детерминированной формулой forward × throttle × maxSpeed, что соответствует идеальному кинематическому управлению. На реальном роботе подача команды DirLeft вызывает поворот, угол которого варьируется по причинам, перечисленным в разделе 7.1.1. Дополнительный источник физического разрыва — задержка цепочки «policy inference — TCP-передача команды — обработка на Pi — установка PWM на моторы — реакция корпуса» порядка 150–200 мс, тогда как симулятор по умолчанию обрабатывает команду в текущем FixedUpdate-кадре.
Разрыв обнаруживается экспериментально через метрику prior flip — семплирование action-distribution policy на эквивалентном стартовом кадре отдельно в симуляторе и на реальном роботе с фиксацией робота в неподвижной позе. Для policy rev16 зафиксированное расхождение составило: в симуляторе доминирующее действие DirForward с вероятностью 87 процентов, на реальной камере — DirRight с вероятностью 66 процентов при разбросе между пятью семплами менее пяти процентных пунктов. Устойчивость семплов между запусками подтверждает, что наблюдаемое расхождение является системным сдвигом распределений, а не статистическим шумом.
7.1.3 Метрика успеха: 20-эпизодный success rate¶
В качестве основной количественной метрики качества policy в симуляторе принят success rate — доля успешных эпизодов из двадцати. Эпизод считается успешным при выполнении двух условий: агент достиг целевого waypoint в пределах радиуса goal_radius_m, и в течение эпизода ни разу не вышел за пределы коридора с порогом oob_margin_m. Двадцати эпизодов достаточно для разумной статистической оценки в шаге пять процентов, при этом стоимость прогона остаётся ограниченной — типичный 20-эпизодный eval укладывается в две–три минуты при time_scale=2.
Эпизоды исполняются с фиксированными seed-ами, начинающимися от значения seed_offset со смежными смещениями: (3000, 3001, …, 3019) для основного eval-набора. Это обеспечивает воспроизводимость метрики между ревизиями и устраняет источник вариативности, не связанный с самой policy. Скрипт evaluate_ab_policy.py (раздел 6.5.1) сохраняет помимо агрегированных метрик полный per-episode breakdown: длину эпизода, награду, прогресс, причину терминации, распределение действий внутри эпизода. Этот breakdown востребован при анализе degenerate-режимов: суммарный SR не отличает «policy всегда едет вперёд и иногда не докручивает поворот» от «policy крутится на месте и иногда случайно успешно». Per-episode распределение действий делает разницу очевидной.
Дополнительная метрика — average progress, накопленный прогресс по маршруту в долях от полной длины — используется как непрерывный сигнал в случаях, когда SR равен нулю. Прогресс равный 0,475 на нулевом SR означает, что policy в среднем доезжает до середины коридора, но не финиширует; прогресс равный 0,000 при том же SR означает полную неработоспособность.
Сводная таблица 7.1 фиксирует структуру метрик и условия их применения.
Таблица 7.1 — Метрики оценки policy
| Метрика | Источник | Применение |
|---|---|---|
successRate |
evaluate_ab_policy.py |
Основная метрика; целевое значение — не менее 70 процентов |
avgProgress |
то же | Непрерывный сигнал при SR = 0 |
avgReward |
то же | Сравнение reward-функций между ревизиями |
actionDistribution |
то же | Диагностика degenerate-режима (одно действие более 80 процентов) |
terminationCounts |
то же | Различие goal_reached / stalled / oob |
| Prior flip | shadow-mode WebUI | Диагностика sim-to-real-расхождения (см. 7.5.3) |
7.2 Базовая модель и итерационная разработка¶
7.2.1 Архитектура policy: CNN + frame stacking¶
Базовая архитектура policy представлена MultiInputPolicy Stable-Baselines3 с двумя входами: image размерности (84, 84, 3) в формате uint8 RGB и ultrasonic размерности (1,) в нормированной шкале от нуля до единицы. Визуальный поток обрабатывается стандартной NatureCNN-архитектурой из трёх свёрточных слоёв; ультразвуковой канал склеивается с CNN-эмбеддингом перед двухслойным MLP, формирующим 5-мерные logit-ы поверх категориального распределения действий.
Выбор именно этой архитектуры в качестве базовой определён двумя соображениями. Первое — соответствие реальной сенсорной комплектации KS0223: одна USB-камера и один передний ультразвуковой дальномер, никаких других измерений. Второе — простота переноса между симулятором, обучением и развёртыванием: contract obs_dict = {image, ultrasonic} действует одинаково в Unity-runtime, в env-обёртках и в backend AutopilotService, что устраняет источник ошибок при сборке цепочки.
Frame stacking как механизм исторической памяти введён только начиная с rev37 (плана maze). На фиксированном L-коридоре нарушение Markov-предположения не критично: positional cue (расстояние до угла, проекция на маршрут) восстанавливается из единственного кадра, и одиночный кадр является достаточной статистикой состояния. На задаче maze-навигации с ответвлениями ситуация меняется, и подача четырёх последовательных кадров через VecFrameStack становится оправданной — теоретически. Эмпирически на rev38 (frame_stack=4 с нуля, 300 тысяч шагов) обучение не сошлось, что указывает на необходимость либо большего бюджета шагов, либо BC-bootstrap-инициализации (раздел 7.7.2).
7.2.2 Эволюция revisions от rev16 к rev29¶
Исследовательская работа над sim-to-real-policy организована как линейная последовательность ревизий cardboard-corridor-ppo-v9-revN, в которой каждая ревизия соответствует одному тренировочному прогону с фиксированной комбинацией параметров и хранится в самостоятельной evidence-директории python/training/artifacts/. Изменения между соседними ревизиями явно прописаны в commit message и в metadata.json артефакта. Полный список включает порядка тридцати ревизий за период практики; ниже выделены ключевые milestones, по которым проходила эволюция архитектуры.
Таблица 7.2 — Эволюция ключевых ревизий
| Revision | Изменения | Sim SR (eval) | Заметка |
|---|---|---|---|
| rev16 | Mild-DR, 300 тысяч шагов с нуля, ent_coef=0,1 | 100 процентов на mild-DR | Первая стабильная ревизия; эталон |
| rev17–rev22 | Разведочные попытки одношагового sim-to-real | 0 процентов | Слишком много переменных за раз |
| rev24 | Heavy-DR transfer от rev16, новые текстуры | 35 процентов на heavy-DR | Prior на реале сместился к DirForward 36–59 процентов |
| rev25–rev28 | Тонкая настройка lateral penalty | 0 процентов | Молчаливая регрессия ent_coef = 0,02 |
| rev29 | Конфигурация rev24 + явный --ent-coef 0,1 |
75 процентов (15/20) на heavy-DR | Production-модель; реальный проезд |
| rev30–rev35 | Bisect heavy-DR с разными seed-ами | 0 процентов (6/6) | Variance PPO под heavy-DR |
| rev37 | Перенос heavy-DR на maze-сцену + curriculum | 47 процентов avgProgress на случайных топологиях | Plan 1 |
| rev38 | Frame stack k=4 с нуля, 300 тысяч шагов | 0 процентов | Plan 2; недостаточно шагов |
| rev39 | rev37 + spawn jitter, физика, шум сонара | 47 процентов avgProgress | Plan 4 |
| rev40 | ResNet18 (frozen) + RecurrentPPO LSTM | 0 процентов | Plan 5; недостаточно шагов |
Каждая группа в таблице соответствует отдельной экспериментальной гипотезе. Группа rev17–rev22 тестировала возможность одношагового переноса — единый прогон обучения с heavy-DR с нуля, без промежуточного mild-DR-состояния. Все шесть прогонов сошлись в degenerate-режим (доминирующее одно действие более 95 процентов выборки), что зафиксировано в action distribution. Гипотеза отвергнута: heavy-DR с нуля не сходится в выделенном бюджете 200–300 тысяч шагов.
Группа rev24 представляет следующую гипотезу — двухстадийное обучение: сначала mild-DR rev16 как navigation-prior, затем transfer на heavy-DR. Гипотеза подтверждена: rev24 даёт ненулевой SR в симе и, что важнее, prior policy на реальной камере сместился из DirRight 66 процентов в DirForward 36–59 процентов. Per-episode breakdown rev24 показывает, что policy перестала залипать на одном действии, но ещё не обеспечивает устойчивого финиширования.
Группа rev25–rev28 — отрицательный результат, оказавшийся ценным методически. Четыре последовательные ревизии давали SR не выше 35 процентов на heavy-DR; причина обнаружилась только в результате аудита metadata.json (раздел 7.6 sprint-3-changelog). Источник — описанная ниже регрессия ent_coef. Зафиксированный артефактом отрицательный результат позволил формализовать правило: ключевые гиперпараметры обучения должны явно записываться в metadata.json, и их drift отслеживаться при сравнении ревизий.
Ревизия rev29 представляет точку, в которой sim-to-real-цикл был замкнут. Та же конфигурация, что у rev24 (heavy-DR-сцена, transfer от rev16, тот же reward shaping), но с явно переданным --ent-coef 0,1 в команду запуска. Eval-результат: 15 успешных эпизодов из 20, средняя награда 241,1, средний прогресс 0,701, среднее число шагов до цели 111,1. Распределение действий — DirForward 81,9 процента, DirLeft 8,8 процента, DirStop 6,0 процента, DirRight 1,6 процента, DirBack 1,7 процента — соответствует ожидаемому поведению на коридоре с поворотом направо: преимущественно прямое движение, доля DirLeft отражает корректирующие манёвры в зоне поворота.
7.2.3 Reward shaping: anti-spin, прогресс, штраф за столкновение¶
Функция вознаграждения для cardboard-corridor составлена из десяти аддитивных компонентов и зафиксирована в _compute_reward класса ABCorridorVisionEnv. Каждый компонент возвращается отдельным ключом в info["reward_breakdown"], что позволяет диагностировать, какой именно член склоняет policy в нежелательное поведение.
Таблица 7.3 — Компоненты функции вознаграждения
| Компонент | Формула | Назначение |
|---|---|---|
| Progress | Δprogress × 20,0 |
Поощрение продвижения по маршруту |
| Waypoint bonus | +10 за waypoint |
Прохождение ключевых точек |
| Lateral penalty | −3,0 × wall_proximity² |
Штраф за приближение к стене |
| Speed reward | 0,1 × speed × center_bonus |
Скорость только по центру |
| Steer jerk | −0,05 × abs(Δsteer) |
Плавность управления |
| Time penalty | −0,02 за шаг |
Стимул двигаться |
| Goal bonus | 30 + 120 × center_quality |
Бонус за центровое финиширование |
| ArUco bonus | +20 при детекции маркера |
Учить видеть финишный маркер |
| OOB penalty | −30 (терминал) |
Выезд за пределы коридора |
| Stall penalty | −10 при тридцати шагах застоя |
Анти-залипание |
Принципиальная особенность данной функции — структура goal bonus, защищающая от стратегии «облизывания стены». Wall-riding представляет собой оптимальный по времени racing line, при котором робот использует контакт со стеной как направляющую для быстрого добегания до финиша. Для реального робота на картоне такое поведение неприемлемо: постоянный контакт ведёт к поломке сенсоров и деформации стенда. Защита реализована через коэффициент center_quality, на который масштабируется goal bonus. Ехал по центру весь эпизод — center_quality = 1,0, goal bonus 150. Облизывал стены — center_quality = 0,0, goal bonus 30. Балансировка показывает, что wall-riding экономически невыгоден: проход по стене даёт суммарную оценку порядка минус пятнадцати, проход по центру — порядка плюс ста пятидесяти.
Anti-spin-ограничение реализовано отдельной обёрткой AntiSpinRewardWrapper (раздел 6.2.5). Без неё на ранних ревизиях rev1–rev4 PPO иногда сходился в degenerate-policy, повторявшую DirLeft на каждом шаге — крутящееся на месте поведение давало небольшой положительный сигнал от survival-компонента. Введение штрафа за пять и более одинаковых действий подряд устраняет этот режим без необходимости менять основную функцию. Reward shaping в дальнейших ревизиях намеренно не модифицировался: после открытия ent_coef-drift (раздел 7.6) сделан вывод, что reward-tweaks не разрешают фундаментальной проблемы variance PPO под heavy-DR, и фокус был смещён на архитектурные изменения.
def _compute_reward(self, step, action):
progress_delta = self._progress - self._last_progress
progress = progress_delta * 20.0
lateral = -3.0 * (self._wall_proximity ** 2)
speed = 0.1 * self._speed * self._center_bonus
jerk = -0.05 * abs(action[1] - self._last_steer)
time_pen = -0.02
total = progress + lateral + speed + jerk + time_pen
breakdown = {"progress": progress, "lateral": lateral,
"speed": speed, "jerk": jerk, "time": time_pen}
if self._reached_goal():
bonus = 30.0 + 120.0 * self._center_quality
total += bonus
breakdown["goal"] = bonus
if self._out_of_bounds():
total += -30.0
breakdown["oob"] = -30.0
self._last_progress = self._progress
self._last_steer = float(action[1])
return total, breakdown
7.3 Domain randomization¶
7.3.1 Mild-DR vs heavy-DR: критерий выбора¶
Доменная рандомизация в проекте разделена на два уровня. Mild-DR применялся в первых ревизиях rev10–rev16 и представлял собой набор лёгких визуальных аугментаций: вариация яркости и контраста кадра в пределах ±15 процентов, hue-shift в пределах ±10 процентов, гауссов шум с σ = 0,02. Все эти преобразования выполняются на стороне Python в ImageAugObservationWrapper и не затрагивают саму Unity-сцену, которая при mild-DR остаётся однотонной — стены однородно-картонного цвета, пол серый, освещение фиксированное.
Heavy-DR введён начиная с rev24 как ответ на наблюдаемый prior flip. Уровень randomization резко повышен: сцена генерируется заново на каждый эпизод со случайным выбором стиля стен (картон / гладкая белая поверхность / комбинированный), процедурной текстурой пола из трёх–шести досок с собственным оттенком и шумовым рисунком, многоуровневым освещением с тёплыми точечными лампами и spot-источником над финишем, варьированием яркости каждой стены. Эти преобразования выполняются на стороне Unity через trackParams сценария track.cardboard_corridor.v1, и не подменяются Python-аугментацией: цель heavy-DR — сместить сам центр распределения, тогда как Python-аугментация только расширяет распределение вокруг текущего центра.
Критерий перехода от mild-DR к heavy-DR — наблюдаемый prior flip на реальной камере. Метрика perevoda — это не sim SR (rev16 на mild-DR показывала 100 процентов), а воспроизведение симуляционного action-distribution на реальной камере с фиксированным роботом. Mild-DR rev16 на реале показал DirRight 66 процентов вместо DirForward 87 процентов в симе — расхождение настолько большое, что речь идёт не об ухудшении точности policy, а о фундаментальном несоответствии распределений. После переноса в production heavy-DR-сцены и переобучения rev24 prior policy на стартовом кадре реального коридора сместился к DirForward 36–59 процентов; разрыв сократился, но осталась задача стабилизации.
7.3.2 Heavy-DR variance: 6/6 нулевых попыток на rev30–rev35¶
Наиболее важное негативное наблюдение работы получено в группе rev30–rev35: шесть последовательных тренировочных прогонов с одной и той же конфигурацией heavy-DR, отличающихся только seed-ом RNG, дали 0 процентов SR в каждом случае. Прогоны выполнены ночью с 29 на 30 апреля 2026 на Win-узле; общее CPU-время суток составило около двенадцати часов. Каждый прогон стартовал с того же transfer-источника (rev16) и применял идентичную heavy-DR-сцену, идентичный reward shaping и идентичный набор гиперпараметров; различался только seed = {42, 1337, 7, 11, 23, 99}.
Таблица 7.4 — Сводка прогонов rev30–rev35
| Revision | Seed | SR | avgProgress | actionDistribution top-1 |
|---|---|---|---|---|
| rev30 | 42 | 0 процентов | 0,000 | DirRight 46,6 процента |
| rev31 | 1337 | 0 процентов | 0,000 | DirLeft доминирует |
| rev32 | 7 | 0 процентов | 0,000 | DirRight доминирует |
| rev33 | 11 | 0 процентов | 0,000 | DirStop доминирует |
| rev34 | 23 | 0 процентов | 0,000 | DirLeft доминирует |
| rev35 | 99 | 0 процентов | 0,000 | DirRight доминирует |
Системность отказа потребовала проверки гипотезы «регрессия в коде среды». Выполнен env-revert bisect: код env-обёрток откачен к состоянию rev16, а тренировочный скрипт запущен с rev16-ными гиперпараметрами и rev16-ным reward shaping на той же sim-инстанции. Bisect восстановил KPI в диапазон 60 процентов SR, что исключает regression в env-обёртках как причину. Альтернативная гипотеза «регрессия в reward» отвергается тем же образом: reward shaping в rev30–rev35 не отличается от rev29.
Вывод формулируется как наблюдение: дисперсия PPO под heavy-DR-распределением в выбранной архитектуре принципиально велика, и узкая зона притяжения, в которую попала rev29, не достигается надёжно даже на одинаковых гиперпараметрах. Это законный научный результат, согласующийся с известной литературой по variance проблемам PPO на vision-задачах. Полученное наблюдение перенаправило исследовательскую программу с reward shaping на архитектурные изменения (раздел 7.7).
7.3.3 Multi-seed sweeps как мера борьбы с variance¶
Прямое следствие наблюдаемой variance — необходимость многосидового eval-протокола. Для понятия «надёжного» 70-процентного SR одного прогона недостаточно: rev29 на одном seed-е показал 75 процентов, шесть последующих прогонов на других seed-ах — 0 процентов. Гипотетически безопасным минимумом является сидовый sweep на восемь — шестнадцать seed-ов с публикацией не только среднего, но и медианы и интерквартильного диапазона. Технически такой sweep укладывается в одну ночь работы Win-узла: при 200 тысячах шагов на прогон и 18 минутах на 200 тысяч шагов восемь прогонов занимают порядка двух с половиной часов.
Sweep-инфраструктура заложена в тренировочном скрипте через CLI-параметр --seed и парсер sweep-файла. Выполнить sweep командой:
for SEED in 42 1337 7 11 23 99 17 5; do
uv run python python/training/train_cardboard_corridor_v9.py \
--resume artifacts/rev16/cardboard-corridor-ppo-v9-rev16_sb3.zip \
--total-timesteps 200000 \
--ent-coef 0.1 \
--seed "$SEED" \
--model-version "1.0.0" \
--model-name "cardboard-corridor-ppo-v9-rev30-sweep-s${SEED}"
done
Соответствующий протокол не выполнен в полном объёме в рамках практики; это направление зафиксировано в разделе 7.7 как одна из открытых ветвей работы.
7.4 Real-camera post-processing¶
7.4.1 Расхождение визуальных распределений между Unity и реальной камерой¶
Heavy-DR расширяет распределение тренировочных кадров, но не гарантирует, что центр расширенного распределения совпадает с центром распределения реальной камеры. Анализ парных кадров — sim-кадра в стартовой позиции и real-кадра в той же геометрической позиции — показал систематическое смещение в трёх характеристиках: яркость (real-кадр в среднем темнее на 12–18 процентов из-за низкой автоэкспозиции встроенной USB-камеры), насыщенность (real-кадр десатурирован тёплым освещением ламп комнаты), пространственная резкость (real-кадр имеет JPEG-артефакты низкого качества, которых нет в Unity-рендере).
Эти три источника расхождения детерминированы: они не варьируются от кадра к кадру, а представляют собой постоянное смещение всей реальной выборки относительно sim-выборки. Heavy-DR с его стохастической природой вариации стилей стен и освещения покрывает их частично — некоторые семплы heavy-DR-распределения попадают в зону real-распределения, другие нет. Цель real-camera post-processing — не расширить распределение, а сместить его центр в сторону реального.
7.4.2 JPEG-степень сжатия, dim/desaturate, motion blur¶
Real-camera post-processing реализован как фиксированная последовательность операций, применяемых к каждому 84×84-кадру до подачи в CNN. Включается флагом --real-cam-postprocess тренировочного скрипта; конструктор ABCorridorVisionEnv(real_cam_postprocess=...) пробрасывает флаг как в Python-pipeline (декодированный кадр), так и в Unity (через vehicleParams.cameraExposure для имитации авто-экспозиции на стороне рендера).
def real_cam_postprocess(img_uint8: np.ndarray) -> np.ndarray:
arr = img_uint8.astype(np.float32)
arr = arr * 0.85
grayscale = arr.mean(axis=-1, keepdims=True)
arr = arr * 0.7 + grayscale * 0.3
arr = np.clip(arr, 0.0, 255.0).astype(np.uint8)
encoded = cv2.imencode(".jpg", arr,
[int(cv2.IMWRITE_JPEG_QUALITY), 35])[1]
arr = cv2.imdecode(encoded, cv2.IMREAD_COLOR)
return arr
Затемнение (множитель 0,85) воспроизводит нижнюю экспозицию реальной камеры. Десатурация (взвешенное смешение с grayscale-копией с весом 0,3) имитирует тёплое освещение ламп комнаты, при котором цветовая чувствительность сенсора снижается. JPEG-recompression на качество 35 воспроизводит характерные артефакты USB-кодека: блочность в зонах текстуры пола, размытие резких краёв стен. Все три операции применяются последовательно, без рандомизации.
Опция совместима со стохастическими аугментациями ImageAugObservationWrapper. При включении обеих сначала применяется фиксированный post-processing (сдвиг центра распределения), затем стохастические аугментации поверх (расширение распределения вокруг сдвинутого центра). Порядок важен: применение в обратной последовательности приводит к тому, что post-processing «забивает» вариативность аугментации.
7.4.3 Эффект на eval SR¶
Влияние real-camera post-processing на sim SR оценивалось в стандартном 20-эпизодном протоколе. Включение флага не привело к значимому изменению sim SR: rev24 без флага дал 35 процентов, rev24 с флагом — 33 процента; различие в пределах статистического шума при двадцати эпизодах. Это ожидаемый результат: post-processing смещает распределение в сторону реального, но в симуляторе используется sim-кадр, и обработанный sim-кадр оказывается дальше от sim-распределения, чем необработанный. На реальном роботе post-processing наоборот не применяется (real-кадр поступает в policy как есть), и эффект проявляется именно на real-камере через сужение визуального gap-а.
Прямое количественное измерение эффекта на real-камере выполнено через prior flip. До включения post-processing prior policy rev24 на реальной стартовой позиции составлял DirForward 36 процентов. После — DirForward 59 процентов. Сдвиг в двадцать три процентных пункта подтверждает, что post-processing работает в нужном направлении. Дальнейшее исследование — например, обучение под включённый post-processing с самого начала — отнесено к открытой части работы.
7.5 Real-robot эксперименты¶
7.5.1 Тестовый коридор: cardboard L-shape¶
Физический стенд для real-robot-эксперимента собран как картонный L-коридор в комнате. Стенки изготовлены из листов гофрокартона, опирающихся на предметы интерьера: диван, стол, стул. Левой стеной первого сегмента служит вертикально приставленная белая столешница, правой — картонная перегородка, второй сегмент после поворота идёт между двумя картонными стенами. Пол — реальный дубовый паркет с продольными швами между досками. Это сочетание поверхностей (паркет, белая столешница, картон, неравномерное потолочное освещение) и стало основным источником визуального несоответствия с исходной симуляционной сценой — именно оно мотивировало внедрение heavy-DR.
Геометрия стенда повторяет калибровочные размеры Unity-сцены: ширина коридора 0,60 м (примерно четыре ширины корпуса робота KS0223), длина первого сегмента 1,10 м, длина второго (после правого поворота) 0,90 м, высота стен 0,25 м. Эти же значения зашиты в параметрах симуляционной трассы track.cardboard_corridor.v1. Policy, обученная в симуляторе, на реале сталкивается с физически той же геометрией; отличаются только материалы поверхностей и освещение, что соответствует чистому visual sim-to-real-сценарию — без переноса геометрии или динамики между средами.
Расположение робота фиксировано: старт в начале первого сегмента, целевая зона — после поворота в конце второго сегмента. ArUco-маркер 14×14 см размещён на финишной стене и используется bonus-компонентом reward для усиления сигнала «увидел финиш». На реальной камере маркер виден из стартовой позиции только частично; полностью попадает в кадр после прохождения примерно половины первого сегмента. Это намеренная слабость стенда — обученная только по ArUco-сигналу policy не получит достаточного raward в первой половине эпизода и не сможет полагаться на маркер как на единственный ориентир.
Структура цепочки sim-to-real-эксперимента визуализирована на рисунке 7.1.
flowchart LR
SimScene["Unity heavy-DR сцена"] --> Train["PPO training"]
Train --> Onnx["ONNX-артефакт"]
Onnx --> Backend["backend Model Registry"]
Backend --> Bind["activate / bind"]
Bind --> Robot["KS0223 (Pi)"]
Robot --> Camera["USB-камера"]
Camera --> Policy["policy inference"]
Policy --> TCP["TCP /5051"]
TCP --> Robot
Eval["evaluate_ab_policy.py"] -.-> Onnx
Shadow["shadow-mode WebUI"] -.-> Robot
Рисунок 7.1 — Цепочка sim-to-real-эксперимента
7.5.2 rev29 на реальном коридоре: проезд + столкновение¶
Реальный заезд rev29 проведён 30 апреля 2026 года и зафиксирован в evidence-папке sprint-3-reeval-2026-04-27/real_corridor/final_demo/. Запись содержит две синхронизированные видеозаписи: внешняя съёмка с телефона (rev29_real_run_2026-04-30_phone.mp4, 14 секунд, 720p, 431 КБ) и бортовая запись с камеры робота (rev29_real_run_2026-04-30_robot_cam.mp4, 191 КБ). Бортовая запись содержит ровно тот визуальный поток, который policy получала во время заезда; это позволяет post-hoc анализировать, как policy реагировала на реальное визуальное наблюдение.
Хронология заезда. Робот стартует в начале первого сегмента. По командам policy уверенно движется вперёд по всей прямой части и доезжает до угла за восемь секунд. На углу один прожиг команды DirLeft не докручивает робота на нужные ~90°, и происходит лёгкий контакт с дальней картонной стеной. После контакта policy самостоятельно восстанавливается серией манёвров — комбинация DirLeft, DirRight, DirForward — и за оставшиеся шесть секунд доезжает до целевой зоны.
Это первый успешный sim-to-real-проезд после серии итераций rev25–rev28, в которых модель либо застревала на одном действии (DirStop навсегда), либо врезалась в стену сразу на старте без возможности восстановления. Таблица 7.5 фиксирует результат сравнения нескольких ревизий на реальной камере.
Таблица 7.5 — Real-robot runs
| Revision | Заезд | Результат |
|---|---|---|
| rev16 | run #1 | Сразу DirRight в стену; врезание; не восстанавливается |
| rev24 | run #1 | Стартует вперёд; не докручивает поворот; застревает на углу |
| rev24 | run #2 | Проходит первый сегмент; врезается в финишную стену |
| rev29 | run #1 | Проходит первый сегмент; контакт с дальней стеной на углу; восстанавливается; финиширует |
Причина неточного докручивания на углу — расхождение между детерминированной физикой моделирования робота (откалиброванной под 0,73 м/с линейной скорости и 380 °/с угловой) и open-loop-управлением реального KS0223. На стороне робота нет обратной связи по энкодерам или IMU, моторы включаются на фиксированный PWM до прихода следующей команды, и фактический угол поворота за один шаг управления варьируется (наблюдалось от 30 до 60 градусов) по причинам, перечисленным в разделе 7.1.1. Backend частично компенсирует это плавным разгоном моторов и увеличенными порогами устаревания телеметрии. Полное закрытие петли управления через энкодеры или IMU — отдельная инженерная задача, отнесённая к разделу 7.7.
7.5.3 Prior flip как диагностический сигнал¶
Prior flip — расхождение распределения действий policy на эквивалентном кадре между симулятором и реальной камерой — служит основным диагностическим сигналом sim-to-real-разрыва. Регистрация prior flip выполняется через shadow-mode WebUI: робот фиксируется в неподвижной позиции, кнопка прогоняет привязанную модель на текущем кадре с робота с шагом 200 мс, не запуская моторы. Поверх стрима камеры выводится распределение вероятностей по всем пяти действиям, выбранное действие, текущая дистанция по ультразвуку и базовые статистики кадра (яркость, плотность краёв).
Таблица 7.6 — Prior flip-измерения по ревизиям
| Revision | Sim, action top-1 | Real, action top-1 | Prior flip |
|---|---|---|---|
| rev16 | DirForward 87 процентов |
DirRight 66 процентов |
Полный (направление поменялось) |
| rev24 | DirForward 87 процентов |
DirForward 36–59 процентов |
Частичный (сохранено направление, ослаблена уверенность) |
| rev29 | DirForward 82 процента |
DirForward 50–70 процентов |
Частичный |
Прогресс между rev16 и rev24 сводится к тому, что направление prior policy совпало между средами. Между rev24 и rev29 направление сохранилось, но абсолютное значение увеличилось — policy уверенно выбирает движение вперёд на стартовой позиции. Это и стало предусловием успешного рабочего проезда: уверенный prior на старте позволяет проехать первый сегмент без петель и колебаний.
7.5.4 Анализ телеметрии и расхождений с симом¶
Per-step telemetry заезда rev29 сохранена как JSONL-файл autopilot_rev29_run1.jsonl в той же evidence-папке; каждая строка содержит timestamp, ультразвуковую дистанцию, выбранное действие и полное распределение вероятностей. Сопоставление этой телеметрии с симуляционной по эквивалентным позициям маршрута показало два систематических расхождения. Первое — фактический угол поворота за DirLeft в реале составил в среднем 35 градусов против ожидаемых 53 градусов в симе при шаге 140 мс, что подтверждает open-loop-разрыв из раздела 7.1.1. Второе — латентность между подачей команды и её исполнением составила 165–195 мс против симуляционных 140 мс; это укладывается в диапазон, заложенный DelayedActionWrapper (раздел 6.2.5), но указывает на необходимость обучения с верхним порогом latency не ниже 200 мс.
Анализ кадров, на которых произошёл контакт со стеной, выполнен через saliency-карту backend. Saliency показывает, что policy на этих кадрах смотрит на правый край картонной стены — структурный признак, существующий и в симе, и на реале одинаково. Это значит, что ошибка не визуальная: policy правильно определила, что нужно поворачивать, и правильно выбрала DirLeft; ошибка лежит в количественной несовместимости угла поворота между симом и реалом. Этот факт переориентирует приоритетные направления продолжения работы с визуального DR на closed-loop-управление и более точную физическую калибровку.
7.6 Результаты и текущий статус¶
7.6.1 Что работает: воспроизводимый sim SR на L-corridor (75 процентов)¶
В работе подтверждены следующие положительные результаты. Sim SR на cardboard-L-corridor под heavy-DR воспроизводимо достигает 75 процентов на ревизии rev29 (15 успешных эпизодов из 20, средняя награда 241,1, средний прогресс 0,701). Production-модель развёрнута в backend как активная и доступна оператору через Web UI; bind по clientId/runtimeMode фиксирует rev29 для real-robot-сессий. Реальный заезд rev29 выполнен и зафиксирован видеозаписями; робот успешно прошёл картонный L-коридор от старта до целевой зоны, преодолев один контакт со стеной самостоятельным восстановлением.
Платформенная часть инфраструктуры подтвердила пригодность для исследований: цикл «обучить — экспортировать ONNX — поставить в backend — запустить на роботе» проходит без пересборки backend или Unity-runtime; регистрация модели через rusim model install срабатывает за один шаг; backend сверяет compatibility модели с runtime-режимом и отказывается активировать неподходящую policy; shadow-mode WebUI позволяет отлаживать policy без риска физической поломки робота. Эти возможности составляют операционную основу sim-to-real-эксперимента и служат основным content-тезисом магистерской работы.
Наблюдение ent_coef-drift как иллюстрация ценности metadata.json. Между rev16 (100 процентов sim SR на mild-DR) и rev24 (35 процентов sim SR на heavy-DR) более десяти подходов давали ровно 0 процентов либо degenerate-режим. Аудит metadata.json всех ревизий, отсортированных по записанному значению entropy_coefficient, выявил, что ревизии rev24–rev28 наследовали значение 0,02 от старого default-а CLI, тогда как rev10–rev18 обучались с 0,1. Default явно поднят в коммите 4c46ef8 с комментарием в коде; metadata.json расширен так, чтобы все ключевые гиперпараметры (entropy coefficient, seed, lateral penalty, источник transfer-а) записывались в артефакт автоматически. Этот эпизод демонстрирует методическую ценность evidence-инфраструктуры: drift-обнаружение возможно ровно потому, что каждая ревизия хранит свой паспорт.
7.6.2 Что не работает: heavy-DR variance, generalization на random tracks¶
Параллельно с положительными результатами зафиксированы три существенные открытые проблемы. Первая — variance PPO под heavy-DR. Шесть последовательных прогонов rev30–rev35 с разными seed-ами на одинаковых гиперпараметрах дали 0 процентов SR в каждом случае. Серия повторных тренировок rev41–rev46 в дополнение к rev30–rev35 показала эмпирический hit rate воспроизведения rev29-результата около одного из одиннадцати попыток. Это значит, что rev29 представляет редкое попадание в узкую зону притяжения, а не надёжно воспроизводимую конфигурацию. Для надёжного 70 процентов SR необходимо либо параллельное многосидовое обучение с выбором лучшего, либо переход на более устойчивую архитектуру.
Вторая открытая проблема — обобщение на случайные maze-топологии. Policy rev37 (Plan 1, перенос heavy-DR на maze-сцену с трёхступенчатым curriculum) даёт 47 процентов средний прогресс на случайных топологиях; робот стабильно проезжает половину каждой сгенерированной maze, но не финиширует. Архитектурные эксперименты rev38 (frame stack k=4 с нуля) и rev40 (ResNet18 frozen + RecurrentPPO LSTM) не сошлись в выделенном бюджете 300 тысяч шагов. Архитектура готова в коде; для ожидаемого результата необходим бюджет порядка одного миллиона шагов, что укладывается в одну ночь работы Win-узла. Это работа, не выполненная в рамках практики.
Третья проблема — open-loop-управление реальным роботом. Фактический угол поворота за один тик команды DirLeft варьируется от 30 до 60 градусов из-за отсутствия обратной связи по энкодерам или IMU. Backend частично компенсирует это плавным разгоном моторов и увеличенными порогами устаревания телеметрии, но фундаментально проблема остаётся: симулятор моделирует поворот детерминированно, реальный робот — стохастично. Это источник sim-to-real-разрыва, ортогональный к визуальному, и требует отдельной инженерной работы на стороне Pi-runtime.
Сводная сравнительная таблица 7.7 фиксирует, что подтверждено эмпирически и какие направления остаются открытыми.
Таблица 7.7 — Что работает / что остаётся открытым
| Аспект | Состояние | Основание |
|---|---|---|
| Sim SR ≥ 70 процентов на heavy-DR L-coridor | Подтверждено | rev29 = 75 процентов (15/20) |
| Реальный проезд KS0223 в L-коридоре | Подтверждено | Видеозаписи 30.04.2026 |
| Воспроизводимость 70 процентов на новых seed-ах | Не подтверждено | 1/11 hit rate на rev30–rev46 |
| Обобщение на случайные maze-топологии | Частично | rev37 = 47 процентов avgProgress |
| RecurrentPPO + R3M на 1 млн шагов | Не выполнено | Бюджет ограничен 300 тысячами шагов |
| Closed-loop control на стороне Pi | Не реализовано | Требует доработки прошивки |
7.6.3 Архивная пауза sim-to-real ML-работы (защитный pivot 2026-05)¶
В первых числах мая 2026 года принято решение о временной паузе sim-to-real ML-направления в пользу платформенной полировки. Обоснование решения сформулировано следующим образом. Тема магистерской диссертации — расширяемая Unity-платформа для KS0223. Sim-to-real, R3M-эксперименты, maze-навигация — варианты использования платформы, демонстрирующие её пригодность для исследований; они не являются центральным научным результатом самой диссертации. К моменту pivot-а sim-to-real-цикл уже замкнут на L-коридоре (rev29 в production), что исчерпывает минимальное требование «продемонстрировать рабочий перенос» для защиты. Дальнейшее улучшение SR и переход на random maze требует значительного дополнительного бюджета и сопряжён с variance-проблемами, не разрешимыми в оставшееся до защиты время с приемлемым риском.
Освобождённое время направлено на завершение платформенных задач: документирование архитектуры (главы 4–6 настоящей работы), полировка плагинной модели (глава 5), завершение Web UI и backend, оформление демонстрационных материалов для защиты. Записи sim-to-real-эксперимента продолжают служить evidence-материалом для главы 7, но новые тренировочные прогоны не запускаются. Открытые ML-направления (раздел 7.7) перечислены как естественные продолжения работы для возможной публикации или дальнейшего исследования, но их выполнение не планируется в рамках текущей магистерской диссертации.
7.7 Направления продолжения¶
7.7.1 Recurrent PPO и архитектурные изменения¶
Первое направление продолжения — переход с CNN на CNN+LSTM-архитектуру через RecurrentPPO из sb3-contrib. Гипотеза: внутренняя память LSTM-блока даёт policy способность интегрировать наблюдения по нескольким шагам, что критично для maze-навигации с ответвлениями. Альтернатива — frame stacking k=4 — демонстрирует те же свойства технически, но обучается медленнее и хуже масштабируется на длинные эпизоды. Ревизия rev42 представляет первую попытку RecurrentPPO; архитектура в коде поднята, но в выделенном бюджете 300 тысяч шагов не сошлась. Ожидаемый бюджет — один миллион шагов с возможным BC-bootstrap.
7.7.2 BC bootstrap (behavior cloning)¶
Второе направление — предобучение policy с учителем на парах (кадр, действие), записанных оператором через Web UI demo-recorder. К моменту pivot-а накоплено около полутора тысяч таких пар от ручных проездов по L-коридору и по нескольким maze-топологиям. Behavior cloning на этом датасете даёт policy навигационный prior, не требующий PPO-исследования. Дотренировка с PPO на heavy-DR-сцене после BC должна снижать variance: policy стартует не с случайно инициализированных весов, а с веса́, уже умеющего проезжать коридор.
Имплементация требует адаптации train_cardboard_corridor_v9.py для двухстадийного режима: фаза BC через стандартный supervised loop поверх dataset-а, затем фаза PPO с весами BC-policy в качестве начальных. SB3 поддерживает кастомную инициализацию через policy.load_state_dict. Это направление зафиксировано как Plan 5-alt в sprint-3-changelog.
7.7.3 R3M backbone¶
Третье направление — замена обучаемой с нуля свёрточной сети на замороженный предобученный визуальный encoder. R3M (Nair, 2022) является целевым кандидатом — это transformer-encoder, обученный на гигантском объёме видеоданных, специфичных для робототехники. В проекте используется функционально эквивалентный аналог — ResNet18, предобученный на ImageNet, со замороженными весами. Ревизия rev40 представляет первую попытку этой архитектуры; в выделенном бюджете 300 тысяч шагов с нуля не сошлась.
Гипотеза, мотивирующая направление: предобученное визуальное представление сужает пространство, в котором PPO ищет policy, поскольку низкоуровневые признаки (углы, контуры, текстуры) уже выучены и фиксированы. Это уменьшает количество параметров, требующих градиентного обновления, и снижает variance между запусками. Технически направление готово: policy_kwargs тренировочного скрипта поддерживает кастомный features_extractor_class, и интеграция R3M-encoder-а сводится к написанию класса-обёртки. Ожидаемый бюджет — один миллион шагов; требуется проверка на multi-seed-sweep для подтверждения снижения variance.
Совокупно три направления формируют программу продолжения. Платформа в её текущем состоянии поддерживает все три без модификаций ядра: смена архитектуры — через CLI-флаг --recurrent или --r3m, BC-bootstrap — через двухстадийный запуск тренировочного скрипта, eval — через тот же evaluate_ab_policy.py. Это и составляет основной content-тезис главы: построенная платформа пригодна для sim-to-real-исследований не только в нынешней постановке, но и в естественных продолжениях.