Neuronale Netze lernen? Buch kaufen!

Sie hätten gerne einen Einstieg in neuronale Netze, wissen aber nicht, wo Sie beginnen sollen? Kaufen Sie sich dieses Buch! Ich bin absoluter Fan davon und habe es schon allen meinen KI-interessierten Bekannten empfohlen. Das Buch richtet sich an Personen, die noch nicht wissen, wie ein neuronales Netz funktioniert und das aber gerne lernen würden.

neuronale netze selbst programmieren buch

Titel: Neuronale Netze selbst programmieren
Untertitel: Ein verständlicher Einstieg mit Python
Autor: Tariq Rashid
Übersetzung: Frank Langenau
Verlag: O’REILLY
ISBN: 978-3-96009-043-4
Erscheinungsdatum: 28.04.2017
Seitenanzahl: 232

Das Buch ist so einfach und genial geschrieben, als würde der Autor mit Ihnen in eine Bar sitzen und Ihnen ganz genau erklären, wie neuronale Netze funktionieren. Es gibt viele Bücher über Machine Learning oder neuronale Netze. Meistens sind diese aber recht kompliziert und kompakt geschrieben. Nicht so dieses Buch. Es hat etliche Bilder und das Tempo ist relativ langsam verglichen mit anderen Büchern. Auch die verwendete Sprache ist, wie gesagt, eher kumpelhaft, als würde man gemeinsam ein Abenteuer erleben (ja ich weiss, ich sollte nicht übertreiben, aber ich werde echt enthusiastisch wenn ich über dieses Buch erzähle).

Es geht hauptsächlich um zwei Teile: Im ersten Teil geht es um die Theorie hinter neuronalen Netzen – Alles noch ohne Programmierung. Dazu gehört auch ein kleiner Teil Mathematik, der aber im Anhang ebenfalls erklärt wird (falls Sie zu wenig Grundwissen in Mathematik haben, fragen Sie mich, dann kann ich entsprechende Blogbeiträge schreiben). Das Schlimmste, was Sie dabei antreffen werden, ist die Differenzialrechnung der Fehler- und Sigmoidfunktion.

Im zweiten Teil geht es dann um ein konkretes Beispiel, nämlich die Erkennung von handschriftlich gemalten Ziffern von Null bis Neun. Dabei wird keine SCIKIT Learn Bibliothek verwendet, sondern alles selber in Python programmiert. Aber selbst wenn Sie noch kein Python können (oder genau dann!) bietet das Buch einen perfekten Einstieg in die Sprache.

Das Buch ist recht dünn ausgefallen (~211 Seiten) und kann auf Deutsch oder Englisch gekauft werden. Falls Sie Deutsch sprechen, empfehle ich aber diese Version, sie ist wirklich gut und ich persönlich habe am Inhalt genug zu studieren, auch ohne Übersetzungsaufwand.

Ich sage es nochmal: Geniales Buch! Absolut empfehlenswert!

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)

 

Python Spick

Ich werde diesen Beitrag jeweils erweitern, wenn ich eine neue Funktion von Python verwende.

for Schleifen

In Python ist die for-Schleife ein wenig anders als in Java oder C++. In Python dient die for-Schleife zur Iteration über ein Sequenz von Objekten.

Beispiel
tiere = ["Affe", "Elefant", "Giraffe", "Schlängli"]
for x in tiere:
    print x

Besonders praktisch ist die „range“ Funktion in Verbindung mit einer for Schleife. Die folgende Schleife gibt etwa alle Zahlen von 0 bis 10 aus:

n = 10
for i in range(1, n+1):
    print i

Ganz hübsch isch die for Schleife mit dem Unterstrich:

for _ in range(10):
    print("geheimen Code")

Die range Funktion erzeugt bekannterweise ganze Zahlen als Integer beginnend bei Null:

range(3) == [0, 1, 2].

Nun möchte man den geheimen Code 10 mal ausgeben, benötigt aber keine Laufvariable. Dafür wird in Python oft „_“ verwendet – Quasi eine Wegwerfvariable, die nicht in der eigentlichen for-Schleife verwendet wird.

Die Zip Funktion

Die zip() Funktion gibt eine Liste von Tupeln zurück, wobei der i-te Tupel jeweils das i-te Element von jedem Argument enthält.

x = [1, 2, 3]
y = [4, 5, 6]
zipped = zip(x, y)
Resultat: zipped = [(1, 4), (2, 5), (3, 6)]

Python Objektorientierung

Eine Klasse in Python wird einfach mit „class“ erstellt.

Man sollte sich nicht von dem „self“ verwirren lassen: Der erste Parameter einer Methode ist immer eine Referenz self auf die Instanz, von der sie aufgerufen wird.
Der Parameter self erscheint nur bei der Definition einer Methode. Beim Aufruf wird er nicht angegeben, dort hat es also immer einen Parameter weniger.

#file tierfile.py
class Tier(object):
    def __init__(self, anzahlFuesse=4):
        self.anzahlFuesse = anzahlFuesse
 
    def machWas(self):
        print("Hallo ich bin ein Tier")

Ein Tier erstellt man dann folgendmassen. Kein new, keine Typisierung, gar nichts deshalb vorsicht!

import tierfile 
huhn = tierfile.Tier(anzahlFuesse=2)