Datenanalyse

Neuronales Netz

Neuronale Netze versuchen die Vernetzung der Neuronen im Gehirn nachzubilden. Sie sind damit ein Verfahren der künstlichen Intelligenz (KI).

Bild 1 zeigt ein neuronales Netz mit drei Schichten von Neuronen. In dieser Darstellung fließen die Daten von oben nach unten durch das Netz. Jedes Neuron bildet die Summe der an seinen Eingängen anliegenden Werte und berechnet daraus ein Aktivierungs­potenzial. Diesen Wert gibt es an alle Neuronen der nächsten Schicht weiter, jeweils mit einem gewissen Faktor gewichtet. Das "Wissen" des neuronalen Netzes, welches es in der Trainings­phase ansammelt, besteht aus nichts anderem als den Werten dieser Gewichte.

Bevor also ein neuronales Netz etwas Sinnvolles tun kann, muss es trainiert werden. In der Trainings­phase oder auch Lernphase werden in der Eingabe­schicht bestimmte Werte eingegeben. Dann wird kontrolliert, was das neuronale Netz in der Ausgabe­schicht ausgibt, und dies wird mit dem gewünschten Ergebnis verglichen. Die Abweichung wird in das neuronale Netz zurückgespeist (backpropagation), um die Werte der Gewichte zu verbessern. Das Ganze wird zig-tausend Mal mit unter­schiedlichen Werten wiederholt, um die Gewichte immer feiner auszutarieren. Die Trainings­phase eines neuronalen Netzes ist entsprechend rechen­aufwendig.

 

Bild 1: Neuronales Netz mit drei Schichten 

Bild 1: Neuronales Netz mit drei Schichten

 

Vorwärtspropagation

An den Eingängen der Neuronen der Eingabe­schicht wird ein Muster eingegeben, dies ist ein Vektor von reellen Zahlen, der Eingabe­vektor. Die Neuronen der Eingabe­schicht berechnen daraus jeweils für sich ein Aktivierungs­potenzial, dies ist eine reelle Zahl zwischen 0 und 1. Diesen Wert geben sie über die Verbindungen an die Neuronen der nächsten Schicht weiter.

Die Verbindungen zwischen Neuronen sind mit reellen Zahlen gewichtet. Die Neuronen der jeweils nächsten Schicht bilden somit die Summe der gewichteten Aktivierungs­potenziale der Neuronen der vorherigen Schicht. Aus diesem Wert berechnen sie jeweils ebenfalls ihr Aktivierungs­potenzial. Genauso geht es dann zur nächsten Schicht weiter.

Die Neuronen der Ausgabe­schicht zeigen an ihren Ausgängen jeweils ihr Aktivierungs­potenzial. Zusammen­genommen ergibt sich ein Ausgabe­vektor. Anhand dieses Ausgabe­vektors soll das neuronale Netz das Muster, das eingegeben wurde, erkennen. Genauer gesagt soll es unter­schiedliche ähnliche Muster klassifizieren, also beispiels­weise hand­geschriebene Ziffern den Werten 0, ..., 9 zuordnen.

Schwellen­wertfunktion σ

Die Neuronen berechnen ihr Aktivierungs­potenzial aus der Summe ihrer Eingabewerte mit einer Art von Schwellen­wertfunktion σ. Das Aktivierungs­potenzial der einzelnen Neuronen wird dadurch auf einen Wert zwischen 0 und 1 normalisiert. Häufig wird für σ die folgende Funktion gewählt:

σ(x)   =   
1
1 + e-x

Bild 2 zeigt die Funktion σ(x).

 

Bild 2: Funktion σ(x) = 1 / (1 + e-x) 

Bild 2: Funktion σ(x) = 1 / (1 + e-x)

 

 

Bezeichnungsweisen

Für die mathe­matische Formulierung der Berechnungen und später für die Implementierung als Computer­programm sind sehr genaue Bezeich­nungsweisen erforderlich; diese werden im Folgenden angegeben.

