Grafik von Pawel Czerwinski – https://unsplash.com/de/@pawel_czerwinski

Klassifikation mittels neuronaler Netze

Es gibt verschiedene klassische Algorithmen um eine Klassifikation durchzuführen. Hier beschreibe ich eine Implementierung mittels neuronaler Netze und meiner Erfahrungen hiermit.

Henrik Bartsch

Henrik Bartsch

Die Texte in diesem Artikel wurden teilweise mit Hilfe künstlicher Intelligenz erarbeitet und von uns korrigiert und überarbeitet. Für die Generierung wurden folgende Dienste verwendet:

Einordnung

Machine Learning spielt schon seit ein paar Jahren eine immer größere Rolle. Dabei wichtig: Die Fähigkeit, relativ simpel und zuverlässig Daten in gelernte Klassen einzuteilen, sogenannte Klassifikation. Vor allem neuronale Netze spielen hierbei immer häufiger eine Rolle, da diese vielseitig anwendbar und flexibel sind; weiterhin sind diese ebenfalls in der Lage starke Nichtlinearitäten in Daten abbilden zu können. Dieser Post soll eine Einführung in die Klassifizierung als Aufgabe im Supervised Learning geben und eine einfache Implementierung mittels künstlicher neuronaler Netze vorstellen.

Klassifikation als Aufgabe

Gegeben seien zwei Mengen, einmal jeweils die Menge der Eingangsvariablen II und die Menge der Klassen CC. Gesucht ist nun eine Funktion ff, welche die Eingangsdaten auf geeignete Klassen abbildet:

f:IC.f: I \rightarrow C.

In der Aufgabenstellung müssen jeweils sowohl die Eingansvariablen als auch die entsprechenden Klassen definiert sein, als auch eine gewisse Grundmenge an Daten vorliegen; andernfalls ist Konvergenz des Algorithmus entsprechend nicht zu erwarten. Die Anzahl der Datenpunkte variiert hierbei jeweils mit der Komplexität der Aufgabenstellung und Anzahl der Eingangsvariablen.

Loss-Metrik

Äquivalent zu nahezu allen Machine Learning-Modellen, wird zum Training eine entsprechende Loss-Metrik verwendet, um die Abweichung zwischen den vorliegenden Trainingsdaten und den Vorhersagen des Modells zu berechnen. Für eine Klassifizierung bietet sich hierbei die categorical Crossentropy (deut.: Kategorische Kreuzentropie) an, welche Abweichungen zwischen verschiedenen vorhergesagten Klassen gut darstellt.

Wenn wir uns in Richtung künstlicher neuronaler Netze bewegen, verwenden wir hierbei meist die diskrete Formulierung. Diese lautet für zwei diskrete Wahrscheinlichkeitsverteilungen p,qp, q wie folgt:

H(P,Q):=xXp(x)log(q(x)).H(P, Q):= - \sum_{x \in X} p(x) log(q(x)).

Das Ziel unseres Modells ist entsprechend, die Abweichung zwischen den Vorhersagen und Daten zu minimieren, was äquivalent dazu ist den Wert der Loss-Funktion zu minimieren.

Aktivierungsfunktionen

Bei einem neuronalen Netzwerk sind die Aktivierungsfunktionen relevant für den Erhalt eines sinnvollen Ergebnisses. In Zwischenlayern ergibt sich hierbei meist wieder die Rectified Linear Unit (ReLU) als sinnvolle Wahl, allerdings ist hierbei die Wahl der letzten Aktivierungsfunktion relevant.

Da es sich hier um eine Klassifizierung handelt, ergibt es Sinn als Ausgabe eine Wahrscheinlichkeitsverteilung zu erhalten, also einen Vektor y=(y1,...,ym)T\vec{y} = (y_1, ..., y_m)^T, welcher

i=1mvi=1\sum_{i = 1}^m v_i = 1

erfüllt. Hierfür gibt es zwei beliebte Ansätze, um dies zu garantieren:

  1. Eine einfache Transformation auf einen Einheitsvektor mittels der Vektornorm. Dies stellt den Ansatz dar, welcher in Mathematik in der Oberstufe von Gymnasien vermittelt wird.

vvi:=vii=1mvi2=viv\vec{v}^{'} \rightarrow v_i^{'} := \frac{v_i}{\sqrt{\sum_{i = 1}^m v_i^2}} = \frac{v_i}{\Vert \vec{v} \Vert}

