如何修复简单遗传算法中的早熟收敛?
昨天我开始研究遗传算法,学了一些基本理论后,我尝试用Python写一个简单的遗传算法,来解决丢番图方程。我对Python和遗传算法都很陌生,所以请不要对我的代码过于苛刻。
问题
我没有得到任何结果,因为出现了过早收敛的问题(有一个无法返回的点,population[n] == population[n+i],其中i是任何整数。即使是随机变异的元素也无法改变这一点,种群的质量下降得非常快)。
遗传算法使用交叉来繁殖,并且采用加权选择父母。
- 问题1:我的代码(见下文)中有没有设计上的错误?
- 问题1.2:我需要加入精英策略吗?
- 问题1.3:我需要改变繁殖逻辑吗?
- 问题2:真的需要深拷贝吗?
代码:
# -*- coding: utf-8 -*-
from random import randint
from copy import deepcopy
from math import floor
import random
class Organism:
#initiate
def __init__(self, alleles, fitness, likelihood):
self.alleles = alleles
self.fitness = fitness
self.likelihood = likelihood
self.result = 0
def __unicode__(self):
return '%s [%s - %s]' % (self.alleles, self.fitness, self.likelihood)
class CDiophantine:
def __init__(self, coefficients, result):
self.coefficients = coefficients
self.result = result
maxPopulation = 40
organisms = []
def GetGene (self,i):
return self.organisms[i]
def OrganismFitness (self,gene):
gene.result = 0
for i in range (0, len(self.coefficients)):
gene.result += self.coefficients[i]*gene.alleles[i]
gene.fitness = abs(gene.result - self.result)
return gene.fitness
def Fitness (self):
for organism in self.organisms:
organism.fitness = self.OrganismFitness(organism)
if organism.fitness == 0:
return organism
return None
def MultiplyFitness (self):
coefficientSum = 0
for organism in self.organisms:
coefficientSum += 1/float(organism.fitness)
return coefficientSum
def GenerateLikelihoods (self):
last = 0
multiplyFitness = self.MultiplyFitness()
for organism in self.organisms:
last = ((1/float(organism.fitness)/multiplyFitness)*100)
#print '1/%s/%s*100 - %s' % (organism.fitness, multiplyFitness, last)
organism.likelihood = last
def Breed (self, parentOne, parentTwo):
crossover = randint (1,len(self.coefficients)-1)
child = deepcopy(parentOne)
initial = 0
final = len(parentOne.alleles) - 1
if randint (1,100) < 50:
father = parentOne
mother = parentTwo
else:
father = parentTwo
mother = parentOne
child.alleles = mother.alleles[:crossover] + father.alleles[crossover:]
if randint (1,100) < 5:
for i in range(initial,final):
child.alleles[i] = randint (0,self.result)
return child
def CreateNewOrganisms (self):
#generating new population
tempPopulation = []
for _ in self.organisms:
iterations = 0
father = deepcopy(self.organisms[0])
mother = deepcopy(self.organisms[1])
while father.alleles == mother.alleles:
father = self.WeightedChoice()
mother = self.WeightedChoice()
iterations+=1
if iterations > 35:
break
kid = self.Breed(father,mother)
tempPopulation.append(kid)
self.organisms = tempPopulation
def WeightedChoice (self):
list = []
for organism in self.organisms:
list.append((organism.likelihood,organism))
list = sorted((random.random() * x[0], x[1]) for x in list)
return list[-1][1]
def AverageFitness (self):
sum = 0
for organism in self.organisms:
sum += organism.fitness
return float(sum)/len(self.organisms)
def AverageLikelihoods (self):
sum = 0
for organism in self.organisms:
sum += organism.likelihood
return sum/len(self.organisms)
def Solve (self):
solution = None
for i in range(0,self.maxPopulation):
alleles = []
#
for j in range(0, len(self.coefficients)):
alleles.append(randint(0, self.result))
self.organisms.append(Organism(alleles,0,0))
solution = self.Fitness()
if solution:
return solution.alleles
iterations = 0
while not solution and iterations <3000:
self.GenerateLikelihoods()
self.CreateNewOrganisms()
solution = self.Fitness()
if solution:
print 'SOLUTION FOUND IN %s ITERATIONS' % iterations
return solution.alleles
iterations += 1
return -1
if __name__ == "__main__":
diophantine = CDiophantine ([1,2,3,4],30)
#cProfile.run('diophantine.Solve()')
print diophantine.Solve()
我尝试改变繁殖和加权随机选择的逻辑,但没有得到任何结果。这个遗传算法应该是有效的,我不知道哪里出了问题。我知道Python上有一些遗传算法的库,我现在正在尝试理解它们——对我来说似乎有点复杂。抱歉我的错误,英语不是我的母语。感谢你的理解。
更新:将染色体存储为格雷码,而不是整数。
1 个回答
3
有一点小逻辑错误:parentTwo当父亲的可能性稍微高于当母亲的可能性。公平的概率应该是用randint (1,100) <= 50,而不是randint (1,100) < 50。这并不是导致你问题的原因。
- 你的人口数量相对较小。40这个数字对于大多数问题来说是非常少的。这会导致它很快收敛。
- 精英策略会让你的人口更快收敛,而不是更慢。
- 你的WeightedChoice函数看起来效率不高,如果我理解得没错的话。我最近没有用Python,所以不太清楚里面的具体情况,但看起来确实有点低效。如果你能改进这个部分,处理速度会加快,这样你就可以增加人口数量(而且,考虑到你的算法可能至少是O(n^2),这会非常重要)。
由于人口数量这么小,200到300代就解决问题并不奇怪。如果你增加人口数量,所需的代数应该会减少。
注意:我找到了一些几年前写的旧代码,用于解决类似的问题。它是用C语言写的,使用了锦标赛选择,也许能给你一些灵感:
/*Diophantine equation solving genetic algorithm
Copyright (C) 2009- by Joel Rein
Licensed under the terms of the MIT License*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define POP 100
//number of variables to solve for
#define VAR 4
//maximum value for a) result and b) variables
#define MAX 100
#define MAX_GENS 500
//probability of crossover (otherwise just one parent will be used)
#define CROSSOVER 0.7
//probability of mutation (per gene)
#define MUTATION 0.4
//print out debug information each generation (recommended: if used, keep RUNS low)
#define DEBUG
//print result of each run individually
#define PRINT_RESULT
//how many times to run the GA
#define RUNS 1
int pop[POP][VAR], scores[POP], new[POP][VAR];
int coefficients[VAR];
int result=0;
int score(int index){
int sum=0;
for(int i=0;i<VAR;i++)
sum+=coefficients[i]*pop[index][i];
return abs(sum-result);
}
int tournament(int size){
int best=rand()%POP;
for(int i=1;i<size;i++){
int comp=rand()%POP;
if(scores[comp]<scores[best])
best=comp;
}
return best;
}
void breed(int target){
int a=tournament(3), b=tournament(3);
//copy a
for(int i=0;i<VAR;i++)
new[target][i]=pop[a][i];
//crossover
if((float)rand()/RAND_MAX<CROSSOVER){
int x=rand()%VAR;
for(int i=x;i<VAR;i++)
new[target][i]=pop[b][i];
}
//mutation
for(int i=0;i<VAR;i++)
if((float)rand()/RAND_MAX<MUTATION)
new[target][i]=rand()%(result*2)-result;
}
void debug(int gen, int best){
#ifdef DEBUG
printf("Gen: %3i Score: %3i --- ", gen, scores[best]);
int sum=0;
for(int i=0;i<VAR;i++){
sum+=coefficients[i]*pop[best][i];
printf("%3i*%3i+", coefficients[i], pop[best][i]);
}
printf("0= %3i (target: %i)\n", sum, result);
#endif
}
int ga(int run){
srand(time(NULL)+run);
//calculate a result for the equation.
//this mustn't be 0, else we get division-by-zero errors while initialising & mutating.
while(!result)
result=rand()%MAX;
for(int i=0;i<VAR;i++)
coefficients[i]=rand()%result;
//initialise population
for(int i=0;i<POP;i++)
for(int j=0;j<VAR;j++)
pop[i][j]=rand()%(result*2)-result;
//main loop
int gen, best;
for(gen=0;gen<MAX_GENS;gen++){
best=0;
//evaluate population
for(int i=0;i<POP;i++){
scores[i]=score(i);
if(scores[i]<scores[best])
best=i;
}
debug(gen, best);
if(scores[best]==0)
break;
//breed and replace
for(int i=0;i<POP;i++)
breed(i);
for(int i=0;i<POP;i++)
for(int j=0;j<VAR;j++)
pop[i][j]=new[i][j];
}
#ifdef PRINT_RESULT
printf("Terminated after %4i generations with a score of %3i\n", gen, scores[best]);
#else
printf(".");
#endif
return gen;
}
int main(){
int total=0;
for(int i=0;i<RUNS;i++)
total+=ga(i);
printf("\nAverage runtime: %i generations\n", total/RUNS);
}