Skip to content

Commit

Permalink
Simplified Genome and FFGenome._mutate_add_connection, and fixed a bu…
Browse files Browse the repository at this point in the history
…g which systematically prevented connections from being added to some nodes.

Temporarily commented connection adding code in Genome and FFGenome.add_hidden_nodes, as these always made the network fully connected regardless of configuration.
Removed the scary "replace the __dict__" code in checkpoint restoration.
Population now throws an exception when all species go extinct, as this is probably something the user should handle.
Simplified speciation code.
Simplified stats logging code, and keep more detailed per-generation fitness statistics.
Added tests to exercise more of the genome-handling code.
Simplification of XOR example.
visualize.draw_net now takes an optional dictionary which is used to provide labels to be used in place of node IDs in the rendered network.
Population now tracks the number of fitness function evaluations directly.
 Fixed bug (elitism setting was being interpreted as a float instead of an int).
Removed compatibility threshold adjustment to control the number of species.
Removed species age-adjusted fitness scheme as does not appear to be necessary: the speciation and stagnation mechanisms already provide the same benefit (as far as I can tell).
Removed now-unused configuration items from examples.
General cleanup of code and comments.
  • Loading branch information
CodeReclaimers committed Dec 22, 2015
1 parent 00bc844 commit 22843ab
Show file tree
Hide file tree
Showing 19 changed files with 365 additions and 443 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
[![Build Status](https://travis-ci.org/CodeReclaimers/neat-python.svg)](https://travis-ci.org/CodeReclaimers/neat-python)

[![Code Issues](https://www.quantifiedcode.com/api/v1/project/2bb1d19f57684f4589cb4700f99dd75e/badge.svg)](https://www.quantifiedcode.com/app/project/2bb1d19f57684f4589cb4700f99dd75e)

[![Coverage Status](https://coveralls.io/repos/CodeReclaimers/neat-python/badge.svg?branch=master&service=github)](https://coveralls.io/github/CodeReclaimers/neat-python?branch=master)

## About ##
Expand Down
14 changes: 1 addition & 13 deletions docs/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,12 @@ NEAT settings.
* *prob_toggle_link*
The probability that the enabled status of a connection will be toggled.
* *elitism*
The number of individuals in each species that will be preserved from one generation to the next.
The number of most fit individuals in each species that will be preserved as-is from one generation to the next.

[genotype compatibility] section
--------------------------------
* *compatibility_threshold*
Individuals whose genomic distance is less than this threshold are considered to be in the same species.
* *compatibility_change*
The amount by which *compatibility_threshold* may be adjusted during a generation to maintain target *species_size*.
* *excess_coefficient*
The coefficient for the excess gene count's contribution to the genomic distance.
* *disjoint_coefficient*
Expand All @@ -81,18 +79,8 @@ NEAT settings.

[species] section
-----------------
* *species_size*
The target number of species to maintain. When the number of species is different from *species_size*, *compatibility_threshold* will be adjusted up or down as necessary to attempt to return to *species_size*.
* *survival_threshold*
The fraction for each species allowed to reproduce on each generation.
* *old_threshold*
The number of generations beyond which species are considered old.
* *youth_threshold*
The number of generations below which species are considered young.
* *old_penalty*
The multiplicative fitness adjustment applied to old species' average fitness. This value is typically on (0.0, 1.0].
* *youth_boost*
The multiplicative fitness adjustment applied to young species' average fitness. This value is typically on [1.0, 2.0].
* *max_stagnation*
Species that have not shown improvement in more than this number of generations will be considered stagnant and removed.

Expand Down
8 changes: 1 addition & 7 deletions examples/pole_balancing/single_pole/ctrnn_config
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,14 @@ prob_mutate_weight = 0.8
prob_replace_weight = 0.1
weight_mutation_power = 1.0
prob_toggle_link = 0.01
elitism = 1
elitism = 2

[genotype compatibility]
compatibility_threshold = 3.0
compatibility_change = 0.0
excess_coefficient = 1.0
disjoint_coefficient = 1.0
weight_coefficient = 0.4

[species]
species_size = 10
survival_threshold = 0.2
old_threshold = 80
youth_threshold = 10
old_penalty = 1.0
youth_boost = 1.0
max_stagnation = 20
4 changes: 2 additions & 2 deletions examples/pole_balancing/single_pole/ctrnn_evolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ def fitness_function(genomes):
pop.epoch(fitness_function, 2000, report=1, save_best=0)

# Save the winner.
print('Number of evaluations: {0:d}'.format(pop.total_evaluations))
winner = pop.most_fit_genomes[-1]
print('Number of evaluations: {0:d}'.format(winner.ID))
with open('ctrnn_winner_genome', 'wb') as f:
pickle.dump(winner, f)

print(winner)

# Plot the evolution of the best/average fitness.
visualize.plot_stats(pop.most_fit_genomes, pop.avg_fitness_scores, ylog=True, filename="ctrnn_fitness.svg")
visualize.plot_stats(pop.most_fit_genomes, pop.fitness_scores, ylog=True, filename="ctrnn_fitness.svg")
# Visualizes speciation
visualize.plot_species(pop.species_log, filename="ctrnn_speciation.svg")
# Visualize the best network.
Expand Down
8 changes: 1 addition & 7 deletions examples/pole_balancing/single_pole/nn_config
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,16 @@ prob_mutate_weight = 0.9
prob_replace_weight = 0.1
weight_mutation_power = 1.5
prob_toggle_link = 0.01
elitism = 1
elitism = 2

[genotype compatibility]
compatibility_threshold = 3.0
compatibility_change = 0.0
excess_coefficient = 1.0
disjoint_coefficient = 1.0
weight_coefficient = 0.4

[species]
species_size = 10
survival_threshold = 0.2
old_threshold = 80
youth_threshold = 10
old_penalty = 1.0
youth_boost = 1.0
max_stagnation = 20


4 changes: 2 additions & 2 deletions examples/pole_balancing/single_pole/nn_evolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ def fitness_function(genomes):
pop.epoch(fitness_function, 1000)

# Save the winner.
print('Number of evaluations: {0:d}'.format(pop.total_evaluations))
winner = pop.most_fit_genomes[-1]
print('Number of evaluations: {0:d}'.format(winner.ID))
with open('nn_winner_genome', 'wb') as f:
pickle.dump(winner, f)

# Plot the evolution of the best/average fitness.
visualize.plot_stats(pop.most_fit_genomes, pop.avg_fitness_scores, ylog=True, filename="nn_fitness.svg")
visualize.plot_stats(pop.most_fit_genomes, pop.fitness_scores, ylog=True, filename="nn_fitness.svg")
# Visualizes speciation
visualize.plot_species(pop.species_log, filename="nn_speciation.svg")
# Visualize the best network.
Expand Down
36 changes: 15 additions & 21 deletions examples/xor/xor2.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
""" 2-input XOR example """
from __future__ import print_function

from neat import nn
from neat import population, visualize
from neat import nn, population, visualize

xor_inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
xor_outputs = [0, 1, 1, 0]
Expand All @@ -23,25 +22,20 @@ def eval_fitness(genomes):
g.fitness = 1 - error


def run():
pop = population.Population('xor2_config')
pop.epoch(eval_fitness, 300)
pop = population.Population('xor2_config')
pop.epoch(eval_fitness, 300)

winner = pop.most_fit_genomes[-1]
print('Number of evaluations: {0:d}'.format(winner.ID))
print('Number of evaluations: {0}'.format(pop.total_evaluations))

# Verify network output against training data.
print('\nBest network output:')
net = nn.create_feed_forward_phenotype(winner)
for inputs, expected in zip(xor_inputs, xor_outputs):
output = net.serial_activate(inputs)
print("expected {0:1.5f} got {1:1.5f}".format(expected, output[0]))
# Verify network output against training data.
print('\nBest network output:')
winner = pop.most_fit_genomes[-1]
net = nn.create_feed_forward_phenotype(winner)
for inputs, expected in zip(xor_inputs, xor_outputs):
output = net.serial_activate(inputs)
print("expected {0:1.5f} got {1:1.5f}".format(expected, output[0]))

# Visualize the winner network and plot statistics.
visualize.plot_stats(pop.most_fit_genomes, pop.avg_fitness_scores)
visualize.plot_species(pop.species_log)
visualize.draw_net(winner, view=True)


if __name__ == '__main__':
run()
# Visualize the winner network and plot statistics.
visualize.plot_stats(pop.most_fit_genomes, pop.fitness_scores)
visualize.plot_species(pop.species_log)
visualize.draw_net(winner, view=True)
6 changes: 0 additions & 6 deletions examples/xor/xor2_config
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,10 @@ elitism = 1

[genotype compatibility]
compatibility_threshold = 3.0
compatibility_change = 0.0
excess_coefficient = 1.0
disjoint_coefficient = 1.0
weight_coefficient = 0.4

[species]
species_size = 10
survival_threshold = 0.2
old_threshold = 30
youth_threshold = 10
old_penalty = 1.0
youth_boost = 1.0
max_stagnation = 100
6 changes: 3 additions & 3 deletions examples/xor/xor2_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,18 @@ def fitness(genomes):
print("total evolution time {0:.3f} sec".format((time.time() - t0)))
print("time per generation {0:.3f} sec".format(((time.time() - t0) / pop.generation)))

winner = pop.most_fit_genomes[-1]
print('Number of evaluations: {0:d}'.format(winner.ID))
print('Number of evaluations: {0:d}'.format(pop.total_evaluations))

# Verify network output against training data.
print('\nBest network output:')
winner = pop.most_fit_genomes[-1]
net = nn.create_feed_forward_phenotype(winner)
for i, inputs in enumerate(xor_inputs):
output = net.serial_activate(inputs) # serial activation
print( "{0:1.5f} \t {1:1.5f}".format(xor_outputs[i], output[0]))

# Visualize the winner network and plot statistics.
visualize.plot_stats(pop.most_fit_genomes, pop.avg_fitness_scores)
visualize.plot_stats(pop.most_fit_genomes, pop.fitness_scores)
visualize.plot_species(pop.species_log)
visualize.draw_net(winner, view=True)

Expand Down
8 changes: 4 additions & 4 deletions examples/xor/xor2_spiking.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ def run():
pop = population.Population(config)
pop.epoch(eval_fitness, 200)

winner = pop.most_fit_genomes[-1]
print('Number of evaluations: {0:d}'.format(winner.ID))
print('Number of evaluations: {0}'.format(pop.total_evaluations))

# Visualize the winner network and plot statistics.
visualize.plot_stats(pop.most_fit_genomes, pop.avg_fitness_scores)
winner = pop.most_fit_genomes[-1]
visualize.draw_net(winner, view=True, node_names={0:'A', 1:'B', 2:'Out1', 3:'Out2'})
visualize.plot_stats(pop.most_fit_genomes, pop.fitness_scores)
visualize.plot_species(pop.species_log)
visualize.draw_net(winner, view=True)

# Verify network output against training data.
print('\nBest network output:')
Expand Down
8 changes: 1 addition & 7 deletions neat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,14 @@ def __init__(self, filename):
self.prob_replace_weight = float(parameters.get('genetic', 'prob_replace_weight'))
self.weight_mutation_power = float(parameters.get('genetic', 'weight_mutation_power'))
self.prob_toggle_link = float(parameters.get('genetic', 'prob_toggle_link'))
self.elitism = float(parameters.get('genetic', 'elitism'))
self.elitism = int(parameters.get('genetic', 'elitism'))

# genotype compatibility
self.compatibility_threshold = float(parameters.get('genotype compatibility', 'compatibility_threshold'))
self.compatibility_change = float(parameters.get('genotype compatibility', 'compatibility_change'))
self.excess_coefficient = float(parameters.get('genotype compatibility', 'excess_coefficient'))
self.disjoint_coefficient = float(parameters.get('genotype compatibility', 'disjoint_coefficient'))
self.weight_coefficient = float(parameters.get('genotype compatibility', 'weight_coefficient'))

# species
self.species_size = int(parameters.get('species', 'species_size'))
self.survival_threshold = float(parameters.get('species', 'survival_threshold'))
self.old_threshold = int(parameters.get('species', 'old_threshold'))
self.youth_threshold = int(parameters.get('species', 'youth_threshold'))
self.old_penalty = float(parameters.get('species', 'old_penalty'))
self.youth_boost = float(parameters.get('species', 'youth_boost'))
self.max_stagnation = int(parameters.get('species', 'max_stagnation'))
12 changes: 3 additions & 9 deletions neat/diversity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
'''
from math import ceil

class AgedFitnessSharing(object):

class ExplicitFitnessSharing(object):
'''
This class encapsulates a fitness sharing scheme. It is responsible for
computing the number of individuals to be spawned for each species in the
next generation, based on species fitness, age, and size.
next generation, based on species fitness and size.
Fitness inside a species is shared by all its members, so that a species
that happens to end up with a large initial number of members is less
Expand Down Expand Up @@ -35,13 +36,6 @@ def compute_spawn_amount(self, species):
for f, s in zip(fitnesses, species):
# Make all adjusted fitnesses positive, and apply adjustment for population size.
af = (f + fitness_shift) / len(s.members)

# Apply adjustments for species age.
if s.age < self.config.youth_threshold:
af *= self.config.youth_boost
elif s.age > self.config.old_threshold:
af *= self.config.old_penalty

adjusted_fitnesses.append(af)
total_adjusted_fitness += af

Expand Down
3 changes: 0 additions & 3 deletions neat/genes.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ def mutate(self, config):
self.__mutate_response(config)





class ConnectionGene(object):
indexer = Indexer(0)
__innovations = {}
Expand Down
Loading

0 comments on commit 22843ab

Please sign in to comment.