Package org.encog.neural.som.training.basic

Source Code of org.encog.neural.som.training.basic.BasicTrainSOM

/*
* Encog(tm) Core v3.3 - Java Version
* http://www.heatonresearch.com/encog/
* https://github.com/encog/encog-java-core
* Copyright 2008-2014 Heaton Research, Inc.
*
* 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 org.encog.neural.som.training.basic;

import org.encog.mathutil.matrices.Matrix;
import org.encog.mathutil.matrices.MatrixMath;
import org.encog.ml.MLMethod;
import org.encog.ml.TrainingImplementationType;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataPair;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLData;
import org.encog.ml.train.BasicTraining;
import org.encog.neural.networks.training.LearningRate;
import org.encog.neural.networks.training.propagation.TrainingContinuation;
import org.encog.neural.som.SOM;
import org.encog.neural.som.training.basic.neighborhood.NeighborhoodFunction;
import org.encog.util.Format;
import org.encog.util.logging.EncogLogging;

/**
* This class implements competitive training, which would be used in a
* winner-take-all neural network, such as the self organizing map (SOM). This
* is an unsupervised training method, no ideal data is needed on the training
* set. If ideal data is provided, it will be ignored.
*
* Training is done by looping over all of the training elements and calculating
* a "best matching unit" (BMU). This BMU output neuron is then adjusted to
* better "learn" this pattern. Additionally, this training may be applied to
* other "nearby" output neurons. The degree to which nearby neurons are update
* is defined by the neighborhood function.
*
* A neighborhood function is required to determine the degree to which
* neighboring neurons (to the winning neuron) are updated by each training
* iteration.
*
* Because this is unsupervised training, calculating an error to measure
* progress by is difficult. The error is defined to be the "worst", or longest,
* Euclidean distance of any of the BMU's. This value should be minimized, as
* learning progresses.
*
* Because only the BMU neuron and its close neighbors are updated, you can end
* up with some output neurons that learn nothing. By default these neurons are
* not forced to win patterns that are not represented well. This spreads out
* the workload among all output neurons. This feature is not used by default,
* but can be enabled by setting the "forceWinner" property.
*
* @author jheaton
*
*/
public class BasicTrainSOM extends BasicTraining implements LearningRate {

  /**
   * The neighborhood function to use to determine to what degree a neuron
   * should be "trained".
   */
  private final NeighborhoodFunction neighborhood;

  /**
   * The learning rate. To what degree should changes be applied.
   */
  private double learningRate;

  /**
   * The network being trained.
   */
  private final SOM network;

  /**
   * How many neurons in the input layer.
   */
  private final int inputNeuronCount;

  /**
   * How many neurons in the output layer.
   */
  private final int outputNeuronCount;

  /**
   * Utility class used to determine the BMU.
   */
  private final BestMatchingUnit bmuUtil;

  /**
   * Holds the corrections for any matrix being trained.
   */
  private final Matrix correctionMatrix;

  /**
   * True is a winner is to be forced, see class description, or forceWinners
   * method. By default, this is true.
   */
  private boolean forceWinner;

  /**
   * When used with autodecay, this is the starting learning rate.
   */
  private double startRate;

  /**
   * When used with autodecay, this is the ending learning rate.
   */
  private double endRate;

  /**
   * When used with autodecay, this is the starting radius.
   */
  private double startRadius;

  /**
   * When used with autodecay, this is the ending radius.
   */
  private double endRadius;

  /**
   * This is the current autodecay learning rate.
   */
  private double autoDecayRate;

  /**
   * This is the current autodecay radius.
   */
  private double autoDecayRadius;

  /**
   * The current radius.
   */
  private double radius;

