[英]Genetic algorithm /w Neural Network playing snake is not improving
我正在尝试创建一个遗传算法来训练神经网络,目标是玩蛇游戏。
我遇到的问题是,几代人的适应度没有提高,它要么停留在不给游戏提供任何输入所期望的适应度上,要么在第一代之后变得更糟。 我怀疑这是神经网络的问题,但我不知道它是什么。
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
是一个self.population
的列表,其中每条snake 都是一个包含控制它的NeuralNet 以及网络实现的适应度的列表。
这是从游戏输出中获取网络输出的方法,因为我怀疑我在这里做错了什么:
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。
对他的问题的任何帮助将不胜感激,我还是这个领域的新手,我发现它真的很有趣。 如果需要,我很乐意回答更多细节。 我的完整代码可以在这里找到,如果有人敢深入研究的话。
更新:
我改变了几件事:
现在算法性能更好,第一代通常会找到一条适应度为 14-16 的蛇,这意味着蛇确实会转弯以避免死亡,但它几乎总是从那里走下坡路。 当靠近东边和北/南边时,第一条蛇实际上已经实现了转弯的策略,但永远不会到达西边。 在第一代之后,适应度只会变得更糟,最终回到可能的最低适应度。 我对出了什么问题感到茫然,但我有一种感觉,这可能是我忽略的大问题。
更新#2:
我想我不妨提一些我尝试过但没有用的东西:
更新 #3:
我改变了一些事情并开始看到更好的结果。 首先,我停止了水果的产卵以简化学习过程,而是给了蛇一个与其年龄相等的适应度(它们存活了多少圈/帧),在关闭输入数组的归一化后,我得到了一条蛇健身300! 300 是蛇在老年死亡之前可以拥有的最大年龄。
然而问题仍然存在,在前几代之后,适应度会直线下降,前 1-5 代可能有 300 的适应度(有时他们没有,而是适应度低,但我认为这是下降的到人口规模。),但在那之后,几代人的适应度将下降到 ~20-30 并保持在那里。
此外,如果我重新打开水果,蛇会再次获得糟糕的适应度。 有时第一代会实现一条能够循环移动的蛇,因此在不拿起任何水果的情况下获得 300 的适应度,但这几乎从未转移到下一个一代。
我注意到在你的伪代码中,在创建每一代时,父代被完全消灭,只有子代被保留。 这自然会导致适应度水平降低,因为无法保证后代的适应度水平可与父代相媲美。 为了确保适应度水平不降低,您必须合并父代和子代并修剪最弱的成员(我建议这样做),或者您可以要求后代生成函数至少产生适合的后代作为父母(通过多次尝试和错误)。
如果您决定专注于后代生成器,(在某种程度上)保证改进后代的一种方法是通过简单地向每个权重向量添加少量噪声来实现无性繁殖。 如果噪音水平足够小,您可以以高达 50% 的成功率生成改进的后代。 然而,更大的噪音水平允许更快的改进,并且它们有助于跳出局部最优,即使它们的成功率低于 50%。
你只变异了 5% 的人口,而不是 5% 的“基因组”。 这意味着您的人口将被快速修复 - https://en.wikipedia.org/wiki/Fixation_(population_genetics) 。
这是人口表现不佳的原因,因为您只是在探索健身景观的一小部分区域 ( https://en.wikipedia.org/wiki/Fitness_landscape )。
您应该更改 mutate 函数以突变 5% 的基因组(即节点之间的权重)。 也可以随意使用突变率——不同的问题在不同的突变率下表现更好。
如果您担心丢失当前的“最佳基因组”,进化计算中的一种典型方法是将适应度最高的个体复制到下一代而不发生突变。
(对不起,这可能应该是评论,但我没有足够的声誉)。
我有一个星期的确切问题,我才刚刚弄清楚问题是什么。 将一条蛇的大脑分配给另一条蛇时,请确保使用 array.copy。 例如这样做:
a = np.array([1, 2, 3, 4])
b = a.copy()
而不是这个:
a = np.array([1, 2, 3, 4])
b = a
这是因为在第二种情况下,python 有时会使 b 和 a 共享相同的内存,因此每当您重新分配 b 时,a 也将被重新分配。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.