{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 5. Training an Anomaly Detection Model for Covid Anomaly Detection\n", "\n", "## Overview\n", "\n", "In this tutorial, we will train an anomaly detection model using a simple [LSTM-AutoEncoder model](https://www.medrxiv.org/content/10.1101/2021.01.08.21249474v1).\n", "Data can be obtained from [this link](https://iscteiul365-my.sharepoint.com/:u:/g/personal/oonia_iscte-iul_pt/ERZLm1ruUNpMqkSwjpqhE9wB_7loVWAC4yZWuIH2RKGOlQ?e=kD4HlI). This is a processed version of data from original Stanford dataset-Phase 2. The overall pre-processing pipeline used is illustrated in Figure below.\n", "\n", "![preprocessing](stanford_data_processing.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Data was aquired from diferent sources (Germin, FitBit, Apple Watch) and pre-processed to have a common format. In this form, data has two columns: heart rate and number of user steps in last minute. \n", "\n", "The processing pipeline is then applied to the data. The pipeline is composed of the following steps:\n", "1. Once data was standardized, the resting heart rate was extracted (``Resting Heart Rate Extractor``, in Figure). This process takes as input `min_minutes_rest` that is the number of minutes that the user has to be at rest to consider the heart rate as resting. `min_minutes_rest` variable looks at user steps and, when user steps is 0 for `min_minutes_rest` minutes, the heart rate is considered as resting. At the end of this process, we will have a new dataframe with: the date (`datetime` column) and the resting heart rate of the last minute (`RHR` column).\n", "\n", "2. The smoother process is applied to the data (`Smoother`, in Figure). This process takes as input `smooth_window_sample` that is the number of samples that we will use to smooth the data, and `sample_rate` that is the sample rate. The smoother process will apply a moving average filter to the data, with a window of `smooth_window_sample` samples. Then the data is downsampled to `sample_rate` samples per minute. This process will produce a new dataframe with the date (`datetime` column), the resting heart rate at desired sampling rate (`RHR` column).\n", "\n", "3. The second step is adding labels (`Label Adder`, in Figure). Is is also illustrated in Figure below. This process takes 3 inputs: `baseline_days`, `before_onset`, and `after_onset`. The `baseline_days` is the number of days before the onset of the symptoms that we consider as baseline (in figure below, this is 21 days). Thus, using the dataframe from last step, a new column named `baseline` is added, which is a boolean column that is True if the date is in the baseline period (21 days before onset). The `before_onset` and `after_onset` are the number of days before and after the onset of the symptoms that we consider as the anomaly period (7 days before and 21 days before, in Figure below). A new column named `anomaly` is added, which is a boolean column that is True if the date is in the anomaly period. Finnaly, we also add a `status` column,that is a metadata column for a descriptive status of the date. If can be: \n", " - `normal`: if the date is in the baseline period; \n", " - `before onset`: if the date is in the period before the onset of the symptoms; \n", " - `onset` if the date is the onset of the symptoms (day); \n", " - `after onset` if the date is in the period after the onset of the symptoms, but before the recovery; \n", " - `recovered` if the date is in the recovery period.\n", "\n", "4. Once the labels were added we normalize the data (`Standardizer` in Figure above). This process perform a Z-norm scale on the data. The Z-norm scale is calculated as: $z = \\frac{x - \\mu}{\\sigma}$, where $x$ is the value, $\\mu$ is the mean of the column and $\\sigma$ is the standard deviation of the column. An important note here is that the mean and standard deviation are calculated only for the baseline period, and then applied to the entire dataset.\n", "\n", "5. The last step is to create the sequences (`Transposer`, in Figure), that will group $n$ rows and transform it into columns (features). This process takes as input `window_size` and `overlap` parameters and creates sequences of `window_size` samples with an overlap of `overlap` samples. Thus, if we have a dataset with 100 samples, a `window_size` of 20 and an `overlap` of 0, we will have 5 sequences of 20 samples each (*i.e.* 5 rows with 20 columns). Each element of the sequence will be a column in the dataframe, numbered from 0 to 19. Thus, for example, the sequences will have columns `RHR-0`, `RHR-1`, ..., `RHR-19`, where the first row is the first 20 samples, the second row is the second 20 samples, and so on. This is useful as it is the format that the LSTM-AutoEncoder model expects as input. An important note is that we do not mix sequences from anomaly and non-anomaly periods. Thus, no label is mixed, that is, an anomaly sample only has anomaly time-steps.\n", "\n", "This will produce a dataframe (CSV file) for each user. In processed dataset, we joined all users in a single file and add a column `participant_id` to identify the user. This makes easier to work with the data in the next steps." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![labeling](anomaly_periods.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We already generated several files, with different parameters and operations of the pre-processing pipeline:\n", "* `rhr_df`: dataframe with the resting heart rate without normalization (step 4) and transposing (step 5). The `min_minutes_rest` is 12, `smooth_window_sample` is 400, `sample_rate` is 1 hour, `baseline_days` is 21, `before_onset` is 7, and `after_onset` is 21.\n", "* `rhr_df_scaled`: same as `rhr_df`, but with normalization.\n", "* `windowed_16_overlap_0_rate_10min_df`: same dataframe as `rhr_df` with the resting heart rate normalized (step 4) and transposed (step 5). The `window_size` is 16, `overlap` is 0, and `sample_rate` is 10 minutes.\n", "* `windowed_16_overlap_0_rate_10min_scaled_df`: same dataframe as `windowed_16_overlap_0_rate_10min_df`, but with normalization.\n", "\n", "**NOTE**: The files follows this naming convention: `windowed_{window_size}_overlap_{overlap}_rate_{sample_rate}_df.csv`. If sample_rate is ommited, it is, by default 1 hour.\n", "**NOTE**: The files may and with `fold_X`, where `X` is the fold number. This is used for cross-validation purposes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's import some libraries" ] }, { "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": "markdown", "metadata": {}, "source": [ "Load data and inspect" ] }, { "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": "markdown", "metadata": {}, "source": [ "Creating a [LightningDataModule](https://lightning.ai/docs/pytorch/stable/data/datamodule.html).\n", "* The first parameter is the path to the CSV file.\n", "* `participants`: is a list of participants to include in dataset. If nothing is passed, all participants in CSV are included.\n", "* `batch_size`: is the batch size to use in the dataloader.\n", "* `num_workers`: is the number of workers to use in the dataloader.\n", "* `reshape`: is the shape of the input data. For LSTM-AutoEncoder, it is `(sequence_length, num_features)`, or, in our case `(16, 1)`\n", "\n", "**NOTE**: The training data is only data where baseline is True. The test data will be only data where baseline is False." ] }, { "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": "markdown", "metadata": {}, "source": [ "Let's create the lightning model" ] }, { "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": "markdown", "metadata": {}, "source": [ "Creting Trainer" ] }, { "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": "markdown", "metadata": {}, "source": [ "Fit the model using training data from the datamodule" ] }, { "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": "markdown", "metadata": {}, "source": [ "### Visualizing Metrics and Confusion Matrix" ] }, { "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 }