Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a77b947

Browse files
antmarakisnorvig
authored andcommitted
Implementation: Genetic Algorithm (Fixing Build) (aimacode#501)
* Update search.py * Update test_search.py * minor edits and notes * removed fuzzywuzzy * Add 8-Queens Test * Optimization without veering from pseudocode * Variable renaming * Update search.py * Optimization * Update test_search.py * Fairer reproduction
1 parent 38a3844 commit a77b947

File tree

2 files changed

+116
-100
lines changed

2 files changed

+116
-100
lines changed

search.py

Lines changed: 77 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,18 @@
44
then create problem instances and solve them with calls to the various search
55
functions."""
66

7-
import bisect
8-
import math
9-
import random
10-
import string
11-
import sys
12-
from collections import defaultdict
13-
14-
from fuzzywuzzy import fuzz
15-
16-
from grid import distance
177
from utils import (
18-
is_in, argmin, argmax_random_tie, probability,
19-
memoize, print_table, DataFile, Stack,
8+
is_in, argmin, argmax, argmax_random_tie, probability, weighted_sampler,
9+
weighted_sample_with_replacement, memoize, print_table, DataFile, Stack,
2010
FIFOQueue, PriorityQueue, name
2111
)
12+
from grid import distance
13+
14+
from collections import defaultdict
15+
import math
16+
import random
17+
import sys
18+
import bisect
2219

2320
infinity = float('inf')
2421

@@ -572,94 +569,74 @@ def LRTA_cost(self, s, a, s1, H):
572569
# Genetic Algorithm
573570

574571

575-
class GAState:
576-
def __init__(self, length):
577-
self.string = ''.join(random.choice(string.ascii_letters)
578-
for _ in range(length))
579-
self.fitness = -1
580-
581-
582-
def ga(in_str=None, population=20, generations=10000):
583-
in_str_len = len(in_str)
584-
individuals = init_individual(population, in_str_len)
585-
586-
for generation in range(generations):
587-
588-
individuals = fitness(individuals, in_str)
589-
individuals = selection(individuals)
590-
individuals = crossover(individuals, population, in_str_len)
591-
592-
if any(individual.fitness >= 90 for individual in individuals):
593-
"""
594-
individuals[0] is the individual with the highest fitness,
595-
because individuals is sorted in the selection function.
596-
Thus we return the individual with the highest fitness value,
597-
among the individuals whose fitness is equal to or greater
598-
than 90.
599-
"""
600-
601-
return individuals[0]
602-
603-
individuals = mutation(individuals, in_str_len)
604-
605-
"""
606-
sufficient number of generations have passed and the individuals
607-
could not evolve to match the desired fitness value.
608-
thus we return the fittest individual among the individuals.
609-
Since individuals are sorted according to their fitness
610-
individuals[0] is the fittest.
611-
"""
612-
return individuals[0]
613-
614-
615-
def init_individual(population, length):
616-
return [GAState(length) for _ in range(population)]
617-
618-
619-
def fitness(individuals, in_str):
620-
for individual in individuals:
621-
individual.fitness = fuzz.ratio(individual.string, in_str) # noqa
622-
623-
return individuals
624-
625-
626-
def selection(individuals):
627-
individuals = sorted(
628-
individuals, key=lambda individual: individual.fitness, reverse=True)
629-
630-
individuals = individuals[:int(0.2 * len(individuals))]
631-
return individuals
632-
633-
634-
def crossover(individuals, population, in_str_len):
635-
offspring = []
636-
for _ in range(int((population - len(individuals)) / 2)):
637-
parent1 = random.choice(individuals)
638-
parent2 = random.choice(individuals)
639-
child1 = GAState(in_str_len)
640-
child2 = GAState(in_str_len)
641-
split = random.randint(0, in_str_len)
642-
child1.string = parent1.string[0:split] + parent2.string[
643-
split:in_str_len]
644-
child2.string = parent2.string[0:split] + parent1.string[
645-
split:in_str_len]
646-
offspring.append(child1)
647-
offspring.append(child2)
648-
649-
individuals.extend(offspring)
650-
return individuals
651-
652-
653-
def mutation(individuals, in_str_len):
654-
for individual in individuals:
655-
656-
for idx, param in enumerate(individual.string):
657-
if random.uniform(0.0, 1.0) <= 0.1:
658-
individual.string = individual.string[0:idx] \
659-
+ random.choice(string.ascii_letters) \
660-
+ individual.string[idx + 1:in_str_len]
661-
662-
return individuals
572+
def genetic_search(problem, fitness_fn, ngen=1000, pmut=0.1, n=20):
573+
"""Call genetic_algorithm on the appropriate parts of a problem.
574+
This requires the problem to have states that can mate and mutate,
575+
plus a value method that scores states."""
576+
577+
# NOTE: This is not tested and might not work.
578+
# TODO: Use this function to make Problems work with genetic_algorithm.
579+
580+
s = problem.initial_state
581+
states = [problem.result(s, a) for a in problem.actions(s)]
582+
random.shuffle(states)
583+
return genetic_algorithm(states[:n], problem.value, ngen, pmut)
584+
585+
586+
def genetic_algorithm(population, fitness_fn, gene_pool=['0', '1'], f_thres=None, ngen=1000, pmut=0.1):
587+
"""[Figure 4.8]"""
588+
for i in range(ngen):
589+
new_population = []
590+
fitnesses = map(fitness_fn, population)
591+
random_selection = weighted_sampler(population, fitnesses)
592+
for j in range(len(population)):
593+
x = random_selection()
594+
y = random_selection()
595+
child = reproduce(x, y)
596+
if random.uniform(0, 1) < pmut:
597+
child = mutate(child, gene_pool)
598+
new_population.append(child)
599+
600+
population = new_population
601+
602+
if f_thres:
603+
fittest_individual = argmax(population, key=fitness_fn)
604+
if fitness_fn(fittest_individual) >= f_thres:
605+
return fittest_individual
606+
607+
return argmax(population, key=fitness_fn)
608+
609+
610+
def init_population(pop_number, gene_pool, state_length):
611+
"""Initializes population for genetic algorithm
612+
pop_number : Number of individuals in population
613+
gene_pool : List of possible values for individuals
614+
(char only)
615+
state_length: The length of each individual"""
616+
g = len(gene_pool)
617+
population = []
618+
for i in range(pop_number):
619+
new_individual = ''.join([gene_pool[random.randrange(0, g)]
620+
for j in range(state_length)])
621+
population.append(new_individual)
622+
623+
return population
624+
625+
626+
def reproduce(x, y):
627+
n = len(x)
628+
c = random.randrange(1, n)
629+
return x[:c] + y[c:]
630+
631+
632+
def mutate(x, gene_pool):
633+
n = len(x)
634+
g = len(gene_pool)
635+
c = random.randrange(0, n)
636+
r = random.randrange(0, g)
637+
638+
new_gene = gene_pool[r]
639+
return x[:c] + new_gene + x[c+1:]
663640

664641
# _____________________________________________________________________________
665642
# The remainder of this file implements examples for the search algorithms.

tests/test_search.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,45 @@ def test_LRTAStarAgent():
8787
assert my_agent('State_5') is None
8888

8989

90+
def test_genetic_algorithm():
91+
# Graph coloring
92+
edges = {
93+
'A': [0, 1],
94+
'B': [0, 3],
95+
'C': [1, 2],
96+
'D': [2, 3]
97+
}
98+
99+
population = init_population(8, ['0', '1'], 4)
100+
101+
def fitness(c):
102+
return sum(c[n1] != c[n2] for (n1, n2) in edges.values())
103+
104+
solution = genetic_algorithm(population, fitness)
105+
assert solution == "0101" or solution == "1010"
106+
107+
# Queens Problem
108+
population = init_population(100, [str(i) for i in range(8)], 8)
109+
110+
def fitness(q):
111+
non_attacking = 0
112+
for row1 in range(len(q)):
113+
for row2 in range(row1+1, len(q)):
114+
col1 = int(q[row1])
115+
col2 = int(q[row2])
116+
row_diff = row1 - row2
117+
col_diff = col1 - col2
118+
119+
if col1 != col2 and row_diff != col_diff and row_diff != -col_diff:
120+
non_attacking += 1
121+
122+
return non_attacking
123+
124+
125+
solution = genetic_algorithm(population, fitness, f_thres=25)
126+
assert fitness(solution) >= 25
127+
128+
90129
# TODO: for .ipynb:
91130
"""
92131
>>> compare_graph_searchers()

0 commit comments

Comments
 (0)