Schriftfarbenvorhersager

Erstellt von Christoph Graml

Einleitung

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.

Es handelt sich dabei um mein erstes ML-Projekt, welches ich 2017 als Einstieg in Machine Learning umgesetzt habe. Inspiriert bzw. die Idee dazu kam durch dieses Video

Idee

Die Idee besteht darin, ein vereinfachtes neuronales Netz zu trainieren, welches für jede Hintergrundfarbe die am besten lesbare Schriftfarbe (weiß oder schwarz) vorhersagt. Es liegt somit ein einfacher Klassifizierungsfall vor. Das neuronale Netz klassifiziert die Hintergrundfarbe und teilt diese einer "Schriftfarbenklasse" (schwarz oder weiß) zu.

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 Rot-, Grün- und Blau-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"). Ähnlich steht ein RGB-Wert von 255,0,0 für ein kräftiges Rot.

rgb.png

(Für mehr Infos zu RGB lese dir den Wikipedia-Artikel durch)

Start

Zu Beginn müssen wir uns um die Daten kümmern. Das heißt wir benötigen einen Datensatz. 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. Dazu gebe ich die nachfolgende Methode vor. Die Methode kann Hintergrundfarben, welche sie übergeben bekommt anzeigen.


Hinweis

In diesem gesamten Notebook befinden sich in den Code-Teile immer wieder drei Punkte "**. . .**" . Diese musst du mit Code ersetzen. Dabei kannst/musst du auch mehrere Zeilen verwenden.

In [ ]:
"""fuehre die beiden nachfolgenden Befehle aus, falls matplotlib und Pillow noch nicht installiert sind"""
# !pip install matplotlib
# !pip install Pillow

%matplotlib inline 
import matplotlib.pyplot as plt 
from PIL import Image, ImageDraw 
from IPython.display import clear_output

def farbeAnzeigen(rgb, textfarbe=''):
    """Zeigt die uebergebene Hintergrundfarbe mit beiden/der Schriftfarbe an
    Args:
            rgb (tuple): Hintergrundfarbe:
                - ein Tuple der Laenge 3
                - fuer den rot-/gruen-/blau- Wert je ein int zwischen 0 un 255
            textfarbe (string, standardmaessig=''): Schriftfarbe
                Wenn der string...
                    ... gleich 'w', dann wird nur eine weisse Schrift auf der Hintergrundfarbe angezeigt
                    ... gleich 's', dann wird nur eine schwarze Schrift auf der Hintergrundfarbe angezeigt
                    ... weder 's' noch 'w', dann wird eine schwarze und weisse Schrift auf der Hintergrundfarbe angezeigt
    """

    
    
    clear_output() # Löscht die vorherige 'farbenAnzeige'
    # Neues Bild erstellen
    img = Image.new('RGB', (100, 30), color = rgb) 
    d = ImageDraw.Draw(img) 
    d.rectangle((0,0,99,29), fill=None, outline=(0))

    # Den Text mit der uebergebenen Farbe darstellen
    if textfarbe == 's':
        d.text((30,10), "Schwarz", fill=(0,0,0))
    elif textfarbe == 'w':
        d.text((35,10), "Weiß", fill=(255,255,255))
    else:
        d.text((10,10), "Schwarz", fill=(0,0,0)) 
        d.text((60,10), "Weiß", fill=(255,255,255)) 

    # Das Bild anzeigen
    plt.rcParams["figure.figsize"] = (10, 5)
    plt.axis("off") 
    plt.imshow(img) 
    plt.show()
In [ ]:
farbeAnzeigen((123,211,180))
In [ ]:
farbeAnzeigen((234,123,99), 'w')
In [ ]:
farbeAnzeigen((234,123,99), 's')

Tool zum Daten sammeln

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.


So sollte das fertige Tool zum Datensammeln aussehen:

daten_sammeln.png

In [ ]:
"""fuehre den nachfolgenden Befehl aus, falls numpy noch nicht installiert ist"""
# !pip install numpy

import numpy as np
import random

