/* * 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.opp; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import org.encog.ml.ea.genome.Genome; import org.encog.ml.ea.opp.EvolutionaryOperator; import org.encog.ml.ea.train.EvolutionaryAlgorithm; import org.encog.neural.neat.NEATGenomeFactory; import org.encog.neural.neat.training.NEATGenome; import org.encog.neural.neat.training.NEATLinkGene; import org.encog.neural.neat.training.NEATNeuronGene; /** * Crossover is performed by mixing the link genes between the parents to * produce an offspring. Only the link genes are considered for crossover. The * neuron genes are chosen by virtue of which link genes were chosen. If a * neuron gene is present in both parents, then we choose the neuron gene from * the more fit of the two parents. * * For NEAT, it does not really matter what parent we get the neuron gene from. * However, because HyperNEAT also encodes a unique activation function into the * neuron, the selection of a neuron gene between two parents is more important. * * The crossover operator defines two broad classes of genes. Matching genes are * those genes that are present in both parents. Non-matching genes are only * present in one person. Non-matching genes are further divided into two more * groups: * * disjoint genes: Genes in the middle of a genome that do not match between the * parents. excess genes: Genes at the edge of a genome that do not match * between the parents. * * Matching genes are inherited randomly, whereas disjoint genes (those that do * not match in the middle) and excess genes (those that do not match in the * end) are inherited from the more fit parent. In this case, equal fitnesses * are assumed, so the disjoint and excess genes are also inherited randomly. * The disabled genes may become enabled again in future generations: there is a * preset chance that an inherited gene is disabled if it is disabled in either * parent. * * This is implemented in this class via the following algorithm. First, create * a counter for each parent. At each step in the loop, perform the following. * * If both parents have the same innovation number, then randomly choose which * parent's gene to use. Increase the parent counter who contributed the gene. * Else if one parent has a lower innovation number than the other, then include * the lower innovation gene if its parent is the most fit. Increase the parent * counter who contributed the gene. * * ----------------------------------------------------------------------------- * 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 NEATCrossover implements EvolutionaryOperator, Serializable { /** * The owning object. */ private EvolutionaryAlgorithm owner; /** * Add a neuron. * @param nodeID The ID. * @param vec The list of ID's used. * @param best The best node. * @param notBest Not the best node. */ public void addNeuronID(final long nodeID, final List<NEATNeuronGene> vec, final NEATGenome best, final NEATGenome notBest) { for (int i = 0; i < vec.size(); i++) { if (vec.get(i).getId() == nodeID) { return; } } vec.add(findBestNeuron(nodeID, best, notBest)); return; } /** * Choose a parent to favor. * * @param rnd * A random number generator. * @param mom * The mother. * @param dad * The father. * @return The parent to favor. */ private NEATGenome favorParent(final Random rnd, final NEATGenome mom, final NEATGenome dad) { // first determine who is more fit, the mother or the father? // see if mom and dad are the same fitness if (mom.getScore() == dad.getScore()) { // are mom and dad the same fitness if (mom.getNumGenes() == dad.getNumGenes()) { // if mom and dad are the same fitness and have the same number // of genes, // then randomly pick mom or dad as the most fit. if (rnd.nextDouble() < 0.5) { return mom; } else { return dad; } } // mom and dad are the same fitness, but different number of genes // favor the parent with fewer genes else { if (mom.getNumGenes() < dad.getNumGenes()) { return mom; } else { return dad; } } } else { // mom and dad have different scores, so choose the better score. // important to note, better score COULD BE the larger or smaller // score. if (this.owner.getSelectionComparator().compare(mom, dad) < 0) { return mom; } else { return dad; } } } /** * Find the best neuron, between two parents by the specified neuron id. * * @param nodeID * The neuron id. * @param best * The best genome. * @param notBest * The non-best (second best) genome. Also the worst, since this * is the 2nd best of 2. * @return The best neuron genome by id. */ private NEATNeuronGene findBestNeuron(final long nodeID, final NEATGenome best, final NEATGenome notBest) { NEATNeuronGene result = best.findNeuron(nodeID); if (result == null) { result = notBest.findNeuron(nodeID); } return result; } /** * Init this operator. This allows the EA to be defined. */ @Override public void init(final EvolutionaryAlgorithm theOwner) { this.owner = theOwner; } /** * {@inheritDoc} */ @Override public int offspringProduced() { return 1; } /** * {@inheritDoc} */ @Override public int parentsNeeded() { return 2; } /** * {@inheritDoc} */ @Override public void performOperation(final Random rnd, final Genome[] parents, final int parentIndex, final Genome[] offspring, final int offspringIndex) { final NEATGenome mom = (NEATGenome) parents[parentIndex + 0]; final NEATGenome dad = (NEATGenome) parents[parentIndex + 1]; final NEATGenome best = favorParent(rnd, mom, dad); final NEATGenome notBest = (best != mom) ? mom : dad; final List<NEATLinkGene> selectedLinks = new ArrayList<NEATLinkGene>(); final List<NEATNeuronGene> selectedNeurons = new ArrayList<NEATNeuronGene>(); int curMom = 0; // current gene index from mom int curDad = 0; // current gene index from dad NEATLinkGene selectedGene = null; // add in the input and bias, they should always be here final int alwaysCount = ((NEATGenome)parents[0]).getInputCount() + ((NEATGenome)parents[0]).getOutputCount() + 1; for (int i = 0; i < alwaysCount; i++) { addNeuronID(i, selectedNeurons, best, notBest); } while ((curMom < mom.getNumGenes()) || (curDad < dad.getNumGenes())) { NEATLinkGene momGene = null; // the mom gene object NEATLinkGene dadGene = null; // the dad gene object long momInnovation = -1; long dadInnovation = -1; // grab the actual objects from mom and dad for the specified // indexes // if there are none, then null if (curMom < mom.getNumGenes()) { momGene = mom.getLinksChromosome().get(curMom); momInnovation = momGene.getInnovationId(); } if (curDad < dad.getNumGenes()) { dadGene = dad.getLinksChromosome().get(curDad); dadInnovation = dadGene.getInnovationId(); } // now select a gene for mom or dad. This gene is for the baby if ((momGene == null) && (dadGene != null)) { if (best == dad) { selectedGene = dadGene; } curDad++; } else if ((dadGene == null) && (momGene != null)) { if (best == mom) { selectedGene = momGene; } curMom++; } else if (momInnovation < dadInnovation) { if (best == mom) { selectedGene = momGene; } curMom++; } else if (dadInnovation < momInnovation) { if (best == dad) { selectedGene = dadGene; } curDad++; } else if (dadInnovation == momInnovation) { if (Math.random() < 0.5f) { selectedGene = momGene; } else { selectedGene = dadGene; } curMom++; curDad++; } if (selectedGene != null) { if (selectedLinks.size() == 0) { selectedLinks.add(selectedGene); } else { if (selectedLinks.get(selectedLinks.size() - 1) .getInnovationId() != selectedGene .getInnovationId()) { selectedLinks.add(selectedGene); } } // Check if we already have the nodes referred to in // SelectedGene. // If not, they need to be added. addNeuronID(selectedGene.getFromNeuronID(), selectedNeurons, best, notBest); addNeuronID(selectedGene.getToNeuronID(), selectedNeurons, best, notBest); } } // now create the required nodes. First sort them into order Collections.sort(selectedNeurons); // finally, create the genome final NEATGenomeFactory factory = (NEATGenomeFactory) this.owner .getPopulation().getGenomeFactory(); final NEATGenome babyGenome = factory.factor(selectedNeurons, selectedLinks, mom.getInputCount(), mom.getOutputCount()); babyGenome.setBirthGeneration(this.owner.getIteration()); babyGenome.setPopulation(this.owner.getPopulation()); babyGenome.sortGenes(); offspring[offspringIndex] = babyGenome; } }