- 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
The Kohonen Network is a self-organising map that is trained through unsupervised competitive learning. The aim is to generate a low-dimensional (usually two-dimensional) representation of a higher-dimensional data set.
Each node of the Kohonen map is assigned a randomly initialised weight vector whose length corresponds to the dimensionality of the input data. During the training process, it is first determined for a given input vector which weight vector is closest to this input and then the weight vector of the "winning node" is pulled a little in the direction of the input vector.
The same applies - to a decreasing extent - to the weight vectors of the neighbours of the winning node, whereby the neighbourhood here refers to the topology of the Kohonen map.
In our example, a two-dimensional map is trained with input data from a two-dimensional square space.
We need two unit types for the network outlined above.
The nodes of the two-dimensional Kohonen map are described by the KohonenUnit
class,
and to determine the "winning node" we need a node of type DecisionUnit
.
Let's start with the DecisionUnit
class.
A single object of this type decides which node of the Kohonen map is the winning node,
and for this decision the Euclidean distances (to the current input)
of all weights are maid available to this "decision maker".
@LayerUnit
public class DecisionUnit { static class DecisionIO { double distance; // Euclidean distance boolean isWinner; // smallest distance to input }@LayerField
DecisionIO[] decisionIO;@LayerMethod
void detWinnerIndex() { double winDist = decisionIO[0].distance; int winIx = 0; for (int i = 1; i < decisionIO.length; i++) { if (decisionIO[i].distance < winDist) { winDist = decisionIO[i].distance; winIx = i; } } for (int i = 0; i < decisionIO.length; i++) { decisionIO[i].isWinner = false; } decisionIO[winIx].isWinner = true; } }
The nested static class DecisionIO
is provided
for the interaction of the decision maker with the nodes of the Kohonen map,
so that the necessary communication can be handled with the help of
the layer vector decisionIO
.
The Euclidean distances can be read out via the distance
field
and the decision, which is determined by the layer method detWinnerIndex()
,
can be communicated via the isWinner
field.
The description of the KohonenUnit
class begins with the static field winIx
,
in which the index of the winning node is entered, and the weight vector w
for storing the representative of the input space.
This is followed by three parameters, which are used to adjust the weights during training,
and two layer fields:
LayerField
decisionIO
is the
counterpart to the layer vector of the same name of the unit type DecisionUnit
and is used to communicate with the "decision unit".
LayerField
ix
of type int[]
is used to store the index of the current node.
Since the parameter isIndex
has the value true
,
LayerField
ix
is calculated automatically
when an array of type KohonenUnit
is wrapped.
@LayerUnit
public class KohonenUnit { // for a two-dimensional Kohonen map static int[] winIx; // index of winner unit double[] w; // weight vector for representing the input space // parameters for weight adaptation static int rad = 4; static double eta = 0.2; static double decay = 0.5;@LayerField
DecisionUnit.DecisionIO decisionIO;@LayerField(isIndex = true)
int[] thisIx; // index of this unit@LayerMethod
public void initWeights(Random r, double lwb, double upb) { w = new double[2]; w[0] = r.nextDouble(lwb, upb); w[1] = r.nextDouble(lwb, upb); }@LayerMethod
public void nextInputSample(double x0, double x1) { decisionIO.distance = euDist(x0, x1, w[0], w[1]); }@LayerMethod
public void setWinnerIndex(){ if (decisionIO.isWinner) winIx = thisIx; }@LayerMethod
public void adaptWeights(double x0, double x1){ double euDist = euDist(thisIx, winIx); if (euDist < rad) { w[0] = w[0] + eta * phi(euDist, decay) * (x0 - w[0]); w[1] = w[1] + eta * phi(euDist, decay) * (x1 - w[1]); } } // utility functions // Euclidean distance static double euDist(double x0, double x1, double w0, double w1) { double delta1 = x0 - w0; double delta2 = x1 - w1; double result = Math.sqrt(delta1*delta1 + delta2*delta2); return result; } static double euDist(int[] ix0, int[] ix1) { double delta1 = ix0[0] - ix1[0]; double delta2 = ix0[1] - ix1[1]; double result = Math.sqrt(delta1*delta1 + delta2*delta2); return result; } // adaptation function static double phi(double dist, double decay) { return 1.0 / Math.exp(decay * dist); } }
Class KohonenUnit
contains four layer methods,
which support the training process.
LayerMethod
initWeights()
initialises the weight vector.
LayerMethod
nextInputSample()
is used to pass the next input
and to calculate the distance to the current weight vector.
LayerMethod
setWinnerIndex()
is used to set the index of the winner unit.
LayerMethod
adaptWeigths()
is used to pull the weight vector in the direction of the current input,
whereby the further away the index of the current node is from the index of the winning node,
the lower the degree of adaptation.
The utility functions at the end compute the Euclidean distances
between weight vectors or indices
or are used for weight adjustment.
Note that the return value of phi()
decreases with increasing parameters.
From the two unit classes defined above, the Jlayer annotation processor generates the
wrapper classes Layer_DecisionUnit_ and Layer_KohonenUnit_.
These classes are used by the following KohonenNet
class to create
a two-dimensional map that is trained with input from a two-dimensional quadratic space.
At the top, the definition of relation full
can be found,
which applies for any pair of indices,
followed by some items to be used, when the weights are randomly initialized.
Next, the central building blocks of the application are defined,
the decisionUnit
array with the associated decisionMaker
wrapper
and the kohArray
array with the associated kohLayer
wrapper.
Method createNet()
instantiates and wraps these arrays
and finally connects the (one-element) vector layer decisionMaker.decisionIO
with the two-dimensional vector field kohLayer.decisionIO
.
Method initNet()
randomly initialises the weights.
public class KohonenNet {Relation full = new Relation()
{ @Override public boolean contains(int[] x, int[] y) { return true; } }; Random rgen; int seed = 4711; double lwb = 0.4, upb = 0.5; private DecisionUnit[] decisionUnit;private Layer_DecisionUnit_ decisionMaker;
private KohonenUnit[][] kohArray;private Layer_KohonenUnit_ kohLayer;
public void createNet(int lines, int cols) { // create decisionMaker decisionUnit = new DecisionUnit[1]; decisionUnit[0] = new DecisionUnit();decisionMaker = new Layer_DecisionUnit_(decisionUnit);
// create Kohonen layer kohArray = new KohonenUnit[width][heigth]; for(int i = 0; < width; i++) { for(int j = 0; j < heigth; j++) { kohArray[i][j] = new KohonenUnit(); } }kohLayer = new Layer_KohonenUnit_(kohArray);
// establish connectionsdecisionMaker.decisionIO.connect(kohLayer.decisionIO, full);
} public void initNet() { rgen = new Random(seed++);kohLayer.initWeights(rgen, lwb, upb);
} public void trainNet() { double x1 = rgen.nextDouble(0.0, 1.0); double x2 = rgen.nextDouble(0.0, 1.0);kohLayer.nextInputSample(x1, x2);
decisionMaker.detWinnerIndex();
kohLayer.setWinnerIndex();
kohLayer.adaptWeights(x1, x2);
} public double[] getWeights(int i, int j) { double[] weights = new double[2]; weights[0] = kohArray[i][j].w[0]; weights[1] = kohArray[i][j].w[1]; return weights; } }
Method trainNet()
implements one training step.
At first, an input sample from the input space is generated randomly
and then the four layer methods from KohonenUnit, which were
designed for that purpose, are executed.
The final method getWeights()
can be used
to extract the networt weights.