Das Perzeptron

Als einfaches Beispiel für Maschinelles Lernen betrachten wir das Perzeptron. Das Perzeptron ist ein einziges Neuron, das Eingabesignale in ein Ausgabesignal transformiert. Die Eingabesignale können dabei Zahlen sein, die als verschiedene Merkmale zu klassifizierender Eingaben interpretiert werden. Die Ausgabe eines Perzeptrons ist Eins oder Null, je nachdem, ob die Eingabe als der zu erkennenden Kategorie zugehörig klassifiziert wird oder nicht.

Die Ausgabe eines Perzeptrons ergibt sich neben der Eingabe aus intern gespeicherten Werten, sogenannten Gewichten. Für jedes Eingabesignal gibt es dabei ein Gewicht. Zur Berechnung der Ausgabe wird die mit den gespeicherten Gewichten gewichtete Summe gebildet. Zusätzlich speichert das Perzeptron einen sogenannten Bias, der zur gewichteten Summe hinzuaddiert wird. Wenn die so gebildete Summe größer als Null ist, liefert das Perzeptron als Ausgabe eine Eins, wenn nicht ist die Ausgabe Null.

Der Algorithmus, mit dem ein Perzeptron lernt, Eingaben zu klassifizieren ist vergleichsweise einfach. Zu Beginn des Trainings können alle Gewichte und der Bias als Null oder auch mit kleinen zufälligen Zahlen initialisiert werden. Anschließend werden diese Werte anhand von Trainingsbeispielen verändert, die aus Eingabesignalen und einer erwarteten Ausgabe bestehen. Nach Verarbeitung einer zunehmenden Anzahl von Trainingsbeispielen lernt das Perzeptron immer besser die erlernten Eingaben gemäß der erwarteten Ausgabe zu klassifizieren und kann dann auch solche Eingaben klassifizieren, die nicht in den Trainigsbeispielen vorkommen.

Die Veränderung des Bias und der Gewichte erfolgt anhand des folgenden Algorithmus.

Für jedes Trainingsbeispiel:

  • Berechne den Klassifizierungsfehler als Differenz aus tatsächlicher und erwarteter Ausgabe.
  • Ziehe von jedem Gewicht das Produkt aus dem zugehörigen Eingabesignal mit dem Klassifizierungsfehler ab.
  • Ziehe den Klassifizierungsfehler vom Bias ab.

Als Beispiel betrachten wir einen Schritt im Lernprozess eines frisch initialisierten Perzeptrons mit zwei Eingängen. Wenn beide Gewichte sowie der Bias mit Null initialisiert werden, liefert dieses Perzeptron unabhängig von der Eingabe eine Null am Augang, da die intern berechnete Summe gleich Null ist. Wir wollen nun das Perzeptron so trainieren, dass es eine Eins am Ausgang liefert, wenn beide Eingänge Eins sind. Dazu berechnen wir zunächst den Klassifizierungsfehler. Da die tatsächliche Ausgabe Null ist, die erwartete Ausgabe aber Eins, ist der Klassifizierungsfehler \(0-1\), also \(-1\). Von jedem Gewicht (beide sind vor dem Lernschritt Null) ziehen wir nun das Produkt aus dem zugehörigen Eingabesignal (beide sind Eins) und den Klassifizierungsfehler \(-1\) ab. Nach dem Lernschritt sind also beide Gewichte \(0 - 1 \cdot (-1)\), also \(1\). Anschließend ziehen wir den Klassifizierungsfehler vom Bias (der vor dem Lernschritt Null ist) ab und erhalten dabei \(0 - (-1)\), also ebenfalls \(1\). Nach diesem Lernschritt berechnet das Perzeptron mit zwei Einsen als Eingabe die Ausgabe \(1\), da die intern berechnete Summe \(1\cdot1 + 1\cdot1 + 1\), also größer als Null ist.

Um solche Berechnungen nicht länger von Hand ausführen zu müssen, wollen wir sie im folgenden programmieren.

Programmierung eines Perzeptrons

Wir können ein Perzeptron als Array darstellen, das die internen Gewichte sowie den Bias enthält. Die folgende Funktion create erzeugt ein so dargestelltes Perzeptron mit übergebener Anzahl an Eingabesignalen.

def create(input_count):
    # last entry is bias
    weights = [None] * (input_count + 1)

    # initialize weights and bias
    for i in range(0, len(weights)):
        weights[i] = 0.0

    return weights

Alle intern gespeicherten Gewichte werden als 0.0 initialisiert. Da zusätzlich zu den Gewichten für die Eingabesignale auch der Bias gespeichert wird, hat das erzeugte Array ein Element mehr als es Eingabesignale gibt. Der Aufruf create(2) liefert beispielsweise als Ergebnis [0.0, 0.0, 0.0].

Die Funktion output erwartet als ersten Parameter ein als Array dargestelltes Perzeptron, als zweiten Parameter ein dazu passendes Array von Eingabesignalen und berechnet anhand der oben diskutierten Vorschrift, die Ausgabe des übergebenen Perzeptrons für die übergebene Eingabe.

def output(weights, inputs):
    sum = 0.0

    # compute weighted sum of inputs
    for i in range(0, len(inputs)):
        sum = sum + weights[i] * inputs[i]

    # add bias
    sum = sum + weights[len(inputs)]

    # compute output based on sum
    if sum > 0:
        return 1
    else:
        return 0