In diesem Ansatz stellt \Vert \cdot \Vert die euklidische Norm dar. Das Ergebnis v\vec{v}^{'} hierbei erfüllt die notwendige Bedingung, welche oben definiert wurde und stellt damit eine Wahrscheinlichkeitsverteilung dar. 1

  1. Transformation auf eine Wahrscheinlichkeitsverteilung mittels der softmax-Aktivierungsfunktion: Bei diesem Ansatz wird die Softmax-Aktivierungsfunktion σ()\sigma(\cdot) 2 verwendet:

vvi=σ(vi):=evii=1mevi\vec{v}^{'} \rightarrow v_i^{'} = \sigma(v_i) := \frac{e^{v_i}}{\sum_{i = 1}^m e^{v_i}}

Auch diese erreicht eine entsprechende Wahrscheinlichkeitsverteilung v\vec{v}^{'}. Vorteil dieser Funktion ist es, dass kleine Fehler nicht proportional skaliert werden, sondern auf größere Werte transformiert werden, was den Trainingsprozess beschleunigt.

Als weitere Möglichkeit kann eine beliebige Basis bRb \in \mathbb{R} anstatt ee verwendet werden, um eine geeignete Skalierung selber bestimmen zu können.

Als letzte Aktivierungsfunktion kann auch wieder ReLU gewählt werden, dann müssen allerdings die Werte vor Weiterverwendung in eine Wahrscheinlichkeitsverteilung umgewandelt werden, um mit diesen trainieren zu können. Für eine Vorhersage einer Klasse reicht das typische argmax() um diese zu berechnen.

Implementierung mittels Tensorflow

Generierung des Datensets

In diesem Post wird das Datenset CIFAR-10, verwendet, welches verschiedene Objekte und Tiere klassifizieren lässt. Als Eingangsvariablen gibt es uns hierbei ein (32,32)(32, 32)-Array, welches die Pixelintensität darstellt.

Die notwendigen Imports lauten wie folgt:

classification.py
import pandas as pd
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import tensorflow_datasets as tfds

from collections import deque
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, InputLayer, Conv2D, MaxPool2D, Flatten
from tensorflow.python.keras.optimizer_v2.adam import Adam
from tensorflow.python.keras.losses import sparse_categorical_crossentropy, categorical_crossentropy
from tensorflow.python.keras.metrics import sparse_categorical_accuracy, categorical_accuracy

Das Datenset lässt sich simpel über die Tensorflow Datasets-API herunterladen:

classification.py
builder = tfds.builder('cifar10')
builder.download_and_prepare()

info = builder.info

train_ds, test_ds = builder.as_dataset(split=['train', 'test'], shuffle_files=True, as_supervised=True)

Bei diesem Code erstellen wir zuerst einen Builder, welcher eine Schnittstelle für den Download bereitstellt. Anschließend wird hier ein Train-Test-Split von 80%80 \% zu 20%20 \% durchgeführt.

classification.py
train_x = [example.numpy() for example, label in train_ds]
train_y = [label.numpy() for example, label in train_ds]

test_x = [example.numpy() for example, label in test_ds]
test_y = [label.numpy() for example, label in test_ds]

Als Nächstes generieren wir uns die Daten in einem Format, sodass diese im weiteren Verlauf sinnvoll verwendet werden können. Beim Überprüfen können wir sehen, dass wir 5000050000 Datenpunkte im Trainingsdatensatz und 1000010000 im Testdatensatz vorhanden haben.

Ein Blick in den Datensatz zeigt uns Bilder wie das folgende Bild:

Flugzeug aus dem Trainingsdatensatz

Hierbei handelt es sich um niedrig auflösendes Bild aus dem originalen Datensatz, dargestellt mit Matplotlib.

classification.py
model = Sequential([
    InputLayer(input_shape=info.features["image"].shape),
    Conv2D(filters=64, kernel_size=(3, 3), activation="relu"),
    MaxPool2D(pool_size=(3, 3)),
    Conv2D(filters=64, kernel_size=(3, 3), activation="relu"),
    MaxPool2D(pool_size=(3, 3)),
    Flatten(),
    Dense(units=128, activation="relu"),
    Dense(units=64, activation="relu"),
    Dense(units=info.features["label"].num_classes, activation="softmax")
])

# model.summary()

optimizer = Adam(learning_rate=1e-4)

Zur Klassifizierung wird hierbei ein Convolutional Neural Network verwendet, welches sich durch die Verwendung von Conv2D, MaxPool2D und Flatten-Layern auszeichnet. Hierbei wird auf das Bild eine Filter- und Vereinigungsoperation angewandt, welche in der Praxis gute Ergebnisse erzielt.

Hinweis: Als Aktivierungsfunktion des letzten Layers wird die softmax-Aktivierung verwendet.

Das Training wird anschließend über die model.fit()-Methode ausgeführt:

classification.py
model.compile(optimizer=optimizer, loss=categorical_crossentropy, metrics=[categorical_accuracy])

train_x_ds = tf.convert_to_tensor(train_x, dtype=tf.float64) / 256
train_y_ds = tf.one_hot(tf.convert_to_tensor(train_y, dtype=tf.int64), depth=info.features["label"].num_classes)

history = model.fit(x=train_x_ds, y=train_y_ds, epochs=50)

Alleine nach 20 Epochen erreicht man hier schon eine categorical accuracy von 70%\approx 70 \% auf dem Trainingsset und 65%\approx 65 \% auf dem Testset. Längeres Training verbessert die Präzision, allerdings auch das Overfitting. Ebenfalls eine Fehlerquelle kann die Netzwerkarchitektur sein, welche durch Hyperparameteroptimierung verbessert werden kann.

Im Folgenden findet sich einmal der Verlauf der Loss-Funktion

ebenso wie der Verlauf der kategoischen Genauigkeit:

Hinweise

  1. Alternativ zu model.fit() kann auch ein eigenes Fitting programmiert werden, welches mit automatischer Differentiation durch tensorflow.GradientTape funktioniert. Siehe hierbei tensorflow.com oder in die Beispiele vom Deep Q Learning.

  2. Für jede Art von Verwendung für neuronalen Netzen bietet es sich an, Eingangs- und Ausgangsdaten zu normalisieren.

Normalisierung bedeutet, die Werte der entsprechenden Variable auf den Wertebereich [0,1][0, 1] zu transformieren. Dies gelingt simpel und effizient beispielsweise mit einer linearen Transformation, siehe microsoft.com.

Quellen

Footnotes

  1. wikipedia.org

  2. deepai.org