def datenSammeln():
    # Liste, welche die Hintergrundfarben mit Label enthaelt
    daten = []

    while True:
        # erstelle eine zufaellige RGB-Farbe (3 Wert) mit Hilfe von random.randint(...)
        # lerne hier mehr über diese Funktion: https://docs.python.org/3/library/random.html#random.randint
        zufallsRGB = (..., ..., ...)

        # zeige nun die neue Hintergrundfarbe mit sowohl weissem als auch schwarzen Text darauf an, 
        # sodass man vergleichen kann, welche Schriftfarbe besser aussieht.
        # Verwende hierzu die farbeAnzeigen-Methode
        farbeAnzeigen(...)

        # Der Betrachter kann die bessere Textfarbe eingeben
        print("Bitte bessere Textfarbe eingeben: s -> schwarz oder w -> weiss")
        print("Zum Beenden einfach Enter drücken!")
        textfarbe = input().lower()

        # Die Textfarbe und die Hintergrundfarbe werden in den Datensatz aufgenommen, 
        # wenn keine Textfarbe eingegeben wurde (das Daten sammeln also beendet wurde), 
        # wird der Datensatz als np.array zurueckgegeben.
        if textfarbe == ...: 
            # Eingabe gleich schwarz (ergänze das if-Statement)
            # Die 0 in der hinzufuegenden Datenzeile ist das Label fuer schwarz
            daten.append([zufallsRGB[0], zufallsRGB[1], zufallsRGB[2], 0])
        elif textfarbe == ...: 
            # Eingabe gleich weiss (ergänze das if-Statement und die hinzufuegende Datenzeile)
            # Die 1 in der hinzufuegenden Datenzeile ist das Label fuer weiss
            daten.append([..., ..., ..., 1])
        else:
            # gibt den Datensatz als np.array zurueck
            return np.array(daten)
In [ ]:
# Nun solltest du mit deiner Methode datenSammeln() Daten sammeln koennen, 
# welche am Ende auch noch zurueckgegeben werden
# Probiere es doch gleich mal aus!

daten = datenSammeln()
print(daten)

Daten

Die nun von dir gesammelten Daten haben folgendes Format:

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)

In [ ]:
# Um deine Daten nun auch ein anderes Mal nutzen zu koennen, 
# kannst du sie ganz einfach abspeichern.
# Verwende hierzu die save-Methode von numpy. Informiere dich hier, 
# wie sie funktioniert: https://numpy.org/doc/stable/reference/generated/numpy.save.html 

with open('deine_daten.npy', 'wb') as f:
    np.save(...)
In [ ]:
# Das Laden deiner abgespeicherten Daten funktioniert genauso simpel.
# Verwende hierzu die load-Methode von numpy. Informiere dich hier, 
# wie sie funktioniert: https://numpy.org/doc/stable/reference/generated/numpy.load.html

with open('deine_daten.npy', 'rb') as f:
    daten = np.load(...)
    print(daten)

Die KI

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. Unsere Problemstellung war folgende: Trainiere eine KI, welche in der Lage ist die bessere Schriftfarbe (schwarz oder weiß) für jede Hintergrundfarbe zu bestimmen


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).

neural_network.png


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. Wenn du dich genauer informieren willst, wie diese Methode funktioniert, dann schau dir folgenden Link an. Wenn du wissen willst, warum wir die Gewichte/Bias zufällig initialisieren schau dir diese beiden Links an: (Link 1, Link 2)

