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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy