- Overview
- executable, Eclipse and Maven projects
- executable, Eclipse and Maven projects
- executable, Eclipse and Maven projects
- executable, Eclipse and Maven projects
- executable, Eclipse and Maven projects
- executable, Eclipse and Maven projects
We consider a Backpropagation Neural Network with one hidden layer,
which is trained to encode patterns.
During training the given 1-out-of-n patterns are presented to
the input and the output layer and the weights of these layers
are adapted based on the resulting output error.
The goal is to adapt the weights such that the given input pattern
is reproduced at the output layer.
If this goal is reached, the hidden layer signals can be considered
as an encoding of the given patterns.
The three unit classes Input, Hidden and Output
model the nodes of the input layer, the hidden layer or the output layer, respectively.
We'll discuss them in turn.
Nodes of the Input class are only used to enter the patterns
and present them to the hidden layer.
Therefore, they contain only a layer field y of type Signal,
which will be connected to all nodes of the hidden layer,
and a layer method to set this field.
Signal is a simple class that only contains a field val of type double.
@LayerUnitpublic class Input {@LayerFieldSignal y = new Signal(); // fanout signal@LayerMethodpublic void setInput(@LayerParam double input){ y.val = input; } }
At the top of the Hidden class you find the elements to make the connections.
There is the input vector x for collecting the input layer signals,
the fanout signal y for the output signal of the node that is sent to the output layer,
and the vector inErr for the error signals coming back from the output layer.
Below that are the so-called bias value b and the weight vector w.
These are the parameters that determine a node's fanout signal and are adjusted during training.
With the initSignals() layer method, these parameters are initialized randomly at the beginning.
@LayerUnitpublic class Hidden {@LayerFieldSignal y = new Signal(); // fanout signal@LayerFieldSignal[] x; // input vector@LayerFieldSignal[] inErr; // incoming error signals Signal b; // bias Signal[] w; // weight vector@LayerMethodpublic void initSignals(Random r) { b = new Signal(); b.val = r.nextDouble(); w = new Signal[x.length]; for (int i = 0; i < x.length; i++) { w[i] = new Signal(); w[i].val = r.nextDouble(); } }@LayerMethodpublic void Forward() { y.val = sigmoid(dotProd(w, x) + b.val); }@LayerMethodpublic void Backward(double eta) { // compute delta double sum = 0.0; for (Signal s : inErr) { sum += s.val; } double delta = y.val * (1 - y.val) * sum; // adjust bias and weights b.val = b.val + eta * delta; for (int i = 0; i < w.length; i++) { w[i].val = w[i].val + eta * delta * x[i].val; } } }
The Forward() layer method calculates the node's signal using the so-called
sigmoid function
applied to the weighted sum of the input vector x plus the bias value b.
And the Backward() layer method performs the training mentioned above.
The adaptation of the parameters is based on the error values of the output layer
present in the vector inErr and a so-called learning rate eta,
which is passed to the method as a parameter.
The Output class is similar to the Hidden class in many ways.
The class has a fanout signal y, an input vector w
and a bias value b, which have the same meaning as in the hidden class.
Instead of a vector inErr for incoming error signals,
there is a vector outErr for outgoing error signals,
which result from a comparison of the current value of y
with the value of a target signal t to be set externally.
The methods initSignals() and Forward() correspond
to those in the Hidden class,
the method setTarget() is used to set the target signal and
method getError() can be used to read the error.
@LayerUnitpublic class Output {@LayerFieldSignal y = new Signal(); // fanout signal@LayerFieldSignal[] x; // input vector@LayerFieldSignal[] outErr; // outgoing error signal Signal b; // bias Signal[] w; // weight vector Signal t; // target signal@LayerMethodpublic void initSignals(Random r) { y = new Signal(); b = new Signal(); b.val = r.nextDouble(); w = new Signal[x.length]; for (int i = 0; i < x.length; i++) { w[i] = new Signal(); w[i].val = r.nextDouble(); } t = new Signal(); }@LayerMethodpublic void setTarget(@LayerParamdouble target){ t.val = target; }@LayerMethodpublic double getError() { return Math.pow(t.val - y.val, 2); }@LayerMethodpublic void Forward() { y.val = sigmoid(dotProd(w, x) + b.val); }@LayerMethodpublic void Backward(double eta) { // compute delta double delta = y.val * (1 - y.val) * (t.val - y.val); // set outgoing error signals for (int i = 0; i < w.length; i++) { outErr[i].val = delta * w[i].val; } // adjust bias and weights b.val = b.val + eta * delta; for (int i = 0; i < w.length; i++) { w[i].val = w[i].val + eta * delta * x[i].val; } } }
The Backward() methods of the Output and Hidden
classes only differ in that the auxiliary value delta depends
on the deviation from the target t in the first case and
on the sum of the incoming error signals in the second case.
The EncoderNetwork class defines the global structure and dynamics
of the backpropagation network. The parts of the code that depend on the JLayer framework
are color coded below.
The simple fields at the top determine global network settings:
patternSize is the size of the input and output layers,
codeSize is the size of the hidden layer,
randomSeed ist the starting value for the random number generator
and eta is the learning rate during training.
Next, the three arrays inputArray, hiddenArray,
outputArray and their wrappers are declared,
followed by the definiton of relation full, which is used
to connect or associate layers. Relation full is an implementation
of the interface Relation from the JLayer Framework.
The two dimensional array patternPool serves to store the
1-out-of-n patterns, the coding of which has to be learned.
Method createNetwork() creates the three layers, connects
the input vectors x of the hiddenLayer / outputLayer to the
fanout signals y of the inputLayer / hiddenLayer
and then associates the vectors outErr of the outputLayer to
the vectors inErr of the hiddenLayer.
Note that since the linking vectors are constructed in ascending order
with respect to the underlying indices, the system ensures that any outgoing error signal
outErr[i] goes to the hidden node that gave the input y[i].
At last,the 1-out-of-n patterns
are stored in the patternPool.
public class EncoderNetwork {
int patternSize, codeSize;
int randomSeed;
double eta;
Output[] outputArray;
Hidden[] hiddenArray;
Input[] inputArray;
Layer_Output_ outputLayer;
Layer_Hidden_ hiddenLayer;
Layer_Input_ inputLayer;
Relation full = new Relation() {
@Override
public boolean contains(int[] x, int[] y) {
return true;
}
};
Double[][] patternPool;
void createNetwork() {
inputArray = new Input[patternSize];
hiddenArray = new Hidden[codeSize];
outputArray = new Output[patternSize];
for (int i = 0; i < patternSize; i++) {
inputArray[i] = new Input();
outputArray[i] = new Output();
}
for (int i = 0; i < codeSize; i++) {
hiddenArray[i] = new Hidden();
}
outputLayer = new Layer_Output_(outputArray);
hiddenLayer = new Layer_Hidden_(hiddenArray);
inputLayer = new Layer_Input_(inputArray);
hiddenLayer.x.connect(inputLayer.y, full);
outputLayer.x.connect(hiddenLayer.y, full);
outputLayer.outErr.associate(hiddenLayer.inErr, full);
patternPool = new Double[patternSize][patternSize];
for (int i = 0; i < patternSize; i++) {
for (int j = 0; j < patternSize; j++) {
patternPool[i][j] = (i == j) ? 1.0 : 0.0;
}
}
}
void initNetwork() {
Random random = new Random(randomSeed);
hiddenLayer.initSignals(random);
outputLayer.initSignals(random);
}
double Forward(int patternNo) {
BasedLayer thisPatterns = new LayerBase(patternPool[patternNo]);
inputLayer.setInput(thisPatterns);
outputLayer.setTarget(thisPatterns);
hiddenLayer.Forward();
outputLayer.Forward();
Double[] outputErrors = outputLayer.getError().getD1().getBase();
double accumulator = 0.0;
for (Double error : outputErrors) {
accumulator += error;
}
return Math.sqrt(accumulator);
}
void Backward() {
outputLayer.Backward(eta);
hiddenLayer.Backward(eta);
}
double RunEpoch() {
double err = 0.0;
for (int p = 0; p < patternSize; p++) {
err += Forward(p);
Backward();
}
return err/patternSize;
}
}
For further dynamic modelling four methods are provided:
initNetwork() intializes the network weights and bias values;
Forward() takes a given pattern and sets it
as input and target. After that it performs the forward pass
and returns the accumulated error:
Backward() performs the backward pass;
RunEpoch() performs a training cycle, i.e.
it performs the forward and backward pass for all patterns,
and returns the normalized overall error.