{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/kleinsteuber/anaconda3/envs/pytorch-gpu/lib/python3.10/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 3 sessions\n",
      "Session 'Beaver_01' at folder: /home/kleinsteuber/vscode/ResizedSessions256_NoBackup/VIELAAS_Spring_Session01-VIELAAS_Beaver_01\n",
      "Loaded scans.\n"
     ]
    }
   ],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "import numpy as np\n",
    "from sklearn.neighbors import KernelDensity\n",
    "from sklearn.svm import OneClassSVM\n",
    "import pandas as pd\n",
    "import torch\n",
    "from glob import glob\n",
    "\n",
    "from py.Dataset import Dataset\n",
    "from py.FileUtils import load\n",
    "from py.PlotUtils import plot_roc_curve, get_percentiles\n",
    "from py.PyTorchData import get_log, create_dataloader, model_output_to_image\n",
    "from py.Labels import LABELS\n",
    "from py.Autoencoder2 import Autoencoder\n",
    "from py.ImageUtils import display_images\n",
    "from torchinfo import summary\n",
    "\n",
    "DIR = '/home/kleinsteuber/vscode/ResizedSessions256_NoBackup' # dataset directory\n",
    "SESSION = \"beaver_01\"\n",
    "TRAIN_NAME = \"ae2_beaver_01_1\"\n",
    "\n",
    "ds = Dataset(DIR)\n",
    "session = ds.create_session(SESSION)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 20704 characters]",
      "text/plain": [
       "<Figure size 864x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "its, losses = get_log(TRAIN_NAME, display=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Output visualization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "==========================================================================================\n",
       "Layer (type:depth-idx)                   Output Shape              Param #\n",
       "==========================================================================================\n",
       "Autoencoder                              [32, 3, 256, 256]         --\n",
       "├─Sequential: 1-1                        [32, 32, 4, 4]            --\n",
       "│    └─Dropout: 2-1                      [32, 3, 256, 256]         --\n",
       "│    └─Conv2d: 2-2                       [32, 32, 128, 128]        4,736\n",
       "│    └─ReLU: 2-3                         [32, 32, 128, 128]        --\n",
       "│    └─Dropout: 2-4                      [32, 32, 128, 128]        --\n",
       "│    └─Conv2d: 2-5                       [32, 64, 64, 64]          51,264\n",
       "│    └─ReLU: 2-6                         [32, 64, 64, 64]          --\n",
       "│    └─Dropout: 2-7                      [32, 64, 64, 64]          --\n",
       "│    └─Conv2d: 2-8                       [32, 64, 32, 32]          36,928\n",
       "│    └─ReLU: 2-9                         [32, 64, 32, 32]          --\n",
       "│    └─Dropout: 2-10                     [32, 64, 32, 32]          --\n",
       "│    └─Conv2d: 2-11                      [32, 64, 16, 16]          36,928\n",
       "│    └─ReLU: 2-12                        [32, 64, 16, 16]          --\n",
       "│    └─Dropout: 2-13                     [32, 64, 16, 16]          --\n",
       "│    └─Conv2d: 2-14                      [32, 128, 8, 8]           73,856\n",
       "│    └─ReLU: 2-15                        [32, 128, 8, 8]           --\n",
       "│    └─Dropout: 2-16                     [32, 128, 8, 8]           --\n",
       "│    └─Conv2d: 2-17                      [32, 128, 4, 4]           147,584\n",
       "│    └─ReLU: 2-18                        [32, 128, 4, 4]           --\n",
       "│    └─Dropout: 2-19                     [32, 128, 4, 4]           --\n",
       "│    └─Conv2d: 2-20                      [32, 32, 4, 4]            36,896\n",
       "│    └─ReLU: 2-21                        [32, 32, 4, 4]            --\n",
       "├─Sequential: 1-2                        [32, 3, 256, 256]         --\n",
       "│    └─Dropout: 2-22                     [32, 32, 4, 4]            --\n",
       "│    └─Conv2d: 2-23                      [32, 128, 4, 4]           36,992\n",
       "│    └─ReLU: 2-24                        [32, 128, 4, 4]           --\n",
       "│    └─Dropout: 2-25                     [32, 128, 4, 4]           --\n",
       "│    └─ConvTranspose2d: 2-26             [32, 128, 8, 8]           262,272\n",
       "│    └─ReLU: 2-27                        [32, 128, 8, 8]           --\n",
       "│    └─Dropout: 2-28                     [32, 128, 8, 8]           --\n",
       "│    └─ConvTranspose2d: 2-29             [32, 64, 16, 16]          131,136\n",
       "│    └─ReLU: 2-30                        [32, 64, 16, 16]          --\n",
       "│    └─Dropout: 2-31                     [32, 64, 16, 16]          --\n",
       "│    └─ConvTranspose2d: 2-32             [32, 64, 32, 32]          65,600\n",
       "│    └─ReLU: 2-33                        [32, 64, 32, 32]          --\n",
       "│    └─Dropout: 2-34                     [32, 64, 32, 32]          --\n",
       "│    └─ConvTranspose2d: 2-35             [32, 64, 64, 64]          65,600\n",
       "│    └─ReLU: 2-36                        [32, 64, 64, 64]          --\n",
       "│    └─Dropout: 2-37                     [32, 64, 64, 64]          --\n",
       "│    └─ConvTranspose2d: 2-38             [32, 32, 128, 128]        73,760\n",
       "│    └─ReLU: 2-39                        [32, 32, 128, 128]        --\n",
       "│    └─Dropout: 2-40                     [32, 32, 128, 128]        --\n",
       "│    └─ConvTranspose2d: 2-41             [32, 16, 256, 256]        32,784\n",
       "│    └─ReLU: 2-42                        [32, 16, 256, 256]        --\n",
       "│    └─Dropout: 2-43                     [32, 16, 256, 256]        --\n",
       "│    └─Conv2d: 2-44                      [32, 3, 256, 256]         435\n",
       "│    └─Tanh: 2-45                        [32, 3, 256, 256]         --\n",
       "==========================================================================================\n",
       "Total params: 1,056,771\n",
       "Trainable params: 1,056,771\n",
       "Non-trainable params: 0\n",
       "Total mult-adds (G): 131.68\n",
       "==========================================================================================\n",
       "Input size (MB): 25.17\n",
       "Forward/backward pass size (MB): 768.74\n",
       "Params size (MB): 4.23\n",
       "Estimated Total Size (MB): 798.13\n",
       "=========================================================================================="
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_path = sorted(glob(f\"./ae_train_NoBackup/{TRAIN_NAME}/model_*.pth\"))[-1]\n",
    "model = Autoencoder()\n",
    "model.load_state_dict(torch.load(model_path, map_location=torch.device(\"cpu\")))\n",
    "model.eval()\n",
    "summary(model, input_size=(32, 3, 256, 256))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "==========================================================================================\n",
       "Layer (type:depth-idx)                   Output Shape              Param #\n",
       "==========================================================================================\n",
       "Sequential                               [32, 32, 4, 4]            --\n",
       "├─Dropout: 1-1                           [32, 3, 256, 256]         --\n",
       "├─Conv2d: 1-2                            [32, 32, 128, 128]        4,736\n",
       "├─ReLU: 1-3                              [32, 32, 128, 128]        --\n",
       "├─Dropout: 1-4                           [32, 32, 128, 128]        --\n",
       "├─Conv2d: 1-5                            [32, 64, 64, 64]          51,264\n",
       "├─ReLU: 1-6                              [32, 64, 64, 64]          --\n",
       "├─Dropout: 1-7                           [32, 64, 64, 64]          --\n",
       "├─Conv2d: 1-8                            [32, 64, 32, 32]          36,928\n",
       "├─ReLU: 1-9                              [32, 64, 32, 32]          --\n",
       "├─Dropout: 1-10                          [32, 64, 32, 32]          --\n",
       "├─Conv2d: 1-11                           [32, 64, 16, 16]          36,928\n",
       "├─ReLU: 1-12                             [32, 64, 16, 16]          --\n",
       "├─Dropout: 1-13                          [32, 64, 16, 16]          --\n",
       "├─Conv2d: 1-14                           [32, 128, 8, 8]           73,856\n",
       "├─ReLU: 1-15                             [32, 128, 8, 8]           --\n",
       "├─Dropout: 1-16                          [32, 128, 8, 8]           --\n",
       "├─Conv2d: 1-17                           [32, 128, 4, 4]           147,584\n",
       "├─ReLU: 1-18                             [32, 128, 4, 4]           --\n",
       "├─Dropout: 1-19                          [32, 128, 4, 4]           --\n",
       "├─Conv2d: 1-20                           [32, 32, 4, 4]            36,896\n",
       "├─ReLU: 1-21                             [32, 32, 4, 4]            --\n",
       "==========================================================================================\n",
       "Total params: 388,192\n",
       "Trainable params: 388,192\n",
       "Non-trainable params: 0\n",
       "Total mult-adds (G): 10.96\n",
       "==========================================================================================\n",
       "Input size (MB): 25.17\n",
       "Forward/backward pass size (MB): 225.05\n",
       "Params size (MB): 1.55\n",
       "Estimated Total Size (MB): 251.77\n",
       "=========================================================================================="
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "summary(model.encoder, input_size=(32, 3, 256, 256))\n",
    "#summary(model.decoder, input_size=(32, 32, 4, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "695 files found\n"
     ]
    }
   ],
   "source": [
    "only_anomalous = False\n",
    "\n",
    "def labeler(filename: str) -> int:\n",
    "    is_normal = (int(filename[-9:-4]) in LABELS[session.name][\"normal\"])\n",
    "    return 0 if is_normal else 1\n",
    "\n",
    "def is_labeled(filename: str) -> bool:\n",
    "    img_nr = int(filename[-9:-4])\n",
    "    return (img_nr <= LABELS[session.name][\"max\"]) and (img_nr not in LABELS[session.name][\"not_annotated\"]) and (not only_anomalous or img_nr in LABELS[session.name][\"anomalous\"])\n",
    "\n",
    "test_dl = create_dataloader(session.get_motion_folder(), shuffle=False, labeler=labeler, filter=is_labeled, skip_transforms=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Feature batch shape: torch.Size([32, 3, 256, 256])\n",
      "Labels batch shape: torch.Size([32])\n"
     ]
    }
   ],
   "source": [
    "features, labels = next(iter(test_dl))\n",
    "print(f\"Feature batch shape: {features.size()}\")\n",
    "print(f\"Labels batch shape: {labels.size()}\")\n",
    "input_imgs = model_output_to_image(features).detach()\n",
    "output_imgs = model_output_to_image(model(features)).detach()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 4015392 characters]",
      "text/plain": [
       "<Figure size 1152x2304 with 32 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "dimgs = []\n",
    "dlbls = []\n",
    "only_normals = False\n",
    "for i in range(32 if only_normals else 16):\n",
    "    if only_normals and labels[i] == 1:\n",
    "        continue\n",
    "    lbl = \"anomalous\" if labels[i] == 1 else \"normal\"\n",
    "    dimgs += [np.einsum(\"ijk->jki\", input_imgs[i]), np.einsum(\"ijk->jki\", output_imgs[i])]\n",
    "    dlbls += [f\"In {i} ({lbl})\", f\"Out {i}\"]\n",
    "display_images(dimgs, dlbls, size=(4, 4), row_size=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {},
   "outputs": [],
   "source": [
    "lapse_losses, lapse_encodings, lapse_labels = load(f\"./ae_train_NoBackup/{TRAIN_NAME}/eval/{session.name}_lapse.pickle\")\n",
    "motion_losses, motion_encodings, motion_labels = load(f\"./ae_train_NoBackup/{TRAIN_NAME}/eval/{session.name}_motion.pickle\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "metadata": {},
   "outputs": [],
   "source": [
    "kde = KernelDensity(kernel=\"gaussian\", bandwidth=0.01).fit(lapse_encodings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {},
   "outputs": [],
   "source": [
    "preds = kde.score_samples(motion_encodings)\n",
    "y_anom = preds[motion_labels == 1]\n",
    "y_norm = preds[motion_labels == 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "count    7.400000e+01\n",
       "mean    -1.048017e+07\n",
       "std      2.071084e+07\n",
       "min     -1.017910e+08\n",
       "25%     -7.436353e+06\n",
       "50%     -2.247392e+06\n",
       "75%     -1.135620e+06\n",
       "max      1.849936e+03\n",
       "dtype: float64"
      ]
     },
     "execution_count": 130,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pd.Series(y_norm).describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "count    6.210000e+02\n",
       "mean    -7.132370e+07\n",
       "std      1.103709e+08\n",
       "min     -7.158724e+08\n",
       "25%     -6.332533e+07\n",
       "50%     -2.771721e+07\n",
       "75%     -1.034139e+07\n",
       "max      1.852779e+03\n",
       "dtype: float64"
      ]
     },
     "execution_count": 131,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pd.Series(y_anom).describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [],
   "source": [
    "mode = \"kde\"\n",
    "\n",
    "if mode == \"kde\":\n",
    "    y_anom = preds[motion_labels == 1]\n",
    "    y_norm = preds[motion_labels == 0]\n",
    "    X = -np.concatenate([y_norm, y_anom]).reshape((-1, 1))\n",
    "    y = np.concatenate([-np.ones_like(y_norm), np.ones_like(y_anom)])\n",
    "elif mode == \"loss\":\n",
    "    y_anom = motion_losses[motion_labels == 1]\n",
    "    y_norm = motion_losses[motion_labels == 0]\n",
    "    X = np.concatenate([y_norm, y_anom]).reshape((-1, 1))\n",
    "    y = np.concatenate([-np.ones_like(y_norm), np.ones_like(y_anom)])\n",
    "elif mode in [\"kde,loss\", \"loss,kde\"]:\n",
    "    mode = \"kde,loss\"\n",
    "    # generate training data for one-class SVM\n",
    "    lapse_preds = kde.score_samples(lapse_encodings)\n",
    "    X_train = np.array([[x1, x2] for x1, x2 in zip(lapse_preds, lapse_losses)])\n",
    "    X_test = np.array([[x1, x2] for x1, x2 in zip(preds, motion_losses)])\n",
    "    # train one-class SVM\n",
    "    clf = OneClassSVM().fit(X_train)\n",
    "    X = clf.decision_function(X_test).reshape((-1, 1)) # values for thresholding\n",
    "    y = np.where(motion_labels == 1, 1, -1)\n",
    "else:\n",
    "    raise ValueError(\"unknown mode\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 20160 characters]",
      "text/plain": [
       "<Figure size 576x576 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9 percentile : TPR = 0.9227, FPR = 0.4324 <-> TNR = 0.5676 @ thresh 2931684.6495045642\n",
      "0.95 percentile : TPR = 0.9501, FPR = 0.7432 <-> TNR = 0.2568 @ thresh 1145874.4191550955\n",
      "0.98 percentile : TPR = 0.9823, FPR = 1.0000 <-> TNR = 0.0000 @ thresh -1850.6103394474567\n",
      "0.99 percentile : TPR = 0.9919, FPR = 1.0000 <-> TNR = 0.0000 @ thresh -1851.015804555487\n"
     ]
    }
   ],
   "source": [
    "fpr, tpr, thresholds, auc_score = plot_roc_curve(y, X[:,0], f\"Autoencoder {mode}\", savefile=f\"plots/approach4/roc_curves/{TRAIN_NAME}_{session.name}_{mode}\")\n",
    "get_percentiles(fpr, tpr, thresholds)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## AUC Scores\n",
    "\n",
    "### Beaver_01\n",
    "\n",
    "| Mode | AUC |\n",
    "| --- | ---: |\n",
    "| Loss | 0.5549 |\n",
    "| KDE | 0.9209 |\n",
    "| KDE + Loss | 0.9209 |\n",
    "\n",
    "### Marten_01\n",
    "\n",
    "| Mode | AUC |\n",
    "| --- | ---: |\n",
    "| Loss | 0.8499 |\n",
    "| KDE | 1.0 |\n",
    "| KDE + Loss | 1.0 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## View most difficult Motion images"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [],
   "source": [
    "lapse_losses, lapse_encodings, lapse_labels = load(f\"./ae_train_NoBackup/{TRAIN_NAME}/eval/{session.name}_lapse.pickle\")\n",
    "motion_losses, motion_encodings, motion_labels, images = load(f\"./ae_train_NoBackup/{TRAIN_NAME}/eval/{session.name}_motion_withimgs.pickle\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "kde = KernelDensity(kernel=\"gaussian\", bandwidth=0.4).fit(lapse_encodings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Difficult anomalies"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1066316 characters]",
      "text/plain": [
       "<Figure size 1440x576 with 10 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "anomalous_encodings = motion_encodings[motion_labels == 1]\n",
    "anomalous_images = images[motion_labels == 1]\n",
    "anomalous_preds = kde.score_samples(anomalous_encodings)\n",
    "difficult_idx = np.argsort(anomalous_preds)[::-1][:10]\n",
    "difficult_imgs = 0.5 * np.einsum(\"ijkl->iklj\", anomalous_images[difficult_idx]) + 0.5\n",
    "difficult_probs = anomalous_preds[difficult_idx]\n",
    "display_images(difficult_imgs, [f\"{p:.2f}\" for p in difficult_probs], row_size=5, size=(4, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Easy anomalies"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1325232 characters]",
      "text/plain": [
       "<Figure size 1440x576 with 10 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "easy_idx = np.argsort(anomalous_preds)[:10]\n",
    "easy_imgs = 0.5 * np.einsum(\"ijkl->iklj\", anomalous_images[easy_idx]) + 0.5\n",
    "easy_probs = anomalous_preds[easy_idx]\n",
    "display_images(easy_imgs, [f\"{p:.2f}\" for p in easy_probs], row_size=5, size=(4, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Difficult normals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1905392 characters]",
      "text/plain": [
       "<Figure size 1440x576 with 10 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "normal_encodings = motion_encodings[motion_labels != 1]\n",
    "normal_images = images[motion_labels != 1]\n",
    "normal_preds = kde.score_samples(normal_encodings)\n",
    "difficult_idx = np.argsort(normal_preds)[:10]\n",
    "difficult_imgs = 0.5 * np.einsum(\"ijkl->iklj\", normal_images[difficult_idx]) + 0.5\n",
    "difficult_probs = normal_preds[difficult_idx]\n",
    "display_images(difficult_imgs, [f\"{p:.2f}\" for p in difficult_probs], row_size=5, size=(4, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Easy normals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1349324 characters]",
      "text/plain": [
       "<Figure size 1440x576 with 10 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "easy_idx = np.argsort(normal_preds)[::-1][:10]\n",
    "easy_imgs = 0.5 * np.einsum(\"ijkl->iklj\", normal_images[easy_idx]) + 0.5\n",
    "easy_probs = normal_preds[easy_idx]\n",
    "display_images(easy_imgs, [f\"{p:.2f}\" for p in easy_probs], row_size=5, size=(4, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.10.4 ('pytorch-gpu')",
   "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.4"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "17cd5c528a3345b75540c61f907eece919c031d57a2ca1e5653325af249173c9"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