Schön ist es nicht, aber im Folgenden notwendig, nämlich Indizes hochgestellt zu schreiben. Während untere Indizes auf die Komponenten einer Struktur verweisen, dient der obere Index dazu, diese Strukturen zu unter­scheiden. Hier sind diese Strukturen die Schichten des neuronalen Netzes, und die Komponenten sind die einzelnen Neuronen dieser Schicht. Somit wird beispiels­weise der Ausgabewert des Neurons j in Schicht r mit sj(r) bezeichnet. Obere Indizes stehen dabei immer in Klammern, um Verwechslungen mit Exponenten zu vermeiden.

Bezeich­nungen

 

n Anzahl der Schichten des neuronalen Netzes
m(r) Anzahl der Neuronen in Schicht r,   r ∈ {0, ..., n-1}
ej(r) Eingabewert für Neuron j in Schicht r
sj(r) Ausgabewert für Neuron j in Schicht r
wi,j(r-1) Gewichtung der Verbindung zwischen Neuron i in Schicht r-1 und Neuron j in Schicht r
xj Eingabewert für Neuron j in Schicht 0
yj Zielwert für den Ausgabewert von Neuron j in der Ausgabe­schicht n-1
fj Fehlerwert für Neuron j in der Ausgabe­schicht n-1
g Gesamtfehler der Ausgabe­schicht
Formeln

Der Eingabewert ej(0) eines Neurons in Schicht 0 ist gleich dem Eingabewert xj:

ej(0)  =  xj

Der Eingabewert ej(r) eines Neurons j in Schicht r > 0 ergibt sich als gewichtete Summe aller Ausgabewerte der Neuronen der vorherigen Schicht r-1:

ej(r)  =   Summek=0, ..., m(r-1)-1  sk(r-1) · wk,j(r-1)

Der Ausgabewert sj(r) eines Neurons j in Schicht r ergibt sich durch Anwendung der Schwellen­wertfunktion σ auf den Eingabewert ej(r):

sj(r)  =  σ(ej(r))

Der Fehlerwert fj für Neuron j in der Ausgabe­schicht n-1 ist die quadratische Abweichung zwischen Ausgabewert sj(n-1) und Zielwert yj:

fj  =  1/2·(sj(n-1) – yj)2

Der Gesamtfehler g der Ausgabe­schicht ist gleich der Summe aller Einzelfehler fj

g  =   Summej = 0, ..., m(n-1)   fj

Vektor­schreibweise

Die Werte in den einzelnen Schichten lassen sich als Vektoren schreiben. In Vektor­schreibweise lassen sich die jeweiligen Berechnungen sehr viel über­sicht­licher darstellen. So ist beispiels­weise der Ausgabe­vektor S(r) einer Schicht r gleich

S(r)  =  s0(r), ..., sm(r)-1(r)

Er wird als Zeilenvektor bzw. als 1×m(r)-Matrix aufgefasst.

Berechnungen in Vektor­schreibweise lassen sich mithilfe der Python-Bibliothek numpy direkt in Programme überführen.

E(r) Eingabe­vektor in Schicht r
S(r) Ausgabe­vektor in Schicht r
W(r-1) die m(r-1)×m(r)-Matrix der Gewichtungen zwischen Schicht r-1 und Schicht r
X Eingabe­vektor in Schicht 0
Y Zielvektor für die Ausgabe­schicht n-1
F Fehlervektor in der Ausgabe­schicht n-1

 

Trainieren des neuronalen Netzes

Zum Trainieren des neuronalen Netzes werden nacheinander unter­schiedliche Eingabe­vektoren X in das neuronale Netz eingegeben und per Vorwärts­propagation jeweils die Aktivierungs­potenziale der folgenden Schichten berechnet. Es wird beobachtet, wie gut sich das entsprechende Aktivierungs­potenzial der Ausgabe­schicht dem jeweils zugehörigen Zielvektor Y annähert.