  /**
   * Create an instance of competitive training.
   *
   * @param network
   *            The network to train.
   * @param learningRate
   *            The learning rate, how much to apply per iteration.
   * @param training
   *            The training set (unsupervised).
   * @param neighborhood
   *            The neighborhood function to use.
   */
  public BasicTrainSOM(final SOM network, final double learningRate,
      final MLDataSet training, final NeighborhoodFunction neighborhood) {
    super(TrainingImplementationType.Iterative);
    this.neighborhood = neighborhood;
    setTraining(training);
    this.learningRate = learningRate;
    this.network = network;
    this.inputNeuronCount = network.getInputCount();
    this.outputNeuronCount = network.getOutputCount();
    this.forceWinner = false;
    setError(0);

    // setup the correction matrix
    this.correctionMatrix = new Matrix(this.outputNeuronCount,this.inputNeuronCount);

    // create the BMU class
    this.bmuUtil = new BestMatchingUnit(network);
  }

  /**
   * Loop over the synapses to be trained and apply any corrections that were
   * determined by this training iteration.
   */
  private void applyCorrection() {
    this.network.getWeights().set(this.correctionMatrix);
  }

  /**
   * Should be called each iteration if autodecay is desired.
   */
  public void autoDecay() {
    if (this.radius > this.endRadius) {
      this.radius += this.autoDecayRadius;
    }

    if (this.learningRate > this.endRate) {
      this.learningRate += this.autoDecayRate;
    }
    getNeighborhood().setRadius(this.radius);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean canContinue() {
    return false;
  }

  /**
   * Copy the specified input pattern to the weight matrix. This causes an
   * output neuron to learn this pattern "exactly". This is useful when a
   * winner is to be forced.
   *
   * @param matrix
   *            The matrix that is the target of the copy.
   * @param outputNeuron
   *            The output neuron to set.
   * @param input
   *            The input pattern to copy.
   */
  private void copyInputPattern(final Matrix matrix, final int outputNeuron,
      final MLData input) {
    for (int inputNeuron = 0; inputNeuron < this.inputNeuronCount; inputNeuron++) {
      matrix.set(outputNeuron, inputNeuron, input.getData(inputNeuron));
    }
  }

  /**
   * Called to decay the learning rate and radius by the specified amount.
   *
   * @param d
   *            The percent to decay by.
   */
  public void decay(final double d) {
    this.radius *= (1.0 - d);
    this.learningRate *= (1.0 - d);
  }

  /**
   * Decay the learning rate and radius by the specified amount.
   *
   * @param decayRate
   *            The percent to decay the learning rate by.
   * @param decayRadius
   *            The percent to decay the radius by.
   */
  public void decay(final double decayRate, final double decayRadius) {
    this.radius *= (1.0 - decayRadius);
    this.learningRate *= (1.0 - decayRate);
    getNeighborhood().setRadius(this.radius);
  }

  /**
   * Determine the weight adjustment for a single neuron during a training
   * iteration.
   *
   * @param weight
   *            The starting weight.
   * @param input
   *            The input to this neuron.
   * @param currentNeuron
   *            The neuron who's weight is being updated.
   * @param bmu
   *            The neuron that "won", the best matching unit.
   * @return The new weight value.
   */
  private double determineNewWeight(final double weight, final double input,
      final int currentNeuron, final int bmu) {

    final double newWeight = weight
        + (this.neighborhood.function(currentNeuron, bmu)
            * this.learningRate * (input - weight));
    return newWeight;
  }

  /**
   * Force any neurons that did not win to off-load patterns from overworked
   * neurons.
   *
   * @param won
   *            An array that specifies how many times each output neuron has
   *            "won".
   * @param leastRepresented
   *            The training pattern that is the least represented by this
   *            neural network.
   * @param matrix
   *            The synapse to modify.
   * @return True if a winner was forced.
   */
  private boolean forceWinners(final Matrix matrix, final int[] won,
      final MLData leastRepresented) {

    double maxActivation = Double.MIN_VALUE;
    int maxActivationNeuron = -1;

    final MLData output = compute(this.network, leastRepresented);

    // Loop over all of the output neurons. Consider any neurons that were
    // not the BMU (winner) for any pattern. Track which of these
    // non-winning neurons had the highest activation.
    for (int outputNeuron = 0; outputNeuron < won.length; outputNeuron++) {
      // Only consider neurons that did not "win".
      if (won[outputNeuron] == 0) {
        if ((maxActivationNeuron == -1)
            || (output.getData(outputNeuron) > maxActivation)) {
          maxActivation = output.getData(outputNeuron);
          maxActivationNeuron = outputNeuron;
        }
      }
    }

    // If a neurons was found that did not activate for any patterns, then
    // force it to "win" the least represented pattern.
    if (maxActivationNeuron != -1) {
      copyInputPattern(matrix, maxActivationNeuron, leastRepresented);
      return true;
    } else {
      return false;
    }
  }

  /**
   * @return The input neuron count.
   */
  public int getInputNeuronCount() {
    return this.inputNeuronCount;
  }

  /**
   * @return The learning rate. This was set when the object was created.
   */
  @Override
  public double getLearningRate() {
    return this.learningRate;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public MLMethod getMethod() {
    return this.network;
  }

  /**
   * @return The network neighborhood function.
   */
  public NeighborhoodFunction getNeighborhood() {
    return this.neighborhood;
  }

  /**
   * @return The output neuron count.
   */
  public int getOutputNeuronCount() {
    return this.outputNeuronCount;
  }

  /**
   * @return Is a winner to be forced of neurons that do not learn. See class
   *         description for more info.
   */
  public boolean isForceWinner() {
    return this.forceWinner;
  }

  /**
   * Perform one training iteration.
   */
  @Override
  public void iteration() {

    EncogLogging.log(EncogLogging.LEVEL_INFO,
        "Performing SOM Training iteration.");

    preIteration();

    // Reset the BMU and begin this iteration.
    this.bmuUtil.reset();
    final int[] won = new int[this.outputNeuronCount];
    double leastRepresentedActivation = Double.MAX_VALUE;
    MLData leastRepresented = null;

    // Reset the correction matrix for this synapse and iteration.
    this.correctionMatrix.clear();

    // Determine the BMU for each training element.
    for (final MLDataPair pair : getTraining()) {
      final MLData input = pair.getInput();

      final int bmu = this.bmuUtil.calculateBMU(input);
      won[bmu]++;

      // If we are to force a winner each time, then track how many
      // times each output neuron becomes the BMU (winner).
      if (this.forceWinner) {


        // Get the "output" from the network for this pattern. This
        // gets the activation level of the BMU.
        final MLData output = compute(this.network,pair.getInput());

        // Track which training entry produces the least BMU. This
        // pattern is the least represented by the network.
        if (output.getData(bmu) < leastRepresentedActivation) {
          leastRepresentedActivation = output.getData(bmu);
          leastRepresented = pair.getInput();
        }
      }

      train(bmu, this.network.getWeights(), input);

      if (this.forceWinner) {
        // force any non-winning neurons to share the burden somewhat\
        if (!forceWinners(this.network.getWeights(), won,
            leastRepresented)) {
          applyCorrection();
        }
      } else {
        applyCorrection();
      }
    }

    // update the error
    setError(this.bmuUtil.getWorstDistance() / 100.0);

    postIteration();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public TrainingContinuation pause() {
    return null;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void resume(final TrainingContinuation state) {

  }

  /**
   * Setup autodecay. This will decrease the radius and learning rate from the
   * start values to the end values.
   *
   * @param plannedIterations
   *            The number of iterations that are planned. This allows the
   *            decay rate to be determined.
   * @param startRate
   *            The starting learning rate.
   * @param endRate
   *            The ending learning rate.
   * @param startRadius
   *            The starting radius.
   * @param endRadius
   *            The ending radius.
   */
  public void setAutoDecay(final int plannedIterations,
      final double startRate, final double endRate,
      final double startRadius, final double endRadius) {
    this.startRate = startRate;
    this.endRate = endRate;
    this.startRadius = startRadius;
    this.endRadius = endRadius;
    this.autoDecayRadius = (endRadius - startRadius) / plannedIterations;
    this.autoDecayRate = (endRate - startRate) / plannedIterations;
    setParams(this.startRate, this.startRadius);
  }

  /**
   * Determine if a winner is to be forced. See class description for more
   * info.
   *
   * @param forceWinner
   *            True if a winner is to be forced.
   */
  public void setForceWinner(final boolean forceWinner) {
    this.forceWinner = forceWinner;
  }

  /**
   * Set the learning rate. This is the rate at which the weights are changed.
   *
   * @param rate
   *            The learning rate.
   */
  @Override
  public void setLearningRate(final double rate) {
    this.learningRate = rate;
  }

  /**
   * Set the learning rate and radius.
   *
   * @param rate
   *            The new learning rate.
   * @param radius
   *            The new radius.
   */
  public void setParams(final double rate, final double radius) {
    this.radius = radius;
    this.learningRate = rate;
    getNeighborhood().setRadius(radius);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    final StringBuilder result = new StringBuilder();
    result.append("Rate=");
    result.append(Format.formatPercent(this.learningRate));
    result.append(", Radius=");
    result.append(Format.formatDouble(this.radius, 2));
    return result.toString();
  }

  /**
   * Train for the specified synapse and BMU.
   *
   * @param bmu
   *            The best matching unit for this input.
   * @param matrix
   *            The synapse to train.
   * @param input
   *            The input to train for.
   */
  private void train(final int bmu, final Matrix matrix, final MLData input) {
    // adjust the weight for the BMU and its neighborhood
    for (int outputNeuron = 0; outputNeuron < this.outputNeuronCount; outputNeuron++) {
      trainPattern(matrix, input, outputNeuron, bmu);
    }
  }

  /**
   * Train for the specified pattern.
   *
   * @param matrix
   *            The synapse to train.
   * @param input
   *            The input pattern to train for.
   * @param current
   *            The current output neuron being trained.
   * @param bmu
   *            The best matching unit, or winning output neuron.
   */
  private void trainPattern(final Matrix matrix, final MLData input,
      final int current, final int bmu) {

    for (int inputNeuron = 0; inputNeuron < this.inputNeuronCount; inputNeuron++) {

      final double currentWeight = matrix.get(current, inputNeuron);
      final double inputValue = input.getData(inputNeuron);

      final double newWeight = determineNewWeight(currentWeight,
          inputValue, current, bmu);

      this.correctionMatrix.set(current, inputNeuron, newWeight);
    }
  }

  /**
   * Train the specified pattern. Find a winning neuron and adjust all neurons
   * according to the neighborhood function.
   *
   * @param pattern
   *            The pattern to train.
   */
  public void trainPattern(final MLData pattern) {

    final MLData input = pattern;
    final int bmu = this.bmuUtil.calculateBMU(input);
    train(bmu, this.network.getWeights(), input);
    applyCorrection();
  }
 
  /**
   * Calculate the output of the SOM, for each output neuron.  Typically,
   * you will use the classify method instead of calling this method.
   * @param input
   *            The input pattern.
   * @return The output activation of each output neuron.
   */
  private MLData compute(final SOM som, final MLData input) {

    final MLData result = new BasicMLData(som.getOutputCount());

    for (int i = 0; i < som.getOutputCount(); i++) {
      final Matrix optr = som.getWeights().getRow(i);
      final Matrix inputMatrix = Matrix.createRowMatrix(input.getData());
      result.setData(i, MatrixMath.dotProduct(inputMatrix, optr));
    }

    return result;
  }


}
TOP

Related Classes of org.encog.neural.som.training.basic.BasicTrainSOM

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.