Обучение ИИ на примере Sonic the Hedgehog: алгоритм NEAT

Разбираемся, как можно научить искусственный интеллект играть в игру. На самом деле, обучение ИИ - это просто, особенно с Sonic the Hedgehog!

Обучение ИИ
Эволюция человека

Из поколения в поколение люди менялись, чтобы стать лучше приспособленными к окружению. Мы начинали как приматы, которые жили в мире «съешь или тебя съедят». В конце концов, превратились в то, что видим сегодня – современное общество. Благодаря процессу эволюции мы становимся умнее. Мы можем лучше взаимодействовать с окружающей средой и выполнять то, что нужно.

Определяем алгоритм NEAT

Концепция обучения с помощью эволюции также применяется к искусственному интеллекту.

Мы в состоянии обучить ИИ выполнять конкретные задачи с использованием алгоритма NEAT (Neuroevolution of Augmented Topologies, нейроэволюции нарастающих топологий). Проще говоря, NEAT – алгоритм, который берёт группу ИИ (геномов), которые пытаются выполнить поставленную задачу. Топовые ИИ «размножаются» для создания следующего поколения. Этот процесс продолжается до тех пор, пока не появится поколение, способное выполнить то, что нужно.

Обучение ИИ
ИИ играет в STH

Алгоритм NEAT удивителен тем, что устраняет необходимость в сборе предварительных данных для обучения ИИ.

Благодаря силе NEAT и OpenAI Gym Retro, разработчик обучил ИИ играть в Sonic the Hedgehog для SEGA Genesis. Предлагаем узнать как!

Обучение ИИ: алгоритм NEAT на Python

GitHub Репозиторий

Репозиторий sonicNEAT.

Понимание OpenAI Gym

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

  • Агент (agent) – ИИ игрок. В этом случае будет Соник.
  • Окружающая среда (environment) – полное окружение агента. Игровая среда.
  • Действие (action) – что способен делать агент (то есть двигаться влево, двигаться вправо, прыгать, ничего не делать).
  • Шаг (step) – выполнение одного действия.
  • Состояние (state) – схема окружающей среды. Текущая ситуация, в которую попал ИИ.
  • Наблюдение (observation) – что ИИ наблюдает в окружающей среде.
  • Приспособленность (fitness) – насколько хорошо работает ИИ.
  • Done – когда ИИ завершил задачу или не может продолжать дальше.

Установка зависимостей

Ниже приводятся ссылки на GitHub для OpenAI и NEAT с инструкциями по установке.

OpenAI: https://github.com/openai/retro

NEAT: https://github.com/CodeReclaimers/neat-python

Устанавливаем с помощью pip install библиотеки cv2, numpy, pickle и другие.

Импорт библиотек и настройка среды

Для начала импортируем все модули, которые будем использовать:

import retro
import numpy as np
import cv2
import neat
import pickle

Также определим нашу среду, которая состоит из игры и состояния:

env = retro.make(game = "SonicTheHedgehog-Genesis", state = "GreenHillZone.Act1")

Чтобы провести обучение ИИ, вам понадобится образ игры Sonic the Hedgehog (файл). Простейший способ получить его – купить игру в Steam за 5 долларов. Также можно найти игру в сети, однако это незаконно, поэтому не делайте так.

В репозитории OpenAI по адресу /retro/retro/data/stable увидите папку для Sonic the Hedgehog Genesis. Разместите здесь образ игры и убедитесь, что он называется rom.md. Эта папка также содержит файлы .state. Выберите один и установите параметр состояния равным ему. Разработчик выбрал GreenHillZone.Act1, так как это первый уровень игры.

Понимание data.json и scenario.json

В папке Sonic the Hedgehog будут эти два файла:

data.json

{
  "info": {
    "act": {
      "address": 16776721,
      "type": "|u1"
    },
    "level_end_bonus": {
      "address": 16775126,
      "type": "|u1"
    },
    "lives": {
      "address": 16776722,
      "type": "|u1"
    },
    "rings": {
      "address": 16776736,
      "type": ">u2"
    },
    "score": {
      "address": 16776742,
      "type": ">u4"
    },
    "screen_x": {
      "address": 16774912,
      "type": ">u2"
    },
    "screen_x_end": {
      "address": 16774954,
      "type": ">u2"
    },
    "screen_y": {
      "address": 16774916,
      "type": ">u2"
    },
    "x": {
      "address": 16764936,
      "type": ">i2"
    },
    "y": {
      "address": 16764940,
      "type": ">u2"
    },
    "zone": {
      "address": 16776720,
      "type": "|u1"
    }
  }
}

scenario.json

{
  "done": {
    "variables": {
      "lives": {
        "op": "zero"
      }
    }
  },
  "reward": {
    "variables": {
      "x": {
        "reward": 10.0
      }
    }
  }
}

