Создание генетического алгоритма для нейросети и нейроcети для графических игр с помощью Python и NumPy

Создание генетического алгоритма для нейросети и нейроcети для графических игр с помощью Python и NumPy

Привет, dtf или DTF))!

Сегодня я расскажу и покажу, как сделать Genetic Algorithm(GA) для нейросети, чтобы с помощью него она смогла проходить разные игры. Я его испробовал на игре Pong и Flappy bird. Он себя показал очень хорошо. Советую прочитать, если вы не читали первую статью: «Создание простого и работоспособного генетического алгоритма для нейросети с Python и NumPy», так как я доработал свой код, который был показан в той статье.

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

Используя скрипт, который я написал и описал в предыдущей статье, я создал сильно изменённый код генетического алгоритма для игры Pong, который я и буду описывать больше всего, так как именно на него я опирался, когда я уже создавал GA для Flappy Bird.

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

import numpy as np import random import ANNPong as anp import pygame as pg import sys from pygame.locals import * pg.init() listNet = {} NewNet = [] goodNet = [] timeNN = 0 moveRight = False moveLeft = False epoch = 0 mainClock = pg.time.Clock() WINDOWWIDTH = 800 WINDOWHEIGHT = 500 windowSurface = pg.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32) pg.display.set_caption('ANN Pong')

AnnPong это скрипт с игрой

listNet, NewNet, goodNet - списки нейросетей(потом разберем подробнее)

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

MoveRight, moveLeft - выбор нейросети куда двигаться

epoch - счетчик эпох

def sigmoid(x): return 1/(1 + np.exp(-x)) class Network(): def __init__(self): self.H1 = np.random.randn(6, 12) self.H2 = np.random.randn(12, 6) self.O1 = np.random.randn(6, 3) self.BH1 = np.random.randn(12) self.BH2 = np.random.randn(6) self.BO1 = np.random.randn(3) self.epoch = 0 def predict(self, x, first, second): nas = x @ self.H1 + self.BH1 nas = sigmoid(nas) nas = nas @ self.H2 + self.BH2 nas = sigmoid(nas) nas = nas @ self.O1 + self.BO1 nas = sigmoid(nas) if nas[0] > nas[1] and nas[0] > nas[2]: first = True second = False return first, second elif nas[1] > nas[0] and nas[1] > nas[2]: first = False second = True return first, second elif nas[2] > nas[0] and nas[2] > nas[1]: first = False second = False return first, second else: first = False second = False return first, second def epoch(self, a): return 0 class Network1(): def __init__(self, H1, H2, O1, BH1, BH2, BO1, ep): self.H1 = H1 self.H2 = H2 self.O1 = O1 self.BH1 = BH1 self.BH2 = BH2 self.BO1 = BO1 self.epoch = ep def predict(self, x, first, second): nas = x @ self.H1 + self.BH1 nas = sigmoid(nas) nas = nas @ self.H2 + self.BH2 nas = sigmoid(nas) nas = nas @ self.O1 + self.BO1 nas = sigmoid(nas) if nas[0] > nas[1] and nas[0] > nas[2]: first = True second = False return first, second elif nas[1] > nas[0] and nas[1] > nas[2]: first = False second = True return first, second elif nas[2] > nas[0] and nas[2] > nas[1]: first = False second = False return first, second else: first = False second = False return first, second

Сигмоида используется как функция активации.

В классе Network мы определяем параметры нейросети, а в функции predict она говорит, куда двигаться в игре. (nas - сокращение от Network answer), функция epoch возвращает эпоху появления этого ИИ для нулевого поколения, так как в классе Network1() для этого задается отдельная переменная.

for s in range (1000): s = Network() timeNN = anp.NNPong(s) listNet.update({ s : timeNN }) listNet = dict(sorted(listNet.items(), key=lambda item: item[1])) NewNet = listNet.keys() goodNet = list(NewNet) NewNet = goodNet[:10] listNet = {} goodNet = NewNet anp.NPong(NewNet[0]) print(str(epoch) + " epoch") print(NewNet[0].epoch) print('next') anp.NPong(NewNet[1]) print(NewNet[1].epoch) print('next') anp.NPong(NewNet[2]) print(NewNet[2].epoch) print('next') anp.NPong(NewNet[3]) print(NewNet[3].epoch) print('next') anp.NPong(NewNet[4]) print(NewNet[4].epoch) print('next') anp.NPong(NewNet[5]) print(NewNet[5].epoch) print('next') anp.NPong(NewNet[6]) print(NewNet[6].epoch) print('next') anp.NPong(NewNet[7]) print(NewNet[7].epoch) print('next') anp.NPong(NewNet[8]) print(NewNet[8].epoch) print('next') anp.NPong(NewNet[9]) print(NewNet[9].epoch) print('that is all')

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

Подробнее:

В timeNN записывается возвращенная из кода с игрой фитнес функция, затем мы добавляем в listNet ИИ и его значение timeNN. После цикла мы сортируем список, записываем в NewNet нейросети из listNet, дальше мы формируем список и оставляем только десять.

