/* * Artificial Intelligence for Humans * Volume 1: Fundamental Algorithms * Java Version * http://www.aifh.org * http://www.jeffheaton.com * * Code repository: * https://github.com/jeffheaton/aifh * Copyright 2013 by Jeff Heaton * * 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 com.heatonresearch.aifh.learning; import com.heatonresearch.aifh.learning.score.ScoreFunction; import com.heatonresearch.aifh.randomize.GenerateRandom; import com.heatonresearch.aifh.randomize.MersenneTwisterGenerateRandom; /** * Train a Machine Learning Algorithm using Simulated Annealing. Simulated Annealing is a Monte Carlo algorithm that * is based on annealing in metallurgy, a technique involving heating and controlled cooling of a material to increase * the size of its crystals and reduce their defects, both are attributes of the material that depend on its * thermodynamic free energy. * <p/> * The Simulated Annealing algorithm works by randomly changing a vector of doubles. This is the long term memory of * the Machine Learning algorithm. While this happens a temperature is slowly decreased. When this temperature is * higher, the Simulated Annealing algorithm is more likely to accept changes that have a higher error (or energy) * than the current state. * <p/> * There are several important components to any Simulated Learning Algorithm: * <p/> * First, the randomization technique. This is performed by the method performRandomize. To randomize differently, * override this method. * <p/> * Secondly, the cooling schedule. This determines how quickly the current temperature will fall. This is controlled * by the coolingSchedule. To define a different cooling schedule, override this method. * <p/> * Finally, the probability of accepting a higher-error (energy) solution. This is defined by a Probability * Distribution Function (PDF) contained in calcProbability. To define a different PDF, override this method. * <p/> * http://en.wikipedia.org/wiki/Simulated_annealing */ public class TrainAnneal implements LearningMethod { /** * The machine learning algorithm to optimize. */ private final MachineLearningAlgorithm algorithm; /** * The random number generator to use. */ private final GenerateRandom rnd = new MersenneTwisterGenerateRandom(); /** * The current error of best solution ever found. */ private double globalBestError = Double.POSITIVE_INFINITY; /** * The current best solution ever found. */ private final double[] globalBest; /** * The current error. */ private double currentError; /** * The scoring function, this determines the energy (error) of the current solution. */ private final ScoreFunction score; /** * The maximum number of iterations. */ private final int kMax; /** * The current iteration number. */ private int k; /** * The starting temperature. */ private final double startingTemperature; /** * The ending temperature. Do not set to zero, as many cooling schedules asymptotically approach zero. * Rather, use something close to zero, like 0.0001. */ private final double endingTemperature; /** * The current temperature. */ private double currentTemperature; /** * The number of random moves to try for each iteration. */ private int cycles = 100; /** * The probability for the last iteration cycle. */ private double lastProbability; /** * Construct the simulated annealing trainer. Use 1000 iterations and temperature from 400 to 0.0001. * * @param theAlgorithm The algorithm to optimize. * @param theScore The score function. */ public TrainAnneal(final MachineLearningAlgorithm theAlgorithm, final ScoreFunction theScore) { this(theAlgorithm, theScore, 100, 400, 0.0001); } /** * Construct the simulated annealing trainer. * * @param theAlgorithm The algorithm to optimize. * @param theScore The score function. * @param theKMax The max number of iterations. * @param theStartingTemperature The starting temperature. * @param theEndingTemperature The ending temperature. */ public TrainAnneal(final MachineLearningAlgorithm theAlgorithm, final ScoreFunction theScore, final int theKMax, final double theStartingTemperature, final double theEndingTemperature) { this.algorithm = theAlgorithm; this.score = theScore; this.kMax = theKMax; this.currentError = score.calculateScore(this.algorithm); this.startingTemperature = theStartingTemperature; this.endingTemperature = theEndingTemperature; this.globalBest = new double[theAlgorithm.getLongTermMemory().length]; System.arraycopy(this.algorithm.getLongTermMemory(), 0, this.globalBest, 0, this.globalBest.length); } /** * The cooling schedule. This is a Probability Distribution Function (PDF) that specifies the probability, * at a given temperature, of accepting a higher-energy move. * * @return The probability. */ public double coolingSchedule() { final double ex = (double) k / (double) kMax; return this.startingTemperature * Math.pow(this.endingTemperature / this.startingTemperature, ex); } /** * {@inheritDoc} */ @Override public void iteration() { final int len = this.algorithm.getLongTermMemory().length; k++; this.currentTemperature = coolingSchedule(); for (int cycle = 0; cycle < this.cycles; cycle++) { // backup current state final double[] oldState = new double[len]; System.arraycopy(this.algorithm.getLongTermMemory(), 0, oldState, 0, len); // randomize the method performRandomize(this.algorithm.getLongTermMemory()); // did we improve it? final double trialError = score.calculateScore(this.algorithm); // was this iteration an improvement? If so, always keep. boolean keep = false; if (trialError < this.currentError) { keep = true; } else { this.lastProbability = calcProbability(currentError, trialError, this.currentTemperature); if (this.lastProbability > this.rnd.nextDouble()) { keep = true; } } if (keep) { this.currentError = trialError; // better than global error if (trialError < this.globalBestError) { this.globalBestError = trialError; System.arraycopy(this.algorithm.getLongTermMemory(), 0, oldState, 0, len); System.arraycopy(this.algorithm.getLongTermMemory(), 0, this.globalBest, 0, len); } } else { System.arraycopy(oldState, 0, this.algorithm.getLongTermMemory(), 0, len); } } } /** * Randomly move to a new location. To specify a new randomization function, override this method. * * @param memory The long term memory. */ public void performRandomize(final double[] memory) { for (int i = 0; i < memory.length; i++) { final double d = this.rnd.nextGaussian() / 10; memory[i] += d; } } /** * @return True, if we have reached the max iterations. */ @Override public boolean done() { return k >= kMax; } /** * @return The error (or energy) from the last iteration. */ @Override public double getLastError() { return this.globalBestError; } /** * Calculate the probability that we will accept a move that takes us to a higher energy (higher error) * position. * * @param ecurrent The current energy. * @param enew The new energy if we move. * @param t The current temperature. * @return The probability. */ public double calcProbability(final double ecurrent, final double enew, final double t) { return Math.exp(-(Math.abs(enew - ecurrent) / t)); } /** * @return The current temperature. */ public double getCurrentTemperature() { return currentTemperature; } /** * @return The current iteration number. */ public int getK() { return k; } /** * @return The starting temperature. */ public double getStartingTemperature() { return startingTemperature; } /** * @return The ending temperature. */ public double getEndingTemperature() { return endingTemperature; } /** * @return The number of cycles per iteration. */ public int getCycles() { return cycles; } /** * @return The last probability. */ public double getLastProbability() { return lastProbability; } /** * Set the number of cycles per iteration. * * @param cycles The number of cycles per iteration. */ public void setCycles(final int cycles) { this.cycles = cycles; } /** * Copy the global best solution to the machine learning algorithm. It is very important to call this method. */ @Override public void finishTraining() { System.arraycopy(this.globalBest, 0, this.algorithm.getLongTermMemory(), 0, this.globalBest.length); } /** * {@inheritDoc} */ @Override public String getStatus() { final StringBuilder result = new StringBuilder(); result.append("k="); result.append(this.k); result.append(",kMax="); result.append(this.kMax); result.append(",t="); result.append(this.currentTemperature); result.append(",prob="); result.append(this.lastProbability); return result.toString(); } }