From ce7d34f908ef24c5f83ff8d7681d1fcc85218a1d Mon Sep 17 00:00:00 2001 From: Emma Gordon Date: Wed, 19 Aug 2015 20:59:01 +0100 Subject: [PATCH 1/2] Function evolution in python --- 05-genetic-algorithms/python/ga.py | 89 ---------- .../python/ga/function_evolution.py | 167 ++++++++++++++++++ 05-genetic-algorithms/python/ga/ga.py | 77 ++++++++ .../python/ga/string_evolution.py | 59 +++++++ 4 files changed, 303 insertions(+), 89 deletions(-) delete mode 100644 05-genetic-algorithms/python/ga.py create mode 100644 05-genetic-algorithms/python/ga/function_evolution.py create mode 100644 05-genetic-algorithms/python/ga/ga.py create mode 100644 05-genetic-algorithms/python/ga/string_evolution.py diff --git a/05-genetic-algorithms/python/ga.py b/05-genetic-algorithms/python/ga.py deleted file mode 100644 index b7bc10e..0000000 --- a/05-genetic-algorithms/python/ga.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python -import random -import string -import sys -from collections import namedtuple - -TARGET="I LOVE GENETIC ALGORITHMS" -MUTATION_RATE = 0.75 -Candidate = namedtuple("Candidate", "string fitness") -Population = namedtuple("Population", "average min max size best") - -def generate_ran_string(): - return "".join([random.choice(string.ascii_letters + " ") for i in xrange(random.randint(1, 100))]) - -def generate_population(): - candidates = [] - for i in xrange(500): - string = generate_ran_string() - fitness = calculate_fitness(string) - candidate = Candidate(string=string, fitness=fitness) - candidates.append(candidate) - return candidates - -def calculate_fitness(string): - fitness = 0 - for (a,b) in zip(string, TARGET): - if a==b: - fitness +=1 - diff_length = abs(len(string) - len(TARGET)) - fitness -= (diff_length*1.1) - return fitness - -def calc_population_stats(pop): - pop = sorted(pop, key = lambda x: -x.fitness) - fitnesses = [cand.fitness for cand in pop] - average = sum(fitnesses) / len(pop) - min_fit = min(fitnesses) - max_fit = max(fitnesses) - length = len(fitnesses) - best = pop[0] - population = Population(average=average, min=min_fit, max=max_fit, size=length, best=best.string) - return population - -def select_candidates(pop): - ordered_pop = sorted(pop, key=lambda x: x.fitness) - return ordered_pop[len(ordered_pop)/2:] - -def breed_population(candidates): - shuffled_cands = sorted(candidates, key=lambda x: random.randint(1,100)) - pairs = zip(candidates, shuffled_cands) - cands = [] - for pa, pb in pairs: - c= breed_candidates(pa, pb) - cands.extend([pa, c]) - return cands - -def breed_candidates(candA, candB): - (a1, a2) = split_cand(candA) - (b1, b2) = split_cand(candB) - string = mutate_child(a1 + b2) - child = Candidate(string=string, fitness=calculate_fitness(string)) - return child - -def split_cand(cand): - return (cand.string[:len(cand.string)/2], cand.string[len(cand.string)/2:]) - -def mutate_child(dna): - new_dna = dna - if random.random() < MUTATION_RATE: - cut_point = random.randint(0, len(dna)) - new_dna = dna[:cut_point-1] + random.choice(string.ascii_letters + " ") + dna[cut_point:] - return new_dna - -def main(): - candidates = generate_population() - num_iter = 0 - while True: - print calc_population_stats(candidates) - for cand in candidates: - if cand.fitness == calculate_fitness(TARGET): - print cand.string - print num_iter - sys.exit(0) - candidates = select_candidates(candidates) - candidates = breed_population(candidates) - num_iter += 1 - -if __name__ == "__main__": - main() diff --git a/05-genetic-algorithms/python/ga/function_evolution.py b/05-genetic-algorithms/python/ga/function_evolution.py new file mode 100644 index 0000000..2e2aee7 --- /dev/null +++ b/05-genetic-algorithms/python/ga/function_evolution.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python + +import copy +import ga +import operator +import random +import sys + + +INBREEDING_RATE = 0.25 +MAX_TREE_DEPTH = 25 + + +def target(x): + return 3*x - 5 + + +class RandomOperator(object): + def __init__(self): + self.function, self._str = random.choice([(operator.add, "+"), + (operator.mul, "*"), + (operator.sub, "-"), + (operator.div, "/")]) + + def __call__(self, *args, **kwargs): + return self.function(*args, **kwargs) + + def __repr__(self): + return self._str + + +class Oper(object): + def __init__(self, depth): + self.depth = depth + self.oper = RandomOperator() + self.children = [generate_tree(depth+1), generate_tree(depth+1)] + + def __call__(self, x_val): + return self.oper(self.children[0](x_val), self.children[1](x_val)) + + def __repr__(self): + return "("+str(self.children[0])+str(self.oper)+str(self.children[1])+")" + + +class Leaf(object): + def __init__(self, depth): + self.oper = None + self.depth = depth + self.value = _choose_terminal() + + def __call__(self, x_val): + return self.value if self.value is not "x" else x_val + + def __repr__(self): + return str(self.value) + + +def _choose_terminal(): + return random.choice(["x", random.randint(-100, 100)]) + + +def _is_leaf(tree): + if hasattr(tree, "children"): + return False + else: + return True + + +def generate_tree(depth=0): + if depth < MAX_TREE_DEPTH: + return random.choice([Oper, Leaf])(depth) + else: + return Leaf(depth) + + +def combine_trees(tree1, tree2): + tree1_copy = copy.deepcopy(tree1) + tree2_copy = copy.deepcopy(tree2) + + if not _is_leaf(tree1): + if not _is_leaf(tree2): + tree1_copy.children[1] = tree2_copy.children[0] + else: + tree1_copy.children[1] = tree2_copy + + child = tree1_copy + else: + if not _is_leaf(tree2): + tree2_copy.children[1] = tree1_copy + else: + tree2_copy.value = _choose_terminal() + + child = tree2_copy + + return child + + +def choose_random_tree_element(tree): + depth = random.randint(0, MAX_TREE_DEPTH) + + tree_element = tree + for i in xrange(depth): + if not _is_leaf(tree_element): + child = random.choice([0, 1]) + tree_element = tree_element.children[child] + else: + break + + return tree_element + + +def mutate(tree): + tree_element = choose_random_tree_element(tree) + + if _is_leaf(tree_element): + tree_element.value = _choose_terminal() + else: + tree_element.oper = RandomOperator() + + return tree + + +def avoid_inbreeding(): + if random.random() < INBREEDING_RATE: + return False + else: + return True + + +def breed(parent1, parent2): + if avoid_inbreeding(): + new_blood = generate_tree() + child = combine_trees(parent1, new_blood) + else: + child = combine_trees(parent1, parent2) + + child = mutate(child) + + return child + + +def calc_fitness(func): + try: + x_vals = xrange(-100, 100, 1) + reference_vals = map(target, x_vals) + tested_vals = map(func, x_vals) + differences = [(r - t) for (r, t) in zip(reference_vals, tested_vals)] + sum_of_squares = sum([a*a for a in differences]) + return -sum_of_squares + + except ZeroDivisionError: + return -sys.maxint + + +def stop_condition(candidate): + if candidate.fitness == 0: + return True + else: + return False + + +if __name__ == "__main__": + ga.run_genetic_algorithm(spawn_func=generate_tree, + breed_func=breed, + fitness_func=calc_fitness, + stop_condition=stop_condition, + population_size=100) diff --git a/05-genetic-algorithms/python/ga/ga.py b/05-genetic-algorithms/python/ga/ga.py new file mode 100644 index 0000000..47477f4 --- /dev/null +++ b/05-genetic-algorithms/python/ga/ga.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +import sys +import random +import time +from collections import namedtuple + +Candidate = namedtuple("Candidate", "this fitness") +Population = namedtuple("Population", "average min max size best") + + +def generate_population(generator_func, fitness_func, population_size=100): + candidates = [] + for i in xrange(population_size): + candidate = generator_func() + fitness = fitness_func(candidate) + candidate = Candidate(this=candidate, fitness=fitness) + candidates.append(candidate) + return candidates + + +def calculate_population_stats(population): + population = sorted(population, key=lambda x: -x.fitness) + population_size = len(population) + fitnesses = [candidate.fitness for candidate in population] + average_fitness = sum(fitnesses) / population_size + min_fitness = min(fitnesses) + max_fitness = max(fitnesses) + best_candidate = population[0] + population = Population(average=average_fitness, + min=min_fitness, + max=max_fitness, + size=population_size, + best=best_candidate.this) + return population + + +def select_candidates(population): + ordered_population = sorted(population, key=lambda x: x.fitness) + return ordered_population[len(ordered_population)/2:] + + +def breed_population(candidates, breed_func, fitness_func): + shuffled_candidates = sorted(candidates, + key=lambda x: random.randint(1, 100)) + pairs = zip(candidates, shuffled_candidates) + next_gen = [] + for parent1, parent2 in pairs: + child_dna = breed_func(parent1.this, parent2.this) + child_fitness = fitness_func(child_dna) + child = Candidate(this=child_dna, fitness=child_fitness) + next_gen.extend([parent1, child]) + return next_gen + + +def run_genetic_algorithm(spawn_func, + breed_func, + fitness_func, + stop_condition, + population_size=100): + start = time.time() + candidates = generate_population(spawn_func, + fitness_func, + population_size) + num_iter = 0 + while True: + print calculate_population_stats(candidates) + for candidate in candidates: + if stop_condition(candidate): + end = time.time() + print candidate + print "Number of Iterations: %d" % num_iter + print "Time Taken: %.1f seconds" % (end - start) + sys.exit(0) + candidates = select_candidates(candidates) + candidates = breed_population(candidates, breed_func, fitness_func) + num_iter += 1 diff --git a/05-genetic-algorithms/python/ga/string_evolution.py b/05-genetic-algorithms/python/ga/string_evolution.py new file mode 100644 index 0000000..ac211a6 --- /dev/null +++ b/05-genetic-algorithms/python/ga/string_evolution.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +import ga +import random +import string + + +TARGET = "I LOVE GENETIC ALGORITHMS" +MUTATION_RATE = 0.75 +MAX_STRING_LENGTH = 100 + + +def generate_random_string(): + return "".join([random.choice(string.ascii_letters + " ") + for i in xrange(random.randint(1, MAX_STRING_LENGTH))]) + + +def calculate_fitness(dna): + fitness = 0 + for (a, b) in zip(dna, TARGET): + if a == b: + fitness += 1 + diff_length = abs(len(dna) - len(TARGET)) + fitness -= (diff_length*1.1) + return fitness + + +def breed_strings(string1, string2): + (a1, a2) = split_string(string1) + (b1, b2) = split_string(string2) + child_dna = mutate(a1 + b2) + return child_dna + + +def split_string(dna): + return (dna[:len(dna)/2], dna[len(dna)/2:]) + + +def mutate(dna): + new_dna = dna + if random.random() < MUTATION_RATE: + cut_point = random.randint(0, len(dna)) + new_dna = dna[:cut_point-1] + random.choice(string.ascii_letters + " ") + dna[cut_point:] + return new_dna + + +def stop_condition(candidate): + if candidate.this == TARGET: + return True + else: + return False + + +if __name__ == "__main__": + ga.run_genetic_algorithm(spawn_func=generate_random_string, + breed_func=breed_strings, + fitness_func=calculate_fitness, + stop_condition=stop_condition, + population_size=100) From fb3b9cbbfb832ebd48d63c6aa64fd6750af025e3 Mon Sep 17 00:00:00 2001 From: Emma Gordon Date: Wed, 19 Aug 2015 21:19:32 +0100 Subject: [PATCH 2/2] Fix integer division error --- 05-genetic-algorithms/python/ga/function_evolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-genetic-algorithms/python/ga/function_evolution.py b/05-genetic-algorithms/python/ga/function_evolution.py index 2e2aee7..887fa51 100644 --- a/05-genetic-algorithms/python/ga/function_evolution.py +++ b/05-genetic-algorithms/python/ga/function_evolution.py @@ -49,7 +49,7 @@ def __init__(self, depth): self.value = _choose_terminal() def __call__(self, x_val): - return self.value if self.value is not "x" else x_val + return float(self.value) if self.value is not "x" else x_val def __repr__(self): return str(self.value)