/* * Artificial Intelligence for Humans * Volume 2: Nature Inspired Algorithms * Java Version * http://www.aifh.org * http://www.jeffheaton.com * * Code repository: * https://github.com/jeffheaton/aifh * * Copyright 2014 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.general.VectorAlgebra; import com.heatonresearch.aifh.learning.score.ScoreFunction; import com.heatonresearch.aifh.randomize.GenerateRandom; import com.heatonresearch.aifh.randomize.MersenneTwisterGenerateRandom; /** * Iteratively trains a population by applying * particle swarm optimisation (PSO). * <p/> * Based on Encog contribution by: * Geoffroy Noel, https://github.com/goffer-looney * <p/> * References: * James Kennedy and Russell C. Eberhart, Particle swarm optimization, * Proceedings of the IEEE International Conference on Neural Networks, * 1995, pp. 1942-1948 * * @author Geoffroy Noel */ public class TrainPSO implements LearningMethod { /** * The machine learning algorithm to optimize. */ private final MLMethod[] particles; /** * The random number generator to use. */ private final GenerateRandom rnd = new MersenneTwisterGenerateRandom(); /** * The current error. */ private double currentError; /** * The scoring function, this determines the energy (error) of the current solution. */ private final ScoreFunction score; /** * Swarm state and memories. */ protected double[][] velocities; protected double[][] bestVectors; protected double[] bestScores; protected int bestVectorIndex; /** * Although this is redundant with m_bestVectors[bestVectorIndex], * bestVectors[bestVectorIndex] is not thread safe. */ private double[] bestVector; // Determines the size of the search space. // The position components of particle will be bounded to // [-maxPos, maxPos] // A well chosen range can improve the performance. // -1 is a special value that represents boundless search space. /** * */ protected double maxPosition = -1; // Maximum change one particle can take during one iteration. // Imposes a limit on the maximum absolute value of the velocity // components of a particle. // Affects the granularity of the search. // If too high, particle can fly past optimum solution. // If too low, particle can get stuck in local minima. // Usually set to a fraction of the dynamic range of the search // space (10% was shown to be good for high dimensional problems). // -1 is a special value that represents boundless velocities. protected double maxVelocity = 2; // c1, cognitive learning rate >= 0 // tendency to return to personal best position protected double c1 = 2.0; // c2, social learning rate >= 0 // tendency to move towards the swarm best position protected double c2 = 2.0; private double bestScore; // w, inertia weight. // Controls global (higher value) vs local exploration // of the search space. // Analogous to temperature in simulated annealing. // Must be chosen carefully or gradually decreased over time. // Value usually between 0 and 1. protected double inertiaWeight = 0.4; public TrainPSO(final MLMethod[] theParticles, final ScoreFunction theCalculateScore) { this.particles = theParticles; this.score = theCalculateScore; int vectorSize = theParticles[0].getLongTermMemory().length; int particleCount = theParticles.length; this.bestVectors = new double[particleCount][vectorSize]; this.velocities = new double[particleCount][vectorSize]; this.bestScores = new double[particleCount]; this.bestVectorIndex = -1; this.bestVector = new double[vectorSize]; for (final double[] velocity : this.velocities) { VectorAlgebra.randomise(this.rnd, velocity, this.maxVelocity); } } /** * Update the velocity, position and personal * best position of a particle * * @param particleIndex index of the particle in the swarm */ protected void updateParticle(int particleIndex) { double[] particlePosition = this.particles[particleIndex].getLongTermMemory(); updateVelocity(particleIndex); // velocity clamping VectorAlgebra.clampComponents(velocities[particleIndex], maxVelocity); // new position (Xt = Xt-1 + Vt) VectorAlgebra.add(particlePosition, velocities[particleIndex]); // pin the particle against the boundary of the search space. // (only for the components exceeding maxPosition) VectorAlgebra.clampComponents(particlePosition, maxPosition); updatePersonalBestPosition(particleIndex, particlePosition); } /** * Update the velocity of a particle * * @param particleIndex index of the particle in the swarm */ protected void updateVelocity(int particleIndex) { double[] particlePosition = this.particles[particleIndex].getLongTermMemory(); double[] vtmp = new double[particlePosition.length]; // Standard PSO formula // inertia weight VectorAlgebra.mul(velocities[particleIndex], inertiaWeight); // cognitive term VectorAlgebra.copy(vtmp, bestVectors[particleIndex]); VectorAlgebra.sub(vtmp, particlePosition); VectorAlgebra.mulRand(this.rnd, vtmp, this.c1); VectorAlgebra.add(velocities[particleIndex], vtmp); // social term if (particleIndex != bestVectorIndex) { VectorAlgebra.copy(vtmp, bestVector); VectorAlgebra.sub(vtmp, particlePosition); VectorAlgebra.mulRand(this.rnd, vtmp, c2); VectorAlgebra.add(velocities[particleIndex], vtmp); } } /** * Update the personal best position of a particle. * * @param particleIndex index of the particle in the swarm * @param particlePosition the particle current position vector */ protected void updatePersonalBestPosition(int particleIndex, double[] particlePosition) { // set the network weights and biases from the vector double score = this.score.calculateScore(this.particles[particleIndex]); // update the best vectors (g and i) if ((bestScores[particleIndex] == 0) || isScoreBetter(score, bestScores[particleIndex])) { bestScores[particleIndex] = score; VectorAlgebra.copy(bestVectors[particleIndex], particlePosition); } } /** * Update the swarm's best position */ protected void updateGlobalBestPosition() { boolean bestUpdated = false; for (int i = 0; i < this.particles.length; i++) { if ((bestVectorIndex == -1) || isScoreBetter(bestScores[i], bestScores[bestVectorIndex])) { bestVectorIndex = i; bestUpdated = true; } } if (bestUpdated) { VectorAlgebra.copy(bestVector, bestVectors[bestVectorIndex]); this.bestScore = bestScores[bestVectorIndex]; } } /** * Compares two scores. * * @param score1 a score * @param score2 a score * @return true if score1 is better than score2 */ boolean isScoreBetter(double score1, double score2) { return ((this.score.shouldMinimize() && (score1 < score2)) || ((!this.score.shouldMinimize()) && (score1 > score2))); } /** * {@inheritDoc} */ @Override public void iteration() { for (int i = 0; i < this.particles.length; i++) { updateParticle(i); } updateGlobalBestPosition(); } /** * @return False, this algorithm can be iterated an unlimited number of times. */ @Override public boolean done() { return false; } /** * @return The error (or energy) from the last iteration. */ @Override public double getLastError() { return this.bestScore; } /** * 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)); } /** * Copy the global best solution to the machine learning algorithm. It is very important to call this method. */ @Override public void finishTraining() { } /** * {@inheritDoc} */ @Override public String getStatus() { return ""; } public MLMethod getBestParticle() { VectorAlgebra.copy(this.particles[bestVectorIndex].getLongTermMemory(), this.bestVector); return this.particles[this.bestVectorIndex]; } }