Künstliche Neuronale Netzwerke

Autor:in

Joern Ploennigs · AI4SC

Emotions are enmeshed in the neural networks of reason.

— António R. Damásio

Künstliche Neuronale Netzwerke haben sich in den letzten Jahren zu den wichtigsten Verfahren im Bereich des maschinellen Lernens und der künstlichen Intelligenz entwickelt. Dabei gehören zu den ältesten und grundlegendsten Algorithmen im Bereich des maschinellen Lernens und der Mustererkennung. Die ersten Grundlagen wurden bereits in den 1940er Jahren entwickelt, als ein Neurophysiologe zusammen mit einem Mathematiker ein Modell für das menschliche Denken vorschlug (McCulloch und Pitts 1943), das später u.a. von Rosenblatt zum Perzeptron weiterentwickelt wurde (Rosenblatt 1958).

Sie sind inspiriert von den biologischen neuronalen Netzwerken des menschlichen Gehirns und versuchen dieses mathematisch nachzuempfinden und somit auch ähnliche Lernerfolge zu erzielen. Sie bestehen aus vielen miteinander verbundenen “Neuronen”, die in Schichten organisiert sind und zur Verarbeitung und Analyse komplexer Datenmuster verwendet werden.

Sie werden heutzutage in vielen Anwendungsfällen genutzt insbesondere bei der Verarbeitung unstrukturierter Daten wie Bilder, Texte oder Sprache. Im Bauwesen werden sie eingesetzt zur automatisierten Rissanalyse in Betonbauwerken mittels Bildverarbeitung, zur Suche nach anomalen Vibrationen oder Dehnungsgeräuschen durch Schallemissionsverfahren oder zur Prognose von Feuchte-/Temperaturverläufen in Bauteilen über neuronale Zeitreihenmodelle.

Grundlagen

Neuronen

Neuronen sind die grundlegenden Bausteine eines neuronalen Netzwerks. Ein Neuron empfängt Eingaben, verarbeitet sie und gibt eine Ausgabe weiter. Die Verarbeitung erfolgt durch eine Aktivierungsfunktion \(f(\cdot)\), die den linearen Eingang in eine nichtlineare Ausgabe umwandelt. Das einfachste Neuron ist ein Perceptron (Rosenblatt 1958). Seine Grundstruktur besteht aus mehreren Komponenten, die in ähnlicher Form auch in modernen Neuronen vorkommen:

  • Eingaben (Inputs, \(x_1, x_2, ..., x_n\)): Signale oder Datenpunkte, die in das Neuron eingespeist werden.
  • Gewichte (Weights, \(w_1, w_2, ..., w_n\)): Faktoren, die die Bedeutung jedes Eingangs steuern.
  • Bias (Bias, \(b\)): Ein zusätzlicher Parameter, der den Schwellenwert für die Aktivierung anpasst.
  • Aktivierungsfunktion (Activation Function, \(f(\cdot)\)): Eine Funktion, die den Summenwert der gewichteten Eingaben und des Bias in eine Ausgabe umwandelt.

Die Eingaben \(x_1, x_2, ..., x_n\) sind die Signale oder Datenpunkte, die in das Neuron eingespeist werden. Gewichte \(w_1, w_2, ..., w_n\) sind Faktoren, die die Bedeutung jedes Eingangs steuern. Der Bias \(b\) ist ein zusätzlicher Parameter, der den Schwellenwert für die Aktivierung anpasst. Schließlich gibt es die Aktivierungsfunktion \(f(\cdot)\), die den Summenwert der gewichteten Eingaben und des Bias in eine Ausgabe umwandelt.

Die Ausgabe eines Neurons wird durch die folgende Formel berechnet: \[ y = f\left(b + \sum_{i=1}^n w_i x_i\right) \]

Damit gleicht das Neuron in dieser Formel einem Generalisierten Linearem Modell (GLM). Dies trifft aber nur für das einfachste neuronale Netzwerk, das Perceptron, zu. Modernere KNN bestehen aus komplexeren Modellen, die komplexe nichtlineare Zusammenhänge modellieren können. Die Aktivierungsfunktion spielt dabei eine entscheidende Rolle, da sie die Nichtlinearität in das Modell einführt und es dem Netzwerk ermöglicht, komplexe Muster in den Daten zu lernen.

Bei GLM zielt die Transformationsfunktion \(f(\cdot)\) darauf ab, die Verteilung der Ausgangsvariable zu verändern. Die meisten Aktivierungsfunktionen bei KNN zielen darauf ab, das Ausgangssignal entweder klarer zu differenzieren (aktiv/inaktiv) oder nichtlinear zu transformieren, um komplexe Zusammenhänge zu lernen. Einige gängige Aktivierungsfunktionen sind:

  • Sigmoid: Diese Funktion kennen wir von der logistischen Regression und GLM. Sie transformiert den Eingang in einen Wert zwischen 0 und 1, was sie besonders geeignet für die Ausgabe von Wahrscheinlichkeiten macht.

    \[f(x) = (1 + e^{-x})^{-1}.\]

  • Tanh (Hyperbolic Tangent): Diese Funktion transformiert den Eingang in einen Wert zwischen -1 und 1 und wurde historisch in früheren neuronalen Netzen verwendet. Sie kann in Aufgaben nützlich sein, bei denen sowohl positive als auch negative Werte relevant sind.

    \[f(x) = \tanh(x).\]

  • ReLU (Rectified Linear Unit): Sie ist eine einfache und häufig verwendete Funktion, die den Eingang direkt zurückgibt, wenn er positiv ist, und ansonsten Null. ReLU ist bekannt dafür, neuronale Netze schneller und effizienter zu trainieren.

    \[f(x) = \max(0, x).\]

  • Softmax: Die Softmax-Funktion normalisiert die Ausgabe eines Neurons auf eine Wahrscheinlichkeitsverteilung, wobei die Summe aller Ausgänge 1 ergibt. Sie wird häufig in mehrstufigen Klassifizierungsaufgaben eingesetzt, wo sie die Wahrscheinlichkeiten aller Klassen gleichzeitig ausgibt.

    \[f(x_i) = e^{x_i}\left(\sum_{j=1}^K e^{x_j}\right)^{-1}.\]

Die Wahl der richtigen Aktivierungsfunktion hängt von der spezifischen Aufgabe und der Architektur des neuronalen Netzes ab. Generell ist ReLU aufgrund seiner Einfachheit und Effizienz eine beliebte Wahl, während Sigmoid und Tanh in Situationen nützlich sein können, wo negative Werte relevant sind oder eine interpretierbare Ausgabe gewünscht ist. Softmax wird typischerweise für mehrstufige Klassifizierungsaufgaben verwendet.

Schichten

Die Fähigkeit komplexe Zusammenhänge zu erlernen, ergibt sich vor allem daraus, dass Neuronale Netzwerke aus mehreren Schichten von Neuronen bestehen, die in einer bestimmten Architektur angeordnet sind.

  • Eingabeschicht (Input Layer) nimmt die Rohdaten entgegen und transformiert sie für die nachfolgenden Schichten .
  • Verborgene Schichten (Hidden Layers) enthalten Neuronen, die die eingehenden Daten verarbeiten und transformieren. Bei modernen Tiefen Neuronalen Netzwerken (DNN - Deep Neural Networks) ist die Anzahl der Schichten hoch (3-100 Schichten).
  • Ausgabeschicht (Output Layer) transformieren die Ausgaben des Netzwerk in die Ergebnisse. Die Anzahl der Neuronen in der Ausgabeschicht hängt von der Art des zu lösenden Problems ab.

Die erste Schicht ist die Eingabeschicht (Input Layer) und nimmt die Rohdaten des Problems entgegen und leitet sie an die nachfolgenden Schichten weiter. Diese Schicht enthält keine Aktivierungsfunktionen und dient lediglich als Schnittstelle zur Datenaufnahme.

Danach folgen mehrerer verborgene Schichten (Hidden Layers). Diese Schichten enthalten Neuronen, die die eingehenden Daten verarbeiten und transformieren. Die Anzahl der verborgenen Schichten sowie die Anzahl der Neuronen in jeder Schicht können je nach Komplexität des Netzwerks variieren. Bei modernen Tiefen Neuronalen Netzwerken (DNN - Deep Neural Networks) ist die Anzahl der Schichten hoch (3-100 Schichten).

Schließlich gibt es die Ausgabeschicht (Output Layer), die das Ergebnis der Verarbeitung durch das Netzwerk liefert. Die Anzahl der Neuronen in der Ausgabeschicht hängt von der Art des zu lösenden Problems ab. Für binäre Klassifikationsprobleme enthält die Ausgabeschicht oft nur ein Neuron, während für Mehrklassenklassifikationsprobleme mehrere Neuronen erforderlich sind, um die verschiedenen Klassen zu repräsentieren.

