From 5de03f036c37d71e532954b77f262c402556d694 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sat, 15 Apr 2017 02:27:38 +0300 Subject: [PATCH 01/11] Update search.py --- search.py | 140 ++++++++++++++++++++---------------------------------- 1 file changed, 52 insertions(+), 88 deletions(-) diff --git a/search.py b/search.py index b073ab2c8..b7a9dc1c5 100644 --- a/search.py +++ b/search.py @@ -572,94 +572,58 @@ def LRTA_cost(self, s, a, s1, H): # Genetic Algorithm -class GAState: - def __init__(self, length): - self.string = ''.join(random.choice(string.ascii_letters) - for _ in range(length)) - self.fitness = -1 - - -def ga(in_str=None, population=20, generations=10000): - in_str_len = len(in_str) - individuals = init_individual(population, in_str_len) - - for generation in range(generations): - - individuals = fitness(individuals, in_str) - individuals = selection(individuals) - individuals = crossover(individuals, population, in_str_len) - - if any(individual.fitness >= 90 for individual in individuals): - """ - individuals[0] is the individual with the highest fitness, - because individuals is sorted in the selection function. - Thus we return the individual with the highest fitness value, - among the individuals whose fitness is equal to or greater - than 90. - """ - - return individuals[0] - - individuals = mutation(individuals, in_str_len) - - """ - sufficient number of generations have passed and the individuals - could not evolve to match the desired fitness value. - thus we return the fittest individual among the individuals. - Since individuals are sorted according to their fitness - individuals[0] is the fittest. - """ - return individuals[0] - - -def init_individual(population, length): - return [GAState(length) for _ in range(population)] - - -def fitness(individuals, in_str): - for individual in individuals: - individual.fitness = fuzz.ratio(individual.string, in_str) # noqa - - return individuals - - -def selection(individuals): - individuals = sorted( - individuals, key=lambda individual: individual.fitness, reverse=True) - - individuals = individuals[:int(0.2 * len(individuals))] - return individuals - - -def crossover(individuals, population, in_str_len): - offspring = [] - for _ in range(int((population - len(individuals)) / 2)): - parent1 = random.choice(individuals) - parent2 = random.choice(individuals) - child1 = GAState(in_str_len) - child2 = GAState(in_str_len) - split = random.randint(0, in_str_len) - child1.string = parent1.string[0:split] + parent2.string[ - split:in_str_len] - child2.string = parent2.string[0:split] + parent1.string[ - split:in_str_len] - offspring.append(child1) - offspring.append(child2) - - individuals.extend(offspring) - return individuals - - -def mutation(individuals, in_str_len): - for individual in individuals: - - for idx, param in enumerate(individual.string): - if random.uniform(0.0, 1.0) <= 0.1: - individual.string = individual.string[0:idx] \ - + random.choice(string.ascii_letters) \ - + individual.string[idx + 1:in_str_len] - - return individuals +def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], fit_threshold=None, ngen=1000, pmut=0.1): + """[Figure 4.8]""" + for i in range(ngen): + new_population = [] + for j in range(len(population)): + fitnesses = map(fitness_fn, population) + p1, p2 = weighted_sample_with_replacement(2, population, fitnesses) + child = reproduce(p1, p2) + if random.uniform(0, 1) < pmut: + child = mutate(child, gene_pool) + new_population.append(child) + + population = new_population + + if fit_threshold: + fittest_individual = argmax(population, key=fitness_fn) + if fitness_fn(fittest_individual) >= fit_threshold: + return fittest_individual + + return argmax(population, key=fitness_fn) + + +def init_population(pop_number, gene_pool, length): + """Initializes population for genetic algorithm + pop_number: Number of individuals in population + gene_pool : List of possible values for individuals + (char only) + length : The length of each individual""" + g = len(gene_pool) + population = [] + for i in range(pop_number): + new_individual = ''.join([gene_pool[random.randrange(0, g)] + for j in range(length)]) + population.append(new_individual) + + return population + + +def reproduce(x, y): + n = len(x) + c = random.randrange(0, n) + return x[:c] + y[c:] + + +def mutate(x, gene_pool): + n = len(x) + g = len(gene_pool) + c = random.randrange(0, n) + r = random.randrange(0, g) + + new_gene = gene_pool[r] + return x[:c] + new_gene + x[c+1:] # _____________________________________________________________________________ # The remainder of this file implements examples for the search algorithms. From 3233625706ce41ede88cfb89a13ab32b25e3fb8e Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sat, 15 Apr 2017 02:30:51 +0300 Subject: [PATCH 02/11] Update test_search.py --- tests/test_search.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_search.py b/tests/test_search.py index 11d522e94..4e7f551ea 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -87,6 +87,23 @@ def test_LRTAStarAgent(): assert my_agent('State_5') is None +def test_genetic_algorithm(): + edges = { + 'A': [0, 1], + 'B': [0, 3], + 'C': [1, 2], + 'D': [2, 3] + } + + population = init_population(8, ['0', '1'], 4) + + def fitness(c): + return sum(c[n1] != c[n2] for (n1, n2) in edges.values()) + + solution = genetic_algorithm(population, fitness) + assert solution == "0101" or solution == "1010" + + # TODO: for .ipynb: """ >>> compare_graph_searchers() From 7b238ad95146de021f7d6649256bbb743040d527 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sat, 15 Apr 2017 02:43:08 +0300 Subject: [PATCH 03/11] minor edits and notes --- search.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/search.py b/search.py index b7a9dc1c5..5b2fc596a 100644 --- a/search.py +++ b/search.py @@ -572,7 +572,21 @@ def LRTA_cost(self, s, a, s1, H): # Genetic Algorithm -def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], fit_threshold=None, ngen=1000, pmut=0.1): +def genetic_search(problem, fitness_fn, ngen=1000, pmut=0.1, n=20): + """Call genetic_algorithm on the appropriate parts of a problem. + This requires the problem to have states that can mate and mutate, + plus a value method that scores states.""" + + # NOTE: This is not tested and might not work. + # TODO: Use this function to make Problems work with genetic_algorithm. + + s = problem.initial_state + states = [problem.result(s, a) for a in problem.actions(s)] + random.shuffle(states) + return genetic_algorithm(states[:n], problem.value, ngen, pmut) + + +def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], f_thres=None, ngen=1000, pmut=0.1): """[Figure 4.8]""" for i in range(ngen): new_population = [] @@ -586,9 +600,9 @@ def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], fit_threshol population = new_population - if fit_threshold: + if f_thres: fittest_individual = argmax(population, key=fitness_fn) - if fitness_fn(fittest_individual) >= fit_threshold: + if fitness_fn(fittest_individual) >= f_thres: return fittest_individual return argmax(population, key=fitness_fn) From 8ba06f117e38b628865d2092dee63b7f7ba3ced9 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sat, 15 Apr 2017 02:54:19 +0300 Subject: [PATCH 04/11] removed fuzzywuzzy --- search.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/search.py b/search.py index 5b2fc596a..e2f25854f 100644 --- a/search.py +++ b/search.py @@ -4,21 +4,18 @@ then create problem instances and solve them with calls to the various search functions.""" -import bisect -import math -import random -import string -import sys -from collections import defaultdict - -from fuzzywuzzy import fuzz - -from grid import distance from utils import ( - is_in, argmin, argmax_random_tie, probability, - memoize, print_table, DataFile, Stack, + is_in, argmin, argmax, argmax_random_tie, probability, + weighted_sample_with_replacement, memoize, print_table, DataFile, Stack, FIFOQueue, PriorityQueue, name ) +from grid import distance + +from collections import defaultdict +import math +import random +import sys +import bisect infinity = float('inf') From c551d602333d02a6fec57fbf6ad3a08914586454 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sat, 15 Apr 2017 18:07:33 +0300 Subject: [PATCH 05/11] Add 8-Queens Test --- tests/test_search.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_search.py b/tests/test_search.py index 4e7f551ea..51a486bec 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -88,6 +88,7 @@ def test_LRTAStarAgent(): def test_genetic_algorithm(): + # Graph coloring edges = { 'A': [0, 1], 'B': [0, 3], @@ -103,6 +104,27 @@ def fitness(c): solution = genetic_algorithm(population, fitness) assert solution == "0101" or solution == "1010" + # Queens Problem + population = init_population(100, [str(i) for i in range(8)], 8) + + def fitness(q): + non_attacking = 0 + for row1 in range(len(q)): + for row2 in range(row1+1, len(q)): + col1 = int(q[row1]) + col2 = int(q[row2]) + row_diff = row1 - row2 + col_diff = col1 - col2 + + if col1 != col2 and row_diff != col_diff and row_diff != -col_diff: + non_attacking += 1 + + return non_attacking + + + solution = genetic_algorithm(population, fitness, f_thres=25) + assert fitness(solution) == 25 + # TODO: for .ipynb: """ From 6522f5069e73d8cbb451c42c4273ecad435a8052 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sun, 16 Apr 2017 17:49:35 +0300 Subject: [PATCH 06/11] Optimization without veering from pseudocode --- search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.py b/search.py index e2f25854f..203939a60 100644 --- a/search.py +++ b/search.py @@ -586,9 +586,9 @@ def genetic_search(problem, fitness_fn, ngen=1000, pmut=0.1, n=20): def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], f_thres=None, ngen=1000, pmut=0.1): """[Figure 4.8]""" for i in range(ngen): + fitnesses = map(fitness_fn, population) new_population = [] for j in range(len(population)): - fitnesses = map(fitness_fn, population) p1, p2 = weighted_sample_with_replacement(2, population, fitnesses) child = reproduce(p1, p2) if random.uniform(0, 1) < pmut: From 2bbf42514e306b9247ec5b57bb3b0811c0e6c2cb Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sun, 16 Apr 2017 17:59:45 +0300 Subject: [PATCH 07/11] Variable renaming --- search.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/search.py b/search.py index 203939a60..db9c005f8 100644 --- a/search.py +++ b/search.py @@ -605,17 +605,17 @@ def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], f_thres=None return argmax(population, key=fitness_fn) -def init_population(pop_number, gene_pool, length): +def init_population(pop_number, gene_pool, state_length): """Initializes population for genetic algorithm - pop_number: Number of individuals in population - gene_pool : List of possible values for individuals - (char only) - length : The length of each individual""" + pop_number : Number of individuals in population + gene_pool : List of possible values for individuals + (char only) + state_length: The length of each individual""" g = len(gene_pool) population = [] for i in range(pop_number): new_individual = ''.join([gene_pool[random.randrange(0, g)] - for j in range(length)]) + for j in range(state_length)]) population.append(new_individual) return population From e08c4259a02c22dd5cfdb92d7041a2edd58ec1da Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Sun, 16 Apr 2017 18:05:40 +0300 Subject: [PATCH 08/11] Update search.py --- search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.py b/search.py index db9c005f8..320b0dd8c 100644 --- a/search.py +++ b/search.py @@ -586,9 +586,9 @@ def genetic_search(problem, fitness_fn, ngen=1000, pmut=0.1, n=20): def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], f_thres=None, ngen=1000, pmut=0.1): """[Figure 4.8]""" for i in range(ngen): - fitnesses = map(fitness_fn, population) new_population = [] for j in range(len(population)): + fitnesses = map(fitness_fn, population) p1, p2 = weighted_sample_with_replacement(2, population, fitnesses) child = reproduce(p1, p2) if random.uniform(0, 1) < pmut: From ae237b48a0567056b77ee30e230ca4d539567cb8 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Mon, 17 Apr 2017 03:36:52 +0300 Subject: [PATCH 09/11] Optimization --- search.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/search.py b/search.py index 320b0dd8c..eaae3fdd4 100644 --- a/search.py +++ b/search.py @@ -5,7 +5,7 @@ functions.""" from utils import ( - is_in, argmin, argmax, argmax_random_tie, probability, + is_in, argmin, argmax, argmax_random_tie, probability, weighted_sampler, weighted_sample_with_replacement, memoize, print_table, DataFile, Stack, FIFOQueue, PriorityQueue, name ) @@ -587,10 +587,12 @@ def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], f_thres=None """[Figure 4.8]""" for i in range(ngen): new_population = [] + fitnesses = map(fitness_fn, population) + random_selection = weighted_sampler(population, fitnesses) for j in range(len(population)): - fitnesses = map(fitness_fn, population) - p1, p2 = weighted_sample_with_replacement(2, population, fitnesses) - child = reproduce(p1, p2) + x = random_selection() + y = random_selection() + child = reproduce(x, y) if random.uniform(0, 1) < pmut: child = mutate(child, gene_pool) new_population.append(child) From 8307145679a5e04de67cb7f516bd2bfe5b430980 Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Mon, 17 Apr 2017 03:38:48 +0300 Subject: [PATCH 10/11] Update test_search.py --- tests/test_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_search.py b/tests/test_search.py index 51a486bec..d50eacfe1 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -123,7 +123,7 @@ def fitness(q): solution = genetic_algorithm(population, fitness, f_thres=25) - assert fitness(solution) == 25 + assert fitness(solution) >= 25 # TODO: for .ipynb: From 3b81fb0371c4ca2f4003ada89f7dbbeba88def2b Mon Sep 17 00:00:00 2001 From: Antonis Maronikolakis Date: Mon, 17 Apr 2017 19:59:15 +0300 Subject: [PATCH 11/11] Fairer reproduction --- search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.py b/search.py index eaae3fdd4..428648614 100644 --- a/search.py +++ b/search.py @@ -625,7 +625,7 @@ def init_population(pop_number, gene_pool, state_length): def reproduce(x, y): n = len(x) - c = random.randrange(0, n) + c = random.randrange(1, n) return x[:c] + y[c:]