首页 文章

遗传算法/ w神经网络玩蛇没有改进

提问于
浏览
23

我正在尝试创建一个遗传算法来训练神经网络,目的是玩游戏蛇 .

我遇到的问题是,几代人的 Health 状况并没有改善,它要么仍然保持在不给予任何输入游戏的期望,或者只是在第一代之后变得更糟 . 我怀疑它是神经网络的一个问题,但我不知道它是什么 .

神经网络设置

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,如果有人敢深入研究它 .

Update:

我改变了一些事情:

  • 修正了重量/偏差的产生,之前它们只在0和1之间产生 .

  • 编辑了我的交叉方法,每组父母返回两个孩子而不是一个 .

  • 将适应度函数更改为仅等于蛇的年龄(用于测试目的)

  • 更改了人口变量

现在算法表现更好,第一代通常会找到一条健身14-16的蛇,这意味着蛇确实转弯以避免死亡,但它几乎总是只从那里下坡 . 实际上,第一条蛇实际上已经实现了靠近东部和北部/南部边缘的转弯战术,但绝不是西部边缘 . 在第一代之后,健身往往会变得更糟,最终恢复到最低的 Health 状态 . 我对出了什么问题感到茫然,但我觉得这可能是我忽略的大事 .

Update #2:

想想我可能会提到我试过的一些不起作用的东西:

  • 将每个隐藏层的节点从8更改为16,这使得蛇表现得更差 .

  • 允许蛇转回自身,这也使蛇表现更差 .

  • 大(我认为它们很大,不确定标准的流行大小是多少 . )人口规模约为1 000 000,父母约为1000,没有正面变化 .
    每个隐藏层有

  • 16或32个节点,看起来几乎没有影响 .

  • 修正了mutate函数,以便在-1和1之间正确分配值,没有明显的影响 .

Update #3:

我改变了一些事情并开始看到更好的结果 . 首先,我停止了产卵的结果,以简化学习过程,而是让蛇的 Health 状态与他们的年龄相等(他们存活了多少转/帧),以及转身后关闭输入数组的规范化我得到了一条300的健身蛇! 300是蛇在死亡之前可以拥有的最大年龄 .

然而问题仍然存在,在前几代之后, Health 将直线下降,前1-5代可能有300的适应性(有时它们不适合,而且适应性较低,但我认为这是下降的人口规模 . ),但在此之后,几代人的 Health 状况将直线下降至20-30并留在那里 .

此外,如果我重新开始水果,那么蛇会再次变得非常适合 . 有时第一代将会获得一条能够在循环中移动的蛇,因此在没有任何水果的情况下获得300的适应性,但这几乎从未转移到下一个代 .

2 回答

  • 4

    我注意到在你的伪代码中,在创建每一代新代时,父代完全消失,只保留子代 . 这自然会导致降低 Health 水平,因为没有什么能保证后代的 Health 水平与父母一代相当 . 为了确保 Health 水平不降低,您必须合并父代和子代并修剪最弱的成员(我建议),或者您可以要求后代生成函数产生至少适合的后代作为父母(通过许多试验和错误) .


    如果你决定专注于后代发生器,一种(某种程度上)保证改善后代的方法是通过简单地为每个权重向量添加少量噪声来实现无性繁殖 . 如果噪音水平足够小,您可以生成改进的后代,成功率高达50% . 然而,更大的噪音水平允许更快的改进,并且它们有助于跳出局部最佳状态,即使它们的成功率低于50% .

  • 2

    你只是变异了5%的人口,而不是"genome"的5% . 这意味着您的人口将以极快的速度得到修复 - https://en.wikipedia.org/wiki/Fixation_(population_genetics) .

    这是有道理的,为什么人口不是很好,因为你只是探索健身景观的一个非常小的区域(https://en.wikipedia.org/wiki/Fitness_landscape) .

    您应该更改mutate函数以突变5%的基因组(即节点之间的权重) . 随意使用突变率 - 不同的问题在不同的突变率下表现更好 .

    如果您担心失去当前的“最佳基因组”,进化计算中的典型方法是将具有最高适应度的个体复制到下一代而不会发生突变 .

    (对不起,这可能应该是一个评论,但我没有足够的声誉) .

相关问题