kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Unüberwachtes Lernen am Beispiel eines Kohonen-Netzes -- SOM -- Self Organizing Maps

(EN google-translate)

(PL google-translate)

Beschreibung des Kohonen-Netzes: https://de.wikipedia.org/wiki/Selbstorganisierende_Karte

Implementierung

Teil 1: Vorarbeit: Eine Klasse, die ein Kohonen-Netz repräsentiert, noch ohne Lernmethode.

Kohonen kh;
Kohonen kh2;

public void setup()
{
     kh = new Kohonen(10,10,3,0.0,255.0);
     kh.show();
     
     println("Zweites kleines Kohonen-Netz:");
     kh2 = new Kohonen(3,3,2,0.0,1.0);
     kh2.show();
     size(500,500);
}

public void draw()
{
    background(255,0,0);
    fill(0,255,0);
    ellipse(100,100,50,50);
}

Code 0-1: Hauptprogramm on Processing

import java.util.Random;

public class Kohonen
{
     double[][][] v;
     Random zufall = new Random(System.currentTimeMillis());
     public Kohonen(int rasterhoehe, int rasterbreite, int vektordimension, double vmin, double vmax)
     {
          v = new double[rasterhoehe][rasterbreite][vektordimension];
          for(int i=0;i<v.length;i++)
              for(int k=0;k<v[i].length;k++)
                   for(int w=0;w<v[i][k].length;w++)
                       v[i][k][w] = zufall.nextDouble()*(vmax-vmin) + vmin;
     }
     
     public void show()
     {
          for(int i=0;i<v.length;i++)
          {
              for(int k=0;k<v[i].length;k++)
              {
                   print("zeile="+i+" spalte="+k+" Komponenten = [");
                   for(int w=0;w<v[i][k].length;w++)
                       print(v[i][k][w]+" ");
                   println("]");    
              }         
          }             
     }
}

Code 0-2: Vom Hauptprogramm benutzte Klasse Kohonen

KohonenNeu001.zip -- Gesamter Sketch

Die objektorientierte Implementierung einer SOM auf der Basis eines Kohonen-Netzes soll fortgesetzt werden. Als Parameter sollen folgende Werte verwendet werden:

Zwischenstand:

public void setup()
{
     kh = new Kohonen(10,10,3,0.0,255.0);
     kh.show();
     
     println("Zweites kleines Kohonen-Netz:");
     kh2 = new Kohonen(3,3,2,0.0,1.0);
     kh2.show();
     size(500,500);
}

public void draw()
{
    background(255,255,255);
    //fill(0,255,0);
    //ellipse(100,100,50,50);
    
    kh.draw(30,30,200,200);
}

Code 0-3: Haupt-Tab zu KohonenNeu002

import java.util.Random;

public class Kohonen
{
     double[][][] v;
     Random zufall = new Random(System.currentTimeMillis());
     public Kohonen(int rasterhoehe, int rasterbreite, int vektordimension, double vmin, double vmax)
     {
          v = new double[rasterhoehe][rasterbreite][vektordimension];
          for(int i=0;i<v.length;i++)
              for(int k=0;k<v[i].length;k++)
                   for(int w=0;w<v[i][k].length;w++)
                       v[i][k][w] = zufall.nextDouble()*(vmax-vmin) + vmin;
     }
     
     public void show()
     {
          for(int i=0;i<v.length;i++)
          {
              for(int k=0;k<v[i].length;k++)
              {
                   print("zeile="+i+" spalte="+k+" Komponenten = [");
                   for(int w=0;w<v[i][k].length;w++)
                       print(v[i][k][w]+" ");
                   println("]");    
              }         
          }             
     }
     
     public void draw(float xoff, float yoff, float w, float h)
     {
          noStroke();
          float dx = w / (float)v[0].length;
          float dy = h / (float)v.length;
          for(int i=0;i<v.length;i++) //Zeilen
          {
              for(int k=0;k<v[i].length;k++)
              {
                   float ROT = 0;
                   float GRUEN = 0;
                   float BLAU = 0;
                   
                   if(v[i][k].length>=1) ROT = (float)v[i][k][0];
                   if(v[i][k].length>=2) GRUEN = (float)v[i][k][1];
                   if(v[i][k].length>=3) BLAU = (float)v[i][k][2];
                   
                   fill(ROT,GRUEN,BLAU);
                   rect(xoff+(float)k*dx,yoff+(float)i*dy,dx,dy);
                   
              }
          }
     }
     
}

