gov.sandia.cognition.learning.algorithm.annealing.SimulatedAnnealer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cognitive-foundry Show documentation
Show all versions of cognitive-foundry Show documentation
A single jar with all the Cognitive Foundry components.
/*
* File: SimulatedAnnealer.java
* Authors: Jonathan McClain, Justin Basilico, and Kevin R. Dixon
* Company: Sandia National Laboratories
* Project: Cognitive Foundry
*
* Copyright February 20, 2006, Sandia Corporation. Under the terms of Contract
* DE-AC04-94AL85000, there is a non-exclusive license for use of this work by
* or on behalf of the U.S. Government. Export of this program may require a
* license from the United States Government. See CopyrightHistory.txt for
* complete details.
*
*/
package gov.sandia.cognition.learning.algorithm.annealing;
import gov.sandia.cognition.algorithm.MeasurablePerformanceAlgorithm;
import gov.sandia.cognition.annotation.CodeReview;
import gov.sandia.cognition.annotation.CodeReviews;
import gov.sandia.cognition.learning.algorithm.AbstractAnytimeBatchLearner;
import gov.sandia.cognition.learning.algorithm.BatchCostMinimizationLearner;
import gov.sandia.cognition.learning.function.cost.CostFunction;
import gov.sandia.cognition.util.DefaultNamedValue;
import gov.sandia.cognition.util.NamedValue;
import gov.sandia.cognition.util.ObjectUtil;
import java.util.Random;
/**
* The SimulatedAnnealer class implements the simulated annealing algorithm
* using the provided cost function and perturbation function.
*
* Simulated annealing is attempts to find the minimum-cost parameters of
* a function using a stochastic hill climbing (descent, actually). A
* lower-cost parameter tweak is always taken, but a higher-cost tweak is
* taken with a probability dictated by an "annealing" schedule. This
* stochastic step toward badness is an attempt to find global minima, instead
* of your vanilla-flavored local minima. Thus, SA only relies on function
* evaluations, not needed the gradient.
*
* Here's my opinion on simulated annealing: it is a method of absolute last
* resort, and I have trouble thinking of a more general, brain-dead approach.
* Use SA only when you are stranded on a desolate glacier, one arm
* trapped under a boulder, and your pocket knife is out of reach.
*
* If you are still reading this, then I assume you still think you need SA
* because you can only evaluate a function against a cost function and,
* oh my goodness, you have a huge search space. At least, you should be using
* Genetic Algorithms. Even better, try Powell's method, which is a powerful
* minimization technique that only relies on function evaluations. Think of it
* as SA, but smart. Generally better than Powell's method is Conjugate
* Gradient with automated gradient approximation, which only relies on
* function evaluations and automatically estimates the gradient for you.
* If you can store the (approximated) Jacobian in memory, then the best
* technique is usually BFGS with automated gradient approximation (sometimes
* Levenberg-Marquardt Estimation is as good as BFGS, but usually not).
*
* If you're still going to use SA, then may the optimization gods have mercy
* on your soul.
*
* @param Class returned from the {@code learn()} method, such as a
* {@code FeedforwardNeuralNetwork}, for example
* @param Cost parameters given to the {@code learn()} method, such
* as {@code Collection}, for example
* @author Jonathan McClain
* @author Justin Basilico
* @author Kevin R. Dixon
* @since 1.0
*/
@CodeReviews(
reviews={
@CodeReview(
reviewer="Kevin R. Dixon",
date="2008-07-22",
changesNeeded=false,
comments={
"Moved previous code review to annotation.",
"Added HTML tags to javadoc.",
"Fixed a few typos in javadoc.",
"Code looks fine."
}
)
,
@CodeReview(
reviewer="Justin Basilico",
date="2006-10-02",
changesNeeded=false,
comments={
"Did some reformatting of the code.",
"Added missing documentation.",
"Cleaned up the use of default parameter values."
}
)
}
)
public class SimulatedAnnealer
extends AbstractAnytimeBatchLearner
implements BatchCostMinimizationLearner,
MeasurablePerformanceAlgorithm
{
/** The default starting temperature for the algorithm, {@value}. */
public static final double DEFAULT_STARTING_TEMPERATURE = 1.0;
/** The default cooling factor for learning, {@value}. */
public static final double DEFAULT_COOLING_FACTOR = 0.1;
/** The default number of maximum iterations, {@value}. */
public static final int DEFAULT_MAX_ITERATIONS = 1000;
/** The cost function to minimize. */
private CostFunction super AnnealedType, ? super CostParametersType> cost;
/** The perturbing function to use to perturb the objects. */
private Perturber perturber;
/** The current temperature. */
private double temperature;
/** The maximum number of iterations to go without improvement before
* stopping. */
private int maxIterationsWithoutImprovement;
/** The number of iterations since the last improvement. */
private int iterationsWithoutImprovement;
/** The cooling factor applied at each step. */
private double coolingFactor;
/** The random number generator to use. */
private Random random;
/** The best state found so far. */
private AnnealedType bestSoFar;
/** The score for the best state found so far. */
private double bestSoFarScore;
/** The current state. */
private AnnealedType current;
/** The score of the current state. */
private double currentScore;
/**
* Creates a new instance of SimulatedAnnealer.
*
* @param initial Initial candidate to consider
* @param perturber The perturbing function to use.
* @param cost The cost function to minimize.
*/
public SimulatedAnnealer(
AnnealedType initial,
Perturber perturber,
CostFunction super AnnealedType, ? super CostParametersType> cost )
{
this( initial, perturber, cost, DEFAULT_MAX_ITERATIONS );
}
/**
* Creates a new instance of SimulatedAnnealer.
*
* @param initial Initial candidate to consider
* @param perturber The perturbing function to use.
* @param cost The cost function to minimize.
* @param maxIterations The maximum number of iterations to perform.
*/
public SimulatedAnnealer(
AnnealedType initial,
Perturber perturber,
CostFunction super AnnealedType, ? super CostParametersType> cost,
int maxIterations )
{
this( initial, perturber, cost, maxIterations, 1 + maxIterations / 10 );
}
/**
* Creates a new instance of SimulatedAnnealer.
*
* @param initial Initial candidate to consider
* @param perturber The perturbing function to use.
* @param cost The cost function to minimize.
* @param maxIterations The maximum number of iterations to perform.
* @param maxIterationsWithoutImprovement The maximum number of iterations
* to go without improvement before stopping.
*/
public SimulatedAnnealer(
AnnealedType initial,
Perturber perturber,
CostFunction super AnnealedType, ? super CostParametersType> cost,
int maxIterations,
int maxIterationsWithoutImprovement )
{
super( maxIterations );
this.setCostFunction( cost );
this.setPerturber( perturber );
this.setTemperature( DEFAULT_STARTING_TEMPERATURE );
this.setMaxIterationsWithoutImprovement(
maxIterationsWithoutImprovement );
this.setIterationsWithoutImprovement( 0 );
this.setCoolingFactor( DEFAULT_COOLING_FACTOR );
this.setRandom( new Random() );
this.setBestSoFar( null );
this.setBestSoFarScore( 0.0 );
this.setCurrent( initial );
this.setCurrentScore( 0.0 );
}
@Override
public SimulatedAnnealer clone()
{
@SuppressWarnings("unchecked")
final SimulatedAnnealer result =
(SimulatedAnnealer) super.clone();
result.cost = ObjectUtil.cloneSafe(this.cost);
result.perturber = ObjectUtil.cloneSmart(this.perturber);
result.random = ObjectUtil.deepCopy(this.random);
result.bestSoFar = null;
result.bestSoFarScore = 0.0;
result.current = null;
result.currentScore = 0.0;
return result;
}
protected boolean initializeAlgorithm()
{
this.setIteration( 0 );
this.setIterationsWithoutImprovement( 0 );
this.setCurrentScore(
this.getCostFunction().evaluate( this.getCurrent() ) );
this.setBestSoFar( this.getCurrent() );
this.setBestSoFarScore( this.getCurrentScore() );
return true;
}
/**
* Takes one step in the Simulated Annealing process.
*
* @return Boolean indicating whether the SA process should continue (i.e.
* no stopping conditions have been met).
*/
protected boolean step()
{
// Perturb the current value
AnnealedType next = this.getPerturber().perturb( this.getCurrent() );
// Score the perturbed value
double nextScore = this.getCostFunction().evaluate( next );
// Check to see if this is the best so far
if (nextScore < this.getBestSoFarScore())
{
this.setBestSoFar( next );
this.setBestSoFarScore( nextScore );
// We have improved, so reset.
this.setIterationsWithoutImprovement( 0 );
}
else
{
this.setIterationsWithoutImprovement(
this.getIterationsWithoutImprovement() + 1 );
}
// Compute the difference in scores
double scoreDiff = nextScore - currentScore;
if ((scoreDiff <= 0) ||
(this.getRandom().nextDouble() < Math.exp( -scoreDiff / this.getTemperature() )))
{
// Use the perturbed value.
this.setCurrent( next );
this.setCurrentScore( nextScore );
}
// Else keep the old value.
// Decrease the temperature.
this.setTemperature( this.getCoolingFactor() * this.getTemperature() );
return (this.getIterationsWithoutImprovement() <=
this.getMaxIterationsWithoutImprovement());
}
public CostFunction super AnnealedType, ? super CostParametersType> getCostFunction()
{
return this.cost;
}
/**
* Gets the perturber.
*
* @return The perturber.
*/
public Perturber getPerturber()
{
return this.perturber;
}
/**
* Gets the current temperature of the system.
*
* @return The current temperature.
*/
protected double getTemperature()
{
return this.temperature;
}
/**
* Gets the maximum number of iterations to go without improvement before
* stopping.
*
* @return The current maximum.
*/
public int getMaxIterationsWithoutImprovement()
{
return this.maxIterationsWithoutImprovement;
}
/**
* Gets the current number of iterations without improvement.
*
* @return The current iteration.
*/
protected int getIterationsWithoutImprovement()
{
return this.iterationsWithoutImprovement;
}
/**
* Gets the cooling factor.
*
* @return The cooling factor.
*/
public double getCoolingFactor()
{
return this.coolingFactor;
}
/**
* Gets the random number generator.
*
* @return The random number generator.
*/
public Random getRandom()
{
return this.random;
}
/**
* Gets the best state found so far.
*
* @return The best state.
*/
protected AnnealedType getBestSoFar()
{
return this.bestSoFar;
}
/**
* Gets the score for the best state found so far.
*
* @return The score.
*/
protected double getBestSoFarScore()
{
return this.bestSoFarScore;
}
/**
* Gets the current state of the system.
*
* @return The current state.
*/
protected AnnealedType getCurrent()
{
return this.current;
}
/**
* Gets the score of the current state.
*
* @return The score.
*/
protected double getCurrentScore()
{
return this.currentScore;
}
/**
* Sets the cost function.
*
* @param cost The new cost function.
*/
public void setCostFunction(
CostFunction super AnnealedType, ? super CostParametersType> cost )
{
this.cost = cost;
}
/**
* Sets the perturber.
*
* @param perturber The new perturber.
*/
public void setPerturber(
Perturber perturber )
{
this.perturber = perturber;
}
/**
* Sets the current temperature of the system.
*
* @param temperature The new temperature.
*/
protected void setTemperature(
double temperature )
{
this.temperature = temperature;
}
/**
* Sets the maximum number of iterations to go without improvement before
* stopping.
*
*
* @param maxIterationsWithoutImprovement The new maximum.
*/
public void setMaxIterationsWithoutImprovement(
int maxIterationsWithoutImprovement )
{
this.maxIterationsWithoutImprovement = maxIterationsWithoutImprovement;
}
/**
* Sets the current number of iterations without improvement.
*
* @param iterationsWithoutImprovement The new iteration.
*/
protected void setIterationsWithoutImprovement(
int iterationsWithoutImprovement )
{
this.iterationsWithoutImprovement = iterationsWithoutImprovement;
}
/**
* Sets the cooling factor.
*
* @param coolingFactor The new cooling factor.
*/
public void setCoolingFactor(
double coolingFactor )
{
if (coolingFactor <= 0.0 || coolingFactor > 1.0)
{
throw new IllegalArgumentException( "The cooling factor must be" + "greater than zero and less than or equal to one." );
}
this.coolingFactor = coolingFactor;
}
/**
* Sets the random number generator.
*
* @param random The new random number generator.
*/
public void setRandom(
Random random )
{
this.random = random;
}
/**
* Sets the best state found so far.
*
* @param bestSoFar The new best state.
*/
protected void setBestSoFar(
AnnealedType bestSoFar )
{
this.bestSoFar = bestSoFar;
}
/**
* Sets the score for the best state found so far.
*
* @param bestSoFarScore The new score.
*/
protected void setBestSoFarScore(
double bestSoFarScore )
{
this.bestSoFarScore = bestSoFarScore;
}
/**
* Sets the current state of the system.
*
* @param current The new current state.
*/
protected void setCurrent(
AnnealedType current )
{
this.current = current;
}
/**
* Sets the score of the current state.
*
* @param currentScore The new score.
*/
protected void setCurrentScore(
double currentScore )
{
this.currentScore = currentScore;
}
protected void cleanupAlgorithm()
{
}
public AnnealedType getResult()
{
return this.getBestSoFar();
}
/**
* Gets the performance, which is the best score so far.
*
* @return The performance of the algorithm.
*/
public NamedValue getPerformance()
{
return new DefaultNamedValue("score", this.getBestSoFarScore());
}
}