Оба эти файла содержат информацию, которая нужна для игры и обучения. Как видно из названия, файл data.json содержит информацию и данные о специфических переменных игры (т. е. х-положение Соника, количество жизней и т. д.). Файл scenario.json синхронизирует выполнение действий со значениями переменных из данных. Например, можно давать вознаграждение 10.0 Сонику каждый раз, когда увеличивается его x-положение. Или поставить условие done в true, когда жизни Соника достигли 0.

Понимание конфигурации алгоритма NEAT

Файл config-feedforward найдёте в указанном выше репозитории GitHub. Это как меню параметров для настройки обучения. Выделим такие параметры:

fitness_threshold     = 10000 # Насколько усовершенствованным хотим видеть Соника 
pop_size              = 20 # Сколько Соников в поколении
num_inputs            = 1120 # Количество входов в нашу модель
num_outputs           = 12 # 12 кнопок на контроллере Genesis

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

Всё вместе: Создание обучающего файла

Настройка конфигурации

Конфигурация сети прямого распространения определяется и сохраняется в переменной config.

config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 
neat.DefaultSpeciesSet, neat.DefaultStagnation, 'config-feedforward')

Создание функции для оценки каждого генома

Начнём с создания функции eval_genomes, которая будет оценивать наши геномы (геном сравним с 1 Соником в поколении Соников). Для каждого генома сбрасываем среду и предпринимаем случайное действие:

for genome_id, genome in genomes:
        ob = env.reset()
        ac = env.action_space.sample()

Также запишем длину, ширину и цвет игровой среды. Делим длину и ширину на 8.

inx, iny, inc = env.observation_space.shape
inx = int(inx/8)
iny = int(iny/8)

Создаём рекуррентную нейронную сеть (RNN) с использованием библиотеки NEAT и вводим геном и выбранную конфигурацию.

net = neat.nn.recurrent.RecurrentNetwork.create(genome, config)

Наконец, определяем переменные: current_max_fitness (наивысшая приспособленность в текущем поколении), fitness_current (текущая приспособленность генома), frame (количество кадров), counter (количество шагов, которые предпринимает агент), xpos (x-положение Соника), и done (достигли ли целевой приспособленности или нет).

current_max_fitness = 0
fitness_current = 0
frame = 0
counter = 0
xpos = 0
done = False

Пока не выполнится требование done, запускаем среду, увеличиваем счётчик кадров и формируем наблюдение так, чтобы имитировать игру (по-прежнему для каждого генома).

env.render()
frame += 1
ob = cv2.resize(ob, (inx, iny))
ob = cv2.cvtColor(ob, cv2.COLOR_BGR2GRAY)
ob = np.reshape(ob, (inx,iny))

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

imgarray = []
imgarray = np.ndarray.flatten(ob)
nnOutput = net.activate(imgarray)

Используя результаты из RNN, ИИ делает шаг. Из этого шага извлекаем свежую информацию: новое наблюдение, вознаграждение, достигли ли требования done или нет, и информацию о переменных в data.json (info).

ob, rew, done, info = env.step(nnOutput)

На этом этапе оценивается приспособленность генома и определяется, выполнилось ли требование done. Смотрим на переменную «x» из data.json и проверяем, превысила ли она длину уровня. Если это так, повышаем приспособленность до нашего порога, и это означает, что задание выполнено.

xpos = info['x']
            
if xpos >= 10000:
        fitness_current += 10000
        done = True

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

fitness_current += rew
if fitness_current > current_max_fitness:
        current_max_fitness = fitness_current
        counter = 0
else:
        counter += 1

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

if done or counter == 250:
        done = True
        print(genome_id, fitness_current)
                
genome.fitness = fitness_current

Определение поколения, печать статистики обучения и прочее

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

p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
p.add_reporter(neat.Checkpointer(1))
winner = p.run(eval_genomes)
with open('winner.pkl', 'wb') as output:
    pickle.dump(winner, output, 1)

Осталось только запустить программу и наблюдать, как Соник медленно учится проходить уровень!

алгоритм neat

алгоритм neat
Раннее поколение против позднего поколения

Чтобы увидеть весь код в одном месте, загрузите файл Training.py из указанного репозитория на GitHub.

Бонус: параллельное обучение ИИ и алгоритм NEAT

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

Заключение

Вот и всё, что нужно сделать! С некоторыми изменениями эта структура применима к любой игре для NES, SNES, SEGA Genesis и других.

Обучение ИИ: ключевые тезисы

  1. Нейроэволюция нарастающих топологий NEAT – алгоритм, который используется для обучения ИИ выполнять конкретные задачи. Создан по образцу генетической эволюции.
  2. NEAT устраняет необходимость получения предварительных данных для обучения ИИ.
  3. Процесс реализации OpenAI и NEAT с использованием Python помогает обучить ИИ играть в любую игру.

Оригинал

Что еще почитать про ИИ:

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Go-разработчик
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