Package org.encog.ml.ea.species

Source Code of org.encog.ml.ea.species.ThresholdSpeciation

/*
* Encog(tm) Core v3.3 - Java Version
* http://www.heatonresearch.com/encog/
* https://github.com/encog/encog-java-core
* Copyright 2008-2014 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.ml.ea.species;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.encog.Encog;
import org.encog.EncogError;
import org.encog.ml.ea.genome.Genome;
import org.encog.ml.ea.population.Population;
import org.encog.ml.ea.sort.SortGenomesForSpecies;
import org.encog.ml.ea.sort.SpeciesComparator;
import org.encog.ml.ea.train.EvolutionaryAlgorithm;
import org.encog.ml.genetic.GeneticError;

/**
* Speciate based on threshold. Any genomes with a compatibility score below a
* level will be in the same species.
*/
public abstract class ThresholdSpeciation implements Speciation, Serializable {
  /**
   * The serial id.
   */
  private static final long serialVersionUID = 1L;

  /**
   * The training being used.
   */
  private EvolutionaryAlgorithm owner;

  /**
   * The minimum compatibility that two genes must have to be in the same
   * species.
   */
  private double compatibilityThreshold = 1.0;

  /**
   * The maximum number of generations allows with no improvement. After this
   * the genomes in this species are not allowed to reproduce or continue.
   * This does not apply to top species.
   */
  private int numGensAllowedNoImprovement = 15;

  /**
   * The maximum number of species. This is just a target. If the number of
   * species goes over this number then the compatibilityThreshold is
   * increased to decrease the number of species.
   *
   */
  private int maxNumberOfSpecies = 40;

  /**
   * The method used to sort the genomes in the species. More desirable
   * genomes should come first for later selection.
   */
  private SortGenomesForSpecies sortGenomes;

  /**
   * The population.
   */
  private Population population;

  /**
   * Add a genome.
   *
   * @param species
   *            The species to add to.
   * @param genome
   *            The genome to add.
   */
  public void addSpeciesMember(final Species species, final Genome genome) {

    if (this.owner.isValidationMode()) {
      if (species.getMembers().contains(genome)) {
        throw new GeneticError("Species already contains genome: "
            + genome.toString());
      }
    }

    if (this.owner.getSelectionComparator().compare(genome,
        species.getLeader()) < 0) {
      species.setBestScore(genome.getAdjustedScore());
      species.setGensNoImprovement(0);
      species.setLeader(genome);
    }

    species.add(genome);
  }

  /**
   * Adjust the species compatibility threshold. This prevents us from having
   * too many species. Dynamically increase or decrease the
   * compatibilityThreshold.
   */
  private void adjustCompatibilityThreshold() {

    // has this been disabled (unlimited species)
    if (this.maxNumberOfSpecies < 1) {
      return;
    }

    final double thresholdIncrement = 0.01;

    if (this.population.getSpecies().size() > this.maxNumberOfSpecies) {
      this.compatibilityThreshold += thresholdIncrement;
    }

    else if (this.population.getSpecies().size() < 2) {
      this.compatibilityThreshold -= thresholdIncrement;
    }
  }

  /**
   * Divide up the potential offspring by the most fit species. To do this we
   * look at the total species score, vs each individual species percent
   * contribution to that score.
   *
   * @param speciesCollection
   *            The current species list.
   * @param totalSpeciesScore
   *            The total score over all species.
   */
  private void divideByFittestSpecies(final List<Species> speciesCollection,
      final double totalSpeciesScore) {
    Species bestSpecies = findBestSpecies();

    // loop over all species and calculate its share
    final Object[] speciesArray = speciesCollection.toArray();
    for (final Object element : speciesArray) {
      final Species species = (Species) element;
      // calculate the species share based on the percent of the total
      // species score
      int share = (int) Math
          .round((species.getOffspringShare() / totalSpeciesScore)
              * this.owner.getPopulation().getPopulationSize());

      // do not give the best species a zero-share
      if ((species == bestSpecies) && (share == 0)) {
        share = 1;
      }

      // if the share is zero, then remove the species
      if ((species.getMembers().size() == 0) || (share == 0)) {
        removeSpecies(species);
      }
      // if the species has not improved over the specified number of
      // generations, then remove it.
      else if ((species.getGensNoImprovement() > this.numGensAllowedNoImprovement)
          && (species != bestSpecies)) {
        removeSpecies(species);
      } else {
        // otherwise assign a share and sort the members.
        species.setOffspringCount(share);
        Collections.sort(species.getMembers(), this.sortGenomes);
      }
    }
  }