In [ ]:
class NeuralNetwork:
  
    def __init__(self):
        """Konstruktor
        """
        self.weights = np.random.normal(size=4)*0.01


    def sigmoid(self, x):
        """Sigmoid Funktion
        Args:
            x (np.ndmatrix): Werte auf denen die Sigmoid-Funktion angewendet wird

        Returns:
            np.array: Transformierte Werte.

        Note:
            Diese Methode wendet die Sigmoid-Aktivierungsfunktion auf die 
            Eingaben x an und gibt das Ergebnis zurueck.
            Implementiere diese Methode (Tipp: Verwende die np.exp Funktion).
        """
        return ...


    def predict(self, X):
        """Wahrscheinlichkeit, dass fuer eine Hintergrundfarbe die Schriftfarbe weiss am besten ist 
            fuer alle Hintergrundfarben gleichzeitig.
        Args:
            X (numpy.ndarray): Datenmatrix:
                - pro Hintergrundfarbe eine Zeile
                - drei Spalten: rot-Wert, gruen-Wert, blau-Wert
        
        Returns:
            numpy.ndarray: Vektor:
                - pro Hintergrundfarbe eine Wahrscheinlichkeit, dass die Schriftfarbe weiss am besten ist
                    (Werte < 0.5: Schriftfarbe schwarz ist wahrscheinlich besser,
                     Werte >= 0.5 Schriftfarbe weiss ist wahrscheinlich besser.)
        
        Note:
            Verwende am besten die self.sigmoid und np.dot Funktionen, um die Gewichte/Bias mit den Eingaben zu verarbeiten
        """
        # Die folgende Zeile haengt an die Datenmatrix eine Spalte mit Einsen an,
        # damit man mit Matrixmultiplikationen arbeiten kann. Die Variable features ist somit
        # die aufbereitete Datenmatrix.
        features = np.hstack((X, np.ones((X.shape[0], 1))))
        return ...


    def calculate_derivative(self, X, Y):
        """Partielle Ableitungen der Kostenfunktionen nach den Gewichten und des Bias
        Args:
            X (numpy.ndarray): Datenmatrix:
                - pro Hintergrundfarbe eine Zeile
                - drei Spalten: rot-Wert, gruen-Wert, blau-Wert
            Y (numpy.ndarray): Vektor mit den Labels:
                - pro Hintergrundfarbe ein Wert mit der tatsaechlich besten Schriftfarbe.
                (1 ≙ Weiss, 0 ≙ Schwarz)
        
        Returns:
            (numpy.ndarray): Die abgeleiteten Gewichte/Bias.

        Note:
            Wir orientieren uns bei den Ableitungen an denen, welche wir im KI-Kurs naeher kennen gelernt haben.
            Verwende also dieses Kapitel des KI-Kurses  https://ki-kurs.org/app/code-submission/id=4_02 und 
            implementiere die Funktion
        """
        predictions = self.predict(X)
        # Die folgende Zeile haengt an die Datenmatrix eine Spalte mit Einsen an,
        # damit man mit Matrixmultiplikationen arbeiten kann. Die Variable features ist somit
        # die aufbereitete Datenmatrix.
        features = np.hstack((X, np.ones((X.shape[0], 1))))
        
        return ...
  

    def update_weights(self, gradients, alpha):
        """Optimieren der Gewichte und des Bias
        Args:
            gradients (numpy.ndarray): 
                Vektor bestehend aus den Gradienten der Gewichte und dem Bias
            alpha (float): 
                Lernrate (learning rate): Wert der bestimmt wie gross der "Optimierungsschritt" ausfallen soll
        """
        self.weights = self.weights - gradients*alpha


    def accuracy(self, Y, predictions):
        """Genauigkeitsfunktion
        Args:
            predictions (numpy.ndarray): Vektor mit den Wahrscheinlichkeiten für jede Hintergrundfarbe
                - pro Hintergrundfarbe ein Wert mit der vorhergesagten besseren Schriftfarbe.
                (Werte sind zwischen 0 und 1, wobei ein Wert < 0.5 die Schrift schwarz und 
                 ein Wert >= 0.5 die Schrift weiss faerben wuerde)
            Y (numpy.ndarray): Vektor mit den Lables:
                - pro Hintergrundfarbe ein Wert mit der tatsaechlich besten Schriftfarbe.
                (1 ≙ Weiss, 0 ≙ Schwarz)

        Returns:
            float: Richtigkeit des Klassifikators. 
                - 0 ≙ Klassifikator klassifiziert die Daten falsch
                - 1 ≙ Klassifikator klassifiziert die Daten perfekt
                (z.B. Rueckgabe 0.73 => Klassifikator klassifiziert 73% der Daten richtig)
        
        Note:
            Implementiere einen Weg, um die Genauigkeit zu berechnen. Wenn z.B. genau 3 aus 10 Vorhersagen richtig waren, 
            d.h. 3 Labels mit deren 3 Vorhersagen uebereinstimmen und die restlichen 7 nicht, 
            soll 3÷10 also 0.3 zurueckgegeben werden. Damit man die Lables mit den Vorhersagen besser vergleichen kann,
            habe ich die predictions schon so angepasst, dass sie entweder 0 oder 1 sind.
        """
        # Vorhersagen < 0.5: Schriftfarbe schwarz ist wahrscheinlich besser
        predictions[predictions < 0.5] = 0
        # Vorhersagen >= 0.5 Schriftfarbe weiss ist wahrscheinlich besser
        predictions[predictions >= 0.5] = 1
        
        return ...


    def cost(self, Y, predictions):
        """Kostenfunktion.
        
        Args:
            predictions (numpy.ndarray): Vektor mit den Wahrscheinlichkeiten für jede Hintergrundfarbe
                - pro Hintergrundfarbe ein Wert mit der vorhergesagten besseren Schriftfarbe.
                (Werte sind zwischen 0 und 1, wobei ein Wert < 0.5 die Schrift schwarz und 
                 ein Wert >= 0.5 die Schrift weiss faerben wuerde)
            Y (numpy.ndarray): Vektor mit den Labels:
                - pro Hintergrundfarbe ein Wert mit der tatsaechlich besten Schriftfarbe.
                (1 ≙ Weiss, 0 ≙ Schwarz)
        Returns:
            float: je kleiner der Wert desto besser ist die Vorhersage des Klassifikators

        Note:
            Wir implementieren die Kostenfunktion, welche ihr im KI-Kurs genauer kennengelernt habt.
            Wenn du nochmal mehr darueber nachlesen willst, 
            schau in den KI-Kurs: https://ki-kurs.org/app/code-submission/id=4_01
        """
        # Abfangen eines Sonderfalls: Der Logarithmus ist an der Stelle 0 nicht definiert.
        epsilon = 1e-12
        predictions[predictions < epsilon] = epsilon
        predictions[predictions > 1. - epsilon] = 1. - epsilon

        # Implementiere die Kostenfunktion
        cost = ...
        return cost


    def train(self, X_train, Y_train, X_test, Y_test, epochs, alpha=0.1, visualize=True):
        """Training des neuronalen Netzes
        Args:
            X_train (numpy.ndarray): Trainingsdatenmatrix:
                - pro Hintergrundfarbe eine Zeile
                - drei Spalten: rot-Wert, gruen-Wert, blau-Wert
            Y_train (numpy.ndarray): Trainingsvektor mit den Lables:
                - pro Hintergrundfarbe ein Wert mit der tatsächlich bester Schriftfarbe.
                (1 ≙ Weiss, 0 ≙ Schwarz)
            X_test (numpy.ndarray): Testdatenmatrix:
                - pro Hintergrundfarbe eine Zeile
                - drei Spalten: rot-Wert, gruen-Wert, blau-Wert
            Y_test (numpy.ndarray): Testvektor mit den Lables:
                - pro Hintergrundfarbe ein Wert mit der tatsächlich bester Schriftfarbe.
                (1 ≙ Weiss, 0 ≙ Schwarz)
            epochs (int): 
                legt die Anzahl der Optimierungsschritte fest
            alpha (float, standardmaessig 0.1):
                Lernrate (learning rate): bestimmt die groesse des Optimierungsschrittes (siehe update_weights Methode)
            visualize (bool, standardmaessig True):
                bestimmt ob Diagramme ueber den Trainingsverlauf angezeigt werden sollen
        
        Note:
            Diese Methode beinhaltet den Trainingsloop, welcher pro Durchgang einen Optimierungsschritt ausfuehrt.
            Das heisst pro Durchgang muessen die Gradienten berechnet werden und die Gewichte/Bias geupdated werden
            Du musst nur die zwei Zeilen implementieren
        """    
        # die folgenden Varibalen speichern die Genauigkeiten/Costs des neuronalen Netzes im
        # Verlauf des Trainings, um diese spaeter in einem Graphen darzustellen und auszuwerten
        train_cost = []
        test_cost = []
        train_accuracy = []
        test_accuracy = []


        for e in range(1, epochs+1):
            if visualize:
                train_cost.append(self.cost(Y_train, self.predict(X_train)))
                test_cost.append(self.cost(Y_test, self.predict(X_test)))
                train_accuracy.append(self.accuracy(Y_train, self.predict(X_train)))
                test_accuracy.append(self.accuracy(Y_test, self.predict(X_test)))

            if e%200 == 0 or e == 1 or e == epochs:
                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))}")

            ##############################################################
            # Implementiere die naechsten zwei Zeilen, in welchen zuerst die Gradienten mit der
            # Methode calculate_derivative berechnet werden und anschliessend in der zweiten Zeile
            # die Gewichte/Bias des neuronalen Netzes mit der Methode update_weights und den berechneten
            # Gradienten optimiert werden
            ...
            ...


        # Nur für die Visualisierung wichtig
        if visualize:
            fig = plt.figure(figsize=(10,10))
            ax1 = fig.add_subplot(2, 1, 1)
            ax1.plot(train_cost, label="Train cost", color="black")
            ax1.plot(test_cost, label="Test cost", color="orange")
            ax1.set_xlabel('Epochs')
            ax1.set_ylabel('Cost')
            ax1.set_title('Cost')
            ax1.legend()

            ax2 = fig.add_subplot(2, 1, 2)
            ax2.plot(train_accuracy, label="Train accuracy", color="black")
            ax2.plot(test_accuracy, label="Test accuracy", color="orange")
            ax2.set_xlabel('Epochs')
            ax2.set_ylabel('Accuracy')
            ax2.set_title('Accuracy')
            ax2.legend()
            plt.show()

Datenaufbereitung

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.

Hier könnt ihr nun euren erstellten Datensatz oder einen von mir vorgegebenen Datensatz verwenden.

In [ ]:
# laedt den von mir bereitgestellten Datensatz
with open('trainingsdaten.npy', 'rb') as f:
    daten = np.load(f)

# berechne einen "Trennindex", mit dem der Datensatz in einen Trainings-
# und Testdatensatz zerlegt werden kann. Waehle ihn am besten so, dass der
# Trainingsdatensatz 80% und der Testdatensatz 20% der Daten beansprucht.
trennindex = ...

# ---------------------------------------------------
# Trainings Datensatz
# ---------------------------------------------------
train = daten[:trennindex]
# Die letzte Spalte beinhaltet die Labels
x_train = train[:, :-1]
# Holt die Labels
y_train = train[:, -1]

# ---------------------------------------------------
# Test Datensatz
# ---------------------------------------------------
test = daten[trennindex:]
# Die letzte Spalte beinhaltet die Labels
x_test = ...
# Holt die Labels
y_test = ...

# --------------------------------------------------
# Normalisierung
# --------------------------------------------------
# Ein normalisieren der Daten hilft dem neuronalen Netz dabei schneller zu lernen, 
# zumal auch die Sigmoid-Aktivierungsfunktion mit Werten nahe der 0 besser arbeiten kann.
# Normalisiere den Test- und Trainingsdatensatz so, dass die Eingaben in das neuronale
# Netz zwischen 0 und 1 liegen. Zur Zeit sind die Daten noch zwischen 0 und 255 
# (Schaue in die datenSammeln-Methode, wenn du nochmal sehen willst wie die Daten gesammelt werden
# oder gebe die Daten einfach mal aus)
x_train = ...
x_test = ...

