{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b65b6b7d",
   "metadata": {},
   "source": [
    "Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "530e70af",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2026-02-24 16:57:49.034186: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:485] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
      "2026-02-24 16:57:49.047136: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:8454] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
      "2026-02-24 16:57:49.050974: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1452] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n",
      "2026-02-24 16:57:49.062835: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
      "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
      "2026-02-24 16:57:49.832762: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd \n",
    "import numpy as np \n",
    "import matplotlib.pyplot as plt \n",
    "import seaborn as sns \n",
    "import random \n",
    "import joblib \n",
    "from pathlib import Path \n",
    "\n",
    "from sklearn.model_selection import GroupKFold, train_test_split \n",
    "from sklearn.preprocessing import StandardScaler \n",
    "from sklearn.metrics import ( \n",
    "    precision_score, recall_score,\n",
    "    confusion_matrix, roc_curve, auc, \n",
    "    precision_recall_curve, f1_score, \n",
    "    balanced_accuracy_score \n",
    ") \n",
    "\n",
    "import tensorflow as tf \n",
    "from tensorflow.keras import Input, layers, models"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d01127c",
   "metadata": {},
   "source": [
    "Seed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "67aaf56e",
   "metadata": {},
   "outputs": [],
   "source": [
    "SEED = 42 \n",
    "np.random.seed(SEED) \n",
    "tf.random.set_seed(SEED) \n",
    "random.seed(SEED)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "844e250c",
   "metadata": {},
   "source": [
    "Daten laden "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "73a34b69",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_path = Path(r\"~/data-paulusjafahrsimulator-gpu/new_datasets/50s_25Hz_dataset.parquet\") \n",
    "data = pd.read_parquet(path=data_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "325179d3",
   "metadata": {},
   "source": [
    "Daten vorbereiten"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a5ad3126",
   "metadata": {},
   "outputs": [],
   "source": [
    "low_all = data[ \n",
    "    ((data[\"PHASE\"] == \"baseline\") | \n",
    "    ((data[\"STUDY\"] == \"n-back\") & \n",
    "    (data[\"PHASE\"] != \"baseline\") & \n",
    "    (data[\"LEVEL\"].isin([1, 4])))) \n",
    "].copy() \n",
    "\n",
    "high_all = pd.concat([ \n",
    "    data[(data[\"STUDY\"] == \"n-back\") & \n",
    "         (data[\"LEVEL\"].isin([2, 3, 5, 6])) & \n",
    "         (data[\"PHASE\"].isin([\"train\", \"test\"]))], \n",
    "    data[(data[\"STUDY\"] == \"k-drive\") & (data[\"PHASE\"] != \"baseline\")] \n",
    "]).copy() \n",
    "\n",
    "low_all[\"label\"] = 0 \n",
    "high_all[\"label\"] = 1 \n",
    "\n",
    "data = pd.concat([low_all, high_all], ignore_index=True).drop_duplicates()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd843b62",
   "metadata": {},
   "source": [
    "Features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5f10e6ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "au_columns = [col for col in data.columns if \"face\" in col.lower()] \n",
    "\n",
    "eye_columns = [ \n",
    "    'Fix_count_short_66_150','Fix_count_medium_300_500','Fix_count_long_gt_1000', \n",
    "    'Fix_count_100','Fix_mean_duration','Fix_median_duration', \n",
    "    'Sac_count','Sac_mean_amp','Sac_mean_dur','Sac_median_dur', \n",
    "    'Blink_count','Blink_mean_dur','Blink_median_dur', \n",
    "    'Pupil_mean','Pupil_IPA' \n",
    "] \n",
    "\n",
    "# NaNs entfernen \n",
    "data = data.dropna(subset=au_columns + eye_columns + [\"label\"]) \n",
    "\n",
    "# Arrays \n",
    "X_au = data[au_columns].values[..., np.newaxis] \n",
    "X_eye = data[eye_columns].values \n",
    "y = data[\"label\"].values \n",
    "groups = data[\"subjectID\"].values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cabe09af",
   "metadata": {},
   "source": [
    "Train/Test Split"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "52d3b7cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train: 6377  | Test: 1595\n"
     ]
    }
   ],
   "source": [
    "X_au_train, X_au_test, X_eye_train, X_eye_test, y_train, y_test, groups_train, groups_test = train_test_split( \n",
    "    X_au, X_eye, y, \n",
    "    groups, test_size=0.2, \n",
    "    random_state=42, \n",
    "    stratify=y \n",
    ") \n",
    "\n",
    "print(\"Train:\", len(y_train), \" | Test:\", len(y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6dedded5",
   "metadata": {},
   "source": [
    "Hybrid CNN-Modell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "41cc1b30",
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_hybrid_model(n_aus, n_eye, lr=1e-4): \n",
    "    input_au = Input(shape=(n_aus, 1), name=\"au_input\") \n",
    "    x = layers.Conv1D(32, 3, activation=\"relu\")(input_au) \n",
    "    x = layers.BatchNormalization()(x) \n",
    "    x = layers.MaxPooling1D(2)(x) \n",
    "    x = layers.Conv1D(64, 3, activation=\"relu\")(x) \n",
    "    x = layers.BatchNormalization()(x) \n",
    "    x = layers.GlobalAveragePooling1D()(x) \n",
    "\n",
    "    input_eye = Input(shape=(n_eye,), name=\"eye_input\") \n",
    "    e = layers.Dense(32, activation=\"relu\")(input_eye) \n",
    "    e = layers.Dropout(0.3)(e) \n",
    "    e = layers.Dense(16, activation=\"relu\")(e) \n",
    "\n",
    "    fused = layers.concatenate([x, e]) \n",
    "    z = layers.Dense(32, activation=\"relu\")(fused) \n",
    "    z = layers.Dropout(0.4)(z) \n",
    "    output = layers.Dense(1, activation=\"sigmoid\")(z) \n",
    "\n",
    "    model = models.Model(inputs=[input_au, input_eye], outputs=output) \n",
    "    model.compile( \n",
    "        optimizer=tf.keras.optimizers.Adam(learning_rate=lr), \n",
    "        loss=\"binary_crossentropy\", \n",
    "        metrics=[\"accuracy\", tf.keras.metrics.AUC(name=\"auc\")] \n",
    "        ) \n",
    "        \n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cea6d0d0",
   "metadata": {},
   "source": [
    "Cross Validation (nur Trainingsdaten)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9c390b46",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "===== FOLD 1 =====\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
      "I0000 00:00:1771948671.077495  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.117462  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.119081  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.125047  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.126507  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.127885  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.321528  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "I0000 00:00:1771948671.323000  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "2026-02-24 16:57:51.324225: W tensorflow/core/common_runtime/gpu/gpu_bfc_allocator.cc:47] Overriding orig_value setting because the TF_FORCE_GPU_ALLOW_GROWTH environment variable is set. Original config value was 0.\n",
      "I0000 00:00:1771948671.324317  347703 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
      "2026-02-24 16:57:51.325558: I tensorflow/core/common_runtime/gpu/gpu_device.cc:2021] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 35614 MB memory:  -> device: 0, name: NVIDIA A100 80GB PCIe MIG 3g.40gb, pci bus id: 0000:1e:00.0, compute capability: 8.0\n",
      "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
      "I0000 00:00:1771948673.622665  347894 service.cc:146] XLA service 0x7fcbc40111e0 initialized for platform CUDA (this does not guarantee that XLA will be used). Devices:\n",
      "I0000 00:00:1771948673.622720  347894 service.cc:154]   StreamExecutor device (0): NVIDIA A100 80GB PCIe MIG 3g.40gb, Compute Capability 8.0\n",
      "2026-02-24 16:57:53.667316: I tensorflow/compiler/mlir/tensorflow/utils/dump_mlir_util.cc:268] disabling MLIR crash reproducer, set env var `MLIR_CRASH_REPRODUCER_DIRECTORY` to enable.\n",
      "2026-02-24 16:57:53.897373: I external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:531] Loaded cuDNN version 8907\n",
      "I0000 00:00:1771948676.141382  347894 device_compiler.h:188] Compiled cluster using XLA!  This line is logged at most once for the lifetime of the process.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Val Loss=0.0339 | Val Acc=0.9836 | Val AUC=0.9994\n",
      "\u001b[1m41/41\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 7ms/step \n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 21320 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "===== FOLD 2 =====\n",
      "Val Loss=0.1241 | Val Acc=0.9591 | Val AUC=0.9861\n",
      "\u001b[1m40/40\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 7ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 21940 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "===== FOLD 3 =====\n",
      "Val Loss=0.1050 | Val Acc=0.9772 | Val AUC=0.9900\n",
      "\u001b[1m40/40\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 8ms/step \n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 21832 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "===== FOLD 4 =====\n",
      "Val Loss=0.1166 | Val Acc=0.9489 | Val AUC=0.9913\n",
      "\u001b[1m40/40\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 7ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 20792 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "===== FOLD 5 =====\n",
      "Val Loss=0.1385 | Val Acc=0.9571 | Val AUC=0.9910\n",
      "\u001b[1m41/41\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 6ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 21672 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 27868 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "gkf = GroupKFold(n_splits=5) \n",
    "cv_histories = [] \n",
    "cv_results = [] \n",
    "all_conf_matrices = [] \n",
    "\n",
    "for fold, (tr_idx, va_idx) in enumerate(gkf.split(X_au_train, y_train, groups_train)): \n",
    "    print(f\"\\n===== FOLD {fold+1} =====\") \n",
    "    \n",
    "    X_tr_au, X_va_au = X_au_train[tr_idx], X_au_train[va_idx] \n",
    "    X_tr_eye, X_va_eye = X_eye_train[tr_idx], X_eye_train[va_idx] \n",
    "    y_tr, y_va = y_train[tr_idx], y_train[va_idx] \n",
    "    \n",
    "    # Scaler pro Fold \n",
    "    scaler_au = StandardScaler() \n",
    "    scaler_eye = StandardScaler() \n",
    "    \n",
    "    X_tr_au = scaler_au.fit_transform(X_tr_au.reshape(len(X_tr_au), -1)).reshape(X_tr_au.shape) \n",
    "    X_va_au = scaler_au.transform(X_va_au.reshape(len(X_va_au), -1)).reshape(X_va_au.shape) \n",
    "    \n",
    "    X_tr_eye = scaler_eye.fit_transform(X_tr_eye) \n",
    "    X_va_eye = scaler_eye.transform(X_va_eye) \n",
    "    \n",
    "    # Modell \n",
    "    model_cv = build_hybrid_model(len(au_columns), len(eye_columns)) \n",
    "    \n",
    "    callbacks = [ \n",
    "        tf.keras.callbacks.EarlyStopping(monitor=\"val_loss\", patience=10, restore_best_weights=True), \n",
    "        tf.keras.callbacks.ReduceLROnPlateau(monitor=\"val_loss\", factor=0.5, patience=5, min_lr=1e-6) \n",
    "    ] \n",
    "    \n",
    "    history = model_cv.fit( \n",
    "        [X_tr_au, X_tr_eye], y_tr, \n",
    "        validation_data=([X_va_au, X_va_eye], y_va), \n",
    "        epochs=100, \n",
    "        batch_size=16, \n",
    "        verbose=0 \n",
    "    ) \n",
    "    \n",
    "    cv_histories.append(history.history) \n",
    "    \n",
    "    # Evaluation \n",
    "    scores = model_cv.evaluate([X_va_au, X_va_eye], y_va, verbose=0) \n",
    "    cv_results.append(scores) \n",
    "    print(f\"Val Loss={scores[0]:.4f} | Val Acc={scores[1]:.4f} | Val AUC={scores[2]:.4f}\") \n",
    "        \n",
    "    # Konfusionsmatrix pro Fold \n",
    "    y_pred_va = (model_cv.predict([X_va_au, X_va_eye]) > 0.5).astype(int) \n",
    "    cm = confusion_matrix(y_va, y_pred_va) \n",
    "    all_conf_matrices.append(cm) \n",
    "        \n",
    "    plt.figure(figsize=(6,5)) \n",
    "    sns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\", \n",
    "                xticklabels=[\"Pred 0\", \"Pred 1\"], \n",
    "                yticklabels=[\"True 0\", \"True 1\"]) \n",
    "    plt.title(f\"Konfusionsmatrix - Fold {fold+1}\") \n",
    "    plt.show() \n",
    "    \n",
    "# Aggregierte Konfusionsmatrix \n",
    "agg_cm = sum(all_conf_matrices) \n",
    "\n",
    "plt.figure(figsize=(6,5)) \n",
    "sns.heatmap(agg_cm, annot=True, fmt=\"d\", cmap=\"Purples\", \n",
    "            xticklabels=[\"Pred 0\", \"Pred 1\"], \n",
    "            yticklabels=[\"True 0\", \"True 1\"]) \n",
    "plt.title(\"Aggregierte Konfusionsmatrix - alle Folds\") \n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e564308",
   "metadata": {},
   "source": [
    "Speichern des Modells"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9afc926b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Finales Modell gespeichert.\n"
     ]
    }
   ],
   "source": [
    "model_cv.save(\"hybrid_fusion_model_Test.keras\") \n",
    "joblib.dump(scaler_au, \"scaler_au_Test.joblib\") \n",
    "joblib.dump(scaler_eye, \"scaler_eye_Test.joblib\") \n",
    "\n",
    "print(\"Finales Modell gespeichert.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "391af5d5",
   "metadata": {},
   "source": [
    "Test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0bb8c14c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1m50/50\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 8ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 20192 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 32268 characters]",
      "text/plain": [
       "<Figure size 700x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 18748 characters]",
      "text/plain": [
       "<Figure size 700x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "F1-Score: 0.9851222104144527\n",
      "Balanced Accuracy: 0.9809860091542237\n",
      "Precision: 0.9809523809523809\n",
      "Recall: 0.9893276414087513\n",
      "AUC: 0.997147009306686\n"
     ]
    }
   ],
   "source": [
    "# Preprocessing Testdaten \n",
    "X_au_test_scaled = scaler_au.transform( \n",
    "    X_au_test.reshape(len(X_au_test), -1) \n",
    ").reshape(X_au_test.shape) \n",
    "\n",
    "X_eye_test_scaled = scaler_eye.transform(X_eye_test) \n",
    "\n",
    "# Vorhersagen \n",
    "y_prob_test = model_cv.predict([X_au_test_scaled, X_eye_test_scaled]).flatten() \n",
    "y_pred_test = (y_prob_test > 0.5).astype(int) \n",
    "\n",
    "# Konfusionsmatrix \n",
    "cm_test = confusion_matrix(y_test, y_pred_test) \n",
    "\n",
    "plt.figure(figsize=(6,5)) \n",
    "sns.heatmap(cm_test, annot=True, fmt=\"d\", cmap=\"Greens\", \n",
    "            xticklabels=[\"Pred 0\", \"Pred 1\"], \n",
    "            yticklabels=[\"True 0\", \"True 1\"]) \n",
    "plt.title(\"Konfusionsmatrix - Testdaten\") \n",
    "plt.show() \n",
    "\n",
    "# ROC \n",
    "fpr, tpr, _ = roc_curve(y_test, y_prob_test) \n",
    "roc_auc = auc(fpr, tpr) \n",
    "\n",
    "plt.figure(figsize=(7,6)) \n",
    "plt.plot(fpr, tpr, label=f\"AUC = {roc_auc:.3f}\") \n",
    "plt.plot([0,1], [0,1], \"k--\") \n",
    "plt.title(\"ROC - Testdaten\") \n",
    "plt.legend() \n",
    "plt.grid(True) \n",
    "plt.show() \n",
    "\n",
    "# Precision-Recall \n",
    "precision, recall, _ = precision_recall_curve(y_test, y_prob_test) \n",
    "plt.figure(figsize=(7,6)) \n",
    "plt.plot(recall, precision) \n",
    "plt.title(\"Precision-Recall - Testdaten\") \n",
    "plt.grid(True) \n",
    "plt.show() \n",
    "\n",
    "# Metriken \n",
    "print(\"F1-Score:\", f1_score(y_test, y_pred_test)) \n",
    "print(\"Balanced Accuracy:\", balanced_accuracy_score(y_test, y_pred_test)) \n",
    "print(\"Precision:\", precision_score(y_test, y_pred_test)) \n",
    "print(\"Recall:\", recall_score(y_test, y_pred_test)) \n",
    "print(\"AUC:\", roc_auc)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
