/* * Encog(tm) Core v3.4 - Java Version * http://www.heatonresearch.com/encog/ * https://github.com/encog/encog-java-core * Copyright 2008-2016 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; } }