• fullscreen
  • Input.pde
  • Node.pde
  • SOM.pde
  • sketch_SOM_2D_Bezier_Normal.pde
  • 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);
      }  
    ///
    
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Marc Fleming

    2D SOM Bezier Spline

    Add to Faves Me Likey@! 3
    You must login/register to add this sketch to your favorites.

    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.

    Marc Fleming
    30 Aug 2011
    A SOM does not need a target output to be specified unlike many other types of network. Instead, where the node weights match the input vector, that area of the lattice is selectively optimized to more closely resemble the data for the class the input vector is a member of. From an initial distribution of random weights, and over many iterations, the SOM eventually converges into a map of stable zones. Each zone is effectively a feature classifier, the graphical output can be thought of as a type of feature map of the input space.
    You need to login/register to comment.