Trainingsprozess

Der Grund, weshalb Neuronale Netzwerke fast 60 Jahre lang nicht genutzt wurden, lag in Skalierbarkeit des Trainings. Das oben abgebildete Beispiel zeigt bereits, die hohe Anzahl an 25 Parametern (Gewichte und Bias) für ein kleines Netzwerk mit vier Schichten. Leistungsfähige Netze haben bis zu Milliarden an Parametern.

Aufgrund der Komplexität können die Parameter nicht direkt berechnet werden und werden deshalb normalerweise iterativ angenähert. Dieser Prozess umfasst typischerweise folgende Schritte:

  1. Zufällige Initialisierung aller Parameter \(w\) und \(b\) mit Zufallszahlen.

  2. Vorwärtsausbreitung (Forward Propagation): Die Eingabedaten werden durch das Netzwerk geleitet und die Ausgabe geschätzt. Jedes Neuron berechnet seine Aktivierungsfunktion basierend auf den gewichteten Eingaben und dem Bias mit Hilfe der oben angegeben Formel.

  3. Berechnung des Fehlers (Loss Calculation): Der Fehler oder Verlust wird durch eine Verlustfunktion berechnet, die die Differenz zwischen der vorhergesagten Ausgabe und der tatsächlichen Ausgabe misst. Die passende Verlustfunktionen richtet sich dabei nach dem Datentyp der Ausgangsvariable. Typische Verlustfunktionen kennen wir bereits:

    \[E^{\text{MSE}} = \frac{1}{n} \sum_{j=1}^{n}(\hat y_j - y_j)^2\]

    \[E^{\text{LL}} = -\frac{1}{n} \sum_{j=1}^{n} \left[y_j \log(\hat y_j) + (1-y_j) \log(1-\hat y_j)\right]\]

  1. Rückwärtsausbreitung (Backpropagation): Der Fehler wird durch das Netzwerk rückwärts propagiert, um die Gradienten der Verlustfunktion bezüglich der Gewichte und Biases zu berechnen. Dabei wird im Wesentlichen der Fehler rückwärts im Netzwerk verteilt, so dass Neuronen, die einen großen Anteil an der Vorhersage haben (also einen hohen Eingangswert und eine steile Aktivierungsfunktion an dieser Stelle), ein größerer Anteil am Fehler zugeteilt wird. Der Gradient \(\nabla {E(w_{ij})}\) des Gewichtes \(w_{ij}\) kann mit der Kettenregel berechnet werden:

    \[ \nabla {E(w_{ij})} = \frac{\partial E}{\partial w_{ij}} \] \[ \nabla {E(b_{i})} = \frac{\partial E}{\partial b_{i}} \]

    Die entsprechende partielle Ableitung richtet sich prinzipiell nach der Aktivierungsfunktion und wird in modernen Bibliotheken meist analytisch mit der Kettenregel sowie automatischer Differentiation berechnet.

  2. Gewichtsaktualisierung (Weight Update): Die Gewichte und Biases werden mithilfe eines Optimierungsalgorithmus wie Gradient Descent oder Adam angepasst, um den Fehler zu minimieren. Beim Gradient Descent geschieht dies durch Anwendung der berechneten Gradienten auf die Gewichte und Biases mit Hilfe der Lernrate \(\alpha\):

    \[w_{ij} = w_{ij} - \alpha * \nabla E(w_{ij})\] \[b_{i} = b_{i} - \alpha * \nabla E(b_{i})\]

  3. Abbruchkriterium: Wiederholung der Schritte 2.-5. bis ein Konvergenzkriteriums erfüllt wurde oder eine maximale Anzahl an Iterationen erreicht wird.

Der Trainingsprozess wird in der Regel über mehrere Epochen wiederholt, wobei jede Epoche eine Durchführung des gesamten Trainingsdatensatzes darstellt. Dies ermöglicht es dem Netzwerk, die Gewichte iterativ zu verbessern und seine Leistung auf neuen Daten zu generalisieren. Ein entscheidender Grund für den heutigen Erfolg von Neuronalen Netzwerken ist, dass diese Berechnung der Gewichte hoch parallelisierbar ist und somit sehr gut auf modernen Graphikkarten parallelisiert werden können.

Ein häufiges Problem beim Training neuronaler Netzwerke ist Overfitting, bei dem das Netzwerk die Trainingsdaten zu gut lernt und auf neuen Daten schlecht generalisiert. Um Overfitting zu vermeiden und die Leistung des Netzwerks zu verbessern, werden verschiedene Techniken der Regularisierung eingesetzt:

  • Regularisierung (z.B. L1, L2): Hinzufügen eines Regularisierungsterms zur Verlustfunktion, um die Komplexität des Modells zu kontrollieren.
  • Dropout: Zufälliges Ausschalten von Neuronen während des Trainings, um die Abhängigkeit von bestimmten Neuronen zu reduzieren.
  • Datenaugmentation: Erweiterung des Trainingsdatensatzes durch zufällige Transformationen der Daten.

Architekturen

Vorwärtsgerichtete Netze

Ein weiterer wichtiger Faktor für den Erfolg von Neuronalen Netzwerken ist die Fähigkeit, Neuronen in unterschiedlichen Architekturen zu verbinden und sie dadurch zu befähigen, bestimmte Datenstrukturen besser zu lernen.

Die einfachsten Formen neuronaler Netzwerke sind Vorwärtsgerichtete Neuronale Netze (FNN - Feedforward Neural Networks). Bei ihnen fließen die Daten in eine Richtung von der Eingabeschicht zur Ausgabeschicht, ohne rückwärts gerichtete Datenflüsse. Sie entsprechen den oben dargestellten Netzwerken. Sie sind besonders geeignet bei einfachen Problemen, wo keine Autokorrelation oder komplexe Muster vorherrschen.

Rekurrente Netze

Rekurrente oder rückgekoppelte Neuronale Netze (RNN - Recurrent Neural Networks) sind neuronalen Netzen, die sich von Feedforward-Netzen dadurch unterscheiden, dass sie Verbindungen zwischen Neuronen einer Schicht zu Neuronen derselben oder vorheriger Schichten ermöglichen. Diese Art der Verschaltung ähnelt der bevorzugten Struktur neuronaler Netze im Gehirn, insbesondere im Neocortex. RNNs sind ideal für sequenzielle Daten wie Zeitreihen oder Text, da sie über Schleifen verfügen, die Informationen über Zeitpunkte (oder Wortfolgen/Sätze) hinweg speichern. Dadurch lässt sich z.B. gut die Autoregression in Zeitreihen oder komplette Sätze erlernen.

\[ h_t = f\left(b + \sum_{i=1}^n w_i x_{i,t} + v\, h_{t-1} \right) \]

Hierbei ist \(h_t\) der versteckte Zustand (hidden state) zum aktuellen Zeitpunkt \(t\) und \(h_{t-1}\) der versteckte Zustand des vorherigen Zeitpunkts, der über das Gewicht \(v\) zurückgeführt wird.

Faltungsnetze

Faltungsnetze (CNN - Convolutional Neural Networks) sind besonders effektiv bei der Verarbeitung von höherdimensionalen Daten wie Bilddaten oder geospatialer Daten. Sie verwenden Faltungsoperationen, um Merkmale (Kanten, Reliefs, Muster, etc.) aus Eingabebildern zu extrahieren oder zu erlernen und gleichzeitig einen höherendimensionalen Eingang auf einen niedrigdimensionaleren Ausgang zu reduzieren, der diese Merkmale encodiert. Dadurch repräsentiert der Ausgang nur die Relevanz des extrahierten oder gelernten Merkmals und es muss nicht das Gesamtbild gelernt werden. Durch das Schichten mehrerer versteckter Faltungsschichten hintereinander werden so zuerst Merkmale extrahiert und dann Muster daraus im Bild gelernt (z.B. die Form eines Hundes). Auf die Faltung und die Bildverarbeitung werden wir im Nachfolgekurs “Künstliche Intelligenz und Foundation Modelle” noch vertieft eingehen.

Neuronale Netzwerke in Python

Neuronale Netzwerke in sklearn

Es gibt verschiedene Bibliotheken für Neuronale Netzwerke in Python. Wir betrachten zuerst sklearn, da wir dort Neuronale Netzwerke einfach mit der bekannten API nutzen können.

Wir demonstrieren neuronale Netzwerke anhand einer erweiterten Version des Yin-Yang-Beispiels aus der Klassifikation indem wir die Anzahl der Farben auf 4 Kategorien und die Verwirbelung erhöhen. Wieder besteht die Aufgabe darin, die richtige Farbe basierend auf dem Punkt vorherzusagen, also eine Klassifikationsaufgabe. Wir erstellen die Daten wie folgt:

import numpy as np # Import von NumPy
import pandas as pd # Import von Pandas
import plotly.express as px # Import von Plotly
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split

np.random.seed(33)
N = 1000  # Anzahl der Punkte
X1 = np.random.normal(size=N)
X2 = np.random.normal(size=N)
X = np.column_stack((X1, X2))

alpha = np.arctan2(X2, X1)
r = np.sqrt(X1**2 + X2**2)

## Teile the sum of a sin and cosine into 5 intervals
category = pd.cut(np.sin(3*alpha + 2*r) + np.cos(3*alpha + 2*r),
           bins=[-1.5, -1.1, -0.6, 0.6, 1.1, 1.5],
           labels=[0, 1, 2, 3, 4])
y = category.astype(int)

# Teile in Test und Trainings-Datensatz
X_train, X_test, y_train, y_test = train_test_split(X, y)
# Darstellung des Datensatzes
fig=px.scatter(x=X2, y=X1, color=y, width=600, height=600)
fig.update_traces(marker_line=dict(width=.3, color="black"))
fig.show()

Das Bild zeigt ein komplexes Muster von Spiralarmen in fünf verschiedenen Farben. Dreiarmige gelbe und blauen Arme entsprechen den größten und kleinsten Werten, während sechsarmige Arme in verschiedenen Rot- und Violetttönen den dazwischen liegenden Werten entsprechen. Diese Entscheidungsgrenzen sind sehr schwer mit einfachen Modellen wie logistischer Regression, SVM oder sogar Bäumen zu erfassen, da die Spiralarme stark nicht-konvexe und ineinander verschlungene Klassenregionen bilden: Bäume können solche Regionen nur grob durch viele achsenparallele Splits annähern, und ein SVM mit einem einzelnen festen Kernel findet keine Transformation, die alle Spiralarme gleichzeitig sauber trennt. Prüfen wir das noch einmal mit einer kleinen Visualisierungsfunktion, die wir auch in der Klassifikation genutzt haben.

mcolors=['blue', 'purple', 'violet', 'orange', 'yellow']

def DBPlot(m, X, y, nGrid = 300):
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, nGrid), 
                           np.linspace(x2_min, x2_max, nGrid))
    XX = np.column_stack((xx1.ravel(), xx2.ravel()))
    hatyy = m.predict(XX).reshape(xx1.shape)
    fig = px.imshow(hatyy, width=600, height=600)
    fig.add_scatter(x=X[:,0]/(x1_max-x1_min)*nGrid+nGrid/2, y=X[:,1]/(x2_max-x2_min)*nGrid+nGrid/2, mode="markers", 
                    marker=dict(color=[mcolors[i] for i in y]), marker_line=dict(width=.3, color="black"))
    fig.update_coloraxes(showscale=False)
    fig.update_layout(showlegend=False)
    fig.show()

Testen wir einen Random Forest, so erhalten wir zwar ein gutes Modell, dass allerdings overfittet und schlecht generalisiert.

from sklearn.ensemble import RandomForestClassifier

m = RandomForestClassifier()
_ = m.fit(X_train, y_train)
m.score(X_test, y_test)
0.76
confusion_matrix(y_test, m.predict(X_test))
array([[51,  1,  2,  0,  0],
       [11, 18,  5,  0,  0],
       [ 2,  8, 50, 10,  0],
       [ 0,  1,  8, 24,  5],
       [ 0,  0,  4,  3, 47]])
DBPlot(m, X_test, y_test)

Ein SVM mit radialem Kernel erkennt die Struktur besser, aber hat einen deutlich höheren Fehler.

from sklearn.svm import SVC

m = SVC(kernel="rbf", gamma=1)
_ = m.fit(X_train, y_train)
m.score(X_test, y_test)
0.544
confusion_matrix(y_test, m.predict(X_test))
array([[43,  0, 11,  0,  0],
       [19,  0, 15,  0,  0],
       [11,  0, 52,  0,  7],
       [ 1,  0, 11,  0, 26],
       [ 3,  0, 10,  0, 41]])
DBPlot(m, X_test, y_test)

Allerdings können neuronale Netzwerke (und KNN) deutlich besser damit umgehen.

sklearn implementiert einfache Feed-Forward-Neuronale Netzwerke, so genannte Multi-Layer Perceptrons. Dies sind einfache dichte Feed-Forward-Netzwerke mit einer beliebigen Anzahl von versteckten Schichten. Auch wenn sie strukturell einfache neuronaler Netzwerke sind, sind sie dennoch leistungsfähig genug für viele Aufgaben.

Wie bei anderen fortgeschrittenen Modellen bietet sklearn zwei unterschiedlichen Klassen für Klassifikationsaufgaben MLPClassifier und für Regressionsaufgaben MLPRegressor. Die grundlegende Verwendung dieser Modelle ist identisch zu den anderen sklearn-Modellen.

Die wichtigsten Argumente für den MLPClassifier sind:

MLPClassifier(hidden_layer_sizes, activation, max_iter, alpha)

Von diesen ist hidden_layer_sizes der zentralste. Dies beschreibt die Anzahl der versteckten Schichten (die Größe der Eingabe- und Ausgabeschicht wird automatisch aus den Daten bestimmt). Es handelt sich um ein Tupel, das die Anzahl der Knoten für jede versteckte Schicht angibt, daher gibt die Länge des Tupels auch die Anzahl der versteckten Schichten an. Zum Beispiel bedeutet hidden_layer_sizes = (32, 16) zwei versteckte Schichten, die erste mit 32 und die folgende mit 16 Knoten. activation beschreibt die Aktivierungsfunktion. Es empfiehlt sich meist es bei der Standardfunktion relu zu lassen, außer es gibt die oben beschriebenen Gründe für andere Aktivierungsfunktionen. alpha ist der L2-Regularisierungsparameter und max_iter gibt die maximale Anzahl von Iterationen an, bevor die Optimierung stoppt. In moderneren Bibliotheken wie Keras oder PyTorch wird die L2-Regularisierung meist nicht global, sondern explizit pro Schicht angegeben, z.B. mit kernel_regularizer=regularizers.L2(alpha) in Keras.

Lassen Sie uns nun die Verwendung von MLPClassifier an den Yin-Yang Beispiel demonstrieren. Beginnen wir mit einem einfachen Perzeptron mit einer einzigen versteckten Schicht von 20 Knoten. Daher verwenden wir hidden_layer_sizes=(20,), ein Tupel mit nur einer Zahl. Das Anpassen des Modells und die Vorhersage sind größtenteils ähnlich wie bei den anderen sklearn-Funktionen, daher diskutieren wir dies hier nicht. Wir erhöhen auch die Anzahl der Iterationen, da die Standardanzahl von 200 in diesem Fall zu gering ist:

from sklearn.neural_network import MLPClassifier

m = MLPClassifier(hidden_layer_sizes = (20,), max_iter=10000)
_ = m.fit(X_train, y_train)
m.score(X_test, y_test)
0.588
confusion_matrix(y_test, m.predict(X_test))
array([[46,  1,  6,  0,  1],
       [18,  2, 13,  0,  1],
       [11,  1, 53,  0,  5],
       [ 1,  0, 16,  0, 21],
       [ 0,  0,  6,  2, 46]])
DBPlot(m, X_test, y_test)

Dieses einfache Modell hat zuerst auch nur eine niedrige Genauigkeit. Das liegt daran, dass es zu einfach ist, nur eine einzelne kleine versteckte Schicht reicht nicht aus, um das komplexe Spiralmuster gut zu modellieren. Was sich gut in der Visualisierung erkennen lässt. Wir können dort sehen, dass das Modell die Idee korrekt einfängt: Spiralen in verschiedenen Farben, aber die Form der Spiralen ist nicht genau genug.

Lassen Sie uns das Modell mit einem leistungsstärkeren Netzwerk wiederholen:

m = MLPClassifier(hidden_layer_sizes = (256, 128, 64), max_iter=10000)
_ = m.fit(X_train, y_train)
m.score(X_test, y_test)
0.912
confusion_matrix(y_test, m.predict(X_test))
array([[52,  1,  1,  0,  0],
       [ 6, 26,  2,  0,  0],
       [ 1,  2, 66,  1,  0],
       [ 0,  0,  4, 34,  0],
       [ 0,  0,  0,  4, 50]])
DBPlot(m, X_test, y_test)

Jetzt sind die Ergebnisse sehr gut mit einer Genauigkeit von ca. 95 %. Die visuelle Inspektion bestätigt, dass das leistungsstärkere neuronale Netzwerk die Gesamtstruktur des Modells gut erfassen kann.

Die angepassten Netzwerkmodelle haben verschiedene Methoden und Attribute, z.B. gibt coefs_ die Modellgewichte aus (als Liste von Gewichtsmatrizen, eine für jede Schicht), und intercepts_ gibt die Modell-Biaswerte aus (als Liste von Bias-Vektoren, einer für jede Schicht). Zum Beispiel enthält das oben angepasste Modell den akkumilierten Bias-Vektor für die erste versteckte Schicht:

np.sum([b.size for b in m.intercepts_])
np.int64(453)

Während sklearn einen einfachen Zugang zu neuronalen Netzwerkmodellen bietet, sind diese Modelle erheblich eingeschränkt. sklearn ist damit gut für kleinere Aufgaben und zum schnellen Prototyping geeignet, aber für tiefere Netzwerke, größere Datenmengen und GPU-Beschleunigung braucht man spezialisierte Frameworks wie tensorflow oder pytorch.

Neuronale Netzwerke in Keras

Tensorflow and Keras

Tensorflow ist eine Bibliothek, die Berechnungsgraphen implementiert, die automatisch Gradienten berechnen können. Sie ermöglicht auch Berechnungen auf der GPU durchzuführen, was potenziell zu erheblichen Geschwindigkeitsverbesserungen gegenüber CPU-Berechnungen führen kann. In vielerlei Hinsicht ähnelt sie numpy, wo man Matrizen und Tensoren erstellen und manipulieren kann. Allerdings ist sie aufgrund des Berechnungsgraphen-Ansatzes und der GPU-bezogenen Überlegungen viel schwieriger zu verwenden.

Keras ist ein TensorFlow-Front-End, ein Submodul, das einen viel benutzerfreundlicheren Zugang zum Aufbau neuronaler Netzwerke bietet. Die Funktionalität umfasst eine Vielzahl von Netzwerkschichten, bei denen man die entsprechenden Parameter anpassen kann. Beim Aufbau des Netzwerks kann man einfach die Schichten hintereinander hinzufügen, die Verbindung zwischen den Schichten wird von Keras selbst übernommen. Eine gute Quelle für die Keras-Dokumentation ist die API-Referenzdokumentation.

Tensorflow kann schwierig und frustrierend zu installieren sein. Normalerweise funktioniert es gut mit conda install tensorflow oder pip install tensorflow. Manchmal können jedoch Probleme auftreten und es kann schwierig sein, die Probleme zu finden und zu beheben. Insbesondere installiert pip normalerweise die neueste Version, auch wenn sie mit dem Rest Ihrer Pakete nicht kompatibel ist. Es installiert auch Abhängigkeiten und kann bestimmte Pakete aktualisieren, was die Python-Installation beeinträchtigen kann. Um Probleme mit dem Rest Ihres Systems zu vermeiden, empfehlen wir dringend, es in einer virtuellen Umgebung wie Anaconda zu installieren.

Beispielnetzwerk in keras

Lassen Sie uns das Farbspiralbeispiel aus dem letzten Abschnitt von sklearn neu implementieren, diesmal jedoch mit keras. Es wird einige bemerkenswerte Unterschiede geben:

Der Aufbau des Netzwerks selbst ist in Keras anders. Keras berechnet nicht die Größe der Eingabe- und Ausgangsschichten aus den Daten. Beide müssen vom Benutzer angegeben werden. Schließlich sagt Keras nur Wahrscheinlichkeiten für alle Kategorien voraus, daher müssen wir Code hinzufügen, der die Spalte (Kategorie) mit der höchsten Wahrscheinlichkeit findet.

Das Modell erzeugen

Zuerst der wichtigste Schritt: Aufbau und Kompilierung des Modells. Dies unterscheidet sich sehr von der Vorgehensweise in sklearn. Lassen Sie uns ein sequentielles Modell mit dichten Schichten aufbauen, die gleiche Architektur, die wir mit sklearn erstellt haben:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input

# sequential (not recursive) model (one input, one output)
model=Sequential()
model.add(Input(shape=(2,)))
model.add(Dense(512, activation="relu"))
model.add(Dense(256, activation="relu"))
model.add(Dense(64, activation="relu"))
nCategories = len(np.unique(category))
model.add(Dense(nCategories, activation="softmax"))

Wir beginnen mit dem Importieren der Funktionalität, die wir von tensorflow.keras benötigen. Danach erstellen wir ein leeres sequenzielles Modell (d.h. ein Modell, das keine Schleifen und rückwärts fließende Daten enthält) und fangen an, Schichten hinzuzufügen. Wir fügen 3 dichte Schichten (Dense) hinzu. Die erste Schicht enthält 512 Knoten, die zweite 256 und die letzte Schicht enthält 64 Knoten. Alle diese Knoten werden mit der relu Funktion aktiviert.

Das erste wichtige neue Feature ist die explizite Definition der Eingabeform, hier mit Input(shape=(2,)) vor der ersten dichten Schicht. Dies sagt Keras, welche Art von Eingaben zu erwarten sind. Derzeit ist es nur ein Tupel (2,), da unsere X-Matrix nur 2 Spalten enthält. Also (2,) ist nur die Form einer einzelnen Zeile von X, einer einzelnen Instanz der Eingabedaten. Sie können die richtige Form mit X[0].shape finden. Aber die Eingabe muss hier nicht nur ein Vektor sein. Zum Beispiel kann es bei Bildern ein 3-D-Tensor mit der Form (Breite, Höhe, #Farbkanäle) sein. Die folgenden Schichten können diese Form dann automatisch ableiten.

Wir müssen auch eine explizite Ausgabeschicht hinzufügen. Da es sich hier um Klassifizierung handelt, benötigen wir so viele Ausgabeknoten wie Kategorien - wir können diese Anzahl berechnen als

nCategories = len(np.unique(category))

Jeder Ausgabeknoten wird die Wahrscheinlichkeit vorhersagen, dass die Eingabe in die entsprechende Kategorie fällt. Wir können die softmax (multi-nomiale Logit)-Aktivierung verwenden, um sicherzustellen, dass die Ergebnisse gültige Wahrscheinlichkeiten sind. Beachten Sie, dass im Falle von nur zwei Kategorien die Softmax-Aktivierung äquivalent zur gewöhnlichen logistischen Regression ist. Aber im Gegensatz zur gewöhnlichen logistischen Regression haben wir eine Reihe anderer Schichten vor der letzten logistischen Schicht.

Dabei ist es hilfreich, zwischen drei Begriffen zu unterscheiden: Logits sind die rohen Ausgaben vor der letzten Aktivierungsfunktion, Wahrscheinlichkeiten sind die durch Sigmoid oder Softmax transformierten Werte, und Klassenlabels erhält man erst durch eine anschließende Entscheidung wie Schwellenwert oder argmax.

Das richtige Festlegen der Eingabeformen und Ausgabeknoten ist eine der Hauptursachen für Frustration, wenn man anfängt, mit keras zu arbeiten. Die Fehlermeldungen sind lang und nicht besonders hilfreich, und es ist schwer zu verstehen, was das Problem ist. Hier ist eine Checkliste, die durchgearbeitet werden kann, wenn etwas nicht funktioniert:

  • Ist die Eingabeform explizit definiert, z.B. mit Input(shape=...) am Anfang des Modells?
  • Stellt diese Form korrekt eine einzelne Instanz der Eingabedaten dar?
  • Haben Sie die richtige Anzahl von Knoten in der Softmax-aktivierten Ausgangsschicht?

Training des Modells

Die nächste Aufgabe besteht darin, das Modell zu kompilieren und anzupassen. Keras-Modelle müssen kompiliert werden - was wir bisher eingerichtet haben, ist nur eine Beschreibung des Modells, nicht das tatsächliche Modell, das für TensorFlow-Tensoren eingerichtet ist und möglicherweise für die GPU-Ausführung. Wir können das Modell wie folgt kompilieren:

Für ein sauberes Training trennt man die Daten typischerweise in Trainings-, Validierungs- und Testdaten. Die Trainingsdaten werden zum Lernen der Gewichte verwendet, die Validierungsdaten zur Kontrolle während des Trainings und zur Wahl von Hyperparametern, und die Testdaten erst ganz am Ende für eine unabhängige Bewertung. Eine häufige Technik ist dabei Early Stopping: Man beendet das Training, wenn sich der Fehler auf den Validierungsdaten nicht weiter verbessert, um Overfitting zu vermeiden.

model.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
print(model.summary())
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ dense (Dense)                   │ (None, 512)            │         1,536 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 256)            │       131,328 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 64)             │        16,448 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (Dense)                 │ (None, 5)              │           325 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 149,637 (584.52 KB)
 Trainable params: 149,637 (584.52 KB)
 Non-trainable params: 0 (0.00 B)
None

