{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Schriftfarbenvorhersager**\n", "\n", "Erstellt von Christoph Graml" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# ***Einleitung*** \n", "\n", "Ihr werdet mit diesem Notebook ein Machine Learning Projekt von Anfang bis Ende umsetzen. Das heißt zu Beginn schreiben wir ein Tool, mit dem Daten gesammelt werden, daraufhin programmieren wir ein vereinfachtes neuronales Netz, welches mit den gesammelten Daten trainiert wird und am Ende analysieren wir die Vorhersagen des Netzes.\n", "\n", "Es handelt sich dabei um mein erstes ML-Projekt, welches ich 2017 als Einstieg in Machine Learning umgesetzt habe. \n", "Inspiriert bzw. die Idee dazu kam durch dieses [Video](https://youtu.be/I74ymkoNTnw?t=425)\n", "\n", "\n", "# ***Idee***\n", "Die Idee besteht darin, ein vereinfachtes neuronales Netz zu trainieren, welches für jede Hintergrundfarbe die am besten lesbare Schriftfarbe (weiß oder schwarz) vorhersagt.\n", "Es liegt somit ein einfacher Klassifizierungsfall vor. Das neuronale Netz klassifiziert die Hintergrundfarbe und teilt diese einer \"Schriftfarbenklasse\" (schwarz oder weiß) zu. \n", "\n", "Die Eingabe ist dabei der RGB-Wert der Hintergrundfarbe. Ein RGB-Wert besteht eigentlich aus 3 Werten, jeweils zwischen 0 und 255. Die 3 Werte untergliedern sich in einen **R**ot-, **G**rün- und **B**lau-Wert. Sie geben an wie sehr die Farbe \"vorhanden\" sein soll (0 bedeutet nicht \"vorhanden\", 255 bedeutet \"vorhanden\"). Ein RGB-Wert von 0,0,0 steht für schwarz (0 \"Rot\", 0 \"Grün\", 0 \"Blau\") und ein RGB-Wert von 255,255,255 für weiß (255 \"Rot\", 255 \"Grün\", 255 \"Blau\"). \n", "Ähnlich steht ein RGB-Wert von 255,0,0 für ein kräftiges Rot. \n", "\n", "\n", "![rgb.png](images/rgb.png)\n", "\n", "\n", "*(Für mehr Infos zu RGB lese dir den [Wikipedia-Artikel](https://de.wikipedia.org/wiki/RGB-Farbraum) durch)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# ***Start***\n", "\n", "Zu Beginn müssen wir uns um die Daten kümmern. Das heißt wir benötigen einen Datensatz.\n", "Wie ich es jedoch schon versprochen hatte, laden wir keinen Datensatz einfach aus dem Internet herunter, sondern erstellen uns ein Tool, mit welchen wir selbst Daten sammeln können.\n", "Dazu gebe ich die nachfolgende Methode vor. Die Methode kann Hintergrundfarben, welche sie übergeben bekommt anzeigen.\n", "\n", "---\n", "\n", "## **Hinweis**\n", "In diesem gesamten Notebook befinden sich in den Code-Teile immer wieder drei Punkte \"**. . .**\" .\n", "Diese musst du mit Code ersetzen. Dabei kannst/musst du auch mehrere Zeilen verwenden." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "47JqXxyJILfN" }, "outputs": [], "source": [ "\"\"\"fuehre die beiden nachfolgenden Befehle aus, falls matplotlib und Pillow noch nicht installiert sind\"\"\"\n", "# !pip install matplotlib\n", "# !pip install Pillow\n", "\n", "%matplotlib inline \n", "import matplotlib.pyplot as plt \n", "from PIL import Image, ImageDraw \n", "from IPython.display import clear_output\n", "\n", "def farbeAnzeigen(rgb, textfarbe=''):\n", " \"\"\"Zeigt die uebergebene Hintergrundfarbe mit beiden/der Schriftfarbe an\n", " Args:\n", " rgb (tuple): Hintergrundfarbe:\n", " - ein Tuple der Laenge 3\n", " - fuer den rot-/gruen-/blau- Wert je ein int zwischen 0 un 255\n", " textfarbe (string, standardmaessig=''): Schriftfarbe\n", " Wenn der string...\n", " ... gleich 'w', dann wird nur eine weisse Schrift auf der Hintergrundfarbe angezeigt\n", " ... gleich 's', dann wird nur eine schwarze Schrift auf der Hintergrundfarbe angezeigt\n", " ... weder 's' noch 'w', dann wird eine schwarze und weisse Schrift auf der Hintergrundfarbe angezeigt\n", " \"\"\"\n", "\n", " \n", " \n", " clear_output() # Löscht die vorherige 'farbenAnzeige'\n", " # Neues Bild erstellen\n", " img = Image.new('RGB', (100, 30), color = rgb) \n", " d = ImageDraw.Draw(img) \n", " d.rectangle((0,0,99,29), fill=None, outline=(0))\n", "\n", " # Den Text mit der uebergebenen Farbe darstellen\n", " if textfarbe == 's':\n", " d.text((30,10), \"Schwarz\", fill=(0,0,0))\n", " elif textfarbe == 'w':\n", " d.text((35,10), \"Weiß\", fill=(255,255,255))\n", " else:\n", " d.text((10,10), \"Schwarz\", fill=(0,0,0)) \n", " d.text((60,10), \"Weiß\", fill=(255,255,255)) \n", "\n", " # Das Bild anzeigen\n", " plt.rcParams[\"figure.figsize\"] = (10, 5)\n", " plt.axis(\"off\") \n", " plt.imshow(img) \n", " plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": false, "id": "Uuw6jLBcUgLt" }, "outputs": [], "source": [ "farbeAnzeigen((123,211,180))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": false, "id": "Vu-9gxKUUgDB" }, "outputs": [], "source": [ "farbeAnzeigen((234,123,99), 'w')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": false, "id": "ktTpI_84710T", "scrolled": true }, "outputs": [], "source": [ "farbeAnzeigen((234,123,99), 's')" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "zYwqfUqnUx_p" }, "source": [ "# ***Tool zum Daten sammeln*** \n", "Um nun Daten zu sammeln und einen Datensatz zu erstellen, können wir die Methode *farbeAnzeigen* verwenden. Der Nutzer soll eine zufällige Hintergrundfarbe angezeigt bekommen und kann daraufhin mit einer Eingabe bestimmen, ob die weiße oder schwarze Schriftfarbe darauf besser aussieht. Somit werden Daten gelabelt, welche daraufhin zum trainieren einer KI verwendet werden können.\n", "\n", "\n", "---\n", "\n", "\n", "\n", "So sollte das fertige Tool zum Datensammeln aussehen:\n", "\n", "![daten_sammeln.png](images/daten_sammeln.png)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "D3QFk2HoILVj" }, "outputs": [], "source": [ "\"\"\"fuehre den nachfolgenden Befehl aus, falls numpy noch nicht installiert ist\"\"\"\n", "# !pip install numpy\n", "\n", "import numpy as np\n", "import random\n", "\n", "def datenSammeln():\n", " # Liste, welche die Hintergrundfarben mit Label enthaelt\n", " daten = []\n", "\n", " while True:\n", " # erstelle eine zufaellige RGB-Farbe (3 Wert) mit Hilfe von random.randint(...)\n", " # lerne hier mehr über diese Funktion: https://docs.python.org/3/library/random.html#random.randint\n", " zufallsRGB = (..., ..., ...)\n", "\n", " # zeige nun die neue Hintergrundfarbe mit sowohl weissem als auch schwarzen Text darauf an, \n", " # sodass man vergleichen kann, welche Schriftfarbe besser aussieht.\n", " # Verwende hierzu die farbeAnzeigen-Methode\n", " farbeAnzeigen(...)\n", "\n", " # Der Betrachter kann die bessere Textfarbe eingeben\n", " print(\"Bitte bessere Textfarbe eingeben: s -> schwarz oder w -> weiss\")\n", " print(\"Zum Beenden einfach Enter drücken!\")\n", " textfarbe = input().lower()\n", "\n", " # Die Textfarbe und die Hintergrundfarbe werden in den Datensatz aufgenommen, \n", " # wenn keine Textfarbe eingegeben wurde (das Daten sammeln also beendet wurde), \n", " # wird der Datensatz als np.array zurueckgegeben.\n", " if textfarbe == ...: \n", " # Eingabe gleich schwarz (ergänze das if-Statement)\n", " # Die 0 in der hinzufuegenden Datenzeile ist das Label fuer schwarz\n", " daten.append([zufallsRGB[0], zufallsRGB[1], zufallsRGB[2], 0])\n", " elif textfarbe == ...: \n", " # Eingabe gleich weiss (ergänze das if-Statement und die hinzufuegende Datenzeile)\n", " # Die 1 in der hinzufuegenden Datenzeile ist das Label fuer weiss\n", " daten.append([..., ..., ..., 1])\n", " else:\n", " # gibt den Datensatz als np.array zurueck\n", " return np.array(daten)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "C-js3TkCILTi" }, "outputs": [], "source": [ "# Nun solltest du mit deiner Methode datenSammeln() Daten sammeln koennen, \n", "# welche am Ende auch noch zurueckgegeben werden\n", "# Probiere es doch gleich mal aus!\n", "\n", "daten = datenSammeln()\n", "print(daten)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# ***Daten***\n", "Die nun von dir gesammelten Daten haben folgendes Format:\n", "\n", "
\n", "\n", "
\n", "\n", "Für jede gelabelte Hintergrundfarbe wurde eine Zeile angehängt. In den ersten 3 Spalten befindet sich der RGB-Wert der Hintergrundfarbe (in den R-, G- und B-Wert zerlegt und je zwischen 0 und 255). In der letzten Spalte befindet sich das jeweilige Label für die Hintergrundfarben (1 ≙ weiß, 0 ≙ schwarz)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "Vu1Wx-d3xTtg" }, "outputs": [], "source": [ "# Um deine Daten nun auch ein anderes Mal nutzen zu koennen, \n", "# kannst du sie ganz einfach abspeichern.\n", "# Verwende hierzu die save-Methode von numpy. Informiere dich hier, \n", "# wie sie funktioniert: https://numpy.org/doc/stable/reference/generated/numpy.save.html \n", "\n", "with open('deine_daten.npy', 'wb') as f:\n", " np.save(...)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "maNDa1VQzgDJ" }, "outputs": [], "source": [ "# Das Laden deiner abgespeicherten Daten funktioniert genauso simpel.\n", "# Verwende hierzu die load-Methode von numpy. Informiere dich hier, \n", "# wie sie funktioniert: https://numpy.org/doc/stable/reference/generated/numpy.load.html\n", "\n", "with open('deine_daten.npy', 'rb') as f:\n", " daten = np.load(...)\n", " print(daten)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "TQpQoF_3a7S9" }, "source": [ "# ***Die KI***\n", "Nun haben wir alle wichtigen Dinge erledigt und sind bereit, einen ML-Algorithmus zu programmieren, der unsere Daten verwendet, um unser Problem zu lösen. \n", "Unsere Problemstellung war folgende: ***Trainiere eine KI, welche in der Lage ist die bessere Schriftfarbe (schwarz oder weiß) für jede Hintergrundfarbe zu bestimmen***\n", "\n", "\n", "---\n", "\n", "\n", "Um dieses Problem zu lösen implementieren wir, so wie ich es Anfangs bereits sagte, ein vereinfachtes neuronales Netz. Dabei erstellen wir eine Klasse ***NeuralNetwork***, welche die Aufgabe eines vereinfachten neuronalen Netzes übernimmt. Dieses Netz besteht aus einem \"Neuron\", welches drei Eingaben (den RGB-Wert einer Hintergrundfarbe) hat. Es hat somit nur 4 Parameter, die es optimiert (*3 Gewichte*, von denen jedes mit je einer Hintergrundfarbe verrechnet werden und *einen Bias* der am Ende addiert wird).\n", "\n", "\n", "![neural_network.png](images/neural_network.png)\n", "\n", "\n", "---\n", "Im Konstruktor der *NeuralNetwork*-Klasse müssen die Gewichte und der Bias initialisiert werden. Dazu erstellen wir einen np.array der Länge 4, welcher sowohl die drei Gewichte als auch den Bias enthält. Um mit zufälligen Gewichten/Bias zu starten verwenden wir die random.normal-Methode von numpy.\n", "Wenn du dich genauer informieren willst, wie diese Methode funktioniert, \n", "dann schau dir folgenden [Link](https://het.as.utexas.edu/HET/Software/Numpy/reference/generated/numpy.random.normal.html) an. \n", "Wenn du wissen willst, warum wir die Gewichte/Bias zufällig initialisieren schau dir diese beiden Links an:\n", "([Link 1](https://machinelearningmastery.com/why-initialize-a-neural-network-with-random-weights/), \n", "[Link 2](https://towardsdatascience.com/weight-initialization-techniques-in-neural-networks-26c649eb3b78))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "W1ccDGhFfyRP" }, "outputs": [], "source": [ "class NeuralNetwork:\n", " \n", " def __init__(self):\n", " \"\"\"Konstruktor\n", " \"\"\"\n", " self.weights = np.random.normal(size=4)*0.01\n", "\n", "\n", " def sigmoid(self, x):\n", " \"\"\"Sigmoid Funktion\n", " Args:\n", " x (np.ndmatrix): Werte auf denen die Sigmoid-Funktion angewendet wird\n", "\n", " Returns:\n", " np.array: Transformierte Werte.\n", "\n", " Note:\n", " Diese Methode wendet die Sigmoid-Aktivierungsfunktion auf die \n", " Eingaben x an und gibt das Ergebnis zurueck.\n", " Implementiere diese Methode (Tipp: Verwende die np.exp Funktion).\n", " \"\"\"\n", " return ...\n", "\n", "\n", " def predict(self, X):\n", " \"\"\"Wahrscheinlichkeit, dass fuer eine Hintergrundfarbe die Schriftfarbe weiss am besten ist \n", " fuer alle Hintergrundfarben gleichzeitig.\n", " Args:\n", " X (numpy.ndarray): Datenmatrix:\n", " - pro Hintergrundfarbe eine Zeile\n", " - drei Spalten: rot-Wert, gruen-Wert, blau-Wert\n", " \n", " Returns:\n", " numpy.ndarray: Vektor:\n", " - pro Hintergrundfarbe eine Wahrscheinlichkeit, dass die Schriftfarbe weiss am besten ist\n", " (Werte < 0.5: Schriftfarbe schwarz ist wahrscheinlich besser,\n", " Werte >= 0.5 Schriftfarbe weiss ist wahrscheinlich besser.)\n", " \n", " Note:\n", " Verwende am besten die self.sigmoid und np.dot Funktionen, um die Gewichte/Bias mit den Eingaben zu verarbeiten\n", " \"\"\"\n", " # Die folgende Zeile haengt an die Datenmatrix eine Spalte mit Einsen an,\n", " # damit man mit Matrixmultiplikationen arbeiten kann. Die Variable features ist somit\n", " # die aufbereitete Datenmatrix.\n", " features = np.hstack((X, np.ones((X.shape[0], 1))))\n", " return ...\n", "\n", "\n", " def calculate_derivative(self, X, Y):\n", " \"\"\"Partielle Ableitungen der Kostenfunktionen nach den Gewichten und des Bias\n", " Args:\n", " X (numpy.ndarray): Datenmatrix:\n", " - pro Hintergrundfarbe eine Zeile\n", " - drei Spalten: rot-Wert, gruen-Wert, blau-Wert\n", " Y (numpy.ndarray): Vektor mit den Labels:\n", " - pro Hintergrundfarbe ein Wert mit der tatsaechlich besten Schriftfarbe.\n", " (1 ≙ Weiss, 0 ≙ Schwarz)\n", " \n", " Returns:\n", " (numpy.ndarray): Die abgeleiteten Gewichte/Bias.\n", "\n", " Note:\n", " Wir orientieren uns bei den Ableitungen an denen, welche wir im KI-Kurs naeher kennen gelernt haben.\n", " Verwende also dieses Kapitel des KI-Kurses https://ki-kurs.org/app/code-submission/id=4_02 und \n", " implementiere die Funktion\n", " \"\"\"\n", " predictions = self.predict(X)\n", " # Die folgende Zeile haengt an die Datenmatrix eine Spalte mit Einsen an,\n", " # damit man mit Matrixmultiplikationen arbeiten kann. Die Variable features ist somit\n", " # die aufbereitete Datenmatrix.\n", " features = np.hstack((X, np.ones((X.shape[0], 1))))\n", " \n", " return ...\n", " \n", "\n", " def update_weights(self, gradients, alpha):\n", " \"\"\"Optimieren der Gewichte und des Bias\n", " Args:\n", " gradients (numpy.ndarray): \n", " Vektor bestehend aus den Gradienten der Gewichte und dem Bias\n", " alpha (float): \n", " Lernrate (learning rate): Wert der bestimmt wie gross der \"Optimierungsschritt\" ausfallen soll\n", " \"\"\"\n", " self.weights = self.weights - gradients*alpha\n", "\n", "\n", " def accuracy(self, Y, predictions):\n", " \"\"\"Genauigkeitsfunktion\n", " Args:\n", " predictions (numpy.ndarray): Vektor mit den Wahrscheinlichkeiten für jede Hintergrundfarbe\n", " - pro Hintergrundfarbe ein Wert mit der vorhergesagten besseren Schriftfarbe.\n", " (Werte sind zwischen 0 und 1, wobei ein Wert < 0.5 die Schrift schwarz und \n", " ein Wert >= 0.5 die Schrift weiss faerben wuerde)\n", " Y (numpy.ndarray): Vektor mit den Lables:\n", " - pro Hintergrundfarbe ein Wert mit der tatsaechlich besten Schriftfarbe.\n", " (1 ≙ Weiss, 0 ≙ Schwarz)\n", "\n", " Returns:\n", " float: Richtigkeit des Klassifikators. \n", " - 0 ≙ Klassifikator klassifiziert die Daten falsch\n", " - 1 ≙ Klassifikator klassifiziert die Daten perfekt\n", " (z.B. Rueckgabe 0.73 => Klassifikator klassifiziert 73% der Daten richtig)\n", " \n", " Note:\n", " Implementiere einen Weg, um die Genauigkeit zu berechnen. Wenn z.B. genau 3 aus 10 Vorhersagen richtig waren, \n", " d.h. 3 Labels mit deren 3 Vorhersagen uebereinstimmen und die restlichen 7 nicht, \n", " soll 3÷10 also 0.3 zurueckgegeben werden. Damit man die Lables mit den Vorhersagen besser vergleichen kann,\n", " habe ich die predictions schon so angepasst, dass sie entweder 0 oder 1 sind.\n", " \"\"\"\n", " # Vorhersagen < 0.5: Schriftfarbe schwarz ist wahrscheinlich besser\n", " predictions[predictions < 0.5] = 0\n", " # Vorhersagen >= 0.5 Schriftfarbe weiss ist wahrscheinlich besser\n", " predictions[predictions >= 0.5] = 1\n", " \n", " return ...\n", "\n", "\n", " def cost(self, Y, predictions):\n", " \"\"\"Kostenfunktion.\n", " \n", " Args:\n", " predictions (numpy.ndarray): Vektor mit den Wahrscheinlichkeiten für jede Hintergrundfarbe\n", " - pro Hintergrundfarbe ein Wert mit der vorhergesagten besseren Schriftfarbe.\n", " (Werte sind zwischen 0 und 1, wobei ein Wert < 0.5 die Schrift schwarz und \n", " ein Wert >= 0.5 die Schrift weiss faerben wuerde)\n", " Y (numpy.ndarray): Vektor mit den Labels:\n", " - pro Hintergrundfarbe ein Wert mit der tatsaechlich besten Schriftfarbe.\n", " (1 ≙ Weiss, 0 ≙ Schwarz)\n", " Returns:\n", " float: je kleiner der Wert desto besser ist die Vorhersage des Klassifikators\n", "\n", " Note:\n", " Wir implementieren die Kostenfunktion, welche ihr im KI-Kurs genauer kennengelernt habt.\n", " Wenn du nochmal mehr darueber nachlesen willst, \n", " schau in den KI-Kurs: https://ki-kurs.org/app/code-submission/id=4_01\n", " \"\"\"\n", " # Abfangen eines Sonderfalls: Der Logarithmus ist an der Stelle 0 nicht definiert.\n", " epsilon = 1e-12\n", " predictions[predictions < epsilon] = epsilon\n", " predictions[predictions > 1. - epsilon] = 1. - epsilon\n", "\n", " # Implementiere die Kostenfunktion\n", " cost = ...\n", " return cost\n", "\n", "\n", " def train(self, X_train, Y_train, X_test, Y_test, epochs, alpha=0.1, visualize=True):\n", " \"\"\"Training des neuronalen Netzes\n", " Args:\n", " X_train (numpy.ndarray): Trainingsdatenmatrix:\n", " - pro Hintergrundfarbe eine Zeile\n", " - drei Spalten: rot-Wert, gruen-Wert, blau-Wert\n", " Y_train (numpy.ndarray): Trainingsvektor mit den Lables:\n", " - pro Hintergrundfarbe ein Wert mit der tatsächlich bester Schriftfarbe.\n", " (1 ≙ Weiss, 0 ≙ Schwarz)\n", " X_test (numpy.ndarray): Testdatenmatrix:\n", " - pro Hintergrundfarbe eine Zeile\n", " - drei Spalten: rot-Wert, gruen-Wert, blau-Wert\n", " Y_test (numpy.ndarray): Testvektor mit den Lables:\n", " - pro Hintergrundfarbe ein Wert mit der tatsächlich bester Schriftfarbe.\n", " (1 ≙ Weiss, 0 ≙ Schwarz)\n", " epochs (int): \n", " legt die Anzahl der Optimierungsschritte fest\n", " alpha (float, standardmaessig 0.1):\n", " Lernrate (learning rate): bestimmt die groesse des Optimierungsschrittes (siehe update_weights Methode)\n", " visualize (bool, standardmaessig True):\n", " bestimmt ob Diagramme ueber den Trainingsverlauf angezeigt werden sollen\n", " \n", " Note:\n", " Diese Methode beinhaltet den Trainingsloop, welcher pro Durchgang einen Optimierungsschritt ausfuehrt.\n", " Das heisst pro Durchgang muessen die Gradienten berechnet werden und die Gewichte/Bias geupdated werden\n", " Du musst nur die zwei Zeilen implementieren\n", " \"\"\" \n", " # die folgenden Varibalen speichern die Genauigkeiten/Costs des neuronalen Netzes im\n", " # Verlauf des Trainings, um diese spaeter in einem Graphen darzustellen und auszuwerten\n", " train_cost = []\n", " test_cost = []\n", " train_accuracy = []\n", " test_accuracy = []\n", "\n", "\n", " for e in range(1, epochs+1):\n", " if visualize:\n", " train_cost.append(self.cost(Y_train, self.predict(X_train)))\n", " test_cost.append(self.cost(Y_test, self.predict(X_test)))\n", " train_accuracy.append(self.accuracy(Y_train, self.predict(X_train)))\n", " test_accuracy.append(self.accuracy(Y_test, self.predict(X_test)))\n", "\n", " if e%200 == 0 or e == 1 or e == epochs:\n", " print(f\"Epoch: {e} / {epochs} Train Cost: {self.cost(Y_train, self.predict(X_train))} Test Cost: {self.cost(Y_test, self.predict(X_test))}\")\n", "\n", " ##############################################################\n", " # Implementiere die naechsten zwei Zeilen, in welchen zuerst die Gradienten mit der\n", " # Methode calculate_derivative berechnet werden und anschliessend in der zweiten Zeile\n", " # die Gewichte/Bias des neuronalen Netzes mit der Methode update_weights und den berechneten\n", " # Gradienten optimiert werden\n", " ...\n", " ...\n", "\n", "\n", " # Nur für die Visualisierung wichtig\n", " if visualize:\n", " fig = plt.figure(figsize=(10,10))\n", " ax1 = fig.add_subplot(2, 1, 1)\n", " ax1.plot(train_cost, label=\"Train cost\", color=\"black\")\n", " ax1.plot(test_cost, label=\"Test cost\", color=\"orange\")\n", " ax1.set_xlabel('Epochs')\n", " ax1.set_ylabel('Cost')\n", " ax1.set_title('Cost')\n", " ax1.legend()\n", "\n", " ax2 = fig.add_subplot(2, 1, 2)\n", " ax2.plot(train_accuracy, label=\"Train accuracy\", color=\"black\")\n", " ax2.plot(test_accuracy, label=\"Test accuracy\", color=\"orange\")\n", " ax2.set_xlabel('Epochs')\n", " ax2.set_ylabel('Accuracy')\n", " ax2.set_title('Accuracy')\n", " ax2.legend()\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "Gu5Z0k98Ro1V" }, "source": [ "# ***Datenaufbereitung***\n", "Nachdem wir das neuronale Netz programmiert haben, können wir fast mit dem Training beginnen. Davor müssen wir noch schnell die Daten aufbereiten. Das heißt wir laden zunächst die Daten, zerlegen sie in einen Test- und Trainingsdatensatz und normalisieren diese. Die Aufteilung in einen Test- und Trainingsdatensatz nehmen wir deswegen vor, um später beim Training des neuronalen Netzes zu erkennen, ob es gut generalisiert und nicht die Daten, auf welche es trainiert wird, \"auswendig\" lernt. Dazu geben wir dem Netz zum trainieren nur die Trainingsdaten und überprüfen mit den Testdaten, welche das Netz nicht lernt und welche somit neu für das Netz sind, die Genauigkeit.\n", "\n", "Hier könnt ihr nun euren erstellten Datensatz oder einen von mir vorgegebenen Datensatz verwenden." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "ldC5Gjm72Uow" }, "outputs": [], "source": [ "# laedt den von mir bereitgestellten Datensatz\n", "with open('trainingsdaten.npy', 'rb') as f:\n", " daten = np.load(f)\n", "\n", "# berechne einen \"Trennindex\", mit dem der Datensatz in einen Trainings-\n", "# und Testdatensatz zerlegt werden kann. Waehle ihn am besten so, dass der\n", "# Trainingsdatensatz 80% und der Testdatensatz 20% der Daten beansprucht.\n", "trennindex = ...\n", "\n", "# ---------------------------------------------------\n", "# Trainings Datensatz\n", "# ---------------------------------------------------\n", "train = daten[:trennindex]\n", "# Die letzte Spalte beinhaltet die Labels\n", "x_train = train[:, :-1]\n", "# Holt die Labels\n", "y_train = train[:, -1]\n", "\n", "# ---------------------------------------------------\n", "# Test Datensatz\n", "# ---------------------------------------------------\n", "test = daten[trennindex:]\n", "# Die letzte Spalte beinhaltet die Labels\n", "x_test = ...\n", "# Holt die Labels\n", "y_test = ...\n", "\n", "# --------------------------------------------------\n", "# Normalisierung\n", "# --------------------------------------------------\n", "# Ein normalisieren der Daten hilft dem neuronalen Netz dabei schneller zu lernen, \n", "# zumal auch die Sigmoid-Aktivierungsfunktion mit Werten nahe der 0 besser arbeiten kann.\n", "# Normalisiere den Test- und Trainingsdatensatz so, dass die Eingaben in das neuronale\n", "# Netz zwischen 0 und 1 liegen. Zur Zeit sind die Daten noch zwischen 0 und 255 \n", "# (Schaue in die datenSammeln-Methode, wenn du nochmal sehen willst wie die Daten gesammelt werden\n", "# oder gebe die Daten einfach mal aus)\n", "x_train = ...\n", "x_test = ..." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "9X0NJ8s5YN5e" }, "source": [ "# ***Training***\n", "Nun können wir aber wirklich mit dem Trainieren beginnen.\n", "\n", "Erstelle dazu ein Objekt der Klasse *NeuralNetwork* und trainiere dieses mit dem Trainingsdatensatz." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "6xr121ma2TEE" }, "outputs": [], "source": [ "# Erstelle ein Objekt der Klasse NeuralNetwrok\n", "neural_network = ...\n", "\n", "# Gib zunaechst die Genauigkeit des Netzes auf die Test- und Trainingsdaten aus,\n", "# um zu sehen, ob das neuronale Netz dann nach dem lernen auch besser wird.\n", "print(f\"Test Accuracy: {neural_network.accuracy(...)}\")\n", "print(f\"Train Accuracy: {neural_network.accuracy(...)}\")\n", "\n", "# ---------------------------------------------------\n", "# Trainieren des neuronalen Netzes\n", "# ---------------------------------------------------\n", "# trainiere das Netz nun auf die Trainingsdaten fuer eine von dir ausgewaehlte Anzahl an Epochen\n", "# Hier kannst du rumspielen. Du kannst mehrere Epochenlaengen ausprobieren und mithilfe der Visualisierung erkennen,\n", "# ob die Epochenanzahl gut ist oder ob das Netz overfitted.\n", "# (Tipp: Eine Epochenanzahl zwischen 500 und 1000 liefert gute Ergebnisse)\n", "neural_network.train(...)\n", "\n", "\n", "# Gib nun die Genauigkeit des Netzes auf die Test- und Trainingsdaten erneut aus,\n", "# um zu sehen, ob das neuronale Netz dann nach dem Lernen auch besser wird.\n", "print(f\"Test Accuracy: {neural_network.accuracy(...)}\")\n", "print(f\"Train Accuracy: {neural_network.accuracy(...)}\")" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "55UaCyu_lbEV" }, "source": [ "# ***Overfitting***\n", "Beim Overfitting lernt das Netz die Trainingsdaten \"auswendig\". Das heißt das Netz wird zwar immer besser auf den Trainingsdaten (Cost sinkt und Accuracy steigt auf den Trainingsdaten), jedoch immer schlechter beim allgemeinen Problem (Cost auf Testdaten steigt und Accuracy auf Testdaten sinkt). \n", "Dies tritt besonders dann auf, wenn ein kleiner Datensatz oder ein einfaches Problem vorliegt. Bei diesem Projekt trifft beides zu. \n", "Um zu erkennen, ob das neuronale Netz overfitted, lässt man sich am besten für das Training die Kurven für den Cost und die Accuracy für den Trainings- und den Testdatensatz zeichnen. \n", "\n", "Für die folgenden Graphen wurde mit 50000 Epochen trainiert (erster Graph Cost, zweiter Graph Accuracy):\n", "\n", "![overfitting.png](images/overfitting.png)\n", "\n", "Man kann sehr gut erkennen, dass zwar der Trainings-Cost immer weiter sinkt, jedoch der Test-Cost schon nach ca. 2000 Epchen (er hat dort sein Minimum) wieder steigt!\n", "\n", "Auch die Accuracy der Testdaten verschlechtert sich nach ca. 1000-2000 Epochen wieder!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "BOcp75Nqlahn" }, "outputs": [], "source": [ "# Du kannst das overfitten gerne selbst einmal ausprobieren. \n", "# Erstelle dir dafuer am besten ein neues Objekt der Klasse NeuralNetwork und trainiere\n", "# dieses mit beliebig vielen Epochen und lasse dir im Anschluss die Graphen anzeigen.\n", "\n", "# Du kannst auch gerne mal mit dem Alpha-Wert rumspielen, welcher die Schrittweite der Optimierung aendert.\n", "# Du solltest erkennen, dass du mit einer viel groesseren Lernrate (learning rate/Alphawert) ueber das Minimum \"hinausschiesst\" und so nicht perfekt zum Ziel gelangst.\n", "overfitted_neural_network = ...\n", "overfitted_neural_network.train(...)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "acPNPmOprsuV" }, "source": [ "# ***Analyse***\n", "Zum Abschluss dieses Projekts sollten wir noch genauer analysieren, wie sich das neuronale Netz verhält. Dabei kannst du einmal vergleichen ob du bei der Aufgabe die Schriftfarben ähnlich wie ich ausgewählt hättest. Im Anschluss schreiben wir noch eine Methode, welche die Vorhersage des Netzes auf verschiedene, neue Hintergrundfarben anzeigt. Somit kannst du für dich beurteilen, wie gut sich das Netz schlägt." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "FHg6AY_1spsl" }, "outputs": [], "source": [ "# lade einen neuen/anderen Datensatz, mit welchem du die Genauigkeit des Netzes testen willst.\n", "with open('deine_daten.npy', 'rb') as f:\n", " neue_daten = np.load(f)\n", "\n", "\n", "x_neue_daten = neue_daten[:, :-1]\n", "# Holt die Labels\n", "y_neue_daten = neue_daten[:, -1]\n", "\n", "# --------------------------------------------------\n", "# Normalisierung nicht vergessen!!\n", "# --------------------------------------------------\n", "# Da wir unsere Trainingsdaten normalisiert hatten musst du nun auch zwingend die neuen\n", "# Daten normalisieren, da das Netz sonst falsche Ergebnisse liefert (hat ja nur die Daten\n", "# eines anderen Bereiches kennen gelernt)\n", "x_neue_daten = ...\n", "\n", "# Genauigkeit der Testdaten von Oben\n", "print(f\"Test Daten Accuracy: {neural_network.accuracy(y_test, neural_network.predict(x_test))}\")\n", "# Die Genauigkeit auf den neuen Daten\n", "print(f\"Neue Daten Accuracy: {neural_network.accuracy(...)}\")" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "_J6r1tfKwo6o" }, "source": [ "Mithilfe der folgenden Methode kann man per Eingabe bestimmen, ob das übergebene neuronale Netz entweder eine zufällige Hintergrundfarbe bekommt (keine Eingabe) oder ob es eine ausgewählte Hintergrundfarbe bekommt (Eingabe: 0..255, 0..255, 0..255; -> siehe Bild). Mit der Eingabe eines **e** beendet man die Methode.\n", "\n", "\n", "---\n", "\n", "\n", "\n", "![analyse.png](images/analyse.png)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "RzvE03OS2Gxx" }, "outputs": [], "source": [ "# ---------------------------------------------------\n", "# Methode zum Analysieren des neuronalen Netzes\n", "# ---------------------------------------------------\n", "def analysieren(neural_network):\n", "\n", " # neue Zufallsfarbe erstellen und Anzeigen\n", " zufallsRGB = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))\n", "\n", " while True:\n", " \n", " textcolor = neural_network.predict(np.array([zufallsRGB])/255) # Eingaben müssen normalisiert werden!\n", " textcolor_string = 'w' if textcolor >= 0.5 else 's'\n", "\n", " # zeige die Hintergrundfarbe mit der von dem neuronalen Netz bestimmten Schriftfarbe an\n", " farbeAnzeigen(zufallsRGB, textcolor_string)\n", "\n", " # Der Betrachter kann die bessere Textfarbe eingeben\n", " print(f\"Hintergrundfarbe in RGB: {zufallsRGB}\")\n", " print(f\"Vorhersage des NN: {textcolor} => {textcolor_string}\")\n", " print()\n", " print(\"Um das NN... \")\n", " print(\" ...auf eine neue zufaellige Farbe zu testen einfach ohne Eingabe Enter drücken.\")\n", " print(f\" ...auf eine eigene Hintergrundfarbe zu testen R,G,B eingeben -> 0..255,0..255,0..255 (z.B.: 100,249,67 )\")\n", " print(f\"Um die Analyse zu beenden ein e eingeben\")\n", " eingabe = input(\"\").lower()\n", "\n", " if 'e' in eingabe:\n", " return\n", " elif eingabe != '':\n", " rgb = [int(x.replace(' ', '')) for x in eingabe.split(',')]\n", " if min(rgb) < 0 or max(rgb) > 255 or len(rgb) != 3:\n", " zufallsRGB = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))\n", " else:\n", " zufallsRGB = tuple(rgb)\n", " else:\n", " zufallsRGB = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "collapsed": true, "id": "JyEiTPaM2GvL" }, "outputs": [], "source": [ "analysieren(neural_network)" ] } ], "metadata": { "anaconda-cloud": {}, "colab": { "collapsed_sections": [], "name": "aufgabe_schriftfarben_vorhersage.ipynb", "provenance": [] }, "kernelspec": { "display_name": "Python [conda env:bwki_env]", "language": "python", "name": "conda-env-bwki_env-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.2" } }, "nbformat": 4, "nbformat_minor": 1 }