ИИ не наш

Повесть о попытке использовать многослойные нейронные сети в гоночной игре.

Пример визаулизации применения нейросети в игре (не в моей)<br /> <a href="https://api.dtf.ru/v2.8/redirect?to=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DgEf9V03HWv0&postId=93628" rel="nofollow noreferrer noopener" target="_blank">mbaske</a>
Пример визаулизации применения нейросети в игре (не в моей)
mbaske

Кто сейчас не слышал этих сладких волшебных слов — искусственный интеллект? Слов одновременно таких манящих, но пугающих? Такое маленькое окошко в большое завораживающее будущее, в котором все наши проблемы будут решены, а нам больше никогда не придётся работать.

Вот и я про себя подумал: вот у меня игра. Там есть боты. А давайте я к ним прикручу ИИ, чтобы мне не приходилось их учить ездить, но чтобы они научились сами, наблюдая за мной. Что из этого вышло — расскажу в этой статье.

С чего всё началось

Уже долгое время я с командой разрабатываю игру с гоночными элементами. Игрок управляет водным скоростным катером и на специальных трассах соревнуется на скорость с другими игроками. Впрочем, это лишь один из режимов игры, наравне с ним в игре можно будет захватывать флаги и сражаться друг с другом. В качестве движка мы выбрали Unity, потому что, как нам казалось, все подобные проекты малыми студиями, кажется, делают именно на нём.

Задача вполне себе типовая: есть карта, наполненная чек-поинтами — точками на карте. Есть агент — в терминах машинного обучения, это такая самостоятельная сущность, способная передвигаться в среде и взаимодействовать с ней. Агенту нужно передвигаться к следующему чек-поинту. На карте помимо этого есть ограничения, которые мешают агенту передвигаться и при столкновении с ними агент получает урон, который может привести к его смерти. Достичь цели нужно как можно быстрее и с наибольшим количеством здоровья.

Для подобных задач есть NavMesh — это такая технология, которая строит карту уровня в виде поверхности, по которой может передвигаться агент, а потом с помощью нехитрых, но точных алгоритмов, помогает выстроить путь из точки «А» в точку «Б» для этого агента. Это и близко не искусственный интеллект, простая задачка из области дискретной математики, но технология оказалась очень простой и удобной, так что стала своеобразным стандартом в задачах передвижения.

Принцип работы NavMesh​
Принцип работы NavMesh​

Тем не менее это решение в начале моего пути казалось мне слишком неподходящим — выстроенный путь представляет собой ломанную линию, по которой агент должен двигаться беспрекословно, с мгновенными поворотами. Катер же не может в мгновение секунды менять направление движения на противоположное, не может в мгновение остановиться. Если перед ним крутой поворот, он просто врежется в стену.

Поэтому нужно не просто попасть из точки «А» в точку «Б», но и сделать это максимально безопасно и как можно быстрее, с учётом крайне высокой инерции, минуя своих врагов, которые, к тому же, являются динамическими объектами и поэтому NavMesh не учитываются. Так что, кажется, пришло время для искусственного интеллекта.

А что предлагают?

К тому же сами разработчики Unity поощряют внедрение технологий машинного обучения в свои игры: опубликовали специальный дев-кит, устроили конкурс среди пользователей, активно создают туториалы по теме. А в качестве одной из демонстрационных задач показывают ровно то, что мне и нужно: гоночный трек и машинки, колесящие на нём на полной скорости.

Туториал по использованию ML для обучения машинок для езды по треку

Впрочем, я лично использовать их дев-кит не стал — пользоваться им предлагают с применением Python, что меня тогда испугало: мало ли, вдруг оно плохо портируется на другие платформы или зашито в библиотеку так, что туда внутрь не залезешь и не подправишь (а я люблю лазить внутрь, да и задача специфическая). Сейчас я изучил его побольше и понял что проблем этих нет.

Был и другой способ — скачать специальную библиотеку для машинного обучения на C‑Sharp. Вот тут советуют большой список всего. Но мне показалось это решение слишком overpowered (короче, слишком много возможностей для моей задачи, мне столько сразу не нужно). К тому же, у Unity некоторые проблемы с импортом сторонних библиотек: если ваш проект изначально не настроен под использование.NET Framework выше версии 3.5, то и библиотеку вам нужно иметь версии 3.5. Не все библиотеки портированы под настолько старую версию (она вышла ещё в 2007-м), а самому их конвертировать проблематично.

Поэтому скачал замечательный пакет отсюда, у которого прилагается довольно впечатляющее видео. Это так называемые многослойные сети с генетическими алгоритмами в качестве метода обучения.

