diff --git a/notebooks/05_covid_anomaly_detection.ipynb b/notebooks/05_covid_anomaly_detection.ipynb new file mode 100644 index 0000000..f8f1156 --- /dev/null +++ b/notebooks/05_covid_anomaly_detection.ipynb @@ -0,0 +1,2350 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from ssl_tools.data.data_modules.covid_anomaly import CovidUserAnomalyDataModule\n", + "from ssl_tools.utils.data import get_full_data_split\n", + "from ssl_tools.models.nets.lstm_ae import LSTMAutoencoder\n", + "import lightning as L\n", + "import torch\n", + "import numpy as np\n", + "from torchmetrics import MeanSquaredError" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
datetimeRHR-0RHR-1RHR-2RHR-3RHR-4RHR-5RHR-6RHR-7RHR-8...RHR-10RHR-11RHR-12RHR-13RHR-14RHR-15anomalybaselinelabelparticipant_id
02027-01-14 21:00:001.1701750.653752-0.392374-1.431553-2.129013-2.755962-3.681322-4.674443-5.668570...-6.937363-7.102118-6.975790-6.554774-6.112156-5.396099FalseTruenormalP110465
12027-01-15 05:00:00-5.668570-6.373289-6.937363-7.102118-6.975790-6.554774-6.112156-5.396099-4.415848...-2.656756-1.305630-0.0727561.0461951.5304671.829053FalseFalsenormalP110465
22027-01-15 13:00:00-4.415848-3.467073-2.656756-1.305630-0.0727561.0461951.5304671.8290531.223064...-0.424000-1.145581-1.355121-2.321206-3.124961-3.928738FalseFalsenormalP110465
32027-01-15 21:00:001.2230640.472444-0.424000-1.145581-1.355121-2.321206-3.124961-3.928738-4.802627...-6.067744-5.460156-4.671143-3.408943-2.237883-1.187843FalseFalsenormalP110465
42027-01-16 05:00:00-4.802627-5.831013-6.067744-5.460156-4.671143-3.408943-2.237883-1.187843-0.062360...2.2669443.7944654.6257454.8277564.7200004.677464FalseFalsenormalP110465
..................................................................
317322024-12-13 00:00:00-0.180702-0.499793-0.749829-0.868485-0.966754-1.004670-0.888210-0.580762-0.467943...0.0920000.3478400.6363950.9581951.1705141.301841FalseFalserecoveredP992022
317332024-12-13 08:00:00-0.467943-0.1627400.0920000.3478400.6363950.9581951.1705141.3018411.477526...1.6603441.6566001.6856521.7472521.7673291.793616FalseFalserecoveredP992022
317342024-12-13 16:00:001.4775261.6573211.6603441.6566001.6856521.7472521.7673291.7936161.728615...1.5098331.3807491.2637441.1399971.0242050.946663FalseFalserecoveredP992022
317352024-12-14 00:00:001.7286151.6162651.5098331.3807491.2637441.1399971.0242050.9466631.136868...1.6421531.9093812.1144392.2822382.4536912.587843FalseFalserecoveredP992022
317362024-12-14 08:00:001.1368681.3804181.6421531.9093812.1144392.2822382.4536912.5878432.437232...2.3598402.1734002.0981401.9676691.7845121.561848FalseFalserecoveredP992022
\n", + "

