====== Neuronale Netze ====== Ziel von diesem Kurs ist, mit Python selbst ein künstliches neuronales Netz zu programmieren, welches handgeschriebene Zahlen erkennen kann. ===== - 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 | ===== - Materialien ===== ==== Slides ==== {{ :talit:01_neuronale_netze_intro_forward.pdf |}} {{ :talit:02_neuronale_netze_backpropagation.pdf |}} ==== Files ==== * {{ :talit:data_toy_problem.zip |}} * {{ :talit:data_mnist.zip |}} ==== Repos ==== * [[https://github.com/tkilla77/ksr_talenta_nn|github.com/tkilla77/ksr_talenta_nn]] ===== - 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=i1uQgU7NOCw]] * [[https://www.youtube.com/watch?v=q2SGW2VgwAM]] Auftrag: * **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. \\ # Stuktur: class Network: def __init__(self, input_neurons, hidden_neurons, output_neurons): # ... def feedforward(self, x): # ... def test(self, data_list): # ... # Erstelle ein Netzwerk für das Toy-Problem: toy_net = Network(4, 3, 2) # Erstelle ein Netzwerk für den Mnist-Datensatz: mnist_net = Network(784, 30, 10) ==== Aufgabe 3 ==== * **Ziel: NN für Toy-Problem trainieren** * Mache eine Kopie des Files `02_feedforward_oop.py` und speichere diese unter dem Namen `03_toy_problem_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 ein `Network`-Objekt erstellt wird. * Erweitere deine `Network`-Klasse um eine Methode `train`. 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 Namen `04_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 * Siehe z.B. hier: https://www.westernsydney.edu.au/bens/home/reproducible_research/emnist === 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. ===== - 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. ++++Lösung| A = np.array([[2,1],[3,0],[2,2]]) B = np.array([[2,1,0,1],[0,2,1,1]]) C = np.dot(A,B) print(C) ++++ **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. ++++Lösung| 1. NICHT möglich, falsche Dimensionen 1. 3x3 Matrix 1. 1x1 Matrix ++++ ==== 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: 1. Verwende passende Python Version (z.B. funktioniert 3.11 gut, 3.8.5 aber nicht, Stand Juli 2023) 1. Jupyter-NB installieren: `pip install notebook` 1. Navigiere mit `cd` in Projektordner. 1. Starte Jupyter-NB (öffnet in Browser): `jupyter-notebook` 1. Ö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