Демонстрация обучения агента с помощью нейросетей
ArztSamuel

Вообще, решений достаточно много и все они проявляются при минимальном использовании гугла. Поэтому я лучше опишу, как они все работают.

Как это работает?

Ну, я не буду рассказывать вам прямо всё. Это не Habrahabr, а сайт про игры.

К сожалению, прогресс пока не дошёл до достаточной генерализированного решения задач машинного обучения, то есть создания такого универсального механизма, который бы решал все наши проблемы. Есть только несколько десятков задач и по десятку решений на каждую из них. Но, в любом случае, всё всегда сводится к двум компонентам:

  • функция, в которую мы можем передать параметры определённого типа и получить результат определённого типа. Причём, нам известно внутреннее устройство функции, но не известна её конфигурация;
  • набор данных, состоящий из пар «параметры — результат».

Пока что для простоты возьмём наипростейшее математическое уравнение f(x) = ax + b. a и b нам заранее неизвестны. Допустим, у нас есть набор данных из двух пар: «2 -> 10» и «3 -> 12». Несложно подставить эти данные в функцию и вычислить коэффициенты a=2 и b=6.

Но так бывает очень редко. Обычно данные частично противоречивы или содержат шум. Например, мы уже не сможем вычислить конфигурацию функции для набора данных «3 -> 7», «4 -> 15» и «10 -> 0», это как попытаться построить прямую линию по трём точкам. На этот случай у математики есть куча всяких регрессионных, аппроксимирующих и экстраполяционных методов, предназначенных для нахождения такой функции, которая наименьшим образом отклоняется от поданных на неё данных, иначе говоря сумма расстояний от линии до точек минимальна.

Для самых простейших типов функций методы наиточнейшего приближения уже давно существуют. Только самые простейшие функции ваших проблем не решат — это как пытаться уместить всю законодательную систему государства в два предложения. Поэтому придумывают намного более изощрённые механизмы, содержащие в себе сотни, тысячи, миллионы коэффициентов, а количество их комбинаций содержит ещё в два раза больше нулей.

Самые популярные из них — искусственные нейросети, о которых вы так много слышали. Вычисление наиточнейшей конфигурации для неё, если оно вообще возможно, будет продолжаться вплоть до нашей с вами смерти и далее, далее, далее. Так что приходится извращаться.

Например, есть метод обратного распространения ошибки — это когда вы потихоньку двигаете коэффициенты в обратную сторону их ошибочности. Например, выдаёт у вас она на выходе «0», хотя должна «1», тогда вы повышаете все положительные коэффициенты и понижаете отрицательные, чтобы результат был ближе к нужному.

А ещё есть Q‑learning — очень грубо говоря это когда вы повышаете коэффициенты, выдающие правильный результат, то есть «поощряете их». Есть ещё генетические и эволюционные алгоритмы — это когда вы просто имитируете эволюцию, то есть генерируете новые конфигурации путём совмещения старых, которые относительно хорошо себя показали.

У каждого из этих способов есть куча всяких недостатков, а ещё куча всяких параметров, которые должны быть идеально настроены, иначе ваше вычислительное время просто улетит в трубу. И в любом случае, всё это похоже на блуждание в невероятных масштабов темноте в поисках фонариков света, в надежде найти «тот самый» среди сотен тысяч. Это уже не говоря о том, что ваша модель в принципе может быть не в состоянии выдавать достаточно близкие результаты, заранее об этом никак не узнать.

И что ты делал?

Во-первых, я построил специальный обучающий комплекс, состоящий из двух карт с препятствиями. Одна предназначалась для гоночного режима (круговой трек), вторая для боевого (широкая площадь с препятствиями).

Первая карта была максимально сложной для ИИ, ведь содержала выход на пустые пространства, столбы препятствий и резкие повороты. Зато содержала всего один вариант пути, в отличии от второй карты, где путь постоянно генерировался случайным образом.

Задача ИИ была проехать по этому пути, причём как можно быстрее и с как можно меньшим уроном. То есть, оценкой являлось количество пройденных чекпоинтов, процент пройденного пути от предыдущего до следующего, скорость прохождения и процент понесённого урона.

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

Нейросеть состояла из примерно 20 входов и четырёх выходов, означавших силу нажатия на кнопки влево, вправо, вперёд и назад соответственно. Проводились опыты с одним и с двумя скрытыми слоями.

В каждом раунде участвовали от 6 до 20 ботов, они передвигались на скорости x3-x5 (мой компьютер больше не потянул, появлялись проблемы с физикой).