Die Vorwärts­propagation ist nichts anderes als die Berechnung des Ausgabe­vektors S(n-1) aus einem Eingabe­vektor X.

 

Vorwärtspropagation

Eingabe:

Neuronales Netz mit n Schichten, Eingabevektor X

Methode:

  1. setze E(0) = X

    setze S(0) = σ(E(0))

    für r = 1 bis n-1

    1. setze E(r) = S(r-1) · W(r-1)
    2. setze S(r) = σ(E(r))

 

 

Hierbei ergibt sich der jeweils nächste Eingabe­vektor E(r) einer Schicht r des neuronalen Netzes durch Matrix­multiplikation zwischen der 1×m(r-1)-Matrix S(r-1) und der m(r-1)×m(r)-Matrix W(r-1). Das Ergebnis ist die 1×m(r)-Matrix E(r).

Die Funktion σ wird komponenten­weise auf den jeweiligen Eingabe­vektor E(r) angewandt.

Programm für die Vorwärts­propagation

Die entsprechende Funktion für die Vorwärts­propagation lautet in der Programmier­sprache Python wie folgt. Zuvor wird die Python-Bibliothek numpy eingebunden.

import numpy as np

# Vorwärtspropagation eines Eingabevektors X
# durch ein neuronales Netz mit n Schichten
def propagate(x):
    e[0]=np.array(x).reshape(1,m[0])     # 1 x m[0]-Matrix
    s[0]=sigma(e[0])
    for r in range(1, n):
        e[r]=np.matmul(s[r-1], w[r-1])
        s[r]=sigma(e[r])

Die Funktion σ lässt sich in Python folgender­maßen implementieren:

def sigma(x):
    return 1.0/(1+np.exp(-x))

 

Damit das neuronale Netz lernt, wird in der Trainings­phase nach jeder Vorwärts­propagation eines Eingabe­vektors der Ausgabe­vektor mit den bekannten Zielwerten verglichen und daraus eine Anpassung der Gewichtungen berechnet.

Im Anschluss an die Trainings­phase, wenn das neuronale Netz gut gelernt hat, kann es Eingabe­vektoren mit unbekannten Zielwerten verarbeiten.

Anpassung der Gewichtungen zwischen Schicht 1 und Schicht 2

Es geht darum, die Abweichung zwischen den Aktivierungs­potenzialen der Ausgabe­schicht und den Zielwerten auszuwerten und anschließend die Gewichtungen des neuronalen Netzes so anzupassen, dass ein Lerneffekt eintritt. Dieser Vorgang wird als Backpropagation (etwa Zurückverbreiten, Rückspeisung) bezeichnet.

Im Folgenden wird ein neuronales Netz mit n = 3 Schichten betrachtet, mit einer Eingabe­schicht 0, einer inneren Schicht 1 und einer Ausgabe­schicht 2. Damit wird die Indizierung mit den oberen Indizes anschau­licher. Die Ver­allgemeinerung auf ein neuronales Netz mit mehreren inneren Schichten ist zum Schluss angegeben.

Wenn das neuronale Netz während der Lernphase noch nicht die richtigen Ausgabewerte liefert, müssen die Gewichtungen angepasst werden. Dies geschieht in Abhängigkeit von den Fehlern fj(2), die an den Neuronen der Ausgabe­schicht 2 auf­treten.

Zunächst werden die Gewichtungen wi,j(1) zwischen Schicht 1 und Schicht 2 angepasst. Die Gewichtungen wk,i(0) zwischen Schicht 0 und Schicht 1 werden später angepasst.

Ziel ist es, die Fehlerfunktion g abhängig von den einzelnen Gewichtungen zu minimieren. Hierzu wird die Methode des Gradientenabstiegs verwendet. Gesucht ist der Gradient, also die Steigung der Fehlerfunktion g in Abhängigkeit von der Gewichtung wi,j(1), mit dem Ziel, die Gewichtung in Richtung des Gradienten anzupassen. Die Steigung wird durch die partielle Ableitung von g nach wi,j(1) berechnet.

