Unüberwachtes Lernen am Beispiel eines Kohonen-Netzes -- SOM -- Self Organizing Maps
(EN google-translate)
(PL google-translate)
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
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:
|
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
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
Bild 0-3: Testen, ob Beispielfarben vom Netz bestimmten Gebieten zugeordnet werden.