ML: Perzeptron Lernalgorithmus in Python

07.02.2017

Der Perzeptron Lernalgorithmus wird am Besten als Klasse implementiert (Ja, Python kann das, Java Jünger freuen sich!). Werden später mehrere Schichten von Perzeptrons benötigt, kann man diese Klasse beliebig instanzieren. Aber halt, zuerst den einfachen Perzeptron wie beschrieben in ML: Was ist der Perzeptron Lernalgorithmus?.

Bitte beachten Sie, dass ich den Code bewusst mit Kommentaren vollgespammt habe! Dazu noch print-Funktionen, die man auf Wunsch ein- oder auskommentieren kann. Und dann sind alle Variablen und Funktionen auch noch auf Deutsch – Schrecklich! Das mache ich nur hier für die einfachere Analyse des Codes. Wenn ich normal code mache ich natürlich noch mehr Komme… uhm ich meine viel weniger Kommentare und alles auf Englisch 🙂

Wie sieht die Perzeptron Klasse aus?

Die Perzeptron Klasse braucht als grundsätzliche Parameter eine Lernrate, die mit dem griechischen Buchstaben eta beschrieben wird plus eine Anzahl Iterationen, wie oft der Algorithmus die gegebenen Trainingsdaten lernen soll.

Anschliessend kommen drei Funktionen:

Die Funktion lernen nimmt die Trainingsdaten als Parameter an und fängt an, durch diese zu laufen. Wie beim überwachten Lernen üblich muss auch bereits die Klasse, die wir suchen, gegeben sein – Dies ist beim Vektor y der Fall. Wird eine Klasse falsch vorhergesagt, wird das Gewicht entsprechend aktualisiert und das Ganze als Fehler gewertet, damit man am Ende weiss, wie oft die Gewichte aktualisiert wurden.

Apropos vorhersagen: Das geschieht in der Funktion vorhersage (you don’t say!) Dort sieht man auch schön, wie die Funktion nettoeingabe aufgerufen und dann mit der Sprungfunktion die Klasse vorhergesagt wird. Grösser als Null gibt eine 1 und sonst eine -1.

Ganz wichtig: Sobald der (die/das?) Perzeptron gelaufen ist, sind die berechneten Gewichte in self.w gespeichert. Man kann nun einfach die Funktion vorhersage mit einem neuen Objekt aufrufen und der Algorithmus sagt einem, ob es sich um eine 1 oder eine -1 handelt, ohne die tatsächliche Klasse zu kennen.

Deutsche Namen im Code finde ich doof

Ja das ist der Nachteil wenn ich den Code auf Deutsch mache, dann sage ich solche spannende Sätze wie „Die Methode Vorhersage macht die Vorhersage“. Vielleicht mache ich den Code in Zukunft doch lieber auf Englisch, um ein wenig Abwechslung in den Text zu bringen. Natürlich gewöhnt man sich auch besser an den „realitätsüblichen“ Code, wenn er auf Englisch ist.

Dann wiederum bin ich überzeugt, dass jede Hürde, die man einem interessierten Studierenden aus dem Weg räumt, sinnvoll ist, damit man auf den eigentlichen Kern des Codes konzentrieren kann. Und genau diese Mentalität vertrete ich auf meinem gesamten Blog (warum, denken Sie, schreibe ich über Tiger und Elefanten? Ich bin ganz sicherlich kein Machine Learning anwendender Zoowärter…)

Was meinen Sie? Feedback ist jederzeit willkommen.

Genug geschwätzt, wo ist der Code?

Datei MeinPerzeptron.py

# -*- coding: utf-8 -*-
import numpy as np

"""Perzeptron-Klassifizierer
Parameter: 
     eta = Lernrate zwischen 0 und 1
     anzIterationen = Anzahl Iterationen durch Trainingsdaten
"""
class MeinPerzeptron(object):
 
def __init__(self, eta=0.01, anzIterationen=10):
    self.eta = eta
    self.anzIterationen = anzIterationen

"""
Parameter: 
X = Eingabemerkmale x, Matrix in der Form
[Anzahl Objekte, Anzahl Merkmale]
y = eindimensionaler Ausgabearray
Hier steht die effektive Klasse der Objekte drin
""" 
def lerne(self, X, y): 
    # Gewichtsarray
    # Mache neuen Gewichts-Array voller Nullen
    # mit Groesse von X + 1 wegen dem Index 0
    self.w = np.zeros(1 + X.shape[1])
    # Anzahl der Fehlklassifizierungen pro Epoche
    # Mache neuen, leeren Fehler-Vektor
    self.errors = []

    # Mache x mal die for Schleife
    for _ in range(self.anzIterationen):
        errors = 0
        zahl = 0

    # Gehe durch jedes i-te Element von X und y
    # xi erhält den Wert von X
    # target erhält den Wert von y
        for xi, target in zip(X, y):
            # print("xi = ", xi, "y = ", target)
 
            # Sage den Wert des aktuellen Objekts voraus
            prediction = self.vorhersage(xi)
            # Berechte das Gewichtsdelta
            update = self.eta * (target - prediction)
 
            if target != prediction:
                print("y: ", target, "prediction: ", prediction)
            if update != 0:
                print("Aktualisiere Gewichte mit Wert: ", update)
 
            # Aktualisiere das Gewichtsdelta
            self.w[1:] += update * xi
            self.w[0] += update 
 
            # print("Gewichte neu: ", self.w)
            errors += int(update != 0.0) 
 
        self.errors.append(errors)
    print("Fertig. Anzahl Fehler gefunden: ", self.errors) 
    return self

