Doomformer превращает нейросеть в карту DOOM и читает цифры

Doomformer превращает нейросеть в карту DOOM и читает цифры

Doomformer, проект на GitHub, который компилирует целочисленные нейросети в карты для движка ZDoom и запускает их в ViZDoom. Вычисления делают voodoo dolls (куклы вуду), копии игрока, которые DOOM порождает для дополнительных стартовых точек игрока 1. Суммы хранятся в уровнях освещённости секторов и в высотах плоскостей (пола и потолка). Python считывает из работающей игры каждое выставленное наружу значение и сравнивает его с целочисленным эталоном на NumPy с допуском 0: если одно значение отличается хоть на единицу, прогон считается проваленным.

Авторы README честно предупреждают, что это медленно: примерно в 23 000 раз медленнее NumPy на небольшой MLP (по времени движка; если засекать таймером весь вызов, то в 65 000 раз). Одна рукописная цифра при расписании по умолчанию (--opt 0) занимает 163 секунды игрового времени DOOM в реальном темпе, при --opt 3, 8 секунд.

Главная демонстрация, чтение цифр MNIST. Морпех зажимает FIRE и «рисует» кистью 3×3 клетки на полу. Команда READ заставляет уровень записать 784 пикселя в сеть, а 1 355 voodoo dolls на той же карте считают результат: 784 пикселя → 64 скрытых нейрона → 45 попарных сравнений → 10 счётчиков побед. Кукла, победившая во всех девяти своих сравнениях, проходит через линию, которая запускает скрипт уровня, и тот зажигает цифру на семисегментном индикаторе. Python проверяет все 248 значений (119 значений, каждое считывается вместе с отрицательным двойником, плюс 10 выходов). При --opt 3 один проход READ занимает 273 игровых такта (8 секунд реального времени DOOM при 35 тактах в секунду, около 3 секунд по часам); при --opt 0, 5 705 тактов. Команда doomformer draw --digit 7 сама нажимает клавиши за игрока, а без --digit открывается окно, и вы рисуете сами.

Точность README разделяет на два вопроса. EXACT означает только, что DOOM совпал с целочисленной сетью, но не говорит, верна ли цифра. На первых 100 тестовых цифрах MNIST DOOM читает верно 99, на выборке из 1 000 цифр из всех 10 000, 966, и все результаты точные. Целочисленная сеть даёт 97,16 % на всех 10 000 (вещественная модель, 97,58 %). 30 цифр, «нарисованных» ходьбой морпеха (цифры 0-9, по три сценарных варианта штриха), прочитаны верно и точно при --opt 3, но сеть дополнительно обучена на таком распределении штрихов: первая версия демо, сеть 784-32-10, обученная только на MNIST, читала лишь 41,08 % таких штрихов.

Есть и «музейная» карта: doomformer museum build создаёт museum.wad, в который можно играть без Python. Нужно встать на семь напольных плит, чтобы зажечь цифру, и нажать READ. Небольшую рукописную сеть считают 136 voodoo dolls: счёт каждой цифры, число совпавших сегментов, а кукла-победитель зажигает индикатор. Кнопка MUTATE меняет одну связь внутри работающего уровня (сегмент a для цифры 7 с +1 на −1), и та же семёрка читается как 1; RESTORE возвращает всё назад. Команда doomformer museum verify проверяет все 148 значений во всех трёх состояниях.

Большая часть README посвящена проверке. Тесты специально портят систему: увеличение одного веса с 5 до 6 должно давать ошибку ровно +1 на тех входах, чьи куклы пересекают эту линию. Фаззер doomformer fuzz --nets 40 сравнил все значения 40 случайных сетей (306 проходов, 0 расхождений); кампания fuzz --edge на 15 000 сетях с весами и входами на границах диапазона дала 113 605 проходов и 1 987 632 сравнённых значения без расхождений. Фаззер и независимые прогоны нашли четыре дефекта в собственных картах репозитория: две ошибки расписания и два ограничения плоскостей. Статический экстрактор умеет восстановить сеть из скомпилированной карты без запуска DOOM; мутационный тест из 1 004 проверенных мест (из 5 578) «убил» 297 мутаций. Метод README называет translation validation (валидация трансляции): это проверка каждой компиляции, а не доказательство правильности компилятора.