Проводились два вида опытов с обучением: оставлять ботов на несколько часов одних, чтобы они учились ездить по карте сами, и применять к ним эволюционный алгоритм. А также ездить по карте самому, записывать данные о моём поведении и обучать нейросеть собранными данными с помощью метода обратного градиентного спуска.

Ботам было поставлено ограничение: им был доступен только маленький кусочек трека, до определённого чекпоинта. Трек открывался дальше ещё на один чекпоинт только после того, как хотя бы половина ботов достигнет последнего открытого чекпоинта. Таким образом, я хотел достичь постоянства, иначе говоря исключить ситуации, в которых какому-то боту просто повезло достичь уровня, но даже на следующий раз он сам не в состоянии повторить свой успех, не говоря уже о его потомках.

Те, кто достигали конца текущего трека, участвовали в следующем заезде без изменений, а также давали потомство. Остальные отправлялись на верную смерть методом беспощадного стирания сборщиком мусора.

Ну и чего ты добился этим?

Ну... ничего. Я провёл примерно три недели за разнообразными опытами, пробуя кучу всего и варьируя коэффициенты, но результатов не добился никаких. Давайте по порядку.

Обучение эволюционными алгоритмами в ситуации, когда боты имеют возможность сталкиваться друг с другом, не помогло: боты толкали друг друга в стену и вообще вели себя очень брутально, ведь те, кто слабее, умирает не доехав до конца и имеет меньше шансов оставить потомство.

Причём, ботам эта брутальность не помогала никак. Логика была такая — чем быстрее ты едешь, тем больше у тебя шансов столкнуть другого игрока в стену и меньше шансов оказаться там самому. Любая более-менее осмысленная стратегия имела все шансы провалиться только из-за того, что тебя вмесили в стену и ты потерял ориентацию. Признаться, даже мне было очень сложно с ними ездить.

Причём, поворачивать боты нормально не научились — а зачем? В итоге, они не выдерживали даже минимальных поворотов на треке. Все они просто вталкивались в одну и ту же стену, но те, кто был быстрее, в итоге оказывались на пару сантиметров ближе к следующему чек-поинту, поэтому получали больше шансов на размножение.

Обучение с учителем через метод обратного спуска, тоже не помогло. Трек составляет из себя в основном ломаную линию с крутыми поворотами, так что не удивительно, что большая часть собранных данных содержала в себе мысль «жать кнопку вперёд независимо от обстоятельств» или же «жать кнопку вперёд, а иногда и кнопки поворота».

В итоге, боты так и делали — всегда ехали на полной скорости вперёд без какой-либо попытки сворачивать, хотя с виду нейросеть показывала минимальную ошибку на предоставленных данных. Всякие модификации метода обратного спуска никак не помогали.

И, наконец, обучение эволюционными алгоритмами в ситуации, когда боты не имеют возможности сталкиваться друг с другом — это пожалуй был вариант, в котором я достиг наибольших успехов. Впрочем, этого всё равно было мало. Зато есть, что рассказать.

В ситуации, когда трек оставался постоянным, у ботов в целом неплохо получалось ездить по нему. В течении нескольких часов обучения мне удалось добиться ситуации, когда боты проходили более 80% трека. Но стоило хоть раз сменить трек, и всё сразу ломалось.

Было очевидно, что боты привыкают к треку и больше ничего не хотят делать, ведь раньше же это всегда работало. Они полностью игнорировали информацию о положении следующего чекпоинта, хотя она была им предоставлена. Некоторые боты пытались сворачивать примерно в нужном направлении, но инстинкты всё же брали верх.

Многие из них пытались смухлевать и проехать свой чек-поинт мимо, но попасть к следующему. Интересно было представлять, что они чувствуют, когда через восемь удачно пройденных чек-поинтов программа им сообщала: «Неправильно, ты допустил ошибку вон там в самом начале и весь твой дальнейший прогресс меня не интересует».

Самое интересное, что если обучить бота ездить по одному пути, потом переобучить его ездить по другому пути, то он полностью забывал первый путь и вёл себя на нём так же неадекватно.

Движение ботов по статичному пути, прогресс после нескольких десятков поколений

Ботам так ни разу и не удалось научиться делать резкий разворот на 180 градусов, с чем всегда было связано много проблем. Да и повороты в 90 градусов им всегда давались очень тяжело.

В связи с этим они выработали интересную тактику — движение по спирали. Не знаешь, как и куда ехать — двигайся по спирали и однажды попадёшь! Частенько это выливалось в своеобразный вальс, в котором десять ботов в такт наворачивали круги на одном месте.

