Создание простого и работоспособного генетического алгоритма для нейросети с 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 t2 == y[0]:             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 t2 == y[0]:             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 t2 == y[0]:             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 t2 == y[0]:             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(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 = []

Само скрещивание и мутация: мы берём одну часть первого родителя, вторую от второго, мутируем и у нас получился ребёнок в списке 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 двумя единичками, но в случае логических операций, там придется давать все варианты, ибо их слишком мало и она ничего не поймет.

Всё!!!


ссылка на оригинал статьи https://habr.com/ru/articles/784192/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *