{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Simple Univariate Time Series Anomaly Detection"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## boilerplate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "\n",
    "import copy\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from pylab import rcParams\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib import rc\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "from torch import nn, optim\n",
    "\n",
    "import torch.nn.functional as F\n",
    "import random\n",
    "# from arff2pandas import a2p"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<torch._C.Generator at 0x7f3c54205290>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format='retina'\n",
    "\n",
    "sns.set(style='whitegrid', palette='muted', font_scale=1.2)\n",
    "\n",
    "HAPPY_COLORS_PALETTE = [\"#01BEFE\", \"#FFDD00\", \"#FF7D00\", \"#FF006D\", \"#ADFF02\", \"#8F00FF\"]\n",
    "\n",
    "sns.set_palette(sns.color_palette(HAPPY_COLORS_PALETTE))\n",
    "\n",
    "# rcParams['figure.figsize'] = 12, 8\n",
    "\n",
    "RANDOM_SEED = 42\n",
    "np.random.seed(RANDOM_SEED)\n",
    "torch.manual_seed(RANDOM_SEED) \n",
    " \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import polars as pl\n",
    "from io import StringIO\n",
    "import math\n",
    "df = pl.read_csv('../data/battery_1.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We only need 'PACK1_CRIDATA_BATT_VOL'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## visualize fault and non-fault regions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "filter_condition =  df['PACK1_CRIDATA_BATT_VOL'].cast(pl.Float32) != 0\n",
    "voltage_data = (df['PACK1_CRIDATA_BATT_VOL']\n",
    "                .filter(filter_condition)\n",
    "                .cast(pl.Float32))\n",
    "\n",
    "def convert_values(values):\n",
    "    numerical_values = []\n",
    "    for value in values:\n",
    "        if value == 'False':\n",
    "            numerical_values.append(0)\n",
    "        elif value == 'True':\n",
    "            numerical_values.append(1)\n",
    "        else:\n",
    "            # numerical_values.append(np.nan)\n",
    "            numerical_values.append(-1)\n",
    "    return numerical_values\n",
    "\n",
    "\n",
    "fault_data = convert_values(df['BATT_PACK_1_FAULT']\n",
    "                            .filter(filter_condition))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'fault incidents')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 102568 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 2 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 537,
       "width": 852
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(10,3 * 2))\n",
    "\n",
    "axs[0].plot(voltage_data)\n",
    "axs[0].set_title(\"voltage\")\n",
    "axs[1].scatter(range(len(fault_data)), fault_data)\n",
    "axs[1].set_title(\"fault incidents\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Train region: 145000-end\n",
    "\n",
    "Test region: 45000-60000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_voltage_data = voltage_data[145000:]\n",
    "test_voltage_data = voltage_data[85000:100000]\n",
    "val_voltage_data = voltage_data[120000:135000]\n",
    "fault_voltage_data = voltage_data[45000:60000]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'fault incidents')"
      ]
     },
     "execution_count": 128,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 109020 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 2 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 537,
       "width": 852
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# fault region\n",
    "fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(10,3 * 2))\n",
    "\n",
    "axs[0].plot(fault_voltage_data)\n",
    "axs[0].set_title(\"voltage\")\n",
    "fault_incidents = fault_data[45000:60000]\n",
    "axs[1].scatter(range(len(fault_incidents)), fault_incidents)\n",
    "axs[1].set_title(\"fault incidents\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f410d197490>]"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 135440 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 673,
       "width": 998
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# test region\n",
    "plt.plot(test_voltage_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f410cf0fdc0>]"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 125960 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 667,
       "width": 998
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# test region\n",
    "plt.plot(val_voltage_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f410d197670>]"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 131540 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 667,
       "width": 998
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "# train region\n",
    "# plt.plot(voltage_data[145000:])\n",
    "plt.plot(train_voltage_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data Processing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(55774,)"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_voltage_data.to_numpy().shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "111.548"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "55774 / 500"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(25000,)"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_voltage_data.to_numpy().shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50.0"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "25000 / 500"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = train_voltage_data.to_numpy()\n",
    "segment_size = 100\n",
    "segments = [ torch.tensor(data[i:i + segment_size]).unsqueeze(1).float() for i in range(0, len(data), segment_size) ]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "if (segments[-1].shape[0] != segment_size):\n",
    "    segments.pop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([557, 100, 1])"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "torch.stack(segments).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "mean = np.mean(train_voltage_data)\n",
    "stdev = np.std(train_voltage_data)\n",
    "\n",
    "def create_dataset(series, segment_size, mean, stdev):\n",
    "    # normalize the data\n",
    "    data = (series.to_numpy() - mean)/stdev\n",
    "    segments = [ torch.tensor(data[i:i + segment_size]).unsqueeze(1).float() for i in range(0, len(data), segment_size) ]\n",
    "    # reject the last segment if it doesn't fit the shape\n",
    "    if (segments[-1].shape[0] != segment_size):\n",
    "        segments.pop()\n",
    "    n_seq, seq_len, n_features = torch.stack(segments).shape\n",
    "\n",
    "    return segments, seq_len, n_features\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "segment_size = 100\n",
    "train_dataset, seq_len, n_features = create_dataset(train_voltage_data, segment_size, mean, stdev)\n",
    "val_dataset, _, _ = create_dataset(val_voltage_data, segment_size, mean, stdev)\n",
    "test_normal_dataset, _, _ = create_dataset(test_voltage_data, segment_size, mean, stdev)\n",
    "test_anomaly_dataset, _, _ = create_dataset(fault_voltage_data, segment_size, mean, stdev)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Encoder Decoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Encoder(nn.Module):\n",
    "\n",
    "  def __init__(self, seq_len, n_features, embedding_dim=64):\n",
    "    super(Encoder, self).__init__()\n",
    "\n",
    "    self.seq_len, self.n_features = seq_len, n_features\n",
    "    self.embedding_dim, self.hidden_dim = embedding_dim, 2 * embedding_dim\n",
    "\n",
    "    self.rnn1 = nn.LSTM(\n",
    "      input_size=n_features,\n",
    "      hidden_size=self.hidden_dim,\n",
    "      num_layers=1,\n",
    "      batch_first=True\n",
    "    )\n",
    "    \n",
    "    self.rnn2 = nn.LSTM(\n",
    "      input_size=self.hidden_dim,\n",
    "      hidden_size=embedding_dim,\n",
    "      num_layers=1,\n",
    "      batch_first=True\n",
    "    )\n",
    "\n",
    "  def forward(self, x):\n",
    "    x = x.reshape((1, self.seq_len, self.n_features))\n",
    "\n",
    "    x, (_, _) = self.rnn1(x)\n",
    "    x, (hidden_n, _) = self.rnn2(x)\n",
    "\n",
    "    return hidden_n.reshape((self.n_features, self.embedding_dim))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Decoder(nn.Module):\n",
    "\n",
    "  def __init__(self, seq_len, input_dim=64, n_features=1):\n",
    "    super(Decoder, self).__init__()\n",
    "\n",
    "    self.seq_len, self.input_dim = seq_len, input_dim\n",
    "    self.hidden_dim, self.n_features = 2 * input_dim, n_features\n",
    "\n",
    "    self.rnn1 = nn.LSTM(\n",
    "      input_size=input_dim,\n",
    "      hidden_size=input_dim,\n",
    "      num_layers=1,\n",
    "      batch_first=True\n",
    "    )\n",
    "\n",
    "    self.rnn2 = nn.LSTM(\n",
    "      input_size=input_dim,\n",
    "      hidden_size=self.hidden_dim,\n",
    "      num_layers=1,\n",
    "      batch_first=True\n",
    "    )\n",
    "\n",
    "    self.output_layer = nn.Linear(self.hidden_dim, n_features)\n",
    "\n",
    "  def forward(self, x):\n",
    "    x = x.repeat(self.seq_len, self.n_features)\n",
    "    x = x.reshape((self.n_features, self.seq_len, self.input_dim))\n",
    "\n",
    "    x, (hidden_n, cell_n) = self.rnn1(x)\n",
    "    x, (hidden_n, cell_n) = self.rnn2(x)\n",
    "    x = x.reshape((self.seq_len, self.hidden_dim))\n",
    "\n",
    "    return self.output_layer(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [],
   "source": [
    "class RecurrentAutoencoder(nn.Module):\n",
    "\n",
    "  def __init__(self, seq_len, n_features, embedding_dim=64):\n",
    "    super(RecurrentAutoencoder, self).__init__()\n",
    "\n",
    "    self.encoder = Encoder(seq_len, n_features, embedding_dim).to(device)\n",
    "    self.decoder = Decoder(seq_len, embedding_dim, n_features).to(device)\n",
    "\n",
    "  def forward(self, x):\n",
    "    x = self.encoder(x)\n",
    "    x = self.decoder(x)\n",
    "\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = RecurrentAutoencoder(seq_len, n_features, 128)\n",
    "model = model.to(device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_model(model, train_dataset, val_dataset, n_epochs):\n",
    "  optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n",
    "  criterion = nn.L1Loss(reduction='sum').to(device)\n",
    "  history = dict(train=[], val=[])\n",
    "\n",
    "  best_model_wts = copy.deepcopy(model.state_dict())\n",
    "  best_loss = 10000.0\n",
    "  \n",
    "  for epoch in range(1, n_epochs + 1):\n",
    "    model = model.train()\n",
    "\n",
    "    train_losses = []\n",
    "    for seq_true in train_dataset:\n",
    "      optimizer.zero_grad()\n",
    "\n",
    "      seq_true = seq_true.to(device)\n",
    "      seq_pred = model(seq_true)\n",
    "\n",
    "      loss = criterion(seq_pred, seq_true)\n",
    "\n",
    "      loss.backward()\n",
    "      optimizer.step()\n",
    "\n",
    "      train_losses.append(loss.item())\n",
    "\n",
    "    val_losses = []\n",
    "    model = model.eval()\n",
    "    with torch.no_grad():\n",
    "      for seq_true in val_dataset:\n",
    "\n",
    "        seq_true = seq_true.to(device)\n",
    "        seq_pred = model(seq_true)\n",
    "\n",
    "        loss = criterion(seq_pred, seq_true)\n",
    "        val_losses.append(loss.item())\n",
    "\n",
    "    train_loss = np.mean(train_losses)\n",
    "    val_loss = np.mean(val_losses)\n",
    "\n",
    "    history['train'].append(train_loss)\n",
    "    history['val'].append(val_loss)\n",
    "\n",
    "    if val_loss < best_loss:\n",
    "      best_loss = val_loss\n",
    "      best_model_wts = copy.deepcopy(model.state_dict())\n",
    "\n",
    "    print(f'Epoch {epoch}: train loss {train_loss} val loss {val_loss}')\n",
    "\n",
    "  model.load_state_dict(best_model_wts)\n",
    "  return model.eval(), history"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1: train loss 83876.04766045781 val loss 76590.19979166667\n",
      "Epoch 2: train loss 69409.47180066202 val loss 62160.938072916666\n",
      "Epoch 3: train loss 55019.510905240124 val loss 47822.004479166666\n",
      "Epoch 4: train loss 40701.095496241025 val loss 33518.067044270836\n",
      "Epoch 5: train loss 26405.11348462747 val loss 19228.23103515625\n",
      "Epoch 6: train loss 12124.039359676559 val loss 5028.084357096354\n",
      "Epoch 7: train loss 1190.5766426237096 val loss 489.6685514322917\n",
      "Epoch 8: train loss 445.39513634180037 val loss 488.57960042317706\n",
      "Epoch 9: train loss 445.98794736468386 val loss 489.6590458170573\n",
      "Epoch 10: train loss 443.86239300767227 val loss 489.81301350911457\n"
     ]
    }
   ],
   "source": [
    "model, history = train_model(\n",
    "  model, \n",
    "  train_dataset, \n",
    "  val_dataset, \n",
    "  n_epochs=10\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 144828 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 716,
       "width": 1046
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "ax = plt.figure().gca()\n",
    "\n",
    "ax.plot(history['train'])\n",
    "ax.plot(history['val'])\n",
    "plt.ylabel('Loss')\n",
    "plt.xlabel('Epoch')\n",
    "plt.legend(['train', 'test'])\n",
    "plt.title('Loss over training epochs')\n",
    "plt.show();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Save the model\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [],
   "source": [
    "MODEL_PATH = 'model.pth'\n",
    "\n",
    "torch.save(model, MODEL_PATH)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [],
   "source": [
    "# reload the model\n",
    "# model.torch.load('model.pth')\n",
    "# model = model.to(device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Check reconstruction error"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict(model, dataset):\n",
    "  predictions, losses = [], []\n",
    "  criterion = nn.L1Loss(reduction='sum').to(device)\n",
    "  with torch.no_grad():\n",
    "    model = model.eval()\n",
    "    for seq_true in dataset:\n",
    "      seq_true = seq_true.to(device)\n",
    "      seq_pred = model(seq_true)\n",
    "\n",
    "      loss = criterion(seq_pred, seq_true)\n",
    "\n",
    "      predictions.append(seq_pred.cpu().numpy().flatten())\n",
    "      losses.append(loss.item())\n",
    "  return predictions, losses"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[478.3055419921875,\n",
       " 324.8333740234375,\n",
       " 323.153564453125,\n",
       " 426.354248046875,\n",
       " 400.283935546875,\n",
       " 310.18212890625,\n",
       " 423.1041259765625,\n",
       " 409.283935546875,\n",
       " 288.3319091796875,\n",
       " 6717.36279296875,\n",
       " 769.3988037109375,\n",
       " 375.0321044921875,\n",
       " 436.6552734375,\n",
       " 294.88232421875,\n",
       " 364.153564453125,\n",
       " 446.8544921875,\n",
       " 384.1339111328125,\n",
       " 318.0609130859375,\n",
       " 423.754150390625,\n",
       " 378.783935546875,\n",
       " 289.281982421875,\n",
       " 6582.95556640625,\n",
       " 1024.248779296875,\n",
       " 359.783935546875,\n",
       " 336.283935546875,\n",
       " 332.783935546875,\n",
       " 347.783935546875,\n",
       " 496.783935546875,\n",
       " 614.783935546875,\n",
       " 614.783935546875,\n",
       " 614.783935546875,\n",
       " 546.283935546875,\n",
       " 355.183349609375,\n",
       " 420.35400390625,\n",
       " 350.0330810546875,\n",
       " 383.153564453125,\n",
       " 415.7130126953125,\n",
       " 353.574462890625,\n",
       " 374.153564453125,\n",
       " 469.783935546875,\n",
       " 330.7822265625,\n",
       " 459.854736328125,\n",
       " 371.48388671875,\n",
       " 331.074462890625,\n",
       " 466.655517578125,\n",
       " 308.3328857421875,\n",
       " 414.7042236328125,\n",
       " 387.2337646484375,\n",
       " 332.153564453125,\n",
       " 489.4053955078125,\n",
       " 336.7330322265625,\n",
       " 274.153564453125,\n",
       " 274.153564453125,\n",
       " 274.153564453125,\n",
       " 447.9044189453125,\n",
       " 523.783935546875,\n",
       " 374.783935546875,\n",
       " 319.8818359375,\n",
       " 459.3052978515625,\n",
       " 345.68359375,\n",
       " 334.153564453125,\n",
       " 462.3057861328125,\n",
       " 302.182373046875,\n",
       " 420.954345703125,\n",
       " 355.8333740234375,\n",
       " 376.553955078125,\n",
       " 428.283935546875,\n",
       " 307.8818359375,\n",
       " 424.5042724609375,\n",
       " 370.28369140625,\n",
       " 330.153564453125,\n",
       " 459.85498046875,\n",
       " 345.3336181640625,\n",
       " 336.153564453125,\n",
       " 449.6048583984375,\n",
       " 327.6331787109375,\n",
       " 357.153564453125,\n",
       " 453.45556640625,\n",
       " 308.7325439453125,\n",
       " 425.954345703125,\n",
       " 410.283935546875,\n",
       " 309.48193359375,\n",
       " 427.754150390625,\n",
       " 435.783935546875,\n",
       " 306.8828125,\n",
       " 387.153564453125,\n",
       " 445.9556884765625,\n",
       " 313.2822265625,\n",
       " 429.8544921875,\n",
       " 369.6336669921875,\n",
       " 337.153564453125,\n",
       " 475.6055908203125,\n",
       " 310.482421875,\n",
       " 430.354248046875,\n",
       " 397.283935546875,\n",
       " 297.5819091796875,\n",
       " 434.7042236328125,\n",
       " 402.783935546875,\n",
       " 292.5321044921875,\n",
       " 400.3038330078125]"
      ]
     },
     "execution_count": 82,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "_, losses = predict(model, test_normal_dataset)\n",
    "losses[:100]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: ylabel='Density'>"
      ]
     },
     "execution_count": 79,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 106328 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 667,
       "width": 1052
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "_, losses = predict(model, train_dataset)\n",
    "sns.kdeplot(losses)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: ylabel='Density'>"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 108716 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 667,
       "width": 1062
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# _, losses = predict(model, train_dataset)\n",
    "_, losses = predict(model, test_normal_dataset)\n",
    "sns.kdeplot(losses)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: ylabel='Density'>"
      ]
     },
     "execution_count": 81,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 124260 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 667,
       "width": 1052
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# _, losses = predict(model, train_dataset)\n",
    "_, losses = predict(model, test_anomaly_dataset)\n",
    "sns.kdeplot(losses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Predictions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compute THRESHOLD with training set data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "predictions, losses = predict(model, train_dataset)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [],
   "source": [
    "loss_array = np.array(losses)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [],
   "source": [
    "stdev = np.std(loss_array)\n",
    "mean = np.mean(loss_array)\n",
    "THRESHOLD = mean + stdev "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Check on test_normal_dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "number of intervals exceeding 1 std dev loss: 8/150\n"
     ]
    }
   ],
   "source": [
    "_, losses = predict(model, test_normal_dataset)\n",
    "exceed_count = sum(l > THRESHOLD for l in losses)\n",
    "print(f'number of intervals exceeding 1 std dev loss: {exceed_count}/{len(test_normal_dataset)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Check on test_anomaly_dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "number of intervals exceeding 1 std dev loss: 118/150\n"
     ]
    }
   ],
   "source": [
    "_, losses = predict(model, test_anomaly_dataset)\n",
    "exceed_count = sum(l > THRESHOLD for l in losses)\n",
    "print(f'number of intervals exceeding 1 std dev loss: {exceed_count}/{len(test_normal_dataset)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## plot construction error vs original"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_prediction(data, model, title, ax):\n",
    "    predictions, pred_losses = predict(model, [data])\n",
    "\n",
    "    ax.plot(data, label='true')\n",
    "    ax.plot(predictions[0], label='reconstructed')\n",
    "    ax.set_title(f'{title} (loss: {np.around(pred_losses[0], 2)})')\n",
    "    ax.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 123,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 266240 characters]",
      "text/plain": [
       "<Figure size 2200x800 with 12 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 777,
       "width": 2173
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(\n",
    "    nrows=2,\n",
    "    ncols=6,\n",
    "    sharey=True,\n",
    "    sharex=True,\n",
    "    figsize=(22, 8)\n",
    ")\n",
    "\n",
    "sample_size = 6\n",
    "sample_indices = random.sample(range(0,len(test_normal_dataset)), sample_size)\n",
    "\n",
    "sampled_test_normal_dataset = [test_normal_dataset[i] for i in sample_indices]\n",
    "sampled_test_anomaly_dataset = [test_anomaly_dataset[i] for i in sample_indices]\n",
    "\n",
    "for i, data in enumerate(sampled_test_normal_dataset):\n",
    "    plot_prediction(data, model, title='Normal', ax=axs[0, i])\n",
    "\n",
    "for i, data in enumerate(sampled_test_anomaly_dataset):\n",
    "    plot_prediction(data, model, title='Anomaly', ax=axs[1, i])\n",
    "\n",
    "fig.tight_layout();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.10.12"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
