evolutionary algorithm
This commit is contained in:
parent
0d933e50e7
commit
c62e9fc2d8
123
evolutionary_algorithm/ea.py
Normal file
123
evolutionary_algorithm/ea.py
Normal file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
class Individual(object):
|
||||
def __init__(self, fitnessFunction, mutationFunction, inheritanceFunction, parents, initialGenome=None):
|
||||
'''
|
||||
|
||||
:param fitnessFunction: lambda with fitness function f:R^L → R
|
||||
:param mutationFunction: lambda with mutation function m:R^L → R^L
|
||||
:param parents: list of individuals used as parents
|
||||
:param initialGenome: if List of parents is None, this genome is used for initialization
|
||||
'''
|
||||
|
||||
|
||||
self.genome = []
|
||||
self.fitnessFunction = fitnessFunction
|
||||
self.parents = parents
|
||||
self.mutationFunction = mutationFunction
|
||||
self.inheritanceFunction = inheritanceFunction
|
||||
|
||||
|
||||
if parents is None:
|
||||
self.genome = initialGenome
|
||||
else:
|
||||
self.inheritate()
|
||||
|
||||
def inheritate(self):
|
||||
'''
|
||||
only one parent, just copy genome
|
||||
:return: None
|
||||
'''
|
||||
|
||||
self.genome = self.parents[0].genome
|
||||
|
||||
def mutate(self):
|
||||
'''
|
||||
juast apply the given mutation function to out genom
|
||||
:return:
|
||||
'''
|
||||
self.genome = self.mutationFunction(self.genome)
|
||||
|
||||
def evaluateFitness(self):
|
||||
'''
|
||||
apply fitness function for fintness evaluation
|
||||
:return: fitness value
|
||||
'''
|
||||
return self.fitnessFunction(self.genome)
|
||||
|
||||
class EvolutionaryPopulation(object):
|
||||
def __init__(self, # dummy default values:
|
||||
L = 3, # genome of length 3
|
||||
offspringSize = 2, # offspring's size
|
||||
fitnessFunction=lambda genome: 0, # dummy fitness function
|
||||
inheritanceFunction=lambda parents: parents[0].genome, # copy genome from first parent
|
||||
mutationFunction=lambda genome: genome, # no mutation
|
||||
externalSelectionFunction=lambda fitness: list(range(len(fitness))), # keep whole population
|
||||
parentSelectionFunction=lambda population, fitness: list(range(len(population))) # all individuals are parents
|
||||
):
|
||||
self.L = 3
|
||||
self.fitnessFunction = fitnessFunction
|
||||
self.inheritanceFunction = inheritanceFunction
|
||||
self.mutationFunction = mutationFunction
|
||||
self.externalSelectionFunction = externalSelectionFunction
|
||||
self.parentSelectionFunction = parentSelectionFunction
|
||||
self.offspringSize = offspringSize
|
||||
|
||||
self.population = []
|
||||
self.fitness = []
|
||||
|
||||
self.generation = 0
|
||||
|
||||
def addIndividual(self, genome):
|
||||
self.population.append(Individual(self.fitnessFunction, self.mutationFunction, self.inheritanceFunction, None, genome))
|
||||
|
||||
def evaluateFitness(self):
|
||||
self.fitness = []
|
||||
for individual in self.population:
|
||||
self.fitness.append(individual.evaluateFitness())
|
||||
|
||||
def externalSelection(self):
|
||||
|
||||
# externalSelectionFunction returns indices of individuals to keep:
|
||||
toKeep = self.externalSelectionFunction(self.fitness)
|
||||
|
||||
oldPopulation = self.population
|
||||
self.population = []
|
||||
for i in range(len(oldPopulation)):
|
||||
if i in toKeep:
|
||||
self.population.append(oldPopulation[i])
|
||||
|
||||
def generateOffspring(self):
|
||||
self.evaluateFitness()
|
||||
for i in range(self.offspringSize):
|
||||
parentIndices = self.parentSelectionFunction(self.population, self.fitness)
|
||||
parents = []
|
||||
for pI in parentIndices:
|
||||
parents.append(self.population[pI])
|
||||
newIndividual = Individual(self.fitnessFunction, self.mutationFunction, self.inheritanceFunction, parents)
|
||||
newIndividual.mutate()
|
||||
self.population.append(newIndividual)
|
||||
self.fitness.append(newIndividual.evaluateFitness())
|
||||
|
||||
def printPopulation(self):
|
||||
# printPopulation sorted by fitness:
|
||||
# (use a dictionary for easy sorting)
|
||||
print("\nGeneration " + str(self.generation) + ":")
|
||||
d = {}
|
||||
for i in range(len(self.population)):
|
||||
d[self.fitness[i]] = self.population[i].genome
|
||||
for key in d.keys():
|
||||
print("fitness: " + str(key) + ", \t\tgenome: " + str(d[key]))
|
||||
|
||||
def performCycle(self, numCycles = 1):
|
||||
if self.generation == 0:
|
||||
self.externalSelection()
|
||||
|
||||
for i in range(numCycles):
|
||||
self.generateOffspring()
|
||||
self.printPopulation()
|
||||
self.externalSelection()
|
||||
self.generation += 1
|
||||
|
||||
|
||||
|
134
evolutionary_algorithm/main.py
Executable file
134
evolutionary_algorithm/main.py
Executable file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import parser
|
||||
import random
|
||||
import numpy as np
|
||||
import ea
|
||||
|
||||
def parsingArguments():
|
||||
# parsing args:
|
||||
parser = argparse.ArgumentParser(description="evolutionary algorithm simulation", formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument('--offspringSize', dest='offspringSize', default = 1 , help='size for new offspring')
|
||||
parser.add_argument('--P', dest='P', default = 2, help = 'start population size')
|
||||
parser.add_argument('--L', dest='L', default=3, help = 'genome length')
|
||||
parser.add_argument('--f', dest='f', default="- (x[0] - 5)**2 + 10", help='fitness function in python syntax. x[0] - x[L] are the arguments')
|
||||
parser.add_argument('--epsilon', dest='epsilon', default=0.25, help='epsilon for random mutation')
|
||||
parser.add_argument('--cycles', dest='cycles', default=100, help='cycles to calculate')
|
||||
|
||||
if (len(sys.argv) == 1):
|
||||
|
||||
# no parameters given. Print help and ask user at runtime for options:
|
||||
|
||||
settings = {}
|
||||
settings["offspringSize"] = 1
|
||||
settings["P"] = 2
|
||||
settings["L"] = 1
|
||||
settings["f"] = "- (x[0] - 5)**2 + 10"
|
||||
settings["epsilon"] = 0.25
|
||||
settings["cycles"] = 100
|
||||
|
||||
|
||||
while True:
|
||||
parser.print_help()
|
||||
print("\ncurrent settings:")
|
||||
for key in settings.keys():
|
||||
print(str(key) + " = " + str(settings[key]))
|
||||
a = input("enter parameter to change. press enter to continue: ")
|
||||
if len(a) == 0:
|
||||
break
|
||||
val = input("enter new value: ")
|
||||
settings[a] = val
|
||||
# passing settings to parser:
|
||||
parser.set_defaults(offspringSize=settings["offspringSize"])
|
||||
parser.set_defaults(P=settings["P"])
|
||||
parser.set_defaults(L=settings["L"])
|
||||
parser.set_defaults(f=settings["f"])
|
||||
parser.set_defaults(epsilon=settings["epsilon"])
|
||||
parser.set_defaults(cycles=settings["cycles"])
|
||||
return parser.parse_args()
|
||||
|
||||
# easy adjustable functions for the ea-cycle. Will be packed in lambda objects in main()
|
||||
def inheritance(parents):
|
||||
|
||||
# just copy genome from first parent:
|
||||
return parents[0].genome
|
||||
|
||||
def mutation(genome, e):
|
||||
|
||||
|
||||
|
||||
# mutate new genome by equally distributed random value in range [-e:e]
|
||||
newGenome = []
|
||||
|
||||
for i in range(len(genome)):
|
||||
newGenome.append(genome[i] + random.random() * 2 * e - e)
|
||||
|
||||
return newGenome
|
||||
|
||||
def externalSelection(fitness):
|
||||
|
||||
# only keep the fittest
|
||||
return [np.argmax(fitness)]
|
||||
|
||||
def parentSelection(population, fitness):
|
||||
|
||||
|
||||
# only first (and only individual so far) is parent
|
||||
return [0]
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# at first, get arguments
|
||||
args = parsingArguments()
|
||||
offspringSize = int(args.offspringSize)
|
||||
P = int(args.P)
|
||||
L = int(args.L)
|
||||
f = args.f
|
||||
epsilon = float(args.epsilon)
|
||||
cycles = int(args.cycles)
|
||||
|
||||
# parse and compile fitness function to evaluateable code
|
||||
functionCode = parser.expr(f).compile()
|
||||
|
||||
# build easy adjustable lambda functions for each step of the EA-cycle:
|
||||
|
||||
# fitness function:
|
||||
fitnessFunctionLambda = lambda x: eval(functionCode)
|
||||
|
||||
# inheritance function: just copy genome from first parent
|
||||
inheritanceFunctionLambda = lambda parents: inheritance(parents)
|
||||
|
||||
# mutation function:
|
||||
mutationFunctionLambda = lambda genome: mutation(genome, epsilon)
|
||||
|
||||
# external selection:
|
||||
externalSelectionLambda = lambda fitness: externalSelection(fitness)
|
||||
|
||||
# parentSelection:
|
||||
parentSelectionLambda = lambda population, fitness: parentSelection(population, fitness)
|
||||
|
||||
ep = ea.EvolutionaryPopulation(L,
|
||||
offspringSize,
|
||||
fitnessFunctionLambda,
|
||||
inheritanceFunctionLambda,
|
||||
mutationFunctionLambda,
|
||||
externalSelectionLambda,
|
||||
parentSelectionLambda)
|
||||
|
||||
#start with random population:
|
||||
for i in range(P):
|
||||
genome = []
|
||||
for i in range(L):
|
||||
genome.append(random.random() * 10 - 5) # random value in range [-5:5]. TODO: make adjustable
|
||||
ep.addIndividual(genome)
|
||||
|
||||
ep.evaluateFitness() # called manually, because population is modified
|
||||
ep.printPopulation()
|
||||
ep.performCycle(cycles)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
Loading…
Reference in New Issue
Block a user