Использование генетического алгоритма для автоматического освоения игры Flappy Bird

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

Фактически мы можем рассматривать каждую игру как задачу оптимизации, цель которой - найти лучшее решение. Обычно это лучшее решение - выиграть игру и часто с максимально возможным количеством очков. Некоторые компьютерные программы с искусственным интеллектом уже смогли побеждать людей, играя в сложные игры, такие как AlphaGo, которая играет в настольную игру го.

В этой статье создается AI-бот для игры в Flappy Bird. Бот может быстро помочь птице лететь, не задев трубы.

Игра, использованная в этой статье, разработана на Python 3 Харисом Кханом из CodeWithHarry. Генетический алгоритм построен с использованием библиотеки PyGAD. Код размещен в репозитории GitHub: ahmedfgad / FlappyBirdPyGAD.



План этой статьи выглядит следующим образом:

  • Оригинальная игра
  • Как оптимизировать игру
  • Оптимизация с использованием генетического алгоритма
  • Увеличение скорости игры

Давайте начнем!

Оригинальная игра

Версия игры Flappy Bird, которую мы используем, взята с сайта CodeWithHarry.com, который был разработан на Python 3 с использованием библиотеки PyGame. В этом разделе исследуются основные части игры, которые помогут при попытке расширить ее с помощью нашей AI-оптимизации.



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

FPS = 32
SCREENWIDTH = 289
SCREENHEIGHT = 511
SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
GROUNDY = SCREENHEIGHT * 0.8
GAME_SPRITES = {}
GAME_SOUNDS = {}
PLAYER = 'gallery/sprites/bird.png'
BACKGROUND = 'gallery/sprites/background.png'
PIPE = 'gallery/sprites/pipe.png'

Словари GAME_SPRITES и GAME_SOUNDS наполнены изображениями и звуками.

GAME_SPRITES['message'] =pygame.image.load('gallery/sprites/message.png').convert_alpha()
GAME_SPRITES['base'] =pygame.image.load('gallery/sprites/base.png').convert_alpha()
GAME_SPRITES['pipe'] =(pygame.transform.rotate(pygame.image.load( PIPE).convert_alpha(), 180), pygame.image.load(PIPE).convert_alpha())
GAME_SPRITES['background'] = pygame.image.load(BACKGROUND).convert()
GAME_SPRITES['player'] = pygame.image.load(PLAYER).convert_alpha()
GAME_SOUNDS['die'] = pygame.mixer.Sound('gallery/audio/die.wav')
GAME_SOUNDS['hit'] = pygame.mixer.Sound('gallery/audio/hit.wav')
GAME_SOUNDS['point'] = pygame.mixer.Sound('gallery/audio/point.wav')
GAME_SOUNDS['swoosh'] = pygame.mixer.Sound('gallery/audio/swoosh.wav')
GAME_SOUNDS['wing'] = pygame.mixer.Sound('gallery/audio/wing.wav')

В игре 4 функции:

  1. welcomeScreen(): вызывается в начале игры для отображения экрана приветствия, как показано на следующем рисунке.
  2. mainGame(): Это игровой цикл, в котором игрок управляет птицей, чтобы пройти через трубы. Эта функция объявляет некоторые переменные, которые управляют поведением игры. Одна переменная - playerFlapAccv, которая контролирует скорость взмахов. Другой - pipeVelX для управления скоростью перемещения труб по экрану.
  3. isCollide(): Используется для проверки, сталкивается ли птица с трубой или землей.
  4. getRandomPipe(): возвращает 2 случайных канала, одну вверху, а другую внизу экрана.

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

  1. Первая часть проверяет наличие столкновения между птицей и землей с помощью оператора if.
  2. Вторая часть проверяет наличие столкновения с трубой в верхней части экрана. Поскольку на экране одновременно будет несколько каналов, цикл for проходит через все эти каналы.
  3. Как и во второй части, цикл в конце проверяет наличие столкновения с трубами в нижней части экрана.

Если есть столкновение, то воспроизводится звук удара, и функция возвращает True. В противном случае возвращается False.

def isCollide(playerx, playery, upperPipes, lowerPipes):
    if playery> GROUNDY - 25  or playery<0:
        GAME_SOUNDS['hit'].play()
        return True
    
    for pipe in upperPipes:
        pipeHeight = GAME_SPRITES['pipe'][0].get_height()
        if(playery < pipeHeight + pipe['y'] and abs(playerx - pipe['x']) < GAME_SPRITES['pipe'][0].get_width()):
            GAME_SOUNDS['hit'].play()
            return True
for pipe in lowerPipes:
        if (playery + GAME_SPRITES['player'].get_height() > pipe['y']) and abs(playerx - pipe['x']) < GAME_SPRITES['pipe'][0].get_width():
            GAME_SOUNDS['hit'].play()
            return True
return False

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

Подпишитесь на еженедельник Deep Learning Weekly и присоединяйтесь к более чем 15 000 ваших коллег. Еженедельный доступ к последним новостям индустрии глубокого обучения, исследованиям, библиотекам кода, руководствам и многому другому.

Как оптимизировать игру?

Для задачи оптимизации существует цель, которая достигается путем принятия наиболее подходящих действий с учетом набора ограничений. Давайте объясним дальше.

Цель игры Flappy Bird - набрать как можно больше очков. Доступны 2 действия:

  1. Хлопните птицу. Это делается касанием экрана.
  2. Не взмахивайте птицей, ничего не делая.

