Як використовувати ШІ для гри в Їжачка Соніка. Це ПРИГОДНО!

Покоління за поколінням люди пристосовувались до того, щоб стати більш пристосованими до нашого оточення. Ми починали як примати, що живуть у світі, де їдять чи їдять. Врешті-решт ми еволюціонували до того, ким ми є сьогодні, відображаючи сучасне суспільство. У процесі еволюції ми стаємо розумнішими. Ми можемо краще працювати з навколишнім середовищем і виконати те, що нам потрібно.

Концепція навчання шляхом еволюції також може бути застосована до штучного інтелекту. Ми можемо навчити ШІ виконувати певні завдання за допомогою NEAT, нейроеволюції доповнених топологій. Простіше кажучи, NEAT - це алгоритм, який приймає партію ШІ (геномів), що намагаються виконати певне завдання. Найбільш ефективні ШІ «породжуються» для створення наступного покоління. Цей процес триває до тих пір, поки ми не отримаємо покоління, здатне виконати те, що йому потрібно.

NEAT є дивовижним, оскільки усуває необхідність у вже існуючих даних, необхідних для навчання наших ШІ. Використовуючи потужність NEAT та OpenAI's Gym Retro, я навчив штучного інтелекту грати Їжака Соніка для SEGA Genesis. Давайте дізнаємось як!

NEAT нейронна мережа (реалізація Python)

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

Vedant-Gupta523 / sonicNEAT

Сприяйте розробці Vedant-Gupta523 / sonicNEAT, створивши обліковий запис на GitHub. github.com

Примітка: Весь код у цій статті та репо-версії - це дещо змінена версія Sonic AI Bot Лукаса Томпсона, що використовує підручники та код Open-AI та NEAT YouTube.

Розуміння тренажерного залу OpenAI

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

агент - гравець ШІ. У цьому випадку це буде Sonic.

середовище - повне оточення агента. Ігрове середовище.

дія - Щось, що може робити агент (тобто рухатись ліворуч, рухатися праворуч, стрибати, нічого не робити).

крок - Виконання 1 дії.

стан - Кадр навколишнього середовища. Поточна ситуація, в якій знаходиться ШІ.

спостереження - Що ШІ спостерігає із навколишнього середовища.

фітнес - наскільки добре працює наш ШІ.

готово - Коли ШІ виконав своє завдання або не може продовжувати далі.

Встановлення залежностей

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

OpenAI : //github.com/openai/retro

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

Бібліотеки Pip для встановлення, такі як cv2, numpy, pickle тощо.

Імпортуйте бібліотеки та встановіть середовище

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

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

Ми також визначимо наше середовище, що складається з гри та стану:

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

Для того, щоб навчити ШІ грати у Їжачка Соніка, вам знадобиться ПЗУ гри (файл гри). Найпростіший спосіб отримати це, придбавши гру від Steam за 5 доларів. Ви також можете знайти безкоштовні файли для завантаження ПЗУ в Інтернеті, проте це незаконно, тому не робіть цього.

У сховищі OpenAI за адресою retro / retro / data / stable / ви знайдете папку для Sonic the Hedgehog Genesis. Помістіть сюди ПЗУ гри та переконайтесь, що вона називається rom.md. Ця папка також містить файли .state. Ви можете вибрати один і встановити рівний йому параметр стану. Я вибрав GreenHillZone Act 1, оскільки це найперший рівень гри.

Розуміння data.json та scenario.json

У папці Sonic the Hedgehog у вас будуть такі два файли:

data.json

{ "info": { "act":  "address": 16776721, "type": ", "level_end_bonus":  "address": 16775126, "type": ", "lives":  "address": 16776722, "type": ", "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": u1"  } }

scenario.json

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

Обидва ці файли містять важливу інформацію, що стосується гри та її навчання.

Як це звучить, файл data.json містить інформацію / дані про різні змінні, характерні для гри (тобто х-позиція Соніка, кількість життів, які він має тощо).

