Разбираемся, как можно научить искусственный интеллект играть в игру. На самом деле, обучение ИИ - это просто, особенно с Sonic the Hedgehog!
Из поколения в поколение люди менялись, чтобы стать лучше приспособленными к окружению. Мы начинали как приматы, которые жили в мире «съешь или тебя съедят». В конце концов, превратились в то, что видим сегодня – современное общество. Благодаря процессу эволюции мы становимся умнее. Мы можем лучше взаимодействовать с окружающей средой и выполнять то, что нужно.
Определяем алгоритм NEAT
Концепция обучения с помощью эволюции также применяется к искусственному интеллекту.
Мы в состоянии обучить ИИ выполнять конкретные задачи с использованием алгоритма NEAT (Neuroevolution of Augmented Topologies, нейроэволюции нарастающих топологий). Проще говоря, NEAT – алгоритм, который берёт группу ИИ (геномов), которые пытаются выполнить поставленную задачу. Топовые ИИ «размножаются» для создания следующего поколения. Этот процесс продолжается до тех пор, пока не появится поколение, способное выполнить то, что нужно.
Алгоритм 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)
Осталось только запустить программу и наблюдать, как Соник медленно учится проходить уровень!
Чтобы увидеть весь код в одном месте, загрузите файл Training.py
из указанного репозитория на GitHub.
Бонус: параллельное обучение ИИ и алгоритм NEAT
Если у вас многоядерный процессор, обучение ИИ можно проводить на нескольких обучающих симуляторах одновременно, экспоненциально увеличивая скорость работы! Хотя в этой статье не будем подробно останавливаться на том, как это сделать, рекомендуем посмотреть реализацию sonicTraning.py
в том же репозитории.
Заключение
Вот и всё, что нужно сделать! С некоторыми изменениями эта структура применима к любой игре для NES, SNES, SEGA Genesis и других.
Обучение ИИ: ключевые тезисы
- Нейроэволюция нарастающих топологий NEAT – алгоритм, который используется для обучения ИИ выполнять конкретные задачи. Создан по образцу генетической эволюции.
- NEAT устраняет необходимость получения предварительных данных для обучения ИИ.
- Процесс реализации OpenAI и NEAT с использованием Python помогает обучить ИИ играть в любую игру.
Комментарии