31737 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " datetime RHR-0 RHR-1 RHR-2 RHR-3 RHR-4 \\\n", + "0 2027-01-14 21:00:00 1.170175 0.653752 -0.392374 -1.431553 -2.129013 \n", + "1 2027-01-15 05:00:00 -5.668570 -6.373289 -6.937363 -7.102118 -6.975790 \n", + "2 2027-01-15 13:00:00 -4.415848 -3.467073 -2.656756 -1.305630 -0.072756 \n", + "3 2027-01-15 21:00:00 1.223064 0.472444 -0.424000 -1.145581 -1.355121 \n", + "4 2027-01-16 05:00:00 -4.802627 -5.831013 -6.067744 -5.460156 -4.671143 \n", + "... ... ... ... ... ... ... \n", + "31732 2024-12-13 00:00:00 -0.180702 -0.499793 -0.749829 -0.868485 -0.966754 \n", + "31733 2024-12-13 08:00:00 -0.467943 -0.162740 0.092000 0.347840 0.636395 \n", + "31734 2024-12-13 16:00:00 1.477526 1.657321 1.660344 1.656600 1.685652 \n", + "31735 2024-12-14 00:00:00 1.728615 1.616265 1.509833 1.380749 1.263744 \n", + "31736 2024-12-14 08:00:00 1.136868 1.380418 1.642153 1.909381 2.114439 \n", + "\n", + " RHR-5 RHR-6 RHR-7 RHR-8 ... RHR-10 RHR-11 \\\n", + "0 -2.755962 -3.681322 -4.674443 -5.668570 ... -6.937363 -7.102118 \n", + "1 -6.554774 -6.112156 -5.396099 -4.415848 ... -2.656756 -1.305630 \n", + "2 1.046195 1.530467 1.829053 1.223064 ... -0.424000 -1.145581 \n", + "3 -2.321206 -3.124961 -3.928738 -4.802627 ... -6.067744 -5.460156 \n", + "4 -3.408943 -2.237883 -1.187843 -0.062360 ... 2.266944 3.794465 \n", + "... ... ... ... ... ... ... ... \n", + "31732 -1.004670 -0.888210 -0.580762 -0.467943 ... 0.092000 0.347840 \n", + "31733 0.958195 1.170514 1.301841 1.477526 ... 1.660344 1.656600 \n", + "31734 1.747252 1.767329 1.793616 1.728615 ... 1.509833 1.380749 \n", + "31735 1.139997 1.024205 0.946663 1.136868 ... 1.642153 1.909381 \n", + "31736 2.282238 2.453691 2.587843 2.437232 ... 2.359840 2.173400 \n", + "\n", + " RHR-12 RHR-13 RHR-14 RHR-15 anomaly baseline label \\\n", + "0 -6.975790 -6.554774 -6.112156 -5.396099 False True normal \n", + "1 -0.072756 1.046195 1.530467 1.829053 False False normal \n", + "2 -1.355121 -2.321206 -3.124961 -3.928738 False False normal \n", + "3 -4.671143 -3.408943 -2.237883 -1.187843 False False normal \n", + "4 4.625745 4.827756 4.720000 4.677464 False False normal \n", + "... ... ... ... ... ... ... ... \n", + "31732 0.636395 0.958195 1.170514 1.301841 False False recovered \n", + "31733 1.685652 1.747252 1.767329 1.793616 False False recovered \n", + "31734 1.263744 1.139997 1.024205 0.946663 False False recovered \n", + "31735 2.114439 2.282238 2.453691 2.587843 False False recovered \n", + "31736 2.098140 1.967669 1.784512 1.561848 False False recovered \n", + "\n", + " participant_id \n", + "0 P110465 \n", + "1 P110465 \n", + "2 P110465 \n", + "3 P110465 \n", + "4 P110465 \n", + "... ... \n", + "31732 P992022 \n", + "31733 P992022 \n", + "31734 P992022 \n", + "31735 P992022 \n", + "31736 P992022 \n", + "\n", + "[31737 rows x 21 columns]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Read CSV data\n", + "data_path = \"/workspaces/hiaac-m4/data/Stanford-COVID/processed/windowed_16_overlap_8_df_scaled.csv\"\n", + "df = pd.read_csv(data_path)\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CovidUserAnomalyDataModule (Data=/workspaces/hiaac-m4/data/Stanford-COVID/processed/windowed_16_overlap_8_df_scaled.csv, 1 participant selected)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dm = CovidUserAnomalyDataModule(\n", + " data_path,\n", + " participants=[\"P992022\"],\n", + " batch_size=32,\n", + " num_workers=0,\n", + " reshape=(16, 1),\n", + ")\n", + "dm" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LSTMAutoencoder(\n", + " (backbone): _LSTMAutoEncoder(\n", + " (lstm1): LSTM(1, 128, batch_first=True)\n", + " (lstm2): LSTM(128, 64, batch_first=True)\n", + " (repeat_vector): Linear(in_features=64, out_features=1024, bias=True)\n", + " (lstm3): LSTM(64, 64, batch_first=True)\n", + " (lstm4): LSTM(64, 128, batch_first=True)\n", + " (time_distributed): Linear(in_features=128, out_features=1, bias=True)\n", + " )\n", + " (loss_fn): MSELoss()\n", + ")" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model = LSTMAutoencoder(input_shape=(16, 1))\n", + "model" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "GPU available: True (cuda), used: False\n", + "TPU available: False, using: 0 TPU cores\n", + "IPU available: False, using: 0 IPUs\n", + "HPU available: False, using: 0 HPUs\n", + "/usr/local/lib/python3.10/dist-packages/lightning/pytorch/trainer/setup.py:187: GPU available but not used. You can set it by doing `Trainer(accelerator='gpu')`.\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer = L.Trainer(max_epochs=100, devices=1, accelerator=\"cpu\")\n", + "trainer" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + " | Name | Type | Params\n", + "----------------------------------------------\n", + "0 | backbone | _LSTMAutoEncoder | 316 K \n", + "1 | loss_fn | MSELoss | 0 \n", + "----------------------------------------------\n", + "316 K Trainable params\n", + "0 Non-trainable params\n", + "316 K Total params\n", + "1.264 Total estimated model params size (MB)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "122a71df981c48c183eb2b4e7585103d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Sanity Checking: | | 0/? [00:00 anomaly_threshold else 0 for loss in losses]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
truepredictedlossanomaly_threshold
0000.0237000.374275
1000.0914130.374275
2000.0542990.374275
3000.0074860.374275
4000.0246010.374275
...............
89100.0898330.374275
90100.0515620.374275
91100.1327480.374275
92100.1586100.374275
93100.0255220.374275
\n", + "

