85 lines
No EOL
3.4 KiB
Python
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() |