  /**
   * Find the best species.
   *
   * @return The best species.
   */
  public Species findBestSpecies() {
    if (this.owner.getBestGenome() != null) {
      return this.owner.getBestGenome().getSpecies();
    }
    return null;
  }

  /**
   * Attempt to remove a removable species. If the species is the best
   * species, then do not remove it. If the species is the last species, don't
   * remove it.
   *
   * @param species
   *            The species to attempt to remove.
   */
  public void removeSpecies(Species species) {
    if (species != findBestSpecies()) {
      if (population.getSpecies().size() > 1) {
        this.population.getSpecies().remove(species);
      }
    }
  }

  /**
   * If no species has a good score then divide the potential offspring amount
   * all species evenly.
   *
   * @param speciesCollection
   *            The current set of species.
   */
  private void divideEven(final List<Species> speciesCollection) {
    final double ratio = 1.0 / speciesCollection.size();
    for (final Species species : speciesCollection) {
      final int share = (int) Math.round(ratio
          * this.owner.getPopulation().getPopulationSize());
      species.setOffspringCount(share);
    }
  }

  /**
   * @return the compatibilityThreshold
   */
  public double getCompatibilityThreshold() {
    return this.compatibilityThreshold;
  }

  /**
   * @return the maxNumberOfSpecies
   */
  public int getMaxNumberOfSpecies() {
    return this.maxNumberOfSpecies;
  }

  /**
   * @return the numGensAllowedNoImprovement
   */
  public int getNumGensAllowedNoImprovement() {
    return this.numGensAllowedNoImprovement;
  }

  /**
   * @return the owner
   */
  public EvolutionaryAlgorithm getOwner() {
    return this.owner;
  }

