/* * 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.discrete; import com.heatonresearch.aifh.randomize.GenerateRandom; import com.heatonresearch.aifh.randomize.MersenneTwisterGenerateRandom; /** * Perform discrete simulated annealing. Discrete simulated annealing involves a problem with * a finite number of positions (or potential solutions). */ public abstract class DiscreteAnneal { /** * The random number generator. */ private final GenerateRandom rnd = new MersenneTwisterGenerateRandom(); /** * The current global best score. The global best score is the best score that has been found over all of the * iterations. */ private double globalBestScore = Double.POSITIVE_INFINITY; /** * The current score. */ private double currentScore; /** * The maximum number of iterations to try. */ private final int kMax; /** * The current iteration. */ private int k; /** * The starting temperature. */ private final double startingTemperature; /** * The ending temperature. */ private final double endingTemperature; /** * The current temperature. */ private double currentTemperature; /** * The number of cycles to try at each temperature. */ private int cycles = 100; /** * The last probability of accepting a new non-improving move. */ private double lastProbability; /** * Construct the Simulated Annealing trainer. * * @param theKMax The maximum number of iterations. * @param theStartingTemperature The starting temperature. * @param theEndingTemperature The ending temperature. */ public DiscreteAnneal(final int theKMax, final double theStartingTemperature, final double theEndingTemperature) { this.kMax = theKMax; this.startingTemperature = theStartingTemperature; this.endingTemperature = theEndingTemperature; } /** * @return The correct temperature for the current iteration. */ public double coolingSchedule() { final double ex = (double) k / (double) kMax; return this.startingTemperature * Math.pow(this.endingTemperature / this.startingTemperature, ex); } /** * Perform one training iteration. This will execute the specified number of cycles at the current * temperature. */ public void iteration() { // Is this the first time through, if so, then setup. if (k == 0) { this.currentScore = evaluate(); foundNewBest(); this.globalBestScore = this.currentScore; } // incrament the current iteration counter k++; // obtain the correct temperature this.currentTemperature = coolingSchedule(); // perform the specified number of cycles for (int cycle = 0; cycle < this.cycles; cycle++) { // backup current state backupState(); // randomize the method moveToNeighbor(); // did we improve it? Only keep the new method if it improved (greedy). final double trialScore = evaluate(); // was this iteration an improvement? If so, always keep. boolean keep = false; if (trialScore < this.currentScore) { // it was better, so always keep it keep = true; } else { // it was worse, so we might keep it this.lastProbability = calcProbability(currentScore, trialScore, this.currentTemperature); if (this.lastProbability > this.rnd.nextDouble()) { keep = true; } } // should we keep this position? if (keep) { this.currentScore = trialScore; // better than global error if (trialScore < this.globalBestScore) { this.globalBestScore = trialScore; foundNewBest(); } } else { // do not keep this position restoreState(); } } } /** * Backup the current position (or state). */ public abstract void backupState(); /** * Restore the current position (or state). */ public abstract void restoreState(); /** * Handle the fact that we found a new global best. */ public abstract void foundNewBest(); /** * Move to a neighbor position. */ public abstract void moveToNeighbor(); /** * Evaluate the current position. * * @return The score. */ public abstract double evaluate(); /** * @return True, if training has reached the last iteration. */ public boolean done() { return k >= kMax; } /** * @return The best score found so far. */ public double getBestScore() { return this.globalBestScore; } /** * Calculate the probability that a worse solution will be accepted. The higher the temperature the more likely * this will happen. * * @param ecurrent The current energy (or score/error). * @param enew The new energy (or score/error). * @param t The current temperature. * @return The probability of accepting a worse solution. */ public double calcProbability(final double ecurrent, final double enew, final double t) { return Math.exp(-(Math.abs(enew - ecurrent) / t)); } /** * @return The current iteration. */ public int getK() { return this.k; } /** * @return The number of cycles per iteration. */ public int getCycles() { return cycles; } /** * Set the number of cycles. * * @param cycles The number of cycles per iteration. */ public void setCycles(final int cycles) { this.cycles = cycles; } /** * @return Returns the current status of the algorithm. */ 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(); } }