/*
* Encog(tm) Core v3.0 - Java Version
* http://www.heatonresearch.com/encog/
* http://code.google.com/p/encog-java/
* Copyright 2008-2011 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.thermal;
import org.encog.mathutil.matrices.BiPolarUtil;
import org.encog.mathutil.matrices.Matrix;
import org.encog.mathutil.matrices.MatrixMath;
import org.encog.ml.data.MLData;
import org.encog.ml.data.specific.BiPolarNeuralData;
import org.encog.neural.NeuralNetworkError;
import org.encog.util.EngineArray;
/**
* Implements a Hopfield network.
*
*/
public class HopfieldNetwork extends ThermalNetwork {
/**
* Serial id.
*/
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public HopfieldNetwork() {
}
/**
* Construct a Hopfield with the specified neuron count.
* @param neuronCount The neuron count.
*/
public HopfieldNetwork(final int neuronCount) {
super(neuronCount);
}
/**
* Train the neural network for the specified pattern. The neural network
* can be trained for more than one pattern. To do this simply call the
* train method more than once.
*
* @param pattern
* The pattern to train for.
*/
public final void addPattern(final MLData pattern) {
if (pattern.size() != getNeuronCount()) {
throw new NeuralNetworkError("Network with " + getNeuronCount()
+ " neurons, cannot learn a pattern of size "
+ pattern.size());
}
// Create a row matrix from the input, convert boolean to bipolar
final Matrix m2 = Matrix.createRowMatrix(pattern.getData());
// Transpose the matrix and multiply by the original input matrix
final Matrix m1 = MatrixMath.transpose(m2);
final Matrix m3 = MatrixMath.multiply(m1, m2);
// matrix 3 should be square by now, so create an identity
// matrix of the same size.
final Matrix identity = MatrixMath.identity(m3.getRows());
// subtract the identity matrix
final Matrix m4 = MatrixMath.subtract(m3, identity);
// now add the calculated matrix, for this pattern, to the
// existing weight matrix.
convertHopfieldMatrix(m4);
}
/**
* Note: for Hopfield networks, you will usually want to call the "run"
* method to compute the output.
*
* This method can be used to copy the input data to the current state. A
* single iteration is then run, and the new current state is returned.
*
* @param input
* The input pattern.
* @return The new current state.
*/
@Override
public final MLData compute(final MLData input) {
final BiPolarNeuralData result = new BiPolarNeuralData(input.size());
EngineArray.arrayCopy(input.getData(), getCurrentState().getData());
run();
for (int i = 0; i < getCurrentState().size(); i++) {
result.setData(i,
BiPolarUtil.double2bipolar(getCurrentState().getData(i)));
}
EngineArray.arrayCopy(getCurrentState().getData(), result.getData());
return result;
}
/**
* Update the Hopfield weights after training.
*
* @param delta
* The amount to change the weights by.
*/
private void convertHopfieldMatrix(final Matrix delta) {
// add the new weight matrix to what is there already
for (int row = 0; row < delta.getRows(); row++) {
for (int col = 0; col < delta.getRows(); col++) {
addWeight(row, col, delta.get(row, col));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public final int getInputCount() {
return super.getNeuronCount();
}
/**
* {@inheritDoc}
*/
@Override
public final int getOutputCount() {
return super.getNeuronCount();
}
/**
* Perform one Hopfield iteration.
*/
public final void run() {
for (int toNeuron = 0; toNeuron < getNeuronCount(); toNeuron++) {
double sum = 0;
for (int fromNeuron = 0; fromNeuron < getNeuronCount(); fromNeuron++) {
sum += getCurrentState().getData(fromNeuron)
* getWeight(fromNeuron, toNeuron);
}
getCurrentState().setData(toNeuron, sum);
}
}
/**
* Run the network until it becomes stable and does not change from more
* runs.
*
* @param max
* The maximum number of cycles to run before giving up.
* @return The number of cycles that were run.
*/
public final int runUntilStable(final int max) {
boolean done = false;
String lastStateStr = getCurrentState().toString();
String currentStateStr = getCurrentState().toString();
int cycle = 0;
do {
run();
cycle++;
lastStateStr = getCurrentState().toString();
if (!currentStateStr.equals(lastStateStr)) {
if (cycle > max) {
done = true;
}
} else {
done = true;
}
currentStateStr = lastStateStr;
} while (!done);
return cycle;
}
/**
* {@inheritDoc}
*/
@Override
public void updateProperties() {
// nothing needed here
}
}