Da g abhängig ist von fj(2), dieses wiederum von sj(r) und dieses wiederum von ej(r), wird die Ableitung mithilfe der Kettenregel durchgeführt. Die Ableitung der Funktion σ(x) ist

σ'(x)  =  σ(x) · (1 – σ(x))

Dies wird bei der Anwendung der Kettenregel benutzt; die Herleitung ist weiter unten angegeben.

Es folgt die Berechnung der partiellen Ableitung des Gesamtfehlers g nach einer Gewichtung wi,j(2).

Die Formeln mit den partiellen Ableitungen erschließen sich nicht beim schnellen Überfliegen – tatsächlich ist es aber nicht schwierig, sich Schritt für Schritt hindurchzuarbeiten.

 

g
wi,j(1)
    =    
g
fj(2)
 · 
fj(2)
sj(2)
 · 
sj(2)
ej(2)
 · 
ej(2)
wi,j(1)

 

Für die einzelnen Ableitungen in der Kette gilt:

g
fj(2)
für g einsetzen    =    
sj(r)
  Summet = 0, ..., m(2)-1  ft(2)ungleich 0 nur für t = j    =     1

 

fj(2)
sj(2)
für fj(2) einsetzen    =    
sj(2)
 1/2·(sj(2) – yj)2ableiten nach der Potenzregel    =     sj(2) – yj

 

sj(2)
ej(2)
für sj(2) einsetzen    =    
ej(2)
 σ(ej(2))Ableitung der Funktion σ verwenden    =     σ(ej(2)) · (1 – σ(ej(2)))zurück einsetzen    =    sj(2) · (1 – sj(2))

 

ej(2)
wi,j(1)
für ej(2) einsetzen    =    
wi,j(1)
 Summet = 0, ..., m(1)-1  st(1) · wt,j(1)wi,j(1) ist eines der wt,j(1) mit t = i    =    si(1)

 

Insgesamt gilt also für die Ableitung

g
wi,j(2)
    =    1 · (sj(2) – yj) · sj(2) · (1 – sj(2)) · si(1)

 

Wenn also die Fehlerwerte fj(2) der Schicht 2 bekannt sind, so lassen sich die Gewichte wi,j(1) anpassen. Die Anpassung wird durchgeführt, indem der Gradient mit einem kleinen Faktor α multipliziert wird. Der Faktor α ist die Lernrate.

Δwi,j(1)    =    α · 
g
wi,j(1)
    =    α · (sj(2) – yj) · sj(2) · (1 – sj(2)) · si(1)
Vektor­schreibweise

In Vektor­schreibweise ergibt sich die Anpassung dieser Gewichtungen wie folgt:

ΔW  =  S(1)T · (α · (S(2) – Y) ⊙ S(2) ⊙ (1 – S(2))

Die m(1)×m(2)-Matrix der Gewichtungsänderungen ergibt sich durch Matrix­multiplikation der m(1)×1-Matrix S(1)T (der transponierten 1×m(1)-Matrix S(1)) und einem Ausdruck, der eine 1×m(2)-Matrix darstellt. Dieser Ausdruck enthält eine Multi­plikation zwischen dem Skalar α und Vektoren, die komponenten­weise miteinander multipliziert werden. Die komponenten­weise Multi­plikation von Vektoren wird hier mit dem Zeichen ⊙ notiert. Der Vektor 1 – S(2) ergibt sich, indem S(2) komponenten­weise von einem Vektor, der aus lauter 1'en besteht, subtrahiert wird.

Anpassung der Gewichtungen zwischen Schicht 0 und Schicht 1

Gegeben ist ein neuronales Netz mit drei Schichten 0, 1, 2. Wie hängt der Fehler fj(2) eines Neurons j in Schicht 2 von einer Gewichtung wk,i(0) von Neuron k in Schicht 0 zu Neuron i in Schicht 1 ab? Die Berechnung erfolgt ganz ähnlich wie die bei der Anpassung der Gewichtungen zwischen Schicht 1 und Schicht 2, ist aber etwas umfang­reicher.

 

Somit ergibt sich für die partielle Ableitung von fj(2) nach wk,i(0):

fj(2)
wk,i(0)
    =    
fj(2)
sj(2)
 · 
sj(2)
ej(2)
 · 
ej(2)
si(1)
 · 
si(1)
ei(1)
 · 
ei(1)
wk,i(0)

 

 

Für die einzelnen Ableitungen in der Kette gilt:

fj(2)
sj(2)
für fj(2) einsetzen    =    
sj(2)
 1/2·(sj(2) – yj)2ableiten nach der Potenzregel    =     sj(2) – yj

 

sj(2)
ej(2)
für sj(2) einsetzen    =    
ej(2)
 σ(ej(2))Ableitung der Funktion σ verwenden    =     σ(ej(2))·(1 – σ(ej(2)))zurück einsetzen    =    sj(2) · (1 – sj(2))

 

ej(2)
si(1)
für ej(2) einsetzen    =    
si(1)
 Summet  st(1)· wt,j(1)eines der t ist gleich i    =    wi,j(1)

 

si(1)
ei(1)
für si(1) einsetzen    =    
ei(1)
 σ(ei(1))Ableitung der Funktion σ verwenden    =     σ(ei(1))·(1 – σ(ei(1)))zurück einsetzen    =    si(1) · (1 – si(1))

 

ei(1)
wk,i(0)
für ei einsetzen    =    
wk,i(0)
 Summet  st(0) · wt,i(0)eines der t ist gleich k    =    sk(0)

 

Insgesamt gilt also für die Ableitung

fj(2)
wk,i(0)
    =    (sj(2) – y) · sj(2) · (1 – sj(2)) · wi,j(1) · si(1) · (1 – si(1)) · sk(0)

 

Um das Gewicht wk,i(0) in Richtung des Gradienten so anzupassen, dass sich fj(2) verringert, wird

Δ wk,i(0)    =    α ·  
fj(2)
wk,i(0)

 

gebildet; hierbei ist α die Lernrate.

Nun beeinflusst wk,i(0) aber alle fj(2) mit j = 0, ..., m(2). Dies bedeutet, dass die Gewichtsänderungen für alle j berechnet und aufsummiert werden müssen, um den Gesamtfehler g zu verringern.

g
wk,i(0)
    =     Summej = 0, ..., m(2)-1  (sj(2) – y) · sj(2) · (1 – sj(2)) · wi,j(1) · si(1) · (1 – si(1)) · sk(0)

 

Etwas umgestellt lautet die Formel

g
wk,i(0)
    =    sk(0) · si(1) · (1 – si(1)) ·  Summej = 0, ..., m(2)-1   wi,j(1) · (sj(2) – y) · sj(2) · (1 – sj(2))

 

In Vektor­schreibweise ergibt sich für alle wk,i(0)

Δ W(0)    =    S(0)T · (S(1) ⊙ (1 – S(1))) · W(1) · (α · (S(2) – Y) ⊙ S(2) ⊙ (1 – S(2)))

 

Hierbei steht das Zeichen ⊙ wieder für die komponenten­weise Multi­plikation von Zeilen­vektoren gleicher Länge. Das normale Multi­plikationszeichen · steht hier für die Matrix­multiplikation, wobei ein Zeilenvektor der Länge m wieder als 1×m-Matrix aufgefasst wird.

In dieser ziemlich komplizierten Formel kommt ein Teil vor, der bereits bei der Anpassung der Gewichte zwischen Schicht 1 und Schicht 2 berechnet worden ist. Dies wird im Backpropagation-Algorithmus ausgenutzt, hier allgemein angegeben für ein neuronales Netz mit n Schichten:

 

Backpropagation

Eingabe:

Neuronales Netz mit n Schichten, Eingabevektor X, Zielvektor Y, Lernrate α

Methode:

  1. setze r = n-1

    setze F(r) = S(r) – Y

    setze H(r) = F(r)S(r) ⊙ (1 – S(r))

    für r = n-2 abwärts bis 1 wiederhole

    1. setze F(r) = H(r+1) · W(r)T
    2. setze H(r) = F(r)S(r) ⊙ (1 – S(r))

    für r = n-1 abwärts bis 1 wiederhole

    1. setze W(r-1) = S(r-1)T · α · H(r)

 

 

Die Implementierung in der Programmier­sprache Python lautet wie folgt.

def backpropagate(y):
    y=np.array(y).reshape(1, m[n-1])  # 1 x m[n-1]-Matrix
    r=n-1
    f[r]=s[r] - y
    h[r]=f[r]*s[r]*(1-s[r])
    # Fehler zurückpropagieren
    for r in range(n-2, 0, -1):
        f[r]=np.matmul(h[r+1], w[r].T)
        h[r]=f[r]*s[r]*(1-s[r])
    # Gewichte anpassen
    for r in range(n-1, 0, -1):
        w[r-1] -= np.matmul(s[r-1].T, alpha*h[r])

 

Implementierung der Trainingsphase

Die folgende Funktion training erzeugt mithilfe einer Funktion randomInput ver­schiedenen Paare (X, Y) von Eingabe­vektoren X und zugehörigen Zielvektoren Y. Der Eingabe­vektor wird mit propagate per Vorwärts­propagation im neuronalen Netz verarbeitet. Die Funktion backpropagate vergleicht den errechneten Ausgabe­vektor mit dem Zielvektor Y und speist den Fehler per Backpropagation zurück in das neuronale Netz, um die Gewichtungen anzupassen. Das Training wird k-mal durchgeführt (zum Beispiel k = 50.000).

def training(k):
    for _ in range(k):
        x, y=randomInput()
        propagate(x)
        backpropagate(y)

 

Anstatt wie hier zufällig erzeugte Daten zu verwenden, wird normaler­weise ein Datensatz mit realen Trainings­daten verwendet, so etwa bei der Erkennung von hand­geschriebenen Ziffern.

Anhang: Ableitung der Funktion σ

Es gilt mit zweimaligem Anwenden der Kettenregel, hier ausführlich Schritt für Schritt durchgeführt:

σ(x)'   =   Klammer auf
1
1 + e-x
Klammer zu
'   =   (1 + e-x)-1 'Kettenregel anwenden   =   (-1) · (1 + e-x)-2 · (1 + e-x)'1 ' = 0   =   (-1) · (1 + e-x)-2 · e-x 'Kettenregel anwenden   =   (-1) · (1 + e-x)-2 · e-x · (-x)'x ' = 1   =   (-1) · (1 + e-x)-2 · e-x · (-1)   =   
e-x
(1 + e-x)2
+ -    =   
e-x
(1 + e-x)2
  + 
1
(1 + e-x)2
  –  
1
(1 + e-x)2
umformen   =   
1 + e-x
(1 + e-x)2
  –   Klammer auf
1
1 + e-x
Klammer zu
2ersten Bruch kürzen   =   
1
1 + e-x
  –   Klammer auf
1
1 + e-x
Klammer zu
2einsetzen   =   σ(x) – (σ(x))2ausklammern   =   σ(x) · (1 – σ(x))

Literatur

[RHW 86]   D.E. Rumelhart, G.E. Hinton, R.J. Williams: Learning representations by back-propagating errors. Nature, Vol. 323, 533-536 (1986)

 

Weiter mit:   [Handgeschriebene Ziffern erkennen] oder   [up]

 


H.W. Lang   mail@hwlang.de   Impressum   Datenschutz
Created: 17.11.2020   Updated: 19.08.2024
Diese Webseiten sind während meiner Lehrtätigkeit an der Hochschule Flensburg entstanden