Die drei wichtigsten Argumente sind

  • loss beschreibt die Modellverlustfunktion, sparse_categorical_crossentropy, im Wesentlichen die Log-Likelihood, ist geeignet für Kategorisierungsaufgaben. Der genaue Typ hängt auch davon ab, wie das Ergebnis genau codiert ist.
  • optimizer ist der Optimierer, der für den stochastischen Gradientenabstieg verwendet werden soll. adam und rmsprop sind gute Optionen, aber es gibt auch andere Möglichkeiten.
  • metrics ist eine Metrik, die während der Optimierung ausgewertet und gedruckt wird und einige Rückmeldungen darüber bietet, wie die Auswertung verläuft.

Die letzte Zeile hier druckt die Modellzusammenfassung aus, eine praktische Übersicht darüber, was wir gemacht haben:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               1536      
_________________________________________________________________
dense_1 (Dense)              (None, 256)               131328    
_________________________________________________________________
dense_2 (Dense)              (None, 64)                16448     
_________________________________________________________________
dense_3 (Dense)              (None, 5)                 325       
=================================================================
Total params: 149,637
Trainable params: 149,637
Non-trainable params: 0

Dieses Netzwerk enthält fast 150.000 Parameter, von denen alle trainierbar sind.

Nach erfolgreicher Kompilierung können wir das Modell anpassen:

history = model.fit(X_train, y_train, epochs=200, validation_split=0.2)
Epoch 1/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 10s 584ms/step - accuracy: 0.1875 - loss: 1.6100

19/19 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.2867 - loss: 1.5816 - val_accuracy: 0.3000 - val_loss: 1.5378

Epoch 2/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.2188 - loss: 1.6405

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.2967 - loss: 1.5445 - val_accuracy: 0.2933 - val_loss: 1.5381

Epoch 3/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.2188 - loss: 1.5586

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.3200 - loss: 1.5297 - val_accuracy: 0.3200 - val_loss: 1.5385

Epoch 4/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3125 - loss: 1.5261

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.3117 - loss: 1.5192 - val_accuracy: 0.3600 - val_loss: 1.5350

Epoch 5/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.2812 - loss: 1.5330

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.3583 - loss: 1.4917 - val_accuracy: 0.3533 - val_loss: 1.5293

Epoch 6/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3438 - loss: 1.4916

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.3583 - loss: 1.4780 - val_accuracy: 0.4000 - val_loss: 1.5397

Epoch 7/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3750 - loss: 1.4100

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.3750 - loss: 1.4580 - val_accuracy: 0.3733 - val_loss: 1.5093

Epoch 8/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3438 - loss: 1.4881

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.3683 - loss: 1.4509 - val_accuracy: 0.4067 - val_loss: 1.5235

Epoch 9/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3750 - loss: 1.5824

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.4067 - loss: 1.4262 - val_accuracy: 0.3867 - val_loss: 1.5096

Epoch 10/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3125 - loss: 1.4763

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.3867 - loss: 1.4120 - val_accuracy: 0.3800 - val_loss: 1.4904

Epoch 11/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5000 - loss: 1.2922

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.4000 - loss: 1.3835 - val_accuracy: 0.4200 - val_loss: 1.4768

Epoch 12/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.3750 - loss: 1.4558

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.4417 - loss: 1.3603 - val_accuracy: 0.4333 - val_loss: 1.4791

Epoch 13/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.2500 - loss: 1.3855

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.4467 - loss: 1.3330 - val_accuracy: 0.4800 - val_loss: 1.4141

Epoch 14/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3438 - loss: 1.3761

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.4533 - loss: 1.3042 - val_accuracy: 0.4533 - val_loss: 1.4377

Epoch 15/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.4688 - loss: 1.2445

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.4933 - loss: 1.2666 - val_accuracy: 0.5267 - val_loss: 1.3797

Epoch 16/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.5938 - loss: 1.1447

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.4933 - loss: 1.2277 - val_accuracy: 0.5467 - val_loss: 1.3066

Epoch 17/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.5625 - loss: 1.0881

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.5317 - loss: 1.1725 - val_accuracy: 0.5533 - val_loss: 1.2744

Epoch 18/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.5625 - loss: 1.1445

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.5617 - loss: 1.1278 - val_accuracy: 0.5267 - val_loss: 1.2700

Epoch 19/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.4688 - loss: 1.1546

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.5650 - loss: 1.1005 - val_accuracy: 0.5667 - val_loss: 1.2319

Epoch 20/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.6250 - loss: 1.0752

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.5800 - loss: 1.0533 - val_accuracy: 0.6200 - val_loss: 1.1549

Epoch 21/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.5000 - loss: 1.0695

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.5850 - loss: 1.0363 - val_accuracy: 0.5867 - val_loss: 1.1526

Epoch 22/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.5938 - loss: 0.9721

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6033 - loss: 0.9872 - val_accuracy: 0.6067 - val_loss: 1.0489

Epoch 23/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.6250 - loss: 0.9482

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6450 - loss: 0.9295 - val_accuracy: 0.6533 - val_loss: 1.0307

Epoch 24/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.7105

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6567 - loss: 0.8896 - val_accuracy: 0.6333 - val_loss: 0.9732

Epoch 25/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.6875 - loss: 0.8916

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6667 - loss: 0.8671 - val_accuracy: 0.6933 - val_loss: 0.9240

Epoch 26/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.6875 - loss: 0.7156

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.6600 - loss: 0.8272 - val_accuracy: 0.6933 - val_loss: 0.9340

Epoch 27/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.8125 - loss: 0.6901

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6917 - loss: 0.8141 - val_accuracy: 0.6867 - val_loss: 0.8841

Epoch 28/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.7188 - loss: 0.6832

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6950 - loss: 0.7879 - val_accuracy: 0.6933 - val_loss: 0.8115

Epoch 29/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8125 - loss: 0.7519

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.6850 - loss: 0.7481 - val_accuracy: 0.7333 - val_loss: 0.8229

Epoch 30/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.6875 - loss: 0.8379

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7433 - loss: 0.7041 - val_accuracy: 0.7000 - val_loss: 0.8023

Epoch 31/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.6562 - loss: 0.7178

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7433 - loss: 0.6890 - val_accuracy: 0.7200 - val_loss: 0.8332

Epoch 32/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.7812 - loss: 0.6154

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7317 - loss: 0.6432 - val_accuracy: 0.7333 - val_loss: 0.7270

Epoch 33/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.5212

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7617 - loss: 0.6229 - val_accuracy: 0.8000 - val_loss: 0.7078

Epoch 34/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.4931

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7850 - loss: 0.5950 - val_accuracy: 0.7200 - val_loss: 0.7695

Epoch 35/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.7812 - loss: 0.6392

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7917 - loss: 0.5778 - val_accuracy: 0.7400 - val_loss: 0.7574

Epoch 36/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.7500 - loss: 0.5394

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.7667 - loss: 0.5840 - val_accuracy: 0.7867 - val_loss: 0.6676

Epoch 37/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.5509

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7600 - loss: 0.6006 - val_accuracy: 0.7733 - val_loss: 0.6641

Epoch 38/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.4093

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8117 - loss: 0.5480 - val_accuracy: 0.7667 - val_loss: 0.6702

Epoch 39/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.7500 - loss: 0.4681

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8033 - loss: 0.5593 - val_accuracy: 0.7333 - val_loss: 0.7175

Epoch 40/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.4592

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.7817 - loss: 0.5538 - val_accuracy: 0.7933 - val_loss: 0.6169

Epoch 41/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.4915

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8133 - loss: 0.4939 - val_accuracy: 0.8267 - val_loss: 0.6030

Epoch 42/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.7812 - loss: 0.5477

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8533 - loss: 0.4652 - val_accuracy: 0.8200 - val_loss: 0.6001

Epoch 43/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.3266

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8383 - loss: 0.4538 - val_accuracy: 0.7867 - val_loss: 0.5607

Epoch 44/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8125 - loss: 0.5635

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8617 - loss: 0.4414 - val_accuracy: 0.7867 - val_loss: 0.5932

Epoch 45/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8438 - loss: 0.4142

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8883 - loss: 0.4165 - val_accuracy: 0.8400 - val_loss: 0.5707

Epoch 46/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.4071

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8567 - loss: 0.4124 - val_accuracy: 0.7933 - val_loss: 0.5858

Epoch 47/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.4313

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8667 - loss: 0.3927 - val_accuracy: 0.8333 - val_loss: 0.5576

Epoch 48/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.2295

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8750 - loss: 0.3804 - val_accuracy: 0.8200 - val_loss: 0.5569

Epoch 49/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.3767

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8733 - loss: 0.3870 - val_accuracy: 0.8067 - val_loss: 0.6084

Epoch 50/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.4515

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8567 - loss: 0.4024 - val_accuracy: 0.8267 - val_loss: 0.5712

Epoch 51/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.3314

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8450 - loss: 0.3775 - val_accuracy: 0.7733 - val_loss: 0.6191

Epoch 52/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.3792

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8650 - loss: 0.3686 - val_accuracy: 0.8467 - val_loss: 0.5421

