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_KarteImplementierung
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
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
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
Bild 0-3: Testen, ob Beispielfarben vom Netz bestimmten Gebieten zugeordnet werden.