**Dies ist eine alte Version des Dokuments!**
Neuronale Netze
Ziel von diesem Kurs ist, mit Python selbst ein künstliches neuronales Netz zu programmieren, welches handgeschriebene Zahlen erkennen kann.
1. Plan
Zeiten: 9 - 12, 13 - 16 (9 ist fix, die anderen Zeiten flexibler)
Datum | Inhalt |
---|---|
MO Morgen | Slides I |
Einführung & Übersicht: Was sind neuronale Netze & künstliche Intelligenz? | |
NN Programmieren: Struktur & Feedforward | |
MO Nachmittag | Aufgaben 1 und 2 |
Aufbau einfache NN (noch ohne backpropagation) | |
DI Morgen | Slides 2 |
Mathematik (Ableitung, Gradientenabstieg, …) | |
Training durch Backpropagation | |
DI Nachmittag | Aufgaben 3 - … |
Neuronales Netz inkl. Backpropagation programmieren | |
MI Morgen | Recap: Neuronale Netze Programmieren |
weiter an Aufgaben | |
MI Nachmittag | weiter an Aufgaben |
DO Morgen | weiter an Aufgaben |
DO Nachmittag | Buffer |
2. Materialien
Slides
Files
3. Aufgaben
Erstelle GitHub-Repo z.B. mit Namen „neural_networks“. Lege alle deine Code-Files hier drin ab. Committe und pushe regelmässig.
Aufgaben: Siehe Slides
Aufgabe 1
- Ziel: Feedforward programmieren, Erfolgsquote berechnen
- Erstelle File
01_toy_problem_feedforward.py
(resp.<...>.ipynb
) - Lese den Datensatz (CSV-Format)
data_dark_bright_test_4000.csv
ein. - Berechne für jeden Datenpunkt (1 Zeile im File) den Output durch Feedforward. Verwende dazu die beiden Gewichtsmatrizen: $$w^A = \begin{pmatrix} −0.3&−0.7&−0.9&−0.9\\−1&−0.6&−0.6&−0.6\\0.8&0.5&0.7&0.8 \end{pmatrix}$$ $$w^B = \begin{pmatrix} 2.6&2.1&−1.2\\−2.3&−2.3&1.1 \end{pmatrix}$$
- Vergleiche jeweils den berechneten Output mit dem Target Output
- Berechne in %, für wie viele Datenpunkte das NN den richtigen Output liefert (Erfolgsquote). Diese Zahl gibt an, wie gut das NN funktioniert.
- Besprich deine Lösung mit dem Lehrer.
Tipps:
- CSV-File einlesen:
### OPTION 1 """File wird direkt in eine Liste eingelesen""" with open('my_file.csv', 'r') as f: data_list = f.readlines() # erstellt Liste ### OPTION 2 """Gehe File Zeile für Zeile durch, muss dann selbst in Liste schreiben""" import csv # ganz oben in Code with open('my_file.csv', 'r',newline='') as csv_file: csv_reader = csv.reader(csv_file) for row in csv_reader: # do something with row
- Achtung: Liest man File ein, werden Zahlen typischerweise als Text interpretiert. Daher muss man diese noch in ints umwandeln. Mit List Comprehensions geht dies sehr einfach.
Aufgabe 2
Bemerkung für 2024: Leider hatten wir keine Zeit, die objektorientierte Programmierung (OOP) zu behandeln. Recherchiere deshalb selbst kurz dazu im Internet oder studiere ein passendes Video, z.B. https://www.youtube.com/watch?v=q2SGW2VgwAM
- Ziel: Grundgerüst für ein neuronales Netzwerk objektorientiert programmieren
- Erstelle File
02_feedforward_oop.py
(resp.<...>.ipynb
) - Der Code soll eine Klasse
Network
enthalten:- Dieser soll als Argumente übergeben werden:
- Anzahl Input-Neuronen
- Anzahl Neuronen im Hidden Layer
- Anzahl Output-Neuronen
- Diese soll die Gewichtsmatrizen als Attribute enthalten:
- in der init-Methode sollen die beiden Gewichtsmatrizen erzeugt werden:
self.wA = ..., self.wB = ...
- diese sollen die richtigen Dimensionen haben
- die Werte sollen Zufallszahlen zw. -0.5 und 0.5 sein (
np.random.rand
)
- Die Klasse hat eine Methode
feedforward
. Dieser wird als Argument ein Input-Array übergeben. Diesen ‘feeded’ sie dann ‘forward’ und berechnet den zugehörigen Output. - Die Klasse hat eine Methode
test
. Dieser kann man als Argument einen Datensatz übergeben, der dann durch das NN gefüttert wird. Es wird die Erfolgsquote zurückgegeben: Für wie viele (in Prozent) der Datenpunkte produziert das NN einen korrekten Output?. - Lade sowohl die Daten des Toy-Problems wie auch die MNIST Daten ein. Erstelle je ein Neuronales Netz (Anzahl Neuronen pro Layer richtig wählen) und teste mit der
test
Methode, wie gut dein NN funktioniert. - Tipp: Da die Gewichtsmatrizen zufällig erzeugt werden, sollte also bei unserem Toy-Problem eine Erfolgsquote von etwa 50% resultieren.
- Besprich deine Lösung mit dem Lehrer.
Aufgabe 3
- Ziel: NN für Toy-Problem trainieren
- Mache eine Kopie des Files
02_feedforward_oop.py
und speichere diese unter dem Namen03_toyproblem_training.py
(resp.<...>.ipynb
) - Kommentiere alles, was mit dem MNIST-Datensatz zu tun hat, aus. Für den Moment wollen wir uns nur um das Toy-Problem kümmern.
- Erweitere deine Network-Klasse um das Attribut
learning_rate
. Ein Wert für diese soll als Argument für die__init__
-Methode übergeben werden, wenn einNetwork
-Objekt erstellt wird. - Erweitere deine
Network
-Klasse um eine Methodetrain
. Dieser soll ein Datensatz übergeben werden, mit dem das Netzwerk trainiert werden soll. - Alles Wichtige für diese Methode findest du auf der Slide «Zusammenfassung: Training NN»
- Bestimme vor und nach dem Trainieren die Erfolgsquote deines Netzwerks.
- Gratuliere: Nun hast du dein erstes komplettes NN programmiert!
- Contest: Wer erzielt das NN mit der höchsten Erfolgsquote?Spielregeln: Verwende ausschliesslich die Trainingsdaten fürs Training und die Testdaten für das Bestimmen der Erfolgsquote.
- Besprich deine Lösung mit dem Lehrer.
Aufgabe 4
- Ziel: NN für beliebigen Datensatz
- Mache eine Kopie des Files
03_toyproblem_training.py
und speichere diese unter dem Namen04_neural_network.py
(resp.<...>.ipynb
) - Passe nun deinen Code so an, dass du einen beliebigen Datensatz einlesen kannst.
- Wenn du die letzte Aufgabe gut und sauber programmiert hast, solltest du diese Aufgabe sehr schnell erledigt haben.
- Lade sowohl die Daten des Toy-Problems wie auch die MNIST Daten ein. Erstelle je ein Neuronales Netz und trainiere dieses mit der
train
Methode. Bestimme danach die Erfolgsquote des NN. - Contest: Wer erzielt das beste Resultat für den Handschrifterkenner? Es sollen wieder nur die Trainingsdaten fürs Training und die Testdaten für die Erfolgsberechnung verwendet werden.
- Besprich deine Lösung mit dem Lehrer.
Zusatzaufgaben
Es gibt verschiedene Arten von Zusatzaufgaben. Lese sie durch und entscheide selbst, welche du lösen möchtest. Der Schwierigkeitsgrad ist in * angegeben:
- *: einfach
- **: mittel
- ***: anspruchsvoll
Aufgabe Z1: Zahlen anzeigen (*)
- Schreibe eine Methode, die als Input eine Zeile aus dem MNIST-Datensatz nimmt (als String, Liste, numpy-Array, was dir am besten gefällt).
- Die Methode erzeugt dann aus der Pixelinformation ein Bild. Dieses kann sowohl direkt angezeigt wie auch auf der Festplatte gespeichert werden.
- Verwende dafür die matplotlib-Library.
Aufgabe Z2: Untersuchung Performance (*)
- Untersuche, wie die Performance von der Grösse des Hidden Layers abhängt.
- Stelle dies graphisch dar: Erfolgsquote (y-Achse) vs. Anzahl Neuronen im Hidden Layer (x-Achse).
- Nutze dafür die matplotlib-Library.
Aufgabe Z3a: Zwei Hidden Layers (*)
Füge deinem Neuronalen Netz einen zusätzlichen Hidden Layer hinzu.
Aufgabe Z3b: Beliebig viele Hidden Layers (**)
- Füge eine beliebige Anzahl Hidden Layers hinzu. Der Benutzer kann über eine beliebig grosse Liste angeben, aus wie vielen und wie grossen Hidden Layers das Netzwerk bestehen soll. Diese Liste wird als Argument beim erzeugen des Netzwerks an die
__init__
-Methode übergeben. - Die Liste $[784,300,200,500,10]$ würde dann automatisch ein NN erzeugen mit:
- Input Layer mit $784$ Neuronen
- $3$ Hidden Layers, welche aus $300$ (1st Hidden Layer), $200$ (2nd Hidden Layer) und $500$ (3rd Hidden Layer) Neuronen bestehen
- Output Layer mit $10$ Neuronen
Aufgabe Z4: Bias (**)
- Lese die Theorie zum Bias (siehe z.B. Theorie unten und Website: http://neuralnetworksanddeeplearning.com/chap1.html)
- Überlege dir: Was bewirkt der Bias im NN? Besprich deine Überlegungen mit dem Lehrer.
- Erweitere deinen Code, in dem du jedem Neuron (ausser Input-Neuronen) einen Bias hinzufügst.
- Falls du bereits eine der Zusatzaufgaben mit den zusätzlichen Hidden Layers gelöst hast, kannst du gerne diese Version mit dem Bias erweitern.
- Gehe vor wie bei den Gewichten:
- Random Werte auswählen
- Optimieren durch Backpropagation
Aufgabe Z5: Anderer Datensatz (*)
- Suche im Internet einen anderen (aber vergleichbaren) Datensatz und programmiere ein entsprechendes neuronales Netzwerk.
- Beispiel: handgeschriebene Buchstaben
Aufgabe Z6: Keras (**)
- Keras ist eine professionelle Python-Library für AI.
- Nutze Keras, um ein NN zur Erkennung von handgeschriebenen Zahlen zu erzeugen.
- Wichtig! Recherchiere zuerst, auf welchen Python-Versionen Keras läuft. Es kann sein, dass es mit den neusten Versionen noch nicht kompatibel ist.
- Installation mit pip:
python -m pip install --upgrade pip python -m pip install tensorflow python -m pip install keras
Aufgabe Z7: GUI (***)
- Erstelle ein Programm, in welchem man mit der Hand/per Stift eine Zahl zeichnen kann und das Netzwerk die gezeichnete Zahl erkennt.
- Das Netzwerk sollte im Vorfeld gut trainiert werden und dann so verwendet werden. Man sollte also nicht bei jeder Eingabe zuerst das Netzwerk trainieren müssen.
- Verwende eine GUI-Library für Python wie PyGame, Tkinter, PyQT … (recherchiere selbst!)
- Es ist auch denkbar, dies in einer anderen Programmiersprache oder einem entsprechenden Framework zu tun. Dies solltest du aber nur tun, wenn du bereits Erfahrung damit hast. Überprüfe zuerst, ob ein NN überhaupt gut umsetzbar ist. Z.B. sollte die Sprache die nötigen Mathe-Tools beinhalten.
Aufgabe Z-ULTIMATE!!!
- Schreibe ein Programm, welches viele der bisherigen Aufgaben & Zusatzaufgaben beinhaltet.
- NN für den MNIST-Datensatz
- Das Programm soll ein GUI haben, bei dem man die Anzahl Hidden Layers und deren Anzahl Neuronen einstellen kann.
- Das NN soll dann per Knopfdruck trainiert und getestet werden können.
- Gute Runs sollen per Knopfdruck gespeichert und geladen werden. Die Gewichtsmatrizen und Learning Rate sollen also irgendwie gespeichert und wieder eingelesen werden können.
- Auf einem quadratischen Feld soll dann per Stift eine Zahl eingezeichnet werden können.
- Diese Zahl wird dann erkennt und das Resultat ausgegeben.
4. Theorie
In diesem Kapitel werden einige zusätzliche Informationen zur benötigten Mathe gegeben.
Numpy
Möchte man mathematische Dinge in Python programmieren, lohnt es sich, mit numpy zu arbeiten, einer umfassenden Library für Alles, was mit Mathematik zu tun hat. Unter anderem bietet es einem die Möglichkeit, Matrizen zu erzeugen und mit diesen zu rechnen.
Zuerst musst du numpy installieren. Dies geht am einfachsten mit pip:
python -m pip install numpy
Um mit Numpy zu arbeiten, musst du dieses zuerst importieren:
import numpy as np
Numpy-Funktionen werden dann wie folgt aufgerufen:
np.sin(1.4) # berechnet den Sinus von 1.4 np.sqrt(2.0) # berechnet die Wurzel von 2
Matrizen & Vektoren
Eine Matrix ist nicht nur eine Computersimulation, in der wir eventuell leben, sondern auch etwas sehr wichtiges in der Mathematik: Sie ist eigentlich nichts anderes als eine Tabelle, bestehend aus Zahlen. Eine $n \times m$-Matrix ist immer von der folgenden Form: $$ \begin{pmatrix} a_{1,1} & a_{1,2} & \ldots & a_{1,m} \\ a_{2,1} & a_{2,2} & \ldots & a_{2,m} \\ \ldots & \ldots & \ddots & \ldots \\ a_{n,1} & a_{n,2} & \ldots & a_{n,m} \\ \end{pmatrix} $$
Beispiel: Die folgende Matrix ist eine $3\times 5$-Matrix : $$ \begin{pmatrix} 1 & 5 & 2 & -4 & 7\\ 0 & -3 & 4 & 0 & 1\\ -2 & -1 & 0 & 3 & 2 \end{pmatrix} $$
Ein Vektor ist eine $n\times 1$-Matrix: $$ \begin{pmatrix} 4 \\ -3 \\ 2 \end{pmatrix} $$
Matrizen in Numpy
Die wichtigsten einfachen Befehle zu Matrizen in Python sind:
A = np.array([[1,2,3],[4,5,6]]) # eine $2\times 3$-Matrix erzeugen A.shape # Dimension von A abfragen -> (2,3) Z = np.zeros((2,3)) # Null-Matrix (alle Komponenten = 0) mit angegebener Dimension erzeugen R = np.random.rand(2,3) # Zahlen mit Zufallswerten im Interval (0,1) erzeugen A.T # Matrix transponieren, z.B. von Dim (2,3) -> Dim (3,2)
Matrixmultiplikation: Zwei Matrizen $A$ und $B$ können miteinander multipliziert werden, wenn ihre Dimensionen kompatibel sind. Machen wir ein einfaches Beispiel. Wir wollen eine $3\times 2$-Matrix $A$ mit einer $2\times 4$-Matrix $B$ multiplizieren. Dies ist möglich, weil die Breite von $A$ mit der Höhe von $B$ übereinstimmt. Die resultierende Matrix $C = A \cdot B$ ist dann eine $3\times 4$-Matrix: $$ A = \left( \begin{array}{cc} 2 & 1 \\ 3 & 0 \\ 2 & 2 \\ \end{array} \right)\,, \quad B = \left( \begin{array}{cccc} 2 & 1 & 0 & 1 \\ 0 & 2 & 1 & 1 \\ \end{array} \right) $$
$$C = A\cdot B = \left( \begin{array}{cccc} 2\cdot 2 + 1\cdot 0 & & 2\cdot 1 + 1\cdot 2 & & 2\cdot 0 + 1\cdot 1 & & 2\cdot 1 + 1\cdot 1\\ 3\cdot 2 + 0\cdot 0 & & 3\cdot 1 + 0\cdot 2 & & 3\cdot 0 + 0\cdot 1 & & 3\cdot 1 + 0\cdot 1\\ 2\cdot 2 + 2\cdot 0 & & 2\cdot 1 + 2\cdot 2 & & 2\cdot 0 + 2\cdot 1 & & 2\cdot 1 + 2\cdot 1\\ \end{array} \right) = \left( \begin{array}{cccc} 4 & 4 & 1 & 3 \\ 6 & 3 & 0 & 3 \\ 4 & 6 & 2 & 4 \\ \end{array} \right) $$
Mit numpy multipliziert man die beiden Matrizen $A$ und $B$ dann wie folgt:
np.dot(A,B)
Aufgabe: Überprüfe das Beispiel oben selbst mithilfe von numpy.
Vektoren: Wie oben gesagt, ist ein Vektor eine $n \times 1$-Matrix. Der Vektor v = np.array([1,2,3])
hat dann die Dimension (3,)
(vergewissere dich mit v.shape
). Für unsere Rechnungen ist es meist besser, wenn die Matrix die Dimension (3,1)
hat. Dies erreicht man mit dem reshape-Befehl:
v = np.array([1,2,3]) # shape: (3,) v = v.reshape((-1,1)) # shape: (3,1)
Aufgabe: Betrachte den Vektor $$V = \begin{pmatrix} 2 \\ 3 \\ 5 \end{pmatrix} $$
Was gibt dann ($^T$ steht für Transponieren):
- $V \cdot V$
- $V \cdot V^T$
- $V^T \cdot V$
Überlege dir zuerst: Welche Dimension hat das Resultat? Rechne dann aus mithilfe von numpy.
Drawing in Jupyter
Mit Stift oder Tastatur direkt in Jupyter-Notebook zeichnen. Kann z.B. Zahlen zeichnen und von neuronalem Netz analysieren lassen.
Achtung: Funktioniert NICHT in VSCode, verwende das 'richtige' Jupyter-Notebook.
Setup Jupyter-Notebook
in Konsole:
- Verwende passende Python Version (z.B. funktioniert 3.11 gut, 3.8.5 aber nicht, Stand Juli 2023)
- Jupyter-NB installieren:
pip install notebook
- Navigiere mit
cd
in Projektordner. - Starte Jupyter-NB (öffnet in Browser):
jupyter-notebook
- Öffne dort bestehendes
.ipynb
File oder erstelle ein neues
DrawingWidget
Installation (kann direkt im Jupyter-Notebook gemacht werden)
!pip install ipycanvas-drawing !pip install Pillow
Drawing Canvas:
from ipycanvas_drawing import DrawingWidget import numpy as np from PIL import Image n = 256 drawing_widget = DrawingWidget(width=n, height=n, background='lightblue', default_radius=15) drawing_widget.show()
Auf Daten von gezeichnetem Bild zugreifen und verkleinern $(28 \times 28)$:
def resize_image(image,dim=(28,28)): image = Image.fromarray(image) # Create a PIL Image object from the np.array scaled_image = image.resize((dim[0],dim[1])) scaled_image = np.array(scaled_image) return scaled_image image = drawing_widget.get_image_data() # get data of image drawn above image_small = resize_image(image) # scale down to 28x28