Ein Listing für C
LIN 8080

Hier folgt ein Listing für ein einfaches Neuronales Netzwerk. Es ordnet die eingegebenen Eigenschaften bestimmten Begriffen zu, d.h. es sortiert nach vorgegebenen Werten. Man kann sehr schön sehen, wie die Gewichtungen in drei bis fünf Durchgängen ein Optimum erreichen, d.h. sich kaum mehr verändern.

Das Listing ist in C gehalten, man kann es mit einem Compiler zur Exe-Datei machen. Es läuft unter DOS, unter Windows sollte man die Dos-Box öffnen, weil sonst die Werte nicht zu sehen sind.

    #include "math.h"
    #include "stdio.h"
    #define L 10
    #define N 7
    #define T 0.5
    float w[N] [L] =
    { {  1, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
      {  1, 0, 1, 1, 0, 0, 0, 0, 0, 0 },
      {  1, 0, 1,-1, 0, 0, 0, 0, 0, 0 },
      { -1, 0, 0, 0, 1, 0, 1, 0, 0, 0 },
      { -1, 0, 0, 0, 1, 0, 0, 1, 0, 0 },
      { -1, 0, 0, 0, 0, 1, 0, 0, 1, 0 },
      { -1, 0, 0, 0, 0, 1, 0, 0, 0, 1 } };
    float b[N];
    float t1(float x, float temp)
    { return 2/(+exp(-x/temp))-1; }

    main() {
    int i, j;
    float inp[L];
    float out[N];
    for (i=0; i<N; i++) {
      b[i]=0;
      for (j=0; j<L; j++)
        b[i] += w[i][j] * w[i][j];
      b[i] = -b[j] + 0.5; }
    printf ("\n" "rizbar plattig koernig loeslich eben
    muschelig hell dunkel durchschnitt gruen" "\n");
    for (i=0; i<L; i++)
      scanf ("%f", &inp[i]);
    printf ("/n"); printf ("/n");
    for (i=0; i<N; i++) {
      out[i]=0;
      for (j=0; j<L; j++)
        out[i] += w[i][j] * inp[j];
      out[i] = t1(out[i] + b[i], T); }
    printf("\n" "Glimmer Klanspat Gips Feldspat Augit
    Quarz Olivin" "\n");
    for (i=0; i<N; i++)
      printf("%10.2f", out[i]);
    }

In den Werten L und N ist die Anzahl der Ein/Ausgabe-Einheiten gegeben. Variable T beeinflusst die Schaltverbindung, hier kann auch eine treppenförmige Funktion stehen. Die Matrix w mit N, L beinhaltet die Gewichtung, ein b von N Feld definiert die Skalenverschiebung. Für die neue Aktivierung ist net i+b zuständig, bei grösser 0 wird aktiviert. Um eine Ausgabe zu aktivieren, muss wegen *0,5 ein Wert von 2,5 überschritten sein, was bei mehreren +1 Inputs möglich wird. Man kann t und b von i variieren und sehen, was dabei rauskommt.

Nach dem Programmstart sieht man eine Zeile mit Eigenschaften, die mit 1 für richtig, 0 für teils teils und -1 für falsch ausgefüllt wird. Das Netz liefert dann die Zuordnung dazu. Man kann die Matrix ausgeben lassen und mehrere Durchgänge ausführen, um zu sehen, was sich wie verändert. Meist reichen drei Abläufe. Um die Fehlertoleranz zu testen, gibt man verschieden viele Werte ein und startet. Bei diesem Netz wird die Gewichtsmatrix von Hand eingegeben. Als Vorgabe hat jede Zelle eine Ausgangs-Aktivität zwischen 0 und 1, die Formel dazu:

s(j) = sigma * ( [summe über k] aus w(j,k) * s(k) )

Nun wird s(k), (das ist die Aktivität der Neuronen der k Schicht) mit Komponenten x(k) des Eingabevektors versorgt. Sigma(x) beschreibt die Antwort eines Neurons durch eine Funktion (Werte zwischen 0 und 1). Gewählt wird die übliche Fermi Funktion mit:

sigma(x) = 1 / (1 + exp(-1))

Das entspricht einer gestreckten Treppenfunktion mit mittlerer Flanken Steilheit für x von 0 bis plus/minus unendlich.

Die Aktivität der Ausgabeschicht ist gleich, sie hat aber andere Indizes, also:

s(i) = sigma * ( [summe über i] aus w(i,j) * s(j) )

Das liefert die Werte y(i) = s(i). Über diese Formeln wird jedem Eingabemuster x ein Ausgabemuster y zugeordnet. Die Art der Zuordnung variiert wegen w(i,j) (auch Synapsenstärke) von der Inputschicht zur Zwischenschicht und von der Zwischenschicht zum Output, wegen w(j,k).

Eine vorgegebene Anzahl von Eingabemustern x(v) wird nun in ein Ausgabemuster y(v) = 1, 2, ..., p abgebildet. Eine Fehlerquelle errechnet sich aus:

E = 0,5 * [summe p über v=1] * [summe über i] ( y(i,v) - s(i) * x(v) ) hoch 2

Über eine Serie vorgegebener Eingabemuster ist E eine Funktion aller Synapsenstärken ( w(i,j) und w(j,k) ) und somit ein Maß dafür, wie gut ein Netz seine Aufgabe erfüllt. Wenn dieser Wert minimal ist, dann ist für w(...) ein guter Wert gewählt (w sei äquivanent zu E). Man kann diesen Vorgabewert auch über Differentiale ausrechnen, oder einfach rumprobieren und selber optimieren. Bei mehreren Zwischenschichten aber weniger zu empfehlen.

Für jedes besuchte Neuron n ergibt sich ein Faktor s(n) * (1 - s(n)) als Aktivität des Neurons n. Für jede Verbindung zwischen zwei aufeinander folgende Neuronen n, n' ergibt sich ein Faktor w(n,n'). Es ergibt sich ein Ausgabefehler eines Neurons ein Faktor [alfa] * [epsylon](v,i) * s(b).

Das Netzwerk führt nun einen Lernschritt aus, wenn ein Musterpaar x(y) und y(v) aufgrund der Neuronen Aktivitäten der Fehler [epsylon](v,i) für jedes v (v = Vorgabemuster) veringert. Bei genügend vielen Lernschritten geht E für alle v gegen 0. Man bekommt aber das Problem lokaler Minimas, das meint, der Lernvorgang kann "steckenbleiben" (alles 0).

Nun, mit oben beschriebenem Netz kann man die XOR Schaltung (out=1, wenn in(1)=1 und in(2)=0 ist) simulieren. Die ist gut vorhersagbar und das bleibt überschaubar für eigene Untersuchungen.

Die Aktivität der Neuronen liefert eine Umkodierung des Eingabemusters durch Ausbildung geeigneter Verbindungen, das Enkoderproblem (lit.: Rumelhardt, 1986, Sejnowski u. Rosenberg, 1987, Zipser u. Andersen, 1988).

Man darf sich nicht von den blöden Formeln abschrecken lassen, die sind nur eine der Mathematik eigenen Exaktheit der Ausdrucksweise zu verdanken, der aber bei grösseren Netzen ganz einfach nützlich ist. Man kann sich das alles auch grafisch anzeigen lassen, dann wird vieles deutlicher.

LIN 8080