Klassifikation von Rot- und Weißwein mit Keras

In diesem Notebook führen wir die vollständige Datenaufbereitung, Modellierung, Visualisierung und Evaluierung eines neuronalen Netzes zur Unterscheidung von Rot- und Weißwein durch.

import pandas as pd
import numpy as np
import keras as K
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
2025-08-15 16:16:39.062033: I external/local_xla/xla/tsl/cuda/cudart_stub.cc:31] Could not find cuda drivers on your machine, GPU will not be used.
2025-08-15 16:16:39.106647: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2025-08-15 16:16:41.176874: I external/local_xla/xla/tsl/cuda/cudart_stub.cc:31] Could not find cuda drivers on your machine, GPU will not be used.

Einlesen der Weindaten

Die CSV-Dateien befinden sich im Unterordner data/processed. Sie enthalten standardisierte chemische Merkmale für Rot- und Weißweine.

white = pd.read_csv('../data/processed/winequality-white.csv', sep=';')
red = pd.read_csv('../data/processed/winequality-red.csv', sep=';')

Labeln und Zusammenführen der Datensätze

Für supervised learning bönitgen wir labels: - Rotwein erhält das Label 1 - Weißwein das Label 0

Danach werden die Daten zusammengeführt.

red['label'] = 1
white['label'] = 0
wines = pd.concat([red, white], ignore_index=True)

Feature-Matrix und Zielvariable erzeugen

Die ersten 11 Spalten (chemische Eigenschaften) dienen als Input-Variablen (Features). Die Zielvariable y ist das binäre Label (1 = Rotwein, 0 = Weißwein), und wird in 1D-Array umgewandelt.

x = wines.iloc[:, 0:11]
y = np.ravel(wines['label'])

Aufteilung in Trainings- und Testdaten

Der Datensatz wird im Verhältnis 70:30 in Trainings- und Testdaten aufgeteilt (70% Trainingsdaten).

x_train, x_test, y_train, y_test = train_test_split(
    x, y, test_size = 0.3, random_state = 42)

Skalierung der Merkmale

Die Daten werden mit dem StandardScaler auf Mittelwert 0 und Standardabweichung 1 standardisiert. Unterschiedliche Skalen der Inputvariablen (z. B. pH vs. Alkohol) können das Training stören. Der Fit erfolgt nur auf dem Trainingsset, aber: Testdaten müssen auch transformiert werden.

scaler = StandardScaler().fit(x_train)
x_train = scaler.transform(x_train)
x_test = scaler.transform(x_test)

Klassifikation von Weinen (rot vs. weiß) als binäres Klassifikationsproblem

Ziel: Vorhersage d. binären Labels (0 = rot, 1 = weiß) anhand chemischer Eigenschaften

Das neuronale Netz wird an diese Problemstruktur angepasst: - ein einzelnes Ausgabeneuron mit Sigmoid-Aktivierung (für binäre Klassifikation) - Eingabedimension entspricht der Anzahl der Input-Features (hier: 11)

Aufbau des neuronalen Netzes

Wir definieren ein sequentielles Keras-Modell, , in dem Layer nacheinander hinzugefügt werden. Hyperparameter wie Anzahl der Hidden-Layer, Neuronenanzahl, Aktivierungsfunktionen etc. sind frei wählbar. Die Sturktur ist wie folgt: - Eingabeschicht: 12 Neuronen, ReLU, input_dim=11 - Hidden Layer: 8 Neuronen, ReLU - Ausgabeschicht: 1 Neuron, Sigmoid (für binäre Klassifikation)

import keras as K  # oder: from tensorflow import keras as K

model = K.Sequential([
    K.layers.Input(shape=(11,)),          # <<— Input-Layer
    K.layers.Dense(12, activation='relu'),
    K.layers.Dense(8,  activation='relu'),
    K.layers.Dense(1,  activation='sigmoid')
])
# So wurde es SB 03 gemacht, aber veraltet ...
# model = K.models.Sequential()
# model.add(K.layers.Dense(units=12, activation='relu', input_dim=11))
# model.add(K.layers.Dense(units=8, activation='relu'))
# model.add(K.layers.Dense(units=1, activation='sigmoid'))
2025-08-15 16:16:41.820203: E external/local_xla/xla/stream_executor/cuda/cuda_platform.cc:51] failed call to cuInit: INTERNAL: CUDA error: Failed call to cuInit: UNKNOWN ERROR (303)

Weitere Hintergründe

  1. Erstellen eines sequenziellen Keras-Modells
  2. Erste Schicht (Input-Layer + erste Dense-Schicht)
    • 12 Neuronen als Startwert (guter Richtwert, entspricht etwa der Feature-Anzahl)
    • Aktivierungsfunktion: ReLU (Standard bei Hidden-Layern)
    • input_dim: 11, da 11 Input-Features (chemische Eigenschaften)
  3. Zweite Schicht (Hidden-Layer)
    • 8 Neuronen (leichte Reduktion gegenüber erster Schicht)
    • erneut ReLU-Aktivierung
  4. Ausgabeschicht für binäre Klassifikation
    • 1 Neuron (0 = rot, 1 = weiß)
    • Aktivierungsfunktion: Sigmoid (liefert Werte zwischen 0 und 1 geeignet für binäre Klassen)
  5. Für mehrklassige Klassifikation würde man stattdessen:
    • mehrere Ausgabeneuronen (entsprechend der Klassenzahl)
    • und eine ‘softmax’-Aktivierung verwenden

Kompilierung des Modells

Festlegen von Optimierer, Verlustfunktion und Metriken

  • Optimierer: Adam (state-of-the-art für viele Probleme, adaptiv)
  • Verlustfunktion: binary_crossentropy (geeignet für binäre Klassifikation)
  • Metrik: accuracy (Anteil korrekt klassifizierter Beispiele pro Epoche)