Файл scenario.json дозволяє нам виконувати дії синхронізовано зі значеннями змінних даних. Наприклад, ми можемо винагороджувати Sonic 10.0 кожного разу, коли його х-позиція збільшується. Ми також могли б встановити нашу виконану умову як істину, коли життя Сонік досягла 0.

Розуміння конфігурації подальшої передачі даних NEAT

Файл config-feedforward можна знайти у моєму сховищі GitHub, зв’язаному вище. Це діє як меню налаштувань для налаштування нашого навчання. Щоб вказати кілька простих налаштувань:

fitness_threshold = 10000 # How fit we want Sonic to become pop_size = 20 # How many Sonics per generation num_inputs = 1120 # Number of inputs into our model num_outputs = 12 # 12 buttons on Genesis controller

Є маса налаштувань, з якими можна поекспериментувати, щоб побачити, як це впливає на навчання вашого ШІ! Щоб дізнатись більше про NEAT та різні налаштування конфігурації зворотного зв'язку, я настійно рекомендую прочитати документацію тут

Putting it all together: Creating the Training File

Setting up configuration

Our feedforward configuration is defined and stored in the variable config.

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

Creating a function to evaluate each genome

We start by creating the function, eval_genomes, which will evaluate our genomes (a genome could be compared to 1 Sonic in a population of Sonics). For each genome we reset the environment and take a random action

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

We will also record the game environment’s length and width and color. We divide the length and width by 8.

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

We create a recurrent neural network (RNN) using the NEAT library and input the genome and our chosen configuration.

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

Finally, we define a few variables: current_max_fitness (the highest fitness in the current population), fitness_current (the current fitness of the genome), frame (the frame count), counter (to count the number of steps our agent takes), xpos (the x-position of Sonic), and done (whether or not we have reached our fitness goal).

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

While we have not reached our done requirement, we need to run the environment, increment our frame counter, and shape our observation to mimic that of the game (still for each genome).

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

We will take our observation and put it in a one-dimensional array, so that our RNN can understand it. We receive our output by feeding this array to our RNN.

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

Using the output from the RNN our AI takes a step. From this step we can extract fresh information: a new observation, a reward, whether or not we have reached our done requirement, and information on variables in our data.json (info).

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

At this point we need to evaluate our genome’s fitness and whether or not it has met the done requirement.

We look at our “x” variable from data.json and check if it has surpassed the length of the level. If it has, we will increase our fitness by our fitness threshold signifying we are done.

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

Otherwise, we will increase our current fitness by the reward we earned from performing the step. We also check if we have a new highest fitness and adjust the value of our current_max_fitness accordingly.

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

Lastly, we check if we are done or if our genome has taken 250 steps. If so, we print information on the genome which was simulated. Otherwise we keep looping until one of the two requirements has been satisfied.

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

Defining the population, printing training stats, and more

The absolute last thing we need to do is define our population, print out statistics from our training, save checkpoints (in case you want to pause and resume training), and pickle our winning genome.

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)

All that’s left is the matter of running the program and watching Sonic slowly learn how to beat the level!

To see all of the code put together check out the Training.py file in my GitHub repository.

Bonus: Parallel Training

If you have a multi-core CPU you can run multiple training simulations at once, exponentially increasing the rate at which you can train your AI! Although I will not go through the specifics on how to do this in this article, I highly suggest you check the sonicTraning.py implementation in my GitHub repository.

Conclusion

That’s all there is to it! With a few adjustments, this framework is applicable to any game for the NES, SNES, SEGA Genesis, and more. If you have any questions or you just want to say hello, feel free to email me at vedantgupta523[at]gmail[dot]com ?

Also, be sure to check out Lucas Thompson's Sonic AI Bot Using Open-AI and NEAT YouTube tutorials and code to see what originally inspired this article.

Key Takeaways

  1. Neuroevolution of Augmenting Topologies (NEAT) is an algorithm used to train AI to perform certain tasks. It is modeled after genetic evolution.
  2. NEAT eliminates the need for pre-existing data when training AI.
  3. The process of implementing OpenAI and NEAT usingPython to train an AI to play any game.