KohonenNetz.java

//
// Klasse       KohonenNetz
//
// Projekt      FingerPrint
//
// Written by Christian Birzer, Peter Söllner Juni 1998
//
// Die KohonenNetz - Klasse ist der Kern allen Lernens - das Gedächtnis. 
// Sie definiert die Feature- und Output - Map, sowie alle notwendigen 
// Initialisierungs-, Lern- und Suchfunktionen. Eine KohonenNetz - Klasse 
// wird für eine bestimmte Eingangs- und Ausgangsdimension von Vektoren 
// erstellt und kann dann nur mit solchen Vektoren verwendet werden.
// Zusätzlich besitzt die Klasse zwei Zeichenmethoden, die zur 
// Visualisierung der Feature - Map dienen.  

import java.awt.*;

public class KohonenNetz 
{

    private NeuroVektor m_FeatureMap[][];   // Die Feature - Map
    private NeuroVektor m_OutputMap[][];    // Die Output - Map
    private float m_eps, m_sig;             // Aktuelle Varianz und Lernrate
    private int m_zi, m_zj;                 // Zentrum der letzten Suche
    private boolean m_autoeps=false;        // Lernen mit oder ohne Autolernrate

    /*
    **
    **  Konstruktor der KohonenNetz - Klasse, der die Dimension des
    **  Netzes, der Eingabevektoren und der Ausgabevektoren festlegt
    **  und die Maps initialisiert.
    **
    */
    public KohonenNetz(int dim, int in, int out)
    {
        m_FeatureMap=new NeuroVektor[dim][dim];
        m_OutputMap=new NeuroVektor[dim][dim];
        // Neuronen (NeuroVektor) erzeugen.
        for (int i=0; i<m_FeatureMap.length;i++)
            for (int j=0; j<m_FeatureMap[i].length;j++) {
                m_FeatureMap[i][j]=new NeuroVektor(in);
                m_OutputMap[i][j]=new NeuroVektor(out);
            }
    }

    public int GetMapDim()
    {
        // Liefert die Dimension der Feature - Map zurück.
        return m_FeatureMap.length;
    }

    public int GetInputDim()
    {
        // Liefert die Dimension der Eingabevektoren zurück.
        return m_FeatureMap[0][0].GetCount();
    }

    public int GetOutputDim()
    {
        // Liefert die Dimension der Ausgabevektoren zurück.
        return m_OutputMap[0][0].GetCount();
    }
    
    public NeuroVektor GetOutputVektor(int i, int j)
    {
        // Liefert das Neuron an der Stelle (i,j) zurück.
        return m_OutputMap[i][j];
    }

    public float Lernrate(float eps)
    {
        // Setzt die Lernrate neu.
        float tmp=m_eps;
        m_eps=eps;
        return tmp;
    }

    public float Varianz(float varianz)
    {
        // Setzt die Varianz (Radius) neu.
        float tmp=m_sig;
        m_sig=varianz;
        return tmp;
    }

    public float Varianz()
    {
        // Gibt die Varianz zurück.
        return m_sig;
    }

    public void AutoLernrate(boolean a)
    {
        // Stellt die Lernmethode ein: 'a' = true -> Autolernrate aktiv.
        m_autoeps=a;
    }

    public boolean AutoLernrate()
    {
        // Liefert die aktuelle Lernmethode zurück.
        return m_autoeps;
    }


    /*
    **
    ** InitFeatureMap - Methoden, zur Vorbelegung der Feature - Map
    ** mit Anfnagswerten. Es gibt neben der klassischen Methode mit der
    ** Vorbelegung durch Zufallszahlen noch die Möglichkeit die Map 
    ** definiert, in einer Art Pyramidenlandschaft vorzubelegen. 
    **
    */
    public void InitFeatureMap(byte val)
    {
        // Initialisiert die Feature - Map mit einem festen Wert 'val'.
        for (int i=0; i<m_FeatureMap.length;i++)
            for (int j=0; j<m_FeatureMap[i].length;j++) 
                m_FeatureMap[i][j].InitVektor(val);
    }

