chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
from __future__ import print_function, division
|
||||
import string
|
||||
import numpy as np
|
||||
|
||||
class GeneticAlgorithm():
|
||||
"""An implementation of a Genetic Algorithm which will try to produce the user
|
||||
specified target string.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
target_string: string
|
||||
The string which the GA should try to produce.
|
||||
population_size: int
|
||||
The number of individuals (possible solutions) in the population.
|
||||
mutation_rate: float
|
||||
The rate (or probability) of which the alleles (chars in this case) should be
|
||||
randomly changed.
|
||||
"""
|
||||
def __init__(self, target_string, population_size, mutation_rate):
|
||||
self.target = target_string
|
||||
self.population_size = population_size
|
||||
self.mutation_rate = mutation_rate
|
||||
self.letters = [" "] + list(string.ascii_letters)
|
||||
|
||||
def _initialize(self):
|
||||
""" Initialize population with random strings """
|
||||
self.population = []
|
||||
for _ in range(self.population_size):
|
||||
# Select random letters as new individual
|
||||
individual = "".join(np.random.choice(self.letters, size=len(self.target)))
|
||||
self.population.append(individual)
|
||||
|
||||
def _calculate_fitness(self):
|
||||
""" Calculates the fitness of each individual in the population """
|
||||
population_fitness = []
|
||||
for individual in self.population:
|
||||
# Calculate loss as the alphabetical distance between
|
||||
# the characters in the individual and the target string
|
||||
loss = 0
|
||||
for i in range(len(individual)):
|
||||
letter_i1 = self.letters.index(individual[i])
|
||||
letter_i2 = self.letters.index(self.target[i])
|
||||
loss += abs(letter_i1 - letter_i2)
|
||||
fitness = 1 / (loss + 1e-6)
|
||||
population_fitness.append(fitness)
|
||||
return population_fitness
|
||||
|
||||
def _mutate(self, individual):
|
||||
""" Randomly change the individual's characters with probability
|
||||
self.mutation_rate """
|
||||
individual = list(individual)
|
||||
for j in range(len(individual)):
|
||||
# Make change with probability mutation_rate
|
||||
if np.random.random() < self.mutation_rate:
|
||||
individual[j] = np.random.choice(self.letters)
|
||||
# Return mutated individual as string
|
||||
return "".join(individual)
|
||||
|
||||
def _crossover(self, parent1, parent2):
|
||||
""" Create children from parents by crossover """
|
||||
# Select random crossover point
|
||||
cross_i = np.random.randint(0, len(parent1))
|
||||
child1 = parent1[:cross_i] + parent2[cross_i:]
|
||||
child2 = parent2[:cross_i] + parent1[cross_i:]
|
||||
return child1, child2
|
||||
|
||||
def run(self, iterations):
|
||||
# Initialize new population
|
||||
self._initialize()
|
||||
|
||||
for epoch in range(iterations):
|
||||
population_fitness = self._calculate_fitness()
|
||||
|
||||
fittest_individual = self.population[np.argmax(population_fitness)]
|
||||
highest_fitness = max(population_fitness)
|
||||
|
||||
# If we have found individual which matches the target => Done
|
||||
if fittest_individual == self.target:
|
||||
break
|
||||
|
||||
# Set the probability that the individual should be selected as a parent
|
||||
# proportionate to the individual's fitness.
|
||||
parent_probabilities = [fitness / sum(population_fitness) for fitness in population_fitness]
|
||||
|
||||
# Determine the next generation
|
||||
new_population = []
|
||||
for i in np.arange(0, self.population_size, 2):
|
||||
# Select two parents randomly according to probabilities
|
||||
parent1, parent2 = np.random.choice(self.population, size=2, p=parent_probabilities, replace=False)
|
||||
# Perform crossover to produce offspring
|
||||
child1, child2 = self._crossover(parent1, parent2)
|
||||
# Save mutated offspring for next generation
|
||||
new_population += [self._mutate(child1), self._mutate(child2)]
|
||||
|
||||
print ("[%d Closest Candidate: '%s', Fitness: %.2f]" % (epoch, fittest_individual, highest_fitness))
|
||||
self.population = new_population
|
||||
|
||||
print ("[%d Answer: '%s']" % (epoch, fittest_individual))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user