Training

Nun können wir aber wirklich mit dem Trainieren beginnen.

Erstelle dazu ein Objekt der Klasse NeuralNetwork und trainiere dieses mit dem Trainingsdatensatz.

In [ ]:
# Erstelle ein Objekt der Klasse NeuralNetwrok
neural_network = ...

# Gib zunaechst die Genauigkeit des Netzes auf die Test- und Trainingsdaten aus,
# um zu sehen, ob das neuronale Netz dann nach dem lernen auch besser wird.
print(f"Test Accuracy: {neural_network.accuracy(...)}")
print(f"Train Accuracy: {neural_network.accuracy(...)}")

# ---------------------------------------------------
# Trainieren des neuronalen Netzes
# ---------------------------------------------------
# trainiere das Netz nun auf die Trainingsdaten fuer eine von dir ausgewaehlte Anzahl an Epochen
# Hier kannst du rumspielen. Du kannst mehrere Epochenlaengen ausprobieren und mithilfe der Visualisierung erkennen,
# ob die Epochenanzahl gut ist oder ob das Netz overfitted.
# (Tipp: Eine Epochenanzahl zwischen 500 und 1000 liefert gute Ergebnisse)
neural_network.train(...)


# Gib nun die Genauigkeit des Netzes auf die Test- und Trainingsdaten erneut aus,
# um zu sehen, ob das neuronale Netz dann nach dem Lernen auch besser wird.
print(f"Test Accuracy: {neural_network.accuracy(...)}")
print(f"Train Accuracy: {neural_network.accuracy(...)}")

