class Input
{
int inputDimension;
float [] v; // weight vectors
Input(int n)
{
inputDimension = n;
v = new float[inputDimension];
// initialize weight vectors to random values
for(int i = 0; i < inputDimension; i++)
{
v[i] = random(0.1, 0.9); // fill weight with random values
}//for i
}//Input
}//class input
class Node
{
int x, y;
int weightCount;
float [] w; // weight vectors
float [] W; // limbo weight vectors
//constructor
Node(int n, int X, int Y)
{
x = X;
y = Y;
weightCount = n;
w = new float[weightCount];
W = new float[weightCount];
// initialize weights to random values
for(int i = 0; i < weightCount; i++)
{
w[i] = random(0.1, 0.9); // fill weight with random values
W[i] = w[i];
}//for i
}//Node
} //class Node
class SOM
{
int mapWidth;
int mapHeight;
Node[][] nodes;
Input[] inputs;
int inputDimension;
int inputPop;
int winx;
int winy;
float radius;
float timeConstant;
float learnRate = 0.1;
PFont font;
SOM(int h, int w, int n, int pop, int space)
{
mapWidth = w;
mapHeight = h;
inputDimension = n;
inputPop = pop;
radius = (h*space + w*space) / 2;
nodes = new Node[w][h];
inputs = new Input[pop];
hint(ENABLE_NATIVE_FONTS);
font = createFont("Times-Roman__",10);
textMode(SHAPE);
textFont(font);
// create nodes/ inputs /initilize map
for(int i = 0; i < w; i++){
for(int j = 0; j < h; j++) {
nodes[i][j] = new Node(n, h, w);
nodes[i][j].x = i*space;
nodes[i][j].y = j*space;
}//for j
}//for i
for(int p = 0; p < pop; p++){
inputs[p] = new Input(n);
}//for p
}//SOM
void initTraining(int iterations)
{
timeConstant = iterations/log(radius);
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void organize(int ipt, int i)
{
radiusDecay = radius*exp(-1*i/timeConstant);
learnDecay = learnRate*exp(-1*i/timeConstant);
//////////////////////////////////////
//get best matching unit i.e. winner//
//////////////////////////////////////
int ndxComposite = bestMatch(inputs[ipt].v);
int x = ndxComposite >> 16;
int y = ndxComposite & 0x0000FFFF;
drawWinner(nodes[x][y].x, nodes[x][y].y, ipt);
//println("bestMatch: " + winx + ", " + winy + " ndx: " + ndxComposite);
/////////////////////////////////////////
//scale best match and neighbors...
for(int a = 0; a < mapWidth; a++) {
for(int b = 0; b < mapHeight; b++) {
float d = dist(nodes[x][y].x, nodes[x][y].y, nodes[a][b].x, nodes[a][b].y);
float influence = exp((-1*sq(d)) / (2*radiusDecay*i));
//println("Best Node: ("+x+", "+y+") Current Node ("+a+", "+b+") distance: "+d+" radiusDecay: "+radiusDecay);
if (d < radiusDecay)
for(int k = 0; k < inputDimension; k++)
nodes[a][b].w[k] += influence*learnDecay*(inputs[ipt].v[k] - nodes[a][b].w[k]);
} //for j
} // for i
} // train()
///////////////////////////////////////////
int bestMatch(float v[])
{
float minDist = sqrt(inputDimension);
int minIndex = 0;
for (int i = 0; i < mapWidth; i++) {
for (int j = 0; j < mapHeight; j++) {
float tmp = weight_distance(nodes[i][j].w, v);
if (tmp < minDist) {
minDist = tmp;
minIndex = (i << 16) + j;
} //if
} //for j
} //for i
// note this index is x << 16 + y.
return minIndex;
}
///////////////////////////////////////////
float weight_distance(float x[], float y[])
{
if (x.length != y.length) {
println ("Error in SOM::distance(): array lens don't match");
exit();
}
float tmp = 0.0;
for(int i = 0; i < x.length; i++)
tmp += sq( (x[i] - y[i]));
tmp = sqrt(tmp);
return tmp;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void drawarc(int amplifier)
{
for (int i = 0; i < mapWidth; i++) {
for (int j = 0; j < mapHeight; j++) {
pushMatrix();
translate(nodes[i][j].x, nodes[i][j].y);
fill(0);
bezier(nodes[i][j].w[0]*amplifier, nodes[i][j].w[1]*amplifier, nodes[i][j].w[2]*amplifier, nodes[i][j].w[3]*amplifier, nodes[i][j].w[4]*amplifier, nodes[i][j].w[5]*amplifier, nodes[i][j].w[6]*amplifier, nodes[i][j].w[7]*amplifier);
popMatrix();
} //for j
} //for i
//println (nodes[3][3].w[0]);
}
void drawWinner (int x, int y, int ipt){
textAlign(CENTER,CENTER);
textFont(font);
fill(0, 255, 162);
text(+ipt, x+(space/2), y+(space/2));
fill(100, map(radiusDecay, 0, radius*exp(-1/timeConstant),0,255));
noStroke();
ellipse(x+(space/2), y+(space/2), radiusDecay, radiusDecay);
} // void drwWinner
void drawInput(int screenH, int space, int amplifier ){
for(int p = 0; p < inputPop; p++){
for(int i = 0; i < inputDimension; i++){
pushMatrix();
translate( p*space +120, screenH);
fill(0);
bezier(inputs[p].v[0]*amplifier, inputs[p].v[1]*amplifier, inputs[p].v[2]*amplifier, inputs[p].v[3]*amplifier, inputs[p].v[4]*amplifier,inputs[p].v[5]*amplifier, inputs[p].v[6]*amplifier, inputs[p].v[7]*amplifier);
popMatrix();
} //for i
textAlign(CENTER,CENTER);
textFont(font);
fill(0, 255, 162);
text(+p, p*space + 140, screenH);
} //for P
} //for void drawInput
}
SOM som;
int maxIters = 5000; //maximum number of interations
int hor = 25; //horizontal nodes
int ver = 20; //vertical nodes
int space = 30; //space between nodes (overlap)
int inputPop = 15; //how many inputs
int inputDimension = 8; //number of feature vectors
int amplifier = 55; //scalar for scaling up circle
int t=0; //
float radiusDecay;
float learnDecay;
PFont font;
int screenW = 800;
int screenH = 700;
int fade = 0;
int last = 0;
String outString;
boolean bGo = false;
void setup(){
//size of applet
size(screenW, screenH+50);
som = new SOM(ver, hor, inputDimension, inputPop, space);
som.initTraining(maxIters);
font = createFont("Times-Roman__.", 10);
textMode(SHAPE);
textFont(font);
}//setup
void draw()
{
bGo = true;
if(keyPressed) {
if (key == 'g' || key == 'G') {
bGo = true;
updateText("Go!");
}
if (key == 'p' || key == 'P') {
bGo = false;
updateText("Paused...");
}
if (key == 'x' || key == 'X') {
maxIters += 100;
som.initTraining(maxIters);
}
if (key == 'z' || key == 'Z') {
maxIters -= 100;
som.initTraining(maxIters);
}
if (key == 's' || key == 'S') {
som.learnRate += 0.05;
learnDecay = som.learnRate;
}
if (key == 'a' || key == 'A') {
som.learnRate -= 0.05;
learnDecay = som.learnRate;
}
if (key == 'w' || key == 'Q') {
som.radius += 10;
radiusDecay = som.radius;
}
if (key == 'q' || key == 'W') {
som.radius -= 10;
radiusDecay = som.radius;
}
////////scale amplifer
if (key == '1' || key == '+') {
amplifier -= 5;
}
if (key == '2' || key == '"') {
amplifier += 5;}
if (key == '1' || key == '+') {
space -= 5;
}
if (key == '2' || key == '"') {
space += 5;}
////////
if (key == 'r' || key == 'R' ) {
setup();
bGo = false;
updateText("Reset");
learnDecay = som.learnRate;
radiusDecay = (som.mapWidth + som.mapHeight) / 2;
}
}
background(255);
if (t < maxIters && bGo){
for(int p = 0; p < inputPop; p++){
som.organize(p, t);
}//for p
som.drawarc(amplifier);
t++;
som.drawInput(screenH, space, amplifier);
}
textPanel();
// fill(0);
// rect(0, 600, 600, 50);
// textFont(font12);
// fill(255);
// text("Radius: "+radiusDecay, 480, 605);
// text("Learning: " +learnDecay, 480, 620);
// text("Iteration " + t + "/" +maxIters, 480, 635);
if (fade > 0) {
fill(255, fade);
textFont(font);
text(outString, 10, 610);
fade -= (millis() - last) / 7;
last = millis();
}
} // for void draw
void updateText(String s)
{
fade = 255;
last = millis();
outString = s;
return;
}
void textPanel() {
fill(0);
rect(10, screenH + 10, 85, 11);
rect(10, screenH + 23, 85, 11);
rect(10, screenH + 36, 85, 11);
fill(255);
textAlign(LEFT);
text("r:" +radiusDecay, 12,screenH + 20);
text("l:" +learnDecay, 12, screenH + 33);
text("t:" + t + "/" +maxIters, 12 ,screenH + 46);
}
///
This sketch represents a 2D self organising feature map. The SOM is presented with a number of bezier splines to cognize/learn. These inputs can be seen along the bottom. The grey circles represent the learn radius.
To control the following parameter click in the applet window and;
Press q / w to toggle learn radius.
Press a / s to toggle learn rate.
press z / x to toggle time.