我在Python中编写一个遗传算法,但我的操作符(MMX)在处理拥有300万权重的个体时(每个个体是一个包含3,000,000个元素的列表)执行时间过长(10秒)。
这是操作符的代码:
def calc_gen(maxel, minel, rec1, rec2, phiC): g = maxel - minel phi = 0 if g > phiC: # Recta 2 phi = rec2[0] * g + rec2[1] elif g < phiC: # Recta 1 phi = rec1[0] * g + rec1[1] #确保不超出范围: maxv = min(1, maxel - phi) minv = max(0, minel + phi) gen1 = random.uniform(minv, maxv) # 保存第一个子代的基因 # 如果C是中心,A是我们已有的元素,B是A的对称点:C - A + C = B -> 2C - A = B # C = (maxv + minv) / 2; 2C - A = B -> maxv + minv - A = B # center = (maxv + minv) / 2 gen2 = maxv + minv - gen1 return gen1, gen2 #return gen1, maxv + minv - gen1def cxMMX(poblacion, rec1, rec2, phiC): start = timer() # 计算整个种群中每个基因的最大值和最小值 max_genes = numpy.amax(poblacion, axis=0).tolist() min_genes = numpy.amin(poblacion, axis=0).tolist() gis = timer() hijo1 = Individual() hijo2 = Individual() # 同时迭代两个列表(zip)并使用其索引(enumerate)。这样我们可以在一个循环中同时创建子代 for i, (maxel, minel) in enumerate(zip(max_genes, min_genes)): gen1, gen2 = calc_gen(maxel, minel, rec1, rec2, phiC) hijo1.append(gen1) hijo2.append(gen2) end = timer() #print("Tiempo Gi: %f Tiempo init: %f Tiempo calc gen: %f Tiempo mate total: %f" % (gis-start, init-gis, end-init, end-start)) return [hijo1, hijo2]
rec1, rec2和phiC是决定如何进行交叉的参数,你不需要关心它们。它们在整个算法中都保持相同的值。
poblacion是一个列表的列表,假设它的形状是[7,3000000]。Individual()是一个自定义类。它基本上是继承了“list”并添加了一些属性来存储适应度值。
分别使用numpy.amax和numpy.amin似乎是多余的工作。另外,可能有更Pythonic的方式来处理“calc_gen()”循环。
附注:“gen1”依赖于“gen2”:gen1在某个范围内随机获得,然后gen2通过寻找对称点获得。
附注2:关于MMX操作符的更详细解释可以在原始论文中找到,不过,你可以假设代码是正确的并且完成了它应该做的工作。DOI是https://doi.org/10.1007/3-540-44522-6_73
附注:enumerate()和i是从旧代码中遗留下来的,忘记删除了!
编辑:使用Dillon Davis的解决方案,时间减少了20%。这是一个非常干净的解决方案,可以与任何自定义列表构建函数一起使用,前提是你通过执行一个函数来获取列表的每个值:
def calc_gen_v2(maxel,minel, rec1m, rec1b, rec2m, rec2b, phiC): g = maxel - minel phi = 0 if g > phiC: # Recta 2 phi = rec2m * g + rec2b elif g < phiC: # Recta 1 phi = rec1m * g + rec1b #确保不超出范围: maxv = min(1, maxel - phi) minv = max(0, minel + phi) gen1 = random.uniform(minv, maxv) # 保存第一个子代的基因 # 如果C是中心,A是我们已有的元素,B是A的对称点:C - A + C = B -> 2C - A = B # C = (maxv + minv) / 2; 2C - A = B -> maxv + minv - A = B # center = (maxv + minv) / 2 gen2 = maxv + minv - gen1 return gen1, gen2def cxMMX_v3(poblacion, rec1, rec2, phiC): start = timer() # 计算整个种群中每个基因的最大值和最小值 max_genes = numpy.amax(poblacion, axis=0) min_genes = numpy.amin(poblacion, axis=0) gis = timer() hijo1, hijo2 = map(Individual, numpy.vectorize(calc_gen_v2)(max_genes, min_genes, rec1[0], rec1[1], rec2[0], rec2[1], phiC)) end = timer() #print("Tiempo Gi: %f Tiempo init: %f Tiempo calc gen: %f Tiempo mate total: %f" % (gis-start, init-gis, end-init, end-start)) return [hijo1, hijo2]
编辑2:如Dillon Davis所建议,我使用纯numpy实现了它,将时间减少到3.5秒!(节省了65%的时间)
def cxMMX_numpy(poblacion, rec1, rec2, phiC): # 计算种群中每个基因的最大值和最小值 max_genes = numpy.amax(poblacion, axis=0) min_genes = numpy.amin(poblacion, axis=0) g_pop = numpy.subtract(max_genes, min_genes) phi_pop = numpy.where(g_pop < phiC, numpy.multiply(g_pop, rec1[0]) + rec1[1], numpy.where(g_pop > phiC, numpy.multiply(g_pop, rec2[0]) + rec2[1], 0)) maxv = numpy.minimum(numpy.subtract(max_genes, phi_pop), 1) minv = numpy.maximum(numpy.sum([min_genes, phi_pop], axis=0), 0) hijo1 = numpy.random.uniform(low=minv, high=maxv, size=minv.size) hijo2 = numpy.subtract(numpy.sum([maxv, minv], axis=0), hijo1) return [Individual(hijo1), Individual(hijo2)]
注意:如果你想重用,Individual继承自list
注意:如果g=phiC,那么rec1[0] * g_pop + rec1[1]=0,总是如此,rec1[0]和rec1[1]保证了这一点!所以也许最好做数学运算而不是三重选项?
回答:
尝试用以下内容替换cxMMX()
中的for循环:
hijo1, hijo2 = map(Individual, numpy.vectorize(calc_gen)(max_genes, min_genes, rec1, rec2, phiC))
并从你的numpy.amin()
和numpy.amax()
中删除.tolist()
。
这将使你的calc_gen函数向量化,避免使用zip和多次.append()
调用的函数开销,总体上应该会快很多。
编辑:
还可以考虑将calc_gen()
直接转换为在numpy数组上工作。用numpy.random.uniform()
替换random.uniform()
的调用,用numpy.minimum()
或numpy.maximum()
替换min()
或max()
,然后完全消除for循环/映射+向量化。这最终将是最快的选项。