94 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " true predicted loss anomaly_threshold\n", + "0 0 0 0.023700 0.374275\n", + "1 0 0 0.091413 0.374275\n", + "2 0 0 0.054299 0.374275\n", + "3 0 0 0.007486 0.374275\n", + "4 0 0 0.024601 0.374275\n", + ".. ... ... ... ...\n", + "89 1 0 0.089833 0.374275\n", + "90 1 0 0.051562 0.374275\n", + "91 1 0 0.132748 0.374275\n", + "92 1 0 0.158610 0.374275\n", + "93 1 0 0.025522 0.374275\n", + "\n", + "[94 rows x 4 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results_dataframe = pd.DataFrame(\n", + " {\n", + " \"true\": y_test,\n", + " \"predicted\": y_test_hat,\n", + " \"loss\": losses,\n", + " \"anomaly_threshold\": anomaly_threshold,\n", + " }\n", + ")\n", + "\n", + "results_dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1-score: 0.0\n", + "Recall: 0.0\n", + "Balanced Accuracy: 0.5\n", + "ROC AUC: 0.5\n" + ] + } + ], + "source": [ + "from sklearn.metrics import f1_score, recall_score, balanced_accuracy_score, roc_auc_score\n", + "\n", + "# Extract true and predicted labels from the results_dataframe\n", + "true_labels = results_dataframe['true']\n", + "predicted_labels = results_dataframe['predicted']\n", + "\n", + "# Calculate the F1-score\n", + "f1 = f1_score(true_labels, predicted_labels)\n", + "\n", + "# Calculate the recall\n", + "recall = recall_score(true_labels, predicted_labels)\n", + "\n", + "# Calculate the balanced accuracy\n", + "balanced_acc = balanced_accuracy_score(true_labels, predicted_labels)\n", + "\n", + "# Calculate the ROC AUC\n", + "roc_auc = roc_auc_score(true_labels, predicted_labels)\n", + "\n", + "# Print the results\n", + "print(\"F1-score:\", f1)\n", + "print(\"Recall:\", recall)\n", + "print(\"Balanced Accuracy:\", balanced_acc)\n", + "print(\"ROC AUC:\", roc_auc)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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j+zDYrEop7dF0deTdCUhWUXy5rI7Xzgol2KuJ7koOsCz+fj46JHqNbRxw3pbW1pamjZuXK/adcNdGBmQvWWrpNLb21v/+te/9N577+nnn3/OcH7v3r3q1KmTunTposOHD2vUqFEaPny45s+fb3fd5MmTVbVqVe3fv1/Dhw+XJN28eVPvvvuuFi9erDVr1mjTpk1q166dvvrqK3311VdauHChZs+erc8++8zWT0pKisaOHauDBw9q5cqVOnv2rHr06GHmlwBuqHd4UVUu5K8Qf6uKB+dW7/CiqvpwgNb/eEWStGT/L2pXJVQNSuZToUBf9ahTREXy5tL/jvILCOAqLw+K1LyoOfrPxwv0w7Fjern/C7p544a6de/p6tDwoLGYeLiZbLdPZbt27VStWjWNHDlSUVFRduemTp2qZs2a2RLFMmXK6OjRo3r77bftkr2mTZtq8ODBttfffvutUlJSNGvWLJUsWVKS1LFjRy1cuFAxMTHy8/NThQoV1KRJE23cuFGdO3eWJPXq1cvWR4kSJfTuu++qVq1aSkhIkJ+f318+S1JSkpKS/r9aFR8f7/gXBC4XlCunXosopXx5fHQjKVVnrt7Q618c077zcZKk5Qej5ePtpRceDZO/bw6dvnJTr31+VBfjqVQCrvKPTp115fJljRk9QjHR0apStZo+X71GISEhf/1mAHeV7ZJKSfr3v/+tpk2basiQIXbtx44dU5s2beza6tevr+nTpys1NVXe3t6SpJo1a2boM3fu3LaEUpJCQkIUFhZmlxyGhITYDW/v3btXo0aN0sGDB3Xt2jWlpd35dJRz586pQoUKf/kcEyZM0OjRozPxxHBnUzac+strFu+7oMX7LtyHaABk1gv9X9IL/V9ydRh4wLH5uZtr2LChWrRooWHDhv2t9+fJkydDW86cOe1eWyyWu7alJ443btxQixYtFBAQoE8++US7d+/WihUrJGV+8c+wYcMUFxdnO86fP/93HgcAALgpT1qoky0rlZI0ceJEVatWTWXLlrW1lS9fXlu3brW7buvWrSpTpoytSplVfvjhB129elUTJ05UkSJFJEl79uxxqA+r1Sqr1ZqlcQEAALhCtqxUSlLlypX1zDPP6N1337W1DR48WOvXr9fYsWP1448/asGCBZoxY0aGYfKsULRoUfn4+Oi9997T6dOn9cUXX2js2LFZfh8A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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "from sklearn.metrics import confusion_matrix\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Get the true and predicted labels from the results_dataframe\n", + "true_labels = results_dataframe['true']\n", + "predicted_labels = results_dataframe['predicted']\n", + "\n", + "# Compute the confusion matrix\n", + "cm = confusion_matrix(true_labels, predicted_labels)\n", + "\n", + "# Define the class labels\n", + "class_labels = ['Normal', 'Anomaly']\n", + "\n", + "# Plot the confusion matrix\n", + "plt.figure(figsize=(8, 6))\n", + "plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)\n", + "plt.title('Confusion Matrix')\n", + "plt.colorbar()\n", + "tick_marks = np.arange(len(class_labels))\n", + "plt.xticks(tick_marks, class_labels, rotation=45)\n", + "plt.yticks(tick_marks, class_labels)\n", + "plt.xlabel('Predicted Label')\n", + "plt.ylabel('True Label')\n", + "\n", + "# Add the values to the confusion matrix plot\n", + "thresh = cm.max() / 2.\n", + "for i in range(cm.shape[0]):\n", + " for j in range(cm.shape[1]):\n", + " plt.text(j, i, format(cm[i, j], 'd'),\n", + " horizontalalignment=\"center\",\n", + " color=\"white\" if cm[i, j] > thresh else \"black\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}