Epoch 53/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.8438 - loss: 0.3675

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.9033 - loss: 0.3486 - val_accuracy: 0.8467 - val_loss: 0.5458

Epoch 54/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8438 - loss: 0.3588

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8767 - loss: 0.3460 - val_accuracy: 0.8600 - val_loss: 0.5799

Epoch 55/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.3358

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8883 - loss: 0.3333 - val_accuracy: 0.8600 - val_loss: 0.5044

Epoch 56/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.3349

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8983 - loss: 0.3256 - val_accuracy: 0.8267 - val_loss: 0.5481

Epoch 57/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.2264

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8633 - loss: 0.3485 - val_accuracy: 0.8467 - val_loss: 0.5763

Epoch 58/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.2343

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8700 - loss: 0.3346 - val_accuracy: 0.8667 - val_loss: 0.4934

Epoch 59/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.2500

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9017 - loss: 0.3124 - val_accuracy: 0.8200 - val_loss: 0.5418

Epoch 60/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.2366

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8967 - loss: 0.2920 - val_accuracy: 0.8600 - val_loss: 0.5292

Epoch 61/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.2798

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9067 - loss: 0.2928 - val_accuracy: 0.8600 - val_loss: 0.5626

Epoch 62/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.3018

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9267 - loss: 0.2785 - val_accuracy: 0.8800 - val_loss: 0.4668

Epoch 63/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.3435

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8983 - loss: 0.3145 - val_accuracy: 0.7267 - val_loss: 0.7593

Epoch 64/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8125 - loss: 0.5474

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8667 - loss: 0.3807 - val_accuracy: 0.8667 - val_loss: 0.4581

Epoch 65/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1996

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9233 - loss: 0.2805 - val_accuracy: 0.8467 - val_loss: 0.5061

Epoch 66/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.2334

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9333 - loss: 0.2585 - val_accuracy: 0.8200 - val_loss: 0.5355

Epoch 67/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.3327

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9033 - loss: 0.2921 - val_accuracy: 0.8400 - val_loss: 0.5349

Epoch 68/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.2718

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8917 - loss: 0.3023 - val_accuracy: 0.8400 - val_loss: 0.5092

Epoch 69/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.2532

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8983 - loss: 0.2865 - val_accuracy: 0.8667 - val_loss: 0.4915

Epoch 70/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.2804

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8967 - loss: 0.2895 - val_accuracy: 0.8533 - val_loss: 0.5395

Epoch 71/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.2707

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9250 - loss: 0.2605 - val_accuracy: 0.8533 - val_loss: 0.4586

Epoch 72/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1523

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9050 - loss: 0.2544 - val_accuracy: 0.8533 - val_loss: 0.5036

Epoch 73/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1904

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9100 - loss: 0.2713 - val_accuracy: 0.8467 - val_loss: 0.5249

Epoch 74/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8438 - loss: 0.3092

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9017 - loss: 0.2682 - val_accuracy: 0.8400 - val_loss: 0.4751

Epoch 75/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.3079

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9017 - loss: 0.2504 - val_accuracy: 0.8467 - val_loss: 0.5227

Epoch 76/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.9375 - loss: 0.2056

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.9333 - loss: 0.2230 - val_accuracy: 0.8467 - val_loss: 0.4605

Epoch 77/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1889

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9317 - loss: 0.2161 - val_accuracy: 0.8600 - val_loss: 0.4976

Epoch 78/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9062 - loss: 0.2442

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9033 - loss: 0.2549 - val_accuracy: 0.8400 - val_loss: 0.5068

Epoch 79/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1626

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9217 - loss: 0.2227 - val_accuracy: 0.8600 - val_loss: 0.4657

Epoch 80/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1522

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9200 - loss: 0.2142 - val_accuracy: 0.8467 - val_loss: 0.5299

Epoch 81/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.2431

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9150 - loss: 0.2364 - val_accuracy: 0.8600 - val_loss: 0.4426

Epoch 82/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.2030

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9233 - loss: 0.2396 - val_accuracy: 0.8267 - val_loss: 0.5379

Epoch 83/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.3066

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9267 - loss: 0.2215 - val_accuracy: 0.8733 - val_loss: 0.4713

Epoch 84/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1521

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9300 - loss: 0.2174 - val_accuracy: 0.8333 - val_loss: 0.5103

Epoch 85/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.3088

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8967 - loss: 0.2727 - val_accuracy: 0.8667 - val_loss: 0.4476

Epoch 86/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1561

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9150 - loss: 0.2293 - val_accuracy: 0.8800 - val_loss: 0.4429

Epoch 87/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1350

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9417 - loss: 0.1997 - val_accuracy: 0.8467 - val_loss: 0.4667

Epoch 88/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1151

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9250 - loss: 0.2145 - val_accuracy: 0.8533 - val_loss: 0.4496

Epoch 89/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1424

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9317 - loss: 0.2084 - val_accuracy: 0.8333 - val_loss: 0.6013

Epoch 90/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.2522

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9083 - loss: 0.2316 - val_accuracy: 0.8533 - val_loss: 0.5163

Epoch 91/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1422

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.8733 - loss: 0.3194 - val_accuracy: 0.7867 - val_loss: 0.5978

Epoch 92/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.2221

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9017 - loss: 0.2568 - val_accuracy: 0.8267 - val_loss: 0.4979

Epoch 93/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.4101

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9000 - loss: 0.2290 - val_accuracy: 0.8533 - val_loss: 0.4525

Epoch 94/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.2595

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9000 - loss: 0.2365 - val_accuracy: 0.8533 - val_loss: 0.4242

Epoch 95/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.2052

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9217 - loss: 0.2164 - val_accuracy: 0.8600 - val_loss: 0.4773

Epoch 96/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1605

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9333 - loss: 0.1982 - val_accuracy: 0.8533 - val_loss: 0.4639

Epoch 97/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1916

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9417 - loss: 0.1817 - val_accuracy: 0.8733 - val_loss: 0.4539

Epoch 98/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1661

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9267 - loss: 0.1905 - val_accuracy: 0.8667 - val_loss: 0.4825

Epoch 99/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9062 - loss: 0.1911

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9417 - loss: 0.1855 - val_accuracy: 0.8533 - val_loss: 0.4996

Epoch 100/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1741

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9367 - loss: 0.1926 - val_accuracy: 0.8600 - val_loss: 0.5019

Epoch 101/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1466

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9267 - loss: 0.2065 - val_accuracy: 0.8467 - val_loss: 0.5506

Epoch 102/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8750 - loss: 0.3293

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9367 - loss: 0.1906 - val_accuracy: 0.8467 - val_loss: 0.5371

Epoch 103/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1675

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9383 - loss: 0.1836 - val_accuracy: 0.9000 - val_loss: 0.4135

Epoch 104/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9375 - loss: 0.2439

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9467 - loss: 0.1721 - val_accuracy: 0.8800 - val_loss: 0.4539

Epoch 105/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1346

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9367 - loss: 0.1828 - val_accuracy: 0.8400 - val_loss: 0.5049

Epoch 106/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1809

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9283 - loss: 0.1964 - val_accuracy: 0.9000 - val_loss: 0.4389

Epoch 107/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1185

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9300 - loss: 0.1961 - val_accuracy: 0.8133 - val_loss: 0.6296

Epoch 108/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9375 - loss: 0.2075

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9217 - loss: 0.2173 - val_accuracy: 0.8733 - val_loss: 0.3952

Epoch 109/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9375 - loss: 0.1179

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9400 - loss: 0.1700 - val_accuracy: 0.8600 - val_loss: 0.4714

Epoch 110/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.0741

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9433 - loss: 0.1650 - val_accuracy: 0.8933 - val_loss: 0.4232

Epoch 111/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1475

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9433 - loss: 0.1572 - val_accuracy: 0.8867 - val_loss: 0.4396

Epoch 112/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1260

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9433 - loss: 0.1683 - val_accuracy: 0.8667 - val_loss: 0.4340

Epoch 113/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1132

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9400 - loss: 0.1680 - val_accuracy: 0.8600 - val_loss: 0.4911

Epoch 114/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1633

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9283 - loss: 0.1866 - val_accuracy: 0.8467 - val_loss: 0.5319

Epoch 115/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0903

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9333 - loss: 0.1893 - val_accuracy: 0.8467 - val_loss: 0.4797

Epoch 116/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1781

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9367 - loss: 0.1722 - val_accuracy: 0.8667 - val_loss: 0.4681

Epoch 117/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0998

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9483 - loss: 0.1590 - val_accuracy: 0.8800 - val_loss: 0.4732

Epoch 118/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 1.0000 - loss: 0.1196

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9617 - loss: 0.1407 - val_accuracy: 0.8733 - val_loss: 0.4687

Epoch 119/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.2167

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9467 - loss: 0.1648 - val_accuracy: 0.8667 - val_loss: 0.4579