"""
Nettoeingabe z berechnen 
z = w0*x0 + w1*x1 + w2*x2 + w3*x3...
w[1:0] = Alle Elemente startend beim Index 1
w[0] = Nur Element an Index 0 
"""
def nettoeingabe(self, X): 
    netto = np.dot(X, self.w[1:]) + self.w[0]
    # print("Nettoeingabe: ", netto)
    return netto

""" 
Klassenbezeichnung zurueckgeben
Gib für alle Daten von X, die grösser gleich 0 sind,
1 zurueck, und fuer alle -1 
""" 
def vorhersage(self, X):
    return np.where(self.nettoeingabe(X) >= 0.0, 1, -1)

Und wie rufe ich diesen nun auf?

Für den Aufruf legt man einfach eine Instanz der Klasse an und ruft dann die Funktion lerne auf.

Natürlich brauchen wir aber noch Trainingsdaten, um den Algorithmus zu testen. So wie ich das kennengelernt habe, ist das Beispiel mit der Iris-Blume ein übliches Einstiegsbeispiel in den Algorithmus. Die Daten können kostenlos vom Irvine Machine Learning Repository geladen werden.

In den Daten, die wir laden, hat es 150 Objekte dreier Iris-Blumenarten. Da der Perzeptron aber nur zwei Arten unterscheiden kann, werden nur die ersten 100 Daten geladen, die der Blumenarten Iris-setosa und Iris-versicolor. Die zwei Namen müssen für die Ausgabevektor y in die Werte 1 oder -1 transformiert werden. Zur Vereinfachung werden nur zwei Merkmale von den vier vorhandenen geladen.

  • Iris-versicolor = 1
  • Iris-setosa = -1

Im Code werden zuerst die Daten von der angegebenen URL geladen. Zusätzlich zu der Ausführung des Codes werden noch zwei Diagramme mit der Python Bibliothek matplotlib gemacht:

Die erste Grafik zeigt die Beziehung zwischen den zwei gewählten Merkmalen. In diesem Graph sieht man schön, wie man zwischen den Werten eine Gerade ziehen kann – Die Daten müssen linear trennbar sein, wir erinnern uns.

Die zweite Grafik zeigt, wieviele Fehler in den verschiedenen Iterationen = Epochen gemacht werden.

Am Ende wird noch ein Test mit einem Zahlenpaar gemacht. Die Klasse wird korrekt vorhergesagt, weil die Maschine vorher die Gewichte korrekt adjustiert hat.

Datei meinPerzeptron-run.py

# -*- coding: utf-8 -*-
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

import meinPerzeptron

""" Daten laden """ 
df = pd.read_csv('https://archive.ics.uci.edu/ml/'
 'machine-learning-databases/iris/iris.data', header=None)
print(df.tail())


""" Setze y und X """
# Setze die Klassenbezeichnungen und wandle sie um in 1 und -1
# Versicolor = 1, Setosa = -1 
y = df.iloc[0:100, 4].values
y = np.where(y == 'Iris-setosa',-1, 1) 
# Setze das X aus den Spalten 0 und 2
X = df.iloc[0:100, [0, 2]].values

# Achtung ab hier hat X nur noch Indizes 0 und 1

""" Zeichne Plot """
plt.scatter(X[:50, 0], X[:50, 1], 
    color='red', marker='o', label='setosa') 
plt.scatter(X[50:100, 0], X[50:100,1],
    color='blue', marker='x', label='vertosa')
plt.xlabel('Länge des Kelchblatts [cm]')
plt.ylabel('Länge des Blütenblatts [cm]')
plt.legend(loc='upper left')
plt.show()


""" Mache ein Perzeptron und zeichne den Epochenplot """
perzi = meinPerzeptron.MeinPerzeptron(eta=0.1, anzIterationen=10)
perzi.lerne(X,y)
gewichte = perzi.w
print("Die gelernten Gewichte sind: ", gewichte[1:])
plt.plot(range(1, len(perzi.errors)+1), perzi.errors, marker='o')
plt.xlabel('Epochen')
plt.ylabel('Anzahl der Fehlerklassifizierungen')
plt.show()

""" Mache noch einen Test mit den gelernten Gewichten """
print("Was ist wohl eine ine Blume mit den Werten 5.1 und 1.4?")
testX = [5.1, 1.4]
test = perzi.vorhersage(testX)
if test == 1:
    blume = "Iris-versicolor"
else:
    blume = "Iris-setosa"
print("Die Vorhersage sagt: ", test, " und somit eine ", blume)

 

Werbeanzeigen

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s