Как игровой робот учился понимать позицию на доске гомоку.
АВТОРЫ - РАСПРЕДЕЛЕННЫЙ, НО МОЩНЫЙ ЮНИТ
Юрий Зибрин - автор статьи и соавтор проекта. AI-инженер (RL · CV · LLM Agents · Embodied AI), ведущий системный аналитик
Антон Петров - соавтор проекта. AI-инженер (CV · LLM Agents) / разработчик / тимлид
Различные ЛЛМ- авторы кусков кода. редакторы текста данной статьи, раскопщики миллиона блокнотов для нее же.
Мы с приятелем делаем игрового робота-компаньона для настольных игр. Сейчас он умеет понимать позицию на игровой доске (гомоку, оно же "пять в ряд"), обдумывать ход (самописный аналог AlphaZero) и понимать базовые фразы для сопровождения игры - моделью, которая вместе с эмбеддингами весит меньше 80 мегабайт и надеюсь легко заведется на Jetson Orin Nano (целевое устройство для нашего робота). Исходные данные задачи: доска, фигурки, кадр с вебки
У этой статьи один тезис. Самое дорогое знание в прикладном ML - не "как устроены методы": это расскажет любой курс и любая нейронка. Самое дорогое - где у каждого метода границы применимости и в каких местах он ломается. Сборник такого знания мне найти не удалось. Эта статья - попытка начать собирать оный: подробный разбор одного неверного подхода, который держал нас почти год, и одного верного, который уложился в недели - а работоспособность первая же версия доказала за час. В конце - предложение, как собирать такой сборник вместе, если вдруг кто увлекается данной темой и думал о чем-то подобном.
Я тут распотрошу мертвую ветку - регрессию координат для определения углов игровой доски гомоку: она в течение года поддавалась, но в итоге никуда не привела, и именно поэтому из нее можно вытащить максимум пользы. Из живого пайплайна расскажу самое интересное и поучительное, но без полной финальной машинерии: ее подробный разбор будет ближе к запуску проекта.
Робот играет в гомоку на физической доске. Точнее, мы скрестили привычные в России крестики-нолики с гомоку: ходим не камнями на пересечения линий, а крестиками и ноликами в центры клеток - как если бы играли на тетрадном листе. Я играл таким способом в эту игру все старшие классы школы - наверное, поэтому и захотелось повторить ) Правила для начала взяли попроще: без ограничений на ходы и выигрышные комбинации для первого игрока.
Чтобы сделать ход, робот должен по кадру с камеры понять игровую позицию - и передать ее альфа зира движку, который принимает решение о ходе. Камера на текущий момент достаточна почти любая - пока обычная вебка Logitech 1080p дает устойчивый CV пайплайн, крутиться все это будет на Jetson Orin Nano 8 ГБ (надеюсь).
План мы придумали почти сразу, и он до сих пор живет в рабочей версии:
найти углы доски;
найти фигурки;
зная размерность доски, сопоставить одно с другим и получить матрицу с позицией.
Цель пайплайна: позиция в виде матрицы - ее и получает движок
Важная деталь: исходно хотели, чтобы сетка могла работать с досками разной размерности - от 12×12 до 19×19 (гомоку играют на разных, а в перспективе хотелось и го) - генератор датасета на всякий случай рендерил размерности от 10×10 до 20×20.
Ну и сначала мы делали CNN с регрессионным выходом для того чтобы понять размерность, потом пробовали классификацию - работало и то и то на удивление хорошо. Но в конечном итоге мы забили на разные размеры, ибо обучить альфа зиру хватило ресурсов и времени только для доски 12 на 12. С постоянными откатами на прошлые состояния сетки после неудачного цикла обучения это заняло пару месяцев (но об этом в другой статье). Для демо думаю 12 на 12 сойдет да и поиграть тоже интересно - не тривиально. По крайней мере один мой приятель, кто много и хорошо играет в го и гомоку, ей проиграл. Ладно, отступление закончил, возвращаясь к сиви пайплайну.
Готовых аналогов задачи мне найти не удалось - хотя мы искали. Ближайшая научная работа - про шахматы (Wölflein & Arandjelović, "Determining Chess Game State From an Image", 2021): система отличная, но задача заметно уже - стандартная доска 8×8, съемка с позиции игрока, адаптация к конкретному комплекту фигур по эталонным фото. Есть и реально продающийся игровой робот - SenseRobot (прикольный такой зайчик с камерами в ушах), играющий в шахматы, го и гомоку, - но там доска приделана к роботу, и он всегда смотрит на нее из одной и той же позиции.
Нам хотелось по-другому: робота просто ставят рядом с более-менее магазинной доской, тип игры которой знает. Доска - в любом положении: главное, чтобы попадала в кадр и не под совсем уж острым углом. Фигурки тоже любые купленные в магазине, цвета и размеры не принципиальны. (Именно крестики и нолики в магазине, понятно, не купишь - но суть в универсальности подхода, а под честные камни переделаем.) Забегая вперед: это удалось.
Так как готового рецепта не было именно для игровых досок - подход пришлось выбирать самому. Ну точнее сначала найти хоть что-то, связанное с координатами объектов.
Выбор для шага с углами подсказал курс Эндрю Ына: там сверточную сеть-классификатор легким движением превращали в детектор координат - "просто замените классификационную голову на регрессионную, и все заработает. Лосс? Да берите любой" (цитирую по памяти, но небрежную интонацию передаю точно). Забегая вперед: и "все заработает", и особенно "любой лосс" оказалось преувеличенно оптимистично для нашего класса задач.
Тут нужен контекст. Я тогда был сразу после курсов по дата-сайенсу - совершенно зеленый. Курс был отличный и с уклоном в практику, но, как и положено учебному курсу, задачи в нем подбирались заведомо решаемые, и метод решения говорили сразу. Ребята честно предупредили об этом в конце - но предупреждение не отложилось. Уверенность, что я могу по диагонали полистать материал про любой подход и все заработает, была просто дикая ) Это меня и подвело в плане потраченного времени, но зато дало сил дожать эту задачу.
PS Нашел я позже (намучившись с подходом, описанным ниже) и сетки для определения ключевых точек и centerNet и многое другое но именно тему с углами доски, которая сама вся в вертикальную и горизонтальную полоску и по сути имеет уйму таких углов в середине - сетки не потянули. Возможно виноваты кривые руки, не знаю.
Итак, регрессия координат. Ей нужен датасет с размеченными углами - а размечать тысячи реальных фотографий не хотелось, поэтому датасет мы сделали в Blender. Технически это headless-Blender с bpy-сценарием: сцена доски, материалы из библиотеки BlenderKit, рендер 448×448 (на всякий случай) с ресайзом под вход сети 224×224 (ну чтобы училось побыстрее); полный датасет рендерился на нашей карте несколько дней. Первая версия - около 12 тысяч рендеров, финальная - 66 тысяч сцен.
За год регрессия углов прошла путь: полный рандом на реальных фото → три угла встают на место, четвертый улетает → почти попадание → ошибка в полклетки или в две... Каждая ступень выглядела прогрессом. В этом и ловушка: неверный подход не отказал ни разу - он поддавался. А курсы воспитали во мне главное правило прилежного студента: заработало - дожимай.
Одна задача, три ступени. Кадр 1 - из архивной диагностики (синтетика). Кадры 2-3 воспроизведены при подготовке статьи: сохранившийся чекпоинт регрессии, прогнанный по реальным фото
Внутри этого пути нас ждало много ошибок - простых и неочевидных:
статичный фон - все рендеры сняты в одной и той же виртуальной комнате: стол, доска, штора;
сплит синтетики на train и test - тест из того же генератора не проверял ничего;
вечно деревянная доска - фон рандомизировали, а материал самой доски забыли;
процедурные фоны - искусственный "шум" вместо фона так и не заработал, и мы до сих пор не знаем почему;
ручные фильтры - попытка скормить сети контуры вместо цвета;
баг с порядком углов - разметка выдавала углы в случайном порядке;
перебор лоссов - от классического MSE до самодельного линейного с "мертвой зоной";
перевернутая ось Y Блендера - след на весь проект.
Дальше пройдусь по самым поучительным - не по хронологии, а по интересу. Что-то будет полезно тем, кто только начинает, что-то - практикам, а одна загадка не разгадана до сих пор.
Календарно эта эпопея растянулась почти на год: проект вечерний, недели чистой работы размазываются по месяцам жизни. Все цифры времени в статье - примерные, по памяти; точны только те, что вытащены из кода и из дат файлов.
Фон у рендеров был статичный: стол, на котором лежит доска (и, кажется, всегда одного цвета), позади - штора, как в моей комнате. Это была самая грубая ошибка - после самого выбора CNN с регрессией.
Дальше мы сделали ровно то, чему учат на курсах (привет моим курсам): поделили данные на train и test. Синтетику - на синтетический train и синтетический test. Метрику взяли жесткую: кадр считается ошибочным, если хотя бы один из четырех углов ушел от таргета больше чем на два пикселя. Получили на тесте 99+ процентов и обрадовались.
Я думаю, понятно, почему это не работает. Синтетика генерировалась с аугментацией по ракурсам и параметрам сцены, но все 12 тысяч картинок - из одного генератора с одним фоном. Случайно поделив их на train и test, получаешь две почти идентичные выборки: тест не проверяет ничего, кроме способности сети запомнить этот конкретный игрушечный мир. От реальных фотографий обе выборки отличаются одинаково сильно. Правильный тест для продукта, который будет работать на реальных фото, - небольшой набор реальных размеченных фотографий. Если бы train и test хотя бы аугментировались по-разному, смысла в сплите было бы больше - но и это не заменило бы реальный тест.
Сфотографировал реальную доску - и, конечно, получил полную ерунду: предсказания были ближе к случайным, чем к углам. Контраст виден на первых двух ступенях лестницы выше: на своей синтетике - гроссмейстер, на физике - коллапс.
В какой-то момент я - тоже по диагонали - прослушал кусок CV-курса, где разбиралась интерпретируемость моделей.
Для тех, кто слышит про это впервые: интерпретируемость - это методы, позволяющие посмотреть, на какие пиксели входа сеть реально опиралась, принимая решение. Один из стандартных инструментов - Captum, библиотека для PyTorch от Meta: несколько строк кода - и поверх картинки рисуется "карта значимости" (мы использовали метод Integrated Gradients).
Применили к нашей провалившейся регрессии - и картина оказалась довольно интересной: не на всех, но на некоторых картинках "важные" пиксели рассыпаны по всей сцене - пятна на однотонном столе, у шторы, вокруг фигурок - где угодно, только не собраны там, где сеть якобы нашла углы. В тогдашних прогонах попадались кадры и выразительнее - с точками ровно вдоль границы доски и стола, на перепаде цвета; те кадры не сохранились. Вывод читался один: сеть не искала углы - она выучила геометрию конкретной сцены и привязалась к перепадам цвета между доской, столом и шторой.
Картинок не сохранилось, а восстанавливать я не стал заморачиваться.
В литературе этот эффект называется shortcut learning: сеть находит самый дешевый признак, коррелирующий с таргетом, а не тот, который вы имели в виду. Систематический разбор - у Geirhos et al., "Shortcut Learning in Deep Neural Networks" (Nature Machine Intelligence, 2020).
Если коротко, то про это есть интересная байка, как где-то в Пентагоне классифицировали танки и получили 100 процентов точности на тестовом датасете. А на реальных картинках почти рандом. Выяснилось, что один тип они снимали при облачной погоде, а второй при солнечной — ну вот и получили отличный классификатор погоды )
Рандомизацию фона мы начали с процедурного генератора - линии, точки, градиенты, цветные пятна: дадим сети "шумный" фон, она перестанет на него опираться.
Фоны из нашего процедурного генератора: немного шума, градиентные фигуры, линии, точки и все рандомно и разного цвета.
На реальных фото - снова ерунда. Помогло другое: мы взяли LSUN - классический датасет сцен - и стали подставлять фоном реальные фотографии конференц-залов (категория conference_room). Настоящие текстуры и настоящий свет - стало заметно лучше.
Почему процедурные фоны не сработали, а фотографии чужих залов сработали - честно говоря, не понимаю до сих пор. Позже мы перепроверяли процедурный вариант уже на зрелом датасете - картина та же: работают только реальные фоны. Если у вас есть убедительная версия - расскажите в комментариях, вопрос живой.
Параллельно датасет оброс полноценной доменной рандомизацией - по рецепту Tobin et al., "Domain Randomization" (OpenAI, 2017): не делай симулятор реалистичным - делай его настолько разнообразным, чтобы реальность оказалась частным случаем. Рандомизировались цвета всех объектов и линий, толщина линий, размеры и высота фигурок, расстояние и угол камеры, материалы доски (эти - позже всех, о чем следующая ветка), освещение.
Фон, как видите, в итоге рандомизировали полностью - а сети все равно разваливались на реальных фото. Разгадка нашлась только при подготовке этой статьи, когда я раскопал старые версии генератора: рандомизировали фон, а саму доску - нет. Вплоть до поздней осени 2024-го в генераторе жестко стояло material = 'Wood': цвета фигурок, толщина и цвет линий разметки, ракурсы - рандомилось все, а доска в каждом кадре была одним и тем же рендерным деревом. У сети оставался идеальный якорь: константная текстура доски и переход "любой фон → вот это конкретное дерево" на ее границе. Тот же shortcut, что и со шторой, - якорь просто переехал с фона на саму доску. Реальные фото ломали его первым же кадром: настоящее дерево под настоящей лампой на рендерное не похоже.
Список из двадцати материалов доски - восемнадцать сортов дерева, медь и алюминий - появился в генераторе только следующей весной, уже в сегментационную эпоху. И заметьте: разницу между процедурным фоном и LSUN из прошлой ветки это не объясняет - доска была одинаково деревянной в обе эпохи. Тот вопрос остается открытым.
У Blender начало оси Y не там, где у картинок. Этот перевертыш, не пойманный в самом начале, остался в проекте навсегда: код по сей день полон переворотов "туда и обратно", половина файлов носит клеймо flipped, а в сохранившемся коде инференса той эпохи прямо живет строка output[:, 1] = 448 - output[:, 1].
Клеймо flipped в дикой природе (кадр из архивной диагностики): маска предсказана в отраженных координатах - трапеция сужается не в ту сторону, и оба контура уезжают вверх
Отдельная ветка - ручные фильтры. Мы подмешивали на вход карты границ Собеля и Лапласа, а затем и вовсе выкинули цвет: перевели вход в grayscale и скормили одноканальной сети бинарную карту границ Canny. Логика: доска - это же линии; оставим сети чистую геометрию, уберем все лишнее.
Для тех, кто слышит про это впервые: Собель, Лаплас, Canny - классические "ручные" фильтры из до-нейросетевого компьютерного зрения: фиксированные операторы, выделяющие перепады яркости - границы, вертикальные и горизонтальные линии. Первые системы зрения собирались именно из таких фильтров, подобранных вручную. Вопрос на понимание: почему подсовывать их сверточной сети обычно не нужно? Потому что обучение сверточной сети - это и есть выращивание нужных фильтров: обратное распространение ошибки настраивает ядра сверток под задачу - и обычно удачнее универсальной классики.
Стало только хуже. Убрав цвет, мы отобрали у сети признаки, за которые она реально держалась, и оставили ей мир, где полторы сотни одинаковых пересечений линий отличать стало еще сложнее.
Таргетом была коллекция координат четырех углов. Проблема: первым элементом в разметке мог оказаться любой угол - как повезло генератору. Сеть, выдавая координаты, не могла угадать, с какого угла "начинать ответ", и получала штраф за правильные углы в неправильном порядке. С точки зрения обучения это выглядело как необъяснимо плохая сходимость. Чинили двумя способами сразу: канонизировали порядок в разметке (всегда с одного угла, по часовой) и матчили при подсчете лосса каждый предсказанный угол с ближайшим таргетом. Помогло и то и другое, но канонизация - заметно сильнее. Вывод переиспользуемый: с неоднозначностью лучше бороться не усложнением лосса, а устранением неоднозначности из данных.
И отдельный привет тому самому "лосс берите любой": именно лосс - самая "любая" часть системы - съел у нас больше всего недель. (Лосс - функция ошибки, по которой сеть на обучении понимает, насколько промахнулась.) Мы перепробовали целое семейство штрафов по удаленности от таргета: начали, как все, с квадратичного MSE - чем дальше промахнулся, тем страшнее наказание, - потом пробовали SmoothL1. А победил, вопреки интуиции, чисто линейный - да еще с "мертвой зоной": отклонения меньше порога не штрафуются вовсе, остальное штрафуется по модулю, линейно. Задним числом все объяснимо: квадрат раздувает вклад самых спорных примеров, а у нас каждый неоднозначный угол давал именно большие промахи - агрессивный штраф превращал их в шум градиентов; линейный этот шум глушит, а мертвая зона не дает сети полировать субпиксельные мелочи вместо сути. Она же рифмуется с метрикой: успешным считался кадр, где все восемь координат легли в два пикселя. Остальные подробности той эпопеи психика вытеснила - и, полистав блокноты, я ее понимаю.
Сначала - о том, что удалось дожать. Исправленные координаты таргетов и чужие залы в фонах свое дело сделали: сеть перестала промахиваться в случайные места кадра, предсказанный контур ложился на доску или совсем рядом. Впервые получилось что-то похожее на правду.
Оставался последний рычаг - архитектура. Ранние сетки писались руками с нуля - четыре сверточных блока с пулингом и полносвязный хвост на восемь координат; потом - самописный U-Net с прикрученным регрессионным выходом... последняя версия ветки регрессировала углы уже по готовой маске доски. То есть U-Net - и даже маска - у нас к тому моменту уже были; до правильного решения оставалось поменять голову, а я продолжал менять все остальное. Нейронки-ассистенты уже прямо советовали сегментационную постановку; я игнорировал - регрессия почти работала, и "почти" выглядело как вопрос еще одной-двух правок. Но и этот рычаг потолка не поднял.
Масштаб перебора лучше всего передает файловая система: только на регрессию углов накопилось порядка двух десятков блокнотов, а всего в проекте их - за две сотни на трех машинах. Файловая система вообще помнит все: итоговый блокнот учебного курса датирован 25 марта 2024-го, первый блокнот робота - 19 апреля. Три недели выдержки.
Лучший результат мертвой ветки - ошибка от полуклетки до двух на реальных фотографиях (оценка на глаз, формальный замер не сохранился).
Потолок мертвой ветки: поздняя двухэтапная регрессия на удачном ракурсе - все четыре угла легли с ошибкой около полуклетки; по ракурсам ошибка гуляла от полуклетки до пары клеток (воспроизведено при подготовке статьи: сохранившийся чекпоинт, прогнанный заново)
Оглядываясь, я вижу в этой эпопее два слоя ошибок. Стратегический - сам выбор регрессии координат для точек, которые локально неотличимы друг от друга: думаю, что эта дорога была тупиком независимо от качества исполнения. И тактический - все те ветки, что выше: обычные ошибки периода обучения, мы ведь учились параллельно с проектом. Проблема в том, что каждый исправленный тактический косяк давал реальное улучшение и создавал ощущение "еще чуть-чуть - и дожмем". И то, что оно поддавалось, и послужило причиной того, что подход не был заменен так долго.
Сам же потолок, как мне кажется, объясняется просто. Регрессия координат плохо переносит задачи, где целевая точка локально не уникальна. Угол доски - это пересечение линий; таких же линий на доске еще две дюжины, а похожих друг на друга пересечений - полторы сотни. Чтобы выдать координату, сеть должна глобально рассудить, какое из одинаковых пересечений крайнее, и свернуть этот вывод в два числа через регрессионную голову; при неоднозначности между кандидатами регрессия к тому же склонна усреднять - "мазать" между похожими точками. С глазом на кропе лица из примера Ына на Курсере все наоборот: точка одна, локально узнаваема, всегда примерно в одном месте кадра - потому тот пример и работал.
Далее я тупо взял unet с датасетом с таргетами в виде маски доски. И получил результат близкий к идеальному за час. В качестве энкодера была предобученная на имеджнете EfficientNet-B7 - самый жирный из линейки, другие давали результат хуже. Повозиться немного пришлось, но это были уже буквально дни.
Дальше просто. По маске строится выпуклая оболочка, и с нее жадно срезаются вершины: на каждом шаге две соседние заменяются пересечением их ребер - выбирается срез с минимальной потерей площади, - и так, пока не останется ровно четыре угла. Алгоритм раскопали в интернете - очень похожая реализация, вплоть до той же sympy-версии, описана в заметке "Efficiently Drawing Bounding Boxes Around Segmented Masks". Sympy-прототип работал пару секунд на кадр. Переписать его на numpy мы поручили нейронке - Claude Opus сделал это за один заход: 4 миллисекунды и примерно 18 итераций на кадр, ускорение в пятьсот раз.
Маска → выпуклая оболочка → четырехугольник и сетка: 18 итераций, 4 мс на кадр
Причина успеха тоже прозаична: попиксельная разметка не оставляет сети места для шорткатов, а точность углов обеспечивает уже не нейросеть, а геометрия.
Фигурки ищутся похожей связкой: сегментация плюс тепловая карта центров. Карта дает кандидатов на фигурки, ложные пики подавляются, а уверенные - подтверждаются сегментацией; эта связка дала около 99% успеха - пайплайн работал почти всегда. Чтобы почти-всегда превратилось в "всегда, под почти любым углом и при почти любом освещении", пришлось сделать еще ряд мелких доводок, практически костылей да еще с кучей настроенных порогов - ими не буду перегружать статью: подробный разбор финальной машинерии покажем ближе к коммерческому запуску проекта. Если он состоится. Ну или сразу, как пойму, что нет ) PS есть детская мечта выложить это на кикстартер как только доделаем последний шаг - манипулятор.
Пайплайн целиком: фото → маска доски → контур и сетка → сегментация фигурок → тепловая карта центров → центры фигурок. Итог - матрица позиции, та же, что в начале статьи
Был еще один косяк, который мы пока поленились править (ну точнее нет сил и времени)... На плотных позициях - когда фигурок много и они стоят стеной - пайплайн начинал шуметь: фантомные пики тепловой карты или пропуски. Развилка была такая: лечить данными (в нашем синтетическом датасете плотных позиций почти нет - сгенерировать их отдельным сценарием никто не мешает) или дополнительной логикой поверх пайплайна, читай костылями. Костыли написались быстро с помощью нейронок - шум ушел. И ответа на вопрос "хватило бы просто датасета с плотными позициями?" у нас пока нет: не проверяли - работает, а следующий блок (манипулятор) важнее чистоты эксперимента. В конечной версии, конечно, доучим датасет плотными позициями - уже просто для чистоты.
Плотная позиция после лечения: распознана каждая фигурка
Для контраста - задача, где правильная постановка была выбрана сразу, и весь проект занял вечер. Здесь рецепт раскрываю полностью.
Роботу нужно понимать базовые игровые фразы: кто ходит первым, "давай переходим", "надоело - заканчиваем". По сути это классификация намерений на N классов, где ответ - команда роботу. Полноценная LLM для этого не нужна, да и не хотелось: все должно жить локально на Jetson.
Рецепт:
Берем Navec - легкие предобученные русские эмбеддинги из проекта Natasha. (Эмбеддинг - числовой вектор слова, устроенный так, что близкие по смыслу слова получают близкие векторы; Navec обучен на большом корпусе русских текстов.) Они уже "знают", что "надоело играть" и "давай закончим" - про одно и то же. Это главный трюк: знание языка мы не учим, а переиспользуем.
Слова лемматизируются (pymorphy2) и переводятся в замороженные Navec-векторы. Поверх - маленький трансформер-энкодер, каркас которого нам написала нейронка за пять минут: проекция 300→256, восемь слоев по восемь голов внимания, представление фразы - усреднение по токенам. Итого ~6,5 млн параметров и около 25 МБ.
Датасет: 131 рукописная фраза на семь команд - "я хожу первым", "ходи первым ты", "поменяемся", "перехожу", "начнем заново", "заканчиваем", "выключайся".
Обучение: пять минут.
Результат: модель уверенно понимает формулировки, не совпадающие с обучающими ни единым словом. Итоговый вес связки - меньше 80 МБ: классификатор ~25, Navec ~50. Раньше, пересказывая эту историю, я говорил "150 мегабайт" - при подготовке этой статьи выяснилось, что модель вдвое легче.
Со словами вне словаря Navec поступили инженерно: надеемся, что их не будет ))) Задача была разобрать N игровых ситуаций, а не построить общий диалог - для нее этого достаточно.
И кстати. Вы заметили, куда эта штука просится помимо робота? В умный дом.
Там ровно та же задача: список команд конечный и известен заранее, а произносят их каждый раз по-новому - "выключи свет", "вырубай", "чет ярко". Именно на этом ломаются самоделки: люди пишут регулярки и списки синонимов, а живой человек все равно скажет двенадцатым способом. Здесь же понимание языка приезжает готовым из Navec, и модели достаточно узнать, что все эти двенадцать способов - про одну и ту же команду.
А главное - цена. 80 мегабайт, никакого интернета и никакого облака. Крутится буквально на чем угодно, новая команда добавляется двадцатью фразами и пятью минутами обучения.
И еще одна поучительная деталь - про роль нейронок в этом рецепте. Каркас трансформера нам написал ассистент (тогдашний Claude Opus) за пять минут - но в его версии эмбеддинги были обычные, "пустые", обучаемые с нуля. На датасете из 131 фразы они не выучили бы ничего: чтобы векторы "узнали", что "надоело" и "закончим" - про одно и то же, нужны миллиарды слов корпуса, а не сотня примеров. Заменить их на предобученный Navec - та единственная догадка, на которой держится все решение, и пришла она не от нейронки. Понимание границ применимости пока остается человеческой работой - собственно, про это вся статья.
С доской неверная постановка стоила месяцев. Здесь верная постановка уложилась в вечер.
Дожать можно почти любой подход - вопрос, какой ценой. Главный навык - понимать цену заранее, и с тех пор именно он стал для меня основным предметом охоты. Накопилось:
работающий самописный аналог AlphaZero (MCTS + ResNet, self-play) - про его находки, от диагностики движка до трюков с политикой, будет отдельная статья;
уроки DQN: он не осилил даже крестики-нолики 3×3, пока один из противников не стал рукописным - два шумных ученика не могут научить друг друга; подробности - в той же статье про движок;
немало раскопанного про RL в целом;
заказные проекты, которые мы делаем с приятелем: LLM-пайплайны для сложных документов, ИИ-наблюдение за критичными событиями, семантический поиск;
сейчас копаем задачи перемещения объектов - впереди манипулятор.
Куда мы идем (концепт)
И все это время меня преследует то самое сожаление, с которого статья началась: места, где методы собраны вместе с границами применимости и узкими местами, по-прежнему не существует. Не "как работает" - такого полно, - а "где ломается и когда не брать". Каждый добывает это знание сам, месяцами, как я со своей регрессией.
Имея такой сборник рецептов, можно было бы клепать всякие АИ штуки намного быстрее.
При этом вокруг ровно то время, когда такие штуки надо делать. Робототехника и ИИ развиваются стремительно, но огромная часть простых полезных вещей до сих пор не придумана и не реализована. Приведу пару примеров:
В "Перекрестке" недавно появились ИИ-весы: положили яблоки - на экране только яблоки; скорее всего это просто CNN, может простой предобученный эфишиент. Ну или обучили йоло на 200 классов того что есть в магазине. Прикол в том, что сети такого класса доступны уже больше десяти лет, и совершенно непонятно, почему этого не было раньше.
Вот второй пример, который я видел в ютубе: занавеска, которая сдвигается за прохожим, чтобы он не заглядывал в квартиру. Думаю, что чувак типа меня сделает такое за неделю. Я бы взял любимую сегментацию и верю что unet с предобученным энкодером определил бы контуры человека без обучения. А если нет придумал бы еще что-то простое.
И такого не реализованного очевидно тьма-тьмущая.
Ситуация напоминает раннюю эпоху персональных компьютеров - времена Homebrew Computer Club, когда будущие основатели индустрии паяли машины в гаражах и сами писали к ним софт. В домашней робототехнике сейчас ровно та же фаза: железо доступно, методы опубликованы, поле не занято - и сообщества вокруг этого уже складываются, посмотрите хотя бы на LeRobot.
Препятствия минимальны: только время, интерес и некоторое понимание того, в какую сторону думать, решая данные задачи, и, главное, какие подходы выбирать в каждом случае.
Первые знания по дата-сайенсу я получил на неплохих практичных курсах: после теории задачу разбирали подробно, но потом давали заметно отличающуюся — тоже по теме, только разобранное решение не скопипастишь: приходилось думать.
Теперь же меня преследует идея-фикс - самому или вместе с кем-то запилить курс по Embodied AI для начинающих. Мне в свое время такого не хватило.
Я более чем уверен, что если наберется 2-3 заинтересованных в передаче знаний практика, хотя бы моего уровня, могла бы получиться увлекательная штука, очень полезная начинающим.
Если не рассматривать вопросы типа равновесия робота, которые давно решены и часто поставляются вместе с железкой (типа Unitree), то кажется, что людям, которые хотят двигаться в сторону Embodied AI, было бы достаточно для начала осознать подходы по решению следующих задач их потенциальным АИ устройством/роботом:
понять сцену - наличие объектов, их контуры и взаимное расположение (CV)
установить состоявшиеся факты по визуальной информации (VLM/VM)
принимать решения (RL или LLM)
совершать движения - перемещаться в пространстве или работать манипулятором (от простейшего Behavior Cloning до Decision Transformer и честного RL в варианте актор-критик, или например VLA)
и отдельно - среды, в которых все это обкатывают: от чистой концепции без физики до полноценной симуляции
А на базовом уровне я бы многое мог рассказать сам уже сейчас:
Локализация ключевых точек и instance segmentation (CV): регрессия координат против инстанс-сегментации.
Способы инстанс-сегментации: от эмбеддингов точек фигуры до сочетания классовой сегментации с тепловыми картами центров объектов.
Пайплайны для взаимного расположения объектов (CV). Позиция на доске - простейший случай, "чашка на столе или за столом" - уже задача домашнего робота.
Создание синтетических датасетов. Как получать датасет и не обмануть себя - ни в генераторе, ни в тесте. Вечно деревянная доска прилагается.
VLM и видеомодели для определения событий (VLM/VM): что модели можно доверить определять, а где это работает плохо.
Гибридные каскады CV + LLM: легкий детектор дешево находит кандидатов, тяжелая модель дорого их перепроверяет - так режется поток ложных тревог. Опыт из заказных проектов по критичным событиям.
Лестница RL: Монте-Карло → DQN и его болячки → актор-критик → AlphaZero. Что чинит каждая ступень и что ломает взамен. Любимая грабля: наш DQN не осилил даже крестики-нолики 3×3, пока одного из противников не написали руками, - два шумных ученика друг друга не учат.
Способы добычи опыта в RL: от честного рандома в начале до записанных траекторий человека - и как этот выбор определяет, чему агент вообще способен научиться.
Лестница подходов к действию: Behavior Cloning → Decision Transformer с return conditioning → актор-критик → VLA. Что дает каждая ступень и чем за нее платишь: объемом данных, наличием симулятора, ценой обучения.
Способы обучения визуального энкодера для VLA (модели "камера → действие"): учить вместе с головой действий, отдельно на картинках или по очереди с заморозками. Что для каких случаев лучше, что универсальнее, но более хрупко при смене задачи или фона среды.
Легкие трансформеры поверх предобученных эмбеддингов (NLU): команды в любой форме в пределах обученного набора - на копеечном железе и без облака. Рецепт из контрпримера выше; та же связка тянет локальный умный дом.
Зачем я это пишу - если кому-то что-то интересно из вышеперечисленного - сигнальте, на что хватит опыта и понимания - помогу.
Сейчас также разбираюсь со средами для симуляции, но пока имею о них только поверхностное представление. По моему текущему представлению:
Gym MiniWorld - рабочий минимум, чтобы крутить концепцию без влияния физики (формально заброшен, но чисто для понимания концепции мне кажется идеальным);
MuJoCo с mujoco_menagerie - фактический стандарт, на нем живут и собственные среды LeRobot;
ManiSkill3 - когда нужна манипуляция побогаче;
NVIDIA Omniverse - пока просто направление, куда смотрю. Предполагаю, что конечную версию буду учить там;
У кого есть реальный опыт - посоветуйте: где вы делаете симуляцию и почему именно там?
Еще было бы интересно послушать, какие области исследуете вы, по работе или в качестве хобби...
PS Если тема зашла - могу дальше рассказать про альфа зиру, а попозже про возню с манипулятором для переноса фигурки на доску
PPS А еще я, как и многие сейчас, ищу работу: подойдет и постоянная, и полставки, и временные проекты. Основная специальность - системный анализ, но есть рабочий опыт решения нетривиальных задач на LLM-пайплайнах, ну а про Embodied AI вы уже поняли )