Эта тактика появлялась с завидной регулярностью, как будто для них это было единственно возможным решением. И была относительно успешной: после нескольких постепенно расширяющихся кругов им удавалось захватить пару чекпоинтов. Другой, не менее популярной тактикой для них оказывалось движение взад-вперёд по траектории звезды, которое мало чем отличается

Ещё они научились не врезаться в стену на полной скорости. То есть, буквально, если они едут на полной скорости и увидели перед собой стену — они сразу останавливаются. Только они не знают, что делать с этим дальше, и просто стоят на месте. Или снова ускоряются и врезаются в стену.

Иногда одному боту удавалось добраться до чекпоинта задним ходом. Я и сам иногда пользовался этой тактикой, но в отличии от меня, ему не приходило в голову развернуться, после чего на следующем шаге все боты начинали ездить исключительно задом.

Обучение на динамическом пути. Уже 30-ое поколение​, а они до сих пор не понимают, что делать. Не поймут и после сотни поколений

В целом, все эти тактики образовывались ещё на первых 10 раундах и в течение последующих сотен ситуация менялась не сильно, что заставило меня сделать вывод, что увеличить время обучения точно не поможет. Как уже было сказано, после трёх недель разработки я потерял всякую надежду и бросил это неблагодарное дело.

Мораль сей басни такова...

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

Знаю, о чём вы подумали: это не ИИ виноват, это ты криворукий. Надо было использовать другие способы обучения, надо было дать больше времени, надо было вычислять всё это в облаке, а не на своём стареньком ПК 2013-го года выпуска. Надо было использовать нормальный тулкит от Unity, в конце концов!

А я и не буду спорить. Наверняка есть способ обучить ботов так, чтобы всё это беспрекословно работало. Только я про такой не знаю. Готовых решений в открытом доступе как минимум под мою задачу, кажется, нет. ИИ всё ещё непокорённая область. Мы научили компьютеры обрабатывать изображения, звуки и текст, но всё ещё не заставили его полноценно думать. Эта задача требует полноценного научного исследования, полного тонны проб и ошибок. И, по моим оценкам, займёт минимум несколько месяцев фулл-тайм работы, а может и больше. Которого у обычного разработчика игр, к сожалению, нет.

Я почитал отзывы в интернете, и, кажется, проблемы были не только у меня. Ссылок не дам, потому что уже и не найду, но всё сводится к тому, что подобные технологии подходят только для очень простых задач.

К тому же, после того, как я окончательно у себя в голове зарыл эту идею с машинным обучением в могилу, я просто открыл новый файл и написал там ровно 20 строчек с наитупейшей эвристикой, которая до сих пор показывает мне результаты лучшие, чем всё, что я видел в исполнении нейросетей за эти три недели.

​Итоговый код моего ИИ. Используя путь, построенный через NavMesh, он вычисляет положение следующей точки относительно себя и в зависимости от угла между векторами решает, куда и с какой скоростью поворачивать и ехать<br />
​Итоговый код моего ИИ. Используя путь, построенный через NavMesh, он вычисляет положение следующей точки относительно себя и в зависимости от угла между векторами решает, куда и с какой скоростью поворачивать и ехать

Так что, мораль сей басни такова: ̶Н̶е̶ ̶л̶е̶з̶ь̶,̶ ̶*̶*̶*̶*̶*̶,̶ ̶*̶*̶*̶*̶*̶.̶ ̶О̶н̶а̶ ̶т̶е̶б̶я̶ ̶с̶о̶ж̶р̶ё̶т̶!̶ на данный момент технологии не развиты настолько, чтобы вы могли смело взять какой-нибудь готовый инструмент и быстренько его прикрутить, чтобы у вас всё внезапно заработало. ИИ — область тяжёлая, без большой предварительной подготовки и знания дела туда лучше не лезть, иначе можете погрязнуть в пучине математики и остаться у разбитого корыта. Если есть возможность написать эвристику — напишите, оно будет и проще, и понятнее.

Хотя, это определённо было очень интересно — часами наблюдать за маленькими компьютерными муравьями, болеть за них и переживать все их успехи и неудачи вместе с ними.

9898
29 комментариев

Автор просто неправильно подошел к обучению ИИ. Если ИИ отказывался ездить на другой трассе, то, скорей всего, он тупо и не понимал, что от него требуется.
Иначе говоря, проблема в постановке цели для обучения ИИ, или цель поставлена некорректно. Если ботам важно было столкнуть других, не поворачивая, то все еще - проблема в постановки цели.

