/*
* 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.art;
import org.encog.mathutil.matrices.Matrix;
import org.encog.ml.MLClassification;
import org.encog.ml.MLResettable;
import org.encog.ml.data.MLData;
import org.encog.ml.data.specific.BiPolarNeuralData;
import org.encog.neural.NeuralNetworkError;
/**
* Implements an ART1 neural network. An ART1 neural network is trained to
* recognize bipolar patterns as it is presented data. There is no distinct
* learning phase, like there is with other neural network types.
*
* The ART1 neural network is a type of Adaptive Resonance Theory (ART) neural
* network. ART1 was developed by Stephen Grossberg and Gail Carpenter.
* This neural network type supports only bipolar input. The ART1 neural
* network is trained as it is used. New patterns are presented to the ART1
* network, and they are classified into either new, or existing, classes.
* Once the maximum number of classes have been used the network will report
* that it is out of classes. ART1 neural networks are used for classification.
*
* There are essentially 2 layers in an ART1 network. The first, named the
* F1 layer, acts as the input. The F1 layer receives bipolar patterns that
* the network is to classify. The F2 layer specifies the maximum number
* classes that the ART1 network can recognize.
*
* Plasticity is an important part for all Adaptive Resonance Theory (ART)
* neural networks. Unlike most neural networks, ART1 does not have a
* distinct training and usage stage. The ART1 network will learn as it is
* used.
*/
public class ART1 extends ART implements MLResettable, MLClassification {
/**
* Serial id.
*/
private static final long serialVersionUID = 1L;
/**
* last winner in F2 layer.
*/
private int winner;
/**
* A parameter for F1 layer.
*/
private double a1 = 1;
/**
* B parameter for F1 layer.
*/
private double b1 = 1.5;
/**
* C parameter for F1 layer.
*/
private double c1 = 5;
/**
* D parameter for F1 layer.
*/
private double d1 = 0.9;
/**
* L parameter for net.
*/
private double l = 3;
/**
* The vigilance parameter.
*/
private double vigilance = 0.9;
/**
* Allows members of the F2 layer to be inhibited.
*/
private transient boolean[] inhibitF2;
/**
* This is the value that is returned if there is no winner.
* This value is generally set to the number of classes, plus 1.
*/
private int noWinner;
/**
* The output from the F1 layer.
*/
private BiPolarNeuralData outputF1;
/**
* The output from the F2 layer.
*/
private BiPolarNeuralData outputF2;
/**
* The F1 layer neuron count.
*/
private int f1Count;
/**
* The F2 layer neuron count.
*/
private int f2Count;
/**
* Weights from f1 to f2.
*/
private Matrix weightsF1toF2;
/**
* Weights from f2 to f1.
*/
private Matrix weightsF2toF1;
/**
* Default constructor, used mainly for persistence.
*/
public ART1() {
}
/**
* Construct the ART1 network.
*
* @param theF1Count
* The neuron count for the f1 layer.
* @param theF2Count
* The neuron count for the f2 layer.
*/
public ART1(final int theF1Count, final int theF2Count) {
this.f1Count = theF1Count;
this.f2Count = theF2Count;
this.weightsF1toF2 = new Matrix(this.f1Count, this.f2Count);
this.weightsF2toF1 = new Matrix(this.f2Count, this.f1Count);
this.inhibitF2 = new boolean[this.f2Count];
this.outputF1 = new BiPolarNeuralData(this.f1Count);
this.outputF2 = new BiPolarNeuralData(this.f2Count);
this.noWinner = this.f2Count;
reset();
}
/**
* Adjust the weights for the pattern just presented.
*/
public void adjustWeights() {
double magnitudeInput;
for (int i = 0; i < this.f1Count; i++) {
if (this.outputF1.getBoolean(i)) {
magnitudeInput = magnitude(this.outputF1);
this.weightsF1toF2.set(i, this.winner, 1);
this.weightsF2toF1.set(this.winner, i, this.l
/ (this.l - 1 + magnitudeInput));
} else {
this.weightsF1toF2.set(i, this.winner, 0);
this.weightsF2toF1.set(this.winner, i, 0);
}
}
}
/**
* Classify the input data to a class number.
*
* @param input
* The input data.
* @return The class that the data belongs to.
*/
@Override
public int classify(final MLData input) {
final BiPolarNeuralData input2 = new BiPolarNeuralData(this.f1Count);
final BiPolarNeuralData output = new BiPolarNeuralData(this.f2Count);
if (input.size() != input2.size()) {
throw new NeuralNetworkError("Input array size does not match.");
}
for (int i = 0; i < input2.size(); i++) {
input2.setData(i, input.getData(i) > 0);
}
this.compute(input2, output);
if (hasWinner()) {
return this.winner;
} else {
return -1;
}
}
/**
* Compute the output from the ART1 network. This can be called directly or
* used by the BasicNetwork class. Both input and output should be bipolar
* numbers.
*
* @param input
* The input to the network.
* @param output
* The output from the network.
*/
public void compute(final BiPolarNeuralData input,
final BiPolarNeuralData output) {
int i;
boolean resonance, exhausted;
double magnitudeInput1, magnitudeInput2;
for (i = 0; i < this.f2Count; i++) {
this.inhibitF2[i] = false;
}
resonance = false;
exhausted = false;
do {
setInput(input);
computeF2();
getOutput(output);
if (this.winner != this.noWinner) {
computeF1(input);
magnitudeInput1 = magnitude(input);
magnitudeInput2 = magnitude(this.outputF1);
if ((magnitudeInput2 / magnitudeInput1) < this.vigilance) {
this.inhibitF2[this.winner] = true;
} else {
resonance = true;
}
} else {
exhausted = true;
}
} while (!(resonance || exhausted));
if (resonance) {
adjustWeights();
}
}
/**
* Compute the output for the BasicNetwork class.
*
* @param input
* The input to the network.
* @return The output from the network.
*/
public MLData compute(final MLData input) {
if (!(input instanceof BiPolarNeuralData)) {
throw new NeuralNetworkError(
"Input to ART1 logic network must be BiPolarNeuralData.");
}
final BiPolarNeuralData output = new BiPolarNeuralData(this.f1Count);
compute((BiPolarNeuralData) input, output);
return output;
}
/**
* Compute the output from the F1 layer.
*
* @param input
* The input to the F1 layer.
*/
private void computeF1(final BiPolarNeuralData input) {
double sum, activation;
for (int i = 0; i < this.f1Count; i++) {
sum = this.weightsF1toF2.get(i, this.winner)
* (this.outputF2.getBoolean(this.winner) ? 1 : 0);
activation = ((input.getBoolean(i) ? 1 : 0) + this.d1 * sum - this.b1)
/ (1 + this.a1
* ((input.getBoolean(i) ? 1 : 0) + this.d1 * sum) + this.c1);
this.outputF1.setData(i, activation > 0);
}
}
/**
* Compute the output from the F2 layer.
*/
private void computeF2() {
int i, j;
double sum, maxOut;
maxOut = Double.NEGATIVE_INFINITY;
this.winner = this.noWinner;
for (i = 0; i < this.f2Count; i++) {
if (!this.inhibitF2[i]) {
sum = 0;
for (j = 0; j < this.f1Count; j++) {
sum += this.weightsF2toF1.get(i, j)
* (this.outputF1.getBoolean(j) ? 1 : 0);
}
if (sum > maxOut) {
maxOut = sum;
this.winner = i;
}
}
this.outputF2.setData(i, false);
}
if (this.winner != this.noWinner) {
this.outputF2.setData(this.winner, true);
}
}
/**
* @return The A1 parameter.
*/
public double getA1() {
return this.a1;
}
/**
* @return The B1 parameter.
*/
public double getB1() {
return this.b1;
}
/**
* @return The C1 parameter.
*/
public double getC1() {
return this.c1;
}
/**
* @return The D1 parameter.
*/
public double getD1() {
return this.d1;
}
/**
* @return the f1Count
*/
public int getF1Count() {
return this.f1Count;
}
/**
* @return the f2Count
*/
public int getF2Count() {
return this.f2Count;
}
@Override
public int getInputCount() {
return this.f1Count;
}
/**
* @return The L parameter.
*/
public double getL() {
return this.l;
}
/**
* @return This is the value that is returned if there is no winner.
* This value is generally set to the index of the last classes, plus 1.
* For example, if there were 3 classes, the network would return 0-2 to
* represent what class was found, in this case the no winner property
* would be set to 3.
*/
public int getNoWinner() {
return this.noWinner;
}
/**
* Copy the output from the network to another object.
*
* @param output
* The target object for the output from the network.
*/
private void getOutput(final BiPolarNeuralData output) {
for (int i = 0; i < this.f2Count; i++) {
output.setData(i, this.outputF2.getBoolean(i));
}
}
/**
* @return The number of neurons in the output count, which is the f2 layer
* count.
*/
@Override
public int getOutputCount() {
return this.f2Count;
}
/**
* @return The vigilance parameter.
*/
public double getVigilance() {
return this.vigilance;
}
/**
* @return the weightsF1toF2
*/
public Matrix getWeightsF1toF2() {
return this.weightsF1toF2;
}
/**
* @return the weightsF2toF1
*/
public Matrix getWeightsF2toF1() {
return this.weightsF2toF1;
}
/**
* @return The winning neuron.
*/
public int getWinner() {
return this.winner;
}
/**
* @return Does this network have a "winner"?
*/
public boolean hasWinner() {
return this.winner != this.noWinner;
}
/**
* Get the magnitude of the specified input.
*
* @param input
* The input to calculate the magnitude for.
* @return The magnitude of the specified pattern.
*/
public double magnitude(final BiPolarNeuralData input) {
double result;
result = 0;
for (int i = 0; i < this.f1Count; i++) {
result += input.getBoolean(i) ? 1 : 0;
}
return result;
}
/**
* Reset the weight matrix back to starting values.
*/
@Override
public void reset() {
reset(0);
}
/**
* Reset with a specic seed.
* @param seed The seed to reset with.
*/
@Override
public void reset(final int seed) {
for (int i = 0; i < this.f1Count; i++) {
for (int j = 0; j < this.f2Count; j++) {
this.weightsF1toF2.set(i, j, (this.b1 - 1) / this.d1 + 0.2);
this.weightsF2toF1.set(j, i, this.l
/ (this.l - 1 + this.f1Count) - 0.1);
}
}
}
/**
* Set the A1 parameter.
*
* @param theA1
* The new value.
*/
public void setA1(final double theA1) {
this.a1 = theA1;
}
/**
* Set the B1 parameter.
*
* @param theB1
* The new value.
*/
public void setB1(final double theB1) {
this.b1 = theB1;
}
/**
* Set the C1 parameter.
*
* @param theC1
* The new value.
*/
public void setC1(final double theC1) {
this.c1 = theC1;
}
/**
* Set the D1 parameter.
*
* @param theD1
* The new value.
*/
public void setD1(final double theD1) {
this.d1 = theD1;
}
/**
* Set the F1 count. The F1 layer is the input layer.
* @param i The count.
*/
public void setF1Count(final int i) {
this.f1Count = i;
this.outputF1 = new BiPolarNeuralData(this.f1Count);
}
/**
* Set the F2 count. The F2 layer is the output layer.
* @param i The count.
*/
public void setF2Count(final int i) {
this.f2Count = i;
this.inhibitF2 = new boolean[this.f2Count];
this.outputF2 = new BiPolarNeuralData(this.f2Count);
}
/**
* Set the input to the neural network.
*
* @param input
* The input.
*/
private void setInput(final BiPolarNeuralData input) {
double activation;
for (int i = 0; i < this.f1Count; i++) {
activation = (input.getBoolean(i) ? 1 : 0)
/ (1 + this.a1 * ((input.getBoolean(i) ? 1 : 0) + this.b1) + this.c1);
this.outputF1.setData(i, (activation > 0));
}
}
/**
* Set the L parameter.
*
* @param theL
* The new value.
*/
public void setL(final double theL) {
this.l = theL;
}
/**
* Set the i parameter.
*
* @param i
* The new value.
*/
public void setNoWinner(final int i) {
this.noWinner = i;
}
/**
* Set the vigilance.
*
* @param theVigilance
* The new value.
*/
public void setVigilance(final double theVigilance) {
this.vigilance = theVigilance;
}
/**
* Set the f1 to f2 matrix.
* @param matrix The new matrix.
*/
public void setWeightsF1toF2(final Matrix matrix) {
this.weightsF1toF2 = matrix;
}
/**
* Set the f2 to f1 matrix.
* @param matrix The new matrix.
*/
public void setWeightsF2toF1(final Matrix matrix) {
this.weightsF2toF1 = matrix;
}
}