/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.tools.math.optimization.ec.es; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import com.rapidminer.datatable.DataTable; import com.rapidminer.datatable.SimpleDataTable; import com.rapidminer.datatable.SimpleDataTableRow; import com.rapidminer.gui.plotter.SimplePlotterDialog; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.performance.PerformanceVector; import com.rapidminer.tools.LoggingHandler; import com.rapidminer.tools.RandomGenerator; import com.rapidminer.tools.math.optimization.Optimization; /** * Evolutionary Strategy approach for all real-valued optimization tasks. * * @author Ingo Mierswa * @version $Id: ESOptimization.java,v 1.8 2008/07/31 17:07:14 ingomierswa Exp $ */ public abstract class ESOptimization implements Optimization { /** The names of all available selection schemes. */ public static final String[] SELECTION_TYPES = { "uniform", "cut", "roulette wheel", "stochastic universal sampling", "Boltzmann", "rank", "tournament", "non dominated sorting" }; /** Indicates a uniform sampling selection scheme. */ public static final int UNIFORM_SELECTION = 0; /** Indicates a cut selection scheme. */ public static final int CUT_SELECTION = 1; /** Indicates a roulette wheel selection scheme. */ public static final int ROULETTE_WHEEL = 2; /** Indicates a stochastic universal sampling selection scheme. */ public static final int STOCHASTIC_UNIVERSAL = 3; /** Indicates a Boltzmann selection scheme. */ public static final int BOLTZMANN_SELECTION = 4; /** Indicates a rank based selection scheme. */ public static final int RANK_SELECTION = 5; /** Indicates a tournament selection scheme. */ public static final int TOURNAMENT_SELECTION = 6; /** Indicates a multi-objective selection scheme (NSGA II). */ public static final int NON_DOMINATED_SORTING_SELECTION = 7; /** The names of the mutation types. */ public static final String[] MUTATION_TYPES = { "none", "gaussian_mutation", "switching_mutation", "sparsity_mutation" }; /** Indicates no mutation. */ public static final int NO_MUTATION = 0; /** Indicates a gaussian mutation. */ public static final int GAUSSIAN_MUTATION = 1; /** Indicates a switching mutation. */ public static final int SWITCHING_MUTATION = 2; /** Indicates a hybrid between switching mutation and Gaussian mutation. */ public static final int SPARSITY_MUTATION = 3; /** The names of the initialization types. */ public static final String[] POPULATION_INIT_TYPES = { "random", "min", "max" }; /** Indicates that the start population should be randomly initialized. */ public static final int INIT_TYPE_RANDOM = 0; /** Indicates that the start population should be initialized with the minimum value. */ public static final int INIT_TYPE_MIN = 1; /** Indicates that the start population should be initialized with the maximum value. */ public static final int INIT_TYPE_MAX = 2; /** Indicates that the start population should be initialized with one. */ public static final int INIT_TYPE_ONE = 3; /** Indicates that the start population should be initialized with zero. */ public static final int INIT_TYPE_ZERO = 4; /** This parameter indicates the minimum value for all genes. */ private double[] min; /** This parameter indicates the maximum value for all genes. */ private double[] max; /** The value types, either DOUBLE (default) or INT. */ private OptimizationValueType[] valueTypes; /** The number of individuals. */ private int populationSize; /** The dimension of each individual. */ private int individualSize; /** The maximum number of generations. */ private int maxGenerations; /** The maximum numbers of generations without improvement. */ private int generationsWithoutImprovement; /** The type of start population initialization. */ private int initType = INIT_TYPE_RANDOM; /** The type of the mutation. */ //private int mutationType = GAUSSIAN_MUTATION; /** The population plotter (if enabled). */ private PopulationPlotter populationPlotter = null; /** The mutation operator. */ private Mutation mutation; /** The current population. */ private Population population; /** Population operators. */ private Collection<PopulationOperator> popOps; /** Indicates if a convergence plot should be drawn. */ private boolean showPlot = false; /** This field counts the total number of evaluations during optimization. */ private int totalEvalCounter = 0; /** This field counts the number of actually calculated evaluations (unchanged individuals do not have * to be re-evaluated). */ private int actualEvalCounter = 0; /** The random number generator. */ private RandomGenerator random; private LoggingHandler logging; /** Creates a new evolutionary SVM optimization. */ public ESOptimization(double minValue, double maxValue, int populationSize, int individualSize, int initType, // population paras int maxGenerations, int generationsWithoutImprovement, // GA paras int selectionType, double tournamentFraction, boolean keepBest, // selection paras int mutationType, // type of mutation double crossoverProb, boolean showPlot, RandomGenerator random, LoggingHandler logging) { this(createBoundArray(minValue, individualSize), createBoundArray(maxValue, individualSize), populationSize, individualSize, initType, maxGenerations, generationsWithoutImprovement, selectionType, tournamentFraction, keepBest, mutationType, Double.NaN, crossoverProb, showPlot, random, logging); } /** Creates a new evolutionary SVM optimization. */ public ESOptimization(double[] minValues, double[] maxValues, int populationSize, int individualSize, int initType, // population paras int maxGenerations, int generationsWithoutImprovement, // GA paras int selectionType, double tournamentFraction, boolean keepBest, // selection paras int mutationType, // type of mutation double defaultSigma, double crossoverProb, boolean showPlot, RandomGenerator random, LoggingHandler logging) { this.logging = logging; this.random = random; this.showPlot = showPlot; this.populationSize = populationSize; this.individualSize = individualSize; this.min = minValues; this.max = maxValues; this.valueTypes = new OptimizationValueType[individualSize]; for (int i = 0; i < this.valueTypes.length; i++) { this.valueTypes[i] = OptimizationValueType.VALUE_TYPE_DOUBLE; } this.initType = initType; this.maxGenerations = maxGenerations; this.generationsWithoutImprovement = generationsWithoutImprovement < 1 ? this.maxGenerations : generationsWithoutImprovement; //this.mutationType = mutationType; // population operators popOps = new LinkedList<PopulationOperator>(); switch (selectionType) { case UNIFORM_SELECTION: popOps.add(new UniformSelection(populationSize, keepBest, random)); break; case CUT_SELECTION: popOps.add(new CutSelection(populationSize)); break; case ROULETTE_WHEEL: popOps.add(new RouletteWheel(populationSize, keepBest, random)); break; case STOCHASTIC_UNIVERSAL: popOps.add(new StochasticUniversalSampling(populationSize, keepBest, random)); break; case BOLTZMANN_SELECTION: popOps.add(new BoltzmannSelection(populationSize, 1.0d, this.maxGenerations, true, keepBest, random)); break; case RANK_SELECTION: popOps.add(new RankSelection(populationSize, keepBest, random)); break; case TOURNAMENT_SELECTION: popOps.add(new TournamentSelection(populationSize, tournamentFraction, keepBest, random)); break; case NON_DOMINATED_SORTING_SELECTION: popOps.add(new NonDominatedSortingSelection(populationSize)); this.populationPlotter = new PopulationPlotter(); popOps.add(this.populationPlotter); break; } popOps.add(new Crossover(crossoverProb, random)); switch (mutationType) { case GAUSSIAN_MUTATION: double[] sigma = new double[this.min.length]; if (!Double.isNaN(defaultSigma)) { for (int s = 0; s < sigma.length; s++) { sigma[s] = defaultSigma; } } else { for (int s = 0; s < sigma.length; s++) { sigma[s] = (this.max[s] - this.min[s]) / 100.0d; } } GaussianMutation gm = new GaussianMutation(sigma, this.min, this.max, this.valueTypes, random); popOps.add(gm); popOps.add(new VarianceAdaption(gm, individualSize, this.logging)); this.mutation = gm; break; case SWITCHING_MUTATION: this.mutation = new SwitchingMutation(1.0d / individualSize, this.min, this.max, this.valueTypes, random); popOps.add(this.mutation); break; case SPARSITY_MUTATION: this.mutation = new SparsityMutation(1.0d / individualSize, this.min, this.max, this.valueTypes, random); popOps.add(this.mutation); break; default: break; // no mutation at all } } private static double[] createBoundArray(double bound, int size) { double[] result = new double[size]; for (int i = 0; i < result.length; i++) result[i] = bound; return result; } /** * Subclasses must implement this method to calculate the fitness of the * given individual. Please note that null might be returned for non-valid * individuals. The fitness will be maximized. */ public abstract PerformanceVector evaluateIndividual(Individual individual) throws OperatorException; /** * This method is invoked after each evaluation. The default implementation * does nothing but subclasses might implement this method to support online * plotting or logging. */ public void nextIteration() throws OperatorException {} public double getMin(int index) { return min[index]; } public double getMax(int index) { return max[index]; } public void setMin(int index, double v) { this.min[index] = v; /* if (mutationType == GAUSSIAN_MUTATION) recalculateSigma((GaussianMutation)this.mutation, this.individualSize); */ } public void setMax(int index, double v) { this.max[index] = v; /* if (mutationType == GAUSSIAN_MUTATION) recalculateSigma((GaussianMutation)this.mutation, this.individualSize); */ } /* protected void recalculateSigma(GaussianMutation mutation, int individualSize) { double[] sigma = new double[individualSize]; for (int s = 0; s < sigma.length; s++) sigma[s] = Math.abs(this.max[s] - this.min[s]) / 100.0d; mutation.setSigma(sigma); } */ public OptimizationValueType getValueType(int index) { return this.valueTypes[index]; } public void setValueType(int index, OptimizationValueType type) { this.valueTypes[index] = type; mutation.setValueType(index, type); } // ================================================================================ // O P T I M I Z A T I O N // ================================================================================ /** * Starts the optimization. */ public void optimize() throws OperatorException { this.totalEvalCounter = 0; this.actualEvalCounter = 0; switch (initType) { case INIT_TYPE_RANDOM: this.population = createRandomStartPopulation(); break; case INIT_TYPE_MIN: this.population = createMinStartPopulation(); break; case INIT_TYPE_MAX: this.population = createMaxStartPopulation(); break; case INIT_TYPE_ONE: this.population = createFixedStartPopulation(1.0d); break; case INIT_TYPE_ZERO: this.population = createFixedStartPopulation(0.0d); break; default: break; // this cannot happen } evaluate(population); DataTable dataTable = null; SimplePlotterDialog plotter = null; if (showPlot) { dataTable = new SimpleDataTable("Fitness vs. Generations", new String[] { "Generations", "Best Fitness", "Current Fitness" }); plotter = new SimplePlotterDialog(dataTable, false); plotter.setXAxis(0); plotter.plotColumn(1, true); plotter.plotColumn(2, true); plotter.setVisible(true); dataTable.add(new SimpleDataTableRow(new double[] { 0.0d, population.getBestEver().getFitness().getMainCriterion().getFitness(), population.getCurrentBest().getFitness().getMainCriterion().getFitness() })); } while (true) { if (population.getGeneration() >= maxGenerations) { logging.log("ES finished: maximum number of iterations reached."); break; } if (population.getGenerationsWithoutImprovement() > generationsWithoutImprovement) { logging.log("ES converged in generation " + population.getGeneration() + ": No improvement in last " + generationsWithoutImprovement + " generations."); break; } Iterator i = popOps.iterator(); while (i.hasNext()) { ((PopulationOperator) i.next()).operate(population); } evaluate(population); if (showPlot) dataTable.add(new SimpleDataTableRow(new double[] { population.getGeneration(), population.getBestEver().getFitness().getMainCriterion().getFitness(), population.getCurrentBest().getFitness().getMainCriterion().getFitness() })); population.nextGeneration(); nextIteration(); } if (showPlot) { plotter.dispose(); } if (populationPlotter != null) { this.populationPlotter.setCreateOtherPlottersEnabled(true); } logging.log("ES Evaluations: " + actualEvalCounter + " / " + totalEvalCounter); } /** Evaluates the individuals of the given population. */ protected void evaluate(Population population) throws OperatorException { Individual currentBest = null; for (int i = population.getNumberOfIndividuals() - 1; i >= 0; i--) { Individual current = population.get(i); if (current.getFitness() == null) { PerformanceVector fitness = evaluateIndividual(current); if (fitness != null) { current.setFitness(fitness); if ((currentBest == null) || (fitness.getMainCriterion().getFitness() > currentBest.getFitness().getMainCriterion().getFitness())) { currentBest = (Individual) current.clone(); currentBest.setFitness(current.getFitness()); } } else { population.remove(current); } actualEvalCounter++; } totalEvalCounter++; } if (currentBest != null) { population.setCurrentBest(currentBest); Individual bestEver = population.getBestEver(); if ((bestEver == null) || (currentBest.getFitness().getMainCriterion().getFitness() > bestEver.getFitness().getMainCriterion().getFitness())) { Individual bestEverClone = (Individual) currentBest.clone(); bestEverClone.setFitness(currentBest.getFitness()); population.setBestEver(bestEverClone); } } } /** Returns the current generation. */ public int getGeneration() { return population.getGeneration(); } /** Returns the best fitness in the current generation. */ public double getBestFitnessInGeneration() { Individual individual = population.getCurrentBest(); if (individual != null) { return individual.getFitnessValues()[0]; } else { return Double.NaN; } } /** Returns the best fitness ever. */ public double getBestFitnessEver() { Individual individual = population.getBestEver(); if (individual != null) { return individual.getFitnessValues()[0]; } else { return Double.NaN; } } /** Returns the best performance vector ever. */ public PerformanceVector getBestPerformanceEver() { Individual individual = population.getBestEver(); if (individual != null) { return individual.getFitness(); } else { return null; } } public Population getPopulation() { return this.population; } /** * Returns the best values ever. Use this method after optimization to get * the best result. Might returns null if the optimization did not work. */ public double[] getBestValuesEver() { Individual individual = population.getBestEver(); if (individual != null) { return individual.getValues(); } else { return null; } } // ================================================================================ // S T A R T P O P U L A T I O N S // ================================================================================ /** Randomly creates the initial population. */ private Population createRandomStartPopulation() { Population population = new Population(); for (int p = 0; p < this.populationSize; p++) { double[] alphas = new double[this.individualSize]; for (int j = 0; j < alphas.length; j++) { if (getValueType(j).equals(OptimizationValueType.VALUE_TYPE_INT)) { alphas[j] = (int)Math.round(random.nextDoubleInRange(this.min[j], this.max[j])); } else if (getValueType(j).equals(OptimizationValueType.VALUE_TYPE_BOUNDS)) { boolean upper = random.nextBoolean(); if (upper) { alphas[j] = this.max[j]; } else { alphas[j] = this.min[j]; } } else { alphas[j] = random.nextDoubleInRange(this.min[j], this.max[j]); } } population.add(new Individual(alphas)); } return population; } /** Randomly creates the initial population. */ private Population createMinStartPopulation() { Population population = new Population(); for (int p = 0; p < this.populationSize; p++) { double[] alphas = new double[this.individualSize]; for (int j = 0; j < alphas.length; j++) alphas[j] = this.min[j]; population.add(new Individual(alphas)); } return population; } /** Randomly creates the initial population. */ private Population createMaxStartPopulation() { Population population = new Population(); for (int p = 0; p < this.populationSize; p++) { double[] alphas = new double[this.individualSize]; for (int j = 0; j < alphas.length; j++) alphas[j] = this.max[j]; population.add(new Individual(alphas)); } return population; } /** Randomly creates the initial population. */ private Population createFixedStartPopulation(double fixedValue) { Population population = new Population(); for (int p = 0; p < this.populationSize; p++) { double[] alphas = new double[this.individualSize]; for (int j = 0; j < alphas.length; j++) alphas[j] = fixedValue; population.add(new Individual(alphas)); } return population; } /** * Getter and setter for subclasses **/ public void increaseCurrentEvaluationCounter() { this.actualEvalCounter++; } public void increaseTotalEvaluationCounter() { this.totalEvalCounter++; } }