/*
* 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.networks;
import org.encog.Encog;
import org.encog.engine.network.activation.ActivationFunction;
import org.encog.mathutil.randomize.NguyenWidrowRandomizer;
import org.encog.mathutil.randomize.RangeRandomizer;
import org.encog.ml.BasicML;
import org.encog.ml.MLClassification;
import org.encog.ml.MLContext;
import org.encog.ml.MLEncodable;
import org.encog.ml.MLError;
import org.encog.ml.MLRegression;
import org.encog.ml.MLResettable;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLData;
import org.encog.neural.NeuralNetworkError;
import org.encog.neural.flat.FlatNetwork;
import org.encog.neural.networks.layers.Layer;
import org.encog.neural.networks.structure.NetworkCODEC;
import org.encog.neural.networks.structure.NeuralStructure;
import org.encog.util.EngineArray;
import org.encog.util.csv.CSVFormat;
import org.encog.util.csv.NumberList;
import org.encog.util.obj.ObjectCloner;
import org.encog.util.simple.EncogUtility;
/**
* This class implements a neural network. This class works in conjunction the
* Layer classes. Layers are added to the BasicNetwork to specify the structure
* of the neural network.
*
* The first layer added is the input layer, the final layer added is the output
* layer. Any layers added between these two layers are the hidden layers.
*
* The network structure is stored in the structure member. It is important to
* call:
*
* network.getStructure().finalizeStructure();
*
* Once the neural network has been completely constructed.
*
*/
public class BasicNetwork extends BasicML implements ContainsFlat, MLContext,
MLRegression, MLEncodable, MLResettable, MLClassification, MLError {
/**
* Tag used for the connection limit.
*/
public static final String TAG_LIMIT = "CONNECTION_LIMIT";
/**
* The default connection limit.
*/
public static final double DEFAULT_CONNECTION_LIMIT = 0.0000000001;
/**
* Serial id for this class.
*/
private static final long serialVersionUID = -136440631687066461L;
/**
* The property for connection limit.
*/
public static final String TAG_CONNECTION_LIMIT = "connectionLimit";
/**
* The property for begin training.
*/
public static final String TAG_BEGIN_TRAINING = "beginTraining";
/**
* The property for context target offset.
*/
public static final String TAG_CONTEXT_TARGET_OFFSET = "contextTargetOffset";
/**
* The property for context target size.
*/
public static final String TAG_CONTEXT_TARGET_SIZE = "contextTargetSize";
/**
* The property for end training.
*/
public static final String TAG_END_TRAINING = "endTraining";
/**
* The property for has context.
*/
public static final String TAG_HAS_CONTEXT = "hasContext";
/**
* The property for layer counts.
*/
public static final String TAG_LAYER_COUNTS = "layerCounts";
/**
* The property for layer feed counts.
*/
public static final String TAG_LAYER_FEED_COUNTS = "layerFeedCounts";
/**
* The property for layer index.
*/
public static final String TAG_LAYER_INDEX = "layerIndex";
/**
* The property for weight index.
*/
public static final String TAG_WEIGHT_INDEX = "weightIndex";
/**
* The property for bias activation.
*/
public static final String TAG_BIAS_ACTIVATION = "biasActivation";
/**
* The property for layer context count.
*/
public static final String TAG_LAYER_CONTEXT_COUNT = "layerContextCount";
/**
* Holds the structure of the network. This keeps the network from having to
* constantly lookup layers and synapses.
*/
private final NeuralStructure structure;
/**
* Construct an empty neural network.
*/
public BasicNetwork() {
this.structure = new NeuralStructure(this);
}
/**
* Add a layer to the neural network. If there are no layers added this
* layer will become the input layer. This function automatically updates
* both the input and output layer references.
*
* @param layer
* The layer to be added to the network.
*/
public final void addLayer(final Layer layer) {
layer.setNetwork(this);
this.structure.getLayers().add(layer);
}
/**
* Add to a weight.
* @param fromLayer The from layer.
* @param fromNeuron The from neuron.
* @param toNeuron The to neuron.
* @param value The value to add.
*/
public final void addWeight(final int fromLayer,
final int fromNeuron,
final int toNeuron, final double value) {
final double old = getWeight(fromLayer, fromNeuron, toNeuron);
setWeight(fromLayer, fromNeuron, toNeuron, old + value);
}
/**
* Calculate the error for this neural network.
*
* @param data
* The training set.
* @return The error percentage.
*/
@Override
public final double calculateError(final MLDataSet data) {
return EncogUtility.calculateRegressionError(this, data);
}
/**
* Calculate the total number of neurons in the network across all layers.
*
* @return The neuron count.
*/
public final int calculateNeuronCount() {
int result = 0;
for (final Layer layer : this.structure.getLayers()) {
result += layer.getNeuronCount();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final int classify(final MLData input) {
return winner(input);
}
/**
* Clear any data from any context layers.
*/
@Override
public final void clearContext() {
if (this.structure.getFlat() != null) {
this.structure.getFlat().clearContext();
}
}
/**
* Return a clone of this neural network. Including structure, weights and
* bias values. This is a deep copy.
*
* @return A cloned copy of the neural network.
*/
@Override
public final Object clone() {
final BasicNetwork result = (BasicNetwork) ObjectCloner.deepCopy(this);
return result;
}
/**
* Compute the output for this network.
*
* @param input
* The input.
* @param output
* The output.
*/
public final void compute(final double[] input, final double[] output) {
final BasicMLData input2 = new BasicMLData(input);
final MLData output2 = this.compute(input2);
EngineArray.arrayCopy(output2.getData(), output);
}
/**
* Compute the output for a given input to the neural network.
*
* @param input
* The input to the neural network.
* @return The output from the neural network.
*/
@Override
public final MLData compute(final MLData input) {
try {
final MLData result = new BasicMLData(this.structure.getFlat()
.getOutputCount());
this.structure.getFlat().compute(input.getData(), result.getData());
return result;
} catch (final ArrayIndexOutOfBoundsException ex) {
throw new NeuralNetworkError(
"Index exception: there was likely a mismatch between layer sizes, or the size of the input presented to the network.",
ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public final void decodeFromArray(final double[] encoded) {
this.structure.requireFlat();
final double[] weights = this.structure.getFlat().getWeights();
if (weights.length != encoded.length) {
throw new NeuralNetworkError(
"Size mismatch, encoded array should be of length "
+ weights.length);
}
EngineArray.arrayCopy(encoded, weights);
}
/**
* @return The weights as a comma separated list.
*/
public final String dumpWeights() {
final StringBuilder result = new StringBuilder();
NumberList.toList(CSVFormat.EG_FORMAT, result, this.structure.getFlat()
.getWeights());
return result.toString();
}
/**
* Enable, or disable, a connection.
*
* @param fromLayer
* The layer that contains the from neuron.
* @param fromNeuron
* The source neuron.
* @param toNeuron
* The target connection.
* @param enable
* True to enable, false to disable.
*/
public final void enableConnection(final int fromLayer,
final int fromNeuron,
final int toNeuron, final boolean enable) {
final double value = getWeight(fromLayer, fromNeuron, toNeuron);
if (enable) {
if (!this.structure.isConnectionLimited()) {
return;
}
if (Math.abs(value) < this.structure.getConnectionLimit()) {
setWeight(fromLayer, fromNeuron, toNeuron,
RangeRandomizer.randomize(-1, 1));
}
} else {
if (!this.structure.isConnectionLimited()) {
this.setProperty(BasicNetwork.TAG_LIMIT,
BasicNetwork.DEFAULT_CONNECTION_LIMIT);
this.structure.updateProperties();
}
setWeight(fromLayer, fromNeuron, toNeuron, 0);
}
}
/**
* {@inheritDoc}
*/
@Override
public final int encodedArrayLength() {
this.structure.requireFlat();
return this.structure.getFlat().getEncodeLength();
}
/**
* {@inheritDoc}
*/
@Override
public final void encodeToArray(final double[] encoded) {
this.structure.requireFlat();
final double[] weights = this.structure.getFlat().getWeights();
if (weights.length != encoded.length) {
throw new NeuralNetworkError(
"Size mismatch, encoded array should be of length "
+ weights.length);
}
EngineArray.arrayCopy(weights, encoded);
}
/**
* Compare the two neural networks. For them to be equal they must be of the
* same structure, and have the same matrix values.
*
* @param other
* The other neural network.
* @return True if the two networks are equal.
*/
public final boolean equals(final BasicNetwork other) {
return equals(other, Encog.DEFAULT_PRECISION);
}
/**
* Determine if this neural network is equal to another. Equal neural
* networks have the same weight matrix and bias values, within a specified
* precision.
*
* @param other
* The other neural network.
* @param precision
* The number of decimal places to compare to.
* @return True if the two neural networks are equal.
*/
public final boolean equals(final BasicNetwork other, final int precision) {
return NetworkCODEC.equals(this, other, precision);
}
/**
* Get the activation function for the specified layer.
* @param layer The layer.
* @return The activation function.
*/
public final ActivationFunction getActivation(final int layer) {
this.structure.requireFlat();
final int layerNumber = getLayerCount() - layer - 1;
return this.structure.getFlat().getActivationFunctions()[layerNumber];
}
/**
* {@inheritDoc}
*/
@Override
public final FlatNetwork getFlat() {
return getStructure().getFlat();
}
/**
* {@inheritDoc}
*/
@Override
public final int getInputCount() {
this.structure.requireFlat();
return getStructure().getFlat().getInputCount();
}
/**
* Get the bias activation for the specified layer.
* @param l The layer.
* @return The bias activation.
*/
public final double getLayerBiasActivation(final int l) {
if (!isLayerBiased(l)) {
throw new NeuralNetworkError(
"Error, the specified layer does not have a bias: " + l);
}
this.structure.requireFlat();
final int layerNumber = getLayerCount() - l - 1;
final int layerOutputIndex
= this.structure.getFlat().getLayerIndex()[layerNumber];
final int count
= this.structure.getFlat().getLayerCounts()[layerNumber];
return this.structure.getFlat().getLayerOutput()[layerOutputIndex
+ count - 1];
}
/**
* @return The layer count.
*/
public final int getLayerCount() {
this.structure.requireFlat();
return this.structure.getFlat().getLayerCounts().length;
}
/**
* Get the neuron count.
* @param l The layer.
* @return The neuron count.
*/
public final int getLayerNeuronCount(final int l) {
this.structure.requireFlat();
final int layerNumber = getLayerCount() - l - 1;
return this.structure.getFlat().getLayerFeedCounts()[layerNumber];
}
/**
* Get the layer output for the specified neuron.
* @param layer The layer.
* @param neuronNumber The neuron number.
* @return The output from the last call to compute.
*/
public final double getLayerOutput(final int layer,
final int neuronNumber) {
this.structure.requireFlat();
final int layerNumber = getLayerCount() - layer - 1;
final int index = this.structure.getFlat().getLayerIndex()[layerNumber]
+ neuronNumber;
final double[] output = this.structure.getFlat().getLayerOutput();
if (index >= output.length) {
throw new NeuralNetworkError("The layer index: " + index
+ " specifies an output index larger than the network has.");
}
return output[index];
}
/**
* Get the total (including bias and context) neuron cont for a layer.
* @param l The layer.
* @return The count.
*/
public final int getLayerTotalNeuronCount(final int l) {
this.structure.requireFlat();
final int layerNumber = getLayerCount() - l - 1;
return this.structure.getFlat().getLayerCounts()[layerNumber];
}
/**
* {@inheritDoc}
*/
@Override
public final int getOutputCount() {
this.structure.requireFlat();
return getStructure().getFlat().getOutputCount();
}
/**
* @return Get the structure of the neural network. The structure allows you
* to quickly obtain synapses and layers without traversing the
* network.
*/
public final NeuralStructure getStructure() {
return this.structure;
}
/**
* Get the weight between the two layers.
* @param fromLayer The from layer.
* @param fromNeuron The from neuron.
* @param toNeuron The to neuron.
* @return The weight value.
*/
public final double getWeight(final int fromLayer,
final int fromNeuron,
final int toNeuron) {
this.structure.requireFlat();
validateNeuron(fromLayer, fromNeuron);
validateNeuron(fromLayer + 1, toNeuron);
final int fromLayerNumber = getLayerCount() - fromLayer - 1;
final int toLayerNumber = fromLayerNumber - 1;
if (toLayerNumber < 0) {
throw new NeuralNetworkError(
"The specified layer is not connected to another layer: "
+ fromLayer);
}
final int weightBaseIndex
= this.structure.getFlat().getWeightIndex()[toLayerNumber];
final int count
= this.structure.getFlat().getLayerCounts()[fromLayerNumber];
final int weightIndex = weightBaseIndex + fromNeuron
+ (toNeuron * count);
return this.structure.getFlat().getWeights()[weightIndex];
}
/**
* Generate a hash code.
*
* @return THe hash code.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Determine if the specified connection is enabled.
*
* @param layer
* The layer to check.
* @param fromNeuron
* The source neuron.
* @param toNeuron
* THe target neuron.
* @return True, if the connection is enabled, false otherwise.
*/
public final boolean isConnected(final int layer, final int fromNeuron,
final int toNeuron) {
/*
* if (!this.structure.isConnectionLimited()) { return true; } final
* double value = synapse.getMatrix().get(fromNeuron, toNeuron);
*
* return (Math.abs(value) > this.structure.getConnectionLimit());
*/
return false;
}
/**
* Determine if the specified layer is biased.
* @param l The layer number.
* @return True, if the layer is biased.
*/
public final boolean isLayerBiased(final int l) {
this.structure.requireFlat();
final int layerNumber = getLayerCount() - l - 1;
return this.structure.getFlat().getLayerCounts()[layerNumber]
!= this.structure
.getFlat().getLayerFeedCounts()[layerNumber];
}
/**
* Reset the weight matrix and the bias values. This will use a
* Nguyen-Widrow randomizer with a range between -1 and 1. If the network
* does not have an input, output or hidden layers, then Nguyen-Widrow
* cannot be used and a simple range randomize between -1 and 1 will be
* used.
*
*/
@Override
public final void reset() {
if (getLayerCount() < 3) {
(new RangeRandomizer(-1, 1)).randomize(this);
} else {
(new NguyenWidrowRandomizer(-1, 1)).randomize(this);
}
}
/**
* Reset the weight matrix and the bias values. This will use a
* Nguyen-Widrow randomizer with a range between -1 and 1. If the network
* does not have an input, output or hidden layers, then Nguyen-Widrow
* cannot be used and a simple range randomize between -1 and 1 will be
* used.
*
* Use the specified seed.
*
*/
@Override
public final void reset(final int seed) {
reset();
}
/**
* Sets the bias activation for every layer that supports bias. Make sure
* that the network structure has been finalized before calling this method.
*
* @param activation
* THe new activation.
*/
public final void setBiasActivation(final double activation) {
// first, see what mode we are on. If the network has not been
// finalized, set the layers
if (this.structure.getFlat() == null) {
for (final Layer layer : this.structure.getLayers()) {
if (layer.hasBias()) {
layer.setBiasActivation(activation);
}
}
} else {
for (int i = 0; i < getLayerCount(); i++) {
if (isLayerBiased(i)) {
setLayerBiasActivation(i, activation);
}
}
}
}
/**
* Set the bias activation for the specified layer.
* @param l The layer to use.
* @param value The bias activation.
*/
public final void setLayerBiasActivation(final int l,
final double value) {
if (!isLayerBiased(l)) {
throw new NeuralNetworkError(
"Error, the specified layer does not have a bias: " + l);
}
this.structure.requireFlat();
final int layerNumber = getLayerCount() - l - 1;
final int layerOutputIndex
= this.structure.getFlat().getLayerIndex()[layerNumber];
final int count
= this.structure.getFlat().getLayerCounts()[layerNumber];
this.structure.getFlat().getLayerOutput()[layerOutputIndex + count - 1]
= value;
}
/**
* Set the weight between the two specified neurons.
* @param fromLayer The from layer.
* @param fromNeuron The from neuron.
* @param toNeuron The to neuron.
* @param value The to value.
*/
public final void setWeight(final int fromLayer, final int fromNeuron,
final int toNeuron, final double value) {
this.structure.requireFlat();
final int fromLayerNumber = getLayerCount() - fromLayer - 1;
final int toLayerNumber = fromLayerNumber - 1;
if (toLayerNumber < 0) {
throw new NeuralNetworkError(
"The specified layer is not connected to another layer: "
+ fromLayer);
}
final int weightBaseIndex
= this.structure.getFlat().getWeightIndex()[toLayerNumber];
final int count
= this.structure.getFlat().getLayerCounts()[fromLayerNumber];
final int weightIndex = weightBaseIndex + fromNeuron
+ (toNeuron * count);
this.structure.getFlat().getWeights()[weightIndex] = value;
}
/**
* {@inheritDoc}
*/
@Override
public final String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("[BasicNetwork: Layers=");
final int layers = this.structure.getLayers().size();
builder.append(layers);
builder.append("]");
return builder.toString();
}
/**
* {@inheritDoc}
*/
@Override
public final void updateProperties() {
this.structure.updateProperties();
}
/**
* Validate the the specified targetLayer and neuron are valid.
* @param targetLayer The target layer.
* @param neuron The target neuron.
*/
public final void validateNeuron(final int targetLayer, final int neuron) {
if ((targetLayer < 0) || (targetLayer >= getLayerCount())) {
throw new NeuralNetworkError("Invalid layer count: " + targetLayer);
}
if ((neuron < 0) || (neuron >= getLayerTotalNeuronCount(targetLayer))) {
throw new NeuralNetworkError("Invalid neuron number: " + neuron);
}
}
/**
* Determine the winner for the specified input. This is the number of the
* winning neuron.
*
* @param input
* The input patter to present to the neural network.
* @return The winning neuron.
*/
public final int winner(final MLData input) {
final MLData output = compute(input);
return EngineArray.maxIndex(output.getData());
}
}