Overfitting

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).
Dies tritt besonders dann auf, wenn ein kleiner Datensatz oder ein einfaches Problem vorliegt. Bei diesem Projekt trifft beides zu. 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.

Für die folgenden Graphen wurde mit 50000 Epochen trainiert (erster Graph Cost, zweiter Graph Accuracy):

overfitting.png

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!

Auch die Accuracy der Testdaten verschlechtert sich nach ca. 1000-2000 Epochen wieder!

In [ ]:
# Du kannst das overfitten gerne selbst einmal ausprobieren. 
# Erstelle dir dafuer am besten ein neues Objekt der Klasse NeuralNetwork und trainiere
# dieses mit beliebig vielen Epochen und lasse dir im Anschluss die Graphen anzeigen.

# Du kannst auch gerne mal mit dem Alpha-Wert rumspielen, welcher die Schrittweite der Optimierung aendert.
# Du solltest erkennen, dass du mit einer viel groesseren Lernrate (learning rate/Alphawert) ueber das Minimum "hinausschiesst" und so nicht perfekt zum Ziel gelangst.
overfitted_neural_network = ...
overfitted_neural_network.train(...)

Analyse

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.

In [ ]:
# lade einen neuen/anderen Datensatz, mit welchem du die Genauigkeit des Netzes testen willst.
with open('deine_daten.npy', 'rb') as f:
    neue_daten = np.load(f)


x_neue_daten = neue_daten[:, :-1]
# Holt die Labels
y_neue_daten = neue_daten[:, -1]

# --------------------------------------------------
# Normalisierung nicht vergessen!!
# --------------------------------------------------
# Da wir unsere Trainingsdaten normalisiert hatten musst du nun auch zwingend die neuen
# Daten normalisieren, da das Netz sonst falsche Ergebnisse liefert (hat ja nur die Daten
# eines anderen Bereiches kennen gelernt)
x_neue_daten = ...

# Genauigkeit der Testdaten von Oben
print(f"Test Daten Accuracy: {neural_network.accuracy(y_test, neural_network.predict(x_test))}")
# Die Genauigkeit auf den neuen Daten
print(f"Neue Daten Accuracy: {neural_network.accuracy(...)}")

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.


analyse.png

In [ ]:
# ---------------------------------------------------
# Methode zum Analysieren des neuronalen Netzes
# ---------------------------------------------------
def analysieren(neural_network):

    # neue Zufallsfarbe erstellen und Anzeigen
    zufallsRGB = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    while True:
        
        textcolor = neural_network.predict(np.array([zufallsRGB])/255) # Eingaben müssen normalisiert werden!
        textcolor_string = 'w' if textcolor >= 0.5 else 's'

        # zeige die Hintergrundfarbe mit der von dem neuronalen Netz bestimmten Schriftfarbe an
        farbeAnzeigen(zufallsRGB, textcolor_string)

        # Der Betrachter kann die bessere Textfarbe eingeben
        print(f"Hintergrundfarbe in RGB: {zufallsRGB}")
        print(f"Vorhersage des NN: {textcolor} => {textcolor_string}")
        print()
        print("Um das NN... ")
        print("  ...auf eine neue zufaellige Farbe zu testen einfach ohne Eingabe Enter drücken.")
        print(f"  ...auf eine eigene Hintergrundfarbe zu testen R,G,B eingeben -> 0..255,0..255,0..255  (z.B.: 100,249,67 )")
        print(f"Um die Analyse zu beenden ein e eingeben")
        eingabe = input("").lower()

        if 'e' in eingabe:
            return
        elif eingabe != '':
            rgb = [int(x.replace(' ', '')) for x in eingabe.split(',')]
            if min(rgb) < 0 or max(rgb) > 255 or len(rgb) != 3:
                zufallsRGB = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
            else:
                zufallsRGB = tuple(rgb)
        else:
            zufallsRGB = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
In [ ]:
analysieren(neural_network)