当您知道神经网络的参数,但不知道输出应该是什么时,需要遗传算法,例如,该算法可用于玩 Google Dinosaur 或 Flappy Bird,因为您不知道输出应该是什么,但您有能力对最可行的选项进行排序, 例如,按时间,这称为适应度函数。
我从来没有找到这样一个有效、简单且可用的算法,所以我开始创建自己的轻量级、简单、完美工作的遗传算法。
我的目标不是拖延这篇文章的写作,也不是用它的长度来折磨读者,所以让我们直接进入代码。如前所述,代码很简单,因此大部分不需要在整篇文章中描述。
首先,我们需要导入模块:
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import numpy as np
import random
</code></span></span>
然后我们添加 Dataset 和答案,但不是使用反向传播算法,而只是计算正确答案的数量。然后,您可以在其他变体上对其进行测试,这些变体现在已被注释掉
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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]])
</code></span></span>
添加列表和激活函数。这些列表的含义将在后面变得清晰。第一个激活函数是 sigmoid,第二个是阈值。
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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]
</code></span></span>
接下来,我们需要创建两个类,第一个用于创建初始种群,第二个用于所有后续类,因为第一次我们需要随机创建权重,然后只需交叉和改变它们。init() 函数用于创建或添加权重,predict() 算法本身和计算最佳选项需要,而 Fredict() 函数的不同之处在于它返回答案和适应度函数以在屏幕上显示数字并查看训练阶段。在输出层,首先使用 sigmoid 函数使答案更接近其中一个选项,然后才使用阈值函数。
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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
</code></span></span>
我们输出第一个答案和变量 good,也就是这里的适应度函数,然后我们为下一个神经网络重置它,打印 'wait0'(你可以在这里写任何你想写的东西)是必要的,以免混淆不同神经网络的答案从哪里开始。
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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
</code></span></span>
第一个周期过去了,在这里和所有后续的周期中,我们只给出六个问题来检查它如何应对它没有完成的任务,也就是说,我们检查它是否填鸭式,这种情况有时会发生。现在让我们更详细地了解一下:根据它正确回答了多少答案,我们将其分配给其中一个类,如果大量数字是正确的,那么我们必须支持这样的神经网络并增加它的数量,这样在随后的突变中就会有更多更聪明的,要理解这一点, 你可以想象,100 个人有一个天才,但对每个人来说都不够,这意味着他的天才将在下一代中消失,这意味着要么神经网络学习得很慢,要么根本不存在,为了避免这种情况,我们在循环中增加具有大量正确答案的神经网络的数量。最后,我们清空主 listNet 列表,按照从最好到最差的顺序为其分配 GoodNet 列表的新值,切入 100 个最佳个体,用于后续突变。
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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 = []
</code></span></span>
交叉和突变本身:我们从第一个父级中获取一部分,从第二个父级中获取第二部分,突变,我们在 NewNet 列表中得到一个子项,所以 1000 次。
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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 = []
</code></span></span>
从代码的上一部分开始,我们使用 Network1(),因为我们现在正在交叉和改变,而不是随机创建。所以我们需要重复 1000 次(这是一个超参数,所以你可以自己选择纪元数,15 对我来说就足够了),我们在第一个纪元上显示答案,第 1000 个是最终版本(例如,如果你有 20 次,那么指定 20 次)。这里代码是重复的,所以我不会描述它,那里的一切都非常清楚。
highlight
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>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
</code></span></span>
就这样,神经网络应该找到的模式,这是最终版本所依赖的数字(第一、第二、第三),并忽略其余的。例如,你可以做一些逻辑运算(XOR、NOT 和 ...),只有在这种情况下在网络类中将输入数据改成 2,我还遵循了隐藏层中神经元等于输入数据乘以 2 的规则,它奏效了,但你可以尝试你的选择,为神经网络提供相同数量的一些答案和其他答案也是非常重要的, 这样正确答案的数量,例如 "A",就等于 "B",否则神经网络会以同样的方式回答所有答案,也就是说,如果有更多的 A,那么它将对所有答案回答 A,并且不会有任何结果,在训练样本中也给它完全不同的选项,以便它理解模式, 例如,如果你制作了一个 XOR 块,那么你必须添加一个带有两个 1 的选项,但在逻辑运算的情况下,你将不得不给出所有选项,因为它们太少了,它不会理解任何东西。