Решение о том, взмахивать ли птицей или нет, основано на этих 2 ограничениях:

  1. Избегайте ударов по верхним и нижним трубам.
  2. Избегайте ударов о землю или небо.

На следующем рисунке показан снимок игры, где на экране показаны 3 разных набора труб:

  1. Трубы, отмеченные черными точками, пройдены. Их не стоит учитывать при расчетах.
  2. Следует учитывать две ближайшие трубы, отмеченные красными точками.
  3. Еще две трубы с желтыми точками, которые принимаются во внимание только после прохождения двух труб посередине.

Основываясь на этом обсуждении, наша цель может быть сужена до:

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

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

Генетическому алгоритму (GA) будет дано положение птицы в дополнение к положениям двух приближающихся трубок. Обратите внимание, что нас интересует только положение Y труб и птицы.

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

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

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

Теперь, когда эти основы объяснены, в следующем разделе библиотека PyGAD используется для создания бота на основе GA, который играет в игру. Обратите внимание, что здесь не используется методология обучения (например, нейронная сеть), и GA - единственный задействованный метод.

Оптимизация с использованием генетического алгоритма

Библиотека PyGAD - это простая в использовании библиотека для построения генетических алгоритмов. Для решения любой проблемы его использование состоит всего из 3 простых шагов:

  1. Создание фитнес-функции.
  2. Создание экземпляра класса pygad.GA.
  3. Запустите экземпляр.

Давайте рассмотрим эти шаги.

Фитнес-функция

ГА предлагает разные решения проблемы (в нашем случае он предлагает только положение птицы по оси Y). Для сравнения различных решений используется функция пригодности, которая вычисляет пригодность для каждого решения для измерения его качества. Чем выше фитнес, тем лучше решение.

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

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

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

Поскольку на экране может быть несколько труб, но мы рассматриваем только ближайшую приближающуюся трубу, переменная nearest_upper_pipe содержит положение Y ближайшей верхней трубы. Это делается путем вызова функции closest_pipe(), которая возвращает индекс ближайшего приближающегося канала. В зависимости от расстояния между предложенным положением и высотой трубы раствору назначается пригодность.

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

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

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

Затем фитнес-функция назначается параметру fitness_func в конструкторе класса pygad.GA, как мы обсудим в следующем разделе.

Фоновая тема

GA должен работать постоянно, пока игра запущена. Лучше всего это делать в потоке с использованием модуля threading, как показано в блоке кода ниже.

Новый класс PygadThread расширяет класс threading.Thread. Внутри метода run() создается экземпляр класса pygad.GA. Некоторые из важных параметров включают:

  1. num_genes: Ему дается 1, так как существует только один ген (позиция птицы по оси Y).
  2. num_generations: Алгоритм будет работать в течение 200 000 поколений. После того, как все поколения пройдут, ГА прекращается. Поэтому ему присвоено большое значение, чтобы гарантировать, что он будет работать долгое время.
  3. sol_per_pop: GA предлагает 200 решений для каждого поколения. Из этих решений выбирается лучшее.
  4. random_mutation_min_val и random_mutation_max_val: это диапазон, из которого значение присваивается каждому решению.
  5. delay_after_gen: Задержка 0,01 секунды после каждого поколения. Без установки задержки ГА среагирует так быстро, и птицу будет трясти.

Ознакомьтесь с документацией PyGad для получения более подробной информации об этих параметрах.

GA начинается с вызова метода run() в конце метода run().

Последнее, что нужно сделать, - это создать экземпляр PygadThread и запустить его внутри метода mainGame() перед циклом while (это игровой цикл).

def mainGame():
    ...
pygad_thread = PygadThread()
    pygad_thread.start()
while True:
        ...

Полный код игры и бота доступен в этом файле GitHub ahmedfgad / FlappyBirdPyGAD / main.py.



В следующем видео показано, как бот играет в игру - и его опыт в расчистке труб.

Https://user-images.githubusercontent.com/16560492/115970766-9869d880-a512-11eb-9ade-3403e0a62317.mp4

Увеличение скорости игры

В предыдущем коде скорость игры была установлена ​​на нормальную, так как переменной pipeVelX было присвоено значение -11. Что будет, если игра станет быстрее? В следующем видео показано, как проходит игра, когда pipeVelX=-20. Реакция GA на игру очень быстрая и все еще способна преодолевать препятствия.

Https://user-images.githubusercontent.com/16560492/135771690-7e1f89e6-25de-4ba7-8910-9903b71e3bb3.mp4

Вывод

В этой статье обсуждалось, как создать бота на Python, который будет играть в игру Flappy Bird с использованием генетического алгоритма. Статья началась с изучения оригинальной игры.

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

Затем стратегия GA реализуется с использованием библиотеки PyGAD, которая использовалась для создания фитнес-функции, учитывающей все ограничения. Наконец, мы протестировали игру в более быстрых условиях… и бот все еще мог без проблем очищать трубы и препятствия!

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

Независимая редакция, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по обработке данных и группам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим участникам и не продаем рекламу.

Если вы хотите внести свой вклад, отправляйтесь на наш призыв к участникам. Вы также можете подписаться на наши еженедельные информационные бюллетени (Deep Learning Weekly и Comet Newsletter), присоединиться к нам в » «Slack и подписаться на Comet в Twitter и LinkedIn для получения ресурсов, событий и гораздо больше, что поможет вам быстрее и лучше строить модели машинного обучения.