/* * Encog(tm) Core v3.4 - Java Version * http://www.heatonresearch.com/encog/ * https://github.com/encog/encog-java-core * Copyright 2008-2016 Heaton Research, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For more information on Heaton Research copyrights, licenses * and trademarks visit: * http://www.heatonresearch.com/copyright */ package org.encog.neural.neat.training; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.encog.EncogError; import org.encog.engine.network.activation.ActivationFunction; import org.encog.mathutil.randomize.RangeRandomizer; import org.encog.ml.ea.genome.BasicGenome; import org.encog.ml.ea.genome.Genome; import org.encog.neural.neat.NEATNeuronType; import org.encog.neural.neat.NEATPopulation; import org.encog.util.Format; /** * Implements a NEAT genome. This is a "blueprint" for creating a neural * network. * * ----------------------------------------------------------------------------- * http://www.cs.ucf.edu/~kstanley/ Encog's NEAT implementation was drawn from * the following three Journal Articles. For more complete BibTeX sources, see * NEATNetwork.java. * * Evolving Neural Networks Through Augmenting Topologies * * Generating Large-Scale Neural Networks Through Discovering Geometric * Regularities * * Automatic feature selection in neuroevolution * */ public class NEATGenome extends BasicGenome implements Cloneable, Serializable { /** * Serial id. */ private static final long serialVersionUID = 1L; /** * The number of inputs. */ private int inputCount; /** * The list that holds the links. */ private final List<NEATLinkGene> linksList = new ArrayList<NEATLinkGene>(); /** * THe network depth. */ private int networkDepth; /** * The list that holds the neurons. */ private final List<NEATNeuronGene> neuronsList = new ArrayList<NEATNeuronGene>(); /** * The number of outputs. */ private int outputCount; /** * Construct a genome by copying another. * * @param other * The other genome. */ public NEATGenome(final NEATGenome other) { this.networkDepth = other.networkDepth; this.setPopulation(other.getPopulation()); setScore(other.getScore()); setAdjustedScore(other.getAdjustedScore()); this.inputCount = other.inputCount; this.outputCount = other.outputCount; this.setSpecies(other.getSpecies()); // copy neurons for (final NEATNeuronGene oldGene : other.getNeuronsChromosome()) { final NEATNeuronGene newGene = new NEATNeuronGene(oldGene); this.neuronsList.add(newGene); } // copy links for (final NEATLinkGene oldGene : other.getLinksChromosome()) { final NEATLinkGene newGene = new NEATLinkGene( oldGene.getFromNeuronID(), oldGene.getToNeuronID(), oldGene.isEnabled(), oldGene.getInnovationId(), oldGene.getWeight()); this.linksList.add(newGene); } } /** * Create a NEAT gnome. Neuron genes will be added by reference, links will * be copied. * * @param neurons * The neurons to create. * @param links * The links to create. * @param inputCount * The input count. * @param outputCount * The output count. */ public NEATGenome(final List<NEATNeuronGene> neurons, final List<NEATLinkGene> links, final int inputCount, final int outputCount) { setAdjustedScore(0); this.inputCount = inputCount; this.outputCount = outputCount; for (NEATLinkGene gene : links) { this.linksList.add(new NEATLinkGene(gene)); } this.neuronsList.addAll(neurons); } /** * Create a new genome with the specified connection density. This * constructor is typically used to create the initial population. * @param rnd Random number generator. * @param pop The population. * @param inputCount The input count. * @param outputCount The output count. * @param connectionDensity The connection density. */ public NEATGenome(final Random rnd, final NEATPopulation pop, final int inputCount, final int outputCount, double connectionDensity) { setAdjustedScore(0); this.inputCount = inputCount; this.outputCount = outputCount; // get the activation function ActivationFunction af = pop.getActivationFunctions().pickFirst(); // first bias int innovationID = 0; NEATNeuronGene biasGene = new NEATNeuronGene(NEATNeuronType.Bias, af, inputCount, innovationID++); this.neuronsList.add(biasGene); // then inputs for (int i = 0; i < inputCount; i++) { NEATNeuronGene gene = new NEATNeuronGene(NEATNeuronType.Input, af, i, innovationID++); this.neuronsList.add(gene); } // then outputs for (int i = 0; i < outputCount; i++) { NEATNeuronGene gene = new NEATNeuronGene(NEATNeuronType.Output, af, i + inputCount + 1, innovationID++); this.neuronsList.add(gene); } // and now links boolean haveInputOutputLink = false; for (int i = 0; i < inputCount + 1; i++) { for (int j = 0; j < outputCount; j++) { if (rnd.nextDouble() < connectionDensity) { long fromID = this.neuronsList.get(i).getId(); long toID = this.neuronsList.get(inputCount + j + 1) .getId(); double w = RangeRandomizer.randomize(rnd, -pop.getWeightRange(), pop.getWeightRange()); NEATLinkGene gene = new NEATLinkGene(fromID, toID, true, innovationID++, w); this.linksList.add(gene); if (i != 0) { // if not bias node haveInputOutputLink = true; } } } } // make sure we have at least one connection between inputs and outputs if (!haveInputOutputLink) { // choose a random input/output pair int inputIndex = (int) (rnd.nextDouble() * inputCount) + 1; int outputIndex = (int) (rnd.nextDouble() * outputCount) + inputCount + 1; long fromID = this.neuronsList.get(inputIndex).getId(); long toID = this.neuronsList.get(outputIndex).getId(); double w = RangeRandomizer.randomize(rnd, -pop.getWeightRange(), pop.getWeightRange()); NEATLinkGene gene = new NEATLinkGene(fromID, toID, true, innovationID, w); this.linksList.add(gene); } } /** * Empty constructor for persistence. */ public NEATGenome() { } /** * @return The number of input neurons. */ public int getInputCount() { return this.inputCount; } /** * @return The network depth. */ public int getNetworkDepth() { return this.networkDepth; } /** * @return The number of genes in the links chromosome. */ public int getNumGenes() { return this.linksList.size(); } /** * @return The output count. */ public int getOutputCount() { return this.outputCount; } /** * @param networkDepth * the networkDepth to set */ public void setNetworkDepth(final int networkDepth) { this.networkDepth = networkDepth; } /** * Sort the genes. */ public void sortGenes() { Collections.sort(this.linksList); } /** * @return the linksChromosome */ public List<NEATLinkGene> getLinksChromosome() { return this.linksList; } /** * @return the neuronsChromosome */ public List<NEATNeuronGene> getNeuronsChromosome() { return this.neuronsList; } /** * @param inputCount * the inputCount to set */ public void setInputCount(int inputCount) { this.inputCount = inputCount; } /** * @param outputCount * the outputCount to set */ public void setOutputCount(int outputCount) { this.outputCount = outputCount; } /** * Validate the structure of this genome. */ public void validate() { // make sure that the bias neuron is where it should be NEATNeuronGene g = this.neuronsList.get(0); if (g.getNeuronType() != NEATNeuronType.Bias) { throw new EncogError("NEAT Neuron Gene 0 should be a bias gene."); } // make sure all input neurons are at the beginning for (int i = 1; i <= this.inputCount; i++) { NEATNeuronGene gene = this.neuronsList.get(i); if (gene.getNeuronType() != NEATNeuronType.Input) { throw new EncogError("NEAT Neuron Gene " + i + " should be an input gene."); } } // make sure that there are no double links Map<String, NEATLinkGene> map = new HashMap<String, NEATLinkGene>(); for (NEATLinkGene nlg : this.linksList) { String key = nlg.getFromNeuronID() + "->" + nlg.getToNeuronID(); if (map.containsKey(key)) { throw new EncogError("Double link found: " + key); } map.put(key, nlg); } } /** * {@inheritDoc} */ @Override public void copy(Genome source) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ @Override public int size() { return this.linksList.size(); } /** * Find the neuron with the specified nodeID. * * @param nodeID * The nodeID to look for. * @return The neuron, if found, otherwise null. */ public NEATNeuronGene findNeuron(long nodeID) { for (NEATNeuronGene gene : this.neuronsList) { if (gene.getId() == nodeID) return gene; } return null; } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append("["); result.append(this.getClass().getSimpleName()); result.append(",score="); result.append(Format.formatDouble(this.getScore(), 2)); result.append(",adjusted score="); result.append(Format.formatDouble(this.getAdjustedScore(), 2)); result.append(",birth generation="); result.append(this.getBirthGeneration()); result.append(",neurons="); result.append(this.neuronsList.size()); result.append(",links="); result.append(this.linksList.size()); result.append("]"); return result.toString(); } }