{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4ac35e21",
   "metadata": {},
   "source": [
    "### Import"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87513def",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from pathlib import Path\n",
    "import sys\n",
    "import os\n",
    "\n",
    "base_dir = os.path.abspath(os.path.join(os.getcwd(), \"..\"))\n",
    "sys.path.append(base_dir)\n",
    "print(base_dir)\n",
    "\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.svm import OneClassSVM\n",
    "from sklearn.model_selection import GridSearchCV, KFold, ParameterGrid, train_test_split\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf\n",
    "import pickle\n",
    "from tools import evaluation_tools, scaler\n",
    "from sklearn.metrics import (balanced_accuracy_score, accuracy_score, precision_score, \n",
    "                                recall_score, f1_score, confusion_matrix, classification_report)  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83dc3a63",
   "metadata": {},
   "source": [
    "### Load data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0eef0bc8",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_path = Path(r\"/home/jovyan/data-paulusjafahrsimulator-gpu/first_AU_dataset/output_windowed.parquet\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "597880bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_parquet(path=data_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa0f0fec",
   "metadata": {},
   "outputs": [],
   "source": [
    "subjects = df['subjectID'].unique()\n",
    "print(subjects)\n",
    "print(len(subjects))\n",
    "print(len(subjects)*0.66)\n",
    "print(len(subjects)*0.33)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e80506dc",
   "metadata": {},
   "source": [
    "### Preprocessing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "897a2342",
   "metadata": {},
   "outputs": [],
   "source": [
    "low_all = df[\n",
    "    ((df[\"PHASE\"] == \"baseline\") |\n",
    "     ((df[\"STUDY\"] == \"n-back\") & (df[\"PHASE\"] != \"baseline\") & (df[\"LEVEL\"].isin([1,4]))))\n",
    "]\n",
    "print(f\"low all: {low_all.shape}\")\n",
    "\n",
    "high_nback = df[\n",
    "    (df[\"STUDY\"]==\"n-back\") &\n",
    "    (df[\"LEVEL\"].isin([2, 3, 5, 6])) &\n",
    "    (df[\"PHASE\"].isin([\"train\", \"test\"]))\n",
    "]\n",
    "print(f\"high n-back: {high_nback.shape}\")\n",
    "high_kdrive = df[\n",
    "    (df[\"STUDY\"] == \"k-drive\") & (df[\"PHASE\"] != \"baseline\")\n",
    "]\n",
    "print(f\"high k-drive: {high_kdrive.shape}\")\n",
    "\n",
    "high_all = pd.concat([high_nback, high_kdrive])\n",
    "print(f\"high all: {high_all.shape}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f6f59455",
   "metadata": {},
   "outputs": [],
   "source": [
    "# First split: separate test set\n",
    "train_val_subjects, test_subjects = train_test_split(\n",
    "    subjects, \n",
    "    train_size=14, \n",
    "    test_size=4, \n",
    "    random_state=42\n",
    ")\n",
    "\n",
    "# Second split: separate train and validation from the remaining subjects\n",
    "# Adjust these numbers based on your total subject count\n",
    "train_subjects, val_subjects = train_test_split(\n",
    "    train_val_subjects,\n",
    "    train_size=8,\n",
    "    test_size=6,\n",
    "    random_state=42\n",
    ")\n",
    "\n",
    "print(f\"Train subjects: {len(train_subjects)}\")\n",
    "print(f\"Validation subjects: {len(val_subjects)}\")\n",
    "print(f\"Test subjects: {len(test_subjects)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f215beb5",
   "metadata": {},
   "outputs": [],
   "source": [
    "au_columns = [col for col in low_all.columns if col.startswith('AU')]\n",
    "\n",
    "# Prepare training data (only normal/low data)\n",
    "train_data = low_all[low_all['subjectID'].isin(train_subjects)][['subjectID'] + au_columns].copy()\n",
    "\n",
    "# Prepare validation data (normal and anomaly) \n",
    "val_normal_data = low_all[low_all['subjectID'].isin(val_subjects)][['subjectID'] + au_columns].copy()\n",
    "val_high_data = high_all[high_all['subjectID'].isin(val_subjects)][['subjectID'] + au_columns].copy()\n",
    "val_normal_data = val_normal_data.sample(n=1000, random_state=42)\n",
    "val_high_data = val_high_data.sample(n=1000, random_state=42)\n",
    "\n",
    "# Prepare test data (normal and anomaly) - 1000 samples each\n",
    "test_normal_data = low_all[low_all['subjectID'].isin(test_subjects)][['subjectID'] + au_columns].copy()\n",
    "test_high_data = high_all[high_all['subjectID'].isin(test_subjects)][['subjectID'] + au_columns].copy()\n",
    "test_normal_data = test_normal_data.sample(n=500, random_state=42)\n",
    "test_high_data = test_high_data.sample(n=500, random_state=42)\n",
    "\n",
    "print(f\"Train samples: {len(train_data)}\")\n",
    "print(f\"Val normal samples: {len(val_normal_data)}, Val high samples: {len(val_high_data)}\")\n",
    "print(f\"Test normal samples: {len(test_normal_data)}, Test high samples: {len(test_high_data)}\")\n",
    "\n",
    "# Track subject split\n",
    "print(f\"\\nSubject split:\")\n",
    "print(f\"Train subjects ({len(train_subjects)}): {sorted(train_subjects)}\")\n",
    "print(f\"Val subjects ({len(val_subjects)}): {sorted(val_subjects)}\")\n",
    "print(f\"Test subjects ({len(test_subjects)}): {sorted(test_subjects)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74e363aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Cell 3: Fit normalizer on training data\n",
    "normalizer = scaler.fit_normalizer(train_data, au_columns, method='minmax', scope='global')\n",
    "print(\"Normalizer fitted on training data\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5dd6c4b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Cell 4: Apply normalization to all datasets\n",
    "train_normalized = scaler.apply_normalizer(train_data, au_columns, normalizer)\n",
    "val_normal_normalized = scaler.apply_normalizer(val_normal_data, au_columns, normalizer)\n",
    "val_high_normalized = scaler.apply_normalizer(val_high_data, au_columns, normalizer)\n",
    "test_normal_normalized = scaler.apply_normalizer(test_normal_data, au_columns, normalizer)\n",
    "test_high_normalized = scaler.apply_normalizer(test_high_data, au_columns, normalizer)\n",
    "\n",
    "print(\"Normalization applied to all datasets\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "624a5374",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Cell 5: Extract AU columns and create labels for grid search\n",
    "# Extract only AU columns (drop subjectID)\n",
    "X_train = train_normalized[au_columns].copy()\n",
    "X_val_normal = val_normal_normalized[au_columns].copy()\n",
    "X_val_high = val_high_normalized[au_columns].copy()\n",
    "X_test_high = test_high_normalized[au_columns].copy()\n",
    "X_test_normal = test_normal_normalized[au_columns].copy()\n",
    "\n",
    "\n",
    "# Create labels for grid search\n",
    "y_train = np.ones(len(X_train))  # 1 for normal (training)\n",
    "y_val_normal = np.ones(len(X_val_normal))  # 1 for normal (validation)\n",
    "y_val_high = -np.ones(len(X_val_high))  # -1 for anomalies (validation)\n",
    "y_test_normal = np.ones(len(X_test_normal))\n",
    "y_test_high = -np.ones(len(X_test_high))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bc3f4d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50fc80dc-fe16-4917-aad6-0dbaa1ce5ef9",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install keras-tuner --quiet  # nur einmal nötig\n",
    "\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "from kerastuner import HyperModel\n",
    "from kerastuner.tuners import RandomSearch\n",
    "\n",
    "# 1️⃣ HyperModel definieren\n",
    "class AutoencoderHyperModel(HyperModel):\n",
    "    def __init__(self, input_dim):\n",
    "        self.input_dim = input_dim\n",
    "\n",
    "    def build(self, hp):\n",
    "        reg = hp.Float(\"l2_reg\", min_value=1e-5, max_value=0.01, sampling=\"log\")\n",
    "        lr = hp.Float(\"learning_rate\", 1e-4, 1e-2, sampling=\"log\")\n",
    "\n",
    "        # Encoder\n",
    "        encoder = keras.Sequential([\n",
    "            keras.layers.Dense(\n",
    "                units=hp.Int(\"enc_units1\", min_value=10, max_value=self.input_dim, step=10),\n",
    "                activation=None,\n",
    "                kernel_regularizer=keras.regularizers.l2(reg)\n",
    "            ),\n",
    "            keras.layers.LeakyReLU(alpha=0.1),\n",
    "            keras.layers.Dense(\n",
    "                units=hp.Int(\"enc_units2\", min_value=5, max_value=20, step=1),\n",
    "                activation=tf.keras.layers.LeakyReLU(alpha=0.1),\n",
    "                kernel_regularizer=keras.regularizers.l2(reg)\n",
    "            ),\n",
    "            keras.layers.Dense(\n",
    "                units=2,  # Bottleneck\n",
    "                activation='linear',\n",
    "                kernel_regularizer=keras.regularizers.l2(reg)\n",
    "            ),\n",
    "        ])\n",
    "\n",
    "        # Decoder\n",
    "        decoder = keras.Sequential([\n",
    "            keras.layers.Dense(\n",
    "                units=hp.Int(\"dec_units1\", min_value=5, max_value=20, step=1),\n",
    "                activation=tf.keras.layers.LeakyReLU(alpha=0.1),\n",
    "                kernel_regularizer=keras.regularizers.l2(reg)\n",
    "            ),\n",
    "            keras.layers.Dense(\n",
    "                units=hp.Int(\"dec_units2\", min_value=10, max_value=self.input_dim, step=10),\n",
    "                activation=tf.keras.layers.LeakyReLU(alpha=0.1),\n",
    "                kernel_regularizer=keras.regularizers.l2(reg)\n",
    "            ),\n",
    "            keras.layers.Dense(\n",
    "                units=self.input_dim,\n",
    "                activation='linear',\n",
    "                kernel_regularizer=keras.regularizers.l2(reg)\n",
    "            ),\n",
    "        ])\n",
    "\n",
    "        # Autoencoder\n",
    "        inputs = keras.Input(shape=(self.input_dim,))\n",
    "        encoded = encoder(inputs)\n",
    "        decoded = decoder(encoded)\n",
    "        autoencoder = keras.Model(inputs, decoded)\n",
    "\n",
    "        autoencoder.compile(\n",
    "            optimizer=keras.optimizers.Adam(learning_rate=lr),\n",
    "            loss='mse'\n",
    "        )\n",
    "\n",
    "        return autoencoder\n",
    "\n",
    "# 2️⃣ RandomSearch-Tuner\n",
    "hypermodel = AutoencoderHyperModel(input_dim=X_train.shape[1])\n",
    "\n",
    "tuner = RandomSearch(\n",
    "    hypermodel,\n",
    "    objective='val_loss',\n",
    "    max_trials=10,            # Anzahl der getesteten Kombinationen\n",
    "    executions_per_trial=1,   # Anzahl Trainings pro Kombination\n",
    "    directory='tuner_dir',\n",
    "    project_name='oc_ae'\n",
    ")\n",
    "\n",
    "# 3️⃣ Hyperparameter-Tuning starten\n",
    "tuner.search(\n",
    "    X_train, X_train,\n",
    "    epochs=100,\n",
    "    batch_size=64,\n",
    "    validation_data=(X_val_normal, X_val_normal),\n",
    "    verbose=0\n",
    ")\n",
    "\n",
    "# 4️⃣ Beste Architektur holen\n",
    "best_model = tuner.get_best_models(num_models=1)[0]\n",
    "best_hyperparameters = tuner.get_best_hyperparameters(1)[0]\n",
    "\n",
    "print(\"Beste Hyperparameter:\", best_hyperparameters.values)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "362c0a6f",
   "metadata": {},
   "source": [
    "\n",
    "Beste Hyperparameter: {'l2_reg': 1.3757411430582133e-05, 'learning_rate': 0.007321002854350309, 'enc_units1': 20, 'enc_units2': 16, 'dec_units1': 14, 'dec_units2': 10}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da6eab4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# reg = 0.1\n",
    "# encoder = tf.keras.Sequential(\n",
    "#     [\n",
    "#         tf.keras.layers.Dense(units=X_train.shape[1], activation='relu', kernel_regularizer=tf.keras.regularizers.l2(reg)),\n",
    "#         tf.keras.layers.Dense(units=10, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(reg)),\n",
    "#         tf.keras.layers.Dense(units=5, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(reg)),\n",
    "        \n",
    "#     ]\n",
    "# )\n",
    "\n",
    "# decoder = tf.keras.Sequential(\n",
    "#     [\n",
    "#         tf.keras.layers.Dense(units=5,activation='relu', kernel_regularizer=tf.keras.regularizers.l2(reg)),\n",
    "#         tf.keras.layers.Dense(units=10, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(reg)),\n",
    "#         tf.keras.layers.Dense(units=X_train.shape[1], activation='linear', kernel_regularizer=tf.keras.regularizers.l2(reg))\n",
    "#     ]\n",
    "# )\n",
    "\n",
    "\n",
    "\n",
    "reg = 1e-5\n",
    "\n",
    "# ENCODER\n",
    "encoder = tf.keras.Sequential([\n",
    "    tf.keras.layers.Dense(\n",
    "        units=X_train.shape[1],\n",
    "        activation=None,\n",
    "        kernel_regularizer=tf.keras.regularizers.l2(reg)\n",
    "    ),\n",
    "    tf.keras.layers.LeakyReLU(alpha=0.1),\n",
    "    \n",
    "    tf.keras.layers.Dense(\n",
    "        units=12,\n",
    "        activation=tf.keras.layers.LeakyReLU(negative_slope=0.1),\n",
    "        kernel_regularizer=tf.keras.regularizers.l2(reg)\n",
    "    ),\n",
    "       \n",
    "    tf.keras.layers.Dense(\n",
    "        units=8,\n",
    "        activation='linear',  # Bottleneck stays linear\n",
    "        kernel_regularizer=tf.keras.regularizers.l2(reg)\n",
    "    ),\n",
    "])\n",
    "\n",
    "# DECODER\n",
    "decoder = tf.keras.Sequential([\n",
    "    tf.keras.layers.Dense(\n",
    "        units=8,\n",
    "        activation=tf.keras.layers.LeakyReLU(negative_slope=0.1),\n",
    "        kernel_regularizer=tf.keras.regularizers.l2(reg)\n",
    "    ),\n",
    "    tf.keras.layers.Dense(\n",
    "        units=12,\n",
    "        activation=tf.keras.layers.LeakyReLU(negative_slope=0.1),\n",
    "        kernel_regularizer=tf.keras.regularizers.l2(reg)\n",
    "    ),\n",
    "    tf.keras.layers.Dense(\n",
    "        units=X_train.shape[1],\n",
    "        activation='linear',\n",
    "        kernel_regularizer=tf.keras.regularizers.l2(reg)\n",
    "    ),\n",
    "])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d60c566a",
   "metadata": {},
   "outputs": [],
   "source": [
    "autoencoder = tf.keras.Sequential([encoder, decoder])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1bb4321d",
   "metadata": {},
   "outputs": [],
   "source": [
    "autoencoder.compile(\n",
    "    optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n",
    "    loss='mse'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eff138ee-b7b4-41f2-b97e-5dfe573a0e42",
   "metadata": {},
   "outputs": [],
   "source": [
    "history = autoencoder.fit(\n",
    "    X_train, X_train,  # Input and target are the same for autoencoder\n",
    "    epochs=200,\n",
    "    batch_size=64,\n",
    "    validation_data=(X_val_normal, X_val_normal),\n",
    "    verbose=1\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "01f5691b-d12c-416b-bf60-f351f8c1cd83",
   "metadata": {},
   "outputs": [],
   "source": [
    "save_path = Path(\"/home/jovyan/data-paulusjafahrsimulator-gpu/saved_models/encoder_model_2_neurons_minmax.keras\")\n",
    "encoder.save(save_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a74bba5f-c147-43e9-99bd-92a4d63aa624",
   "metadata": {},
   "source": [
    "## Load of encoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "096b858c",
   "metadata": {},
   "outputs": [],
   "source": [
    "load_path = Path(\"/home/jovyan/data-paulusjafahrsimulator-gpu/saved_models/encoder_model_2_neurons_minmax.keras\")\n",
    "encoder = tf.keras.models.load_model(load_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1da405de",
   "metadata": {},
   "source": [
    "## OCSVM Training with Encoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd70f560",
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_encoded = encoder.predict(X_train)\n",
    "X_val_normal_encoded = encoder.predict(X_val_normal)\n",
    "X_val_high_encoded = encoder.predict(X_val_high)\n",
    "X_test_normal_encoded = encoder.predict(X_test_normal)\n",
    "X_test_high_encoded = encoder.predict(X_test_high)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc036af7-5fb8-45aa-9420-07d2b772579e",
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_encoded[-20:, :5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a292d06",
   "metadata": {},
   "outputs": [],
   "source": [
    "ocsvm = OneClassSVM(kernel='rbf', gamma='auto', nu=0.2)\n",
    "ocsvm.fit(X_train_encoded)\n",
    "\n",
    "# Predict on validation/test sets\n",
    "val_predictions = ocsvm.predict(np.vstack([X_val_normal_encoded, X_val_high_encoded]))\n",
    "test_predictions = ocsvm.predict(np.vstack([X_test_normal_encoded, X_test_high_encoded]))\n",
    "test_predictions.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "759118d8-989d-489c-9d35-331454b4795e",
   "metadata": {},
   "outputs": [],
   "source": [
    "all_zero = {\n",
    "    \"X_train_encoded\":        np.all(X_train_encoded == 0),\n",
    "    \"X_val_normal_encoded\":   np.all(X_val_normal_encoded == 0),\n",
    "    \"X_val_high_encoded\":     np.all(X_val_high_encoded == 0),\n",
    "    \"X_test_normal_encoded\":  np.all(X_test_normal_encoded == 0),\n",
    "    \"X_test_high_encoded\":    np.all(X_test_high_encoded == 0),\n",
    "}\n",
    "\n",
    "print(all_zero)\n",
    "print(X_train_encoded.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c0ad242-5db5-4346-a718-278c87426322",
   "metadata": {},
   "outputs": [],
   "source": [
    "# fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
    "\n",
    "# # Subplot A: Normal\n",
    "# axes[0].scatter(\n",
    "#     X_val_normal_encoded[:, 0],\n",
    "#     X_val_normal_encoded[:, 1],\n",
    "#     color=\"blue\",\n",
    "#     label=\"Normal\"\n",
    "# )\n",
    "# axes[0].set_title(\"Val Normal (encoded)\")\n",
    "# axes[0].set_xlabel(\"latent feature 1\")\n",
    "# axes[0].set_ylabel(\"latent feature 2\")\n",
    "# axes[0].legend()\n",
    "\n",
    "# # Subplot B: High\n",
    "# axes[1].scatter(\n",
    "#     X_val_high_encoded[:, 0],\n",
    "#     X_val_high_encoded[:, 1],\n",
    "#     color=\"orange\",\n",
    "#     label=\"High\"\n",
    "# )\n",
    "# axes[1].set_title(\"ValHigh (encoded)\")\n",
    "# axes[1].set_xlabel(\"latent feature 1\")\n",
    "# axes[1].set_ylabel(\"latent feature 2\")\n",
    "# axes[1].legend()\n",
    "\n",
    "# # Subplot C: Both\n",
    "# axes[2].scatter(\n",
    "#     X_val_normal_encoded[:, 0],\n",
    "#     X_val_normal_encoded[:, 1],\n",
    "#     color=\"blue\",\n",
    "#     label=\"Normal\"\n",
    "# )\n",
    "# axes[2].scatter(\n",
    "#     X_val_high_encoded[:, 0],\n",
    "#     X_val_high_encoded[:, 1],\n",
    "#     color=\"orange\",\n",
    "#     label=\"High\"\n",
    "# )\n",
    "# axes[2].set_title(\"Normal vs High (encoded)\")\n",
    "# axes[2].set_xlabel(\"latent feature 1\")\n",
    "# axes[2].set_ylabel(\"latent feature 2\")\n",
    "# axes[2].legend()\n",
    "\n",
    "\n",
    "\n",
    "latent_dim = 8\n",
    "fig, axes = plt.subplots(2, 4, figsize=(20, 10))\n",
    "axes = axes.flatten()  # flatten to index easily\n",
    "\n",
    "for i in range(latent_dim):\n",
    "    axes[i].scatter(\n",
    "        X_val_normal_encoded[:, i],\n",
    "        [0]*X_val_normal_encoded.shape[0],  # optional: place on a line for 1D visualization\n",
    "        color='blue',\n",
    "        label='Normal',\n",
    "        alpha=0.6\n",
    "    )\n",
    "    axes[i].scatter(\n",
    "        X_val_high_encoded[:, i],\n",
    "        [0]*X_val_high_encoded.shape[0],  # same for High\n",
    "        color='orange',\n",
    "        label='High',\n",
    "        alpha=0.6\n",
    "    )\n",
    "    axes[i].set_title(f'Latent dim {i+1}')\n",
    "    axes[i].set_xlabel(f'Feature {i+1}')\n",
    "    axes[i].set_yticks([])  # hide y-axis as it's just a 1D comparison\n",
    "    axes[i].legend()\n",
    "    axes[i].grid(True)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "82be2766-5f59-4019-9f29-7c6580ddd2fc",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(classification_report(y_true=np.concatenate([y_val_normal, y_val_high]),y_pred=val_predictions))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1e08cc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(classification_report(y_true=np.concatenate([y_test_normal, y_test_high]), y_pred=test_predictions))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c16bfeee-ea3f-4e4a-8afe-ac05d36c515b",
   "metadata": {},
   "outputs": [],
   "source": [
    "f1_score(y_true=np.concatenate([y_test_normal, y_test_high]), y_pred=test_predictions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f39eb5d-6189-4219-a4db-49808202fb8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "balanced_accuracy_score(y_true=np.concatenate([y_test_normal, y_test_high]), y_pred=test_predictions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "552828b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluation_tools.plot_confusion_matrix(true_labels=np.concatenate([y_test_normal, y_test_high]), predictions=test_predictions, label_names=['high', 'low'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c9c78dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Save\n",
    "with open('ocsvm_model.pkl', 'wb') as f:\n",
    "    pickle.dump(ocsvm, f)\n",
    "\n",
    "# Load later\n",
    "with open('ocsvm_model.pkl', 'rb') as f:\n",
    "    ocsvm_loaded = pickle.load(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbad13e2",
   "metadata": {},
   "source": [
    "### One Class Gridsearch Approach"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b99a07d3-8cc9-46b2-bf33-6d3c40904f28",
   "metadata": {},
   "outputs": [],
   "source": [
    "# X_combined = np.concatenate([X_train_encoded, X_val_normal_encoded, X_val_high_encoded], axis=0)\n",
    "# y_combined = np.concatenate([\n",
    "#     np.ones(X_train_encoded.shape[0]+X_val_normal_encoded.shape[0]),      # normal = 1\n",
    "#     -np.ones(X_val_high_encoded.shape[0])   # anomaly = -1\n",
    "# ], axis=0)\n",
    "X_combined = np.concatenate([X_train_encoded, X_val_high_encoded], axis=0)\n",
    "y_combined = np.concatenate([\n",
    "    np.ones(X_train_encoded.shape[0]),      # normal = 1\n",
    "    -np.ones(X_val_high_encoded.shape[0])   # anomaly = -1\n",
    "], axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e52a4859-08e3-4aad-922a-8ec7d4f75b12",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(42)\n",
    "idx = np.arange(X_combined.shape[0])\n",
    "anomaly_idx = idx[y_combined == -1]\n",
    "normal_idx = idx[y_combined != -1]\n",
    "\n",
    "np.random.shuffle(normal_idx)\n",
    "cv_splits = [\n",
    "    (normal_idx[train], np.concatenate([normal_idx[test], anomaly_idx]))\n",
    "    for train, test in KFold(n_splits=5).split(normal_idx)\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02e9e79b-9a26-428b-872a-96120e80e699",
   "metadata": {},
   "outputs": [],
   "source": [
    "supervised_ocsvm = Pipeline([\n",
    "    ('ocsvm', OneClassSVM())\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3b14a88-2438-42a7-ab4d-ba4fed4457be",
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'ocsvm__nu': [*np.linspace(0.01, 0.2, 10).tolist(), 0.2],\n",
    "    'ocsvm__gamma': [*np.logspace(-2, 2, 20).tolist(), 'scale','auto'],\n",
    "    'ocsvm__kernel': ['rbf', 'poly']\n",
    "}\n",
    "grid_search = GridSearchCV(\n",
    "    estimator=supervised_ocsvm,\n",
    "    param_grid=param_grid,\n",
    "    scoring=lambda est, X, y: balanced_accuracy_score(y, est.predict(X)),\n",
    "    cv=cv_splits,\n",
    "    n_jobs=-1,\n",
    "    verbose=1,\n",
    "    refit=False  # Set to True to get best estimator\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b24b9f3-b82a-4e6c-a5d7-d3cebc7c04c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_search.fit(X_combined, y_combined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a5aaca2-1623-4ad3-a612-d401ef76284e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Results\n",
    "print(f'Best parameters: {grid_search.best_params_}')\n",
    "print(f'Best score: {grid_search.best_score_:.4f}')\n",
    "print(f'Std: {grid_search.cv_results_[\"std_test_score\"][grid_search.best_index_]:.4f}')\n",
    "\n",
    "# Access the best model\n",
    "supervised_ocsvm.set_params(**grid_search.best_params_)\n",
    "supervised_ocsvm.fit(X_train_encoded)\n",
    "\n",
    "# Make predictions\n",
    "predictions = supervised_ocsvm.predict(np.vstack([X_test_normal_encoded, X_test_high_encoded]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e02c19e-a2d4-4808-b66e-6fc431c9fa02",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluation_tools.plot_confusion_matrix(true_labels=np.concatenate([y_test_normal, y_test_high]), predictions=predictions, label_names=['high', 'low'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca037a2b-6dbd-4386-8e9f-f73a92dbb7a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "balanced_accuracy_score(y_true=np.concatenate([y_test_normal, y_test_high]), y_pred=predictions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15a837f4-db3d-4dee-8cd1-e5d9118f82e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "f1_score(y_true=np.concatenate([y_test_normal, y_test_high]), y_pred=predictions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e412041-7534-40fe-8486-ee97349a6168",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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
}
