【问题标题】:Fast Evolutionary Algorithm Mutation: Mutating n random genes instead of evaluating every gene快速进化算法突变:突变 n 个随机基因而不是评估每个基因
【发布时间】:2019-10-31 10:00:30
【问题描述】:

我正在尝试优化我的遗传算法的代码。 DNA 目前是一个字典,用于提高适应度计算的查找速度,但可以轻松更改为 numpy 数组。

突变率应该是1/L,L是DNA的长度。

此解决方案有效,但速度很慢:

# Iterate through genome, flip a gene with a probability of 1/L
def mutate(self):
      self.dna = dict(
        [(i, flip(self.dna[i])) if random.randint(0,num_genes) < 1 
        else (i, self.dna[i]) for i in range(num_genes)]
        )

这个解决方案的速度大约是原来的两倍,但由于某种原因,它会产生更糟糕的结果:

# Select n number of genes calculated by 1/L, then change n random genes
def mutate(self):
      num_mutations = sum(np.random.choice([0,1], num_genes, p=[(num_genes-1)/num_genes, 1/num_genes]))
      for i in np.random.choice(num_genes-1, num_mutations):
        self.dna[i] = flip(self.dna[i])

据我所知,它们突变相同数量的基因,并且输出应该相同。使用设置的随机种子运行 10 次表明后一种方法会导致更差的适应度结果。

为什么第二种方法会导致更差的 dna 适应度?这些方法的结果有何不同?

感谢您的帮助。

【问题讨论】:

  • 您是否尝试过使用np.random.binomial 并为 size=num_genes 生成?
  • @dgumo 谢谢你的建议,现在试试。它的速度快了一点,但结果与使用 random.choice 相同。

标签: python numpy genetic-algorithm mutation


【解决方案1】:

首先:向量​​化

当您的索引是整数时,使用 dict 毫无意义 - 查找整数索引总是比使用哈希表快。您也可以使用 numpy 对其进行矢量化 - 使您的 self.dna 成为 numpy 数组而不是列表或字典,这可能会提高 10 倍至 100 倍的速度。

def flip(x):  # x is a vector of genes, lets a binary array here
    return ~x
mutation_mask = np.random.rand(n_genes)<1./len(self.dna)
self.dna[mutation_mask] = flip(dna[mutation_mask])

第二点:为什么您的两种算法不同:

我不知道,它们看起来应该在统计上是相同的。我能想到的一件事是,在第二个你用self.dna[i]=... 修改self.dna,而不是重新分配self.dna=...,所以代码中任何其他具有旧self.dna 的区域都会有它们的在第二种情况下,副本也发生了变化。您可以通过在第二个示例中的 for 循环之前插入 self.dna = self.dna.copy() 来解决此问题。

【讨论】:

  • 谢谢彼得,您对算法为何不同的直觉是正确的,并解决了我的问题。我的第一种方法也是使用向量,但与 dicts 相比,我看到了速度损失,因为我的适应度函数基本上是带有查找的列表理解。我想我需要做一些工作来改用向量操作来重写它。
【解决方案2】:

复杂性是由于对 random 的多次调用:您正在为每个基因调用它。

您的第二个示例执行相同的操作,但这次它们都在同一个函数中调用。

大幅提高性能的一种方法是减少随机调用的数量。为此,您可以使用一些数学方法预先知道基因组将接收多少突变,公式如下

P(L, n, p) # probability of modifying n-times a genome of size L with a succes p (here p is 1/L)
P(L, n, p) = binomial(n, L) * p**n * (1-p)**(L-n)

如果您对数学不太熟悉,这里有一个 Python 函数可以为您完成:

def binomial(n, k):
    if 0 <= k <= n:
        ntok = ktok = 1
        for t in range(1, min(k, n - k) + 1):
            ntok *= n; ktok *= t; n -= 1
        return ntok // ktok
    else: return 0

def P(L, n, p): return binomial(L, n) * p**n * (1-p)**(L-n)

现在您可以预先计算并将其保存在列表中:

proba = [P(L, i, 1/L) for i in range(0, L+1)]

另外我会建议对它进行部分求和,以便于随机使用

probaS = [sum(proba[:k]) for k in range(0, L+1)] + [1]

现在您只能生成一个随机数,您将直接知道该基因组需要多少突变:

r = random()
i = 0
while r > probaS[i]: i += 1

在循环结束时,i-1 会告诉你需要多少突变。

现在您只需随机选择基因组的 i-1 个不同部分即可!您从 L 次随机调用减少到平均只有 2 或 3 次。

在我进行的基本测试中,L=50 和 100,000 个基因组的时间复杂度从 5.74 秒变为 196 毫秒,因此快了大约 30 倍。

我的回答有点技术性,如果不清楚,请随时提问。

【讨论】:

  • 感谢您的回复@Philippe,这大大加快了计算速度。但是,我仍然存在这样的问题,即这会以某种方式影响解决方案的适用性。你知道为什么会发生这种情况吗?
  • 好吧,除非您使用特定的幸运种子,否则我不明白为什么会有不同。您是否多次运行它并准确地获得了较低的性能?另外,我没有包括在内,但您应该确保您生成的基因组的 (i-1) 索引是不同的
  • 正如彼得在回答中所评论的那样,这个问题与 DNA 的变化有关,因为它已被原地改变,所以没有传播。
【解决方案3】:

在第一个示例中,您的基因突变可能超过 num_mutations 次。虽然您的第一种方法平均具有 num_mutations 突变,但有时它更大的事实可以通过您的交叉功能加以利用,这可能会将更多样化的基因贡献到池中。理想情况下,您的适应度函数能够丢弃不良排列,同时利用可能更多样化的候选者,从而使池多样化。

此外,分块变异往往会产生更快但更差的结果。因为突变在整个基因中的均质化并不顺利,所以某个块可能会发生突变,从而使池适应度出现偏差,并且可能会被不成比例地挑选出来。因为突变不是同质化的,你可能会错过通过突变可能对适应度没有太大影响的其他基因所做的工作。如果您这样做,这是使用指数适应度函数的最大缺陷之一,因为这将使这种差异成指数。如果您要对基因的随机选择进行均质化,那么这个可能更适合的基因将对解决方案空间搜索做出更加多样化的贡献。有几种方法可以轻松解决这个问题,例如,如果种群中有足够的基因,则强制最适合的解决方案与其他池成员进行繁殖而不进行基因替换。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-12
    • 2012-09-21
    • 2018-01-24
    相关资源
    最近更新 更多