Code 0-4: Tab mit Klasse zu KohonenNeu002

KohonenNeu002.zip
Die Vektoren == Gewichte im Rasternetz werden als farbige Rechtecke dargestellt.

Bild 0-1: Die Vektoren == Gewichte im Rasternetz werden als farbige Rechtecke dargestellt.

         deltmin = 0.1;
         deltmax = 0.5*Math.sqrt( (double)(zeilen*zeilen) + (double)(spalten*spalten) );
         epsmin = 0.0005;  
         epsmax = 0.05;  

Code 0-5: Parameter -- vergl. Darstellung auf Wikipedia.

Nähere Spezifikation:

  • Das Netz soll RGB-Werte (Rot,Grün,Blau-Werte) von Farbvektoren mit drei Komponenten klassifizieren.
  • Dazu werden dem SOM in der Lernphase zufällige Vektoren [0..255,0..255,0..255] präsentiert.
  • Aufgrund dessen entstehen Gebiete ähnlicher Vektoren.
  • Nach dem Lernen können Testvektoren einer Stelle in dem zweidimensionalen Gebiet zugeordnet werden.
Musterlösung
import java.util.Random;

Kohonen kh;
ArrayList<double[]> tests = new ArrayList<double[]>(); // R G B X Y, X Y wird von Kohonennetz berechnet (Mapping)
Random zufall = new Random(System.currentTimeMillis());
double[][] stimuli;
float xoff = 0.0f;
float yoff = 0.0f;
float w = 300.0f;
float h = 300.0f;
float dx,dy;
int zyklen = 100;
public void setup()
{       
     size(600,600);
     int zeilen = 200;
     int spalten = 200;
     w = height;
     h = height;
     dx = w/(float)spalten;
     dy = h/(float)zeilen;
     double deltstart = 0.5*Math.sqrt( (double)(zeilen*zeilen) + (double)(spalten*spalten) );
     double deltend = 0.1;
     double epsstart = 0.1;   
     double epsend = 0.001;  
     stimuli = new double[100][3];
            for(int i=0;i<stimuli.length;i++)
            {              
                 stimuli[i][0] = 255.0*zufall.nextDouble();
                 stimuli[i][1] = 255.0*zufall.nextDouble();
                 stimuli[i][2] = 255.0*zufall.nextDouble();                 
            }
     
     int anzahlLernschritte = stimuli.length*zyklen;;
     double[][] vminmax = {
           {0.0,255.0},
           {0.0,255.0},
           {0.0,255.0}
     };
     kh = new Kohonen(zeilen,spalten,3,vminmax,
     anzahlLernschritte,epsstart,epsend,deltstart,deltend);
     kh.init();
          
     frameRate(60);
}

boolean aktiv = true;
public void draw()
{
    background(0);
    scale(3.0);
    //kh.zeitschritt(100);
    if(aktiv == true) //Lernphase
    {
         for(int i=0;i<zyklen;i++)
            aktiv = kh.lernschrittDurchfuehren(stimuli[zufall.nextInt(stimuli.length)]);
         kh.draw(xoff,yoff,w,h);
    }
    else //Testphase
    {
         double r = zufall.nextDouble()*255.0; 
         double g = zufall.nextDouble()*255.0; 
         double b = zufall.nextDouble()*255.0;
         double[] XY = kh.berechneMappositionMaxwert(new double[] {r,g,b});
         //neuen Wert merken:
         tests.add(new double[] {r,g,b,XY[0],XY[1]});
         noStroke();
         for(int i=0;i<tests.size();i++)
         {
             double[] rgbxy = tests.get(i);
             fill((float)rgbxy[0],(float)rgbxy[1],(float)rgbxy[2]);
             ellipse(xoff+(float)(dx*rgbxy[3]),yoff+(float)(dy*rgbxy[4]),
                     dx,dy);
         }    
    }
}

Code 0-6: Haupt-Tab zu Kohonenneuneuneu011

UML-Klassendiagramm der nachfolgenden Klasse.

Bild 0-2: UML-Klassendiagramm der nachfolgenden Klasse.