Цель обучения ИИ какая была? Чтобы он конкретную трассу прошел? Как прошел? Какие условия он должен выполнять, чтобы считалось, что он ее прошел? А что такое - пройти трассу? Приехать к какому-то чекпоинту? Как он должен реагировать на рядом проезжающего другого ИИ? В каком случае можно и нужно применять агрессию, а в каком - нет?
Самое первое, что бросается в глаза при прочтении текста - у тебя ИИ не хотел ехать к цели, а хотел завалить других таких же придурковатых. Почему так?

Я рекомендую изначально не просто бросаться тыкать ИИ и включать его в игру, а изучить идею ИИ. Большая часть проблемы создания ИИ - это постановка цели. Это первое, чему учит Andrew Ng на своем курсе Machine Learning.
Он в самом начале (если память не изменяет) рассказывает, как многие из-за не правильной постановки цели тратят бешеные деньги, а потом зовут его, и он за 5 минут решает проблему.

Вот здесь его курс. Ознакомься, пройди курс и еще раз напиши статью =)
А пока - это просто какое-то непонятное высказывание, что технология - гавно, потому что я им не умею пользоваться.

https://www.coursera.org/learn/machine-learning

67
Ответить

Автор просто пытается использовать перцептрон, там где нужно использовать рекуррентные НС. Перцептрон не умеет работать с данными, которые динамически меняются во времени. Максимум чего можно добиться - один агент будет проходить трассу или даже разные трассы - тут уже зависит от того насколько большая обучающая выборка. В ситуации когда есть много агентов, которые конкурируют между собой в одном пространстве, нужно использовать другой подход к построению сети, когда есть целостная цель - пройти трассу и частные задачи - не врезаться в окружение, контролировать скорость, обгонять соперников если они рядом и т.д.

Перцептроном банально невозможно решить задачу, которую ставит перед собой автор.

11
Ответить

Ну, скажем так, я вижу логику в действиях ботов. Я бы поступал примерно так же. Скорее складывается ощущение, что мощности нейросети не хватает на какие-то более осмысленные действия. Например, сбивать других кажется хорошим решением, но его определённо недостаточно. Как и ехать задом, когда это необходимо. Будто, нейросети хватает на обучение только одной конкретной тактике - и не больше.
В какой-то момент я просто понял, что мне скорее всего нужно что-то более мощное, чем простая сеть с двумя скрытыми слоями и генетический алгоритм. И очень много времени на вычисления. Но что конкретно - там нужно очень долго разбираться, у меня нет столько времени, да и эвристики справляются пока хорошо. В этом и мораль статьи, я не говорю, что сделал всё правильно.

А так, я уже описал в статье, какие цели были перед ботом: есть чекпоинт, к которой нужно двигаться. Причём, она в области видимости, не за стеной. Нужно это сделать как можно быстрее и с меньшей потерей здоровья, то есть тут я делал функцию результата. Всё остальное меня не интересовало. После того, как он достигал чекпоинта в определённом радиусе, информация о чекпоинте менялась на новую. Как реагировать на окружение, бот решал сам, я ему ничего не задавал.

3
Ответить

Чисто интуитивно кажется, что автор ставил неправильные задачи ботам. Поощрять их надо было за что-то другое.
Цель вообще выглядит чрезмерно сложной. Это как сказать обезьяне "Долети до Луны" и за каждый успех награждать её вкуснейшим бананом. В этом случае сэкономится ОЧЕНЬ много бананов, но цель будет всё еще далека.

Можно попробовать посмотреть задом наперед. Предположим, уже есть бот/человек который супер прекрасно проходит все карты. Чем характеризуется его поведение? Чего простого он делает очень много раз?
Всё относительно банально - он часто едет на высокой скорости, он ЧАСТО, ОЧЕНЬ ЧАСТО сбрасывает скорость, он поворачивает.

Это очень грубо, но вот за количество и связки таких полезных дел как "притормозить + повернуть", "повернуть + ускориться" и надо попробовать награждать.

13
Ответить

При такой грубой постановке задачи ИИ почти наверняка будет просто вихлять - тут нужно вводить бальную систему где награда будет за чистую и аккуратную езду по разным параметрам - от урона до скорости прохождения что тоже достаточно просто.

1
Ответить

А мне кажется у тебя просто матмодель неверная. Машинное обучение — это же не просто пройтись по сетке в одну сторону, а потом в обратную. 

я сам пытался написать нейронную сеть для ботов в FPS. В итоге плюнул. Тут одно из двух: либо ты пишешь нейронку, либо ты делаешь игру.

7
Ответить

Ну я тоже так подумал и плюнул всё на это. Подождём, когда умные дяди с тоннами финансирования сделают всё за нас, их для этого и наняли.

1
Ответить