From 6a3efe063f425b827206c978a250800b041b4c55 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 16 Jan 2023 11:44:09 +0100 Subject: [PATCH 01/12] initial commit anomaly detection with gradient guidance --- ...r_guidance_anomalydetection_tutorial.ipynb | 778 ++++++++++++++++++ ...fier_guidance_anomalydetection_tutorial.py | 337 ++++++++ 2 files changed, 1115 insertions(+) create mode 100644 tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb create mode 100644 tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb new file mode 100644 index 00000000..f8e67fbd --- /dev/null +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb @@ -0,0 +1,778 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "63d95da6", + "metadata": {}, + "source": [ + "# Classifier-free Guidance\n", + "\n", + "This tutorial illustrates how to use MONAI for training a denoising diffusion probabilistic model (DDPM)[1] to create synthetic 2D images using the classifier-free guidance technique [2] to perform conditioning.\n", + "\n", + "\n", + "[1] - Ho et al. \"Denoising Diffusion Probabilistic Models\" https://arxiv.org/abs/2006.11239\n", + "[2] - Ho and Salimans \"Classifier-Free Diffusion Guidance\" https://arxiv.org/abs/2207.12598\n", + "\n", + "\n", + "TODO: Add Open in Colab\n", + "\n", + "## Setup environment" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "75f2d5f3", + "metadata": {}, + "outputs": [], + "source": [ + "!python -c \"import monai\" || pip install -q \"monai-weekly[pillow, tqdm, einops]\"\n", + "!python -c \"import matplotlib\" || pip install -q matplotlib\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "6b766027", + "metadata": {}, + "source": [ + "## Setup imports" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "972ed3f3", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MONAI version: 1.1.dev2239\n", + "Numpy version: 1.23.3\n", + "Pytorch version: 1.8.0+cu111\n", + "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", + "MONAI rev id: 13b24fa92b9d98bd0dc6d5cdcb52504fd09e297b\n", + "MONAI __file__: /media/walter/Storage/Projects/GenerativeModels/venv/lib/python3.8/site-packages/monai/__init__.py\n", + "\n", + "Optional dependencies:\n", + "Pytorch Ignite version: 0.4.10\n", + "Nibabel version: 4.0.2\n", + "scikit-image version: NOT INSTALLED or UNKNOWN VERSION.\n", + "Pillow version: 9.2.0\n", + "Tensorboard version: 2.11.0\n", + "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", + "TorchVision version: 0.9.0+cu111\n", + "tqdm version: 4.64.1\n", + "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", + "psutil version: 5.9.3\n", + "pandas version: NOT INSTALLED or UNKNOWN VERSION.\n", + "einops version: 0.6.0\n", + "transformers version: NOT INSTALLED or UNKNOWN VERSION.\n", + "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", + "pynrrd version: NOT INSTALLED or UNKNOWN VERSION.\n", + "\n", + "For details about installing the optional dependencies, please visit:\n", + " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", + "\n" + ] + } + ], + "source": [ + "# Copyright 2020 MONAI Consortium\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "import os\n", + "import shutil\n", + "import tempfile\n", + "import time\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn.functional as F\n", + "from monai import transforms\n", + "from monai.apps import MedNISTDataset\n", + "from monai.config import print_config\n", + "from monai.data import CacheDataset, DataLoader\n", + "from monai.utils import first, set_determinism\n", + "from torch.cuda.amp import GradScaler, autocast\n", + "from tqdm import tqdm\n", + "\n", + "from generative.inferers import DiffusionInferer\n", + "\n", + "# TODO: Add right import reference after deployed\n", + "from generative.networks.nets import DiffusionModelUNet\n", + "from generative.schedulers import DDPMScheduler\n", + "\n", + "print_config()" + ] + }, + { + "cell_type": "markdown", + "id": "7d4ff515", + "metadata": {}, + "source": [ + "## Setup data directory" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8b4323e7", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/tmp142o2qtd\n" + ] + } + ], + "source": [ + "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", + "root_dir = tempfile.mkdtemp() if directory is None else directory\n", + "print(root_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "99175d50", + "metadata": {}, + "source": [ + "## Set deterministic training for reproducibility" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "34ea510f", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "set_determinism(42)" + ] + }, + { + "cell_type": "markdown", + "id": "fac55e9d", + "metadata": {}, + "source": [ + "## Setup MedNIST Dataset and training and validation dataloaders\n", + "In this tutorial, we will train our models on the MedNIST dataset available on MONAI\n", + "(https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset).\n", + "Here, we will use the \"Hand\" and \"HeadCT\", where our conditioning variable `class` will specify the modality." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "da1927b0", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2022-12-10 11:50:40,187 - INFO - Downloaded: /tmp/tmp142o2qtd/MedNIST.tar.gz\n", + "2022-12-10 11:50:40,255 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", + "2022-12-10 11:50:40,256 - INFO - Writing into directory: /tmp/tmp142o2qtd.\n" + ] + } + ], + "source": [ + "train_data = MedNISTDataset(root_dir=root_dir, section=\"training\", download=True, progress=False, seed=0)\n", + "train_datalist = []\n", + "for item in train_data.data:\n", + " if item[\"class_name\"] in [\"Hand\", \"HeadCT\"]:\n", + " train_datalist.append({\"image\": item[\"image\"], \"class\": 1 if item[\"class_name\"] == \"Hand\" else 2})" + ] + }, + { + "cell_type": "markdown", + "id": "6986f55c", + "metadata": {}, + "source": [ + "Here we use transforms to augment the training dataset, as usual:\n", + "\n", + "1. `LoadImaged` loads the hands images from files.\n", + "1. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n", + "1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1].\n", + "1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform.\n", + "\n", + "### Classifier-free guidance during training\n", + "\n", + "In order to use the classifier-free guidance during training time, we need to not just have the `class` variable saying the modality of the image (`1` for Hands and `2` for HeadCTs) but we also need to train the model with an \"unconditional\" class.\n", + "Here we specify the \"unconditional\" class with the value `-1` with a probability of training on unconditional being 15%. Specified in the following line using MONAI's RandLambdad:\n", + "\n", + "`transforms.RandLambdad(keys=[\"class\"], prob=0.15, func=lambda x: -1 * torch.ones_like(x))`\n", + "\n", + "Finally, our conditioning variable need to have the format (batch_size, 1, cross_attention_dim) when feeding into the model. For this reason, we use Lambdad to reshape our variables in the right format." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e3184009", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading dataset: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 15990/15990 [00:08<00:00, 1784.85it/s]\n" + ] + } + ], + "source": [ + "train_transforms = transforms.Compose(\n", + " [\n", + " transforms.LoadImaged(keys=[\"image\"]),\n", + " transforms.EnsureChannelFirstd(keys=[\"image\"]),\n", + " transforms.ScaleIntensityRanged(keys=[\"image\"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True),\n", + " transforms.RandAffined(\n", + " keys=[\"image\"],\n", + " rotate_range=[(-np.pi / 36, np.pi / 36), (-np.pi / 36, np.pi / 36)],\n", + " translate_range=[(-1, 1), (-1, 1)],\n", + " scale_range=[(-0.05, 0.05), (-0.05, 0.05)],\n", + " spatial_size=[64, 64],\n", + " padding_mode=\"zeros\",\n", + " prob=0.5,\n", + " ),\n", + " transforms.RandLambdad(keys=[\"class\"], prob=0.15, func=lambda x: -1 * torch.ones_like(x)),\n", + " transforms.Lambdad(\n", + " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", + " ),\n", + " ]\n", + ")\n", + "train_ds = CacheDataset(data=train_datalist, transform=train_transforms)\n", + "train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4, persistent_workers=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4c11b93f", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2022-12-10 11:51:08,067 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", + "2022-12-10 11:51:08,067 - INFO - File exists: /tmp/tmp142o2qtd/MedNIST.tar.gz, skipped downloading.\n", + "2022-12-10 11:51:08,068 - INFO - Non-empty folder exists in /tmp/tmp142o2qtd/MedNIST, skipped extracting.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading dataset: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1977/1977 [00:01<00:00, 1545.02it/s]\n" + ] + } + ], + "source": [ + "val_data = MedNISTDataset(root_dir=root_dir, section=\"validation\", download=True, progress=False, seed=0)\n", + "val_datalist = []\n", + "for item in val_data.data:\n", + " if item[\"class_name\"] in [\"Hand\", \"HeadCT\"]:\n", + " val_datalist.append({\"image\": item[\"image\"], \"class\": 1 if item[\"class_name\"] == \"Hand\" else 2})\n", + "\n", + "\n", + "val_transforms = transforms.Compose(\n", + " [\n", + " transforms.LoadImaged(keys=[\"image\"]),\n", + " transforms.EnsureChannelFirstd(keys=[\"image\"]),\n", + " transforms.ScaleIntensityRanged(keys=[\"image\"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True),\n", + " transforms.Lambdad(\n", + " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", + " ),\n", + " ]\n", + ")\n", + "val_ds = CacheDataset(data=val_datalist, transform=val_transforms)\n", + "val_loader = DataLoader(val_ds, batch_size=128, shuffle=False, num_workers=4, persistent_workers=True)" + ] + }, + { + "cell_type": "markdown", + "id": "7f108ebb", + "metadata": {}, + "source": [ + "### Visualisation of the training images" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4105a01f", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", + "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", + "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", + "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "batch shape: (128, 1, 64, 64)\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "check_data = first(train_loader)\n", + "print(f\"batch shape: {check_data['image'].shape}\")\n", + "image_visualisation = torch.cat(\n", + " [check_data[\"image\"][0, 0], check_data[\"image\"][1, 0], check_data[\"image\"][2, 0], check_data[\"image\"][3, 0]], dim=1\n", + ")\n", + "plt.figure(\"training images\", (12, 6))\n", + "plt.imshow(image_visualisation, vmin=0, vmax=1, cmap=\"gray\")\n", + "plt.axis(\"off\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "08428bc6", + "metadata": {}, + "source": [ + "### Define network, scheduler, optimizer, and inferer\n", + "At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using\n", + "the original DDPM scheduler containing 1000 timesteps in its Markov chain, and a 2D UNET with attention mechanisms\n", + "in the 3rd level, each with 1 attention head (`num_head_channels=64`).\n", + "\n", + "In order to pass conditioning variables with dimension of 1 (just specifying the modality of the image), we use:\n", + "\n", + "`\n", + "with_conditioning=True,\n", + "cross_attention_dim=1,\n", + "`" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "bee5913e", + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "lines_to_next_cell": 0 + }, + "outputs": [], + "source": [ + "device = torch.device(\"cuda\")\n", + "\n", + "model = DiffusionModelUNet(\n", + " spatial_dims=2,\n", + " in_channels=1,\n", + " out_channels=1,\n", + " num_channels=(64, 64, 64),\n", + " attention_levels=(False, False, True),\n", + " num_res_blocks=1,\n", + " num_head_channels=64,\n", + " with_conditioning=True,\n", + " cross_attention_dim=1,\n", + ")\n", + "model.to(device)\n", + "\n", + "scheduler = DDPMScheduler(\n", + " num_train_timesteps=1000,\n", + ")\n", + "\n", + "optimizer = torch.optim.Adam(params=model.parameters(), lr=2.5e-5)\n", + "\n", + "inferer = DiffusionInferer(scheduler)" + ] + }, + { + "cell_type": "markdown", + "id": "2a4d3ab2", + "metadata": {}, + "source": [ + "### Model training\n", + "Here, we are training our model for 75 epochs (training time: ~50 minutes)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6c0ed909", + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "lines_to_next_cell": 0 + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 100%|██████████| 125/125 [00:27<00:00, 4.61it/s, loss=0.723]\n", + "Epoch 1: 100%|██████████| 125/125 [00:27<00:00, 4.60it/s, loss=0.276]\n", + "Epoch 2: 100%|█████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0965]\n", + "Epoch 3: 100%|█████████| 125/125 [00:27<00:00, 4.62it/s, loss=0.0376]\n", + "Epoch 4: 100%|█████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0224]\n", + "Epoch 5: 100%|█████████| 125/125 [00:27<00:00, 4.47it/s, loss=0.0187]\n", + "Epoch 6: 100%|█████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0179]\n", + "Epoch 7: 100%|█████████| 125/125 [00:28<00:00, 4.44it/s, loss=0.0169]\n", + "Epoch 8: 100%|█████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0161]\n", + "Epoch 9: 100%|██████████| 125/125 [00:27<00:00, 4.50it/s, loss=0.016]\n", + "Epoch 10: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0156]\n", + "Epoch 11: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0152]\n", + "Epoch 12: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0152]\n", + "Epoch 13: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0151]\n", + "Epoch 14: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0147]\n", + "Epoch 15: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0151]\n", + "Epoch 16: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0151]\n", + "Epoch 17: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0146]\n", + "Epoch 18: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0144]\n", + "Epoch 19: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0143]\n", + "Epoch 20: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0145]\n", + "Epoch 21: 100%|████████| 125/125 [00:27<00:00, 4.53it/s, loss=0.0143]\n", + "Epoch 22: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0138]\n", + "Epoch 23: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", + "Epoch 24: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0134]\n", + "Epoch 25: 100%|████████| 125/125 [00:27<00:00, 4.49it/s, loss=0.0135]\n", + "Epoch 26: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", + "Epoch 27: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0136]\n", + "Epoch 28: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0135]\n", + "Epoch 29: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0131]\n", + "Epoch 30: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0128]\n", + "Epoch 31: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0129]\n", + "Epoch 32: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0128]\n", + "Epoch 33: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", + "Epoch 34: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0138]\n", + "Epoch 35: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0131]\n", + "Epoch 36: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0132]\n", + "Epoch 37: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0125]\n", + "Epoch 38: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0124]\n", + "Epoch 39: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0124]\n", + "Epoch 40: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0132]\n", + "Epoch 41: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0128]\n", + "Epoch 42: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0122]\n", + "Epoch 43: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0127]\n", + "Epoch 44: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0129]\n", + "Epoch 45: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0132]\n", + "Epoch 46: 100%|████████| 125/125 [00:27<00:00, 4.53it/s, loss=0.0125]\n", + "Epoch 47: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0123]\n", + "Epoch 48: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0123]\n", + "Epoch 49: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0125]\n", + "Epoch 50: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0127]\n", + "Epoch 51: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0125]\n", + "Epoch 52: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0124]\n", + "Epoch 53: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0127]\n", + "Epoch 54: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0123]\n", + "Epoch 55: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0127]\n", + "Epoch 56: 100%|█████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.012]\n", + "Epoch 57: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0126]\n", + "Epoch 58: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0121]\n", + "Epoch 59: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0126]\n", + "Epoch 60: 100%|████████| 125/125 [00:27<00:00, 4.60it/s, loss=0.0119]\n", + "Epoch 61: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0122]\n", + "Epoch 62: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0119]\n", + "Epoch 63: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0125]\n", + "Epoch 64: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0121]\n", + "Epoch 65: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0121]\n", + "Epoch 66: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0117]\n", + "Epoch 67: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0121]\n", + "Epoch 68: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0123]\n", + "Epoch 69: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0121]\n", + "Epoch 70: 100%|█████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.012]\n", + "Epoch 71: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0118]\n", + "Epoch 72: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0117]\n", + "Epoch 73: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0119]\n", + "Epoch 74: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0125]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "train completed, total time: 2074.1517136096954.\n" + ] + } + ], + "source": [ + "n_epochs = 75\n", + "val_interval = 5\n", + "epoch_loss_list = []\n", + "val_epoch_loss_list = []\n", + "\n", + "scaler = GradScaler()\n", + "total_start = time.time()\n", + "for epoch in range(n_epochs):\n", + " model.train()\n", + " epoch_loss = 0\n", + " progress_bar = tqdm(enumerate(train_loader), total=len(train_loader), ncols=70)\n", + " progress_bar.set_description(f\"Epoch {epoch}\")\n", + " for step, batch in progress_bar:\n", + " images = batch[\"image\"].to(device)\n", + " classes = batch[\"class\"].to(device)\n", + " optimizer.zero_grad(set_to_none=True)\n", + "\n", + " with autocast(enabled=True):\n", + " # Generate random noise\n", + " noise = torch.randn_like(images).to(device)\n", + "\n", + " # Get model prediction\n", + " noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes)\n", + "\n", + " loss = F.mse_loss(noise_pred.float(), noise.float())\n", + "\n", + " scaler.scale(loss).backward()\n", + " scaler.step(optimizer)\n", + " scaler.update()\n", + "\n", + " epoch_loss += loss.item()\n", + "\n", + " progress_bar.set_postfix(\n", + " {\n", + " \"loss\": epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " epoch_loss_list.append(epoch_loss / (step + 1))\n", + "\n", + " if (epoch + 1) % val_interval == 0:\n", + " model.eval()\n", + " val_epoch_loss = 0\n", + " for step, batch in enumerate(val_loader):\n", + " images = batch[\"image\"].to(device)\n", + " classes = batch[\"class\"].to(device)\n", + " with torch.no_grad():\n", + " with autocast(enabled=True):\n", + " noise = torch.randn_like(images).to(device)\n", + " noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes)\n", + " val_loss = F.mse_loss(noise_pred.float(), noise.float())\n", + "\n", + " val_epoch_loss += val_loss.item()\n", + " progress_bar.set_postfix(\n", + " {\n", + " \"val_loss\": val_epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " val_epoch_loss_list.append(val_epoch_loss / (step + 1))\n", + "\n", + "total_time = time.time() - total_start\n", + "print(f\"train completed, total time: {total_time}.\")" + ] + }, + { + "cell_type": "markdown", + "id": "a676b3fe", + "metadata": {}, + "source": [ + "### Learning curves" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f8385176", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArsAAAILCAYAAADoqVT3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABsNklEQVR4nO3deXwTdf7H8ffk6EFLkVuEolYERESrKKgccixWLu9rVwFBRfFAcFVcF11cZAEX3Z8WVlyVXV1PVNBVAQURUVEsKogKHhSkgCA3PWiTzPz+SJM2toVOm5Jp+3o+Hn00+WZm8s2nQd/55jvfMSzLsgQAAADUQa5YdwAAAACoKYRdAAAA1FmEXQAAANRZhF0AAADUWYRdAAAA1FmEXQAAANRZhF0AAADUWYRdAAAA1FmEXQAAANRZhF0AOMJef/11dejQQR06dFBOTk6suwMAdZon1h0AUHdde+21WrlypTp27Kg33ngj1t1xjEaNGqljx46SJK/XG+PeVE1RUZHeeecdLV++XF9//bV2796tgoICNWjQQG3atFGXLl10wQUXqHv37rHuKoB6zrAsy4p1JwDUTYTdumnhwoX629/+pl9++UVSMLA3b95cDRo00K5du7Rnz57wtmeccYamTp2qtm3bxqq7AOo5RnYBAJX2xBNP6NFHH5UkpaWl6bbbblPv3r2VlJQU3ub777/X008/rTfffFOrVq3SVVddpf/+979KS0uLVbcB1GPM2QUAVMrixYvDQXfAgAF64403NHDgwIigK0nt27fXtGnTNHPmTHk8Hu3atUvjx4+XaZqx6DaAeo6wC8DRsrOz9eCDD+qCCy7QaaedptNOO039+vXTvffeq7Vr1x5y302bNumhhx7SkCFDlJ6erk6dOumss87SH/7wBz3//PPy+/3l7hc6eeztt9/WDz/8oOuuu05du3bVlVdeGd7m2muvVYcOHXT//fdLkpYtW6ZRo0apR48e6ty5s3r06KE777xTGzZsKHP8ik5Qy8nJCbd/9dVXKigo0BNPPKEhQ4bo9NNP16mnnqrBgwcrMzNTRUVF5fbd5/PpmWee0cUXX6z09HR17dpVV111ld58801J0osvvhh+Djt8Pp8mT54sSWrXrp1mzJihuLi4Q+7Tt29fDRs2THFxcTr66KP166+/hh+bMGGCOnTooL59+1a4f2XrlJWVpSuvvFLp6ekaP368/vnPf4Yf37p16yH7OGLECHXo0EEZGRllHluxYoXuuOMO9enTR507d1bXrl01ZMgQPfzwwxGv5bd++eUXTZs2TRdeeKFOP/10de7cWT179tQVV1yhf/3rX9q9e/ch+wQgupjGAMCx5s+fr4kTJ6qoqEiGYahly5ayLEs5OTnKycnR/PnzNX78eN1www1l9l2yZInuuOOOcCg8+uijlZiYqG3btikrK0tZWVl655139PTTTyshIaHc58/NzdX111+vPXv2qE2bNmrYsGG52z399NOaPn26kpOTdfTRR8s0Tf3666966623tGzZMs2bN0+pqam2XntBQYGGDx+u1atXq1WrVjr66KOVk5OjH374QT/88IPWrl2rJ554ImKfwsJCjRo1Sp9//rkkqUGDBmrevLl+/PFH3XXXXfr88891wgkn2OpHyMKFC7Vt2zZJ0h133HHYoBty++2365ZbblFycnKVnvdwtm7dqokTJ8o0TbVu3VqJiYkaPHiw/vGPf0iSFi1apOuuu67cfXft2qWVK1dKki688MJwu2VZ+utf/6rnn39eUnBOcqtWrbR//359//33+v777/XSSy8pMzNTZ599dsQxV69erZEjRyo3N1eS1KJFCx199NHavXu3Vq9erdWrV2vOnDl67rnnqvy3AGAPI7sAHGnVqlW67777VFRUpIyMDC1btkzLli3Thx9+qBUrVujCCy+UaZr6+9//riVLlkTse+DAAd1zzz0qKipS+/bt9d5772nZsmVauHChVq1apXHjxkmSsrKy9NRTT1XYh7lz56pVq1b64IMP9M4775S77Zo1a/R///d/mjRpkj799FO9/fbb+uSTT/Twww+H+/Kvf/3L9ut/+OGHlZeXp3nz5oWff8WKFfrd734nSVq6dKm+/PLLiH2eeOKJcNC97rrr9Omnn2rBggVasWKF7r33Xr366qtasGCB7b5I0vLlyyVJDRs2VJ8+fSq9X2JiYo0FXUl65pln1L17d3300Ud666239NBDDyk1NVXp6emSgmG3IgsXLlQgEJBhGBo6dGi4/V//+peef/55GYahO+64QytXrtR7772nzz77TG+88YZOOeUU5ebm6pZbbtH27dsjjnn//fcrNzdXJ598st577z0tX75c77zzjj799FPNnTtXxx9/vHbt2qUHHnigZgoCoAzCLgBHmjFjhvx+v8444ww9+uijatmyZfixJk2aaPr06Tr33HMlKTyKF/Lxxx+HR9buueeeiJUAPB6PbrrpJp155pmSpHfeeafCPnzzzTeaPn26mjRpUuE23333nW6//XZdddVVEcuIDR06VGeddZYk6bPPPqvkqy7x008/6cknn1SnTp3CbUlJSZowYUL4funjBgIBvfDCC5KCKyBMmDBB8fHxkoIjkyNGjNAtt9yir776ynZfpGCol6RTTjlFHo9zvhTcsGGDpk6dWmbe8JAhQyRJX331VXjViN8KBf+uXbuqdevWkqT9+/eHR8xvvPFG3XzzzWrQoEF4n44dO+qZZ55RkyZNlJeXF/FBZu/evVq3bp0kafTo0WVWoOjSpYv+9re/6cwzz1SbNm0qnIoCILoIuwAcZ+vWrVq1apWk4JxKl6v8/1T94Q9/kBQ8+3/Tpk3h9oyMDK1du1Yffvhhheu8nnLKKZKkzZs3V9iPzp07H3bJLK/Xq6uvvrrcx0Jr6VYUtg6lf//+4QBWWunpFKWPu3btWu3du1eSdPHFF5d7zOuuuy4iuNkRmmd69NFHV2n/mtKrV69yp5dccMEF8ng8siyr3NHd7du3h99jpacwvP/++8rLy5PL5dKIESPKfc6UlJTwSPC7774bbi+9kueOHTvK3Tc9PV3//e9/NXXq1EpPBQFQPYRdAI5T+uv5E088scLtTj311PDtb775JuIxj8ejli1bVjgKGQp9hxpdq8ycyrS0tDKjiiGhr+8PHjx42OP8ViiMH+q4hYWF4bbs7Ozw7ZNPPrnc/ZKSknTGGWfY7osk5eXlSQpOS3CSiv5GTZo0UY8ePSSVP5Vh4cKFMk1T8fHxESenffHFF5Kkpk2bHnJEP/Te2759u3bt2iVJaty4sU466SRJ0tSpUzVjxoxyT1AEcGQ557soAChW+kz38s6SL89v5076/X699dZbWrRokTZs2KDdu3dr//79tvrRuHHjw26TkpJS4WMVjUhXRkUnw5U+bumRxFDgkqRmzZpVuO/xxx8fnn9rR3Jysvbu3asDBw7Y3rcmHSqQDhkyRB988IG++OIL7dixQy1atAg/FprC0Ldv34ha79y5U1LwPVjZFSt++eUXNW3aVFJwrvXIkSO1Y8cOPfnkk3ryySd1zDHHqFu3burRo4f69OlT4YcjADWDsAvAcQoKCsK327dvX6nQWPrr+QMHDuj666+PmJ/avHlztWvXLjzSu3PnznCwqcwxK2IYxmG3qQq7xy09enyor8erOo2hRYsW2rt3b8R0ESc41Ehzv3791KBBA+Xn5+vdd9/VNddcIyk4TSb03rjooosi9gm99+Lj43X88cfb7s+JJ56ohQsX6qWXXtKrr76qDRs2aOvWrZo3b57mzZunpKQkjRw5UmPGjKnWhyEAlUfYBeA4pQPZrFmzbC/bNXXq1HCYGTZsmIYPH642bdpEbPP4448rMzOz2n11itInx1W0frBUtSkVUvBr+++//17ffvutcnNza3SFhZDqXs0+MTFR/fv315tvvqmFCxeGw+6CBQtkWVbEVIeQ0HuvSZMmVb7EdVJSkkaNGqVRo0Zp8+bN+vjjj/XJJ59o+fLlysvL0+OPP66tW7dqypQp1Xp9ACqHj5UAHKf0SVB2T+4KBAJ66623JEk9e/bUfffdVyboStK+ffuq10mHadSoUfj2oS5aUHpurx29e/eWFLy4xGuvvVbp/UzT1OTJk8usAhEauT5UoI3GlInQqgyrVq0Kj+SHVuAYNGhQmTndoffer7/+GpUrvqWmpuqqq67SY489pg8//FDnn3++JOm1115jPi9whBB2AThOly5dwrcPtVSWZVllRjF3794dHr0MLS/2W6ZpVmneqpOVXjXihx9+KHeb/Px8ZWVlVen4ffv2DX9omDVr1iGvIFbas88+q+eee05XXnmlPv7443B7aKpFaIm48nz99ddV6mtp55xzjpo2bSrTNPXBBx8oJycnfOW9305hkEpODPT7/Ye8Qp/P56vwsYpG1hs2bKi//OUv4fvff/99JV4BgOoi7AJwnFatWoVXDXjhhRfCKwH81htvvKGzzz5bd911lwKBgCRFXA2tohHOp59+OmLuaelVDWqrU089NRwg33777XK3+c9//lNhLQ/H7XZr0qRJMgxDe/fu1ejRow972dv58+dr+vTpkqRzzz1X55xzTvix0Ajq/v37tWXLljL7btmyRYsXL65SX0vzeDwaOHCgJOnDDz8MH/OEE05Q586dy2zfv3//8FSGJ598ssLjPvDAA+rTp0/EOrv//ve/1atXL914440V7lc6CHOiGnBkEHYBONKdd94pl8ulrVu36oYbbogIp0VFRXrllVf0wAMPaP/+/UpOTpbb7ZYUHD1r3769pOBXxaWXJNu5c6ceeughZWZm6pZbbgm3V3W000kaNGigQYMGSQpeKvnpp58OfwDw+Xz697//rccff7zC0e7K6NGjhyZMmCDDMPTNN99o8ODBev7557Vnz56I7b777jvdcccdmjBhggKBgDp16qRHHnkk4qS70AU3JGnatGkRJyWuW7dON954Y/jvWF2hqQyffPJJ+Gp7pdfWLS05OVk333yzJOm9997TX/7yl4gpL7t379bf/vY3vfbaa9q6dWvEFJn27dtr+/bt+vjjjzVp0qQya+1u2bIlfFGQJk2aVOtvAaDyOEENQI3Lzs6uMFyUdtVVV4Uv0HDGGWdoypQpmjhxolatWqUBAwaodevW8nq9+uWXX8JTFc455xzdfffdEce58847dfPNN+vAgQO69NJLdcwxx8gwDG3dulVut1vTp09Xenq6Zs+eLZ/Pp5tuukmpqanKzMxUWlpa9AtwhNx5551auXKltmzZounTp2vWrFk6+uij9csvvyg3N1e33XabLMsKX1K4KkaMGKHU1FQ99NBD2rJlix588EFNnjxZzZs3V0pKinbu3BkOv263W5dddpnuueeeMqOYZ5xxhnr37q1ly5Zp0aJF+vDDD9W6dWsVFBRoy5YtOuWUU/THP/5Rw4YNq1ZNpOCod9u2bfXzzz9r5cqVZS4P/FvXX3+9tmzZopdeekkvvviiXn31VbVu3VpFRUXasWNHeHR29OjRuuCCC8L7nXPOObrpppv0xBNP6IUXXtBLL70Ursv+/fvDy+M1aNBAM2bMiPgWAkDNIewCqHGFhYXhy6geym+XArv44ot1xhln6D//+Y9WrFihbdu2yefz6aijjlL37t114YUX6oILLiizTNd5552nZ555Rk8++aS+/vpr/fLLL2rSpImGDBmikSNHhq9s9uCDDyozM1M7duyQZVmOu2CCXc2bN9drr72mWbNmaenSpdq+fbv27dun0047Tdddd5169Oihxx57TFL1lkzr16+fevbsqXfeeUcffvihvvnmG+3atUu7du1ScnKy0tPT1b17d1100UU67rjjKjxOZmamZs+erYULF2rz5s3hkdKxY8fquuuu048//ljlPv7WkCFDNHPmTEnBUeVWrVpVuK3L5dKkSZM0cOBAvfzyy/ryyy+1detWGYYRnmJz9dVX67TTTiuz77hx43Teeedp/vz5WrFihbZv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9BctWPCWJOn222+SJM2d+6a2bduq22+/Sdddd4NGjRodPubq1V/pueee0TffrFVBQb4aN26iM8/sppEjb9TRR7eK6HtyckNNnTpDmZmP6quvvlBRkU/HH5+m0aNv0emnd61EtaOPsOsAXo9bEieoAQDgVAMHDtG//jVLb775erlh9403XldCQoIGDhyqSZP+rI8+WqZhw0bqrLO66+DBg3r55Rc0bdpkFRUV2ZrmsGPHdt1++81KSEjQ2LF/1LHHttXmzdmaMOGPKiryKTGxZNtfftmmMWNuUHJysm67bZxatTpGmzf/rCeeyNTtt9+k//znRbVo0VIjR94oj8er//1vnv74x3vVseNJatasubZt21rm+T/99BPdc884nXhiB40ff7eaN2+hjRs36KmnntBnn63Qv//9gho3bhLePj8/T3/841hlZAzUpZdeqS1bNisz8x/605/+qJdfnq9GjY6yVfdoIOw6QGjOLheVAADUVl/sWKO3NryrwkCh7X1dLkOmadVAr0rEu+M1OG2ATm/RpUr7Jycna8CAC/TGG6/r669X65RTTg0/9t133+j779dpyJCLFB8fJ4/Hoyuu+L1uuOHm8DYnn3yKBg3qpwUL3rIVdufNe1X5+Xm6774H1Lt3X3k8LvXvf548nnhNnjxJjRo1Cm+7efMmdelymi699Ap163a2JOmUU05VQUGBHn10upYvX6ZLL71CrVodo2bNmkmS2rY9Vh07dqrw+TMz/6GEhAQ98sjjSkkJPtdpp52uo45qrD//+R69/PILuummW8Pbb926Rffd9xddcMFgSVJ6+hnatGmTXnzxOWVlrVS/fgMq/dqjhbDrAKFpDAHTUsA05XYxlRoAULss3rRM2/N3xLobh7T452VVDrtS8MSzN954XfPnvxYRdkMnpl1yyRWKj0/QX/86tcy+ycnJatq0mX75ZZut5/z669UyDEPdup0T0d6nTz899NCDEW1nntldZ57Zvcwxjj32OEnS9u32nnvHju3auHGDevXqEw66Ieee20tut1urVn0e0W4Yhvr27R/R1qZNqiRp3759tp4/Wgi7DlD6whJ+vyV3XAw7AwBAFfQ/trfjR3b7t+1drWOkpbXTaaedrqVLl2js2DuVktJIeXm5WrLkXZ1yyqnhObHff79Or776slat+lx79uxWUVFR+BilR2IrY9eunUpK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82JTU1N133336ZJLLlFiIuvBVRZXUAMAAIiOal9BTZISEhKUkJBQ7mPXXnttNJ6iXmHOLgAAQHRU+1pz3377raZMmRLRtm7dOl1zzTVKT0/XoEGDtHDhwuo+Tb3idhkKXcCNsAsAAFB11Qq769ev1zXXXKMXXnhBphkMZfv379fIkSOVlZWluLg4bdiwQePHj9eqVaui0uH6wDCM8Ogu0xgAAACqrlph95lnnpHf79esWbPClwueO3eudu/erd///vf67LPPtGjRIqWkpOjZZ5+NSofri9C8XUZ2AQAAqq5aYffzzz/XgAED1KtXr3Dbe++9J4/HE740b9u2bTVgwAB9+eWX1etpPRMe2SXsAgAAVFm1wu7OnTvVrl278P28vDytXbtWp556qpo0aRJub926tXbv3l2dp6p3CLsAAADVV62w63a7VVhYGL6/cuVK+f3+MpcLLigoYOkxm7wetyTm7AIAAFRHtcLuscceqxUrVoTvv/jiizIMQ+edd17Edl9//bVatmxZnaeqd0Jzdv2M7AIAAFRZtdbZHTBggB577DFdddVVcrlc+vLLL3XaaaepU6dOkqRAIKAXX3xRK1as0MiRI6PS4foiNI0hYFoKmKbcrmqvEgcAAFDvVCvsjho1SqtWrdLHH38sSWrVqpWmT58efnzjxo2aPHmyjjnmGMKuTaUvLOH3W3LHxbAzAAAAtVS1wm58fLyefvppbdy4Ufv371fHjh0VF1eSytLS0jRixAhdd911ESes4fAirqIWMBUvdwx7AwAAUDtF5XLBxx13XLnthmFowoQJ0XiKeic0Z1diRQYAAICqikrY/eWXX7Rw4UJ9++232rNnjwzDUNOmTdW5c2cNHDhQjRs3jsbT1CsRI7v+QAx7AgAAUHtVO+z++9//1owZM+T3+2VZVsRj8+fP14wZM/Tggw9q8ODB1X2qesXjYWQXAACguqoVdpctW6apU6cqMTFRF154obp06aImTZrINE3t3r1bq1at0qJFizRhwgS1bdtWXbp0iVa/67zSI7tFhF0AAIAqqVbYfe6559SoUSO98sorOvbYY8s8ftVVV+mGG27Q1VdfraeeekqPPfZYdZ6uXmHOLgAAQPVVa/HWtWvX6vzzzy836Ia0b99e559/vr744ovqPFW989vVGAAAAGBftcJubm6ujj766MNu16ZNG+3du7c6T1XvxDFnFwAAoNqqFXZTUlK0efPmw263detWpaSkVOep6h2vp2RdXS4ZDAAAUDXVCrunnnqq3n33Xa1fv77CbdatW6cFCxbotNNOq85T1TteRnYBAACqrVonqF133XX64IMPdPnll2vQoEFKT08PXylt165dysrK0qJFixQIBDRq1KiodLi+YM4uAABA9VUr7J511ll68MEH9dBDD2nevHmaP39+xOOWZSkxMVGTJ0/WGWecUZ2nqndYjQEAAKD6qn1Ricsvv1x9+vTRO++8o7Vr12rXrl3hK6idcsopGjRoEFdQqwKmMQAAAFRfVC4X3KxZMw0bNqzCx5csWaJ58+YpMzMzGk9XL3i4XDAAAEC1VesEtcratGmTlixZciSeqs5gzi4AAED1HZGwC/uYswsAAFB9hF2HYs4uAABA9RF2HYqwCwAAUH2EXYdizi4AAED1EXYdijm7AAAA1UfYdSimMQAAAFSf7XV2zz77bNtPcvDgQdv71HeEXQAAgOqzHXb37NlTpScyDKNK+9VXzNkFAACoPtthl4tDHBlul0suw5BpWfL5CLsAAABVYTvstm7duib6gXJ4PS4V+gKM7AIAAFQRJ6g5WGgqg88fiHFPAAAAaifCroOVhF1GdgEAAKqCsOtghF0AAIDqIew6WDjsMmcXAACgSgi7Dha6iprPb8qyrBj3BgAAoPZxdNidO3euBg4cqM6dO6tnz56aNm2afD5fhdvv2bNHkydPVt++fdW5c2edd955mjZtWq29qEVoZNeypIBJ2AUAALDL9tJjR8r8+fM1ceJETZgwQf369dP69es1ceJE5efna9KkSWW2N01T119/vfLz8/XQQw+pTZs2ysrK0v33369ff/1Vf//732PwKqrnt1dR87gd/dkEAADAcRwbdjMzMzVo0CCNGDFCkpSamqqdO3dq0qRJGjNmjFq2bBmx/XfffadNmzZp1qxZOuuss8L7ZGVlacGCBbIsq9Zdxc3rjryKWmIM+wIAAFAbOXKocOPGjdq8ebN69+4d0d6rVy+Zpqnly5eX2efkk09WVlZWOOiGuFwuud3uWhd0pciRXT8rMgAAANjmyJHd7OxsSVLbtm0j2lu1aiWv16sNGzYc9hh+v1/vv/++3nrrLd122222nt/lMuRyRS8cu4tHaN02pyHEed3h26Ykj8eRn02irqr1qq+olz3U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9hj00jleffVVXXjhhUpPT1efPn305z//Wbt27Qo//sMPP+j6669Xenq60tPTdcMNN+inn36KYY9jxzRNPfPMMxo8eLC6dOmibt26aezYsdqyZUt4G95jFRs5cqQ6dOignJyccBv1KtG3b1916NChzM/gwYPD21CvsnJycnTrrbfq9NNP15lnnqkxY8Zo69at4cepWVBOTk6576/Qz+uvvy6JelWahRozb948q0OHDtacOXOsn3/+2Xrvvfes7t27W/fff3+suxZze/bssUaPHm316NHDOv3006077rgj4vGffvrJ6tKli3XPPfdYP/30k5WVlWVdfvnl1uDBg62ioqIY9Tq2nnnmGatjx47W008/bW3cuNFatmyZ1atXL+vqq6+2TNO0du/ebXXv3t0aNWqUtW7dOuvrr7+2Ro8ebZ177rnWvn37Yt39I27KlCnWaaedZs2fP9/6+eefrY8++sjq16+f1bdvX6uwsJD32CHMnTvX6tSpk9W+fXtr8+bNlmXxb/K3+vTpY02dOtXasWNHxM/u3bsty6Je5dm3b5/Vp08f66abbrK+//57a/Xq1dbFF19sZWRkWIFAgJqV4vf7y7y3duzYYb3xxhtW586drU2bNlEvGwi7Nahfv37W+PHjI9pefPFFq2PHjtYvv/wSo145w3PPPWdde+211s6dO60+ffqUCbsTJkywevfubfl8vnDbTz/9ZLVv39763//+d6S7G3OmaVrnnnuuNWHChIj2l19+2Wrfvr313XffWY8//rh16qmnWnv37g0/vnfvXqtLly7WE088caS7HFM+n88677zzrMzMzIj2+fPnW+3bt7fWrFnDe6wC27dvt7p27WpNmjQpIuxSr0h9+vSxHnvssQofp15lZWZmWueee65VUFAQbsvOzrYWLFhgHTx4kJodRlFRkZWRkWE9/PDDlmXxHrODaQw1ZOPGjdq8ebN69+4d0d6rVy+Zpqnly5fHqGfO0Lt3b82ZM0dNmzYt9/GPPvpIPXr0kMfjCbelpaWpTZs2+vDDD49UNx3DMAy99dZb+tOf/hTR3rJlS0lSXl6ePvroI6Wnp6tRo0bhxxs1aqRTTz213tXM4/Fo6dKluuWWWyLaXa7gf/K8Xi/vsQo8+OCDSk9P1/nnnx/RTr3soV5lvfvuu+rfv78SEhLCbccdd5wyMjIUHx9PzQ7jP//5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8tSoUSOlp6dr1KhR5Y4a//LLL5o1a5Y+/PBD7dy5U40aNVKfPn1066236uijjy63bytWrNBjjz2mdevWSZJOPvlkjR8/XqeffnrEdl9++aWeeuoprV69Wnv27FFycrJSU1M1ZMgQXXPNNXK73dUvFABUAmEXABzqs88+04IFC/S73/1OPXr0UE5Ojt58803ddNNNmjVrlvr27Rve9rHHHtPMmTPVuHFjDRgwQC1bttTPP/+sRYsW6f3339eMGTN0wQUXhLffsGGDrrrqKhUUFGjo0KFq06aNfvzxR7322mt67733NHfuXLVt2zaiP5988omeeeYZDR06VL1799aKFSv06aefatSoUXrnnXfUqlUrSVJWVpaGDx+uhIQEXXDBBWrdurUOHDigZcuWacqUKVq9erUeeeSRI1NEALAAADVi8+bNVvv27a2zzjrL1n733HOP1b59e6tDhw7W8uXLIx579dVXrfbt21sZGRnhtm+++cbq0KGDddZZZ1nbtm2L2P7zzz+3OnbsaJ155plWfn5+uP2SSy6x2rdvX+b4//3vf6327dtbo0ePDrddc801Vvv27a3u3btb2dnZ4XbTNK0RI0ZY7du3t+bMmRNuHz9+vNW+fXvrgw8+iDh2UVGRdfXVV1tnnHGGtXXrVls1AYCqYmQXAGqYZVnKyck55DZer1ctW7aMaEtPT1ePHj0i2i666CJNmzZNGzZs0ObNm5Wamqr58+fLsiz9/ve/LzP9oGvXrurWrZtWrFih5cuXa8CAAfruu++0du1adezYsczxL730Um3ZskUtWrQo08crrrhCxx13XPi+YRjq2bOnPvnkE23ZsiXcvm/fPkkqM1XB6/Xq2WeflcfD/3oAHDn8FwcAati+ffsOuwRZx44d9cYbb0S0/XYerBQMkMcff7y++uorbdiwQampqVq7dm2F20tSly5dtGLFCn3zzTcaMGBAeL3ck046qcy2CQkJuvvuu8s9TufOncu0paSkSJJyc3PDbX369NHy5cs1fvx4jRo1Sv3799cJJ5wgSQRdAEcc/9UBgBqWlJSk6dOnH3Kb5OTkMm1NmzYtd9ujjjpKkrR//35J0q5duw65fZMmTSRJe/bsidg+FFQrq7ztXa7gtYksywq3/eEPf1BeXp6eeOIJPfLII3rkkUfUvHlz9ejRQxdffLG6detm63kBoDoIuwBQw7xer/r37297v1CQ/C3TNCUFlzSTgtMJpMjAWd72oe1Cxy0qKrLdp8q68cYbdfXVV+uDDz7QRx99pI8//ljz5s3TvHnzdPnll2vy5Mk19twAUBqXCwYAhwqNxP7W3r17JZWM5IZ+h0Zsf2v37t3lbh9qrykNGzbUkCFDNG3aNC1fvlxPP/20WrZsqblz52rFihU1+twAEELYBQCHWr16dZk2v9+v7OxsSVKbNm0kSaeccookadWqVeUe54svvojYLvQ7KytLgUAgYlvTNHXHHXfo9ttvl9/vr1K/9+3bF3HCmhQcVe7Ro4euv/56SdI333xTpWMDgF2EXQBwqM8++0yff/55RNvrr7+uAwcOqFOnTuHVGy699FK5XC699NJL2rZtW8T2H3/8sVatWqWWLVuGV17o0KGDTj75ZO3atUuvv/56xPbvvPOOFixYoLy8vCqdTLZnzx6dc845uu6668KrMpQWCrmhNXkBoKYxZxcAalhhYaGefvrpw24XHx+va665Jnz/wgsv1I033qh+/frp+OOPD19Uwu1266677gpvd+KJJ+qOO+7QI488oksuuUQZGRlq2rSpNmzYoPfee08JCQmaNm2avF5veJ+HHnpI1157re6//3599tlnOuGEE/TTTz9pwYIFSk5OrnBFhsNp3Lixbr75Zj3++OMaNGiQ+vfvr6OPPloFBQX64osvtHLlSp188sn63e9+V6XjA4BdhF0AqGEFBQWHXY1BCs5xLR12O3furEsvvVSZmZlaunSpTNNUly5ddNttt+mcc86J2Hf06NFq166dnnvuOb311lsqKChQkyZNlJGREX6stJNOOknz5s1TZmamPvnkEy1cuFCNGjXSoEGDdOutt5a5epodt956qzp06KBXXnlFixcv1t69e+X1enXcccfp9ttv1/DhwxUXF1fl4wOAHYZV0em7AICYmDBhgubNm6eJEydGhF8AgH3M2QUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdxQlqAAAAqLMY2QUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ31/+1MPX1gW6EJAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.style.use(\"seaborn-v0_8\")\n", + "plt.title(\"Learning Curves\", fontsize=20)\n", + "plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", + "plt.plot(\n", + " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", + " val_epoch_loss_list,\n", + " color=\"C1\",\n", + " linewidth=2.0,\n", + " label=\"Validation\",\n", + ")\n", + "plt.yticks(fontsize=12)\n", + "plt.xticks(fontsize=12)\n", + "plt.xlabel(\"Epochs\", fontsize=16)\n", + "plt.ylabel(\"Loss\", fontsize=16)\n", + "plt.legend(prop={\"size\": 14})\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "0cd48c2d", + "metadata": {}, + "source": [ + "### Sampling process with classifier-free guidance\n", + "In order to sample using classifier-free guidance, for each step of the process we need to have 2 elements, one generated conditioned in the desired class (here we want to condition on Hands `=1`) and one using the unconditional class (`=-1`).\n", + "Instead using directly the predicted class in every step, we use the unconditional plus the direction vector pointing to the condition that we want (`noise_pred_text - noise_pred_uncond`). The effect of the condition is defined by the `guidance_scale` defining the influence of our direction vector." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f71e4924", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1000/1000 [00:14<00:00, 71.08it/s]\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model.eval()\n", + "guidance_scale = 7.0\n", + "conditioning = torch.cat([-1 * torch.ones(1, 1, 1).float(), torch.ones(1, 1, 1).float()], dim=0).to(device)\n", + "\n", + "noise = torch.randn((1, 1, 64, 64))\n", + "noise = noise.to(device)\n", + "scheduler.set_timesteps(num_inference_steps=1000)\n", + "progress_bar = tqdm(scheduler.timesteps)\n", + "for t in progress_bar:\n", + " with autocast(enabled=True):\n", + " with torch.no_grad():\n", + " noise_input = torch.cat([noise] * 2)\n", + " model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device), context=conditioning)\n", + " noise_pred_uncond, noise_pred_text = model_output.chunk(2)\n", + " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", + "\n", + " noise, _ = scheduler.step(noise_pred, t, noise)\n", + "\n", + "plt.style.use(\"default\")\n", + "plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap=\"gray\")\n", + "plt.tight_layout()\n", + "plt.axis(\"off\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3483b097", + "metadata": {}, + "source": [ + "### Cleanup data directory\n", + "\n", + "Remove directory if a temporary was used." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b00d4f9a", + "metadata": {}, + "outputs": [], + "source": [ + "if directory is None:\n", + " shutil.rmtree(root_dir)" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "py:percent,ipynb" + }, + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py new file mode 100644 index 00000000..ad765369 --- /dev/null +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py @@ -0,0 +1,337 @@ +# --- +# jupyter: +# jupytext: +# formats: py:percent,ipynb +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.14.1 +# kernelspec: +# display_name: Python 3 +# language: python +# name: python3 +# --- + +# %% [markdown] +# # Anomaly Detection with classifier guidance +# +# This tutorial illustrates how to use MONAI for training a 2D gradient-guided anomaly detection using DDIMs [1]. +# +# +# [1] - Wolleb et al. "Diffusion Models for Medical Anomaly Detection" https://arxiv.org/abs/2203.04306 + +# +# TODO: Add Open in Colab +# +# ## Setup environment + +# %% +# !python -c "import monai" || pip install -q "monai-weekly[pillow, tqdm, einops]" +# !python -c "import matplotlib" || pip install -q matplotlib +# %matplotlib inline + +# %% [markdown] +# ## Setup imports + +# %% jupyter={"outputs_hidden": false} +# Copyright 2020 MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import shutil +import tempfile +import time + +import matplotlib.pyplot as plt +import numpy as np +import torch +import torch.nn.functional as F +from monai import transforms +from monai.apps import MedNISTDataset +from monai.config import print_config +from monai.data import CacheDataset, DataLoader +from monai.utils import first, set_determinism +from torch.cuda.amp import GradScaler, autocast +from tqdm import tqdm + +from generative.inferers import DiffusionInferer + +# TODO: Add right import reference after deployed +from generative.networks.nets import DiffusionModelUNet +from generative.schedulers import DDPMScheduler + +print_config() + +# %% [markdown] +# ## Setup data directory + +# %% jupyter={"outputs_hidden": false} +directory = os.environ.get("MONAI_DATA_DIRECTORY") +root_dir = tempfile.mkdtemp() if directory is None else directory +print(root_dir) + +# %% [markdown] +# ## Set deterministic training for reproducibility + +# %% jupyter={"outputs_hidden": false} +set_determinism(42) + +# %% [markdown] +# ## Setup MedNIST Dataset and training and validation dataloaders +# In this tutorial, we will train our models on the MedNIST dataset available on MONAI +# (https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset). +# Here, we will use the "Hand" and "HeadCT", where our conditioning variable `class` will specify the modality. + +# %% jupyter={"outputs_hidden": false} +train_data = MedNISTDataset(root_dir=root_dir, section="training", download=True, progress=False, seed=0) +train_datalist = [] +for item in train_data.data: + if item["class_name"] in ["Hand", "HeadCT"]: + train_datalist.append({"image": item["image"], "class": 1 if item["class_name"] == "Hand" else 2}) + +# %% [markdown] +# Here we use transforms to augment the training dataset, as usual: +# +# 1. `LoadImaged` loads the hands images from files. +# 1. `EnsureChannelFirstd` ensures the original data to construct "channel first" shape. +# 1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1]. +# 1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform. +# +# ### Classifier-free guidance during training +# +# In order to use the classifier-free guidance during training time, we need to not just have the `class` variable saying the modality of the image (`1` for Hands and `2` for HeadCTs) but we also need to train the model with an "unconditional" class. +# Here we specify the "unconditional" class with the value `-1` with a probability of training on unconditional being 15%. Specified in the following line using MONAI's RandLambdad: +# +# `transforms.RandLambdad(keys=["class"], prob=0.15, func=lambda x: -1 * torch.ones_like(x))` +# +# Finally, our conditioning variable need to have the format (batch_size, 1, cross_attention_dim) when feeding into the model. For this reason, we use Lambdad to reshape our variables in the right format. + +# %% jupyter={"outputs_hidden": false} +train_transforms = transforms.Compose( + [ + transforms.LoadImaged(keys=["image"]), + transforms.EnsureChannelFirstd(keys=["image"]), + transforms.ScaleIntensityRanged(keys=["image"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True), + transforms.RandAffined( + keys=["image"], + rotate_range=[(-np.pi / 36, np.pi / 36), (-np.pi / 36, np.pi / 36)], + translate_range=[(-1, 1), (-1, 1)], + scale_range=[(-0.05, 0.05), (-0.05, 0.05)], + spatial_size=[64, 64], + padding_mode="zeros", + prob=0.5, + ), + transforms.RandLambdad(keys=["class"], prob=0.15, func=lambda x: -1 * torch.ones_like(x)), + transforms.Lambdad( + keys=["class"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0) + ), + ] +) +train_ds = CacheDataset(data=train_datalist, transform=train_transforms) +train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4, persistent_workers=True) + +# %% jupyter={"outputs_hidden": false} +val_data = MedNISTDataset(root_dir=root_dir, section="validation", download=True, progress=False, seed=0) +val_datalist = [] +for item in val_data.data: + if item["class_name"] in ["Hand", "HeadCT"]: + val_datalist.append({"image": item["image"], "class": 1 if item["class_name"] == "Hand" else 2}) + + +val_transforms = transforms.Compose( + [ + transforms.LoadImaged(keys=["image"]), + transforms.EnsureChannelFirstd(keys=["image"]), + transforms.ScaleIntensityRanged(keys=["image"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True), + transforms.Lambdad( + keys=["class"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0) + ), + ] +) +val_ds = CacheDataset(data=val_datalist, transform=val_transforms) +val_loader = DataLoader(val_ds, batch_size=128, shuffle=False, num_workers=4, persistent_workers=True) + +# %% [markdown] +# ### Visualisation of the training images + +# %% jupyter={"outputs_hidden": false} +check_data = first(train_loader) +print(f"batch shape: {check_data['image'].shape}") +image_visualisation = torch.cat( + [check_data["image"][0, 0], check_data["image"][1, 0], check_data["image"][2, 0], check_data["image"][3, 0]], dim=1 +) +plt.figure("training images", (12, 6)) +plt.imshow(image_visualisation, vmin=0, vmax=1, cmap="gray") +plt.axis("off") +plt.tight_layout() +plt.show() + +# %% [markdown] +# ### Define network, scheduler, optimizer, and inferer +# At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using +# the original DDPM scheduler containing 1000 timesteps in its Markov chain, and a 2D UNET with attention mechanisms +# in the 3rd level, each with 1 attention head (`num_head_channels=64`). +# +# In order to pass conditioning variables with dimension of 1 (just specifying the modality of the image), we use: +# +# ` +# with_conditioning=True, +# cross_attention_dim=1, +# ` + +# %% jupyter={"outputs_hidden": false} +device = torch.device("cuda") + +model = DiffusionModelUNet( + spatial_dims=2, + in_channels=1, + out_channels=1, + num_channels=(64, 64, 64), + attention_levels=(False, False, True), + num_res_blocks=1, + num_head_channels=64, + with_conditioning=True, + cross_attention_dim=1, +) +model.to(device) + +scheduler = DDPMScheduler( + num_train_timesteps=1000, +) + +optimizer = torch.optim.Adam(params=model.parameters(), lr=2.5e-5) + +inferer = DiffusionInferer(scheduler) +# %% [markdown] +# ### Model training +# Here, we are training our model for 75 epochs (training time: ~50 minutes). + +# %% jupyter={"outputs_hidden": false} +n_epochs = 75 +val_interval = 5 +epoch_loss_list = [] +val_epoch_loss_list = [] + +scaler = GradScaler() +total_start = time.time() +for epoch in range(n_epochs): + model.train() + epoch_loss = 0 + progress_bar = tqdm(enumerate(train_loader), total=len(train_loader), ncols=70) + progress_bar.set_description(f"Epoch {epoch}") + for step, batch in progress_bar: + images = batch["image"].to(device) + classes = batch["class"].to(device) + optimizer.zero_grad(set_to_none=True) + + with autocast(enabled=True): + # Generate random noise + noise = torch.randn_like(images).to(device) + + # Get model prediction + noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes) + + loss = F.mse_loss(noise_pred.float(), noise.float()) + + scaler.scale(loss).backward() + scaler.step(optimizer) + scaler.update() + + epoch_loss += loss.item() + + progress_bar.set_postfix( + { + "loss": epoch_loss / (step + 1), + } + ) + epoch_loss_list.append(epoch_loss / (step + 1)) + + if (epoch + 1) % val_interval == 0: + model.eval() + val_epoch_loss = 0 + for step, batch in enumerate(val_loader): + images = batch["image"].to(device) + classes = batch["class"].to(device) + with torch.no_grad(): + with autocast(enabled=True): + noise = torch.randn_like(images).to(device) + noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes) + val_loss = F.mse_loss(noise_pred.float(), noise.float()) + + val_epoch_loss += val_loss.item() + progress_bar.set_postfix( + { + "val_loss": val_epoch_loss / (step + 1), + } + ) + val_epoch_loss_list.append(val_epoch_loss / (step + 1)) + +total_time = time.time() - total_start +print(f"train completed, total time: {total_time}.") +# %% [markdown] +# ### Learning curves + +# %% jupyter={"outputs_hidden": false} +plt.style.use("seaborn-v0_8") +plt.title("Learning Curves", fontsize=20) +plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color="C0", linewidth=2.0, label="Train") +plt.plot( + np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)), + val_epoch_loss_list, + color="C1", + linewidth=2.0, + label="Validation", +) +plt.yticks(fontsize=12) +plt.xticks(fontsize=12) +plt.xlabel("Epochs", fontsize=16) +plt.ylabel("Loss", fontsize=16) +plt.legend(prop={"size": 14}) +plt.show() + +# %% [markdown] +# ### Sampling process with classifier-free guidance +# In order to sample using classifier-free guidance, for each step of the process we need to have 2 elements, one generated conditioned in the desired class (here we want to condition on Hands `=1`) and one using the unconditional class (`=-1`). +# Instead using directly the predicted class in every step, we use the unconditional plus the direction vector pointing to the condition that we want (`noise_pred_text - noise_pred_uncond`). The effect of the condition is defined by the `guidance_scale` defining the influence of our direction vector. + +# %% jupyter={"outputs_hidden": false} +model.eval() +guidance_scale = 7.0 +conditioning = torch.cat([-1 * torch.ones(1, 1, 1).float(), torch.ones(1, 1, 1).float()], dim=0).to(device) + +noise = torch.randn((1, 1, 64, 64)) +noise = noise.to(device) +scheduler.set_timesteps(num_inference_steps=1000) +progress_bar = tqdm(scheduler.timesteps) +for t in progress_bar: + with autocast(enabled=True): + with torch.no_grad(): + noise_input = torch.cat([noise] * 2) + model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device), context=conditioning) + noise_pred_uncond, noise_pred_text = model_output.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + noise, _ = scheduler.step(noise_pred, t, noise) + +plt.style.use("default") +plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap="gray") +plt.tight_layout() +plt.axis("off") +plt.show() + +# %% [markdown] +# ### Cleanup data directory +# +# Remove directory if a temporary was used. + +# %% +if directory is None: + shutil.rmtree(root_dir) From 4d9c30d8fcd52d29489c33c91664634386c90049 Mon Sep 17 00:00:00 2001 From: SANCHES-Pedro Date: Tue, 17 Jan 2023 15:33:16 +0000 Subject: [PATCH 02/12] brats 2d healthy/unhealthy loader --- .../anomaly_detection/load_2d_brats.ipynb | 433 ++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 tutorials/anomaly_detection/load_2d_brats.ipynb diff --git a/tutorials/anomaly_detection/load_2d_brats.ipynb b/tutorials/anomaly_detection/load_2d_brats.ipynb new file mode 100644 index 00000000..2e8b244a --- /dev/null +++ b/tutorials/anomaly_detection/load_2d_brats.ipynb @@ -0,0 +1,433 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "63d95da6", + "metadata": {}, + "source": [ + "# Diff-SCM\n", + "\n", + "This tutorial illustrates how to load the 2D BRATS dataset.\n", + "\n", + "\n", + "## Setup environment" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "75f2d5f3", + "metadata": {}, + "outputs": [], + "source": [ + "!python -c \"import monai\" || pip install -q \"monai-weekly[pillow, tqdm, einops]\"\n", + "!python -c \"import matplotlib\" || pip install -q matplotlib\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "6b766027", + "metadata": {}, + "source": [ + "## Setup imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "972ed3f3", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MONAI version: 1.1.dev2247\n", + "Numpy version: 1.20.0\n", + "Pytorch version: 1.13.0+cu117\n", + "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", + "MONAI rev id: a201cb93d8fb49e6c7070fa22d86e6582c8adb2a\n", + "MONAI __file__: /remote/rds/users/s2086085/miniconda3/envs/torch_gpu/lib/python3.8/site-packages/monai/__init__.py\n", + "\n", + "Optional dependencies:\n", + "Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.\n", + "Nibabel version: 4.0.2\n", + "scikit-image version: 0.17.2\n", + "Pillow version: 8.1.1\n", + "Tensorboard version: 2.8.0\n", + "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", + "TorchVision version: 0.8.2\n", + "tqdm version: 4.59.0\n", + "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", + "psutil version: 5.8.0\n", + "pandas version: 1.2.0\n", + "einops version: 0.4.1\n", + "transformers version: 4.19.4\n", + "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", + "pynrrd version: NOT INSTALLED or UNKNOWN VERSION.\n", + "\n", + "For details about installing the optional dependencies, please visit:\n", + " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", + "\n" + ] + } + ], + "source": [ + "# Copyright 2020 MONAI Consortium\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "import os\n", + "import shutil\n", + "import tempfile\n", + "import time\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn.functional as F\n", + "from monai import transforms\n", + "from monai.apps import DecathlonDataset\n", + "from monai.config import print_config\n", + "from monai.data import CacheDataset, DataLoader\n", + "from monai.utils import first, set_determinism\n", + "from torch.cuda.amp import GradScaler, autocast\n", + "from tqdm import tqdm\n", + "\n", + "from generative.inferers import DiffusionInferer\n", + "\n", + "# TODO: Add right import reference after deployed\n", + "from generative.networks.nets import DiffusionModelUNet\n", + "from generative.networks.schedulers import DDPMScheduler\n", + "\n", + "print_config()" + ] + }, + { + "cell_type": "markdown", + "id": "7d4ff515", + "metadata": {}, + "source": [ + "## Setup data directory" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "8b4323e7", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/tmp6l7agkii\n" + ] + } + ], + "source": [ + "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", + "root_dir = tempfile.mkdtemp() if directory is None else directory\n", + "print(root_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "99175d50", + "metadata": {}, + "source": [ + "## Set deterministic training for reproducibility" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "34ea510f", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "set_determinism(42)" + ] + }, + { + "cell_type": "markdown", + "id": "fac55e9d", + "metadata": {}, + "source": [ + "## Setup MedNIST Dataset and training and validation dataloaders\n", + "In this tutorial, we will train our models on the MedNIST dataset available on MONAI\n", + "(https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset).\n", + "Here, we will use the \"Hand\" and \"HeadCT\", where our conditioning variable `class` will specify the modality." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5c29c6a2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/remote/rds/users/s2086085/miniconda3/envs/torch_gpu/lib/python3.8/site-packages/monai/utils/deprecate_utils.py:107: FutureWarning: : Class `AddChannel` has been deprecated since version 0.8. please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.\n", + " warn_deprecated(obj, msg, warning_category)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Image shape torch.Size([1, 64, 64, 64])\n" + ] + } + ], + "source": [ + "batch_size = 2\n", + "channel = 0 # 0 = Flair\n", + "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", + "\n", + "train_transforms = transforms.Compose(\n", + " [\n", + " transforms.LoadImaged(keys=[\"image\",\"label\"]),\n", + " transforms.EnsureChannelFirstd(keys=[\"image\",\"label\"]),\n", + " transforms.Lambdad(keys=[\"image\"], func=lambda x: x[channel, :, :, :]),\n", + " transforms.AddChanneld(keys=[\"image\"]),\n", + " transforms.EnsureTyped(keys=[\"image\",\"label\"]),\n", + " transforms.Orientationd(keys=[\"image\",\"label\"], axcodes=\"RAS\"),\n", + " transforms.Spacingd(\n", + " keys=[\"image\",\"label\"],\n", + " pixdim=(3.0, 3.0, 2.0),\n", + " mode=(\"bilinear\", \"nearest\"),\n", + " ),\n", + " transforms.CenterSpatialCropd(keys=[\"image\",\"label\"], roi_size=(64, 64, 64)),\n", + " transforms.ScaleIntensityRangePercentilesd(keys=\"image\", lower=0, upper=99.5, b_min=0, b_max=1),\n", + " transforms.CopyItemsd(keys=[\"label\"], times=1, names=[\"slice_label\"]),\n", + " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", + " ]\n", + ")\n", + "train_ds = DecathlonDataset(\n", + " root_dir=root_dir,\n", + " task=\"Task01_BrainTumour\",\n", + " section=\"training\", # validation\n", + " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", + " num_workers=4,\n", + " download=False, # Set download to True if the dataset hasnt been downloaded yet\n", + " seed=0,\n", + " transform=train_transforms,\n", + ")\n", + "nb_3D_images_to_mix = 2\n", + "train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", + "print(f'Image shape {train_ds[0][\"image\"].shape}')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "16e750a6", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Dict\n", + "def get_batched_2d_axial_slices(data : Dict):\n", + " images_3D = data['image']\n", + " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1])\n", + " slice_label = data['slice_label']\n", + " #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float()\n", + " slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze()\n", + " return batched_2d_slices, slice_label" + ] + }, + { + "cell_type": "markdown", + "id": "7f108ebb", + "metadata": {}, + "source": [ + "### Visualisation of the training images" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "310b925c", + "metadata": {}, + "outputs": [], + "source": [ + "check_data = first(train_loader_3D)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "4105a01f", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Batch shape: torch.Size([128, 1, 64, 64])\n", + "Slices class: tensor([0., 0., 1., 0.])\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data)\n", + "idx = list(torch.randperm(batched_2d_slices.shape[0]))\n", + "slices = [0,30,45,63]\n", + "print(f\"Batch shape: {batched_2d_slices.shape}\")\n", + "print(f\"Slices class: {slice_label[idx][slices].view(-1)}\")\n", + "image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze()\n", + "plt.figure(\"training images\", (12, 6))\n", + "plt.imshow(image_visualisation, vmin=0, vmax=1, cmap=\"gray\")\n", + "plt.axis(\"off\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 200, + "id": "21e0c944", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([128])" + ] + }, + "execution_count": 200, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "slice_label.shape" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "2ac96cc9", + "metadata": {}, + "source": [ + "## Check Distribution of Healthy / Unhealthy" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "1114650d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(torch.Size([2, 1, 64, 64]), torch.Size([2]))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data)\n", + "idx = list(torch.randperm(batched_2d_slices.shape[0]))\n", + "subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))#\n", + "a,b = next(subset_2D)\n", + "a.shape, b.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5633a8c8", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(slice_label.view(-1).numpy(),bins = 5);\n", + "plt.title(\"Distribution of slices with and without tumour \\n 0 = no tumour, 1 = tumour\");" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "py:percent,ipynb" + }, + "kernelspec": { + "display_name": "torch_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.8.2" + }, + "vscode": { + "interpreter": { + "hash": "a7e6f8385898884a13cbe220eefefb32cba5012927a94186742ddc14746e4dba" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From a750e53872d10c25b4fdccd0d4d1350ac8296357 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 9 Feb 2023 17:29:10 +0100 Subject: [PATCH 03/12] first reversed loop for DDIM is implemented. Classifier guidance is on the way --- .../networks/nets/diffusion_model_encoder.py | 1661 ++++++++++++++ .../networks/nets/diffusion_model_unet.py | 243 ++ generative/networks/schedulers/ddim.py | 87 + tutorials/Untitled.ipynb | 33 + ...pm_classifier_free_guidance_tutorial.ipynb | 471 +++- ..._ddpm_classifier_free_guidance_tutorial.py | 10 +- ...r_guidance_anomalydetection_tutorial.ipynb | 2022 +++++++++++++---- ...fier_guidance_anomalydetection_tutorial.py | 569 +++-- .../Untitled.ipynb | 33 + .../Untitled1.ipynb | 6 + .../load_2d_brats.ipynb | 437 ++++ .../load_2d_brats.py | 201 ++ 12 files changed, 5092 insertions(+), 681 deletions(-) create mode 100644 generative/networks/nets/diffusion_model_encoder.py create mode 100644 tutorials/Untitled.ipynb create mode 100644 tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb create mode 100644 tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb create mode 100644 tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb create mode 100644 tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py diff --git a/generative/networks/nets/diffusion_model_encoder.py b/generative/networks/nets/diffusion_model_encoder.py new file mode 100644 index 00000000..7d87181c --- /dev/null +++ b/generative/networks/nets/diffusion_model_encoder.py @@ -0,0 +1,1661 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ========================================================================= +# Adapted from https://github.com/huggingface/diffusers +# which has the following license: +# https://github.com/huggingface/diffusers/blob/main/LICENSE + +# Copyright 2022 UC Berkeley Team and The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ========================================================================= + +import math +from typing import List, Optional, Sequence, Tuple, Union + +import torch +import torch.nn.functional as F +from monai.networks.blocks import Convolution +from monai.networks.layers.factories import Pool +from torch import nn + +__all__ = ["DiffusionModelEncoder"] + + +def zero_module(module: nn.Module) -> nn.Module: + """ + Zero out the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().zero_() + return module + + +class GEGLU(nn.Module): + """ + A variant of the gated linear unit activation function from https://arxiv.org/abs/2002.05202. + + Args: + dim_in: number of channels in the input. + dim_out: number of channels in the output. + """ + + def __init__(self, dim_in: int, dim_out: int) -> None: + super().__init__() + self.proj = nn.Linear(dim_in, dim_out * 2) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x, gate = self.proj(x).chunk(2, dim=-1) + return x * F.gelu(gate.to(dtype=torch.float32)).to(dtype=gate.dtype) + + +class FeedForward(nn.Module): + """ + A feed-forward layer. + + Args: + num_channels: number of channels in the input. + dim_out: number of channels in the output. If not given, defaults to `dim`. + mult: multiplier to use for the hidden dimension. + dropout: dropout probability to use. + """ + + def __init__(self, num_channels: int, dim_out: Optional[int] = None, mult: int = 4, dropout: float = 0.0) -> None: + super().__init__() + inner_dim = int(num_channels * mult) + dim_out = dim_out if dim_out is not None else num_channels + + self.net = nn.Sequential(GEGLU(num_channels, inner_dim), nn.Dropout(dropout), nn.Linear(inner_dim, dim_out)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.net(x) + + +class CrossAttention(nn.Module): + """ + A cross attention layer. + + Args: + query_dim: number of channels in the query. + cross_attention_dim: number of channels in the context. + num_attention_heads: number of heads to use for multi-head attention. + num_head_channels: number of channels in each head. + dropout: dropout probability to use. + """ + + def __init__( + self, + query_dim: int, + cross_attention_dim: Optional[int] = None, + num_attention_heads: int = 8, + num_head_channels: int = 64, + dropout: float = 0.0, + ) -> None: + super().__init__() + inner_dim = num_head_channels * num_attention_heads + cross_attention_dim = cross_attention_dim if cross_attention_dim is not None else query_dim + + self.scale = num_head_channels**-0.5 + self.heads = num_attention_heads + + self.to_q = nn.Linear(query_dim, inner_dim, bias=False) + self.to_k = nn.Linear(cross_attention_dim, inner_dim, bias=False) + self.to_v = nn.Linear(cross_attention_dim, inner_dim, bias=False) + + self.to_out = nn.Sequential(nn.Linear(inner_dim, query_dim), nn.Dropout(dropout)) + + def reshape_heads_to_batch_dim(self, x: torch.Tensor) -> torch.Tensor: + batch_size, seq_len, dim = x.shape + head_size = self.heads + x = x.reshape(batch_size, seq_len, head_size, dim // head_size) + x = x.permute(0, 2, 1, 3).reshape(batch_size * head_size, seq_len, dim // head_size) + return x + + def reshape_batch_dim_to_heads(self, x: torch.Tensor) -> torch.Tensor: + batch_size, seq_len, dim = x.shape + head_size = self.heads + x = x.reshape(batch_size // head_size, head_size, seq_len, dim) + x = x.permute(0, 2, 1, 3).reshape(batch_size // head_size, seq_len, dim * head_size) + return x + + def _attention(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor) -> torch.Tensor: + attention_scores = torch.matmul(query, key.transpose(-1, -2)) * self.scale + attention_probs = attention_scores.softmax(dim=-1) + # compute attention output + hidden_states = torch.matmul(attention_probs, value) + + # reshape hidden_states + hidden_states = self.reshape_batch_dim_to_heads(hidden_states) + return hidden_states + + def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: + query = self.to_q(x) + context = context if context is not None else x + key = self.to_k(context) + value = self.to_v(context) + + query = self.reshape_heads_to_batch_dim(query) + key = self.reshape_heads_to_batch_dim(key) + value = self.reshape_heads_to_batch_dim(value) + + x = self._attention(query, key, value) + + return self.to_out(x) + + +class BasicTransformerBlock(nn.Module): + """ + A basic Transformer block. + + Args: + num_channels: number of channels in the input and output. + num_attention_heads: number of heads to use for multi-head attention. + num_head_channels: number of channels in each attention head. + dropout: dropout probability to use. + cross_attention_dim: size of the context vector for cross attention. + """ + + def __init__( + self, + num_channels: int, + num_attention_heads: int, + num_head_channels: int, + dropout: float = 0.0, + cross_attention_dim: Optional[int] = None, + ) -> None: + super().__init__() + self.attn1 = CrossAttention( + query_dim=num_channels, + num_attention_heads=num_attention_heads, + num_head_channels=num_head_channels, + dropout=dropout, + ) # is a self-attention + self.ff = FeedForward(num_channels, dropout=dropout) + self.attn2 = CrossAttention( + query_dim=num_channels, + cross_attention_dim=cross_attention_dim, + num_attention_heads=num_attention_heads, + num_head_channels=num_head_channels, + dropout=dropout, + ) # is a self-attention if context is None + self.norm1 = nn.LayerNorm(num_channels) + self.norm2 = nn.LayerNorm(num_channels) + self.norm3 = nn.LayerNorm(num_channels) + + def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: + # 1. Self-Attention + x = self.attn1(self.norm1(x)) + x + + # 2. Cross-Attention + x = self.attn2(self.norm2(x), context=context) + x + + # 3. Feed-forward + x = self.ff(self.norm3(x)) + x + return x + + +class SpatialTransformer(nn.Module): + """ + Transformer block for image-like data. First, project the input (aka embedding) and reshape to b, t, d. Then apply + standard transformer action. Finally, reshape to image. + + Args: + spatial_dims: number of spatial dimensions. + in_channels: number of channels in the input and output. + num_attention_heads: number of heads to use for multi-head attention. + num_head_channels: number of channels in each attention head. + num_layers: number of layers of Transformer blocks to use. + dropout: dropout probability to use. + norm_num_groups: number of groups for the normalization. + norm_eps: epsilon for the normalization. + cross_attention_dim: number of context dimensions to use. + """ + + def __init__( + self, + spatial_dims: int, + in_channels: int, + num_attention_heads: int, + num_head_channels: int, + num_layers: int = 1, + dropout: float = 0.0, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + cross_attention_dim: Optional[int] = None, + ) -> None: + super().__init__() + self.spatial_dims = spatial_dims + self.in_channels = in_channels + inner_dim = num_attention_heads * num_head_channels + + self.norm = nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=norm_eps, affine=True) + + self.proj_in = Convolution( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=inner_dim, + strides=1, + kernel_size=1, + padding=0, + conv_only=True, + ) + + self.transformer_blocks = nn.ModuleList( + [ + BasicTransformerBlock( + num_channels=inner_dim, + num_attention_heads=num_attention_heads, + num_head_channels=num_head_channels, + dropout=dropout, + cross_attention_dim=cross_attention_dim, + ) + for _ in range(num_layers) + ] + ) + + self.proj_out = zero_module( + Convolution( + spatial_dims=spatial_dims, + in_channels=inner_dim, + out_channels=in_channels, + strides=1, + kernel_size=1, + padding=0, + conv_only=True, + ) + ) + + def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: + # note: if no context is given, cross-attention defaults to self-attention + batch = channel = height = width = depth = -1 + if self.spatial_dims == 2: + batch, channel, height, width = x.shape + if self.spatial_dims == 3: + batch, channel, height, width, depth = x.shape + + residual = x + x = self.norm(x) + x = self.proj_in(x) + + inner_dim = x.shape[1] + + if self.spatial_dims == 2: + x = x.permute(0, 2, 3, 1).reshape(batch, height * width, inner_dim) + if self.spatial_dims == 3: + x = x.permute(0, 2, 3, 4, 1).reshape(batch, height * width * depth, inner_dim) + + for block in self.transformer_blocks: + x = block(x, context=context) + + if self.spatial_dims == 2: + x = x.reshape(batch, height, width, inner_dim).permute(0, 3, 1, 2) + if self.spatial_dims == 3: + x = x.reshape(batch, height, width, depth, inner_dim).permute(0, 4, 1, 2, 3) + + x = self.proj_out(x) + return x + residual + + +class AttentionBlock(nn.Module): + """ + An attention block that allows spatial positions to attend to each other. Uses three q, k, v linear layers to + compute attention. + + Args: + spatial_dims: number of spatial dimensions. + num_channels: number of channels in the input and output. + num_head_channels: number of channels in each attention head. + norm_num_groups: number of groups to use for group norm. + norm_eps: epsilon value to use for group norm. + """ + + def __init__( + self, + spatial_dims: int, + num_channels: int, + num_head_channels: Optional[int] = None, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + ) -> None: + super().__init__() + self.spatial_dims = spatial_dims + self.num_channels = num_channels + + self.num_heads = num_channels // num_head_channels if num_head_channels is not None else 1 + self.num_head_size = num_head_channels + self.norm = nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels, eps=norm_eps, affine=True) + + # define q,k,v as linear layers + self.query = nn.Linear(num_channels, num_channels) + self.key = nn.Linear(num_channels, num_channels) + self.value = nn.Linear(num_channels, num_channels) + + self.proj_attn = nn.Linear(num_channels, num_channels) + + def transpose_for_scores(self, projection: torch.Tensor) -> torch.Tensor: + new_projection_shape = projection.size()[:-1] + (self.num_heads, -1) + # move heads to 2nd position (B, T, H * D) -> (B, T, H, D) -> (B, H, T, D) + new_projection = projection.view(new_projection_shape).permute(0, 2, 1, 3) + return new_projection + + def forward(self, x: torch.Tensor) -> torch.Tensor: + residual = x + + batch = channel = height = width = depth = -1 + if self.spatial_dims == 2: + batch, channel, height, width = x.shape + if self.spatial_dims == 3: + batch, channel, height, width, depth = x.shape + + # norm + x = self.norm(x) + + if self.spatial_dims == 2: + x = x.view(batch, channel, height * width).transpose(1, 2) + if self.spatial_dims == 3: + x = x.view(batch, channel, height * width * depth).transpose(1, 2) + + # proj to q, k, v + query_proj = self.query(x) + key_proj = self.key(x) + value_proj = self.value(x) + + # transpose + query_states = self.transpose_for_scores(query_proj) + key_states = self.transpose_for_scores(key_proj) + value_states = self.transpose_for_scores(value_proj) + + # get scores + scale = 1 / math.sqrt(math.sqrt(self.num_channels / self.num_heads)) + attention_scores = torch.matmul(query_states * scale, key_states.transpose(-1, -2) * scale) + attention_probs = torch.softmax(attention_scores.float(), dim=-1) + + # compute attention output + x = torch.matmul(attention_probs, value_states) + + x = x.permute(0, 2, 1, 3).contiguous() + new_x_shape = x.size()[:-2] + (self.num_channels,) + x = x.view(new_x_shape) + + # compute next hidden states + x = self.proj_attn(x) + + if self.spatial_dims == 2: + x = x.transpose(-1, -2).reshape(batch, channel, height, width) + if self.spatial_dims == 3: + x = x.transpose(-1, -2).reshape(batch, channel, height, width, depth) + + return x + residual + + +def get_timestep_embedding(timesteps: torch.Tensor, embedding_dim: int, max_period: int = 10000) -> torch.Tensor: + """ + Create sinusoidal timestep embeddingsfollowing the implementation in Ho et al. "Denoising Diffusion Probabilistic + Models" https://arxiv.org/abs/2006.11239. + + Args: + timesteps: a 1-D Tensor of N indices, one per batch element. + embedding_dim: the dimension of the output. + max_period: controls the minimum frequency of the embeddings. + """ + assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array" + + half_dim = embedding_dim // 2 + exponent = -math.log(max_period) * torch.arange(start=0, end=half_dim, dtype=torch.float32, device=timesteps.device) + freqs = torch.exp(exponent / half_dim) + + args = timesteps[:, None].float() * freqs[None, :] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + + # zero pad + if embedding_dim % 2 == 1: + embedding = torch.nn.functional.pad(embedding, (0, 1, 0, 0)) + + return embedding + + +class Downsample(nn.Module): + """ + Downsampling layer. + + Args: + spatial_dims: number of spatial dimensions. + num_channels: number of input channels. + use_conv: if True uses Convolution instead of Pool average to perform downsampling. + out_channels: number of output channels. + padding: controls the amount of implicit zero-paddings on both sides for padding number of points + for each dimension. + """ + + def __init__( + self, + spatial_dims: int, + num_channels: int, + use_conv: bool, + out_channels: Optional[int] = None, + padding: int = 1, + ) -> None: + super().__init__() + self.num_channels = num_channels + self.out_channels = out_channels or num_channels + self.use_conv = use_conv + if use_conv: + self.op = Convolution( + spatial_dims=spatial_dims, + in_channels=self.num_channels, + out_channels=self.out_channels, + strides=2, + kernel_size=3, + padding=padding, + conv_only=True, + ) + else: + assert self.num_channels == self.out_channels + self.op = Pool[Pool.AVG, spatial_dims](kernel_size=2, stride=2) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + assert x.shape[1] == self.num_channels + return self.op(x) + + +class Upsample(nn.Module): + """ + Upsampling layer with an optional convolution. + + Args: + spatial_dims: number of spatial dimensions. + num_channels: number of input channels. + use_conv: if True uses Convolution instead of Pool average to perform downsampling. + out_channels: number of output channels. + padding: controls the amount of implicit zero-paddings on both sides for padding number of points for each + dimension. + """ + + def __init__( + self, + spatial_dims: int, + num_channels: int, + use_conv: bool, + out_channels: Optional[int] = None, + padding: int = 1, + ) -> None: + super().__init__() + self.num_channels = num_channels + self.out_channels = out_channels or num_channels + self.use_conv = use_conv + if use_conv: + self.conv = Convolution( + spatial_dims=spatial_dims, + in_channels=self.num_channels, + out_channels=self.out_channels, + strides=1, + kernel_size=3, + padding=padding, + conv_only=True, + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + assert x.shape[1] == self.num_channels + x = F.interpolate(x, scale_factor=2.0, mode="nearest") + if self.use_conv: + x = self.conv(x) + return x + + +class ResnetBlock(nn.Module): + """ + Residual block with timestep conditioning. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + temb_channels: number of timestep embedding channels. + out_channels: number of output channels. + up: if True, performs upsampling. + down: if True, performs downsampling. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + """ + + def __init__( + self, + spatial_dims: int, + in_channels: int, + temb_channels: int, + out_channels: Optional[int] = None, + up: bool = False, + down: bool = False, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + ) -> None: + super().__init__() + self.spatial_dims = spatial_dims + self.channels = in_channels + self.emb_channels = temb_channels + self.out_channels = out_channels or in_channels + self.up = up + self.down = down + + self.norm1 = nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=norm_eps, affine=True) + self.nonlinearity = nn.SiLU() + self.conv1 = Convolution( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=self.out_channels, + strides=1, + kernel_size=3, + padding=1, + conv_only=True, + ) + + self.upsample = self.downsample = None + if self.up: + self.upsample = Upsample(spatial_dims, in_channels, use_conv=False) + elif down: + self.downsample = Downsample(spatial_dims, in_channels, use_conv=False) + + self.time_emb_proj = nn.Linear( + temb_channels, + self.out_channels, + ) + + self.norm2 = nn.GroupNorm(num_groups=norm_num_groups, num_channels=self.out_channels, eps=norm_eps, affine=True) + self.conv2 = zero_module( + Convolution( + spatial_dims=spatial_dims, + in_channels=self.out_channels, + out_channels=self.out_channels, + strides=1, + kernel_size=3, + padding=1, + conv_only=True, + ) + ) + + if self.out_channels == in_channels: + self.skip_connection = nn.Identity() + else: + self.skip_connection = Convolution( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=self.out_channels, + strides=1, + kernel_size=1, + padding=0, + conv_only=True, + ) + + def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor: + h = x + h = self.norm1(h) + h = self.nonlinearity(h) + + if self.upsample is not None: + if h.shape[0] >= 64: + x = x.contiguous() + h = h.contiguous() + x = self.upsample(x) + h = self.upsample(h) + elif self.downsample is not None: + x = self.downsample(x) + h = self.downsample(h) + + h = self.conv1(h) + + if self.spatial_dims == 2: + temb = self.time_emb_proj(self.nonlinearity(emb))[:, :, None, None] + else: + temb = self.time_emb_proj(self.nonlinearity(emb))[:, :, None, None, None] + h = h + temb + + h = self.norm2(h) + h = self.nonlinearity(h) + h = self.conv2(h) + + return self.skip_connection(x) + h + + +class DownBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int = 1, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + add_downsample: bool = True, + downsample_padding: int = 1, + ) -> None: + """ + Unet's down block containing resnet and downsamplers blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + out_channels: number of output channels. + temb_channels: number of timestep embedding channels. + num_res_blocks: number of residual blocks. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + add_downsample: if True add downsample block. + downsample_padding: padding used in the downsampling block. + """ + super().__init__() + resnets = [] + + for i in range(num_res_blocks): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsampler = Downsample( + spatial_dims=spatial_dims, + num_channels=out_channels, + use_conv=True, + out_channels=out_channels, + padding=downsample_padding, + ) + else: + self.downsampler = None + + def forward( + self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None + ) -> Tuple[torch.Tensor, List[torch.Tensor]]: + del context + output_states = [] + + for resnet in self.resnets: + hidden_states = resnet(hidden_states, temb) + output_states.append(hidden_states) + + if self.downsampler is not None: + hidden_states = self.downsampler(hidden_states) + output_states.append(hidden_states) + + return hidden_states, output_states + + +class AttnDownBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int = 1, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + add_downsample: bool = True, + downsample_padding: int = 1, + num_head_channels: int = 1, + ) -> None: + """ + Unet's down block containing resnet, downsamplers and self-attention blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + out_channels: number of output channels. + temb_channels: number of timestep embedding channels. + num_res_blocks: number of residual blocks. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + add_downsample: if True add downsample block. + downsample_padding: padding used in the downsampling block. + num_head_channels: number of channels in each attention head. + """ + super().__init__() + resnets = [] + attentions = [] + + for i in range(num_res_blocks): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + attentions.append( + AttentionBlock( + spatial_dims=spatial_dims, + num_channels=out_channels, + num_head_channels=num_head_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsampler = Downsample( + spatial_dims=spatial_dims, + num_channels=out_channels, + use_conv=True, + out_channels=out_channels, + padding=downsample_padding, + ) + else: + self.downsampler = None + + def forward( + self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None + ) -> Tuple[torch.Tensor, List[torch.Tensor]]: + del context + output_states = [] + + for resnet, attn in zip(self.resnets, self.attentions): + hidden_states = resnet(hidden_states, temb) + hidden_states = attn(hidden_states) + output_states.append(hidden_states) + + if self.downsampler is not None: + hidden_states = self.downsampler(hidden_states) + output_states.append(hidden_states) + + return hidden_states, output_states + + +class CrossAttnDownBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int = 1, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + add_downsample: bool = True, + downsample_padding: int = 1, + num_head_channels: int = 1, + transformer_num_layers: int = 1, + cross_attention_dim: Optional[int] = None, + ) -> None: + """ + Unet's down block containing resnet, downsamplers and cross-attention blocks. + + Args: + spatial_dims: number of spatial dimensions. + in_channels: number of input channels. + out_channels: number of output channels. + temb_channels: number of timestep embedding channels. + num_res_blocks: number of residual blocks. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + add_downsample: if True add downsample block. + downsample_padding: padding used in the downsampling block. + num_head_channels: number of channels in each attention head. + transformer_num_layers: number of layers of Transformer blocks to use. + cross_attention_dim: number of context dimensions to use. + """ + super().__init__() + resnets = [] + attentions = [] + + for i in range(num_res_blocks): + in_channels = in_channels if i == 0 else out_channels + resnets.append( + ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + + attentions.append( + SpatialTransformer( + spatial_dims=spatial_dims, + in_channels=out_channels, + num_attention_heads=out_channels // num_head_channels, + num_head_channels=num_head_channels, + num_layers=transformer_num_layers, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + cross_attention_dim=cross_attention_dim, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_downsample: + self.downsampler = Downsample( + spatial_dims=spatial_dims, + num_channels=out_channels, + use_conv=True, + out_channels=out_channels, + padding=downsample_padding, + ) + else: + self.downsampler = None + + def forward( + self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None + ) -> Tuple[torch.Tensor, List[torch.Tensor]]: + output_states = [] + + for resnet, attn in zip(self.resnets, self.attentions): + hidden_states = resnet(hidden_states, temb) + hidden_states = attn(hidden_states, context=context) + output_states.append(hidden_states) + + if self.downsampler is not None: + hidden_states = self.downsampler(hidden_states) + output_states.append(hidden_states) + + return hidden_states, output_states + + +class AttnMidBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + temb_channels: int, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + num_head_channels: int = 1, + ) -> None: + """ + Unet's mid block containing resnet and self-attention blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + temb_channels: number of timestep embedding channels. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + num_head_channels: number of channels in each attention head. + """ + super().__init__() + self.attention = None + + self.resnet_1 = ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + self.attention = AttentionBlock( + spatial_dims=spatial_dims, + num_channels=in_channels, + num_head_channels=num_head_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + + self.resnet_2 = ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + + def forward( + self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None + ) -> torch.Tensor: + del context + hidden_states = self.resnet_1(hidden_states, temb) + hidden_states = self.attention(hidden_states) + hidden_states = self.resnet_2(hidden_states, temb) + + return hidden_states + + +class CrossAttnMidBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + temb_channels: int, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + num_head_channels: int = 1, + transformer_num_layers: int = 1, + cross_attention_dim: Optional[int] = None, + ) -> None: + """ + Unet's mid block containing resnet and cross-attention blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + temb_channels: number of timestep embedding channels + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + num_head_channels: number of channels in each attention head. + transformer_num_layers: number of layers of Transformer blocks to use. + cross_attention_dim: number of context dimensions to use. + """ + super().__init__() + self.attention = None + + self.resnet_1 = ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + self.attention = SpatialTransformer( + spatial_dims=spatial_dims, + in_channels=in_channels, + num_attention_heads=in_channels // num_head_channels, + num_head_channels=num_head_channels, + num_layers=transformer_num_layers, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + cross_attention_dim=cross_attention_dim, + ) + self.resnet_2 = ResnetBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=in_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + + def forward( + self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None + ) -> torch.Tensor: + hidden_states = self.resnet_1(hidden_states, temb) + hidden_states = self.attention(hidden_states, context=context) + hidden_states = self.resnet_2(hidden_states, temb) + + return hidden_states + + +class UpBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int = 1, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + add_upsample: bool = True, + ) -> None: + """ + Unet's up block containing resnet and upsamplers blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + prev_output_channel: number of channels from residual connection. + out_channels: number of output channels. + temb_channels: number of timestep embedding channels. + num_res_blocks: number of residual blocks. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + add_upsample: if True add downsample block. + """ + super().__init__() + resnets = [] + + for i in range(num_res_blocks): + res_skip_channels = in_channels if (i == num_res_blocks - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock( + spatial_dims=spatial_dims, + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsampler = Upsample( + spatial_dims=spatial_dims, num_channels=out_channels, use_conv=True, out_channels=out_channels + ) + else: + self.upsampler = None + + def forward( + self, + hidden_states: torch.Tensor, + res_hidden_states_list: List[torch.Tensor], + temb: torch.Tensor, + context: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + del context + for i, resnet in enumerate(self.resnets): + # pop res hidden states + res_hidden_states = res_hidden_states_list[-1] + res_hidden_states_list = res_hidden_states_list[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + hidden_states = resnet(hidden_states, temb) + + if self.upsampler is not None: + hidden_states = self.upsampler(hidden_states) + + return hidden_states + + +class AttnUpBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int = 1, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + add_upsample: bool = True, + num_head_channels: int = 1, + ) -> None: + """ + Unet's up block containing resnet, upsamplers, and self-attention blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + prev_output_channel: number of channels from residual connection. + out_channels: number of output channels. + temb_channels: number of timestep embedding channels. + num_res_blocks: number of residual blocks. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + add_upsample: if True add downsample block. + num_head_channels: number of channels in each attention head. + """ + super().__init__() + resnets = [] + attentions = [] + + for i in range(num_res_blocks): + res_skip_channels = in_channels if (i == num_res_blocks - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock( + spatial_dims=spatial_dims, + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + attentions.append( + AttentionBlock( + spatial_dims=spatial_dims, + num_channels=out_channels, + num_head_channels=num_head_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + + self.resnets = nn.ModuleList(resnets) + self.attentions = nn.ModuleList(attentions) + + if add_upsample: + self.upsampler = Upsample( + spatial_dims=spatial_dims, num_channels=out_channels, use_conv=True, out_channels=out_channels + ) + else: + self.upsampler = None + + def forward( + self, + hidden_states: torch.Tensor, + res_hidden_states_list: List[torch.Tensor], + temb: torch.Tensor, + context: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + del context + for resnet, attn in zip(self.resnets, self.attentions): + # pop res hidden states + res_hidden_states = res_hidden_states_list[-1] + res_hidden_states_list = res_hidden_states_list[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + hidden_states = resnet(hidden_states, temb) + hidden_states = attn(hidden_states) + + if self.upsampler is not None: + hidden_states = self.upsampler(hidden_states) + + return hidden_states + + +class CrossAttnUpBlock(nn.Module): + def __init__( + self, + spatial_dims: int, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int = 1, + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + add_upsample: bool = True, + num_head_channels: int = 1, + transformer_num_layers: int = 1, + cross_attention_dim: Optional[int] = None, + ) -> None: + """ + Unet's up block containing resnet, upsamplers, and self-attention blocks. + + Args: + spatial_dims: The number of spatial dimensions. + in_channels: number of input channels. + prev_output_channel: number of channels from residual connection. + out_channels: number of output channels. + temb_channels: number of timestep embedding channels. + num_res_blocks: number of residual blocks. + norm_num_groups: number of groups for the group normalization. + norm_eps: epsilon for the group normalization. + add_upsample: if True add downsample block. + num_head_channels: number of channels in each attention head. + transformer_num_layers: number of layers of Transformer blocks to use. + cross_attention_dim: number of context dimensions to use. + """ + super().__init__() + resnets = [] + attentions = [] + + for i in range(num_res_blocks): + res_skip_channels = in_channels if (i == num_res_blocks - 1) else out_channels + resnet_in_channels = prev_output_channel if i == 0 else out_channels + + resnets.append( + ResnetBlock( + spatial_dims=spatial_dims, + in_channels=resnet_in_channels + res_skip_channels, + out_channels=out_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + ) + ) + attentions.append( + SpatialTransformer( + spatial_dims=spatial_dims, + in_channels=out_channels, + num_attention_heads=out_channels // num_head_channels, + num_head_channels=num_head_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + ) + + self.attentions = nn.ModuleList(attentions) + self.resnets = nn.ModuleList(resnets) + + if add_upsample: + self.upsampler = Upsample( + spatial_dims=spatial_dims, num_channels=out_channels, use_conv=True, out_channels=out_channels + ) + else: + self.upsampler = None + + def forward( + self, + hidden_states: torch.Tensor, + res_hidden_states_list: List[torch.Tensor], + temb: torch.Tensor, + context: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + for resnet, attn in zip(self.resnets, self.attentions): + # pop res hidden states + res_hidden_states = res_hidden_states_list[-1] + res_hidden_states_list = res_hidden_states_list[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + + hidden_states = resnet(hidden_states, temb) + hidden_states = attn(hidden_states, context=context) + + if self.upsampler is not None: + hidden_states = self.upsampler(hidden_states) + + return hidden_states + + +def get_down_block( + spatial_dims: int, + in_channels: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int, + norm_num_groups: int, + norm_eps: float, + add_downsample: bool, + with_attn: bool, + with_cross_attn: bool, + num_head_channels: int, + transformer_num_layers: int, + cross_attention_dim: Optional[int], +) -> nn.Module: + if with_attn: + return AttnDownBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_downsample=add_downsample, + num_head_channels=num_head_channels, + ) + elif with_cross_attn: + return CrossAttnDownBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_downsample=add_downsample, + num_head_channels=num_head_channels, + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + else: + return DownBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=out_channels, + temb_channels=temb_channels, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_downsample=add_downsample, + ) + + +def get_mid_block( + spatial_dims: int, + in_channels: int, + temb_channels: int, + norm_num_groups: int, + norm_eps: float, + with_conditioning: bool, + num_head_channels: int, + transformer_num_layers: int, + cross_attention_dim: Optional[int], +) -> nn.Module: + if with_conditioning: + return CrossAttnMidBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + num_head_channels=num_head_channels, + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + else: + return AttnMidBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + temb_channels=temb_channels, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + num_head_channels=num_head_channels, + ) + + +def get_up_block( + spatial_dims: int, + in_channels: int, + prev_output_channel: int, + out_channels: int, + temb_channels: int, + num_res_blocks: int, + norm_num_groups: int, + norm_eps: float, + add_upsample: bool, + with_attn: bool, + with_cross_attn: bool, + num_head_channels: int, + transformer_num_layers: int, + cross_attention_dim: Optional[int], +) -> nn.Module: + if with_attn: + return AttnUpBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + prev_output_channel=prev_output_channel, + out_channels=out_channels, + temb_channels=temb_channels, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_upsample=add_upsample, + num_head_channels=num_head_channels, + ) + elif with_cross_attn: + return CrossAttnUpBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + prev_output_channel=prev_output_channel, + out_channels=out_channels, + temb_channels=temb_channels, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_upsample=add_upsample, + num_head_channels=num_head_channels, + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + else: + return UpBlock( + spatial_dims=spatial_dims, + in_channels=in_channels, + prev_output_channel=prev_output_channel, + out_channels=out_channels, + temb_channels=temb_channels, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_upsample=add_upsample, + ) + + +class DiffusionModelEncoder(nn.Module): + """ + Unet network with timestep embedding and attention mechanisms for conditioning based on + Rombach et al. "High-Resolution Image Synthesis with Latent Diffusion Models" https://arxiv.org/abs/2112.10752 + and Pinaya et al. "Brain Imaging Generation with Latent Diffusion Models" https://arxiv.org/abs/2209.07162 + + Args: + spatial_dims: number of spatial dimensions. + in_channels: number of input channels. + out_channels: number of output channels. + num_res_blocks: number of residual blocks (see ResnetBlock) per level. + num_channels: tuple of block output channels. + attention_levels: list of levels to add attention. + norm_num_groups: number of groups for the normalization. + norm_eps: epsilon for the normalization. + num_head_channels: number of channels in each attention head. + with_conditioning: if True add spatial transformers to perform conditioning. + transformer_num_layers: number of layers of Transformer blocks to use. + cross_attention_dim: number of context dimensions to use. + num_class_embeds: if specified (as an int), then this model will be class-conditional with `num_class_embeds` + classes. + """ + + def __init__( + self, + spatial_dims: int, + in_channels: int, + out_channels: int, + num_res_blocks: int, + num_channels: Sequence[int] = (32, 64, 64, 64), + attention_levels: Sequence[bool] = (False, False, True, True), + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + num_head_channels: Union[int, Sequence[int]] = 8, + with_conditioning: bool = False, + transformer_num_layers: int = 1, + cross_attention_dim: Optional[int] = None, + num_class_embeds: Optional[int] = None, + ) -> None: + super().__init__() + if with_conditioning is True and cross_attention_dim is None: + raise ValueError( + ( + "DiffusionModelUNet expects dimension of the cross-attention conditioning (cross_attention_dim) " + "when using with_conditioning." + ) + ) + if cross_attention_dim is not None and with_conditioning is False: + raise ValueError( + "DiffusionModelUNet expects with_conditioning=True when specifying the cross_attention_dim." + ) + + # All number of channels should be multiple of num_groups + if any((out_channel % norm_num_groups) != 0 for out_channel in num_channels): + raise ValueError("DiffusionModelUNet expects all num_channels being multiple of norm_num_groups") + + if isinstance(num_head_channels, int): + num_head_channels = (num_head_channels,) * len(attention_levels) + + if len(num_head_channels) != len(attention_levels): + raise ValueError( + "num_head_channels should have the same length as attention_levels. For the i levels without attention," + " i.e. `attention_level[i]=False`, the num_head_channels[i] will be ignored." + ) + + self.in_channels = in_channels + self.block_out_channels = num_channels + self.out_channels = out_channels + self.num_res_blocks = num_res_blocks + self.attention_levels = attention_levels + self.num_head_channels = num_head_channels + self.with_conditioning = with_conditioning + + # input + self.conv_in = Convolution( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=num_channels[0], + strides=1, + kernel_size=3, + padding=1, + conv_only=True, + ) + + # time + time_embed_dim = num_channels[0] * 4 + self.time_embed = nn.Sequential( + nn.Linear(num_channels[0], time_embed_dim), + nn.SiLU(), + nn.Linear(time_embed_dim, time_embed_dim), + ) + + # class embedding + self.num_class_embeds = num_class_embeds + if num_class_embeds is not None: + self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim) + + # down + self.down_blocks = nn.ModuleList([]) + output_channel = num_channels[0] + for i in range(len(num_channels)): + input_channel = output_channel + output_channel = num_channels[i] + is_final_block = i == len(num_channels) - 1 + + down_block = get_down_block( + spatial_dims=spatial_dims, + in_channels=input_channel, + out_channels=output_channel, + temb_channels=time_embed_dim, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_downsample=not is_final_block, + with_attn=(attention_levels[i] and not with_conditioning), + with_cross_attn=(attention_levels[i] and with_conditioning), + num_head_channels=num_head_channels[i], + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + + self.down_blocks.append(down_block) + + # mid + self.middle_block = get_mid_block( + spatial_dims=spatial_dims, + in_channels=num_channels[-1], + temb_channels=time_embed_dim, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + with_conditioning=with_conditioning, + num_head_channels=num_head_channels[-1], + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + + # up + self.up_blocks = nn.ModuleList([]) + reversed_block_out_channels = list(reversed(num_channels)) + reversed_attention_levels = list(reversed(attention_levels)) + reversed_num_head_channels = list(reversed(num_head_channels)) + output_channel = reversed_block_out_channels[0] + for i in range(len(reversed_block_out_channels)): + prev_output_channel = output_channel + output_channel = reversed_block_out_channels[i] + input_channel = reversed_block_out_channels[min(i + 1, len(num_channels) - 1)] + + is_final_block = i == len(num_channels) - 1 + + up_block = get_up_block( + spatial_dims=spatial_dims, + in_channels=input_channel, + prev_output_channel=prev_output_channel, + out_channels=output_channel, + temb_channels=time_embed_dim, + num_res_blocks=num_res_blocks + 1, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_upsample=not is_final_block, + with_attn=(reversed_attention_levels[i] and not with_conditioning), + with_cross_attn=(reversed_attention_levels[i] and with_conditioning), + num_head_channels=reversed_num_head_channels[i], + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + + self.up_blocks.append(up_block) + + # out + self.out = nn.Sequential( + nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels[0], eps=norm_eps, affine=True), + nn.SiLU(), + zero_module( + Convolution( + spatial_dims=spatial_dims, + in_channels=num_channels[0], + out_channels=out_channels, + strides=1, + kernel_size=3, + padding=1, + conv_only=True, + ) + ), + ) + + def forward( + self, + x: torch.Tensor, + timesteps: torch.Tensor, + context: Optional[torch.Tensor] = None, + class_labels: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + """ + Args: + x: input tensor (N, C, SpatialDims). + timesteps: timestep tensor (N,). + context: context tensor (N, 1, ContextDim). + class_labels: context tensor (N, ). + """ + # 1. time + t_emb = get_timestep_embedding(timesteps, self.block_out_channels[0]) + emb = self.time_embed(t_emb) + + # 2. class + if self.num_class_embeds is not None: + if class_labels is None: + raise ValueError("class_labels should be provided when num_class_embeds > 0") + class_emb = self.class_embedding(class_labels) + emb = emb + class_emb + + # 3. initial convolution + h = self.conv_in(x) + + # 4. down + if context is not None and self.with_conditioning is False: + raise ValueError("model should have with_conditioning = True if context is provided") + down_block_res_samples: List[torch.Tensor] = [h] + for downsample_block in self.down_blocks: + h, res_samples = downsample_block(hidden_states=h, temb=emb, context=context) + for residual in res_samples: + down_block_res_samples.append(residual) + h=h.flatten() + print('h', h.shape) + + # # 5. mid + # h = self.middle_block(hidden_states=h, temb=emb, context=context) + # + # # 6. up + # for upsample_block in self.up_blocks: + # res_samples = down_block_res_samples[-len(upsample_block.resnets) :] + # down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] + # h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, temb=emb, context=context) + + # 7. output block + + self.out = nn.Linear(len(h), self.out_channels) + output=self.out(h) + + return output diff --git a/generative/networks/nets/diffusion_model_unet.py b/generative/networks/nets/diffusion_model_unet.py index d29aa2d6..506b3010 100644 --- a/generative/networks/nets/diffusion_model_unet.py +++ b/generative/networks/nets/diffusion_model_unet.py @@ -1655,3 +1655,246 @@ def forward( h = self.out(h) return h + + + +class DiffusionModelEncoder(nn.Module): + """ + Unet network with timestep embedding and attention mechanisms for conditioning based on + Rombach et al. "High-Resolution Image Synthesis with Latent Diffusion Models" https://arxiv.org/abs/2112.10752 + and Pinaya et al. "Brain Imaging Generation with Latent Diffusion Models" https://arxiv.org/abs/2209.07162 + + Args: + spatial_dims: number of spatial dimensions. + in_channels: number of input channels. + out_channels: number of output channels. + num_res_blocks: number of residual blocks (see ResnetBlock) per level. + num_channels: tuple of block output channels. + attention_levels: list of levels to add attention. + norm_num_groups: number of groups for the normalization. + norm_eps: epsilon for the normalization. + num_head_channels: number of channels in each attention head. + with_conditioning: if True add spatial transformers to perform conditioning. + transformer_num_layers: number of layers of Transformer blocks to use. + cross_attention_dim: number of context dimensions to use. + num_class_embeds: if specified (as an int), then this model will be class-conditional with `num_class_embeds` + classes. + """ + + def __init__( + self, + spatial_dims: int, + in_channels: int, + out_channels: int, + num_res_blocks: int, + num_channels: Sequence[int] = (32, 64, 64, 64), + attention_levels: Sequence[bool] = (False, False, True, True), + norm_num_groups: int = 32, + norm_eps: float = 1e-6, + num_head_channels: Union[int, Sequence[int]] = 8, + with_conditioning: bool = False, + transformer_num_layers: int = 1, + cross_attention_dim: Optional[int] = None, + num_class_embeds: Optional[int] = None, + ) -> None: + super().__init__() + if with_conditioning is True and cross_attention_dim is None: + raise ValueError( + ( + "DiffusionModelUNet expects dimension of the cross-attention conditioning (cross_attention_dim) " + "when using with_conditioning." + ) + ) + if cross_attention_dim is not None and with_conditioning is False: + raise ValueError( + "DiffusionModelUNet expects with_conditioning=True when specifying the cross_attention_dim." + ) + + # All number of channels should be multiple of num_groups + if any((out_channel % norm_num_groups) != 0 for out_channel in num_channels): + raise ValueError("DiffusionModelUNet expects all num_channels being multiple of norm_num_groups") + + if isinstance(num_head_channels, int): + num_head_channels = (num_head_channels,) * len(attention_levels) + + if len(num_head_channels) != len(attention_levels): + raise ValueError( + "num_head_channels should have the same length as attention_levels. For the i levels without attention," + " i.e. `attention_level[i]=False`, the num_head_channels[i] will be ignored." + ) + + self.in_channels = in_channels + self.block_out_channels = num_channels + self.out_channels = out_channels + self.num_res_blocks = num_res_blocks + self.attention_levels = attention_levels + self.num_head_channels = num_head_channels + self.with_conditioning = with_conditioning + + # input + self.conv_in = Convolution( + spatial_dims=spatial_dims, + in_channels=in_channels, + out_channels=num_channels[0], + strides=1, + kernel_size=3, + padding=1, + conv_only=True, + ) + + # time + time_embed_dim = num_channels[0] * 4 + self.time_embed = nn.Sequential( + nn.Linear(num_channels[0], time_embed_dim), + nn.SiLU(), + nn.Linear(time_embed_dim, time_embed_dim), + ) + + # class embedding + self.num_class_embeds = num_class_embeds + if num_class_embeds is not None: + self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim) + + # down + self.down_blocks = nn.ModuleList([]) + output_channel = num_channels[0] + for i in range(len(num_channels)): + input_channel = output_channel + output_channel = num_channels[i] + is_final_block = i == len(num_channels) - 1 + + down_block = get_down_block( + spatial_dims=spatial_dims, + in_channels=input_channel, + out_channels=output_channel, + temb_channels=time_embed_dim, + num_res_blocks=num_res_blocks, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_downsample=not is_final_block, + with_attn=(attention_levels[i] and not with_conditioning), + with_cross_attn=(attention_levels[i] and with_conditioning), + num_head_channels=num_head_channels[i], + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + + self.down_blocks.append(down_block) + + # mid + self.middle_block = get_mid_block( + spatial_dims=spatial_dims, + in_channels=num_channels[-1], + temb_channels=time_embed_dim, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + with_conditioning=with_conditioning, + num_head_channels=num_head_channels[-1], + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + + # up + self.up_blocks = nn.ModuleList([]) + reversed_block_out_channels = list(reversed(num_channels)) + reversed_attention_levels = list(reversed(attention_levels)) + reversed_num_head_channels = list(reversed(num_head_channels)) + output_channel = reversed_block_out_channels[0] + for i in range(len(reversed_block_out_channels)): + prev_output_channel = output_channel + output_channel = reversed_block_out_channels[i] + input_channel = reversed_block_out_channels[min(i + 1, len(num_channels) - 1)] + + is_final_block = i == len(num_channels) - 1 + + up_block = get_up_block( + spatial_dims=spatial_dims, + in_channels=input_channel, + prev_output_channel=prev_output_channel, + out_channels=output_channel, + temb_channels=time_embed_dim, + num_res_blocks=num_res_blocks + 1, + norm_num_groups=norm_num_groups, + norm_eps=norm_eps, + add_upsample=not is_final_block, + with_attn=(reversed_attention_levels[i] and not with_conditioning), + with_cross_attn=(reversed_attention_levels[i] and with_conditioning), + num_head_channels=reversed_num_head_channels[i], + transformer_num_layers=transformer_num_layers, + cross_attention_dim=cross_attention_dim, + ) + + self.up_blocks.append(up_block) + self.out = nn.Linear(16384, self.out_channels) + # out + # self.out = nn.Sequential( + # nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels[0], eps=norm_eps, affine=True), + # nn.SiLU(), + # zero_module( + # Convolution( + # spatial_dims=spatial_dims, + # in_channels=num_channels[0], + # out_channels=out_channels, + # strides=1, + # kernel_size=3, + # padding=1, + # conv_only=True, + # ) + # ), + # ) + + + def forward( + self, + x: torch.Tensor, + timesteps: torch.Tensor, + context: Optional[torch.Tensor] = None, + class_labels: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + """ + Args: + x: input tensor (N, C, SpatialDims). + timesteps: timestep tensor (N,). + context: context tensor (N, 1, ContextDim). + class_labels: context tensor (N, ). + """ + # 1. time + t_emb = get_timestep_embedding(timesteps, self.block_out_channels[0]) + emb = self.time_embed(t_emb) + + # 2. class + if self.num_class_embeds is not None: + if class_labels is None: + raise ValueError("class_labels should be provided when num_class_embeds > 0") + class_emb = self.class_embedding(class_labels) + emb = emb + class_emb + + # 3. initial convolution + h = self.conv_in(x) + + # 4. down + if context is not None and self.with_conditioning is False: + raise ValueError("model should have with_conditioning = True if context is provided") + down_block_res_samples: List[torch.Tensor] = [h] + for downsample_block in self.down_blocks: + h, res_samples = downsample_block(hidden_states=h, temb=emb, context=context) + for residual in res_samples: + down_block_res_samples.append(residual) + h=h.reshape(h.shape[0] ,-1) + + + # # 5. mid + # h = self.middle_block(hidden_states=h, temb=emb, context=context) + # + # # 6. up + # for upsample_block in self.up_blocks: + # res_samples = down_block_res_samples[-len(upsample_block.resnets) :] + # down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] + # h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, temb=emb, context=context) + + # 7. output block + + + output=self.out(h) + + return output diff --git a/generative/networks/schedulers/ddim.py b/generative/networks/schedulers/ddim.py index 916d2f30..fba6d406 100644 --- a/generative/networks/schedulers/ddim.py +++ b/generative/networks/schedulers/ddim.py @@ -223,6 +223,93 @@ def step( return pred_prev_sample, pred_original_sample + + + def reversed_step( + self, + model_output: torch.Tensor, + timestep: int, + sample: torch.Tensor, + eta: float = 0.0, + generator: Optional[torch.Generator] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Predict the sample at the previous timestep by reversing the SDE. Core function to propagate the diffusion + process from the learned model outputs (most often the predicted noise). + + Args: + model_output: direct output from learned diffusion model. + timestep: current discrete timestep in the diffusion chain. + sample: current instance of sample being created by diffusion process. + eta: weight of noise for added noise in diffusion step. + predict_epsilon: flag to use when model predicts the samples directly instead of the noise, epsilon. + generator: random number generator. + + Returns: + pred_prev_sample: Predicted previous sample + pred_original_sample: Predicted original sample + """ + # See formulas (12) and (16) of DDIM paper https://arxiv.org/pdf/2010.02502.pdf + # Ideally, read DDIM paper in-detail understanding + + # Notation ( -> + # - model_output -> e_theta(x_t, t) + # - pred_original_sample -> f_theta(x_t, t) or x_0 + # - std_dev_t -> sigma_t + # - eta -> η + # - pred_sample_direction -> "direction pointing to x_t" + # - pred_prev_sample -> "x_t-1" + + # 1. get previous step value (=t-1) + prev_timestep = timestep - self.num_train_timesteps // self.num_inference_steps #t-1 + post_timestep = timestep + self.num_train_timesteps // self.num_inference_steps #t+1 + + # 2. compute alphas, betas + alpha_prod_t = self.alphas_cumprod[timestep] + alpha_prod_t_prev = self.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod #alpha at timestep t-1 + alpha_prod_t_post = self.alphas_cumprod[post_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod #alpha at timestep t+1 + + + beta_prod_t = 1 - alpha_prod_t + + # 3. compute predicted original sample from predicted noise also called + # "predicted x_0" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf + if self.prediction_type == "epsilon": + pred_original_sample = (sample - beta_prod_t ** (0.5) * model_output) / alpha_prod_t ** (0.5) + elif self.prediction_type == "sample": + pred_original_sample = model_output + elif self.prediction_type == "v_prediction": + pred_original_sample = (alpha_prod_t**0.5) * sample - (beta_prod_t**0.5) * model_output + # predict V + model_output = (alpha_prod_t**0.5) * model_output + (beta_prod_t**0.5) * sample + + # 4. Clip "predicted x_0" + if self.clip_sample: + pred_original_sample = torch.clamp(pred_original_sample, -1, 1) + + # 5. compute variance: "sigma_t(η)" -> see formula (16) #I thought we set sigma to 0 here??? + # σ_t = sqrt((1 − α_t−1)/(1 − α_t)) * sqrt(1 − α_t/α_t−1) + variance = self._get_variance(timestep, prev_timestep) + std_dev_t = eta * variance ** (0.5) + + # 6. compute "direction pointing to x_t" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf + pred_sample_direction = (1 - alpha_prod_t_post - std_dev_t**2) ** (0.5) * model_output + + # 7. compute x_t+1 without "random noise" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf + pred_post_sample = alpha_prod_t_post ** (0.5) * pred_original_sample + pred_sample_direction + + if eta > 0: + # randn_like does not support generator https://github.com/pytorch/pytorch/issues/27072 + device = model_output.device if torch.is_tensor(model_output) else "cpu" + noise = torch.randn(model_output.shape, dtype=model_output.dtype, generator=generator).to(device) + variance = self._get_variance(timestep, prev_timestep) ** (0.5) * eta * noise + + pred_prev_sample = pred_prev_sample + variance + + return pred_post_sample, pred_original_sample + + + def add_noise( self, original_samples: torch.Tensor, diff --git a/tutorials/Untitled.ipynb b/tutorials/Untitled.ipynb new file mode 100644 index 00000000..c0c04ff7 --- /dev/null +++ b/tutorials/Untitled.ipynb @@ -0,0 +1,33 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "761199be-b371-4cb7-a66f-ae739eccb554", + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.ipynb b/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.ipynb index f8e67fbd..f8a2cdff 100644 --- a/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.ipynb +++ b/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.ipynb @@ -41,9 +41,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "id": "972ed3f3", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -53,25 +54,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "MONAI version: 1.1.dev2239\n", - "Numpy version: 1.23.3\n", - "Pytorch version: 1.8.0+cu111\n", + "MONAI version: 1.1.dev2248\n", + "Numpy version: 1.23.2\n", + "Pytorch version: 1.12.1\n", "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", - "MONAI rev id: 13b24fa92b9d98bd0dc6d5cdcb52504fd09e297b\n", - "MONAI __file__: /media/walter/Storage/Projects/GenerativeModels/venv/lib/python3.8/site-packages/monai/__init__.py\n", + "MONAI rev id: 3400bd91422ccba9ccc3aa2ffe7fecd4eb5596bf\n", + "MONAI __file__: /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/monai/__init__.py\n", "\n", "Optional dependencies:\n", - "Pytorch Ignite version: 0.4.10\n", - "Nibabel version: 4.0.2\n", - "scikit-image version: NOT INSTALLED or UNKNOWN VERSION.\n", + "Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.\n", + "Nibabel version: 4.0.1\n", + "scikit-image version: 0.19.3\n", "Pillow version: 9.2.0\n", - "Tensorboard version: 2.11.0\n", + "Tensorboard version: NOT INSTALLED or UNKNOWN VERSION.\n", "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", - "TorchVision version: 0.9.0+cu111\n", + "TorchVision version: 0.13.1\n", "tqdm version: 4.64.1\n", "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", - "psutil version: 5.9.3\n", - "pandas version: NOT INSTALLED or UNKNOWN VERSION.\n", + "psutil version: 5.9.4\n", + "pandas version: 1.5.3\n", "einops version: 0.6.0\n", "transformers version: NOT INSTALLED or UNKNOWN VERSION.\n", "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", @@ -114,8 +115,9 @@ "from generative.inferers import DiffusionInferer\n", "\n", "# TODO: Add right import reference after deployed\n", - "from generative.networks.nets import DiffusionModelUNet\n", - "from generative.schedulers import DDPMScheduler\n", + "\n", + "from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet\n", + "from generative.networks.schedulers.ddpm import DDPMScheduler\n", "\n", "print_config()" ] @@ -130,9 +132,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "8b4323e7", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -142,7 +145,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "/tmp/tmp142o2qtd\n" + "/tmp/tmp_kncxscb\n" ] } ], @@ -162,9 +165,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "34ea510f", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -187,9 +191,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "da1927b0", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -199,9 +204,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "2022-12-10 11:50:40,187 - INFO - Downloaded: /tmp/tmp142o2qtd/MedNIST.tar.gz\n", - "2022-12-10 11:50:40,255 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", - "2022-12-10 11:50:40,256 - INFO - Writing into directory: /tmp/tmp142o2qtd.\n" + "2023-01-20 12:00:04,842 - INFO - Downloaded: /tmp/tmp_kncxscb/MedNIST.tar.gz\n", + "2023-01-20 12:00:04,994 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", + "2023-01-20 12:00:04,995 - INFO - Writing into directory: /tmp/tmp_kncxscb.\n" ] } ], @@ -237,9 +242,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "id": "e3184009", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -249,7 +255,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Loading dataset: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 15990/15990 [00:08<00:00, 1784.85it/s]\n" + "Loading dataset: 100%|███████████████████| 15990/15990 [00:18<00:00, 879.46it/s]\n" ] } ], @@ -280,9 +286,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "4c11b93f", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -292,16 +299,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "2022-12-10 11:51:08,067 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", - "2022-12-10 11:51:08,067 - INFO - File exists: /tmp/tmp142o2qtd/MedNIST.tar.gz, skipped downloading.\n", - "2022-12-10 11:51:08,068 - INFO - Non-empty folder exists in /tmp/tmp142o2qtd/MedNIST, skipped extracting.\n" + "2023-01-20 12:08:21,572 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", + "2023-01-20 12:08:21,573 - INFO - File exists: /tmp/tmp_kncxscb/MedNIST.tar.gz, skipped downloading.\n", + "2023-01-20 12:08:21,574 - INFO - Non-empty folder exists in /tmp/tmp_kncxscb/MedNIST, skipped extracting.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Loading dataset: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1977/1977 [00:01<00:00, 1545.02it/s]\n" + "Loading dataset: 100%|█████████████████████| 1977/1977 [00:02<00:00, 735.36it/s]\n" ] } ], @@ -337,9 +344,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "id": "4105a01f", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -349,13 +357,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + "/tmp/ipykernel_14682/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + "/tmp/ipykernel_14682/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + "/tmp/ipykernel_14682/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + "/tmp/ipykernel_14682/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n" ] }, @@ -363,12 +371,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "batch shape: (128, 1, 64, 64)\n" + "batch shape: torch.Size([128, 1, 64, 64])\n" ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAE4CAYAAACKfUBxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABtbUlEQVR4nO3dWcxe1132/1XaJLYTu57nebZjO3HiTE3akgpBqYDSqoflBM6R4ICzAgdUHCIVkFAlhBAHtEWIilKavm2aqRnt2Ek8z3Y8D/EQx3Zip/Q9+L9/ve+6fld8/7qy7/08tr+fs7W17j2utfZ6tp597U/88pe//GUBAAAAAAAAevRrI70DAAAAAAAAuP3wUAoAAAAAAAC946EUAAAAAAAAesdDKQAAAAAAAPSOh1IAAAAAAADoHQ+lAAAAAAAA0DseSgEAAAAAAKB3PJQCAAAAAABA73goBQAAAAAAgN59Kltx6dKlYdkvf/nLTnfm//eJT3yi03qD/NqvDX429z//8z8D63z44Ydh2Z133jlwPfo7tz+ZYx3m+XDr1mWZ3/3iF7/obPstdZxPfvKTA+tcvnw5LBs3blxVvnbtWqijx+KOTduEO4677rorLNPtjRkzJtS5evVqVb7jjjtCnYxPfSoOFZlj03ObOTa3rY0bN1bl6dOnhzqub2l7c2OW/s6tR3+XbWu6Lrf9zDiqdTLH0bruTF93dbL7NEjm3GbPY+a8tdRx+hx/W69/5rpl1u3quP3WMcptX8ckN45NmDChKuvYW0op8+bNC8suXbpUlT/96U+HOjqWXL9+PdTR/T537lyo88Mf/jAsA/qSGTdcncx9vKXOMLff1Vh7s3j//fer8vnz50doT4CRl51/ZujY4uZRLfPf7P5k5naZfczUGWmZc8J/SgEAAAAAAKB3PJQCAAAAAABA73goBQAAAAAAgN6lM6UyWUB9a3mH0r2v3pK74rbntu+yiJTL0FHu/GfyQjJ5WYPWm1135neZOq3vwmaO1R3HBx98UJVdxlQm08llimX2Seu47V+5cmXgelydrt6XHqZMplamjWQydTK/G2amVJ+5P9nfqWFmSmXHlkF1WjOlnK5yvzLnLaM192qYbUvHkUx+YimlLFiwoCq7sW3WrFlVWfNTSill/PjxVdndM10W1Fe/+tWq/Mgjj4Q6f/VXf1WVNYeqlDjWu2wqjKzMHGWYmUp9bv92y1S63bXM44FbVWY+5vJz3Ryh5XeZ7bu5TuuzBuXG/9GYIdWCkQ4AAAAAAAC946EUAAAAAAAAesdDKQAAAAAAAPRuVGZKtea1tP4ms73Wd1gzdPuZ/CinyyycDPfOrOpqe5l36jPnyNHj0IypUnyGiWafuEyVTKaQrjublzRmzJiqnMm0crlTun2Xn+XWnbn+g/anlFzuUyZTyu3j7ZQp1ZrppLrMlGq5tpl9yvb11iymQetx+sx5ac2Ual239i3Xj9euXRuWTZs2rSqfP38+1Jk3b94Ny6WUMmfOnKq8fPnyUGfixIlhmY7RDz30UKizYsWKqvzyyy+HOjpGurFmpA0z00jXnVlP39sHblW0d+D/cn9rae6jy49yf8fpvbyrbKbWbFJH98n97TUac79bMNIBAAAAAACgdzyUAgAAAAAAQO94KAUAAAAAAIDe8VAKAAAAAAAAvRsVQed9BsR2ua1MiK/WcYGFGsjmzrULaMsEZGeCflX2HLW0iZZw7I/aVlfXUtfjAvKcTPikhui566jXzdXJhK9nwnfvvvvusEyPNxv0PqwQZbctlQ11zgSNt4SRO649arsdZoiio9tvPY7WDw20BJ239uvWYMm+r8kgrR/e6Cro3K1HQ0THjRsX6hw9ejQsO3PmTFW+fPlyqPOXf/mXVfmJJ54YuP2dO3eGOqtWrQrLvvWtb1Xls2fPhjp//Md/XJV3794d6rz77rtV2d0jJkyYEJYNK0Sc4GPg9kF/B/4vnQ+UEsPPXZ3WuZ7+TeLmVV3N9VvD2HWM6Gpe2zdGOgAAAAAAAPSOh1IAAAAAAADoHQ+lAAAAAAAA0Lt0plTr+4mt+RjD2lar1iwWPW9uPZlMFXf+M++wdvUuutunluvWmk3mjkO333r99d3jO+64I7V9fc/X5aVkrv/7778/cPsui0tzTdzx6z5dvXo11BkzZkxYlqHHksn9ylzHVpmcnUzuUDavR2X6bSZTyWnJhvs4v1OZ4289t131464ypfrMOHQybSSTjVZKd+dWx4grV66EOm7dmo/gxrZ//ud/rsrf/va3Qx3NedKMp1JK+fd///ewTPfz+eefD3XGjx9flZ988slQ5zvf+U5VdplaLq8PAD4uMqWAG9P5j7tHu7+jLl26NHDd2v9cXpWu2/095Lav9Vqfmei6yZQCAAAAAAAAkngoBQAAAAAAgN7xUAoAAAAAAAC946EUAAAAAAAAetdp0PlIB8Q6uk+Z4NuugpdLiSFmLvzszjvvrMoaDltKDNUuJYatuRC1TIhvyzkapmzQe0YmIFLX7c6/hpE799xzT1g2c+bMgevRNuGutbsmGtDrfjdt2rSqrG2tlHi8LiBw48aNYdmwuOuv1zHbHlqCtlsDs1uD1vsMOm+t07qtPsMWu7q2rdeoK8Nso610jHLjyMWLFweu5+DBg2GZrsuFiE6fPr0q33XXXaGOu//p+OfC0Dds2FCVP/vZz4Y6Ov6dOnUq1AGAPnT14SHgZpT5W/eDDz4IdaZMmRKWfeYzn6nKx44dC3V02fLly0OdM2fO3LBciv8bUedN7u+4jNaPiI02/KcUAAAAAAAAesdDKQAAAAAAAPSOh1IAAAAAAADoXTpTajS+r6z7lMlLallvdj0ui0jzea5evRrqXLlypSq7d0NdNpLuk6uj68qco+z76l2d28x6W6+tvmfszpHuk8s0uX79elimuSYTJ04MdRYtWlSVL1y4EOo89thjVXnevHmhjvPuu+9W5WXLloU6+g71n/3Zn4U6d9xxR1XOvpucySvTc5vJGOoy02lYmVLZ8bDleFvPY0s2VWsdx+3TsDLtsm2k5dpmzmN2jGxZd2sO1zDv0Tr+ZXLf3O90PCwl3iPHjh0b6mg+hKvzN3/zN2GZnpNVq1aFOs8880xVdrkPuo/z588Pdfbu3RuWAUDXMnN94Fbl2vqYMWOqssvvdff/v//7v6/K7m/0kydPVuVJkyaFOt/4xjeq8lNPPRXqtM5tM3Pk0fiMpgX/KQUAAAAAAIDe8VAKAAAAAAAAveOhFAAAAAAAAHrHQykAAAAAAAD0rveg867CeH+Ver+qbIiYHsuKFStCnalTp1bls2fPhjrbt2+vyu+9916oo6HajgvjzgSkZQLTnZbz3xKO3uW6WwOi77zzzoF1Tp06FZbpddNw8lJK+aM/+qOq7EJ0z58/H5Z96UtfqsqbNm0KdTTo/P777w91fvazn1XlmTNnhjqOXn8XPqjn1n0MQH/n1tMakN5Vndag8z7HqK7WlQ3xztTpat0t23LLWgMiuwqa7DuMsiVo3e2jBn3rxzlKiWHkpZQya9asquzuLRos7j4YoSGmn/zkJ0MdF2x68eLFquyO7fjx41XZ3aPfeeedquyOVfcRAIaBoHPcztw8xt3/1c9//vOwbOnSpVV52rRpoc63vvWtqvzSSy+FOhs2bKjKP/rRj0Kdy5cvD9zHjC6fo4w2/KcUAAAAAAAAesdDKQAAAAAAAPSOh1IAAAAAAADoHQ+lAAAAAAAA0Lt00HkmaNiFb2UCubROdj2ZQO6uwrgzwbYaqlpKDHpdvXp1qKPB1idPngx19u7dG5adPn26Krswbg2EdWFwd9xxR1X+8MMPB67HceseO3bswN9pQKM71277uixzrV2b0bbtAiP1HLnfaWBvKTEQ2J2jv/iLv/A7+/+4du1aWPbtb3+7Ku/ZsyfU0RD93//93w91NLTPBeY7Ws+dW12WGUdcncy1dr/TZe53mfaXCbrOhOhntt8yZpbi+20m6Fu378LoB633o37n9kllrm1riGsm6Lu1TXalJbSy9WMcrR96cOOPcvefSZMmVeXx48cPXPeFCxdCHf34h6vjxtbM+KvnxJ0PbaPZj4EAv6rWDya4Npm5/+jv3Firv3Njves3OkfI7ndm3bczxp/hcX9rtMw/uvqoi1tXZj2tx+H+1sn+TdCXzPzTjVFu/qPnxB2//k3+n//5n6HOH/7hH1ZlN9e5dOlSWKbzJjfX0mvp5tV6vJm592jEyAYAAAAAAIDe8VAKAAAAAAAAveOhFAAAAAAAAHqXzpTK5FVkfzeoTjZTquX93NZ3ejMOHjw4cHuLFy8OdWbOnFmVH3jggVBn0aJFYdnmzZur8o4dO0IdfRfYvWeceafVLRszZkxVdu/w6vbdO736fnw2r0DfBc7kpbh38XV7mW2VEnNOXH7W1KlTq/KUKVNCnUymw1133RWWPf/881V5165doY5ub+nSpaHOo48+WpUPHz4c6pw4cSIsa71uKpOX06eR3n5Gdh/1vfY5c+aEOpqFd+rUqVAnk2mQyaJz/e+DDz644bbcerLHn8lUatFlXoRy+9i67sy9VWXOv7vW77zzTli2adOmqpy9t6jWTBvdb3f/07GVvJaR1TrXbO2TmUzT1v6XyTTM3P+1veuYWUqcj7l1u99phkqmH7n1uHFL7z8ur0VzVtzY4vLqbmeMUd3J9D+VyV3K5qe1jFGOriczjrh1j7b8KMf9PaRjkuZZlhL/ZiullOnTp1dll1f5zW9+84bbKqWUt956qyq7v/U047KU3Pwn099v1gwpxcgGAAAAAACA3vFQCgAAAAAAAL3joRQAAAAAAAB6x0MpAAAAAAAA9O4Tv0wmOLrQsJbwyS5DLLsKrR1msLEGy7nwMw0xX7VqVajjAtLff//9qrxnz55Q59VXX63KLsRYg9Zaz7U7jxoI6IJmM9c/E/7r1p3ZfiYM3h2bhtbNmjUr1Fm5cmVVdgF9GRoGWkrc70z44tWrV0MdPf8uVPTYsWNhmQbruRBFPZfuWuvvXGDr3r17q7I7jy6gUcMe3XXUtpVp667OMEO0lQuxdEGHc+fOrcoPPvhgqKMhikePHg11NPzxzJkzoc758+fDMj0nrUGfKhtGnAkazgQdd/VRj4zMBxuyMkHnek3cNcqEvzotfcuNYzqOZD6Y4Za5Oq3zD3X33XcPrHO7afnQjZO5j2T6f6ZvZ9bjZNpf5iMqbvt6v3NBu5kwXPc7d99Wek9258ONEXpP1jlrKbHfuPu/2+/b2bvvvhuWXb58eQT25Nbj+lGm/+uHjtxvumrHmQ82uflgV3PUkZa5RtnrmPnQl9IPOJQS/0Zz8wE3RmY+2KV1Wj9GM9Iy93r+UwoAAAAAAAC946EUAAAAAAAAesdDKQAAAAAAAPRu8MvkN5DJq9A67p3ClvV0KZMpkOGyiDTDx2Xz6PvhLq/FvUOueUXr168PdebPn1+VNWOqlFK2bt1alS9evBjquHOix5vJYnLvuWYyXVqzyPT94My1de/9utwFzT5xmTobN26syi5TQY/DnSO3fT22TF6VW3cmC8XlTGXe4VaZNtJ6rTNjS6uWcayUtiygrnJYSolt9OTJk6GO5gXOmzcv1NG8NJeN57KodLzLZIpkshhcf3DnRPuya3/DNKz7Vus9srWPZPLjMut2v9NjyeS1ubGuqzEikw3Udzu6WWm7cedN20Qmd8ndozPXVnNfXB237swcMTP+dDVHzmbz6fZacx91/M1kQ7p6mT6amSPd7jJzLbRxY1Qmi0nnyG5e7f6Oe++996pyZoxw+6N/D7Teo9zvWjMlh8UdfyZTy80bMjlf48aNq8ou41fPm8umcrm3Ldw1Go0ZUi0Y2QAAAAAAANA7HkoBAAAAAACgdzyUAgAAAAAAQO94KAUAAAAAAIDepYPOMyGKwwoVdtv6qGUt625dbybEWwPJXEChBjsePHgw1Llw4UJYpoHoq1evDnVWrFhRlSdOnDiwjoZzlxLD0Esp5cqVK1V5zJgxoY4G0rlzq+ckG+KYuU6ZEM9M0Klbpvvp2lYmfFBD9LJheJm2pUHXmeN37TgT9OhkQlwzIbaD1vtRy1rqZGTXM8wPNGScOnWqKrvAem037kMHGhC8fPnyUEc/qlBKHEvc2Kbjn2truo+ujbjfdRWQ21XbytTJjH9dfgwkE6LqQoxVpt9mA5qVjnUuDDkTou3qtASkt46Ht5uu5mit/Uavt7u3Zq5lJjS4dW6py9w+6jzCHWvmODIh8pmPkbg6LjBYj0U/qlFK3G8XBp35iMvthOD37ugcOTP/dvdDDb92QedujqR/x7kPXWX+RtJ5e+aDDaXEvnwzBGa748jstwsfV+7c6t+67vrrOObaUeYelZnbZj4YNtJ/e7TiP6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3qWDzh0N0moJDHWyIa7DCvLqcr16LJkwXrf9EydOhGUaiOaCJjVE24Whr1q1qiovW7Ys1PnJT34Slr322mtV2QVU6vFqqGAp8fizofZ6nlyIXSYgMNOOs+H7g+q46+/Om3LnLRMir+fEhehpiKgLw3YyIfJ9Bt1m1zVIlyHSfYYPuv0+f/58VXbXVtuR+xiC0uDHUkoZP358WPbII49UZReQu3PnzqrszpH+LhNYWUouxLQ1WDyjq+vdVTtq/WBJpk7mPGY+IuHOtQaLuqBR17Yy+5T5GANB5210vHHnLRNQq23CXdfWENmW9TiZgNrM2Obav67HjePuPOo46cKXdd1uHNf7iDtHkydPDst0bnPu3LlQRz+Q4+4/V69eDctuZ9mPAWGwrj5+pf3IfTDGtW39e8v10SNHjlRl19d1THBzpJs1/LqFG7Mz9+3MRyS6DIPPftjqdsHIBgAAAAAAgN7xUAoAAAAAAAC946EUAAAAAAAAepfOlMpkIWV0lWnxUfVa6mR0lSnjcgcymUru/J86daoqX758OdTR94rfeeedUOf++++vyi53av78+WHZ8uXLq/Irr7wS6hw+fLgqnzlzJtTR93Pd8WvuUSkxLyZz3tz513ePM7lTbt2ZLAqXBaHXKNuvMm1Ss1fce9aZTJXWvDhd1pLx0lqnb6Mtd6iU2N5cfplmH7hMg2nTplVll1eg+XWllDJz5syqvG7dulBH36l/++23Q51MFoxzM9wjMutpyT1yWu+jmTHa9e1Mn9BxPDNGZDNVWjIk3Loz4ygiPZfuerTcR12bdTkfOrZkMvXcWKPryW5f67lsSK2TuUdn5yja/+bMmRPqTJgwoSrrWF9KKXv37q3KOq8rxc/RFi9eXJVPnz4d6uickL41GJlS3dF+6/IKtY7r63pN3N9jLhv4nnvuqcrr168fuO4DBw6EOpm/IzJ/I3WZqTosw8yYdXXGjh1blTMZd9m/4zP0vpFpf61z5pHGyAYAAAAAAIDe8VAKAAAAAAAAveOhFAAAAAAAAHrHQykAAAAAAAD0Lh10PsygM113NmhttAXUuhCzTBh3JpBszJgxA3936dKlUOfll1+uyi5o/ODBg1X5wQcfDHXWrl0blm3YsKEqa6hxKaUcOnSoKu/atSvUOXLkSFU+evRoqONCAzUQ0wUUah0XoqnnMRsQp9c2E5Dq9jETYurajbaJ999//6N39v9wYdSZ0D4XrJeh+93VBxNuZZnA2uwYqe09E77owtCvXLkycFsamOvWvXDhwlBH25YLUddxJBu03dJuWwPDu9IadO50tZ861rl+nPkYhKuj4dNunz/44IMb7s9H/a6l37h91LE9c69H7H+ZD724e6T27UyoeClx/MvcozMfjHDzCLffur2ugoZd+3PHpvf7lStXhjq67Pjx46HOsmXLqvJbb70V6jz//PNhmZ4TN0fUvv3ee++FOpm+1dXHWLIB0SOJoPPhGWZAtJtb7dmzpyq7v/XWrFlTlSdOnBjqbNu2rSq7eVRm3pD9iMJIyvwdl6VjlLu36N9ImTD87DnTsU3vR26Zu0Y3a7C5YmQDAAAAAABA73goBQAAAAAAgN7xUAoAAAAAAAC9+1iZUpn3/DPvPuu63fui7h36ljqZ9/WzmT76Dmcmi6L1vXf3nqmu66677gp19H19zWYpJb4v6zKdXBaV5ky57X/lK1+pyjt27Ah1Lly4UJX37dsX6rgsqhMnTlTlixcvhjqas+Tes86cR0fbWyYvyl1HzavIvi+s23f7rdvX9uC257aVec/c/U7fj3fHr8eRyYvJZJO4ZW6M0PGmNRvH7bdrb4O278ZRXY8bI7UdlZIbozK5U7qe/fv3D6xTSilPPvlkVXaZZpppkskr0hy6j9p+Ji9g0LaydbrK9Mrkrrnr35q7mMld0nObzZ3Qdbk+osfrrlEm0yqTReRoncwcYbRlzIxWXeX1ZDK93P1v0qRJNyyXEu9Rbj2Z+5ibI2k+UyYvKdOP3X3c/W7p0qVV+aGHHgp1tm7dWpV/9rOfhTp6/Pfcc0+oM3Xq1IH75MYtPf7Mfex2z1S6WY8/87dd5h7pZDJenczcTs93po9m79H698/hw4dDHR235s2bF+ro8b/55puhTiZ31rWtlty7LN1e5lpnrm32b4SWLKYu85vcvWSQzDnK0nMy0vlhN+fIBgAAAAAAgJsaD6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDv0kHnTkuIaiYM3MmE37qARA1NzYSxu3Bit48aLOeC7XRdmRBpF9jqZM6/7qMLejt9+vTAOj//+c/DskuXLlXlr371q6GOXrdFixaFOpMnT67K9913X6izffv2sOzkyZNV+dSpU6HO22+/XZXPnz8/cD0uDDATPu+u29ixY6tyJmjPtXUXPqhhp6796TLX/jJhzE5m3VeuXKnK7jh0Pa7/ab/N9hE9lkyIvAse1OvvApv1WF09d251e66OBoS7NnL58uWB22/9GISeN3eO9MMDpZSycePGqnz33XeHOtr/ly1bNnAf3fV34evabtx1awmazMp86CLzoY3MRwUyMmHkbh8zYfyZ+5Hr23q8bhzTOu5en/lAgZOZI7R+DON21xIQnJnruT4yf/78sOyBBx6oyrNmzQp1NMTbGT9+fFWeMGFCqLNnz56w7Lvf/e7AdWto8bFjx0IdHdtd/58zZ05YtmLFiqrsPhjz/PPPV2UXxq73H3fO3PY1tNldfx3LW8PwWwN6W9Y90h86uFmDzrsMiFat9/FM0HZLsHT2WLX9698spcQPCzz88MOhjvZ1N9fZvHlzWKYfusr8jdzlx1gy50mPpfVadzVGZD9G1cW2Wuu0hqGP+Ng2olsHAAAAAADAbYmHUgAAAAAAAOgdD6UAAAAAAADQu3SmVCYLoDUvQN+pnTJlSqij7/SXEvMp3HuemXevdZ9c7oV7hzXzzqbWce+dZupofpD7nTtWPRa3Hs09cu8Uu3OiGTKvvfZaqDNz5syq/Pjjj4c6em5dXse6devCstWrV99wf0qJ72e73J133333hr8pJeZOue25LIZMH9H3pd274Jm8ssz70plMMyfznr3bR92ey6LIjBGaIZMZa0rp7t1rzRCZNm3awN+UEtvWuXPnQp3MdcxkWmRybjJ5PZlMLddmXBbb8ePHq/KmTZtCnQcffLAqT506NdTRvBh3zXQcK6WUI0eOVOWLFy+GOhl63jK5N447b3otW9fdmrvSch93ddyx6b299Tj0nuTGscy6XRaVzi2mT58e6ui9Zvny5aHOP/7jPw7cft8ymZ7a3jLX1l2jTM6IayP6u8wc0Y2/mTmCGyP0nuTy6jQv0N3r3e9chqaaNGlSVXZjlM5bMrmDpcTxz81tdN2//du/HeosXry4Krvjd+P/jBkzBm5f83LcelrukcPUOtfoykjnvrTKzBFb72OZsc71Ub23ZP6OdH8jDfqN28fsunQetXv37lBH72NPPvlkqOPGv23btlVl17fdMqXnO3Ncpfg2oTLXSLVmKjmZTM/MPrXOrTLrUdlMry7PUxf4TykAAAAAAAD0jodSAAAAAAAA6B0PpQAAAAAAANA7HkoBAAAAAACgd+mgcxfslQmk09+5oC0NaJw3b16oo0GLpcRgNw2jdHVcGKSGqLmgaReQquFvLjBM1+WOX4MdXdDlqVOnwjIN2na/0+25oO+77767KrswwMmTJ4dlGpq3a9euUEdDk13QsV7vWbNmhTouNE+XLVy4MNR54IEHqvKBAwdCHQ0RXLBgQajjAuI1/NyFeB49erQqHzt2LNTRfuS25YLmtU1mgq5dP9bz6IKuXZ/Q/XR9JNP/M0H32m41HNVtq5Xb/ty5c6vyqlWrQp0LFy6EZTomuD6q7ejMmTOhjo5t2RBJ7ctujNLz5q6/hjhmAkNLiedE+1opcWxxoZoaRuzuBxqqW0ocI904qsfmgna1rWc+WFFK7Lfu+uuyTPCkC7F09+NMn9DfuX6sx58J9SzFj1stdPzRe1YppUycODEs0367ZMmSUEdD9PXjHKXEe5IL2h6NQefallwb0bbk6mSCzjMhqq7d6lji+p+O9/fdd1+o8+ijj4Zl+oEKN7ZpW3Ljj47RV69eDXVcW9fz9s4774Q62rcyQe8uQF0/GFFK3O9MQLKbI+s4vmPHjlDHWblyZVXW4PVSYrtxfTvzMaDWjx+0BhSPNnocoy3AOKt1v/X4M3PdUuJY1nofHbTeUtrbkc4RtmzZEurouPXFL34x1Fm6dGlYpn/HufO/b9++qnzp0qVQR8cIN0ZmPuKSuf6jsT+Otv7W+vdQ60cUMv0og/+UAgAAAAAAQO94KAUAAAAAAIDe8VAKAAAAAAAAveOhFAAAAAAAAHqXDjrvigso00AuF0bnQiQzwWIafqeBuaXE8E0X6j19+vSB23Ihthoa68K/tI4LmnQBoRos50I0NVjYBRRr0KYG35WSC5Z0QbMaiOeCxvfv31+VP/3pT4c67vzrMndtJ02aVJXXrVsX6qxZs6YquzBOF+x3/vz5qqyhnqXEYFX9jVu3C+x3Idp6vC6wU6+t63+6PbeeTIizCyjVNuJClDXEWK9ZKaW8+uqrVdmFgWe448iE8WqfdPs4ZcqUsEzDJzVUtpQYrOwCIrUfu8Bu9zu9/q5OJmhSz4kLg8+0EXfdNETTtVHtoy6M2vURbVvuIwoarOzGaD1e10bc8Wv/d+df+5/7YMLp06ersgYYu205mfBV96EFlbmPumVubNf7xtSpU0MdvW4aYF6K75N6L3cfSNBlrh9riL6rMxrp9c4E5Lt7fSbo37Ut7X9u/qH9z13/tWvXVuXHHnss1HHzj3fffbcq61zDbd+NrRrs7cbxr33ta2GZfnzlv/7rv0IdHbfcR0X04ytf+cpXQp177703LNP7pgtI1jnJnj17Qh0d29wcyV1/vZZuHrd3796q7Ob/ek5aQ4Vbw3dvBjdD0HnLhzdKifcod//RPpn9O1LX7ebfGTq2ZoOm3Zio9FjcHOngwYNV+aWXXgp13N8o69evr8ruQxM6/zl37lyok/kYj/sbWeeE7vzr8WY+auD+jnFtq6WfuPM/zLElEyKuy7IfQ9Lz5OYImfOf+ahKan+afgUAAAAAAAB8DDyUAgAAAAAAQO94KAUAAAAAAIDefaxMKfdepdL3Fd1vLl++XJXde/8uQ0O5vAjNGXBZAPo7976yy8vRdblMC63j1qNcplQmZ8m9G6vv8GrGQikxi2rVqlUD97GU+F7x4sWLQx3NOXGZMrpP7lq7vCzdvsur0uyRFStWhDqa++PeRXY5F5l2o+3PZZFopoR7p9qdN30X3bX/TBaDXiN3/O7Y9J1h17f03WO3Hm3vro/83d/9XVX+t3/7t1DH0fHGjT96TlzugOZuuEwN128zY4SeI3ce9b1/926+y0LRPnL27NlQR9fl2prWcefItRt9P137mttHd/w6Jrh25LJYZs+eXZXde+56/3Hv4mfeu3fL9Hdu3ZrXM2fOnFBHxwjX/tz4p+fN5U7puOHO//Lly6uyy+aaNm1aWKZ92fVtvW6ZcdRl07lMIe1v7j6aqeO2dzPQNpnJ9HT3CK3j1uPav3J5Jbq9RYsWhTq/9Vu/VZXdfVT7cSmxvel4UEophw8frspPPvlkqPOnf/qnVXn+/Pmhjus3r7/+elV+6qmnQh2d27h+9NBDD1VlzTgrpZRjx46FZTr+LFu2LNTZuXPnDcuOyxRx2Tiac+PmlpncUT2OLvNi3LpU5m+dkZY5jpGWyYvK5OW4uVYmY9atW+/JbhzTscXlV+p93N1r3fin229tj9qPdu/eHeq4cUPHW5cX+tnPfrYqZ/5GcfNId050ey53V8cR94xA8zq7zFTL5CNl2nHmd611lGtHmfW4/da+5O51mXlUxugfxQAAAAAAAHDL4aEUAAAAAAAAesdDKQAAAAAAAPSOh1IAAAAAAADoXTro3AVk6TIXfpgJ+9KgNw2+LSUX9OyCbjPBXhpi6oLuXLCeBnu5oGkNrXSB2RoINnbs2FDHLdPwMRdQN2HChKrsAsp0+y6M2AW9arCgW7eGmC9YsCDU0fOYCfErJRc+rQHNu3btCnWWLl1alV1gbibYeObMmaGOtomFCxeGOtr+XYigC3rX43UBqXqNNIyxlHhtXWBpJqDY0bBBF76XqaPtODMefdQypdtzYYwnTpyoyi6wct68eWGZtj8Xvpnp/9qOXHt0fVTbmwvo1HPkQtQ1WPL48eOhjguo1PBJd2za3lxg8aFDh6qyO373EQP90IFrD268G8QFlrvwd23bmaBpd4/Q+6gLDHb3Tb2Wrm/r+OvGP723uKBpF9Cu/dbRdut+o9ufPHnywPU4mQ8dOJkQb7eekQ4f1naamaO586jjVvY8tgTEuvFHz7+7j7m2rf1/zZo1oY62LRcGrOvOfLChlHgs7t6u9+1777031FmyZElVdvNh1/9+/dd//YbrKSXe77Zt2xbqvPzyy1U5O0fX8+TaiB7/0aNHQx29J4x0vxqNboZzkpmPZcZj14+0r7mxx/2Npn/buT6iv3MfjNG/UXTuU4r/GI2uy41/Ot65+UdmjHbb1w89uLlF5u8Inf+68dgdm44bbmzVa+Lm2vr3kBuP3H7rvC1zH3P3Ub0m7u9x9xxBl7k5stZx7Vh/5+ZR7t6uf7e73+m63T7qetw+Zoz+UQwAAAAAAAC3HB5KAQAAAAAAoHc8lAIAAAAAAEDvPlamlL576d7F1Pcs3Xu++u6pexc1k8Xh3hfV96zduvXdX/e+qMtiOXbsWFV2uROZvAbN8Mhkarh1u2PT9zpdXsL8+fOr8rp160Id9360nif3Trsuc9dI3w9376u6DBPNgnrooYdCHc200rLj8kpczo2eW5expO3GHb9eW3f9XRaRtj/3Dq+eN5fN5d5zVq7fan93OUtax7VRPbfuWms7cvvTmiml++Te19f33F2mgXuHXfOBXF6Pvovt3rvX3CmX3+bajbblzHvurv+tXr26KmvbKyXmTpUSs6fc2Krn37V1HVvdOTp58mRYpufJZaPpPrlxXPfJ9ZlMpqFrN9on3Tiq++2ukaPbd/0vk/ujv3NjzfLly8MybX/uPqLnzV1/PY7WTCN3rHq93XoybWQ0Zkq15GVl5l/uOrpxU6+ty1TUe7LLZnvqqacGrsdtX/f7K1/5SqijbSKTe+LmEa7/nz59uiq7LJTHHnusKrtsPj0Od101P6uUUpYtW1aVXV6ijj9PPPFEqPPII49U5RdeeCHUee6558IyvZe7+5/m1bi59pYtW6qy61eZsc0df6b/6/l3/T8z1ximkR5rMjKZoplz6+aamkWmfa+UmJ9YSsw0c3Mknce5e5Teo91cJ3OPfPPNN0MdzcJ1Y5T+befaoxsjdS7n5t96/C73Upe58+jofrp59MGDB6uyG0d3795dlTWHtJTc3yiu/WXyc3VOmM2UymRD6+/cPFbnhO44MnnBbm6p+5TJxnV/o2SM/lEMAAAAAAAAtxweSgEAAAAAAKB3PJQCAAAAAABA73goBQAAAAAAgN6lg85daJvKBL269WgYoQtjy/zO0RAztx4N32sNDHTr1rAxF3StYcCnTp0Kddw+ZQKyNURPA5tLiaGd586dC3XmzJkTlunxuqBhDVFz4XsabOqC1jRErZQYyLdkyZJQR/fbBbRpO3Ihrm6fMmHDek1cG9HwTdeP3O9mzZo1cH/0fLt16/G6UHcXPquheZkxwoXvadt2QaP6u0w/dsvc7zL7rcfq2rELUXz77ber8uHDhweu27VRDYh1bX3x4sVhmYZGaqhnKbHduDDETIihCx/WQE7Xt3QfXRi49hEX9Oj2OxOirOfEfehAx1p3HK7dakCl9tlSYtt2/U+PP/NxglJif3cB5XpOXBiwjmPugx1ujNa+5X6n59ZtPxM06mTmCKr1/p8JFe+bG2+VHq+bf+nYlmkjpcQQa7fuRYsWVeUFCxaEOgcOHKjKrj24fvOjH/2oKruxXj+Q4sZx7SPuWrtzomG7Lnz3M5/5TFV2x6Zjmxv/Nm/eHJbpfC9zb88EFLuxfu/evWGZzgnd3PKBBx6oyu48atCxW4+7J2v7d327JaB8pEPNnZsh6DzzMYrMOJr50I2717u/rXT+4drxzJkzq7J+ZKmUONa5eZybW+j4px+VKSWGeLv5h34gwY0jJ06cCMv0XGpgfClx3HYfVdCxxo0R7kNbuv3M37HuYwh6/l2ovDtv7u9WpfMmF5ivY427/o7Ov93fcdq3XZ3MB4vc2K776fY7E3Suy1rnbKN/FAMAAAAAAMAth4dSAAAAAAAA6B0PpQAAAAAAANC7dKaUy4LQ90zdO7xdvdPtshH0d24fM3kJut/Z98UzWTiZ96N1v91xuH3Sdzbd7zR3Sd9fLiVmOMyePTvUce85K81vKSXmA7h91JwHlynjshBOnz5dlS9duhTqaPaAy92ZOnVqVc5kGrl6mfwStx5d5q61ez9Y3+t2fUvfodb350uJ59b148z7yS7TJ9O29bxlsqHctlpzHnT7mbwCty23T9pGXNvSMUlzmEqJeQHa9kspZceOHWGZZh+4vq3tSMeMUuL1z2SjlRLfxXfvouv2XRvVDIFMNlEpub6leSVnz54NdXSMcNlc7rxpm3Bjm15vza8oJfZJ19bcNdHsGZcNqH3L5Q5qFqK7j7p+q/co10ZUa//L/K4lYyprNGZKZc5by7l1+WHLli0Ly+bNm1eV3X1k1apVN/xNKbFNutyT1157beD2XaaM9ncdax13HK7faM7T/v37Q51t27ZVZTe26Di6c+fOUMf1SR1v3fxHj9/dfzTnZf78+aGO2289Nrffuk+uHT366KNV+Zlnngl13PnP5JzomJTJZsqOP326GTKlMmN0RmaOmBnrSsnN0TSLKTNHcOOYu/+/8847Vdndo3Xe5vqo/v3l/mZbt25dWKbzf3eP1nPrMp00i8/lV7lxY82aNQO3r8fr7j86Juv1KMXPrXTe6DLF9L5x7733hjo6trjcVTdv0ywol6ml8103jo30+NOV0T+KAQAAAAAA4JbDQykAAAAAAAD0jodSAAAAAAAA6B0PpQAAAAAAANC7dNC5C4jLBMu1hG+5oLlMaJ0Lem0JH3VhZJmAdg2VLSWGmLkwXg0tc2HALjRNQ+OWLFkS6syaNasqr127NtTREDUXxucCMjWQzYX/aWioCwidNGlSVdZzVkouDDFTx11HPX7Xjlq5YPPMPmVou3F9TetoeyglBoS6oPNMsLE7b25dKnOO9DgygZVun9w+trQttx53HfWaZILuM+fRBf9r0GQpMWxy4sSJoc7ChQursgva1GDJ6dOnD6xTSjwW1x704wtu/NV9dGOtGyMz9x9tW24fNejbBc278FE9J67/aRh95j7mxkgXEJoJFtfz7e5Ruk+u/bkPXejYqh9ecHXc9ddr5II++5QN0R1pOra4cVzPt5t/aBivW4/7+ICGX3/xi18MdXRuoaHepcSx1YWRP/HEE2GZtlv3MRRt7zofKSXeI916tm7dGpZp0LeeR1fny1/+cqjzxhtvVOXjx4+HOn/+538elulY7s6tHtv3vve9UEfHPzfWu/DfQ4cOVWUXIqzH//jjj4c6+jEMN466dev1d21Ux1Z3j9b7iGv/mXnMMN0MQcduH3VZ5m82Vydz/JkxOnPPdHMEvbe5v4cOHz4clukHUtw+ami6zodKiX3CzZHcHEXHBPc7/btN+2Mp8fjdfMCdk40bN1bl+++/P9TROaK7RjqOu7mG+9CXHpubR+sYmQlRd2Nt5m/L1rlNZt2Z/peR6Uet2+I/pQAAAAAAANA7HkoBAAAAAACgdzyUAgAAAAAAQO94KAUAAAAAAIDepYPOXbCYBmS6oF/9XSbEzq0nEyLsZAJSNTTWBV26YEUNO9MwulJi+K4LqNWgNReQ5gIaNdhNg8dLiYGgbvt6Ht05ciFqGnbrrq0GYro6Z86cqcouaNcFa2q9TNCkC7/TYFfXrrJtUuk+ZQLi3LnO7FMm/NG1o8mTJw/cJ0e35/ZbA0Hd8WtopIaqumWtgeWtofLKhTG7gMTMNVKZwPrsRyW0nmv/O3bsqMpHjhwJdTQw1401btmMGTOqsuvHGr7prr9u3wW2ZwKa3TnS/c58sMKNNe4eqe3f7aPukxv/dD1uH13b1nOZ+YiIG/913HAfrHDb1+25Onou3Ril/W00hvq2fFRl2DSg1fVR3W8X4qv91gU9uzb5pS99qSo/+OCDoY7Od9zYpuO96/8LFiwIy86dO1eVX3/99VBHx0Sdj5QSxxEX4vvcc8+FZTr/Wr9+faijfdT1v3Xr1lXlr33ta6GOCz/W8ca1UZ3vumPTD2asWbMm1HHXX7mgYw1xd210xYoVVdkFFh89ejQs0zE584GCTEDvaBx/uprbDFPr/KflYzTZvxlb9sn1UR0T3cdA3LILFy5UZXf/P3nyZFV2H1pZvnx5VXbzKDduzp49uyq786H3X3f8+sEK14/dHEmPzc2j9f7j6ujf2pmPM5US73fugzl6jtzfHzr+ZbZVSrxOmbbu/v5Qbh7VOrbpsQxzrBn9oxgAAAAAAABuOTyUAgAAAAAAQO94KAUAAAAAAIDepTOl3HuG+s6mq6PZEy5TQzMlXF6Fe89TM50yeSHTpk0buG6Xu+SyUPRdfPcu7vTp02/4m1LiO8TufV33frDmRbj3VfW9Uvcu7pUrV6qye1/Zveeqx6L5CaXEfBrNeCglvp+beae2lHje5s+fH+roe87uOur2s++iZ/dzkJZMg6xMpkumH2fyuly7dcuUjgluH9278CpzPVwf1WWtuRPuHGk9t2737nuLzPbdu/BK81NKibkHLj9v5syZYZmOJXPnzh24PZdNov3WnUe9H5QSj9/lHOj9JpON6N7pd2N7JgtM153JInC5L+7YMrmPeo5ce9T1uPuoyzDI5Hy0yGaqDUtrNknfNK8p07Zdpon2kd/93d8Ndf7gD/4gLNN7spv/qUymojuOS5cuhWW7d++uyi+//HKos3bt2qrsxj/NnXLzKLffOk6uXr061Mkcm2YqPfDAA6GOa396T3D3CM3ZcpmCOm9zcz23TPfJZYrpPPbVV18NdTTTxeWuujG55d7aev8faTdDplRmHp05jswYkbmOTiYb0f2tpVx7dNvXccuNLdr+Xbs+cOBAVXb5Vfr3aClxbufo38R79+4duO6VK1eGOkuWLAnLdN7ijk3HZPf3QCab0J1bXeYydnVdbv517NixquyOQ//WLiW2E3eP1DqZjOXseJDJlGoZ71rnaKN/FAMAAAAAAMAth4dSAAAAAAAA6B0PpQAAAAAAANA7HkoBAAAAAACgd+mgcxeQqGFvLqBLg81cQG4m6GvGjBlhmYbIuvAv/Z0LSNOgs4sXL4Y6LqBL99MFxmlApgs/0+25wEi3bheapjREzgXGa2jgmTNnQp09e/aEZRpsmwmadOFrmfBB1yY0/P2tt94KdTR8zoXvaZtw4dyu/et5c0Gj2m7cevScZEPVMyHmGXrdsgHumWBfvf7u+LWOC0zWc5IJLHcyQc+Z43fXKBO+mQmMd9vPBJS73+kY7UKMNejYbUt/54IeHQ0fd2OLjr/uHJ06daoqu/Ews0/ugxHHjx+vyi5o3bVJ5cYoDT93Yex633Ljup7/bKivtgl3bnWZO0c6Jrp25LR8DCITUDsaQ31HY9B5ZvzVgNjPfvazoc7Xv/71qrxhw4ZQJxM+fujQoVBHQ2wzwbtuWxqYXUoM+818MMOFaOtxuPX83u/9Xlh28uTJqjxlypRQJ9O39Hh1XlmK/0DQa6+9VpU1DLmUUv7jP/6jKu/cuTPU0THR3f90rlVKvLYuIF8/kPHUU0+FOrrMfVSjNehdx9LM3CIbYt2n0TgmqtZzlPkYTuZau2ur/S9zj3TXX7n7caaNug+W6Hjj2r/+zo1R7m+bzLxJ+7Gb6+zbt68q79q1K9Rx458+E1i3bl2oo3MSN0fVc6vz2lJ8QL22JTf/0+27OZKeI7d9d/01WN1tX6+ba3+67i4/mNWidVujfxQDAAAAAADALYeHUgAAAAAAAOgdD6UAAAAAAADQu3SmVCYvwb2vqu+5u/dl9f3ITO5OKTHnw71n7zIMlL776d4FzWzf2b17d1V2792fOHGiKrvjcO+w6jXJ7M+sWbPCMs05cddx2rRpA9ftzpFe20xek3sX2x2bHot7X1mXuePXLAr3Tm/mHXL3vnzLe7XZvIjM+/n6DrV7p1ozNLL7rNe7tY/o9tz2dT1uW26M0nfPM+9ZZzKdMtty68pcs0zumqvj+pZe29ZMDd2+y+9zGQa6bpeXov0tk1fkrqPLRtB1nTt3LtTRvuUynbTfuP7ochbGjRtXld04kmlbeh3dtXZ03ZncKXduM9vLtO1h5r71mZeQyfgbDbS9aXssJd4T77333lBn4cKFVdm1I5ch8uabb1Zlzd0oJY5Jbh91vHHXWrPpSonH7/KiNm3aVJXdPVKzUd31f+ONN8IyHUsyuT9u+w888EBVdufItT/NnnHXSOcbLi9MM/3cWKu5W6XEvFTN7yullEWLFlVlbWulxCwsd63dHFnHZDf+6jJ3/pW71490ptNIbz8jM0d0f2voPN7NEbRPuL8HXKau9lHXt3Uf3VxH5wSZe20p8Vjc3ELbu/s7Rn/n7tku01n30/UtvW6ur+nY6uZR7pwsWLCgKmfmEZlsVsfN0fQ8ufOm9x/XtrSNuHPk8qIzfyPoeJPJmM387VVKbt7U1dwmM0aN/lEMAAAAAAAAtxweSgEAAAAAAKB3PJQCAAAAAABA73goBQAAAAAAgN6lg85dsJYGZLmAtMOHD1fly5cvhzqZYFsXUKYBhS5YTEOs3T5q+Fom1LqUGEjmjk1D0134m4aIuRA3F76ov3Phl0uXLr3h/pQSw+c1VLOUUtavXx+WZUJ0dd0uRE6XuWvtAko1INmt+8KFC1X55MmToY6eb3f9XfhgJmxP+0gm6M0F3bm2rW3ZhThq2F0mxDrTjl09N0a4dSndJ3etNSDVtTV3bJmgWz1vrv1lrrWONaW0BRK6fqztz9Vxx6brzpy3TB91MuHrbvzVMcJ9aGDu3LlVef78+aGO6zeTJk0aWEf30d2P9Py7Oi5YUs+Ja0e6Lrce3Ud3HTNt1J1/7duujvYJNx65NpIZ7zJ9JBN+2lX4eWZbzs0QdD5jxoxQ5/HHH6/Kn//850MdDaPevHlzqPO9730vLNN5y+rVq0OdyZMnV2XXt7Rtuzbj+o1u331oZvv27VXZjaM6/3HbP3LkSFim4edbtmwJdebMmVOVv/rVr4Y6Gqzr5iiurWuIsJsj6BzRrUf7xNmzZ0Mdd0/S6/bKK6+EOnv37q3KOmcrJTfX0LG+lBjingnaduOf3pPcvCbzEZVhuhmCzt0YqW3E3f/dB7KUXkfXHmbPnh2W6d9k7t6q7cjd63SM1H5Vir9GGn7tPgag7c0dm46b7j7mlukHCtw8Qo/NjSO6fTeOu76tH03IfDDGrbv1g2l6j3AfbNA24cY6PbduH6dPnx6WqcwHk9z4r/M419cyY9SIj2O9bQkAAAAAAAD4P3goBQAAAAAAgN7xUAoAAAAAAAC9S2dKOfqeoXuH8cyZMzcsu/U47l1Y3V5mPV3mPuh7nZncrcw+uvW495z1HWr3vrS+i/3lL3851Pn0pz9dlV3ug3unW48/c201P6KU+A65O1Z3To4ePVqV9d1kt26XFzFx4sSwTOk75aXEc+Lalv7OveesOVenTp0Kddy70HpOXO6YLnN5Ce+8884Ny6X4vCTNlXGZHnq8mf7nztGBAweqsnun2h2/Xtt169aFOvoOu8vP0j7iMiVcH9H2596X12viMhWUO9Zly5aFZXpN3HnTTAV3jXQfs31Uz79rWzomuCwGzVRx2Xyuj2o9148y47hy63HtRs+Ty6LQ8+1yFzTDwGUauPFP99Pdf/R43XnM5CVkuLaVyULJ5C46emxu+zqOubwsXZbNtBtp2t5mzpwZ6ixcuPCG5VLi8bt7lDtvmk/psuC037h+pO3W9SM3Jmo70Wy6UuJ1c2OL3v9cm3U5K7rf7rzpsbhsKs1wmjZtWqjjxq1MXuC5c+eqsuvbuh53H7t48WJYptfStRE9R5m8vEw2jNsnN9fTOaEb//Q43FynNYvudqdzC9f+NXcpkzHr2uPu3bvDMu23mdwf10c0i2jr1q2hjrtHaNty93at4+ZR2kdc7pSb/+vc1s0RdIxwmXLaJ9y9JpOX6fqxzhFd/9f9duNh5m/0TKZhJr8yOx5k7m26LneNWudkow3/KQUAAAAAAIDe8VAKAAAAAAAAveOhFAAAAAAAAHrHQykAAAAAAAD07mMFnffJhYZp2JkLkdNAsEyoqgtRddt3gYxKw8dc0JxyQccutG7OnDlVedGiRaHOk08+WZXvu+++UEfD5zTUrhR/bjV82AX06fnfvn17qKOhfS4M1IWoa7Cgu7YabOnC92bMmFGV582bF+q48FcNH3Qh0hrIuHHjxlBny5YtVXnXrl2hjmsTuj0Xvq2BhC4gUYOVsyHSLrS0hfY3FxirofYuDNHR8+8+BqBtxPXRsWPHVmV3PrSO254Lejx27FhVzoTouu27ZTr+uLFN25Eb13SZa2uu/Wv4uwto1HHEhZjq+OuukdtvHTenTp0a6mRCszMhlpnz5tqtXjcXIqzn24VaunOr63ZtS5e5oGkd/zOBxaX49q50ey7oU9d9+vTpUMe1fw27dWHw+jt3HfV6u/vhaAw617bk5gh6vO6a6T3BtRG9j5YS+5/7YIb2CXf/yYTGujraT9wYpeHnbv5z/PjxquzGKBeQrHMJN0fSD8u4vq33P3cd3dii93vXR7X/uzmahiEvWbIk1HEfaNBgYfcRi8cff7wqu/nf008/XZVXrFgxcB9LKWXz5s1V2fVt7SOu/ev9JvPBilIIP1eZD224uYUuc+O/rtv1dbdMxyjXjvR6u7meziPcWJf5YJe7/2t/dx9s0Dnizp07Qx03/9HtuT6SmW/rmOg+KuA+BqQfv3Af7NHz7fZH7+3ub0a3T/q3lWujeo4yHyxzdTK/czLzj5ZnHa3rzshuP/yu6VcAAAAAAADAx8BDKQAAAAAAAPSOh1IAAAAAAADoHQ+lAAAAAAAA0LuhB51riFYmMCsbDpgJ0tLtZQJK3T5mwnBd+KEGJLoQRQ3IdGHcLnx73bp1NyyXEoNeXfibrtsFjU6ePDks06BxF6y3Z8+equzC/3SZCyN0NHzOhYHr+XZBoxp06wJzXZvQsF8NbCwlBjS6EE8NbXSBoRq06n7nrq2uyx2bBhu6/pe5Jq4/ZsL3tP+5EF091y4M1v1Ol7n2p1yItm7fhUG68EsNmnUhlnq+XTvSvuaO1Y0tui73u8zHF3Tdrj+4c6LtzYWYZvZRx0gXhu/OW2b8031yx5a5j7k2qeOmC4PWOq6v6f3HhbFqGyklHr/bvvZJF/Ss599dI3dOdLx1AaU6jrkwcr0n6XGV4tuf9ls3/umxuHak++2OYzSGGut4d+DAgVBHw6cPHToU6miwtAu6dh/o0DmBC7qdNm1aVXbjqF6jzJhVSrz+bh6n8zZ3bfWcuA9muPBh7duzZs0KdXS+59qx9i1XJxPsrKHCpZTy4IMPVmXXt/RjFBrOXor/iIp+xOOhhx4KdZYvX16VT548Gepo0LO71504cSIs07HMfaBB1+Xm8ZmPumTuG7e7zPnIzCMzf8e5fuz6jc6t3VxbuXu97rc71swczX3USo9FP2BUSry3u48xuIB4nUu485/pIzpuu7HWHb/O5dw4ptfN3SN03e5e74LOtd24eZSu280RdK6T+aiEW5b5YErmeYRbT6ZvZdY9TPynFAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6N3HypTK5ENl6rTSnAG3LV2WyYty72K6d0j1/Uy3bs0LeOyxx0KdxYsXV2WXTaDv3Tsui0PfK3Z5AfoO744dO0KdjRs3hmW6LpcXo9fIZeoM2p+PkskiyryLrtffvXfs8qqUy4J68cUXq7LLS9i/f39VfuONN1LrPn/+fFV2x6/vfrt3uvWauHfh3XnTdbl3kXWZu7aa1+Dee9Z3+N3+uOPXTIndu3eHOnptXT/OvHfv8or0/fTMu/hu3Xpsro5ms5Ti36tXmjvjjl/3WzNGSvHXVs+Jq6PH78YRHWsy2VxumWv/eryur+uY4HIf3Lihx+bOreZFuHuNnkd3/O7c6vG7vAxtIy73TtuIa39u+3q+3TimORcuv0WPw23LZYjo9lwWh9Zx91Gt43InMu1vmPMhR8+lu7dopqQ7t5qN5DKl1q5dG5Zt3ry5Kh89ejTU0Zwrl5em45/rxzqPKiXmfLj2p9fb9T89/mz71/vUvn37Qh3NVHH5obo9dxxu3qjH77Kw9Hy79Sg3/r3++uthWSYvUcc2l02m1//RRx8NdVzOoLaTrVu3hjqaV+Ouo55/N9Zk8lowWCZ3qsv8rpbfZTLtMtlIpeTmdgsWLKjKCxcuDHV0TrBy5cpQx2UxZbJ59Ry5bF5d5vIDXV6yzi0zeZFurqt/a7hjdeOPjv+ZLDJ3/d19S7m/UXS+6zKddY7m2pFeo8zfUaXE8+TurZlMWd1e9u/4sJ6mXwEAAAAAAAAfAw+lAAAAAAAA0DseSgEAAAAAAKB3PJQCAAAAAABA79JJVKMxsM+FBisXLKY0xMuFoTkaIu7CF1evXl2V16xZE+rMmDHjhvtTig/j04A0F+KqoZEu6FbXraG2H7VMw9ZciJqG37njmDp1alV2bc0F67kgN6Uhfu7aaoicO//vvPNOWKZhn25/9HgPHToU6rz88stV2V0jd040SM6FH+s+ufOf+RiAO7bMBwLcskHbzwQmuzDATB/RUN0sbduuHbllLpBQ6Tly578laNDVy4QPuvVoYLKOWaX48HNtkxoYXEouIFL7fyawsZQY2unCN3Ucz4SBurbmAjJdaKfSa+TWrW3LXUd3HjXE040/GjTuwvF1/HPH6gLKtS25/qD3Fhciqv1YP2BQig8oP3bsWFU+ceLEwN+5fqx9NNOv3e/cxyD65O4RGn7uzr/2t89//vOhzpNPPhmWaSC6C4jXsVznA6XEMGy9rqX4cUvPvwsI13W5oHEN6HXt37WtF154oSq7jwjs3bu3KruA4jlz5lRl9zEINx/WMdndNzT8XMfDUmJAvbuObvzX7f2v//W/Qh29Jxw5ciTUmT9/flV2oeYu/Fh/d/DgwVBHx1J3b9HznQ06R83dt3S8zXwwolXLfNTJzKPdWO/+jtG/I9wYvXTp0qq8bt26getxgeWuj+qYqKHqpZQyffr0quzmGq5PKnf+dUx093E9FjdG6TVx9zE3/9G5jftgjT5HcH8P65zEzf3c3+j6Ozf/0vPm+pGO/24e69qfzondtdU6maB7t3133RSjKAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6F06U8ppffe2Zb1uPfqep3sXXN+9dFkQuh73vuTixYvDsvXr11fl5cuXhzqaveKyMPQdTvdu+iuvvBKWaRbBvn37Qh3NC3GZCvoOr3un2b2fq+c2856rO4+6PXf93bXV8+QyFfS9bpd7ou9Lu+Nw7/nqfu/YsSPU2bZtW1X+/ve/H+ocPny4Krt3cV2byORFZfLR9By5d+Ez7/ln+rqro8fhtp95X9+1EX0X27VjPUfuXOv1d1l1mYw79059Jp9G9zszHpYSz5PLwtB1uePQdbttuX6jbTmTTeayCWbNmnXD8kf9TrfvxjbNC8nkdbk+6tqkti03/ug1cbkzmqmQza/S8d/ltWheTybTwG3f5czoutw+6vbdPUpzH/Q3HyWTIaLt1rV/bROZbEK3/ZHOlDp//nxYpm3SZfM8++yzVXnz5s2hzpe+9KWw7OGHH67KLq9Rz60bI3TekJ0j6Dj1wx/+MNTRedOyZctCHZ0juWwWl8Wh2SM/+clPQh3N9HL9SOek2dxTHdtd39Jr4sZ23Sc3RmVyL1370z7ickc008tlA2bm/66Pan93WTzab929zvXtzPhzO2nNdMrkdbXmTum6Xd/SOm4c02w0zXgqxecVaYaT5vC5fXLr1nbsco8y5yjzd5zLvdV9zOau6TJXR7efyfR02VTubzTNmXPXSMcNN4/TbET3t4Ybf3S8zeSVZfJjHTdG6pzYjX86JrtnJDr/dvPx3/md3xm4j/ynFAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6B0PpQAAAAAAANC7dNB5JkQrUyfzu0zQVykxkM2FjykXULlo0aKq7IIuXUC3rsuFuGr4oQs6e+mll6ryyZMnQ50tW7aEZRrk5kIkNfwtExjoZELEXUCmht9pqFwpMeh53rx5oY4LFtSwYRc0qet2x7Fw4cKq7IIO3e9OnDhRld01+vnPf16V3fFr+FwmsNstc/0mE2Ku3HXMtJtMiGEmxDITGJoN+suMSZnt67l116g1DN6tS+nY5o7fBRRq2KKro+Po1KlTQx0NOnRhyBr0WUr8iIALsZ09e3ZVXrFiRaijwZ4aKlmKDwjWQFwXYqnr1uDzUmKwr9u+u/9ou3XjiC5z11ZDS929xu2TBoIfO3Ys1NH7iAso1XuSCzp2Y5t+2KP1QwN6Hl0Ya2tAqPb31jEqE3Q+0tzYriHy7mMsem3dtd65c2dY9oUvfKEqf+5znwt11qxZU5UzYayOO9fatzZs2BDqaLvRcPZS4sdI3BjhwofnzJlTld34+y//8i9V2R2rHseBAwdCHXf8jz76aFXWD6+UUsqqVauqshsjdGx1Y4SbN+k45fqWjgn33XdfqPPII49UZXf/cWOizv/cfis3tugyd65H+iMGN4PWoHPto63jauYauT6qcxT3oZWlS5fesFyKn3/o9tz4q/dfF+KtbdvNtTIfyHnmmWdCncwHo/Q43N9sbllm/qPn2wVt6/HrR15KyZ1b/buulDj/ch8n0vHH3Wvd+KPrcuOotvdM+898HKyUeE3c/ScTdK4f+nAfoyDoHAAAAAAAAKMSD6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDv0kHnTmuweVc0tE2Dr0spZcGCBVV55cqVoc78+fOrsgvaduFfGiyugaGlxPBPV+fo0aNV2YXIuoBYXdYa9KxBZy7ELRMs68LXdN0uoE+P1wWGumubCWjTQFIX/qfhgy6gzoUfvvDCC1X5+eefH1jHBf1lQgTd+dfQOhdiqKF5rj3o9jPbcjJ1XNvS7WdC9d05cuvOBB2rzHl058iFGGqbdOvW32UC011gpzs2Hcvmzp0b6mjfcmGcuszVcfuk4ecahuh+566tjnXu/LsxwvV3tWnTpoHb1/Pvxhq3fT02N0bq9lavXh3q6Lhx6NChUMeFH2tosQsxfvvtt6vym2++Gerofcv1NXdsLXOEzAcTslr6vwsazYyjzmgLOndh+Dre6nyklFxgvwvR1vB3Dd4vJd6jly9fHuroBxPcdXTh4xpsq4HlpcSxxQXk6nG4EFc3t5s5c2ZV/uxnPxvq6MdQzp49G+osWbKkKm/evDnUcQG9n/nMZ6py5kMLbq6p44/rI+68adhuZm7hxmy9R+7duzfUcfP2TP/PfERCx3t3j3B9y833bmet86/MOKpta/z48aGOa1v694aONaXEj2G5dqx/I7i/Y1xAtLYb1450/NmzZ0+oo3MNN45kPmLkQtz1nLj5j/Z1N9a48G09NldHP0bl5h8XLlyoyu4cHTlyJCw7fvx4VXYfGtNluq1S4piQ+ThVKbm/o7VO5oMt7lo7Let2c31tE66tf+Mb3xi4P/ynFAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6F06U8q956vvFWZyJtw71rpul83i3mHUfBJ9776UUhYvXlyVZ8yYEepoPorLBnDvomoWkr6bWkp8P11zqEqJ7/C7c+0yLPS95q5yN9y5dvuk3LXNZPHoOdJ3/EvxGTa63+5dbM15efzxxwduX98fL8VnOPz3f/93VX7xxRcH7qN7X1rf/c3kd7l1u+vfUse1Nbf9THvTY3Hr0XfP3bvQmWyezLFlcqcymVKurbtlmUyNzPWfMmVKVXbZUO4dbs0n0vWUEvNRXBaAjsluHNNsgFJiX9aMlVJizpHmwJRSyrRp06qyGw8czVlybUvfhXftSPMpsmOtjkmu3bqcw0Hrcdl8LgtFs6c0v6aUUnbs2FGV3fifyVTqKmPS9VGVPf/alzPzmEymQuv2R5pmvJUSszBcFolrE4PWU0qcI+3bty/U+du//duqfN9994U6mjPlcjddzpDmY7ksEO1/c+bMCXV0HHeZKi53UsdEt+7Zs2dX5e3bt4c6OrYfPHgw1HFZXOqRRx4Jy15//fWqfOnSpVBHx5GNGzeGOprNV0rsy+68aZ3nnnsu1NFzollZpfi5vY7/mf7v7m06j3T9n/yowTLztsyY6e7/U6dOrcpurHM5U/fee29VdvcxHdsyuWsuv85l6u3fv78qu/6nY7L7O0LnbZn7eClx3HLnVq+b276eN3et3d92mUxNzXRy11bHepcppRm/pcT5j5tbZXKfbnfu2rbgP6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3qWDzjMhpi5YLRMirEGHLmhXQ81LKWXDhg1V2YX4akChC4M9ceJEVXaB5Rr0WEopp0+frsouRM4tUxr054KOW2VCrIdJt+eurYaBuqDjpUuXDly3C9H8jd/4jarswkg1aM+FiD711FNh2csvv1yVXUCmBuK5a6vX312jYYaYt9Rxy1z/12WZwPRM0LA7jy5od9B63O9cG501a1ZVdqHiGsbt1uXOo65Lg8dLiQGdLozS7beey0mTJoU699xzT1V2QdOZMToz1rnwSQ0NdWO0fnzCnSMXvqnB6i6MVs+ROzYNGnfX3wVk6jlxH1FQGnxaShy3NBy+FD9uvfbaa1V5586doY72pUzQ+GgL8P44Wo4l+5vRFojqAnozYfB6HG4cc8eqffmtt94KdbS/6XyslPgRETfWz58/Pyxz80b1uc99rirrx3FKiWOLu4+4MUE/EKHz0VLiHEX7bCml/Mmf/ElVfuCBB0IdF2K+devWquzmyK+88kpV1uDzUmKwvPvwj7v+OidzH5rQ+Z4Lo9bw+2XLloU6u3btCsu2bdtWlV271XuCG/+0jutH7v6X+UDA7aT1fGj/c/MfbduuP7r7v95L3Ye2dJm71tpu3IeX3NxGA6Ld73Ru4fpa5p7kzr/OpVwf0Tpu/qXH7+bo7iNiDz/8cFV2fUtpOHkpcRx75plnQh33EQkXLI+Rw39KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3nWaKeVk3iHWd2Fdpox7F1h/595X1fdFz5w5E+ro+7r6jm8puSwGd6yZvKBM7k7mHeJsFpHqKmcq806zq5PJtHB5LStXrqzKv/mbvxnqaKbE0aNHQx3N1NGsqFJK+clPfhKWaa6Cy7TSc+ves870kcy1bc2UyrRRR/c7m0U1aPvuOHRZJr+olNhv3Lq1vbm8gvXr11dlbTMftW7NS3HjmG7PZSNl3tfP5GW5vATNMHCZBpcvX67Kbox0eQmZdSv3jr/+bsaMGaGOy4/RvDp33rQtufFH8yncvcZZs2ZNVXZZDJoh5fqM3rfefvvtUMdlwWiGT2aMducokzN1K8vca53RlinlZLIBB/3mo36nfcv1bR1L9+7dG+pozpC717vcSbVq1aqwbO3atVXZtX+d/7nco8mTJ4dlek7c2P71r3+9KmsOVCmlfPe7363Kmmdais9Z0fuPHmsppRw+fLgqP/vss6GOHr8bf7/whS+EZXq8mvFUSikLFy6syu48aj9y87if/vSnYZne/1ymqI7lmh9ZSswrc/lV7t6KWnbepvTcuvmH9rVsfpXOW9y9LvM3qu6jzplK8cev442rk/k7btB6S/H3Iz2XbvzVDDc3/9U50oULF0KdJ598MizTeZubR+q5ffrpp0MdzeJ74403Qp1MXpVrN5l5081wr78Z8J9SAAAAAAAA6B0PpQAAAAAAANA7HkoBAAAAAACgdzyUAgAAAAAAQO8+VtB5S7CXCxHTZS7ozQVkbtmypSq7MDINOtTg21JisJ1bj9tvDS3OhJE7+rvMb9w+ZQLaugo1z65b6+j1KCWeRxfq50KMlYZ6lhLD/lzQ8LFjx6ryj3/841DHhf/p9tz2MyHuev3ddWwNEe9KS9DiMLlQURcifv369aqcCWx2IY7Tp0+vyi4w1Y0tmTEy00YyIZqub+nvzp07N/B3LmhSj81ty/1OQzRd0Ln7iIGaOXNmVV6xYkWoM3v27LBMgy1diK0GdLq2pfcf91ED/fCCW/e+ffsG7qNrM9u3b6/K7mMMmzZtCsv0fLuxRftEn+NKlu5T9h6Zkel/g37zUQg/HUzHDddGNQx7yZIloY6O0aXE8+9CtPWjASdPngx19D7i7jXunjB37tyq7ALC9Xf6cYRSSvnZz35WlV0Y+pQpU8Kyhx9+uCq7sVbHRHeO9EMLbvw/depUWKZjstvHQ4cOVWU3/uq8TX/zUTIfmlmwYEFVnjZtWqij46g7j2PHjg3L6P+DZcZ2XebmMRqs7dbj7m0j/RGPlu23Bm9nlrmPuOgYuXz58lBHP2rgPkbh+oiO/+5vvb/+67+uym+++Waoo3/HZULNS4ljQuZ6jLa/h24lnFkAAAAAAAD0jodSAAAAAAAA6B0PpQAAAAAAANC7dKaUexfVvZ896HfunV59P9O90+kyVHSZe89Tt+e2r8fhMq3cssw7q7o9t4+aoZA5Dif7DvVIclkMmk/grr/L+Vm2bFlVdrlj+u6/vvdcSikvvPBCVX7ttdcG7mMpMVcmk3Pk2pFmUWXzSrrKVWl9P125dqu/c+vR9p/JZnPbcuORXrdMHzl//nyoc+DAgaqsWUGl+HarbcTlpeg7/G5c0UyLTH5bKTHTKTOOue3rMpef5fqfy5BSet3c9T979mxVdpkSmp9SShw33HnTvJhJkyaFOpr7omNPKf7aHj16tCq7dqP79OKLL4Y6mhelGTOl+POv/cRdf60z0jkoo+2eVUp7NuNIn8ubQSZTUfNi9u/fH+q4vCLNJ3Fju45tLhtJ+42bj7r7j46Tbr+1L+t8pJTY3jQHqZRS3njjjbBMs1fmz58f6qxdu7Yqu7w8vSZuPuTmTXpt3di6aNGiquzO7ebNm2+43lJ8ppeO/y53UMdkvde4ZW4e6zJF3XlCLfM3WmYe2dVYm8kLymwr8/dxKbkMo8z9pvW+qfs5fvz4UEczpNzfOqtWrRq4P65v6XjzzW9+M9R56623qvLBgwdDHbdPKpOpiZHFf0oBAAAAAACgdzyUAgAAAAAAQO94KAUAAAAAAIDe8VAKAAAAAAAAvUsHnbsQXQ2EywTUZYJWXRhZZvuZ8Du3j7rMBdR1GT4+aD3ZwDpdd+YcteoqVNsFjWsY6NSpU0MdF5iswXbTp08PdTRE75/+6Z9CnZ/+9KdVORPGV0ruumn4pWujmTBipzV8t2U9mYDw1vDJloBK10ddYKGu2wWUKheGq0GLEyZMCHVcu9Vz5ELENYzchYjrsuyHFzTEO/sRhxat7T/TjvR6nzhxItT58Y9/PHAfp02bFpbpmDR27NhQR6+tXrNS/PnX0FAX0P6DH/ygKrvA4i1btlRlF2rugnZbQjzd/VevSVf3g1LaQly73L7qct0EnQ+mfcSNrdrfXND/yZMnw7LPfe5zVVlDtUuJ/d/NIzJB3+6+oaHpR44cCXVeeumlqjxnzpxQ54knnqjK+gGXUkr50Y9+FJbphxa0XEocE9evXx/q3HvvvVV59+7doc7x48fDMg02nzFjRqhz5syZquyuo963XRtxH8PReZu7t7799ttV2QWt633UIdS8TVdjZFd/j3W1P9l7b+bepnM093ed/i7zt0YpcbxbvHhxqKNj0po1a0Id12+UCyP/h3/4h6r8+uuvhzo632v5u/qjftfyNzL39eHhP6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3qWDzl0YmIZ9uTouNHWQTBh5KTH8zdXJhLFntuVktq9aA9rcudVlmWvUpZbw2QsXLoRlEydOHPg7FyKt4ZMvvvhiqKPH/9xzz4U627dvr8rZoFutlwkIdtdWj8OtJ9snWnTVJzJtNBO+2PpRgcxHFDKB7W5bFy9erMousPHw4cNhmQa0uuPX/e4qsN1xbcsFZCvdJ3euMx+6yIxt7vprGLEL1XQhtvrRAhfGqyGeLkRXj0PDcUsp5e677w7LNFj4qaeeCnU0xHzv3r2hjgYbZ4P+M2OUXkvXHjLXsauA8Naxbpjh563bIhB1MA3t17G2lBhi7T584u7tGqJ93333hTpz586tym4+otufMmVKqOPGZA0237NnT6ijYejuoyo6jrig8UOHDoVlOm66cXvnzp1VedWqVaGOhp/rOSsl96GTbdu2hTp633T7qOO9u/6ZD224e0Tm3pqZ62bmFre71o9odDVHdevWe2lmbpOZ67Tej7L3dpX5YNHMmTPDsmXLllXlDRs2hDp63dwYqeO2fhyolFJee+21sOz555+vyu4jDi0f48n2UdX339Go8Z9SAAAAAAAA6B0PpQAAAAAAANA7HkoBAAAAAACgdx8rU0rf83Tva+o73Zn1ZN6fdevKZFFk3nvPZOO4dbfmpajsu8iZ99yH+U57Sz6WZhyUEt/h1/yGUvy70Lou9776d77znaq8devWUEevicsUcBlCmv2QyYsaN25cqJPJJsssG2ad1pwnrZPpW5ntZ9t6JlNC1+XaqF4jt55MFo9rR9pu3Xoy79Rnx03VkoXg9tH9Ttu/o/vtMhU0U8T19RUrVoRlCxYsqMqu/+nxu2v7wQcfVGV3/Dt27AjLNC9B81vc79w9Srfn6rRmWGi/cdcs0/9GWp8Ze1lkUQyWycfT/uf6sdYpJfY3l7uk+VAuL0Xv9ZMnTw513BxFxxLNuCol3hPcGLFr164b7k8ppYwdOzYs07HV9dvz589XZZcFo/vtxt/3338/LHvvvfeqsssL0/EmkzuZpet295YWZEW1ackGKiV3v2m9J7XkNWVk24iuO7M/rv/pelzu3fz588OyWbNmVeXp06cP3P6mTZvCMs29c/lRbmzTfLhsXqnKZGplfufGn5ZMMbThP6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3qVT/1yIl4YGujDClhBdF+KWDX9Wur3WoGVHj98dmy7LBP21hihmzpHbfibo2YWI6nXKBNS5fdTQ0AkTJoQ6K1euDMu03gsvvBDqvPzyy1XZXSM9DtfWXRhrJlhP24gLMezqereGmGdk+r/Tsv3WDwa4fdQ26fY500cz44hrW5mPKGTWM2i92XVnPvTQOo6432m/cdvXPpEZI5ctWxaWLVmyJCybNGlSVV66dGmoo+OIhgqXEkN8L126FOq8+eabYdnmzZur8vHjx0OdTEBmJjA+o6ug2a5C1d2yLkOEW/pSVx8syW6/T+7YRts+uo8IKDcfcddE5zKuH2mfPHHiRKiT+RiFmyPo9tx+Z+4tyn0wIyMz/3H7+Pbbb1flbNCvjjeZsc7N/1vv/6OtbeP20eU9MjNHXbRoUVV2H35xf1s9/vjjVXnGjBmhzv79+6vy008/Heps2bKlKutHDkrxY4ty/b9l3tJl3+9q/oPB+E8pAAAAAAAA9I6HUgAAAAAAAOgdD6UAAAAAAADQu3SmlNOSD9NlXsTNoOV4s7/JnNtMXoZy7++6ZZlMKTV27NiwbNasWVX5/vvvD3WmTJkSluk7y88880yoo1kwmbyybH5Zaz7SsLbfVR0nm6HSomXdXeautbwf7n4zGse2TO5Z5vy7LLQMHRMyY4TLZpk+fXpVXrBgQajjMqXmzJlTlU+fPh3qaM6Yy0I4cuRIVXa5U9u2bQvLzp49W5Vdu9F8mq7yo7rUZT5Gy7a67O9drLv1fLRmwd2sMudJ62RyR1rH8UymaCYb0O2jWzas9tclPd5MNpM7R+4ekbkmLdmw5EfhVpWZI0ybNi3UWbVqVVWeOnVqqKO5U6WUMmbMmKqsc51SSvnXf/3Xqnzw4MFQR3M2W8e11txL3Bpu3dkQAAAAAAAARi0eSgEAAAAAAKB3PJQCAAAAAABA73goBQAAAAAAgN6lg85bAxu7Ch8cZtBpV7raVmvQeYYL+syESGa27661BosvX7481FmzZk1VdmF8LkR0+/btVXnr1q2hzl133VWV3bHdDEHjw9x+Rldh6K6OrjsTRut0dW6HGZjaEuo6bHpuMyGy7nq4jwho+G0maF1DPUspZfXq1VV54sSJoc7kyZMHbt8d2/Hjx6tyJsR87969oY4GfTruvI3GYPNhaR1/+uwTw5wzjMag85EO2h5tujwffZ7b1j6SCTHPaP2IxqD9KSX2m+zHEEa6bwG/KtdmNYx8xYoVoc6ECROqsvvwy9133x2W7d+/vyp///vfD3U02DzzMQo3H3T9NhNs3uccHSOLERsAAAAAAAC946EUAAAAAAAAesdDKQAAAAAAAPQunSnltOTVZHKnstkww3qvdDTmV7WuW99Pbr1GmSwq9zvNfpkxY0aoM27cuKrs3jF+7733wrLXXnutKl+5ciXU0XyaO++8M9QZbZlSXWY6DFNLhlRXfavLa9TVONJVpkfrbzLH7zJtMnkdrWN0hv7uiSeeCHUmTZpUlVetWhXqTJ06NSy7fPlyVXbHtmnTpqq8ZcuWUEczFa5duxbquP6g44373a2iyzbRldb73bBk8xoxuox0O8rcI7q6t7Vuy2XIZOY2mXsLGTK4Xbh7hM5/9G+mUmKmpptr7Ny5Myx79tlnq/KpU6cG7qPr6/p3m+Z5ltL+9w+ZUrcP/lMKAAAAAAAAveOhFAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6F066Lw1RLh13S11bhXDPFYXIp4Jo3bBdhrI5343ffr0G5ZLKWXu3LlVec6cOaHOvn37wrJXX321KrvwPw3EcwF5emytIdou6Lg1oLtFNvy6xWgM/89sq8+g+ew+qUwbzaw307YzQeeOtm03jrhlum4N4yyllAcffLAquzFi2rRpVXnWrFkD97GUUsaPH1+Vn3766VDnlVdeqcpHjx4NdVxop3Ln8f333x/4O/0Yw/Xr1wf+BtHNMEcg6PzmNNJtK/PBiszvhvlRj+yyFpm5FuHHuBW4jzFNmDChKi9atCjU0Y+6uLnOkSNHwjL9QJT7W0/nP12Oh5kxivvm7YP/lAIAAAAAAEDveCgFAAAAAACA3vFQCgAAAAAAAL3joRQAAAAAAAB6lw46d7oKOu8q2HekwygzRnofXWBc63XMBJ1PnDixKrvA4PPnz1flT30qNsuNGzeGZe++++7A7Wv48pgxY0Kd1jDyloD+1lD/PtuNCxFtNayg9ex69Vj6Dkxsud6tga2Zbblrq+ckE9h/7dq11D5p0PiGDRtCnRkzZtzwN6WUMnv27Kr83nvvhTouIP3ZZ5+tyi+//HKoc/z48arsxqiWwPosFxA/2g0z6PRWRmArutD6UZOu5ujZD220rNvJ3CNvp3EEty43t9I50oULF0KdH/zgB1VZ/z4qJdfXMh91cWHouu7svIYPFOD/xX9KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3qUzpYb5vnrr9kd6n1oyrfrcHyeTF5PJnSrFv1es7rrrrqp8zz33hDq67K233gp1nnvuubDs+vXrVdll0XT1vnKm/bVe25FuN9omXBvJtJvWTKPMerrKuRrpvK6+cy+0/bs+q/29tc+4/rdixYqq7HKfxo4dW5UnTZoU6uh+u/bw/PPPh2UvvfRSVd6/f3+o0zJuto6RLq/hZsgZuln7yLDyKoaZn4nRp6v80q6uf3Y9Xc1RMtyYrPvZVe5UNtOKnCncbFyb3bVrV1XetGlTqHPx4sWB67777rvDssuXL/8Ke/f/yeROOa1/o+qy1u1j9OM/pQAAAAAAANA7HkoBAAAAAACgdzyUAgAAAAAAQO94KAUAAAAAAIDepYPOndEWIjjMwOhMiGLfgdUt4Zuf+lS85Boid+3atYF13Lp+8YtfDPydW8+YMWOq8t69e0Odc+fOhWUakOy2r8ei23L6DuzP1LlZ90mv9zC3Ncww+kxAY2b7rUGvmf12624JiM2GeKvJkyeHZStXrqzK2mdLKWXZsmVV2Y0/J0+erMqnTp0KdZ5++umw7MiRI35nPyZ3Ptx5c2OSyoSoj7SRvte3tv+MPsPHR+O1xa9upAPrs/fIYX7opWVbI/2hEeBmdOzYsars/o5T+pGpUnKh5pkPDWXmNe6jOpnfuXsk983bB/8pBQAAAAAAgN7xUAoAAAAAAAC946EUAAAAAAAAepfOlHLvi2eyMPR3LlNIf5ep0yqTu+K05sW01OmSbi9zrO69X/d+8AcffFCV3TvM+u7z7NmzQx3Ni3n99ddDnXHjxoVl2ibuuOOOgXVcO9J9bL0erXkJLbk/2XVntOZFuDYxaN2Z99WdzLYy28/UyeyPOx+ubWXWlVl3S36cq5fp227d2tddNptmQ5US+6QbI1xenNq2bVtV3rhxY6hz9uzZsCzT/jI5B5k6Tma8bbm3DTM/JiO7rZHOlGvJS3N9tqUfO2RjoAtdtUcAo0vm3vrhhx8OrKNzto+z/Zb5T+ucCbc37mwAAAAAAADoHQ+lAAAAAAAA0DseSgEAAAAAAKB3PJQCAAAAAABA7z5W0HnGMEO8MyG2w9qWW9Z6joYZUKuBmJnAehcY7oKmMyH2ly9frsrvvvtuqLNjx46qfPXq1VDHBZ1fu3atKmtguVvm6nQVdD9MXQUEtwak9hlQ7PaxNQw+sz+txzasdXcV6lxK7LeZj1G0hjG766Zjyd133x3qXLhwoSpv3bo11NmyZUtVdiGebvvXr1+vyiPdjzNaP2rQVfj5MM/RSJ/rzLG11skg6BwAACDiP6UAAAAAAADQOx5KAQAAAAAAoHc8lAIAAAAAAEDveCgFAAAAAACA3qWDzp1M+GcmoLjPwHLndgpx/cUvfhHqaEBwa2CuhgqXUsqpU6eqsgsxfvPNNwdu3wWta4iyq6P71GWIcJ8BuU4mtDxzbVvW26WW89jVtrLbb92fzMcQumoj7ncarJz50IGrox8IGDNmTKjjQsynTJlyw/WUUsrrr79eld96661Q5/3336/Kro26/q/L3LEN80MTI22YIf5dbT8j81GNVn3etwk6BwAAiPhPKQAAAAAAAPSOh1IAAAAAAADoHQ+lAAAAAAAA0LtOM6VczkNLplRmPX1rzeIZ5n53lQ+U2UeXhaHb//DDD0Ods2fPVuWLFy+GOpcuXarK48aNC3XcujXXJnP+M5k62UyjliyW1vW05Edlt9+SO/Wr1LvRtrpab9Ywc7+GmXvVOo5kMmw058nlzmWysa5duzZw2c6dO0OdN954Y+B6JkyYUJUvX74c6rgx4s4776zKH3zwQajTd4ZaF7rsI31mSnXZtrvSZ6YdmVIAAADRzTcbBwAAAAAAwE2Ph1IAAAAAAADoHQ+lAAAAAAAA0DseSgEAAAAAAKB36aDz1oBgDfbMBC0PMzC87xBl1XocrWG0d9xxR9P2MuvWZZ/85CdDHQ0fdkHDGkbsrr8LP77rrrtuuC1XpzVoezQGnXcVYt5ViLHbR20Tbltap3U9mb7V1fFnw7Fb9qnLMOhMiLlyYcy6zAWN7927NyzTjxhs3bp14PZ1n9163DnScaSUOCZk+pHTOra3fOgjE4Y9zKDzzD66Y+3qIwLDvEdmfjfM4HeCzgEAACL+UwoAAAAAAAC946EUAAAAAAAAesdDKQAAAAAAAPTuY2VKZXNVBq0nk03jciZatp+RzYvoKueqVVdZOJnck0wWi8uU0gwNl/uidVzuxtixYwfuk8vL0e253KlMpllXeSVOV7lPXeVOufVklrk6mg+U2b5rR5n9cbRtZ7JwWjNl3D5lMmRaxrHs2KPrdu0/s496HV3G29GjR8OyEydOVOX33nsv1BkzZkxVdtdf+7ZmxZXiz+P169erssvYy+QeZnSVKeUMMwsxs63MPdrJjG2ZPjpovR+lJQsrO/61IFMKGN3cGKH9NlPHLWuto9vLrAcAbjb8pxQAAAAAAAB6x0MpAAAAAAAA9I6HUgAAAAAAAOgdD6UAAAAAAADQu3TQudNV0LmG+LUGnbcGj490iOwwaUCwO7cu/DijJcQ3c21dHRd0rsfmQoxbQtxbQ3wzQZMuxLk1RLgloLt1+26ZrmuYIc6t5ygTYt0y/rQeq2sjmd9lxja3nszxa4i525bWcdyHBt5///2qrKHmpZRy9erVquz68bhx4wZuK/MRA3f+u/rQQFdB510Gn7fcE7vcfp9B55l1O30GnTuZNgKMpJbA7tbfZULEh7l9AMDI4D+lAAAAAAAA0DseSgEAAAAAAKB3PJQCAAAAAABA79KZUl3lxQwzL6M1Uyqz/dZMl5Y6rdw+6vZcNkxrhofLdRn0O5f7cueddw7cH7ctXabrKaWU69evV2WXTZXJ1HJZTCqTO9LaRlrbTSbTpTVTS7X2v8zvWjOdMsfSkumSPUdd5VVlspEy58S147vuuqsqu/7Q0tfdut0+ap90Y9S1a9cG7o/r23pudT2ldJcplbkmXfXt7D1K62XqZLaXXc+wMqWy5zGz3y3Xv8v7uLabzL0G/eoq06irvKRhbr+reTQAAL8K/lMKAAAAAAAAveOhFAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6N0nfkmqIQAAAAAAAHrGf0oBAAAAAACgdzyUAgAAAAAAQO94KAUAAAAAAIDe8VAKAAAAAAAAveOhFAAAAAAAAHrHQykAAAAAAAD0jodSAAAAAAAA6B0PpQAAAAAAANA7HkoBAAAAAACgd/8bQYaLGb7jjI0AAAAASUVORK5CYII=\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -410,9 +418,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "id": "bee5913e", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -455,9 +464,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "id": "6c0ed909", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -468,88 +478,286 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 100%|██████████| 125/125 [00:27<00:00, 4.61it/s, loss=0.723]\n", - "Epoch 1: 100%|██████████| 125/125 [00:27<00:00, 4.60it/s, loss=0.276]\n", - "Epoch 2: 100%|█████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0965]\n", - "Epoch 3: 100%|█████████| 125/125 [00:27<00:00, 4.62it/s, loss=0.0376]\n", - "Epoch 4: 100%|█████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0224]\n", - "Epoch 5: 100%|█████████| 125/125 [00:27<00:00, 4.47it/s, loss=0.0187]\n", - "Epoch 6: 100%|█████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0179]\n", - "Epoch 7: 100%|█████████| 125/125 [00:28<00:00, 4.44it/s, loss=0.0169]\n", - "Epoch 8: 100%|█████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0161]\n", - "Epoch 9: 100%|██████████| 125/125 [00:27<00:00, 4.50it/s, loss=0.016]\n", - "Epoch 10: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0156]\n", - "Epoch 11: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0152]\n", - "Epoch 12: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0152]\n", - "Epoch 13: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0151]\n", - "Epoch 14: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0147]\n", - "Epoch 15: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0151]\n", - "Epoch 16: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0151]\n", - "Epoch 17: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0146]\n", - "Epoch 18: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0144]\n", - "Epoch 19: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0143]\n", - "Epoch 20: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0145]\n", - "Epoch 21: 100%|████████| 125/125 [00:27<00:00, 4.53it/s, loss=0.0143]\n", - "Epoch 22: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0138]\n", - "Epoch 23: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", - "Epoch 24: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0134]\n", - "Epoch 25: 100%|████████| 125/125 [00:27<00:00, 4.49it/s, loss=0.0135]\n", - "Epoch 26: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", - "Epoch 27: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0136]\n", - "Epoch 28: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0135]\n", - "Epoch 29: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0131]\n", - "Epoch 30: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0128]\n", - "Epoch 31: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0129]\n", - "Epoch 32: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0128]\n", - "Epoch 33: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", - "Epoch 34: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0138]\n", - "Epoch 35: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0131]\n", - "Epoch 36: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0132]\n", - "Epoch 37: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0125]\n", - "Epoch 38: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0124]\n", - "Epoch 39: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0124]\n", - "Epoch 40: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0132]\n", - "Epoch 41: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0128]\n", - "Epoch 42: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0122]\n", - "Epoch 43: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0127]\n", - "Epoch 44: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0129]\n", - "Epoch 45: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0132]\n", - "Epoch 46: 100%|████████| 125/125 [00:27<00:00, 4.53it/s, loss=0.0125]\n", - "Epoch 47: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0123]\n", - "Epoch 48: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0123]\n", - "Epoch 49: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0125]\n", - "Epoch 50: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0127]\n", - "Epoch 51: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0125]\n", - "Epoch 52: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0124]\n", - "Epoch 53: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0127]\n", - "Epoch 54: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0123]\n", - "Epoch 55: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0127]\n", - "Epoch 56: 100%|█████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.012]\n", - "Epoch 57: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0126]\n", - "Epoch 58: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0121]\n", - "Epoch 59: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0126]\n", - "Epoch 60: 100%|████████| 125/125 [00:27<00:00, 4.60it/s, loss=0.0119]\n", - "Epoch 61: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0122]\n", - "Epoch 62: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0119]\n", - "Epoch 63: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0125]\n", - "Epoch 64: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0121]\n", - "Epoch 65: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0121]\n", - "Epoch 66: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0117]\n", - "Epoch 67: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0121]\n", - "Epoch 68: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0123]\n", - "Epoch 69: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0121]\n", - "Epoch 70: 100%|█████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.012]\n", - "Epoch 71: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0118]\n", - "Epoch 72: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0117]\n", - "Epoch 73: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0119]\n", - "Epoch 74: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0125]\n" + "Epoch 0: 0%| | 0/125 [00:00 24\u001b[0m noise_pred \u001b[38;5;241m=\u001b[39m \u001b[43minferer\u001b[49m\u001b[43m(\u001b[49m\u001b[43minputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mimages\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdiffusion_model\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnoise\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnoise\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcondition\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mclasses\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 26\u001b[0m loss \u001b[38;5;241m=\u001b[39m F\u001b[38;5;241m.\u001b[39mmse_loss(noise_pred\u001b[38;5;241m.\u001b[39mfloat(), noise\u001b[38;5;241m.\u001b[39mfloat())\n\u001b[1;32m 28\u001b[0m scaler\u001b[38;5;241m.\u001b[39mscale(loss)\u001b[38;5;241m.\u001b[39mbackward()\n", + "\u001b[0;31mTypeError\u001b[0m: DiffusionInferer.__call__() missing 1 required positional argument: 'timesteps'" ] } ], @@ -569,6 +777,7 @@ " for step, batch in progress_bar:\n", " images = batch[\"image\"].to(device)\n", " classes = batch[\"class\"].to(device)\n", + " print('images', images.shape, 'classes', classes.shape, classes)\n", " optimizer.zero_grad(set_to_none=True)\n", "\n", " with autocast(enabled=True):\n", @@ -627,23 +836,34 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "id": "f8385176", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAArsAAAILCAYAAADoqVT3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABsNklEQVR4nO3deXwTdf7H8ffk6EFLkVuEolYERESrKKgccixWLu9rVwFBRfFAcFVcF11cZAEX3Z8WVlyVXV1PVNBVAQURUVEsKogKHhSkgCA3PWiTzPz+SJM2toVOm5Jp+3o+Hn00+WZm8s2nQd/55jvfMSzLsgQAAADUQa5YdwAAAACoKYRdAAAA1FmEXQAAANRZhF0AAADUWYRdAAAA1FmEXQAAANRZhF0AAADUWYRdAAAA1FmEXQAAANRZhF0AOMJef/11dejQQR06dFBOTk6suwMAdZon1h0AUHdde+21WrlypTp27Kg33ngj1t1xjEaNGqljx46SJK/XG+PeVE1RUZHeeecdLV++XF9//bV2796tgoICNWjQQG3atFGXLl10wQUXqHv37rHuKoB6zrAsy4p1JwDUTYTdumnhwoX629/+pl9++UVSMLA3b95cDRo00K5du7Rnz57wtmeccYamTp2qtm3bxqq7AOo5RnYBAJX2xBNP6NFHH5UkpaWl6bbbblPv3r2VlJQU3ub777/X008/rTfffFOrVq3SVVddpf/+979KS0uLVbcB1GPM2QUAVMrixYvDQXfAgAF64403NHDgwIigK0nt27fXtGnTNHPmTHk8Hu3atUvjx4+XaZqx6DaAeo6wC8DRsrOz9eCDD+qCCy7QaaedptNOO039+vXTvffeq7Vr1x5y302bNumhhx7SkCFDlJ6erk6dOumss87SH/7wBz3//PPy+/3l7hc6eeztt9/WDz/8oOuuu05du3bVlVdeGd7m2muvVYcOHXT//fdLkpYtW6ZRo0apR48e6ty5s3r06KE777xTGzZsKHP8ik5Qy8nJCbd/9dVXKigo0BNPPKEhQ4bo9NNP16mnnqrBgwcrMzNTRUVF5fbd5/PpmWee0cUXX6z09HR17dpVV111ld58801J0osvvhh+Djt8Pp8mT54sSWrXrp1mzJihuLi4Q+7Tt29fDRs2THFxcTr66KP166+/hh+bMGGCOnTooL59+1a4f2XrlJWVpSuvvFLp6ekaP368/vnPf4Yf37p16yH7OGLECHXo0EEZGRllHluxYoXuuOMO9enTR507d1bXrl01ZMgQPfzwwxGv5bd++eUXTZs2TRdeeKFOP/10de7cWT179tQVV1yhf/3rX9q9e/ch+wQgupjGAMCx5s+fr4kTJ6qoqEiGYahly5ayLEs5OTnKycnR/PnzNX78eN1www1l9l2yZInuuOOOcCg8+uijlZiYqG3btikrK0tZWVl655139PTTTyshIaHc58/NzdX111+vPXv2qE2bNmrYsGG52z399NOaPn26kpOTdfTRR8s0Tf3666966623tGzZMs2bN0+pqam2XntBQYGGDx+u1atXq1WrVjr66KOVk5OjH374QT/88IPWrl2rJ554ImKfwsJCjRo1Sp9//rkkqUGDBmrevLl+/PFH3XXXXfr88891wgkn2OpHyMKFC7Vt2zZJ0h133HHYoBty++2365ZbblFycnKVnvdwtm7dqokTJ8o0TbVu3VqJiYkaPHiw/vGPf0iSFi1apOuuu67cfXft2qWVK1dKki688MJwu2VZ+utf/6rnn39eUnBOcqtWrbR//359//33+v777/XSSy8pMzNTZ599dsQxV69erZEjRyo3N1eS1KJFCx199NHavXu3Vq9erdWrV2vOnDl67rnnqvy3AGAPI7sAHGnVqlW67777VFRUpIyMDC1btkzLli3Thx9+qBUrVujCCy+UaZr6+9//riVLlkTse+DAAd1zzz0qKipS+/bt9d5772nZsmVauHChVq1apXHjxkmSsrKy9NRTT1XYh7lz56pVq1b64IMP9M4775S77Zo1a/R///d/mjRpkj799FO9/fbb+uSTT/Twww+H+/Kvf/3L9ut/+OGHlZeXp3nz5oWff8WKFfrd734nSVq6dKm+/PLLiH2eeOKJcNC97rrr9Omnn2rBggVasWKF7r33Xr366qtasGCB7b5I0vLlyyVJDRs2VJ8+fSq9X2JiYo0FXUl65pln1L17d3300Ud666239NBDDyk1NVXp6emSgmG3IgsXLlQgEJBhGBo6dGi4/V//+peef/55GYahO+64QytXrtR7772nzz77TG+88YZOOeUU5ebm6pZbbtH27dsjjnn//fcrNzdXJ598st577z0tX75c77zzjj799FPNnTtXxx9/vHbt2qUHHnigZgoCoAzCLgBHmjFjhvx+v8444ww9+uijatmyZfixJk2aaPr06Tr33HMlKTyKF/Lxxx+HR9buueeeiJUAPB6PbrrpJp155pmSpHfeeafCPnzzzTeaPn26mjRpUuE23333nW6//XZdddVVEcuIDR06VGeddZYk6bPPPqvkqy7x008/6cknn1SnTp3CbUlJSZowYUL4funjBgIBvfDCC5KCKyBMmDBB8fHxkoIjkyNGjNAtt9yir776ynZfpGCol6RTTjlFHo9zvhTcsGGDpk6dWmbe8JAhQyRJX331VXjViN8KBf+uXbuqdevWkqT9+/eHR8xvvPFG3XzzzWrQoEF4n44dO+qZZ55RkyZNlJeXF/FBZu/evVq3bp0kafTo0WVWoOjSpYv+9re/6cwzz1SbNm0qnIoCILoIuwAcZ+vWrVq1apWk4JxKl6v8/1T94Q9/kBQ8+3/Tpk3h9oyMDK1du1Yffvhhheu8nnLKKZKkzZs3V9iPzp07H3bJLK/Xq6uvvrrcx0Jr6VYUtg6lf//+4QBWWunpFKWPu3btWu3du1eSdPHFF5d7zOuuuy4iuNkRmmd69NFHV2n/mtKrV69yp5dccMEF8ng8siyr3NHd7du3h99jpacwvP/++8rLy5PL5dKIESPKfc6UlJTwSPC7774bbi+9kueOHTvK3Tc9PV3//e9/NXXq1EpPBQFQPYRdAI5T+uv5E088scLtTj311PDtb775JuIxj8ejli1bVjgKGQp9hxpdq8ycyrS0tDKjiiGhr+8PHjx42OP8ViiMH+q4hYWF4bbs7Ozw7ZNPPrnc/ZKSknTGGWfY7osk5eXlSQpOS3CSiv5GTZo0UY8ePSSVP5Vh4cKFMk1T8fHxESenffHFF5Kkpk2bHnJEP/Te2759u3bt2iVJaty4sU466SRJ0tSpUzVjxoxyT1AEcGQ557soAChW+kz38s6SL89v5076/X699dZbWrRokTZs2KDdu3dr//79tvrRuHHjw26TkpJS4WMVjUhXRkUnw5U+bumRxFDgkqRmzZpVuO/xxx8fnn9rR3Jysvbu3asDBw7Y3rcmHSqQDhkyRB988IG++OIL7dixQy1atAg/FprC0Ldv34ha79y5U1LwPVjZFSt++eUXNW3aVFJwrvXIkSO1Y8cOPfnkk3ryySd1zDHHqFu3burRo4f69OlT4YcjADWDsAvAcQoKCsK327dvX6nQWPrr+QMHDuj666+PmJ/avHlztWvXLjzSu3PnznCwqcwxK2IYxmG3qQq7xy09enyor8erOo2hRYsW2rt3b8R0ESc41Ehzv3791KBBA+Xn5+vdd9/VNddcIyk4TSb03rjooosi9gm99+Lj43X88cfb7s+JJ56ohQsX6qWXXtKrr76qDRs2aOvWrZo3b57mzZunpKQkjRw5UmPGjKnWhyEAlUfYBeA4pQPZrFmzbC/bNXXq1HCYGTZsmIYPH642bdpEbPP4448rMzOz2n11itInx1W0frBUtSkVUvBr+++//17ffvutcnNza3SFhZDqXs0+MTFR/fv315tvvqmFCxeGw+6CBQtkWVbEVIeQ0HuvSZMmVb7EdVJSkkaNGqVRo0Zp8+bN+vjjj/XJJ59o+fLlysvL0+OPP66tW7dqypQp1Xp9ACqHj5UAHKf0SVB2T+4KBAJ66623JEk9e/bUfffdVyboStK+ffuq10mHadSoUfj2oS5aUHpurx29e/eWFLy4xGuvvVbp/UzT1OTJk8usAhEauT5UoI3GlInQqgyrVq0Kj+SHVuAYNGhQmTndoffer7/+GpUrvqWmpuqqq67SY489pg8//FDnn3++JOm1115jPi9whBB2AThOly5dwrcPtVSWZVllRjF3794dHr0MLS/2W6ZpVmneqpOVXjXihx9+KHeb/Px8ZWVlVen4ffv2DX9omDVr1iGvIFbas88+q+eee05XXnmlPv7443B7aKpFaIm48nz99ddV6mtp55xzjpo2bSrTNPXBBx8oJycnfOW9305hkEpODPT7/Ye8Qp/P56vwsYpG1hs2bKi//OUv4fvff/99JV4BgOoi7AJwnFatWoVXDXjhhRfCKwH81htvvKGzzz5bd911lwKBgCRFXA2tohHOp59+OmLuaelVDWqrU089NRwg33777XK3+c9//lNhLQ/H7XZr0qRJMgxDe/fu1ejRow972dv58+dr+vTpkqRzzz1X55xzTvix0Ajq/v37tWXLljL7btmyRYsXL65SX0vzeDwaOHCgJOnDDz8MH/OEE05Q586dy2zfv3//8FSGJ598ssLjPvDAA+rTp0/EOrv//ve/1atXL914440V7lc6CHOiGnBkEHYBONKdd94pl8ulrVu36oYbbogIp0VFRXrllVf0wAMPaP/+/UpOTpbb7ZYUHD1r3769pOBXxaWXJNu5c6ceeughZWZm6pZbbgm3V3W000kaNGigQYMGSQpeKvnpp58OfwDw+Xz697//rccff7zC0e7K6NGjhyZMmCDDMPTNN99o8ODBev7557Vnz56I7b777jvdcccdmjBhggKBgDp16qRHHnkk4qS70AU3JGnatGkRJyWuW7dON954Y/jvWF2hqQyffPJJ+Gp7pdfWLS05OVk333yzJOm9997TX/7yl4gpL7t379bf/vY3vfbaa9q6dWvEFJn27dtr+/bt+vjjjzVp0qQya+1u2bIlfFGQJk2aVOtvAaDyOEENQI3Lzs6uMFyUdtVVV4Uv0HDGGWdoypQpmjhxolatWqUBAwaodevW8nq9+uWXX8JTFc455xzdfffdEce58847dfPNN+vAgQO69NJLdcwxx8gwDG3dulVut1vTp09Xenq6Zs+eLZ/Pp5tuukmpqanKzMxUWlpa9AtwhNx5551auXKltmzZounTp2vWrFk6+uij9csvvyg3N1e33XabLMsKX1K4KkaMGKHU1FQ99NBD2rJlix588EFNnjxZzZs3V0pKinbu3BkOv263W5dddpnuueeeMqOYZ5xxhnr37q1ly5Zp0aJF+vDDD9W6dWsVFBRoy5YtOuWUU/THP/5Rw4YNq1ZNpOCod9u2bfXzzz9r5cqVZS4P/FvXX3+9tmzZopdeekkvvviiXn31VbVu3VpFRUXasWNHeHR29OjRuuCCC8L7nXPOObrpppv0xBNP6IUXXtBLL70Ursv+/fvDy+M1aNBAM2bMiPgWAkDNIewCqHGFhYXhy6geym+XArv44ot1xhln6D//+Y9WrFihbdu2yefz6aijjlL37t114YUX6oILLiizTNd5552nZ555Rk8++aS+/vpr/fLLL2rSpImGDBmikSNHhq9s9uCDDyozM1M7duyQZVmOu2CCXc2bN9drr72mWbNmaenSpdq+fbv27dun0047Tdddd5169Oihxx57TFL1lkzr16+fevbsqXfeeUcffvihvvnmG+3atUu7du1ScnKy0tPT1b17d1100UU67rjjKjxOZmamZs+erYULF2rz5s3hkdKxY8fquuuu048//ljlPv7WkCFDNHPmTEnBUeVWrVpVuK3L5dKkSZM0cOBAvfzyy/ryyy+1detWGYYRnmJz9dVX67TTTiuz77hx43Teeedp/vz5WrFihbZv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9BctWPCWJOn222+SJM2d+6a2bduq22+/Sdddd4NGjRodPubq1V/pueee0TffrFVBQb4aN26iM8/sppEjb9TRR7eK6HtyckNNnTpDmZmP6quvvlBRkU/HH5+m0aNv0emnd61EtaOPsOsAXo9bEieoAQDgVAMHDtG//jVLb775erlh9403XldCQoIGDhyqSZP+rI8+WqZhw0bqrLO66+DBg3r55Rc0bdpkFRUV2ZrmsGPHdt1++81KSEjQ2LF/1LHHttXmzdmaMOGPKiryKTGxZNtfftmmMWNuUHJysm67bZxatTpGmzf/rCeeyNTtt9+k//znRbVo0VIjR94oj8er//1vnv74x3vVseNJatasubZt21rm+T/99BPdc884nXhiB40ff7eaN2+hjRs36KmnntBnn63Qv//9gho3bhLePj8/T3/841hlZAzUpZdeqS1bNisz8x/605/+qJdfnq9GjY6yVfdoIOw6QGjOLheVAADUVl/sWKO3NryrwkCh7X1dLkOmadVAr0rEu+M1OG2ATm/RpUr7Jycna8CAC/TGG6/r669X65RTTg0/9t133+j779dpyJCLFB8fJ4/Hoyuu+L1uuOHm8DYnn3yKBg3qpwUL3rIVdufNe1X5+Xm6774H1Lt3X3k8LvXvf548nnhNnjxJjRo1Cm+7efMmdelymi699Ap163a2JOmUU05VQUGBHn10upYvX6ZLL71CrVodo2bNmkmS2rY9Vh07dqrw+TMz/6GEhAQ98sjjSkkJPtdpp52uo45qrD//+R69/PILuummW8Pbb926Rffd9xddcMFgSVJ6+hnatGmTXnzxOWVlrVS/fgMq/dqjhbDrAKFpDAHTUsA05XYxlRoAULss3rRM2/N3xLobh7T452VVDrtS8MSzN954XfPnvxYRdkMnpl1yyRWKj0/QX/86tcy+ycnJatq0mX75ZZut5/z669UyDEPdup0T0d6nTz899NCDEW1nntldZ57Zvcwxjj32OEnS9u32nnvHju3auHGDevXqEw66Ieee20tut1urVn0e0W4Yhvr27R/R1qZNqiRp3759tp4/Wgi7DlD6whJ+vyV3XAw7AwBAFfQ/trfjR3b7t+1drWOkpbXTaaedrqVLl2js2DuVktJIeXm5WrLkXZ1yyqnhObHff79Or776slat+lx79uxWUVFR+BilR2IrY9eunUpK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82JTU1N133336ZJLLlFiIuvBVRZXUAMAAIiOal9BTZISEhKUkJBQ7mPXXnttNJ6iXmHOLgAAQHRU+1pz3377raZMmRLRtm7dOl1zzTVKT0/XoEGDtHDhwuo+Tb3idhkKXcCNsAsAAFB11Qq769ev1zXXXKMXXnhBphkMZfv379fIkSOVlZWluLg4bdiwQePHj9eqVaui0uH6wDCM8Ogu0xgAAACqrlph95lnnpHf79esWbPClwueO3eudu/erd///vf67LPPtGjRIqWkpOjZZ5+NSofri9C8XUZ2AQAAqq5aYffzzz/XgAED1KtXr3Dbe++9J4/HE740b9u2bTVgwAB9+eWX1etpPRMe2SXsAgAAVFm1wu7OnTvVrl278P28vDytXbtWp556qpo0aRJub926tXbv3l2dp6p3CLsAAADVV62w63a7VVhYGL6/cuVK+f3+MpcLLigoYOkxm7wetyTm7AIAAFRHtcLuscceqxUrVoTvv/jiizIMQ+edd17Edl9//bVatmxZnaeqd0Jzdv2M7AIAAFRZtdbZHTBggB577DFdddVVcrlc+vLLL3XaaaepU6dOkqRAIKAXX3xRK1as0MiRI6PS4foiNI0hYFoKmKbcrmqvEgcAAFDvVCvsjho1SqtWrdLHH38sSWrVqpWmT58efnzjxo2aPHmyjjnmGMKuTaUvLOH3W3LHxbAzAAAAtVS1wm58fLyefvppbdy4Ufv371fHjh0VF1eSytLS0jRixAhdd911ESes4fAirqIWMBUvdwx7AwAAUDtF5XLBxx13XLnthmFowoQJ0XiKeic0Z1diRQYAAICqikrY/eWXX7Rw4UJ9++232rNnjwzDUNOmTdW5c2cNHDhQjRs3jsbT1CsRI7v+QAx7AgAAUHtVO+z++9//1owZM+T3+2VZVsRj8+fP14wZM/Tggw9q8ODB1X2qesXjYWQXAACguqoVdpctW6apU6cqMTFRF154obp06aImTZrINE3t3r1bq1at0qJFizRhwgS1bdtWXbp0iVa/67zSI7tFhF0AAIAqqVbYfe6559SoUSO98sorOvbYY8s8ftVVV+mGG27Q1VdfraeeekqPPfZYdZ6uXmHOLgAAQPVVa/HWtWvX6vzzzy836Ia0b99e559/vr744ovqPFW989vVGAAAAGBftcJubm6ujj766MNu16ZNG+3du7c6T1XvxDFnFwAAoNqqFXZTUlK0efPmw263detWpaSkVOep6h2vp2RdXS4ZDAAAUDXVCrunnnqq3n33Xa1fv77CbdatW6cFCxbotNNOq85T1TteRnYBAACqrVonqF133XX64IMPdPnll2vQoEFKT08PXylt165dysrK0qJFixQIBDRq1KiodLi+YM4uAABA9VUr7J511ll68MEH9dBDD2nevHmaP39+xOOWZSkxMVGTJ0/WGWecUZ2nqndYjQEAAKD6qn1Ricsvv1x9+vTRO++8o7Vr12rXrl3hK6idcsopGjRoEFdQqwKmMQAAAFRfVC4X3KxZMw0bNqzCx5csWaJ58+YpMzMzGk9XL3i4XDAAAEC1VesEtcratGmTlixZciSeqs5gzi4AAED1HZGwC/uYswsAAFB9hF2HYs4uAABA9RF2HYqwCwAAUH2EXYdizi4AAED1EXYdijm7AAAA1UfYdSimMQAAAFSf7XV2zz77bNtPcvDgQdv71HeEXQAAgOqzHXb37NlTpScyDKNK+9VXzNkFAACoPtthl4tDHBlul0suw5BpWfL5CLsAAABVYTvstm7duib6gXJ4PS4V+gKM7AIAAFQRJ6g5WGgqg88fiHFPAAAAaifCroOVhF1GdgEAAKqCsOtghF0AAIDqIew6WDjsMmcXAACgSgi7Dha6iprPb8qyrBj3BgAAoPZxdNidO3euBg4cqM6dO6tnz56aNm2afD5fhdvv2bNHkydPVt++fdW5c2edd955mjZtWq29qEVoZNeypIBJ2AUAALDL9tJjR8r8+fM1ceJETZgwQf369dP69es1ceJE5efna9KkSWW2N01T119/vfLz8/XQQw+pTZs2ysrK0v33369ff/1Vf//732PwKqrnt1dR87gd/dkEAADAcRwbdjMzMzVo0CCNGDFCkpSamqqdO3dq0qRJGjNmjFq2bBmx/XfffadNmzZp1qxZOuuss8L7ZGVlacGCBbIsq9Zdxc3rjryKWmIM+wIAAFAbOXKocOPGjdq8ebN69+4d0d6rVy+Zpqnly5eX2efkk09WVlZWOOiGuFwuud3uWhd0pciRXT8rMgAAANjmyJHd7OxsSVLbtm0j2lu1aiWv16sNGzYc9hh+v1/vv/++3nrrLd122222nt/lMuRyRS8cu4tHaN02pyHEed3h26Ykj8eRn02irqr1qq+olz3Uyz5qZg/1sod62UO97HNk2M3NzZUkJSUlRbQbhqGkpKTw4xW56qqrtHr1aiUlJelPf/qTLr/8clvP36RJUo2MBKek2JuIkJwUH76d2CBejRsnHWLrusduveo76mUP9bKPmtlDveyhXvZQr8pzZNitrkcffVT79u3TRx99pAcffFA7duzQLbfcUun9d+/Oi/rIbkpKovbvL1DAxpq5ZqDkMsG7dueqUYL7EFvXHVWtV31FveyhXvZRM3uolz3Uyx7qVaKyg4CODLspKSmSVGYE17Is5eXlhR+vSKtWrdSqVSt17NhRhmFoxowZuvzyy9WiRYtKPb9pWjJrYKmvQMC0NffW4yr5iuJgYaDezdu1W6/6jnrZQ73so2b2UC97qJc91KvyHDnhIy0tTZK0adOmiPacnBz5fD61a9euzD4bNmzQm2++Wab9xBNPVCAQCM8Drk08v1l6DAAAAPY4MuympqYqLS1NS5cujWhfsmSJPB6PevbsWWafNWvW6K677tKaNWsi2tetWydJZZYqqw1+u84uAAAA7HFk2JWksWPHatGiRZozZ462bNmixYsXa+bMmRo2bJiaNm2qNWvWKCMjQ1lZWZKkCy64QGlpabr77ru1fPlybd68WW+++ab+9a9/qUePHjruuONi+4Kq4Lfr7AIAAMAeR87ZlaSMjAxNnz5ds2fP1owZM9SsWTMNHz5cY8aMkSQVFBQoOztb+fn5kqT4+Hj9+9//1owZM3T33XcrNzdXxxxzjK6++mqNHj06li+lyiJHdgOH2BIAAADlcWzYlaShQ4dq6NCh5T7WrVs3rV+/PqKtZcuWmj59+pHo2hFROuwWMY0BAADANsdOYwBzdgEAAKqLsOtgpefssrwIAACAfYRdB2NkFwAAoHoIuw4WEXZZjQEAAMA2wq6DxXlKLg/MyC4AAIB9hF0Hi1iNwcfSYwAAAHYRdh2sQULJynB5B/0x7AkAAEDtRNh1sOREb/h2boEvhj0BAAConQi7DpYQ55bHbUiSDuQTdgEAAOwi7DqYYRjh0d3cgqIY9wYAAKD2Iew6XHJinKTgyK5lWTHuDQAAQO1C2HW4hg2CI7sB09LBIlZkAAAAsIOw63ChsCtJBzhJDQAAwBbCrsNFrMjASWoAAAC2EHYdLnL5MU5SAwAAsIOw63ANG8SFb7P8GAAAgD2EXYfjwhIAAABVR9h1uOQGhF0AAICqIuw6XMNSI7tMYwAAALCHsOtwyRFhlxPUAAAA7CDsOlxDpjEAAABUGWHX4bwet+Lj3JIIuwAAAHYRdmuB0Lxd5uwCAADYQ9itBULzdvMO+mSaVox7AwAAUHsQdmuB0PJjliXlF/pj3BsAAIDag7BbCzRkRQYAAIAqIezWAsmJJZcM5iQ1AACAyiPs1gKlr6LGSWoAAACVR9itBVhrFwAAoGoIu7UAc3YBAACqhrBbC5S+ZDAjuwAAAJVH2K0FkhuUOkGNObsAAACVRtitBSKmMTCyCwAAUGmE3VogKdETvs00BgAAgMoj7NYCbpdLSQnBwMs0BgAAgMoj7NYSoXm7BwpYjQEAAKCyCLu1RGjebkFhQP6AGePeAAAA1A6E3VqC5ccAAADsI+zWEqUvGcy8XQAAgMoh7NYSLD8GAABgH2G3logY2SXsAgAAVIqjw+7cuXM1cOBAde7cWT179tS0adPk81Uc9PLz8zVjxgydf/75OvXUU5WRkaEnnnjikPvUFhFzdvNZkQEAAKAyPIffJDbmz5+viRMnasKECerXr5/Wr1+viRMnKj8/X5MmTSp3n/Hjx2v16tWaNGmSOnbsqBUrVujBBx9UQUGBxo0bd4RfQXQ1TCy5ZDDTGAAAACrHsWE3MzNTgwYN0ogRIyRJqamp2rlzpyZNmqQxY8aoZcuWEdv/9NNPWrp0qaZOnaoBAwZIktq2bauVK1fqhRdeqP1hlxPUAAAAbHPkNIaNGzdq8+bN6t27d0R7r169ZJqmli9fXmaf448/Xh999JEGDRoU0d6yZUsVFBTINGv32rSl5+wysgsAAFA5jhzZzc7OlhQcmS2tVatW8nq92rBhQ5l9XC6XmjdvHtHm9/v14YcfqkuXLnK5HJnrK60hc3YBAABsc2TYzc3NlSQlJSVFtBuGoaSkpPDjhzNjxgxt2LBBzz77rK3nd7kMuVyGrX0Oxe12RfyuioZJcXIZhkzLUu5Bvzye2h3eDyUa9apPqJc91Ms+amYP9bKHetlDvexzZNitLsuyNG3aNP373//WpEmT1LVrV1v7N2mSJMOIXtgNSUlJrN7+yXHae6BQeQf9atw46fA71HLVrVd9Q73soV72UTN7qJc91Mse6lV5jgy7KSkpklRmBNeyLOXl5YUfL4/P59OECRO0aNEiTZ8+XUOHDrX9/Lt350V9ZDclJVH79xcoEKj63OGkBI/2HijU/txC7dmTF7X+OU206lVfUC97qJd91Mwe6mUP9bKHepWo7MCfI8NuWlqaJGnTpk1KT08Pt+fk5Mjn86ldu3bl7mdZlu655x598MEH+te//qWzzz67Ss9vmpZM06rSvocSCJjy+6v+xkxOCM7bLfKbyivwKd7rjlbXHKm69apvqJc91Ms+amYP9bKHetlDvSrPkRM+UlNTlZaWpqVLl0a0L1myRB6PRz179ix3v5kzZ2rJkiXVCrpOlszyYwAAALY4MuxK0tixY7Vo0SLNmTNHW7Zs0eLFizVz5kwNGzZMTZs21Zo1a5SRkaGsrCxJ0rZt2/TEE0/ommuuUdu2bfXrr79G/BQV1f4VDBo2KLmwBJcMBgAAODxHTmOQpIyMDE2fPl2zZ8/WjBkz1KxZMw0fPlxjxoyRJBUUFCg7O1v5+fmSpE8//VQ+n09PPfWUnnrqqTLHe/bZZ9WtW7cj+hqirfQlgw+w/BgAAMBhOTbsStLQoUMrPMGsW7duWr9+ffj+xRdfrIsvvvhIdS0mSq+1y4UlAAAADs+x0xhQFnN2AQAA7CHs1iKM7AIAANhD2K1FIkZ2CbsAAACHRditRUqfoJbLCWoAAACHRditRRomsvQYAACAHYTdWiQ+zq04T/BPxpxdAACAwyPs1jKhebsHWI0BAADgsAi7tUxo3m5uvk+WZcW4NwAAAM5G2K1lQsuPmZalgkJ/jHsDAADgbITdWia5QclJaszbBQAAODTCbi0TufwYYRcAAOBQCLu1DFdRAwAAqDzCbi0TcRU1RnYBAAAOibBbyzRswIUlAAAAKouwW8uUnrN7gEsGAwAAHBJht5Zhzi4AAEDlEXZrGebsAgAAVB5ht5aJWHqMkV0AAIBDIuzWMh63S4nxbklMYwAAADgcwm4tFBrdzeUENQAAgEMi7NZCyYnB5cfyD/oVMM0Y9wYAAMC5CLu1UMPik9QsSXkH/bHtDAAAgIMRdmuh0suPsSIDAABAxQi7tVDp5ce4sAQAAEDFCLu1EMuPAQAAVA5htxZq2CAufJvlxwAAACpG2K2FkpmzCwAAUCmE3VqIaQwAAACVQ9ithRpGnKBG2AUAAKgIYbcWKj1nl5FdAACAihF2a6EG8R4ZRvB2bgFLjwEAAFSEsFsLuVyGkhKCUxmYxgAAAFAxwm4tFZq3y9JjAAAAFSPs1lKhFRkKiwLy+QMx7g0AAIAzEXZrqcjlx/wx7AkAAIBzEXZrqcjlxzhJDQAAoDyE3VoqOZHlxwAAAA6HsFtLlR7ZJewCAACUj7BbS5Wes8vyYwAAAOUj7NZSzNkFAAA4PMJuLcWcXQAAgMNzdNidO3euBg4cqM6dO6tnz56aNm2afL5DB7v8/Hzdc8896tChg1588cUj1NMjL5k5uwAAAIfliXUHKjJ//nxNnDhREyZMUL9+/bR+/XpNnDhR+fn5mjRpUrn7rF+/XnfccYcMwzjCvT3yGjJnFwAA4LAcO7KbmZmpQYMGacSIEUpNTVX//v01duxYvfLKK9q+fXu5+8ycOVM9evTQrFmzjnBvj7yEOLfcrmCoZ2QXAACgfI4Muxs3btTmzZvVu3fviPZevXrJNE0tX7683P3uvPNO3XffffJ4HDtgHTWGYYSnMhB2AQAAyufIsJudnS1Jatu2bUR7q1at5PV6tWHDhnL3O/bYY2u8b07SsPgktQP5PlmWFePeAAAAOI8jh0Bzc3MlSUlJSRHthmEoKSkp/HhNcbkMuVzRm/frdrsifkdLSpJX+lXyB0wFLEsJXndUjx8rNVWvuop62UO97KNm9lAve6iXPdTLPkeG3Vhr0iSpRk5yS0lJjOrxmjRKlLRHkmR4PGrcOOnQO9Qy0a5XXUe97KFe9lEze6iXPdTLHupVeY4MuykpKZJUZgTXsizl5eWFH68pu3fnRX1kNyUlUfv3FygQMKN23ARvyae67M17FF9HPuTVVL3qKuplD/Wyj5rZQ73soV72UK8SlR3kc2TYTUtLkyRt2rRJ6enp4facnBz5fD61a9euRp/fNC2ZZvTnwAYCpvz+6L0x2zRPDt9e//MetWvdKGrHdoJo16uuo172UC/7qJk91Mse6mUP9ao8R44FpqamKi0tTUuXLo1oX7JkiTwej3r27BmjnjlL+9Sjwrd/yNkXu44AAAA4lCPDriSNHTtWixYt0pw5c7RlyxYtXrxYM2fO1LBhw9S0aVOtWbNGGRkZysrKCu/z66+/6tdff9Xu3bslBadBhNoCgUCsXkqNadk4USnFy4/9kLO3RkajAQAAajNHTmOQpIyMDE2fPl2zZ8/WjBkz1KxZMw0fPlxjxoyRJBUUFCg7O1v5+fnhfXr06BFxjL///e/6+9//Lik4KtymTZsj9wKOAMMwdGLqUVq1/lcVFAaU82uu2rZsGOtuAQAAOIZjw64kDR06VEOHDi33sW7dumn9+vURbb+9Xx+0bxMMu5L0/ea9hF0AAIBSHDuNAZVTet7u98zbBQAAiEDYreVSWyQrIS54MYkfNu/lSmoAAAClEHZrOZfLCC85ti+vSDv2FsS4RwAAAM5B2K0DTiw9lWHz3pj1AwAAwGkIu3VA+zYlF5P4YTPzdgEAAEIIu3VA2jEp8riDlzdmZBcAAKAEYbcO8HrcOq5ViiRpx94C7c0tjHGPAAAAnIGwW0e0b3NU+DajuwAAAEGE3Tqi9Hq7zNsFAAAIIuzWEe1aN5JRfPv7nL2x7AoAAIBjEHbriAYJHqW2SJYk5ezIVf5BX4x7BAAAEHuE3ToktN6uJenHLUxlAAAAIOzWIe0jLi5B2AUAACDs1iGlLy7BvF0AAADCbp3SKDleLRonSpKyt+5XkS8Q4x4BAADEFmG3jgmttxswLWVv2x/bzgAAAMQYYbeOOTG11FQGLi4BAADqOcJuHRNxkloOJ6kBAID6jbBbx7Q4KlGNkuIkBZcfC5hmjHsEAAAQO4TdOsYwjPB6u4VFAW3ekRvbDgEAAMQQYbcOiliCjPV2AQBAPUbYrYNKz9v9gZPUAABAPUbYrYPaNE9WYrxHUvDiEpZlxbhHAAAAsUHYrYNcLkMnFk9lOJDv0y+782PcIwAAgNgg7NZRJ7ZhvV0AAADCbh1Vet7uup/3xqwfAAAAsUTYraOOOzpFcZ7gn/ezb7drxdpfYtwjAACAI4+wW0d5PS4NPue48P2n3/5OX/24M3YdAgAAiAHCbh026Oxj1ef01pIk07L0z/lrtf7nPTHuFQAAwJFD2K3DDMPQH37XXmed1EKS5PObeuy1Nfp5+4EY9wwAAODIIOzWcS7D0PWDO6lzWhNJUkFhQI+8/JW2sxwZAACoBwi79YDH7dItF52idq2Dy5Htz/fp7y99pT0HCmPcMwAAgJpF2K0n4uPcGnt5F7VpniRJ2rX/oGa8/JVyC3wx7hkAAEDNIezWI0kJXo2/8jQ1a5QgSdq6M08zXvpKn377i/blFcW4dwAAANHniXUHcGQdlRyvP151mqY8/7kOtvlE25P26dmfvbI2eBVnJKhRQpKaJqeoVaNGSklIVpI3UQ08DZTkbaAG3kQleRqogbeBEtzxMgwj1i8HAADgkAi79VCLxg10xaDmej67eBkyd6GMuEL5latd2qldedL3eYc+hiFDCa5EJXoSleRtoOS4BmoYl6QG3kQ18DYoDsXBx4JhOdjewJMol8EXCgAA4Mgg7NZT3Y7toJyic7X21++VW5SvQvOg5ApUen9LlgrMfBUU5Wt30S7pMOG4tDgjXvGuRCW4ExTvSlCCOzH425WoeHeCGngbqFFSknxFPlmy5HJJbsOQyyUZLoV/S5JlWbJkybIsmTIj7gfbim+Xesw81D6hdssstU3JY5LkMdxyuzzyuNzyGB65Xe7iNvdhHiu+7/KUu73biHyMDwUAAFQfYbeecrvcuqLDhbqiQ0nb3vx8fb1xu77N2a6fftmp3fkHZHh8Mjw+yeOT4S7+HWpzh277bT13kVWookChDlQ+W9dLhgy55JbLCAZfd/Ftt+GWS265DVfwt8str8crvz8QDPKlgr4sS5YlWYZVfFRLRvDg4fuh24YR/Am1hbYzwltakmFJVvC2peCHDUXcVvhDgmRJMuR1eeRxexXn8sjr8gZ/3F55Q/fdXsW5vPK4PIoLPxb5uNcV3N/j8iqu9L6ljlXbPxyYpqWAacrjdjFFqB4wLVMBy1TA9MtvBRQwA/KbAQUsf/HvQKnf/rL3zUDJftZh7hcfN2AGZBhG8EN18Y/X5ZHHKP7tKvntcXnlcbnljfhd+vHi/dyR+/Pejb7Q39Jn+uQ3/bKKTBV68pR/0CeZrnDt3Yab+lfAsEL/h0LYr79G96ILHo9LjRsnac+ePPn9ZlSPXZMO5Bcpt8Cng0UBHSz0q6AooIJCf/B+kV/5hX7lH/Qrt6BQBwoLlOvLU4G/QAcDBSqyCiVPUTAMVxCS5fGJf5eIGtMlWS7JckumW4blkmF55JJbhlX8AUEeuQ2PvG6vLMuSywhefMVwBdekdhmh0G+UjPSrVJC3QuP7wSAvWTKMknvBR6zi4Br8FiFgWjJNM9gW+iAS+hAS/hYidMzggdwuyeUygj9Gye3whxEp/E1DyX/CrXDfg6+n5LWUfJAp3j/i351V6pZZ5nWGXpthGDJNyTIV7LslWZYRvm3IJZdhyDCM4lq6gh/YDJdcLkNuwyW3K7iN2+WW2+WS22XI7XJJxccxzdAxreLnMhR6eZZlBHtiSbKMcJtpBesbsII1Ni0z3GYp+E2Q220Ef4pvu1yS22UUf0NklfmQF/5gV/obH5X6m8mSZZnh+2bpb48sK/jh0rDkCwQDZkABmVZApszw71Ct65rgB3G33EbJN1yhkBz88O6RSy655JGs4Ad2Q24leL2SKbndbsW5PfK63YpzuxXn9SjOHQzRhmUoeF598e3i94QhV/H7yRV8zuL3V/C2S6ZlyAxIgeIff8CS3y8F/JYCpoLP5fEo3uOR1xN8/nhv8L7hUvjDhs/0F38Y8ctf/NtUQJYCMg1Tpvwyi7fzl/rxhX4CPhUF/CoyffIF/OHwGvwJhtmIDzdW8PksVS6mGQr+2/IapT+4eIsHHMr/UOM9xAeb0h+IvKU+4FiWS5bpkuU3ZJoumQFDAX/wp3lKstq2bHjEQnfz5g0rtR0ju6hQwwZxatggrkr7BkxTBYUB5Rf6VXDQr4LC4E9+8c/BQr9M05JPRfJZB4OjvdZB+XVQfhVJHlNFRYHwf4xM01IgEPyfYSAQChPB2+HfAYVvS8F/+JIkyxX8n5kVum/ILP4fa+n/aap4r+B/V4yI7cO/DUmGWfxjyXCV3JbLlGFE3pdR3OaywvsF9ym+H94m8n7lj12lP09YyUfd0Osuvi2V1CG8ceknO/z2lmVEvi5XoGY/3LhMSaYkf7g7VnGLLaEdy1PZ/rtUqbVujEMcMvRqquVQr6W6DtX53/ahJr/FMSS5bWwfUM32p54zi4O9zyqy9wYuqLEu1RuWrGBwlj9m73GrKF7nt7hYF55+emw6UAHCLmqE2+VScqJLyYle2/seyZHw0KhacCQoOAoXMC35/KZ8AVN+vxlxO2BZivO4FOdxy+txKc7jktfjCo4GeIOhumQkSlJ4NCgY2H0BU0W+gHx+U0U+U0X+QPC3LyBfwAz3wzRValQw+BMabSv9XUzACkiGqfgEj3w+vwwrODLidgVH1Dzu4MiZIYU/NFimikcYFXy9gWC/fMV98fmD/fL5Q7dNBQKm/KYV/B2w5A+Y4f0i+mVZMq3I2paMLloyXJLhCgSDvNuUjIAsIyAZAZlGMIVYRkCWKyBLpgx3oFRQDv4OBv2S31bx7dBx5ArIMszwbRk1lfZqj/I/1BTflw7/waZko5J6Fo98ylDwQw0khWptSKZR/E1D8ci1Gfxdtq34xzRklXos9E2FFdqn1P5W8fahfcsc+1DHkko+SIf+LZb+4O0q9aHbFQj+bV2B8Dblbx8o9UH+UMeK6Z/G0SzTVfJ3Cr0vituCf293uC34t5X9v90RqL8RV6hN+RskEXYrbe7cuZozZ45+/vlnNW7cWIMHD9b48ePl9ZYfoIqKivToo4/q7bff1u7du5Wamqrrr79el1566RHuOWqL0Fe8Lhn2RoeqKLEGjllbp8kcKQEz+LViob9IBb4i+UyfvAlu7d1foKKigPymGf5mIHTbsoLTAFyGKzzVwVX8AaLkfxhGqekNwUhoWsFvFOLj3IrzuBXvdSvOG/ztcRvhr/aMUsOihlHqW4jQY8VtRvGxfX5LhT6//AGreGqAUfzeNYrvB7f3maaKfJZ8PlOFRQEV+U0V+gIqLP6AFTAtWWbwA0now5RVPN3CLH7NUnBKR+grfcMITgE4qlGiLL+pOI9LCXFuJcR5in+75fG45PMHSj40Ff/4AoHw/UKfX0X+4BSookBARb6ACv1++f2mPG5DHo8hjyf4fva4DXncwas/utzBMG0Ulyw05cAonirgdrnldbvkcbvkcbnkcbvlcbvldQe/3vYFTPl8por8VvBDpc8q/oAZ/OBmWpIZCH3gVfEHzOB0jfBfIfz3CE7NCP0dg1MziqdqGEawv67g7+TkeBUeDF60x+UyiqdshN5HwekXllnyQTv0N7DC9xWe8hL8sKvw9Bd38XO43Ubx6y657zKMkg/qpX6HPrQbMorr6ypzDJfLCL5niqeqFfoCOlgUKL4fPCcgIS74fo73uhVf/PcPvc9NyyqudeSHZZ8/IH/Akscjud2W3B5LLnfwvstlyu215Il3a9+BfBUU+VXo84XfMz6/X75AoPjbruAJy4ah4tvB90HwY5cZnMIS+m2ZsorvG4bkcgenBwVPcrZKfhtW8dxps3gedaD4bxE6hsJToQy5Im4bljscTq2AS6YZ/ABjBoJf7VsBV/HJx57iE5Q98rrc4akFXlewLXhSsqvk/eEN/h2DU3uC059M0wwPfliW5Pa4VVjkC/73KvT+tUr+fYffR5Is0wpP8wnIL6t4UCE4nab4tmFKxVMy5Cr1wSb0rWKpEO1yW3K5zeI6Rn4IOiq+kf5wWv+a+Y95NTg27M6fP18TJ07UhAkT1K9fP61fv14TJ05Ufn6+Jk2aVO4+DzzwgJYuXaopU6bohBNO0AcffKA///nPSkxM1MCBA4/wKwDgBMH5oW4leOLVKKGWfjio2myiqKmVNYsh6mUP9bKHetnn2NOXMzMzNWjQII0YMUKpqanq37+/xo4dq1deeUXbt28vs/2WLVs0b948jRs3Tn379tWxxx6r4cOH64ILLtD//d//xeAVAAAAINYcGXY3btyozZs3q3fv3hHtvXr1kmmaWr58eZl9Pv74Y1mWpfPOO6/MPqHjAQAAoH5xZNjNzs6WJLVt2zaivVWrVvJ6vdqwYUO5+8TFxally5YR7aFjlLcPAAAA6jZHztnNzc2VJCUlJUW0G4ahpKSk8OO/3ee320tScnKyJOnAgcqvnRta0zJa3G5XxG8cGvWyh3rZQ73so2b2UC97qJc91Ms+R4bdWGvSJKlGFkROSamJc/HrLuplD/Wyh3rZR83soV72UC97qFflOTLspqSkSFKZEVzLspSXlxd+vLSGDRsqLy+vTHtoRLe8fSqye3de1Ed2U1IStX9/gQIBzpw8HOplD/Wyh3rZR83soV72UC97qFeJxo3LfqNfHkeG3bS0NEnSpk2blJ6eHm7PycmRz+dTu3btyt2nqKhI27ZtU6tWrcLtGzdulKRy96lIaBH/aAsUr3mIyqFe9lAve6iXfdTMHuplD/Wyh3pVniMnfKSmpiotLU1Lly6NaF+yZIk8Ho969uxZZp+ePXvK5XLp/fffj2hfvHixOnTooGOOOaZG+wwAAADncWTYlaSxY8dq0aJFmjNnjrZs2aLFixdr5syZGjZsmJo2bao1a9YoIyNDWVlZkqSWLVvq97//vR577DG9//772rJli/71r39p6dKlGjduXIxfDQAAAGLBkdMYJCkjI0PTp0/X7NmzNWPGDDVr1kzDhw/XmDFjJEkFBQXKzs5Wfn5+eJ97771XycnJ+stf/qLdu3fr+OOP16OPPqo+ffrE6mUAAAAghgwrdHF3hP36a+WXKasMLu1nD/Wyh3rZQ73so2b2UC97qJc91KtE8+YNK7WdY6cxAAAAANVF2AUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAncXSYwAAAKizGNkFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRditYXPnztXAgQPVuXNn9ezZU9OmTZPP54t1txzj3//+tzp37qxx48aVeSwrK0t/+MMfdOqpp6pr16664447tH379hj00jleffVVXXjhhUpPT1efPn305z//Wbt27Qo//sMPP+j6669Xenq60tPTdcMNN+inn36KYY9jxzRNPfPMMxo8eLC6dOmibt26aezYsdqyZUt4G95jFRs5cqQ6dOignJyccBv1KtG3b1916NChzM/gwYPD21CvsnJycnTrrbfq9NNP15lnnqkxY8Zo69at4cepWVBOTk6576/Qz+uvvy6JelWahRozb948q0OHDtacOXOsn3/+2Xrvvfes7t27W/fff3+suxZze/bssUaPHm316NHDOv3006077rgj4vGffvrJ6tKli3XPPfdYP/30k5WVlWVdfvnl1uDBg62ioqIY9Tq2nnnmGatjx47W008/bW3cuNFatmyZ1atXL+vqq6+2TNO0du/ebXXv3t0aNWqUtW7dOuvrr7+2Ro8ebZ177rnWvn37Yt39I27KlCnWaaedZs2fP9/6+eefrY8++sjq16+f1bdvX6uwsJD32CHMnTvX6tSpk9W+fXtr8+bNlmXxb/K3+vTpY02dOtXasWNHxM/u3bsty6Je5dm3b5/Vp08f66abbrK+//57a/Xq1dbFF19sZWRkWIFAgJqV4vf7y7y3duzYYb3xxhtW586drU2bNlEvGwi7Nahfv37W+PHjI9pefPFFq2PHjtYvv/wSo145w3PPPWdde+211s6dO60+ffqUCbsTJkywevfubfl8vnDbTz/9ZLVv39763//+d6S7G3OmaVrnnnuuNWHChIj2l19+2Wrfvr313XffWY8//rh16qmnWnv37g0/vnfvXqtLly7WE088caS7HFM+n88677zzrMzMzIj2+fPnW+3bt7fWrFnDe6wC27dvt7p27WpNmjQpIuxSr0h9+vSxHnvssQofp15lZWZmWueee65VUFAQbsvOzrYWLFhgHTx4kJodRlFRkZWRkWE9/PDDlmXxHrODaQw1ZOPGjdq8ebN69+4d0d6rVy+Zpqnly5fHqGfO0Lt3b82ZM0dNmzYt9/GPPvpIPXr0kMfjCbelpaWpTZs2+vDDD49UNx3DMAy99dZb+tOf/hTR3rJlS0lSXl6ePvroI6Wnp6tRo0bhxxs1aqRTTz213tXM4/Fo6dKluuWWWyLaXa7gf/K8Xi/vsQo8+OCDSk9P1/nnnx/RTr3soV5lvfvuu+rfv78SEhLCbccdd5wyMjIUHx9PzQ7jP//5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8tSoUSOlp6dr1KhR5Y4a//LLL5o1a5Y+/PBD7dy5U40aNVKfPn1066236uijjy63bytWrNBjjz2mdevWSZJOPvlkjR8/XqeffnrEdl9++aWeeuoprV69Wnv27FFycrJSU1M1ZMgQXXPNNXK73dUvFABUAmEXABzqs88+04IFC/S73/1OPXr0UE5Ojt58803ddNNNmjVrlvr27Rve9rHHHtPMmTPVuHFjDRgwQC1bttTPP/+sRYsW6f3339eMGTN0wQUXhLffsGGDrrrqKhUUFGjo0KFq06aNfvzxR7322mt67733NHfuXLVt2zaiP5988omeeeYZDR06VL1799aKFSv06aefatSoUXrnnXfUqlUrSVJWVpaGDx+uhIQEXXDBBWrdurUOHDigZcuWacqUKVq9erUeeeSRI1NEALAAADVi8+bNVvv27a2zzjrL1n733HOP1b59e6tDhw7W8uXLIx579dVXrfbt21sZGRnhtm+++cbq0KGDddZZZ1nbtm2L2P7zzz+3OnbsaJ155plWfn5+uP2SSy6x2rdvX+b4//3vf6327dtbo0ePDrddc801Vvv27a3u3btb2dnZ4XbTNK0RI0ZY7du3t+bMmRNuHz9+vNW+fXvrgw8+iDh2UVGRdfXVV1tnnHGGtXXrVls1AYCqYmQXAGqYZVnKyck55DZer1ctW7aMaEtPT1ePHj0i2i666CJNmzZNGzZs0ObNm5Wamqr58+fLsiz9/ve/LzP9oGvXrurWrZtWrFih5cuXa8CAAfruu++0du1adezYsczxL730Um3ZskUtWrQo08crrrhCxx13XPi+YRjq2bOnPvnkE23ZsiXcvm/fPkkqM1XB6/Xq2WeflcfD/3oAHDn8FwcAati+ffsOuwRZx44d9cYbb0S0/XYerBQMkMcff7y++uorbdiwQampqVq7dm2F20tSly5dtGLFCn3zzTcaMGBAeL3ck046qcy2CQkJuvvuu8s9TufOncu0paSkSJJyc3PDbX369NHy5cs1fvx4jRo1Sv3799cJJ5wgSQRdAEcc/9UBgBqWlJSk6dOnH3Kb5OTkMm1NmzYtd9ujjjpKkrR//35J0q5duw65fZMmTSRJe/bsidg+FFQrq7ztXa7gtYksywq3/eEPf1BeXp6eeOIJPfLII3rkkUfUvHlz9ejRQxdffLG6detm63kBoDoIuwBQw7xer/r37297v1CQ/C3TNCUFlzSTgtMJpMjAWd72oe1Cxy0qKrLdp8q68cYbdfXVV+uDDz7QRx99pI8//ljz5s3TvHnzdPnll2vy5Mk19twAUBqXCwYAhwqNxP7W3r17JZWM5IZ+h0Zsf2v37t3lbh9qrykNGzbUkCFDNG3aNC1fvlxPP/20WrZsqblz52rFihU1+twAEELYBQCHWr16dZk2v9+v7OxsSVKbNm0kSaeccookadWqVeUe54svvojYLvQ7KytLgUAgYlvTNHXHHXfo9ttvl9/vr1K/9+3bF3HCmhQcVe7Ro4euv/56SdI333xTpWMDgF2EXQBwqM8++0yff/55RNvrr7+uAwcOqFOnTuHVGy699FK5XC699NJL2rZtW8T2H3/8sVatWqWWLVuGV17o0KGDTj75ZO3atUuvv/56xPbvvPOOFixYoLy8vCqdTLZnzx6dc845uu6668KrMpQWCrmhNXkBoKYxZxcAalhhYaGefvrpw24XHx+va665Jnz/wgsv1I033qh+/frp+OOPD19Uwu1266677gpvd+KJJ+qOO+7QI488oksuuUQZGRlq2rSpNmzYoPfee08JCQmaNm2avF5veJ+HHnpI1157re6//3599tlnOuGEE/TTTz9pwYIFSk5OrnBFhsNp3Lixbr75Zj3++OMaNGiQ+vfvr6OPPloFBQX64osvtHLlSp188sn63e9+V6XjA4BdhF0AqGEFBQWHXY1BCs5xLR12O3furEsvvVSZmZlaunSpTNNUly5ddNttt+mcc86J2Hf06NFq166dnnvuOb311lsqKChQkyZNlJGREX6stJNOOknz5s1TZmamPvnkEy1cuFCNGjXSoEGDdOutt5a5epodt956qzp06KBXXnlFixcv1t69e+X1enXcccfp9ttv1/DhwxUXF1fl4wOAHYZV0em7AICYmDBhgubNm6eJEydGhF8AgH3M2QUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdxQlqAAAAqLMY2QUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ31/+1MPX1gW6EJAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "ename": "OSError", + "evalue": "'seaborn-v0_8' not found in the style library and input is not a valid URL or path; see `style.available` for list of available styles", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/style/core.py:127\u001b[0m, in \u001b[0;36muse\u001b[0;34m(style)\u001b[0m\n\u001b[1;32m 126\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 127\u001b[0m rc \u001b[38;5;241m=\u001b[39m \u001b[43mrc_params_from_file\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstyle\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muse_default_template\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 128\u001b[0m _apply_style(rc)\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/__init__.py:854\u001b[0m, in \u001b[0;36mrc_params_from_file\u001b[0;34m(fname, fail_on_error, use_default_template)\u001b[0m\n\u001b[1;32m 840\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 841\u001b[0m \u001b[38;5;124;03mConstruct a `RcParams` from file *fname*.\u001b[39;00m\n\u001b[1;32m 842\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;124;03m parameters specified in the file. (Useful for updating dicts.)\u001b[39;00m\n\u001b[1;32m 853\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m--> 854\u001b[0m config_from_file \u001b[38;5;241m=\u001b[39m \u001b[43m_rc_params_in_file\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfail_on_error\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfail_on_error\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 856\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m use_default_template:\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/__init__.py:780\u001b[0m, in \u001b[0;36m_rc_params_in_file\u001b[0;34m(fname, transform, fail_on_error)\u001b[0m\n\u001b[1;32m 779\u001b[0m rc_temp \u001b[38;5;241m=\u001b[39m {}\n\u001b[0;32m--> 780\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m _open_file_or_url(fname) \u001b[38;5;28;01mas\u001b[39;00m fd:\n\u001b[1;32m 781\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/contextlib.py:135\u001b[0m, in \u001b[0;36m_GeneratorContextManager.__enter__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 134\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 135\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgen\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 136\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m:\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/__init__.py:757\u001b[0m, in \u001b[0;36m_open_file_or_url\u001b[0;34m(fname)\u001b[0m\n\u001b[1;32m 756\u001b[0m encoding \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m--> 757\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mfname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mencoding\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[1;32m 758\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m f\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'seaborn-v0_8'", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[8], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mplt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstyle\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43muse\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseaborn-v0_8\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2\u001b[0m plt\u001b[38;5;241m.\u001b[39mtitle(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mLearning Curves\u001b[39m\u001b[38;5;124m\"\u001b[39m, fontsize\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m20\u001b[39m)\n\u001b[1;32m 3\u001b[0m plt\u001b[38;5;241m.\u001b[39mplot(np\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m1\u001b[39m, n_epochs, n_epochs), epoch_loss_list, color\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mC0\u001b[39m\u001b[38;5;124m\"\u001b[39m, linewidth\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m2.0\u001b[39m, label\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTrain\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/style/core.py:130\u001b[0m, in \u001b[0;36muse\u001b[0;34m(style)\u001b[0m\n\u001b[1;32m 128\u001b[0m _apply_style(rc)\n\u001b[1;32m 129\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIOError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[0;32m--> 130\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mIOError\u001b[39;00m(\n\u001b[1;32m 131\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{!r}\u001b[39;00m\u001b[38;5;124m not found in the style library and input is not a \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 132\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mvalid URL or path; see `style.available` for list of \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 133\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mavailable styles\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(style)) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n", + "\u001b[0;31mOSError\u001b[0m: 'seaborn-v0_8' not found in the style library and input is not a valid URL or path; see `style.available` for list of available styles" + ] } ], "source": [ @@ -680,6 +900,7 @@ "execution_count": 15, "id": "f71e4924", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -756,7 +977,7 @@ "formats": "py:percent,ipynb" }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -770,7 +991,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.10.5" } }, "nbformat": 4, diff --git a/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.py b/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.py index d59d634c..ca0c9f23 100644 --- a/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.py +++ b/tutorials/generative/classifier_free_guidance/2d_ddpm_classifier_free_guidance_tutorial.py @@ -6,9 +6,9 @@ # extension: .py # format_name: percent # format_version: '1.3' -# jupytext_version: 1.14.1 +# jupytext_version: 1.14.4 # kernelspec: -# display_name: Python 3 +# display_name: Python 3 (ipykernel) # language: python # name: python3 # --- @@ -66,8 +66,9 @@ from generative.inferers import DiffusionInferer # TODO: Add right import reference after deployed -from generative.networks.nets import DiffusionModelUNet -from generative.schedulers import DDPMScheduler + +from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet +from generative.networks.schedulers.ddpm import DDPMScheduler print_config() @@ -231,6 +232,7 @@ for step, batch in progress_bar: images = batch["image"].to(device) classes = batch["class"].to(device) + print('images', images.shape, 'classes', classes.shape, classes) optimizer.zero_grad(set_to_none=True) with autocast(enabled=True): diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb index f8e67fbd..248180d7 100644 --- a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb @@ -5,13 +5,12 @@ "id": "63d95da6", "metadata": {}, "source": [ - "# Classifier-free Guidance\n", + "# Anomaly Detection with classifier guidance\n", "\n", - "This tutorial illustrates how to use MONAI for training a denoising diffusion probabilistic model (DDPM)[1] to create synthetic 2D images using the classifier-free guidance technique [2] to perform conditioning.\n", + "This tutorial illustrates how to use MONAI for training a 2D gradient-guided anomaly detection using DDIMs [1].\n", "\n", "\n", - "[1] - Ho et al. \"Denoising Diffusion Probabilistic Models\" https://arxiv.org/abs/2006.11239\n", - "[2] - Ho and Salimans \"Classifier-Free Diffusion Guidance\" https://arxiv.org/abs/2207.12598\n", + "[1] - Wolleb et al. \"Diffusion Models for Medical Anomaly Detection\" https://arxiv.org/abs/2203.04306\n", "\n", "\n", "TODO: Add Open in Colab\n", @@ -21,13 +20,132 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "id": "75f2d5f3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "running install\n", + "/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "running bdist_egg\n", + "running egg_info\n", + "writing generative.egg-info/PKG-INFO\n", + "writing dependency_links to generative.egg-info/dependency_links.txt\n", + "writing requirements to generative.egg-info/requires.txt\n", + "writing top-level names to generative.egg-info/top_level.txt\n", + "reading manifest file 'generative.egg-info/SOURCES.txt'\n", + "writing manifest file 'generative.egg-info/SOURCES.txt'\n", + "installing library code to build/bdist.linux-x86_64/egg\n", + "running install_lib\n", + "warning: install_lib: 'build/lib' does not exist -- no Python modules to install\n", + "\n", + "creating build/bdist.linux-x86_64/egg\n", + "creating build/bdist.linux-x86_64/egg/EGG-INFO\n", + "copying generative.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO\n", + "copying generative.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO\n", + "copying generative.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO\n", + "copying generative.egg-info/requires.txt -> build/bdist.linux-x86_64/egg/EGG-INFO\n", + "copying generative.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO\n", + "zip_safe flag not set; analyzing archive contents...\n", + "creating 'dist/generative-0.1.0-py3.10.egg' and adding 'build/bdist.linux-x86_64/egg' to it\n", + "removing 'build/bdist.linux-x86_64/egg' (and everything under it)\n", + "Processing generative-0.1.0-py3.10.egg\n", + "Removing /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg\n", + "Copying generative-0.1.0-py3.10.egg to /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "generative 0.1.0 is already the active version in easy-install.pth\n", + "\n", + "Installed /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg\n", + "Processing dependencies for generative==0.1.0\n", + "Searching for lpips==0.1.4\n", + "Best match: lpips 0.1.4\n", + "Processing lpips-0.1.4-py3.10.egg\n", + "lpips 0.1.4 is already the active version in easy-install.pth\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/lpips-0.1.4-py3.10.egg\n", + "Searching for tqdm==4.64.1\n", + "Best match: tqdm 4.64.1\n", + "Processing tqdm-4.64.1-py3.10.egg\n", + "tqdm 4.64.1 is already the active version in easy-install.pth\n", + "Installing tqdm script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/tqdm-4.64.1-py3.10.egg\n", + "Searching for scipy==1.9.0\n", + "Best match: scipy 1.9.0\n", + "Adding scipy 1.9.0 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for numpy==1.23.2\n", + "Best match: numpy 1.23.2\n", + "Adding numpy 1.23.2 to easy-install.pth file\n", + "Installing f2py script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "Installing f2py3 script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "Installing f2py3.10 script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for torchvision==0.13.1\n", + "Best match: torchvision 0.13.1\n", + "Adding torchvision 0.13.1 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for torch==1.12.1\n", + "Best match: torch 1.12.1\n", + "Adding torch 1.12.1 to easy-install.pth file\n", + "Installing convert-caffe2-to-onnx script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "Installing convert-onnx-to-caffe2 script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "Installing torchrun script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for Pillow==9.2.0\n", + "Best match: Pillow 9.2.0\n", + "Adding Pillow 9.2.0 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for requests==2.28.1\n", + "Best match: requests 2.28.1\n", + "Adding requests 2.28.1 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for typing-extensions==4.3.0\n", + "Best match: typing-extensions 4.3.0\n", + "Adding typing-extensions 4.3.0 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for certifi==2022.6.15\n", + "Best match: certifi 2022.6.15\n", + "Adding certifi 2022.6.15 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for urllib3==1.26.11\n", + "Best match: urllib3 1.26.11\n", + "Adding urllib3 1.26.11 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for idna==3.3\n", + "Best match: idna 3.3\n", + "Adding idna 3.3 to easy-install.pth file\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Searching for charset-normalizer==2.1.1\n", + "Best match: charset-normalizer 2.1.1\n", + "Adding charset-normalizer 2.1.1 to easy-install.pth file\n", + "Installing normalizer script to /home/juliawolleb/anaconda3/envs/experiment/bin\n", + "\n", + "Using /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages\n", + "Finished processing dependencies for generative==0.1.0\n" + ] + } + ], "source": [ + "!python /home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/setup.py install\n", "!python -c \"import monai\" || pip install -q \"monai-weekly[pillow, tqdm, einops]\"\n", "!python -c \"import matplotlib\" || pip install -q matplotlib\n", + "!python -c \"import seaborn\" || pip install -q seaborn\n", "%matplotlib inline" ] }, @@ -41,45 +159,27 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "id": "972ed3f3", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "MONAI version: 1.1.dev2239\n", - "Numpy version: 1.23.3\n", - "Pytorch version: 1.8.0+cu111\n", - "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", - "MONAI rev id: 13b24fa92b9d98bd0dc6d5cdcb52504fd09e297b\n", - "MONAI __file__: /media/walter/Storage/Projects/GenerativeModels/venv/lib/python3.8/site-packages/monai/__init__.py\n", - "\n", - "Optional dependencies:\n", - "Pytorch Ignite version: 0.4.10\n", - "Nibabel version: 4.0.2\n", - "scikit-image version: NOT INSTALLED or UNKNOWN VERSION.\n", - "Pillow version: 9.2.0\n", - "Tensorboard version: 2.11.0\n", - "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", - "TorchVision version: 0.9.0+cu111\n", - "tqdm version: 4.64.1\n", - "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", - "psutil version: 5.9.3\n", - "pandas version: NOT INSTALLED or UNKNOWN VERSION.\n", - "einops version: 0.6.0\n", - "transformers version: NOT INSTALLED or UNKNOWN VERSION.\n", - "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", - "pynrrd version: NOT INSTALLED or UNKNOWN VERSION.\n", - "\n", - "For details about installing the optional dependencies, please visit:\n", - " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", - "\n" + "ename": "ZipImportError", + "evalue": "bad local file header: '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mZipImportError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 29\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtorch\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcuda\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mamp\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m GradScaler, autocast\n\u001b[1;32m 27\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtqdm\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m tqdm\n\u001b[0;32m---> 29\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mgenerative\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01minferers\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m DiffusionInferer\n\u001b[1;32m 31\u001b[0m \u001b[38;5;66;03m# TODO: Add right import reference after deployed\u001b[39;00m\n\u001b[1;32m 32\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mgenerative\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnetworks\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnets\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdiffusion_model_unet\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m DiffusionModelUNet, DiffusionModelEncoder\n", + "File \u001b[0;32m:196\u001b[0m, in \u001b[0;36mget_code\u001b[0;34m(self, fullname)\u001b[0m\n", + "File \u001b[0;32m:752\u001b[0m, in \u001b[0;36m_get_module_code\u001b[0;34m(self, fullname)\u001b[0m\n", + "File \u001b[0;32m:598\u001b[0m, in \u001b[0;36m_get_data\u001b[0;34m(archive, toc_entry)\u001b[0m\n", + "\u001b[0;31mZipImportError\u001b[0m: bad local file header: '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg'" ] } ], @@ -98,13 +198,14 @@ "import shutil\n", "import tempfile\n", "import time\n", - "\n", + "import os\n", "import matplotlib.pyplot as plt\n", + "import seaborn\n", "import numpy as np\n", "import torch\n", "import torch.nn.functional as F\n", "from monai import transforms\n", - "from monai.apps import MedNISTDataset\n", + "from monai.apps import MedNISTDataset, DecathlonDataset\n", "from monai.config import print_config\n", "from monai.data import CacheDataset, DataLoader\n", "from monai.utils import first, set_determinism\n", @@ -114,10 +215,13 @@ "from generative.inferers import DiffusionInferer\n", "\n", "# TODO: Add right import reference after deployed\n", - "from generative.networks.nets import DiffusionModelUNet\n", - "from generative.schedulers import DDPMScheduler\n", + "from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet, DiffusionModelEncoder\n", "\n", - "print_config()" + "from generative.networks.schedulers.ddpm import DDPMScheduler\n", + "from generative.networks.schedulers.ddim import DDIMScheduler\n", + "print_config()\n", + "\n", + "\n" ] }, { @@ -130,9 +234,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "id": "8b4323e7", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -142,13 +247,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "/tmp/tmp142o2qtd\n" + "/home/juliawolleb/PycharmProjects/MONAI/data_brats\n" ] } ], "source": [ "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", - "root_dir = tempfile.mkdtemp() if directory is None else directory\n", + "#root_dir = tempfile.mkdtemp() if directory is None else directory\n", + "root_dir='/home/juliawolleb/PycharmProjects/MONAI/data_brats'\n", + "\n", "print(root_dir)" ] }, @@ -162,9 +269,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 15, "id": "34ea510f", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -179,152 +287,122 @@ "id": "fac55e9d", "metadata": {}, "source": [ - "## Setup MedNIST Dataset and training and validation dataloaders\n", - "In this tutorial, we will train our models on the MedNIST dataset available on MONAI\n", - "(https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset).\n", - "Here, we will use the \"Hand\" and \"HeadCT\", where our conditioning variable `class` will specify the modality." + "## Setup BRATS Dataset for 2D slices and training and validation dataloaders\n", + "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "da1927b0", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "2022-12-10 11:50:40,187 - INFO - Downloaded: /tmp/tmp142o2qtd/MedNIST.tar.gz\n", - "2022-12-10 11:50:40,255 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", - "2022-12-10 11:50:40,256 - INFO - Writing into directory: /tmp/tmp142o2qtd.\n" + "Task01_BrainTumour.tar: 71%|█████████▉ | 5.03G/7.09G [04:43<01:46, 20.8MB/s]" ] } ], "source": [ - "train_data = MedNISTDataset(root_dir=root_dir, section=\"training\", download=True, progress=False, seed=0)\n", - "train_datalist = []\n", - "for item in train_data.data:\n", - " if item[\"class_name\"] in [\"Hand\", \"HeadCT\"]:\n", - " train_datalist.append({\"image\": item[\"image\"], \"class\": 1 if item[\"class_name\"] == \"Hand\" else 2})" - ] - }, - { - "cell_type": "markdown", - "id": "6986f55c", - "metadata": {}, - "source": [ - "Here we use transforms to augment the training dataset, as usual:\n", - "\n", - "1. `LoadImaged` loads the hands images from files.\n", - "1. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n", - "1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1].\n", - "1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform.\n", "\n", - "### Classifier-free guidance during training\n", "\n", - "In order to use the classifier-free guidance during training time, we need to not just have the `class` variable saying the modality of the image (`1` for Hands and `2` for HeadCTs) but we also need to train the model with an \"unconditional\" class.\n", - "Here we specify the \"unconditional\" class with the value `-1` with a probability of training on unconditional being 15%. Specified in the following line using MONAI's RandLambdad:\n", + "batch_size = 2\n", + "channel = 0 # 0 = Flair\n", + "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", "\n", - "`transforms.RandLambdad(keys=[\"class\"], prob=0.15, func=lambda x: -1 * torch.ones_like(x))`\n", - "\n", - "Finally, our conditioning variable need to have the format (batch_size, 1, cross_attention_dim) when feeding into the model. For this reason, we use Lambdad to reshape our variables in the right format." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "e3184009", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading dataset: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 15990/15990 [00:08<00:00, 1784.85it/s]\n" - ] - } - ], - "source": [ "train_transforms = transforms.Compose(\n", " [\n", - " transforms.LoadImaged(keys=[\"image\"]),\n", - " transforms.EnsureChannelFirstd(keys=[\"image\"]),\n", - " transforms.ScaleIntensityRanged(keys=[\"image\"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True),\n", - " transforms.RandAffined(\n", - " keys=[\"image\"],\n", - " rotate_range=[(-np.pi / 36, np.pi / 36), (-np.pi / 36, np.pi / 36)],\n", - " translate_range=[(-1, 1), (-1, 1)],\n", - " scale_range=[(-0.05, 0.05), (-0.05, 0.05)],\n", - " spatial_size=[64, 64],\n", - " padding_mode=\"zeros\",\n", - " prob=0.5,\n", - " ),\n", - " transforms.RandLambdad(keys=[\"class\"], prob=0.15, func=lambda x: -1 * torch.ones_like(x)),\n", - " transforms.Lambdad(\n", - " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", + " transforms.LoadImaged(keys=[\"image\",\"label\"]),\n", + " transforms.EnsureChannelFirstd(keys=[\"image\",\"label\"]),\n", + " transforms.Lambdad(keys=[\"image\"], func=lambda x: x[channel, :, :, :]),\n", + " transforms.AddChanneld(keys=[\"image\"]),\n", + " transforms.EnsureTyped(keys=[\"image\",\"label\"]),\n", + " transforms.Orientationd(keys=[\"image\",\"label\"], axcodes=\"RAS\"),\n", + " transforms.Spacingd(\n", + " keys=[\"image\",\"label\"],\n", + " pixdim=(3.0, 3.0, 2.0),\n", + " mode=(\"bilinear\", \"nearest\"),\n", " ),\n", + " transforms.CenterSpatialCropd(keys=[\"image\",\"label\"], roi_size=(64, 64, 64)),\n", + " transforms.ScaleIntensityRangePercentilesd(keys=\"image\", lower=0, upper=99.5, b_min=0, b_max=1),\n", + " transforms.CopyItemsd(keys=[\"label\"], times=1, names=[\"slice_label\"]),\n", + " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", " ]\n", ")\n", - "train_ds = CacheDataset(data=train_datalist, transform=train_transforms)\n", - "train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4, persistent_workers=True)" + "train_ds = DecathlonDataset(\n", + " root_dir=root_dir,\n", + " task=\"Task01_BrainTumour\",\n", + " section=\"training\", # validation\n", + " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", + " num_workers=4,\n", + " download=True, # Set download to True if the dataset hasnt been downloaded yet\n", + " seed=0,\n", + " transform=train_transforms,\n", + ")\n", + "nb_3D_images_to_mix = 2\n", + "train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", + "print(f'Image shape {train_ds[0][\"image\"].shape}')\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 7, - "id": "4c11b93f", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "execution_count": 13, + "id": "73d72110-a8b3-4e03-91cc-1dab4d5a7b87", + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2022-12-10 11:51:08,067 - INFO - Verified 'MedNIST.tar.gz', md5: 0bc7306e7427e00ad1c5526a6677552d.\n", - "2022-12-10 11:51:08,067 - INFO - File exists: /tmp/tmp142o2qtd/MedNIST.tar.gz, skipped downloading.\n", - "2022-12-10 11:51:08,068 - INFO - Non-empty folder exists in /tmp/tmp142o2qtd/MedNIST, skipped extracting.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading dataset: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1977/1977 [00:01<00:00, 1545.02it/s]\n" + "2023-02-02 10:39:45,467 - INFO - Verified 'Task01_BrainTumour.tar', md5: 240a19d752f0d9e9101544901065d872.\n", + "2023-02-02 10:39:45,469 - INFO - File exists: /tmp/tmpyurp7egh/Task01_BrainTumour.tar, skipped downloading.\n", + "2023-02-02 10:39:45,471 - INFO - Non-empty folder exists in /tmp/tmpyurp7egh/Task01_BrainTumour, skipped extracting.\n", + "Image shape torch.Size([1, 64, 64, 64])\n" ] } ], "source": [ - "val_data = MedNISTDataset(root_dir=root_dir, section=\"validation\", download=True, progress=False, seed=0)\n", - "val_datalist = []\n", - "for item in val_data.data:\n", - " if item[\"class_name\"] in [\"Hand\", \"HeadCT\"]:\n", - " val_datalist.append({\"image\": item[\"image\"], \"class\": 1 if item[\"class_name\"] == \"Hand\" else 2})\n", - "\n", "\n", - "val_transforms = transforms.Compose(\n", - " [\n", - " transforms.LoadImaged(keys=[\"image\"]),\n", - " transforms.EnsureChannelFirstd(keys=[\"image\"]),\n", - " transforms.ScaleIntensityRanged(keys=[\"image\"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True),\n", - " transforms.Lambdad(\n", - " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - " ),\n", - " ]\n", + "val_ds = DecathlonDataset(\n", + " root_dir=root_dir,\n", + " task=\"Task01_BrainTumour\",\n", + " section=\"validation\", # validation\n", + " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", + " num_workers=4,\n", + " download=True, # Set download to True if the dataset hasnt been downloaded yet\n", + " seed=0,\n", + " transform=train_transforms,\n", ")\n", - "val_ds = CacheDataset(data=val_datalist, transform=val_transforms)\n", - "val_loader = DataLoader(val_ds, batch_size=128, shuffle=False, num_workers=4, persistent_workers=True)" + "val_loader_3D = DataLoader(val_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", + "print(f'Image shape {val_ds[0][\"image\"].shape}')\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "6986f55c", + "metadata": {}, + "source": [ + "Here we use transforms to augment the training dataset, as usual:\n", + "\n", + "1. `LoadImaged` loads the hands images from files.\n", + "1. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n", + "1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1].\n", + "1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform.\n", + "\n" ] }, { @@ -337,38 +415,26 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 47, "id": "4105a01f", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", - " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", - " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", - " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n", - "/tmp/ipykernel_16221/734547315.py:17: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", - " keys=[\"class\"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "batch shape: (128, 1, 64, 64)\n" + "Batch shape: torch.Size([128, 1, 64, 64])\n", + "Slices class: tensor([0., 0., 0., 1.])\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAE4CAYAAACKfUBxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAABMH0lEQVR4nO3dd7Ae5Xn+8ZsgEOoNSUe9N4RACASIJmHTERYlpqWYODCQjPFMGJLYMxmXJDOeYGfSJnEyJjEekoBjGwvRi6lCCCFEUQf1o37UBQjsGP3+zO+57gveRdZZCZ3v57/nmfvsu+++u88+u9Jee9T+/fv3BwAAAAAAAFCj3zrUKwAAAAAAAIC2h5tSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUrl3VwqOOOqo11wMAAAAAAABHiP379zes4X9KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKhdu0O9AgAAAAAAHIkGDBiQ+vbv31+0O3bsmGq6du1atNu1y5fur7/+etH+1a9+VWmdpk+fXrQfeeSRhusItBb+pxQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQu6P2V0wwO+qoo1p7XQAAAAAAqNVxxx2X+vQyuU+fPqlGr5E/97nPpZrzzz8/9Q0dOrRo/9Zv5f8rsmnTpqL9/PPPp5olS5YU7d27d6eaPXv2pL5hw4YV7bFjx6aad999t2hv3rw51cyePbto7927N9Wgbatyu4n/KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGpHphQAAADanL59+xZtzU+JiPj1r39dtLt06ZJqXBZNc3Pzb7h2AFrTgAEDivYll1ySai677LKi3a5du1SjeU39+vVLNSeccELq69WrV9F2WUw6Jh177LGp5r333ivajz/+eKp55JFHUt/ZZ59dtC+88MJUo9f/77//fqrRTKvvfve7qWbNmjWpD20HmVIAAAAAAAA4LHFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6gcwBowzp27Fi0XYjloabBwr179041mzZtSn3dunUr2i4gdMOGDUVbQ40BHBwa6hsR0b1796K9b9++VLNx48aD8vkaah4RMWLEiKKt42FExDHHHFO0u3btmmqamppS37Bhw4p2S0tLqpkzZ07R3rFjR6oZN25c0b7//vtTDYD/465Zv/GNb6Q+Pf5HjRqVasaMGVO0P/zww1SjAeVHH310qnFji67nL3/5y1Sjl+lujPzVr37V8PMdne989NFHDdfRLfvtt98u2i+88EKqWblyZep74oknirbbtjgyEHQOAAAAAACAwxI3pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO0IOgeAI9Rxxx1XtF1Ar3KnBA3WfPfdd3+zFfuUhg8fXrR/+7d/O9VMnz499WmI56xZs1LNSy+9VLTnzZt3IKsItBnt27dPfW5s6dChQ9H+rd/K/w6qwb4u6Fb/zgXtbt68ueE6XXLJJalm0qRJRfv0009PNRr+6wLbO3XqlPp03qyB6RF5jF6/fn2qefTRR4v2ggULUs3s2bNT37Zt21If8FmnweMReY6gweMREddee23q++IXv1i03ctQ9Dh245jOmzR4PCJi9+7dqU/nUm78q/L5ut5urHEvcdH1/t///d9U88EHH3zq5bg5ooahR0S88sorRfv1119PNTqOMa59NhF0DgAAAAAAgMMSN6UAAAAAAABQO25KAQAAAAAAoHbtDvUKAAB+cy73TzNVXM5BlbxAzT3p3LlzqtmyZUvqqxhZ2JBmCLi8CPdZAwYMKNqf+9znUs3xxx9ftMmUQlvW1NSU+nSMcJlKPXv2TH179uwp2jt27Eg1mtfkslD02Ha5K47mVbnxTzNNli9fnmo0d2rq1KmVPl+/v8trqeL8888v2i5Tx/0m99xzT9F2WTDAoaRjywUXXJBqunfvXrT79OmTaq655pqifc4556SaF198MfX9x3/8R9EeOHBgqjnttNOKtsuP05w9l/vUrl2+5Nbv73Kndu3aVbR1PuaW7dbRzds0+8nNrXTcdONIlfxSN27pWO4yvXRMdpmCeh5paWlJNTj88T+lAAAAAAAAUDtuSgEAAAAAAKB23JQCAAAAAABA7bgpBQAAAAAAgNodtb9iEm2VMFwAwG/GBT1qaKWGOn7c33Xp0qVou/BN5UIkP/jgg6L93nvvpZp9+/alPld3MLgw1Jtvvjn1DR48uGivX78+1WjQ5ve+971U8+abb37aVQQ+EwYNGlS03fGvx4gLutWgX8fNIzt06NBwOfrCgmnTpqUaPdYjIn76058W7bVr16aaL33pSw2XvXnz5qLtAoNdsLBOr9120z5Xo+O9C2xftmxZ6luzZk3R/ru/+7tUo2M7cDC4MG53bF188cVF2x3HW7duLdpun9Uwcm1H5BcfuGW5lxHouOWOUf07fclBRP4ers59N13vKtfjbo7o/k7nhC6MXbm5pgab67geEdGxY8fUp+Om+zs9J7nf8dFHHy3aDz74YKp5+umnUx/qU+V2E/9TCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I5MqcOAPi/bv3//VDNp0qTUp89eu2eIddn/+I//2HB9ZsyYkfq6devWsO/+++9PNS0tLQ0/D2jL9Dl799y9Hsc9evRINX379m34WS6L5P333y/abhwZMmRI0XZ5EZs2bUp9s2fPbrhOB6JXr16p7zvf+U7qmzBhQsNl7dq1q2g//PDDqUazCIYOHZpq3Bjt1gmoi56j3TxOxxaXF3LMMccUbZf74o5JHVt27tyZanQsGTVqVKrRY2vixImpxo2b8+bNK9qPP/54qtFMFTe2NTU1Fe2xY8emGjdH0pwVlzul+TRu/NW8Gjdtr5IXqBlTERG33nprw78DGvnd3/3don3nnXemmpNPPvmAlq37vxtHNJvJ5T65vDwdo9z4p30u00jzM12m1O7duxt+fpXcuSrHf5XluGW5Zev4371791SjY5tbjtv+ut5uHNOx9fjjj081mhfoMvZuueWW1If6kCkFAAAAAACAwxI3pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO0IOj8MnH/++UX7rLPOSjVf/OIXU1+HDh2K9o4dO1KNBlu+9tprqUZD+6ZNm5ZqXPioBpv/+Mc/TjUaUOgCAjVE8MMPP0w1Ltjwgw8+SH3AoeLGyD59+hRtFzSpob3XXXddqtHAXBeGu2XLltT3+uuvF+3hw4enmrPPPrtouxDLxx57rGi7AHMX4jl37tzU11pc+OVXv/rVoj1o0KBUM3DgwKLttq2GmC5dujTVrFixIvXpGHXfffelGuBgcOH7Ot64869OAV1guL4gYdiwYanm8ssvT30aCP7uu++mmpUrVxbtV155JdU0NzcXbfdSAR1rI/L3b9euXap5/vnni/aCBQtSjc5jnDFjxqS+P/iDPyjaLiBdt78LA9bwdQ0e/ri/0/XWbR0RsWrVqqL9X//1X6lm3bp1qQ9t10MPPZT69BrBHQ+OzvfdGKHHSJUXNrhj1r2MZdu2bUXbHVsa4q3zgYj8wpS9e/emGjf+uj6l4d/u87XPjQfuu1V50YNeo7nl6HnDBca7uZV+f3dLQq913Us1dJ3c7+/mrffcc0/RXrx4carBwUHQOQAAAAAAAA5L3JQCAAAAAABA7bgpBQAAAAAAgNqRKXUYeO6554p2+/btU417PlafxXbPMOvz2e55YX1eumfPnqlGn+mNyBky+mx2RMTChQuLtuYXROS8nNGjR6cafV47ImLJkiVF2+W8vPHGG6kP+E25bCKXDaDHsss90Zwnd4xq7tOIESMaflZEfq7fHaP6nL/LAnjzzTeL9vvvv59q/vmf/zn1tbS0pL46felLXyraI0eOTDW9e/cu2i6LQfOqdMyK8OdIHX/uuOOOj11XoKoBAwakPjeV033SZdrp8X711Venmttvv71oa8ZURMTWrVtTn84/3Ofrud3lpegYtWzZslSj84GInOnmxui33nrrE/+mqqamptR3ww03FG133tDxx2X6aRaWG6NcFueePXuKdo8ePVKNrrfL9PqjP/qj1Ie249RTTy3a9957b6oZN27cAS1b91vdZyPyeLNv375Uo+OfO9Y3b96c+nRO5K61dNxyNdrn8pPcGK2ZTu7vdExy46gux81HXKaecp+vqizbzSPddWSVuZVy21+vY91yXn311dT35JNPFm2X+0l+8cFBphQAAAAAAAAOS9yUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAtWuceoaDygVdaojwwIEDU02/fv1S36ZNm4r2M888k2o0fNSFiI8ZM6Zoa2BehA9f1oDMIUOGpJoTTzyxaLuAOg0/c0F7LsR0+fLlRdsF63Xr1q1ou4BEF4iItssF/WtAuQvjdSGOF110UdE++eSTU40GbboQyb59+xZtFyrq1luDht3YokGj7hjVZevYExFx2WWXpb4f/ehHqa+1uPDflStXFm0XEK9jovv+VcLgO3bsWGmdgE9Lxw0XdOv2Pw3NdgHpOiboSxXcsrds2ZJq3DGh851evXqlGg0xXr16dapZsWJF0XaB3RMnTkx9uiz34pODFWLrXsai46SG+kbk7eYCevV84z7Lhc/ruOWCZnXZLugXbdtpp51WtN1LDfSYPOaYY1KN20c1fHz9+vWV/k65MVG5Y13nP+4aQV/s4l4qo2Oku2Zyczudb7jvqmOEq9HrGDcf7NKlS+qrQscRF0av29/9/q5P50iuRsdEN/7rdnS/tYahR+TfSff1iIjZs2enPrQO/qcUAAAAAAAAasdNKQAAAAAAANSOm1IAAAAAAACoHTelAAAAAAAAUDuCzlvZ7bffXrSnTJmSasaPH1+0XRhdlfBxF76nAX0a2ByRw0dd0JyGAUbkYDsXEKjr7b6HhjHv3bs31QwdOjT1fec73ynaLqBYg02XLl2aaubNm1e0Z86cmWpw5NLjxgWW677lQiTd32mwsAtR1PBbF46t6+heBrB79+7Up8GaOh5E5PBbV6PHkQsMdgHFGizpAuIPFhd0quGfLnxUxzsXRqrLcdtox44dqW/NmjVFW18OEZGDXtG29e7dO/W1tLQ0rHFzCw1Idy86mDRpUtF240+7duVU0Y1127dvT30bN24s2osXL041Om/o379/qtE5kr6cJcKHeOtLZDT4PSKPUW+99VaqOVD6Qgo3j9Hxp8oY3blz51Sjv1FVOt8677zzUs0LL7xQtN15bP78+Qf0+Ti0dC5z3XXXpRrdJyZPnpxqNOjbzVHcOVL3W7dv6zVClWPEfb6jx1+Va60qy3bLcQHdOpa6EHMN7XafrzXuesjNkfTzXNC8bn/3PfTa0r14w13b6m+pnxURsWrVqk9sR+SXb7gat211Pd3LwPSlWno+xsHD/5QCAAAAAABA7bgpBQAAAAAAgNpxUwoAAAAAAAC1I1PqILrllltS35/+6Z8Wbc0miYjYtm1b0XaZBosWLUp9muHgnsXWfAaXhaPPYu/cuTPVuJwVfa7Y5TVoFpV7Xlif6e7bt2+qcXlZyj1nrd9XMx7c52vGT0TEnDlzUt+SJUsarhMOf5op4o5R3UeGDx+ealymy+mnn1603f6nmR7uuX/NXXDPxrvcN801cMeRZhG4TBk9jtwxcumll6a+K6+8smjfcccdqaY1aV7c1VdfnWo008H9/rr9jz322FTjMm00i2Ps2LGp5lvf+lbR3rRpU6rBkUvPm27f0nO7O4+7vKKRI0cWbXf8a6ZTlWxKlx/l8mJ0WZpf5Wpc7lyVMdJlWmrOiRujNS/mQDOldByNiHjssceKtht/+vXrV7RdXot+f5epo+exiLzfuJyZKsvRTC/yo44cer5zmTq6H2lWXESea7i5vjtH6rHsstHcmKjcvEW5Zevczq2jHjeuRvvcNZPLWdJ1chmTW7duLdpu/qXXelW+a0Re70GDBqUa3UfcHKnK2OLGSB3/3XprhqKb62qmqtv/3N/p5y1YsCDVuHMLWgf/UwoAAAAAAAC146YUAAAAAAAAasdNKQAAAAAAANSOm1IAAAAAAACoHUHnFblg0S5duhRtF9CtobUuIG/Xrl1FW0PdIiKmT5+e+jT8T4MGI3L4twuo02DRKmGEETkQ3S1bP9+FmOrnafDox/Vp+JyrGTNmTNF2YYjr168v2i5UvmvXrqlPw65XrVqVanB4cb+/Hjdu/9cwxsmTJ6eayy+/PPVpiKILCNYaFwapIY4ujLhK0KcL39SASvf5GqypAe4RES0tLalv5cqVDdepTu631aBzt4327NlTtN02csvW0OKBAwemGhdsirZDz5HdunVLNSeffHLRnjhxYqo55ZRTUp+GqLuAbD1vu5coaECue6mJHkcR+TjRwO6IPCa6wFzdRu6lJm7+o3MpDQOO8KHNB8vcuXOLtnvRhc4tdF4ZkQOSqwT9RuSAZBe0rJ/nxvavf/3rqQ9HBg2EdvuWHqPumHFjgnLHrbtuUL169SraLrBbufP4hg0bGva5UHU9Rl0Yt57/3XjkvquOt24ep+cEd61T5WUsbr11vHXLVu576PZ2n+XmTfrCKHeOGjx4cNGucq3trqPd/qcvmnC/m7smROvgf0oBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDakSll3HLLLanvhhtuSH3nn39+0X755ZdTzaxZs4p2U1NTqhk7dmzRdvlF7vlc5Z4F12d/3fPKnTp1Ktou98nlTOizt+45Y31e2WVT6bPI7vM19ykiorm5ueGyTzzxxKKtzyZHRJx66qlF2+UFvfnmm6lvwYIFRXvp0qWp5oUXXkh9OHRc7onu2+5Z9EGDBhVtfQ49IuKEE05Iffp8vMsP0iyCzp07pxp9zt8dj65Pj0n3vL5mw7lsEh1bXH7S8uXLU9/8+fOLtvtummnRmlw2yj333NOwRjMt3Djqci50bHNjlNuWaDv0fOMyPTRDyuX+uLwOpftxRLV9VI//Kud6t2yX86L5eC4LRY+RKrlXrs99vs4RXH7ojh07Ut+B+PGPf5z6zj333KLttr9m+rjf2u03+l2qZCoebjmAqJdeD0TkLDLNSozI1y1uruH27SqZltrnjmMdE9z1kBsjBgwY0HDZVcYxzSuqms2rdW4dNffNbdstW7YUbZfx5fIK9fh347hyY7T+Rm4dNZszImfxvv3226lG8xJPO+20VKPX1m48dPuWrqc7t6A+/E8pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHYEnUfEFVdcUbRfffXVVKMhdhE5WM6FGGuwm4bqRfhgO+XChzWQzq2jButVCRF0AXkatBeRw/5cGLuG+GmonqtxIYIuWHr06NFF24Xv7d27t2hv27Yt1QwcOLBou6DrdevWpT4NZHefP378+KK9ePHiVIP6uBBPDf8966yzUo0GK5500kmVPk/DFt3n79u3r2i741iPdf2bCB8suWvXrk9sR+TjzYXBu+NfuWBPDdZ0x/Gzzz5btPUFBq1NA9r//M//PNXcdtttRdsFTbvxTwNitQ0oN0boucUFxrqXCOi5vMocwdExwp3r3DxGA9Fd+LCukwva1XV0QesuoLbK+KvnZA1eP5iuvfba1PflL3+5aP/e7/1ew78bNmxYqnHbpEr4vY5b7rdF2+HC8Ku8jEDPbe44cvujHv/uONbxp8o6uhcWuHmT9rnjyI2bSudN7lrHrbdyL1XQl8G4batzNPdZbqzXcbvKdZz7jXT+6eZx7vpL59JTp05NNfp93e8xdOjQon3jjTemGheirtfx7rvp/M+9DOt//ud/ivYTTzyRatAY/1MKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUrs1lSl188cWpT/Oa3POq5557buq7/vrrP/Xnu2yiFStWFO01a9akGvecsWYv9O3bN9VopoR7prjKM90ur0afIXfPIuuy3OdrjXte3T0frd9/+/btqWbOnDlF+7XXXks155xzTtF2mULu+2s+mMuL0UwdMqUOrSrZaC5TSX/rPn36VPo8zRVwx5YeE1XyC1x+i+vTY8Tltehx63JfqnyWy4saMmRI0XbHsY5t69evTzUur6q1aMZARMTjjz9etF2mw9lnn91w2T/4wQ8OfMVwRNLzhst903mDOx40GzEiYsqUKUXbncdcPlUjLi/F0ePd/Z3mzLj10fV2y6mybDeP0vmfZgy6v3NzHc2YdMaOHduw5t577019Oo+77LLLUo0bkzRTq0qmzcqVKxvW4Mjl8mt1/3PZUDq3cNlMLtNJ90k3t9A5iZuj6LI1hynCH7eaxefm8bpO7jjS7+vmWm6M0nVy2YD6eW7b6vd1n+XyerXPzT+1zy1b57Hut3bntuOPP77hsvWaUMc193fuXHfCCSekPp23um00atSoou3mepoN6HKn3G+rOVuLFi1KNW0J/1MKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqF2bCzqfNGlS6luyZEnRdiFyO3fuTH0aCOnC7zR8zoWvaWipC3FzIcIavucC2jRszgWNVwnxcwHNGuzpajQgz4XoaUCnCzp0wa4adugCqjWM/rbbbks1P/zhD4v2rbfemmqmTp2a+n7/93+/aLsQ+//8z/8s2qNHj041q1atKtouIBEHh9v/9JhwYYwaft3S0pJqNAzULbvK51cJ9d+7d2+qcQGlOia4oEX9fHesaY0bR6rst26M0mDhSy65JNW8/PLLRdv9Rq3plVdeKdqrV69ONT/72c9S34wZM4r23//93x/U9cJnnwbruuNv4cKFRXvBggWp5pprrkl9ek52y9bzrZt/6PHugo6r9LlxS+cRVQKCXWCxGzd1/uMCkvWc3NTUlGr03L506dJUs3HjxtSnLzb52te+lmqquPvuu4u2O/9MnDgx9Z166qlF252jNET4W9/61qdePxw53PGnx3GVoHF3PVAl/NwtW5dVZYzo1KlTqnHXFlpX5VrDXcfpOrrrEfeiHT3+3NxGP/+tt95KNXPnzi3aQ4cOTTUXXHBB6tOgcbeNdNu67a9/58aa5ubm1Ld169ai7fabHj16FG33Ui89j+pLpiL8SyzGjBlTtN2Lbl599dWi/aMf/SjV6OddfvnlDdcxIuJf/uVfUl9bxv+UAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrX5oLOH3nkkdQ3ZcqUoj127NhU44KuR4wY0fDzNMTNhWFWCQh2AcUa9rlnz55Uo30u/E8DQjV4MCKHkUbkQEAXPlolRF2D/tx31aC9iBy+6EKktc+FQU+fPr1oa4B5hA/f09BWt/11W+7YsSPVaCCh20dcsCI+vQ0bNqQ+DR9cv359qnnnnXeKdteuXVON6+vZs2fRduGL+ndVjj8XRumOP93fXYimho+7Y1Q/z32+69Nje8KECalG+zZt2pRqBg0aVLR/8YtfpBoXUKrjr7544kDpcj+u77XXXjson4cjl5433LlGXwYyatSoVKNhsBF5LNPlROTjxs1HNGjXhRE7ev51f6fnNjeOaPi6mw9UGbfcixZ0e2vwb0Tetv3790817rytdRp8/nF/p/SccP/996eaZcuWpb4qL+P513/914afj7bDBVTruOECy/U4dudjd/zpPunGKOXGkSpB25s3b059ekxWmSO5uZ6O49u2bUs17mUQOv64+eeKFSuKtr7AICKHmA8ePDjV6Hw0Ir/Ewv1GVa5RdZ9w8yH3gphhw4Y1XEddJ7cdda6p7Yhq5xZ3/T9u3LiGn68h7m+//Xaq0TB6x62jmxMcqfifUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHZHfKbUpZdeWrTPOOOMVHPOOecU7VNOOSXVvPLKK6nvjjvuKNqaDRQRMXHixKLtshA0m8g9r+qeM9Vnr/v06ZNqtM8tu8rzwi6vSZ8Zr1LjMg30edmWlpaGy3HLcttW/06fn47Iz6e73Bn3fHKHDh2K9vDhw1PNVVddVbQnTZqUajR3Rp8fx8HjMp26dOlStE8//fRUc8MNNxTt3r17pxqXhabP2a9bty7V6P7nsqF0jHAZYy7nQY8tN47o8V8l08ZlKrj1dset0nXSjIGInCl14YUXpppXX3019S1durRoP/TQQw3XB6jTli1binaVvBR3HnfnHx2T3Biln+fyI13OiNIxKiKPU+67VVm2jiPuXO/O0VXO/zqPcPMYzTlx4/+YMWNS3/jx44u2m3/efffdRdvl3lTxxhtvVOoDPonb//Q4cvu/ZjhpVmeEn1voOXr27NmpplevXkW7X79+qUYzjFx+kRsj9Rh1c0Qdb9wYofMvlw3q8qI059R9/oABA4q2u9bT7e+utVymrn4Xl6mnc+Tt27enGr1uc9t//vz5qU/3LZd7rDUud0z3LXdd6+ajek6oco3sfv+BAwcW7SFDhqQaza+KyBlm7rtppm2VefVnFf9TCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKjdER903r9//6LtAsI0NE5DLSMiJkyYkPo0ENuFeG7atKlou6BNDQRsampKNS4MVAPZ3LI1oM+Fv2mwqVuOC2jTZbug4yoBgfrdXECiC2itElCqwX4uIFpDZN02cvvEiBEjirYLWtWAuj179qQaDRZ0YegLFixIffj0NAwyImLy5MlF2+1rGuLYo0ePVOPCv/UYdSGOeoy4oGENEXb7qAu21PV0x7GOY+7zNdTfBQ27oHVdT3eM6Ljhlq017nicMmVK6hs3blzRJugchxs9btzLCPTctnPnzobLifChrUrPv24cqXIed3S+5b6bfp6bo1UJdnU1ut5uHqV9bh6j45gbf92ydZxy4++dd975iW2gTu78r9c2bozQfdsFfbtj66STTira7oUBGoa+cuXKVKNzHXfN5sYI/b7uhQ1VXtik80Z3HaXXAxH5BS3uRTvTpk1LfY3W0b2wyY2/eo5wL+PRMG4NlY+I2LZtW9F+9NFHU427ttPP1+B3V6Pz0Yg8/67y4quIHJBe5YVB7hhZvnx50V60aFGqcS+xam5uLtpVXrTl7jUcKfifUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHZHfKbUzJkzi/bZZ5+dagYPHly0Bw4cmGrcM8SjR48u2i4vSLlMlSrZUO4ZWn1m1uUl6bOvLgtBuUyd9u3bpz7NUHDP6+pz5lXyMtwztRs3bkx9+nyyywvS54xd7o/+tu436tatW+rTbemeM16/fn3R3r59e6oZO3Zs0T7//PNTzUsvvZT67r333tSHT+b2o8WLFxftKjks7ljbvHlz6tPn090z9bq/6fPrEXlfc/kl7tjWZ89dFsCSJUuKtvse48ePL9ou98HlpVTJa9O8Ake3kcsLcM/Z62/5hS98IdXMmjWr4ecDraVfv35F2+3HOrdwmU6a6RERsXr16qLdt2/fVOPObUrHO3eOdmOifhc3bun5t0rukxtr3Plf/85tWx2j3XlcxxGXO+LmbTqXcpmCLkMFOFTcHLlLly5F251/9Rhx10yuT8cSl2mkx63muUbkY82Nke4aRecWbvzRGrdsndu4Gp1HReTrzyrb3401uv01TzfCX6PquO2uUbTP5SXp3Hbq1KmpZujQoalPt62r0WtyN9a76zZVJS/RbVvNQnPXyEOGDCnal112WapxmVKaM/bWW2+lmrvvvjv1Han4n1IAAAAAAACoHTelAAAAAAAAUDtuSgEAAAAAAKB23JQCAAAAAABA7Y7a75LfXKEJfztSjBw5smi7gLoZM2akvltuuaVou6C1vXv3Fm0X9KvBxq7GBY0rF9BcJURd+1wYpwuI0/V0IaLa52o0WFmDlyMiNm3alPo+//nPF20XrNfc3Fy0NdT649ZJuW2r4XduOfr5LkRw586dDT9LA9MjqgVEo7GbbrqpaPfp0yfV6EsNNJwyImLQoEGpT0MU3e9fJQxYafBlRN4fI3KIpQsj3rJlS9F2+5Ueky+++GKq0aDHiIiLL764aLtt5ILdlQamu9NWlaDzNWvWpJpvfOMbDT8faC36EhN3HA8bNqxoDxgwINXoeSQihwifccYZqUbPo8cff3yq0WBxNx9xAeH79u0r2lXCh11Ar84RXNDu0qVLU9+oUaOK9mmnnZZq9Pu6EGcdf9xv5GgguguIf/vtt4v2XXfdVWnZQGv45je/mfr0GHEvOtFxzAX4a6hzRMT8+fOL9tq1a1ONjltnnXVWqtFjzb2wxR23PXv2LNpubqXjnRvH9CUy7sUTTU1NDfvc+FMljFvXyV1HuDG6paWlaLvtry8IcsvRsc19V0fnpC4MXs8/bhvpOXHChAmpxr3oQ7eTm4/qvu1eWKTnX3fNtmDBgtT32muvFW33MiY9Rj6rqtxu4n9KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2lV7MP4IpzkvLq/hrbfeSn3f/e53i3b37t1TTY8ePRouW3NWXDbKM888k/o0i+DMM89MNZqhoBlXETlTxuU1vPnmm6lPM6X0uduInCnhci90OS7TQp/7joi4//77i/bdd9+daq644opPbEdE9OrVq2i7vIzXX3899c2aNatou0yLHTt2FG33LLY+L71y5cpUg9ajx9bXvva1VHPBBRcU7UceeSTV/OVf/mXq0+f83b6teTEuL0Fz71w2lB5rETmfqkpejdv/NWfKZRq4sUXzYdxxrPkALi9Aufy+PXv2pL4qOQ/AoaQZjppfFJFzP3S//ri/c8eJ0vOvy9TQZVfJr4zIcxm3bP18nddE5DmJy+ZzY1KV3EutcdtMMzVd7qbb/nq+d5kaR3JeKz573Niima4uG6pbt25F2801dK4bkTM83bGt84b+/funGjf+KJeN27lz56Ltjn+dx7ncXz3+e/fuXenzNfvK5RXptnVzNB1HVq9enWoefvjh1Lds2bKi7eaoeh3rfke9jnLXo25urXnNeq6LiFi3bl3RdvNfzbBy+8PJJ5+c+vS84c4RVTIFq+QMunOE7v/u3HKkZEpVwf+UAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGpH0Hnk0DwXRq5h4BE5kPPiiy9ONddff33R1lBht2wX4nbTTTelPg0E1jDiiBz2NnTo0FQzYcKEov3LX/4y1bigc/08DQyMyNvWhaErtx2nTZuW+nQ9XYhglc979913i7aGGkZEjBkzJvV99atfLdou6PwXv/hF0XYh5hps5wKrN27cmPpwcOi2bW5uTjVLliwp2i6M09FjtGvXrqlGQyRdGKKGKLrlaBhmRN6XXNC+hvG7wHINP3Vh5C5oWMefKgGh7vhz663c+NOlS5eGfwccSnosu8BUDd91Qbcu2FWP5eXLl6caPW6HDx+eak466aSiraGyH0fX040ROt9xIbq6HPf99TwekV+0sHbt2lRT5WUUOt7pciP8uK1/5+YjGhAMHEpPPfVU6hs4cGDRPvfcc1PNpEmTirbbr3WuE5HHBPfCEg3ovu2221LNqFGjirZ7qZG+VCoiv3zBXf9UeWGMjknuBQbuRQ/6eW7Z+oIEN49Sbhy/+eabU9+GDRuK9htvvJFqNAx9zZo1qUZf9PWTn/yk4TpGRDz44INF24WhX3XVVUXbzev0+s+NtW6M1vOP+910bunOUbofu9/aza11e7uXYbQl/E8pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHYEnUcOLHdBcx9++GHq0/BRV6Mhwh07dkw1GnTultOzZ8/Up8tyQd8atOmC1nbu3Fm0R48enWr+7M/+LPVpsKqGIbvPHz9+fKrRgDwX9OaC5fTzXUDitm3birYLCNSgWQ0ej4jo27dvw79zwXoa0OxC9DUQz23/Bx54IPW50FZ8erpPrF+/PtXMmzevaLswdPf7a4iuC8jV49+Feuu+7o4RF9Cp4YsuxFHDH11guv6dO45cQLl+vvs7/S4ujFK3rRsPdDyOyOvt1hE4lHSOcMopp6SaW2+9tWhfd911qUYDYyMi/umf/qloa6htRA4Id2OLBh2786ELH9c5is413Oe5gF59YYqOxxF+bNVluzmS9lUZW90Y5ehc0r0gQs//bv5ZJdgYOBh+53d+J/XpcTxkyJBUo8eoC6N252idt7sX/YwcObJoX3vttQ3X0V1ruWUrN0c6kBBzdx3n5mgatO3GCN1GbjvqmOSu9dzcVq+R3NjWu3fvou3GSH35j7uOci+MWrFiRdF21zX9+vUr2u5lFBqs737/Ki/acNtNg93deUxfIuJeKtLS0pL69IVhVV7OdSTjf0oBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDatblMKfec8/Tp04v2jTfemGr0eemInGvi8kr0OWPNhonIz966Z3r1udeInAXhPl+zCNyz0I3WJ8I/n6s0d8Jxz0LrNtKMjQj/LK6up8ti0Oez3fPaym1rfe46Imf/uOfV9Rlyfe7afZ7Lj3DfDa3DZQHoM+R67EX45+y1Tp9Nj8j79rBhw1KNZri4Z+Pds+i6Tm782bx5c9HWZ9zd5w0ePDjV9OnTJ/VpPozLotG8BJe7oN/NbWv33XRs2bRpU6oBDiXdl6ucR5yJEyemvqlTpxbt+fPnpxodk1auXJlqdP7jzkdNTU2pT3M+XBaccse2HsfnnHNOqrngggtSn84R3Plf5036WW6dquR3RuS5nPs7/Xzyo3AouWw4vW7q0aNHqtGMVXcdUyXTzY0ROo/QjKGIPEevkt8UkcdbdxzrOrnrmCrXGu7aZt26dUXbjaNVcoZ03uqu9YYOHZr6dC7nsnl1Tui2v54j5s6dm2rmzJmT+nQ/eeaZZ1KN5kxddNFFqUZ/b5fN6jJ9dZu47+b2W6XZ1O77v/jii6lPz7+6nLaG/ykFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1K7NpSd//vOfT31//Md/XLRdiKajgZgbNmxINRoaunv37lSjoXkuaNAF62mfCzrXgE4XkKx9bjku/E6D9aoEDbtlKxd06AKSNWzVha9qiKFbR61xy3GhgbptXY2G5rnwxSVLlhRtDT6M8L8bWofb/hp0vnfv3lTTvXv31HfWWWcVbRfGq0GTLlRff38XIur2LT3e3D6qx5aGE0fk8HUNUP64z9fj372wQEM8u3Xrlmp0rHPBn83Nzalv8eLFRfupp55KNcCh9OUvf7lhje7vW7ZsSTWvvfZa6luxYkXR1sDgiIhbbrmlaLuXGFQ5/7iAcA0Wdudf7XOfpedkN/65OVKVEHPtczUaWuzmKC7YWOcW7gUZDz30UOo7WDT8fsSIEalGg4X1XIe2xc1tdN92L4PRgH4313fzDz3+3d/p57sXL/Xs2bNou/3YHaP6ghYNbI/I3829jEDHLTfWVQmRd/MYDch2L+w66aSTirZ78VTnzp1Tn863Bg0alGp0buvGSP1tTzjhhFTj5m16jlq4cGHDdXRzRPeiHeX2CT1HuRp9QdU777yTarTP7WuTJk1KfXosuYD0toT/KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrX5jKljj766NTnno+uQp9Fdc85n3jiiZ/4NxE5Z8DlTrm8qq1btxbtHj16pBp9zto906zP+brtUWUbuWex9TljzWGKyM+Qaw5DhN8mmn3jvr9+X7cc/f7ue3Tq1Cn16XPNLotK9wn3LPTGjRuLtssLWbNmTerDwaHPy7tMpf79+xdtt6+NHj069enxr8uJyPko7rl/HTdcNsl7773XsM+NP5qF4MZI5fKjHM15cVksVezatatou2f6n3/++dT3s5/97IA+70C4ceOyyy4r2ps2bUo1br3Rdmj2iMtG0hp3/Gl+XUTEaaedVrRdNuT69euLtsu00zHSZcO487bm07jzv8teUTpGunOtW7bOW1wWSpVMTe1z299liGheyl133ZVqXIaP0nmEG2uOP/741KfnEpe78m//9m9Fe9y4calGsxDdHAVHhqamptSn8183H9Zj1M0j3DGix6ib2+i8xWU66fHv5to61kVEPPfcc0Vbr6si8nWEy7TT3M0hQ4akGvf9db3dPE7HrdWrV6cazc9048G5556b+jR3zs0/3XWj0t9R83Qj/Nxa58jnnXdeqtFzi1sf3W/cedRtf8053bFjR6rRDK/Jkyenmj/5kz9JfcqdN1Hif0oBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAtWtzQecuxE2DPV3QpwaGR/ggPaXhaxqqGZFD3FxAoAtW1oBuF+Kmn+eCPl1on3IhotrnajRsrkr4ofuNXPioLsuFH2qN+x11HV2IowvW09/NBRRqQLMLddXw50WLFqUatB4NaHTHw4gRI4r20KFDU42GCkdEDB48uGi7MGDtcyG+uo/qsR/hxxbd31yNHjdujFAusNH1VXmJgY4b7vNXrlxZtN96661UM3PmTLuurWHSpEmpzwVNa/i9Cz/927/926Lt9iMcuVatWlW03csQ9DxWZT4Qkcc2d47WeYwbI/Q4dudx9/la50K9dfxxn+/Wu0qNjkku/FbP7W780WBzN9fYuXNn6vv+979ftKuEmruA4r59+xZt913d2HLllVcW7fHjx6ca/S5u/vvzn//cruv/z41/+OxxLwPS498do3r8Vz2ONUTajSN6bGk4dUS1Y0uPo4h83Lh5jB7/VV4Y464H3PWHrpM7jvSaxF0PrV27tmi7cWzQoEGpr3fv3kXbvURBxwg3/uk2cdvRjb96jnDXWrpt3edrn3s5l/tNdL9xIfL6gpBZs2alGhcij0+P/ykFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1K7NBZ0/8MADqU+DPocNG5ZqNOguIgd0axhaRA5RcyFuumwXBqxh2BF5vV2wnYa9uWVrsKULqKsSvuxC7HQbuaBvDQPXdoQPFtRA0KamplSjIXouIFC3kfsdV6xYkfpeeeWVou0C8jTE1m1/HFr6mzz77LMN/8Ydj46GprsQdV2WvhwhIgc9uv3Y9VUJEdYxyh1rumw3HrixTccSF1Cp28SFob766qtF+7HHHks1rekv/uIvirYGmEdEDBgwIPVpsKkLY9UQ12eeeSbVXHrppUXbhXjis+mJJ54o2jfffHOq0fOYG39c+HiV8N/hw4cXbTdGbN++vWi7MOwqY6ILMdZ5gxv/dIx2cx03blR50UKVc7Ku00svvZRq7r///obLqcK9RGHKlClFW1+8ERHRvXv31Oe2t9Jt6cKAdR7T3NycanSMiqh/nMZvzs3/9dhycwQ9j7n5gNsfdb7t5v86R3DnWt1vXWC7u/6o8jIqne+4eZz2ue/v5k3a5+ZxOo67dRw4cGDRdtdjjs4l3BxNv4vbRzp27Fi03bjqxhbdl9z1l66ThuNH5Bdkud/Iff6SJUuK9ssvv5xq3nzzzdSH1sH/lAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALVrc5lSLmPhySefLNobNmxINaNGjUp9F110UdF2WVT6nL/7fH2m2eVXueeMNQtA85tcn3vOVpfjsiF69OiR+vSZZfeceZVl9+/fv2iPGzcu1bi/05yLHTt2NPy7Tp06pZo+ffoUbffcu65jRH7O2W3bxYsXpz4c3lwWwRtvvFG0Bw8enGrcs/hVavS4cc+963P/LlPAZRjoc/4ui6XKcnRMcplGLq9Bjz93jFTJFHj66af9ytbklFNOKdouv8VlOOh2c99f8zrcPqKZMs8999zHrSo+4+bPn5/6hgwZUrRbWlpSTe/evVOf5k66Y1vHBDf/0H1Ux5UIv29rzpUbNzR7xB1HVTL13HGjfVVyp9z337x5c9FetmxZqjlYtm7dmvp0TPzhD3+YajRTJiLiggsuKNruvKXf321/nSO5+aDLwpoxY0bRvu2221INDi86ZkTk413zeyLyvNkda25u4443peOGG0f089w83o1/Ora4ZbvxRmk2lMv4c8dNv379irbLYlq/fn3R1vwux83HHB233W+k38UtW9e7ylgbkcc2HWsj8m+pecKuxm0jl4Wo5z83t+vbt2/R3rJlS6rBwcH/lAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABq1+aCzh0NRHMhZhpG57gQYQ3fdiGW+ncuRNQF9GmfC+jTEEEN44vI4XsuMNQFxGn4nQuI06BjF4auwXoa/P5x66SBnC7EXMPv3LbVGhfY6sIfx44d23DZP//5z1MfPns0fLFr166ppkoYZpWgcxdirsetW44Ln9SwSRdQqp/nlqNjlFvHo446quGy3fG/fPnyT2y7z6+bjn/uxQfu++tY4r6HvozCBaT+wz/8Q9E++eSTP35l8ZmmL1WIyMftqlWrUo0LOh8wYEDRPumkk1KN7svuXKefX+VYj6gWUKzHhJuj6Dq5MG43b9D5jwvf1ePPzWN02953332p5mBZunRp6rv00kuL9u23355qXIh5lRD7KuOPjmPuHOGCpc8666yi7eaxbp/AofPwww+nPp1bX3jhhalGw6DdsebouOHGEe2rEqLtAsPdvqbXPy6MXK9R3PxLXzQwcuTIVNOlS5fUp9zLsHS8czX64ic313J0W7qxvcrcVpdT9YVdVa4j9Tp65syZDWvGjx+fanr16pX6dDu5sU2D1Qk6bz38TykAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFA7MqUiP0N85ZVXppoxY8akPn2G3z3DvHDhwqLtMqWGDRtWtF02hHsWXzOMXM7Crl27irZ7Xluf83XPFLssGq1zmQ66TdwzxZoh4Z67ds8C63Pe+ky163PPYmvu1bhx4xrWOFWf4cZnjz5n7p6Xd8+ZDxo0qGi7vBQ9jt2+phkGmgPycX2aoeAyjfTYdplqemzrsRfht0mVnIHm5uaiPWfOnFRTpxtuuCH16ZjsfscqOQ9ujFbuPLJmzZqi/e1vfzvVfPOb32y4bHw2zZ49+xPbERHTp09PfX369CnaVTKVXKaQ7rduHHH7rc433Niiy3aZHnpudfOIKhk2LndSj2V3bKuvfOUrqe/OO+9s+HdVTJgwIfVppl2VbRSRc1Zcjc6t3PxL94mePXummo0bN6a+p59+uuHfbdq0KfXh0HnyySdTn2aaueuB4cOHF223r+3evTv16dzGXX9UObZ1bHHZRG780UzhPXv2pBodR91+rLlDVfKjHHetp9mAbv6lY5v7HlXmbW4b6Zi4bt26VLN48eKi7b6Hy2bW+a/L5tXxzo01K1euLNrumtFlKlbJXX388cdTH1oH/1MKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqB1B5xGxbNmyov3cc8+lGhd0ruFvGnTn/q5v376pRv/OBR2/8MILqW/u3LkNP3/gwIFFu6mpKdVoIJwLqHPBohqQ6gLiNBBQg9cjctCgC0zXMOSIiCFDhhRtF1CuIX5u2Vqzdu3aVON+Ew2RXrp0aarBkemuu+5KfW6M0GPShThqiKwLmtRjzYXxdujQwa/s/0dDRd3nu+VojTseXPixBh27lziMHTu2aF933XWp5rbbbiva7oURB2ratGlF2wWUrl+/vmi7wFAXNK9jqws617/Tl2NERMyaNatou3MU2rYFCxakvvPOO69ot7S0pBo9ll3QsI4b7lh3Y1vXrl2LtnsZgM4J3BilcwsXfFwlINeF/2qIuAvR1e32zjvvpJoDNXHixKJ9xhlnpJpRo0YVbTf+u2DpKmO7bm+3jXSMcnNE97tpnRtbCTo//D322GNF2811+vfvX7Q1HDzC//7dunUr2m7f0nm7m8drn5sjuHHLvXxF6TzOBZ3rud29VMr16edXeRmVG/90u7l5pBvbddluHNEXtuiLVyIiVq1aVbR17I/w4eP6+7uxTfetq6++OtXo340cOTLVuPB7vUaeNGlSqvnBD36Q+tA6+J9SAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdmRKGQ8//HDq0+f+IyKGDh1atN3zspo7pM/mRuRnat2z2FdddVXqu+aaaxoue8eOHUXbPa+szx5rDsHH0WeoXaaKPsPsvpvL2VLuWXB9FtrlZWzYsKFouywI3W6acRURsXr16tSneRWzZ89ONWg7/vAP/zD1PfDAA0V78ODBqUaPCXeM6nP+LgehShaMq9HP27dvX6rRY8099+9y5zR7yWVB7N69u2hrNkHEwcuQGj9+fOobNmxY0R40aFCq0TFKx/6IPNa7PpfFpVkQLlODDCk04rKQZs6cWbQnT56cajRDw53/q+SluDFJ/86NEZpz5DKN9Bzt8ov0fOzW0303zRlxcxQ9jufPn59qqnC5VzqWXHDBBalGxyiX++LGH52TuTmabhO3jdx4r1xe0PDhw4v2gAEDUs3bb7/dcNk4vCxevDj1nXTSSUXbZcyOGDEi9en5zu3HOkdw2bTa57LKXBakjj96PRaRM41cppRy45Eb/5SboymXF6V5nS5j2F0j6XbavHlzqtHxxv22eo3s8utcpqZup06dOqUaHVvcd9O5VZVs1og8t3NZWKgP/1MKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqB1B5xX99V//der7m7/5m6Ltwrg1xPKUU05JNRdddFHRdmFwLthTAyldQKAG+2moZ0TEwIEDi7YL2nUh6hpa7ELk9Lu4baTLcWGAbp00EM8Fzeuy3ffQEOF58+alGuBAXH311UX76aefTjUaENrc3JxqNMTYhcq6gE49bg70RQcaYuyOUfd3Oia4lwj893//d9E+0BBhR9epSoimGyN1/HXB6y7EWANBXRi0jkkujBU4EHPmzCnaLlS6R48eRdvtoxpQrOHoERHbtm1Lfbpvu8BsnSO4oFkdx9zLGNzxt3fv3qK9ZcuWVKNzBDeP0HFEXyDzcfS76FwrIm9/9zIEDT92v5Gb/+k47eZoum3d5+s5woUY674WkV/08eyzz6YafPY89dRTqU9DtK+//vpUM2HChNSnx5abo2iN2//0mHBzJHf9octycwR33DTi/qbKCxrcOur3d2Hoeqy7oO8hQ4Y07HNh6GvXrm1Yo3Mi9zIGDayPyONPletIN0bp711lHIvI29+dI3QfcecfHBz8TykAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdgSd/wZcIJ1as2bNJ7YjImbOnFm0v/GNb6Safv36pT4NH9XAzoiIXbt2Fe2FCxemGg1WdkHrgwYNSn0a4ulCTLVPg38j8ncbPHhwqunbt2/qW758edF+9dVXU41+30cffTTVLFq0KPUBreHf//3fU9+wYcOKtgv6HD58eNF2Ydgahh6Rwz9dQKgGS2rwY0S1sc7VaLCkC9psTfp9XfilBiS7baRjogtVdiGaGrT88ssvpxoNdnfjKHAwuDDyxx57rGj3798/1UyfPr1oT5w4MdW4gGA937ugW335gXuJghsTlTtuNLTcfTedIz344IOpZu7cuUV79erVDdcnIs9t3DxGX1DhXgah45Ybo12Isv6dG/90bHPbUUOL3cs41q1bl/p038KR64033ija+pKnCH8dMXLkyKI9atSoVKPHsTv/Vgksdy8x0ONtz549qca9RKCR/fv3V6rT4829MEZrqrwwwi2nCreN9OUveu0VkbebG7PdtaXOG93LqDRY3C1bl1PlfBQRsXnz5qLtQtw1RJ2g89bD/5QCAAAAAABA7bgpBQAAAAAAgNpxUwoAAAAAAAC1I7ziMKDPHn/7299ONbfffnvqW7JkSdF2eQX6DPcJJ5yQajQfwmUTuAyD9u3bF233nLcua9asWanm3nvvLdouG6sK97zwgS4LaA333Xdf6nvllVeK9ve///1Uo8eoZkVFRAwcODD16THqMmU0n8otW7NgXF6CZrO4PpeX4satg0XHrR07dqQazRTQjJeIiB49ehRtl9ewdOnS1KfZMy6L4KyzziraOh4CrUnHH0dzNlw22tSpU1PftGnTirY7Rys3/9BMF81qi/A5e5qFpMe6W5bm90VEHHXUUUXbjRErVqxIfTr+ukwnXbbLS9Hv77aRG5M0w6VKpqCuT0Qet1w22F/91V+lPrRdmpUb4TNtJ0+eXLQvvvjiVKPZbFX2dTfWaDZQRLXjT483d/xpzpM7jhydS1XJS6qSF+WO9Srcd9N5nJvr6Vjrsrnc3FLHn549e6YaHW/d76jzSFej43FEziJ+6KGHUo3LuULr4H9KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2nFTCgAAAAAAALUj6PwwcOqppxbt1157LdV06tQp9Q0YMKBou2C/Pn36FG0XtKlBcx999FGq0RDBiBwap2GEERHHHHNM0XYhoi787kAQao7PolWrVhVtF/SpYZi9evVKNS78UsMnNTAzIgdiuqBJDQhfs2ZNqqkS4uuCxt3fHSwa/u5e9DBkyJCi3bt371SjQZuupn///qnv1ltvbbiOV1xxRdHWF1gAh9rs2bOLtgsad2PSo48+WrT1xSsRedzSwOCIiClTpjT8LBe+rXMbt2z9fBdQrJ+nc6+IPNZG5BDnyy+/vOGy3UtlqoQm61wrIs/bdHtEVAtoXrduXdH+yle+0nB9ALVy5crUp3MJN7fQ0Go3Zxg6dGjRvvDCC1PNmWeemfr0+HfHkc6R3LWGBoS76yg3/ri+Rtxc70CDzdXatWtT3+LFi4u2vvgiIm8T973cS230JTLut9VrTfdd9Tpy7ty5qca9oGP58uVF+/HHH081LnwerYP/KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGp31H4NK/m4wgrPtKP1nHbaaanvmmuuKdrup3z44YeL9lVXXZVq9Png5557LtXMmzevymomut7z588/oOUA+D8u9+nOO+9s+HcuC0ZzprZs2ZJqNNPOZaq4nAM9b3z9619vuI4H6sQTT0x9muHicp8mTJhQtF3uza5du4q2y1RwY+uHH35o1xVoCzSfo3v37g3/ZvPmzalv4cKFRdvlR+kxGhGxdevWor1hw4ZUoxmeXbt2TTW63i4b6gtf+ELq03Ha5dVUydSsMv92mVaal+Ly6nQsczVvvvlmw88HWosef5ofFRHR1NRUtGfMmJFqzjnnnNSnx6TLXXNzG6XzKJdD5HKWNC/JfZYuy9VofrDLAXbzFs00dTU7d+4s2m5eo5laLgfZZUrpb1slY2vPnj2pT88bLj9KMw4jIlasWNHw83BwVLndxP+UAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGpH0PlnmAbJucBMDd8DcGTq3bt36tNgz0suuSTVdOvWreGye/ToUbRvvPHGVLN9+/aGyzlYRowYkfo0sDwih5/36tUr1WiwpgvR1Jc/vPDCC6mGUHPgk02bNi313XTTTUXbHds619Fw4Aj/goa1a9cW7ZtvvjnVaIjw4MGDU42GCJ977rmpxo2tbrxRGrTcpUuXVKNj1AcffJBqmpubU99Pf/rTov3II4+kms6dOxdtF3QOHO70uHEvHunXr1/qGzNmTNGePHlyqvnoo4+Ktruu0s93L6NxIer6ggI3j9AxyoWoa9C4W862bdtSn4aYOzpGudsGuk10m0X4gHbdbu43UgsWLEh9+sKKJ554ItWsWbOm4bLRegg6BwAAAAAAwGGJm1IAAAAAAACoHTelAAAAAAAAUDsypQCgjdBsgIiI0aNHF+1333031Wg2y6HWsWPH1Kf5URE5i6alpSXVvP3220XbZfMBOHT0OHbZLC7DRKe3mt9SlebDjB07NtWMHDky9WlezZlnnplqdLxxWSizZ88u2i4HxuW16NgGtBXueJg4cWLqu+iii4q2y4bT3KPjjjsu1WjOk8t00tyniDxGuRodI1ymlc7t3Fxv3759qU/ne+7zdbx18y/Npjv22GNTjVu2bic3RmuG3rx581LN9773vdSHwwuZUgAAAAAAADgscVMKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjqBzAMARqUePHkXbBQQDwKfRoUOH1DdkyJDUpyHKxx9/fKrZuHFj0f7JT36SarZv3/5pVxFABZMmTSraffr0STX6goJLL7001eix7V6Y4vr0ElwD0yNyGPh7772XajRE3IWKu/FHXyLhAto1fLx9+/appmvXrg1rnHXr1hXt+++/P9U88MADRXvXrl2pxoW/4/BC0DkAAAAAAAAOS9yUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAtSPoHAAAADiIOnfuXLRdQLq+fMEFFAM4dPr27Vu0XWD4ueeeW7RnzJiRavTFKxERxxxzTNHu1q1bqtEQ748++ijVHHvssZ/YjojYs2dP6tMQ83bt2qUaDUN3oeKbN28u2suWLUs1L774YupbtGhR0d67d2+qaW5uTn347CHoHAAAAAAAAIclbkoBAAAAAACgdtyUAgAAAAAAQO3IlAIAAAAA4FPSvLj27dunml//+tepT6+tzz777FRzySWXFO2JEyemmscff7xoP/PMM6nG5dVphlSXLl1SzdFHH120t2/fnmp27dr1icuN8DlTaDvIlAIAAAAAAMBhiZtSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdgSdAwAAAABwGDnmmGOKdrdu3VKNXqO7UPOdO3ce3BUDPgWCzgEAAAAAAHBY4qYUAAAAAAAAasdNKQAAAAAAANSOTCkAAAAAAAAcVGRKAQAAAAAA4LDETSkAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFA7bkoBAAAAAACgdtyUAgAAAAAAQO24KQUAAAAAAIDacVMKAAAAAAAAteOmFAAAAAAAAGrHTSkAAAAAAADUjptSAAAAAAAAqB03pQAAAAAAAFC7dlUL9+/f35rrAQAAAAAAgDaE/ykFAAAAAACA2nFTCgAAAAAAALXjphQAAAAAAABqx00pAAAAAAAA1I6bUgAAAAAAAKgdN6UAAAAAAABQO25KAQAAAAAAoHbclAIAAAAAAEDtuCkFAAAAAACA2v0/tLOjeCKlGBgAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] @@ -378,11 +444,24 @@ } ], "source": [ - "check_data = first(train_loader)\n", - "print(f\"batch shape: {check_data['image'].shape}\")\n", - "image_visualisation = torch.cat(\n", - " [check_data[\"image\"][0, 0], check_data[\"image\"][1, 0], check_data[\"image\"][2, 0], check_data[\"image\"][3, 0]], dim=1\n", - ")\n", + "\n", + "\n", + "from typing import Dict\n", + "def get_batched_2d_axial_slices(data : Dict):\n", + " images_3D = data['image']\n", + " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1])\n", + " slice_label = data['slice_label']\n", + " #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float()\n", + " slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze()\n", + " return batched_2d_slices, slice_label\n", + "\n", + "check_data = first(train_loader_3D)\n", + "batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data)\n", + "idx = list(torch.randperm(batched_2d_slices.shape[0]))\n", + "slices = [0,30,45,63]\n", + "print(f\"Batch shape: {batched_2d_slices.shape}\")\n", + "print(f\"Slices class: {slice_label[idx][slices].view(-1)}\")\n", + "image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze()\n", "plt.figure(\"training images\", (12, 6))\n", "plt.imshow(image_visualisation, vmin=0, vmax=1, cmap=\"gray\")\n", "plt.axis(\"off\")\n", @@ -390,6 +469,30 @@ "plt.show()" ] }, + { + "cell_type": "code", + "execution_count": 48, + "id": "4249e4be-f7e7-48e9-9aa9-436da8c1d1e5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(torch.Size([2, 1, 64, 64]), torch.Size([2]))" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))#\n", + "a,b = next(subset_2D) #what is a, what is b? Are these the next images? \n", + "a.shape, b.shape" + ] + }, { "cell_type": "markdown", "id": "08428bc6", @@ -410,9 +513,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 49, "id": "bee5913e", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -430,8 +534,8 @@ " attention_levels=(False, False, True),\n", " num_res_blocks=1,\n", " num_head_channels=64,\n", - " with_conditioning=True,\n", - " cross_attention_dim=1,\n", + " with_conditioning=False,\n", + " # cross_attention_dim=1,\n", ")\n", "model.to(device)\n", "\n", @@ -447,17 +551,20 @@ { "cell_type": "markdown", "id": "2a4d3ab2", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ - "### Model training\n", - "Here, we are training our model for 75 epochs (training time: ~50 minutes)." + "### Model training of the Diffusion Model\n", + "Here, we are training our diffusion model for 75 epochs (training time: ~50 minutes)." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 50, "id": "6c0ed909", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -468,218 +575,1347 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 100%|██████████| 125/125 [00:27<00:00, 4.61it/s, loss=0.723]\n", - "Epoch 1: 100%|██████████| 125/125 [00:27<00:00, 4.60it/s, loss=0.276]\n", - "Epoch 2: 100%|█████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0965]\n", - "Epoch 3: 100%|█████████| 125/125 [00:27<00:00, 4.62it/s, loss=0.0376]\n", - "Epoch 4: 100%|█████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0224]\n", - "Epoch 5: 100%|█████████| 125/125 [00:27<00:00, 4.47it/s, loss=0.0187]\n", - "Epoch 6: 100%|█████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0179]\n", - "Epoch 7: 100%|█████████| 125/125 [00:28<00:00, 4.44it/s, loss=0.0169]\n", - "Epoch 8: 100%|█████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0161]\n", - "Epoch 9: 100%|██████████| 125/125 [00:27<00:00, 4.50it/s, loss=0.016]\n", - "Epoch 10: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0156]\n", - "Epoch 11: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0152]\n", - "Epoch 12: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0152]\n", - "Epoch 13: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0151]\n", - "Epoch 14: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0147]\n", - "Epoch 15: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0151]\n", - "Epoch 16: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0151]\n", - "Epoch 17: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0146]\n", - "Epoch 18: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0144]\n", - "Epoch 19: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0143]\n", - "Epoch 20: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0145]\n", - "Epoch 21: 100%|████████| 125/125 [00:27<00:00, 4.53it/s, loss=0.0143]\n", - "Epoch 22: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0138]\n", - "Epoch 23: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", - "Epoch 24: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0134]\n", - "Epoch 25: 100%|████████| 125/125 [00:27<00:00, 4.49it/s, loss=0.0135]\n", - "Epoch 26: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", - "Epoch 27: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0136]\n", - "Epoch 28: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0135]\n", - "Epoch 29: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0131]\n", - "Epoch 30: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0128]\n", - "Epoch 31: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0129]\n", - "Epoch 32: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0128]\n", - "Epoch 33: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0135]\n", - "Epoch 34: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0138]\n", - "Epoch 35: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0131]\n", - "Epoch 36: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0132]\n", - "Epoch 37: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0125]\n", - "Epoch 38: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0124]\n", - "Epoch 39: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0124]\n", - "Epoch 40: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0132]\n", - "Epoch 41: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0128]\n", - "Epoch 42: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0122]\n", - "Epoch 43: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0127]\n", - "Epoch 44: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0129]\n", - "Epoch 45: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0132]\n", - "Epoch 46: 100%|████████| 125/125 [00:27<00:00, 4.53it/s, loss=0.0125]\n", - "Epoch 47: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0123]\n", - "Epoch 48: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0123]\n", - "Epoch 49: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0125]\n", - "Epoch 50: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0127]\n", - "Epoch 51: 100%|████████| 125/125 [00:27<00:00, 4.54it/s, loss=0.0125]\n", - "Epoch 52: 100%|████████| 125/125 [00:27<00:00, 4.52it/s, loss=0.0124]\n", - "Epoch 53: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0127]\n", - "Epoch 54: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0123]\n", - "Epoch 55: 100%|████████| 125/125 [00:27<00:00, 4.55it/s, loss=0.0127]\n", - "Epoch 56: 100%|█████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.012]\n", - "Epoch 57: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0126]\n", - "Epoch 58: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0121]\n", - "Epoch 59: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0126]\n", - "Epoch 60: 100%|████████| 125/125 [00:27<00:00, 4.60it/s, loss=0.0119]\n", - "Epoch 61: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0122]\n", - "Epoch 62: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0119]\n", - "Epoch 63: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0125]\n", - "Epoch 64: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0121]\n", - "Epoch 65: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0121]\n", - "Epoch 66: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0117]\n", - "Epoch 67: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0121]\n", - "Epoch 68: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0123]\n", - "Epoch 69: 100%|████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.0121]\n", - "Epoch 70: 100%|█████████| 125/125 [00:27<00:00, 4.57it/s, loss=0.012]\n", - "Epoch 71: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0118]\n", - "Epoch 72: 100%|████████| 125/125 [00:27<00:00, 4.59it/s, loss=0.0117]\n", - "Epoch 73: 100%|████████| 125/125 [00:27<00:00, 4.58it/s, loss=0.0119]\n", - "Epoch 74: 100%|████████| 125/125 [00:27<00:00, 4.56it/s, loss=0.0125]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "train completed, total time: 2074.1517136096954.\n" + "Epoch 0: 1%| | 1/128 [00:00<00:16, 7.89it/s, loss=0.982]" ] - } - ], - "source": [ - "n_epochs = 75\n", - "val_interval = 5\n", - "epoch_loss_list = []\n", - "val_epoch_loss_list = []\n", - "\n", - "scaler = GradScaler()\n", - "total_start = time.time()\n", - "for epoch in range(n_epochs):\n", - " model.train()\n", - " epoch_loss = 0\n", - " progress_bar = tqdm(enumerate(train_loader), total=len(train_loader), ncols=70)\n", - " progress_bar.set_description(f\"Epoch {epoch}\")\n", - " for step, batch in progress_bar:\n", - " images = batch[\"image\"].to(device)\n", - " classes = batch[\"class\"].to(device)\n", - " optimizer.zero_grad(set_to_none=True)\n", - "\n", - " with autocast(enabled=True):\n", - " # Generate random noise\n", - " noise = torch.randn_like(images).to(device)\n", - "\n", - " # Get model prediction\n", - " noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes)\n", - "\n", - " loss = F.mse_loss(noise_pred.float(), noise.float())\n", - "\n", - " scaler.scale(loss).backward()\n", - " scaler.step(optimizer)\n", - " scaler.update()\n", - "\n", - " epoch_loss += loss.item()\n", - "\n", - " progress_bar.set_postfix(\n", - " {\n", - " \"loss\": epoch_loss / (step + 1),\n", - " }\n", - " )\n", - " epoch_loss_list.append(epoch_loss / (step + 1))\n", - "\n", - " if (epoch + 1) % val_interval == 0:\n", - " model.eval()\n", - " val_epoch_loss = 0\n", - " for step, batch in enumerate(val_loader):\n", - " images = batch[\"image\"].to(device)\n", - " classes = batch[\"class\"].to(device)\n", - " with torch.no_grad():\n", - " with autocast(enabled=True):\n", - " noise = torch.randn_like(images).to(device)\n", - " noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes)\n", - " val_loss = F.mse_loss(noise_pred.float(), noise.float())\n", - "\n", - " val_epoch_loss += val_loss.item()\n", - " progress_bar.set_postfix(\n", - " {\n", - " \"val_loss\": val_epoch_loss / (step + 1),\n", - " }\n", - " )\n", - " val_epoch_loss_list.append(val_epoch_loss / (step + 1))\n", - "\n", - "total_time = time.time() - total_start\n", - "print(f\"train completed, total time: {total_time}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a676b3fe", - "metadata": {}, - "source": [ - "### Learning curves" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "f8385176", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ + }, { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAArsAAAILCAYAAADoqVT3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABsNklEQVR4nO3deXwTdf7H8ffk6EFLkVuEolYERESrKKgccixWLu9rVwFBRfFAcFVcF11cZAEX3Z8WVlyVXV1PVNBVAQURUVEsKogKHhSkgCA3PWiTzPz+SJM2toVOm5Jp+3o+Hn00+WZm8s2nQd/55jvfMSzLsgQAAADUQa5YdwAAAACoKYRdAAAA1FmEXQAAANRZhF0AAADUWYRdAAAA1FmEXQAAANRZhF0AAADUWYRdAAAA1FmEXQAAANRZhF0AOMJef/11dejQQR06dFBOTk6suwMAdZon1h0AUHdde+21WrlypTp27Kg33ngj1t1xjEaNGqljx46SJK/XG+PeVE1RUZHeeecdLV++XF9//bV2796tgoICNWjQQG3atFGXLl10wQUXqHv37rHuKoB6zrAsy4p1JwDUTYTdumnhwoX629/+pl9++UVSMLA3b95cDRo00K5du7Rnz57wtmeccYamTp2qtm3bxqq7AOo5RnYBAJX2xBNP6NFHH5UkpaWl6bbbblPv3r2VlJQU3ub777/X008/rTfffFOrVq3SVVddpf/+979KS0uLVbcB1GPM2QUAVMrixYvDQXfAgAF64403NHDgwIigK0nt27fXtGnTNHPmTHk8Hu3atUvjx4+XaZqx6DaAeo6wC8DRsrOz9eCDD+qCCy7QaaedptNOO039+vXTvffeq7Vr1x5y302bNumhhx7SkCFDlJ6erk6dOumss87SH/7wBz3//PPy+/3l7hc6eeztt9/WDz/8oOuuu05du3bVlVdeGd7m2muvVYcOHXT//fdLkpYtW6ZRo0apR48e6ty5s3r06KE777xTGzZsKHP8ik5Qy8nJCbd/9dVXKigo0BNPPKEhQ4bo9NNP16mnnqrBgwcrMzNTRUVF5fbd5/PpmWee0cUXX6z09HR17dpVV111ld58801J0osvvhh+Djt8Pp8mT54sSWrXrp1mzJihuLi4Q+7Tt29fDRs2THFxcTr66KP166+/hh+bMGGCOnTooL59+1a4f2XrlJWVpSuvvFLp6ekaP368/vnPf4Yf37p16yH7OGLECHXo0EEZGRllHluxYoXuuOMO9enTR507d1bXrl01ZMgQPfzwwxGv5bd++eUXTZs2TRdeeKFOP/10de7cWT179tQVV1yhf/3rX9q9e/ch+wQgupjGAMCx5s+fr4kTJ6qoqEiGYahly5ayLEs5OTnKycnR/PnzNX78eN1www1l9l2yZInuuOOOcCg8+uijlZiYqG3btikrK0tZWVl655139PTTTyshIaHc58/NzdX111+vPXv2qE2bNmrYsGG52z399NOaPn26kpOTdfTRR8s0Tf3666966623tGzZMs2bN0+pqam2XntBQYGGDx+u1atXq1WrVjr66KOVk5OjH374QT/88IPWrl2rJ554ImKfwsJCjRo1Sp9//rkkqUGDBmrevLl+/PFH3XXXXfr88891wgkn2OpHyMKFC7Vt2zZJ0h133HHYoBty++2365ZbblFycnKVnvdwtm7dqokTJ8o0TbVu3VqJiYkaPHiw/vGPf0iSFi1apOuuu67cfXft2qWVK1dKki688MJwu2VZ+utf/6rnn39eUnBOcqtWrbR//359//33+v777/XSSy8pMzNTZ599dsQxV69erZEjRyo3N1eS1KJFCx199NHavXu3Vq9erdWrV2vOnDl67rnnqvy3AGAPI7sAHGnVqlW67777VFRUpIyMDC1btkzLli3Thx9+qBUrVujCCy+UaZr6+9//riVLlkTse+DAAd1zzz0qKipS+/bt9d5772nZsmVauHChVq1apXHjxkmSsrKy9NRTT1XYh7lz56pVq1b64IMP9M4775S77Zo1a/R///d/mjRpkj799FO9/fbb+uSTT/Twww+H+/Kvf/3L9ut/+OGHlZeXp3nz5oWff8WKFfrd734nSVq6dKm+/PLLiH2eeOKJcNC97rrr9Omnn2rBggVasWKF7r33Xr366qtasGCB7b5I0vLlyyVJDRs2VJ8+fSq9X2JiYo0FXUl65pln1L17d3300Ud666239NBDDyk1NVXp6emSgmG3IgsXLlQgEJBhGBo6dGi4/V//+peef/55GYahO+64QytXrtR7772nzz77TG+88YZOOeUU5ebm6pZbbtH27dsjjnn//fcrNzdXJ598st577z0tX75c77zzjj799FPNnTtXxx9/vHbt2qUHHnigZgoCoAzCLgBHmjFjhvx+v8444ww9+uijatmyZfixJk2aaPr06Tr33HMlKTyKF/Lxxx+HR9buueeeiJUAPB6PbrrpJp155pmSpHfeeafCPnzzzTeaPn26mjRpUuE23333nW6//XZdddVVEcuIDR06VGeddZYk6bPPPqvkqy7x008/6cknn1SnTp3CbUlJSZowYUL4funjBgIBvfDCC5KCKyBMmDBB8fHxkoIjkyNGjNAtt9yir776ynZfpGCol6RTTjlFHo9zvhTcsGGDpk6dWmbe8JAhQyRJX331VXjViN8KBf+uXbuqdevWkqT9+/eHR8xvvPFG3XzzzWrQoEF4n44dO+qZZ55RkyZNlJeXF/FBZu/evVq3bp0kafTo0WVWoOjSpYv+9re/6cwzz1SbNm0qnIoCILoIuwAcZ+vWrVq1apWk4JxKl6v8/1T94Q9/kBQ8+3/Tpk3h9oyMDK1du1Yffvhhheu8nnLKKZKkzZs3V9iPzp07H3bJLK/Xq6uvvrrcx0Jr6VYUtg6lf//+4QBWWunpFKWPu3btWu3du1eSdPHFF5d7zOuuuy4iuNkRmmd69NFHV2n/mtKrV69yp5dccMEF8ng8siyr3NHd7du3h99jpacwvP/++8rLy5PL5dKIESPKfc6UlJTwSPC7774bbi+9kueOHTvK3Tc9PV3//e9/NXXq1EpPBQFQPYRdAI5T+uv5E088scLtTj311PDtb775JuIxj8ejli1bVjgKGQp9hxpdq8ycyrS0tDKjiiGhr+8PHjx42OP8ViiMH+q4hYWF4bbs7Ozw7ZNPPrnc/ZKSknTGGWfY7osk5eXlSQpOS3CSiv5GTZo0UY8ePSSVP5Vh4cKFMk1T8fHxESenffHFF5Kkpk2bHnJEP/Te2759u3bt2iVJaty4sU466SRJ0tSpUzVjxoxyT1AEcGQ557soAChW+kz38s6SL89v5076/X699dZbWrRokTZs2KDdu3dr//79tvrRuHHjw26TkpJS4WMVjUhXRkUnw5U+bumRxFDgkqRmzZpVuO/xxx8fnn9rR3Jysvbu3asDBw7Y3rcmHSqQDhkyRB988IG++OIL7dixQy1atAg/FprC0Ldv34ha79y5U1LwPVjZFSt++eUXNW3aVFJwrvXIkSO1Y8cOPfnkk3ryySd1zDHHqFu3burRo4f69OlT4YcjADWDsAvAcQoKCsK327dvX6nQWPrr+QMHDuj666+PmJ/avHlztWvXLjzSu3PnznCwqcwxK2IYxmG3qQq7xy09enyor8erOo2hRYsW2rt3b8R0ESc41Ehzv3791KBBA+Xn5+vdd9/VNddcIyk4TSb03rjooosi9gm99+Lj43X88cfb7s+JJ56ohQsX6qWXXtKrr76qDRs2aOvWrZo3b57mzZunpKQkjRw5UmPGjKnWhyEAlUfYBeA4pQPZrFmzbC/bNXXq1HCYGTZsmIYPH642bdpEbPP4448rMzOz2n11itInx1W0frBUtSkVUvBr+++//17ffvutcnNza3SFhZDqXs0+MTFR/fv315tvvqmFCxeGw+6CBQtkWVbEVIeQ0HuvSZMmVb7EdVJSkkaNGqVRo0Zp8+bN+vjjj/XJJ59o+fLlysvL0+OPP66tW7dqypQp1Xp9ACqHj5UAHKf0SVB2T+4KBAJ66623JEk9e/bUfffdVyboStK+ffuq10mHadSoUfj2oS5aUHpurx29e/eWFLy4xGuvvVbp/UzT1OTJk8usAhEauT5UoI3GlInQqgyrVq0Kj+SHVuAYNGhQmTndoffer7/+GpUrvqWmpuqqq67SY489pg8//FDnn3++JOm1115jPi9whBB2AThOly5dwrcPtVSWZVllRjF3794dHr0MLS/2W6ZpVmneqpOVXjXihx9+KHeb/Px8ZWVlVen4ffv2DX9omDVr1iGvIFbas88+q+eee05XXnmlPv7443B7aKpFaIm48nz99ddV6mtp55xzjpo2bSrTNPXBBx8oJycnfOW9305hkEpODPT7/Ye8Qp/P56vwsYpG1hs2bKi//OUv4fvff/99JV4BgOoi7AJwnFatWoVXDXjhhRfCKwH81htvvKGzzz5bd911lwKBgCRFXA2tohHOp59+OmLuaelVDWqrU089NRwg33777XK3+c9//lNhLQ/H7XZr0qRJMgxDe/fu1ejRow972dv58+dr+vTpkqRzzz1X55xzTvix0Ajq/v37tWXLljL7btmyRYsXL65SX0vzeDwaOHCgJOnDDz8MH/OEE05Q586dy2zfv3//8FSGJ598ssLjPvDAA+rTp0/EOrv//ve/1atXL914440V7lc6CHOiGnBkEHYBONKdd94pl8ulrVu36oYbbogIp0VFRXrllVf0wAMPaP/+/UpOTpbb7ZYUHD1r3769pOBXxaWXJNu5c6ceeughZWZm6pZbbgm3V3W000kaNGigQYMGSQpeKvnpp58OfwDw+Xz697//rccff7zC0e7K6NGjhyZMmCDDMPTNN99o8ODBev7557Vnz56I7b777jvdcccdmjBhggKBgDp16qRHHnkk4qS70AU3JGnatGkRJyWuW7dON954Y/jvWF2hqQyffPJJ+Gp7pdfWLS05OVk333yzJOm9997TX/7yl4gpL7t379bf/vY3vfbaa9q6dWvEFJn27dtr+/bt+vjjjzVp0qQya+1u2bIlfFGQJk2aVOtvAaDyOEENQI3Lzs6uMFyUdtVVV4Uv0HDGGWdoypQpmjhxolatWqUBAwaodevW8nq9+uWXX8JTFc455xzdfffdEce58847dfPNN+vAgQO69NJLdcwxx8gwDG3dulVut1vTp09Xenq6Zs+eLZ/Pp5tuukmpqanKzMxUWlpa9AtwhNx5551auXKltmzZounTp2vWrFk6+uij9csvvyg3N1e33XabLMsKX1K4KkaMGKHU1FQ99NBD2rJlix588EFNnjxZzZs3V0pKinbu3BkOv263W5dddpnuueeeMqOYZ5xxhnr37q1ly5Zp0aJF+vDDD9W6dWsVFBRoy5YtOuWUU/THP/5Rw4YNq1ZNpOCod9u2bfXzzz9r5cqVZS4P/FvXX3+9tmzZopdeekkvvviiXn31VbVu3VpFRUXasWNHeHR29OjRuuCCC8L7nXPOObrpppv0xBNP6IUXXtBLL70Ursv+/fvDy+M1aNBAM2bMiPgWAkDNIewCqHGFhYXhy6geym+XArv44ot1xhln6D//+Y9WrFihbdu2yefz6aijjlL37t114YUX6oILLiizTNd5552nZ555Rk8++aS+/vpr/fLLL2rSpImGDBmikSNHhq9s9uCDDyozM1M7duyQZVmOu2CCXc2bN9drr72mWbNmaenSpdq+fbv27dun0047Tdddd5169Oihxx57TFL1lkzr16+fevbsqXfeeUcffvihvvnmG+3atUu7du1ScnKy0tPT1b17d1100UU67rjjKjxOZmamZs+erYULF2rz5s3hkdKxY8fquuuu048//ljlPv7WkCFDNHPmTEnBUeVWrVpVuK3L5dKkSZM0cOBAvfzyy/ryyy+1detWGYYRnmJz9dVX67TTTiuz77hx43Teeedp/vz5WrFihbZv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9BctWPCWJOn222+SJM2d+6a2bduq22+/Sdddd4NGjRodPubq1V/pueee0TffrFVBQb4aN26iM8/sppEjb9TRR7eK6HtyckNNnTpDmZmP6quvvlBRkU/HH5+m0aNv0emnd61EtaOPsOsAXo9bEieoAQDgVAMHDtG//jVLb775erlh9403XldCQoIGDhyqSZP+rI8+WqZhw0bqrLO66+DBg3r55Rc0bdpkFRUV2ZrmsGPHdt1++81KSEjQ2LF/1LHHttXmzdmaMOGPKiryKTGxZNtfftmmMWNuUHJysm67bZxatTpGmzf/rCeeyNTtt9+k//znRbVo0VIjR94oj8er//1vnv74x3vVseNJatasubZt21rm+T/99BPdc884nXhiB40ff7eaN2+hjRs36KmnntBnn63Qv//9gho3bhLePj8/T3/841hlZAzUpZdeqS1bNisz8x/605/+qJdfnq9GjY6yVfdoIOw6QGjOLheVAADUVl/sWKO3NryrwkCh7X1dLkOmadVAr0rEu+M1OG2ATm/RpUr7Jycna8CAC/TGG6/r669X65RTTg0/9t133+j779dpyJCLFB8fJ4/Hoyuu+L1uuOHm8DYnn3yKBg3qpwUL3rIVdufNe1X5+Xm6774H1Lt3X3k8LvXvf548nnhNnjxJjRo1Cm+7efMmdelymi699Ap163a2JOmUU05VQUGBHn10upYvX6ZLL71CrVodo2bNmkmS2rY9Vh07dqrw+TMz/6GEhAQ98sjjSkkJPtdpp52uo45qrD//+R69/PILuummW8Pbb926Rffd9xddcMFgSVJ6+hnatGmTXnzxOWVlrVS/fgMq/dqjhbDrAKFpDAHTUsA05XYxlRoAULss3rRM2/N3xLobh7T452VVDrtS8MSzN954XfPnvxYRdkMnpl1yyRWKj0/QX/86tcy+ycnJatq0mX75ZZut5/z669UyDEPdup0T0d6nTz899NCDEW1nntldZ57Zvcwxjj32OEnS9u32nnvHju3auHGDevXqEw66Ieee20tut1urVn0e0W4Yhvr27R/R1qZNqiRp3759tp4/Wgi7DlD6whJ+vyV3XAw7AwBAFfQ/trfjR3b7t+1drWOkpbXTaaedrqVLl2js2DuVktJIeXm5WrLkXZ1yyqnhObHff79Or776slat+lx79uxWUVFR+BilR2IrY9eunUpKSlJCQkJEe1JSsho0aBDRZlmW3nnnf1q48G1lZ/+k/fv3yzRLvjW2W+MdO4IfXlq0aFnmMa/Xq6OOaqydO3+NaG/YMEXx8Qlltg32LzbfYBN2HSDiKmoBU/Fyx7A3AADYd3qLLlUaNfV4XGrcOEl79uTViul8l1xyhb76aoLeeed/uuqqa7Rw4TsqKCgIT0348ccfdNNNoxQfH68RI0apY8dO4VD6xz+Old/vs/V81iHy6W/D65NPztJzz83Rqaema9y4e9SqVSt5vV6tW/edpk2bbO+FKjhKW9yLSmwTum/7aWocYdcBQnN2JVZkAADAyXr1Ok/NmjXX22+/qauuukZvv/2mmjZtqvPO6ydJWrjwbRUVFeqBByard+8+4f38fr8OHNivxNJnlFVC48aNtWXLZhUWFio+Pj7cvnfvHhUU5EeMFL/55us66qjG+sc/ZoVHU6VgAK+Ko48+WpK0ffsvZR4rLCzU3r171KnTyVU69pHE5FAHiBjZ9QcOsSUAAIglj8ejCy+8RNnZG/TBB0v0/ffrNHToJfJ4guOHobVrGzduHLHfyy8/r6KiojJr2x5Op06dZVmWVqz4KKL9/feXlNk2EAgoOTk5Iuj6fD7NnftiRN+kkhHZQ/WnadNmat++o7KyPtfevXsjHvvoow8VCATKzCV2IsKuA3g8jOwCAFBbDB16sTwej6ZPnxIOvyFnntlNkvTPfz6mzz//TJ9//pn+9rcHtWrV5+ra9Szl5ubq3XcX6NdfK3cy30UXXaq4uHj9/e9T9fbbb2rVqizNnj1bL7/8QpmTxrp27aacnM168slZWrPmKy1evEjXXz9M/fufL0nKyvpMq1d/Kb/fr+bNW0iS3nxznpYte7/c0VtJuu22cfL5ivTHP96upUsX66uvvtDcuS/p73//m9q0SdVll11lu35HGmHXAX47ZxcAADhX06bNdN55/bR//z716tUn4opj55zTQ3fc8Uft2bNH99wzTlOn/lWNGjXS3/72d1177XVq2rSZpk9/SFlZKyv1XKmpbfXoo5lq0yZVM2ZM0913j9eqVav097//Q0cddVTEtnfeOUG/+12G3nxznu688za9/PILGjnyBv3+98N02WVXatu2rbr//gk6ePCg+vUboK5dz9JHHy3T3/721wpXiUhPP0OZmU+qUaOjNG3aQxo79ma99NJ/9bvfna9//vMZJScnV7mOR4phWYea+lw//frrgage73CT719a8oPe/XyzJOnea07XiW2Oiurz1za17WSFWKNe9lAv+6iZPdTLHuplD/Uq0bx5w0ptx8iuA3iZxgAAAFAjCLsOQNgFAACoGYRdB4jzlKyrS9gFAACIHsKuA3CCGgAAQM0g7DoA0xgAAABqBmHXAbiCGgAAQM0g7DoAI7sAAAA1g7DrAB4uFwwAAFAjCLsOwAlqAAAANYOw6wDM2QUAAKgZhF0HYM4uAABAzSDsOgBhFwAAoGYQdh2AObsAAAA1g7DrAMzZBQAAqBmEXQdgGgMAAEDNIOw6AGEXAACgZhB2HYA5uwAAADWDsOsAbpdLLsOQxMguAABANBF2HSI0uusn7AIAAEQNYdchQmGXkV0AAIDocXTYnTt3rgYOHKjOnTurZ8+emjZtmnw+3yH3+fTTT3XllVeqS5cu6tGjhyZPnqyioqIj1OOqC4dd5uwCAABEjWPD7vz58zVx4kRdccUVWrBggR544AHNnz9fkydPrnCf1atX6/rrr9c555yjt99+W3/961/1v//9T3/961+PYM+rJrTWLiO7AAAA0eOJdQcqkpmZqUGDBmnEiBGSpNTUVO3cuVOTJk3SmDFj1LJlyzL7PPLII+rVq5fGjh0b3iczM1N+v/9Idr1KvF7CLgAAQLQ5cmR348aN2rx5s3r37h3R3qtXL5mmqeXLl5fZZ+/evVq5cqUGDx4c0X7mmWfq7LPPrtH+RgMjuwAAANHnyLCbnZ0tSWrbtm1Ee6tWreT1erVhw4Yy+6xfv16maaphw4YaP368zj33XPXp00f/+Mc/DjvP1wlCc3ZNy5KfebsAAABR4chpDLm5uZKkpKSkiHbDMJSUlBR+vLRdu3ZJkiZPnqzrrrtON9xwg1auXKmHH35Y+/fv1/3331/p53e5DLlcRjVeQSR38aht6Hd54rzu8G1LksfjyM8hR0Rl6oUS1Mse6mUfNbOHetlDveyhXvY5MuxWRWj0duDAgbrqqqskSSeddJK2bdum5557TrfeequaNGlSqWM1aZIkw4he2A1JSUms8LEGid7w7aTkBDVKjo/689c2h6oXyqJe9lAv+6iZPdTLHuplD/WqPEeG3ZSUFEkqM4JrWZby8vLCj5fWsGFDSVLnzp0j2rt27ao5c+bohx9+ULdu3Sr1/Lt350V9ZDclJVH79xcoUNEUBcsK3/x1Z65Mn/NPqqsplaoXwqiXPdTLPmpmD/Wyh3rZQ71KNG6cdPiN5NCwm5aWJknatGmT0tPTw+05OTny+Xxq165dmX2OO+44SdK+ffsi2q3iEJmcnFzp5zdNS6ZpHX5DmwIBs8IrpHlKheuDhX6upKZD1wtlUS97qJd91Mwe6mUP9bKHelWeIyd8pKamKi0tTUuXLo1oX7JkiTwej3r27Flmn7S0NKWmpuq9996LaM/KylJ8fHw4DDuVt9QcXVZkAAAAiA5Hhl1JGjt2rBYtWqQ5c+Zoy5YtWrx4sWbOnKlhw4apadOmWrNmjTIyMpSVlRXe54477tD777+vxx57TJs3b9bcuXP14osvavjw4WVOdnMar7vkBDWuogYAABAdjpzGIEkZGRmaPn26Zs+erRkzZqhZs2YaPny4xowZI0kqKChQdna28vPzw/sMHjxYlmVp9uzZevLJJ9W0aVPdeuutuv7662P1MiqNkV0AAIDoc2zYlaShQ4dq6NCh5T7WrVs3rV+/vkz7kCFDNGTIkJruWtR5CLsAAABR59hpDPUNI7sAAADRR9h1CG+pxaGZswsAABAdhF2HiBzZDcSwJwAAAHUHYdchmMYAAAAQfYRdhyDsAgAARB9h1yGYswsAABB9hF2HYGQXAAAg+gi7DkHYBQAAiD7CrkMQdgEAAKKPsOsQcR53+DZzdgEAAKKDsOsQEZcL9hF2AQAAooGw6xAR0xgY2QUAAIgKwq5DRCw9xpxdAACAqCDsOgQnqAEAAEQfYdchIsNuIIY9AQAAqDsIuw7BFdQAAACij7DrEC6XIbfLkMQ0BgAAgGgh7DpIaCoDYRcAACA6CLsOQtgFAACILsKug4TDLnN2AQAAooKw6yChk9T8jOwCAABEBWHXQZjGAAAAEF2EXQcpHXYty4pxbwAAAGo/wq6DhKYxWJICJmEXAACgugi7DsIlgwEAAKKLsOsgXo87fJuwCwAAUH2EXQfxMLILAAAQVYRdBwnN2ZVYaxcAACAaCLsOEudlZBcAACCaCLsOUnpkt8gfiGFPAAAA6gbCroOUXo2Bq6gBAABUH2HXQVh6DAAAILoIuw5C2AUAAIguwq6DsBoDAABAdBF2HYSRXQAAgOgi7DoIF5UAAACILsKugzCyCwAAEF2EXQfxut3h28zZBQAAqD7CroMwsgsAABBdhF0HIewCAABEF2HXQQi7AAAA0eWJdQcOZe7cuZozZ45+/vlnNW7cWIMHD9b48ePl9XrLbJuTk6N+/fqVe5w//OEPuv/++2u6u9XGOrsAAADR5diwO3/+fE2cOFETJkxQv379tH79ek2cOFH5+fmaNGlShfs9/vjjSk9Pj2hLTEys6e5GReTIbiCGPQEAAKgbHBt2MzMzNWjQII0YMUKSlJqaqp07d2rSpEkaM2aMWrZsWe5+jRo1UvPmzY9gT6OHaQwAAADR5cg5uxs3btTmzZvVu3fviPZevXrJNE0tX748Rj2rWYRdAACA6HJk2M3OzpYktW3bNqK9VatW8nq92rBhQyy6VeMiwi5zdgEAAKrNkdMYcnNzJUlJSUkR7YZhKCkpKfx4ed5++23NmDFDP//8s4466ihdcsklGjFihOLi4ir9/C6XIZfLqFrny+EuPvHM7T70Z4vE+JI/RyBgRVw+uD6pbL0QRL3soV72UTN7qJc91Mse6mWfI8NuVbjdbjVr1kwHDx7U3XffrQYNGuijjz7SY489po0bN2rKlCmVPlaTJkkyjOiF3ZCUlEOfKGeaVsltSY0bJ1W8cT1wuHohEvWyh3rZR83soV72UC97qFflOTLspqSkSFKZEVzLspSXlxd+vLRWrVrp448/jmjr1KmT8vLy9MQTT+jWW2/VMcccU6nn3707L+ojuykpidq/v0CBw0xP8Lpd8gVMFRz0ac+evKj1oTaxUy9QL7uol33UzB7qZQ/1sod6lajsoKAjw25aWpokadOmTRHLiOXk5Mjn86ldu3aVPtZJJ50kSdq+fXulw65pWhGjrNESCJjyH+bEM48nGHaLfIfftq6rTL1QgnrZQ73so2b2UC97qJc91KvyHDnhIzU1VWlpaVq6dGlE+5IlS+TxeNSzZ88y+yxevFgTJkyQ3++PaP/666/lcrnKnOzmVKGT1FiNAQAAoPocGXYlaezYsVq0aJHmzJmjLVu2aPHixZo5c6aGDRumpk2bas2aNcrIyFBWVpYkqWXLlnrrrbc0btw4rV27Vps2bdJ///tfPfvss7rsssvUtGnTGL+iygldRY3VGAAAAKrPkdMYJCkjI0PTp0/X7NmzNWPGDDVr1kzDhw/XmDFjJEkFBQXKzs5Wfn6+JOmUU07RnDlzNGvWLF1//fXKzc1V69atdeutt2rUqFGxfCm2MLILAAAQPY4Nu5I0dOhQDR06tNzHunXrpvXr10e0nXnmmZozZ86R6FqNIewCAABEj2OnMdRXobDrD5iyrOifJAcAAFCfEHYdxltqkWg/83YBAACqhbDrMBGXDGYqAwAAQLUQdh2GsAsAABA9hF2HIewCAABED2HXYUrP2WWtXQAAgOoh7DoMI7sAAADRQ9h1GA9hFwAAIGoIuw7DyC4AAED0EHYdhjm7AAAA0UPYdRhGdgEAAKKHsOswXo87fJuwCwAAUD2EXYdhZBcAACB6CLsOEzFn1x+IYU8AAABqP8Kuw8R5GdkFAACIFsKuw7AaAwAAQPQQdh2GObsAAADRQ9h1GMIuAABA9BB2HYbLBQMAAEQPYddhmLMLAAAQPYRdh2EaAwAAQPQQdh2GsAsAABA9hF2H4XLBAAAA0UPYdRjm7AIAAEQPYddhmMYAAAAQPYRdh/G4jfBtwi4AAED1EHYdxjCM8OguYRcAAKB6CLsOFJq3y5xdAACA6iHsOlBoZNfvD8S4JwAAALUbYdeBmMYAAAAQHYRdBwqHXaYxAAAAVAth14HCc3YZ2QUAAKgWwq4DhefsBiyZphXj3gAAANRehF0HiriwBFMZAAAAqoyw60Bejzt8m6kMAAAAVUfYdSAuGQwAABAdhF0HYhoDAABAdBB2HSi0GoPEyC4AAEB1EHYdqPTIrp+wCwAAUGWEXQdizi4AAEB0EHYdKDLsBmLYEwAAgNrN0WF37ty5GjhwoDp37qyePXtq2rRp8vl8ldp37969Ovfcc9W3b98a7mX0RczZ5QQ1AACAKnNs2J0/f74mTpyoK664QgsWLNADDzyg+fPna/LkyZXaf8qUKdq7d2/NdrKGMI0BAAAgOhwbdjMzMzVo0CCNGDFCqamp6t+/v8aOHatXXnlF27dvP+S+H374oRYtWqShQ4ceod5Gl4ewCwAAEBWODLsbN27U5s2b1bt374j2Xr16yTRNLV++vMJ9c3Nz9cADD+i2227TMcccU9NdrRGM7AIAAERHjYbdPXv2yO/3294vOztbktS2bduI9latWsnr9WrDhg0V7jtjxgw1btxY1113ne3ndQrm7AIAAESHp7oHWLZsmebOnavMzMxw2yeffKL77rtPv/zyi5KSknTLLbfYCp+5ubmSpKSkpIh2wzCUlJQUfvy3srKyNHfuXL3yyityu91VeDVBLpchl8uo8v6/5S4Or2535T5bJMSX/FkCphUxraE+sFuv+o562UO97KNm9lAve6iXPdTLvmqF3aysLN1yyy0yDEOmacrlcmnHjh265ZZbVFBQoE6dOiknJ0fTp0/Xcccdpz59+kSr32UUFhbqvvvu04gRI9SpU6dqHatJkyQZRvTCbkhKSmKltmvcqGQ7j9ejxo2TDrF13VXZeiGIetlDveyjZvZQL3uolz3Uq/KqFXafffZZJSYm6vnnn5fLFfyE8fLLL6ugoEC33367xowZo7179+qiiy7SSy+9VOmwm5KSIkllRnAty1JeXl748dIef/xxeTwe3XbbbdV5SZKk3bvzoj6ym5KSqP37CxSoxLSEwoMly6vtP3BQe/bkRa0vtYHdetV31Mse6mUfNbOHetlDveyhXiUqOxhYrbC7Zs0aDRgwQO3btw+3LV26VAkJCRo2bJgk6aijjlL//v21YMGCSh83LS1NkrRp0yalp6eH23NycuTz+dSuXbsy+7zzzjvatm1bxPamacqyLHXq1EljxozRrbfeWqnnN01LpmlVur+VFQiYlbr8b+mcXegL1NtLBle2XgiiXvZQL/uomT3Uyx7qZQ/1qrxqhd1du3bp2GOPDd/ft2+fvvvuO51zzjlKTk4Ot7do0UL79u2r9HFTU1OVlpampUuX6qKLLgq3L1myRB6PRz179iyzz9NPP13mghMvvPCClixZoqefflpNmza18cpiy+spmW/MagwAAABVV63ZzXFxcRFTDT7++GNZlqVzzz03Yrvc3NwyJ5sdztixY7Vo0SLNmTNHW7Zs0eLFizVz5kwNGzZMTZs21Zo1a5SRkaGsrCxJ0vHHH6/27dtH/DRt2lRerzd8u7bgcsEAAADRUa2we8IJJ2jp0qXy+/0yTVPPPvusDMMoMzd35cqVat26ta1jZ2RkaPr06Xr11Vd1/vnna/LkyRo+fLjuuusuSVJBQYGys7OVn59fnZfgSKyzCwAAEB3VmsYwePBgTZkyRQMGDJAkbdu2Tb169dLxxx8vScrPz9fjjz+u1atXV+nEsaFDh1Z4FbRu3bpp/fr1h9z/tttui8oJa0daHGEXAAAgKqoVdq+55hr9+OOPev311+X3+3XKKado6tSp4cd37dqlOXPm6KSTTqrVF3k40rhcMAAAQHRUK+y6XC49+OCD+tOf/qS8vLwy82JTU1N133336ZJLLlFiIuvBVRZXUAMAAIiOal9BTZISEhKUkJBQ7mPXXnttNJ6iXmHOLgAAQHRU+1pz3377raZMmRLRtm7dOl1zzTVKT0/XoEGDtHDhwuo+Tb3idhkKXcCNsAsAAFB11Qq769ev1zXXXKMXXnhBphkMZfv379fIkSOVlZWluLg4bdiwQePHj9eqVaui0uH6wDCM8Ogu0xgAAACqrlph95lnnpHf79esWbPClwueO3eudu/erd///vf67LPPtGjRIqWkpOjZZ5+NSofri9C8XUZ2AQAAqq5aYffzzz/XgAED1KtXr3Dbe++9J4/HE740b9u2bTVgwAB9+eWX1etpPRMe2SXsAgAAVFm1wu7OnTvVrl278P28vDytXbtWp556qpo0aRJub926tXbv3l2dp6p3CLsAAADVV62w63a7VVhYGL6/cuVK+f3+MpcLLigoYOkxm7wetyTm7AIAAFRHtcLuscceqxUrVoTvv/jiizIMQ+edd17Edl9//bVatmxZnaeqd0Jzdv2M7AIAAFRZtdbZHTBggB577DFdddVVcrlc+vLLL3XaaaepU6dOkqRAIKAXX3xRK1as0MiRI6PS4foiNI0hYFoKmKbcrmqvEgcAAFDvVCvsjho1SqtWrdLHH38sSWrVqpWmT58efnzjxo2aPHmyjjnmGMKuTaUvLOH3W3LHxbAzAAAAtVS1wm58fLyefvppbdy4Ufv371fHjh0VF1eSytLS0jRixAhdd911ESes4fAirqIWMBUvdwx7AwAAUDtF5XLBxx13XLnthmFowoQJ0XiKeic0Z1diRQYAAICqikrY/eWXX7Rw4UJ9++232rNnjwzDUNOmTdW5c2cNHDhQjRs3jsbT1CsRI7v+QAx7AgAAUHtVO+z++9//1owZM+T3+2VZVsRj8+fP14wZM/Tggw9q8ODB1X2qesXjYWQXAACguqoVdpctW6apU6cqMTFRF154obp06aImTZrINE3t3r1bq1at0qJFizRhwgS1bdtWXbp0iVa/67zSI7tFhF0AAIAqqVbYfe6559SoUSO98sorOvbYY8s8ftVVV+mGG27Q1VdfraeeekqPPfZYdZ6uXmHOLgAAQPVVa/HWtWvX6vzzzy836Ia0b99e559/vr744ovqPFW989vVGAAAAGBftcJubm6ujj766MNu16ZNG+3du7c6T1XvxDFnFwAAoNqqFXZTUlK0efPmw263detWpaSkVOep6h2vp2RdXS4ZDAAAUDXVCrunnnqq3n33Xa1fv77CbdatW6cFCxbotNNOq85T1TteRnYBAACqrVonqF133XX64IMPdPnll2vQoEFKT08PXylt165dysrK0qJFixQIBDRq1KiodLi+YM4uAABA9VUr7J511ll68MEH9dBDD2nevHmaP39+xOOWZSkxMVGTJ0/WGWecUZ2nqndYjQEAAKD6qn1Ricsvv1x9+vTRO++8o7Vr12rXrl3hK6idcsopGjRoEFdQqwKmMQAAAFRfVC4X3KxZMw0bNqzCx5csWaJ58+YpMzMzGk9XL3i4XDAAAEC1VesEtcratGmTlixZciSeqs5gzi4AAED1HZGwC/uYswsAAFB9hF2HYs4uAABA9RF2HYqwCwAAUH2EXYdizi4AAED1EXYdijm7AAAA1UfYdSimMQAAAFSf7XV2zz77bNtPcvDgQdv71HeEXQAAgOqzHXb37NlTpScyDKNK+9VXzNkFAACoPtthl4tDHBlul0suw5BpWfL5CLsAAABVYTvstm7duib6gXJ4PS4V+gKM7AIAAFQRJ6g5WGgqg88fiHFPAAAAaifCroOVhF1GdgEAAKqCsOtghF0AAIDqIew6WDjsMmcXAACgSgi7Dha6iprPb8qyrBj3BgAAoPZxdNidO3euBg4cqM6dO6tnz56aNm2afD5fhdvv2bNHkydPVt++fdW5c2edd955mjZtWq29qEVoZNeypIBJ2AUAALDL9tJjR8r8+fM1ceJETZgwQf369dP69es1ceJE5efna9KkSWW2N01T119/vfLz8/XQQw+pTZs2ysrK0v33369ff/1Vf//732PwKqrnt1dR87gd/dkEAADAcRwbdjMzMzVo0CCNGDFCkpSamqqdO3dq0qRJGjNmjFq2bBmx/XfffadNmzZp1qxZOuuss8L7ZGVlacGCBbIsq9Zdxc3rjryKWmIM+wIAAFAbOXKocOPGjdq8ebN69+4d0d6rVy+Zpqnly5eX2efkk09WVlZWOOiGuFwuud3uWhd0pciRXT8rMgAAANjmyJHd7OxsSVLbtm0j2lu1aiWv16sNGzYc9hh+v1/vv/++3nrrLd122222nt/lMuRyRS8cu4tHaN02pyHEed3h26Ykj8eRn02irqr1qq+olz3U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9hj00jleffVVXXjhhUpPT1efPn305z//Wbt27Qo//sMPP+j6669Xenq60tPTdcMNN+inn36KYY9jxzRNPfPMMxo8eLC6dOmibt26aezYsdqyZUt4G95jFRs5cqQ6dOignJyccBv1KtG3b1916NChzM/gwYPD21CvsnJycnTrrbfq9NNP15lnnqkxY8Zo69at4cepWVBOTk6576/Qz+uvvy6JelWahRozb948q0OHDtacOXOsn3/+2Xrvvfes7t27W/fff3+suxZze/bssUaPHm316NHDOv3006077rgj4vGffvrJ6tKli3XPPfdYP/30k5WVlWVdfvnl1uDBg62ioqIY9Tq2nnnmGatjx47W008/bW3cuNFatmyZ1atXL+vqq6+2TNO0du/ebXXv3t0aNWqUtW7dOuvrr7+2Ro8ebZ177rnWvn37Yt39I27KlCnWaaedZs2fP9/6+eefrY8++sjq16+f1bdvX6uwsJD32CHMnTvX6tSpk9W+fXtr8+bNlmXxb/K3+vTpY02dOtXasWNHxM/u3bsty6Je5dm3b5/Vp08f66abbrK+//57a/Xq1dbFF19sZWRkWIFAgJqV4vf7y7y3duzYYb3xxhtW586drU2bNlEvGwi7Nahfv37W+PHjI9pefPFFq2PHjtYvv/wSo145w3PPPWdde+211s6dO60+ffqUCbsTJkywevfubfl8vnDbTz/9ZLVv39763//+d6S7G3OmaVrnnnuuNWHChIj2l19+2Wrfvr313XffWY8//rh16qmnWnv37g0/vnfvXqtLly7WE088caS7HFM+n88677zzrMzMzIj2+fPnW+3bt7fWrFnDe6wC27dvt7p27WpNmjQpIuxSr0h9+vSxHnvssQofp15lZWZmWueee65VUFAQbsvOzrYWLFhgHTx4kJodRlFRkZWRkWE9/PDDlmXxHrODaQw1ZOPGjdq8ebN69+4d0d6rVy+Zpqnly5fHqGfO0Lt3b82ZM0dNmzYt9/GPPvpIPXr0kMfjCbelpaWpTZs2+vDDD49UNx3DMAy99dZb+tOf/hTR3rJlS0lSXl6ePvroI6Wnp6tRo0bhxxs1aqRTTz213tXM4/Fo6dKluuWWWyLaXa7gf/K8Xi/vsQo8+OCDSk9P1/nnnx/RTr3soV5lvfvuu+rfv78SEhLCbccdd5wyMjIUHx9PzQ7jP//5j/bv36+bbrpJEu8xOwi7NSQ7O1uS1LZt24j2Vq1ayev1asOGDbHolmOkpqbK7XaX+1heXp527NhRpnaSdOyxx9bb2h111FFq2LBhRNuSJUvUoEEDtW/fXtnZ2UpNTS2zX32uWWnffvutZs2apT59+ig1NZX3WDkWLFigjz/+WJMmTYpo59+kPdSrLJ/Ppx9//FGpqal65JFH1LdvX5199tm68847tXv3bmp2GPn5+Xrqqac0cuRIJScnUy+bCLs1JDc3V5KUlJQU0W4YhpKSksKPo6yKaidJycnJOnDgwJHukiO9//77euWVVzR69Gg1bNhQeXl51KwcDz/8sDp37qxLL71U5557rh5//HHeY+XYu3evJk+erDvvvFOtWrWKeIx6le+bb77R9ddfrx49eqh37966//77tWvXLupVjn379snv9+s///mPCgsLlZmZqUmTJunzzz/XiBEjqNlhvPLKKzJNU1deeaUk/k3a5Tn8JgCcZsGCBbrrrrs0ZMgQjR49OtbdcbRRo0bp4osv1rfffqtHHnlE2dnZmjJlSqy75ThTpkxRamqqfv/738e6K7VC48aNlZubq5EjR6pNmzb67rvvNGPGDK1atUrPPPNMrLvnOH6/X1LwW717771XktSpUyd5PB7dfPPN+uyzz2LZPcd79tlndemllyo5OTnWXamVCLs1JCUlRZLKjOBalqW8vLzw4ygr9FV9eaPfBw4ciJiTWh8999xzmjJlin7/+9/rvvvuk2EYkhQe3f2t+l6zJk2aqEmTJmrXrp2OP/54XXbZZfrkk08k8R4L+fDDD/Xuu+/qtddeC89rLo1/k2W99tprEffbt2+v5s2b67rrruP9VY5QSOvcuXNE+5lnnilJ+u677yRRs/J8/fXX2rJli/r16xdu49+kPYTdGpKWliZJ2rRpk9LT08PtOTk58vl8ateuXay65ngNGjRQq1attGnTpjKPbdy4Ud27d49Br5zhxRdf1EMPPaQ777xTN9xwQ8RjaWlpFdbshBNOOFJddITdu3fr008/1ZlnnqnmzZuH29u3by8p+O+Q91iJBQsW6ODBgxoyZEi4zbIsSdKAAQN05plnUq9K6NixoyRpx44d1Os3kpOT1bx5c+3bty+i3TRNSVKLFi2oWQUWL16sRo0aRWQJ/j9pD3N2a0hqaqrS0tK0dOnSiPYlS5bI4/GoZ8+eMepZ7dC7d28tX7484gIc3377rbZu3aq+ffvGsGexs2LFCj344IOaMGFCmaArBWv25Zdfas+ePeG2nTt36quvvqp3NSssLNS4ceM0f/78iPZ169ZJCq5iwXusxB133KE333xT8+fPD/9MnjxZkvTkk09q8uTJ1KuUn376SXfffXeZC7Z8/fXXkoIrDFCvsnr16qUPP/xQhYWF4basrCxJUocOHahZBT799FN16dKlzEnd1MuGWK99VpctWLDA6tChg/XMM89YOTk51nvvvWd17drVmjp1aqy7FnN79uwJL5Ldq1cv6+abbw7fLygosH7++WcrPT3duuuuu6wNGzZYq1evtoYOHWpdfvnlViAQiHX3jzjTNK0LLrjAuvrqq8tdaDw3N9fav3+/1bNnT2vkyJHWunXrrHXr1lnDhw+3+vTpY+Xl5cX6JRxxEyZMsNLT061XX33V2rRpk/XJJ59YgwcPDl9kg/fYoX366acR6+xSrxK5ubnWeeedZw0ePNj66KOPwhcNOu+886xBgwZZRUVF1Ksc2dnZVnp6unXTTTdZP/30k/XRRx9Zffr0sa688krLsniPVSS07vVvUa/KMyyr+Lsq1Ig333xTs2fP1qZNm9SsWTNddtllGjNmTLnz4uqTa6+9VitXriz3sb/97W+65JJL9PXXX2vatGlas2aNEhIS1KdPH02YMEGNGzc+wr2NvS1bthzyk/qtt96q2267TZs2bdKUKVO0cuVKGYahs88+W/fee6/atGlzBHvrDEVFRZo5c6beeustbd++Xc2aNdMZZ5yhcePGhevBe6xin332mYYNG6YlS5ZQr3Lk5OTo//7v//TZZ59p9+7dOuqoo9SnTx+NGzdOTZo0kUS9yrN27dpwTeLi4vS73/1Of/rTn8JzeqlZJNM0ddJJJ+mmm27SuHHjyjxOvSqHsAsAAIA6q34PLwIAAKBOI+wCAACgziLsAgAAoM4i7AIAAKDOIuwCAACgziLsAgAAoM4i7AIAAKDOIuwCAA7p2muvVYcOHcKXwwWA2sQT6w4AQF2Vk5Ojfv36VXr70JXwAADRQ9gFgBqWmJhYqRCbnp5+BHoDAPULYRcAalh8fLxGjRoV624AQL1E2AUAh5kwYYLmzZunadOmqXnz5srMzNT69etlWZY6dOigm266Seedd16Z/RYvXqznn39e3377rfLy8tSoUSOlp6dr1KhR5Y4a//LLL5o1a5Y+/PBD7dy5U40aNVKfPn1066236uijjy63bytWrNBjjz2mdevWSZJOPvlkjR8/XqeffnrEdl9++aWeeuoprV69Wnv27FFycrJSU1M1ZMgQXXPNNXK73dUvFABUAmEXABzqs88+04IFC/S73/1OPXr0UE5Ojt58803ddNNNmjVrlvr27Rve9rHHHtPMmTPVuHFjDRgwQC1bttTPP/+sRYsW6f3339eMGTN0wQUXhLffsGGDrrrqKhUUFGjo0KFq06aNfvzxR7322mt67733NHfuXLVt2zaiP5988omeeeYZDR06VL1799aKFSv06aefatSoUXrnnXfUqlUrSVJWVpaGDx+uhIQEXXDBBWrdurUOHDigZcuWacqUKVq9erUeeeSRI1NEALAAADVi8+bNVvv27a2zzjrL1n733HOP1b59e6tDhw7W8uXLIx579dVXrfbt21sZGRnhtm+++cbq0KGDddZZZ1nbtm2L2P7zzz+3OnbsaJ155plWfn5+uP2SSy6x2rdvX+b4//3vf6327dtbo0ePDrddc801Vvv27a3u3btb2dnZ4XbTNK0RI0ZY7du3t+bMmRNuHz9+vNW+fXvrgw8+iDh2UVGRdfXVV1tnnHGGtXXrVls1AYCqYmQXAGqYZVnKyck55DZer1ctW7aMaEtPT1ePHj0i2i666CJNmzZNGzZs0ObNm5Wamqr58+fLsiz9/ve/LzP9oGvXrurWrZtWrFih5cuXa8CAAfruu++0du1adezYsczxL730Um3ZskUtWrQo08crrrhCxx13XPi+YRjq2bOnPvnkE23ZsiXcvm/fPkkqM1XB6/Xq2WeflcfD/3oAHDn8FwcAati+ffsOuwRZx44d9cYbb0S0/XYerBQMkMcff7y++uorbdiwQampqVq7dm2F20tSly5dtGLFCn3zzTcaMGBAeL3ck046qcy2CQkJuvvuu8s9TufOncu0paSkSJJyc3PDbX369NHy5cs1fvx4jRo1Sv3799cJJ5wgSQRdAEcc/9UBgBqWlJSk6dOnH3Kb5OTkMm1NmzYtd9ujjjpKkrR//35J0q5duw65fZMmTSRJe/bsidg+FFQrq7ztXa7gtYksywq3/eEPf1BeXp6eeOIJPfLII3rkkUfUvHlz9ejRQxdffLG6detm63kBoDoIuwBQw7xer/r37297v1CQ/C3TNCUFlzSTgtMJpMjAWd72oe1Cxy0qKrLdp8q68cYbdfXVV+uDDz7QRx99pI8//ljz5s3TvHnzdPnll2vy5Mk19twAUBqXCwYAhwqNxP7W3r17JZWM5IZ+h0Zsf2v37t3lbh9qrykNGzbUkCFDNG3aNC1fvlxPP/20WrZsqblz52rFihU1+twAEELYBQCHWr16dZk2v9+v7OxsSVKbNm0kSaeccookadWqVeUe54svvojYLvQ7KytLgUAgYlvTNHXHHXfo9ttvl9/vr1K/9+3bF3HCmhQcVe7Ro4euv/56SdI333xTpWMDgF2EXQBwqM8++0yff/55RNvrr7+uAwcOqFOnTuHVGy699FK5XC699NJL2rZtW8T2H3/8sVatWqWWLVuGV17o0KGDTj75ZO3atUuvv/56xPbvvPOOFixYoLy8vCqdTLZnzx6dc845uu6668KrMpQWCrmhNXkBoKYxZxcAalhhYaGefvrpw24XHx+va665Jnz/wgsv1I033qh+/frp+OOPD19Uwu1266677gpvd+KJJ+qOO+7QI488oksuuUQZGRlq2rSpNmzYoPfee08JCQmaNm2avF5veJ+HHnpI1157re6//3599tlnOuGEE/TTTz9pwYIFSk5OrnBFhsNp3Lixbr75Zj3++OMaNGiQ+vfvr6OPPloFBQX64osvtHLlSp188sn63e9+V6XjA4BdhF0AqGEFBQWHXY1BCs5xLR12O3furEsvvVSZmZlaunSpTNNUly5ddNttt+mcc86J2Hf06NFq166dnnvuOb311lsqKChQkyZNlJGREX6stJNOOknz5s1TZmamPvnkEy1cuFCNGjXSoEGDdOutt5a5epodt956qzp06KBXXnlFixcv1t69e+X1enXcccfp9ttv1/DhwxUXF1fl4wOAHYZV0em7AICYmDBhgubNm6eJEydGhF8AgH3M2QUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdxQlqAAAAqLMY2QUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ1F2AUAAECdRdgFAABAnUXYBQAAQJ31/+1MPX1gW6EJAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.style.use(\"seaborn-v0_8\")\n", - "plt.title(\"Learning Curves\", fontsize=20)\n", - "plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", - "plt.plot(\n", - " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", - " val_epoch_loss_list,\n", - " color=\"C1\",\n", - " linewidth=2.0,\n", - " label=\"Validation\",\n", - ")\n", - "plt.yticks(fontsize=12)\n", - "plt.xticks(fontsize=12)\n", - "plt.xlabel(\"Epochs\", fontsize=16)\n", - "plt.ylabel(\"Loss\", fontsize=16)\n", - "plt.legend(prop={\"size\": 14})\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "0cd48c2d", - "metadata": {}, - "source": [ - "### Sampling process with classifier-free guidance\n", - "In order to sample using classifier-free guidance, for each step of the process we need to have 2 elements, one generated conditioned in the desired class (here we want to condition on Hands `=1`) and one using the unconditional class (`=-1`).\n", - "Instead using directly the predicted class in every step, we use the unconditional plus the direction vector pointing to the condition that we want (`noise_pred_text - noise_pred_uncond`). The effect of the condition is defined by the `guidance_scale` defining the influence of our direction vector." - ] - }, - { - "cell_type": "code", - "execution_count": 15, + "name": "stdout", + "output_type": "stream", + "text": [ + "step 0 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 1 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 2%|▍ | 3/128 [00:00<00:13, 9.55it/s, loss=1]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 2 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 3 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 4 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 5%|▋ | 7/128 [00:00<00:11, 10.26it/s, loss=0.992]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 5 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 6 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 7 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 7%|▊ | 9/128 [00:00<00:11, 10.38it/s, loss=0.988]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 8 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 9 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 10 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 10%|█ | 13/128 [00:01<00:10, 10.52it/s, loss=0.981]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 11 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 12 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 13 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 12%|█▎ | 15/128 [00:01<00:10, 10.51it/s, loss=0.978]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 14 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 15 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 16 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 15%|█▋ | 19/128 [00:01<00:10, 10.65it/s, loss=0.971]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 17 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 18 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 19 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 16%|█▊ | 21/128 [00:02<00:10, 10.22it/s, loss=0.968]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 20 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 21 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 18%|█▉ | 23/128 [00:02<00:10, 9.82it/s, loss=0.964]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 22 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 23 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 20%|██▏ | 25/128 [00:02<00:10, 9.50it/s, loss=0.961]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 24 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 25 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 21%|██▎ | 27/128 [00:02<00:10, 9.34it/s, loss=0.956]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 26 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([1., 1.], device='cuda:0')\n", + "step 27 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 1.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 1.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 23%|██▍ | 29/128 [00:02<00:10, 9.18it/s, loss=0.953]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 28 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 29 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 24%|██▋ | 31/128 [00:03<00:10, 9.21it/s, loss=0.949]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 30 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 31 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 26%|██▊ | 33/128 [00:03<00:10, 9.20it/s, loss=0.944]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 32 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 33 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 27%|███▎ | 35/128 [00:03<00:10, 9.12it/s, loss=0.94]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 34 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 35 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 29%|███▏ | 37/128 [00:03<00:10, 9.07it/s, loss=0.934]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 36 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 37 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 30%|███▎ | 39/128 [00:04<00:09, 9.09it/s, loss=0.929]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 38 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 39 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 32%|███▌ | 41/128 [00:04<00:09, 9.15it/s, loss=0.925]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 40 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 41 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 34%|███▋ | 43/128 [00:04<00:09, 9.17it/s, loss=0.921]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 42 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 43 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 35%|███▊ | 45/128 [00:04<00:09, 9.15it/s, loss=0.916]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 44 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 45 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 37%|████ | 47/128 [00:04<00:09, 8.95it/s, loss=0.912]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 46 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 47 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 38%|████▏ | 49/128 [00:05<00:08, 9.00it/s, loss=0.906]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 48 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 49 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 40%|████▍ | 51/128 [00:05<00:08, 9.08it/s, loss=0.904]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 50 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 51 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 41%|████▌ | 53/128 [00:05<00:08, 9.06it/s, loss=0.899]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 52 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 53 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 43%|████▋ | 55/128 [00:05<00:07, 9.18it/s, loss=0.894]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 54 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 55 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 45%|████▉ | 57/128 [00:05<00:07, 9.18it/s, loss=0.889]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 56 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 57 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 46%|█████ | 59/128 [00:06<00:07, 9.22it/s, loss=0.884]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 58 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 59 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 48%|█████▎ | 62/128 [00:06<00:06, 10.20it/s, loss=0.877]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 60 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 61 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n", + "step 62 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", + "image torch.Size([2, 1, 64, 64])\n", + "classes tensor([0., 0.], device='cuda:0')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 49%|█████▍ | 63/128 [00:06<00:06, 9.58it/s, loss=0.874]\n", + "Epoch 1: 0%| | 0/128 [00:00)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7611, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7668, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7561, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7131, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.6271, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.6277, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.6392, device='cuda:0', grad_fn=)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 9%|██▏ | 12/128 [00:00<00:03, 35.77it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.6372, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7021, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7493, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7826, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7407, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7278, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7590, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7495, device='cuda:0', grad_fn=)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 17%|███▉ | 22/128 [00:00<00:03, 35.29it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7754, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7786, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7738, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7787, device='cuda:0', grad_fn=)\n", + "h torch.Size([6, 64, 16, 16]) 6\n", + "h torch.Size([6, 16384])\n", + "loss tensor(0.7888, device='cuda:0', grad_fn=)\n", + "h torch.Size([2, 64, 16, 16]) 2\n", + "h torch.Size([2, 16384])\n", + "loss tensor(0.7906, device='cuda:0', grad_fn=)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 1: 0%| | 0/128 [00:00 3\u001b[0m \u001b[43mplt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinspace\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_epochs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_epochs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mepoch_loss_list\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcolor\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mC0\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlinewidth\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m2.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlabel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mTrain\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4\u001b[0m plt\u001b[38;5;241m.\u001b[39mplot(\n\u001b[1;32m 5\u001b[0m np\u001b[38;5;241m.\u001b[39mlinspace(val_interval, n_epochs, \u001b[38;5;28mint\u001b[39m(n_epochs \u001b[38;5;241m/\u001b[39m val_interval)),\n\u001b[1;32m 6\u001b[0m val_epoch_loss_list,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 9\u001b[0m label\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValidation\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 10\u001b[0m )\n\u001b[1;32m 11\u001b[0m plt\u001b[38;5;241m.\u001b[39myticks(fontsize\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m12\u001b[39m)\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/pyplot.py:2767\u001b[0m, in \u001b[0;36mplot\u001b[0;34m(scalex, scaley, data, *args, **kwargs)\u001b[0m\n\u001b[1;32m 2765\u001b[0m \u001b[38;5;129m@_copy_docstring_and_deprecators\u001b[39m(Axes\u001b[38;5;241m.\u001b[39mplot)\n\u001b[1;32m 2766\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mplot\u001b[39m(\u001b[38;5;241m*\u001b[39margs, scalex\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, scaley\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, data\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m-> 2767\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mgca\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2768\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscalex\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mscalex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscaley\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mscaley\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2769\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m{\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mdata\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m}\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/axes/_axes.py:1635\u001b[0m, in \u001b[0;36mAxes.plot\u001b[0;34m(self, scalex, scaley, data, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1393\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1394\u001b[0m \u001b[38;5;124;03mPlot y versus x as lines and/or markers.\u001b[39;00m\n\u001b[1;32m 1395\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1632\u001b[0m \u001b[38;5;124;03m(``'green'``) or hex strings (``'#008000'``).\u001b[39;00m\n\u001b[1;32m 1633\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1634\u001b[0m kwargs \u001b[38;5;241m=\u001b[39m cbook\u001b[38;5;241m.\u001b[39mnormalize_kwargs(kwargs, mlines\u001b[38;5;241m.\u001b[39mLine2D)\n\u001b[0;32m-> 1635\u001b[0m lines \u001b[38;5;241m=\u001b[39m [\u001b[38;5;241m*\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_get_lines(\u001b[38;5;241m*\u001b[39margs, data\u001b[38;5;241m=\u001b[39mdata, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)]\n\u001b[1;32m 1636\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m line \u001b[38;5;129;01min\u001b[39;00m lines:\n\u001b[1;32m 1637\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39madd_line(line)\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/axes/_base.py:312\u001b[0m, in \u001b[0;36m_process_plot_var_args.__call__\u001b[0;34m(self, data, *args, **kwargs)\u001b[0m\n\u001b[1;32m 310\u001b[0m this \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m args[\u001b[38;5;241m0\u001b[39m],\n\u001b[1;32m 311\u001b[0m args \u001b[38;5;241m=\u001b[39m args[\u001b[38;5;241m1\u001b[39m:]\n\u001b[0;32m--> 312\u001b[0m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_plot_args\u001b[49m\u001b[43m(\u001b[49m\u001b[43mthis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/axes/_base.py:498\u001b[0m, in \u001b[0;36m_process_plot_var_args._plot_args\u001b[0;34m(self, tup, kwargs, return_kwargs)\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes\u001b[38;5;241m.\u001b[39myaxis\u001b[38;5;241m.\u001b[39mupdate_units(y)\n\u001b[1;32m 497\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m x\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m!=\u001b[39m y\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m]:\n\u001b[0;32m--> 498\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx and y must have same first dimension, but \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 499\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhave shapes \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mx\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00my\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 500\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m x\u001b[38;5;241m.\u001b[39mndim \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m2\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m y\u001b[38;5;241m.\u001b[39mndim \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m2\u001b[39m:\n\u001b[1;32m 501\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx and y can be no greater than 2D, but have \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 502\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mshapes \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mx\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00my\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mValueError\u001b[0m: x and y must have same first dimension, but have shapes (20,) and (5,)" + ] + }, + { + "data": { + "image/png": "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c/jwYcfl1aXee++9CvcyZWVl6f/+7/8klT03BqhpnOOCeiktLU2rV6++ar877rhDt956q0aMGKEtW7bozTffVEpKijp37qxJkyapf//+atGihc6cOaMff/xRycnJ2rhxo06ePKkxY8Y4ltOsWTMNGTJEH330kRITE3X33Xdr0qRJatOmjU6cOKF3331Xq1evVrt27XTq1KlrPpfDNJMnT9aqVau0d+9evfzyyzp48KAmTZqk4OBgxy3/P/roI/Xs2dNx35DSIOiuRo0a6e2339a9996rr776Sh988IFuueUWjRo1SgMGDFBwcLC8vLyUk5OjXbt26d1333VshK/UvXt39e7dW7t27dLrr7+u8+fPa+zYsbLb7Tpw4IBeeeUVff755+rTp89V7+tTFQ8//LBeffVVnTt3zvFFii1atNCgQYMqHLdq1SpFRkYqOztb8+fP15YtW/TII4/o9ttvl6+vr/Ly8vTdd9/pk08+0aeffqphw4ZpxIgRjvGLFy/WqFGjdM8992jAgAHq1KmT7Ha7fvnlF+3evVtLlixxXBX32GOPVfvnBipUp/ftBarR5bf8r+xj1apVjvEXL160/vSnP1keHh5XHefn52edPXvWaf2ZmZlWmzZtyh3Tpk0ba9++fU5fuHelq906vip9Dx065PLzlnLnSxYr0r9/f0uS1b9/f5fv//TTT9Ytt9xS7s8nOjra+vjjjx2vd+3aVeH6rubcuXPW1KlTLW9v76vOp81msx5++GHr6NGjZZaTkZFhtWzZstyxcXFxbn3JojtKSkqcvi5AFXxlwpUOHz5s/fa3v63U38H48eOdxpbOZUUPDw8P629/+5tbnweoDhwqAv4fDw8PPffcc0pPT9eTTz6p7t276+abb5aHh4duvPFG3XbbbRo1apTefPNNZWdnq3Hjxk7jQ0JCtGfPHs2cOVMdOnSQj4+P7Ha7unbtqrlz5yotLc1xLk1D1KZNG3377beaN2+ewsPD1bhxY910003q3bu3li1bpo8//tjp8NvlX/pXFb6+vlq8eLEOHDigv//97xo4cKDatGmjxo0by9fXV0FBQYqOjtZf//pXHTp0SP/85z9dfuVAx44dtWfPHj322GMKDQ2Vt7e3WrRoobvvvlsfffSRy0NI1cVms5W5pf+Vr8sTGhqqr776Sps2bdJDDz2ksLAwNWnSRF5eXmrR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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.style.use(\"seaborn-bright\")\n", + "plt.title(\"Learning Curves\", fontsize=20)\n", + "plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", + "plt.plot(\n", + " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", + " val_epoch_loss_list,\n", + " color=\"C1\",\n", + " linewidth=2.0,\n", + " label=\"Validation\",\n", + ")\n", + "plt.yticks(fontsize=12)\n", + "plt.xticks(fontsize=12)\n", + "plt.xlabel(\"Epochs\", fontsize=16)\n", + "plt.ylabel(\"Loss\", fontsize=16)\n", + "plt.legend(prop={\"size\": 14})\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "0cd48c2d", + "metadata": {}, + "source": [ + "### Sampling process with classifier-free guidance\n", + "In order to sample using classifier-free guidance, for each step of the process we need to have 2 elements, one generated conditioned in the desired class (here we want to condition on Hands `=1`) and one using the unconditional class (`=-1`).\n", + "Instead using directly the predicted class in every step, we use the unconditional plus the direction vector pointing to the condition that we want (`noise_pred_text - noise_pred_uncond`). The effect of the condition is defined by the `guidance_scale` defining the influence of our direction vector." + ] + }, + { + "cell_type": "code", + "execution_count": 27, "id": "f71e4924", "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false } @@ -689,12 +1925,12 @@ "name": "stderr", "output_type": "stream", "text": [ - "100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1000/1000 [00:14<00:00, 71.08it/s]\n" + "100%|███████████████████████████████████████| 1000/1000 [00:12<00:00, 77.06it/s]\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -716,7 +1952,7 @@ " with autocast(enabled=True):\n", " with torch.no_grad():\n", " noise_input = torch.cat([noise] * 2)\n", - " model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device), context=conditioning)\n", + " model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device))\n", " noise_pred_uncond, noise_pred_text = model_output.chunk(2)\n", " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", "\n", @@ -756,7 +1992,7 @@ "formats": "py:percent,ipynb" }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -770,7 +2006,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.10.5" } }, "nbformat": 4, diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py index ad765369..da6de829 100644 --- a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py @@ -6,9 +6,9 @@ # extension: .py # format_name: percent # format_version: '1.3' -# jupytext_version: 1.14.1 +# jupytext_version: 1.14.4 # kernelspec: -# display_name: Python 3 +# display_name: Python 3 (ipykernel) # language: python # name: python3 # --- @@ -20,15 +20,17 @@ # # # [1] - Wolleb et al. "Diffusion Models for Medical Anomaly Detection" https://arxiv.org/abs/2203.04306 - +# # # TODO: Add Open in Colab # # ## Setup environment # %% +# !python /home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/setup.py install # !python -c "import monai" || pip install -q "monai-weekly[pillow, tqdm, einops]" # !python -c "import matplotlib" || pip install -q matplotlib +# !python -c "import seaborn" || pip install -q seaborn # %matplotlib inline # %% [markdown] @@ -49,13 +51,14 @@ import shutil import tempfile import time - +import os import matplotlib.pyplot as plt +import seaborn import numpy as np import torch import torch.nn.functional as F from monai import transforms -from monai.apps import MedNISTDataset +from monai.apps import MedNISTDataset, DecathlonDataset from monai.config import print_config from monai.data import CacheDataset, DataLoader from monai.utils import first, set_determinism @@ -65,18 +68,25 @@ from generative.inferers import DiffusionInferer # TODO: Add right import reference after deployed -from generative.networks.nets import DiffusionModelUNet -from generative.schedulers import DDPMScheduler +from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet, DiffusionModelEncoder +from generative.networks.schedulers.ddpm import DDPMScheduler +from generative.networks.schedulers.ddim import DDIMScheduler print_config() +train=False + + # %% [markdown] # ## Setup data directory # %% jupyter={"outputs_hidden": false} directory = os.environ.get("MONAI_DATA_DIRECTORY") -root_dir = tempfile.mkdtemp() if directory is None else directory -print(root_dir) +#root_dir = tempfile.mkdtemp() if directory is None else directory +root_dir='/home/juliawolleb/PycharmProjects/MONAI/val_brats' +root_dir_val='/home/juliawolleb/PycharmProjects/MONAI/val_brats' + +print(root_dir, root_dir_val) # %% [markdown] # ## Set deterministic training for reproducibility @@ -85,17 +95,71 @@ set_determinism(42) # %% [markdown] -# ## Setup MedNIST Dataset and training and validation dataloaders -# In this tutorial, we will train our models on the MedNIST dataset available on MONAI -# (https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset). -# Here, we will use the "Hand" and "HeadCT", where our conditioning variable `class` will specify the modality. +# ## Setup BRATS Dataset for 2D slices and training and validation dataloaders +# As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150 # %% jupyter={"outputs_hidden": false} -train_data = MedNISTDataset(root_dir=root_dir, section="training", download=True, progress=False, seed=0) -train_datalist = [] -for item in train_data.data: - if item["class_name"] in ["Hand", "HeadCT"]: - train_datalist.append({"image": item["image"], "class": 1 if item["class_name"] == "Hand" else 2}) + + +batch_size = 2 +channel = 0 # 0 = Flair +assert channel in [0, 1, 2, 3], "Choose a valid channel" + +train_transforms = transforms.Compose( + [ + transforms.LoadImaged(keys=["image","label"]), + transforms.EnsureChannelFirstd(keys=["image","label"]), + transforms.Lambdad(keys=["image"], func=lambda x: x[channel, :, :, :]), + transforms.AddChanneld(keys=["image"]), + transforms.EnsureTyped(keys=["image","label"]), + transforms.Orientationd(keys=["image","label"], axcodes="RAS"), + transforms.Spacingd( + keys=["image","label"], + pixdim=(3.0, 3.0, 2.0), + mode=("bilinear", "nearest"), + ), + transforms.CenterSpatialCropd(keys=["image","label"], roi_size=(64, 64, 64)), + transforms.ScaleIntensityRangePercentilesd(keys="image", lower=0, upper=99.5, b_min=0, b_max=1), + transforms.CopyItemsd(keys=["label"], times=1, names=["slice_label"]), + transforms.Lambdad(keys=["slice_label"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()), + ] +) +print('download training set') +train_ds = DecathlonDataset( + root_dir=root_dir, + task="Task01_BrainTumour", + section="training", # validation + cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise + num_workers=4, + download=False, # Set download to True if the dataset hasnt been downloaded yet + seed=0, + transform=train_transforms, +) +nb_3D_images_to_mix =20 +train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4) + +print(f'Image shape {train_ds[0]["image"].shape}') + + + +print('download val set') + +# %% + +val_ds = DecathlonDataset( + root_dir=root_dir_val, + task="Task01_BrainTumour", + section="validation", # validation + cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise + num_workers=4, + download=False, # Set download to True if the dataset hasnt been downloaded yet + seed=0, + transform=train_transforms, +) +val_loader_3D = DataLoader(val_ds, batch_size=2, shuffle=True, num_workers=4) +print(f'Image shape {val_ds[0]["image"].shape}') + + # %% [markdown] # Here we use transforms to augment the training dataset, as usual: @@ -105,75 +169,54 @@ # 1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1]. # 1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform. # -# ### Classifier-free guidance during training -# -# In order to use the classifier-free guidance during training time, we need to not just have the `class` variable saying the modality of the image (`1` for Hands and `2` for HeadCTs) but we also need to train the model with an "unconditional" class. -# Here we specify the "unconditional" class with the value `-1` with a probability of training on unconditional being 15%. Specified in the following line using MONAI's RandLambdad: -# -# `transforms.RandLambdad(keys=["class"], prob=0.15, func=lambda x: -1 * torch.ones_like(x))` # -# Finally, our conditioning variable need to have the format (batch_size, 1, cross_attention_dim) when feeding into the model. For this reason, we use Lambdad to reshape our variables in the right format. -# %% jupyter={"outputs_hidden": false} -train_transforms = transforms.Compose( - [ - transforms.LoadImaged(keys=["image"]), - transforms.EnsureChannelFirstd(keys=["image"]), - transforms.ScaleIntensityRanged(keys=["image"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True), - transforms.RandAffined( - keys=["image"], - rotate_range=[(-np.pi / 36, np.pi / 36), (-np.pi / 36, np.pi / 36)], - translate_range=[(-1, 1), (-1, 1)], - scale_range=[(-0.05, 0.05), (-0.05, 0.05)], - spatial_size=[64, 64], - padding_mode="zeros", - prob=0.5, - ), - transforms.RandLambdad(keys=["class"], prob=0.15, func=lambda x: -1 * torch.ones_like(x)), - transforms.Lambdad( - keys=["class"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0) - ), - ] -) -train_ds = CacheDataset(data=train_datalist, transform=train_transforms) -train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4, persistent_workers=True) +# %% [markdown] +# ### Visualisation of the training images # %% jupyter={"outputs_hidden": false} -val_data = MedNISTDataset(root_dir=root_dir, section="validation", download=True, progress=False, seed=0) -val_datalist = [] -for item in val_data.data: - if item["class_name"] in ["Hand", "HeadCT"]: - val_datalist.append({"image": item["image"], "class": 1 if item["class_name"] == "Hand" else 2}) -val_transforms = transforms.Compose( - [ - transforms.LoadImaged(keys=["image"]), - transforms.EnsureChannelFirstd(keys=["image"]), - transforms.ScaleIntensityRanged(keys=["image"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True), - transforms.Lambdad( - keys=["class"], func=lambda x: torch.tensor(x, dtype=torch.float32).unsqueeze(0).unsqueeze(0) - ), - ] -) -val_ds = CacheDataset(data=val_datalist, transform=val_transforms) -val_loader = DataLoader(val_ds, batch_size=128, shuffle=False, num_workers=4, persistent_workers=True) +from typing import Dict +def get_batched_2d_axial_slices(data : Dict): + images_3D = data['image'] + batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1]) + slice_label = data['slice_label'] + #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float() + slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze() + return batched_2d_slices, slice_label +print('check data') -# %% [markdown] -# ### Visualisation of the training images +if train==True: + check_data = first(train_loader_3D) + batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data) + idx = list(torch.randperm(batched_2d_slices.shape[0])) + print('idx', len(idx)) + print(f"Batch shape: {batched_2d_slices.shape}") + print(f"Slices class: {slice_label[idx][slices].view(-1)}") + subset_2D = zip(batched_2d_slices.split(batch_size), slice_label.split(batch_size)) # -# %% jupyter={"outputs_hidden": false} -check_data = first(train_loader) -print(f"batch shape: {check_data['image'].shape}") -image_visualisation = torch.cat( - [check_data["image"][0, 0], check_data["image"][1, 0], check_data["image"][2, 0], check_data["image"][3, 0]], dim=1 -) +check_data_val = first(val_loader_3D) +batched_2d_slices_val, slice_label_val = get_batched_2d_axial_slices(check_data_val) + + + +idx_val=list(torch.randperm(batched_2d_slices_val.shape[0])) +slices = [0,30,45,63] + +image_visualisation = torch.cat(batched_2d_slices_val[idx_val][slices].squeeze().split(1), dim=2).squeeze() plt.figure("training images", (12, 6)) plt.imshow(image_visualisation, vmin=0, vmax=1, cmap="gray") plt.axis("off") plt.tight_layout() plt.show() +# %% + +subset_2D_val = zip(batched_2d_slices_val.split(1),slice_label_val.split(1))# + + + # %% [markdown] # ### Define network, scheduler, optimizer, and inferer # At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using @@ -198,104 +241,245 @@ attention_levels=(False, False, True), num_res_blocks=1, num_head_channels=64, - with_conditioning=True, - cross_attention_dim=1, + with_conditioning=False, + # cross_attention_dim=1, ) model.to(device) -scheduler = DDPMScheduler( +scheduler = DDIMScheduler( num_train_timesteps=1000, ) optimizer = torch.optim.Adam(params=model.parameters(), lr=2.5e-5) inferer = DiffusionInferer(scheduler) -# %% [markdown] -# ### Model training -# Here, we are training our model for 75 epochs (training time: ~50 minutes). +# %% [markdown] tags=[] +# ### Model training of the Diffusion Model +# Here, we are training our diffusion model for 75 epochs (training time: ~50 minutes). # %% jupyter={"outputs_hidden": false} -n_epochs = 75 -val_interval = 5 +n_epochs =100 +val_interval = 1 epoch_loss_list = [] val_epoch_loss_list = [] -scaler = GradScaler() -total_start = time.time() -for epoch in range(n_epochs): - model.train() - epoch_loss = 0 - progress_bar = tqdm(enumerate(train_loader), total=len(train_loader), ncols=70) - progress_bar.set_description(f"Epoch {epoch}") - for step, batch in progress_bar: - images = batch["image"].to(device) - classes = batch["class"].to(device) - optimizer.zero_grad(set_to_none=True) - - with autocast(enabled=True): - # Generate random noise - noise = torch.randn_like(images).to(device) - - # Get model prediction - noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes) - - loss = F.mse_loss(noise_pred.float(), noise.float()) - - scaler.scale(loss).backward() - scaler.step(optimizer) - scaler.update() - - epoch_loss += loss.item() - - progress_bar.set_postfix( - { - "loss": epoch_loss / (step + 1), - } - ) - epoch_loss_list.append(epoch_loss / (step + 1)) - - if (epoch + 1) % val_interval == 0: - model.eval() - val_epoch_loss = 0 - for step, batch in enumerate(val_loader): - images = batch["image"].to(device) - classes = batch["class"].to(device) - with torch.no_grad(): - with autocast(enabled=True): - noise = torch.randn_like(images).to(device) - noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, condition=classes) - val_loss = F.mse_loss(noise_pred.float(), noise.float()) - - val_epoch_loss += val_loss.item() +if train==False: + model.load_state_dict(torch.load("./model.pt", map_location={'cuda:0': 'cpu'})) +else: + scaler = GradScaler() + total_start = time.time() + for epoch in range(n_epochs): + model.train() + epoch_loss = 0 + subset_2D = zip(batched_2d_slices.split(batch_size), slice_label.split(batch_size)) + subset_2D_val = zip(batched_2d_slices_val.split(1), slice_label.split(1)) # + + progress_bar = tqdm(enumerate(subset_2D), total=len(idx), ncols=10) + progress_bar.set_description(f"Epoch {epoch}") + for step, (a,b) in progress_bar: + print('step', step, a.shape, b.shape, b) + images = a.to(device) + classes = b.to(device) + optimizer.zero_grad(set_to_none=True) + timesteps = torch.randint(0, 1000, (len(images),)).to(device) + + with autocast(enabled=True): + # Generate random noise + noise = torch.randn_like(images).to(device) + + # Get model prediction + noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps) #remove the class conditioning + + loss = F.mse_loss(noise_pred.float(), noise.float()) + + scaler.scale(loss).backward() + scaler.step(optimizer) + scaler.update() + + epoch_loss += loss.item() + progress_bar.set_postfix( { - "val_loss": val_epoch_loss / (step + 1), + "loss": epoch_loss / (step + 1), } ) - val_epoch_loss_list.append(val_epoch_loss / (step + 1)) + epoch_loss_list.append(epoch_loss / (step + 1)) + + if (epoch) % val_interval == 0: + model.eval() + val_epoch_loss = 0 + progress_bar_val = tqdm(enumerate(subset_2D_val), total=len(idx_val), ncols=70) + progress_bar.set_description(f"Epoch {epoch}") + for step, (a, b) in progress_bar_val: + images = a.to(device) + classes = b.to(device) + timesteps = torch.randint(0, 1000, (len(images),)).to(device)#torch.from_numpy(np.arange(0, 1000)[::-1].copy()) + + with torch.no_grad(): + with autocast(enabled=True): + noise = torch.randn_like(images).to(device) + noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps) + val_loss = F.mse_loss(noise_pred.float(), noise.float()) + + val_epoch_loss += val_loss.item() + progress_bar.set_postfix( + { + "val_loss": val_epoch_loss / (step + 1), + } + ) + val_epoch_loss_list.append(val_epoch_loss / (step + 1)) + + total_time = time.time() - total_start + print(f"train diffusion completed, total time: {total_time}.") + + plt.style.use("seaborn-bright") + plt.title("Learning Curves Diffusion Model", fontsize=20) + plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color="C0", linewidth=2.0, label="Train") + plt.plot( + np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)), + val_epoch_loss_list, + color="C1", + linewidth=2.0, + label="Validation", + ) + plt.yticks(fontsize=12) + plt.xticks(fontsize=12) + plt.xlabel("Epochs", fontsize=16) + plt.ylabel("Loss", fontsize=16) + plt.legend(prop={"size": 14}) + #plt.show() + #torch.save(model.state_dict(), "./model.pt") -total_time = time.time() - total_start -print(f"train completed, total time: {total_time}.") + +# %% +### Model training of the Classification Model +#Here, we are training our binary classification model for 5 epochs. + +# %% +## First, we define the classification model + + +# %% +classifier = DiffusionModelEncoder( + spatial_dims=2, + in_channels=1, + out_channels=1, + num_channels=(64, 64, 64), + # attention_levels=(False, False, True), + num_res_blocks=1, + num_head_channels=64, + with_conditioning=False, + # cross_attention_dim=1, +) +classifier.to(device) +batch_size=6 + + +# %% +n_epochs = 100 +val_interval = 1 +epoch_loss_list = [] +val_epoch_loss_list = [] +optimizer = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5) + +classifier.to(device) + + +if train==False: + classifier.load_state_dict(torch.load("./classifier.pt", map_location={'cuda:0': 'cpu'})) +else: + scaler = GradScaler() + total_start = time.time() + for epoch in range(n_epochs): + classifier.train() + epoch_loss = 0 + subset_2D = zip(batched_2d_slices.split(batch_size), slice_label.split(batch_size)) + subset_2D_val = zip(batched_2d_slices_val.split(1), slice_label.split(1)) # + progress_bar = tqdm(enumerate(subset_2D), total=len(idx), ncols=20) + progress_bar.set_description(f"Epoch {epoch}") + + + for step, (a,b) in progress_bar: + images = a.to(device) + classes = b.to(device) + optimizer.zero_grad(set_to_none=True) + timesteps = torch.randint(0, 1000, (len(images),)).to(device) + + with autocast(enabled=True): + # Generate random noise + noise = 0*torch.randn_like(images).to(device) + + # Get model prediction + # pred=classifier(images) + + pred = inferer(inputs=images, diffusion_model=classifier, noise=noise, timesteps=timesteps) #remove the class conditioning + print('pred', pred) + # noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps) #remove the class conditioning + loss = F.binary_cross_entropy_with_logits(pred[:,0].float(), classes.float()) + print('loss', loss) + #scaler.scale(loss).backward() + # scaler.step(optimizer) + loss.backward() + optimizer.step() + #scaler.update() + + epoch_loss += loss.item() + + progress_bar.set_postfix( + { + "loss": epoch_loss / (step + 1), + } + ) + epoch_loss_list.append(epoch_loss / (step + 1)) + + if (epoch + 1) % val_interval == 0: + classifier.eval() + val_epoch_loss = 0 + progress_bar = tqdm(enumerate(subset_2D_val), total=len(idx), ncols=70) + progress_bar.set_description(f"Epoch {epoch}") + for step, (a,b) in progress_bar: + images = a.to(device) + classes = b.to(device) + + timesteps = torch.randint(0, 1000, (len(images),)).to(device)#torch.from_numpy(np.arange(0, 1000)[::-1].copy()) + + with torch.no_grad(): + with autocast(enabled=True): + noise = 0*torch.randn_like(images).to(device) + pred = inferer(inputs=images, diffusion_model=classifier, noise=noise, timesteps=timesteps) + val_loss = F.binary_cross_entropy_with_logits(pred[:,0].float(), classes.float()) + + val_epoch_loss += val_loss.item() + progress_bar.set_postfix( + { + "val_loss": val_epoch_loss / (step + 1), + } + ) + val_epoch_loss_list.append(val_epoch_loss / (step + 1)) + + total_time = time.time() - total_start + print(f"train completed, total time: {total_time}.") + # torch.save(classifier.state_dict(), "./classifier.pt") # %% [markdown] # ### Learning curves # %% jupyter={"outputs_hidden": false} -plt.style.use("seaborn-v0_8") -plt.title("Learning Curves", fontsize=20) -plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color="C0", linewidth=2.0, label="Train") -plt.plot( - np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)), - val_epoch_loss_list, - color="C1", - linewidth=2.0, - label="Validation", -) -plt.yticks(fontsize=12) -plt.xticks(fontsize=12) -plt.xlabel("Epochs", fontsize=16) -plt.ylabel("Loss", fontsize=16) -plt.legend(prop={"size": 14}) -plt.show() + plt.style.use("seaborn-bright") + plt.title("Learning Curves", fontsize=20) + plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color="C0", linewidth=2.0, label="Train") + plt.plot( + np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)), + val_epoch_loss_list, + color="C1", + linewidth=2.0, + label="Validation", + ) + plt.yticks(fontsize=12) + plt.xticks(fontsize=12) + plt.xlabel("Epochs", fontsize=16) + plt.ylabel("Loss", fontsize=16) + plt.legend(prop={"size": 14}) + #plt.show() # %% [markdown] # ### Sampling process with classifier-free guidance @@ -304,22 +488,83 @@ # %% jupyter={"outputs_hidden": false} model.eval() -guidance_scale = 7.0 +guidance_scale = 0 conditioning = torch.cat([-1 * torch.ones(1, 1, 1).float(), torch.ones(1, 1, 1).float()], dim=0).to(device) -noise = torch.randn((1, 1, 64, 64)) +# %% [markdown] +# ### Pick an input slice to be transformed + +inputimg = batched_2d_slices_val[50][0,...] +plt.figure("input") +plt.imshow(inputimg, vmin=0, vmax=1, cmap="gray") +plt.axis("off") +plt.tight_layout() +plt.show() + + +noise = inputimg[None,None,...]#torch.randn((1, 1, 64, 64)) noise = noise.to(device) scheduler.set_timesteps(num_inference_steps=1000) -progress_bar = tqdm(scheduler.timesteps) -for t in progress_bar: +L=20 +progress_bar = tqdm(range(L)) #go back and forth L timesteps + + + +for t in progress_bar: #go through the noising process + print('t noising', t) + with autocast(enabled=True): with torch.no_grad(): - noise_input = torch.cat([noise] * 2) - model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device), context=conditioning) - noise_pred_uncond, noise_pred_text = model_output.chunk(2) - noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) - noise, _ = scheduler.step(noise_pred, t, noise) + noise_input = noise + print('inputshape', noise_input.shape) + model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device)) + # noise_pred_uncond, noise_pred_text = model_output.chunk(2) #this is supposed to be epsilon + noise_pred = model_output #this is supposed to be epsilon + + noise, _ = scheduler.reversed_step(noise_pred, t, noise) + +plt.style.use("default") +plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap="gray") +plt.tight_layout() +plt.axis("off") +plt.show() + + +def cond_fn(x, t, y=None): #compute the gradient + assert y is not None + with torch.enable_grad(): + x_in = x.detach().requires_grad_(True) + logits = classifier(x_in, t) + log_probs = F.log_softmax(logits, dim=-1) + selected = log_probs[range(len(logits)), y.view(-1)] + a = th.autograd.grad(selected.sum(), x_in)[0] + return a, a * args.classifier_scale +#desired class +y=torch.tensor(0) +scale=100 + +for i in progress_bar: #go through the denoising process + t=L-i + print('t denoising', t) + with autocast(enabled=True): + with torch.enable_grad(): + noise_input = noise + print('inputshape', noise_input.shape) + model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device)) + + x_in = noise_input.detach().requires_grad_(True) + + logits = classifier(x_in, timesteps=torch.Tensor((t,)).to(noise.device)) + print('logits', logits) + log_probs = F.log_softmax(logits, dim=-1) + selected = log_probs[range(len(logits)), y.view(-1)] + a = torch.autograd.grad(selected.sum(), x_in)[0] + # noise_pred_uncond, noise_pred_text = model_output.chunk(2) #this is supposed to be epsilon + noise_pred = model_output # this is supposed to be epsilon + updated_noise=noise_pred - scale*a + + noise, _ = scheduler.step(updated_noise, t, noise) plt.style.use("default") plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap="gray") @@ -327,11 +572,17 @@ plt.axis("off") plt.show() + +diff=inputimg.cpu()-noise[0, 0].cpu() +plt.style.use("default") +plt.imshow(diff, cmap="jet") +plt.tight_layout() +plt.axis("off") +plt.show() # %% [markdown] # ### Cleanup data directory # # Remove directory if a temporary was used. # %% -if directory is None: - shutil.rmtree(root_dir) + diff --git a/tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb new file mode 100644 index 00000000..3f9e39a8 --- /dev/null +++ b/tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb @@ -0,0 +1,33 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f37e04b7-9695-4a24-85bb-fffdd87ee1b9", + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb new file mode 100644 index 00000000..363fcab7 --- /dev/null +++ b/tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb new file mode 100644 index 00000000..06ad686a --- /dev/null +++ b/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb @@ -0,0 +1,437 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "cf6673e1", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# # Diff-SCM\n", + "# \n", + "# This tutorial illustrates how to load the 2D BRATS dataset.\n", + "# \n", + "# \n", + "# ## Setup environment" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "2dc388db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "done\n" + ] + } + ], + "source": [ + "\n", + "\n", + "get_ipython().system('python -c \"import monai\" || pip install -q \"monai-weekly[pillow, tqdm, einops]\"')\n", + "get_ipython().system('python -c \"import matplotlib\" || pip install -q matplotlib')\n", + "get_ipython().run_line_magic('matplotlib', 'inline')\n", + "print('done')\n", + "\n", + "\n", + "# ## Setup imports" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "4167c04e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MONAI version: 1.1.dev2248\n", + "Numpy version: 1.23.2\n", + "Pytorch version: 1.12.1\n", + "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", + "MONAI rev id: 3400bd91422ccba9ccc3aa2ffe7fecd4eb5596bf\n", + "MONAI __file__: /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/monai/__init__.py\n", + "\n", + "Optional dependencies:\n", + "Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.\n", + "Nibabel version: 4.0.1\n", + "scikit-image version: 0.19.3\n", + "Pillow version: 9.2.0\n", + "Tensorboard version: NOT INSTALLED or UNKNOWN VERSION.\n", + "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", + "TorchVision version: 0.13.1\n", + "tqdm version: 4.64.1\n", + "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", + "psutil version: 5.9.4\n", + "pandas version: NOT INSTALLED or UNKNOWN VERSION.\n", + "einops version: 0.6.0\n", + "transformers version: NOT INSTALLED or UNKNOWN VERSION.\n", + "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", + "pynrrd version: NOT INSTALLED or UNKNOWN VERSION.\n", + "\n", + "For details about installing the optional dependencies, please visit:\n", + " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", + "\n" + ] + } + ], + "source": [ + "\n", + "\n", + "# Copyright 2020 MONAI Consortium\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "import os\n", + "import shutil\n", + "import tempfile\n", + "import time\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn.functional as F\n", + "from monai import transforms\n", + "from monai.apps import DecathlonDataset\n", + "from monai.config import print_config\n", + "from monai.data import CacheDataset, DataLoader\n", + "from monai.utils import first, set_determinism\n", + "from torch.cuda.amp import GradScaler, autocast\n", + "from tqdm import tqdm\n", + "\n", + "from generative.inferers import DiffusionInferer\n", + "\n", + "# TODO: Add right import reference after deployed\n", + "from generative.networks.nets import DiffusionModelUNet\n", + "from generative.networks.schedulers import DDPMScheduler\n", + "\n", + "print_config()\n", + "\n", + "\n", + "# ## Setup data directory" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "86b390cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/tmpf7ygl4zq\n" + ] + } + ], + "source": [ + "\n", + "\n", + "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", + "root_dir = tempfile.mkdtemp() if directory is None else directory\n", + "print(root_dir)\n", + "root_dir= '/tmp/tmp6o69ziv1'\n", + "\n", + "\n", + "# ## Set deterministic training for reproducibility" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "6d644892", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "set_determinism(42)\n", + "\n", + "\n", + "# ## Setup MedNIST Dataset and training and validation dataloaders\n", + "# In this tutorial, we will train our models on the MedNIST dataset available on MONAI\n", + "# (https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset).\n", + "# Here, we will use the \"Hand\" and \"HeadCT\", where our conditioning variable `class` will specify the modality." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "5c29c6a2", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2023-01-20 09:47:29,125 - INFO - Verified 'Task01_BrainTumour.tar', md5: 240a19d752f0d9e9101544901065d872.\n", + "2023-01-20 09:47:29,126 - INFO - File exists: /tmp/tmp6o69ziv1/Task01_BrainTumour.tar, skipped downloading.\n", + "2023-01-20 09:47:29,127 - INFO - Non-empty folder exists in /tmp/tmp6o69ziv1/Task01_BrainTumour, skipped extracting.\n", + "Image shape torch.Size([1, 64, 64, 64])\n" + ] + } + ], + "source": [ + "\n", + "\n", + "batch_size = 2\n", + "channel = 0 # 0 = Flair\n", + "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", + "\n", + "train_transforms = transforms.Compose(\n", + " [\n", + " transforms.LoadImaged(keys=[\"image\",\"label\"]),\n", + " transforms.EnsureChannelFirstd(keys=[\"image\",\"label\"]),\n", + " transforms.Lambdad(keys=[\"image\"], func=lambda x: x[channel, :, :, :]),\n", + " transforms.AddChanneld(keys=[\"image\"]),\n", + " transforms.EnsureTyped(keys=[\"image\",\"label\"]),\n", + " transforms.Orientationd(keys=[\"image\",\"label\"], axcodes=\"RAS\"),\n", + " transforms.Spacingd(\n", + " keys=[\"image\",\"label\"],\n", + " pixdim=(3.0, 3.0, 2.0),\n", + " mode=(\"bilinear\", \"nearest\"),\n", + " ),\n", + " transforms.CenterSpatialCropd(keys=[\"image\",\"label\"], roi_size=(64, 64, 64)),\n", + " transforms.ScaleIntensityRangePercentilesd(keys=\"image\", lower=0, upper=99.5, b_min=0, b_max=1),\n", + " transforms.CopyItemsd(keys=[\"label\"], times=1, names=[\"slice_label\"]),\n", + " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", + " ]\n", + ")\n", + "train_ds = DecathlonDataset(\n", + " root_dir=root_dir,\n", + " task=\"Task01_BrainTumour\",\n", + " section=\"training\", # validation\n", + " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", + " num_workers=4,\n", + " download=True, # Set download to True if the dataset hasnt been downloaded yet\n", + " seed=0,\n", + " transform=train_transforms,\n", + ")\n", + "nb_3D_images_to_mix = 2\n", + "train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", + "print(f'Image shape {train_ds[0][\"image\"].shape}')" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "16e750a6", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "from typing import Dict\n", + "def get_batched_2d_axial_slices(data : Dict):\n", + " images_3D = data['image']\n", + " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1])\n", + " slice_label = data['slice_label']\n", + " #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float()\n", + " slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze()\n", + " return batched_2d_slices, slice_label\n", + "\n", + "\n", + "# ### Visualisation of the training images" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "310b925c", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "check_data torch.Size([2, 1, 64, 64, 64]) torch.Size([2, 64])\n" + ] + } + ], + "source": [ + "\n", + "\n", + "check_data = first(train_loader_3D)\n", + "print('check_data', check_data[\"image\"].shape, check_data[\"slice_label\"].shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "4105a01f", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "idx [tensor(125), tensor(70), tensor(71), tensor(10), tensor(112), tensor(72), tensor(100), tensor(108), tensor(48), tensor(90), tensor(5), tensor(83), tensor(53), tensor(38), tensor(121), tensor(115), tensor(116), tensor(75), tensor(34), tensor(2), tensor(118), tensor(46), tensor(57), tensor(64), tensor(107), tensor(126), tensor(109), tensor(98), tensor(15), tensor(13), tensor(113), tensor(93), tensor(106), tensor(73), tensor(4), tensor(102), tensor(21), tensor(96), tensor(30), tensor(18), tensor(91), tensor(16), tensor(77), tensor(49), tensor(50), tensor(123), tensor(28), tensor(42), tensor(23), tensor(6), tensor(12), tensor(65), tensor(31), tensor(41), tensor(3), tensor(101), tensor(67), tensor(54), tensor(62), tensor(120), tensor(94), tensor(80), tensor(35), tensor(9), tensor(82), tensor(84), tensor(14), tensor(32), tensor(127), tensor(59), tensor(25), tensor(63), tensor(87), tensor(92), tensor(40), tensor(97), tensor(51), tensor(7), tensor(105), tensor(19), tensor(88), tensor(36), tensor(20), tensor(110), tensor(29), tensor(111), tensor(60), tensor(44), tensor(45), tensor(52), tensor(68), tensor(124), tensor(37), tensor(117), tensor(85), tensor(17), tensor(95), tensor(55), tensor(0), tensor(56), tensor(86), tensor(58), tensor(47), tensor(89), tensor(122), tensor(22), tensor(78), tensor(79), tensor(11), tensor(61), tensor(119), tensor(27), tensor(114), tensor(103), tensor(43), tensor(99), tensor(24), tensor(8), tensor(81), tensor(33), tensor(104), tensor(26), tensor(66), tensor(39), tensor(69), tensor(76), tensor(74), tensor(1)] 128\n", + "Batch shape: torch.Size([128, 1, 64, 64])\n", + "Slices class: tensor([0., 1., 0., 0.])\n" + ] + }, + { + "data": { + "image/png": "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0DkAAAAA05KlFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNzZvq4OTk5Nk8DwAAAADOI74pBQAAAEBzllIAAAAANGcpBQAAAEBzllIAAAAANGcpBQAAAEBzllIAAAAANGcpBQAAAEBzllIAAAAANGcpBQAAAEBz/w/KFbC7XwVz4QAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "\n", + "batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data)\n", + "idx = list(torch.randperm(batched_2d_slices.shape[0]))\n", + "print('idx', idx, len(idx))\n", + "slices = [0,30,45,63]\n", + "print(f\"Batch shape: {batched_2d_slices.shape}\")\n", + "print(f\"Slices class: {slice_label[idx][slices].view(-1)}\")\n", + "image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze()\n", + "plt.figure(\"training images\", (12, 6))\n", + "plt.imshow(image_visualisation, vmin=0, vmax=1, cmap=\"gray\")\n", + "plt.axis(\"off\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "21e0c944", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([128])" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "\n", + "slice_label.shape\n", + "\n", + "\n", + "# ## Check Distribution of Healthy / Unhealthy" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "1114650d", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(torch.Size([2, 1, 64, 64]), torch.Size([2]))" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))#\n", + "a,b = next(subset_2D) #what is a, what is b?\n", + "a.shape, b.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "5633a8c8", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAHDCAYAAACESXgYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA9mElEQVR4nO3de5yN5f7/8fcyhzUHMxODOWSMoSHFRAgjezBm2kjbRiclkk50kG0TkqVvjd1UUom9dcCuSAdKXyWDTGSqITrQg9rOmyGSmRxGzPX7w2/W1zIHs8bMpaXX8/G4Hw/rWte678+61j1rvd1HhzHGCAAAwJIa57sAAADwx0L4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+DgPZs2aJYfD4Z6CgoIUHR2tLl26aNKkSdq3b1+J17hcLjkcDq+Wc+TIEblcLq1YscKr15W2rIYNG+raa6/1aj5nM2fOHE2ZMqXU5xwOh1wuV5Uur6otW7ZMbdq0UWhoqBwOh957771Kz6t4ndi2bZu7bdCgQWrYsOE51/l70bBhQw0aNMj9ePfu3XK5XFq/fn2JvoMGDVLNmjXtFVdJ27Ztk8Ph0KxZs6pl/qWtAxkZGaWua8Xr0Jo1a6qllvKU91mWZuPGjXK5XB7rO/5YCB/n0cyZM5WTk6OsrCy9+OKLatmypZ588kk1a9ZMS5cu9eg7ZMgQ5eTkeDX/I0eOaOLEiV6Hj8osqzLKCx85OTkaMmRItddQWcYY3XDDDQoICNDChQuVk5OjlJSUKl3G+PHjtWDBgiqd5/m0YMECjR8/3v149+7dmjhxYoV/sP6ISlsHygof55O3n+XGjRs1ceJEwscfmP/5LuCPrHnz5mrTpo37cd++ffXQQw/p6quvVp8+ffTDDz8oKipKklS/fn3Vr1+/Wus5cuSIQkJCrCzrbNq3b39el382u3fv1s8//6y//vWvSk1NrZZlNG7cuFrme760atXqfJfgcy60deCP6OjRowoKCvJ6y/WFji0fvzMNGjTQM888o4KCAv3rX/9yt5e2K2T58uXq3LmzIiMjFRwcrAYNGqhv3746cuSItm3bprp160qSJk6c6N7FU7zZu3h+X331lfr166datWq5v+jK28WzYMECJSUlKSgoSI0aNdLzzz/v8Xxpuw8kacWKFXI4HO6tMJ07d9aiRYu0fft2j11QxUrb7fLdd9/pL3/5i2rVqqWgoCC1bNlSs2fPLnU5c+fO1bhx4xQbG6vw8HB169ZNmzZtKnvgT7Nq1SqlpqYqLCxMISEhSk5O1qJFi9zPu1wudzgbPXq0HA5HubtHioqK9Pjjj6tp06YKDg7WRRddpKSkJD333HPl1lHaJveioiK98MILatmypXte7du318KFCz36zZs3Tx06dFBoaKhq1qypa665RuvWrfPos2XLFt10002KjY2V0+lUVFSUUlNTy/3f66JFi+RwOJSbm+tue/fdd+VwONSzZ0+PvklJSerbt6/78em7XVasWKG2bdtKkm6//Xb353/mZ/7jjz+qR48eqlmzpuLi4vS3v/1NhYWF5Q2b+/2np6crJiZGwcHBatasmR5++GEdPnzYo1/x7p2KLGf37t264YYbFBYWpoiICN14443Ky8s7ay35+fny9/fXU0895W7bv3+/atSooYiICJ04ccLd/sADD6hu3boqvt/nmeuAw+HQ4cOHNXv2bPeYde7c2WN5BQUFuvfee1WnTh1FRkaqT58+2r17t0efoqIiZWZm6tJLL5XT6VS9evV02223adeuXR79ztxVVqxz587u5Vb0syw2a9YsXX/99ZKkLl26uPsX77qqyDKLl+twODRnzhyNHj1aMTExqlmzpnr16qW9e/eqoKBAd911l+rUqaM6dero9ttv16+//uoxz2PHjmnMmDFKSEhQYGCgLr74Yg0bNky//PKLR7+y3s+ZtRZ//y1ZskSDBw9W3bp1FRISUqF19o+G8PE71KNHD/n5+enTTz8ts8+2bdvUs2dPBQYG6tVXX9XixYv1j3/8Q6GhoTp+/LhiYmK0ePFiSdIdd9yhnJwc5eTkeGz2lqQ+ffrokksu0dtvv61//vOf5da1fv16DR8+XA899JAWLFig5ORkPfjgg3r66ae9fo/Tpk1Tx44dFR0d7a6tvF09mzZtUnJysjZs2KDnn39e8+fP12WXXaZBgwYpMzOzRP+xY8dq+/btevnllzVjxgz98MMP6tWrl06ePFluXdnZ2eratasOHTqkV155RXPnzlVYWJh69eqlefPmSTq1W2r+/PmSpPvvv185OTnl7h7JzMyUy+XSzTffrEWLFmnevHm64447SnzBVcSgQYP04IMPqm3btpo3b57efPNNXXfddR5hLyMjQzfffLMuu+wyvfXWW3rttddUUFCgTp06aePGje5+PXr00Nq1a5WZmamsrCxNnz5drVq1KreulJQUBQQEeOwWXLp0qYKDg5Wdna3ffvtNkrRv3z5999136tatW6nzufLKKzVz5kxJ0iOPPOL+/E/f1fbbb7/puuuuU2pqqt5//30NHjxYzz77rJ588smzjtMPP/ygHj166JVXXtHixYs1fPhwvfXWW+rVq1eJvhVZztGjR9WtWzctWbJEkyZN0ttvv63o6GjdeOONZ60lPDxcbdu29RizZcuWyel0qqCgQF9++aW7fenSperatWuZ4T8nJ0fBwcHq0aOHe8ymTZvm0WfIkCEKCAjQnDlzlJmZqRUrVujWW2/16HPvvfdq9OjRSktL08KFC/U///M/Wrx4sZKTk7V///6zvqfTVeSzPF3Pnj2VkZEhSXrxxRfd/c8MrxU1duxY7du3T7NmzdIzzzyjFStW6Oabb1bfvn0VERGhuXPnatSoUXrttdc0duxY9+uMMerdu7eefvppDRgwQIsWLdKIESM0e/Zsde3a9ZwCw+DBgxUQEKDXXntN77zzjgICAio9rwuWgXUzZ840kkxubm6ZfaKiokyzZs3cjydMmGBO/7jeeecdI8msX7++zHn89NNPRpKZMGFCieeK5/foo4+W+dzp4uPjjcPhKLG8tLQ0Ex4ebg4fPuzx3rZu3erR75NPPjGSzCeffOJu69mzp4mPjy+19jPrvummm4zT6TQ7duzw6Ne9e3cTEhJifvnlF4/l9OjRw6PfW2+9ZSSZnJycUpdXrH379qZevXqmoKDA3XbixAnTvHlzU79+fVNUVGSMMWbr1q1GknnqqafKnZ8xxlx77bWmZcuW5fYpbdwGDhzoMT6ffvqpkWTGjRtX5nx27Nhh/P39zf333+/RXlBQYKKjo80NN9xgjDFm//79RpKZMmXKWes/09VXX226du3qfnzJJZeYv//976ZGjRomOzvbGGPMG2+8YSSZzZs3u/vFx8ebgQMHuh/n5uYaSWbmzJklljFw4EAjybz11lse7T169DBNmzb1qt6ioiLz22+/mezsbCPJfP31114vZ/r06UaSef/99z363XnnnWW+h9M98sgjJjg42Bw7dswYY8yQIUPMn//8Z5OUlGQmTpxojDHmv//9r5FkZsyY4VHfmX8joaGhHuNYrHgdGjp0qEd7ZmamkWT27NljjDHm+++/L7XfF198YSSZsWPHutvO/MyKpaSkmJSUFPfj8j7L0rz99tslvg+8XWbx33qvXr08+g0fPtxIMg888IBHe+/evU3t2rXdjxcvXmwkmczMTI9+8+bNK/E5lPU9ematxZ/BbbfdVsq7xunY8vE7Zf7/ZteytGzZUoGBgbrrrrs0e/ZsbdmypVLLOX2z+NlcfvnluuKKKzza+vfvr/z8fH311VeVWn5FLV++XKmpqYqLi/NoHzRokI4cOVJiq8l1113n8TgpKUmStH379jKXcfjwYX3xxRfq16+fx5kWfn5+GjBggHbt2lXhXTenu+qqq/T1119r6NCh+vjjj5Wfn+/1PCTpo48+kiQNGzaszD4ff/yxTpw4odtuu00nTpxwT0FBQUpJSXHv9qpdu7YaN26sp556SpMnT9a6detUVFRUoTpSU1P12Wef6ejRo9q+fbt+/PFH3XTTTWrZsqWysrIknfoffIMGDZSYmFip9yqd2tR95paKpKSkcj/DYlu2bFH//v0VHR0tPz8/BQQEuA8I/v77771ezieffKKwsLAS61X//v0r9F5SU1N19OhRrV69WtKp8UlLS1O3bt08xkxSmVuLKups6/4nn3wiSSV2bVx11VVq1qyZli1bdk7Lt+3Ms/CaNWsmSSW2pDRr1kw///yze9fL8uXLJZUch+uvv16hoaHnNA7efK/+URE+focOHz6sAwcOKDY2tsw+jRs31tKlS1WvXj0NGzZMjRs3VuPGjc96HMGZYmJiKtw3Ojq6zLYDBw54tVxvHThwoNRai8fozOVHRkZ6PHY6nZJObT4vy8GDB2WM8Wo5FTFmzBg9/fTT+vzzz9W9e3dFRkYqNTXV61Mif/rpJ/n5+ZX6ORTbu3evJKlt27YKCAjwmObNm+fepO5wOLRs2TJdc801yszM1JVXXqm6devqgQceUEFBQbl1dOvWTYWFhVq1apWysrJUp04dtWrVSt26dXP/gC5btuycf0RDQkIUFBTk0eZ0OnXs2LFyX/frr7+qU6dO+uKLL/T4449rxYoVys3Nde8qO3MdqMhyDhw44D74+3TlfRanS05OVkhIiJYuXaoff/xR27Ztc4ePL774Qr/++quWLl2qRo0aKSEhoULzLMvZ1v3idbis9by6/5arWu3atT0eBwYGltte/LkeOHBA/v7+7mPjijkcDkVHR5/TOHjzvfpHxdkuv0OLFi3SyZMnSxxIdqZOnTqpU6dOOnnypNasWaMXXnhBw4cPV1RUlG666aYKLcubI7BLO7iuuK34C6/4S/zM/aXe7kc+U2RkpPbs2VOivfhAujp16pzT/CWpVq1aqlGjRpUvx9/fXyNGjNCIESP0yy+/aOnSpRo7dqyuueYa7dy5UyEhIRWaT926dXXy5Enl5eWV+eVWXN8777yj+Pj4cucXHx+vV155RZK0efNmvfXWW3K5XDp+/Hi5x/+0a9dONWvW1NKlS7Vt2zalpqbK4XAoNTVVzzzzjHJzc7Vjx45zDh+VtXz5cu3evVsrVqzwOP25MsfYFIuMjPQ4NqNYRQ44lU798F199dVaunSp6tevr+joaLVo0UKNGjWSdOrgyWXLllX5tXRKU/y3umfPnhJnte3evdtjHQ8KCir12If9+/dXyd9caWwtMzIyUidOnNBPP/3kEUCMMcrLy3MfRCudCnCl1VRWQOHMlrNjy8fvzI4dOzRy5EhFRETo7rvvrtBr/Pz81K5dO7344ouS5N4FUpH/7Xtjw4YN+vrrrz3a5syZo7CwMF155ZWS5D4y/5tvvvHod+bZGMX1VbS21NRU94/K6f79738rJCSkSk7NDQ0NVbt27TR//nyPuoqKivT666+rfv36atKkyTkt46KLLlK/fv00bNgw/fzzz15d56B79+6SpOnTp5fZ55prrpG/v7/+85//qE2bNqVOpWnSpIkeeeQRtWjR4qy70AICAvSnP/1JWVlZWr58udLS0iSdCsP+/v565JFH3GGkPFW9fhYr/uIvnn+x088e81aXLl1UUFBQYj2eM2dOhefRrVs3rV27Vu+++647mIWGhqp9+/Z64YUXtHv37goFNm/+bkrTtWtXSdLrr7/u0Z6bm6vvv//e43Nr2LBhib/lzZs3l9j96O1nWV7/ii7zXBW/zzPH4d1339Xhw4fPOg7Lly8vcfYMKo4tH+fRd999594nv2/fPq1cuVIzZ86Un5+fFixYUGJz4On++c9/avny5erZs6caNGigY8eO6dVXX5X0f/uMw8LCFB8fr/fff1+pqamqXbu26tSpU+mrZsbGxuq6666Ty+VSTEyMXn/9dWVlZenJJ590/++9bdu2atq0qUaOHKkTJ06oVq1aWrBggVatWlVifi1atND8+fM1ffp0tW7dWjVq1Cjzx3HChAn63//9X3Xp0kWPPvqoateurTfeeEOLFi1SZmamIiIiKvWezjRp0iSlpaWpS5cuGjlypAIDAzVt2jR99913mjt3bqX+R9OrVy/3NV3q1q2r7du3a8qUKYqPj/fqmIhOnTppwIABevzxx7V3715de+21cjqdWrdunUJCQnT//ferYcOGeuyxxzRu3Dht2bJFf/7zn1WrVi3t3btXX375pUJDQzVx4kR98803uu+++3T99dcrMTFRgYGBWr58ub755hs9/PDDZ60lNTVVf/vb3yT93/oWHBys5ORkLVmyRElJSapXr16582jcuLGCg4P1xhtvqFmzZqpZs6ZiY2PL3d1YEcnJyapVq5buueceTZgwQQEBAXrjjTdKBGdv3HbbbXr22Wd122236YknnlBiYqI+/PBDffzxxxWeR2pqqk6ePKlly5Z5nCLerVs3TZgwQQ6Hwx0MytOiRQutWLFCH3zwgWJiYhQWFqamTZtWuI6mTZvqrrvu0gsvvKAaNWqoe/fu2rZtm8aPH6+4uDg99NBD7r4DBgzQrbfeqqFDh6pv377avn27MjMzS3w3eftZNm/eXJI0Y8YMhYWFKSgoSAkJCYqMjKzwMs9VWlqarrnmGo0ePVr5+fnq2LGjvvnmG02YMEGtWrXSgAEDPMZh/PjxevTRR5WSkqKNGzdq6tSpVfa984d0ng94/UMqPiK6eAoMDDT16tUzKSkpJiMjw+zbt6/Ea848AyUnJ8f89a9/NfHx8cbpdJrIyEiTkpJiFi5c6PG6pUuXmlatWhmn02kkuY/MLp7fTz/9dNZlGXPqqO6ePXuad955x1x++eUmMDDQNGzY0EyePLnE6zdv3mzS09NNeHi4qVu3rrn//vvNokWLShzd/vPPP5t+/fqZiy66yDgcDo9lqpSjy7/99lvTq1cvExERYQIDA80VV1xR4uj64iPg3377bY/24rNTKnI0/sqVK03Xrl1NaGioCQ4ONu3btzcffPBBqfOryNkuzzzzjElOTjZ16tQxgYGBpkGDBuaOO+4w27Ztc/epyNkuxhhz8uRJ8+yzz5rmzZubwMBAExERYTp06FCivvfee8906dLFhIeHG6fTaeLj402/fv3M0qVLjTHG7N271wwaNMhceumlJjQ01NSsWdMkJSWZZ5991pw4ceKs7+nrr782kkxiYqJH+xNPPGEkmREjRpR4TWlnMcydO9dceumlJiAgwOMzHzhwoAkNDS0xj9LWzdKsXr3adOjQwYSEhJi6deuaIUOGmK+++qrEOuDNcnbt2mX69u1ratasacLCwkzfvn3N6tWrK7xeFRUVmTp16hhJ5r///a+7/bPPPjOSzJVXXlniNaWtA+vXrzcdO3Y0ISEhRpL7DJCyzqIr7UyzkydPmieffNI0adLEBAQEmDp16phbb73V7Ny5s0TNmZmZplGjRiYoKMi0adPGLF++vMSZJ8aU/VmWZcqUKSYhIcH4+fl5jGFFl1nW33pZ41Dad97Ro0fN6NGjTXx8vAkICDAxMTHm3nvvNQcPHvR4bWFhoRk1apSJi4szwcHBJiUlxaxfv77Ms13KO5MRpziMOctpFQAAAFWIYz4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPuDzXnjhBffdORMSEjRx4kT3Dc4uNBs3bpTL5fLq+iB/JKtWrdKQIUPUunVrOZ3OUu+wbMu0adPcd2oF4InwAZ/2xBNP6MEHH1SfPn308ccfa+jQocrIyCj3/ie+bOPGjZo4cSLhowzLli1z31cmOTn5vNZC+ADKRviAzzpw4IAef/xx3XnnncrIyFDnzp3197//XRMmTNDLL7/scft4+J6jR4+e9QaLZxo/fry2bdumBQsWVPoW7fBeVV+lFhc+wgd81uLFi3Xs2DHdfvvtHu233367jDF67733rNWyYsUKORwOzZ07V+PGjVNsbKzCw8PVrVu3Ui8L/eqrr+qKK65QUFCQateurb/+9a8l7rZ6plmzZun666+XdOpy3w6HQw6Hw/2/64YNG5a4Q6ckde7c2eM+QcW1zpkzR6NHj1ZMTIxq1qypXr16ae/evSooKNBdd92lOnXqqE6dOrr99ttLXEb62LFjGjNmjBISEhQYGKiLL75Yw4YNK3H/FIfDIZfLVaKmM2udNWuWHA6HlixZosGDB6tu3boKCQkp9X4a5alR4/fxldawYUNt2LBB2dnZ7s+p+MrCxe/1zK1XxZ9L8Z2HpVOfXfPmzZWTk6Pk5GQFBwerYcOGmjlzpqRT94G68sorFRISohYtWmjx4sUlalm1apVSU1MVFhamkJAQJScna9GiRR59XC5XqVfvLa3Whg0b6tprr9X8+fPVqlUrBQUFaeLEiZUbKPxhcXl1+KzvvvtO0qnLTZ8uJiZGderUcT9fnhMnTlRoWX5+fhW6tPrYsWPVsWNHvfzyy8rPz9fo0aPVq1cvff/99/Lz85N06hLuY8eO1c0336xJkybpwIEDcrlc6tChg3Jzc8u85HrPnj2VkZGhsWPH6sUXX3TfT6dx48YVeg+l1dqlSxfNmjVL27Zt08iRI3XzzTfL399fV1xxhebOnat169Zp7NixCgsL0/PPPy/p1I23evfurWXLlmnMmDHq1KmT+7LUOTk5ysnJKXFflYoaPHiwevbsqddee02HDx9WQEBApeZTWSdPnqzQ1pYaNWqUG3QWLFigfv36KSIiQtOmTZNU8l4zFZWXl6fbb79do0aNUv369fXCCy9o8ODB2rlzp9555x2NHTtWEREReuyxx9S7d29t2bLFfVnz7OxspaWlKSkpSa+88oqcTqemTZumXr16ae7cubrxxhsrVdNXX32l77//Xo888ogSEhIUGhpaqfngD+x8Xl4VOBd33nmncTqdpT7XpEkTk56eXu7riy+RXpHp9EtTl6b4Us89evTwaH/rrbeMJJOTk2OMMebgwYMmODi4RL8dO3YYp9Np+vfvX+5y3n777TLrKe3y5caYMi9L3atXL49+w4cPN5LMAw884NHeu3dvU7t2bffjxYsXG0kmMzPTo9+8efOMJDNjxgx3m8q4zHZZl6W+7bbbSnnXlfPUU0+VuGT92aSkpFRofShtnM90+eWXl7gEuTGlX0rfmNIvg15cz5o1a9xtBw4cMH5+fiY4ONjjMu3r1683kszzzz/vbmvfvr2pV6+eKSgocLedOHHCNG/e3NSvX98UFRUZY8q+bH1ptcbHxxs/Pz+zadOms44BUBa2fMCnlbc14mxbKmJjY5Wbm1uh5VT0xl3XXXedx+OkpCRJ0vbt29W+fXvl5OTo6NGjJXaPxMXFqWvXrlq2bFmFllMVzrx9e7NmzSSpxLESzZo103vvvadff/1VNWvW1PLlyyWpxHu4/vrrNXjwYC1btkx33nlnpWrq27dvpV5XVf71r3+poKDgrP2q63bypYmJiVHr1q3dj2vXrq169eqpYcOGHjduK/78tm/fLkk6fPiwvvjiC917772qWbOmu5+fn58GDBig0aNHa9OmTbr00ku9rikpKemc7/CMPzbCB3xWZGSkjh07piNHjrjvqlvs559/9vjCLk1gYKBatmxZoWUV7zKpSE2nO/PW4QcOHJB06gflTLGxscrKyqrQcqpC7dq1PR4HBgaW237s2DHVrFlTBw4ckL+/f4m7jDocDkVHR7vfY2WUNi42XXLJJRXe7WLLmZ+HdOozKe9zkqSDBw/KGFPmuiap0p/V+f6c4Pt+H0dnAZVQfKzHt99+69Gel5en/fv3u2/bXZZt27YpICCgQlN2dnaV1FwcTvbs2VPiud27d5/T/6iDgoJKPUBz//79lZ5naSIjI3XixAn99NNPHu3GGOXl5Xm8B6fTWWpNZf3oVeS4muqUmppaofVh8ODBlV5GUFCQJJUYl6r+nGrVqqUaNWqUua5J/7cFx9uazvfnBN/Hlg/4rD//+c8KCgrSrFmz1K5dO3d78RH6vXv3Lvf11bHb5Ww6dOig4OBgvf766+4zVyRp165dWr58ufr161fu68/cknK6hg0b6ptvvvFo27x5szZt2lSluwlSU1OVmZmp119/XQ899JC7/d1339Xhw4eVmppabk3Lly8vcfbM70VV7nZxOp1lfk6S9M0333isVwsXLqx4oRUQGhqqdu3aaf78+Xr66acVHBwsSSoqKtLrr7+u+vXru3ednF5T27Zt3fP44IMPqrQmoBjhAz6rdu3aeuSRRzR+/HjVrl1b6enpys3Nlcvl0pAhQ3TZZZeV+/rAwEC1adPGUrWnXHTRRRo/frzGjh2r2267TTfffLMOHDigiRMnKigoSBMmTCj39cVbc2bMmKGwsDAFBQUpISFBkZGRGjBggG699VYNHTpUffv21fbt25WZmVli98i5SktL0zXXXKPRo0crPz9fHTt2dJ/t0qpVKw0YMMDdd8CAARo/frweffRRpaSkaOPGjZo6daoiIiK8WqbD4VBKSorHaail+emnn9xbqYq3iH300UeqW7eu6tatq5SUlHJfX1UhUzq1Ze7NN9/UvHnz1KhRIwUFBalFixZq27atmjZtqpEjR+rEiROqVauWFixYoFWrVlXZsotNmjRJaWlp6tKli0aOHKnAwEBNmzZN3333nebOnevegtGjRw/Vrl1bd9xxhx577DH5+/tr1qxZ2rlzZ5XXBEjibBf4vueee840adLEBAYGmgYNGpgJEyaY48ePW62h+EyFt99+26O9+IyamTNnerS//PLLJikpyQQGBpqIiAjzl7/8xWzYsKFCy5oyZYpJSEgwfn5+HvMuKioymZmZplGjRiYoKMi0adPGLF++vMyzXc6stfjMhtzcXI/24jMhfvrpJ3fb0aNHzejRo018fLwJCAgwMTEx5t577zUHDx70eG1hYaEZNWqUiYuLM8HBwSYlJcWsX7++zLNdzly2McYUFBQYSeamm24669gUv7fSptLOPKlO27ZtM+np6SYsLMxIMvHx8e7nNm/ebNLT0014eLipW7euuf/++82iRYtKPdvl8ssvLzHv+Ph407NnzxLtksywYcM82lauXGm6du1qQkNDTXBwsGnfvr354IMPSrz2yy+/NMnJySY0NNRcfPHFZsKECebll18u9WyX0pYNeMNhjJeXEAQAiz788ENde+21+vrrr0tc0wWAb+KAUwC/a5988oluuukmggdwAWHLBwAAsIotHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAqt/dFU6Lioq0e/duhYWFcf8AAAB8hDFGBQUFio2NPevNF3934WP37t2Ki4s732UAAIBK2Llzp+rXr19un99d+AgLC5N0qvjw8PDzXA0AAKiI/Px8xcXFuX/Hy/O7Cx/Fu1rCw8MJHwAA+JiKHDLBAacAAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALDK/3wXYFvDhxed7xL+ELb9o+f5LgGAD+I72o7z/R3Nlg8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYJXX4eO///2vbr31VkVGRiokJEQtW7bU2rVr3c8bY+RyuRQbG6vg4GB17txZGzZsqNKiAQCA7/IqfBw8eFAdO3ZUQECAPvroI23cuFHPPPOMLrroInefzMxMTZ48WVOnTlVubq6io6OVlpamgoKCqq4dAAD4IH9vOj/55JOKi4vTzJkz3W0NGzZ0/9sYoylTpmjcuHHq06ePJGn27NmKiorSnDlzdPfdd1dN1QAAwGd5teVj4cKFatOmja6//nrVq1dPrVq10ksvveR+fuvWrcrLy1N6erq7zel0KiUlRatXry51noWFhcrPz/eYAADAhcur8LFlyxZNnz5diYmJ+vjjj3XPPffogQce0L///W9JUl5eniQpKirK43VRUVHu5840adIkRUREuKe4uLjKvA8AAOAjvAofRUVFuvLKK5WRkaFWrVrp7rvv1p133qnp06d79HM4HB6PjTEl2oqNGTNGhw4dck87d+708i0AAABf4lX4iImJ0WWXXebR1qxZM+3YsUOSFB0dLUkltnLs27evxNaQYk6nU+Hh4R4TAAC4cHkVPjp27KhNmzZ5tG3evFnx8fGSpISEBEVHRysrK8v9/PHjx5Wdna3k5OQqKBcAAPg6r852eeihh5ScnKyMjAzdcMMN+vLLLzVjxgzNmDFD0qndLcOHD1dGRoYSExOVmJiojIwMhYSEqH///tXyBgAAgG/xKny0bdtWCxYs0JgxY/TYY48pISFBU6ZM0S233OLuM2rUKB09elRDhw7VwYMH1a5dOy1ZskRhYWFVXjwAAPA9XoUPSbr22mt17bXXlvm8w+GQy+WSy+U6l7oAAMAFinu7AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKzyKny4XC45HA6PKTo62v28MUYul0uxsbEKDg5W586dtWHDhiovGgAA+C6vt3xcfvnl2rNnj3v69ttv3c9lZmZq8uTJmjp1qnJzcxUdHa20tDQVFBRUadEAAMB3eR0+/P39FR0d7Z7q1q0r6dRWjylTpmjcuHHq06ePmjdvrtmzZ+vIkSOaM2dOlRcOAAB8k9fh44cfflBsbKwSEhJ00003acuWLZKkrVu3Ki8vT+np6e6+TqdTKSkpWr16dZnzKywsVH5+vscEAAAuXF6Fj3bt2unf//63Pv74Y7300kvKy8tTcnKyDhw4oLy8PElSVFSUx2uioqLcz5Vm0qRJioiIcE9xcXGVeBsAAMBXeBU+unfvrr59+6pFixbq1q2bFi1aJEmaPXu2u4/D4fB4jTGmRNvpxowZo0OHDrmnnTt3elMSAADwMed0qm1oaKhatGihH374wX3Wy5lbOfbt21dia8jpnE6nwsPDPSYAAHDhOqfwUVhYqO+//14xMTFKSEhQdHS0srKy3M8fP35c2dnZSk5OPudCAQDAhcHfm84jR45Ur1691KBBA+3bt0+PP/648vPzNXDgQDkcDg0fPlwZGRlKTExUYmKiMjIyFBISov79+1dX/QAAwMd4FT527dqlm2++Wfv371fdunXVvn17ff7554qPj5ckjRo1SkePHtXQoUN18OBBtWvXTkuWLFFYWFi1FA8AAHyPV+HjzTffLPd5h8Mhl8sll8t1LjUBAIALGPd2AQAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFh1TuFj0qRJcjgcGj58uLvNGCOXy6XY2FgFBwerc+fO2rBhw7nWCQAALhCVDh+5ubmaMWOGkpKSPNozMzM1efJkTZ06Vbm5uYqOjlZaWpoKCgrOuVgAAOD7KhU+fv31V91yyy166aWXVKtWLXe7MUZTpkzRuHHj1KdPHzVv3lyzZ8/WkSNHNGfOnCorGgAA+K5KhY9hw4apZ8+e6tatm0f71q1blZeXp/T0dHeb0+lUSkqKVq9eXeq8CgsLlZ+f7zEBAIALl7+3L3jzzTf11VdfKTc3t8RzeXl5kqSoqCiP9qioKG3fvr3U+U2aNEkTJ070tgwAAOCjvNrysXPnTj344IN6/fXXFRQUVGY/h8Ph8dgYU6Kt2JgxY3To0CH3tHPnTm9KAgAAPsarLR9r167Vvn371Lp1a3fbyZMn9emnn2rq1KnatGmTpFNbQGJiYtx99u3bV2JrSDGn0ymn01mZ2gEAgA/yastHamqqvv32W61fv949tWnTRrfccovWr1+vRo0aKTo6WllZWe7XHD9+XNnZ2UpOTq7y4gEAgO/xastHWFiYmjdv7tEWGhqqyMhId/vw4cOVkZGhxMREJSYmKiMjQyEhIerfv3/VVQ0AAHyW1wecns2oUaN09OhRDR06VAcPHlS7du20ZMkShYWFVfWiAACADzrn8LFixQqPxw6HQy6XSy6X61xnDQAALkDc2wUAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABglVfhY/r06UpKSlJ4eLjCw8PVoUMHffTRR+7njTFyuVyKjY1VcHCwOnfurA0bNlR50QAAwHd5FT7q16+vf/zjH1qzZo3WrFmjrl276i9/+Ys7YGRmZmry5MmaOnWqcnNzFR0drbS0NBUUFFRL8QAAwPd4FT569eqlHj16qEmTJmrSpImeeOIJ1axZU59//rmMMZoyZYrGjRunPn36qHnz5po9e7aOHDmiOXPmVFf9AADAx1T6mI+TJ0/qzTff1OHDh9WhQwdt3bpVeXl5Sk9Pd/dxOp1KSUnR6tWry5xPYWGh8vPzPSYAAHDh8jp8fPvtt6pZs6acTqfuueceLViwQJdddpny8vIkSVFRUR79o6Ki3M+VZtKkSYqIiHBPcXFx3pYEAAB8iNfho2nTplq/fr0+//xz3XvvvRo4cKA2btzoft7hcHj0N8aUaDvdmDFjdOjQIfe0c+dOb0sCAAA+xN/bFwQGBuqSSy6RJLVp00a5ubl67rnnNHr0aElSXl6eYmJi3P337dtXYmvI6ZxOp5xOp7dlAAAAH3XO1/kwxqiwsFAJCQmKjo5WVlaW+7njx48rOztbycnJ57oYAABwgfBqy8fYsWPVvXt3xcXFqaCgQG+++aZWrFihxYsXy+FwaPjw4crIyFBiYqISExOVkZGhkJAQ9e/fv7rqBwAAPsar8LF3714NGDBAe/bsUUREhJKSkrR48WKlpaVJkkaNGqWjR49q6NChOnjwoNq1a6clS5YoLCysWooHAAC+x6vw8corr5T7vMPhkMvlksvlOpeaAADABYx7uwAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACs8ip8TJo0SW3btlVYWJjq1aun3r17a9OmTR59jDFyuVyKjY1VcHCwOnfurA0bNlRp0QAAwHd5FT6ys7M1bNgwff7558rKytKJEyeUnp6uw4cPu/tkZmZq8uTJmjp1qnJzcxUdHa20tDQVFBRUefEAAMD3+HvTefHixR6PZ86cqXr16mnt2rX605/+JGOMpkyZonHjxqlPnz6SpNmzZysqKkpz5szR3XffXXWVAwAAn3ROx3wcOnRIklS7dm1J0tatW5WXl6f09HR3H6fTqZSUFK1evbrUeRQWFio/P99jAgAAF65Khw9jjEaMGKGrr75azZs3lyTl5eVJkqKiojz6RkVFuZ8706RJkxQREeGe4uLiKlsSAADwAZUOH/fdd5+++eYbzZ07t8RzDofD47ExpkRbsTFjxujQoUPuaefOnZUtCQAA+ACvjvkodv/992vhwoX69NNPVb9+fXd7dHS0pFNbQGJiYtzt+/btK7E1pJjT6ZTT6axMGQAAwAd5teXDGKP77rtP8+fP1/Lly5WQkODxfEJCgqKjo5WVleVuO378uLKzs5WcnFw1FQMAAJ/m1ZaPYcOGac6cOXr//fcVFhbmPo4jIiJCwcHBcjgcGj58uDIyMpSYmKjExERlZGQoJCRE/fv3r5Y3AAAAfItX4WP69OmSpM6dO3u0z5w5U4MGDZIkjRo1SkePHtXQoUN18OBBtWvXTkuWLFFYWFiVFAwAAHybV+HDGHPWPg6HQy6XSy6Xq7I1AQCACxj3dgEAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+AAAAFYRPgAAgFWEDwAAYBXhAwAAWEX4AAAAVhE+AACAVYQPAABgFeEDAABY5XX4+PTTT9WrVy/FxsbK4XDovffe83jeGCOXy6XY2FgFBwerc+fO2rBhQ1XVCwAAfJzX4ePw4cO64oorNHXq1FKfz8zM1OTJkzV16lTl5uYqOjpaaWlpKigoOOdiAQCA7/P39gXdu3dX9+7dS33OGKMpU6Zo3Lhx6tOnjyRp9uzZioqK0pw5c3T33XefW7UAAMDnVekxH1u3blVeXp7S09PdbU6nUykpKVq9enWpryksLFR+fr7HBAAALlxVGj7y8vIkSVFRUR7tUVFR7ufONGnSJEVERLinuLi4qiwJAAD8zlTL2S4Oh8PjsTGmRFuxMWPG6NChQ+5p586d1VESAAD4nfD6mI/yREdHSzq1BSQmJsbdvm/fvhJbQ4o5nU45nc6qLAMAAPyOVemWj4SEBEVHRysrK8vddvz4cWVnZys5ObkqFwUAAHyU11s+fv31V/3444/ux1u3btX69etVu3ZtNWjQQMOHD1dGRoYSExOVmJiojIwMhYSEqH///lVaOAAA8E1eh481a9aoS5cu7scjRoyQJA0cOFCzZs3SqFGjdPToUQ0dOlQHDx5Uu3bttGTJEoWFhVVd1QAAwGd5HT46d+4sY0yZzzscDrlcLrlcrnOpCwAAXKC4twsAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAqmoLH9OmTVNCQoKCgoLUunVrrVy5sroWBQAAfEi1hI958+Zp+PDhGjdunNatW6dOnTqpe/fu2rFjR3UsDgAA+JBqCR+TJ0/WHXfcoSFDhqhZs2aaMmWK4uLiNH369OpYHAAA8CH+VT3D48ePa+3atXr44Yc92tPT07V69eoS/QsLC1VYWOh+fOjQIUlSfn5+VZcmSSoqPFIt84Wn6vr8AFzY+I62ozq+o4vnaYw5a98qDx/79+/XyZMnFRUV5dEeFRWlvLy8Ev0nTZqkiRMnlmiPi4ur6tJgUcSU810BAKAs1fkdXVBQoIiIiHL7VHn4KOZwODweG2NKtEnSmDFjNGLECPfjoqIi/fzzz4qMjCy1/7nIz89XXFycdu7cqfDw8CqdN/4P42wH42wPY20H42xHdY2zMUYFBQWKjY09a98qDx916tSRn59fia0c+/btK7E1RJKcTqecTqdH20UXXVTVZXkIDw9nxbaAcbaDcbaHsbaDcbajOsb5bFs8ilX5AaeBgYFq3bq1srKyPNqzsrKUnJxc1YsDAAA+plp2u4wYMUIDBgxQmzZt1KFDB82YMUM7duzQPffcUx2LAwAAPqRawseNN96oAwcO6LHHHtOePXvUvHlzffjhh4qPj6+OxVWY0+nUhAkTSuzmQdVinO1gnO1hrO1gnO34PYyzw1TknBgAAIAqwr1dAACAVYQPAABgFeEDAABYRfgAAABWXXDhY9q0aUpISFBQUJBat26tlStXlts/OztbrVu3VlBQkBo1aqR//vOflir1bd6M8/z585WWlqa6desqPDxcHTp00Mcff2yxWt/l7fpc7LPPPpO/v79atmxZvQVeILwd58LCQo0bN07x8fFyOp1q3LixXn31VUvV+jZvx/qNN97QFVdcoZCQEMXExOj222/XgQMHLFXrez799FP16tVLsbGxcjgceu+99876mvPyO2guIG+++aYJCAgwL730ktm4caN58MEHTWhoqNm+fXup/bds2WJCQkLMgw8+aDZu3GheeuklExAQYN555x3LlfsWb8f5wQcfNE8++aT58ssvzebNm82YMWNMQECA+eqrryxX7lu8Hediv/zyi2nUqJFJT083V1xxhZ1ifVhlxvm6664z7dq1M1lZWWbr1q3miy++MJ999pnFqn2Tt2O9cuVKU6NGDfPcc8+ZLVu2mJUrV5rLL7/c9O7d23LlvuPDDz8048aNM++++66RZBYsWFBu//P1O3hBhY+rrrrK3HPPPR5tl156qXn44YdL7T9q1Chz6aWXerTdfffdpn379tVW44XA23EuzWWXXWYmTpxY1aVdUCo7zjfeeKN55JFHzIQJEwgfFeDtOH/00UcmIiLCHDhwwEZ5FxRvx/qpp54yjRo18mh7/vnnTf369autxgtJRcLH+fodvGB2uxw/flxr165Venq6R3t6erpWr15d6mtycnJK9L/mmmu0Zs0a/fbbb9VWqy+rzDifqaioSAUFBapdu3Z1lHhBqOw4z5w5U//5z380YcKE6i7xglCZcV64cKHatGmjzMxMXXzxxWrSpIlGjhypo0eP2ijZZ1VmrJOTk7Vr1y59+OGHMsZo7969euedd9SzZ08bJf8hnK/fwWq7q61t+/fv18mTJ0vcvC4qKqrETe6K5eXlldr/xIkT2r9/v2JiYqqtXl9VmXE+0zPPPKPDhw/rhhtuqI4SLwiVGecffvhBDz/8sFauXCl//wvmT7taVWact2zZolWrVikoKEgLFizQ/v37NXToUP38888c91GOyox1cnKy3njjDd144406duyYTpw4oeuuu04vvPCCjZL/EM7X7+AFs+WjmMPh8HhsjCnRdrb+pbXDk7fjXGzu3LlyuVyaN2+e6tWrV13lXTAqOs4nT55U//79NXHiRDVp0sRWeRcMb9bnoqIiORwOvfHGG7rqqqvUo0cPTZ48WbNmzWLrRwV4M9YbN27UAw88oEcffVRr167V4sWLtXXrVu4TVsXOx+/gBfPfozp16sjPz69Egt63b1+JVFcsOjq61P7+/v6KjIystlp9WWXGudi8efN0xx136O2331a3bt2qs0yf5+04FxQUaM2aNVq3bp3uu+8+Sad+JI0x8vf315IlS9S1a1crtfuSyqzPMTExuvjiiz1uHd6sWTMZY7Rr1y4lJiZWa82+qjJjPWnSJHXs2FF///vfJUlJSUkKDQ1Vp06d9Pjjj7N1ugqcr9/BC2bLR2BgoFq3bq2srCyP9qysLCUnJ5f6mg4dOpTov2TJErVp00YBAQHVVqsvq8w4S6e2eAwaNEhz5sxhf20FeDvO4eHh+vbbb7V+/Xr3dM8996hp06Zav3692rVrZ6t0n1KZ9bljx47avXu3fv31V3fb5s2bVaNGDdWvX79a6/VllRnrI0eOqEYNz58pPz8/Sf/3v3Ocm/P2O1ith7NaVnwa1yuvvGI2btxohg8fbkJDQ822bduMMcY8/PDDZsCAAe7+xacYPfTQQ2bjxo3mlVde4VTbCvB2nOfMmWP8/f3Niy++aPbs2eOefvnll/P1FnyCt+N8Js52qRhvx7mgoMDUr1/f9OvXz2zYsMFkZ2ebxMREM2TIkPP1FnyGt2M9c+ZM4+/vb6ZNm2b+85//mFWrVpk2bdqYq6666ny9hd+9goICs27dOrNu3TojyUyePNmsW7fOfTrz7+V38IIKH8YY8+KLL5r4+HgTGBhorrzySpOdne1+buDAgSYlJcWj/4oVK0yrVq1MYGCgadiwoZk+fbrlin2TN+OckpJiJJWYBg4caL9wH+Pt+nw6wkfFeTvO33//venWrZsJDg429evXNyNGjDBHjhyxXLVv8nasn3/+eXPZZZeZ4OBgExMTY2655Raza9cuy1X7jk8++aTc79vfy++gwxi2XQEAAHsumGM+AACAbyB8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsOr/AejmQumx7IhfAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "\n", + "plt.hist(slice_label.view(-1).numpy(),bins = 5);\n", + "plt.title(\"Distribution of slices with and without tumour \\n 0 = no tumour, 1 = tumour\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97cbfc78-54b5-4a98-b0b3-60e3a71fd25e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "formats": "py:percent,ipynb" + }, + "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.5" + }, + "vscode": { + "interpreter": { + "hash": "a7e6f8385898884a13cbe220eefefb32cba5012927a94186742ddc14746e4dba" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py b/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py new file mode 100644 index 00000000..db954dba --- /dev/null +++ b/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py @@ -0,0 +1,201 @@ +# --- +# jupyter: +# jupytext: +# formats: py:percent,ipynb +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.14.4 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% + +# # Diff-SCM +# +# This tutorial illustrates how to load the 2D BRATS dataset. +# +# +# ## Setup environment + +# %% + + +get_ipython().system('python -c "import monai" || pip install -q "monai-weekly[pillow, tqdm, einops]"') +get_ipython().system('python -c "import matplotlib" || pip install -q matplotlib') +get_ipython().run_line_magic('matplotlib', 'inline') +print('done') + + +# ## Setup imports + +# %% + + +# Copyright 2020 MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import shutil +import tempfile +import time + +import matplotlib.pyplot as plt +import numpy as np +import torch +import torch.nn.functional as F +from monai import transforms +from monai.apps import DecathlonDataset +from monai.config import print_config +from monai.data import CacheDataset, DataLoader +from monai.utils import first, set_determinism +from torch.cuda.amp import GradScaler, autocast +from tqdm import tqdm + +from generative.inferers import DiffusionInferer + +# TODO: Add right import reference after deployed +from generative.networks.nets import DiffusionModelUNet +from generative.networks.schedulers import DDPMScheduler + +print_config() + + +# ## Setup data directory + +# %% + + +directory = os.environ.get("MONAI_DATA_DIRECTORY") +root_dir = tempfile.mkdtemp() if directory is None else directory +print(root_dir) +root_dir= '/tmp/tmp6o69ziv1' + + +# ## Set deterministic training for reproducibility + +# %% + + +set_determinism(42) + + +# ## Setup MedNIST Dataset and training and validation dataloaders +# In this tutorial, we will train our models on the MedNIST dataset available on MONAI +# (https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset). +# Here, we will use the "Hand" and "HeadCT", where our conditioning variable `class` will specify the modality. + +# %% + + +batch_size = 2 +channel = 0 # 0 = Flair +assert channel in [0, 1, 2, 3], "Choose a valid channel" + +train_transforms = transforms.Compose( + [ + transforms.LoadImaged(keys=["image","label"]), + transforms.EnsureChannelFirstd(keys=["image","label"]), + transforms.Lambdad(keys=["image"], func=lambda x: x[channel, :, :, :]), + transforms.AddChanneld(keys=["image"]), + transforms.EnsureTyped(keys=["image","label"]), + transforms.Orientationd(keys=["image","label"], axcodes="RAS"), + transforms.Spacingd( + keys=["image","label"], + pixdim=(3.0, 3.0, 2.0), + mode=("bilinear", "nearest"), + ), + transforms.CenterSpatialCropd(keys=["image","label"], roi_size=(64, 64, 64)), + transforms.ScaleIntensityRangePercentilesd(keys="image", lower=0, upper=99.5, b_min=0, b_max=1), + transforms.CopyItemsd(keys=["label"], times=1, names=["slice_label"]), + transforms.Lambdad(keys=["slice_label"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()), + ] +) +train_ds = DecathlonDataset( + root_dir=root_dir, + task="Task01_BrainTumour", + section="training", # validation + cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise + num_workers=4, + download=True, # Set download to True if the dataset hasnt been downloaded yet + seed=0, + transform=train_transforms, +) +nb_3D_images_to_mix = 2 +train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4) +print(f'Image shape {train_ds[0]["image"].shape}') + + +# %% + + +from typing import Dict +def get_batched_2d_axial_slices(data : Dict): + images_3D = data['image'] + batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1]) + slice_label = data['slice_label'] + #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float() + slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze() + return batched_2d_slices, slice_label + + +# ### Visualisation of the training images + +# %% + + +check_data = first(train_loader_3D) +print('check_data', check_data["image"].shape, check_data["slice_label"].shape) + + +# %% + + +batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data) +idx = list(torch.randperm(batched_2d_slices.shape[0])) +print('idx', idx, len(idx)) +slices = [0,30,45,63] +print(f"Batch shape: {batched_2d_slices.shape}") +print(f"Slices class: {slice_label[idx][slices].view(-1)}") +image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze() +plt.figure("training images", (12, 6)) +plt.imshow(image_visualisation, vmin=0, vmax=1, cmap="gray") +plt.axis("off") +plt.tight_layout() +plt.show() + + +# %% + + +slice_label.shape + + +# ## Check Distribution of Healthy / Unhealthy + +# %% + +subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))# +a,b = next(subset_2D) #what is a, what is b? +a.shape, b.shape + + +# %% + + +plt.hist(slice_label.view(-1).numpy(),bins = 5); +plt.title("Distribution of slices with and without tumour \n 0 = no tumour, 1 = tumour"); + + +# %% From cc7b704b2165504b5a05cd2293859ebace48c1b6 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 22 Feb 2023 17:31:22 +0100 Subject: [PATCH 04/12] anomaly detection tutorial is complete, the training needs to be checked --- .../networks/nets/diffusion_model_unet.py | 23 +- ...r_guidance_anomalydetection_tutorial.ipynb | 1836 ++++++----------- ...fier_guidance_anomalydetection_tutorial.py | 395 ++-- 3 files changed, 882 insertions(+), 1372 deletions(-) diff --git a/generative/networks/nets/diffusion_model_unet.py b/generative/networks/nets/diffusion_model_unet.py index 506b3010..729c0bd0 100644 --- a/generative/networks/nets/diffusion_model_unet.py +++ b/generative/networks/nets/diffusion_model_unet.py @@ -1761,7 +1761,7 @@ def __init__( for i in range(len(num_channels)): input_channel = output_channel output_channel = num_channels[i] - is_final_block = i == len(num_channels) - 1 + is_final_block = i == len(num_channels) #- 1 down_block = get_down_block( spatial_dims=spatial_dims, @@ -1825,7 +1825,15 @@ def __init__( ) self.up_blocks.append(up_block) - self.out = nn.Linear(16384, self.out_channels) + # self.out = nn.Linear(4096, self.out_channels) + self.out = nn.Sequential( + nn.Linear(8192, 512), + nn.ReLU(), + nn.Dropout(0.2), + nn.Linear(512, self.out_channels), + #nn.Sigmoid(), + ) + # out # self.out = nn.Sequential( # nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels[0], eps=norm_eps, affine=True), @@ -1877,14 +1885,15 @@ def forward( raise ValueError("model should have with_conditioning = True if context is provided") down_block_res_samples: List[torch.Tensor] = [h] for downsample_block in self.down_blocks: - h, res_samples = downsample_block(hidden_states=h, temb=emb, context=context) - for residual in res_samples: - down_block_res_samples.append(residual) - h=h.reshape(h.shape[0] ,-1) + h, _ = downsample_block(hidden_states=h, temb=emb, context=context) + # for residual in res_samples: + # down_block_res_samples.append(residual) + # # 5. mid - # h = self.middle_block(hidden_states=h, temb=emb, context=context) + + h = h.reshape(h.shape[0], -1) # # # 6. up # for upsample_block in self.up_blocks: diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb index 248180d7..fbeaf69c 100644 --- a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "75f2d5f3", "metadata": {}, "outputs": [ @@ -145,8 +145,7 @@ "!python /home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/setup.py install\n", "!python -c \"import monai\" || pip install -q \"monai-weekly[pillow, tqdm, einops]\"\n", "!python -c \"import matplotlib\" || pip install -q matplotlib\n", - "!python -c \"import seaborn\" || pip install -q seaborn\n", - "%matplotlib inline" + "!python -c \"import seaborn\" || pip install -q seaborn" ] }, { @@ -159,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "972ed3f3", "metadata": { "collapsed": false, @@ -169,17 +168,44 @@ }, "outputs": [ { - "ename": "ZipImportError", - "evalue": "bad local file header: '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mZipImportError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[4], line 29\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtorch\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcuda\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mamp\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m GradScaler, autocast\n\u001b[1;32m 27\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtqdm\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m tqdm\n\u001b[0;32m---> 29\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mgenerative\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01minferers\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m DiffusionInferer\n\u001b[1;32m 31\u001b[0m \u001b[38;5;66;03m# TODO: Add right import reference after deployed\u001b[39;00m\n\u001b[1;32m 32\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mgenerative\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnetworks\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnets\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdiffusion_model_unet\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m DiffusionModelUNet, DiffusionModelEncoder\n", - "File \u001b[0;32m:196\u001b[0m, in \u001b[0;36mget_code\u001b[0;34m(self, fullname)\u001b[0m\n", - "File \u001b[0;32m:752\u001b[0m, in \u001b[0;36m_get_module_code\u001b[0;34m(self, fullname)\u001b[0m\n", - "File \u001b[0;32m:598\u001b[0m, in \u001b[0;36m_get_data\u001b[0;34m(archive, toc_entry)\u001b[0m\n", - "\u001b[0;31mZipImportError\u001b[0m: bad local file header: '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg'" + "name": "stderr", + "output_type": "stream", + "text": [ + "Setting up a new session...\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "path ['/home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/tutorials/generative/classifier_guidance_anomalydetection', '/home/juliawolleb/anaconda3/envs/experiment/lib/python310.zip', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/lib-dynload', '', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/PyYAML-6.0-py3.10-linux-x86_64.egg', '/home/juliawolleb/PycharmProjects/Python_Tutorials/Calgary_Infants/calgary/HD-BET', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/lpips-0.1.4-py3.10.egg', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/tqdm-4.64.1-py3.10.egg', '/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg', '/home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/']\n", + "MONAI version: 1.1.dev2248\n", + "Numpy version: 1.23.2\n", + "Pytorch version: 1.12.1\n", + "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", + "MONAI rev id: 3400bd91422ccba9ccc3aa2ffe7fecd4eb5596bf\n", + "MONAI __file__: /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/monai/__init__.py\n", + "\n", + "Optional dependencies:\n", + "Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.\n", + "Nibabel version: 4.0.1\n", + "scikit-image version: 0.19.3\n", + "Pillow version: 9.2.0\n", + "Tensorboard version: NOT INSTALLED or UNKNOWN VERSION.\n", + "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", + "TorchVision version: 0.13.1\n", + "tqdm version: 4.64.1\n", + "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", + "psutil version: 5.9.4\n", + "pandas version: 1.5.3\n", + "einops version: 0.6.0\n", + "transformers version: NOT INSTALLED or UNKNOWN VERSION.\n", + "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", + "pynrrd version: NOT INSTALLED or UNKNOWN VERSION.\n", + "\n", + "For details about installing the optional dependencies, please visit:\n", + " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", + "\n" ] } ], @@ -198,9 +224,10 @@ "import shutil\n", "import tempfile\n", "import time\n", + "from typing import Dict\n", "import os\n", + "import torch.nn as nn\n", "import matplotlib.pyplot as plt\n", - "import seaborn\n", "import numpy as np\n", "import torch\n", "import torch.nn.functional as F\n", @@ -211,9 +238,14 @@ "from monai.utils import first, set_determinism\n", "from torch.cuda.amp import GradScaler, autocast\n", "from tqdm import tqdm\n", + "torch.multiprocessing.set_sharing_strategy('file_system')\n", + "import sys\n", + "sys.path.append('/home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/')\n", + "print('path', sys.path)\n", "\n", "from generative.inferers import DiffusionInferer\n", "\n", + "\n", "# TODO: Add right import reference after deployed\n", "from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet, DiffusionModelEncoder\n", "\n", @@ -221,7 +253,18 @@ "from generative.networks.schedulers.ddim import DDIMScheduler\n", "print_config()\n", "\n", - "\n" + "losstrain_window = viz.line(Y=torch.zeros((1)).cpu(), X=torch.zeros((1)).cpu(),\n", + " opts=dict(xlabel='epoch', ylabel='Loss', title='training loss'))\n", + "lossval_window = viz.line(Y=torch.zeros((1)).cpu(), X=torch.zeros((1)).cpu(),\n", + " opts=dict(xlabel='epoch', ylabel='Loss', title='val loss '))\n", + "\n", + "train_classifier=False\n", + "train_diffusionmodel=False\n", + "def visualize(img):\n", + " _min = img.min()\n", + " _max = img.max()\n", + " normalized_img = (img - _min)/ (_max - _min)\n", + " return normalized_img" ] }, { @@ -234,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 4, "id": "8b4323e7", "metadata": { "collapsed": false, @@ -242,21 +285,11 @@ "outputs_hidden": false } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/home/juliawolleb/PycharmProjects/MONAI/data_brats\n" - ] - } - ], + "outputs": [], "source": [ "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", "#root_dir = tempfile.mkdtemp() if directory is None else directory\n", - "root_dir='/home/juliawolleb/PycharmProjects/MONAI/data_brats'\n", - "\n", - "print(root_dir)" + "root_dir='/home/juliawolleb/PycharmProjects/MONAI/brats' #path to where the data is stored" ] }, { @@ -269,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 5, "id": "34ea510f", "metadata": { "collapsed": false, @@ -279,13 +312,15 @@ }, "outputs": [], "source": [ - "set_determinism(42)" + "set_determinism(36)" ] }, { "cell_type": "markdown", - "id": "fac55e9d", - "metadata": {}, + "id": "c3f70dd1-236a-47ff-a244-575729ad92ba", + "metadata": { + "tags": [] + }, "source": [ "## Setup BRATS Dataset for 2D slices and training and validation dataloaders\n", "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150" @@ -293,7 +328,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "id": "da1927b0", "metadata": { "collapsed": false, @@ -306,14 +341,25 @@ "name": "stderr", "output_type": "stream", "text": [ - "Task01_BrainTumour.tar: 71%|█████████▉ | 5.03G/7.09G [04:43<01:46, 20.8MB/s]" + "/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/monai/utils/deprecate_utils.py:107: FutureWarning: : Class `AddChannel` has been deprecated since version 0.8. please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.\n", + " warn_deprecated(obj, msg, warning_category)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "download training set\n", + "len train data 388\n", + "total slices torch.Size([17072, 1, 64, 64])\n", + "total lbaels torch.Size([17072])\n", + "download val set\n" ] } ], "source": [ "\n", "\n", - "batch_size = 2\n", "channel = 0 # 0 = Flair\n", "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", "\n", @@ -336,59 +382,114 @@ " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", " ]\n", ")\n", + "print('download training set')\n", "train_ds = DecathlonDataset(\n", " root_dir=root_dir,\n", " task=\"Task01_BrainTumour\",\n", " section=\"training\", # validation\n", " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", " num_workers=4,\n", - " download=True, # Set download to True if the dataset hasnt been downloaded yet\n", + " download=False, # Set download to True if the dataset hasnt been downloaded yet\n", " seed=0,\n", " transform=train_transforms,\n", ")\n", - "nb_3D_images_to_mix = 2\n", - "train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", - "print(f'Image shape {train_ds[0][\"image\"].shape}')\n", + "print('len train data', len(train_ds))\n", "\n", + "def get_batched_2d_axial_slices(data : Dict):\n", + " images_3D = data['image']\n", + " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1)[10:-10], 0).squeeze(-1) # we cut the lowest and highest 10 slices, because we are interested in the middle part of the brain.\n", + " slice_label = data['slice_label']\n", + " slice_label = torch.cat(slice_label.split(1, dim = -1)[10:-10],0).squeeze()\n", + " return batched_2d_slices, slice_label\n", "\n", + "preprocessing_train=False\n", + "if preprocessing_train == True:\n", + " train_loader_3D = DataLoader(train_ds, batch_size=1, shuffle=True, num_workers=4)\n", + " print(f'Image shape {train_ds[0][\"image\"].shape}')\n", "\n", + " data_2d_slices=[]\n", + " data_slice_label = []\n", + " check_data = first(train_loader_3D)\n", + " for i, data in enumerate(train_loader_3D):\n", + " b2d, slice_label2d = get_batched_2d_axial_slices(data)\n", + " data_2d_slices.append(b2d)\n", + " data_slice_label.append(slice_label2d)\n", + " total_train_slices=torch.cat(data_2d_slices,0)\n", + " total_train_labels=torch.cat(data_slice_label,0)\n", "\n", + " torch.save(total_train_slices, 'total_train_slices.pt')\n", + " torch.save(total_train_labels, 'total_train_labels.pt')\n", "\n", + "else:\n", + " total_train_slices=torch.load('total_train_slices.pt')\n", + " total_train_labels=torch.load('total_train_labels.pt')\n", + " print('total slices', total_train_slices.shape)\n", + " print('total lbaels', total_train_labels.shape)\n", "\n" ] }, + { + "cell_type": "markdown", + "id": "fac55e9d", + "metadata": { + "tags": [] + }, + "source": [ + "## Setup BRATS Dataset for 2D slices validation dataloader\n", + "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150" + ] + }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 7, "id": "73d72110-a8b3-4e03-91cc-1dab4d5a7b87", - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2023-02-02 10:39:45,467 - INFO - Verified 'Task01_BrainTumour.tar', md5: 240a19d752f0d9e9101544901065d872.\n", - "2023-02-02 10:39:45,469 - INFO - File exists: /tmp/tmpyurp7egh/Task01_BrainTumour.tar, skipped downloading.\n", - "2023-02-02 10:39:45,471 - INFO - Non-empty folder exists in /tmp/tmpyurp7egh/Task01_BrainTumour, skipped extracting.\n", - "Image shape torch.Size([1, 64, 64, 64])\n" + "total slices torch.Size([4224, 1, 64, 64])\n", + "total lbaels torch.Size([4224])\n" ] } ], "source": [ - "\n", "val_ds = DecathlonDataset(\n", " root_dir=root_dir,\n", " task=\"Task01_BrainTumour\",\n", " section=\"validation\", # validation\n", " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", " num_workers=4,\n", - " download=True, # Set download to True if the dataset hasnt been downloaded yet\n", + " download=False, # Set download to True if the dataset hasnt been downloaded yet\n", " seed=0,\n", " transform=train_transforms,\n", ")\n", - "val_loader_3D = DataLoader(val_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", - "print(f'Image shape {val_ds[0][\"image\"].shape}')\n", - "\n" + "\n", + "\n", + "preprocessing_val=False\n", + "if preprocessing_val == True:\n", + " val_loader_3D = DataLoader(val_ds, batch_size=1, shuffle=True, num_workers=4)\n", + " print(f'Image shape {val_ds[0][\"image\"].shape}')\n", + " print('len val data', len(val_ds))\n", + " data_2d_slices_val=[]\n", + " data_slice_label_val = []\n", + " for i, data in enumerate(val_loader_3D):\n", + " b2d, slice_label2d = get_batched_2d_axial_slices(data)\n", + " data_2d_slices_val.append(b2d)\n", + " data_slice_label_val.append(slice_label2d)\n", + " total_val_slices=torch.cat(data_2d_slices_val,0)\n", + " total_val_labels=torch.cat(data_slice_label_val,0)\n", + " torch.save(total_val_slices, 'total_val_slices.pt')\n", + " torch.save(total_val_labels, 'total_val_labels.pt')\n", + "\n", + "else:\n", + " total_val_slices=torch.load('total_val_slices.pt')\n", + " total_val_labels=torch.load('total_val_labels.pt')\n", + " print('total slices', total_val_slices.shape)\n", + " print('total lbaels', total_val_labels.shape)" ] }, { @@ -405,94 +506,6 @@ "\n" ] }, - { - "cell_type": "markdown", - "id": "7f108ebb", - "metadata": {}, - "source": [ - "### Visualisation of the training images" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "4105a01f", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Batch shape: torch.Size([128, 1, 64, 64])\n", - "Slices class: tensor([0., 0., 0., 1.])\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "\n", - "from typing import Dict\n", - "def get_batched_2d_axial_slices(data : Dict):\n", - " images_3D = data['image']\n", - " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1])\n", - " slice_label = data['slice_label']\n", - " #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float()\n", - " slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze()\n", - " return batched_2d_slices, slice_label\n", - "\n", - "check_data = first(train_loader_3D)\n", - "batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data)\n", - "idx = list(torch.randperm(batched_2d_slices.shape[0]))\n", - "slices = [0,30,45,63]\n", - "print(f\"Batch shape: {batched_2d_slices.shape}\")\n", - "print(f\"Slices class: {slice_label[idx][slices].view(-1)}\")\n", - "image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze()\n", - "plt.figure(\"training images\", (12, 6))\n", - "plt.imshow(image_visualisation, vmin=0, vmax=1, cmap=\"gray\")\n", - "plt.axis(\"off\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "4249e4be-f7e7-48e9-9aa9-436da8c1d1e5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(torch.Size([2, 1, 64, 64]), torch.Size([2]))" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))#\n", - "a,b = next(subset_2D) #what is a, what is b? Are these the next images? \n", - "a.shape, b.shape" - ] - }, { "cell_type": "markdown", "id": "08428bc6", @@ -501,19 +514,12 @@ "### Define network, scheduler, optimizer, and inferer\n", "At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using\n", "the original DDPM scheduler containing 1000 timesteps in its Markov chain, and a 2D UNET with attention mechanisms\n", - "in the 3rd level, each with 1 attention head (`num_head_channels=64`).\n", - "\n", - "In order to pass conditioning variables with dimension of 1 (just specifying the modality of the image), we use:\n", - "\n", - "`\n", - "with_conditioning=True,\n", - "cross_attention_dim=1,\n", - "`" + "in the 3rd level, each with 1 attention head (`num_head_channels=64`).\n" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 8, "id": "bee5913e", "metadata": { "collapsed": false, @@ -539,7 +545,7 @@ ")\n", "model.to(device)\n", "\n", - "scheduler = DDPMScheduler(\n", + "scheduler = DDIMScheduler(\n", " num_train_timesteps=1000,\n", ")\n", "\n", @@ -561,13 +567,158 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 9, "id": "6c0ed909", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false - }, + } + }, + "outputs": [], + "source": [ + "n_epochs =75\n", + "batch_size=32\n", + "val_interval = 1\n", + "epoch_loss_list = []\n", + "val_epoch_loss_list = []\n", + "train_diffusionmodel=False\n", + "if train_diffusionmodel==False:\n", + " model.load_state_dict(torch.load(\"./diffusion_model.pt\", map_location={'cuda:0': 'cpu'}))\n", + "else:\n", + " scaler = GradScaler()\n", + " total_start = time.time()\n", + " for epoch in range(n_epochs):\n", + " model.train()\n", + " epoch_loss = 0\n", + " indexes = list(torch.randperm(total_train_slices.shape[0])) #shuffle training data new\n", + " data_train = total_train_slices[indexes] # shuffle the training data\n", + " labels_train = total_train_labels[indexes]\n", + " subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size))\n", + "\n", + " subset_2D_val = zip(total_val_slices.split(1), total_val_labels.split(1)) #\n", + "\n", + " progress_bar = tqdm(enumerate(subset_2D), total=len(indexes), ncols=10)\n", + " progress_bar.set_description(f\"Epoch {epoch}\")\n", + " for step, (a,b) in progress_bar:\n", + " images = a.to(device)\n", + " classes = b.to(device)\n", + " optimizer.zero_grad(set_to_none=True)\n", + " timesteps = torch.randint(0, 1000, (len(images),)).to(device)\n", + "\n", + " with autocast(enabled=True):\n", + " # Generate random noise\n", + " noise = torch.randn_like(images).to(device)\n", + "\n", + " # Get model prediction\n", + " noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps) #remove the class conditioning\n", + "\n", + " loss = F.mse_loss(noise_pred.float(), noise.float())\n", + "\n", + " scaler.scale(loss).backward()\n", + " scaler.step(optimizer)\n", + " scaler.update()\n", + " if step%20==0:\n", + " print('step', step, loss)\n", + "\n", + " epoch_loss += loss.item()\n", + "\n", + " progress_bar.set_postfix(\n", + " {\n", + " \"loss\": epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " epoch_loss_list.append(epoch_loss / (step + 1))\n", + "\n", + "\n", + " if (epoch) % val_interval == 0:\n", + " model.eval()\n", + " val_epoch_loss = 0\n", + " progress_bar_val = tqdm(enumerate(subset_2D_val))\n", + " progress_bar.set_description(f\"Epoch {epoch}\")\n", + " for step, (a, b) in progress_bar_val:\n", + " images = a.to(device)\n", + " classes = b.to(device)\n", + "\n", + " timesteps = torch.randint(0, 1000, (len(images),)).to(device)\n", + " with torch.no_grad():\n", + " with autocast(enabled=True):\n", + " noise = torch.randn_like(images).to(device)\n", + " noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps)\n", + " val_loss = F.mse_loss(noise_pred.float(), noise.float())\n", + "\n", + " val_epoch_loss += val_loss.item()\n", + " progress_bar.set_postfix(\n", + " {\n", + " \"val_loss\": val_epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " val_epoch_loss_list.append(val_epoch_loss / (step + 1))\n", + "\n", + " total_time = time.time() - total_start\n", + " torch.save(model.state_dict(), \"./diffusion_model.pt\") #save the trained model\n", + "\n", + " print(f\"train diffusion completed, total time: {total_time}.\")\n", + "\n", + " plt.style.use(\"seaborn-bright\")\n", + " plt.title(\"Learning Curves Diffusion Model\", fontsize=20)\n", + " plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", + " plt.plot(\n", + " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", + " val_epoch_loss_list,\n", + " color=\"C1\",\n", + " linewidth=2.0,\n", + " label=\"Validation\",\n", + " )\n", + " plt.yticks(fontsize=12)\n", + " plt.xticks(fontsize=12)\n", + " plt.xlabel(\"Epochs\", fontsize=16)\n", + " plt.ylabel(\"Loss\", fontsize=16)\n", + " plt.legend(prop={\"size\": 14})\n", + " plt.show()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "45fab83a-b4c8-42cb-96c9-4e9f1e191111", + "metadata": {}, + "source": [ + "### Model training of the Classification Model\n", + "#First, we define the classification model. It follows the encoder architecture of the diffusion model, combined with linear layers for binary classification between healthy and diseased slices.\n", + "#Here, we are training our binary classification model for 20 epochs." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "44cc6928-2525-4e61-8805-15b409097bbb", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "\n", + "\n", + "classifier = DiffusionModelEncoder(\n", + " spatial_dims=2,\n", + " in_channels=1,\n", + " out_channels=2,\n", + " num_channels=(32,64,128),\n", + " attention_levels=(False, True, True),\n", + " num_res_blocks=1,\n", + " num_head_channels=64,\n", + " with_conditioning=False,\n", + ")\n", + "classifier.to(device)\n", + "batch_size=32" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "de18d5cb-68e7-407c-afe9-8efd7a5a904a", + "metadata": { "lines_to_next_cell": 0 }, "outputs": [ @@ -575,728 +726,347 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 1%| | 1/128 [00:00<00:16, 7.89it/s, loss=0.982]" + "Epoch 0: : 534it [00:29, 18.32it/s, loss=0.576] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 0 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 1 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 2%|▍ | 3/128 [00:00<00:13, 9.55it/s, loss=1]" + "Epoch 0: : 132it [00:02, 56.95it/s, val_loss=0.305]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 2 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 3 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 4 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" + "final step val 131\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 5%|▋ | 7/128 [00:00<00:11, 10.26it/s, loss=0.992]" + "Epoch 1: : 534it [00:29, 18.34it/s, loss=0.576] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 5 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 6 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 7 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 7%|▊ | 9/128 [00:00<00:11, 10.38it/s, loss=0.988]" + "Epoch 1: : 132it [00:02, 56.37it/s, val_loss=0.315]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 8 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 9 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 10 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" + "final step val 131\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 10%|█ | 13/128 [00:01<00:10, 10.52it/s, loss=0.981]" + "Epoch 2: : 534it [00:31, 16.99it/s, loss=0.573] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 11 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 12 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 13 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 12%|█▎ | 15/128 [00:01<00:10, 10.51it/s, loss=0.978]" + "Epoch 2: : 132it [00:02, 52.98it/s, val_loss=0.312]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 14 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 15 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 16 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n" + "final step val 131\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 15%|█▋ | 19/128 [00:01<00:10, 10.65it/s, loss=0.971]" + "Epoch 3: : 534it [00:31, 17.21it/s, loss=0.572] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 17 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 18 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 19 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 16%|█▊ | 21/128 [00:02<00:10, 10.22it/s, loss=0.968]" + "Epoch 3: : 132it [00:02, 53.04it/s, val_loss=0.334]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 20 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 21 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n" + "final step val 131\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 18%|█▉ | 23/128 [00:02<00:10, 9.82it/s, loss=0.964]" + "Epoch 4: : 534it [00:31, 17.16it/s, loss=0.569] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 22 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 23 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: 20%|██▏ | 25/128 [00:02<00:10, 9.50it/s, loss=0.961]" + "Epoch 4: : 132it [00:02, 52.93it/s, val_loss=0.36] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "step 24 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 25 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 21%|██▎ | 27/128 [00:02<00:10, 9.34it/s, loss=0.956]" + "final step val 131\n", + "train completed, total time: 167.07577848434448.\n", + "epl 5\n" ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 26 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([1., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([1., 1.], device='cuda:0')\n", - "step 27 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 1.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 1.], device='cuda:0')\n" - ] - }, + "data": { + "image/png": "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aDk4AUCXLl0QHByMzZs3W9wHERER1Q82H55Onz6Ntm3bmoQaAAgPD5e3V2bw4MFQq9Xw9vZGbGxsmX1Kvi9p88F+qtIHERERPRxs/rJdRkYGgoKCypR7e3vL28vTpEkTTJ8+HVFRUfDw8MCpU6ewYMECREVF4cCBA4iIiDBpo6TNB/upqI/CwkIUFhbK3+fk5FRtYkRERGSXbD48AYAkSdXaNmDAAAwYMED+vnv37hg0aBDCwsIwc+ZMbNmypUptVdTH/PnzMWfOnHK3ExERUf1i85ftfHx8zJ75yczMBGD+bFFFWrRogW7duiEpKcmkD8D8WazMzMwK+5g2bRqys7PlT3JysqLxEBERkX2x+fAUFhaGc+fOQa/Xm5SfOnUKQPGddEoJIaBS/TH1kjZK2nywn4r6cHR0hIeHh8mHiIiI6i+bD09Dhw5FXl4eNm7caFIeHx8PPz8/dO7cWVF7V65cwYEDBxAVFSWXNWvWDJ06dcLq1athMBjk8qSkJJw/fx6xsbGWTYKIiIjqDZtf8zRw4ED07dsXkydPRk5ODlq3bo2EhARs27YNq1evlp/xNGHCBMTHx+PSpUsIDAwEAPTp0wfdu3dHeHi4vGB84cKFkCQJ7777rkk/7733Hvr27Ythw4bhxRdfRFpaGt58802EhoZi3LhxtT5vIiIisk02H54AYNOmTZg+fTpmzpyJzMxMhISEICEhASNGjJDrGAwGGAwGCCHksrCwMKxfvx4ffPAB8vPz4evri169euHtt99GcHCwSR89e/ZEYmIiZs6ciSFDhsDFxQWDBw/G+++/z6eLExERkUwSpdMGWSwnJweenp7Izs7m+iciIiI7oeTvt82veSIiIiKyJQxPRERERAowPBEREREpwPBEREREpADDExEREZECDE9ERERECjA8ERERESnA8ERERESkAMMTERERkQIMT0REREQKMDwRERERKcDwRERERKQAwxMRERGRAgxPRERERAowPBEREREpwPBEREREpADDExEREZECDE9ERERECjA8ERERESnA8ERERESkAMMTERERkQIMT0REREQKMDwRERERKcDwRERERKQAwxMRERGRAgxPRERERAowPBEREREpwPBEREREpADDExEREZECDE9ERERECjA8ERERESnA8ERERESkAMMTERERkQKauh4AERHVTzqdDgaDoa6H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szRs2LDMXXel9erVy1pdEREREdUZq7/bLj8/H5cuXUJubi7c3d3RqlUrODs7W7sbIiIiojphtfeObN++HT179oSnpyciIiLQrVs3REREwNPTE7169cKOHTus1RUREdFDZ/bs2ZAkSX4cUAlJksy+y1VpO9Y0duxYSJKEq1ev1lgfdckq4Wn27NmIiYnB3r17odfrodVq4efnB61WC71ej927d2PgwIGYPXu2NbojIiKyOXFxcZAkCevWrauwXkZGBhwdHdGwYUMUFRXV0uisa9WqVZAkCatWrarrodQJi8PTtm3b8M4770ClUuHFF1/E+fPnUVBQgOTkZBQUFOD8+fN48cUX5Rfvbt++3RrjJiIisikTJkwA8Mfd5eVZvXo1ioqKMHr0aJNXm1XXuXPn8J///Mfidqxp/vz5OHfuHJo1a1bXQ6kRFoenf/3rX5AkCStWrMBHH32ENm3amGxv06YNPvroI6xYsQJCCCxZssTSLomIiGxO79690aJFC+zcuRPJycnl1isJVyVhy1IhISFo3ry5VdqylqZNmyIkJARarbauh1IjLA5PR44cgb+/P0aPHl1hvVGjRiEgIACHDx+2tEsiIiKbI0kSxo0bB6PRiPj4eLN1fv75Z5w4cQKdOnWCt7c3Zs2ahaioKPj6+sLR0REtWrTAiy++iLS0NEX9mlvzlJycjLi4OHh7e8PNzQ09evTA3r17zbZRVFSEpUuXon///ggICICjoyN8fX0RGxuLX375xaTu2LFjMW7cOADAuHHjIEmS/Cldp7w1T/Hx8YiKioKbmxvc3NwQFRVl9ue1e/duSJKE2bNn49ixY+jfvz/c3d3h6emJoUOH1ul6KovDU25ubpVfudK4cWPcvXvX0i6JiIhs0rhx46BSqbBq1SoIIcpsL33Wae/evVi0aBEaN26MuLg4vPLKK2jVqhU++eQTREdHIzs7u9rj+P333xEdHY1169ahU6dO+Nvf/gZvb2/07dtXfp1aaZmZmZg6dSoKCwsRExOD//f//h969uyJxMREdOnSBUeOHJHrPv3003jqqacAAE899RRmzZolfyrz//7f/8PYsWNx48YNTJgwARMnTkRKSgrGjh2Lv//972b3OXr0KJ544gloNBq88MIL6NixI/773/+iT58+KCgoqOZPyELCQi1bthTu7u4iLy+vwnp5eXnCzc1NtGzZ0tIubVp2drYAILKzs+t6KEREtSo/P1+cPXtW5Ofn1/VQ6lT//v0FALF7926T8oKCAtGgQQPh4uIisrOzxa1bt0Rubm6Z/ePj4wUAMXfuXJPyWbNmCQBi165dJuUARI8ePUzKxowZY7aN5cuXCwBl2ikoKBA3btwoM5bTp08LNzc30adPH5PylStXCgBi5cqVZn8GJf1fuXJFLtu7d68AINq2bSuysrLk8qysLBESEiIAiH379snlu3btkse6bt06k/ZHjx4tAIiEhASz/T+oKr+bSv5+W/ycp/79+2P58uX4y1/+glWrVpld/FZUVISJEyfi3r17GDBggKVdEhGRHeqcPR43jZl1PYwKNVF545DnCovaGD9+PLZv344VK1agR48ecvnmzZtx584djBkzBh4eHvDw8DC7/+jRo/HKK69g586dmD59uuL+i4qKsH79evj6+uLVV1812TZx4kQsWrQIFy5cMCl3dHQ0u7i7ffv2ePLJJ7F9+3bodDqL1jCV3Jk3e/ZseHp6yuWenp6YNWsW4uLisGrVKnTr1s1kv+7du2P48OEmZePHj8eXX36JI0eOYMSIEdUeU3VZHJ7eeustrF+/HuvXr8fu3bvxl7/8Be3atYOvry/S0tJw9uxZfP7557h16xY8PT0xbdo0a4ybiIjszE1jJlJEel0Po2JGy5t4+umn4ePjg6+//hofffQR3N3dAQArVhSHsvHjx8t1N23ahOXLl+PYsWO4c+cODAaDvC01NbVa/Zfc9d6rVy84OTmZbFOpVOjSpUuZ8AQAx48fx8KFC7F//37cvHkTOp3OZPvt27fRtGnTao0JgLx2ytz6rJKy48ePl9n26KOPlinz9/cHAGRlZVV7PJawODwFBARg69atePbZZ5GcnIy5c+eWqSOEQPPmzbFhwwYEBARY2iUREdmhJipvq4STmtRE5W1xGw4ODhg1ahSWLFmCDRs2YMKECUhOTsYPP/yANm3aoHv37gCARYsW4bXXXkOjRo3Qr18/+Pv7y2/kWLx4MQoLC6vVf8laKV9fX7Pbza1TPnjwoPwatX79+qFNmzZwc3ODJEn473//ixMnTlR7PCVycnKgUqnQqFEjs2NSqVRm13mVPktVQqMpji+lw2ZtssrrWTp37oxff/0Va9euxY4dO3DhwgXk5eXBzc0NwcHB6N+/P+Li4nDlyhWcPHkS4eHh1uiWiIjsiKWXw+zJhAkTsGTJEqxYsQITJkzAqlWrYDQa5bNOer0e7777Lvz8/HD8+HGTQCGEwMKFC6vdd0nYKO+OvVu3bpUpmzdvHgoLC7F//3507drVZFtSUhJOnDhR7fGU8PDwgNFoRHp6eplgl5aWBqPRWO6lTFtjtXfbOTs7Y8KECRU+t6JHjx64c+cO9Hq9tbolIiKyOWFhYXj88cdx8OBB/Prrr1i1ahXUajXGjBkDoPgSWHZ2Nnr37l3mTMzRo0eRn59f7b4feeQRODk54ejRoygoKDC5dGc0GnHw4MEy+1y6dAne3t5lgtO9e/dw7NixMvXVajUAZWd+OnTogF9++QW7d+/Gs88+a7Jtz549AIDIyMgqt1eXrPZuu6oSZm7dJCIiqm9KTiZMnDgRly9fRkxMjLxmyNfXF87Ozjh27Bju3bsn73Pnzh288sorFvXr4OCAZ599FmlpaVi0aJHJtn//+99m1zsFBgbizp07OHPmjFxmMBjw2muvIT297Do1b+/iy5s3btyo8rhKguOcOXOQk5Mjl+fk5GDOnDkmdWyd1c48ERER0R/i4uLw97//HQcOHABg+kTxkleaLVq0CBERERgyZAhycnKwdetWBAYGws/Pz6K+FyxYgB9++AEzZszA/v370aFDB5w7dw6JiYno168fduzYYVL/lVdewY4dO9CtWzc8++yzcHJywu7du5GSkoKePXuWeYlwdHQ0nJ2dsXjxYuTk5Mhnz958881yx9S9e3e88sorWLp0KUJDQ/HMM89ACIFNmzYhOTkZf/vb3+T1YLau1s88ERERPQw8PDzw5z//GUDxguhBgwaZbJ8/fz7mzZsHSZKwbNkyfP/99xgxYgR27Nhh8WtNmjZtioMHD2L48OFISkrCkiVLkJGRge+//x7R0dFl6g8ePBhff/01goKCsHr1aqxduxYhISE4fPgwAgMDy9T39vbG119/jTZt2uCTTz7BtGnTqnQ3/b/+9S+sWLECTZo0wWeffYbPP/8cTZo0wYoVK+zq9W2SqMXraI0aNUJmZmadrY6vDTk5OfD09ER2drbdLHwjIrKGgoICXLlyBS1btixzizxRXarK76aSv98880RERESkAMMTERERkQKKF4z/5z//qXZnlj5gi4iIiKiuKQ5PY8eOhSRJ1epMCFHtfYmIiIhsgeLw1Lx5cwYgIiIiemgpDk9Xr16tgWEQERER2QcuGCciIiJSgOGJiIisiq/hIltj7d9JhiciIrKKkpfF6nS6Oh4JkamS38mS31FLMTwREZFVaLVaODo6Ijs7m2efyGYIIZCdnQ1HR0eLX3tTgi8GJiIiq2nYsCFSUlJw48YNeHp6QqvV8g5tqhNCCOh0OmRnZyMvLw/NmjWzWtsMT0REZDUl7wS7ffs2UlJS6ng0RICjoyOaNWtm1ffNMjwREZFVeXh4wMPDAzqdrl6/CJ5sn1qtttqlutIYnoiIqEZotdoa+cNFVNe4YJyIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIF7CI85eXlYerUqfDz84OTkxMiIyOxbt06xe3MmDEDkiQhNDTU7PadO3ciOjoaLi4uaNiwIcaOHYu0tDRLh09ERET1iF2Ep9jYWMTHx2PWrFnYunUrHn/8ccTFxWHt2rVVbuP48eP44IMP0LhxY7Pb9+zZg4EDB6Jx48bYsmULlixZgp07d6J3794oLCy01lSIiIjIzklCCFHXg6hIYmIiBg0ahLVr1yIuLk4u79evH86cOYPr169DrVZX2IZer8fjjz+O7t2748SJE7h9+zZOnz5tUqdTp064e/cuTpw4AY2m+H3JBw8eRNeuXbFs2TJMnjy5SuPNycmBp6cnsrOz4eHhoXC2REREVBeU/P22+TNPmzdvhpubG4YNG2ZSPm7cOKSmpuLQoUOVtrFgwQJkZmZi3rx5ZrenpKTgyJEjGD16tBycAKBLly4IDg7G5s2bLZsEERER1Rs2H55Onz6Ntm3bmoQaAAgPD5e3V+Ts2bOYO3cuPvnkE7i5uZXbR+k2H+ynsj6IiIjo4aGpvErdysjIQFBQUJlyb29veXt5jEYjxo8fj9jYWMTExFTYR+k2H+ynoj4KCwtN1kTl5OSUW5eIiIjsn82feQIASZKqte3DDz/ExYsXsXjxYov6qaiP+fPnw9PTU/4EBARUqS8iIiKyTzYfnnx8fMye+cnMzARg/mwRAFy/fh0zZ87ErFmz4ODggKysLGRlZUGv18NoNCIrKwv5+flyH4D5s1iZmZnl9gEA06ZNQ3Z2tvxJTk5WPEciIiKyHzYfnsLCwnDu3Dno9XqT8lOnTgFAuc9sunz5MvLz8zFlyhQ0aNBA/hw4cADnzp1DgwYNMG3aNJM2Stp8sJ/y+gAAR0dHeHh4mHyIiIio/rL58DR06FDk5eVh48aNJuXx8fHw8/ND586dze4XGRmJXbt2lflERESgRYsW2LVrF15++WUAQLNmzdCpUyesXr0aBoNBbiMpKQnnz59HbGxszU2QiIiI7IrNLxgfOHAg+vbti8mTJyMnJwetW7dGQkICtm3bhtWrV8vPeJowYQLi4+Nx6dIlBAYGwsvLCz179izTnpeXF/R6fZlt7733Hvr27Ythw4bhxRdfRFpaGt58802EhoZi3LhxtTBTIiIisgc2f+YJADZt2oTRo0dj5syZGDBgAA4dOoSEhAQ899xzch2DwQCDwYDqPvOzZ8+eSExMxO+//44hQ4bglVdewZNPPokffvgBjo6O1poKERER2Tmbf8K4veETxomIiOxPvXrCOBEREZEtYXgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBTR1PQAiIiKiyhQJHfbqjyOx6CAO6c9gr8cnUEvqOhkLwxMRERHZpN+Nt7FV9xMSiw5ip+4I8pAvbzusP4dobWidjIvhiYiIiGyCURhx1PArvis6gK26n3DMcN5sPTXUOG24xPBERERED59sYx6+1x9GYtFBbNMlIU3cMVuvoeSFAdooxGij0VfbCQ1UHrU80j8wPBEREVGtEULgV+M1JBYdRKLuIA7oT0IPg9m6keo2GKjtghhtF3TStK2zNU4PYngiIiKiGlUgCrFHdxxbdcWB6bIx1Ww9Vzijt7YjYrRdMNAhGs1UjWp5pFXD8ERERERWl2JMl88u/aA7insoMFsvSOWHQdquGOgQjR6aDnCUHGp5pMoxPBEREZHFDMKAw/pzSNQdRKLuAE4YfjNbTwM1umkiEKPtghiHLnhE1RySJNXyaC3D8ERERETVcseYgx26w0jUHcR23SHcFllm6/lKDTBQG40Yhy7oo3kcniq32h2oldnFE8bz8vIwdepU+Pn5wcnJCZGRkVi3bl2l++3cuRN9+/aFn58fHB0d4evri169eiExMbFM3Z49e0KSpDKfAQMG1MSUiIiI7I4QAmf0l7EwfzV65ryIJlmD8dzdWVhTtL1McHpM/QhmOI3DTx7/xg2vb/CF23Q84/Ck3QcnwE7OPMXGxuLIkSNYsGABgoODsXbtWsTFxcFoNGLkyJHl7peRkYH27dtj4sSJaNKkCTIzM/Hpp59i0KBB+PLLLzFq1CiT+kFBQVizZo1JmZeXV01MiYiIyC7ki0Ls0v1c/LBK3UFcM940W88Nzuir7YQYhy4YoI1CU1XDWh5p7ZGEEKKuB1GRxMREDBo0SA5MJfr164czZ87g+vXrUKurfuuiTqdDy5YtERQUhL1798rlPXv2xO3bt3H69GmLxpuTkwNPT09kZ2fDw6PunkFBRERUXdcNN5F4Pyzt0v2MfBSarResCih+lIBDFzyhiYCDpK3lkVqPkr/fNn/mafPmzXBzc8OwYcNMyseNG4eRI0fi0KFD6NKlS5Xb02q18PLygkZj81MnIiKqFXqhR5L+zP3F3gdx2nDZbD0tNOiuiUSMQ/Gzl9qoA2p5pLbB5hPE6dOn0bZt2zJhJzw8XN5eWXgyGo0wGo1IS0vD8uXLceHCBbz33ntl6l26dAne3t7IyclBYGAgRowYgRkzZsDZ2dl6EyIiIrIBGcZsbNclIVH3E7brknBH5Jqt11TywUCHaMRou6C3tiPcJddaHqntsfnwlJGRgaCgoDLl3t7e8vbKxMTEYPv27QAADw8PrF+/HoMGDTKp061bNwwfPhwhISHIz8/H1q1bsXDhQuzfvx+7du2CSmV+bX1hYSEKC/84nZmTk1PluREREdUWIQROGS4hUXcQ3+kO4pD+DIwwlqknQUJHdVsMun92KVLdBirJLu4vqzU2H54AVPj8h6o8G2Lp0qXIysrC77//jtWrV2P48OGIj483WUM1d+5ck31iYmLQokULvPbaa9iyZQuGDh1qtu358+djzpw5VZwJERFR7bknCvCD7uj9J3v/hBvGNLP1PCRX9NN2Row2GgO00fBVNajlkdoXm18wHh0dDYPBgMOHD5uUnzlzBqGhoVi+fDkmTZqkqM2BAwfi0KFDuH37drlnlADg1q1baNKkCf7xj3+YvcwHmD/zFBAQwAXjRERUJ64YUu+vXfoJu3XHUIgis/XaqlrIl+O6asKhlezifEqNqVcLxsPCwpCQkAC9Xm+y7unUqVMAgNDQUMVtdurUCdu2bUN6ejoaN25caf2KApajoyMcHR0Vj4GIiMgadEKPg/pTSNQdxFbdQZw1XDVbzxEO6KHtgBhtcWAKUjer3YHWIzYfnoYOHYrPP/8cGzduxPDhw+Xy+Ph4+Pn5oXPnzoraE0Jgz5498PLygo+PT4V14+PjAQBRUVHKB05ERFRD0o13sE2XhETdQezQHUa2yDNbr5nU6P6dcdHope0IV4k3QFmDzYengQMHom/fvpg8eTJycnLQunVrJCQkYNu2bVi9erX8jKcJEyYgPj4ely5dQmBgIADgqaeeQkREBCIjI+Hj44PU1FSsWrUKe/bswccffyyfydq3bx/mzZuHoUOHIigoCAUFBdi6dSs+++wz9OrVC0OGDKmz+RMREQkhcNxwsfhyXNFBHDachUDZVTcSJHTWtMcgbfFi73B1a7t7b5w9sPnwBACbNm3C9OnTMXPmTGRmZiIkJAQJCQkYMWKEXMdgMMBgMKD0Eq6uXbvi66+/xkcffYScnBx4eXmhY8eO+Pbbb03utmvatCnUajXeffdd3L59G5IkoU2bNnjnnXfw6quvVnjZjoiIqCbkiXv4QXe0+HJc0U9IFbfN1vOS3NFf2xkx2i7or+2Mhiqv2h3oQ8jmF4zbGz5hnIiIqus3ww357NJe/XEUQWe2Xqg6qPjJ3tpoRGtCoXnIF3tbQ71aME5ERFRfFQkd9utP3j+7dBDnjdfN1nOCA57UPoaY+5fjAtVNanmkVBrDExERUS26Zcwsfslu0UF8rzuMXNwzW6+5qjFitF0wUNsFT2ofhYvkVMsjpfIwPBEREdUgozDimOF88Yt2iw7iqOGc2XoqqBCtCcUgbVfEaLugvbolF3vbKIYnIiIiK8sRd7FTdxiJRT9hq+4n3BKZZut5Sx4YoI1CjLYL+mk7w1vFtbL2gOGJiIjICi4YruO7ooNI1B3Efv0J6KA3Wy9C3fr+Yu8u6KxpB7WkruWRkqUYnoiIiKqhUBRhr/44thYVvwrlN+MNs/Vc4IRe9xd7D9RGI0Bd+ZstyLYxPBEREVVRqjEdW4t+QqLuJ/ygO4I85Jut11LlhxhtNAZqu6CntgOcJL7Gqz5heCIiIiqHURhxxHAOiUUHsVX3E44Zzputp4Ya3TThxY8ScOiCEFUgF3vXYwxPREREpWQZc/G97jASdQexTZeEdJFltl4jyUte7N1X2wleKvfaHSjVGYYnIiJ6qAkh8KvxGhLvL/Y+oD8JPQxm6z6qfgQDtdGIceiCx9VtoZL4+q6HEcMTERE9dApEIXbrfsFWXfFi7yvGVLP1XOGMPtrHMcihCwZoo+CnalTLIyVbxPBERET1nlEYccZwBfv1J7Bdl4QfdT/jHgrM1m2t8i9e7O3QBd01kXCUHGp5tGTrGJ6IiKjeKRI6/Kw/j/36E9ivP4GD+pO4I3LN1tVAje6aDohxiEaMtguC1c1rebRkbxieiIjI7uWJe0jSn8E+3XHs15/AYf1Z5KOw3PqNJe/7a5ei0UfbCR6Say2OluwdwxMREdmd28YsHNCfxD79CezXncAvhgswlLPIGwAaSl7opglHN20EntBEooM6mIu9qdoYnoiIyOZdM9yUL8Ht153AOePVCusHqpqgmyai+KON4HOXyKoYnoiIyKYIIXDOeBX7dSfkwHTdeKvCfdqrW/4RljQRfAUK1SiGJyIiqlN6occvhovyeqUD+pPIENnl1tdAjQ7qR+5fgotAV004fFSetThietgxPBERUa26JwpwSH9GvgSXpD+Du+W8Iw4AnOGIKE2ofAkuStMerpJzLY6YyBTDExER1ag7xhzs15+Uw9Ixw3nooC+3fgPJHV014XhCE4lu2gg8qn4EWol/rsh28LeRiIisKsWYbrJe6ZThUoX1/VW+JuuV2qlb8E44smkMT0REVG1CCFwwXi8Vlk6W+6qTEo+omsuPDOimiUCgqgnvhCO7wvBERERVZhAGnDD8hv36E9inO44D+pNIE3fKra+CCh3UbdBVE4EntBHoqomAr6pBLY6YyPoYnoiIqFwFohCH9efkS3A/6U4hF/fKre8IB3TWtJcfSBmtCYU7n95N9QzDExERybKNeTioP4V9+uPYrz+Jo/pzKIKu3Pqekhu6aMLk9UodNSF8kS7VewxPREQPsZvGDPkuuP36Ezhh+A0Cotz6TSQfPKH9Y3F3qDoIakldiyMmqnsMT0REDwkhBC4ZU0zC0m/GGxXu01rlLz9fqZsmAq1Uzbi4mx56DE9ERPWUQRhw2nDZ5J1wv4uMcutLkBChbi2Hpa6acDRVNazFERPZB4YnIqJ6olAU4aj+VzksHdSfQrbIK7e+A7ToqGlbvLhbE4EumjB4qdxrccRE9onhiYjITuWKu/hJf/r+JbiTOKw/gwIUlVvfHS6I1v6xuLuTpi2cJMdaHDFR/cDwRERkJ9KMd3BAfwL7dCdwQH8Cxw2/wQBDufUbSV4m65Ui1K2h4WtOiCzGf4uIiGyQEALXjDflS3D7dMdx3ni9wn1aqvzkS3DdtBEIVjXn4m6iGsDwRERkA4zCiLOGq38s7tafwA1jWoX7hKqD/ngnnDYC/irfWhot0cON4YmIqA7ohB7HDOexT3dcXtydKXLKra+BGo+pQ+RLcF014fBWedTiiImoBMMTkZUJIXBbZOGGMR0pxnSkGNPuf52G340ZkCQJLnCEs+QIF8kJznCEU6mvnSUH+WsXyQlOcl3H+9sdTbZruYbFLtwV+UjSn5Gfr3RIfwb3UFBufRc4IUoTev8FuhHorGkPF8mpFkdMROXhf3WJFDAIA26JTDkMpRjT5a//KLuNwgrueLI2NdRwxv3AdT9guUhOxYHsfthylhzhguLtpcv/CGzF4czJTDj7o01HOMKBa2iqKMOYjQP6k9h3//lKvxjOQ1/B4m4fyRNdS61X6qAOZjAmslH8N5PoviKhw+/GDNwwppUKQ+lIEen3y9KRarxd4d1NdcEAA/KQjzyRjwreqmEVEiQ4lQpqJSHMyUzQKv7aqUx4c5Yc4AKnB862mT+zppJUNTshK0o23DJZr3TGcKXC+gGqxvLi7ie0kQhRBdrVfIkeZgxP9FDIF4X3zxKllfpn8VmikqB0S2RW+E6vqvCS3OGvaoRmKl+Tf/pJjeCv8oWfqiEkAPkoxD1RiHxRiHsoQP79r/NRgHxRVKb8HgpQIO7vg/tlogD5KDQpvycK7tcvrJGQJyDk/ms6qAGAIxzuh7A/AlvpoPbgmTUn+euyZ9aczYSz0oFPyVkeIQR+NV6TL8Ht15/ANePNCvdpq2ohr1fqpolAoLqJpT8eIqojDE9k93LFXdwoE4xM1xtliGyL+2kkecFf5YtmqkYP/NMXzVQN0UzVCG6SS5XaamDxaCqnE/oyQevBr/MfCF0loa6gTHgrfCDgmZbX1GXKQhShUBQhC6jxsKaB2ky4Kr7MWTpo5YtC/KQ/hXSRVW5baqjRQd0G3bSReOL+k7sbqWrjqBNRbWB4IpslhMAdkYsbxrQyweiGMQ2poni9UY64a1E/KqjQRPKWzxKZC0h+qoZwlBysNLPaoZU00EIDD8m1xvsyCAMKUGQmqBWWCmoFJmfcSoe2gpKvUWQS5PLlr03LLT1DaI4eBuTiHnLFPcVBzQkO6KxpL69XitaEVjlIE5H9YXiiOmEURqSLLJMwZO7MUT4KLepHCw2aqRrBT9XI5DJa6WDURPLmU5ctpJbUcIUzXCXnGu9LCIHCkqBW6oyYucuf5s64lXf509yZuPIuf3pJ7uiiCcMTmkh000TgMc0jcJC0NT53IrIN/ItBVqcXevxuzECKSC8VjEzvSEs13oYOeov6cYajHIJKPv4qX5Ng1Ejy4iLcekaSJDjdf7xDXVz+FBBooWrK3yuihxjDEylSKIrk2/NTH7iMVhKMbopMGGG0qB8PybX4TJHkK182Kx2K/FW+aCC587Z5qnG1efmTiOwDwxPJ7op8OQQ9GIxK/lnRItmq8pE8y9yRVvrMUTNVI/6hIiIim8Xw9BAQQiBb5JW6Iy0NKeK2yWW0G8Z0ZIlci/qRIKGx5G3+Vv1SwchZcrTSzIiIiGofw5OdK/0qEJM70R548vVd5FvUjxpq+N2/Hd9f5YtmUsnlsz/OHDVV+XDRLBER1XsMT3bimP48DuhPlglG1ngViCMc7j+nyNfMHWnFX/tKDaCW1FaaDRERkf1ieLIT3+oO4J38LxTv5wpnBKh8y9yqXzog+UieXHhNRERURQxPdsJf1ahMWQPJ/YFb9X1L3aFWHIw8JFcGIyIiIiuyiweV5OXlYerUqfDz84OTkxMiIyOxbt26SvfbuXMn+vbtCz8/Pzg6OsLX1xe9evVCYmJiufWjo6Ph4uKChg0bYuzYsUhLS7P2dKqlh6YDVrrOwA73f+GsZwKyG+xEeoNt+MXzP/jWfRGWu76Jmc7jMd5xCPo7dEZ7TRA8VW4MTkRERFZmF2eeYmNjceTIESxYsADBwcFYu3Yt4uLiYDQaMXLkyHL3y8jIQPv27TFx4kQ0adIEmZmZ+PTTTzFo0CB8+eWXGDVqlFx3z549GDhwIAYNGoQtW7YgLS0Nb7zxBnr37o2jR4/C0bFu7xBrpfZHK7V/nY6BiIiIAEkIUQvvRq++xMREDBo0SA5MJfr164czZ87g+vXrUKurvpBZp9OhZcuWCAoKwt69e+XyTp064e7duzhx4gQ0muJMefDgQXTt2hXLli3D5MmTq9R+Tk4OPD09kZ2dDQ8PjyqPi4iIiOqOkr/fNn/ZbvPmzXBzc8OwYcNMyseNG4fU1FQcOnRIUXtarRZeXl5yQAKAlJQUHDlyBKNHjzYp79KlC4KDg7F582bLJkFERET1hs2Hp9OnT6Nt27YmoQYAwsPD5e2VMRqN0Ov1SE1NxaxZs3DhwgW8+uqrJn2UbvPBfqrSBxERET0cbH7NU0ZGBoKCgsqUe3t7y9srExMTg+3btwMAPDw8sH79egwaNMikj9JtPthPRX0UFhaisLBQ/j4nJ6fS8RAREZH9svkzTwAqvGOsKneTLV26FIcPH8aWLVvQv39/DB8+HAkJCVVuq6I+5s+fD09PT/kTEBBQ6XiIiIjIftl8ePLx8TF75iczMxOA+bNFD2rTpg0ef/xx/OlPf8KGDRvQu3dvvPTSSzAajXIfgPmzWJmZmRX2MW3aNGRnZ8uf5OTkKs2LiIiI7JPNh6ewsDCcO3cOer3epPzUqVMAgNDQUMVtdurUCXfu3EF6erpJGyVtPthPRX04OjrCw8PD5ENERET1l82Hp6FDhyIvLw8bN240KY+Pj4efnx86d+6sqD0hBPbs2QMvLy/5jFOzZs3QqVMnrF69GgaDQa6blJSE8+fPIzY21vKJEBERUb1g8wvGBw4ciL59+2Ly5MnIyclB69atkZCQgG3btmH16tXyM54mTJiA+Ph4XLp0CYGBgQCAp556ChEREYiMjISPjw9SU1OxatUq7NmzBx9//LHJHXzvvfce+vbti2HDhuHFF19EWloa3nzzTYSGhmLcuHF1MnciIiKyPTYfngBg06ZNmD59OmbOnInMzEyEhIQgISEBI0aMkOsYDAYYDAaUfuZn165d8fXXX+Ojjz5CTk4OvLy80LFjR3z77bcmd9sBQM+ePZGYmIiZM2diyJAhcHFxweDBg/H+++/X+dPFiYiIyHbY/BPG7Q2fME5ERGR/6tUTxomIiIhsCcMTERERkQJ2sebJnpRcBeWTxomIiOxHyd/tqqxmYniystzcXADgk8aJiIjsUG5uLjw9PSuswwXjVmY0GpGamgp3d/cqvTpGiZycHAQEBCA5ObleLkav7/MD6v8cOT/7V9/nyPnZv5qaoxACubm58PPzg0pV8aomnnmyMpVKBX9//xrto74/yby+zw+o/3Pk/OxffZ8j52f/amKOlZ1xKsEF40REREQKMDwRERERKcDwZEccHR0xa9asevvE8/o+P6D+z5Hzs3/1fY6cn/2zhTlywTgRERGRAjzzRERERKQAwxMRERGRAgxPRERERAowPNmA3Nxc/OMf/0C/fv3QqFEjSJKE2bNnV3n/tLQ0jB07Fg0bNoSLiwuio6Pxww8/1NyAFbJkfqtWrYIkSWY/N2/erNmBV9GPP/6I8ePHIyQkBK6urmjWrBmeeuop/Pzzz1Xa39aPnyXzs4fjd/z4cQwaNAjNmzeHs7MzvL29ER0djdWrV1dpf1s/foBlc7SHY2jOv//9b0iSBDc3tyrVt4fjWJqS+dnDMdy9e3e5Y0xKSqp0/9o+fnxIpg3IyMjAZ599hoiICDz99NP497//XeV9CwsL0bt3b2RlZWHJkiXw9fXFxx9/jAEDBmDnzp3o0aNHDY68aiyZX4mVK1ciJCTEpMzHx8daQ7TIJ598goyMDEyZMgXt2rVDeno6Fi1ahKioKGzfvh29evUqd197OH6WzK+ELR+/rKwsBAQEIC4uDs2aNcPdu3exZs0ajB49GlevXsWMGTPK3dcejh9g2RxL2PIxfFBKSgpee+01+Pn5ITs7u9L69nIcSyidXwl7OIb//Oc/8eSTT5qUhYaGVrhPnRw/QXXOaDQKo9EohBAiPT1dABCzZs2q0r4ff/yxACAOHjwol+l0OtGuXTvRqVOnmhiuYpbMb+XKlQKAOHLkSA2O0DK3bt0qU5abmysaN24sevfuXeG+9nD8LJmfPRy/8nTu3FkEBARUWMcejl9FqjJHezyGgwcPFkOGDBFjxowRrq6ulda3t+OodH72cAx37dolAIivvvpK8b51cfx42c4GlJyarI7NmzfjkUceQXR0tFym0WgwatQoHD58GCkpKdYaZrVZMj974OvrW6bMzc0N7dq1Q3JycoX72sPxs2R+9qxhw4bQaCo+OW8Px68iVZmjvVm9ejX27NmDZcuWVXkfezqO1ZlffVcXx4/hyc6dPn0a4eHhZcpLys6cOVPbQ6oRgwcPhlqthre3N2JjY3H69Om6HlKFsrOzcezYMbRv377CevZ6/Ko6vxL2cPyMRiP0ej3S09OxbNkybN++HW+88UaF+9jb8avOHEvYwzFMS0vD1KlTsWDBAkXvGLWX41jd+ZWwh2P40ksvQaPRwMPDA/3798f+/fsr3acujl/9+l+Oh1BGRga8vb3LlJeUZWRk1PaQrKpJkyaYPn06oqKi4OHhgVOnTmHBggWIiorCgQMHEBERUddDNOull17C3bt3MX369Arr2evxq+r87On4vfjii1i+fDkAwMHBAf/617/wwgsvVLiPvR2/6szR3o7hI488gsmTJyvaz16OY3XnZw/H0NPTE1OmTEHPnj3h4+OD3377De+//z569uyJ7777Dv379y933zo5fjVyMZCqTemaIK1WK/7617+WKT948KAAIBISEqw8QssonZ85V65cEW5ubuJPf/qT9QZmRTNmzBAAxNKlSyuta2/HTwhl8zPHVo/ftWvXxJEjR8R3330n/vrXvwqVSiXef//9Cvext+NXnTmaY4vH8OuvvxYODg7izJkzcllV1wTZw3G0ZH7m2OIxfNCdO3eEv7+/CA8Pr7BeXRw/nnmycz4+PmZTdWZmJgCYTeP2rkWLFujWrVuVbl+tbXPmzMHcuXMxb948vPzyy5XWt7fjp3R+5tjq8WvevDmaN28OAIiJiQEATJs2DWPGjEGjRo3M7mNvx686czTH1o5hXl4eXnrpJbzyyivw8/NDVlYWAKCoqAhA8d2GWq0Wrq6uZve39eNo6fzMsbVjaI6XlxcGDx6MTz/9FPn5+XB2djZbry6OH9c82bmwsDCcOnWqTHlJWWW3eNorIQRUKtv69Z0zZw5mz56N2bNn46233qrSPvZ0/Kozv/LY4vF7UKdOnaDX63H58uVy69jT8TOnKnMsjy0dw9u3b+PWrVtYtGgRGjRoIH8SEhJw9+5dNGjQAM8991y5+9v6cbR0fuWxpWNYHnH/9bsV3XRUJ8fP6ueyyCJKL2stW7ZMABBJSUlymU6nE+3btxedO3euoVFWnzUu212+fFm4ubmJp59+2noDs9A777wjAIgZM2Yo2s9ejl9152eOLR4/c0aPHi1UKpVIS0srt469HL/yVGWO5tjaMczPzxe7du0q8+nfv79wcnISu3btEqdOnSp3f1s/jpbOzxxbO4bmZGZmimbNmonIyMgK69XF8WN4shGJiYniq6++EitWrBAAxLBhw8RXX30lvvrqK3H37l0hhBDjx48XarVaXL16Vd6voKBAtG/fXgQEBIg1a9aI77//XgwdOlRoNBqxe/fuuppOGdWdX+/evcWcOXPE5s2bxQ8//CAWL14s/Pz8hLu7u+L/WNSUDz74QAAQAwYMED/99FOZTwl7PX6WzM8ejt9f/vIX8eqrr4r169eL3bt3i6+//loMHz5cABCvv/66XM9ej58Qls3RHo5hecytCbLn4/igqs7PHo5hXFyceOONN8RXX30ldu3aJT777DPxyCOPCI1GI77//nu5nq0cP4YnGxEYGCgAmP1cuXJFCFH8L0rp70vcvHlTPP/888Lb21s4OTmJqKgok182W1Dd+U2dOlW0a9dOuLu7C41GI/z8/MSoUaPE+fPn62YiZvTo0aPcuZU+uWuvx8+S+dnD8VuxYoV44oknRMOGDYVGoxFeXl6iR48e4ssvvzSpZ6/HTwjL5mgPx7A85sKFPR/HB1V1fvZwDOfPny8iIyOFp6enUKvVolGjRmLo0KHi8OHDJvVs5fhJQty/oEhERERElbLtlWJERERENobhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiGqRJEkVvqeLiGwfwxMR2awWLVrIYaOiz6pVq+p6qET0ENHU9QCIiCrTpk0b+Pr6lru9cePGtTgaInrYMTwRkc176623MHbs2LoeBhERAF62IyIiIlKE4YmI6pXSC7LXrl2LTp06wc3NDd7e3nj66adx+vTpcve9e/cu5s6di/DwcLi6usLDwwOdO3fGxx9/DL1eX+5+mZmZmDVrFjp06AAPDw+4ubmhbdu2+Otf/4pffvml3P22bt2K7t27w93dHZ6enhg4cGC59a9du4YXXngBQUFBcHR0hLu7O4KCgjB06FCsW7euij8dIrIKQURkowIDAwUAsXLlyirvA0AAEO+9954AIJo0aSI6duwo3N3dBQDh7Ows9u3bV2a/tLQ0ERYWJgAIlUolwsPDRdu2beX2+vbtK/Lz88vsd/z4ceHn5yfv165dOxEZGSk8PDwEADFmzBiz4/vkk0+EJEmiadOm4tFHHxWurq4CgHBzcxPnzp0z2efKlSuiYcOGAoBwcXERYWFhIjIyUnh7ewsAIiIioso/HyKyHMMTEdksS8KTVqsVixYtEgaDQQghxN27d8Vzzz0nAIjAwEBx7949k/2eeeYZAUC0b99e/Pbbb3L5kSNHROPGjQUA8Y9//MNkn+zsbNG8eXMBQAwYMEAkJyebbN+7d69YvXq12fG5uLiYzCsnJ0f07t1bABDDhw832efll1+Wg1hubq7JtnPnzonly5dX+edDRJZjeCIim1USnir73LlzR96npOxPf/pTmfYKCwtFkyZNBACxYsUKufzChQtCkiQBQBw7dqzMfhs2bBAAhKurq8jJyZHLFy5cKACItm3bioKCgirNqWR8r7zySpltJ0+eFACEp6enSXn//v0FAHHixIkq9UFENYt32xGRzavsUQUaTdn/lL300ktlyhwcHDBx4kTMnTsX27dvx7hx4wAA33//PYQQ6NatGzp06FBmv2eeeQb+/v64ceMGDhw4gAEDBgAAtmzZAgCYMmUKHB0dFc1p4sSJZcrCwsLg5OSE7OxsZGRkwMfHBwAQEBAAAPj6668RFhbGh2wS1TGGJyKyedV5VEHbtm0rLL9w4YJcVvJ1u3btzO6jUqkQEhKCGzdu4MKFC3J4OnfuHAAgKipK0dgAoFWrVmbLGzVqhOTkZOTl5cnh6aWXXkJ8fDzeffdd/Oc//8GAAQPwxBNP4Mknn4Sfn5/ivonIMrzbjojqpfLOVJU8UDM3N1cuy8vLq3Cf8vbLyckBAHh5eSken6urq9lylar4P8tCCLksMjISe/fuRb9+/ZCSkoLly5dj1KhR8Pf3R//+/eUQR0S1g+GJiOql9PR0s+VpaWkAAHd3d7nMzc3NZJs5t27dKrNfyddZWVkWjbUqoqKisH37dty5cwfbtm3DG2+8AX9/f+zYsQN9+/atlTEQUTGGJyKql8o7G1NSHhwcLJeVfH327Fmz+xiNRvz6669l9mvfvj0AICkpyfIBV5Gbmxv69++PBQsW4Ndff0WrVq2QkpKCrVu31toYiB52DE9EVC8tW7asTFlRURG++OILAEC/fv3k8n79+kGSJOzfv9/sQyo3bdqEGzduwNXVFV27dpXLn376aQDA0qVLUVRUZOUZVM7FxQVhYWEAgNTU1Frvn+hhxfBERPXSd999hyVLlshrh/Lz8/GXv/wFqampCAgIwIgRI+S6rVu3RmxsLADg+eefx+XLl+Vtx44dw9/+9jcAwMsvv2xy2W7SpEkIDAzEmTNnEBsbi5SUFJMx7N+/H2vWrLF4LpMnT8b69etx7949k/K9e/fihx9+AAA8+uijFvdDRFUjidKrEomIbEiLFi1w7dq1Sh9V8Oyzz8oBp+Q2/vfeew9vvPEGmjRpgoCAAJw/fx45OTlwcnLC9u3b0b17d5M20tPT0bt3b5w6dQpqtRqhoaHQ6XTypbw+ffrgf//7H5ycnEz2O3HiBAYMGICbN29CpVKhbdu20Gq1uHLlCrKzszFmzBisWrVKrl8yvvL+01sy5ytXrqBFixYAiheMnzhxAhqNBm3atIG7uztu3bqFa9euAQBGjRqFL7/8soo/VSKyFMMTEdmskiBRmSlTpmDx4sUATMPJ2rVrsXjxYpw5cwZarRY9evTAu+++i/DwcLPt3L17Fx9++CE2bNiAS5cuQaVSoV27dnj++efxwgsvQKvVmt0vIyMDixYtwjfffIMrV65ArVbD398fPXv2xAsvvICIiAi5bnXC065du7Blyxbs27cPycnJyM7ORtOmTRESEoKXXnoJgwcP5rOfiGoRwxMR1SuVhRMiIktxzRMRERGRAgxPRERERAowPBEREREpwPBEREREpABfDExE9QoXihNRTeOZJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBf4/ufJxWPBy+noAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "n_epochs = 5\n", + "val_interval = 1\n", + "epoch_loss_list = []\n", + "val_epoch_loss_list = []\n", + "optimizer_cls = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5)\n", + "\n", + "classifier.to(device)\n", + "\n", + "train_classifier=False\n", + "if train_classifier==False:\n", + " classifier.load_state_dict(torch.load(\"./classifier5.pt\", map_location={'cuda:0': 'cpu'}))\n", + "else:\n", + "\n", + " scaler = GradScaler()\n", + " total_start = time.time()\n", + " for epoch in range(n_epochs):\n", + " classifier.train()\n", + " epoch_loss = 0\n", + " indexes = list(torch.randperm(total_train_slices.shape[0]))\n", + " data_train = total_train_slices[indexes] # shuffle the training data\n", + " labels_train = total_train_labels[indexes]\n", + " subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size))\n", + " progress_bar = tqdm(enumerate(subset_2D), total=len(indexes)/batch_size)\n", + " progress_bar.set_description(f\"Epoch {epoch}\")\n", + "\n", + " for step, (a,b) in progress_bar:\n", + " images = a.to(device)\n", + " classes = b.to(device)\n", + " weight=torch.tensor((3,1)).float().to(device) #account for the class imbalance in the dataset\n", + " optimizer_cls.zero_grad(set_to_none=True)\n", + " timesteps = torch.randint(0, 1000, (len(images),)).to(device)\n", + "\n", + " with autocast(enabled=False):\n", + " # Generate random noise\n", + " noise = torch.randn_like(images).to(device)\n", + "\n", + " # Get model prediction\n", + " noisy_img=scheduler.add_noise(images,noise, timesteps ) #add t steps of noise to the input image\n", + " pred=classifier(noisy_img, timesteps)\n", + " loss = F.cross_entropy(pred, classes.long(), weight=weight, reduction=\"mean\")\n", + "\n", + " loss.backward()\n", + " optimizer_cls.step()\n", + "\n", + " epoch_loss += loss.item()\n", + " progress_bar.set_postfix(\n", + " {\n", + " \"loss\": epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " epoch_loss_list.append(epoch_loss / (step + 1))\n", + " print('final step train', step)\n", + "\n", + "\n", + " if (epoch + 1) % val_interval == 0:\n", + " classifier.eval()\n", + " val_epoch_loss = 0\n", + " subset_2D_val = zip(total_val_slices.split(batch_size), total_val_labels.split(batch_size)) #\n", + " progress_bar_val = tqdm(enumerate(subset_2D_val))\n", + " progress_bar_val.set_description(f\"Epoch {epoch}\")\n", + " for step, (a,b) in progress_bar_val:\n", + " images = a.to(device)\n", + " classes = b.to(device)\n", + " timesteps = torch.randint(0, 1, (len(images),)).to(device) #check validation accuracy on the original images, i.e., do not add noise\n", + "\n", + " with torch.no_grad():\n", + " with autocast(enabled=False):\n", + " noise = torch.randn_like(images).to(device)\n", + " pred = classifier(images, timesteps)\n", + " val_loss = F.cross_entropy(pred, classes.long(), reduction=\"mean\")\n", + "\n", + " val_epoch_loss += val_loss.item()\n", + " _, predicted = torch.max(pred, 1);\n", + " progress_bar_val.set_postfix(\n", + " {\n", + " \"val_loss\": val_epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " val_epoch_loss_list.append(val_epoch_loss / (step + 1))\n", + " print('final step val', step)\n", + "\n", + "\n", + " total_time = time.time() - total_start\n", + " print(f\"train completed, total time: {total_time}.\")\n", + " torch.save(classifier.state_dict(), \"./classifier5.pt\")\n", + " \n", + " ## Learning curves for the Classifier\n", + " \n", + " plt.style.use(\"seaborn-bright\")\n", + " plt.title(\"Learning Curves\", fontsize=20)\n", + " print('epl', len(epoch_loss_list))\n", + " plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", + " plt.plot(\n", + " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", + " val_epoch_loss_list,\n", + " color=\"C1\",\n", + " linewidth=2.0,\n", + " label=\"Validation\",\n", + " )\n", + " plt.yticks(fontsize=12)\n", + " plt.xticks(fontsize=12)\n", + " plt.xlabel(\"Epochs\", fontsize=16)\n", + " plt.ylabel(\"Loss\", fontsize=16)\n", + " plt.legend(prop={\"size\": 14})\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a676b3fe", + "metadata": {}, + "source": [ + "### For Image-to-Image Translation to a Healthy Subject, we pick a disesed subject of the validation set" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "fe0d9eac-1477-4d6d-a885-d3c4acb4a781", + "metadata": {}, + "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 23%|██▍ | 29/128 [00:02<00:10, 9.18it/s, loss=0.953]" - ] + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 28 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 29 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 24%|██▋ | 31/128 [00:03<00:10, 9.21it/s, loss=0.949]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 30 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 31 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 26%|██▊ | 33/128 [00:03<00:10, 9.20it/s, loss=0.944]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 32 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 33 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 27%|███▎ | 35/128 [00:03<00:10, 9.12it/s, loss=0.94]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 34 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 35 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 29%|███▏ | 37/128 [00:03<00:10, 9.07it/s, loss=0.934]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 36 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 37 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 30%|███▎ | 39/128 [00:04<00:09, 9.09it/s, loss=0.929]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 38 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 39 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 32%|███▌ | 41/128 [00:04<00:09, 9.15it/s, loss=0.925]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 40 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 41 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 34%|███▋ | 43/128 [00:04<00:09, 9.17it/s, loss=0.921]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 42 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 43 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 35%|███▊ | 45/128 [00:04<00:09, 9.15it/s, loss=0.916]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 44 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 45 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 37%|████ | 47/128 [00:04<00:09, 8.95it/s, loss=0.912]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 46 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 47 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 38%|████▏ | 49/128 [00:05<00:08, 9.00it/s, loss=0.906]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 48 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 49 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 40%|████▍ | 51/128 [00:05<00:08, 9.08it/s, loss=0.904]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 50 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 51 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 41%|████▌ | 53/128 [00:05<00:08, 9.06it/s, loss=0.899]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 52 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 53 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 43%|████▋ | 55/128 [00:05<00:07, 9.18it/s, loss=0.894]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 54 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 55 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 45%|████▉ | 57/128 [00:05<00:07, 9.18it/s, loss=0.889]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 56 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 57 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 46%|█████ | 59/128 [00:06<00:07, 9.22it/s, loss=0.884]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 58 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 59 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 48%|█████▎ | 62/128 [00:06<00:06, 10.20it/s, loss=0.877]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 60 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 61 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n", - "step 62 torch.Size([2, 1, 64, 64]) torch.Size([2]) tensor([0., 0.])\n", - "image torch.Size([2, 1, 64, 64])\n", - "classes tensor([0., 0.], device='cuda:0')\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 49%|█████▍ | 63/128 [00:06<00:06, 9.58it/s, loss=0.874]\n", - "Epoch 1: 0%| | 0/128 [00:00)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7611, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7668, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7561, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7131, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.6271, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.6277, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.6392, device='cuda:0', grad_fn=)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 9%|██▏ | 12/128 [00:00<00:03, 35.77it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.6372, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7021, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7493, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7826, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7407, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7278, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7590, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7495, device='cuda:0', grad_fn=)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 0: 17%|███▉ | 22/128 [00:00<00:03, 35.29it/s]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7754, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7786, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7738, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7787, device='cuda:0', grad_fn=)\n", - "h torch.Size([6, 64, 16, 16]) 6\n", - "h torch.Size([6, 16384])\n", - "loss tensor(0.7888, device='cuda:0', grad_fn=)\n", - "h torch.Size([2, 64, 16, 16]) 2\n", - "h torch.Size([2, 16384])\n", - "loss tensor(0.7906, device='cuda:0', grad_fn=)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Epoch 1: 0%| | 0/128 [00:00 3\u001b[0m \u001b[43mplt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinspace\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_epochs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_epochs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mepoch_loss_list\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcolor\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mC0\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlinewidth\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m2.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlabel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mTrain\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4\u001b[0m plt\u001b[38;5;241m.\u001b[39mplot(\n\u001b[1;32m 5\u001b[0m np\u001b[38;5;241m.\u001b[39mlinspace(val_interval, n_epochs, \u001b[38;5;28mint\u001b[39m(n_epochs \u001b[38;5;241m/\u001b[39m val_interval)),\n\u001b[1;32m 6\u001b[0m val_epoch_loss_list,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 9\u001b[0m label\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValidation\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 10\u001b[0m )\n\u001b[1;32m 11\u001b[0m plt\u001b[38;5;241m.\u001b[39myticks(fontsize\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m12\u001b[39m)\n", - "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/pyplot.py:2767\u001b[0m, in \u001b[0;36mplot\u001b[0;34m(scalex, scaley, data, *args, **kwargs)\u001b[0m\n\u001b[1;32m 2765\u001b[0m \u001b[38;5;129m@_copy_docstring_and_deprecators\u001b[39m(Axes\u001b[38;5;241m.\u001b[39mplot)\n\u001b[1;32m 2766\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mplot\u001b[39m(\u001b[38;5;241m*\u001b[39margs, scalex\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, scaley\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, data\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m-> 2767\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mgca\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2768\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscalex\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mscalex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscaley\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mscaley\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2769\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m{\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mdata\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m}\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/axes/_axes.py:1635\u001b[0m, in \u001b[0;36mAxes.plot\u001b[0;34m(self, scalex, scaley, data, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1393\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1394\u001b[0m \u001b[38;5;124;03mPlot y versus x as lines and/or markers.\u001b[39;00m\n\u001b[1;32m 1395\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1632\u001b[0m \u001b[38;5;124;03m(``'green'``) or hex strings (``'#008000'``).\u001b[39;00m\n\u001b[1;32m 1633\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1634\u001b[0m kwargs \u001b[38;5;241m=\u001b[39m cbook\u001b[38;5;241m.\u001b[39mnormalize_kwargs(kwargs, mlines\u001b[38;5;241m.\u001b[39mLine2D)\n\u001b[0;32m-> 1635\u001b[0m lines \u001b[38;5;241m=\u001b[39m [\u001b[38;5;241m*\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_get_lines(\u001b[38;5;241m*\u001b[39margs, data\u001b[38;5;241m=\u001b[39mdata, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)]\n\u001b[1;32m 1636\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m line \u001b[38;5;129;01min\u001b[39;00m lines:\n\u001b[1;32m 1637\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39madd_line(line)\n", - "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/axes/_base.py:312\u001b[0m, in \u001b[0;36m_process_plot_var_args.__call__\u001b[0;34m(self, data, *args, **kwargs)\u001b[0m\n\u001b[1;32m 310\u001b[0m this \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m args[\u001b[38;5;241m0\u001b[39m],\n\u001b[1;32m 311\u001b[0m args \u001b[38;5;241m=\u001b[39m args[\u001b[38;5;241m1\u001b[39m:]\n\u001b[0;32m--> 312\u001b[0m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_plot_args\u001b[49m\u001b[43m(\u001b[49m\u001b[43mthis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/anaconda3/envs/experiment/lib/python3.10/site-packages/matplotlib/axes/_base.py:498\u001b[0m, in \u001b[0;36m_process_plot_var_args._plot_args\u001b[0;34m(self, tup, kwargs, return_kwargs)\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes\u001b[38;5;241m.\u001b[39myaxis\u001b[38;5;241m.\u001b[39mupdate_units(y)\n\u001b[1;32m 497\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m x\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m!=\u001b[39m y\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m]:\n\u001b[0;32m--> 498\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx and y must have same first dimension, but \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 499\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhave shapes \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mx\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00my\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 500\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m x\u001b[38;5;241m.\u001b[39mndim \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m2\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m y\u001b[38;5;241m.\u001b[39mndim \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m2\u001b[39m:\n\u001b[1;32m 501\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx and y can be no greater than 2D, but have \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 502\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mshapes \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mx\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00my\u001b[38;5;241m.\u001b[39mshape\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[0;31mValueError\u001b[0m: x and y must have same first dimension, but have shapes (20,) and (5,)" + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|█████████████████████████████████████████| 100/100 [00:01<00:00, 51.06it/s]\n" ] }, { "data": { - "image/png": "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c/jwYcfl1aXee++9CvcyZWVl6f/+7/8klT03BqhpnOOCeiktLU2rV6++ar877rhDt956q0aMGKEtW7bozTffVEpKijp37qxJkyapf//+atGihc6cOaMff/xRycnJ2rhxo06ePKkxY8Y4ltOsWTMNGTJEH330kRITE3X33Xdr0qRJatOmjU6cOKF3331Xq1evVrt27XTq1KlrPpfDNJMnT9aqVau0d+9evfzyyzp48KAmTZqk4OBgxy3/P/roI/Xs2dNx35DSIOiuRo0a6e2339a9996rr776Sh988IFuueUWjRo1SgMGDFBwcLC8vLyUk5OjXbt26d1333VshK/UvXt39e7dW7t27dLrr7+u8+fPa+zYsbLb7Tpw4IBeeeUVff755+rTp89V7+tTFQ8//LBeffVVnTt3zvFFii1atNCgQYMqHLdq1SpFRkYqOztb8+fP15YtW/TII4/o9ttvl6+vr/Ly8vTdd9/pk08+0aeffqphw4ZpxIgRjvGLFy/WqFGjdM8992jAgAHq1KmT7Ha7fvnlF+3evVtLlixxXBX32GOPVfvnBipUp/ftBarR5bf8r+xj1apVjvEXL160/vSnP1keHh5XHefn52edPXvWaf2ZmZlWmzZtyh3Tpk0ba9++fU5fuHelq906vip9Dx065PLzlnLnSxYr0r9/f0uS1b9/f5fv//TTT9Ytt9xS7s8nOjra+vjjjx2vd+3aVeH6rubcuXPW1KlTLW9v76vOp81msx5++GHr6NGjZZaTkZFhtWzZstyxcXFxbn3JojtKSkqcvi5AFXxlwpUOHz5s/fa3v63U38H48eOdxpbOZUUPDw8P629/+5tbnweoDhwqAv4fDw8PPffcc0pPT9eTTz6p7t276+abb5aHh4duvPFG3XbbbRo1apTefPNNZWdnq3Hjxk7jQ0JCtGfPHs2cOVMdOnSQj4+P7Ha7unbtqrlz5yotLc1xLk1D1KZNG3377beaN2+ewsPD1bhxY910003q3bu3li1bpo8//tjp8NvlX/pXFb6+vlq8eLEOHDigv//97xo4cKDatGmjxo0by9fXV0FBQYqOjtZf//pXHTp0SP/85z9dfuVAx44dtWfPHj322GMKDQ2Vt7e3WrRoobvvvlsfffSRy0NI1cVms5W5pf+Vr8sTGhqqr776Sps2bdJDDz2ksLAwNWnSRF5eXmrR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\n", + "image/png": "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1qhl2d4i48mTJ8vc0qVLg3Hv+E92drbMVa5cORi//PLLZU1OU8/fI488Imv27t0rc9498XvzjiB5R3n+hW92AIDo0ewAANGj2QEAokezAwBEj2YHAIgezQ4AEL08qf6Hjz/+uMz97W9/C8a9EfkaNWoE42+88Yas8cb0vSMGyvHHHy9zbdq0Cca3bt0qa/r16xeMe9vb33nnHZmrW7duMO798kLbtm1lTh2n8H71oFy5csG4d2Jl/vz5MlenTp1g/NVXX5U18+bNkzl1TyTZpv/FF1/Imuuuu07mtm/fHoxfc801subss8+WOXWMoHTp0rJm165dwfhpp50ma5588kmZa9euXTB+3nnnyZq5c+fKnHo/3XzzzbJGHVfwfv3hrbfekjl1RGTTpk2yZsWKFTKX5BdL/v73vwfj3jGoGTNmyJyqU7/AYma2f/9+mcvJowfecbUUT7z9D7lz5z6cy+GbHQAgfjQ7AED0aHYAgOjR7AAA0aPZAQCil/I0pre4VvEWIKupvDfffFPWrF+/XubGjBkTjF955ZWyxlsa++CDD8qcsnbt2mDcmwz0qMm3QoUKyZotW7bI3D//+c9gfOrUqbLmk08+Cca9xc3eBKCiljObmf3Hf/yHzKmpwXPPPVfWTJkyJRhXi33N9JJvM7MOHToE45deeqms8ZZbq6lZz6OPPhqMq0XZZmZXX321zKnn1Zu4rFChgsyp58hb7qsmChctWiRrvMndHj16BOPe4mZvUlnxlm/36tUrGPfuB28aUz0Xa9askTWHO9WYqpyexvQmTFPBNzsAQPRodgCA6NHsAADRo9kBAKJHswMARI9mBwCIXspHD37++WeZUyPWO3bskDUZGRnhC8qjL6lo0aIyt23btmDcO8rgHS8YNGhQMH7hhRfKmgceeCAY90Zwn3rqKZkbMmRIMN60aVNZM2LECJlTxzPy5csna84666xgXL1+ZmYXXHCBzH366afBeIECBWTN888/n/ZjeUcFatasGYyrxcNmZmeccYbMKd9++23aNWZ62bg6MmGmj73UqlVL1nhj/wMHDgzGR40aJWtWr14tc++//34wrhZYm5nddNNNwXixYsVkTfPmzWVu2bJlwXiVKlVkTRJ33323zC1cuDAYHzp0aKLHWr58edo13iLonOQdcTh48GDafy9Jza/xzQ4AED2aHQAgejQ7AED0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\n", "text/plain": [ "
" ] @@ -1882,55 +1361,54 @@ } ], "source": [ - "plt.style.use(\"seaborn-bright\")\n", - "plt.title(\"Learning Curves\", fontsize=20)\n", - "plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", - "plt.plot(\n", - " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", - " val_epoch_loss_list,\n", - " color=\"C1\",\n", - " linewidth=2.0,\n", - " label=\"Validation\",\n", - ")\n", - "plt.yticks(fontsize=12)\n", - "plt.xticks(fontsize=12)\n", - "plt.xlabel(\"Epochs\", fontsize=16)\n", - "plt.ylabel(\"Loss\", fontsize=16)\n", - "plt.legend(prop={\"size\": 14})\n", - "plt.show()" + "L=100\n", + "current_img = inputimg[None,None,...].to(device)\n", + "scheduler.set_timesteps(num_inference_steps=1000)\n", + "\n", + "\n", + "progress_bar = tqdm(range(L)) #go back and forth L timesteps\n", + "for t in progress_bar: #go through the noising process\n", + "\n", + " with autocast(enabled=False):\n", + " with torch.no_grad():\n", + " model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device))\n", + " current_img, _ = scheduler.reversed_step(model_output, t, current_img)\n", + "\n", + "plt.style.use(\"default\")\n", + "plt.imshow(current_img[0, 0].cpu(), vmin=0, vmax=1, cmap=\"gray\")\n", + "plt.tight_layout()\n", + "plt.axis(\"off\")\n", + "plt.show()\n" ] }, { "cell_type": "markdown", - "id": "0cd48c2d", + "id": "a7c8346a-6296-4800-b978-c10fcdf09779", "metadata": {}, "source": [ - "### Sampling process with classifier-free guidance\n", - "In order to sample using classifier-free guidance, for each step of the process we need to have 2 elements, one generated conditioned in the desired class (here we want to condition on Hands `=1`) and one using the unconditional class (`=-1`).\n", - "Instead using directly the predicted class in every step, we use the unconditional plus the direction vector pointing to the condition that we want (`noise_pred_text - noise_pred_uncond`). The effect of the condition is defined by the `guidance_scale` defining the influence of our direction vector." + "### Denoising Process using gradient guidance\n", + "From the noisy image, we apply DDIM sampling scheme for denoising for L steps.\n", + "Additionally, we apply gradient guidance using the classifier network towards the desired class label y=0 (healthy). The scale s is used to amplify the gradient." ] }, { "cell_type": "code", - "execution_count": 27, - "id": "f71e4924", + "execution_count": 38, + "id": "7ab274bd-ea60-4674-b59b-d41de98fee5b", "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } + "lines_to_next_cell": 0 }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "100%|███████████████████████████████████████| 1000/1000 [00:12<00:00, 77.06it/s]\n" + "100%|█████████████████████████████████████████| 100/100 [00:06<00:00, 14.41it/s]\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1940,50 +1418,72 @@ } ], "source": [ - "model.eval()\n", - "guidance_scale = 7.0\n", - "conditioning = torch.cat([-1 * torch.ones(1, 1, 1).float(), torch.ones(1, 1, 1).float()], dim=0).to(device)\n", "\n", - "noise = torch.randn((1, 1, 64, 64))\n", - "noise = noise.to(device)\n", - "scheduler.set_timesteps(num_inference_steps=1000)\n", - "progress_bar = tqdm(scheduler.timesteps)\n", - "for t in progress_bar:\n", + "\n", + "y=torch.tensor(0) #define the desired class label\n", + "scale=10 #define the desired gradient scale s\n", + "progress_bar = tqdm(range(L)) #go back and forth L timesteps\n", + "\n", + "for i in progress_bar: #go through the denoising process\n", + "\n", + " t=L-i\n", " with autocast(enabled=True):\n", - " with torch.no_grad():\n", - " noise_input = torch.cat([noise] * 2)\n", - " model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device))\n", - " noise_pred_uncond, noise_pred_text = model_output.chunk(2)\n", - " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", + " model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device)) # this is supposed to be epsilon\n", + "\n", + " with torch.enable_grad():\n", + " x_in = current_img.detach().requires_grad_(True)\n", + " logits = classifier(x_in, timesteps=torch.Tensor((t,)).to(current_img.device))\n", + " log_probs = F.log_softmax(logits, dim=-1)\n", + " selected = log_probs[range(len(logits)), y.view(-1)]\n", + " a = torch.autograd.grad(selected.sum(), x_in)[0]\n", + " alpha_prod_t = scheduler.alphas_cumprod[t]\n", + " updated_noise = model_output- (1 - alpha_prod_t).sqrt() * scale*a #update the predicted noise epsilon with the gradient of the classifier\n", "\n", - " noise, _ = scheduler.step(noise_pred, t, noise)\n", + " current_img, _ = scheduler.step(updated_noise, t, current_img)\n", + " torch.cuda.empty_cache()\n", "\n", "plt.style.use(\"default\")\n", - "plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap=\"gray\")\n", + "plt.imshow(current_img[0, 0].cpu().detach().numpy(), vmin=0, vmax=1, cmap=\"gray\")\n", "plt.tight_layout()\n", "plt.axis(\"off\")\n", - "plt.show()" + "plt.show()\n", + "\n" ] }, { "cell_type": "markdown", - "id": "3483b097", + "id": "d2e343f8-c6f3-4071-a5e6-771e2343c3bc", "metadata": {}, "source": [ - "### Cleanup data directory\n", - "\n", - "Remove directory if a temporary was used." + "### Anomaly Detection\n", + "To get the anomaly map, we compute the difference between the input image the output of our image-to-image translation model, which is the healthy reconstruction." ] }, { "cell_type": "code", - "execution_count": 13, - "id": "b00d4f9a", + "execution_count": 39, + "id": "ecffaaf3-a7df-453e-81a9-757113d85084", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbsAAAG7CAYAAABaaTseAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiJ0lEQVR4nO3db6im6X0X8N+0p/UkPVsPyZAsZMQTnJTdsFnX9E8CWXGRrk2lFGkrLRixr1oE0SK+EOmLiEJ9UWp9Ibboi0AtUptCsdW0pOgWE0htEpfNslnNaAacwCROlqOZZk/tieMLaWvj8/3OnLtnd2eu8/m8vK5z3c/93M/9PD8OfK/ffenOnTt3BgAW9jWv9QkAwCtNsQNgeYodAMtT7ABYnmIHwPIUOwCWp9gBsDzFDoDl7d3rH1669Pky+4ZzfOlLZc2Duv+9vSd4kLTvoPv8wXeev7HtfvidDXNfF1fcuZPnfpf/7ABYnmIHwPIUOwCWp9gBsDzFDoDlKXYALO/SvT7P7tKlL5fZ15/T6QBwsaWSlLcy3EsV858dAMtT7ABYnmIHwPIUOwCWp9gBsLx7bgTdmnACwPl4ZRqK+88OgOUpdgAsT7EDYHmKHQDLU+wAWJ5iB8DybD0AYHn+swNgeYodAMtT7ABYnmIHwPIUOwCWd4Y0Znvu+SvTuBMAzoP/7ABYnmIHwPIUOwCWp9gBsDzFDoDlKXYALO8MWw/gftS2xCS2ysBF4z87AJan2AGwPMUOgOUpdgAsT7EDYHmKHQDLs/WA89fuqtPzfjHbCIC7858dAMtT7ABYnmIHwPIUOwCWp9gBsDxpTKY3U345jJdY5ek3nv0Utt6JW9Kd+xuOdbnM3T7j+Gbtc5JKhcZ/dgAsT7EDYHmKHQDLU+wAWJ5iB8DyFDsAlnfpzp07Lc/8+394Sez5wfc7ZxyfmXn97uEU35+ZOWnncE+321dp91c63oZ7sr6n58rk42H8i3nJ5Tfewwl9lS1bGepnAWu4lyrmPzsAlqfYAbA8xQ6A5Sl2ACxPsQNgeYodAMu7AFsPtkTdZ+6P95TOPT2JYGbm6za8zpY1JVY/LVaf1rUnJbStEenct7ynrfd4Or92Dp8tc1fOfryDMN6e5PDAbkt4UH+LeKXYegAAo9gBcAEodgAsT7EDYHmKHQDL23utT+CVtzWddY4NhlNSbuYuzX1T6rKl/Npcek8tWZmOVxKXh+VwxykeeN7vaUuC89X01g1rPp+nbr9585ns9HAYPy5rzj3deY7fQS48/9kBsDzFDoDlKXYALE+xA2B5ih0Ay1PsAFjeBWgE/WoK12ivXJ/T58rx0s6QR8uaL5S5N5S5syox+NjIePJbOv1IOd47y9zrw/iXyzmENaetOXOJ9h+F410v5xDPeyZH7ktX572wneK0fU5vKnPXwvjVsmbD78B+mXtgG1XzatMIGgBGsQPgAlDsAFieYgfA8hQ7AJZ3H6Yx7/fUZ2swnOZa8q4cLyXsmhLYy7Yk9j5a1rwtTz0RUo3PtuTiS2UuJT833EeXy5J2XY+fCRNP5TWH7XhhvLVtP03NvEvD7mpLsrgdL51fuccPvnHD6xQaVS9LGhMARrED4AJQ7ABYnmIHwPIUOwCWp9gBsLzXcOvBfRDbbVHuNHfSGjenZsHthUpz5sd2X4uv+bXfiku+fv+3y2vtdvLL5Rzel7ZGtPf0yTJ3FMZDzHxmZl4uc21dcBDGb3+oLHpvmQv3bOmHPTfaFpYUx7+RlzwWXuz5tqWjbW3ZsO1lU1Pn1nz7rbuHHy5Lbpa52IT8ft/uxN3YegAAo9gBcAEodgAsT7EDYHmKHQDLU+wAWN4Zth5siUpvcc4x4E1x6Jncpb10kU+vddLe05fKXIjV/0Be8V3/4ufj3K9+8Tt2jv/OydfnA34wvKkfyUtmPlzm0laBdn89XuY+HcbfVdakCH/aOjIzl8s9fmvLNpoX8tT+28N4Odzxq7WV53/mqaOyDeT6ltd6Jow/VdbYRnAR2XoAAKPYAXABKHYALE+xA2B5ih0Ay7sP05hF6z2cnLbJ3yhzj4bxh85+DgclBXa7Xf6XwvFKIvSv5akf+bEf2zn+D1/8O3HNP3rkh3aO/+hv/f245k9/w7+Pcx/69e/ZPfGTccnMi2XuVhovidCDp3eP3/5MWfO2PHf782GiNNhuTZ1TA+RpTZ1fF8ZbAvGevvpnOF5xOYzfSqnnmfy7UlKfqcn3zMzt9Bt2zo3GedVJYwLAKHYAXACKHQDLU+wAWJ5iB8DyFDsAlvdgbT3Y5LNl7vV5ai80BU4R6pm8NaI1nL5d5q6G8XYOxZV/tzta/yvz3rjmdSHu/lA58X98Kc+9/2O7x//pu94X1/zQL/xMnJv3h/Hn85LcdLrcx23by2k6XltUGoBfCXH3tM1i5i5NzYPaJD1tw2jbKdrvQGggvX+lnEMYf7i8zM2yfWTK9pGkfu5nPxyvDFsPAGAUOwAuAMUOgOUpdgAsT7EDYHmKHQDLe7C2HtQYcHob4ckBMzNTnh5wGMaPQ4R6ZuYgRMbb9oIYg29zZcvElG7/4UkOX7iTnvAw88K8fef4y7HL/swbJ3ey/9r5ys7xH5+/Fdd8Zb42zv3Lv/tXdk+8v9zWh6Fz/2N5SY3p/1r4nN5dvhcfa08wSJ/vc3nJ3uO7xzfH48t9HrUnBKRzD+c9M+f+VIZNT00556cy8Iqw9QAARrED4AJQ7ABYnmIHwPIUOwCWd/+lMQ/KXEuWnbywe3xvd5rwrsdLDaTf/da85GMpwXbO6ax2jW5/okx+bufoL9z5qbjie771QzvHf+I3/2pc8x/nT8W5n/n5H9o98Ufjkvnvfy6/4T87/3bn+PPv+NZ8wOfTLV+SfLVpchjf2rD4KDQsvtnOIaUGS+K43kfh+xTSuf9XSy7uvvdm3lHWvBzGSxq5NUlP3/fjsua8E6G8IqQxAWAUOwAuAMUOgOUpdgAsT7EDYHmKHQDLew23HmyIf9eo9OfDxJu3He9qep2y5lpq7tsaN5e49l6IjZ9uaSKc/fE7L8a575sP7hz/+HxLXPNH5rfj3JfmoZ3jPzw/Hdd8cL4vzv3yv/6Luye+q9yv7w3366/kJbUx8sNha0nbXjBhe8HMzITP93L5bNP2h9vne69slz6PLb8daVvETN8acWP38OUrecmtj5TjvSeM23rwarP1AABGsQPgAlDsAFieYgfA8hQ7AJZ3/zWC3itzpx8uk0+f/bVyoHDm4xsSccdh/DSkwGZmnihJsGdDUjOlNGdmTkMD65mZp0IT65/MSx77k7+5c/zPz7+Ja56Zp+Lcta/sjrm+dO0t+STK5YsJyh8va5LyUdRzmJDYu/xkXnKrpDvji7UTTM3GU0p5piaVU+KxNlZv6dMUb/5UWfN4mUvaz1lKSbbm6Y+WufS7d46/h9wTaUwAGMUOgAtAsQNgeYodAMtT7ABYnmIHwPJe4a0H93ToP+ioNFG9WdalRrgPlzU3W/z7dWG8RLn3QzT8cnmZR8pc8myZa5H2n9wdT3/4b/zXuOTN84Wd4//pf3xTXHNyvLvZ88zM3Aj3Su5Fnbd0zMTU//xiWZMagLcm360RdIz9lybfV8v2kXQeN9t3MG1XCNtNZmambFOJ3+my/aF+18K2hCdKQ+xnnwsT78hrLpffj1t5Kjosc8dhe9Je2Z50uqEBPndl6wEAjGIHwAWg2AGwPMUOgOUpdgAsT7EDYHntGQPb//T3lDhtin9fb/Hql/LUfujgXrcXlPf0SIrIv6EcL2iR9jZ3FMaPy5ofTTH4iVsgvmN+NS75pa98987xk4+V6/BY2gcyMx8P17XFwn+5zJ2miRL7Pw2x/yfK6zxb5uJrle7311pW+uXdw1dKpP122GJwXJ4Usl+eFHKS3lN5ssHlso3g5s/uHn/x/XlNfOpBeCLDzMxxeSpD2mpxpWzPuNGe5BC+a6flc+I14z87AJan2AGwPMUOgOUpdgAsT7EDYHlniFjG2NvE1FlKXM7kprHXWnryTXlqP4yftLdYUlMvpsa6n8trDt8VzqGcQmuA/HwYPyprymv957/0x3aOf9Ov/re4Zv/dIQF7vZzDY2Xu42H8WlmTrsPMzNU0ERKNTW0E3VJ5wWPfnOee/0RZGM795pN5yWlqUP6WvOakJZ9To+ry/Xy+zF15/+7x9DWrr1USl+3rfhpSlzeeaSdRhAR4peHza8V/dgAsT7EDYHmKHQDLU+wAWJ5iB8DyFDsAlnfpzp07rSPt7//hpRZTLg1voxQrLvHlvSt57jQ1ri0Niy+XGPCtL+8e3y/bFU7CmoOyJjRnnpm82+PbN6xpr1W2iPyZv/crO8f/y/yJuObGO0pD4BQ1P85L6j0RtxiUbSoPh8/9ZtsO8GiZS/f/p/OSw9TkePIWiLaV5zg0OZ7ynbnaGlWnibTFYaY2347Xr0Xx0/HStoiZmXP+nTosc98Xxv9Zuw6pUfuW31B+171UMf/ZAbA8xQ6A5Sl2ACxPsQNgeYodAMtT7ABY3hm2HpQ/Owzx4dZFPnZpL53Ej8rxrofxFte+XTrZXw7x+VutTXuKD78uL7maosiTn2CQnhgx07vIv3v38P4HwpMNZubkB8LWjY+V1zkuczG6/lBZU7ZuTNjuUaXjtVh9+QxjRL6dd3uCR/g+nWy5Xz9czuE9eeoonPv1Fu1vjxxI2zDKEwyithWlfJ/S1ptb7bXaz+NHw3h74sZT7cUC2xLuxtYDABjFDoALQLEDYHmKHQDLU+wAWN75NIK+HNJCt+7p0F8ldqC9i5SAKgmxh0sS7GaaKO/pYEMqtfTpjU1o2/Hiec/M1TDeLnlKhG5uuJuSdC3tWNJ3h6Ep8HFrxpsaCX+krHmyzIUU515JFp+270a6l1P6b2auPL17/Ea7Du2afymMt8Rla9Ac3u9eaQR9mqLF5f7aL9f8JFyLdA/N3CVZnLRIdPrCt/uhNctmRhoTAGZGsQPgAlDsAFieYgfA8hQ7AJan2AGwvJYjvvc/PU0TnyzHSxHhlI+fmaslgvtIGG9NXlsz4xoFDg7DeNsq0Ob2w/j1T5RFLe4eYs8nrbFu+Nz3SpPjeD80JVbfmucep4kWg38hjL+rrPlsmXvr7uHWhPx4Q5z8MGwvmJm5kZpEH5UDti0d4T46brH65gu7h0/b1ohwv+635unteGHLwvGWZuIzeetG+Q4mqfn3zMxJO7/WbJz/l//sAFieYgfA8hQ7AJan2AGwPMUOgOWdoRF0S+yVdFS0u5Hw19z8X3HF//6pb4hz+z/y0s7xk7/whnwKz+SpmKzclNz6UJ66/L157tYzYeLR8lolNXsQEoq3nyvHezyMb2l2O5OTkKUp9yYpnTgz87Yw3ppbt+Byapq89Rql79pbypqQdqzn/ek89UhofP1iaHp919cKvxGpifzMzK30Wi3tuCE1u1n4nK6U38OtYdYkXfJNiegHl0bQADCKHQAXgGIHwPIUOwCWp9gBsDzFDoDlnWHrQYtlp/jwhvj3L5YlP17mUkPlm2XNzbaN4HNhvMWrU9735bKmxclTBL01OW6vdYa+378nvad2rNQgdybH9JsWNU/x9Ha/pmtert3l0nA3Nhtv51CapD8cGlLfbE2Ok7YtqLzfFJ+/0bLzu7f/zMzMUdjCcn3LFpGmbY1I92X7nNp3LXkVGzfbejAzth4AwMwodgBcAIodAMtT7ABYnmIHwPIUOwCWd4Yseokc74du4iflSQnfFcbbGZ2UuRS13S9rWgz4kRB7bpHeuP2hxavbAdNci5O3eHqK/b+jrEnd9Nv2gtLJft4UxtuWifbEjS3HS9e1bS9ocfIQub9anmxwLZ33lPulbFM5DOd+3J4CcJSn4netXdeyReR6yoaXc4i/Oe2JEeX+3wtP1jhtWw9Kpv3w0u7x47b9IT15oX1vy/aHwzAet8NcXP6zA2B5ih0Ay1PsAFieYgfA8hQ7AJZ3hkbQLRGX0oHt0LuTTE/e+XBc8cPz03HuL//sB3dPvK81rm2pwZSoulrWfCqMH5U1zZbGzU17v0lK37UEW7tXUrKspTtbUi0lAFvD3XDuj5Tr8+JHyvFCuvPwqbzkuFy/q+E8rm1oMHxQltzekgDc0hC+rWv3SkrNlntlrySVT1MyNSUkZ+q9dzlco5qETL+JIdlZ19xt3at1vNeeRtAAMIodABeAYgfA8hQ7AJan2AGwPMUOgOWdYetBmUzNlk+eKYveE8a3xJfLupbej1HkmR5HTlJMuTVubu/3N8L4u+7tdP4/aTtFaVz7xOO7x59tr9O2e7whjLfr0BppP7p7eL/csCdpe0u5DvvvK8dLEy/kNa1pcrwWJaa/H5ojn7TvzIfy1NF37x6/Xta0huLx/J4rxwufx7c8nZd8/AN5bu8Hd4+ftvsrNPmemTkI38PbefvU7IVzr82oW2PpdK+0+2s9th4AwCh2AFwAih0Ay1PsAFieYgfA8hQ7AJZ3hq0HLf56njHXLU9XKNK2iJkSGW/a5Upd2lus/ryfJvGFMvdQGG9PHEgR/mfKmrAdoJ5DerrCTO7AP9OvRRLeU71XWkQ+bM8IafuZmblRuuk/Ed7vs+V46ekG6ZacmTnZ0v2+3a/lPjoM34HjtL1mJt9HZT/RUXnaxfX05Ip3lnNoT89I2tMkkrQlZ2YeK9tonk/bfNrNtx5bDwBgFDsALgDFDoDlKXYALE+xA2B5Z0hjtia0ITV40hotp5jY28qaLemxloxqKb+UPm3J03SNWjfq62Xu7WG8NFq+XFJYx2H8tCTi9kOz25rka1LqsiVWWxI4vd/2uafXamnfdg6f3D189J15yfUt9+WWxuWfKGu+ucyFezl912fu0nQ6NVQua9K9fKt9Fuetfd/TuZfm0d8ejvdr93o+Xy3dE62Rffuc2m9V0p4S8OqQxgSAUewAuAAUOwCWp9gBsDzFDoDlKXYALO8MOdMSOb4cxktCfg7CFoPb7RxaxDVEuY/K9oLr7bW2NLfe0Kg6bi9oWnS4OP3w7vEnns5rnn0hTLTr05o6p60CLU7etiWkBs2hOfPMzKT31D6Lcs0fCVsMXkyNh2dm3lPm0raEcl2fCOPPlsbg7dt/mrYTtUbQ18tcul/Kdb2VtsS8qbxOi9wn7T016b78cl6yaYtB+25saVTdvk8pw//aby/4w/KfHQDLU+wAWJ5iB8DyFDsAlqfYAbC8V7gR9IYz2tKHdGbmdENT1vlMnrr85O7x9p5u/0SY+Jt5TXu/Kbh4vaWzHipzKc3X0pPpuqZG3jOzX5p5n4RzPyjpztutmXFYd9gaYqe0XEu2lWbZ8Zq3xGq7L9P1a42gUxL402VNuMdnJicKy/Eul8bSt9LvR0kwH4bx47ykX6P0+bbPqR0vrXtdWVO+N1FLi6a0+Zam+TP5+94SnK89jaABYBQ7AC4AxQ6A5Sl2ACxPsQNgeYodAMs7w9aDFndPjVm/VNZsaZpcGqymWPFhWXLc3vq1MN5iymEfweUSab8VmjPPzMyjYbxdu9YkOr2nd+Ul+2H8pEXx31Hm0rVIzY9nZj5X5lrMO7kaxst2gMdKQ/Hn0xaWt5Rz+FSeuhI+jxslgn4Q7onbv5DXHH5vnjtOn0e797ZE5NvnXq551H6nkjeUufZ9SvfeJ8uad4bxth3gnn6iz3C85sFsBG3rAQCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgeWfYetBi/6kjduumv2XrQXEQxlN0fmbmVosVb+j2HzvCl2h/9XO7h/e/Py9pWwIeCefxYjuH959xfKY+TSJ+7iWuffU789y1Z8JE2f6wFyLtp8/lNfN4mQvrrpQ1N9rX7qNhvEXk3x7GP1TWtC0i6akRZavM0dN57voLaVE5h7TlpD0x4tvy1EGIz7evdH1yS3pP6bOY/JSTLQ9D4PfYegAAo9gBcAEodgAsT7EDYHmKHQDLO0Mas/1ZSDm1JORJSjumZOfMpoaoKYE1M3P7s2XhW3cP1/cUUoj/4G15zd8ux5uP7B6+/GRecuuZcrzUhLalJ1vj66Q1Z06fb2sI/IUyl5ottybMSWs4ndKJM7lB+S+VNSUJuV/SfMnJJ8JEi/ml+2EmpmOPSrL4+r8qx3tPGG/NnlNT5y335ExuVF2S4Sk9OTNzmj730vh9U6PlDb+9F4w0JgCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgeWfYepBiwDMxCnxYlhzfy6ueRXobLXrdtjmk9/tQXnIYIsfHHyivU5ocx4h1acY7pRlvfE8pkj0zE7ZNHJUl10MD65mZSU2s2/aHsnVjngnjLVa/pQl5u+bhul5+X15Sm5CnBurtO3g9jJeY/lFpVH09nV85h4OyPeP2hth/+k7vlbj9aWmEvh+2TbTm6bX5djr3rVsjElsP7sbWAwAYxQ6AC0CxA2B5ih0Ay1PsAFieYgfA8s6w9eBGmU0d5kssNj09ID4NYWYOylaB2ym6XmLrmzqat47+ITK+V7qgn7Y4eXjqwbu/Ny/5WDvem3YPH5bP6ThNvFBe56Uyl57YUO6vwxJpT/fRzfQUgJmZL+0evvJUXnKjHS/dl2WbSnqqxszMpO/AP8lLrv713ePX2ufUnq6Q3m+L4rf39FwYL9sf4j3RnkDRhO/0Qfl+3m5P40jXwnaAV5utBwAwih0AF4BiB8DyFDsAlqfYAbC8M6QxSypp741nf+XWn/k8pbTezMxJW9jSp0lKpbY325owp0azrYF1SHDWde/YsKY1bm4pv2d2Dx88lZfcLoeb1MS3pQavhfGreclRSfVeT/dK+2wfLXO/HsafKmvC534lpV9n5sYz5Xjp+pX05KbvWvv5Sd+bdv834bWulvTktfY7sDUVynmTxgSAUewAuAAUOwCWp9gBsDzFDoDlKXYALO8MWw9SY+SZ3Bz5PmiIWps9bznghi0Ytdnzm8/3HKa81pWwJeBG+2w/HcbbeZdm3jGuHZpoz0zdBhLfU7kO++HcT9p2iraVYUtD4PYZphuzbWVI214+VdYclblwjQ7LkuMyNx/ePbz/dF5ysqFxc9tOdJruy+tlUfsBaY2veTXZegAAo9gBcAEodgAsT7EDYHmKHQDLa1Gjr5ISlzMxddaOnuZSAuuuQkKrpjGfyXN7T4U17Xjp3FuKrl3XYL803j4pKcQbL4SJ9qa+effwu8uSW2Xu2jNh4tvKoo/mqRupQXO5j+LbLdfucmkEfetDYSIlJGd6ujPdE6kxeFvTmnyXVGNq9H1crkO75qnxdW3GHs6vNgZP9/jM7Ifk7kn7DrZrntKdWxtV80rynx0Ay1PsAFieYgfA8hQ7AJan2AGwPMUOgOWdoRH0K30q92B/w5qDMtcizCme3lL6aZvDaWv626RI9JfKmtKg+XIYv9UaN38yjL9U1rRYfYig1y0Y7fySFqvfosXq003Rmls/lKceC+f+fDuHsCZ95jMzt0pMPzaJbte1/ZRc2z28V7YybGrU3qTvYbv32vu19eB+oRE0AIxiB8AFoNgBsDzFDoDlKXYALE+xA2B599/Wg/qUgi0HbG+vRcND5PiwLDn+ud3jB99fFhW3PxEmwpMIZmbmuTKXuvB/Li85enz3+PXyMvMbZe5qGG9PhmjCEyCOSrf66+m10jaLmf5Uhi03Ztl6cBS+bNf/eTne02H8Tfd6Ql/9YmG8bG3ZtN0jPF1hZmbStoS2FaVtiWnnzoPM1gMAGMUOgAtAsQNgeYodAMtT7ABY3hnSmO3PzjGq2Zo9n7SFocnrYUjrzcwclwTgQUjz3f58OYctaa/WJPp6GG9pzNbcNzWQTinNmZn0fkNKc2ZqI9zYLLs0OX5vSfn9Skp+HuU1MQnZ0oQtLRrulUfKkhfb55Tu2XZ/pe9n+26WVOPD4TO8WQ537sJ7Oizv6fgVORHuc9KYADCKHQAXgGIHwPIUOwCWp9gBsDzFDoDl3X+NoJtNTaJLZPzh0iz4Zms2G+yHuPZJidVv0hoPl/cUtfNL2xVaDL7F6tO2hK0dwNPn1K5D2E7xcNnScfP9eW4/zJ20bSpty8nbN6x5XRhv59CuUWqonJozv5rae2r38lvP+0S4T9h6AACj2AFwASh2ACxPsQNgeYodAMtT7ABY3v331IOtDsJ4S62fbHnqQbsO6cWu5yWHJcp9nCY+U86hRcNDZHuvbCM4vREmrpTXSU8imJl5VxhPrzNTo+b77QkQQdwS8Ia8Zq88yeE0bX8o12HvyXK8D+weP/jBvCbel2kLwcxcKU8ESU83OH0ur6lPwtgibbUo582FZOsBAIxiB8AFoNgBsDzFDoDlKXYALG+dNOak8yvntqX38GFZc5ya0KZmyjM1AXgQEoC3y+EOy9xxmmiNdUOD4aulifC11ow3NSy+npfslQa+LW0bpUbVqQHz3aRU70N5yX65L9N7Om0p13eG1ykp0pZGnpfDeLlX9koa87S9VjxgGH/9hmOxMmlMABjFDoALQLEDYHmKHQDLU+wAWJ5iB8DyWvj+AbNh+0OLrafG0tWndw9fKc2KW//j2Ny3bGU4LnH3+Q9h/NG8JDXEvvbhvGbv6TwXI+jlPZ1+Ns9N2JbQPr+TsMWgvk5plh1j+mV7Ro39p3XtZjkKr9Puh9RoeSZe17pNpQnX6OFyXU/C+HFqvD0zU7ZacKH5zw6A5Sl2ACxPsQNgeYodAMtT7ABY3kKNoF8lLeUXGzSXBNtBSaPF47UUXUkAJpdLgu1WSg2mhs4zPRGXrkVpiF2l1/pEWZNea8O1m5mZl8L4W8qado22JArTPVEix4fl3jtOydSjcg7n/TuQUpcSl/xBGkEDwCh2AFwAih0Ay1PsAFieYgfA8hQ7AJZn68GZbWhCu2m7QlnX1jT7YfykNRgOWw8OQjPlmZnbXy7HC1H4vRL7j82jZ/IWiBZPb1s3ks/lqYcf3z1+s13Xts1hyxaILVs6UgPrcg6HZclxmZv0+9F+O7as4SKy9QAARrED4AJQ7ABYnmIHwPIUOwCWp9gBsDxbD15raTvATG5Yf1q2P9QnGKSJ8tnuh8/2JC/pwrkflvM+fqEc79Ew/qWyJmxXOCjnULd7pOv30bLmyTLXtrckYevB3pW8pDwQAR4kth4AwCh2AFwAih0Ay1PsAFieYgfA8qQxl7Phc2qJ0JS63NrcOmrn3ZKVqWlyaow8E9OYtRn1Z/Lc5bftHr/VGkGXlGRMVr45LzkK49fKy8AipDEBYBQ7AC4AxQ6A5Sl2ACxPsQNgeYodAMuz9WA5Wz6nL5c1rz/7mr20Zkrz4XLeB+X+itscvpjX7L9x9/heXjK37+lr8lXHK+fdtm4cn/2lcvPo0twaFmHrAQCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgebYenNn9fh3u9/O7j7UHEbQHGGzRtjmcbtlGsGWNe4U12HoAAKPYAXABKHYALE+xA2B5ih0AyztDGjOlvWY0m4XztKHptPQkF5g0JgCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgea0d7fY/Bf4QbCOA8+Y/OwCWp9gBsDzFDoDlKXYALE+xA2B5ih0Ay1PsAFieYgfA8hQ7AJan2AGwPMUOgOUpdgAsT7EDYHmKHQDLU+wAWJ5iB8DyFDsAlqfYAbA8xQ6A5e3d+59eeuXOAgCqO2Xu7vXJf3YALE+xA2B5ih0Ay1PsAFieYgfA8s6QxvzihsN/Y5n7ug3H4/7RklH3g5bOut/PPZGIZhVbkpUvlzWvv+sr+s8OgOUpdgAsT7EDYHmKHQDLU+wAWJ5iB8DyLt25c+dBzWEDwD3xnx0Ay1PsAFieYgfA8hQ7AJan2AGwPMUOgOUpdgAsT7EDYHmKHQDL+z/bnJwCJp0DEQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "if directory is None:\n", - " shutil.rmtree(root_dir)" + "\n", + "diff=inputimg.cpu()-current_img[0, 0].cpu().detach().numpy()\n", + "plt.style.use(\"default\")\n", + "plt.imshow(diff, cmap=\"jet\")\n", + "plt.tight_layout()\n", + "plt.axis(\"off\")\n", + "plt.show()" ] } ], diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py index da6de829..c02f1003 100644 --- a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py @@ -31,7 +31,6 @@ # !python -c "import monai" || pip install -q "monai-weekly[pillow, tqdm, einops]" # !python -c "import matplotlib" || pip install -q matplotlib # !python -c "import seaborn" || pip install -q seaborn -# %matplotlib inline # %% [markdown] # ## Setup imports @@ -51,9 +50,10 @@ import shutil import tempfile import time +from typing import Dict import os +import torch.nn as nn import matplotlib.pyplot as plt -import seaborn import numpy as np import torch import torch.nn.functional as F @@ -64,9 +64,14 @@ from monai.utils import first, set_determinism from torch.cuda.amp import GradScaler, autocast from tqdm import tqdm +torch.multiprocessing.set_sharing_strategy('file_system') +import sys +sys.path.append('/home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/') +print('path', sys.path) from generative.inferers import DiffusionInferer + # TODO: Add right import reference after deployed from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet, DiffusionModelEncoder @@ -74,7 +79,13 @@ from generative.networks.schedulers.ddim import DDIMScheduler print_config() -train=False +train_classifier=False +train_diffusionmodel=False +def visualize(img): + _min = img.min() + _max = img.max() + normalized_img = (img - _min)/ (_max - _min) + return normalized_img # %% [markdown] @@ -83,25 +94,21 @@ # %% jupyter={"outputs_hidden": false} directory = os.environ.get("MONAI_DATA_DIRECTORY") #root_dir = tempfile.mkdtemp() if directory is None else directory -root_dir='/home/juliawolleb/PycharmProjects/MONAI/val_brats' -root_dir_val='/home/juliawolleb/PycharmProjects/MONAI/val_brats' - -print(root_dir, root_dir_val) +root_dir='/home/juliawolleb/PycharmProjects/MONAI/brats' #path to where the data is stored # %% [markdown] # ## Set deterministic training for reproducibility # %% jupyter={"outputs_hidden": false} -set_determinism(42) +set_determinism(36) -# %% [markdown] +# %% [markdown] tags=[] # ## Setup BRATS Dataset for 2D slices and training and validation dataloaders # As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150 # %% jupyter={"outputs_hidden": false} -batch_size = 2 channel = 0 # 0 = Flair assert channel in [0, 1, 2, 3], "Choose a valid channel" @@ -135,19 +142,48 @@ seed=0, transform=train_transforms, ) -nb_3D_images_to_mix =20 -train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4) +print('len train data', len(train_ds)) + +def get_batched_2d_axial_slices(data : Dict): + images_3D = data['image'] + batched_2d_slices = torch.cat(images_3D.split(1, dim = -1)[10:-10], 0).squeeze(-1) # we cut the lowest and highest 10 slices, because we are interested in the middle part of the brain. + slice_label = data['slice_label'] + slice_label = torch.cat(slice_label.split(1, dim = -1)[10:-10],0).squeeze() + return batched_2d_slices, slice_label -print(f'Image shape {train_ds[0]["image"].shape}') +preprocessing_train=False +if preprocessing_train == True: + train_loader_3D = DataLoader(train_ds, batch_size=1, shuffle=True, num_workers=4) + print(f'Image shape {train_ds[0]["image"].shape}') + + data_2d_slices=[] + data_slice_label = [] + check_data = first(train_loader_3D) + for i, data in enumerate(train_loader_3D): + b2d, slice_label2d = get_batched_2d_axial_slices(data) + data_2d_slices.append(b2d) + data_slice_label.append(slice_label2d) + total_train_slices=torch.cat(data_2d_slices,0) + total_train_labels=torch.cat(data_slice_label,0) + torch.save(total_train_slices, 'total_train_slices.pt') + torch.save(total_train_labels, 'total_train_labels.pt') +else: + total_train_slices=torch.load('total_train_slices.pt') + total_train_labels=torch.load('total_train_labels.pt') + print('total slices', total_train_slices.shape) + print('total lbaels', total_train_labels.shape) -print('download val set') -# %% +# %% [markdown] tags=[] +# ## Setup BRATS Dataset for 2D slices validation dataloader +# As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150 + +# %% val_ds = DecathlonDataset( - root_dir=root_dir_val, + root_dir=root_dir, task="Task01_BrainTumour", section="validation", # validation cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise @@ -156,10 +192,30 @@ seed=0, transform=train_transforms, ) -val_loader_3D = DataLoader(val_ds, batch_size=2, shuffle=True, num_workers=4) -print(f'Image shape {val_ds[0]["image"].shape}') +preprocessing_val=False +if preprocessing_val == True: + val_loader_3D = DataLoader(val_ds, batch_size=1, shuffle=True, num_workers=4) + print(f'Image shape {val_ds[0]["image"].shape}') + print('len val data', len(val_ds)) + data_2d_slices_val=[] + data_slice_label_val = [] + for i, data in enumerate(val_loader_3D): + b2d, slice_label2d = get_batched_2d_axial_slices(data) + data_2d_slices_val.append(b2d) + data_slice_label_val.append(slice_label2d) + total_val_slices=torch.cat(data_2d_slices_val,0) + total_val_labels=torch.cat(data_slice_label_val,0) + torch.save(total_val_slices, 'total_val_slices.pt') + torch.save(total_val_labels, 'total_val_labels.pt') + +else: + total_val_slices=torch.load('total_val_slices.pt') + total_val_labels=torch.load('total_val_labels.pt') + print('total slices', total_val_slices.shape) + print('total lbaels', total_val_labels.shape) + # %% [markdown] # Here we use transforms to augment the training dataset, as usual: @@ -171,64 +227,12 @@ # # -# %% [markdown] -# ### Visualisation of the training images - -# %% jupyter={"outputs_hidden": false} - - -from typing import Dict -def get_batched_2d_axial_slices(data : Dict): - images_3D = data['image'] - batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1]) - slice_label = data['slice_label'] - #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float() - slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze() - return batched_2d_slices, slice_label -print('check data') - -if train==True: - check_data = first(train_loader_3D) - batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data) - idx = list(torch.randperm(batched_2d_slices.shape[0])) - print('idx', len(idx)) - print(f"Batch shape: {batched_2d_slices.shape}") - print(f"Slices class: {slice_label[idx][slices].view(-1)}") - subset_2D = zip(batched_2d_slices.split(batch_size), slice_label.split(batch_size)) # - -check_data_val = first(val_loader_3D) -batched_2d_slices_val, slice_label_val = get_batched_2d_axial_slices(check_data_val) - - - -idx_val=list(torch.randperm(batched_2d_slices_val.shape[0])) -slices = [0,30,45,63] - -image_visualisation = torch.cat(batched_2d_slices_val[idx_val][slices].squeeze().split(1), dim=2).squeeze() -plt.figure("training images", (12, 6)) -plt.imshow(image_visualisation, vmin=0, vmax=1, cmap="gray") -plt.axis("off") -plt.tight_layout() -plt.show() - -# %% - -subset_2D_val = zip(batched_2d_slices_val.split(1),slice_label_val.split(1))# - - - # %% [markdown] # ### Define network, scheduler, optimizer, and inferer # At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using # the original DDPM scheduler containing 1000 timesteps in its Markov chain, and a 2D UNET with attention mechanisms # in the 3rd level, each with 1 attention head (`num_head_channels=64`). # -# In order to pass conditioning variables with dimension of 1 (just specifying the modality of the image), we use: -# -# ` -# with_conditioning=True, -# cross_attention_dim=1, -# ` # %% jupyter={"outputs_hidden": false} device = torch.device("cuda") @@ -258,26 +262,30 @@ def get_batched_2d_axial_slices(data : Dict): # Here, we are training our diffusion model for 75 epochs (training time: ~50 minutes). # %% jupyter={"outputs_hidden": false} -n_epochs =100 +n_epochs =75 +batch_size=32 val_interval = 1 epoch_loss_list = [] val_epoch_loss_list = [] - -if train==False: - model.load_state_dict(torch.load("./model.pt", map_location={'cuda:0': 'cpu'})) +train_diffusionmodel=False +if train_diffusionmodel==False: + model.load_state_dict(torch.load("model.pt", map_location={'cuda:0': 'cpu'})) else: scaler = GradScaler() total_start = time.time() for epoch in range(n_epochs): model.train() epoch_loss = 0 - subset_2D = zip(batched_2d_slices.split(batch_size), slice_label.split(batch_size)) - subset_2D_val = zip(batched_2d_slices_val.split(1), slice_label.split(1)) # + indexes = list(torch.randperm(total_train_slices.shape[0])) #shuffle training data new + data_train = total_train_slices[indexes] # shuffle the training data + labels_train = total_train_labels[indexes] + subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size)) - progress_bar = tqdm(enumerate(subset_2D), total=len(idx), ncols=10) + subset_2D_val = zip(total_val_slices.split(1), total_val_labels.split(1)) # + + progress_bar = tqdm(enumerate(subset_2D), total=len(indexes), ncols=10) progress_bar.set_description(f"Epoch {epoch}") for step, (a,b) in progress_bar: - print('step', step, a.shape, b.shape, b) images = a.to(device) classes = b.to(device) optimizer.zero_grad(set_to_none=True) @@ -295,6 +303,8 @@ def get_batched_2d_axial_slices(data : Dict): scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() + if step%20==0: + print('step', step, loss) epoch_loss += loss.item() @@ -305,16 +315,17 @@ def get_batched_2d_axial_slices(data : Dict): ) epoch_loss_list.append(epoch_loss / (step + 1)) + if (epoch) % val_interval == 0: model.eval() val_epoch_loss = 0 - progress_bar_val = tqdm(enumerate(subset_2D_val), total=len(idx_val), ncols=70) + progress_bar_val = tqdm(enumerate(subset_2D_val)) progress_bar.set_description(f"Epoch {epoch}") for step, (a, b) in progress_bar_val: images = a.to(device) classes = b.to(device) - timesteps = torch.randint(0, 1000, (len(images),)).to(device)#torch.from_numpy(np.arange(0, 1000)[::-1].copy()) + timesteps = torch.randint(0, 1000, (len(images),)).to(device) with torch.no_grad(): with autocast(enabled=True): noise = torch.randn_like(images).to(device) @@ -330,6 +341,8 @@ def get_batched_2d_axial_slices(data : Dict): val_epoch_loss_list.append(val_epoch_loss / (step + 1)) total_time = time.time() - total_start + torch.save(model.state_dict(), "./diffusion_model.pt") #save the trained model + print(f"train diffusion completed, total time: {total_time}.") plt.style.use("seaborn-bright") @@ -347,125 +360,124 @@ def get_batched_2d_axial_slices(data : Dict): plt.xlabel("Epochs", fontsize=16) plt.ylabel("Loss", fontsize=16) plt.legend(prop={"size": 14}) - #plt.show() - #torch.save(model.state_dict(), "./model.pt") + plt.show() -# %% -### Model training of the Classification Model -#Here, we are training our binary classification model for 5 epochs. + +# %% [markdown] +# ### Model training of the Classification Model +# #First, we define the classification model. It follows the encoder architecture of the diffusion model, combined with linear layers for binary classification between healthy and diseased slices. +# #Here, we are training our binary classification model for 20 epochs. # %% -## First, we define the classification model -# %% classifier = DiffusionModelEncoder( spatial_dims=2, in_channels=1, - out_channels=1, - num_channels=(64, 64, 64), - # attention_levels=(False, False, True), + out_channels=2, + num_channels=(32,64,128), + attention_levels=(False, True, True), num_res_blocks=1, num_head_channels=64, with_conditioning=False, - # cross_attention_dim=1, ) classifier.to(device) -batch_size=6 +batch_size=32 # %% -n_epochs = 100 +n_epochs = 20 val_interval = 1 epoch_loss_list = [] val_epoch_loss_list = [] -optimizer = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5) +optimizer_cls = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5) classifier.to(device) - -if train==False: +train_classifier=False +if train_classifier==False: classifier.load_state_dict(torch.load("./classifier.pt", map_location={'cuda:0': 'cpu'})) else: + scaler = GradScaler() total_start = time.time() for epoch in range(n_epochs): classifier.train() epoch_loss = 0 - subset_2D = zip(batched_2d_slices.split(batch_size), slice_label.split(batch_size)) - subset_2D_val = zip(batched_2d_slices_val.split(1), slice_label.split(1)) # - progress_bar = tqdm(enumerate(subset_2D), total=len(idx), ncols=20) + indexes = list(torch.randperm(total_train_slices.shape[0])) + data_train = total_train_slices[indexes] # shuffle the training data + labels_train = total_train_labels[indexes] + subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size)) + progress_bar = tqdm(enumerate(subset_2D), total=len(indexes)/batch_size) progress_bar.set_description(f"Epoch {epoch}") - for step, (a,b) in progress_bar: images = a.to(device) classes = b.to(device) - optimizer.zero_grad(set_to_none=True) + weight=torch.tensor((3,1)).float().to(device) #account for the class imbalance in the dataset + optimizer_cls.zero_grad(set_to_none=True) timesteps = torch.randint(0, 1000, (len(images),)).to(device) - with autocast(enabled=True): + with autocast(enabled=False): # Generate random noise - noise = 0*torch.randn_like(images).to(device) + noise = torch.randn_like(images).to(device) # Get model prediction - # pred=classifier(images) - - pred = inferer(inputs=images, diffusion_model=classifier, noise=noise, timesteps=timesteps) #remove the class conditioning - print('pred', pred) - # noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps) #remove the class conditioning - loss = F.binary_cross_entropy_with_logits(pred[:,0].float(), classes.float()) - print('loss', loss) - #scaler.scale(loss).backward() - # scaler.step(optimizer) + noisy_img=scheduler.add_noise(images,noise, timesteps ) #add t steps of noise to the input image + pred=classifier(noisy_img, timesteps) + loss = F.cross_entropy(pred, classes.long(), weight=weight, reduction="mean") + loss.backward() - optimizer.step() - #scaler.update() + optimizer_cls.step() epoch_loss += loss.item() - progress_bar.set_postfix( - { - "loss": epoch_loss / (step + 1), - } - ) + { + "loss": epoch_loss / (step + 1), + } + ) epoch_loss_list.append(epoch_loss / (step + 1)) + print('final step train', step) + if (epoch + 1) % val_interval == 0: classifier.eval() val_epoch_loss = 0 - progress_bar = tqdm(enumerate(subset_2D_val), total=len(idx), ncols=70) - progress_bar.set_description(f"Epoch {epoch}") - for step, (a,b) in progress_bar: + subset_2D_val = zip(total_val_slices.split(batch_size), total_val_labels.split(batch_size)) # + progress_bar_val = tqdm(enumerate(subset_2D_val)) + progress_bar_val.set_description(f"Epoch {epoch}") + for step, (a,b) in progress_bar_val: images = a.to(device) classes = b.to(device) - - timesteps = torch.randint(0, 1000, (len(images),)).to(device)#torch.from_numpy(np.arange(0, 1000)[::-1].copy()) + timesteps = torch.randint(0, 1, (len(images),)).to(device) #check validation accuracy on the original images, i.e., do not add noise with torch.no_grad(): - with autocast(enabled=True): - noise = 0*torch.randn_like(images).to(device) - pred = inferer(inputs=images, diffusion_model=classifier, noise=noise, timesteps=timesteps) - val_loss = F.binary_cross_entropy_with_logits(pred[:,0].float(), classes.float()) + with autocast(enabled=False): + noise = torch.randn_like(images).to(device) + pred = classifier(images, timesteps) + val_loss = F.cross_entropy(pred, classes.long(), reduction="mean") val_epoch_loss += val_loss.item() - progress_bar.set_postfix( + _, predicted = torch.max(pred, 1); + progress_bar_val.set_postfix( { "val_loss": val_epoch_loss / (step + 1), } ) val_epoch_loss_list.append(val_epoch_loss / (step + 1)) + print('final step val', step) + total_time = time.time() - total_start print(f"train completed, total time: {total_time}.") - # torch.save(classifier.state_dict(), "./classifier.pt") -# %% [markdown] -# ### Learning curves - -# %% jupyter={"outputs_hidden": false} + torch.save(classifier.state_dict(), "./classifier.pt") + + ## Learning curves for the Classifier + plt.style.use("seaborn-bright") plt.title("Learning Curves", fontsize=20) + print('epl', len(epoch_loss_list)) plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color="C0", linewidth=2.0, label="Train") plt.plot( np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)), @@ -479,110 +491,99 @@ def get_batched_2d_axial_slices(data : Dict): plt.xlabel("Epochs", fontsize=16) plt.ylabel("Loss", fontsize=16) plt.legend(prop={"size": 14}) - #plt.show() - + plt.show() # %% [markdown] -# ### Sampling process with classifier-free guidance -# In order to sample using classifier-free guidance, for each step of the process we need to have 2 elements, one generated conditioned in the desired class (here we want to condition on Hands `=1`) and one using the unconditional class (`=-1`). -# Instead using directly the predicted class in every step, we use the unconditional plus the direction vector pointing to the condition that we want (`noise_pred_text - noise_pred_uncond`). The effect of the condition is defined by the `guidance_scale` defining the influence of our direction vector. +# ### For Image-to-Image Translation to a Healthy Subject, we pick a disesed subject of the validation set -# %% jupyter={"outputs_hidden": false} -model.eval() -guidance_scale = 0 -conditioning = torch.cat([-1 * torch.ones(1, 1, 1).float(), torch.ones(1, 1, 1).float()], dim=0).to(device) +# %% -# %% [markdown] -# ### Pick an input slice to be transformed -inputimg = batched_2d_slices_val[50][0,...] -plt.figure("input") +inputimg = total_val_slices[27][0,...] # Pick an input slice to be transformed (100,20 +inputlabel= total_val_labels[27] # Check whether it is healthy or diseased + +plt.figure("input"+str(inputlabel)) plt.imshow(inputimg, vmin=0, vmax=1, cmap="gray") plt.axis("off") plt.tight_layout() plt.show() +model.eval() +classifier.eval() -noise = inputimg[None,None,...]#torch.randn((1, 1, 64, 64)) -noise = noise.to(device) -scheduler.set_timesteps(num_inference_steps=1000) -L=20 -progress_bar = tqdm(range(L)) #go back and forth L timesteps +# %% [markdown] +# ### Encoding the input image in noise with the reversed DDIM sampling scheme +# In order to sample using gradient guidance, we first need to encode the input image in noise by using the reversed DDIM sampling scheme. +# We define the number of steps in the noising and denoising process by L. +# +# %% jupyter={"outputs_hidden": false} +L=180 +current_img = inputimg[None,None,...].to(device) +scheduler.set_timesteps(num_inference_steps=1000) +progress_bar = tqdm(range(L)) #go back and forth L timesteps for t in progress_bar: #go through the noising process - print('t noising', t) - with autocast(enabled=True): + with autocast(enabled=False): with torch.no_grad(): - - noise_input = noise - print('inputshape', noise_input.shape) - model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device)) - # noise_pred_uncond, noise_pred_text = model_output.chunk(2) #this is supposed to be epsilon - noise_pred = model_output #this is supposed to be epsilon - - noise, _ = scheduler.reversed_step(noise_pred, t, noise) + model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device)) + current_img, _ = scheduler.reversed_step(model_output, t, current_img) plt.style.use("default") -plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap="gray") +plt.imshow(current_img[0, 0].cpu(), vmin=0, vmax=1, cmap="gray") plt.tight_layout() plt.axis("off") plt.show() -def cond_fn(x, t, y=None): #compute the gradient - assert y is not None - with torch.enable_grad(): - x_in = x.detach().requires_grad_(True) - logits = classifier(x_in, t) - log_probs = F.log_softmax(logits, dim=-1) - selected = log_probs[range(len(logits)), y.view(-1)] - a = th.autograd.grad(selected.sum(), x_in)[0] - return a, a * args.classifier_scale -#desired class -y=torch.tensor(0) -scale=100 + +# %% [markdown] +# ### Denoising Process using gradient guidance +# From the noisy image, we apply DDIM sampling scheme for denoising for L steps. +# Additionally, we apply gradient guidance using the classifier network towards the desired class label y=0 (healthy). The scale s is used to amplify the gradient. + +# %% + + +y=torch.tensor(0) #define the desired class label +scale=1 #define the desired gradient scale s +progress_bar = tqdm(range(L)) #go back and forth L timesteps for i in progress_bar: #go through the denoising process + t=L-i - print('t denoising', t) with autocast(enabled=True): - with torch.enable_grad(): - noise_input = noise - print('inputshape', noise_input.shape) - model_output = model(noise_input, timesteps=torch.Tensor((t,)).to(noise.device)) + model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device)) # this is supposed to be epsilon - x_in = noise_input.detach().requires_grad_(True) - - logits = classifier(x_in, timesteps=torch.Tensor((t,)).to(noise.device)) - print('logits', logits) + with torch.enable_grad(): + x_in = current_img.detach().requires_grad_(True) + logits = classifier(x_in, timesteps=torch.Tensor((t,)).to(current_img.device)) log_probs = F.log_softmax(logits, dim=-1) selected = log_probs[range(len(logits)), y.view(-1)] a = torch.autograd.grad(selected.sum(), x_in)[0] - # noise_pred_uncond, noise_pred_text = model_output.chunk(2) #this is supposed to be epsilon - noise_pred = model_output # this is supposed to be epsilon - updated_noise=noise_pred - scale*a + alpha_prod_t = scheduler.alphas_cumprod[t] + updated_noise = model_output- (1 - alpha_prod_t).sqrt() * scale*a #update the predicted noise epsilon with the gradient of the classifier - noise, _ = scheduler.step(updated_noise, t, noise) + current_img, _ = scheduler.step(updated_noise, t, current_img) + torch.cuda.empty_cache() plt.style.use("default") -plt.imshow(noise[0, 0].cpu(), vmin=0, vmax=1, cmap="gray") +plt.imshow(current_img[0, 0].cpu().detach().numpy(), vmin=0, vmax=1, cmap="gray") plt.tight_layout() plt.axis("off") plt.show() -diff=inputimg.cpu()-noise[0, 0].cpu() +# %% [markdown] +# ### Anomaly Detection +# To get the anomaly map, we compute the difference between the input image the output of our image-to-image translation model, which is the healthy reconstruction. + +# %% + +diff=abs(inputimg.cpu()-current_img[0, 0].cpu()).detach().numpy() plt.style.use("default") plt.imshow(diff, cmap="jet") plt.tight_layout() plt.axis("off") plt.show() -# %% [markdown] -# ### Cleanup data directory -# -# Remove directory if a temporary was used. - -# %% - From 2a0bed97d8399a4c4a8bc72400a6a17fae8ebbbd Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 22 Feb 2023 17:42:30 +0100 Subject: [PATCH 05/12] cleaned up the classification network for gradient guidance --- .../networks/nets/diffusion_model_unet.py | 83 +------------------ 1 file changed, 2 insertions(+), 81 deletions(-) diff --git a/generative/networks/nets/diffusion_model_unet.py b/generative/networks/nets/diffusion_model_unet.py index 729c0bd0..59492ab4 100644 --- a/generative/networks/nets/diffusion_model_unet.py +++ b/generative/networks/nets/diffusion_model_unet.py @@ -1660,9 +1660,7 @@ def forward( class DiffusionModelEncoder(nn.Module): """ - Unet network with timestep embedding and attention mechanisms for conditioning based on - Rombach et al. "High-Resolution Image Synthesis with Latent Diffusion Models" https://arxiv.org/abs/2112.10752 - and Pinaya et al. "Brain Imaging Generation with Latent Diffusion Models" https://arxiv.org/abs/2209.07162 + Classification Network based on the Encoder of the Diffusion Model, followed by fully connected layers for classification Args: spatial_dims: number of spatial dimensions. @@ -1781,76 +1779,16 @@ def __init__( self.down_blocks.append(down_block) - # mid - self.middle_block = get_mid_block( - spatial_dims=spatial_dims, - in_channels=num_channels[-1], - temb_channels=time_embed_dim, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - with_conditioning=with_conditioning, - num_head_channels=num_head_channels[-1], - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - # up - self.up_blocks = nn.ModuleList([]) - reversed_block_out_channels = list(reversed(num_channels)) - reversed_attention_levels = list(reversed(attention_levels)) - reversed_num_head_channels = list(reversed(num_head_channels)) - output_channel = reversed_block_out_channels[0] - for i in range(len(reversed_block_out_channels)): - prev_output_channel = output_channel - output_channel = reversed_block_out_channels[i] - input_channel = reversed_block_out_channels[min(i + 1, len(num_channels) - 1)] - - is_final_block = i == len(num_channels) - 1 - up_block = get_up_block( - spatial_dims=spatial_dims, - in_channels=input_channel, - prev_output_channel=prev_output_channel, - out_channels=output_channel, - temb_channels=time_embed_dim, - num_res_blocks=num_res_blocks + 1, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_upsample=not is_final_block, - with_attn=(reversed_attention_levels[i] and not with_conditioning), - with_cross_attn=(reversed_attention_levels[i] and with_conditioning), - num_head_channels=reversed_num_head_channels[i], - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - - self.up_blocks.append(up_block) - # self.out = nn.Linear(4096, self.out_channels) self.out = nn.Sequential( nn.Linear(8192, 512), nn.ReLU(), nn.Dropout(0.2), nn.Linear(512, self.out_channels), - #nn.Sigmoid(), ) - # out - # self.out = nn.Sequential( - # nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels[0], eps=norm_eps, affine=True), - # nn.SiLU(), - # zero_module( - # Convolution( - # spatial_dims=spatial_dims, - # in_channels=num_channels[0], - # out_channels=out_channels, - # strides=1, - # kernel_size=3, - # padding=1, - # conv_only=True, - # ) - # ), - # ) - + def forward( self, @@ -1883,27 +1821,10 @@ def forward( # 4. down if context is not None and self.with_conditioning is False: raise ValueError("model should have with_conditioning = True if context is provided") - down_block_res_samples: List[torch.Tensor] = [h] for downsample_block in self.down_blocks: h, _ = downsample_block(hidden_states=h, temb=emb, context=context) - # for residual in res_samples: - # down_block_res_samples.append(residual) - - - - # # 5. mid h = h.reshape(h.shape[0], -1) - # - # # 6. up - # for upsample_block in self.up_blocks: - # res_samples = down_block_res_samples[-len(upsample_block.resnets) :] - # down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] - # h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, temb=emb, context=context) - - # 7. output block - - output=self.out(h) return output From 20535f0491913056dd46fb5bc194e88c4241b50e Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 23 Feb 2023 14:04:46 +0100 Subject: [PATCH 06/12] cleaning up --- .../networks/nets/diffusion_model_encoder.py | 1661 ------------- .../networks/nets/diffusion_model_unet.py | 19 +- generative/networks/schedulers/ddim.py | 25 +- tutorials/Untitled.ipynb | 33 - .../generative/2d_vqvae/2d_vqvae_tutorial.py | 1 - ...r_guidance_anomalydetection_tutorial.ipynb | 2158 +++++++++++++---- ...fier_guidance_anomalydetection_tutorial.py | 334 +-- .../Untitled.ipynb | 33 - .../Untitled1.ipynb | 6 - .../load_2d_brats.ipynb | 437 ---- .../load_2d_brats.py | 201 -- 11 files changed, 1882 insertions(+), 3026 deletions(-) delete mode 100644 generative/networks/nets/diffusion_model_encoder.py delete mode 100644 tutorials/Untitled.ipynb delete mode 100644 tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb delete mode 100644 tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb delete mode 100644 tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb delete mode 100644 tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py diff --git a/generative/networks/nets/diffusion_model_encoder.py b/generative/networks/nets/diffusion_model_encoder.py deleted file mode 100644 index 7d87181c..00000000 --- a/generative/networks/nets/diffusion_model_encoder.py +++ /dev/null @@ -1,1661 +0,0 @@ -# Copyright (c) MONAI Consortium -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ========================================================================= -# Adapted from https://github.com/huggingface/diffusers -# which has the following license: -# https://github.com/huggingface/diffusers/blob/main/LICENSE - -# Copyright 2022 UC Berkeley Team and The HuggingFace Team. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ========================================================================= - -import math -from typing import List, Optional, Sequence, Tuple, Union - -import torch -import torch.nn.functional as F -from monai.networks.blocks import Convolution -from monai.networks.layers.factories import Pool -from torch import nn - -__all__ = ["DiffusionModelEncoder"] - - -def zero_module(module: nn.Module) -> nn.Module: - """ - Zero out the parameters of a module and return it. - """ - for p in module.parameters(): - p.detach().zero_() - return module - - -class GEGLU(nn.Module): - """ - A variant of the gated linear unit activation function from https://arxiv.org/abs/2002.05202. - - Args: - dim_in: number of channels in the input. - dim_out: number of channels in the output. - """ - - def __init__(self, dim_in: int, dim_out: int) -> None: - super().__init__() - self.proj = nn.Linear(dim_in, dim_out * 2) - - def forward(self, x: torch.Tensor) -> torch.Tensor: - x, gate = self.proj(x).chunk(2, dim=-1) - return x * F.gelu(gate.to(dtype=torch.float32)).to(dtype=gate.dtype) - - -class FeedForward(nn.Module): - """ - A feed-forward layer. - - Args: - num_channels: number of channels in the input. - dim_out: number of channels in the output. If not given, defaults to `dim`. - mult: multiplier to use for the hidden dimension. - dropout: dropout probability to use. - """ - - def __init__(self, num_channels: int, dim_out: Optional[int] = None, mult: int = 4, dropout: float = 0.0) -> None: - super().__init__() - inner_dim = int(num_channels * mult) - dim_out = dim_out if dim_out is not None else num_channels - - self.net = nn.Sequential(GEGLU(num_channels, inner_dim), nn.Dropout(dropout), nn.Linear(inner_dim, dim_out)) - - def forward(self, x: torch.Tensor) -> torch.Tensor: - return self.net(x) - - -class CrossAttention(nn.Module): - """ - A cross attention layer. - - Args: - query_dim: number of channels in the query. - cross_attention_dim: number of channels in the context. - num_attention_heads: number of heads to use for multi-head attention. - num_head_channels: number of channels in each head. - dropout: dropout probability to use. - """ - - def __init__( - self, - query_dim: int, - cross_attention_dim: Optional[int] = None, - num_attention_heads: int = 8, - num_head_channels: int = 64, - dropout: float = 0.0, - ) -> None: - super().__init__() - inner_dim = num_head_channels * num_attention_heads - cross_attention_dim = cross_attention_dim if cross_attention_dim is not None else query_dim - - self.scale = num_head_channels**-0.5 - self.heads = num_attention_heads - - self.to_q = nn.Linear(query_dim, inner_dim, bias=False) - self.to_k = nn.Linear(cross_attention_dim, inner_dim, bias=False) - self.to_v = nn.Linear(cross_attention_dim, inner_dim, bias=False) - - self.to_out = nn.Sequential(nn.Linear(inner_dim, query_dim), nn.Dropout(dropout)) - - def reshape_heads_to_batch_dim(self, x: torch.Tensor) -> torch.Tensor: - batch_size, seq_len, dim = x.shape - head_size = self.heads - x = x.reshape(batch_size, seq_len, head_size, dim // head_size) - x = x.permute(0, 2, 1, 3).reshape(batch_size * head_size, seq_len, dim // head_size) - return x - - def reshape_batch_dim_to_heads(self, x: torch.Tensor) -> torch.Tensor: - batch_size, seq_len, dim = x.shape - head_size = self.heads - x = x.reshape(batch_size // head_size, head_size, seq_len, dim) - x = x.permute(0, 2, 1, 3).reshape(batch_size // head_size, seq_len, dim * head_size) - return x - - def _attention(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor) -> torch.Tensor: - attention_scores = torch.matmul(query, key.transpose(-1, -2)) * self.scale - attention_probs = attention_scores.softmax(dim=-1) - # compute attention output - hidden_states = torch.matmul(attention_probs, value) - - # reshape hidden_states - hidden_states = self.reshape_batch_dim_to_heads(hidden_states) - return hidden_states - - def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: - query = self.to_q(x) - context = context if context is not None else x - key = self.to_k(context) - value = self.to_v(context) - - query = self.reshape_heads_to_batch_dim(query) - key = self.reshape_heads_to_batch_dim(key) - value = self.reshape_heads_to_batch_dim(value) - - x = self._attention(query, key, value) - - return self.to_out(x) - - -class BasicTransformerBlock(nn.Module): - """ - A basic Transformer block. - - Args: - num_channels: number of channels in the input and output. - num_attention_heads: number of heads to use for multi-head attention. - num_head_channels: number of channels in each attention head. - dropout: dropout probability to use. - cross_attention_dim: size of the context vector for cross attention. - """ - - def __init__( - self, - num_channels: int, - num_attention_heads: int, - num_head_channels: int, - dropout: float = 0.0, - cross_attention_dim: Optional[int] = None, - ) -> None: - super().__init__() - self.attn1 = CrossAttention( - query_dim=num_channels, - num_attention_heads=num_attention_heads, - num_head_channels=num_head_channels, - dropout=dropout, - ) # is a self-attention - self.ff = FeedForward(num_channels, dropout=dropout) - self.attn2 = CrossAttention( - query_dim=num_channels, - cross_attention_dim=cross_attention_dim, - num_attention_heads=num_attention_heads, - num_head_channels=num_head_channels, - dropout=dropout, - ) # is a self-attention if context is None - self.norm1 = nn.LayerNorm(num_channels) - self.norm2 = nn.LayerNorm(num_channels) - self.norm3 = nn.LayerNorm(num_channels) - - def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: - # 1. Self-Attention - x = self.attn1(self.norm1(x)) + x - - # 2. Cross-Attention - x = self.attn2(self.norm2(x), context=context) + x - - # 3. Feed-forward - x = self.ff(self.norm3(x)) + x - return x - - -class SpatialTransformer(nn.Module): - """ - Transformer block for image-like data. First, project the input (aka embedding) and reshape to b, t, d. Then apply - standard transformer action. Finally, reshape to image. - - Args: - spatial_dims: number of spatial dimensions. - in_channels: number of channels in the input and output. - num_attention_heads: number of heads to use for multi-head attention. - num_head_channels: number of channels in each attention head. - num_layers: number of layers of Transformer blocks to use. - dropout: dropout probability to use. - norm_num_groups: number of groups for the normalization. - norm_eps: epsilon for the normalization. - cross_attention_dim: number of context dimensions to use. - """ - - def __init__( - self, - spatial_dims: int, - in_channels: int, - num_attention_heads: int, - num_head_channels: int, - num_layers: int = 1, - dropout: float = 0.0, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - cross_attention_dim: Optional[int] = None, - ) -> None: - super().__init__() - self.spatial_dims = spatial_dims - self.in_channels = in_channels - inner_dim = num_attention_heads * num_head_channels - - self.norm = nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=norm_eps, affine=True) - - self.proj_in = Convolution( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=inner_dim, - strides=1, - kernel_size=1, - padding=0, - conv_only=True, - ) - - self.transformer_blocks = nn.ModuleList( - [ - BasicTransformerBlock( - num_channels=inner_dim, - num_attention_heads=num_attention_heads, - num_head_channels=num_head_channels, - dropout=dropout, - cross_attention_dim=cross_attention_dim, - ) - for _ in range(num_layers) - ] - ) - - self.proj_out = zero_module( - Convolution( - spatial_dims=spatial_dims, - in_channels=inner_dim, - out_channels=in_channels, - strides=1, - kernel_size=1, - padding=0, - conv_only=True, - ) - ) - - def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: - # note: if no context is given, cross-attention defaults to self-attention - batch = channel = height = width = depth = -1 - if self.spatial_dims == 2: - batch, channel, height, width = x.shape - if self.spatial_dims == 3: - batch, channel, height, width, depth = x.shape - - residual = x - x = self.norm(x) - x = self.proj_in(x) - - inner_dim = x.shape[1] - - if self.spatial_dims == 2: - x = x.permute(0, 2, 3, 1).reshape(batch, height * width, inner_dim) - if self.spatial_dims == 3: - x = x.permute(0, 2, 3, 4, 1).reshape(batch, height * width * depth, inner_dim) - - for block in self.transformer_blocks: - x = block(x, context=context) - - if self.spatial_dims == 2: - x = x.reshape(batch, height, width, inner_dim).permute(0, 3, 1, 2) - if self.spatial_dims == 3: - x = x.reshape(batch, height, width, depth, inner_dim).permute(0, 4, 1, 2, 3) - - x = self.proj_out(x) - return x + residual - - -class AttentionBlock(nn.Module): - """ - An attention block that allows spatial positions to attend to each other. Uses three q, k, v linear layers to - compute attention. - - Args: - spatial_dims: number of spatial dimensions. - num_channels: number of channels in the input and output. - num_head_channels: number of channels in each attention head. - norm_num_groups: number of groups to use for group norm. - norm_eps: epsilon value to use for group norm. - """ - - def __init__( - self, - spatial_dims: int, - num_channels: int, - num_head_channels: Optional[int] = None, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - ) -> None: - super().__init__() - self.spatial_dims = spatial_dims - self.num_channels = num_channels - - self.num_heads = num_channels // num_head_channels if num_head_channels is not None else 1 - self.num_head_size = num_head_channels - self.norm = nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels, eps=norm_eps, affine=True) - - # define q,k,v as linear layers - self.query = nn.Linear(num_channels, num_channels) - self.key = nn.Linear(num_channels, num_channels) - self.value = nn.Linear(num_channels, num_channels) - - self.proj_attn = nn.Linear(num_channels, num_channels) - - def transpose_for_scores(self, projection: torch.Tensor) -> torch.Tensor: - new_projection_shape = projection.size()[:-1] + (self.num_heads, -1) - # move heads to 2nd position (B, T, H * D) -> (B, T, H, D) -> (B, H, T, D) - new_projection = projection.view(new_projection_shape).permute(0, 2, 1, 3) - return new_projection - - def forward(self, x: torch.Tensor) -> torch.Tensor: - residual = x - - batch = channel = height = width = depth = -1 - if self.spatial_dims == 2: - batch, channel, height, width = x.shape - if self.spatial_dims == 3: - batch, channel, height, width, depth = x.shape - - # norm - x = self.norm(x) - - if self.spatial_dims == 2: - x = x.view(batch, channel, height * width).transpose(1, 2) - if self.spatial_dims == 3: - x = x.view(batch, channel, height * width * depth).transpose(1, 2) - - # proj to q, k, v - query_proj = self.query(x) - key_proj = self.key(x) - value_proj = self.value(x) - - # transpose - query_states = self.transpose_for_scores(query_proj) - key_states = self.transpose_for_scores(key_proj) - value_states = self.transpose_for_scores(value_proj) - - # get scores - scale = 1 / math.sqrt(math.sqrt(self.num_channels / self.num_heads)) - attention_scores = torch.matmul(query_states * scale, key_states.transpose(-1, -2) * scale) - attention_probs = torch.softmax(attention_scores.float(), dim=-1) - - # compute attention output - x = torch.matmul(attention_probs, value_states) - - x = x.permute(0, 2, 1, 3).contiguous() - new_x_shape = x.size()[:-2] + (self.num_channels,) - x = x.view(new_x_shape) - - # compute next hidden states - x = self.proj_attn(x) - - if self.spatial_dims == 2: - x = x.transpose(-1, -2).reshape(batch, channel, height, width) - if self.spatial_dims == 3: - x = x.transpose(-1, -2).reshape(batch, channel, height, width, depth) - - return x + residual - - -def get_timestep_embedding(timesteps: torch.Tensor, embedding_dim: int, max_period: int = 10000) -> torch.Tensor: - """ - Create sinusoidal timestep embeddingsfollowing the implementation in Ho et al. "Denoising Diffusion Probabilistic - Models" https://arxiv.org/abs/2006.11239. - - Args: - timesteps: a 1-D Tensor of N indices, one per batch element. - embedding_dim: the dimension of the output. - max_period: controls the minimum frequency of the embeddings. - """ - assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array" - - half_dim = embedding_dim // 2 - exponent = -math.log(max_period) * torch.arange(start=0, end=half_dim, dtype=torch.float32, device=timesteps.device) - freqs = torch.exp(exponent / half_dim) - - args = timesteps[:, None].float() * freqs[None, :] - embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) - - # zero pad - if embedding_dim % 2 == 1: - embedding = torch.nn.functional.pad(embedding, (0, 1, 0, 0)) - - return embedding - - -class Downsample(nn.Module): - """ - Downsampling layer. - - Args: - spatial_dims: number of spatial dimensions. - num_channels: number of input channels. - use_conv: if True uses Convolution instead of Pool average to perform downsampling. - out_channels: number of output channels. - padding: controls the amount of implicit zero-paddings on both sides for padding number of points - for each dimension. - """ - - def __init__( - self, - spatial_dims: int, - num_channels: int, - use_conv: bool, - out_channels: Optional[int] = None, - padding: int = 1, - ) -> None: - super().__init__() - self.num_channels = num_channels - self.out_channels = out_channels or num_channels - self.use_conv = use_conv - if use_conv: - self.op = Convolution( - spatial_dims=spatial_dims, - in_channels=self.num_channels, - out_channels=self.out_channels, - strides=2, - kernel_size=3, - padding=padding, - conv_only=True, - ) - else: - assert self.num_channels == self.out_channels - self.op = Pool[Pool.AVG, spatial_dims](kernel_size=2, stride=2) - - def forward(self, x: torch.Tensor) -> torch.Tensor: - assert x.shape[1] == self.num_channels - return self.op(x) - - -class Upsample(nn.Module): - """ - Upsampling layer with an optional convolution. - - Args: - spatial_dims: number of spatial dimensions. - num_channels: number of input channels. - use_conv: if True uses Convolution instead of Pool average to perform downsampling. - out_channels: number of output channels. - padding: controls the amount of implicit zero-paddings on both sides for padding number of points for each - dimension. - """ - - def __init__( - self, - spatial_dims: int, - num_channels: int, - use_conv: bool, - out_channels: Optional[int] = None, - padding: int = 1, - ) -> None: - super().__init__() - self.num_channels = num_channels - self.out_channels = out_channels or num_channels - self.use_conv = use_conv - if use_conv: - self.conv = Convolution( - spatial_dims=spatial_dims, - in_channels=self.num_channels, - out_channels=self.out_channels, - strides=1, - kernel_size=3, - padding=padding, - conv_only=True, - ) - - def forward(self, x: torch.Tensor) -> torch.Tensor: - assert x.shape[1] == self.num_channels - x = F.interpolate(x, scale_factor=2.0, mode="nearest") - if self.use_conv: - x = self.conv(x) - return x - - -class ResnetBlock(nn.Module): - """ - Residual block with timestep conditioning. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - temb_channels: number of timestep embedding channels. - out_channels: number of output channels. - up: if True, performs upsampling. - down: if True, performs downsampling. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - """ - - def __init__( - self, - spatial_dims: int, - in_channels: int, - temb_channels: int, - out_channels: Optional[int] = None, - up: bool = False, - down: bool = False, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - ) -> None: - super().__init__() - self.spatial_dims = spatial_dims - self.channels = in_channels - self.emb_channels = temb_channels - self.out_channels = out_channels or in_channels - self.up = up - self.down = down - - self.norm1 = nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=norm_eps, affine=True) - self.nonlinearity = nn.SiLU() - self.conv1 = Convolution( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=self.out_channels, - strides=1, - kernel_size=3, - padding=1, - conv_only=True, - ) - - self.upsample = self.downsample = None - if self.up: - self.upsample = Upsample(spatial_dims, in_channels, use_conv=False) - elif down: - self.downsample = Downsample(spatial_dims, in_channels, use_conv=False) - - self.time_emb_proj = nn.Linear( - temb_channels, - self.out_channels, - ) - - self.norm2 = nn.GroupNorm(num_groups=norm_num_groups, num_channels=self.out_channels, eps=norm_eps, affine=True) - self.conv2 = zero_module( - Convolution( - spatial_dims=spatial_dims, - in_channels=self.out_channels, - out_channels=self.out_channels, - strides=1, - kernel_size=3, - padding=1, - conv_only=True, - ) - ) - - if self.out_channels == in_channels: - self.skip_connection = nn.Identity() - else: - self.skip_connection = Convolution( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=self.out_channels, - strides=1, - kernel_size=1, - padding=0, - conv_only=True, - ) - - def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor: - h = x - h = self.norm1(h) - h = self.nonlinearity(h) - - if self.upsample is not None: - if h.shape[0] >= 64: - x = x.contiguous() - h = h.contiguous() - x = self.upsample(x) - h = self.upsample(h) - elif self.downsample is not None: - x = self.downsample(x) - h = self.downsample(h) - - h = self.conv1(h) - - if self.spatial_dims == 2: - temb = self.time_emb_proj(self.nonlinearity(emb))[:, :, None, None] - else: - temb = self.time_emb_proj(self.nonlinearity(emb))[:, :, None, None, None] - h = h + temb - - h = self.norm2(h) - h = self.nonlinearity(h) - h = self.conv2(h) - - return self.skip_connection(x) + h - - -class DownBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int = 1, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - add_downsample: bool = True, - downsample_padding: int = 1, - ) -> None: - """ - Unet's down block containing resnet and downsamplers blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - out_channels: number of output channels. - temb_channels: number of timestep embedding channels. - num_res_blocks: number of residual blocks. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - add_downsample: if True add downsample block. - downsample_padding: padding used in the downsampling block. - """ - super().__init__() - resnets = [] - - for i in range(num_res_blocks): - in_channels = in_channels if i == 0 else out_channels - resnets.append( - ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=out_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - - self.resnets = nn.ModuleList(resnets) - - if add_downsample: - self.downsampler = Downsample( - spatial_dims=spatial_dims, - num_channels=out_channels, - use_conv=True, - out_channels=out_channels, - padding=downsample_padding, - ) - else: - self.downsampler = None - - def forward( - self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None - ) -> Tuple[torch.Tensor, List[torch.Tensor]]: - del context - output_states = [] - - for resnet in self.resnets: - hidden_states = resnet(hidden_states, temb) - output_states.append(hidden_states) - - if self.downsampler is not None: - hidden_states = self.downsampler(hidden_states) - output_states.append(hidden_states) - - return hidden_states, output_states - - -class AttnDownBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int = 1, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - add_downsample: bool = True, - downsample_padding: int = 1, - num_head_channels: int = 1, - ) -> None: - """ - Unet's down block containing resnet, downsamplers and self-attention blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - out_channels: number of output channels. - temb_channels: number of timestep embedding channels. - num_res_blocks: number of residual blocks. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - add_downsample: if True add downsample block. - downsample_padding: padding used in the downsampling block. - num_head_channels: number of channels in each attention head. - """ - super().__init__() - resnets = [] - attentions = [] - - for i in range(num_res_blocks): - in_channels = in_channels if i == 0 else out_channels - resnets.append( - ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=out_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - attentions.append( - AttentionBlock( - spatial_dims=spatial_dims, - num_channels=out_channels, - num_head_channels=num_head_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - - self.attentions = nn.ModuleList(attentions) - self.resnets = nn.ModuleList(resnets) - - if add_downsample: - self.downsampler = Downsample( - spatial_dims=spatial_dims, - num_channels=out_channels, - use_conv=True, - out_channels=out_channels, - padding=downsample_padding, - ) - else: - self.downsampler = None - - def forward( - self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None - ) -> Tuple[torch.Tensor, List[torch.Tensor]]: - del context - output_states = [] - - for resnet, attn in zip(self.resnets, self.attentions): - hidden_states = resnet(hidden_states, temb) - hidden_states = attn(hidden_states) - output_states.append(hidden_states) - - if self.downsampler is not None: - hidden_states = self.downsampler(hidden_states) - output_states.append(hidden_states) - - return hidden_states, output_states - - -class CrossAttnDownBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int = 1, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - add_downsample: bool = True, - downsample_padding: int = 1, - num_head_channels: int = 1, - transformer_num_layers: int = 1, - cross_attention_dim: Optional[int] = None, - ) -> None: - """ - Unet's down block containing resnet, downsamplers and cross-attention blocks. - - Args: - spatial_dims: number of spatial dimensions. - in_channels: number of input channels. - out_channels: number of output channels. - temb_channels: number of timestep embedding channels. - num_res_blocks: number of residual blocks. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - add_downsample: if True add downsample block. - downsample_padding: padding used in the downsampling block. - num_head_channels: number of channels in each attention head. - transformer_num_layers: number of layers of Transformer blocks to use. - cross_attention_dim: number of context dimensions to use. - """ - super().__init__() - resnets = [] - attentions = [] - - for i in range(num_res_blocks): - in_channels = in_channels if i == 0 else out_channels - resnets.append( - ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=out_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - - attentions.append( - SpatialTransformer( - spatial_dims=spatial_dims, - in_channels=out_channels, - num_attention_heads=out_channels // num_head_channels, - num_head_channels=num_head_channels, - num_layers=transformer_num_layers, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - cross_attention_dim=cross_attention_dim, - ) - ) - - self.attentions = nn.ModuleList(attentions) - self.resnets = nn.ModuleList(resnets) - - if add_downsample: - self.downsampler = Downsample( - spatial_dims=spatial_dims, - num_channels=out_channels, - use_conv=True, - out_channels=out_channels, - padding=downsample_padding, - ) - else: - self.downsampler = None - - def forward( - self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None - ) -> Tuple[torch.Tensor, List[torch.Tensor]]: - output_states = [] - - for resnet, attn in zip(self.resnets, self.attentions): - hidden_states = resnet(hidden_states, temb) - hidden_states = attn(hidden_states, context=context) - output_states.append(hidden_states) - - if self.downsampler is not None: - hidden_states = self.downsampler(hidden_states) - output_states.append(hidden_states) - - return hidden_states, output_states - - -class AttnMidBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - temb_channels: int, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - num_head_channels: int = 1, - ) -> None: - """ - Unet's mid block containing resnet and self-attention blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - temb_channels: number of timestep embedding channels. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - num_head_channels: number of channels in each attention head. - """ - super().__init__() - self.attention = None - - self.resnet_1 = ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=in_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - self.attention = AttentionBlock( - spatial_dims=spatial_dims, - num_channels=in_channels, - num_head_channels=num_head_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - - self.resnet_2 = ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=in_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - - def forward( - self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None - ) -> torch.Tensor: - del context - hidden_states = self.resnet_1(hidden_states, temb) - hidden_states = self.attention(hidden_states) - hidden_states = self.resnet_2(hidden_states, temb) - - return hidden_states - - -class CrossAttnMidBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - temb_channels: int, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - num_head_channels: int = 1, - transformer_num_layers: int = 1, - cross_attention_dim: Optional[int] = None, - ) -> None: - """ - Unet's mid block containing resnet and cross-attention blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - temb_channels: number of timestep embedding channels - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - num_head_channels: number of channels in each attention head. - transformer_num_layers: number of layers of Transformer blocks to use. - cross_attention_dim: number of context dimensions to use. - """ - super().__init__() - self.attention = None - - self.resnet_1 = ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=in_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - self.attention = SpatialTransformer( - spatial_dims=spatial_dims, - in_channels=in_channels, - num_attention_heads=in_channels // num_head_channels, - num_head_channels=num_head_channels, - num_layers=transformer_num_layers, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - cross_attention_dim=cross_attention_dim, - ) - self.resnet_2 = ResnetBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=in_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - - def forward( - self, hidden_states: torch.Tensor, temb: torch.Tensor, context: Optional[torch.Tensor] = None - ) -> torch.Tensor: - hidden_states = self.resnet_1(hidden_states, temb) - hidden_states = self.attention(hidden_states, context=context) - hidden_states = self.resnet_2(hidden_states, temb) - - return hidden_states - - -class UpBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - prev_output_channel: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int = 1, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - add_upsample: bool = True, - ) -> None: - """ - Unet's up block containing resnet and upsamplers blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - prev_output_channel: number of channels from residual connection. - out_channels: number of output channels. - temb_channels: number of timestep embedding channels. - num_res_blocks: number of residual blocks. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - add_upsample: if True add downsample block. - """ - super().__init__() - resnets = [] - - for i in range(num_res_blocks): - res_skip_channels = in_channels if (i == num_res_blocks - 1) else out_channels - resnet_in_channels = prev_output_channel if i == 0 else out_channels - - resnets.append( - ResnetBlock( - spatial_dims=spatial_dims, - in_channels=resnet_in_channels + res_skip_channels, - out_channels=out_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - - self.resnets = nn.ModuleList(resnets) - - if add_upsample: - self.upsampler = Upsample( - spatial_dims=spatial_dims, num_channels=out_channels, use_conv=True, out_channels=out_channels - ) - else: - self.upsampler = None - - def forward( - self, - hidden_states: torch.Tensor, - res_hidden_states_list: List[torch.Tensor], - temb: torch.Tensor, - context: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - del context - for i, resnet in enumerate(self.resnets): - # pop res hidden states - res_hidden_states = res_hidden_states_list[-1] - res_hidden_states_list = res_hidden_states_list[:-1] - hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) - - hidden_states = resnet(hidden_states, temb) - - if self.upsampler is not None: - hidden_states = self.upsampler(hidden_states) - - return hidden_states - - -class AttnUpBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - prev_output_channel: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int = 1, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - add_upsample: bool = True, - num_head_channels: int = 1, - ) -> None: - """ - Unet's up block containing resnet, upsamplers, and self-attention blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - prev_output_channel: number of channels from residual connection. - out_channels: number of output channels. - temb_channels: number of timestep embedding channels. - num_res_blocks: number of residual blocks. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - add_upsample: if True add downsample block. - num_head_channels: number of channels in each attention head. - """ - super().__init__() - resnets = [] - attentions = [] - - for i in range(num_res_blocks): - res_skip_channels = in_channels if (i == num_res_blocks - 1) else out_channels - resnet_in_channels = prev_output_channel if i == 0 else out_channels - - resnets.append( - ResnetBlock( - spatial_dims=spatial_dims, - in_channels=resnet_in_channels + res_skip_channels, - out_channels=out_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - attentions.append( - AttentionBlock( - spatial_dims=spatial_dims, - num_channels=out_channels, - num_head_channels=num_head_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - - self.resnets = nn.ModuleList(resnets) - self.attentions = nn.ModuleList(attentions) - - if add_upsample: - self.upsampler = Upsample( - spatial_dims=spatial_dims, num_channels=out_channels, use_conv=True, out_channels=out_channels - ) - else: - self.upsampler = None - - def forward( - self, - hidden_states: torch.Tensor, - res_hidden_states_list: List[torch.Tensor], - temb: torch.Tensor, - context: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - del context - for resnet, attn in zip(self.resnets, self.attentions): - # pop res hidden states - res_hidden_states = res_hidden_states_list[-1] - res_hidden_states_list = res_hidden_states_list[:-1] - hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) - - hidden_states = resnet(hidden_states, temb) - hidden_states = attn(hidden_states) - - if self.upsampler is not None: - hidden_states = self.upsampler(hidden_states) - - return hidden_states - - -class CrossAttnUpBlock(nn.Module): - def __init__( - self, - spatial_dims: int, - in_channels: int, - prev_output_channel: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int = 1, - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - add_upsample: bool = True, - num_head_channels: int = 1, - transformer_num_layers: int = 1, - cross_attention_dim: Optional[int] = None, - ) -> None: - """ - Unet's up block containing resnet, upsamplers, and self-attention blocks. - - Args: - spatial_dims: The number of spatial dimensions. - in_channels: number of input channels. - prev_output_channel: number of channels from residual connection. - out_channels: number of output channels. - temb_channels: number of timestep embedding channels. - num_res_blocks: number of residual blocks. - norm_num_groups: number of groups for the group normalization. - norm_eps: epsilon for the group normalization. - add_upsample: if True add downsample block. - num_head_channels: number of channels in each attention head. - transformer_num_layers: number of layers of Transformer blocks to use. - cross_attention_dim: number of context dimensions to use. - """ - super().__init__() - resnets = [] - attentions = [] - - for i in range(num_res_blocks): - res_skip_channels = in_channels if (i == num_res_blocks - 1) else out_channels - resnet_in_channels = prev_output_channel if i == 0 else out_channels - - resnets.append( - ResnetBlock( - spatial_dims=spatial_dims, - in_channels=resnet_in_channels + res_skip_channels, - out_channels=out_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - ) - ) - attentions.append( - SpatialTransformer( - spatial_dims=spatial_dims, - in_channels=out_channels, - num_attention_heads=out_channels // num_head_channels, - num_head_channels=num_head_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - ) - - self.attentions = nn.ModuleList(attentions) - self.resnets = nn.ModuleList(resnets) - - if add_upsample: - self.upsampler = Upsample( - spatial_dims=spatial_dims, num_channels=out_channels, use_conv=True, out_channels=out_channels - ) - else: - self.upsampler = None - - def forward( - self, - hidden_states: torch.Tensor, - res_hidden_states_list: List[torch.Tensor], - temb: torch.Tensor, - context: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - for resnet, attn in zip(self.resnets, self.attentions): - # pop res hidden states - res_hidden_states = res_hidden_states_list[-1] - res_hidden_states_list = res_hidden_states_list[:-1] - hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) - - hidden_states = resnet(hidden_states, temb) - hidden_states = attn(hidden_states, context=context) - - if self.upsampler is not None: - hidden_states = self.upsampler(hidden_states) - - return hidden_states - - -def get_down_block( - spatial_dims: int, - in_channels: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int, - norm_num_groups: int, - norm_eps: float, - add_downsample: bool, - with_attn: bool, - with_cross_attn: bool, - num_head_channels: int, - transformer_num_layers: int, - cross_attention_dim: Optional[int], -) -> nn.Module: - if with_attn: - return AttnDownBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=out_channels, - temb_channels=temb_channels, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_downsample=add_downsample, - num_head_channels=num_head_channels, - ) - elif with_cross_attn: - return CrossAttnDownBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=out_channels, - temb_channels=temb_channels, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_downsample=add_downsample, - num_head_channels=num_head_channels, - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - else: - return DownBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=out_channels, - temb_channels=temb_channels, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_downsample=add_downsample, - ) - - -def get_mid_block( - spatial_dims: int, - in_channels: int, - temb_channels: int, - norm_num_groups: int, - norm_eps: float, - with_conditioning: bool, - num_head_channels: int, - transformer_num_layers: int, - cross_attention_dim: Optional[int], -) -> nn.Module: - if with_conditioning: - return CrossAttnMidBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - num_head_channels=num_head_channels, - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - else: - return AttnMidBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - temb_channels=temb_channels, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - num_head_channels=num_head_channels, - ) - - -def get_up_block( - spatial_dims: int, - in_channels: int, - prev_output_channel: int, - out_channels: int, - temb_channels: int, - num_res_blocks: int, - norm_num_groups: int, - norm_eps: float, - add_upsample: bool, - with_attn: bool, - with_cross_attn: bool, - num_head_channels: int, - transformer_num_layers: int, - cross_attention_dim: Optional[int], -) -> nn.Module: - if with_attn: - return AttnUpBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - prev_output_channel=prev_output_channel, - out_channels=out_channels, - temb_channels=temb_channels, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_upsample=add_upsample, - num_head_channels=num_head_channels, - ) - elif with_cross_attn: - return CrossAttnUpBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - prev_output_channel=prev_output_channel, - out_channels=out_channels, - temb_channels=temb_channels, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_upsample=add_upsample, - num_head_channels=num_head_channels, - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - else: - return UpBlock( - spatial_dims=spatial_dims, - in_channels=in_channels, - prev_output_channel=prev_output_channel, - out_channels=out_channels, - temb_channels=temb_channels, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_upsample=add_upsample, - ) - - -class DiffusionModelEncoder(nn.Module): - """ - Unet network with timestep embedding and attention mechanisms for conditioning based on - Rombach et al. "High-Resolution Image Synthesis with Latent Diffusion Models" https://arxiv.org/abs/2112.10752 - and Pinaya et al. "Brain Imaging Generation with Latent Diffusion Models" https://arxiv.org/abs/2209.07162 - - Args: - spatial_dims: number of spatial dimensions. - in_channels: number of input channels. - out_channels: number of output channels. - num_res_blocks: number of residual blocks (see ResnetBlock) per level. - num_channels: tuple of block output channels. - attention_levels: list of levels to add attention. - norm_num_groups: number of groups for the normalization. - norm_eps: epsilon for the normalization. - num_head_channels: number of channels in each attention head. - with_conditioning: if True add spatial transformers to perform conditioning. - transformer_num_layers: number of layers of Transformer blocks to use. - cross_attention_dim: number of context dimensions to use. - num_class_embeds: if specified (as an int), then this model will be class-conditional with `num_class_embeds` - classes. - """ - - def __init__( - self, - spatial_dims: int, - in_channels: int, - out_channels: int, - num_res_blocks: int, - num_channels: Sequence[int] = (32, 64, 64, 64), - attention_levels: Sequence[bool] = (False, False, True, True), - norm_num_groups: int = 32, - norm_eps: float = 1e-6, - num_head_channels: Union[int, Sequence[int]] = 8, - with_conditioning: bool = False, - transformer_num_layers: int = 1, - cross_attention_dim: Optional[int] = None, - num_class_embeds: Optional[int] = None, - ) -> None: - super().__init__() - if with_conditioning is True and cross_attention_dim is None: - raise ValueError( - ( - "DiffusionModelUNet expects dimension of the cross-attention conditioning (cross_attention_dim) " - "when using with_conditioning." - ) - ) - if cross_attention_dim is not None and with_conditioning is False: - raise ValueError( - "DiffusionModelUNet expects with_conditioning=True when specifying the cross_attention_dim." - ) - - # All number of channels should be multiple of num_groups - if any((out_channel % norm_num_groups) != 0 for out_channel in num_channels): - raise ValueError("DiffusionModelUNet expects all num_channels being multiple of norm_num_groups") - - if isinstance(num_head_channels, int): - num_head_channels = (num_head_channels,) * len(attention_levels) - - if len(num_head_channels) != len(attention_levels): - raise ValueError( - "num_head_channels should have the same length as attention_levels. For the i levels without attention," - " i.e. `attention_level[i]=False`, the num_head_channels[i] will be ignored." - ) - - self.in_channels = in_channels - self.block_out_channels = num_channels - self.out_channels = out_channels - self.num_res_blocks = num_res_blocks - self.attention_levels = attention_levels - self.num_head_channels = num_head_channels - self.with_conditioning = with_conditioning - - # input - self.conv_in = Convolution( - spatial_dims=spatial_dims, - in_channels=in_channels, - out_channels=num_channels[0], - strides=1, - kernel_size=3, - padding=1, - conv_only=True, - ) - - # time - time_embed_dim = num_channels[0] * 4 - self.time_embed = nn.Sequential( - nn.Linear(num_channels[0], time_embed_dim), - nn.SiLU(), - nn.Linear(time_embed_dim, time_embed_dim), - ) - - # class embedding - self.num_class_embeds = num_class_embeds - if num_class_embeds is not None: - self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim) - - # down - self.down_blocks = nn.ModuleList([]) - output_channel = num_channels[0] - for i in range(len(num_channels)): - input_channel = output_channel - output_channel = num_channels[i] - is_final_block = i == len(num_channels) - 1 - - down_block = get_down_block( - spatial_dims=spatial_dims, - in_channels=input_channel, - out_channels=output_channel, - temb_channels=time_embed_dim, - num_res_blocks=num_res_blocks, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_downsample=not is_final_block, - with_attn=(attention_levels[i] and not with_conditioning), - with_cross_attn=(attention_levels[i] and with_conditioning), - num_head_channels=num_head_channels[i], - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - - self.down_blocks.append(down_block) - - # mid - self.middle_block = get_mid_block( - spatial_dims=spatial_dims, - in_channels=num_channels[-1], - temb_channels=time_embed_dim, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - with_conditioning=with_conditioning, - num_head_channels=num_head_channels[-1], - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - - # up - self.up_blocks = nn.ModuleList([]) - reversed_block_out_channels = list(reversed(num_channels)) - reversed_attention_levels = list(reversed(attention_levels)) - reversed_num_head_channels = list(reversed(num_head_channels)) - output_channel = reversed_block_out_channels[0] - for i in range(len(reversed_block_out_channels)): - prev_output_channel = output_channel - output_channel = reversed_block_out_channels[i] - input_channel = reversed_block_out_channels[min(i + 1, len(num_channels) - 1)] - - is_final_block = i == len(num_channels) - 1 - - up_block = get_up_block( - spatial_dims=spatial_dims, - in_channels=input_channel, - prev_output_channel=prev_output_channel, - out_channels=output_channel, - temb_channels=time_embed_dim, - num_res_blocks=num_res_blocks + 1, - norm_num_groups=norm_num_groups, - norm_eps=norm_eps, - add_upsample=not is_final_block, - with_attn=(reversed_attention_levels[i] and not with_conditioning), - with_cross_attn=(reversed_attention_levels[i] and with_conditioning), - num_head_channels=reversed_num_head_channels[i], - transformer_num_layers=transformer_num_layers, - cross_attention_dim=cross_attention_dim, - ) - - self.up_blocks.append(up_block) - - # out - self.out = nn.Sequential( - nn.GroupNorm(num_groups=norm_num_groups, num_channels=num_channels[0], eps=norm_eps, affine=True), - nn.SiLU(), - zero_module( - Convolution( - spatial_dims=spatial_dims, - in_channels=num_channels[0], - out_channels=out_channels, - strides=1, - kernel_size=3, - padding=1, - conv_only=True, - ) - ), - ) - - def forward( - self, - x: torch.Tensor, - timesteps: torch.Tensor, - context: Optional[torch.Tensor] = None, - class_labels: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - """ - Args: - x: input tensor (N, C, SpatialDims). - timesteps: timestep tensor (N,). - context: context tensor (N, 1, ContextDim). - class_labels: context tensor (N, ). - """ - # 1. time - t_emb = get_timestep_embedding(timesteps, self.block_out_channels[0]) - emb = self.time_embed(t_emb) - - # 2. class - if self.num_class_embeds is not None: - if class_labels is None: - raise ValueError("class_labels should be provided when num_class_embeds > 0") - class_emb = self.class_embedding(class_labels) - emb = emb + class_emb - - # 3. initial convolution - h = self.conv_in(x) - - # 4. down - if context is not None and self.with_conditioning is False: - raise ValueError("model should have with_conditioning = True if context is provided") - down_block_res_samples: List[torch.Tensor] = [h] - for downsample_block in self.down_blocks: - h, res_samples = downsample_block(hidden_states=h, temb=emb, context=context) - for residual in res_samples: - down_block_res_samples.append(residual) - h=h.flatten() - print('h', h.shape) - - # # 5. mid - # h = self.middle_block(hidden_states=h, temb=emb, context=context) - # - # # 6. up - # for upsample_block in self.up_blocks: - # res_samples = down_block_res_samples[-len(upsample_block.resnets) :] - # down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] - # h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, temb=emb, context=context) - - # 7. output block - - self.out = nn.Linear(len(h), self.out_channels) - output=self.out(h) - - return output diff --git a/generative/networks/nets/diffusion_model_unet.py b/generative/networks/nets/diffusion_model_unet.py index 59492ab4..a264cd89 100644 --- a/generative/networks/nets/diffusion_model_unet.py +++ b/generative/networks/nets/diffusion_model_unet.py @@ -1656,8 +1656,7 @@ def forward( return h - - + class DiffusionModelEncoder(nn.Module): """ Classification Network based on the Encoder of the Diffusion Model, followed by fully connected layers for classification @@ -1759,7 +1758,7 @@ def __init__( for i in range(len(num_channels)): input_channel = output_channel output_channel = num_channels[i] - is_final_block = i == len(num_channels) #- 1 + is_final_block = i == len(num_channels) # - 1 down_block = get_down_block( spatial_dims=spatial_dims, @@ -1779,16 +1778,12 @@ def __init__( self.down_blocks.append(down_block) - - self.out = nn.Sequential( - nn.Linear(8192, 512), - nn.ReLU(), - nn.Dropout(0.2), + nn.Linear(4096, 512), + nn.ReLU(), + nn.Dropout(0.1), nn.Linear(512, self.out_channels), - ) - - + ) def forward( self, @@ -1825,6 +1820,6 @@ def forward( h, _ = downsample_block(hidden_states=h, temb=emb, context=context) h = h.reshape(h.shape[0], -1) - output=self.out(h) + output = self.out(h) return output diff --git a/generative/networks/schedulers/ddim.py b/generative/networks/schedulers/ddim.py index fba6d406..6acc253a 100644 --- a/generative/networks/schedulers/ddim.py +++ b/generative/networks/schedulers/ddim.py @@ -223,8 +223,6 @@ def step( return pred_prev_sample, pred_original_sample - - def reversed_step( self, model_output: torch.Tensor, @@ -249,8 +247,7 @@ def reversed_step( pred_prev_sample: Predicted previous sample pred_original_sample: Predicted original sample """ - # See formulas (12) and (16) of DDIM paper https://arxiv.org/pdf/2010.02502.pdf - # Ideally, read DDIM paper in-detail understanding + # See Appendix F at https://arxiv.org/pdf/2105.05233.pdf, or Equation (6) in https://arxiv.org/pdf/2203.04306.pdf # Notation ( -> # - model_output -> e_theta(x_t, t) @@ -258,17 +255,20 @@ def reversed_step( # - std_dev_t -> sigma_t # - eta -> η # - pred_sample_direction -> "direction pointing to x_t" - # - pred_prev_sample -> "x_t-1" + # - pred_post_sample -> "x_t+1" # 1. get previous step value (=t-1) - prev_timestep = timestep - self.num_train_timesteps // self.num_inference_steps #t-1 - post_timestep = timestep + self.num_train_timesteps // self.num_inference_steps #t+1 + prev_timestep = timestep - self.num_train_timesteps // self.num_inference_steps # t-1 + post_timestep = timestep + self.num_train_timesteps // self.num_inference_steps # t+1 # 2. compute alphas, betas alpha_prod_t = self.alphas_cumprod[timestep] - alpha_prod_t_prev = self.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod #alpha at timestep t-1 - alpha_prod_t_post = self.alphas_cumprod[post_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod #alpha at timestep t+1 - + alpha_prod_t_prev = ( + self.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod + ) # alpha at timestep t-1 + alpha_prod_t_post = ( + self.alphas_cumprod[post_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod + ) # alpha at timestep t+1 beta_prod_t = 1 - alpha_prod_t @@ -302,14 +302,9 @@ def reversed_step( # randn_like does not support generator https://github.com/pytorch/pytorch/issues/27072 device = model_output.device if torch.is_tensor(model_output) else "cpu" noise = torch.randn(model_output.shape, dtype=model_output.dtype, generator=generator).to(device) - variance = self._get_variance(timestep, prev_timestep) ** (0.5) * eta * noise - - pred_prev_sample = pred_prev_sample + variance return pred_post_sample, pred_original_sample - - def add_noise( self, original_samples: torch.Tensor, diff --git a/tutorials/Untitled.ipynb b/tutorials/Untitled.ipynb deleted file mode 100644 index c0c04ff7..00000000 --- a/tutorials/Untitled.ipynb +++ /dev/null @@ -1,33 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "761199be-b371-4cb7-a66f-ae739eccb554", - "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.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/generative/2d_vqvae/2d_vqvae_tutorial.py b/tutorials/generative/2d_vqvae/2d_vqvae_tutorial.py index b1e313fe..f1ac0a53 100644 --- a/tutorials/generative/2d_vqvae/2d_vqvae_tutorial.py +++ b/tutorials/generative/2d_vqvae/2d_vqvae_tutorial.py @@ -42,7 +42,6 @@ import matplotlib.pyplot as plt import numpy as np import torch -import torch.nn.functional as F from monai import transforms from monai.apps import MedNISTDataset from monai.config import print_config diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb index fbeaf69c..4a5f4384 100644 --- a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb @@ -5,7 +5,7 @@ "id": "63d95da6", "metadata": {}, "source": [ - "# Anomaly Detection with classifier guidance\n", + "# Weakly Supervised Anomaly Detection with Classifier Guidance\n", "\n", "This tutorial illustrates how to use MONAI for training a 2D gradient-guided anomaly detection using DDIMs [1].\n", "\n", @@ -158,22 +158,16 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "972ed3f3", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false - } + }, + "lines_to_next_cell": 2 }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Setting up a new session...\n" - ] - }, { "name": "stdout", "output_type": "stream", @@ -248,23 +242,9 @@ "\n", "# TODO: Add right import reference after deployed\n", "from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet, DiffusionModelEncoder\n", - "\n", "from generative.networks.schedulers.ddpm import DDPMScheduler\n", "from generative.networks.schedulers.ddim import DDIMScheduler\n", - "print_config()\n", - "\n", - "losstrain_window = viz.line(Y=torch.zeros((1)).cpu(), X=torch.zeros((1)).cpu(),\n", - " opts=dict(xlabel='epoch', ylabel='Loss', title='training loss'))\n", - "lossval_window = viz.line(Y=torch.zeros((1)).cpu(), X=torch.zeros((1)).cpu(),\n", - " opts=dict(xlabel='epoch', ylabel='Loss', title='val loss '))\n", - "\n", - "train_classifier=False\n", - "train_diffusionmodel=False\n", - "def visualize(img):\n", - " _min = img.min()\n", - " _max = img.max()\n", - " normalized_img = (img - _min)/ (_max - _min)\n", - " return normalized_img" + "print_config()" ] }, { @@ -277,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "8b4323e7", "metadata": { "collapsed": false, @@ -302,7 +282,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "34ea510f", "metadata": { "collapsed": false, @@ -312,7 +292,7 @@ }, "outputs": [], "source": [ - "set_determinism(36)" + "set_determinism(42)" ] }, { @@ -322,20 +302,32 @@ "tags": [] }, "source": [ - "## Setup BRATS Dataset for 2D slices and training and validation dataloaders\n", - "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150" + "## Setup BRATS Dataset in 2D slices for training\n", + "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150.\n", + "If we set `preprocessing_train=True`, we stack all slices into a tensor and save it as _total_train_slices.pt_. \n", + "If we set `preprocessing_train=False`, we load the saved tensor.\n", + "The corresponding labels are saved as _total_train_labels.pt._" + ] + }, + { + "cell_type": "markdown", + "id": "6986f55c", + "metadata": {}, + "source": [ + "Here we use transforms to augment the training dataset, as usual:\n", + "\n", + "1. `LoadImaged` loads the hands images from files.\n", + "1. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n", + "1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1].\n", + "1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform.\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "da1927b0", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, + "execution_count": 5, + "id": "c68d2d91-9a0b-4ac1-ae49-f4a64edbd82a", + "metadata": {}, "outputs": [ { "name": "stderr", @@ -344,22 +336,9 @@ "/home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/monai/utils/deprecate_utils.py:107: FutureWarning: : Class `AddChannel` has been deprecated since version 0.8. please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.\n", " warn_deprecated(obj, msg, warning_category)\n" ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "download training set\n", - "len train data 388\n", - "total slices torch.Size([17072, 1, 64, 64])\n", - "total lbaels torch.Size([17072])\n", - "download val set\n" - ] } ], "source": [ - "\n", - "\n", "channel = 0 # 0 = Flair\n", "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", "\n", @@ -381,8 +360,32 @@ " transforms.CopyItemsd(keys=[\"label\"], times=1, names=[\"slice_label\"]),\n", " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", " ]\n", - ")\n", - "print('download training set')\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "da1927b0", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "len train data 388\n", + "total slices torch.Size([17072, 1, 64, 64])\n", + "total lbaels torch.Size([17072])\n" + ] + } + ], + "source": [ + "\n", "train_ds = DecathlonDataset(\n", " root_dir=root_dir,\n", " task=\"Task01_BrainTumour\",\n", @@ -403,6 +406,7 @@ " return batched_2d_slices, slice_label\n", "\n", "preprocessing_train=False\n", + "\n", "if preprocessing_train == True:\n", " train_loader_3D = DataLoader(train_ds, batch_size=1, shuffle=True, num_workers=4)\n", " print(f'Image shape {train_ds[0][\"image\"].shape}')\n", @@ -435,8 +439,11 @@ "tags": [] }, "source": [ - "## Setup BRATS Dataset for 2D slices validation dataloader\n", - "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150" + "## Setup BRATS Dataset in 2D slices for validation \n", + "As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150.\n", + "If we set `preprocessing_val=True`, we stack all slices into a tensor and save it as _total_val_slices.pt_.\n", + "If we set `preprocessing_val=False`, we load the saved tensor.\n", + "The corresponding labels are saved as _total_val_labels.pt_." ] }, { @@ -492,20 +499,6 @@ " print('total lbaels', total_val_labels.shape)" ] }, - { - "cell_type": "markdown", - "id": "6986f55c", - "metadata": {}, - "source": [ - "Here we use transforms to augment the training dataset, as usual:\n", - "\n", - "1. `LoadImaged` loads the hands images from files.\n", - "1. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n", - "1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1].\n", - "1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform.\n", - "\n" - ] - }, { "cell_type": "markdown", "id": "08428bc6", @@ -513,8 +506,8 @@ "source": [ "### Define network, scheduler, optimizer, and inferer\n", "At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using\n", - "the original DDPM scheduler containing 1000 timesteps in its Markov chain, and a 2D UNET with attention mechanisms\n", - "in the 3rd level, each with 1 attention head (`num_head_channels=64`).\n" + "the deterministic DDIM scheduler containing 1000 timesteps, and a 2D UNET with attention mechanisms\n", + "in the 3rd level (`num_head_channels=64`).\n" ] }, { @@ -526,7 +519,7 @@ "jupyter": { "outputs_hidden": false }, - "lines_to_next_cell": 0 + "lines_to_next_cell": 2 }, "outputs": [], "source": [ @@ -562,7 +555,8 @@ }, "source": [ "### Model training of the Diffusion Model\n", - "Here, we are training our diffusion model for 75 epochs (training time: ~50 minutes)." + "If we set `train_diffusionmodel=True`, we are training our diffusion model for 75 epochs, and save the model as _diffusion_model.pt_.\n", + "If we set `train_diffusionmodel=False`, we load a pretrained model." ] }, { @@ -577,14 +571,16 @@ }, "outputs": [], "source": [ - "n_epochs =75\n", + "n_epochs =1\n", "batch_size=32\n", "val_interval = 1\n", "epoch_loss_list = []\n", "val_epoch_loss_list = []\n", + "\n", "train_diffusionmodel=False\n", + "\n", "if train_diffusionmodel==False:\n", - " model.load_state_dict(torch.load(\"./diffusion_model.pt\", map_location={'cuda:0': 'cpu'}))\n", + " model.load_state_dict(torch.load(\"model.pt\", map_location={'cuda:0': 'cpu'}))\n", "else:\n", " scaler = GradScaler()\n", " total_start = time.time()\n", @@ -598,13 +594,13 @@ "\n", " subset_2D_val = zip(total_val_slices.split(1), total_val_labels.split(1)) #\n", "\n", - " progress_bar = tqdm(enumerate(subset_2D), total=len(indexes), ncols=10)\n", + " progress_bar = tqdm(enumerate(subset_2D), total=len(indexes)/batch_size)\n", " progress_bar.set_description(f\"Epoch {epoch}\")\n", " for step, (a,b) in progress_bar:\n", " images = a.to(device)\n", " classes = b.to(device)\n", " optimizer.zero_grad(set_to_none=True)\n", - " timesteps = torch.randint(0, 1000, (len(images),)).to(device)\n", + " timesteps = torch.randint(0, 1000, (len(images),)).to(device) #pick a random time step t\n", "\n", " with autocast(enabled=True):\n", " # Generate random noise\n", @@ -618,11 +614,7 @@ " scaler.scale(loss).backward()\n", " scaler.step(optimizer)\n", " scaler.update()\n", - " if step%20==0:\n", - " print('step', step, loss)\n", - "\n", " epoch_loss += loss.item()\n", - "\n", " progress_bar.set_postfix(\n", " {\n", " \"loss\": epoch_loss / (step + 1),\n", @@ -681,30 +673,27 @@ }, { "cell_type": "markdown", - "id": "45fab83a-b4c8-42cb-96c9-4e9f1e191111", + "id": "546f9983-c2e2-4c24-b03a-ebe34627638a", "metadata": {}, "source": [ - "### Model training of the Classification Model\n", - "#First, we define the classification model. It follows the encoder architecture of the diffusion model, combined with linear layers for binary classification between healthy and diseased slices.\n", - "#Here, we are training our binary classification model for 20 epochs." + "## Define the Classification Model\n", + "First, we define the classification model. It follows the encoder architecture of the diffusion model, combined with linear layers for binary classification between healthy and diseased slices.\n" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 10, "id": "44cc6928-2525-4e61-8805-15b409097bbb", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ - "\n", - "\n", "classifier = DiffusionModelEncoder(\n", " spatial_dims=2,\n", " in_channels=1,\n", " out_channels=2,\n", - " num_channels=(32,64,128),\n", + " num_channels=(32,64,64),\n", " attention_levels=(False, True, True),\n", " num_res_blocks=1,\n", " num_head_channels=64,\n", @@ -714,9 +703,19 @@ "batch_size=32" ] }, + { + "cell_type": "markdown", + "id": "45fab83a-b4c8-42cb-96c9-4e9f1e191111", + "metadata": {}, + "source": [ + "## Model training of the Classification Model\n", + "If we set `train_classifier=True`, we are training our diffusion model for 100 epochs, and save the model as _classifier.pt_.\n", + "If we set `train_classifier=False`, we load a pretrained model." + ] + }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 110, "id": "de18d5cb-68e7-407c-afe9-8efd7a5a904a", "metadata": { "lines_to_next_cell": 0 @@ -726,7 +725,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: : 534it [00:29, 18.32it/s, loss=0.576] \n" + "Epoch 0: : 534it [00:24, 21.51it/s, loss=0.534] \n" ] }, { @@ -740,21 +739,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 0: : 132it [00:02, 56.95it/s, val_loss=0.305]\n" + "Epoch 0: : 132it [00:01, 66.23it/s, val_loss=0.259]\n", + "Epoch 1: : 534it [00:27, 19.68it/s, loss=0.538] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "final step val 131\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 1: : 534it [00:29, 18.34it/s, loss=0.576] \n" + "Epoch 1: : 132it [00:02, 57.32it/s, val_loss=0.28] \n", + "Epoch 2: : 534it [00:27, 19.43it/s, loss=0.531] \n" ] }, { @@ -768,21 +769,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 1: : 132it [00:02, 56.37it/s, val_loss=0.315]\n" + "Epoch 2: : 132it [00:02, 60.17it/s, val_loss=0.32] \n", + "Epoch 3: : 534it [00:27, 19.41it/s, loss=0.539] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "final step val 131\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 2: : 534it [00:31, 16.99it/s, loss=0.573] \n" + "Epoch 3: : 132it [00:02, 58.98it/s, val_loss=0.294]\n", + "Epoch 4: : 534it [00:27, 19.40it/s, loss=0.536] \n" ] }, { @@ -796,21 +799,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 2: : 132it [00:02, 52.98it/s, val_loss=0.312]\n" + "Epoch 4: : 132it [00:02, 59.34it/s, val_loss=0.256]\n", + "Epoch 5: : 534it [00:27, 19.47it/s, loss=0.536] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "final step val 131\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 3: : 534it [00:31, 17.21it/s, loss=0.572] \n" + "Epoch 5: : 132it [00:02, 59.58it/s, val_loss=0.278]\n", + "Epoch 6: : 534it [00:27, 19.49it/s, loss=0.53] \n" ] }, { @@ -824,21 +829,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 3: : 132it [00:02, 53.04it/s, val_loss=0.334]\n" + "Epoch 6: : 132it [00:02, 59.91it/s, val_loss=0.29] \n", + "Epoch 7: : 534it [00:27, 19.58it/s, loss=0.533] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "final step val 131\n" + "final step train 533\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Epoch 4: : 534it [00:31, 17.16it/s, loss=0.569] \n" + "Epoch 7: : 132it [00:02, 59.94it/s, val_loss=0.271]\n", + "Epoch 8: : 534it [00:27, 19.67it/s, loss=0.54] \n" ] }, { @@ -852,166 +859,1533 @@ "name": "stderr", "output_type": "stream", "text": [ - "Epoch 4: : 132it [00:02, 52.93it/s, val_loss=0.36] \n" + "Epoch 8: : 132it [00:02, 60.28it/s, val_loss=0.261]\n", + "Epoch 9: : 534it [00:26, 19.84it/s, loss=0.535] \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "final step val 131\n", - "train completed, total time: 167.07577848434448.\n", - "epl 5\n" + "final step train 533\n" ] }, { - "data": { - "image/png": "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aDk4AUCXLl0QHByMzZs3W9wHERER1Q82H55Onz6Ntm3bmoQaAAgPD5e3V2bw4MFQq9Xw9vZGbGxsmX1Kvi9p88F+qtIHERERPRxs/rJdRkYGgoKCypR7e3vL28vTpEkTTJ8+HVFRUfDw8MCpU6ewYMECREVF4cCBA4iIiDBpo6TNB/upqI/CwkIUFhbK3+fk5FRtYkRERGSXbD48AYAkSdXaNmDAAAwYMED+vnv37hg0aBDCwsIwc+ZMbNmypUptVdTH/PnzMWfOnHK3ExERUf1i85ftfHx8zJ75yczMBGD+bFFFWrRogW7duiEpKcmkD8D8WazMzMwK+5g2bRqys7PlT3JysqLxEBERkX2x+fAUFhaGc+fOQa/Xm5SfOnUKQPGddEoJIaBS/TH1kjZK2nywn4r6cHR0hIeHh8mHiIiI6i+bD09Dhw5FXl4eNm7caFIeHx8PPz8/dO7cWVF7V65cwYEDBxAVFSWXNWvWDJ06dcLq1athMBjk8qSkJJw/fx6xsbGWTYKIiIjqDZtf8zRw4ED07dsXkydPRk5ODlq3bo2EhARs27YNq1evlp/xNGHCBMTHx+PSpUsIDAwEAPTp0wfdu3dHeHi4vGB84cKFkCQJ7777rkk/7733Hvr27Ythw4bhxRdfRFpaGt58802EhoZi3LhxtT5vIiIisk02H54AYNOmTZg+fTpmzpyJzMxMhISEICEhASNGjJDrGAwGGAwGCCHksrCwMKxfvx4ffPAB8vPz4evri169euHtt99GcHCwSR89e/ZEYmIiZs6ciSFDhsDFxQWDBw/G+++/z6eLExERkUwSpdMGWSwnJweenp7Izs7m+iciIiI7oeTvt82veSIiIiKyJQxPRERERAowPBEREREpwPBEREREpADDExEREZECDE9ERERECjA8ERERESnA8ERERESkAMMTERERkQIMT0REREQKMDwRERERKcDwRERERKQAwxMRERGRAgxPRERERAowPBEREREpwPBEREREpADDExEREZECDE9ERERECjA8ERERESnA8ERERESkAMMTERERkQIMT0REREQKMDwRERERKcDwRERERKQAwxMRERGRAgxPRERERAowPBEREREpwPBEREREpADDExEREZECDE9ERERECjA8ERERESnA8ERERESkAMMTERERkQKauh4AERHVTzqdDgaDoa6H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szRs2LDMXXel9erVy1pdEREREdUZq7/bLj8/H5cuXUJubi7c3d3RqlUrODs7W7sbIiIiojphtfeObN++HT179oSnpyciIiLQrVs3REREwNPTE7169cKOHTus1RUREdFDZ/bs2ZAkSX4cUAlJksy+y1VpO9Y0duxYSJKEq1ev1lgfdckq4Wn27NmIiYnB3r17odfrodVq4efnB61WC71ej927d2PgwIGYPXu2NbojIiKyOXFxcZAkCevWrauwXkZGBhwdHdGwYUMUFRXV0uisa9WqVZAkCatWrarrodQJi8PTtm3b8M4770ClUuHFF1/E+fPnUVBQgOTkZBQUFOD8+fN48cUX5Rfvbt++3RrjJiIisikTJkwA8Mfd5eVZvXo1ioqKMHr0aJNXm1XXuXPn8J///Mfidqxp/vz5OHfuHJo1a1bXQ6kRFoenf/3rX5AkCStWrMBHH32ENm3amGxv06YNPvroI6xYsQJCCCxZssTSLomIiGxO79690aJFC+zcuRPJycnl1isJVyVhy1IhISFo3ry5VdqylqZNmyIkJARarbauh1IjLA5PR44cgb+/P0aPHl1hvVGjRiEgIACHDx+2tEsiIiKbI0kSxo0bB6PRiPj4eLN1fv75Z5w4cQKdOnWCt7c3Zs2ahaioKPj6+sLR0REtWrTAiy++iLS0NEX9mlvzlJycjLi4OHh7e8PNzQ09evTA3r17zbZRVFSEpUuXon///ggICICjoyN8fX0RGxuLX375xaTu2LFjMW7cOADAuHHjIEmS/Cldp7w1T/Hx8YiKioKbmxvc3NwQFRVl9ue1e/duSJKE2bNn49ixY+jfvz/c3d3h6emJoUOH1ul6KovDU25ubpVfudK4cWPcvXvX0i6JiIhs0rhx46BSqbBq1SoIIcpsL33Wae/evVi0aBEaN26MuLg4vPLKK2jVqhU++eQTREdHIzs7u9rj+P333xEdHY1169ahU6dO+Nvf/gZvb2/07dtXfp1aaZmZmZg6dSoKCwsRExOD//f//h969uyJxMREdOnSBUeOHJHrPv3003jqqacAAE899RRmzZolfyrz//7f/8PYsWNx48YNTJgwARMnTkRKSgrGjh2Lv//972b3OXr0KJ544gloNBq88MIL6NixI/773/+iT58+KCgoqOZPyELCQi1bthTu7u4iLy+vwnp5eXnCzc1NtGzZ0tIubVp2drYAILKzs+t6KEREtSo/P1+cPXtW5Ofn1/VQ6lT//v0FALF7926T8oKCAtGgQQPh4uIisrOzxa1bt0Rubm6Z/ePj4wUAMXfuXJPyWbNmCQBi165dJuUARI8ePUzKxowZY7aN5cuXCwBl2ikoKBA3btwoM5bTp08LNzc30adPH5PylStXCgBi5cqVZn8GJf1fuXJFLtu7d68AINq2bSuysrLk8qysLBESEiIAiH379snlu3btkse6bt06k/ZHjx4tAIiEhASz/T+oKr+bSv5+W/ycp/79+2P58uX4y1/+glWrVpld/FZUVISJEyfi3r17GDBggKVdEhGRHeqcPR43jZl1PYwKNVF545DnCovaGD9+PLZv344VK1agR48ecvnmzZtx584djBkzBh4eHvDw8DC7/+jRo/HKK69g586dmD59uuL+i4qKsH79evj6+uLVV1812TZx4kQsWrQIFy5cMCl3dHQ0u7i7ffv2ePLJJ7F9+3bodDqL1jCV3Jk3e/ZseHp6yuWenp6YNWsW4uLisGrVKnTr1s1kv+7du2P48OEmZePHj8eXX36JI0eOYMSIEdUeU3VZHJ7eeustrF+/HuvXr8fu3bvxl7/8Be3atYOvry/S0tJw9uxZfP7557h16xY8PT0xbdo0a4ybiIjszE1jJlJEel0Po2JGy5t4+umn4ePjg6+//hofffQR3N3dAQArVhSHsvHjx8t1N23ahOXLl+PYsWO4c+cODAaDvC01NbVa/Zfc9d6rVy84OTmZbFOpVOjSpUuZ8AQAx48fx8KFC7F//37cvHkTOp3OZPvt27fRtGnTao0JgLx2ytz6rJKy48ePl9n26KOPlinz9/cHAGRlZVV7PJawODwFBARg69atePbZZ5GcnIy5c+eWqSOEQPPmzbFhwwYEBARY2iUREdmhJipvq4STmtRE5W1xGw4ODhg1ahSWLFmCDRs2YMKECUhOTsYPP/yANm3aoHv37gCARYsW4bXXXkOjRo3Qr18/+Pv7y2/kWLx4MQoLC6vVf8laKV9fX7Pbza1TPnjwoPwatX79+qFNmzZwc3ODJEn473//ixMnTlR7PCVycnKgUqnQqFEjs2NSqVRm13mVPktVQqMpji+lw2ZtssrrWTp37oxff/0Va9euxY4dO3DhwgXk5eXBzc0NwcHB6N+/P+Li4nDlyhWcPHkS4eHh1uiWiIjsiKWXw+zJhAkTsGTJEqxYsQITJkzAqlWrYDQa5bNOer0e7777Lvz8/HD8+HGTQCGEwMKFC6vdd0nYKO+OvVu3bpUpmzdvHgoLC7F//3507drVZFtSUhJOnDhR7fGU8PDwgNFoRHp6eplgl5aWBqPRWO6lTFtjtXfbOTs7Y8KECRU+t6JHjx64c+cO9Hq9tbolIiKyOWFhYXj88cdx8OBB/Prrr1i1ahXUajXGjBkDoPgSWHZ2Nnr37l3mTMzRo0eRn59f7b4feeQRODk54ejRoygoKDC5dGc0GnHw4MEy+1y6dAne3t5lgtO9e/dw7NixMvXVajUAZWd+OnTogF9++QW7d+/Gs88+a7Jtz549AIDIyMgqt1eXrPZuu6oSZm7dJCIiqm9KTiZMnDgRly9fRkxMjLxmyNfXF87Ozjh27Bju3bsn73Pnzh288sorFvXr4OCAZ599FmlpaVi0aJHJtn//+99m1zsFBgbizp07OHPmjFxmMBjw2muvIT297Do1b+/iy5s3btyo8rhKguOcOXOQk5Mjl+fk5GDOnDkmdWyd1c48ERER0R/i4uLw97//HQcOHABg+kTxkleaLVq0CBERERgyZAhycnKwdetWBAYGws/Pz6K+FyxYgB9++AEzZszA/v370aFDB5w7dw6JiYno168fduzYYVL/lVdewY4dO9CtWzc8++yzcHJywu7du5GSkoKePXuWeYlwdHQ0nJ2dsXjxYuTk5Mhnz958881yx9S9e3e88sorWLp0KUJDQ/HMM89ACIFNmzYhOTkZf/vb3+T1YLau1s88ERERPQw8PDzw5z//GUDxguhBgwaZbJ8/fz7mzZsHSZKwbNkyfP/99xgxYgR27Nhh8WtNmjZtioMHD2L48OFISkrCkiVLkJGRge+//x7R0dFl6g8ePBhff/01goKCsHr1aqxduxYhISE4fPgwAgMDy9T39vbG119/jTZt2uCTTz7BtGnTqnQ3/b/+9S+sWLECTZo0wWeffYbPP/8cTZo0wYoVK+zq9W2SqMXraI0aNUJmZmadrY6vDTk5OfD09ER2drbdLHwjIrKGgoICXLlyBS1btixzizxRXarK76aSv98880RERESkAMMTERERkQKKF4z/5z//qXZnlj5gi4iIiKiuKQ5PY8eOhSRJ1epMCFHtfYmIiIhsgeLw1Lx5cwYgIiIiemgpDk9Xr16tgWEQERER2QcuGCciIiJSgOGJiIisiq/hIltj7d9JhiciIrKKkpfF6nS6Oh4JkamS38mS31FLMTwREZFVaLVaODo6Ijs7m2efyGYIIZCdnQ1HR0eLX3tTgi8GJiIiq2nYsCFSUlJw48YNeHp6QqvV8g5tqhNCCOh0OmRnZyMvLw/NmjWzWtsMT0REZDUl7wS7ffs2UlJS6ng0RICjoyOaNWtm1ffNMjwREZFVeXh4wMPDAzqdrl6/CJ5sn1qtttqlutIYnoiIqEZotdoa+cNFVNe4YJyIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIF7CI85eXlYerUqfDz84OTkxMiIyOxbt06xe3MmDEDkiQhNDTU7PadO3ciOjoaLi4uaNiwIcaOHYu0tDRLh09ERET1iF2Ep9jYWMTHx2PWrFnYunUrHn/8ccTFxWHt2rVVbuP48eP44IMP0LhxY7Pb9+zZg4EDB6Jx48bYsmULlixZgp07d6J3794oLCy01lSIiIjIzklCCFHXg6hIYmIiBg0ahLVr1yIuLk4u79evH86cOYPr169DrVZX2IZer8fjjz+O7t2748SJE7h9+zZOnz5tUqdTp064e/cuTpw4AY2m+H3JBw8eRNeuXbFs2TJMnjy5SuPNycmBp6cnsrOz4eHhoXC2REREVBeU/P22+TNPmzdvhpubG4YNG2ZSPm7cOKSmpuLQoUOVtrFgwQJkZmZi3rx5ZrenpKTgyJEjGD16tBycAKBLly4IDg7G5s2bLZsEERER1Rs2H55Onz6Ntm3bmoQaAAgPD5e3V+Ts2bOYO3cuPvnkE7i5uZXbR+k2H+ynsj6IiIjo4aGpvErdysjIQFBQUJlyb29veXt5jEYjxo8fj9jYWMTExFTYR+k2H+ynoj4KCwtN1kTl5OSUW5eIiIjsn82feQIASZKqte3DDz/ExYsXsXjxYov6qaiP+fPnw9PTU/4EBARUqS8iIiKyTzYfnnx8fMye+cnMzARg/mwRAFy/fh0zZ87ErFmz4ODggKysLGRlZUGv18NoNCIrKwv5+flyH4D5s1iZmZnl9gEA06ZNQ3Z2tvxJTk5WPEciIiKyHzYfnsLCwnDu3Dno9XqT8lOnTgFAuc9sunz5MvLz8zFlyhQ0aNBA/hw4cADnzp1DgwYNMG3aNJM2Stp8sJ/y+gAAR0dHeHh4mHyIiIio/rL58DR06FDk5eVh48aNJuXx8fHw8/ND586dze4XGRmJXbt2lflERESgRYsW2LVrF15++WUAQLNmzdCpUyesXr0aBoNBbiMpKQnnz59HbGxszU2QiIiI7IrNLxgfOHAg+vbti8mTJyMnJwetW7dGQkICtm3bhtWrV8vPeJowYQLi4+Nx6dIlBAYGwsvLCz179izTnpeXF/R6fZlt7733Hvr27Ythw4bhxRdfRFpaGt58802EhoZi3LhxtTBTIiIisgc2f+YJADZt2oTRo0dj5syZGDBgAA4dOoSEhAQ899xzch2DwQCDwYDqPvOzZ8+eSExMxO+//44hQ4bglVdewZNPPokffvgBjo6O1poKERER2Tmbf8K4veETxomIiOxPvXrCOBEREZEtYXgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBTR1PQAiIiKiyhQJHfbqjyOx6CAO6c9gr8cnUEvqOhkLwxMRERHZpN+Nt7FV9xMSiw5ip+4I8pAvbzusP4dobWidjIvhiYiIiGyCURhx1PArvis6gK26n3DMcN5sPTXUOG24xPBERERED59sYx6+1x9GYtFBbNMlIU3cMVuvoeSFAdooxGij0VfbCQ1UHrU80j8wPBEREVGtEULgV+M1JBYdRKLuIA7oT0IPg9m6keo2GKjtghhtF3TStK2zNU4PYngiIiKiGlUgCrFHdxxbdcWB6bIx1Ww9Vzijt7YjYrRdMNAhGs1UjWp5pFXD8ERERERWl2JMl88u/aA7insoMFsvSOWHQdquGOgQjR6aDnCUHGp5pMoxPBEREZHFDMKAw/pzSNQdRKLuAE4YfjNbTwM1umkiEKPtghiHLnhE1RySJNXyaC3D8ERERETVcseYgx26w0jUHcR23SHcFllm6/lKDTBQG40Yhy7oo3kcniq32h2oldnFE8bz8vIwdepU+Pn5wcnJCZGRkVi3bl2l++3cuRN9+/aFn58fHB0d4evri169eiExMbFM3Z49e0KSpDKfAQMG1MSUiIiI7I4QAmf0l7EwfzV65ryIJlmD8dzdWVhTtL1McHpM/QhmOI3DTx7/xg2vb/CF23Q84/Ck3QcnwE7OPMXGxuLIkSNYsGABgoODsXbtWsTFxcFoNGLkyJHl7peRkYH27dtj4sSJaNKkCTIzM/Hpp59i0KBB+PLLLzFq1CiT+kFBQVizZo1JmZeXV01MiYiIyC7ki0Ls0v1c/LBK3UFcM940W88Nzuir7YQYhy4YoI1CU1XDWh5p7ZGEEKKuB1GRxMREDBo0SA5MJfr164czZ87g+vXrUKurfuuiTqdDy5YtERQUhL1798rlPXv2xO3bt3H69GmLxpuTkwNPT09kZ2fDw6PunkFBRERUXdcNN5F4Pyzt0v2MfBSarResCih+lIBDFzyhiYCDpK3lkVqPkr/fNn/mafPmzXBzc8OwYcNMyseNG4eRI0fi0KFD6NKlS5Xb02q18PLygkZj81MnIiKqFXqhR5L+zP3F3gdx2nDZbD0tNOiuiUSMQ/Gzl9qoA2p5pLbB5hPE6dOn0bZt2zJhJzw8XN5eWXgyGo0wGo1IS0vD8uXLceHCBbz33ntl6l26dAne3t7IyclBYGAgRowYgRkzZsDZ2dl6EyIiIrIBGcZsbNclIVH3E7brknBH5Jqt11TywUCHaMRou6C3tiPcJddaHqntsfnwlJGRgaCgoDLl3t7e8vbKxMTEYPv27QAADw8PrF+/HoMGDTKp061bNwwfPhwhISHIz8/H1q1bsXDhQuzfvx+7du2CSmV+bX1hYSEKC/84nZmTk1PluREREdUWIQROGS4hUXcQ3+kO4pD+DIwwlqknQUJHdVsMun92KVLdBirJLu4vqzU2H54AVPj8h6o8G2Lp0qXIysrC77//jtWrV2P48OGIj483WUM1d+5ck31iYmLQokULvPbaa9iyZQuGDh1qtu358+djzpw5VZwJERFR7bknCvCD7uj9J3v/hBvGNLP1PCRX9NN2Row2GgO00fBVNajlkdoXm18wHh0dDYPBgMOHD5uUnzlzBqGhoVi+fDkmTZqkqM2BAwfi0KFDuH37drlnlADg1q1baNKkCf7xj3+YvcwHmD/zFBAQwAXjRERUJ64YUu+vXfoJu3XHUIgis/XaqlrIl+O6asKhlezifEqNqVcLxsPCwpCQkAC9Xm+y7unUqVMAgNDQUMVtdurUCdu2bUN6ejoaN25caf2KApajoyMcHR0Vj4GIiMgadEKPg/pTSNQdxFbdQZw1XDVbzxEO6KHtgBhtcWAKUjer3YHWIzYfnoYOHYrPP/8cGzduxPDhw+Xy+Ph4+Pn5oXPnzoraE0Jgz5498PLygo+PT4V14+PjAQBRUVHKB05ERFRD0o13sE2XhETdQezQHUa2yDNbr5nU6P6dcdHope0IV4k3QFmDzYengQMHom/fvpg8eTJycnLQunVrJCQkYNu2bVi9erX8jKcJEyYgPj4ely5dQmBgIADgqaeeQkREBCIjI+Hj44PU1FSsWrUKe/bswccffyyfydq3bx/mzZuHoUOHIigoCAUFBdi6dSs+++wz9OrVC0OGDKmz+RMREQkhcNxwsfhyXNFBHDachUDZVTcSJHTWtMcgbfFi73B1a7t7b5w9sPnwBACbNm3C9OnTMXPmTGRmZiIkJAQJCQkYMWKEXMdgMMBgMKD0Eq6uXbvi66+/xkcffYScnBx4eXmhY8eO+Pbbb03utmvatCnUajXeffdd3L59G5IkoU2bNnjnnXfw6quvVnjZjoiIqCbkiXv4QXe0+HJc0U9IFbfN1vOS3NFf2xkx2i7or+2Mhiqv2h3oQ8jmF4zbGz5hnIiIqus3ww357NJe/XEUQWe2Xqg6qPjJ3tpoRGtCoXnIF3tbQ71aME5ERFRfFQkd9utP3j+7dBDnjdfN1nOCA57UPoaY+5fjAtVNanmkVBrDExERUS26Zcwsfslu0UF8rzuMXNwzW6+5qjFitF0wUNsFT2ofhYvkVMsjpfIwPBEREdUgozDimOF88Yt2iw7iqOGc2XoqqBCtCcUgbVfEaLugvbolF3vbKIYnIiIiK8sRd7FTdxiJRT9hq+4n3BKZZut5Sx4YoI1CjLYL+mk7w1vFtbL2gOGJiIjICi4YruO7ooNI1B3Efv0J6KA3Wy9C3fr+Yu8u6KxpB7WkruWRkqUYnoiIiKqhUBRhr/44thYVvwrlN+MNs/Vc4IRe9xd7D9RGI0Bd+ZstyLYxPBEREVVRqjEdW4t+QqLuJ/ygO4I85Jut11LlhxhtNAZqu6CntgOcJL7Gqz5heCIiIiqHURhxxHAOiUUHsVX3E44Zzputp4Ya3TThxY8ScOiCEFUgF3vXYwxPREREpWQZc/G97jASdQexTZeEdJFltl4jyUte7N1X2wleKvfaHSjVGYYnIiJ6qAkh8KvxGhLvL/Y+oD8JPQxm6z6qfgQDtdGIceiCx9VtoZL4+q6HEcMTERE9dApEIXbrfsFWXfFi7yvGVLP1XOGMPtrHMcihCwZoo+CnalTLIyVbxPBERET1nlEYccZwBfv1J7Bdl4QfdT/jHgrM1m2t8i9e7O3QBd01kXCUHGp5tGTrGJ6IiKjeKRI6/Kw/j/36E9ivP4GD+pO4I3LN1tVAje6aDohxiEaMtguC1c1rebRkbxieiIjI7uWJe0jSn8E+3XHs15/AYf1Z5KOw3PqNJe/7a5ei0UfbCR6Say2OluwdwxMREdmd28YsHNCfxD79CezXncAvhgswlLPIGwAaSl7opglHN20EntBEooM6mIu9qdoYnoiIyOZdM9yUL8Ht153AOePVCusHqpqgmyai+KON4HOXyKoYnoiIyKYIIXDOeBX7dSfkwHTdeKvCfdqrW/4RljQRfAUK1SiGJyIiqlN6occvhovyeqUD+pPIENnl1tdAjQ7qR+5fgotAV004fFSetThietgxPBERUa26JwpwSH9GvgSXpD+Du+W8Iw4AnOGIKE2ofAkuStMerpJzLY6YyBTDExER1ag7xhzs15+Uw9Ixw3nooC+3fgPJHV014XhCE4lu2gg8qn4EWol/rsh28LeRiIisKsWYbrJe6ZThUoX1/VW+JuuV2qlb8E44smkMT0REVG1CCFwwXi8Vlk6W+6qTEo+omsuPDOimiUCgqgnvhCO7wvBERERVZhAGnDD8hv36E9inO44D+pNIE3fKra+CCh3UbdBVE4EntBHoqomAr6pBLY6YyPoYnoiIqFwFohCH9efkS3A/6U4hF/fKre8IB3TWtJcfSBmtCYU7n95N9QzDExERybKNeTioP4V9+uPYrz+Jo/pzKIKu3Pqekhu6aMLk9UodNSF8kS7VewxPREQPsZvGDPkuuP36Ezhh+A0Cotz6TSQfPKH9Y3F3qDoIakldiyMmqnsMT0REDwkhBC4ZU0zC0m/GGxXu01rlLz9fqZsmAq1Uzbi4mx56DE9ERPWUQRhw2nDZ5J1wv4uMcutLkBChbi2Hpa6acDRVNazFERPZB4YnIqJ6olAU4aj+VzksHdSfQrbIK7e+A7ToqGlbvLhbE4EumjB4qdxrccRE9onhiYjITuWKu/hJf/r+JbiTOKw/gwIUlVvfHS6I1v6xuLuTpi2cJMdaHDFR/cDwRERkJ9KMd3BAfwL7dCdwQH8Cxw2/wQBDufUbSV4m65Ui1K2h4WtOiCzGf4uIiGyQEALXjDflS3D7dMdx3ni9wn1aqvzkS3DdtBEIVjXn4m6iGsDwRERkA4zCiLOGq38s7tafwA1jWoX7hKqD/ngnnDYC/irfWhot0cON4YmIqA7ohB7HDOexT3dcXtydKXLKra+BGo+pQ+RLcF014fBWedTiiImoBMMTkZUJIXBbZOGGMR0pxnSkGNPuf52G340ZkCQJLnCEs+QIF8kJznCEU6mvnSUH+WsXyQlOcl3H+9sdTbZruYbFLtwV+UjSn5Gfr3RIfwb3UFBufRc4IUoTev8FuhHorGkPF8mpFkdMROXhf3WJFDAIA26JTDkMpRjT5a//KLuNwgrueLI2NdRwxv3AdT9guUhOxYHsfthylhzhguLtpcv/CGzF4czJTDj7o01HOMKBa2iqKMOYjQP6k9h3//lKvxjOQ1/B4m4fyRNdS61X6qAOZjAmslH8N5PoviKhw+/GDNwwppUKQ+lIEen3y9KRarxd4d1NdcEAA/KQjzyRjwreqmEVEiQ4lQpqJSHMyUzQKv7aqUx4c5Yc4AKnB862mT+zppJUNTshK0o23DJZr3TGcKXC+gGqxvLi7ie0kQhRBdrVfIkeZgxP9FDIF4X3zxKllfpn8VmikqB0S2RW+E6vqvCS3OGvaoRmKl+Tf/pJjeCv8oWfqiEkAPkoxD1RiHxRiHsoQP79r/NRgHxRVKb8HgpQIO7vg/tlogD5KDQpvycK7tcvrJGQJyDk/ms6qAGAIxzuh7A/AlvpoPbgmTUn+euyZ9aczYSz0oFPyVkeIQR+NV6TL8Ht15/ANePNCvdpq2ohr1fqpolAoLqJpT8eIqojDE9k93LFXdwoE4xM1xtliGyL+2kkecFf5YtmqkYP/NMXzVQN0UzVCG6SS5XaamDxaCqnE/oyQevBr/MfCF0loa6gTHgrfCDgmZbX1GXKQhShUBQhC6jxsKaB2ky4Kr7MWTpo5YtC/KQ/hXSRVW5baqjRQd0G3bSReOL+k7sbqWrjqBNRbWB4IpslhMAdkYsbxrQyweiGMQ2poni9UY64a1E/KqjQRPKWzxKZC0h+qoZwlBysNLPaoZU00EIDD8m1xvsyCAMKUGQmqBWWCmoFJmfcSoe2gpKvUWQS5PLlr03LLT1DaI4eBuTiHnLFPcVBzQkO6KxpL69XitaEVjlIE5H9YXiiOmEURqSLLJMwZO7MUT4KLepHCw2aqRrBT9XI5DJa6WDURPLmU5ctpJbUcIUzXCXnGu9LCIHCkqBW6oyYucuf5s64lXf509yZuPIuf3pJ7uiiCcMTmkh000TgMc0jcJC0NT53IrIN/ItBVqcXevxuzECKSC8VjEzvSEs13oYOeov6cYajHIJKPv4qX5Ng1Ejy4iLcekaSJDjdf7xDXVz+FBBooWrK3yuihxjDEylSKIrk2/NTH7iMVhKMbopMGGG0qB8PybX4TJHkK182Kx2K/FW+aCC587Z5qnG1efmTiOwDwxPJ7op8OQQ9GIxK/lnRItmq8pE8y9yRVvrMUTNVI/6hIiIim8Xw9BAQQiBb5JW6Iy0NKeK2yWW0G8Z0ZIlci/qRIKGx5G3+Vv1SwchZcrTSzIiIiGofw5OdK/0qEJM70R548vVd5FvUjxpq+N2/Hd9f5YtmUsnlsz/OHDVV+XDRLBER1XsMT3bimP48DuhPlglG1ngViCMc7j+nyNfMHWnFX/tKDaCW1FaaDRERkf1ieLIT3+oO4J38LxTv5wpnBKh8y9yqXzog+UieXHhNRERURQxPdsJf1ahMWQPJ/YFb9X1L3aFWHIw8JFcGIyIiIiuyiweV5OXlYerUqfDz84OTkxMiIyOxbt26SvfbuXMn+vbtCz8/Pzg6OsLX1xe9evVCYmJiufWjo6Ph4uKChg0bYuzYsUhLS7P2dKqlh6YDVrrOwA73f+GsZwKyG+xEeoNt+MXzP/jWfRGWu76Jmc7jMd5xCPo7dEZ7TRA8VW4MTkRERFZmF2eeYmNjceTIESxYsADBwcFYu3Yt4uLiYDQaMXLkyHL3y8jIQPv27TFx4kQ0adIEmZmZ+PTTTzFo0CB8+eWXGDVqlFx3z549GDhwIAYNGoQtW7YgLS0Nb7zxBnr37o2jR4/C0bFu7xBrpfZHK7V/nY6BiIiIAEkIUQvvRq++xMREDBo0SA5MJfr164czZ87g+vXrUKurvpBZp9OhZcuWCAoKwt69e+XyTp064e7duzhx4gQ0muJMefDgQXTt2hXLli3D5MmTq9R+Tk4OPD09kZ2dDQ8PjyqPi4iIiOqOkr/fNn/ZbvPmzXBzc8OwYcNMyseNG4fU1FQcOnRIUXtarRZeXl5yQAKAlJQUHDlyBKNHjzYp79KlC4KDg7F582bLJkFERET1hs2Hp9OnT6Nt27YmoQYAwsPD5e2VMRqN0Ov1SE1NxaxZs3DhwgW8+uqrJn2UbvPBfqrSBxERET0cbH7NU0ZGBoKCgsqUe3t7y9srExMTg+3btwMAPDw8sH79egwaNMikj9JtPthPRX0UFhaisLBQ/j4nJ6fS8RAREZH9svkzTwAqvGOsKneTLV26FIcPH8aWLVvQv39/DB8+HAkJCVVuq6I+5s+fD09PT/kTEBBQ6XiIiIjIftl8ePLx8TF75iczMxOA+bNFD2rTpg0ef/xx/OlPf8KGDRvQu3dvvPTSSzAajXIfgPmzWJmZmRX2MW3aNGRnZ8uf5OTkKs2LiIiI7JPNh6ewsDCcO3cOer3epPzUqVMAgNDQUMVtdurUCXfu3EF6erpJGyVtPthPRX04OjrCw8PD5ENERET1l82Hp6FDhyIvLw8bN240KY+Pj4efnx86d+6sqD0hBPbs2QMvLy/5jFOzZs3QqVMnrF69GgaDQa6blJSE8+fPIzY21vKJEBERUb1g8wvGBw4ciL59+2Ly5MnIyclB69atkZCQgG3btmH16tXyM54mTJiA+Ph4XLp0CYGBgQCAp556ChEREYiMjISPjw9SU1OxatUq7NmzBx9//LHJHXzvvfce+vbti2HDhuHFF19EWloa3nzzTYSGhmLcuHF1MnciIiKyPTYfngBg06ZNmD59OmbOnInMzEyEhIQgISEBI0aMkOsYDAYYDAaUfuZn165d8fXXX+Ojjz5CTk4OvLy80LFjR3z77bcmd9sBQM+ePZGYmIiZM2diyJAhcHFxweDBg/H+++/X+dPFiYiIyHbY/BPG7Q2fME5ERGR/6tUTxomIiIhsCcMTERERkQJ2sebJnpRcBeWTxomIiOxHyd/tqqxmYniystzcXADgk8aJiIjsUG5uLjw9PSuswwXjVmY0GpGamgp3d/cqvTpGiZycHAQEBCA5ObleLkav7/MD6v8cOT/7V9/nyPnZv5qaoxACubm58PPzg0pV8aomnnmyMpVKBX9//xrto74/yby+zw+o/3Pk/OxffZ8j52f/amKOlZ1xKsEF40REREQKMDwRERERKcDwZEccHR0xa9asevvE8/o+P6D+z5Hzs3/1fY6cn/2zhTlywTgRERGRAjzzRERERKQAwxMRERGRAgxPRERERAowPNmA3Nxc/OMf/0C/fv3QqFEjSJKE2bNnV3n/tLQ0jB07Fg0bNoSLiwuio6Pxww8/1NyAFbJkfqtWrYIkSWY/N2/erNmBV9GPP/6I8ePHIyQkBK6urmjWrBmeeuop/Pzzz1Xa39aPnyXzs4fjd/z4cQwaNAjNmzeHs7MzvL29ER0djdWrV1dpf1s/foBlc7SHY2jOv//9b0iSBDc3tyrVt4fjWJqS+dnDMdy9e3e5Y0xKSqp0/9o+fnxIpg3IyMjAZ599hoiICDz99NP497//XeV9CwsL0bt3b2RlZWHJkiXw9fXFxx9/jAEDBmDnzp3o0aNHDY68aiyZX4mVK1ciJCTEpMzHx8daQ7TIJ598goyMDEyZMgXt2rVDeno6Fi1ahKioKGzfvh29evUqd197OH6WzK+ELR+/rKwsBAQEIC4uDs2aNcPdu3exZs0ajB49GlevXsWMGTPK3dcejh9g2RxL2PIxfFBKSgpee+01+Pn5ITs7u9L69nIcSyidXwl7OIb//Oc/8eSTT5qUhYaGVrhPnRw/QXXOaDQKo9EohBAiPT1dABCzZs2q0r4ff/yxACAOHjwol+l0OtGuXTvRqVOnmhiuYpbMb+XKlQKAOHLkSA2O0DK3bt0qU5abmysaN24sevfuXeG+9nD8LJmfPRy/8nTu3FkEBARUWMcejl9FqjJHezyGgwcPFkOGDBFjxowRrq6ulda3t+OodH72cAx37dolAIivvvpK8b51cfx42c4GlJyarI7NmzfjkUceQXR0tFym0WgwatQoHD58GCkpKdYaZrVZMj974OvrW6bMzc0N7dq1Q3JycoX72sPxs2R+9qxhw4bQaCo+OW8Px68iVZmjvVm9ejX27NmDZcuWVXkfezqO1ZlffVcXx4/hyc6dPn0a4eHhZcpLys6cOVPbQ6oRgwcPhlqthre3N2JjY3H69Om6HlKFsrOzcezYMbRv377CevZ6/Ko6vxL2cPyMRiP0ej3S09OxbNkybN++HW+88UaF+9jb8avOHEvYwzFMS0vD1KlTsWDBAkXvGLWX41jd+ZWwh2P40ksvQaPRwMPDA/3798f+/fsr3acujl/9+l+Oh1BGRga8vb3LlJeUZWRk1PaQrKpJkyaYPn06oqKi4OHhgVOnTmHBggWIiorCgQMHEBERUddDNOull17C3bt3MX369Arr2evxq+r87On4vfjii1i+fDkAwMHBAf/617/wwgsvVLiPvR2/6szR3o7hI488gsmTJyvaz16OY3XnZw/H0NPTE1OmTEHPnj3h4+OD3377De+//z569uyJ7777Dv379y933zo5fjVyMZCqTemaIK1WK/7617+WKT948KAAIBISEqw8QssonZ85V65cEW5ubuJPf/qT9QZmRTNmzBAAxNKlSyuta2/HTwhl8zPHVo/ftWvXxJEjR8R3330n/vrXvwqVSiXef//9Cvext+NXnTmaY4vH8OuvvxYODg7izJkzcllV1wTZw3G0ZH7m2OIxfNCdO3eEv7+/CA8Pr7BeXRw/nnmycz4+PmZTdWZmJgCYTeP2rkWLFujWrVuVbl+tbXPmzMHcuXMxb948vPzyy5XWt7fjp3R+5tjq8WvevDmaN28OAIiJiQEATJs2DWPGjEGjRo3M7mNvx686czTH1o5hXl4eXnrpJbzyyivw8/NDVlYWAKCoqAhA8d2GWq0Wrq6uZve39eNo6fzMsbVjaI6XlxcGDx6MTz/9FPn5+XB2djZbry6OH9c82bmwsDCcOnWqTHlJWWW3eNorIQRUKtv69Z0zZw5mz56N2bNn46233qrSPvZ0/Kozv/LY4vF7UKdOnaDX63H58uVy69jT8TOnKnMsjy0dw9u3b+PWrVtYtGgRGjRoIH8SEhJw9+5dNGjQAM8991y5+9v6cbR0fuWxpWNYHnH/9bsV3XRUJ8fP6ueyyCJKL2stW7ZMABBJSUlymU6nE+3btxedO3euoVFWnzUu212+fFm4ubmJp59+2noDs9A777wjAIgZM2Yo2s9ejl9152eOLR4/c0aPHi1UKpVIS0srt469HL/yVGWO5tjaMczPzxe7du0q8+nfv79wcnISu3btEqdOnSp3f1s/jpbOzxxbO4bmZGZmimbNmonIyMgK69XF8WN4shGJiYniq6++EitWrBAAxLBhw8RXX30lvvrqK3H37l0hhBDjx48XarVaXL16Vd6voKBAtG/fXgQEBIg1a9aI77//XgwdOlRoNBqxe/fuuppOGdWdX+/evcWcOXPE5s2bxQ8//CAWL14s/Pz8hLu7u+L/WNSUDz74QAAQAwYMED/99FOZTwl7PX6WzM8ejt9f/vIX8eqrr4r169eL3bt3i6+//loMHz5cABCvv/66XM9ej58Qls3RHo5hecytCbLn4/igqs7PHo5hXFyceOONN8RXX30ldu3aJT777DPxyCOPCI1GI77//nu5nq0cP4YnGxEYGCgAmP1cuXJFCFH8L0rp70vcvHlTPP/888Lb21s4OTmJqKgok182W1Dd+U2dOlW0a9dOuLu7C41GI/z8/MSoUaPE+fPn62YiZvTo0aPcuZU+uWuvx8+S+dnD8VuxYoV44oknRMOGDYVGoxFeXl6iR48e4ssvvzSpZ6/HTwjL5mgPx7A85sKFPR/HB1V1fvZwDOfPny8iIyOFp6enUKvVolGjRmLo0KHi8OHDJvVs5fhJQty/oEhERERElbLtlWJERERENobhiYiIiEgBhiciIiIiBRieiIiIiBRgeCIiIiJSgOGJiIiISAGGJyIiIiIFGJ6IiGqRJEkVvqeLiGwfwxMR2awWLVrIYaOiz6pVq+p6qET0ENHU9QCIiCrTpk0b+Pr6lru9cePGtTgaInrYMTwRkc176623MHbs2LoeBhERAF62IyIiIlKE4YmI6pXSC7LXrl2LTp06wc3NDd7e3nj66adx+vTpcve9e/cu5s6di/DwcLi6usLDwwOdO3fGxx9/DL1eX+5+mZmZmDVrFjp06AAPDw+4ubmhbdu2+Otf/4pffvml3P22bt2K7t27w93dHZ6enhg4cGC59a9du4YXXngBQUFBcHR0hLu7O4KCgjB06FCsW7euij8dIrIKQURkowIDAwUAsXLlyirvA0AAEO+9954AIJo0aSI6duwo3N3dBQDh7Ows9u3bV2a/tLQ0ERYWJgAIlUolwsPDRdu2beX2+vbtK/Lz88vsd/z4ceHn5yfv165dOxEZGSk8PDwEADFmzBiz4/vkk0+EJEmiadOm4tFHHxWurq4CgHBzcxPnzp0z2efKlSuiYcOGAoBwcXERYWFhIjIyUnh7ewsAIiIioso/HyKyHMMTEdksS8KTVqsVixYtEgaDQQghxN27d8Vzzz0nAIjAwEBx7949k/2eeeYZAUC0b99e/Pbbb3L5kSNHROPGjQUA8Y9//MNkn+zsbNG8eXMBQAwYMEAkJyebbN+7d69YvXq12fG5uLiYzCsnJ0f07t1bABDDhw832efll1+Wg1hubq7JtnPnzonly5dX+edDRJZjeCIim1USnir73LlzR96npOxPf/pTmfYKCwtFkyZNBACxYsUKufzChQtCkiQBQBw7dqzMfhs2bBAAhKurq8jJyZHLFy5cKACItm3bioKCgirNqWR8r7zySpltJ0+eFACEp6enSXn//v0FAHHixIkq9UFENYt32xGRzavsUQUaTdn/lL300ktlyhwcHDBx4kTMnTsX27dvx7hx4wAA33//PYQQ6NatGzp06FBmv2eeeQb+/v64ceMGDhw4gAEDBgAAtmzZAgCYMmUKHB0dFc1p4sSJZcrCwsLg5OSE7OxsZGRkwMfHBwAQEBAAAPj6668RFhbGh2wS1TGGJyKyedV5VEHbtm0rLL9w4YJcVvJ1u3btzO6jUqkQEhKCGzdu4MKFC3J4OnfuHAAgKipK0dgAoFWrVmbLGzVqhOTkZOTl5cnh6aWXXkJ8fDzeffdd/Oc//8GAAQPwxBNP4Mknn4Sfn5/ivonIMrzbjojqpfLOVJU8UDM3N1cuy8vLq3Cf8vbLyckBAHh5eSken6urq9lylar4P8tCCLksMjISe/fuRb9+/ZCSkoLly5dj1KhR8Pf3R//+/eUQR0S1g+GJiOql9PR0s+VpaWkAAHd3d7nMzc3NZJs5t27dKrNfyddZWVkWjbUqoqKisH37dty5cwfbtm3DG2+8AX9/f+zYsQN9+/atlTEQUTGGJyKql8o7G1NSHhwcLJeVfH327Fmz+xiNRvz6669l9mvfvj0AICkpyfIBV5Gbmxv69++PBQsW4Ndff0WrVq2QkpKCrVu31toYiB52DE9EVC8tW7asTFlRURG++OILAEC/fv3k8n79+kGSJOzfv9/sQyo3bdqEGzduwNXVFV27dpXLn376aQDA0qVLUVRUZOUZVM7FxQVhYWEAgNTU1Frvn+hhxfBERPXSd999hyVLlshrh/Lz8/GXv/wFqampCAgIwIgRI+S6rVu3RmxsLADg+eefx+XLl+Vtx44dw9/+9jcAwMsvv2xy2W7SpEkIDAzEmTNnEBsbi5SUFJMx7N+/H2vWrLF4LpMnT8b69etx7949k/K9e/fihx9+AAA8+uijFvdDRFUjidKrEomIbEiLFi1w7dq1Sh9V8Oyzz8oBp+Q2/vfeew9vvPEGmjRpgoCAAJw/fx45OTlwcnLC9u3b0b17d5M20tPT0bt3b5w6dQpqtRqhoaHQ6XTypbw+ffrgf//7H5ycnEz2O3HiBAYMGICbN29CpVKhbdu20Gq1uHLlCrKzszFmzBisWrVKrl8yvvL+01sy5ytXrqBFixYAiheMnzhxAhqNBm3atIG7uztu3bqFa9euAQBGjRqFL7/8soo/VSKyFMMTEdmskiBRmSlTpmDx4sUATMPJ2rVrsXjxYpw5cwZarRY9evTAu+++i/DwcLPt3L17Fx9++CE2bNiAS5cuQaVSoV27dnj++efxwgsvQKvVmt0vIyMDixYtwjfffIMrV65ArVbD398fPXv2xAsvvICIiAi5bnXC065du7Blyxbs27cPycnJyM7ORtOmTRESEoKXXnoJgwcP5rOfiGoRwxMR1SuVhRMiIktxzRMRERGRAgxPRERERAowPBEREREpwPBEREREpABfDExE9QoXihNRTeOZJyIiIiIFGJ6IiIiIFGB4IiIiIlKA4YmIiIhIAYYnIiIiIgUYnoiIiIgUYHgiIiIiUoDhiYiIiEgBhiciIiIiBf4/ufJxWPBy+noAAAAASUVORK5CYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "n_epochs = 5\n", - "val_interval = 1\n", - "epoch_loss_list = []\n", - "val_epoch_loss_list = []\n", - "optimizer_cls = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5)\n", - "\n", - "classifier.to(device)\n", - "\n", - "train_classifier=False\n", - "if train_classifier==False:\n", - " classifier.load_state_dict(torch.load(\"./classifier5.pt\", map_location={'cuda:0': 'cpu'}))\n", - "else:\n", - "\n", - " scaler = GradScaler()\n", - " total_start = time.time()\n", - " for epoch in range(n_epochs):\n", - " classifier.train()\n", - " epoch_loss = 0\n", - " indexes = list(torch.randperm(total_train_slices.shape[0]))\n", - " data_train = total_train_slices[indexes] # shuffle the training data\n", - " labels_train = total_train_labels[indexes]\n", - " subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size))\n", - " progress_bar = tqdm(enumerate(subset_2D), total=len(indexes)/batch_size)\n", - " progress_bar.set_description(f\"Epoch {epoch}\")\n", - "\n", - " for step, (a,b) in progress_bar:\n", - " images = a.to(device)\n", - " classes = b.to(device)\n", - " weight=torch.tensor((3,1)).float().to(device) #account for the class imbalance in the dataset\n", - " optimizer_cls.zero_grad(set_to_none=True)\n", - " timesteps = torch.randint(0, 1000, (len(images),)).to(device)\n", - "\n", - " with autocast(enabled=False):\n", - " # Generate random noise\n", - " noise = torch.randn_like(images).to(device)\n", - "\n", - " # Get model prediction\n", - " noisy_img=scheduler.add_noise(images,noise, timesteps ) #add t steps of noise to the input image\n", - " pred=classifier(noisy_img, timesteps)\n", - " loss = F.cross_entropy(pred, classes.long(), weight=weight, reduction=\"mean\")\n", - "\n", - " loss.backward()\n", - " optimizer_cls.step()\n", - "\n", - " epoch_loss += loss.item()\n", - " progress_bar.set_postfix(\n", - " {\n", - " \"loss\": epoch_loss / (step + 1),\n", - " }\n", - " )\n", - " epoch_loss_list.append(epoch_loss / (step + 1))\n", - " print('final step train', step)\n", - "\n", - "\n", - " if (epoch + 1) % val_interval == 0:\n", - " classifier.eval()\n", - " val_epoch_loss = 0\n", - " subset_2D_val = zip(total_val_slices.split(batch_size), total_val_labels.split(batch_size)) #\n", - " progress_bar_val = tqdm(enumerate(subset_2D_val))\n", - " progress_bar_val.set_description(f\"Epoch {epoch}\")\n", - " for step, (a,b) in progress_bar_val:\n", - " images = a.to(device)\n", - " classes = b.to(device)\n", - " timesteps = torch.randint(0, 1, (len(images),)).to(device) #check validation accuracy on the original images, i.e., do not add noise\n", - "\n", - " with torch.no_grad():\n", - " with autocast(enabled=False):\n", - " noise = torch.randn_like(images).to(device)\n", - " pred = classifier(images, timesteps)\n", - " val_loss = F.cross_entropy(pred, classes.long(), reduction=\"mean\")\n", - "\n", - " val_epoch_loss += val_loss.item()\n", - " _, predicted = torch.max(pred, 1);\n", - " progress_bar_val.set_postfix(\n", - " {\n", - " \"val_loss\": val_epoch_loss / (step + 1),\n", - " }\n", - " )\n", - " val_epoch_loss_list.append(val_epoch_loss / (step + 1))\n", - " print('final step val', step)\n", - "\n", - "\n", - " total_time = time.time() - total_start\n", - " print(f\"train completed, total time: {total_time}.\")\n", - " torch.save(classifier.state_dict(), \"./classifier5.pt\")\n", - " \n", - " ## Learning curves for the Classifier\n", - " \n", - " plt.style.use(\"seaborn-bright\")\n", - " plt.title(\"Learning Curves\", fontsize=20)\n", - " print('epl', len(epoch_loss_list))\n", - " plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", - " plt.plot(\n", - " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", - " val_epoch_loss_list,\n", - " color=\"C1\",\n", - " linewidth=2.0,\n", - " label=\"Validation\",\n", - " )\n", - " plt.yticks(fontsize=12)\n", - " plt.xticks(fontsize=12)\n", - " plt.xlabel(\"Epochs\", fontsize=16)\n", - " plt.ylabel(\"Loss\", fontsize=16)\n", - " plt.legend(prop={\"size\": 14})\n", - " plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "a676b3fe", - "metadata": {}, - "source": [ - "### For Image-to-Image Translation to a Healthy Subject, we pick a disesed subject of the validation set" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "fe0d9eac-1477-4d6d-a885-d3c4acb4a781", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 9: : 132it [00:02, 59.37it/s, val_loss=0.243]\n", + "Epoch 10: : 534it [00:27, 19.77it/s, loss=0.537] \n" + ] }, { - "data": { - "text/plain": [ - "DiffusionModelEncoder(\n", - " (conv_in): Convolution(\n", + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 10: : 132it [00:02, 60.28it/s, val_loss=0.274]\n", + "Epoch 11: : 534it [00:26, 19.79it/s, loss=0.529] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 11: : 132it [00:02, 60.96it/s, val_loss=0.278]\n", + "Epoch 12: : 534it [00:26, 19.95it/s, loss=0.529] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 12: : 132it [00:02, 60.47it/s, val_loss=0.292]\n", + "Epoch 13: : 534it [00:26, 19.90it/s, loss=0.533] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 13: : 132it [00:02, 59.46it/s, val_loss=0.248]\n", + "Epoch 14: : 534it [00:26, 19.80it/s, loss=0.528] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 14: : 132it [00:02, 61.53it/s, val_loss=0.284]\n", + "Epoch 15: : 534it [00:26, 19.92it/s, loss=0.531] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 15: : 132it [00:02, 61.58it/s, val_loss=0.273]\n", + "Epoch 16: : 534it [00:26, 19.88it/s, loss=0.529] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 16: : 132it [00:02, 61.05it/s, val_loss=0.267]\n", + "Epoch 17: : 534it [00:26, 19.95it/s, loss=0.535] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 17: : 132it [00:02, 60.88it/s, val_loss=0.26] \n", + "Epoch 18: : 534it [00:26, 19.83it/s, loss=0.533] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 18: : 132it [00:02, 60.91it/s, val_loss=0.318]\n", + "Epoch 19: : 534it [00:26, 20.29it/s, loss=0.528] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 19: : 132it [00:02, 63.26it/s, val_loss=0.265]\n", + "Epoch 20: : 534it [00:26, 19.84it/s, loss=0.532] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 20: : 132it [00:02, 60.27it/s, val_loss=0.289]\n", + "Epoch 21: : 534it [00:26, 20.11it/s, loss=0.534] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 21: : 132it [00:02, 60.49it/s, val_loss=0.27] \n", + "Epoch 22: : 534it [00:26, 19.89it/s, loss=0.533] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 22: : 132it [00:02, 60.80it/s, val_loss=0.322]\n", + "Epoch 23: : 534it [00:26, 20.02it/s, loss=0.532] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 23: : 132it [00:02, 60.56it/s, val_loss=0.3] \n", + "Epoch 24: : 534it [00:26, 20.01it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 24: : 132it [00:02, 60.69it/s, val_loss=0.287]\n", + "Epoch 25: : 534it [00:26, 20.00it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 25: : 132it [00:02, 61.55it/s, val_loss=0.263]\n", + "Epoch 26: : 534it [00:26, 20.04it/s, loss=0.528] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 26: : 132it [00:02, 61.16it/s, val_loss=0.328]\n", + "Epoch 27: : 534it [00:26, 19.95it/s, loss=0.533] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 27: : 132it [00:02, 61.15it/s, val_loss=0.263]\n", + "Epoch 28: : 534it [00:26, 20.06it/s, loss=0.528] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 28: : 132it [00:02, 60.67it/s, val_loss=0.292]\n", + "Epoch 29: : 534it [00:26, 20.10it/s, loss=0.528] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 29: : 132it [00:02, 61.57it/s, val_loss=0.294]\n", + "Epoch 30: : 534it [00:26, 19.98it/s, loss=0.53] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 30: : 132it [00:02, 61.60it/s, val_loss=0.284]\n", + "Epoch 31: : 534it [00:26, 20.08it/s, loss=0.53] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 31: : 132it [00:02, 61.04it/s, val_loss=0.276]\n", + "Epoch 32: : 534it [00:26, 19.86it/s, loss=0.525] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 32: : 132it [00:02, 62.82it/s, val_loss=0.285]\n", + "Epoch 33: : 534it [00:26, 20.13it/s, loss=0.525] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 33: : 132it [00:02, 61.12it/s, val_loss=0.277]\n", + "Epoch 34: : 534it [00:26, 20.05it/s, loss=0.53] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 34: : 132it [00:02, 61.71it/s, val_loss=0.278]\n", + "Epoch 35: : 534it [00:26, 20.08it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 35: : 132it [00:02, 62.17it/s, val_loss=0.27] \n", + "Epoch 36: : 534it [00:26, 20.21it/s, loss=0.525] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 36: : 132it [00:02, 62.01it/s, val_loss=0.267]\n", + "Epoch 37: : 534it [00:26, 20.04it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 37: : 132it [00:02, 61.29it/s, val_loss=0.278]\n", + "Epoch 38: : 534it [00:26, 20.21it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 38: : 132it [00:02, 62.59it/s, val_loss=0.285]\n", + "Epoch 39: : 534it [00:26, 20.13it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 39: : 132it [00:02, 60.36it/s, val_loss=0.279]\n", + "Epoch 40: : 534it [00:26, 20.04it/s, loss=0.532] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 40: : 132it [00:02, 61.89it/s, val_loss=0.274]\n", + "Epoch 41: : 534it [00:26, 20.00it/s, loss=0.528] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 41: : 132it [00:02, 61.62it/s, val_loss=0.275]\n", + "Epoch 42: : 534it [00:26, 20.11it/s, loss=0.527] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 42: : 132it [00:02, 61.35it/s, val_loss=0.308]\n", + "Epoch 43: : 534it [00:26, 19.83it/s, loss=0.529] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 43: : 132it [00:02, 60.97it/s, val_loss=0.31] \n", + "Epoch 44: : 534it [00:26, 20.22it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 44: : 132it [00:02, 61.49it/s, val_loss=0.306]\n", + "Epoch 45: : 534it [00:26, 19.95it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 45: : 132it [00:02, 60.71it/s, val_loss=0.293]\n", + "Epoch 46: : 534it [00:26, 20.02it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 46: : 132it [00:02, 61.20it/s, val_loss=0.254]\n", + "Epoch 47: : 534it [00:26, 19.88it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 47: : 132it [00:02, 61.35it/s, val_loss=0.253]\n", + "Epoch 48: : 534it [00:26, 20.19it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 48: : 132it [00:02, 61.04it/s, val_loss=0.274]\n", + "Epoch 49: : 534it [00:26, 19.95it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 49: : 132it [00:02, 61.74it/s, val_loss=0.28] \n", + "Epoch 50: : 534it [00:26, 20.03it/s, loss=0.525] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 50: : 132it [00:02, 59.53it/s, val_loss=0.292]\n", + "Epoch 51: : 534it [00:26, 19.93it/s, loss=0.52] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 51: : 132it [00:02, 60.65it/s, val_loss=0.299]\n", + "Epoch 52: : 534it [00:26, 19.89it/s, loss=0.526] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 52: : 132it [00:02, 61.33it/s, val_loss=0.279]\n", + "Epoch 53: : 534it [00:26, 20.04it/s, loss=0.52] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 53: : 132it [00:02, 61.07it/s, val_loss=0.282]\n", + "Epoch 54: : 534it [00:26, 19.83it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 54: : 132it [00:02, 60.71it/s, val_loss=0.296]\n", + "Epoch 55: : 534it [00:26, 20.05it/s, loss=0.521] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 55: : 132it [00:02, 60.82it/s, val_loss=0.296]\n", + "Epoch 56: : 534it [00:26, 19.90it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 56: : 132it [00:02, 60.35it/s, val_loss=0.288]\n", + "Epoch 57: : 534it [00:26, 19.92it/s, loss=0.522] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 57: : 132it [00:02, 60.57it/s, val_loss=0.279]\n", + "Epoch 58: : 534it [00:27, 19.76it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 58: : 132it [00:02, 61.55it/s, val_loss=0.265]\n", + "Epoch 59: : 534it [00:26, 19.85it/s, loss=0.525] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 59: : 132it [00:02, 60.26it/s, val_loss=0.309]\n", + "Epoch 60: : 534it [00:26, 19.94it/s, loss=0.521] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 60: : 132it [00:02, 60.22it/s, val_loss=0.284]\n", + "Epoch 61: : 534it [00:27, 19.57it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 61: : 132it [00:02, 58.50it/s, val_loss=0.267]\n", + "Epoch 62: : 534it [00:27, 19.67it/s, loss=0.527] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 62: : 132it [00:02, 61.49it/s, val_loss=0.278]\n", + "Epoch 63: : 534it [00:27, 19.65it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 63: : 132it [00:02, 59.89it/s, val_loss=0.291]\n", + "Epoch 64: : 534it [00:27, 19.59it/s, loss=0.52] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 64: : 132it [00:02, 61.56it/s, val_loss=0.31] \n", + "Epoch 65: : 534it [00:27, 19.39it/s, loss=0.517] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 65: : 132it [00:02, 55.48it/s, val_loss=0.353]\n", + "Epoch 66: : 534it [00:28, 19.05it/s, loss=0.516] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 66: : 132it [00:02, 56.32it/s, val_loss=0.294]\n", + "Epoch 67: : 534it [00:27, 19.12it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 67: : 132it [00:02, 57.70it/s, val_loss=0.303]\n", + "Epoch 68: : 534it [00:27, 19.11it/s, loss=0.521] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 68: : 132it [00:02, 56.41it/s, val_loss=0.278]\n", + "Epoch 69: : 534it [00:27, 19.10it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 69: : 132it [00:02, 58.40it/s, val_loss=0.302]\n", + "Epoch 70: : 534it [00:27, 19.32it/s, loss=0.517] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 70: : 132it [00:02, 59.59it/s, val_loss=0.285]\n", + "Epoch 71: : 534it [00:27, 19.31it/s, loss=0.518] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 71: : 132it [00:02, 58.24it/s, val_loss=0.302]\n", + "Epoch 72: : 534it [00:27, 19.33it/s, loss=0.525] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 72: : 132it [00:02, 59.33it/s, val_loss=0.301]\n", + "Epoch 73: : 534it [00:27, 19.47it/s, loss=0.522] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 73: : 132it [00:02, 59.77it/s, val_loss=0.301]\n", + "Epoch 74: : 534it [00:26, 19.83it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 74: : 132it [00:02, 60.28it/s, val_loss=0.321]\n", + "Epoch 75: : 534it [00:26, 19.82it/s, loss=0.523] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 75: : 132it [00:02, 59.62it/s, val_loss=0.3] \n", + "Epoch 76: : 534it [00:26, 19.90it/s, loss=0.518] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 76: : 132it [00:02, 60.27it/s, val_loss=0.292]\n", + "Epoch 77: : 534it [00:26, 19.87it/s, loss=0.522] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 77: : 132it [00:02, 60.70it/s, val_loss=0.302]\n", + "Epoch 78: : 534it [00:26, 19.78it/s, loss=0.522] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 78: : 132it [00:02, 59.56it/s, val_loss=0.292]\n", + "Epoch 79: : 534it [00:26, 19.85it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 79: : 132it [00:02, 61.08it/s, val_loss=0.29] \n", + "Epoch 80: : 534it [00:27, 19.76it/s, loss=0.518] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 80: : 132it [00:02, 59.27it/s, val_loss=0.305]\n", + "Epoch 81: : 534it [00:26, 19.92it/s, loss=0.517] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 81: : 132it [00:02, 61.01it/s, val_loss=0.314]\n", + "Epoch 82: : 534it [00:27, 19.75it/s, loss=0.515] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 82: : 132it [00:02, 59.88it/s, val_loss=0.309]\n", + "Epoch 83: : 534it [00:26, 19.84it/s, loss=0.52] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 83: : 132it [00:02, 59.66it/s, val_loss=0.296]\n", + "Epoch 84: : 534it [00:27, 19.69it/s, loss=0.519] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 84: : 132it [00:02, 59.83it/s, val_loss=0.332]\n", + "Epoch 85: : 534it [00:26, 19.85it/s, loss=0.522] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 85: : 132it [00:02, 59.60it/s, val_loss=0.317]\n", + "Epoch 86: : 534it [00:27, 19.77it/s, loss=0.522] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 86: : 132it [00:02, 58.63it/s, val_loss=0.302]\n", + "Epoch 87: : 534it [00:26, 19.79it/s, loss=0.519] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 87: : 132it [00:02, 60.47it/s, val_loss=0.296]\n", + "Epoch 88: : 534it [00:27, 19.74it/s, loss=0.515] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 88: : 132it [00:02, 60.28it/s, val_loss=0.312]\n", + "Epoch 89: : 534it [00:26, 19.89it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 89: : 132it [00:02, 60.52it/s, val_loss=0.289]\n", + "Epoch 90: : 534it [00:26, 19.79it/s, loss=0.519] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 90: : 132it [00:02, 59.43it/s, val_loss=0.332]\n", + "Epoch 91: : 534it [00:26, 19.82it/s, loss=0.517] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 91: : 132it [00:02, 60.42it/s, val_loss=0.31] \n", + "Epoch 92: : 534it [00:26, 19.81it/s, loss=0.514] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 92: : 132it [00:02, 59.98it/s, val_loss=0.299]\n", + "Epoch 93: : 534it [00:26, 19.90it/s, loss=0.524] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 93: : 132it [00:02, 61.64it/s, val_loss=0.315]\n", + "Epoch 94: : 534it [00:26, 19.84it/s, loss=0.516] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 94: : 132it [00:02, 60.29it/s, val_loss=0.331]\n", + "Epoch 95: : 534it [00:26, 19.84it/s, loss=0.514] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 95: : 132it [00:02, 61.00it/s, val_loss=0.306]\n", + "Epoch 96: : 534it [00:27, 19.72it/s, loss=0.52] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 96: : 132it [00:02, 59.72it/s, val_loss=0.307]\n", + "Epoch 97: : 534it [00:26, 19.99it/s, loss=0.52] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 97: : 132it [00:02, 60.52it/s, val_loss=0.336]\n", + "Epoch 98: : 534it [00:26, 19.83it/s, loss=0.512] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 98: : 132it [00:02, 60.33it/s, val_loss=0.36] \n", + "Epoch 99: : 534it [00:26, 19.87it/s, loss=0.514] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final step train 533\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 99: : 132it [00:02, 60.57it/s, val_loss=0.327]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "train completed, total time: 2959.368038415909.\n", + "epl 100\n" + ] + }, + { + "data": { + "image/png": "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rVsxbtw41K1bF2FhYRg+fLjs8CwhhUGz7QgpQcRv9itXrnQ442P5RjhhwgRh+O/555/H6NGjUa9ePYSFhcHLy0vo5VO5cmXEx8cX+AleqVQ6820QAPXr18fu3bsBAKdPnxaGEj2Fo7/T/v374/XXX0dOTg5WrVqFdu3aAeBeq2vXrgUAdO3aVTbY5F/P1apVw5YtWxy+tmrVqknuv/DCC+jcuTPWrl2LnTt34tChQ3j06BGSkpKwYsUKrFixAiNGjMDSpUup7om4BQVPhJQgfA0SwBV1161b1+ljpKenC29qQ4YMkRT0WhJnYkoDcZBZUFaksK0K2rVrh88++wwA8Oeff2LgwIGFOh4fFJhMJrv7uXuIMCAgAH369MG6devw22+/YfHixdBoNNi7d68wvGarzo1/PT948ADR0dEONwmVExgYiLFjx2Ls2LEAuBqoLVu2YPHixUhMTERsbCwaNmyICRMmuHwOQngUghNSgjRs2FC4vWvXLpeOce3aNej1egDAoEGDbO535coVZGZmunSOkqpOnTrCbXF9mZyCHi9I165dhRl7v/32GxISEgp1PL6GKjU11e5+fEG8O/HB0ePHj4XO9HwBua+vL/r27Sv7PP71nJ2djSNHjrj1mmrXro333nsPR48eFQry161b59ZzkNKLgidCSpAaNWqgdu3aAIA1a9bgzp07Th/DYDAIt+0thfL99987f4ElXKVKlRAVFQWAC2hsLQmSm5uL3377rVDn0mg0mDx5snC8MWPGOFzndffuXezdu1eyjR/KysjIsBkg5eXlYf369YW4ank9evQQivBXrlyJ3NxcbNiwAQA3LGxrNqE4qFqwYIHbrwvgZg3yv9OCJj4Q4igKnggpYT744AMA3Btu//797Q4f6XQ6fPvtt5IgoEaNGkJNE9+l3NLWrVuxePFiN151yfHaa68B4KbET5kyRXafKVOmIDExsdDnmjBhAjp06ACA64rer18/u79PlmWxcuVKNG7cGOfOnZM8xtcaAcDnn38u+9wJEya45botqdVqoXXDH3/8gVWrViE9PR2A/dYUTZs2RdeuXQEA27Ztw4wZM+ye59atW0LrA96mTZvsZtvi4+Nx+fJlANa1UoS4imqeCClmZ86cwfLlywvcr02bNqhRowYGDx6MnTt3IjY2FqdOnULt2rXx2muvoV27dihbtiyysrJw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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "train_classifier=True\n", + "\n", + "n_epochs = 100\n", + "val_interval = 1\n", + "epoch_loss_list = []\n", + "val_epoch_loss_list = []\n", + "optimizer_cls = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5)\n", + "\n", + "classifier.to(device)\n", + "weight=torch.tensor((3,1)).float().to(device) #account for the class imbalance in the dataset\n", + "\n", + "\n", + "if train_classifier==False:\n", + " classifier.load_state_dict(torch.load(\"./classifier_small.pt\", map_location={'cuda:0': 'cpu'}))\n", + "else:\n", + "\n", + " scaler = GradScaler()\n", + " total_start = time.time()\n", + " for epoch in range(n_epochs):\n", + " classifier.train()\n", + " epoch_loss = 0\n", + " indexes = list(torch.randperm(total_train_slices.shape[0]))\n", + " data_train = total_train_slices[indexes] # shuffle the training data\n", + " labels_train = total_train_labels[indexes]\n", + " subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size))\n", + " progress_bar = tqdm(enumerate(subset_2D), total=len(indexes)/batch_size)\n", + " progress_bar.set_description(f\"Epoch {epoch}\")\n", + "\n", + " for step, (a,b) in progress_bar:\n", + " images = a.to(device)\n", + " classes = b.to(device)\n", + " \n", + " optimizer_cls.zero_grad(set_to_none=True)\n", + " timesteps = torch.randint(0, 1000, (len(images),)).to(device)\n", + "\n", + " with autocast(enabled=False):\n", + " # Generate random noise\n", + " noise = torch.randn_like(images).to(device)\n", + "\n", + " # Get model prediction\n", + " noisy_img=scheduler.add_noise(images,noise, timesteps ) #add t steps of noise to the input image\n", + " pred=classifier(noisy_img, timesteps)\n", + " loss = F.cross_entropy(pred, classes.long(), weight=weight, reduction=\"mean\")\n", + "\n", + " loss.backward()\n", + " optimizer_cls.step()\n", + "\n", + " epoch_loss += loss.item()\n", + " progress_bar.set_postfix(\n", + " {\n", + " \"loss\": epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " epoch_loss_list.append(epoch_loss / (step + 1))\n", + " print('final step train', step)\n", + "\n", + "\n", + " if (epoch + 1) % val_interval == 0:\n", + " classifier.eval()\n", + " val_epoch_loss = 0\n", + " subset_2D_val = zip(total_val_slices.split(batch_size), total_val_labels.split(batch_size)) #\n", + " progress_bar_val = tqdm(enumerate(subset_2D_val))\n", + " progress_bar_val.set_description(f\"Epoch {epoch}\")\n", + " for step, (a,b) in progress_bar_val:\n", + " images = a.to(device)\n", + " classes = b.to(device)\n", + " timesteps = torch.randint(0, 1, (len(images),)).to(device) #check validation accuracy on the original images, i.e., do not add noise\n", + "\n", + " with torch.no_grad():\n", + " with autocast(enabled=False):\n", + " noise = torch.randn_like(images).to(device)\n", + " pred = classifier(images, timesteps)\n", + " val_loss = F.cross_entropy(pred, classes.long(), reduction=\"mean\")\n", + "\n", + " val_epoch_loss += val_loss.item()\n", + " _, predicted = torch.max(pred, 1);\n", + " progress_bar_val.set_postfix(\n", + " {\n", + " \"val_loss\": val_epoch_loss / (step + 1),\n", + " }\n", + " )\n", + " val_epoch_loss_list.append(val_epoch_loss / (step + 1))\n", + "\n", + "\n", + " total_time = time.time() - total_start\n", + " print(f\"train completed, total time: {total_time}.\")\n", + " torch.save(classifier.state_dict(), \"./classifier_100.pt\")\n", + " \n", + " ## Learning curves for the Classifier\n", + " \n", + " plt.style.use(\"seaborn-bright\")\n", + " plt.title(\"Learning Curves\", fontsize=20)\n", + " print('epl', len(epoch_loss_list))\n", + " plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color=\"C0\", linewidth=2.0, label=\"Train\")\n", + " plt.plot(\n", + " np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)),\n", + " val_epoch_loss_list,\n", + " color=\"C1\",\n", + " linewidth=2.0,\n", + " label=\"Validation\",\n", + " )\n", + " plt.yticks(fontsize=12)\n", + " plt.xticks(fontsize=12)\n", + " plt.xlabel(\"Epochs\", fontsize=16)\n", + " plt.ylabel(\"Loss\", fontsize=16)\n", + " plt.legend(prop={\"size\": 14})\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a676b3fe", + "metadata": {}, + "source": [ + "# Image-to-Image Translation to a Healthy Subject\n", + "We pick a diseased subject of the validation set as input image. We want to translate it to its healthy reconstruction." + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "id": "fe0d9eac-1477-4d6d-a885-d3c4acb4a781", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "DiffusionModelEncoder(\n", + " (conv_in): Convolution(\n", " (conv): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", " )\n", " (time_embed): Sequential(\n", @@ -1078,11 +2452,11 @@ " (2): AttnDownBlock(\n", " (attentions): ModuleList(\n", " (0): AttentionBlock(\n", - " (norm): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (query): Linear(in_features=128, out_features=128, bias=True)\n", - " (key): Linear(in_features=128, out_features=128, bias=True)\n", - " (value): Linear(in_features=128, out_features=128, bias=True)\n", - " (proj_attn): Linear(in_features=128, out_features=128, bias=True)\n", + " (norm): GroupNorm(32, 64, eps=1e-06, affine=True)\n", + " (query): Linear(in_features=64, out_features=64, bias=True)\n", + " (key): Linear(in_features=64, out_features=64, bias=True)\n", + " (value): Linear(in_features=64, out_features=64, bias=True)\n", + " (proj_attn): Linear(in_features=64, out_features=64, bias=True)\n", " )\n", " )\n", " (resnets): ModuleList(\n", @@ -1090,216 +2464,33 @@ " (norm1): GroupNorm(32, 64, eps=1e-06, affine=True)\n", " (nonlinearity): SiLU()\n", " (conv1): Convolution(\n", - " (conv): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=128, bias=True)\n", - " (norm2): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", - " )\n", - " )\n", - " (downsampler): Downsample(\n", - " (op): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))\n", - " )\n", - " )\n", - " )\n", - " )\n", - " (middle_block): AttnMidBlock(\n", - " (resnet_1): ResnetBlock(\n", - " (norm1): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=128, bias=True)\n", - " (norm2): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Identity()\n", - " )\n", - " (attention): AttentionBlock(\n", - " (norm): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (query): Linear(in_features=128, out_features=128, bias=True)\n", - " (key): Linear(in_features=128, out_features=128, bias=True)\n", - " (value): Linear(in_features=128, out_features=128, bias=True)\n", - " (proj_attn): Linear(in_features=128, out_features=128, bias=True)\n", - " )\n", - " (resnet_2): ResnetBlock(\n", - " (norm1): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=128, bias=True)\n", - " (norm2): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Identity()\n", - " )\n", - " )\n", - " (up_blocks): ModuleList(\n", - " (0): AttnUpBlock(\n", - " (resnets): ModuleList(\n", - " (0): ResnetBlock(\n", - " (norm1): GroupNorm(32, 256, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(256, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=128, bias=True)\n", - " (norm2): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", - " )\n", - " (1): ResnetBlock(\n", - " (norm1): GroupNorm(32, 192, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(192, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=128, bias=True)\n", - " (norm2): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(192, 128, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", - " )\n", - " )\n", - " (attentions): ModuleList(\n", - " (0): AttentionBlock(\n", - " (norm): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (query): Linear(in_features=128, out_features=128, bias=True)\n", - " (key): Linear(in_features=128, out_features=128, bias=True)\n", - " (value): Linear(in_features=128, out_features=128, bias=True)\n", - " (proj_attn): Linear(in_features=128, out_features=128, bias=True)\n", - " )\n", - " (1): AttentionBlock(\n", - " (norm): GroupNorm(32, 128, eps=1e-06, affine=True)\n", - " (query): Linear(in_features=128, out_features=128, bias=True)\n", - " (key): Linear(in_features=128, out_features=128, bias=True)\n", - " (value): Linear(in_features=128, out_features=128, bias=True)\n", - " (proj_attn): Linear(in_features=128, out_features=128, bias=True)\n", - " )\n", - " )\n", - " (upsampler): Upsample(\n", - " (conv): Convolution(\n", - " (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " )\n", - " )\n", - " (1): AttnUpBlock(\n", - " (resnets): ModuleList(\n", - " (0): ResnetBlock(\n", - " (norm1): GroupNorm(32, 192, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(192, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)\n", - " (norm2): GroupNorm(32, 64, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", " (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(192, 64, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", - " )\n", - " (1): ResnetBlock(\n", - " (norm1): GroupNorm(32, 96, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(96, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", " (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)\n", " (norm2): GroupNorm(32, 64, eps=1e-06, affine=True)\n", " (conv2): Convolution(\n", " (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(96, 64, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", - " )\n", - " )\n", - " (attentions): ModuleList(\n", - " (0): AttentionBlock(\n", - " (norm): GroupNorm(32, 64, eps=1e-06, affine=True)\n", - " (query): Linear(in_features=64, out_features=64, bias=True)\n", - " (key): Linear(in_features=64, out_features=64, bias=True)\n", - " (value): Linear(in_features=64, out_features=64, bias=True)\n", - " (proj_attn): Linear(in_features=64, out_features=64, bias=True)\n", - " )\n", - " (1): AttentionBlock(\n", - " (norm): GroupNorm(32, 64, eps=1e-06, affine=True)\n", - " (query): Linear(in_features=64, out_features=64, bias=True)\n", - " (key): Linear(in_features=64, out_features=64, bias=True)\n", - " (value): Linear(in_features=64, out_features=64, bias=True)\n", - " (proj_attn): Linear(in_features=64, out_features=64, bias=True)\n", - " )\n", - " )\n", - " (upsampler): Upsample(\n", - " (conv): Convolution(\n", - " (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (skip_connection): Identity()\n", " )\n", " )\n", - " )\n", - " (2): UpBlock(\n", - " (resnets): ModuleList(\n", - " (0): ResnetBlock(\n", - " (norm1): GroupNorm(32, 96, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(96, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)\n", - " (norm2): GroupNorm(32, 32, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(96, 32, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", - " )\n", - " (1): ResnetBlock(\n", - " (norm1): GroupNorm(32, 64, eps=1e-06, affine=True)\n", - " (nonlinearity): SiLU()\n", - " (conv1): Convolution(\n", - " (conv): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)\n", - " (norm2): GroupNorm(32, 32, eps=1e-06, affine=True)\n", - " (conv2): Convolution(\n", - " (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " )\n", - " (skip_connection): Convolution(\n", - " (conv): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))\n", - " )\n", + " (downsampler): Downsample(\n", + " (op): Convolution(\n", + " (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))\n", " )\n", " )\n", " )\n", " )\n", " (out): Sequential(\n", - " (0): Linear(in_features=8192, out_features=512, bias=True)\n", + " (0): Linear(in_features=4096, out_features=512, bias=True)\n", " (1): ReLU()\n", - " (2): Dropout(p=0.2, inplace=False)\n", + " (2): Dropout(p=0.1, inplace=False)\n", " (3): Linear(in_features=512, out_features=2, bias=True)\n", " )\n", ")" ] }, - "execution_count": 36, + "execution_count": 124, "metadata": {}, "output_type": "execute_result" } @@ -1307,8 +2498,8 @@ "source": [ "\n", "\n", - "inputimg = total_val_slices[100][0,...] # Pick an input slice to be transformed\n", - "inputlabel= total_val_labels[100] # Check whether it is healthy or diseased\n", + "inputimg = total_val_slices[150][0,...] # Pick an input slice of the validation set to be transformed \n", + "inputlabel= total_val_labels[150] # Check whether it is healthy or diseased\n", "\n", "plt.figure(\"input\"+str(inputlabel))\n", "plt.imshow(inputimg, vmin=0, vmax=1, cmap=\"gray\")\n", @@ -1326,32 +2517,32 @@ "metadata": {}, "source": [ "### Encoding the input image in noise with the reversed DDIM sampling scheme\n", - "In order to sample using gradient guidance, we first need to encode the input image in noise by using the reversed DDIM sampling scheme.\n", - "We define the number of steps in the noising and denoising process by L.\n" + "In order to sample using gradient guidance, we first need to encode the input image in noise by using the reversed DDIM sampling scheme.\\\n", + "We define the number of steps in the noising and denoising process by L.\\\n", + "The encoding process is presented in Equation of the paper \"Diffusion Models for Medical Anomaly Detection\" (https://arxiv.org/pdf/2203.04306.pdf).\n" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 125, "id": "f71e4924", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false - }, - "lines_to_next_cell": 2 + } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "100%|█████████████████████████████████████████| 100/100 [00:01<00:00, 51.06it/s]\n" + "100%|█████████████████████████████████████████| 200/200 [00:04<00:00, 44.00it/s]\n" ] }, { "data": { - "image/png": "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1qhl2d4i48mTJ8vc0qVLg3Hv+E92drbMVa5cORi//PLLZU1OU8/fI488Imv27t0rc9498XvzjiB5R3n+hW92AIDo0ewAANGj2QEAokezAwBEj2YHAIgezQ4AEL08qf6Hjz/+uMz97W9/C8a9EfkaNWoE42+88Yas8cb0vSMGyvHHHy9zbdq0Cca3bt0qa/r16xeMe9vb33nnHZmrW7duMO798kLbtm1lTh2n8H71oFy5csG4d2Jl/vz5MlenTp1g/NVXX5U18+bNkzl1TyTZpv/FF1/Imuuuu07mtm/fHoxfc801subss8+WOXWMoHTp0rJm165dwfhpp50ma5588kmZa9euXTB+3nnnyZq5c+fKnHo/3XzzzbJGHVfwfv3hrbfekjl1RGTTpk2yZsWKFTKX5BdL/v73vwfj3jGoGTNmyJyqU7/AYma2f/9+mcvJowfecbUUT7z9D7lz5z6cy+GbHQAgfjQ7AED0aHYAgOjR7AAA0aPZAQCil/I0pre4VvEWIKupvDfffFPWrF+/XubGjBkTjF955ZWyxlsa++CDD8qcsnbt2mDcmwz0qMm3QoUKyZotW7bI3D//+c9gfOrUqbLmk08+Cca9xc3eBKCiljObmf3Hf/yHzKmpwXPPPVfWTJkyJRhXi33N9JJvM7MOHToE45deeqms8ZZbq6lZz6OPPhqMq0XZZmZXX321zKnn1Zu4rFChgsyp58hb7qsmChctWiRrvMndHj16BOPe4mZvUlnxlm/36tUrGPfuB28aUz0Xa9askTWHO9WYqpyexvQmTFPBNzsAQPRodgCA6NHsAADRo9kBAKJHswMARI9mBwCIXspHD37++WeZUyPWO3bskDUZGRnhC8qjL6lo0aIyt23btmDcO8rgHS8YNGhQMH7hhRfKmgceeCAY90Zwn3rqKZkbMmRIMN60aVNZM2LECJlTxzPy5csna84666xgXL1+ZmYXXHCBzH366afBeIECBWTN888/n/ZjeUcFatasGYyrxcNmZmeccYbMKd9++23aNWZ62bg6MmGmj73UqlVL1nhj/wMHDgzGR40aJWtWr14tc++//34wrhZYm5nddNNNwXixYsVkTfPmzWVu2bJlwXiVKlVkTRJ33323zC1cuDAYHzp0aKLHWr58edo13iLonOQdcTh48GDafy9Jza/xzQ4AED2aHQAgejQ7AED0aHYAgOjR7AAA0aPZAQCil/LRA8+qVauC8YkTJ8oaNVY8fvx4WXPPPffI3Nlnnx2Mv/7667LGU6pUqWDcOxqhRsO9Dd/e0Qj1iw3e5nlvhFkdm1Cj+GZ6i/wzzzwja7xfXlC84w8edZShdevWskZtsv/yyy9lTb169WSuZMmSwfiwYcNkzc033yxzarS+U6dOsmbJkiXB+IABA2SNp3///sH4SSedlOjvvfLKK8F47969ZU2/fv2Cce896L2Gd9xxRzCujlmYmbVv317mlFatWslcw4YNg/Fy5crJGnUUxcxsz549qV5WSvLnzx+MZ2Vl5ejjJJH0M+Jf+GYHAIgezQ4AED2aHQAgejQ7AED0aHYAgOjlyDRmnTp1gvEmTZrIGjU1mJ2dLWvq168vcxMmTAjGW7RoIWs+++wzmXvppZeC8fLly8ua7t27B+OzZs2SNccff7zMeUt3FTXBZmbWtm3bYPyRRx6RNWry7ZtvvpE13uLm2rVrB+MLFiyQNd40Wrdu3YJxb8mxej28xeCvvvqqzD377LPB+PTp02WNN42p7lk1cWmmJw1ffvllWXPjjTfK3CmnnBKMt2nTRtZ41PtpzZo1skZNX3sTiMcee6zM1ahRIxivUKGCrPFcdNFFwfh7770naypVqhSMe0u0vUlIb8m8cswx+juONzmerpz8W2aHv8Cab3YAgOjR7AAA0aPZAQCiR7MDAESPZgcAiB7NDgAQvRw5evDuu+8G4xdffLGsUWPPK1eulDWVK1eWueXLlwfj3tJkb6T3iSeeCMbVv9VMX59agmtm1q5dO5lTvDH9a665RubUyL33b3rooYeCcbVU2sxs5MiRMte5c+e0r2HatGkypxYd9+3bV9YsXrw4GL/zzjtljbdg+/LLLw/G1ai7mb8kvUOHDsH4ww8/LGvUsRJvKbG6bjN/kbayYcMGmVNHdrzx9EGDBgXjd999t6xZtGiRzKkF4N7z4Ln00kuD8XHjxsmahQsXBuNFihSRNd5RAXU0yBvTP9wR/lTl9OOwCBoAgEOg2QEAokezAwBEj2YHAIgezQ4AEL0cmcZUE1VVq1aVNRs3bgzGveXMXk4tvPUmLps3by5z6vqKFSsma1588cVg/KOPPpI1VapUkbl69eoF42qZsplZRkaGzD355JPB+K5du2TNjz/+GIwnnYxSE7redOczzzwjc1deeWUw7v2blKlTp8qcWmRsZlawYMFg3JuMbdiwocypybyaNWvKGjXt2LFjR1lz7rnnypx6nfr06SNrPvjgA5lTC5p3794taxo0aBCMf/HFF7LmvPPOk7lNmzYF4++//76s8XTp0iUYP+6442SNet94z4O6v8ySLVs+cOCAzB08eDDtv/dbyZ8//2HV880OABA9mh0AIHo0OwBA9Gh2AIDo0ewAANGj2QEAopcjRw8Ub0z/1FNPDcZHjRola9TyaDOzL7/8Mhi//vrrZc3mzZtlTo3gXnHFFbKmevXqwbg3Vuwdp9ixY0cwvnfvXlmjFi17tm7dKnNPP/10MD5z5kxZ4x05UX9PLbQ1Mzv99NNlTo15d+vWTdasXr06GJ89e7as+eSTT2ROja5791elSpVkrkmTJsH4hx9+KGsKFSoUjHtHOnLlyiVzaqn5pEmTZM3SpUtlTt3L6rrN9D2RN29eWaOO/5iZlSxZMhhPsmDes2XLFplTR4N27twpa7xF0OoYQe7cuWVNkuMKRwPv35QKvtkBAKJHswMARI9mBwCIHs0OABA9mh0AIHo0OwBA9I7o0QN1HMBMbyc/6aSTZM3bb78tc+XKlQvGFyxYIGu8MeoTTjghGG/Tpo2sUaPcSUd91Wb84cOHy5pLL71U5m6//fZgfPHixbKma9euMqc88cQTMqeei6+//lrWqG31Zma1atUKxr37aO7cucF4y5YtZU337t1lrkSJEjKn7NmzJ+2aFi1ayNxDDz0UjJ944omyxvslh969ewfj6tcLzMzuvfdemVO/SnLaaafJGvXrBv369ZM13uukrr1Ro0aypkOHDjL3l7/8ReYUdQzJG6v3jh6o99PR/OsFh6I+R70jJ6ngmx0AIHo0OwBA9Gh2AIDo0ewAANGj2QEAondEpzGnTJkic1OnTg3GvYmzefPmydzatWuDcTWBaGbWsWNHmbv44ouD8VtuuUXW/Pjjj8G4WjxspqfezMzWrVsXjHvTnVlZWTLXo0ePYNx7ztXzN2TIEFmjJgM9BQsWlLmaNWvKXNu2bYNxb7m1Wgjct29fWZOZmSlzavJTTRybmd1xxx0yp+6X9evXy5oNGzYE496Un7d8eNmyZTKnePe5Wo48ffp0WaNeJ29Csm7dujKn3k/ec/TNN9/InKKWk5uZ7dq1KxjPzs6WNfv27ZO5/fv3p35hfxDq8+1w/618swMARI9mBwCIHs0OABA9mh0AIHo0OwBA9Gh2AIDo5folxS3FajlnUqecckowfs0118iaAwcOyFzPnj3TvoazzjpL5saOHRuMf/TRR7KmXr16wfiIESNkTaFChWSuYsWKwfj27dtljbeMVy3FVmPrZnpJtHekI8mCXG/ZbZ48+oSMei5KlSolaz7++ONg3Ftc7t3/6r70FmJ7xwj69OkTjP/jH/+QNWpM/7vvvpM1tWvXlrk5c+akdW1mesmxmdmqVauC8aJFi8qa5s2bB+OzZs2SNR51JGbFihWyZsyYMTKnlnlnZGSkXeMtOfbuvb179wbjSZfPH83UcTAz/3P5X/hmBwCIHs0OABA9mh0AIHo0OwBA9Gh2AIDo0ewAANE7or964Jk9e3Ywvm3bNlnTuXNnmbvvvvuC8eeee07WeBvXb7755mC8WbNmskZtkZ88ebKs+frrr2VObdpv166drDn11FNlTo3Wd+3aVdZ8/vnnwfiSJUtkzbhx42ROjf0//fTTssYbn/d+WUNRY9kffPCBrNmxY4fMffrpp8G4d+Rk0qRJMqd+AcIbJ1f3Xr58+WSNGu03M2vTpk0wft1118makSNHypw6WlK2bFlZs2bNmmC8dOnSssY7nqTG/r3X3TtGoH6NwPs1CfULCwcPHpQ13rGcGI8YHCl8swMARI9mBwCIHs0OABA9mh0AIHo0OwBA9H63aUxl+fLlMte/f3+Z69SpUzD+5JNPypo33nhD5lavXh2M//zzz7Kmb9++wbi3TFZNJ5rpxadDhgyRNWohsJlZ06ZNg3HveT3zzDOD8WrVqsmaQYMGyZw3xal4E5dZWVnBuFo8bKancC+99FJZs27dOplT98ro0aNljfc6qcm8xo0by5pnnnkmGPcmLsePHy9zahL4uOOOkzUe9TolWTDv/ZuqV68uc2oC3FusrqYnzfTkp/dvKlCgQDDuTVxmZ2fL3L8T7zlKqT6HrgMAgKMWzQ4AED2aHQAgejQ7AED0aHYAgOjR7AAA0Tvqjh54fvrpJ5l76KGHgvG2bdvKGm88vXXr1sF4ksWr77zzjsy9+eabMqdGw73jGZUqVZK5Dh06BOM9evSQNWrMu0yZMrLGuz717/3oo49kzdVXXy1z7733XjDujaCrxb+33367rGnVqpXM7d69Oxj3lhyr6zYzq127djD+1VdfyZpZs2YF41u3bpU1ajG49/e8ozKbN2+WuVGjRgXjr7/+uqxRo/333nuvrPnwww9lTi1Jz5s3r6xRy5493meEOoKR9O/9O0ny3P0a3+wAANGj2QEAokezAwBEj2YHAIgezQ4AEL0/1DSmR03EvfDCC7KmYsWKMqcW9aoFuZ5hw4bJnDe5+N133wXjEydOlDUrV66Uue7duwfjd9xxh6x5/vnng/GhQ4fKmoIFC8qcWrrrTaV6C2D37t0bjLdo0ULWqOXWF1xwgazxphrV5OLUqVNljZq4NDMrX758ML5hwwZZo5Zle/f/6aefLnPe1KWyadOmtGtuvfXWtGuuuuoqmZswYYLMqfeGN3Hp3XtqYbe3PFrl1OSpWbJl2fi/+GYHAIgezQ4AED2aHQAgejQ7AED0aHYAgOjR7AAA0cv1S4pbRpOMv3ojuN6o7dGgQoUKwXjPnj1ljTpGcMopp8gab1G1GtNXy5nNzObMmSNz9evXD8bVCLWZPv6wdu1aWaMWLZuZ5c+fPxi/8MILZc3AgQNlTh0j8Mbq+/fvH4x7/6YvvvhC5tQRg/vvv1/WXHLJJTJXuXLlYLxWrVqyJsn701sa3rJly2Bc3Q9mZvny5ZM5dTTCu5fnzp2b9uMsW7ZM5goXLhyM79y5U9Z48uRJ/+SWunZvybH32u7fvz/ta/ijaty4scyp++vX+GYHAIgezQ4AED2aHQAgejQ7AED0aHYAgOjR7AAA0TuiRw9ipMaXzczOPvvsYHz9+vWy5owzzpC5QoUKBeNqdP5IaN++fTDujdV7v7yg/k1nnXWWrLnttttkrnPnzsH4li1bZI0a4S9btqys+fHHH2VO/fLC9ddfL2smT54sc+pXI7xf6VCj66eeeqqs8cb0a9SoEYwnPfYyduzYYNz7+ClTpkww7r2fvM+pJGP/efPmlTl17d4vJajjCt4vL3hHg472I1w5yTtONGPGjEPW880OABA9mh0AIHo0OwBA9Gh2AIDo0ewAANFjGjMHqefIe4pPOOEEmdu9e3daj2NmtmHDBplTU41fffWVrKlXr14w7k2lTpo0SebUta9evVrWeIoVKxaMb926Vdaoab7SpUvLmpdeeknmpk+fHowPHTpU1qil3GZm8+fPD8ZbtWola7p27RqMq9fPTE87mpk1bNgwGP/mm29kjWfdunWJ6o5marLSm5BMMo2J/3LyySfL3OzZsw9Zzzc7AED0aHYAgOjR7AAA0aPZAQCiR7MDAESPZgcAiF54DhaJpHiK439YsWLFEbiSsAsuuCAY90bkH3rooWC8bdu2OXJNqejXr5/MderUKe2/16tXr2DcGxkfOHCgzFWtWjUYP/HEE2VNixYtZO77778Pxr3xarVQXP1bzcyee+45mVPHKbZt2yZrvIXKOUmN75v5x3LUgm11xOdQkixh/nda3JzTDve545sdACB6NDsAQPRodgCA6NHsAADRo9kBAKJHswMARI+jB/9GHn/88WA8f/78smbkyJE5eg1qbHz//v2yZufOnTLXvn37YHz48OGyZuLEicG4Gt8/lLVr1wbje/bskTWVK1dO+3G8EXn1vA4ePFjWZGZmypy6J36r4wUe717xjh6o5++YY/T/83uPpXhHkPj1mN8P3+wAANGj2QEAokezAwBEj2YHAIgezQ4AEL1cv6S4vTjJFFHu3LlljoWofwwlSpQIxrOzs2WNN92pliaXKlVK1lSoUEHmihcvHozv2LFD1qjpztdee03WePe/+jetX79e1pQsWVLmli9fLnN/VOr5y5cvn6xJMgnpfa6oqUvvteVzKjnveU2yNL969eoyt3jx4kPW880OABA9mh0AIHo0OwBA9Gh2AIDo0ewAANGj2QEAondEF0EXLFhQ5tRS1oMHDx6py0EC3rJgxRv737x5c9p/z7sn1PEWb2RcHafwjsp4I/KrVq0KxvPmzStrtm/fLnO/FW8Bck6/D9VxFG88XT1/SY8DqNfXuwbvnlDPkTdWrxZ2e8+39/fU8YykY/9J3k9Kks9/M/26Fy1aNO1r+DW+2QEAokezAwBEj2YHAIgezQ4AED2aHQAgekd0GtObZEqyCPS3nB47Gqh/r5roMvOf13379h32NR2unH6d1JSYNz2pFkF7C6y96THFm2Dbs2dP2n/Po67du1e8ib3ChQsH496krff+VLzrSzJpmOT+Svo5pa7Du4Ykz5FH3WNJpzFz8v2Z9N+qPqe85emp4JsdACB6NDsAQPRodgCA6NHsAADRo9kBAKJHswMARC9Hjh6oxbply5aVNbt27QrGN2zYIGuys7NlTi0PzcrKkjUeNY6cZEzZq1Hj1WZ6LNtbMOyN3O/duzcY98aN1QizN9qc5IiDN6afZAmtd68o3qi0dyxB3WPec+T9PXVPeCPj6tq956FQoUJpX4N33UleJ+85V/d50veTej2SHj1Q/17vecjJz5VD5ZLUqM8c9dlhpp9X717xqKNBxYsXT/T3/oVvdgCA6NHsAADRo9kBAKJHswMARI9mBwCIHs0OABC9HDl6kJmZGYyXK1dO1px44onBeLVq1WSNNyKfkZERjCcZHTbTI7jeqHROj+mrceQk122mR+S9a1D/Xq/Ge87VaLg3Mu5R/17v76mc99x5RzqS/D1v1FzVJTmu4N17Hu/fq3jvzyTHctS1J31PK0l/RUTlkhw98HifOeo9neRxzPRxD+8a1C9heEek1qxZI3NLly4Nxr3ekAq+2QEAokezAwBEj2YHAIgezQ4AED2aHQAgerl+SXGTaNKpLgAAjqRU2hjf7AAA0aPZAQCiR7MDAESPZgcAiB7NDgAQPZodACB6KS+CTvGEAgAARx2+2QEAokezAwBEj2YHAIgezQ4AED2aHQAgejQ7AED0aHYAgOjR7AAA0aPZAQCi9/8AuDeQHVcWchIAAAAASUVORK5CYII=\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1361,7 +2552,7 @@ } ], "source": [ - "L=100\n", + "L=200\n", "current_img = inputimg[None,None,...].to(device)\n", "scheduler.set_timesteps(num_inference_steps=1000)\n", "\n", @@ -1378,7 +2569,8 @@ "plt.imshow(current_img[0, 0].cpu(), vmin=0, vmax=1, cmap=\"gray\")\n", "plt.tight_layout()\n", "plt.axis(\"off\")\n", - "plt.show()\n" + "plt.show()\n", + "\n" ] }, { @@ -1386,29 +2578,30 @@ "id": "a7c8346a-6296-4800-b978-c10fcdf09779", "metadata": {}, "source": [ - "### Denoising Process using gradient guidance\n", + "### Denoising Process using Gradient Guidance\n", "From the noisy image, we apply DDIM sampling scheme for denoising for L steps.\n", - "Additionally, we apply gradient guidance using the classifier network towards the desired class label y=0 (healthy). The scale s is used to amplify the gradient." + "Additionally, we apply gradient guidance using the classifier network towards the desired class label y=0 (healthy). This is presented in Algorithm 2 of https://arxiv.org/pdf/2105.05233.pdf, and in Algorithm 1 of https://arxiv.org/pdf/2203.04306.pdf. \\\n", + "The scale s is used to amplify the gradient." ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 126, "id": "7ab274bd-ea60-4674-b59b-d41de98fee5b", "metadata": { - "lines_to_next_cell": 0 + "lines_to_next_cell": 2 }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "100%|█████████████████████████████████████████| 100/100 [00:06<00:00, 14.41it/s]\n" + "100%|█████████████████████████████████████████| 200/200 [00:11<00:00, 17.41it/s]\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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U1Onji0iYsaMGXKtubm5GHdbD9TP2r9/v8w5ceKEXHviiSeKcfe2i6FDh8q1Xr16FePr1q2TOWo7ituK8sEHH8g1dV/Onz9f5hw4cECu/f73v5dryrRp04rxzZs3d/qzkBtvPQAAICh2AIAqQLEDAKRHsQMApEexAwCkRzfmh2jp0qXFuBvc7Abh9ujRoxjv1k3P71bDniP0AF3VRerW3DBe11nZ3t5ejA8YMEDmvPXWW3LtscceK8bdMGp1zrt37y5z3DlqaWkpxseNGydz1JDvCN1JOnjwYJnzgx/8oNM/Z+/evXLNdfUCtxq6MQEACIodAKAKUOwAAOlR7AAA6VHsAADpUewAAOmx9eAmW7FihVxTQ5PdkONdu3bJtffff78Yf/TRR2VOJW3658+fl2s1NTXFuNueobYrROjv9KlPfUrmtLW1FeO9e/fudE6EHnzt2v7dMO9+/foV4xcvXpQ577zzTjH+wgsvyJwOPvrALY+tBwAABMUOAFAFKHYAgPQodgCA9Ch2AID06MasIuoaLlu2TOaozs8rV67IHLemui5dp6EaiB2huyQ3btwocxYtWlSMHzlyROaojssIPcR69erVMscNWlYDml0XLlDN6MYEACAodgCAKkCxAwCkR7EDAKRHsQMApEexAwCkx9YDWN/4xjeK8dGjR8scN+RYDZZubW2VOYcPH5Zra9asKcbdYOkdO3YU408++aTMeeWVV+QagJuLrQcAAATFDgBQBSh2AID0KHYAgPQodgCA9Ch2AID02HoAAPhYY+sBAABBsQMAVAGKHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AID2KHQAgPYodACA9ih0AIL1uHf2H169fv5HHAQDADcNfdgCA9Ch2AID0KHYAgPQodgCA9Ch2AID0KHYAgPQodgCA9Ch2AID0KHYAgPT+HzbxLsbNP3t+AAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -1421,14 +2614,15 @@ "\n", "\n", "y=torch.tensor(0) #define the desired class label\n", - "scale=10 #define the desired gradient scale s\n", + "scale=5 #define the desired gradient scale s\n", "progress_bar = tqdm(range(L)) #go back and forth L timesteps\n", "\n", "for i in progress_bar: #go through the denoising process\n", "\n", " t=L-i\n", " with autocast(enabled=True):\n", - " model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device)) # this is supposed to be epsilon\n", + " with torch.no_grad():\n", + " model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device)).detach() # this is supposed to be epsilon\n", "\n", " with torch.enable_grad():\n", " x_in = current_img.detach().requires_grad_(True)\n", @@ -1446,8 +2640,7 @@ "plt.imshow(current_img[0, 0].cpu().detach().numpy(), vmin=0, vmax=1, cmap=\"gray\")\n", "plt.tight_layout()\n", "plt.axis(\"off\")\n", - "plt.show()\n", - "\n" + "plt.show()" ] }, { @@ -1455,19 +2648,19 @@ "id": "d2e343f8-c6f3-4071-a5e6-771e2343c3bc", "metadata": {}, "source": [ - "### Anomaly Detection\n", + "# Anomaly Detection\n", "To get the anomaly map, we compute the difference between the input image the output of our image-to-image translation model, which is the healthy reconstruction." ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 127, "id": "ecffaaf3-a7df-453e-81a9-757113d85084", "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbsAAAG7CAYAAABaaTseAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiJ0lEQVR4nO3db6im6X0X8N+0p/UkPVsPyZAsZMQTnJTdsFnX9E8CWXGRrk2lFGkrLRixr1oE0SK+EOmLiEJ9UWp9Ibboi0AtUptCsdW0pOgWE0htEpfNslnNaAacwCROlqOZZk/tieMLaWvj8/3OnLtnd2eu8/m8vK5z3c/93M/9PD8OfK/ffenOnTt3BgAW9jWv9QkAwCtNsQNgeYodAMtT7ABYnmIHwPIUOwCWp9gBsDzFDoDl7d3rH1669Pky+4ZzfOlLZc2Duv+9vSd4kLTvoPv8wXeev7HtfvidDXNfF1fcuZPnfpf/7ABYnmIHwPIUOwCWp9gBsDzFDoDlKXYALO/SvT7P7tKlL5fZ15/T6QBwsaWSlLcy3EsV858dAMtT7ABYnmIHwPIUOwCWp9gBsLx7bgTdmnACwPl4ZRqK+88OgOUpdgAsT7EDYHmKHQDLU+wAWJ5iB8DybD0AYHn+swNgeYodAMtT7ABYnmIHwPIUOwCWd4Y0Znvu+SvTuBMAzoP/7ABYnmIHwPIUOwCWp9gBsDzFDoDlKXYALO8MWw/gftS2xCS2ysBF4z87AJan2AGwPMUOgOUpdgAsT7EDYHmKHQDLs/WA89fuqtPzfjHbCIC7858dAMtT7ABYnmIHwPIUOwCWp9gBsDxpTKY3U345jJdY5ek3nv0Utt6JW9Kd+xuOdbnM3T7j+Gbtc5JKhcZ/dgAsT7EDYHmKHQDLU+wAWJ5iB8DyFDsAlnfpzp07Lc/8+394Sez5wfc7ZxyfmXn97uEU35+ZOWnncE+321dp91c63oZ7sr6n58rk42H8i3nJ5Tfewwl9lS1bGepnAWu4lyrmPzsAlqfYAbA8xQ6A5Sl2ACxPsQNgeYodAMu7AFsPtkTdZ+6P95TOPT2JYGbm6za8zpY1JVY/LVaf1rUnJbStEenct7ynrfd4Or92Dp8tc1fOfryDMN6e5PDAbkt4UH+LeKXYegAAo9gBcAEodgAsT7EDYHmKHQDL23utT+CVtzWddY4NhlNSbuYuzX1T6rKl/Npcek8tWZmOVxKXh+VwxykeeN7vaUuC89X01g1rPp+nbr9585ns9HAYPy5rzj3deY7fQS48/9kBsDzFDoDlKXYALE+xA2B5ih0Ay1PsAFjeBWgE/WoK12ivXJ/T58rx0s6QR8uaL5S5N5S5syox+NjIePJbOv1IOd47y9zrw/iXyzmENaetOXOJ9h+F410v5xDPeyZH7ktX572wneK0fU5vKnPXwvjVsmbD78B+mXtgG1XzatMIGgBGsQPgAlDsAFieYgfA8hQ7AJZ3H6Yx7/fUZ2swnOZa8q4cLyXsmhLYy7Yk9j5a1rwtTz0RUo3PtuTiS2UuJT833EeXy5J2XY+fCRNP5TWH7XhhvLVtP03NvEvD7mpLsrgdL51fuccPvnHD6xQaVS9LGhMARrED4AJQ7ABYnmIHwPIUOwCWp9gBsLzXcOvBfRDbbVHuNHfSGjenZsHthUpz5sd2X4uv+bXfiku+fv+3y2vtdvLL5Rzel7ZGtPf0yTJ3FMZDzHxmZl4uc21dcBDGb3+oLHpvmQv3bOmHPTfaFpYUx7+RlzwWXuz5tqWjbW3ZsO1lU1Pn1nz7rbuHHy5Lbpa52IT8ft/uxN3YegAAo9gBcAEodgAsT7EDYHmKHQDLU+wAWN4Zth5siUpvcc4x4E1x6Jncpb10kU+vddLe05fKXIjV/0Be8V3/4ufj3K9+8Tt2jv/OydfnA34wvKkfyUtmPlzm0laBdn89XuY+HcbfVdakCH/aOjIzl8s9fmvLNpoX8tT+28N4Odzxq7WV53/mqaOyDeT6ltd6Jow/VdbYRnAR2XoAAKPYAXABKHYALE+xA2B5ih0Ay7sP05hF6z2cnLbJ3yhzj4bxh85+DgclBXa7Xf6XwvFKIvSv5akf+bEf2zn+D1/8O3HNP3rkh3aO/+hv/f245k9/w7+Pcx/69e/ZPfGTccnMi2XuVhovidCDp3eP3/5MWfO2PHf782GiNNhuTZ1TA+RpTZ1fF8ZbAvGevvpnOF5xOYzfSqnnmfy7UlKfqcn3zMzt9Bt2zo3GedVJYwLAKHYAXACKHQDLU+wAWJ5iB8DyFDsAlvdgbT3Y5LNl7vV5ai80BU4R6pm8NaI1nL5d5q6G8XYOxZV/tzta/yvz3rjmdSHu/lA58X98Kc+9/2O7x//pu94X1/zQL/xMnJv3h/Hn85LcdLrcx23by2k6XltUGoBfCXH3tM1i5i5NzYPaJD1tw2jbKdrvQGggvX+lnEMYf7i8zM2yfWTK9pGkfu5nPxyvDFsPAGAUOwAuAMUOgOUpdgAsT7EDYHmKHQDLe7C2HtQYcHob4ckBMzNTnh5wGMaPQ4R6ZuYgRMbb9oIYg29zZcvElG7/4UkOX7iTnvAw88K8fef4y7HL/swbJ3ey/9r5ys7xH5+/Fdd8Zb42zv3Lv/tXdk+8v9zWh6Fz/2N5SY3p/1r4nN5dvhcfa08wSJ/vc3nJ3uO7xzfH48t9HrUnBKRzD+c9M+f+VIZNT00556cy8Iqw9QAARrED4AJQ7ABYnmIHwPIUOwCWd/+lMQ/KXEuWnbywe3xvd5rwrsdLDaTf/da85GMpwXbO6ax2jW5/okx+bufoL9z5qbjie771QzvHf+I3/2pc8x/nT8W5n/n5H9o98Ufjkvnvfy6/4T87/3bn+PPv+NZ8wOfTLV+SfLVpchjf2rD4KDQsvtnOIaUGS+K43kfh+xTSuf9XSy7uvvdm3lHWvBzGSxq5NUlP3/fjsua8E6G8IqQxAWAUOwAuAMUOgOUpdgAsT7EDYHmKHQDLew23HmyIf9eo9OfDxJu3He9qep2y5lpq7tsaN5e49l6IjZ9uaSKc/fE7L8a575sP7hz/+HxLXPNH5rfj3JfmoZ3jPzw/Hdd8cL4vzv3yv/6Luye+q9yv7w3366/kJbUx8sNha0nbXjBhe8HMzITP93L5bNP2h9vne69slz6PLb8daVvETN8acWP38OUrecmtj5TjvSeM23rwarP1AABGsQPgAlDsAFieYgfA8hQ7AJZ3/zWC3itzpx8uk0+f/bVyoHDm4xsSccdh/DSkwGZmnihJsGdDUjOlNGdmTkMD65mZp0IT65/MSx77k7+5c/zPz7+Ja56Zp+Lcta/sjrm+dO0t+STK5YsJyh8va5LyUdRzmJDYu/xkXnKrpDvji7UTTM3GU0p5piaVU+KxNlZv6dMUb/5UWfN4mUvaz1lKSbbm6Y+WufS7d46/h9wTaUwAGMUOgAtAsQNgeYodAMtT7ABYnmIHwPJe4a0H93ToP+ioNFG9WdalRrgPlzU3W/z7dWG8RLn3QzT8cnmZR8pc8myZa5H2n9wdT3/4b/zXuOTN84Wd4//pf3xTXHNyvLvZ88zM3Aj3Su5Fnbd0zMTU//xiWZMagLcm360RdIz9lybfV8v2kXQeN9t3MG1XCNtNZmambFOJ3+my/aF+18K2hCdKQ+xnnwsT78hrLpffj1t5Kjosc8dhe9Je2Z50uqEBPndl6wEAjGIHwAWg2AGwPMUOgOUpdgAsT7EDYHntGQPb//T3lDhtin9fb/Hql/LUfujgXrcXlPf0SIrIv6EcL2iR9jZ3FMaPy5ofTTH4iVsgvmN+NS75pa98987xk4+V6/BY2gcyMx8P17XFwn+5zJ2miRL7Pw2x/yfK6zxb5uJrle7311pW+uXdw1dKpP122GJwXJ4Usl+eFHKS3lN5ssHlso3g5s/uHn/x/XlNfOpBeCLDzMxxeSpD2mpxpWzPuNGe5BC+a6flc+I14z87AJan2AGwPMUOgOUpdgAsT7EDYHlniFjG2NvE1FlKXM7kprHXWnryTXlqP4yftLdYUlMvpsa6n8trDt8VzqGcQmuA/HwYPyprymv957/0x3aOf9Ov/re4Zv/dIQF7vZzDY2Xu42H8WlmTrsPMzNU0ERKNTW0E3VJ5wWPfnOee/0RZGM795pN5yWlqUP6WvOakJZ9To+ry/Xy+zF15/+7x9DWrr1USl+3rfhpSlzeeaSdRhAR4peHza8V/dgAsT7EDYHmKHQDLU+wAWJ5iB8DyFDsAlnfpzp07rSPt7//hpRZTLg1voxQrLvHlvSt57jQ1ri0Niy+XGPCtL+8e3y/bFU7CmoOyJjRnnpm82+PbN6xpr1W2iPyZv/crO8f/y/yJuObGO0pD4BQ1P85L6j0RtxiUbSoPh8/9ZtsO8GiZS/f/p/OSw9TkePIWiLaV5zg0OZ7ynbnaGlWnibTFYaY2347Xr0Xx0/HStoiZmXP+nTosc98Xxv9Zuw6pUfuW31B+171UMf/ZAbA8xQ6A5Sl2ACxPsQNgeYodAMtT7ABY3hm2HpQ/Owzx4dZFPnZpL53Ej8rxrofxFte+XTrZXw7x+VutTXuKD78uL7maosiTn2CQnhgx07vIv3v38P4HwpMNZubkB8LWjY+V1zkuczG6/lBZU7ZuTNjuUaXjtVh9+QxjRL6dd3uCR/g+nWy5Xz9czuE9eeoonPv1Fu1vjxxI2zDKEwyithWlfJ/S1ptb7bXaz+NHw3h74sZT7cUC2xLuxtYDABjFDoALQLEDYHmKHQDLU+wAWN75NIK+HNJCt+7p0F8ldqC9i5SAKgmxh0sS7GaaKO/pYEMqtfTpjU1o2/Hiec/M1TDeLnlKhG5uuJuSdC3tWNJ3h6Ep8HFrxpsaCX+krHmyzIUU515JFp+270a6l1P6b2auPL17/Ea7Du2afymMt8Rla9Ac3u9eaQR9mqLF5f7aL9f8JFyLdA/N3CVZnLRIdPrCt/uhNctmRhoTAGZGsQPgAlDsAFieYgfA8hQ7AJan2AGwvJYjvvc/PU0TnyzHSxHhlI+fmaslgvtIGG9NXlsz4xoFDg7DeNsq0Ob2w/j1T5RFLe4eYs8nrbFu+Nz3SpPjeD80JVbfmucep4kWg38hjL+rrPlsmXvr7uHWhPx4Q5z8MGwvmJm5kZpEH5UDti0d4T46brH65gu7h0/b1ohwv+635unteGHLwvGWZuIzeetG+Q4mqfn3zMxJO7/WbJz/l//sAFieYgfA8hQ7AJan2AGwPMUOgOWdoRF0S+yVdFS0u5Hw19z8X3HF//6pb4hz+z/y0s7xk7/whnwKz+SpmKzclNz6UJ66/L157tYzYeLR8lolNXsQEoq3nyvHezyMb2l2O5OTkKUp9yYpnTgz87Yw3ppbt+Byapq89Rql79pbypqQdqzn/ek89UhofP1iaHp919cKvxGpifzMzK30Wi3tuCE1u1n4nK6U38OtYdYkXfJNiegHl0bQADCKHQAXgGIHwPIUOwCWp9gBsDzFDoDlnWHrQYtlp/jwhvj3L5YlP17mUkPlm2XNzbaN4HNhvMWrU9735bKmxclTBL01OW6vdYa+378nvad2rNQgdybH9JsWNU/x9Ha/pmtert3l0nA3Nhtv51CapD8cGlLfbE2Ok7YtqLzfFJ+/0bLzu7f/zMzMUdjCcn3LFpGmbY1I92X7nNp3LXkVGzfbejAzth4AwMwodgBcAIodAMtT7ABYnmIHwPIUOwCWd4Yseokc74du4iflSQnfFcbbGZ2UuRS13S9rWgz4kRB7bpHeuP2hxavbAdNci5O3eHqK/b+jrEnd9Nv2gtLJft4UxtuWifbEjS3HS9e1bS9ocfIQub9anmxwLZ33lPulbFM5DOd+3J4CcJSn4netXdeyReR6yoaXc4i/Oe2JEeX+3wtP1jhtWw9Kpv3w0u7x47b9IT15oX1vy/aHwzAet8NcXP6zA2B5ih0Ay1PsAFieYgfA8hQ7AJZ3hkbQLRGX0oHt0LuTTE/e+XBc8cPz03HuL//sB3dPvK81rm2pwZSoulrWfCqMH5U1zZbGzU17v0lK37UEW7tXUrKspTtbUi0lAFvD3XDuj5Tr8+JHyvFCuvPwqbzkuFy/q+E8rm1oMHxQltzekgDc0hC+rWv3SkrNlntlrySVT1MyNSUkZ+q9dzlco5qETL+JIdlZ19xt3at1vNeeRtAAMIodABeAYgfA8hQ7AJan2AGwPMUOgOWdYetBmUzNlk+eKYveE8a3xJfLupbej1HkmR5HTlJMuTVubu/3N8L4u+7tdP4/aTtFaVz7xOO7x59tr9O2e7whjLfr0BppP7p7eL/csCdpe0u5DvvvK8dLEy/kNa1pcrwWJaa/H5ojn7TvzIfy1NF37x6/Xta0huLx/J4rxwufx7c8nZd8/AN5bu8Hd4+ftvsrNPmemTkI38PbefvU7IVzr82oW2PpdK+0+2s9th4AwCh2AFwAih0Ay1PsAFieYgfA8hQ7AJZ3hq0HLf56njHXLU9XKNK2iJkSGW/a5Upd2lus/ryfJvGFMvdQGG9PHEgR/mfKmrAdoJ5DerrCTO7AP9OvRRLeU71XWkQ+bM8IafuZmblRuuk/Ed7vs+V46ekG6ZacmTnZ0v2+3a/lPjoM34HjtL1mJt9HZT/RUXnaxfX05Ip3lnNoT89I2tMkkrQlZ2YeK9tonk/bfNrNtx5bDwBgFDsALgDFDoDlKXYALE+xA2B5Z0hjtia0ITV40hotp5jY28qaLemxloxqKb+UPm3J03SNWjfq62Xu7WG8NFq+XFJYx2H8tCTi9kOz25rka1LqsiVWWxI4vd/2uafXamnfdg6f3D189J15yfUt9+WWxuWfKGu+ucyFezl912fu0nQ6NVQua9K9fKt9Fuetfd/TuZfm0d8ejvdr93o+Xy3dE62Rffuc2m9V0p4S8OqQxgSAUewAuAAUOwCWp9gBsDzFDoDlKXYALO8MOdMSOb4cxktCfg7CFoPb7RxaxDVEuY/K9oLr7bW2NLfe0Kg6bi9oWnS4OP3w7vEnns5rnn0hTLTr05o6p60CLU7etiWkBs2hOfPMzKT31D6Lcs0fCVsMXkyNh2dm3lPm0raEcl2fCOPPlsbg7dt/mrYTtUbQ18tcul/Kdb2VtsS8qbxOi9wn7T016b78cl6yaYtB+25saVTdvk8pw//aby/4w/KfHQDLU+wAWJ5iB8DyFDsAlqfYAbC8V7gR9IYz2tKHdGbmdENT1vlMnrr85O7x9p5u/0SY+Jt5TXu/Kbh4vaWzHipzKc3X0pPpuqZG3jOzX5p5n4RzPyjpztutmXFYd9gaYqe0XEu2lWbZ8Zq3xGq7L9P1a42gUxL402VNuMdnJicKy/Eul8bSt9LvR0kwH4bx47ykX6P0+bbPqR0vrXtdWVO+N1FLi6a0+Zam+TP5+94SnK89jaABYBQ7AC4AxQ6A5Sl2ACxPsQNgeYodAMs7w9aDFndPjVm/VNZsaZpcGqymWPFhWXLc3vq1MN5iymEfweUSab8VmjPPzMyjYbxdu9YkOr2nd+Ul+2H8pEXx31Hm0rVIzY9nZj5X5lrMO7kaxst2gMdKQ/Hn0xaWt5Rz+FSeuhI+jxslgn4Q7onbv5DXHH5vnjtOn0e797ZE5NvnXq551H6nkjeUufZ9SvfeJ8uad4bxth3gnn6iz3C85sFsBG3rAQCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgeWfYetBi/6kjduumv2XrQXEQxlN0fmbmVosVb+j2HzvCl2h/9XO7h/e/Py9pWwIeCefxYjuH959xfKY+TSJ+7iWuffU789y1Z8JE2f6wFyLtp8/lNfN4mQvrrpQ1N9rX7qNhvEXk3x7GP1TWtC0i6akRZavM0dN57voLaVE5h7TlpD0x4tvy1EGIz7evdH1yS3pP6bOY/JSTLQ9D4PfYegAAo9gBcAEodgAsT7EDYHmKHQDLO0Mas/1ZSDm1JORJSjumZOfMpoaoKYE1M3P7s2XhW3cP1/cUUoj/4G15zd8ux5uP7B6+/GRecuuZcrzUhLalJ1vj66Q1Z06fb2sI/IUyl5ottybMSWs4ndKJM7lB+S+VNSUJuV/SfMnJJ8JEi/ml+2EmpmOPSrL4+r8qx3tPGG/NnlNT5y335ExuVF2S4Sk9OTNzmj730vh9U6PlDb+9F4w0JgCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgeWfYepBiwDMxCnxYlhzfy6ueRXobLXrdtjmk9/tQXnIYIsfHHyivU5ocx4h1acY7pRlvfE8pkj0zE7ZNHJUl10MD65mZSU2s2/aHsnVjngnjLVa/pQl5u+bhul5+X15Sm5CnBurtO3g9jJeY/lFpVH09nV85h4OyPeP2hth/+k7vlbj9aWmEvh+2TbTm6bX5djr3rVsjElsP7sbWAwAYxQ6AC0CxA2B5ih0Ay1PsAFieYgfA8s6w9eBGmU0d5kssNj09ID4NYWYOylaB2ym6XmLrmzqat47+ITK+V7qgn7Y4eXjqwbu/Ny/5WDvem3YPH5bP6ThNvFBe56Uyl57YUO6vwxJpT/fRzfQUgJmZL+0evvJUXnKjHS/dl2WbSnqqxszMpO/AP8lLrv713ePX2ufUnq6Q3m+L4rf39FwYL9sf4j3RnkDRhO/0Qfl+3m5P40jXwnaAV5utBwAwih0AF4BiB8DyFDsAlqfYAbC8M6QxSypp741nf+XWn/k8pbTezMxJW9jSp0lKpbY325owp0azrYF1SHDWde/YsKY1bm4pv2d2Dx88lZfcLoeb1MS3pQavhfGreclRSfVeT/dK+2wfLXO/HsafKmvC534lpV9n5sYz5Xjp+pX05KbvWvv5Sd+bdv834bWulvTktfY7sDUVynmTxgSAUewAuAAUOwCWp9gBsDzFDoDlKXYALO8MWw9SY+SZ3Bz5PmiIWps9bznghi0Ytdnzm8/3HKa81pWwJeBG+2w/HcbbeZdm3jGuHZpoz0zdBhLfU7kO++HcT9p2iraVYUtD4PYZphuzbWVI214+VdYclblwjQ7LkuMyNx/ePbz/dF5ysqFxc9tOdJruy+tlUfsBaY2veTXZegAAo9gBcAEodgAsT7EDYHmKHQDLa1Gjr5ISlzMxddaOnuZSAuuuQkKrpjGfyXN7T4U17Xjp3FuKrl3XYL803j4pKcQbL4SJ9qa+effwu8uSW2Xu2jNh4tvKoo/mqRupQXO5j+LbLdfucmkEfetDYSIlJGd6ujPdE6kxeFvTmnyXVGNq9H1crkO75qnxdW3GHs6vNgZP9/jM7Ifk7kn7DrZrntKdWxtV80rynx0Ay1PsAFieYgfA8hQ7AJan2AGwPMUOgOWdoRH0K30q92B/w5qDMtcizCme3lL6aZvDaWv626RI9JfKmtKg+XIYv9UaN38yjL9U1rRYfYig1y0Y7fySFqvfosXq003Rmls/lKceC+f+fDuHsCZ95jMzt0pMPzaJbte1/ZRc2z28V7YybGrU3qTvYbv32vu19eB+oRE0AIxiB8AFoNgBsDzFDoDlKXYALE+xA2B599/Wg/qUgi0HbG+vRcND5PiwLDn+ud3jB99fFhW3PxEmwpMIZmbmuTKXuvB/Li85enz3+PXyMvMbZe5qGG9PhmjCEyCOSrf66+m10jaLmf5Uhi03Ztl6cBS+bNf/eTne02H8Tfd6Ql/9YmG8bG3ZtN0jPF1hZmbStoS2FaVtiWnnzoPM1gMAGMUOgAtAsQNgeYodAMtT7ABY3hnSmO3PzjGq2Zo9n7SFocnrYUjrzcwclwTgQUjz3f58OYctaa/WJPp6GG9pzNbcNzWQTinNmZn0fkNKc2ZqI9zYLLs0OX5vSfn9Skp+HuU1MQnZ0oQtLRrulUfKkhfb55Tu2XZ/pe9n+26WVOPD4TO8WQ537sJ7Oizv6fgVORHuc9KYADCKHQAXgGIHwPIUOwCWp9gBsDzFDoDl3X+NoJtNTaJLZPzh0iz4Zms2G+yHuPZJidVv0hoPl/cUtfNL2xVaDL7F6tO2hK0dwNPn1K5D2E7xcNnScfP9eW4/zJ20bSpty8nbN6x5XRhv59CuUWqonJozv5rae2r38lvP+0S4T9h6AACj2AFwASh2ACxPsQNgeYodAMtT7ABY3v331IOtDsJ4S62fbHnqQbsO6cWu5yWHJcp9nCY+U86hRcNDZHuvbCM4vREmrpTXSU8imJl5VxhPrzNTo+b77QkQQdwS8Ia8Zq88yeE0bX8o12HvyXK8D+weP/jBvCbel2kLwcxcKU8ESU83OH0ur6lPwtgibbUo582FZOsBAIxiB8AFoNgBsDzFDoDlKXYALG+dNOak8yvntqX38GFZc5ya0KZmyjM1AXgQEoC3y+EOy9xxmmiNdUOD4aulifC11ow3NSy+npfslQa+LW0bpUbVqQHz3aRU70N5yX65L9N7Om0p13eG1ykp0pZGnpfDeLlX9koa87S9VjxgGH/9hmOxMmlMABjFDoALQLEDYHmKHQDLU+wAWJ5iB8DyWvj+AbNh+0OLrafG0tWndw9fKc2KW//j2Ny3bGU4LnH3+Q9h/NG8JDXEvvbhvGbv6TwXI+jlPZ1+Ns9N2JbQPr+TsMWgvk5plh1j+mV7Ro39p3XtZjkKr9Puh9RoeSZe17pNpQnX6OFyXU/C+HFqvD0zU7ZacKH5zw6A5Sl2ACxPsQNgeYodAMtT7ABY3kKNoF8lLeUXGzSXBNtBSaPF47UUXUkAJpdLgu1WSg2mhs4zPRGXrkVpiF2l1/pEWZNea8O1m5mZl8L4W8qado22JArTPVEix4fl3jtOydSjcg7n/TuQUpcSl/xBGkEDwCh2AFwAih0Ay1PsAFieYgfA8hQ7AJZn68GZbWhCu2m7QlnX1jT7YfykNRgOWw8OQjPlmZnbXy7HC1H4vRL7j82jZ/IWiBZPb1s3ks/lqYcf3z1+s13Xts1hyxaILVs6UgPrcg6HZclxmZv0+9F+O7as4SKy9QAARrED4AJQ7ABYnmIHwPIUOwCWp9gBsDxbD15raTvATG5Yf1q2P9QnGKSJ8tnuh8/2JC/pwrkflvM+fqEc79Ew/qWyJmxXOCjnULd7pOv30bLmyTLXtrckYevB3pW8pDwQAR4kth4AwCh2AFwAih0Ay1PsAFieYgfA8qQxl7Phc2qJ0JS63NrcOmrn3ZKVqWlyaow8E9OYtRn1Z/Lc5bftHr/VGkGXlGRMVr45LzkK49fKy8AipDEBYBQ7AC4AxQ6A5Sl2ACxPsQNgeYodAMuz9WA5Wz6nL5c1rz/7mr20Zkrz4XLeB+X+itscvpjX7L9x9/heXjK37+lr8lXHK+fdtm4cn/2lcvPo0twaFmHrAQCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgebYenNn9fh3u9/O7j7UHEbQHGGzRtjmcbtlGsGWNe4U12HoAAKPYAXABKHYALE+xA2B5ih0AyztDGjOlvWY0m4XztKHptPQkF5g0JgCMYgfABaDYAbA8xQ6A5Sl2ACxPsQNgea0d7fY/Bf4QbCOA8+Y/OwCWp9gBsDzFDoDlKXYALE+xA2B5ih0Ay1PsAFieYgfA8hQ7AJan2AGwPMUOgOUpdgAsT7EDYHmKHQDLU+wAWJ5iB8DyFDsAlqfYAbA8xQ6A5e3d+59eeuXOAgCqO2Xu7vXJf3YALE+xA2B5ih0Ay1PsAFieYgfA8s6QxvzihsN/Y5n7ug3H4/7RklH3g5bOut/PPZGIZhVbkpUvlzWvv+sr+s8OgOUpdgAsT7EDYHmKHQDLU+wAWJ5iB8DyLt25c+dBzWEDwD3xnx0Ay1PsAFieYgfA8hQ7AJan2AGwPMUOgOUpdgAsT7EDYHmKHQDL+z/bnJwCJp0DEQAAAABJRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1477,14 +2670,35 @@ } ], "source": [ + "def visualize(img):\n", + " _min = img.min()\n", + " _max = img.max()\n", + " normalized_img = (img - _min)/ (_max - _min)\n", + " return normalized_img\n", "\n", - "diff=inputimg.cpu()-current_img[0, 0].cpu().detach().numpy()\n", + "diff=abs(inputimg.cpu()-current_img[0, 0].cpu()).detach().numpy()\n", "plt.style.use(\"default\")\n", "plt.imshow(diff, cmap=\"jet\")\n", "plt.tight_layout()\n", "plt.axis(\"off\")\n", "plt.show()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0aff7cd-91a5-406d-81d6-69921f9dc141", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfec3184-a975-4f23-a054-3a327789b435", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py index c02f1003..466c7295 100644 --- a/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py +++ b/tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py @@ -14,7 +14,7 @@ # --- # %% [markdown] -# # Anomaly Detection with classifier guidance +# # Weakly Supervised Anomaly Detection with Classifier Guidance # # This tutorial illustrates how to use MONAI for training a 2D gradient-guided anomaly detection using DDIMs [1]. # @@ -47,45 +47,34 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -import shutil -import tempfile +import sys import time from typing import Dict -import os -import torch.nn as nn + import matplotlib.pyplot as plt import numpy as np import torch import torch.nn.functional as F from monai import transforms -from monai.apps import MedNISTDataset, DecathlonDataset +from monai.apps import DecathlonDataset from monai.config import print_config -from monai.data import CacheDataset, DataLoader +from monai.data import DataLoader from monai.utils import first, set_determinism from torch.cuda.amp import GradScaler, autocast from tqdm import tqdm -torch.multiprocessing.set_sharing_strategy('file_system') -import sys -sys.path.append('/home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/') -print('path', sys.path) from generative.inferers import DiffusionInferer +from generative.networks.nets.diffusion_model_unet import DiffusionModelEncoder, DiffusionModelUNet +from generative.networks.schedulers.ddim import DDIMScheduler +torch.multiprocessing.set_sharing_strategy("file_system") -# TODO: Add right import reference after deployed -from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet, DiffusionModelEncoder -from generative.networks.schedulers.ddpm import DDPMScheduler -from generative.networks.schedulers.ddim import DDIMScheduler -print_config() +sys.path.append("/home/juliawolleb/PycharmProjects/MONAI/GenerativeModels/") +print("path", sys.path) -train_classifier=False -train_diffusionmodel=False -def visualize(img): - _min = img.min() - _max = img.max() - normalized_img = (img - _min)/ (_max - _min) - return normalized_img + +print_config() # %% [markdown] @@ -93,45 +82,60 @@ def visualize(img): # %% jupyter={"outputs_hidden": false} directory = os.environ.get("MONAI_DATA_DIRECTORY") -#root_dir = tempfile.mkdtemp() if directory is None else directory -root_dir='/home/juliawolleb/PycharmProjects/MONAI/brats' #path to where the data is stored +# root_dir = tempfile.mkdtemp() if directory is None else directory +root_dir = "/home/juliawolleb/PycharmProjects/MONAI/brats" # path to where the data is stored # %% [markdown] # ## Set deterministic training for reproducibility # %% jupyter={"outputs_hidden": false} -set_determinism(36) +set_determinism(42) # %% [markdown] tags=[] -# ## Setup BRATS Dataset for 2D slices and training and validation dataloaders -# As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150 - -# %% jupyter={"outputs_hidden": false} +# ## Setup BRATS Dataset in 2D slices for training +# As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150. +# If we set `preprocessing_train=True`, we stack all slices into a tensor and save it as _total_train_slices.pt_. +# If we set `preprocessing_train=False`, we load the saved tensor. +# The corresponding labels are saved as _total_train_labels.pt._ +# %% [markdown] +# Here we use transforms to augment the training dataset, as usual: +# +# 1. `LoadImaged` loads the hands images from files. +# 1. `EnsureChannelFirstd` ensures the original data to construct "channel first" shape. +# 1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1]. +# 1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform. +# +# +# %% channel = 0 # 0 = Flair assert channel in [0, 1, 2, 3], "Choose a valid channel" train_transforms = transforms.Compose( [ - transforms.LoadImaged(keys=["image","label"]), - transforms.EnsureChannelFirstd(keys=["image","label"]), + transforms.LoadImaged(keys=["image", "label"]), + transforms.EnsureChannelFirstd(keys=["image", "label"]), transforms.Lambdad(keys=["image"], func=lambda x: x[channel, :, :, :]), transforms.AddChanneld(keys=["image"]), - transforms.EnsureTyped(keys=["image","label"]), - transforms.Orientationd(keys=["image","label"], axcodes="RAS"), + transforms.EnsureTyped(keys=["image", "label"]), + transforms.Orientationd(keys=["image", "label"], axcodes="RAS"), transforms.Spacingd( - keys=["image","label"], + keys=["image", "label"], pixdim=(3.0, 3.0, 2.0), mode=("bilinear", "nearest"), ), - transforms.CenterSpatialCropd(keys=["image","label"], roi_size=(64, 64, 64)), + transforms.CenterSpatialCropd(keys=["image", "label"], roi_size=(64, 64, 64)), transforms.ScaleIntensityRangePercentilesd(keys="image", lower=0, upper=99.5, b_min=0, b_max=1), transforms.CopyItemsd(keys=["label"], times=1, names=["slice_label"]), - transforms.Lambdad(keys=["slice_label"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()), + transforms.Lambdad( + keys=["slice_label"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0).float().squeeze() + ), ] ) -print('download training set') + +# %% jupyter={"outputs_hidden": false} + train_ds = DecathlonDataset( root_dir=root_dir, task="Task01_BrainTumour", @@ -142,44 +146,51 @@ def visualize(img): seed=0, transform=train_transforms, ) -print('len train data', len(train_ds)) +print("len train data", len(train_ds)) -def get_batched_2d_axial_slices(data : Dict): - images_3D = data['image'] - batched_2d_slices = torch.cat(images_3D.split(1, dim = -1)[10:-10], 0).squeeze(-1) # we cut the lowest and highest 10 slices, because we are interested in the middle part of the brain. - slice_label = data['slice_label'] - slice_label = torch.cat(slice_label.split(1, dim = -1)[10:-10],0).squeeze() + +def get_batched_2d_axial_slices(data: Dict): + images_3D = data["image"] + batched_2d_slices = torch.cat(images_3D.split(1, dim=-1)[10:-10], 0).squeeze( + -1 + ) # we cut the lowest and highest 10 slices, because we are interested in the middle part of the brain. + slice_label = data["slice_label"] + slice_label = torch.cat(slice_label.split(1, dim=-1)[10:-10], 0).squeeze() return batched_2d_slices, slice_label -preprocessing_train=False -if preprocessing_train == True: + +preprocessing_train = False + +if preprocessing_train is True: train_loader_3D = DataLoader(train_ds, batch_size=1, shuffle=True, num_workers=4) print(f'Image shape {train_ds[0]["image"].shape}') - data_2d_slices=[] + data_2d_slices = [] data_slice_label = [] check_data = first(train_loader_3D) for i, data in enumerate(train_loader_3D): b2d, slice_label2d = get_batched_2d_axial_slices(data) data_2d_slices.append(b2d) data_slice_label.append(slice_label2d) - total_train_slices=torch.cat(data_2d_slices,0) - total_train_labels=torch.cat(data_slice_label,0) + total_train_slices = torch.cat(data_2d_slices, 0) + total_train_labels = torch.cat(data_slice_label, 0) - torch.save(total_train_slices, 'total_train_slices.pt') - torch.save(total_train_labels, 'total_train_labels.pt') + torch.save(total_train_slices, "total_train_slices.pt") + torch.save(total_train_labels, "total_train_labels.pt") else: - total_train_slices=torch.load('total_train_slices.pt') - total_train_labels=torch.load('total_train_labels.pt') - print('total slices', total_train_slices.shape) - print('total lbaels', total_train_labels.shape) - + total_train_slices = torch.load("total_train_slices.pt") + total_train_labels = torch.load("total_train_labels.pt") + print("total slices", total_train_slices.shape) + print("total lbaels", total_train_labels.shape) # %% [markdown] tags=[] -# ## Setup BRATS Dataset for 2D slices validation dataloader -# As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150 +# ## Setup BRATS Dataset in 2D slices for validation +# As baseline, we use the load_2d_brats.ipynb written by Pedro in issue 150. +# If we set `preprocessing_val=True`, we stack all slices into a tensor and save it as _total_val_slices.pt_. +# If we set `preprocessing_val=False`, we load the saved tensor. +# The corresponding labels are saved as _total_val_labels.pt_. # %% val_ds = DecathlonDataset( @@ -194,44 +205,34 @@ def get_batched_2d_axial_slices(data : Dict): ) -preprocessing_val=False -if preprocessing_val == True: +preprocessing_val = False +if preprocessing_val is True: val_loader_3D = DataLoader(val_ds, batch_size=1, shuffle=True, num_workers=4) print(f'Image shape {val_ds[0]["image"].shape}') - print('len val data', len(val_ds)) - data_2d_slices_val=[] + print("len val data", len(val_ds)) + data_2d_slices_val = [] data_slice_label_val = [] for i, data in enumerate(val_loader_3D): b2d, slice_label2d = get_batched_2d_axial_slices(data) data_2d_slices_val.append(b2d) data_slice_label_val.append(slice_label2d) - total_val_slices=torch.cat(data_2d_slices_val,0) - total_val_labels=torch.cat(data_slice_label_val,0) - torch.save(total_val_slices, 'total_val_slices.pt') - torch.save(total_val_labels, 'total_val_labels.pt') + total_val_slices = torch.cat(data_2d_slices_val, 0) + total_val_labels = torch.cat(data_slice_label_val, 0) + torch.save(total_val_slices, "total_val_slices.pt") + torch.save(total_val_labels, "total_val_labels.pt") else: - total_val_slices=torch.load('total_val_slices.pt') - total_val_labels=torch.load('total_val_labels.pt') - print('total slices', total_val_slices.shape) - print('total lbaels', total_val_labels.shape) - + total_val_slices = torch.load("total_val_slices.pt") + total_val_labels = torch.load("total_val_labels.pt") + print("total slices", total_val_slices.shape) + print("total lbaels", total_val_labels.shape) -# %% [markdown] -# Here we use transforms to augment the training dataset, as usual: -# -# 1. `LoadImaged` loads the hands images from files. -# 1. `EnsureChannelFirstd` ensures the original data to construct "channel first" shape. -# 1. `ScaleIntensityRanged` extracts intensity range [0, 255] and scales to [0, 1]. -# 1. `RandAffined` efficiently performs rotate, scale, shear, translate, etc. together based on PyTorch affine transform. -# -# # %% [markdown] # ### Define network, scheduler, optimizer, and inferer # At this step, we instantiate the MONAI components to create a DDPM, the UNET, the noise scheduler, and the inferer used for training and sampling. We are using -# the original DDPM scheduler containing 1000 timesteps in its Markov chain, and a 2D UNET with attention mechanisms -# in the 3rd level, each with 1 attention head (`num_head_channels=64`). +# the deterministic DDIM scheduler containing 1000 timesteps, and a 2D UNET with attention mechanisms +# in the 3rd level (`num_head_channels=64`). # # %% jupyter={"outputs_hidden": false} @@ -246,7 +247,7 @@ def get_batched_2d_axial_slices(data : Dict): num_res_blocks=1, num_head_channels=64, with_conditioning=False, - # cross_attention_dim=1, + # cross_attention_dim=1, ) model.to(device) @@ -257,57 +258,60 @@ def get_batched_2d_axial_slices(data : Dict): optimizer = torch.optim.Adam(params=model.parameters(), lr=2.5e-5) inferer = DiffusionInferer(scheduler) + + # %% [markdown] tags=[] # ### Model training of the Diffusion Model -# Here, we are training our diffusion model for 75 epochs (training time: ~50 minutes). +# If we set `train_diffusionmodel=True`, we are training our diffusion model for 100 epochs, and save the model as _diffusion_model.pt_. +# If we set `train_diffusionmodel=False`, we load a pretrained model. # %% jupyter={"outputs_hidden": false} -n_epochs =75 -batch_size=32 +n_epochs = 100 +batch_size = 32 val_interval = 1 epoch_loss_list = [] val_epoch_loss_list = [] -train_diffusionmodel=False -if train_diffusionmodel==False: - model.load_state_dict(torch.load("model.pt", map_location={'cuda:0': 'cpu'})) + +train_diffusionmodel = False + +if train_diffusionmodel is False: + model.load_state_dict(torch.load("diffusion_model.pt", map_location={"cuda:0": "cpu"})) else: scaler = GradScaler() total_start = time.time() for epoch in range(n_epochs): model.train() epoch_loss = 0 - indexes = list(torch.randperm(total_train_slices.shape[0])) #shuffle training data new + indexes = list(torch.randperm(total_train_slices.shape[0])) # shuffle training data new data_train = total_train_slices[indexes] # shuffle the training data labels_train = total_train_labels[indexes] subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size)) subset_2D_val = zip(total_val_slices.split(1), total_val_labels.split(1)) # - progress_bar = tqdm(enumerate(subset_2D), total=len(indexes), ncols=10) + progress_bar = tqdm(enumerate(subset_2D), total=len(indexes) / batch_size) progress_bar.set_description(f"Epoch {epoch}") - for step, (a,b) in progress_bar: + for step, (a, b) in progress_bar: images = a.to(device) classes = b.to(device) optimizer.zero_grad(set_to_none=True) - timesteps = torch.randint(0, 1000, (len(images),)).to(device) + timesteps = torch.randint(0, 1000, (len(images),)).to(device) # pick a random time step t with autocast(enabled=True): # Generate random noise noise = torch.randn_like(images).to(device) # Get model prediction - noise_pred = inferer(inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps) #remove the class conditioning + noise_pred = inferer( + inputs=images, diffusion_model=model, noise=noise, timesteps=timesteps + ) # remove the class conditioning loss = F.mse_loss(noise_pred.float(), noise.float()) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() - if step%20==0: - print('step', step, loss) - epoch_loss += loss.item() - progress_bar.set_postfix( { "loss": epoch_loss / (step + 1), @@ -315,13 +319,12 @@ def get_batched_2d_axial_slices(data : Dict): ) epoch_loss_list.append(epoch_loss / (step + 1)) - if (epoch) % val_interval == 0: model.eval() val_epoch_loss = 0 progress_bar_val = tqdm(enumerate(subset_2D_val)) progress_bar.set_description(f"Epoch {epoch}") - for step, (a, b) in progress_bar_val: + for step, (a, b) in progress_bar_val: images = a.to(device) classes = b.to(device) @@ -341,7 +344,7 @@ def get_batched_2d_axial_slices(data : Dict): val_epoch_loss_list.append(val_epoch_loss / (step + 1)) total_time = time.time() - total_start - torch.save(model.state_dict(), "./diffusion_model.pt") #save the trained model + torch.save(model.state_dict(), "./diffusion_model.pt") # save the trained model print(f"train diffusion completed, total time: {total_time}.") @@ -363,41 +366,46 @@ def get_batched_2d_axial_slices(data : Dict): plt.show() - # %% [markdown] -# ### Model training of the Classification Model -# #First, we define the classification model. It follows the encoder architecture of the diffusion model, combined with linear layers for binary classification between healthy and diseased slices. -# #Here, we are training our binary classification model for 20 epochs. +# ## Define the Classification Model +# First, we define the classification model. It follows the encoder architecture of the diffusion model, combined with linear layers for binary classification between healthy and diseased slices. +# # %% - - classifier = DiffusionModelEncoder( spatial_dims=2, in_channels=1, out_channels=2, - num_channels=(32,64,128), + num_channels=(32, 64, 64), attention_levels=(False, True, True), num_res_blocks=1, num_head_channels=64, with_conditioning=False, ) classifier.to(device) -batch_size=32 +batch_size = 32 +# %% [markdown] +# ## Model training of the Classification Model +# If we set `train_classifier=True`, we are training our diffusion model for 100 epochs, and save the model as _classifier.pt_. +# If we set `train_classifier=False`, we load a pretrained model. + # %% -n_epochs = 20 +train_classifier = True + +n_epochs = 100 val_interval = 1 epoch_loss_list = [] val_epoch_loss_list = [] -optimizer_cls = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5) +optimizer_cls = torch.optim.Adam(params=classifier.parameters(), lr=2.5e-5) classifier.to(device) +weight = torch.tensor((3, 1)).float().to(device) # account for the class imbalance in the dataset + -train_classifier=False -if train_classifier==False: - classifier.load_state_dict(torch.load("./classifier.pt", map_location={'cuda:0': 'cpu'})) +if train_classifier is False: + classifier.load_state_dict(torch.load("./classifier.pt", map_location={"cuda:0": "cpu"})) else: scaler = GradScaler() @@ -409,13 +417,13 @@ def get_batched_2d_axial_slices(data : Dict): data_train = total_train_slices[indexes] # shuffle the training data labels_train = total_train_labels[indexes] subset_2D = zip(data_train.split(batch_size), labels_train.split(batch_size)) - progress_bar = tqdm(enumerate(subset_2D), total=len(indexes)/batch_size) + progress_bar = tqdm(enumerate(subset_2D), total=len(indexes) / batch_size) progress_bar.set_description(f"Epoch {epoch}") - for step, (a,b) in progress_bar: + for step, (a, b) in progress_bar: images = a.to(device) classes = b.to(device) - weight=torch.tensor((3,1)).float().to(device) #account for the class imbalance in the dataset + optimizer_cls.zero_grad(set_to_none=True) timesteps = torch.randint(0, 1000, (len(images),)).to(device) @@ -424,8 +432,8 @@ def get_batched_2d_axial_slices(data : Dict): noise = torch.randn_like(images).to(device) # Get model prediction - noisy_img=scheduler.add_noise(images,noise, timesteps ) #add t steps of noise to the input image - pred=classifier(noisy_img, timesteps) + noisy_img = scheduler.add_noise(images, noise, timesteps) # add t steps of noise to the input image + pred = classifier(noisy_img, timesteps) loss = F.cross_entropy(pred, classes.long(), weight=weight, reduction="mean") loss.backward() @@ -433,13 +441,12 @@ def get_batched_2d_axial_slices(data : Dict): epoch_loss += loss.item() progress_bar.set_postfix( - { - "loss": epoch_loss / (step + 1), - } - ) + { + "loss": epoch_loss / (step + 1), + } + ) epoch_loss_list.append(epoch_loss / (step + 1)) - print('final step train', step) - + print("final step train", step) if (epoch + 1) % val_interval == 0: classifier.eval() @@ -447,10 +454,12 @@ def get_batched_2d_axial_slices(data : Dict): subset_2D_val = zip(total_val_slices.split(batch_size), total_val_labels.split(batch_size)) # progress_bar_val = tqdm(enumerate(subset_2D_val)) progress_bar_val.set_description(f"Epoch {epoch}") - for step, (a,b) in progress_bar_val: + for step, (a, b) in progress_bar_val: images = a.to(device) classes = b.to(device) - timesteps = torch.randint(0, 1, (len(images),)).to(device) #check validation accuracy on the original images, i.e., do not add noise + timesteps = torch.randint(0, 1, (len(images),)).to( + device + ) # check validation accuracy on the original images, i.e., do not add noise with torch.no_grad(): with autocast(enabled=False): @@ -459,25 +468,23 @@ def get_batched_2d_axial_slices(data : Dict): val_loss = F.cross_entropy(pred, classes.long(), reduction="mean") val_epoch_loss += val_loss.item() - _, predicted = torch.max(pred, 1); + _, predicted = torch.max(pred, 1) progress_bar_val.set_postfix( { "val_loss": val_epoch_loss / (step + 1), } ) val_epoch_loss_list.append(val_epoch_loss / (step + 1)) - print('final step val', step) - total_time = time.time() - total_start print(f"train completed, total time: {total_time}.") torch.save(classifier.state_dict(), "./classifier.pt") - + ## Learning curves for the Classifier - + plt.style.use("seaborn-bright") plt.title("Learning Curves", fontsize=20) - print('epl', len(epoch_loss_list)) + print("epl", len(epoch_loss_list)) plt.plot(np.linspace(1, n_epochs, n_epochs), epoch_loss_list, color="C0", linewidth=2.0, label="Train") plt.plot( np.linspace(val_interval, n_epochs, int(n_epochs / val_interval)), @@ -493,15 +500,16 @@ def get_batched_2d_axial_slices(data : Dict): plt.legend(prop={"size": 14}) plt.show() # %% [markdown] -# ### For Image-to-Image Translation to a Healthy Subject, we pick a disesed subject of the validation set +# # Image-to-Image Translation to a Healthy Subject +# We pick a diseased subject of the validation set as input image. We want to translate it to its healthy reconstruction. # %% -inputimg = total_val_slices[27][0,...] # Pick an input slice to be transformed (100,20 -inputlabel= total_val_labels[27] # Check whether it is healthy or diseased +inputimg = total_val_slices[150][0, ...] # Pick an input slice of the validation set to be transformed +inputlabel = total_val_labels[150] # Check whether it is healthy or diseased -plt.figure("input"+str(inputlabel)) +plt.figure("input" + str(inputlabel)) plt.imshow(inputimg, vmin=0, vmax=1, cmap="gray") plt.axis("off") plt.tight_layout() @@ -512,18 +520,19 @@ def get_batched_2d_axial_slices(data : Dict): # %% [markdown] # ### Encoding the input image in noise with the reversed DDIM sampling scheme -# In order to sample using gradient guidance, we first need to encode the input image in noise by using the reversed DDIM sampling scheme. -# We define the number of steps in the noising and denoising process by L. +# In order to sample using gradient guidance, we first need to encode the input image in noise by using the reversed DDIM sampling scheme.\ +# We define the number of steps in the noising and denoising process by L.\ +# The encoding process is presented in Equation of the paper "Diffusion Models for Medical Anomaly Detection" (https://arxiv.org/pdf/2203.04306.pdf). # # %% jupyter={"outputs_hidden": false} -L=180 -current_img = inputimg[None,None,...].to(device) +L = 200 +current_img = inputimg[None, None, ...].to(device) scheduler.set_timesteps(num_inference_steps=1000) -progress_bar = tqdm(range(L)) #go back and forth L timesteps -for t in progress_bar: #go through the noising process +progress_bar = tqdm(range(L)) # go back and forth L timesteps +for t in progress_bar: # go through the noising process with autocast(enabled=False): with torch.no_grad(): @@ -537,24 +546,27 @@ def get_batched_2d_axial_slices(data : Dict): plt.show() - # %% [markdown] -# ### Denoising Process using gradient guidance +# ### Denoising Process using Gradient Guidance # From the noisy image, we apply DDIM sampling scheme for denoising for L steps. -# Additionally, we apply gradient guidance using the classifier network towards the desired class label y=0 (healthy). The scale s is used to amplify the gradient. +# Additionally, we apply gradient guidance using the classifier network towards the desired class label y=0 (healthy). This is presented in Algorithm 2 of https://arxiv.org/pdf/2105.05233.pdf, and in Algorithm 1 of https://arxiv.org/pdf/2203.04306.pdf. \ +# The scale s is used to amplify the gradient. # %% -y=torch.tensor(0) #define the desired class label -scale=1 #define the desired gradient scale s -progress_bar = tqdm(range(L)) #go back and forth L timesteps +y = torch.tensor(0) # define the desired class label +scale = 5 # define the desired gradient scale s +progress_bar = tqdm(range(L)) # go back and forth L timesteps -for i in progress_bar: #go through the denoising process +for i in progress_bar: # go through the denoising process - t=L-i + t = L - i with autocast(enabled=True): - model_output = model(current_img, timesteps=torch.Tensor((t,)).to(current_img.device)) # this is supposed to be epsilon + with torch.no_grad(): + model_output = model( + current_img, timesteps=torch.Tensor((t,)).to(current_img.device) + ).detach() # this is supposed to be epsilon with torch.enable_grad(): x_in = current_img.detach().requires_grad_(True) @@ -563,7 +575,9 @@ def get_batched_2d_axial_slices(data : Dict): selected = log_probs[range(len(logits)), y.view(-1)] a = torch.autograd.grad(selected.sum(), x_in)[0] alpha_prod_t = scheduler.alphas_cumprod[t] - updated_noise = model_output- (1 - alpha_prod_t).sqrt() * scale*a #update the predicted noise epsilon with the gradient of the classifier + updated_noise = ( + model_output - (1 - alpha_prod_t).sqrt() * scale * a + ) # update the predicted noise epsilon with the gradient of the classifier current_img, _ = scheduler.step(updated_noise, t, current_img) torch.cuda.empty_cache() @@ -576,14 +590,24 @@ def get_batched_2d_axial_slices(data : Dict): # %% [markdown] -# ### Anomaly Detection +# # Anomaly Detection # To get the anomaly map, we compute the difference between the input image the output of our image-to-image translation model, which is the healthy reconstruction. # %% +def visualize(img): + _min = img.min() + _max = img.max() + normalized_img = (img - _min) / (_max - _min) + return normalized_img -diff=abs(inputimg.cpu()-current_img[0, 0].cpu()).detach().numpy() + +diff = abs(inputimg.cpu() - current_img[0, 0].cpu()).detach().numpy() plt.style.use("default") plt.imshow(diff, cmap="jet") plt.tight_layout() plt.axis("off") plt.show() + +# %% + +# %% diff --git a/tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb deleted file mode 100644 index 3f9e39a8..00000000 --- a/tutorials/generative/classifier_guidance_anomalydetection/Untitled.ipynb +++ /dev/null @@ -1,33 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "f37e04b7-9695-4a24-85bb-fffdd87ee1b9", - "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.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb deleted file mode 100644 index 363fcab7..00000000 --- a/tutorials/generative/classifier_guidance_anomalydetection/Untitled1.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb b/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb deleted file mode 100644 index 06ad686a..00000000 --- a/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.ipynb +++ /dev/null @@ -1,437 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "cf6673e1", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "# # Diff-SCM\n", - "# \n", - "# This tutorial illustrates how to load the 2D BRATS dataset.\n", - "# \n", - "# \n", - "# ## Setup environment" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "2dc388db", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "done\n" - ] - } - ], - "source": [ - "\n", - "\n", - "get_ipython().system('python -c \"import monai\" || pip install -q \"monai-weekly[pillow, tqdm, einops]\"')\n", - "get_ipython().system('python -c \"import matplotlib\" || pip install -q matplotlib')\n", - "get_ipython().run_line_magic('matplotlib', 'inline')\n", - "print('done')\n", - "\n", - "\n", - "# ## Setup imports" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "4167c04e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MONAI version: 1.1.dev2248\n", - "Numpy version: 1.23.2\n", - "Pytorch version: 1.12.1\n", - "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", - "MONAI rev id: 3400bd91422ccba9ccc3aa2ffe7fecd4eb5596bf\n", - "MONAI __file__: /home/juliawolleb/anaconda3/envs/experiment/lib/python3.10/site-packages/monai/__init__.py\n", - "\n", - "Optional dependencies:\n", - "Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.\n", - "Nibabel version: 4.0.1\n", - "scikit-image version: 0.19.3\n", - "Pillow version: 9.2.0\n", - "Tensorboard version: NOT INSTALLED or UNKNOWN VERSION.\n", - "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n", - "TorchVision version: 0.13.1\n", - "tqdm version: 4.64.1\n", - "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n", - "psutil version: 5.9.4\n", - "pandas version: NOT INSTALLED or UNKNOWN VERSION.\n", - "einops version: 0.6.0\n", - "transformers version: NOT INSTALLED or UNKNOWN VERSION.\n", - "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n", - "pynrrd version: NOT INSTALLED or UNKNOWN VERSION.\n", - "\n", - "For details about installing the optional dependencies, please visit:\n", - " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", - "\n" - ] - } - ], - "source": [ - "\n", - "\n", - "# Copyright 2020 MONAI Consortium\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "import os\n", - "import shutil\n", - "import tempfile\n", - "import time\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import torch\n", - "import torch.nn.functional as F\n", - "from monai import transforms\n", - "from monai.apps import DecathlonDataset\n", - "from monai.config import print_config\n", - "from monai.data import CacheDataset, DataLoader\n", - "from monai.utils import first, set_determinism\n", - "from torch.cuda.amp import GradScaler, autocast\n", - "from tqdm import tqdm\n", - "\n", - "from generative.inferers import DiffusionInferer\n", - "\n", - "# TODO: Add right import reference after deployed\n", - "from generative.networks.nets import DiffusionModelUNet\n", - "from generative.networks.schedulers import DDPMScheduler\n", - "\n", - "print_config()\n", - "\n", - "\n", - "# ## Setup data directory" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "86b390cd", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/tmp/tmpf7ygl4zq\n" - ] - } - ], - "source": [ - "\n", - "\n", - "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", - "root_dir = tempfile.mkdtemp() if directory is None else directory\n", - "print(root_dir)\n", - "root_dir= '/tmp/tmp6o69ziv1'\n", - "\n", - "\n", - "# ## Set deterministic training for reproducibility" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "6d644892", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "\n", - "set_determinism(42)\n", - "\n", - "\n", - "# ## Setup MedNIST Dataset and training and validation dataloaders\n", - "# In this tutorial, we will train our models on the MedNIST dataset available on MONAI\n", - "# (https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset).\n", - "# Here, we will use the \"Hand\" and \"HeadCT\", where our conditioning variable `class` will specify the modality." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "5c29c6a2", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-01-20 09:47:29,125 - INFO - Verified 'Task01_BrainTumour.tar', md5: 240a19d752f0d9e9101544901065d872.\n", - "2023-01-20 09:47:29,126 - INFO - File exists: /tmp/tmp6o69ziv1/Task01_BrainTumour.tar, skipped downloading.\n", - "2023-01-20 09:47:29,127 - INFO - Non-empty folder exists in /tmp/tmp6o69ziv1/Task01_BrainTumour, skipped extracting.\n", - "Image shape torch.Size([1, 64, 64, 64])\n" - ] - } - ], - "source": [ - "\n", - "\n", - "batch_size = 2\n", - "channel = 0 # 0 = Flair\n", - "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", - "\n", - "train_transforms = transforms.Compose(\n", - " [\n", - " transforms.LoadImaged(keys=[\"image\",\"label\"]),\n", - " transforms.EnsureChannelFirstd(keys=[\"image\",\"label\"]),\n", - " transforms.Lambdad(keys=[\"image\"], func=lambda x: x[channel, :, :, :]),\n", - " transforms.AddChanneld(keys=[\"image\"]),\n", - " transforms.EnsureTyped(keys=[\"image\",\"label\"]),\n", - " transforms.Orientationd(keys=[\"image\",\"label\"], axcodes=\"RAS\"),\n", - " transforms.Spacingd(\n", - " keys=[\"image\",\"label\"],\n", - " pixdim=(3.0, 3.0, 2.0),\n", - " mode=(\"bilinear\", \"nearest\"),\n", - " ),\n", - " transforms.CenterSpatialCropd(keys=[\"image\",\"label\"], roi_size=(64, 64, 64)),\n", - " transforms.ScaleIntensityRangePercentilesd(keys=\"image\", lower=0, upper=99.5, b_min=0, b_max=1),\n", - " transforms.CopyItemsd(keys=[\"label\"], times=1, names=[\"slice_label\"]),\n", - " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", - " ]\n", - ")\n", - "train_ds = DecathlonDataset(\n", - " root_dir=root_dir,\n", - " task=\"Task01_BrainTumour\",\n", - " section=\"training\", # validation\n", - " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", - " num_workers=4,\n", - " download=True, # Set download to True if the dataset hasnt been downloaded yet\n", - " seed=0,\n", - " transform=train_transforms,\n", - ")\n", - "nb_3D_images_to_mix = 2\n", - "train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4)\n", - "print(f'Image shape {train_ds[0][\"image\"].shape}')" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "16e750a6", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "\n", - "from typing import Dict\n", - "def get_batched_2d_axial_slices(data : Dict):\n", - " images_3D = data['image']\n", - " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1])\n", - " slice_label = data['slice_label']\n", - " #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float()\n", - " slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze()\n", - " return batched_2d_slices, slice_label\n", - "\n", - "\n", - "# ### Visualisation of the training images" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "310b925c", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "check_data torch.Size([2, 1, 64, 64, 64]) torch.Size([2, 64])\n" - ] - } - ], - "source": [ - "\n", - "\n", - "check_data = first(train_loader_3D)\n", - "print('check_data', check_data[\"image\"].shape, check_data[\"slice_label\"].shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "4105a01f", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "idx [tensor(125), tensor(70), tensor(71), tensor(10), tensor(112), tensor(72), tensor(100), tensor(108), tensor(48), tensor(90), tensor(5), tensor(83), tensor(53), tensor(38), tensor(121), tensor(115), tensor(116), tensor(75), tensor(34), tensor(2), tensor(118), tensor(46), tensor(57), tensor(64), tensor(107), tensor(126), tensor(109), tensor(98), tensor(15), tensor(13), tensor(113), tensor(93), tensor(106), tensor(73), tensor(4), tensor(102), tensor(21), tensor(96), tensor(30), tensor(18), tensor(91), tensor(16), tensor(77), tensor(49), tensor(50), tensor(123), tensor(28), tensor(42), tensor(23), tensor(6), tensor(12), tensor(65), tensor(31), tensor(41), tensor(3), tensor(101), tensor(67), tensor(54), tensor(62), tensor(120), tensor(94), tensor(80), tensor(35), tensor(9), tensor(82), tensor(84), tensor(14), tensor(32), tensor(127), tensor(59), tensor(25), tensor(63), tensor(87), tensor(92), tensor(40), tensor(97), tensor(51), tensor(7), tensor(105), tensor(19), tensor(88), tensor(36), tensor(20), tensor(110), tensor(29), tensor(111), tensor(60), tensor(44), tensor(45), tensor(52), tensor(68), tensor(124), tensor(37), tensor(117), tensor(85), tensor(17), tensor(95), tensor(55), tensor(0), tensor(56), tensor(86), tensor(58), tensor(47), tensor(89), tensor(122), tensor(22), tensor(78), tensor(79), tensor(11), tensor(61), tensor(119), tensor(27), tensor(114), tensor(103), tensor(43), tensor(99), tensor(24), tensor(8), tensor(81), tensor(33), tensor(104), tensor(26), tensor(66), tensor(39), tensor(69), tensor(76), tensor(74), tensor(1)] 128\n", - "Batch shape: torch.Size([128, 1, 64, 64])\n", - "Slices class: tensor([0., 1., 0., 0.])\n" - ] - }, - { - "data": { - "image/png": "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0DkAAAAA05KlFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNWUoBAAAA0JylFAAAAADNzZvq4OTk5Nk8DwAAAADOI74pBQAAAEBzllIAAAAANGcpBQAAAEBzllIAAAAANGcpBQAAAEBzllIAAAAANGcpBQAAAEBzllIAAAAANGcpBQAAAEBz/w/KFbC7XwVz4QAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "\n", - "batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data)\n", - "idx = list(torch.randperm(batched_2d_slices.shape[0]))\n", - "print('idx', idx, len(idx))\n", - "slices = [0,30,45,63]\n", - "print(f\"Batch shape: {batched_2d_slices.shape}\")\n", - "print(f\"Slices class: {slice_label[idx][slices].view(-1)}\")\n", - "image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze()\n", - "plt.figure(\"training images\", (12, 6))\n", - "plt.imshow(image_visualisation, vmin=0, vmax=1, cmap=\"gray\")\n", - "plt.axis(\"off\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "21e0c944", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([128])" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "\n", - "slice_label.shape\n", - "\n", - "\n", - "# ## Check Distribution of Healthy / Unhealthy" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "1114650d", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(torch.Size([2, 1, 64, 64]), torch.Size([2]))" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))#\n", - "a,b = next(subset_2D) #what is a, what is b?\n", - "a.shape, b.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "5633a8c8", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAHDCAYAAACESXgYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA9mElEQVR4nO3de5yN5f7/8fcyhzUHMxODOWSMoSHFRAgjezBm2kjbRiclkk50kG0TkqVvjd1UUom9dcCuSAdKXyWDTGSqITrQg9rOmyGSmRxGzPX7w2/W1zIHs8bMpaXX8/G4Hw/rWte678+61j1rvd1HhzHGCAAAwJIa57sAAADwx0L4AAAAVhE+AACAVYQPAABgFeEDAABYRfgAAABWET4AAIBVhA8AAGAV4QMAAFhF+DgPZs2aJYfD4Z6CgoIUHR2tLl26aNKkSdq3b1+J17hcLjkcDq+Wc+TIEblcLq1YscKr15W2rIYNG+raa6/1aj5nM2fOHE2ZMqXU5xwOh1wuV5Uur6otW7ZMbdq0UWhoqBwOh957771Kz6t4ndi2bZu7bdCgQWrYsOE51/l70bBhQw0aNMj9ePfu3XK5XFq/fn2JvoMGDVLNmjXtFVdJ27Ztk8Ph0KxZs6pl/qWtAxkZGaWua8Xr0Jo1a6qllvKU91mWZuPGjXK5XB7rO/5YCB/n0cyZM5WTk6OsrCy9+OKLatmypZ588kk1a9ZMS5cu9eg7ZMgQ5eTkeDX/I0eOaOLEiV6Hj8osqzLKCx85OTkaMmRItddQWcYY3XDDDQoICNDChQuVk5OjlJSUKl3G+PHjtWDBgiqd5/m0YMECjR8/3v149+7dmjhxYoV/sP6ISlsHygof55O3n+XGjRs1ceJEwscfmP/5LuCPrHnz5mrTpo37cd++ffXQQw/p6quvVp8+ffTDDz8oKipKklS/fn3Vr1+/Wus5cuSIQkJCrCzrbNq3b39el382u3fv1s8//6y//vWvSk1NrZZlNG7cuFrme760atXqfJfgcy60deCP6OjRowoKCvJ6y/WFji0fvzMNGjTQM888o4KCAv3rX/9yt5e2K2T58uXq3LmzIiMjFRwcrAYNGqhv3746cuSItm3bprp160qSJk6c6N7FU7zZu3h+X331lfr166datWq5v+jK28WzYMECJSUlKSgoSI0aNdLzzz/v8Xxpuw8kacWKFXI4HO6tMJ07d9aiRYu0fft2j11QxUrb7fLdd9/pL3/5i2rVqqWgoCC1bNlSs2fPLnU5c+fO1bhx4xQbG6vw8HB169ZNmzZtKnvgT7Nq1SqlpqYqLCxMISEhSk5O1qJFi9zPu1wudzgbPXq0HA5HubtHioqK9Pjjj6tp06YKDg7WRRddpKSkJD333HPl1lHaJveioiK98MILatmypXte7du318KFCz36zZs3Tx06dFBoaKhq1qypa665RuvWrfPos2XLFt10002KjY2V0+lUVFSUUlNTy/3f66JFi+RwOJSbm+tue/fdd+VwONSzZ0+PvklJSerbt6/78em7XVasWKG2bdtKkm6//Xb353/mZ/7jjz+qR48eqlmzpuLi4vS3v/1NhYWF5Q2b+/2np6crJiZGwcHBatasmR5++GEdPnzYo1/x7p2KLGf37t264YYbFBYWpoiICN14443Ky8s7ay35+fny9/fXU0895W7bv3+/atSooYiICJ04ccLd/sADD6hu3boqvt/nmeuAw+HQ4cOHNXv2bPeYde7c2WN5BQUFuvfee1WnTh1FRkaqT58+2r17t0efoqIiZWZm6tJLL5XT6VS9evV02223adeuXR79ztxVVqxz587u5Vb0syw2a9YsXX/99ZKkLl26uPsX77qqyDKLl+twODRnzhyNHj1aMTExqlmzpnr16qW9e/eqoKBAd911l+rUqaM6dero9ttv16+//uoxz2PHjmnMmDFKSEhQYGCgLr74Yg0bNky//PKLR7+y3s+ZtRZ//y1ZskSDBw9W3bp1FRISUqF19o+G8PE71KNHD/n5+enTTz8ts8+2bdvUs2dPBQYG6tVXX9XixYv1j3/8Q6GhoTp+/LhiYmK0ePFiSdIdd9yhnJwc5eTkeGz2lqQ+ffrokksu0dtvv61//vOf5da1fv16DR8+XA899JAWLFig5ORkPfjgg3r66ae9fo/Tpk1Tx44dFR0d7a6tvF09mzZtUnJysjZs2KDnn39e8+fP12WXXaZBgwYpMzOzRP+xY8dq+/btevnllzVjxgz98MMP6tWrl06ePFluXdnZ2eratasOHTqkV155RXPnzlVYWJh69eqlefPmSTq1W2r+/PmSpPvvv185OTnl7h7JzMyUy+XSzTffrEWLFmnevHm64447SnzBVcSgQYP04IMPqm3btpo3b57efPNNXXfddR5hLyMjQzfffLMuu+wyvfXWW3rttddUUFCgTp06aePGje5+PXr00Nq1a5WZmamsrCxNnz5drVq1KreulJQUBQQEeOwWXLp0qYKDg5Wdna3ffvtNkrRv3z5999136tatW6nzufLKKzVz5kxJ0iOPPOL+/E/f1fbbb7/puuuuU2pqqt5//30NHjxYzz77rJ588smzjtMPP/ygHj166JVXXtHixYs1fPhwvfXWW+rVq1eJvhVZztGjR9WtWzctWbJEkyZN0ttvv63o6GjdeOONZ60lPDxcbdu29RizZcuWyel0qqCgQF9++aW7fenSperatWuZ4T8nJ0fBwcHq0aOHe8ymTZvm0WfIkCEKCAjQnDlzlJmZqRUrVujWW2/16HPvvfdq9OjRSktL08KFC/U///M/Wrx4sZKTk7V///6zvqfTVeSzPF3Pnj2VkZEhSXrxxRfd/c8MrxU1duxY7du3T7NmzdIzzzyjFStW6Oabb1bfvn0VERGhuXPnatSoUXrttdc0duxY9+uMMerdu7eefvppDRgwQIsWLdKIESM0e/Zsde3a9ZwCw+DBgxUQEKDXXntN77zzjgICAio9rwuWgXUzZ840kkxubm6ZfaKiokyzZs3cjydMmGBO/7jeeecdI8msX7++zHn89NNPRpKZMGFCieeK5/foo4+W+dzp4uPjjcPhKLG8tLQ0Ex4ebg4fPuzx3rZu3erR75NPPjGSzCeffOJu69mzp4mPjy+19jPrvummm4zT6TQ7duzw6Ne9e3cTEhJifvnlF4/l9OjRw6PfW2+9ZSSZnJycUpdXrH379qZevXqmoKDA3XbixAnTvHlzU79+fVNUVGSMMWbr1q1GknnqqafKnZ8xxlx77bWmZcuW5fYpbdwGDhzoMT6ffvqpkWTGjRtX5nx27Nhh/P39zf333+/RXlBQYKKjo80NN9xgjDFm//79RpKZMmXKWes/09VXX226du3qfnzJJZeYv//976ZGjRomOzvbGGPMG2+8YSSZzZs3u/vFx8ebgQMHuh/n5uYaSWbmzJklljFw4EAjybz11lse7T169DBNmzb1qt6ioiLz22+/mezsbCPJfP31114vZ/r06UaSef/99z363XnnnWW+h9M98sgjJjg42Bw7dswYY8yQIUPMn//8Z5OUlGQmTpxojDHmv//9r5FkZsyY4VHfmX8joaGhHuNY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KsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsIrwAQAArCJ8AAAAqwgfAADAqmoLH9OmTVNCQoKCgoLUunVrrVy5sroWBQAAfEi1hI958+Zp+PDhGjdunNatW6dOnTqpe/fu2rFjR3UsDgAA+JBqCR+TJ0/WHXfcoSFDhqhZs2aaMmWK4uLiNH369OpYHAAA8CH+VT3D48ePa+3atXr44Yc92tPT07V69eoS/QsLC1VYWOh+fOjQIUlSfn5+VZcmSSoqPFIt84Wn6vr8AFzY+I62ozq+o4vnaYw5a98qDx/79+/XyZMnFRUV5dEeFRWlvLy8Ev0nTZqkiRMnlmiPi4ur6tJgUcSU810BAKAs1fkdXVBQoIiIiHL7VHn4KOZwODweG2NKtEnSmDFjNGLECPfjoqIi/fzzz4qMjCy1/7nIz89XXFycdu7cqfDw8CqdN/4P42wH42wPY20H42xHdY2zMUYFBQWKjY09a98qDx916tSRn59fia0c+/btK7E1RJKcTqecTqdH20UXXVTVZXkIDw9nxbaAcbaDcbaHsbaDcbajOsb5bFs8ilX5AaeBgYFq3bq1srKyPNqzsrKUnJxc1YsDAAA+plp2u4wYMUIDBgxQmzZt1KFDB82YMUM7duzQPffcUx2LAwAAPqRawseNN96oAwcO6LHHHtOePXvUvHlzffjhh4qPj6+OxVWY0+nUhAkTSuzmQdVinO1gnO1hrO1gnO34PYyzw1TknBgAAIAqwr1dAACAVYQPAABgFeEDAABYRfgAAABWXXDhY9q0aUpISFBQUJBat26tlStXlts/OztbrVu3VlBQkBo1aqR//vOflir1bd6M8/z585WWlqa6desqPDxcHTp00Mcff2yxWt/l7fpc7LPPPpO/v79atmxZvQVeILwd58LCQo0bN07x8fFyOp1q3LixXn31VUvV+jZvx/qNN97QFVdcoZCQEMXExOj222/XgQMHLFXrez799FP16tVLsbGxcjgceu+99876mvPyO2guIG+++aYJCAgwL730ktm4caN58MEHTWhoqNm+fXup/bds2WJCQkLMgw8+aDZu3GheeuklExAQYN555x3LlfsWb8f5wQcfNE8++aT58ssvzebNm82YMWNMQECA+eqrryxX7lu8Hediv/zyi2nUqJFJT083V1xxhZ1ifVhlxvm6664z7dq1M1lZWWbr1q3miy++MJ999pnFqn2Tt2O9cuVKU6NGDfPcc8+ZLVu2mJUrV5rLL7/c9O7d23LlvuPDDz8048aNM++++66RZBYsWFBu//P1O3hBhY+rrrrK3HPPPR5tl156qXn44YdL7T9q1Chz6aWXerTdfffdpn379tVW44XA23EuzWWXXWYmTpxY1aVdUCo7zjfeeKN55JFHzIQJEwgfFeDtOH/00UcmIiLCHDhwwEZ5FxRvx/qpp54yjRo18mh7/vnnTf369autxgtJRcLH+fodvGB2uxw/flxr165Venq6R3t6erpWr15d6mtycnJK9L/mmmu0Zs0a/fbbb9VWqy+rzDifqaioSAUFBapdu3Z1lHhBqOw4z5w5U//5z380YcKE6i7xglCZcV64cKHatGmjzMxMXXzxxWrSpIlGjhypo0eP2ijZZ1VmrJOTk7Vr1y59+OGHMsZo7969euedd9SzZ08bJf8hnK/fwWq7q61t+/fv18mTJ0vcvC4qKqrETe6K5eXlldr/xIkT2r9/v2JiYqqtXl9VmXE+0zPPPKPDhw/rhhtuqI4SLwiVGecffvhBDz/8sFauXCl//wvmT7taVWact2zZolWrVikoKEgLFizQ/v37NXToUP38888c91GOyox1cnKy3njjDd144406duyYTpw4oeuuu04vvPCCjZL/EM7X7+AFs+WjmMPh8HhsjCnRdrb+pbXDk7fjXGzu3LlyuVyaN2+e6tWrV13lXTAqOs4nT55U//79NXHiRDVp0sRWeRcMb9bnoqIiORwOvfHGG7rqqqvUo0cPTZ48WbNmzWLrRwV4M9YbN27UAw88oEcffVRr167V4sWLtXXrVu4TVsXOx+/gBfPfozp16sjPz69Egt63b1+JVFcsOjq61P7+/v6KjIystlp9WWXGudi8efN0xx136O2331a3bt2qs0yf5+04FxQUaM2aNVq3bp3uu+8+Sad+JI0x8vf315IlS9S1a1crtfuSyqzPMTExuvjiiz1uHd6sWTMZY7Rr1y4lJiZWa82+qjJjPWnSJHXs2FF///vfJUlJSUkKDQ1Vp06d9Pjjj7N1ugqcr9/BC2bLR2BgoFq3bq2srCyP9qysLCUnJ5f6mg4dOpTov2TJErVp00YBAQHVVqsvq8w4S6e2eAwaNEhz5sxhf20FeDvO4eHh+vbbb7V+/Xr3dM8996hp06Zav3692rVrZ6t0n1KZ9bljx47avXu3fv31V3fb5s2bVaNGDdWvX79a6/VllRnrI0eOqEYNz58pPz8/Sf/3v3Ocm/P2O1ith7NaVnwa1yuvvGI2btxohg8fbkJDQ822bduMMcY8/PDDZsCAAe7+xacYPfTQQ2bjxo3mlVde4VTbCvB2nOfMmWP8/f3Niy++aPbs2eOefvnll/P1FnyCt+N8Js52qRhvx7mgoMDUr1/f9OvXz2zYsMFkZ2ebxMREM2TIkPP1FnyGt2M9c+ZM4+/vb6ZNm2b+85//mFWrVpk2bdqYq6666ny9hd+9goICs27dOrNu3TojyUyePNmsW7fOfTrz7+V38IIKH8YY8+KLL5r4+HgTGBhorrzySpOdne1+buDAgSYlJcWj/4oVK0yrVq1MYGCgadiwoZk+fbrlin2TN+OckpJiJJWYBg4caL9wH+Pt+nw6wkfFeTvO33//venWrZsJDg429evXNyNGjDBHjhyxXLVv8nasn3/+eXPZZZeZ4OBgExMTY2655Raza9cuy1X7jk8++aTc79vfy++gwxi2XQEAAHsumGM+AACAbyB8AAAAqwgfAADAKsIHAACwivABAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKsIHwAAwCrCBwAAsOr/AejmQumx7IhfAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "\n", - "plt.hist(slice_label.view(-1).numpy(),bins = 5);\n", - "plt.title(\"Distribution of slices with and without tumour \\n 0 = no tumour, 1 = tumour\");" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "97cbfc78-54b5-4a98-b0b3-60e3a71fd25e", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "jupytext": { - "formats": "py:percent,ipynb" - }, - "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.5" - }, - "vscode": { - "interpreter": { - "hash": "a7e6f8385898884a13cbe220eefefb32cba5012927a94186742ddc14746e4dba" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py b/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py deleted file mode 100644 index db954dba..00000000 --- a/tutorials/generative/classifier_guidance_anomalydetection/load_2d_brats.py +++ /dev/null @@ -1,201 +0,0 @@ -# --- -# jupyter: -# jupytext: -# formats: py:percent,ipynb -# text_representation: -# extension: .py -# format_name: percent -# format_version: '1.3' -# jupytext_version: 1.14.4 -# kernelspec: -# display_name: Python 3 (ipykernel) -# language: python -# name: python3 -# --- - -# %% - -# # Diff-SCM -# -# This tutorial illustrates how to load the 2D BRATS dataset. -# -# -# ## Setup environment - -# %% - - -get_ipython().system('python -c "import monai" || pip install -q "monai-weekly[pillow, tqdm, einops]"') -get_ipython().system('python -c "import matplotlib" || pip install -q matplotlib') -get_ipython().run_line_magic('matplotlib', 'inline') -print('done') - - -# ## Setup imports - -# %% - - -# Copyright 2020 MONAI Consortium -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import os -import shutil -import tempfile -import time - -import matplotlib.pyplot as plt -import numpy as np -import torch -import torch.nn.functional as F -from monai import transforms -from monai.apps import DecathlonDataset -from monai.config import print_config -from monai.data import CacheDataset, DataLoader -from monai.utils import first, set_determinism -from torch.cuda.amp import GradScaler, autocast -from tqdm import tqdm - -from generative.inferers import DiffusionInferer - -# TODO: Add right import reference after deployed -from generative.networks.nets import DiffusionModelUNet -from generative.networks.schedulers import DDPMScheduler - -print_config() - - -# ## Setup data directory - -# %% - - -directory = os.environ.get("MONAI_DATA_DIRECTORY") -root_dir = tempfile.mkdtemp() if directory is None else directory -print(root_dir) -root_dir= '/tmp/tmp6o69ziv1' - - -# ## Set deterministic training for reproducibility - -# %% - - -set_determinism(42) - - -# ## Setup MedNIST Dataset and training and validation dataloaders -# In this tutorial, we will train our models on the MedNIST dataset available on MONAI -# (https://docs.monai.io/en/stable/apps.html#monai.apps.MedNISTDataset). -# Here, we will use the "Hand" and "HeadCT", where our conditioning variable `class` will specify the modality. - -# %% - - -batch_size = 2 -channel = 0 # 0 = Flair -assert channel in [0, 1, 2, 3], "Choose a valid channel" - -train_transforms = transforms.Compose( - [ - transforms.LoadImaged(keys=["image","label"]), - transforms.EnsureChannelFirstd(keys=["image","label"]), - transforms.Lambdad(keys=["image"], func=lambda x: x[channel, :, :, :]), - transforms.AddChanneld(keys=["image"]), - transforms.EnsureTyped(keys=["image","label"]), - transforms.Orientationd(keys=["image","label"], axcodes="RAS"), - transforms.Spacingd( - keys=["image","label"], - pixdim=(3.0, 3.0, 2.0), - mode=("bilinear", "nearest"), - ), - transforms.CenterSpatialCropd(keys=["image","label"], roi_size=(64, 64, 64)), - transforms.ScaleIntensityRangePercentilesd(keys="image", lower=0, upper=99.5, b_min=0, b_max=1), - transforms.CopyItemsd(keys=["label"], times=1, names=["slice_label"]), - transforms.Lambdad(keys=["slice_label"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()), - ] -) -train_ds = DecathlonDataset( - root_dir=root_dir, - task="Task01_BrainTumour", - section="training", # validation - cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise - num_workers=4, - download=True, # Set download to True if the dataset hasnt been downloaded yet - seed=0, - transform=train_transforms, -) -nb_3D_images_to_mix = 2 -train_loader_3D = DataLoader(train_ds, batch_size=nb_3D_images_to_mix, shuffle=True, num_workers=4) -print(f'Image shape {train_ds[0]["image"].shape}') - - -# %% - - -from typing import Dict -def get_batched_2d_axial_slices(data : Dict): - images_3D = data['image'] - batched_2d_slices = torch.cat(images_3D.split(1, dim = -1), 0).squeeze(-1) # images_3D.view(images_3D.shape[0]*images_3D.shape[-1],*images_3D.shape[1:-1]) - slice_label = data['slice_label'] - #slice_label = (mask_label.reshape(mask_label.shape[0], -1, mask_label.shape[-1]).sum(1) > 0 ).float() - slice_label = torch.cat(slice_label.split(1, dim = -1),0).squeeze() - return batched_2d_slices, slice_label - - -# ### Visualisation of the training images - -# %% - - -check_data = first(train_loader_3D) -print('check_data', check_data["image"].shape, check_data["slice_label"].shape) - - -# %% - - -batched_2d_slices, slice_label = get_batched_2d_axial_slices(check_data) -idx = list(torch.randperm(batched_2d_slices.shape[0])) -print('idx', idx, len(idx)) -slices = [0,30,45,63] -print(f"Batch shape: {batched_2d_slices.shape}") -print(f"Slices class: {slice_label[idx][slices].view(-1)}") -image_visualisation = torch.cat(batched_2d_slices[idx][slices].squeeze().split(1), dim=2).squeeze() -plt.figure("training images", (12, 6)) -plt.imshow(image_visualisation, vmin=0, vmax=1, cmap="gray") -plt.axis("off") -plt.tight_layout() -plt.show() - - -# %% - - -slice_label.shape - - -# ## Check Distribution of Healthy / Unhealthy - -# %% - -subset_2D = zip(batched_2d_slices.split(batch_size),slice_label.split(batch_size))# -a,b = next(subset_2D) #what is a, what is b? -a.shape, b.shape - - -# %% - - -plt.hist(slice_label.view(-1).numpy(),bins = 5); -plt.title("Distribution of slices with and without tumour \n 0 = no tumour, 1 = tumour"); - - -# %% From b8a2a487cef1b6012ed341914f1686067d02cd5c Mon Sep 17 00:00:00 2001 From: SANCHES-Pedro Date: Tue, 14 Mar 2023 17:44:27 +0000 Subject: [PATCH 07/12] ddim clean-up --- generative/networks/schedulers/ddim.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/generative/networks/schedulers/ddim.py b/generative/networks/schedulers/ddim.py index cd3c0d31..4b33e234 100644 --- a/generative/networks/schedulers/ddim.py +++ b/generative/networks/schedulers/ddim.py @@ -231,8 +231,7 @@ def reversed_step( timestep: int, sample: torch.Tensor, eta: float = 0.0, - generator: Optional[torch.Generator] = None, - ) -> Tuple[torch.Tensor, torch.Tensor]: + ) -> tuple[torch.Tensor, torch.Tensor]: """ Predict the sample at the previous timestep by reversing the SDE. Core function to propagate the diffusion process from the learned model outputs (most often the predicted noise). @@ -258,18 +257,16 @@ def reversed_step( # - eta -> η # - pred_sample_direction -> "direction pointing to x_t" # - pred_post_sample -> "x_t+1" + + assert eta == 0, "eta must be 0 for reversed_step" # 1. get previous step value (=t-1) - prev_timestep = timestep - self.num_train_timesteps // self.num_inference_steps # t-1 - post_timestep = timestep + self.num_train_timesteps // self.num_inference_steps # t+1 + prev_timestep = timestep + self.num_train_timesteps // self.num_inference_steps # t+1 # 2. compute alphas, betas alpha_prod_t = self.alphas_cumprod[timestep] alpha_prod_t_prev = ( self.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod - ) # alpha at timestep t-1 - alpha_prod_t_post = ( - self.alphas_cumprod[post_timestep] if prev_timestep >= 0 else self.final_alpha_cumprod ) # alpha at timestep t+1 beta_prod_t = 1 - alpha_prod_t @@ -289,21 +286,12 @@ def reversed_step( if self.clip_sample: pred_original_sample = torch.clamp(pred_original_sample, -1, 1) - # 5. compute variance: "sigma_t(η)" -> see formula (16) #I thought we set sigma to 0 here??? - # σ_t = sqrt((1 − α_t−1)/(1 − α_t)) * sqrt(1 − α_t/α_t−1) - variance = self._get_variance(timestep, prev_timestep) - std_dev_t = eta * variance ** (0.5) # 6. compute "direction pointing to x_t" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf - pred_sample_direction = (1 - alpha_prod_t_post - std_dev_t**2) ** (0.5) * model_output + pred_sample_direction = (1 - alpha_prod_t_prev) ** (0.5) * model_output # 7. compute x_t+1 without "random noise" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf - pred_post_sample = alpha_prod_t_post ** (0.5) * pred_original_sample + pred_sample_direction - - if eta > 0: - # randn_like does not support generator https://github.com/pytorch/pytorch/issues/27072 - device = model_output.device if torch.is_tensor(model_output) else "cpu" - noise = torch.randn(model_output.shape, dtype=model_output.dtype, generator=generator).to(device) + pred_post_sample = alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction return pred_post_sample, pred_original_sample @@ -358,4 +346,4 @@ def get_velocity(self, sample: torch.Tensor, noise: torch.Tensor, timesteps: tor sqrt_one_minus_alpha_prod = sqrt_one_minus_alpha_prod.unsqueeze(-1) velocity = sqrt_alpha_prod * noise - sqrt_one_minus_alpha_prod * sample - return velocity + return velocity \ No newline at end of file From c25950e7f1ced49704cbb521dea126cfc8fe9037 Mon Sep 17 00:00:00 2001 From: SANCHES-Pedro Date: Tue, 14 Mar 2023 18:03:43 +0000 Subject: [PATCH 08/12] Add tutorial --- ...e_guidance_anomalydetection_tutorial.ipynb | 950 ++++++ ...free_guidance_anomalydetection_tutorial.py | 486 +++ ...r_guidance_anomalydetection_tutorial.ipynb | 2728 ----------------- ...fier_guidance_anomalydetection_tutorial.py | 613 ---- 4 files changed, 1436 insertions(+), 3341 deletions(-) create mode 100644 tutorials/anomaly_detection/2d_classifierfree_guidance_anomalydetection_tutorial.ipynb create mode 100644 tutorials/anomaly_detection/2d_classifierfree_guidance_anomalydetection_tutorial.py delete mode 100644 tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.ipynb delete mode 100644 tutorials/generative/classifier_guidance_anomalydetection/2d_classifier_guidance_anomalydetection_tutorial.py diff --git a/tutorials/anomaly_detection/2d_classifierfree_guidance_anomalydetection_tutorial.ipynb b/tutorials/anomaly_detection/2d_classifierfree_guidance_anomalydetection_tutorial.ipynb new file mode 100644 index 00000000..a663c4ae --- /dev/null +++ b/tutorials/anomaly_detection/2d_classifierfree_guidance_anomalydetection_tutorial.ipynb @@ -0,0 +1,950 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "63d95da6", + "metadata": {}, + "source": [ + "# Weakly Supervised Anomaly Detection with Classifier Guidance\n", + "\n", + "This tutorial illustrates how to use MONAI Generative Models for training a 2D gradient-guided anomaly detection using DDIMs [1].\n", + "\n", + "In summary, the tutorial will cover:\n", + "1. Loading and preprocessing a dataset (we extract the brain MRI dataset 2D slices from 3D volumes from the BraTS dataset)\n", + "2. Training a 2D diffusion model\n", + "3. Anomlay detection with the trained model\n", + "\n", + "This method results in anomaly heatmaps. It is weakly supervised. The information about labels is not fed to the model as segmentation masks, but as a scalar signal (is there an anomaly or not) which is used to guide the diffusion process.\n", + "\n", + "During inference, the model is used to generate a counterfactual image, which is then compared to the original image. The difference between the two images is used to generate an anomaly heatmap.\n", + "\n", + "[1] - Sanchez et al. [What is Healthy? Generative Counterfactual Diffusion for Lesion Localization](https://arxiv.org/abs/2207.12268). DGM 4 MICCAI 2022" + ] + }, + { + "cell_type": "markdown", + "id": "6b766027", + "metadata": {}, + "source": [ + "## Setup imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "972ed3f3", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/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": [ + "path ['/remote/rds/users/s2086085/GenerativeModels/tutorials/anomaly_detection', '/remote/rds/users/s2086085/GenerativeModels/tutorials/anomaly_detection', '/home/s2086085/RDS/counterfactual_ebm', '/home/s2086085/RDS/anomaly_detection', '/home/s2086085/RDS/deci/azua', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python310.zip', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/lib-dynload', '', '/home/s2086085/.local/lib/python3.10/site-packages', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/generative-0.1.0-py3.10.egg', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/monai_weekly-1.2.dev2304-py3.10.egg', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/wheel-0.38.4-py3.10.egg', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/setuptools-67.4.0-py3.10.egg', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/grad_cam-1.4.6-py3.10.egg', '/remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/ttach-0.0.3-py3.10.egg', '/home/s2086085/RDS/GenerativeModels']\n", + "2023-03-14 16:48:17,060 - A matching Triton is not available, some optimizations will not be enabled.\n", + "Error caught was: No module named 'triton'\n", + "MONAI version: 1.1.0\n", + "Numpy version: 1.23.5\n", + "Pytorch version: 1.13.1+cu117\n", + "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", + "MONAI rev id: a2ec3752f54bfc3b40e7952234fbeb5452ed63e3\n", + "MONAI __file__: /remote/rds/users/s2086085/miniconda3/envs/pytorch_monai/lib/python3.10/site-packages/monai/__init__.py\n", + "\n", + "Optional dependencies:\n", + "Pytorch Ignite version: 0.4.10\n", + "Nibabel version: 5.0.1\n", + "scikit-image version: 0.19.3\n", + "Pillow version: 9.4.0\n", + "Tensorboard version: 2.12.0\n", + "gdown version: 4.6.4\n", + "TorchVision version: 0.14.1+cu117\n", + "tqdm version: 4.64.1\n", + "lmdb version: 1.4.0\n", + "psutil version: 5.9.4\n", + "pandas version: 1.5.3\n", + "einops version: 0.6.0\n", + "transformers version: 4.21.3\n", + "mlflow version: 2.1.1\n", + "pynrrd version: 1.0.0\n", + "\n", + "For details about installing the optional dependencies, please visit:\n", + " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", + "\n" + ] + } + ], + "source": [ + "# Copyright 2020 MONAI Consortium\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "import os\n", + "import shutil\n", + "import tempfile\n", + "import time\n", + "from typing import Dict\n", + "import os\n", + "import torch.nn as nn\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn.functional as F\n", + "from monai import transforms\n", + "from monai.apps import MedNISTDataset, DecathlonDataset\n", + "from monai.config import print_config\n", + "from monai.data import CacheDataset, DataLoader\n", + "from monai.utils import first, set_determinism\n", + "from torch.cuda.amp import GradScaler, autocast\n", + "from tqdm import tqdm\n", + "torch.multiprocessing.set_sharing_strategy('file_system')\n", + "import sys\n", + "sys.path.append('/home/s2086085/RDS/GenerativeModels')\n", + "print('path', sys.path)\n", + "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\"\n", + "from generative.inferers import DiffusionInferer\n", + "\n", + "from generative.networks.nets.diffusion_model_unet import DiffusionModelUNet\n", + "from generative.networks.schedulers.ddim import DDIMScheduler\n", + "print_config()" + ] + }, + { + "cell_type": "markdown", + "id": "7d4ff515", + "metadata": {}, + "source": [ + "## Setup data directory" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8b4323e7", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", + "root_dir = tempfile.mkdtemp() if directory is None else directory" + ] + }, + { + "cell_type": "markdown", + "id": "99175d50", + "metadata": {}, + "source": [ + "## Set deterministic training for reproducibility" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "34ea510f", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "set_determinism(42)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c3f70dd1-236a-47ff-a244-575729ad92ba", + "metadata": { + "tags": [] + }, + "source": [ + "## Setup BRATS Dataset - Extract 2D slices from 3D volumes\n", + "\n", + "We now download the BraTS dataset and extract the 2D slices from the 3D volumes. The `slice_label` are used to indicate whether the slice contains an anomaly or not." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "6986f55c", + "metadata": {}, + "source": [ + "Here we use transforms to augment the training dataset, as usual:\n", + "\n", + "1. `LoadImaged` loads the hands images from files.\n", + "2. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n", + "3. `ScaleIntensityRangePercentilesd` Apply range scaling to a numpy array based on the intensity distribution of the input. Transform is very common with MRI images.\n", + "\n", + "\n", + "`get_batched_2d_axial_slices` is a utility function that extracts 2D slices from 3D volumes.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c68d2d91-9a0b-4ac1-ae49-f4a64edbd82a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ": Class `AddChannel` has been deprecated since version 0.8. please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.\n" + ] + } + ], + "source": [ + "channel = 0 # 0 = Flair\n", + "assert channel in [0, 1, 2, 3], \"Choose a valid channel\"\n", + "\n", + "train_transforms = transforms.Compose(\n", + " [\n", + " transforms.LoadImaged(keys=[\"image\",\"label\"]),\n", + " transforms.EnsureChannelFirstd(keys=[\"image\",\"label\"]),\n", + " transforms.Lambdad(keys=[\"image\"], func=lambda x: x[channel, :, :, :]),\n", + " transforms.AddChanneld(keys=[\"image\"]),\n", + " transforms.EnsureTyped(keys=[\"image\",\"label\"]),\n", + " transforms.Orientationd(keys=[\"image\",\"label\"], axcodes=\"RAS\"),\n", + " transforms.Spacingd(\n", + " keys=[\"image\",\"label\"],\n", + " pixdim=(3.0, 3.0, 2.0),\n", + " mode=(\"bilinear\", \"nearest\"),\n", + " ),\n", + " transforms.CenterSpatialCropd(keys=[\"image\",\"label\"], roi_size=(64, 64, 64)),\n", + " transforms.ScaleIntensityRangePercentilesd(keys=\"image\", lower=0, upper=99.5, b_min=0, b_max=1),\n", + " transforms.CopyItemsd(keys=[\"label\"], times=1, names=[\"slice_label\"]),\n", + " transforms.Lambdad(keys=[\"slice_label\"], func=lambda x: (x.reshape(x.shape[0], -1, x.shape[-1]).sum(1) > 0 ).float().squeeze()),\n", + " ]\n", + ")\n", + "\n", + "def get_batched_2d_axial_slices(data : Dict):\n", + " images_3D = data['image']\n", + " batched_2d_slices = torch.cat(images_3D.split(1, dim = -1)[10:-10], 0).squeeze(-1) # we cut the lowest and highest 10 slices, because we are interested in the middle part of the brain.\n", + " slice_label = data['slice_label']\n", + " slice_label = torch.cat(slice_label.split(1, dim = -1)[10:-10],0).squeeze()\n", + " return batched_2d_slices, slice_label" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "9d378ac6", + "metadata": {}, + "source": [ + "### Training Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "da1927b0", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "len train data 388\n", + "Image shape torch.Size([1, 64, 64, 64])\n", + "total slices torch.Size([17072, 1, 64, 64])\n", + "total lbaels torch.Size([17072])\n" + ] + } + ], + "source": [ + "\n", + "train_ds = DecathlonDataset(\n", + " root_dir=root_dir,\n", + " task=\"Task01_BrainTumour\",\n", + " section=\"training\", # validation\n", + " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", + " num_workers=4,\n", + " download=False, # Set download to True if the dataset hasnt been downloaded yet\n", + " seed=0,\n", + " transform=train_transforms,\n", + ")\n", + "print('len train data', len(train_ds))\n", + "\n", + "train_loader_3D = DataLoader(train_ds, batch_size=1, shuffle=True, num_workers=4)\n", + "print(f'Image shape {train_ds[0][\"image\"].shape}')\n", + "\n", + "data_2d_slices=[]\n", + "data_slice_label = []\n", + "check_data = first(train_loader_3D)\n", + "for i, data in enumerate(train_loader_3D):\n", + " b2d, slice_label2d = get_batched_2d_axial_slices(data)\n", + " data_2d_slices.append(b2d)\n", + " data_slice_label.append(slice_label2d)\n", + "total_train_slices=torch.cat(data_2d_slices,0)\n", + "total_train_labels=torch.cat(data_slice_label,0)\n", + "\n", + "print('total slices', total_train_slices.shape)\n", + "print('total lbaels', total_train_labels.shape)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "fac55e9d", + "metadata": { + "tags": [] + }, + "source": [ + "### Validation Dataset\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "73d72110-a8b3-4e03-91cc-1dab4d5a7b87", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Image shape torch.Size([1, 64, 64, 64])\n", + "len val data 96\n", + "total slices torch.Size([4224, 1, 64, 64])\n", + "total lbaels torch.Size([4224])\n" + ] + } + ], + "source": [ + "val_ds = DecathlonDataset(\n", + " root_dir=root_dir,\n", + " task=\"Task01_BrainTumour\",\n", + " section=\"validation\", # validation\n", + " cache_rate=0.0, # you may need a few Gb of RAM... Set to 0 otherwise\n", + " num_workers=4,\n", + " download=False, # Set download to True if the dataset hasnt been downloaded yet\n", + " seed=0,\n", + " transform=train_transforms,\n", + ")\n", + "\n", + "val_loader_3D = DataLoader(val_ds, batch_size=1, shuffle=True, num_workers=4)\n", + "print(f'Image shape {val_ds[0][\"image\"].shape}')\n", + "print('len val data', len(val_ds))\n", + "data_2d_slices_val=[]\n", + "data_slice_label_val = []\n", + "for i, data in enumerate(val_loader_3D):\n", + " b2d, slice_label2d = get_batched_2d_axial_slices(data)\n", + " data_2d_slices_val.append(b2d)\n", + " data_slice_label_val.append(slice_label2d)\n", + "total_val_slices=torch.cat(data_2d_slices_val,0)\n", + "total_val_labels=torch.cat(data_slice_label_val,0)\n", + "\n", + "print('total slices', total_val_slices.shape)\n", + "print('total lbaels', total_val_labels.shape)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "08428bc6", + "metadata": {}, + "source": [ + "## Define network, scheduler, optimizer, and inferer\n", + "\n", + "At this step, we instantiate the MONAI components to create a DDIM, the UNET with conditioning, the noise scheduler, and the inferer used for training and sampling. We are using\n", + "the deterministic DDIM scheduler containing 1000 timesteps, and a 2D UNET with attention mechanisms.\n", + "\n", + "The `attention` mechanism is essential for ensuring good conditioning and images manipulation here. \n", + "\n", + "An `embedding layer`, which is also optimised during training, is used in the original work because it was empirically shown to improve conditioning compared to a single scalar information.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bee5913e", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "device = torch.device(\"cuda\")\n", + "\n", + "model = DiffusionModelUNet(\n", + " spatial_dims=2,\n", + " in_channels=1,\n", + " out_channels=1,\n", + " num_channels=(64, 128, 128),\n", + " attention_levels=(False, True, True),\n", + " num_res_blocks=1,\n", + " num_head_channels=16,\n", + " with_conditioning=True,\n", + " cross_attention_dim=64,\n", + " ).to(device)\n", + "embed = torch.nn.Embedding(num_embeddings = 3, embedding_dim= 64, padding_idx=0).to(device)\n", + "\n", + "scheduler = DDIMScheduler(\n", + " num_train_timesteps=1000,\n", + ")\n", + "optimizer = torch.optim.Adam(params=list(model.parameters()) + list(embed.parameters()), lr=5e-5)\n", + "\n", + "inferer = DiffusionInferer(scheduler)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f815ff34", + "metadata": {}, + "source": [ + "## Training a diffusion model with classifier-free guidance" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9a4fc901", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 0: 100%|█████████▉| 266/266.75 [00:54<00:00, 5.02it/s, loss=0.0189]clamping frac to range [0, 1]\n", + "Epoch 0: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0189]\n", + "4it [00:00, 20.78it/s]\n", + "Epoch 1: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0187]\n", + "4it [00:00, 20.77it/s]\n", + "Epoch 2: 100%|██████████| 267/266.75 [00:55<00:00, 4.85it/s, loss=0.018] \n", + "4it [00:00, 20.83it/s]\n", + "Epoch 3: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0184]\n", + "4it [00:00, 20.53it/s]\n", + "Epoch 4: 100%|██████████| 267/266.75 [00:54<00:00, 4.92it/s, loss=0.018] \n", + "4it [00:00, 11.78it/s]\n", + "Epoch 5: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0172]\n", + "4it [00:00, 13.08it/s]\n", + "Epoch 6: 100%|██████████| 267/266.75 [00:55<00:00, 4.84it/s, loss=0.0175]\n", + "4it [00:00, 16.40it/s]\n", + "Epoch 7: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.017] \n", + "4it [00:00, 20.78it/s]\n", + "Epoch 8: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0174]\n", + "4it [00:00, 15.99it/s]\n", + "Epoch 9: 100%|██████████| 267/266.75 [00:55<00:00, 4.82it/s, loss=0.0173]\n", + "4it [00:00, 15.43it/s]\n", + "Epoch 10: 100%|██████████| 267/266.75 [00:54<00:00, 4.90it/s, loss=0.0165]\n", + "4it [00:00, 21.11it/s]\n", + "Epoch 11: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.0164]\n", + "4it [00:00, 13.50it/s]\n", + "Epoch 12: 100%|██████████| 267/266.75 [00:55<00:00, 4.84it/s, loss=0.0162]\n", + "4it [00:00, 15.31it/s]\n", + "Epoch 13: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.017] \n", + "4it [00:00, 20.83it/s]\n", + "Epoch 14: 100%|██████████| 267/266.75 [00:55<00:00, 4.85it/s, loss=0.0169]\n", + "4it [00:00, 20.40it/s]\n", + "Epoch 15: 100%|██████████| 267/266.75 [00:54<00:00, 4.90it/s, loss=0.016] \n", + "4it [00:00, 15.72it/s]\n", + "Epoch 16: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0171]\n", + "4it [00:00, 12.48it/s]\n", + "Epoch 17: 100%|██████████| 267/266.75 [00:54<00:00, 4.86it/s, loss=0.0165]\n", + "4it [00:00, 20.23it/s]\n", + "Epoch 18: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0163]\n", + "4it [00:00, 13.36it/s]\n", + "Epoch 19: 100%|██████████| 267/266.75 [00:54<00:00, 4.86it/s, loss=0.0165]\n", + "4it [00:00, 20.46it/s]\n", + "Epoch 20: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.016] \n", + "4it [00:00, 13.82it/s]\n", + "Epoch 21: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0165]\n", + "4it [00:00, 18.02it/s]\n", + "Epoch 22: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.0161]\n", + "4it [00:00, 20.31it/s]\n", + "Epoch 23: 100%|██████████| 267/266.75 [00:54<00:00, 4.94it/s, loss=0.0163]\n", + "4it [00:00, 18.12it/s]\n", + "Epoch 24: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0155]\n", + "4it [00:00, 20.64it/s]\n", + "Epoch 25: 100%|██████████| 267/266.75 [00:55<00:00, 4.83it/s, loss=0.0162]\n", + "4it [00:00, 21.10it/s]\n", + "Epoch 26: 100%|██████████| 267/266.75 [00:55<00:00, 4.82it/s, loss=0.0163]\n", + "4it [00:00, 16.26it/s]\n", + "Epoch 27: 100%|██████████| 267/266.75 [00:55<00:00, 4.85it/s, loss=0.0171]\n", + "4it [00:00, 14.07it/s]\n", + "Epoch 28: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0163]\n", + "4it [00:00, 16.26it/s]\n", + "Epoch 29: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0164]\n", + "4it [00:00, 19.45it/s]\n", + "Epoch 30: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0158]\n", + "4it [00:00, 16.05it/s]\n", + "Epoch 31: 100%|██████████| 267/266.75 [00:55<00:00, 4.79it/s, loss=0.0162]\n", + "4it [00:00, 13.29it/s]\n", + "Epoch 32: 100%|██████████| 267/266.75 [00:54<00:00, 4.90it/s, loss=0.0157]\n", + "4it [00:00, 15.42it/s]\n", + "Epoch 33: 100%|██████████| 267/266.75 [00:54<00:00, 4.87it/s, loss=0.0157]\n", + "4it [00:00, 19.60it/s]\n", + "Epoch 34: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.0158]\n", + "4it [00:00, 18.39it/s]\n", + "Epoch 35: 100%|██████████| 267/266.75 [00:55<00:00, 4.83it/s, loss=0.0161]\n", + "4it [00:00, 19.75it/s]\n", + "Epoch 36: 100%|██████████| 267/266.75 [00:55<00:00, 4.83it/s, loss=0.0155]\n", + "4it [00:00, 20.84it/s]\n", + "Epoch 37: 100%|██████████| 267/266.75 [00:54<00:00, 4.92it/s, loss=0.0165]\n", + "4it [00:00, 20.73it/s]\n", + "Epoch 38: 100%|██████████| 267/266.75 [00:55<00:00, 4.83it/s, loss=0.0162]\n", + "4it [00:00, 20.95it/s]\n", + "Epoch 39: 100%|██████████| 267/266.75 [00:54<00:00, 4.92it/s, loss=0.0158]\n", + "4it [00:00, 14.70it/s]\n", + "Epoch 40: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0162]\n", + "4it [00:00, 13.98it/s]\n", + "Epoch 41: 100%|██████████| 267/266.75 [00:54<00:00, 4.91it/s, loss=0.0157]\n", + "4it [00:00, 20.94it/s]\n", + "Epoch 42: 100%|██████████| 267/266.75 [00:54<00:00, 4.91it/s, loss=0.0158]\n", + "4it [00:00, 14.47it/s]\n", + "Epoch 43: 100%|██████████| 267/266.75 [00:54<00:00, 4.86it/s, loss=0.016] \n", + "4it [00:00, 20.78it/s]\n", + "Epoch 44: 100%|██████████| 267/266.75 [00:54<00:00, 4.91it/s, loss=0.0162]\n", + "4it [00:00, 21.05it/s]\n", + "Epoch 45: 100%|██████████| 267/266.75 [00:54<00:00, 4.92it/s, loss=0.0161]\n", + "4it [00:00, 21.00it/s]\n", + "Epoch 46: 100%|██████████| 267/266.75 [00:54<00:00, 4.86it/s, loss=0.0164]\n", + "4it [00:00, 19.66it/s]\n", + "Epoch 47: 100%|██████████| 267/266.75 [00:54<00:00, 4.89it/s, loss=0.0157]\n", + "4it [00:00, 15.67it/s]\n", + "Epoch 48: 100%|██████████| 267/266.75 [00:54<00:00, 4.88it/s, loss=0.0158]\n", + "4it [00:00, 20.87it/s]\n", + "Epoch 49: 100%|██████████| 267/266.75 [00:54<00:00, 4.86it/s, loss=0.0157]\n", + "4it [00:00, 20.91it/s]\n", + "The seaborn styles shipped by Matplotlib are deprecated since 3.6, as they no longer correspond to the styles shipped by seaborn. However, they will remain available as 'seaborn-v0_8-