EvolutionaryAlgo/ga_test.py

85 lines
No EOL
3.4 KiB
Python

import random
import numpy as np
# Let's solve the function f(x, y) = -2x^2 - 3(y - 4)^2
def fitness(x):
return -2 * (x[:, 0] ** 2) - 3 * (x[:, 1] - 4)**2
class Population:
def __init__(self, output_size, population_size, fitness_fn, low = 0., high = 1., keep_best = 1, mutation_rate = 0.001):
self.population_size = population_size
self.output_size = output_size
self.low = low
self.high = high
self.mutation_rate = mutation_rate
self.keep_best = keep_best
assert self.keep_best >= 0
assert self.population_size > 0
assert self.keep_best < self.population_size
self.pop = self._generate_population(output_size, population_size, low = low, high = high)
# Probability that an individual will last to the next generation
self.survivability = np.full(shape=(population_size), fill_value = 1 / population_size)
self.calculate_fitness = fitness_fn
def _generate_population(self, output_size, population_size, low = 0., high = 1.):
return np.random.uniform(low, high, size=(population_size, output_size))
def _calculate_survivability(self, pop):
fitness = self.calculate_fitness(pop)
# Make fitness non-negative
if fitness.min() <= 0:
fitness += (-1 * fitness.min()) + np.finfo('float').eps
return fitness / fitness.sum()
def _select_survivors(self, population, survivability):
population_size = len(population)
survivors_indices = np.random.choice(range(0, population_size), size=(population_size - self.keep_best) * 2, p=survivability)
return population.take(survivors_indices, axis = 0)
def _crossover(self, parents):
parent_ind = np.array(range(0, len(parents)))
parent1_ind = np.random.choice(parent_ind, size = len(parents) // 2, replace=False)
parent2_ind = np.setdiff1d(parent_ind, parent1_ind)
parents1 = parents[parent1_ind]
parents2 = parents[parent2_ind]
children = []
for parent1, parent2 in zip(parents1, parents2):
crossover_ind = random.randint(0, self.output_size)
child = np.zeros_like(parent1)
child[:crossover_ind] = parent1[:crossover_ind]
child[crossover_ind:] = parent2[crossover_ind:]
child = self._mutate(child)
children.append(child)
return np.vstack(children)
def _mutate(self, child):
for i in range(len(child)):
if np.random.rand() < self.mutation_rate:
child[i] = np.random.uniform(self.low, self.high)
return child
def __iter__(self):
return self
# This function is suppose to take us to the next generation
def __next__(self):
survivability = self._calculate_survivability(self.pop)
if self.keep_best > 0:
survivor_ind = np.argsort(survivability)[-self.keep_best:]
parents = self._select_survivors(self.pop, survivability)
children = self._crossover(parents)
next_pop = np.concatenate((self.pop.take(survivor_ind, axis = 0), children))
self.pop = next_pop
return next_pop
def solution(self):
return self.pop.take(sorted(self.survivability)[-1], axis = 0)
def test():
p = Population(2, 100, fitness, low = -10, high = 10)
for i in range(10000):
next(p)
return p.solution()