public class Kohonen
{
    Random zufall = new Random(System.currentTimeMillis());
    double[][][] v;
    int anzahlLernschritte;
    /** t: aktueller Lernschritt */
    int t;
    /** Epsilon: zeitabhängige Lernrate */
    double e, e_start, e_end;
    /** h: zeitabhängige Entfernungsgewichtung */
    double h;
    /** Delta: Adaptionsradius, Reichweite eines Lernschritts */ 
    double d, d_start, d_end;
       
    double[][] minmax;    
    
     public Kohonen(int rasterhoehe, int rasterbreite, int vektordimension, double[][] vminmax,
     int anzahlLernschritte,  double e_start, double e_end, double d_start, double d_end )
     {
          this.anzahlLernschritte = anzahlLernschritte;
          this.e_start = e_start;
          this.e_end = e_end;
          this.d_start = d_start;
          this.d_end = d_end;
          minmax = new double[vminmax.length][vminmax[0].length];
          for(int i=0;i<minmax.length;i++)
              for(int k=0;k<minmax[i].length;k++)
                 minmax[i][k] = vminmax[i][k];
          
          v = new double[rasterhoehe][rasterbreite][vektordimension];
          init();          
     }

     /** Initialisierung vor der Lernphase: 
     Zufallsbelegung der Neuronen und Zurücksetzen des aktuellen Lernschrittes t*/
     public void init()
     {
          t = 0;
          for(int i=0;i<v.length;i++)
              for(int k=0;k<v[i].length;k++)
                   for(int w=0;w<v[i][k].length;w++)
                       v[i][k][w] = zufall.nextDouble()*(minmax[w][1]-minmax[w][0]) + minmax[w][0];
     }
    
     /** Berechnung der Distanz zwischen zwei Vektoren gemäß der Euklidnorm */
     public double dist(double[] u, double[] v)
     {
         double z=0.0;
         for(int i=0;i<u.length;i++)
            z+=(u[i]-v[i])*(u[i]-v[i]);
         return Math.sqrt(z);   
     }

     /** Berechnung der Distanz zwischen zwei Neuronen gemäß ihren Koordinaten */
     public double distNeuron(int i1, int k1, int i2, int k2)
     {
         return Math.sqrt((double)((i1-i2)*(i1-i2)+(k1-k2)*(k1-k2)));   
     }
     
     /** BERECHNE aktuelles Epsilon: zeitabhängige Lernrate */
     public double berechneE()
     {
         return e_start * Math.pow(e_end/e_start,(double)t/(double)anzahlLernschritte);
     }
     
     /** BERECHNE aktuelles Delta: Adaptionsradius, Reichweite eines Lernschritts */ 
     double berechneD()
     {
         return d_start * Math.pow(d_end/d_start,(double)t/(double)anzahlLernschritte);
     }
     
     
     /** BERECHNE aktuelles h: zeitabhängige Entfernungsgewichtung abhängig von Neuron/Vektor "winner"
         zu Neuron/Vektor "other" */
     public double berechneH(double[] winner, double[] other)
     {
          double dA = dist(winner,other);
          double delta = berechneD();
          return Math.exp((-dA*dA)/(2.0*delta*delta));
     }


    /**
        Liefert die Koordinaten, wo ein Teststimulus nach dem Lernvorgang platziert würde.
        Schwerpunktberechnung in Richtung beider Koordinatenachsen.
    */
    public double[] berechneMapposition(double[] stimulus)
    {
          
          double xm = 0.0;
          double ym = 0.0;
          double summe = 0.0; //Summe aller Gewichte
          
          double maxdistanz = 0.0;
          for(int i=0;i<v.length;i++)
          {
            for(int k=0;k<v[i].length;k++)
            {
                double gewicht = dist(v[i][k],stimulus);
                if(gewicht>maxdistanz)
                   maxdistanz = gewicht;
            }
          }
          for(int i=0;i<v.length;i++)
          {
            for(int k=0;k<v[i].length;k++)
            {
                double gewicht = maxdistanz - dist(v[i][k],stimulus);
                
                xm += (double)k*gewicht*gewicht;
                ym += (double)i*gewicht*gewicht;
                summe += gewicht*gewicht;
            }   
          }
          
          return new double[] {xm/summe,ym/summe};
    }

