Forked from
Nikolai.Hartmann / KerasROOTClassification
253 commits behind, 4 commits ahead of the upstream repository.
optimizer.py 5.36 KiB
#!/usr/bin/env python
"""
Class that holds a genetic algorithm for evolving a network.
Credit:
A lot of those code was originally inspired by:
http://lethain.com/genetic-algorithms-cool-name-damn-simple/
"""
from functools import reduce
from operator import add
import random
from network import Network
class Optimizer(object):
"""Class that implements genetic algorithm for MLP optimization."""
def __init__(self, nn_param_choices, retain=0.4,
random_select=0.1, mutate_chance=0.2):
"""Create an optimizer.
Args:
nn_param_choices (dict): Possible network paremters
retain (float): Percentage of population to retain after
each generation
random_select (float): Probability of a rejected network
remaining in the population
mutate_chance (float): Probability a network will be
randomly mutated
"""
self.mutate_chance = mutate_chance
self.random_select = random_select
self.retain = retain
self.nn_param_choices = nn_param_choices
def create_population(self, count):
"""Create a population of random networks.
Args:
count (int): Number of networks to generate, aka the
size of the population
Returns:
(list): Population of network objects
"""
pop = []
for _ in range(0, count):
# Create a random network.
network = Network(self.nn_param_choices)
network.create_random()
# Add the network to our population.
pop.append(network)
return pop
@staticmethod
def fitness(network):
"""Return the accuracy, which is our fitness function."""
return network.accuracy
def grade(self, pop):
"""Find average fitness for a population.
Args:
pop (list): The population of networks
Returns:
(float): The average accuracy of the population
"""
summed = reduce(add, (self.fitness(network) for network in pop))
return summed / float((len(pop)))
def breed(self, mother, father):
"""Make two children as parts of their parents.
Args:
mother (dict): Network parameters
father (dict): Network parameters
Returns:
(list): Two network objects
"""
children = []
for _ in range(2):
child = {}
# Loop through the parameters and pick params for the kid.
for param in self.nn_param_choices:
child[param] = random.choice(
[mother.network[param], father.network[param]]
)
# Now create a network object.
network = Network(self.nn_param_choices)
network.create_set(child)
# Randomly mutate some of the children.
if self.mutate_chance > random.random():
network = self.mutate(network)
children.append(network)
return children
def mutate(self, network):
"""Randomly mutate one part of the network.
Args:
network (dict): The network parameters to mutate
Returns:
(Network): A randomly mutated network object
"""
# Choose a random key.
mutation = random.choice(list(self.nn_param_choices.keys()))
# Mutate one of the params.
network.network[mutation] = random.choice(self.nn_param_choices[mutation])
return network
def evolve(self, pop):
"""Evolve a population of networks.
Args:
pop (list): A list of network parameters
Returns:
(list): The evolved population of networks
"""
# Get scores for each network.
graded = [(self.fitness(network), network) for network in pop]
# Sort on the scores.
graded = [x[1] for x in sorted(graded, key=lambda x: x[0], reverse=True)]
# Get the number we want to keep for the next gen.
retain_length = int(len(graded)*self.retain)
# The parents are every network we want to keep.
parents = graded[:retain_length]
# For those we aren't keeping, randomly keep some anyway.
for individual in graded[retain_length:]:
if self.random_select > random.random():
parents.append(individual)
# Now find out how many spots we have left to fill.
parents_length = len(parents)
desired_length = len(pop) - parents_length
children = []
# Add children, which are bred from two remaining networks.
while len(children) < desired_length:
# Get a random mom and dad.
male = random.randint(0, parents_length-1)
female = random.randint(0, parents_length-1)
# Assuming they aren't the same network...
if male != female:
male = parents[male]
female = parents[female]
# Breed them.
babies = self.breed(male, female)
# Add the children one at a time.
for baby in babies:
# Don't grow larger than desired length.
if len(children) < desired_length:
children.append(baby)
parents.extend(children)
return parents