【问题标题】:Genetic algorithm /w Neural Network playing snake is not improving遗传算法/w神经网络玩蛇没进步
【发布时间】: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


【解决方案1】:

我注意到在您的伪代码中,在创建每个新代时,父代被完全清除,只保留子代。这自然会导致健康水平降低,因为无法保证后代的健康水平可以与父代相媲美。为了确保适应度水平不降低,您必须合并父代和子代并修剪最弱的成员(我会推荐),或者您可以要求后代生成功能至少产生适合的后代作为父母(经过多次尝试和错误)。


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

【讨论】:

  • 我已经离开了几天,所以我没有时间实施一些建议,但我明天会试试这个。看到赏金即将到期我还不如奖励它,但我会等到我看到结果后再接受任何答案。谢谢。
  • 我期待听到您的结果。 :)
  • 所以我更新了我的 keep_per_gen 变量,它将每代前 N 条蛇保持在更大的范围内。目前我保持每代50000 25000,现在健身在第一代之后并没有开始下降,woo。然而,即使在运行该算法大约 200 代之后(我应该运行更长时间吗?),适应度从未显着提高到 300 以上,这表明蛇学会了循环,但从未学会追求水果。
  • 您可以通过在地图上放置大量水果来增加寻求水果的行为。一旦你开始看到寻求水果的行为,你就可以逐渐减少水果的数量。这是因为如果水果数量很少,那么算法会更关心影响适应度的其他因素,最终很难让一个突变在以后添加寻果行为。
【解决方案2】:

您只变异了 5% 的人口,而不是 5% 的“基因组”。这意味着您的人口将很快得到修复 - https://en.wikipedia.org/wiki/Fixation_(population_genetics)

为什么人口表现不佳是有道理的,因为您只是在探索健身景观的一小部分 (https://en.wikipedia.org/wiki/Fitness_landscape)。

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

如果您担心丢失当前的“最佳基因组”,进化计算中的典型方法是将适应度最高的个体复制到没有突变的下一代。

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

【讨论】:

    【解决方案3】:

    我有这个确切的问题一个星期,我只是想通了问题是什么。将大脑从一条蛇分配给另一条蛇时,请确保使用 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也会被重新分配。

    【讨论】:

      猜你喜欢
      • 2010-10-24
      • 2019-04-15
      • 2011-06-30
      • 2017-10-25
      • 2016-04-05
      • 2011-07-04
      • 2011-01-06
      • 2011-04-08
      • 2018-05-11
      相关资源
      最近更新 更多