    /**
        Liefert die Koordinaten, wo ein Teststimulus nach dem Lernvorgang platziert würde.
        Liefert Koordinate des Neurons mit bester Übereinstimmung
    */
    public double[] berechneMappositionMaxwert(double[] stimulus)
    {
          double mindist = Double.MAX_VALUE;
          int inxbest_i = -1;
          int inxbest_k = -1;
          for(int i=0;i<v.length;i++)
          {
            for(int k=0;k<v[i].length;k++)
            {
                double gewicht = dist(v[i][k],stimulus);
                if(gewicht<=mindist)
                {
                     mindist = gewicht;
                     inxbest_i = i;
                     inxbest_k = k;
                }
            }    
          }
          
          return new double[] {(double)(inxbest_k),(double)(inxbest_i)};
    }


    public boolean lernschrittDurchfuehren(double[] w)
    {
         if(t>=anzahlLernschritte)
             return false;
         
         //1. Herausfinden, welches neuron am nächsten an w liegt:
         int index_best_i = -1;
         int index_best_k = -1;
         double distanz_best = Double.MAX_VALUE;
         int shiftwert_i = zufall.nextInt(v.length); //  ... um nicht immer mit dem gleichen Neuron zu beginnen!
         int shiftwert_k = zufall.nextInt(v[0].length); //  ... um nicht immer mit dem gleichen Neuron zu beginnen!
         for(int i=0;i<v.length;i++)
         {
            int ii = (i+shiftwert_i)%v.length;
            for(int k=0;k<v[0].length;k++)
            {
               int kk = (k+shiftwert_k)%v[0].length;
               double dist_neu = dist(v[ii][kk],w);
               if(dist_neu<=distanz_best)
               {
                     index_best_i = ii;
                     index_best_k = kk;
                     distanz_best = dist_neu;
               }
            }   
         }
         int index_best = index_best_i*v[0].length+index_best_k;
         
         //2. Aktuelles DELTA, EPSILON und H berechnen: (H ist die Gewichtung eines entfernten Neurons)
         double eps = berechneE();
         double delt = berechneD();
         //double h =  ist abhängig von gerade betrachtetem Neuron!
         
         //3. Für alle Neuronen, die nahe genug an dem gefundenen liegen einen Lernschritt durchführen: 
         
         for(int i=0;i<v.length;i++)
         {
           for(int k=0;k<v[i].length;k++)
           {
               //3a prüfen, ob aktuelles Neuron innerhalb von DELTA liegt:
               double distneuron = distNeuron(i,k,index_best_i,index_best_k); //Distanz nur von Indices abhängig!
               //double distneuron = distanzNeuronen(i,index_best); //Distanz nur von Indices abhängig!
               
               //Nur weiter machen, wenn kleiner Maximalabstand von bestem Neuron:
               if(distneuron <= delt)
               {
                    //Jetzt h für aktuelles Neuron bezüglich bestem bestimmen:
                    double h = Math.exp((-distneuron*distneuron)/(2.0*delt*delt));
                    
                    //Jetzt den eigentlischen Lernschritt durchführen:
                    for(int u=0;u<v[i][k].length;u++)
                    {
                         v[i][k][u] += eps*h*(w[u]-v[i][k][u]); //Neuron in Richtung Stimulus bewegen!
                    }
               }
           }   
         }
         
             
             
         t++;    
         return true;    
    }
    //Kohonen spezifische Hilfsmethoden:
    
     public void draw(float xoff, float yoff, float w, float h)
     {
          noStroke();
          float dx = w / (float)v[0].length;
          float dy = h / (float)v.length;
          for(int i=0;i<v.length;i++) //Zeilen
          {
              for(int k=0;k<v[i].length;k++)
              {
                   float ROT = 0;
                   float GRUEN = 0;
                   float BLAU = 0;
                   
                   if(v[i][k].length>=1) ROT = (float)v[i][k][0];
                   if(v[i][k].length>=2) GRUEN = (float)v[i][k][1];
                   if(v[i][k].length>=3) BLAU = (float)v[i][k][2];
                   
                   fill(ROT,GRUEN,BLAU);
                   rect(xoff+(float)k*dx,yoff+(float)i*dy,dx,dy);
                   
              }
          }
     }
     
    
}

Code 0-7: Klassen-Tab zu Kohonenneuneuneu011

Kohonenneuneuneu011.zip
Testen, ob Beispielfarben vom Netz bestimmten Gebieten zugeordnet werden.

Bild 0-3: Testen, ob Beispielfarben vom Netz bestimmten Gebieten zugeordnet werden.