Ключевые факты

  • Doomformer компилирует целочисленные нейросети в карты ZDoom и запускает их в ViZDoom: арифметику делают voodoo dolls (копии игрока), суммы лежат в освещённости секторов и высотах плоскостей.
  • Каждое выставленное наружу значение Python сверяет с целочисленным эталоном на NumPy с допуском 0; в демо чтения цифр MNIST проверяются все 248 значений, сеть считают 1 355 voodoo dolls.
  • Очень медленно: примерно в 23 000 раз медленнее NumPy на небольшой MLP; одна цифра занимает 163 секунды реального времени DOOM по умолчанию и 8 секунд при --opt 3.
  • DOOM верно читает 99 из первых 100 тестовых цифр MNIST и 966 из выборки в 1 000; целочисленная сеть даёт 97,16 % на всех 10 000 (вещественная, 97,58 %).
  • В музейной карте кнопка MUTATE меняет один вес прямо в работающем уровне, и та же семёрка читается как 1; фаззинг нашёл четыре дефекта в собственных картах репозитория.

Почему это важно

Практической пользы от такого вывода нейросети никто не заявляет: README прямо говорит, что это очень медленно. Ценность проекта в другом, это наглядная и строго проверяемая демонстрация того, что нейросеть можно свести к целым числам, сложению и сравнениям и выполнить внутри игрового движка, где вычисляют только предметы уровня и копии игрока. При этом результат не «примерно похож»: каждое значение совпадает с эталоном до единицы. Для Show HN-хака это необычно строгий подход к проверке.

Кому это важно

Тем, кто любит нестандартные вычисления и игровые хаки: энтузиастам DOOM и модостроения, а также тем, кто интересуется тем, как устроена целочисленная (квантованная) нейросеть изнутри. Интересен и методологически: набор тестов, фаззинг, мутационный анализ и проверка карт без запуска игры, пример того, как проверять странный компилятор.

Как это применить

Установка: pip install git+https://github.com/mottopanikeiku/doomformer (нужны Python 3.11+, Linux или macOS). Команда doomformer hello за около 2 секунд проверяет небольшую MLP, doomformer draw --digit 7 за около 15 секунд проходит цифру 7, а doomformer doctor проверяет ViZDoom, Freedoom и дисплей и собирает ACC при его отсутствии. Для первой сборки нужны git, cmake, компилятор C и доступ к github.com. Без установки можно открыть Colab-ноутбук: цифру рисуют на холсте в браузере, и doomformer mnist --image передаёт её в DOOM, но без морпеха. Музейную карту собирает doomformer museum build (файл museum.wad играбелен без Python).

Можно ли доверять

Источник, README самого проекта, поэтому все цифры скорости и точности, заявления автора репозитория; в тексте автор не назван, а проект размещён на аккаунте mottopanikeiku. Код открыт, а проверки встроены в сам проект (doomformer verify, fuzz, museum verify), так что их можно перезапустить. README сам аккуратно оговаривает границы: проверка, это равенство целых чисел на тех прогонах, которые были сделаны, и она ничего не говорит о невыполненных входах; метод, тест каждой компиляции, а не доказательство правильности компилятора.

Риски и подводные камни

Скорость: на практике это игрушка, а не способ запускать модели. Равенство с эталоном (EXACT) не гарантирует, что цифра распознана верно. Результаты на «нарисованных» ходьбой цифрах зависят от того, что сеть дообучена именно на таких штрихах: прежняя версия читала лишь 41,08 %. Утверждение «нет ACS между слоями» верно только при --opt 0-2: при --opt 3 скрипт ACS копирует освещённость каждого слоя в высоты шлюзов следующего (сложений и сравнений он не делает). Автор запускал всю музейную карту только в ViZDoom; самопроверочная карта на GitHub-раннере даёт точные значения в нескольких других движках, а в Zandronum 3.2.1, только если все теги секторов меньше 32 768. Мутационный тест «убил» 297 из 1 004 проверенных мутаций; из 707 выживших 590 объяснены правилом, 117, пробелы во входах, и ни одна не доказана эквивалентной.

«EXACT означает, что DOOM равен целочисленной сети. Это ничего не говорит о том, верна ли цифра.»

— README проекта Doomformer