  /**
   * @return the sortGenomes
   */
  public SortGenomesForSpecies getSortGenomes() {
    return this.sortGenomes;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void init(final EvolutionaryAlgorithm theOwner) {
    this.owner = theOwner;
    this.population = theOwner.getPopulation();
    this.sortGenomes = new SortGenomesForSpecies(this.owner);
  }

  /**
   * Level off all of the species shares so that they add up to the desired
   * population size. If they do not add up to the desired species size, this
   * was a result of rounding the floating point share amounts to integers.
   */
  private void levelOff() {
    int total = 0;
    final List<Species> list = this.population.getSpecies();

    if (list.size() == 0) {
      throw new EncogError(
          "Can't speciate, next generation contains no species.");
    }

    Collections.sort(list, new SpeciesComparator(this.owner));

    // best species gets at least one offspring
    if (list.get(0).getOffspringCount() == 0) {
      list.get(0).setOffspringCount(1);
    }

    // total up offspring
    for (final Species species : list) {
      total += species.getOffspringCount();
    }

    // how does the total offspring count match the target
    int diff = this.population.getPopulationSize() - total;

    if (diff < 0) {
      // need less offspring
      int index = list.size() - 1;
      while ((diff != 0) && (index > 0)) {
        final Species species = list.get(index);
        final int t = Math.min(species.getOffspringCount(),
            Math.abs(diff));
        species.setOffspringCount(species.getOffspringCount() - t);
        if (species.getOffspringCount() == 0) {
          list.remove(index);
        }
        diff += t;
        index--;
      }
    } else {
      // need more offspring
      list.get(0).setOffspringCount(
          list.get(0).getOffspringCount() + diff);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void performSpeciation(List<Genome> genomeList) {
    final List<Genome> newGenomeList = resetSpecies(genomeList);
    speciateAndCalculateSpawnLevels(newGenomeList);
  }

  /**
   * Reset for an iteration.
   *
   * @return The genomes to speciate.
   */
  private List<Genome> resetSpecies(List<Genome> inputGenomes) {
    final List<Genome> result = new ArrayList<Genome>();
    final Object[] speciesArray = this.population.getSpecies().toArray();

    // Add the genomes
    for (final Genome genome : inputGenomes) {
      result.add(genome);
    }

    for (final Object element : speciesArray) {
      final BasicSpecies s = (BasicSpecies) element;
      s.purge();

      // did the leader die? If so, disband the species. (but don't kill
      // the genomes)
      if (!inputGenomes.contains(s.getLeader())) {
        removeSpecies(s);
      } else if (s.getGensNoImprovement() > this.numGensAllowedNoImprovement) {
        removeSpecies(s);
      }

      // remove the leader from the list we return. the leader already has
      // a species
      result.remove(s.getLeader());
    }

    if (this.population.getSpecies().size() == 0) {
      throw new EncogError("Can't speciate, the population is empty.");
    }

    return result;
  }

  /**
   * @param compatibilityThreshold
   *            the compatibilityThreshold to set
   */
  public void setCompatibilityThreshold(final double compatibilityThreshold) {
    this.compatibilityThreshold = compatibilityThreshold;
  }

  /**
   * @param maxNumberOfSpecies
   *            the maxNumberOfSpecies to set
   */
  public void setMaxNumberOfSpecies(final int maxNumberOfSpecies) {
    this.maxNumberOfSpecies = maxNumberOfSpecies;
  }

  /**
   * @param numGensAllowedNoImprovement
   *            the numGensAllowedNoImprovement to set
   */
  public void setNumGensAllowedNoImprovement(
      final int numGensAllowedNoImprovement) {
    this.numGensAllowedNoImprovement = numGensAllowedNoImprovement;
  }

  /**
   * @param sortGenomes
   *            the sortGenomes to set
   */
  public void setSortGenomes(final SortGenomesForSpecies sortGenomes) {
    this.sortGenomes = sortGenomes;
  }

  /**
   * Determine the species.
   *
   * @param genomes
   *            The genomes to speciate.
   */
  private void speciateAndCalculateSpawnLevels(final List<Genome> genomes) {
    double maxScore = 0;

    if (genomes.size() == 0) {
      throw new EncogError("Can't speciate, the population is empty.");
    }

    final List<Species> speciesCollection = this.population.getSpecies();

    if (speciesCollection.size() == 0) {
      throw new EncogError("Can't speciate, there are no species.1");
    }

    // calculate compatibility between genomes and species
    adjustCompatibilityThreshold();

    // assign genomes to species (if any exist)
    for (final Genome g : genomes) {
      Species currentSpecies = null;
      final Genome genome = g;

      if (!Double.isNaN(genome.getAdjustedScore())
          && !Double.isInfinite(genome.getAdjustedScore())) {
        maxScore = Math.max(genome.getAdjustedScore(), maxScore);
      }

      for (final Species s : speciesCollection) {
        final double compatibility = getCompatibilityScore(genome,
            s.getLeader());

        if (compatibility <= this.compatibilityThreshold) {
          currentSpecies = s;
          addSpeciesMember(s, genome);
          genome.setSpecies(s);
          break;
        }
      }

      // if this genome did not fall into any existing species, create a
      // new species
      if (currentSpecies == null) {
        currentSpecies = new BasicSpecies(this.population, genome);
        this.population.getSpecies().add(currentSpecies);
      }
    }

    //
    double totalSpeciesScore = 0;
    for (final Species species : speciesCollection) {
      totalSpeciesScore += species.calculateShare(this.owner
          .getScoreFunction().shouldMinimize(), maxScore);
    }

    if (speciesCollection.size() == 0) {
      throw new EncogError("Can't speciate, there are no species.2");
    }
    if (totalSpeciesScore < Encog.DEFAULT_DOUBLE_EQUAL) {
      // This should not happen much, or if it does, only in the
      // beginning.
      // All species scored zero. So they are all equally bad. Just divide
      // up the right to produce offspring evenly.
      divideEven(speciesCollection);
    } else {
      // Divide up the number of offspring produced to the most fit
      // species.
      divideByFittestSpecies(speciesCollection, totalSpeciesScore);
    }

    levelOff();

  }

  /**
   * Determine how compatible two genomes are. More compatible genomes will be
   * placed into the same species. The lower the number, the more compatible.
   *
   * @param genome1
   *            The first genome.
   * @param genome2
   *            The second genome.
   * @return The compatability level.
   */
  public abstract double getCompatibilityScore(Genome genome1, Genome genome2);
}
TOP

Related Classes of org.encog.ml.ea.species.ThresholdSpeciation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.