我正在尝试为我的遗传算法优化代码。 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适应性降低?这些方法的结果有何不同?
谢谢您的帮助。
答案 0 :(得分:1)
复杂性是由于多次调用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.74s缩短到196ms,因此快了30倍。
我的回答有点技术性,请随时询问是否不清楚。
答案 1 :(得分: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
修改self.dna[i]=...
,而不是重新分配self.dna=...
,因此代码中的任何其他区域都具有旧的在第二种情况下,self.dna
也将更改其副本。您可以通过在第二个示例的for循环之前插入self.dna = self.dna.copy()
来解决此问题。
答案 2 :(得分:0)
在第一个示例中,您可能使基因变异超过 num_mutations 次。虽然您的第一种方法平均有 num_mutations 突变,但它有时更大的事实可以被您的交叉函数利用,这可能会将更多不同的基因贡献到池中。理想情况下,您的适应度函数能够丢弃不良排列,同时利用可能使池多样化的更多样化的候选者。
此外,分块变异往往会产生更快但更糟糕的结果。因为突变在整个基因中没有那么平滑地同质化,那么某个块可能会发生突变,从而使池适应度发生偏差,并可能被不成比例地挑选出来。由于突变未均质化,因此您可能会错过通过对其他可能对适应度没有太大影响的基因进行突变而完成的工作。这是使用指数适应度函数的最大陷阱之一,如果你这样做,因为这将使这种差异指数化。如果您要对基因的随机选择进行同质化,那么这个可能更适合的基因将对解决方案空间搜索产生更加多样化的贡献。有几种方法可以轻松解决这个问题,例如,如果种群中有足够的基因,则强制最适合的解决方案与其他池成员进行繁殖,而无需进行基因替换。