Zusätzlich zu den bisher eingeführten Programm-Konstrukten enthält diese Deklaration am Ende des Funktions-Rumpfes eine Bedingte Anweisung, die entsprechend der berechneten Summe den Rückgabewert 1 oder 0 spezifiziert. Der Aufruf output(create(2), [1,1]) liefert beispielsweise das Ergebnis 0 (wie alle Aufrufe mit einem frisch initialisierten Perzeptron).

Die Prozedur train erwartet als Parameter ein Perzeptron, ein Array von Eingabesignalen und eine erwartete Ausgabe. Sie passt die intern gespeicherten Gewichte und den Bias entsprechend der oben diskutierten Vorschrift an.

def train(weights, inputs, target):
    # compute error
    error = output(weights, inputs) - target

    # adjust weights
    for i in range(0, len(inputs)):
        weights[i] = weights[i] - error * inputs[i]

    # adjust bias
    weights[len(inputs)] = weights[len(inputs)] - error

Die Prozedur train speichert zunächst den Klassifizierungsfehler in einer Variablen error, die dann verwendet wird, um die Gewichte und den Bias anzupassen. Wir können die Interaktive Python-Umgebung verwenden, um den Effekt der train-Prozedur zu testen.

>>> neuron = create(2)
>>> neuron
[0.0, 0.0, 0.0]
>>> train(neuron, [1,1], 1)
>>> neuron
[1.0, 1.0, 1.0]

Durch den Aufruf der Prozedur train werden die in neuron gespeicherten Gewichte verändert. Das gezeigte Beispiel vollzieht den Lernschritt nach, den wir oben bereits von Hand berechnet hatten.

Um ein Perzeptron mit mehreren Trainings-Beispielen zu trainieren, können wir jene in einem Array speichern. Hier ist ein Array mit vier Trainings-Beispielen, die jeweils als Hash-Map1 dargestellt sind.

training_data = [
    {"inputs": [0, 0], "target": 0},
    {"inputs": [0, 1], "target": 0},
    {"inputs": [1, 0], "target": 0},
    {"inputs": [1, 1], "target": 1},
]

Eine Hash-Map wird zwischen geschweiften Klammern notiert. Jedes sogenannte Feld der Hash-Map hat einen Namen, dem ein Wert zugeordnet ist. Hier haben Hash-Maps für Trainings-Beispiele jeweils zwei Felder, eins mit dem Namen inputs und eins mit dem Namen target. Die gezeigten Paare aus Eingaben und erwarteter Ausgabe sind die der logischen Und-Verknüpfung: Die Ausgabe soll Eins sein, wenn beide Eingaben Eins sind und ansonsten Null.

Die folgende Zählschleife durchläuft das Array mit Trainings-Beispielen und wendet dann die Prozedur train für jedes Beispiel auf ein vorher erzeugtes Perzeptron an.

neuron = create(2)

for i in range(0, len(training_data)):
    example = training_data[i]
    train(neuron, example["inputs"], example["target"])

Auf die in einer Hash-Map (hier in der Variable example gespeichert) enthaltenen Komponenten können wir mit Hilfe von deren Namen zwischen eckigen Klammern zugreifen, wobei wir den Namen einen Doppelpunkt voranstellen.

Da das frisch initialisierte Neuron für die drei ersten Beispiele keinen Klassifizierungsfehler aufweist, wirkt sich nur das letzte (von uns bereits zwei mal berechnete) Beispiel auf die intern gespeicherten Gewichte aus. Nach dem Durchlauf dieser Schleife hat das in der Variablen neuron gespeicherte Array mit internen Gewichten also den Wert [1.0, 1.0, 1.0]. Dieses Neuron liefert für alle Trainings-Beispiele die Ausgabe 1, in den drei ersten Fällen also nicht mehr die erwartete Ausgabe. In der Hoffnung, dass weiteres Training die Situation verbessert, können wir die gezeigte Trainings-Schleife mehrfach ausführen.

neuron = create(2)

for j in range(0,5):
    for i in range(0, len(training_data)):
        example = training_data[i]
        train(neuron, example["inputs"], example["target"])

print(neuron)

In diesem Fall genügt es, das Perzeptron fünf mal hintereinander mit allen Trainings-Beispielen zu trainieren, damit für alle betrachteten Eingaben die erwartete Ausgabe erzeugt wird. Nach dem Ablauf dieser Schleife hat das in neuron gespeicherte Array den Wert [2.0, 1.0, -2.0]. Der erste Eingang wird also mit 2.0 gewichtet, der zweite mit 1.0 und der Bias ist -2.0.

Nach der Schleife wird der Wert der Variablen neuron im Terminal ausgegeben.

Aufgrund der einfachen Struktur eines Perzeptrons lässt sich nicht jede Klassifizierungsaufgabe mit einem einzigen Perzeptron lösen. Ein einfaches Beispiel, das von einem Perzeptron nicht erlernt werden kann, ist die Exklusive Oder-Verknüpfung (XOR). Die Kombination mehrerer Neuronen erlaubt es, komplexere Klassifizierungen (inklusive XOR) zu erlernen, wozu aber auch komplexere Trainingsmethoden erforderlich sind.


  1. Hash-Maps können ähnlich wie Arrays verwendet werden, erlauben aber nicht nur Zahlen als Indizes sondern beliebige Werte als sogenannte Schlüssel. Sie werden in einem anderen Kapitel ausführlicher behandelt. ↩︎