    public void InitFeatureMap(byte min, byte max)
    {
        // Initialisiert die Feature - Map mit Zufallswerten 
        // zwischen dem Wert 'min' und dem Wert 'max'.
        for (int i=0; i<m_FeatureMap.length;i++)
            for (int j=0; j<m_FeatureMap[i].length;j++) 
                m_FeatureMap[i][j].InitVektor(min,max);
    }
    
    public void InitFeatureMap()
    {
        // Initialisiert die Feature - Map pyramidenförmig, mit 
        // bereits vorhandenen Zentren an den Spitzen (Null - Vektor).
        byte max=50, ri, rj;
        byte n=(byte)(m_FeatureMap.length/5), n2=(byte)(n/2);
        double dy=(4*max)/(n*n), dx=0.0, val=0.0;

        for (int i=0; i<m_FeatureMap.length;i++) {
            if ((ri=(byte)(i%n))==0) dx=0;
            else dx+=((ri<=n2)?dy:-dy);
            for (int j=0; j<m_FeatureMap[i].length;j++) {
                if ((rj=(byte)(j%n))==0) val=max;
                else val+=((rj<=n2)?-dx:dx);
                if (val>max) val=max;
                m_FeatureMap[i][j].InitVektor((byte)val);
            }
        }
    }

    /*
    **
    **  InitOutput - Methoden, zum Vorbelegen der Output - Map.
    **
    */
    public void InitOutputMap(byte val)
    {
        // Initialisiert die Output - Map mit dem festen Wert 'val'.
        for (int i=0; i<m_OutputMap.length;i++)
            for (int j=0; j<m_OutputMap[i].length;j++) 
                m_OutputMap[i][j].InitVektor(val);
    }

    public void InitOutputMap(byte min, byte max)
    {
        // Initialisiert die Output - Map mit Zufallswerten zwischen
        // dem Wert 'min' und dem Wert 'max'.
        for (int i=0; i<m_OutputMap.length;i++)
            for (int j=0; j<m_OutputMap[i].length;j++) 
                m_OutputMap[i][j].InitVektor(min,max);
    }

    /*
    **
    **  Zentrum - Funktion, die das Neuron mit der besten Aktivität
    **  bzgl. dem Eingabevektor ermittelt. Hiefür kommt die GetLevel-
    **  Methode der NeuroVektor - Klasse zum Einsatz.
    **  
    **
    */
    public int Zentrum(NeuroVektor input)
    {
        // Berechnet die Koordinaten des Neurons mit der größten 
        // Aktivierungsenergie für das Eingabemuster 'muster'.
        int i, j, level, minlevel=Integer.MAX_VALUE;

        for (i=0; i<m_FeatureMap.length; i++)
            for (j=0; j<m_FeatureMap[i].length; j++) {
                // Aktivierungslevel für dieses Neuron berechnen.
                level=input.GetLevel(m_FeatureMap[i][j]);
                if (level<minlevel) {
                    // Falls besser wie das bisherige, merken und 
                    // altes verwerfen. 
                    minlevel=level;
                    m_zi=i;
                    m_zj=j;
                }
            }

        return minlevel;
        // Falls 'minlevel' 0 ist, dann konnte bzw. wurde Muster zu 
        // 100% gelernt; es existiert ein genaues Zentrum
        // für diesen Eingabevektor
    }   

    public int ZentrumI()
    {
        // Gibt die Zeile des Zentrum der letzten Suche zurück 
        // (die von der Funktion Zentrum ermittelt wurde). 
        return m_zi;
    }

    public int ZentrumJ()
    {
        // Gibt die Spalte des Zentrums der letzten Suche zurück
        // (die von der Funktion Zentrum ermittelt wurde).
        return m_zj;
    }