Epoch 120/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.0905

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9333 - loss: 0.1941 - val_accuracy: 0.8533 - val_loss: 0.4393

Epoch 121/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9375 - loss: 0.1525

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9167 - loss: 0.2020 - val_accuracy: 0.8533 - val_loss: 0.5083

Epoch 122/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9375 - loss: 0.2242

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9450 - loss: 0.1498 - val_accuracy: 0.8667 - val_loss: 0.5067

Epoch 123/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1225

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9450 - loss: 0.1515 - val_accuracy: 0.8533 - val_loss: 0.4898

Epoch 124/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1381

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9567 - loss: 0.1490 - val_accuracy: 0.8800 - val_loss: 0.4441

Epoch 125/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1381

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9483 - loss: 0.1514 - val_accuracy: 0.8933 - val_loss: 0.4255

Epoch 126/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1117

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9467 - loss: 0.1397 - val_accuracy: 0.8933 - val_loss: 0.4584

Epoch 127/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1142

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9417 - loss: 0.1426 - val_accuracy: 0.8733 - val_loss: 0.4350

Epoch 128/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1252

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9383 - loss: 0.1651 - val_accuracy: 0.8733 - val_loss: 0.4712

Epoch 129/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 1.0000 - loss: 0.1114

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.9600 - loss: 0.1507 - val_accuracy: 0.8600 - val_loss: 0.4947

Epoch 130/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8438 - loss: 0.2308

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9300 - loss: 0.1748 - val_accuracy: 0.8733 - val_loss: 0.4685

Epoch 131/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1502

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9500 - loss: 0.1469 - val_accuracy: 0.8600 - val_loss: 0.4648

Epoch 132/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1676

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9450 - loss: 0.1530 - val_accuracy: 0.8400 - val_loss: 0.5693

Epoch 133/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.8438 - loss: 0.2883

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9283 - loss: 0.1783 - val_accuracy: 0.8800 - val_loss: 0.4488

Epoch 134/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1905

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9383 - loss: 0.1657 - val_accuracy: 0.8800 - val_loss: 0.4884

Epoch 135/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1362

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9633 - loss: 0.1320 - val_accuracy: 0.9067 - val_loss: 0.4386

Epoch 136/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0934

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9450 - loss: 0.1411 - val_accuracy: 0.8867 - val_loss: 0.4809

Epoch 137/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.0849

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9333 - loss: 0.1650 - val_accuracy: 0.8800 - val_loss: 0.5075

Epoch 138/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1989

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9317 - loss: 0.1634 - val_accuracy: 0.8600 - val_loss: 0.4806

Epoch 139/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0605

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9433 - loss: 0.1589 - val_accuracy: 0.8800 - val_loss: 0.4605

Epoch 140/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.0821

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9400 - loss: 0.1530 - val_accuracy: 0.8600 - val_loss: 0.5356

Epoch 141/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1569

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9200 - loss: 0.2025 - val_accuracy: 0.8933 - val_loss: 0.4506

Epoch 142/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.1272

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9350 - loss: 0.2026 - val_accuracy: 0.8733 - val_loss: 0.5137

Epoch 143/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 1.0000 - loss: 0.1080

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9483 - loss: 0.1609 - val_accuracy: 0.8533 - val_loss: 0.4935

Epoch 144/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0755

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1253 - val_accuracy: 0.9000 - val_loss: 0.4657

Epoch 145/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1698

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9383 - loss: 0.1433 - val_accuracy: 0.9000 - val_loss: 0.4827

Epoch 146/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1460

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9367 - loss: 0.1565 - val_accuracy: 0.8400 - val_loss: 0.5308

Epoch 147/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1161

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9450 - loss: 0.1454 - val_accuracy: 0.9000 - val_loss: 0.4410

Epoch 148/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1203

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9250 - loss: 0.2012 - val_accuracy: 0.8467 - val_loss: 0.5564

Epoch 149/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.0986

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9517 - loss: 0.1306 - val_accuracy: 0.8867 - val_loss: 0.4923

Epoch 150/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1034

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9400 - loss: 0.1738 - val_accuracy: 0.8800 - val_loss: 0.5012

Epoch 151/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.2819

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9400 - loss: 0.1585 - val_accuracy: 0.8467 - val_loss: 0.5256

Epoch 152/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0938

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1417 - val_accuracy: 0.8733 - val_loss: 0.4613

Epoch 153/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.0649

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9517 - loss: 0.1337 - val_accuracy: 0.8800 - val_loss: 0.4986

Epoch 154/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1045

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9517 - loss: 0.1443 - val_accuracy: 0.8667 - val_loss: 0.5049

Epoch 155/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.0803

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9333 - loss: 0.1611 - val_accuracy: 0.8800 - val_loss: 0.5307

Epoch 156/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1305

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9617 - loss: 0.1216 - val_accuracy: 0.8667 - val_loss: 0.4871

Epoch 157/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1954

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9583 - loss: 0.1266 - val_accuracy: 0.8800 - val_loss: 0.4478

Epoch 158/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1154

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9700 - loss: 0.1063 - val_accuracy: 0.8800 - val_loss: 0.4655

Epoch 159/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0807

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9617 - loss: 0.1233 - val_accuracy: 0.8867 - val_loss: 0.4907

Epoch 160/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1425

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9483 - loss: 0.1345 - val_accuracy: 0.8800 - val_loss: 0.4708

Epoch 161/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 1.0000 - loss: 0.1145

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9533 - loss: 0.1228 - val_accuracy: 0.8733 - val_loss: 0.4763

Epoch 162/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1143

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1188 - val_accuracy: 0.8800 - val_loss: 0.4717

Epoch 163/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1005

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9767 - loss: 0.0991 - val_accuracy: 0.8733 - val_loss: 0.4652

Epoch 164/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 1.0000 - loss: 0.0794

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1231 - val_accuracy: 0.9000 - val_loss: 0.4294

Epoch 165/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0879

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9733 - loss: 0.1032 - val_accuracy: 0.8600 - val_loss: 0.4850

Epoch 166/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1603

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9567 - loss: 0.1261 - val_accuracy: 0.8733 - val_loss: 0.4644

Epoch 167/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1682

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1244 - val_accuracy: 0.8533 - val_loss: 0.4968

Epoch 168/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.8750 - loss: 0.2806

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9417 - loss: 0.1369 - val_accuracy: 0.8933 - val_loss: 0.4950

Epoch 169/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1324

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9417 - loss: 0.1458 - val_accuracy: 0.8467 - val_loss: 0.5546

Epoch 170/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 1.0000 - loss: 0.0686

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9617 - loss: 0.1146 - val_accuracy: 0.8933 - val_loss: 0.5045

Epoch 171/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9062 - loss: 0.1303

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1030 - val_accuracy: 0.8933 - val_loss: 0.4884

Epoch 172/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1293

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9583 - loss: 0.1186 - val_accuracy: 0.8600 - val_loss: 0.5266

Epoch 173/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.1418

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9650 - loss: 0.1157 - val_accuracy: 0.8667 - val_loss: 0.5317

Epoch 174/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1143

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9567 - loss: 0.1269 - val_accuracy: 0.8733 - val_loss: 0.4806

Epoch 175/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0496

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1241 - val_accuracy: 0.8600 - val_loss: 0.5221

Epoch 176/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1053

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9500 - loss: 0.1310 - val_accuracy: 0.8533 - val_loss: 0.5370

Epoch 177/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0642

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9500 - loss: 0.1282 - val_accuracy: 0.9000 - val_loss: 0.4583

Epoch 178/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1185

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9500 - loss: 0.1285 - val_accuracy: 0.8400 - val_loss: 0.5501

Epoch 179/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1364

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9467 - loss: 0.1330 - val_accuracy: 0.8733 - val_loss: 0.5023

Epoch 180/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.0832

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9533 - loss: 0.1200 - val_accuracy: 0.8800 - val_loss: 0.5336

Epoch 181/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1003

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9583 - loss: 0.1246 - val_accuracy: 0.8733 - val_loss: 0.5345

Epoch 182/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0467

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9433 - loss: 0.1615 - val_accuracy: 0.8600 - val_loss: 0.5283

Epoch 183/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1961

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9150 - loss: 0.2182 - val_accuracy: 0.8733 - val_loss: 0.5385

Epoch 184/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0927

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9200 - loss: 0.2407 - val_accuracy: 0.8200 - val_loss: 0.7162

Epoch 185/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.3454

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9283 - loss: 0.1870 - val_accuracy: 0.8333 - val_loss: 0.7321

Epoch 186/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0361

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9300 - loss: 0.1744 - val_accuracy: 0.8867 - val_loss: 0.4387

Epoch 187/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0810

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9733 - loss: 0.1043 - val_accuracy: 0.8867 - val_loss: 0.4754