model.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

Modelltraining

Wir trainieren das Modell 20 Epochen lang mit validation_split=0.3.

hist = model.fit(
    x_train,
    y_train,
    epochs=20,
    validation_split=0.3,
    verbose=2
)
Epoch 1/20
100/100 - 1s - 11ms/step - accuracy: 0.7206 - loss: 0.6057 - val_accuracy: 0.9223 - val_loss: 0.4833
Epoch 2/20
100/100 - 0s - 2ms/step - accuracy: 0.9503 - loss: 0.3391 - val_accuracy: 0.9626 - val_loss: 0.2183
Epoch 3/20
100/100 - 0s - 2ms/step - accuracy: 0.9714 - loss: 0.1415 - val_accuracy: 0.9780 - val_loss: 0.1041
Epoch 4/20
100/100 - 0s - 2ms/step - accuracy: 0.9830 - loss: 0.0769 - val_accuracy: 0.9868 - val_loss: 0.0676
Epoch 5/20
100/100 - 0s - 2ms/step - accuracy: 0.9884 - loss: 0.0539 - val_accuracy: 0.9905 - val_loss: 0.0516
Epoch 6/20
100/100 - 0s - 2ms/step - accuracy: 0.9909 - loss: 0.0432 - val_accuracy: 0.9927 - val_loss: 0.0431
Epoch 7/20
100/100 - 0s - 2ms/step - accuracy: 0.9925 - loss: 0.0374 - val_accuracy: 0.9919 - val_loss: 0.0392
Epoch 8/20
100/100 - 0s - 2ms/step - accuracy: 0.9928 - loss: 0.0334 - val_accuracy: 0.9919 - val_loss: 0.0366
Epoch 9/20
100/100 - 0s - 2ms/step - accuracy: 0.9934 - loss: 0.0309 - val_accuracy: 0.9927 - val_loss: 0.0352
Epoch 10/20
100/100 - 0s - 2ms/step - accuracy: 0.9934 - loss: 0.0289 - val_accuracy: 0.9927 - val_loss: 0.0346
Epoch 11/20
100/100 - 0s - 2ms/step - accuracy: 0.9934 - loss: 0.0271 - val_accuracy: 0.9934 - val_loss: 0.0327
Epoch 12/20
100/100 - 0s - 2ms/step - accuracy: 0.9940 - loss: 0.0257 - val_accuracy: 0.9941 - val_loss: 0.0325
Epoch 13/20
100/100 - 0s - 2ms/step - accuracy: 0.9947 - loss: 0.0245 - val_accuracy: 0.9949 - val_loss: 0.0324
Epoch 14/20
100/100 - 0s - 2ms/step - accuracy: 0.9953 - loss: 0.0235 - val_accuracy: 0.9934 - val_loss: 0.0321
Epoch 15/20
100/100 - 0s - 2ms/step - accuracy: 0.9950 - loss: 0.0226 - val_accuracy: 0.9934 - val_loss: 0.0319
Epoch 16/20
100/100 - 0s - 2ms/step - accuracy: 0.9953 - loss: 0.0221 - val_accuracy: 0.9941 - val_loss: 0.0317
Epoch 17/20
100/100 - 0s - 2ms/step - accuracy: 0.9953 - loss: 0.0212 - val_accuracy: 0.9934 - val_loss: 0.0329
Epoch 18/20
100/100 - 0s - 2ms/step - accuracy: 0.9956 - loss: 0.0207 - val_accuracy: 0.9941 - val_loss: 0.0326
Epoch 19/20
100/100 - 0s - 2ms/step - accuracy: 0.9953 - loss: 0.0202 - val_accuracy: 0.9941 - val_loss: 0.0329
Epoch 20/20
100/100 - 0s - 2ms/step - accuracy: 0.9959 - loss: 0.0196 - val_accuracy: 0.9941 - val_loss: 0.0324
  • x_train, y_train sind die vorbereiteten Trainingsdaten
  • epochs = 20: 20 Trainingsdurchläufe (kann bei Bedarf erhöht werden)
  • validation_split = 0.3: 30% der Trainingsdaten werden zur Validierung abgezweigt
  • Achtung: dies betrifft nur x_train/y_train, nicht die vorab separat gehaltenen Testdaten!

Verlauf der Genauigkeit während des Trainings

Die Trainings- und Validierungsgenauigkeit werden für jede Epoche geplottet.

plt.plot(hist.history['accuracy'], label='Train Accuracy')
plt.plot(hist.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epoche')
plt.ylabel('Genauigkeit')
plt.legend()
plt.tight_layout()
plt.show()
plt.savefig('../figs/training_accuracy.png')

<Figure size 672x480 with 0 Axes>

Evaluation auf Testdaten

Nach dem Training evaluieren wir das Modell auf der echten Testmenge und geben die Loss- und Accuracy-Werte aus.

test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Testverlust: {test_loss:.4f}")
print(f"Testgenauigkeit: {test_acc:.4f}")
 1/61 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 1.0000 - loss: 0.0320

59/61 ━━━━━━━━━━━━━━━━━━━ 0s 870us/step - accuracy: 0.9908 - loss: 0.0359

61/61 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.9913 - loss: 0.0400  

Testverlust: 0.0400

Testgenauigkeit: 0.9913

Hinweis: - Die Validierung während des Trainings basiert auf validation_split - Die finale Testgenauigkeit stammt aus einer separaten, vorher unberührten Testmenge - Dadurch erhalten wir eine realistische Einschätzung der Generalisierungsfähigkeit

Back to top