    /*
    **
    **  LerneMuster - Funktion, die für ein Ausgabemuster ein Eingabe-
    **  muster anhand der aktuellen Lernrate und Varianz lernt.
    **
    */
    public int LerneMuster(NeuroVektor input, NeuroVektor output)
    {
        // Führt einen Lernschritt für das Eingabemuster 'input' und
        // das dazugehörige Ausgabemuster 'output' durch.
        int level=Zentrum(input), zi=0, zj=0, di, dj, i, j, z;
        float sig2=(m_sig*m_sig)*2, t, eps=(float)Math.sqrt(level+1);
        // Matrixgrenzen berechnen, anhand des Radius (Varianz).
        int iup=m_zi+(int)m_sig, idown=(m_zi<(int)m_sig)?0:(m_zi-(int)m_sig);
        int jup=m_zj+(int)m_sig, jdown=(m_zj<(int)m_sig)?0:(m_zj-(int)m_sig);
 
        iup=(iup>m_FeatureMap.length)?m_FeatureMap.length:iup;
        jup=(jup>m_FeatureMap[0].length)?m_FeatureMap[0].length:jup;
    
        // Falls Autolernen aktiv ist, ignoriere gesetzte Lernrate in 'm_eps'.
        eps=(m_autoeps)?eps/(eps+10):m_eps;      
        
        for (i=idown; i<iup; i++) {
            di=i-m_zi; di*=di;
            for (j=jdown; j<jup; j++) {
                dj=j-m_zj; dj*=dj;
                t=(float)Math.exp(-((di+dj) / sig2))*eps;
                // Berechne die Gewichte in der Feature - Map neu.
                for (z=0; z<m_FeatureMap[i][j].GetCount();z++) 
                    m_FeatureMap[i][j].Add((int)(t*(input.Value(z)-m_FeatureMap[i][j].Value(z))),z);
                // Berechne die Gewichte in der Output - Map neu.
                for (z=0; z<m_OutputMap[i][j].GetCount();z++)
                    m_OutputMap[i][j].Add((int)(t*(output.Value(z)-m_OutputMap[i][j].Value(z))),z);
            }
        }
        return level;
        // Je kleiner 'level', um so besser ist das Zentrum ausgebildet
    }

    /*
    // Diese Methode arbeitet nach einem modifiziertem Verfahren,
    // das anhand der Varianz den Radius der Neuronenbeeinflussung
    // ermittelt. 
    public int LerneMuster(NeuroVektor input, NeuroVektor output)
    {
        int level=Zentrum(input), zi=0, zj=0, di, dj, i, j, z;
        float sig2=m_sig*2, t, eps=(float)Math.sqrt(level+1);
        // 'r' ist maximaler Radius um das Erregungszentrum 
        int r=(int) Math.round(Math.sqrt(sig2*m_lim)), iup=m_zi+r, jup=m_zj+r;
        int idown=(m_zi<r)?0:(m_zi-r), jdown=(m_zj<r)?0:(m_zj-r);
 
        iup=(iup>m_FeatureMap.length)?m_FeatureMap.length:iup;
        jup=(jup>m_FeatureMap[0].length)?m_FeatureMap[0].length:jup;

        // eps=(m_autoeps)?eps/(eps+6):m_eps;           // 97,4% 100%
        eps=(m_autoeps)?eps/(eps+4):m_eps;              // 97,3% 100%
        // eps=(m_autoeps)?eps/(eps+3):m_eps;           // 96,6% 100%       
        // eps=(m_autoeps)?eps/(eps+(float)2.5):m_eps;  // 91,3% 99,1%
        // eps=(m_autoeps)?eps/(eps+2):m_eps;           // 97,7% 100%

        for (i=idown; i<iup; i++) {
            di=i-m_zi; di*=di;
            for (j=jdown; j<jup; j++) {
                dj=j-m_zj; dj*=dj;
                t=((float)(di+dj)) / sig2;
                t=(t<m_lim)?(float)Math.exp(-t)*eps:0;
                for (z=0; z<m_FeatureMap[i][j].GetCount();z++) 
                    m_FeatureMap[i][j].Add((int)Math.round(t*(input.Value(z)-m_FeatureMap[i][j].Value(z))),z);
                for (z=0; z<m_OutputMap[i][j].GetCount();z++)
                    m_OutputMap[i][j].Add((int)Math.round(t*(output.Value(z)-m_OutputMap[i][j].Value(z))),z);
            }
        }
        return level;
        // Je kleiner 'level', um so besser ist das Zentrum ausgebildet
    }
    */