for g in range(990): parent1 = random.choice(NewNet) parent2 = random.choice(NewNet) ch1H = np.vstack((parent1.H1[:3], parent2.H1[3:])) * random.uniform(-2, 2) ch2H = np.vstack((parent1.H2[:6], parent2.H2[6:])) * random.uniform(-2, 2) ch1O = np.vstack((parent1. O1[:3], parent2. O1[3:])) * random.uniform(-2, 2) chB1 = parent1.BH1 * random.uniform(-2, 2) chB2 = parent2.BH2 * random.uniform(-2, 2) chB3 = parent2.BO1 * random.uniform(-2, 2) g = Network1(ch1H, ch2H, ch1O, chB1, chB2, chB3, 1) goodNet.append(g) NewNet = []

Здесь происходит скрещивание и мутация.(Такие моменты более подробно были описаны в первой статье)

while True: epoch += 1 print(str(epoch) + " epoch") for s in goodNet: timeNN = anp.NNPong(s) listNet.update({ s : timeNN }) goodNet =[] listNet = dict(sorted(listNet.items(), key=lambda item: item[1], reverse=True)) goodNet = list(listNet.keys()) NewNet.append(goodNet[0]) goodNet = list(listNet.values()) for i in listNet: a = goodNet[0] if listNet.get(i) == a: NewNet.append(i) goodNet = list(NewNet) listNet = {} try: print(NewNet[0].epoch) anp.NPong(NewNet[0]) print('next') print(NewNet[1].epoch) anp.NPong(NewNet[1]) print('next') print(NewNet[2].epoch) anp.NPong(NewNet[2]) print('next') print(NewNet[3].epoch) anp.NPong(NewNet[3]) print('next') print(NewNet[4].epoch) anp.NPong(NewNet[4]) print('next') print(NewNet[5].epoch) anp.NPong(NewNet[5]) print('next') print(NewNet[6].epoch) anp.NPong(NewNet[6]) print('next') print(NewNet[7].epoch) anp.NPong(NewNet[7]) print('next') except IndexError: print('that is all') for g in range(1000 - len(NewNet)): parent1 = random.choice(NewNet) parent2 = random.choice(NewNet) ch1H = np.vstack((parent1.H1[:3], parent2.H1[3:])) * random.uniform(-2, 2) ch2H = np.vstack((parent1.H2[:6], parent2.H2[6:])) * random.uniform(-2, 2) ch1O = np.vstack((parent1. O1[:3], parent2. O1[3:])) * random.uniform(-2, 2) chB1 = parent1.BH1 * random.uniform(-2, 2) chB2 = parent2.BH2 * random.uniform(-2, 2) chB3 = parent2.BO1 * random.uniform(-2, 2) g = Network1(ch1H, ch2H, ch1O, chB1, chB2, chB3, epoch) goodNet.append(g) print(len(NewNet)) print(len(goodNet)) NewNet = []

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

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

Это всё по первому коду!

Перейдем к коду игры. Тут я объясню только то, что касается обучения ИИ(весь размещу ссылкой на диск).

В игре Pong нейросеть играла дважды: в первый раз мячик отскакивает влево, второй раз - вправо

*whGo - это переменная в коде(сокращение от "where to go")

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

После месяцев работы и доработок, у меня получилось создать алгоритм обучения для игры Pong, однако для уверенности я решил проверить ИИ не на своей игре, а созданную другим человеком(проверка на всеядность)))), я выбрал игру Flappy Bird на pygame с этого видео: https://youtu.be/7IqrZb0Sotw?feature=shared

Немного изменив игру для нейросети, например, добавил переменные расстояния от птицы до трубы. Их 3 по 3, так как нам нужно знать высоту каждой трубы(y) и расстояние по х, а на экране не было больше трех пар труб, поэтому и три по три(всего девять). Также после столкновения функция перезапускалась и третьим параметром, который назван rep функции передавалось какой это перезапуск, если он был равен трем, то игра возвращала фитнес функцию в Genetic Algorithm, а если нулю, то мы присуждаем переменной time значение 0. Также я не писал две очень похожие друг на друга функции, а просто проверял, если переменная checkNN равна True, то нужно обновить экран.

Я также доработал код обучения

while True: for event in pg.event.get(): if event.type == KEYDOWN: if event.key == K_1: showNN = True epoch += 1 print(str(epoch) + " epoch") if epoch < 10: for s in goodNet: timeNN = anp.NPong(s, False, 0, 0) listNet.update({ s : timeNN }) if epoch >= 10: for s in goodNet: timeNN = anp.NPong(s, False, 0, 1) listNet.update({ s : timeNN })

После десятой эпохи из-за последнего параметра, который мы меняем на единицу(в коде игры я назвал этот параметр varRe от слов variant of return), игра возвращает не время, а кол-во труб до столкновения(так нейросеть учиться лучше)

howALot = 1000 - len(NewNet) if howALot < 40: howALot = 40

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

Моя нейросеть проходит игру Pong
Моя нейросеть проходит игру Flappy Bird

На этом всё, если есть вопросы, пишите в комментариях, пока!

Эта же моя статья, но на Хабр -

22
2 комментария

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

Ответить

Нейросети это попытка создать что-то обучаемое, подобие человеческого мозга, чтобы могла думать. Ее надо учить. Есть три варианта: алгоритм обратного распространения ошибки(когда знаешь, что на получится выходе, вообще всё). Генетический алгоритм(знаешь только размер, здесь известно , какой результат должен выдавать выходной нейрон при всех разных данных?) и NEAT(вообще ничего не знаешь(даже размер) :). Нейросети другую нейросеть самостоятельно не обучают

1
Ответить