Epoch 188/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1116

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9550 - loss: 0.1256 - val_accuracy: 0.8733 - val_loss: 0.5109

Epoch 189/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1423

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9500 - loss: 0.1357 - val_accuracy: 0.9000 - val_loss: 0.4611

Epoch 190/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9062 - loss: 0.1618

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9733 - loss: 0.0917 - val_accuracy: 0.8933 - val_loss: 0.4567

Epoch 191/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1214

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9617 - loss: 0.1040 - val_accuracy: 0.8933 - val_loss: 0.4647

Epoch 192/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0558

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9633 - loss: 0.1081 - val_accuracy: 0.8800 - val_loss: 0.4807

Epoch 193/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1588

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9683 - loss: 0.0944 - val_accuracy: 0.8867 - val_loss: 0.4742

Epoch 194/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.0593

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9800 - loss: 0.0800 - val_accuracy: 0.8867 - val_loss: 0.4830

Epoch 195/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0532

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9617 - loss: 0.1028 - val_accuracy: 0.8867 - val_loss: 0.4583

Epoch 196/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1218

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9633 - loss: 0.1282 - val_accuracy: 0.8733 - val_loss: 0.5288

Epoch 197/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1233

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9600 - loss: 0.1215 - val_accuracy: 0.8867 - val_loss: 0.5250

Epoch 198/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 1.0000 - loss: 0.0595

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9683 - loss: 0.1071 - val_accuracy: 0.9000 - val_loss: 0.4805

Epoch 199/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9375 - loss: 0.1002

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9717 - loss: 0.0868 - val_accuracy: 0.9000 - val_loss: 0.5176

Epoch 200/200


 1/19 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.9688 - loss: 0.1236

19/19 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9667 - loss: 0.1116 - val_accuracy: 0.8800 - val_loss: 0.4911

In diesem Beispiel ist X die Designmatrix, y der Ergebnisvektor, und das Argument epochs gibt an, wie viele Epochen der Optimierer durchlaufen soll. Keras gibt den Fortschritt beim Optimieren an, es könnte so aussehen

Epoch 1/200
25/25 [==============================] - 0s 2ms/step - loss: 1.5984 - accuracy: 0.2438
Epoch 2/200
25/25 [==============================] - 0s 2ms/step - loss: 1.5709 - accuracy: 0.2763
Epoch 3/200
25/25 [==============================] - 0s 3ms/step - loss: 1.5550 - accuracy: 0.3013

Hierbei kann man die aktuelle Epoche, den Batch (für stochastischen Gradientenabstieg) und die aktuelle Genauigkeit der Trainingsdaten sehen. In diesem Beispiel werden alle Epochen bis zum Abschluss ausgeführt, daher sehen wir 32/32 für Batches, aber normalerweise kann man die Batch-Nummer sehen, die sich während des Trainings fortschreitet. Dieses Beispiel funktioniert sehr schnell, Keras meldet 2 ms pro Schritt (Batch) und die Gesamtzeit für die Epoche ist zu gering, um gemeldet zu werden. Aber eine einzelne Epoche kann bei komplexeren Modellen und mehr Daten viele Minuten dauern.

WarnungAchtung: Batchqualität vs. Modellqualität

Die während des Trainings angezeigte Modellqualität (hier Genauigkeit) ist eine laufende Trainingsmetrik und kann zwischen Batches und Epochen schwanken. Sie gibt nur eine grobe Richtlinie für die tatsächliche Modellgüte und sollte durch eine getrennte Auswertung auf Validierungs- oder Testdaten ergänzt werden.

WarnungAchtung: Modelle fertig trainieren

Keras ermöglicht es Ihnen, Vorhersagen mit einem Modell zu treffen, das nicht angepasst ist (im Gegensatz zu scikit-learn, wo das einen Fehler verursacht). Die Ergebnisse werden bestenfalls mittelmäßig aussehen.

Vorhersagen und Plotten

Wenn die Anpassung abgeschlossen ist, können wir das Modell für Vorhersagen verwenden. Die Vorhersage funktioniert ähnlich wie in sklearn, nur dass die predict-Methode die Wahrscheinlichkeit vorhersagt, nicht die Kategorie (analog zu sklearn’s predict_proba).

phat = model.predict(X_test)
1/8 ━━━━━━━━━━━━━━━━━━━━ 0s 21ms/step

8/8 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step 

In diesem Beispiel handelt es sich um eine Matrix mit 5 Spalten, wobei jede Spalte die Wahrscheinlichkeit darstellt, dass der Datenpunkt zur entsprechenden Kategorie gehört. Beispielsweise könnten Zeilen von phat so aussehen:

phat[:5]
array([[6.7852611e-30, 4.7236767e-11, 3.7823245e-01, 6.1790609e-01,
        3.8614913e-03],
       [2.3876208e-01, 7.5912380e-01, 2.1141015e-03, 3.8631465e-13,
        8.4735198e-23],
       [9.9991900e-01, 8.1076039e-05, 3.2490283e-10, 4.2791960e-22,
        1.5502635e-32],
       [9.9842817e-01, 1.5717631e-03, 2.1607143e-09, 2.3510848e-21,
        2.8858791e-35],
       [9.9999034e-01, 9.6942849e-06, 5.6804259e-11, 1.2430986e-23,
        2.6429934e-34]], dtype=float32)

Wir sehen, dass für jedes Test-Fall eine Wahrscheinlichkeit für jede Kategorie angegeben ist. Als nächstes wollen wir mit np.argmax(phat, axis=-1) die Spalte (Kategorie) finden, mit der höchsten Wahrscheinlichkeit. Es findet einfach die Position der größten Elemente im Array entlang der letzten Achse (axis=-1), d.h. Spalten. Für jede Zeile finden wir also die entsprechende Spaltennummer. Beachten Sie, dass np.argmax die Spalten ab 0 zählt, nicht ab 1:

yhat = np.argmax(phat, axis=-1)
yhat[:5]
array([3, 1, 0, 0, 0])

Endlich können wir die Verwirrungsmatrix mit pd.crosstab berechnen und die Genauigkeit berechnen:

from sklearn.metrics import confusion_matrix

print("confusion matrix:\n", confusion_matrix(y_test, yhat))
print("Accuracy (on test data):", np.mean(y_test == yhat))
confusion matrix:
 [[52  2  0  0  0]
 [ 4 23  6  1  0]
 [ 2  2 59  7  0]
 [ 0  0  3 31  4]
 [ 0  0  0  3 51]]
Accuracy (on test data): 0.864

In diesem Beispiel machen wir Vorhersagen auf Testdaten, aber wir können natürlich auch einen anderen Datensatz für Vorhersagen auswählen. Da der vorhergesagte Wert eine Wahrscheinlichkeitsmatrix mit 5 Spalten sein wird, berechnen wir yhat als die Spaltennummer, die die größte Wahrscheinlichkeit für jede Zeile enthält.

Wie man sehen kann, ist die Verwirrungsmatrix fast ausschließlich auf der Hauptdiagonalen besetzt, und die Genauigkeit ist hoch.

Schließlich müssen wir, wenn wir ein ähnliches Diagramm wie oben erstellen möchten, die DBPlot-Funktion anpassen, um zu berücksichtigen, dass Keras-Modelle nur Wahrscheinlichkeiten vorhersagen.

def DBPlot(m, X, y, nGrid = 300):
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, nGrid), 
                           np.linspace(x2_min, x2_max, nGrid))
    XX = np.column_stack((xx1.ravel(), xx2.ravel()))
    ## predict probability
    phat = m.predict(XX, verbose=0)
    ## find the column that corresponds to the maximum probability
    hatyy = np.argmax(phat, axis=-1).reshape(xx1.shape)
    fig = px.imshow(hatyy, width=600, height=600)
    fig.add_scatter(x=X[:,0]/(x1_max-x1_min)*nGrid+nGrid/2, y=X[:,1]/(x2_max-x2_min)*nGrid+nGrid/2, mode="markers", 
                    marker=dict(color=[mcolors[i] for i in y]), marker_line=dict(width=.3, color="black"))
    fig.update_coloraxes(showscale=False)
    fig.update_layout(showlegend=False)
    fig.show()
DBPlot(model, X_test, y_test)

Noch einmal, die Funktion ist fast identisch mit der sklearn Version, außer der Zeile, die np.argmax(phat, axis=-1) enthält, die die vorhergesagten Wahrscheinlichkeiten in Kategorien umwandelt.

Referenzen

McCulloch, Warren S, und Walter Pitts. 1943. „A logical calculus of the ideas immanent in nervous activity“. The bulletin of mathematical biophysics 5 (4): 115–33.
Rosenblatt, Frank. 1958. „The perceptron: a probabilistic model for information storage and organization in the brain.“ Psychological review 65 (6): 386.