    /*
    **
    **  Die SucheMuster - Funktion liefert für einen Eingabevektor 
    **  einen Ausgabevektor zurück.
    **
    */
    public NeuroVektor SucheMuster(NeuroVektor input)
    {
        // Sucht für das Eingabemuster 'input' das passende Ausgabemuster.
        // Nachdem das Zentrum berchnet wurde, werden dessen Koordinaten
        // zur Ermittlung des Ausgabemusters in der Output - Map verwendet.
        int level=Zentrum(input);
        return m_OutputMap[m_zi][m_zj];
    }

    public void drawZentrum(Image img, Color c, NeuroVektor nv, boolean drawOval)
    {
        // Zeichnet eine Markierung an die Stelle im Bild 'img', an der 
        // für das Eingabemuster 'nv' das Zentrum berechnet wurde. 
        double neurodim = img.getWidth(null) / m_FeatureMap[0].length;
        int d=(int)(neurodim * 2), d2=(int)neurodim;
        Zentrum(nv);
        int x=(int)(m_zj*neurodim), y=(int)(m_zi*neurodim); 

        Graphics offgraphics = img.getGraphics();
                
        if (drawOval) {
            offgraphics.setColor(c);
            offgraphics.drawRect(x,y,d,d);
        } else {
            offgraphics.setColor(Color.black);
            offgraphics.fillRect(x-1,y+d2-1,d+3,3);
            offgraphics.fillRect(x+d2-1,y-1,3,d+3);
            offgraphics.setColor(c);
            offgraphics.drawLine(x,y+d2,x+d,y+d2);
            offgraphics.drawLine(x+d2,y,x+d2,y+d);
        }
    }

    public void drawMuster(Image img, Color c, NeuroVektor nv, int lp[][])
    {
        // Zeichnet die Aktivierungsenergien, die das Eingabemuster 'nv' in den
        // einzelnen Neuronen der Feature - Map erzeugt, in das Bild 'img'.
        // Dabei wird durch Farbabstufungen die stärke der Aktivierung sichtbar.
        // Je kleiner der Rückgabewert der Funktion GetLevel ist, desto größer
        // ist die Aktivierung und desto heller der gezeichnete Farbpunkt.
        double neurodim = img.getWidth(null) / m_FeatureMap[0].length;
        int d=(int)Math.round(neurodim), level; 
        int nr,r=c.getRed(), ng,g=c.getGreen(), nb,b=c.getBlue();

        Graphics offgraphics = img.getGraphics();

        for (int i=0; i<m_FeatureMap.length; i++)
            for (int j=0; j<m_FeatureMap[0].length; j++) {
                level=(int)(Math.sqrt(nv.GetLevel(m_FeatureMap[i][j])));
                if (level<lp[i][j]) {
                    // Nur falls der Levelwert größer ist als der in der
                    // Matrix 'lp', erfolgt eine Ausgabe.
                    lp[i][j]=level;
                    if      (level<2)   level=0;
                    else if (level<5)   level=5;
                    else if (level<10)  level=10;
                    else if (level<20)  level=20;
                    else if (level<30)  level=30;
                    else if (level<40)  level=40;
                    else if (level<50)  level=50;
                    else if (level<100) level=60;
                    else if (level<200) level=75;
                    else if (level<300) level=90;
                    else if (level<400) level=100;
                    else if (level<500) level=110;
                    else if (level<550) level=120;
                    else if (level<580) level=130;
                    else if (level<610) level=140;
                    else if (level<650) level=150;
                    else if (level<999) level=165;
                    else                level=255;
                    nr=(r<=level)?0:(r-level);
                    ng=(g<=level)?0:(g-level);
                    nb=(b<=level)?0:(b-level);
                    offgraphics.setColor(new Color(nr,ng,nb));
                    offgraphics.fillRect((int)(j*neurodim),(int)(i*neurodim),d,d);
                }
            }
    }
}