Генетический алгоритм нужен, когда ты знаешь параметры своей нейросети, но не знаешь, что должно получиться на выходе, например, этот алгоритм можно использовать для игры в 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 двумя единичками, но в случае логических операций, там придется давать все варианты, ибо их слишком мало и она ничего не поймет.
Всё!!!