Создание простого и работоспособного генетического алгоритма с Python и NumPy

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

У меня никогда не получалось найти такой алгоритм и чтобы он работал, и был простым, и его можно было использовать, поэтому я приступил к созданию своего легкого, простого, прекрасно работающего Genetic Algorithm.

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

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

import numpy as np import random

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

x = np.array([[1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 1, 1], [1, 1, 1]]) y = np.array([[0],[1],[1], [0], [0], [0], [0], [1], [1]]) #x = np.array([[0, 1, 1], [0, 0, 1], [1, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 1, 1]]) #y = np.array([[1],[0], [0], [1], [0], [1], [0], [1], [1]]) #x = np.array([[1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 0], [1, 0, 0], [0, 0, 0], [1, 1, 0], [0, 1, 1], [1, 1, 1]]) #y = np.array([[1],[0],[1], [0], [1], [0], [1], [0], [1]])

Добавляем списки и функции активации. Смысл списков станет понятен позже. Первая функция активации это сигмоида, а вторая - пороговая

listNet = [] NewNet = [] goodNET = [] GoodNet0 = [] GoodNet1 = [] GoodNet2 = [] GoodNet3 = [] GoodNet4 = [] GoodNet5 = [] GoodNet6 = [] good = 0 epoch = 0 good = 0 epoch = 0 def sigmoid(x): return 1/(1 + np.exp(-x)) def finfunc(x): if x[0] >= 0.5: x[0] = 1 return x[0] else: x[0] = 0 return x[0]

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

class Network(): def __init__(self): self.H1 = np.random.randn(3, 6) self.O1 = np.random.randn(6, 1) def predict(self, x, y): t1 = x @ self.H1 t1 = sigmoid(t1) t2 = t1 @ self.O1 t2 = sigmoid(t2) t2 = finfunc(t2) if np.array_equal(t2, y): global good good += 1 def Fpredict(self, x, y): t1 = x @ self.H1 t1 = sigmoid(t1) t2 = t1 @ self.O1 t2 = sigmoid(t2) t2 = finfunc(t2) if np.array_equal(t2, y): global good good += 1 return t2, good class Network1(): def __init__(self, H1, O1): self.H1 = H1 self.O1 = O1 def predict(self, x, y): t1 = x @ self.H1 t1 = sigmoid(t1) t2 = t1 @ self.O1 t2 = sigmoid(t2) t2 = finfunc(t2) if np.array_equal(t2, y): global good good += 1 def Fpredict(self, x, y): t1 = x @ self.H1 t1 = sigmoid(t1) t2 = t1 @ self.O1 t2 = sigmoid(t2) t2 = finfunc(t2) if np.array_equal(t2, y): global good good += 1 return t2, good

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

s = Network() print(s.Fpredict(x[0], y[0])) print(s.Fpredict(x[1], y[1])) print(s.Fpredict(x[2], y[2])) print(s.Fpredict(x[3], y[3])) print("wait0") good = 0

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

for s in range (1000): s = Network() good = 0 s.predict(x[0], y[0]) s.predict(x[1], y[1]) s.predict(x[2], y[2]) s.predict(x[3], y[3]) s.predict(x[4], y[4]) s.predict(x[5], y[5]) if good == 6: GoodNet6.append(s) for r in range(15): GoodNet4.append(s) elif good == 5: GoodNet5.append(s) for r in range(10): GoodNet4.append(s) elif good == 4: GoodNet4.append(s) for r in range(5): GoodNet4.append(s) elif good == 3: GoodNet3.append(s) elif good == 2: GoodNet2.append(s) elif good == 1: GoodNet1.append(s) elif good == 0: GoodNet0.append(s) good = 0 listNet = [] listNet.extend(GoodNet4) listNet.extend(GoodNet3) listNet.extend(GoodNet2) listNet.extend(GoodNet1) GoodNet1 = [] GoodNet2 = [] GoodNet3 = [] GoodNet4 = [] GoodNet5 = [] GoodNet6 = [] goodNET = listNet[:100] listNet = goodNET goodNET = []

Само скрещивание и мутация: мы берём одну часть первого родителя, вторую от второго, мутируем и у нас получился ребёнок в списке NewNet, так 1000 раз.

for g in range(1000): parent1 = random.choice(listNet) parent2 = random.choice(listNet) ch1H = np.vstack((parent1.H1[:1], parent2.H1[1:])) * random.uniform(-0.2, 0.2) ch1O = parent1.O1 * random.uniform(-0.2, 0.2) g = Network1(ch1H, ch1O) NewNet.append(g) listNet = NewNet NewNet = []

Начиная с предыдущей части кода, мы используем Network1(), так как мы сейчас скрещиваем и мутируем, но не создаем рандомно. Так нужно повторить 1000 раз(это гиперпараметр, поэтому можете выбрать количество эпох самостоятельно, мне хватало 15), мы показываем ответы на первый эпохе и 1000-й - финальный вариант(если у вас, например, 20, то указываете 20). Здесь код повторяется, поэтому я не буду его описывать, там всё очень понятно.

for i in range(1000): good = 0 epoch += 1 for s in listNet: good = 0 s.predict(x[0], y[0]) s.predict(x[1], y[1]) s.predict(x[2], y[2]) s.predict(x[3], y[3]) s.predict(x[4], y[4]) s.predict(x[5], y[5]) if good == 6: GoodNet6.append(s) for r in range(15): GoodNet4.append(s) elif good == 5: GoodNet5.append(s) for r in range(10): GoodNet4.append(s) elif good == 4: GoodNet4.append(s) for r in range(5): GoodNet4.append(s) elif good == 3: GoodNet3.append(s) elif good == 2: GoodNet2.append(s) elif good == 1: GoodNet1.append(s) elif good == 0: GoodNet0.append(s) good = 0 listNet = [] listNet.extend(GoodNet6) listNet.extend(GoodNet5) listNet.extend(GoodNet4) listNet.extend(GoodNet3) listNet.extend(GoodNet2) listNet.extend(GoodNet1) GoodNet1 = [] GoodNet2 = [] GoodNet3 = [] GoodNet4 = [] GoodNet5 = [] GoodNet6 = [] goodNET = listNet[:100] listNet = goodNET goodNET = [] if epoch == 1000: print(listNet[0].Fpredict(x[0], y[0])) print(listNet[0].Fpredict(x[1], y[1])) print(listNet[0].Fpredict(x[2], y[2])) print(listNet[0].Fpredict(x[3], y[3])) print(listNet[0].Fpredict(x[4], y[4])) print(listNet[0].Fpredict(x[5], y[5])) print(listNet[0].Fpredict(x[6], y[6])) print(listNet[0].Fpredict(x[7], y[7])) print(listNet[0].Fpredict(x[8], y[8])) good = 0 print('wait') elif epoch == 1: good = 0 print(listNet[0].Fpredict(x[0], y[0])) print(listNet[0].Fpredict(x[1], y[1])) print(listNet[0].Fpredict(x[2], y[2])) print(listNet[0].Fpredict(x[3], y[3])) print('wait1') for g in range(1000): parent1 = random.choice(listNet) parent2 = random.choice(listNet) ch1H = np.vstack((parent1.H1[:1], parent2.H1[1:])) * random.uniform(-2, 2) ch1O = parent1.O1 * random.uniform(2, 2) g = Network1(ch1H, ch1O) NewNet.append(g) listNet = NewNet

Это всё, закономерность, которую должна найти нейросеть, это от какого числа(первого, второго, третьего) зависит окончательный вариант и на остальные не обращать внимание. Вы можете делать, например, логические операции(XOR, NOT, AND…), только в этом случае в классе network изменить входные данные на два, я также придерживался правила нейроны в скрытом слое равны входным данным умноженным на два, это работало, но вы можете попробовать свои варианты, также очень важно предоставить нейросети одинаковое количество одних ответов и других ответов, чтобы количество правильных ответе, например "a", было бы равно "b", а иначе нейросеть будет отвечать на все ответы одинаково, то есть, если больше a, то она на все ответит a и ничего не выйдет, также давать ей в обучающей выборке совершенно разные варианты, чтобы она поняла закономерность, например если вы делаете блок XOR, то надо обязательно добавить вариант c двумя единичками, но в случае логических операций, там придется давать все варианты, ибо их слишком мало и она ничего не поймет.

Многие могут спросить, а как с помощью этого сделать нейросеть для Google динозаврика и тому подобное. Ответ: используйте три списка, первый: listNet, второй: NewNet, третий: фитнесс функция.

a = []#listNet b = []#NewNet с = []# фитнесс функция d = 0# переменная e = 0# счетчик f = 100 #создавая нейросеть мы добавляем в listNet нейросеть, а в c - время a.append(нейросеть) c.append(время) '''далаете так 100 нейросетй(поэтому в f - 100), в каждой проверяете сколько она продержалась добавляете время в c, потом выбор 10 самых лучших: ''' for r in range(10): for i in c: e += 1 if d < i: d = i if e == f: n = c.index(d) b.append(a[n]) c.remove(d) e = 0 f -= 1 a = b b = [] c = [] '''Срез списка не нужен, дальше скрещиваете, мутируете, повторяете и готово!!!'''
2
Начать дискуссию