【发布时间】:2018-10-22 21:49:50
【问题描述】:
我正在尝试创建一种遗传算法来训练神经网络,目标是玩蛇游戏。
我遇到的问题是几代人的适应度并没有提高,它要么保持在没有为游戏提供任何输入而可以预期的适应度,要么在第一代之后变得更糟。我怀疑这是神经网络的问题,但我不知道它是什么。
神经网络设置
24输入节点
2隐藏层
8每层节点
4 输出节点(蛇可以采取的每个方向一个)
输入是蛇可以看到的每个方向的数组。对于每个方向,它都会检查到墙壁、水果或自身的距离。最终结果是一个长度为3*8 = 24 的数组。
权重和偏差是介于 -1 和 1 之间的随机浮点数,在创建网络时生成。
遗传算法设置
人口规模:50000
每一代选择的父母:1000
保持最高每代:25000(新变量,看到更好的结果)
每个孩子的变异几率:5%
(我尝试了很多不同的尺寸比例,虽然我仍然不确定 典型的比率是多少。)
我正在使用单点交叉。每个权重和偏差数组都在父母之间交叉,并传递给孩子(交叉的每个“版本”都有一个孩子)。
我正在使用我认为是轮盘选择来选择父母,我将在下面发布确切的方法。
蛇的适应度计算公式为:age * 2**score(不再是,更新中的更多信息),其中年龄是蛇存活的圈数,得分是它收集的水果数量。
详情
下面是一些伪代码,试图总结我的遗传算法(应该)如何工作:
pop = Population(size=1000)
while True: # Have yet to implement a 'converged' check
pop.calc_fitness()
new_pop = []
for i in range(n_parents):
parent1 = pop.fitness_based_selection()
parent2 = pop.fitness_based_selection()
child_snake1, child_snake2 = parent1.crossover(parent2)
if rand() <= mutate_chance:
child_snake.mutate()
new_pop.append(child_snake1, child_snake2)
pop.population = new_pop
print(generation_statistics)
gen += 1
这是我用来选择父母的方法:
def fitness_based_selection(self):
"""
A slection process that chooses a snake, where a snake with a higher fitness has a higher chance of being
selected
:return: The chosen snake's brain
"""
sum_fitnesses = sum(list([snake[1] for snake in self.population]))
# A random cutoff digit.
r = randint(0, sum_fitnesses)
current_sum = 0
for snake in self.population:
current_sum += snake[1]
if current_sum > r:
# Return brain of chosen snake
return snake[0]
值得注意的是,self.population 是一个蛇列表,其中每条蛇都是一个列表,其中包含控制它的神经网络,以及网络达到的适应度。
这是从游戏输出中获取网络输出的方法,因为我怀疑我在这里可能做错了什么:
def get_output(self, input_array: np.ndarray):
"""
Get output from input by feed forwarding it through the network
:param input_array: The input to get an output from, should be an array of the inputs
:return: an output array with 4 values of the shape 1x4
"""
# Add biases then multiply by weights, input => h_layer_1, this is done opposite because the input can be zero
h_layer_1_b = input_array + self.biases_input_hidden1
h_layer_1_w = np.dot(h_layer_1_b, self.weights_input_hidden1)
h_layer_1 = self.sigmoid(h_layer_1_w) # Run the output through a sigmoid function
# Multiply by weights then add biases, h_layer_1 => h_layer_2
h_layer_2_w = np.dot(h_layer_1, self.weights_hidden1_hidden2)
h_layer_2_b = h_layer_2_w + self.biases_hidden1_hidden2
h_layer_2 = self.sigmoid(h_layer_2_b)
# Multiply by weights then add biases, h_layer_2 => output
output_w = np.dot(h_layer_2, self.weights_hidden2_output)
output_b = output_w + self.biases_hidden2_output
output = self.sigmoid(output_b)
return output
当手动运行神经网络并启用游戏的图形版本时,很明显网络几乎不会多次改变方向。这让我很困惑,因为我的印象是,如果所有的权重和偏差都是随机生成的,那么输入将被随机处理并给出随机输出,而输出似乎在游戏的第一轮改变一次,然后永远不会再次发生重大变化。
在运行遗传算法时,每一代的最高适应度几乎不会超过没有输入的蛇所期望的适应度(在本例中为 16),我认为这与神经网络的问题有关。当它确实超过时,下一代将再次恢复到 16。
对于他的问题的任何帮助将不胜感激,我还是这个领域的新手,我发现它真的很有趣。如果需要,我很乐意回答更多细节。我的完整代码可以在here 找到,如果有人敢于深入研究的话。
更新:
我改变了几件事:
- 修复了权重/偏差的生成,以前它们只生成在 0 和 1 之间。
- 编辑了我的交叉方法,使每组父母返回两个孩子,而不是一个。
- 将适应度函数更改为仅等于蛇的年龄(用于测试目的)
- 更改了总体变量
现在算法性能更好,第一代通常会找到一条适应度为 14-16 的蛇,这意味着蛇确实会转弯以避免死亡,但它几乎总是从那里走下坡路。当靠近东边和南北边时,第一条蛇实际上已经实现了转弯的策略,但从来没有在西边。在第一代之后,适应度只会变得更差,最终会回到可能的最低适应度。我不知道出了什么问题,但我觉得这可能是我忽略的大问题。
更新 #2:
我想我不妨提一下我尝试过的一些没有工作的事情:
- 将每个隐藏层的节点从 8 个更改为 16 个,这使得蛇的性能明显变差。
- 让蛇变回自己,这也让蛇的表现更差。
- 大(我认为它们很大,不确定标准人口规模是多少。)约 1 000 000 的人口规模,约 1000 名父母,没有积极变化。
- 每个隐藏层 16 或 32 个节点,似乎几乎没有影响。
- 修复了 mutate 函数以在 -1 和 1 之间正确分配值,没有明显影响。
更新 #3:
我改变了一些东西并开始看到更好的结果。首先,我停止了水果的产卵以简化学习过程,而是让蛇的适应度等于它们的年龄(它们存活了多少转/帧),在关闭输入数组的归一化后,我得到了一条蛇体能300! 300 岁是蛇在老年死亡之前的最大年龄。
但是问题仍然存在,在前几代之后适应度会直线下降,前 1-5 代的适应度可能为 300(有时他们没有,而是适应度较低,但我假设这取决于人口规模。),但之后几代人的适应度将下降到 ~20-30 并保持在那里。
另外,如果我重新打开水果,蛇的适应度又会变得很差。有时第一代会实现一条能够循环移动的蛇,因此在不拾取任何水果的情况下获得 300 的适应度,但这几乎从未转移传给下一代。
【问题讨论】:
-
几件事:标准化视觉阵列似乎很奇怪。在其他 NN 应用程序中,您将对整个数据集进行规范化。当您像您正在做的那样标准化单个样本时,几个近处的对象和几个远处的对象会“看起来”相似,这似乎是错误的。如果这没有任何改变,请创建一些特定情况并验证视觉功能是否正常工作。然后可能尝试单层神经网络,并在调试器中逐个执行,查看每一步的值是否符合您的预期。抱歉,我没有真正的答案。
-
很遗憾,我对遗传算法没有太多经验,但也许您的更改/交叉太剧烈了?也许您可以将更改偏向更适合的父级,以便好+坏的蛇大多跟随好父级。我还没有机会测试你的代码,但看起来交叉函数可能会改变传递给它的数组,所以当你尝试生孩子时,你也在修改父母,我不认为你想要那个。
-
您基于适应度的选择永远无法选择种群中的最后一条蛇。如果人口足够大,我认为这应该不会引起问题,但这是一个小偏差。
-
您可以考虑尝试不同类型的重组方法,例如均匀交叉或(尤其是)中间交叉(参见例如Essentials of Metaheuristics)。我希望它们比单点交叉更适合 ANN 重组。还要非常小心你的适应度函数——GA 显然对此非常敏感。我的Santa Fe Trail solver 的健身功能被困了一周。
-
和选择。听起来您正在使用一种截断选择。考虑尝试锦标赛选择(从锦标赛规模 2 开始;合理的顶部是接近 7 人)——它允许在选择过程中更多的多样性(并且还控制多样性)(这通常是好的),并且在实施中甚至比锦标赛选择更简单。
标签: python python-3.x neural-network genetic-algorithm unsupervised-learning