From c62e9fc2d85f0840e3d0898628fba8e0757886f5 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 25 Jun 2017 18:11:00 +0200 Subject: [PATCH] evolutionary algorithm --- evolutionary_algorithm/ea.py | 123 ++++++++++++++++++++++++++++++ evolutionary_algorithm/main.py | 134 +++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 evolutionary_algorithm/ea.py create mode 100755 evolutionary_algorithm/main.py diff --git a/evolutionary_algorithm/ea.py b/evolutionary_algorithm/ea.py new file mode 100644 index 0000000..fff0d5c --- /dev/null +++ b/evolutionary_algorithm/ea.py @@ -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 + + + diff --git a/evolutionary_algorithm/main.py b/evolutionary_algorithm/main.py new file mode 100755 index 0000000..b0a9cc9 --- /dev/null +++ b/evolutionary_algorithm/main.py @@ -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() +