From bb785deb30ebd938a2c23620e99eb75c101fb2b6 Mon Sep 17 00:00:00 2001 From: Tom Hosking Date: Tue, 9 Apr 2024 16:16:39 +0100 Subject: [PATCH] Add model - Hierarchical Residual Quantization (HRQVAE) (#140) * Add HRQVAE model - seems to work OK * Bug fixes * Add example notebook and update readme * Update expected output in test * Fix typos, update docstrings --- README.md | 1 + .../models_training/hrqvae_training.ipynb | 4125 +++++++++++++++++ src/pythae/models/__init__.py | 3 + src/pythae/models/auto_model/auto_config.py | 5 + src/pythae/models/auto_model/auto_model.py | 5 + src/pythae/models/hrq_vae/__init__.py | 19 + src/pythae/models/hrq_vae/hrq_vae_config.py | 37 + src/pythae/models/hrq_vae/hrq_vae_model.py | 128 + src/pythae/models/hrq_vae/hrq_vae_utils.py | 153 + .../models/nn/benchmarks/mnist/resnets.py | 230 + tests/test_HRQVAE.py | 760 +++ 11 files changed, 5466 insertions(+) create mode 100644 examples/notebooks/models_training/hrqvae_training.ipynb create mode 100755 src/pythae/models/hrq_vae/__init__.py create mode 100755 src/pythae/models/hrq_vae/hrq_vae_config.py create mode 100755 src/pythae/models/hrq_vae/hrq_vae_model.py create mode 100755 src/pythae/models/hrq_vae/hrq_vae_utils.py create mode 100755 tests/test_HRQVAE.py diff --git a/README.md b/README.md index b601f2e8..ecaacdc2 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ VAE with Inverse Autoregressive Flows (VAE_IAF) | [![Open In Colab](https://col | Regularized AE with L2 decoder param (RAE_L2) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/clementchadebec/benchmark_VAE/blob/main/examples/notebooks/models_training/rae_l2_training.ipynb) | [link](https://arxiv.org/abs/1903.12436) | [link](https://github.com/ParthaEth/Regularized_autoencoders-RAE-/tree/master/) | | Regularized AE with gradient penalty (RAE_GP) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/clementchadebec/benchmark_VAE/blob/main/examples/notebooks/models_training/rae_gp_training.ipynb) | [link](https://arxiv.org/abs/1903.12436) | [link](https://github.com/ParthaEth/Regularized_autoencoders-RAE-/tree/master/) | | Riemannian Hamiltonian VAE (RHVAE) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/clementchadebec/benchmark_VAE/blob/main/examples/notebooks/models_training/rhvae_training.ipynb) | [link](https://arxiv.org/abs/2105.00026) | [link](https://github.com/clementchadebec/pyraug)| +| Hierarchical Residual Quantization (HRQVAE) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/clementchadebec/benchmark_VAE/blob/main/examples/notebooks/models_training/hrqvae_training.ipynb) | [link](https://aclanthology.org/2022.acl-long.178/) | [link](https://github.com/tomhosking/hrq-vae)| **See [reconstruction](#Reconstruction) and [generation](#Generation) results for all aforementionned models** diff --git a/examples/notebooks/models_training/hrqvae_training.ipynb b/examples/notebooks/models_training/hrqvae_training.ipynb new file mode 100644 index 00000000..dc71a51b --- /dev/null +++ b/examples/notebooks/models_training/hrqvae_training.ipynb @@ -0,0 +1,4125 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Install the library\n", + "%pip install pythae" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torchvision.datasets as datasets\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "mnist_trainset = datasets.MNIST(root='../../data', train=True, download=True, transform=None)\n", + "\n", + "train_dataset = mnist_trainset.data[:-50000].reshape(-1, 1, 28, 28) / 255.\n", + "eval_dataset = mnist_trainset.data[-10000:].reshape(-1, 1, 28, 28) / 255." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from pythae.models import HRQVAE, HRQVAEConfig\n", + "from pythae.trainers import BaseTrainerConfig\n", + "from pythae.pipelines.training import TrainingPipeline\n", + "from pythae.models.nn.benchmarks.mnist.resnets import Encoder_ResNet_HRQVAE_MNIST, Decoder_ResNet_HRQVAE_MNIST" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "config = BaseTrainerConfig(\n", + " output_dir='my_model',\n", + " learning_rate=1e-4,\n", + " per_device_train_batch_size=64,\n", + " per_device_eval_batch_size=64,\n", + " num_epochs=100, # Change this to train the model a bit more\n", + ")\n", + "\n", + "\n", + "model_config = HRQVAEConfig(\n", + " latent_dim=128,\n", + " input_dim=(1, 28, 28),\n", + " num_embeddings=10,\n", + " num_levels = 3,\n", + " kl_weight = 0.01,\n", + " init_scale = 2.0,\n", + " init_decay_weight = 0.5,\n", + " norm_loss_weight = 1.0, # 0.5\n", + " norm_loss_scale = 1.5,\n", + " temp_schedule_gamma=10,\n", + " depth_drop_rate = 0.1,\n", + ")\n", + "\n", + "model = HRQVAE(\n", + " model_config=model_config,\n", + " encoder=Encoder_ResNet_HRQVAE_MNIST(model_config), \n", + " decoder=Decoder_ResNet_HRQVAE_MNIST(model_config) \n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "pipeline = TrainingPipeline(\n", + " training_config=config,\n", + " model=model\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Preprocessing train data...\n", + "Checking train dataset...\n", + "Preprocessing eval data...\n", + "\n", + "Checking eval dataset...\n", + "Using Base Trainer\n", + "\n", + "Model passed sanity check !\n", + "Ready for training.\n", + "\n", + "Created my_model folder since did not exist.\n", + "\n", + "Created my_model/HRQVAE_training_2024-04-05_09-25-49. \n", + "Training config, checkpoints and final model will be saved here.\n", + "\n", + "Training params:\n", + " - max_epochs: 100\n", + " - per_device_train_batch_size: 64\n", + " - per_device_eval_batch_size: 64\n", + " - checkpoint saving every: None\n", + "Optimizer: Adam (\n", + "Parameter Group 0\n", + " amsgrad: False\n", + " betas: (0.9, 0.999)\n", + " capturable: False\n", + " differentiable: False\n", + " eps: 1e-08\n", + " foreach: None\n", + " fused: None\n", + " lr: 0.0001\n", + " maximize: False\n", + " weight_decay: 0\n", + ")\n", + "Scheduler: None\n", + "\n", + "Successfully launched training !\n", + "\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d501d8fe00884236b5bbef59c0a552bb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training of epoch 1/100: 0%| | 0/157 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# show reconstructions\n", + "fig, axes = plt.subplots(nrows=5, ncols=5, figsize=(10, 10))\n", + "\n", + "for i in range(5):\n", + " for j in range(5):\n", + " axes[i][j].imshow(reconstructions[i*5 + j].cpu().squeeze(0), cmap='gray')\n", + " axes[i][j].axis('off')\n", + "plt.tight_layout(pad=0.)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+tnU75bp06cnr6XpO+KII0pdd8ghhyS7vx/4f3izDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmrN5rotZaa62wjRo1Kmy9evUK27vvvpt85nHHHVfzwcjW/fffH7bq6uqw/eY3v0ne9y9/+UvYPvrooxrPVZfat28ftquvvjp57dixY8O29957h23FFVes8Vw0fZ07dw7bzjvvXOqeH374YdnjQL058cQTw9aiRYuwHXTQQcn73nDDDaXOM3ny5LBNmDAhbK1btw5b6vdYTT/jn3rqqbCl1vLNnj07eV/y9vHHHzf0EWgivNkHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDOGfQAAAMhMVXVqQfaPv7CqqtJn4T/sv//+YTvrrLPCtsIKK4Rtzpw5YRs0aFDyPI8//niy03wNGzYsbKeffnry2nfeeSds22yzTdimTJlS47l+qoceeihsm266afLa9dZbL2yvv/566TORhyFDhoTt2muvDdsjjzwStu222y5sCxYsqN3BoA4stdRSYXvvvffCNnLkyLCddNJJi3Sm+pT6PbbyyiuXvu+IESPCdtxxx5W+L/Wrd+/eYXvppZfC9t1334Vt9dVXTz7z448/rvlgNGm1HOG92QcAAIDcGPYBAAAgM4Z9AAAAyIxhHwAAADJj2AcAAIDMGPYBAAAgM1bv1YN27dqF7fjjjw/baaedFrbFFov/7zQzZswIW//+/cP29ttvhw1SWrduHbbrr78+ee2uu+4atvfffz9sAwcODNtnn30Wtr/97W9hO/jgg8N2wgknhK0o0iuSyF/Lli2T/a233gpbjx49wvbzn/88bJ988knNB4N60Ldv37A999xzYdtqq63C9uijjy7SmerTTjvtFLY777wzeW3qz/BZs2aFLbXS78svv0w+k/q17rrrhu3FF18M27Rp08LWtWvXRToTTZ/VewAAANBMGfYBAAAgM4Z9AAAAyIxhHwAAADJj2AcAAIDMGPYBAAAgM+ldQdSJ6667Lmw777xzqXvecccdYfvLX/4SNuv1qITvv/8+bAcddFDy2mWXXTZsm266adiefPLJsI0aNSps++yzT9hGjx4dNqv1SEmtkCyKolhppZXCduihh4atKa3X23bbbcM2ePDgsD3wwAPJ+z700ENhS/3sof6ss846pa575ZVX6vgkDeO+++4LW2qFbFGkfzakvr+//fbbmg8GP9HSSy8dtkGDBoXtuOOOK/W8yZMnJ3vPnj3DNnXq1LCl5qSRI0eGbd68ecnzNEXe7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGav36kFqrUpZV1xxRdieeeaZOn8elPX1118n+4477hi2YcOGhe3oo48O20knnVTTsf6rSy+9tNR10L1799LXtmrVqg5PUlm//e1vw/a3v/0tbK1btw7bIYccknzmrFmzwjZ27NiwHXDAAcn7UnfGjx8ftoULF4bt4YcfDltqzVdRFMVnn31W88Hqyaqrrhq21Pd+URTFNttsE7Y2bdqEzdrJ/HXq1Clsffr0SV774osvhq1Xr15he+SRR8KW+j03Z86csP3rX/8KW02r91J9yJAhYdtyyy3DlvrM7bLLLsnzNEXe7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGav36sFDDz0UtrXWWqvO75lay3feeeeF7dNPPy11FlgUs2fPDtsZZ5wRtq222ipsq622WqmzpFa1pFZLQWqVUU3efvvtOjzJouvYsWPYLr744rClVozNnz8/bKn1a0VRFP379w/bPvvsEzar9+rPG2+8EbZx48aFLbV69a233ko+84EHHgjb6NGjw/bYY4+FrVu3bmFLrdf7y1/+ErauXbuGrSjSn4277roreS1Nw4wZM8L21Vdfha1Dhw6lWlEUxYorrhi21Gdg+eWXD9ujjz4atsMPPzxs7777btgWxd133x22MWPGhO0Xv/hFJY7TaHmzDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmqqqrq6tr9YVVVZU+S7aWXHLJsN10001hW2+99cLWvXv3UmeZOnVq2IYMGZK89sEHHyz1TChru+22C1tqrcriiy9e6nlz584N22GHHZa8duTIkaWeSR7uvffeZF9nnXXCttxyy9X1cRbJ0UcfHbbU6r3U77NLLrkkbB999FHyPKkVa2uuuWbYWrVqlbwv9SP1N9C5554btiOPPLISx0muQevUqVNFnpmy2267hS21QpA8vPnmm2FLrYir6W+O1AriDTbYIGyPPPJI2Hbaaaewffvtt8nz1LcRI0aEbeuttw7b6quvXonjVEQtR3hv9gEAACA3hn0AAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADITMuGPkBzMGfOnLDtvffeYWvZMv7PM3v27FJn6dKlS9hSq8yKoiiOPfbYsF155ZWlzgMpm222WdhSK0dS62FSa5fGjRsXtiuuuCJsRVEU06dPD9s999yTvJamL7XKqCjSax1z8emnn4Zt+eWXD9tVV12VvO+6664bNithG7/U30CpNY+333578r6pv59SOnfuXOq6efPmhS31+f/5z3+evO93331X6jzk4a677gpbavVeTeuyU6655pqwpT6TTel7NfU579q1a9hSq81rWhPbWHmzDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQmXiRO/Xi+++/L3Xd2muvHbYRI0aELbW3vHXr1slnnnTSSWG78sork9dCpHfv3mE76qijwpbae3/33XeXOsvQoUPDdu211yavveWWW8K2+uqrh62p7m3l/3TnnXcm+6BBg+rpJDWrqqpapB458cQTS11XXV2d7JdddlnYTjnllFLPpPF75plnFqnXpxtvvDFsPXv2TF67YMGCOj4NTcn5558ftr322itsqX3wNXnzzTfD9t1335W+b2Oy+eabh22JJZYI25JLLlmJ4zQob/YBAAAgM4Z9AAAAyIxhHwAAADJj2AcAAIDMGPYBAAAgM4Z9AAAAyIzVe7XUpk2bsDXEmopJkyaFbddddw3bP/7xj7DtuOOOyWem1nx07do1bJ999lnyvjRv7du3D1vLlvGPqDvuuKPOzzJq1Kiw9ejRI3ltan3OeuutFzar95qHjh07hi21tuumm24KW+rzseeee4atU6dOYSuKothuu+2SPfLtt9+Gbfz48WG74IILkvd9/PHHS50HmoKVVlqpoY9AA5o1a1bYDj/88LD985//TN63bdu2YTv77LPDtsEGG4TtnHPOCdvrr7+ePE8lpH5XLb300mF79913w/bOO+8s0pkaI2/2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDOGfQAAAMiM1Xs/klp/klobdO+99ybvm1pHkVpLd+CBB4Zt8cUXD1u3bt3C1qtXr7DV5N///nfYrNejrLXXXjtsU6dODVvqM1kJl112WbIPHTo0bKn1OWPGjCl9JhqP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q666ati233775LV1raa9tSlffvll2G688caw3XnnnWEbP3586fPQuPTr1y9shx9+eD2epGapz+zChQvr8SRQ91ZZZZWGPsL/4bTTTgvb6aefHraxY8dW4DQ0FdXV1ck+d+7cejrJ//bCCy+Ebfnll6/Hk/CfvNkHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDNW79XS5ptvXvraESNG1OFJ/reWLeP/dC1atAhb165dw5ZayVYURTF79uyaD0aj16lTp7C1bds2bDWteYkcc8wxyZ5ad1f2mSmp9TC33XZb2O67777kfb/55puwffLJJzUfjKz179+/zu/5+eefh+2KK64I22KLpf/v/Kl1X+eee27YDjrooLDNnDkz+Uz4qc4+++ywbbzxxmGr75Wuqc9iUaTPc8MNN4Tt66+/Ln0m8rf44ovX6/NSa7979uwZtppWHlfi78Dmxpt9AAAAyIxhHwAAADJj2AcAAIDMGPYBAAAgM4Z9AAAAyIxhHwAAADJj9V4tpda4TJs2LXnt008/XdfHKb777ruw3XvvvWE75JBDwtahQ4fkM63ey8PkyZPD9sUXX4QttbKvsUmtZPrrX/8athkzZlTiOFAMHz48bC+99FLYvv3227A9+eSTYZs7d27Yalp1NGrUqLA9+uijYbvmmmvCduCBB4Zt1qxZyfPAf/P666+H7dhjjw3bP//5z7ANGjQobA8//HDYhgwZEraDDz44bEVRFH379g1b6mcD+Zs3b17YFi5cmLy27ErHSlh//fXD1r59++S1Zo9F580+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJmxeu9H2rZtG7Zf/epXYUutOCqK9OqkSrDGiLJSa7VWXXXVijzzqaeeCtvo0aPDdsstt4Rt5syZYatpXQ1Uwvz588M2duzY+jtIURTV1dXJnlppNnTo0LCNGTMmbI8//njYLrvssuR54KeaPn162H744Yew3XrrrWF77rnnwta7d++wpdZOFoX1esSef/75sKX+zimKolhuueXq+jilPfvss2GzWq/yvNkHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDNW7/1ImzZtwtajR4+wTZkypRLHKe2rr74qdV2HDh2SvbH976TunXzyyWEbNGhQ2Lp37176mQMHDix9LVC/7r777rD985//DFvqZ8ttt92WfOYXX3xR88HgR1KrHi+99NKwHX/88WHbbLPNwnbHHXeE7brrrgsbVMqcOXMa+gg0Et7sAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZq/fqQKtWrZJ9vfXWC9v3338fthkzZoRtySWXDFt1dXXyPJErrrgi2TfffPOwzZs3r9QzaVy++eabsKXWau2zzz5h69atW/KZU6dODduoUaPCduaZZ4Yt9dkBKuOSSy4J21577RW2gw8+OHnfc845p/SZ4D8tt9xydX7Pa665ps7vCYtiiy22CNtf//rXsH3wwQdh+9e//hW21ArmjTbaKGxjxowJW0369OkTtl122SVsEydOLP3MpsibfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyU1Vdyz1tVVVVlT5Lg1tmmWXC9vnnn1fkmfPnzw9bag1aat1fmzZtFulMkZ122ilsY8eOrcgzaRpSa7WuvPLK5LXt27cPW+rH0zPPPBO2wYMHh23mzJnJ8wDltG7dOmwTJkwI26RJk5L3HTJkSOkz0TytvfbaYXvyySfDttRSS4Vt4cKFYdt2223D9vDDD4cNyrruuuuSfb/99qufgzSgG264IWwHHXRQ2FKzV1NS21Xr3uwDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGSmqrqWS/qqqqoqfZYG16JFi7CdffbZYTv55JMrcZx69+KLLyb7hhtuGLYFCxbU9XHIRPfu3ZP9mmuuCdsWW2xR6plvvvlm2Hbbbbewvf3226WeR/PQrVu3sF155ZXJa3fdddew/fDDD6XP1FScdtppYfvd736XvHbNNdcM26xZs8oeiYwdeeSRYbv44ovDNnTo0LCdf/75YXvsscfCtueee4YNyqppLpsxY0bYWrduHbaWLVuGbd68eWFbfPHFw5aar+68886wFUVRHHfccWH76KOPwlbbHfRNWW3/N3qzDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmrN6rpdTaiGWXXTZ5bdu2bcO23XbbhS21PizVUmuKHnzwwbA988wzYSuKoujXr1+yQxmplY5jx44N2zLLLFPqeS+88ELYfv/734etptWU5K9nz55h++CDD5LX3njjjWE78cQTwzZt2rQaz9UUpFbvnXXWWclrV1xxxbBNnjy57JHI2IQJE8L20EMPhW348OFhu+2228K23HLLhW2bbbYJW1EUxXfffZfsUNdSa/JWXnnlsKVmj4MPPjhsZ555ZqnnFYXPR4rVewAAANBMGfYBAAAgM4Z9AAAAyIxhHwAAADJj2AcAAIDMGPYBAAAgMy0b+gBNxYIFC8L22Weflb7vpZdeWvraSGoFDDQ2zz33XNgOOeSQsI0ePbrU89Zff/2wpdZWWr3H3Llzw/btt98mr913333Dllo/mfoMPP3002GbP39+8jyVsNNOO4VtyJAhYfvkk0+S9505c2bpM5GvoUOHhm2dddYJ2x577FHnZ9loo43CtuWWWyavvfvuu+v6OJA0b968sKXW66W8/PLLYWvTpk3YFlvMe+dK8y8MAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGas3svQhx9+GLYvv/wybL169Uret0OHDmH76quvaj4Y/BeHHnpo2C6//PJ6PElRDBgwIGwjR46sx5PQGH366adhS62WK4qiuP3228O28sorh+3RRx8N27Rp08JWXV0dtrvuuitsRVEUO+64Y7JHOnXqFLZWrVqF7Y9//GPyvn6/8N9stdVWYUuts/v4449LPa+qqqpU22STTZL3tXqP3KXmh5YtjaKV5s0+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJmx7yBD06dPD9u7774bto022ih53zZt2oTNaqTmbZtttgnbySefnLw2tZYotT6sEl544YV6fR75GDNmTLLvsMMOYTvppJPC1q9fv7B17ty55oP9F4ccckiyV+Jzd80114Tt0ksvrfPnkb/U9+mGG24YtjXWWCNsr7/+eqnnpf7uqulnAzRnqc9jURTF+PHj6+kk+fJmHwAAADJj2AcAAIDMGPYBAAAgM4Z9AAAAyIxhHwAAADJj2AcAAIDMWL3XzIwaNSpsNa3e69u3b9juuuuu0mei8dhuu+3CdvDBB4dt2223DVurVq0W6UxlnH322WF7+eWXw3b33XdX4jg0AwsWLEj2e++9N2z3339/2FI/d3fdddewbbzxxmFLrSUriqKYO3du2FK/Qy655JKwpT53Cxcu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ZMawDwAAAJlp2dAHyEGlVuulVhVtttlmFXkm1LWPPvqooY/wf3jxxRdLXbfxxhsne2qVzdSpU0s9E5qK9957ryL33W233cJm9V7TsOWWW4btww8/DNuAAQOS9/3hhx9Kn6mMyZMnhy31/b/WWmsl7ztt2rSwff755zWei3y1atUqbOecc07y2rLr9VKfq/3337/UPVNq+nyU9fDDD4dt5syZFXlmY+XNPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZsXqvloYNG1bvzxw+fHi9PxOas1mzZoWtQ4cOyWv79u0btrvvvrvskaBJGDp0aEXuO27cuIrcl8ahffv2YevRo0fy2nfffbeuj1Na9+7dS1978sknh+2LL74ofV+avgsvvDBsgwYNKn3fb775Jmy777572GbMmFHqeauttlrYfv3rX5e6Z03GjBkTtgULFlTkmY2VN/sAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJlp2dAHaCo23XTTen/mE088Ue/PhObszjvvDNuQIUPq8STQ+PTq1StsO+ywQ+n7fvXVV2G79NJLS9+XxuHdd98NW6dOncJ23333Je+77bbbhu3999+v+WA/0Z/+9KewtW/fvvR9x48fX/pamr7OnTuHLfU9vihuvvnmsN1///11/rytt946bO3atSt9388//zxsI0eOLH3f3HizDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmrN77kYEDB5ZqQB5Sq1oOOOCAejwJVE7btm3DNmLEiLDtsccepe5ZFEUxc+bMsJ133nlhmzFjRvK+NH6PPvpo2KZOnRq2n//858n7PvTQQ2H71a9+Fba33347bFtssUXYTjjhhLAttlj87mz06NFhK4qi+PTTT5OdvP3xj38M2yqrrFL6vk899VTYfv/735e+bxnrrLNORe47bty4sH3//fcVeWZT5M0+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJmxeg+gFqqrqxv6CFBrf/nLX8K20047ha1bt24VOE1RvPvuu2G76KKLKvJMGofUarntttsubA888EDyvj169AjbpEmTwvbYY4+FrUOHDmFLrdf75JNPwnbwwQeHrSiKYs6cOclO3jbffPOK3Pedd94J2/z58+v8eS1atAhbnz596vx5RZH+3PH/8WYfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADIjGEfAAAAMmPYBwAAgMxYvfcjAwcObOgj/B9Sq74222yzsD3xxBMVOA2Uk1qPVBRFscIKK4Rt6tSpYVt66aXD9vzzz9d8MGjiBg8eHLYjjjgibKkVYgsXLlykM0U23HDDsJ1wwglhu/DCCytxHBqJ1Iq8rbfeOnntI488ErZlllkmbFtttVXYqqqqwpb6m+xXv/pV2GbNmhU2qJQ77rij1HWpdZidO3cOW2r13mqrrVbqLDVJrXTl/+PNPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZsXqviXr88cdLXTd8+PCwDRs2rORpaO722muvsJ111lnJa7t37x62uXPnhq1ly/jH12OPPRa21157LWyplZY16dChQ+lroYzp06eH7bvvvgtbavXehAkTwrbBBhuErW3btmGrSWpN4FVXXRW2r776qvQzafxef/31ZF955ZXDllpBvM4664QttXrvlVdeCZsVYDQ2l19+edhefPHFsO25556VOE5pDz/8cNhuvvnmejxJ0+XNPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZqaqurq6u1Rcm1pHkYuDAgWEru+oO6/5y0aNHj7BNmjQpbO3atUveN/WzpZY/nurMopzl1VdfDVu/fv3C9v3339d4LvipVllllVLXpVaI9erVK2w1rUBab731Sp3nhBNOCNuIESNK3ZM8LL744mFLfT/usssuYSv7O2DLLbcMm78fSdl7773DlvobOfXzOCeXXHJJ2I4++uj6O0gjVNu/kb3ZBwAAgMwY9gEAACAzhn0AAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzVu/VUmotX6rV5Mwzzyx9bVNh9V4eLrjggrDtuuuuYUutziqKohg9enSp86RWAf7mN78J2znnnBO2Sq0BfOSRR8KW+rebPXt26WdCfdp+++2T/a677ip13x122CFs999/f6l7kodOnTqF7Ysvvih1zz/96U9hO+mkk8K2cOHCsNW0dvL111+v+WDwH3bfffdkX3LJJev8mfvvv3/YNttss1L3XLBgQbJvtdVWYWvuay2t3gMAAIBmyrAPAAAAmTHs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hn0AAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADITMuGPkBTscQSS4Tt+uuvT1678847hy216uuJJ54I29ChQ8P273//O3meyLrrrpvsqdV7/fr1C1vfvn3DNnHixLC9+uqryfMANAW9e/cufe3NN98cttRaptTvgbK/I6CMXXfdNWypFcOp7/2iSK/Q69GjR9hOO+205H0jl156adhSn8XUSrKiKIpzzz231HlSq4tT6z5pOtZff/2wXXfddclrU39D77///iVPRFPkzT4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmbF670dS6/XGjBkTtm233TZ53+nTp4ftrLPOCttll12WvG8ZqbOOGjWqzp9XFEWxzz77hM16PRqT6urqsKXWZELKHnvsUfra4cOHh+39998vfV+oL6mfnal2/vnnl37mKaecErb27duH7eSTTw7b+PHjS53lggsuSPYVV1wxbAcffHDYUmud+/fvH7ay/zuojNR/q0p9PmhevNkHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDNW7/3I9ddfH7bUyrpJkyYl77vffvuVvraM7bbbLmypFYKtWrVK3je1AiS1WqZSK/0AGovU+qxll1229H0HDBhQqvXp06fU8+6+++6wTZw4MWwzZ84s9TzykFpd3Lt377Cl1p2+++67pc/TunXrUs9cfvnlSz+zrCOPPDJsqc/xuuuuG7ZddtklbFbvNS6pFYqp79WOHTsm77vnnnuGrXv37mHr1atX2FLrXm+//fawTZ48OWxUnjf7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZadnQB2hMevbsWeq6P//5z8k+adKkUvdNSe1eHTNmTNhatWoVtu+//z75zMsuuyxsjz/+ePJagJx16dIlbDXtQ0659tprS19bxqGHHhq2CRMmhO20005L3jd17fz582s+GI3atttuG7att946bI888kgljlMstdRSYfv666/Ddvnll1fiOElz584N27hx48K27rrrhu23v/1t2M4///ywTZ06NWyUl/odsP3225e65zPPPFPyNOVVVVWFbfjw4WF74IEHwrbffvsln5n6vFI73uwDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBmr9+pATav3vvzyy7Ddf//9Ydtyyy3Dllqdklqv9/HHH4ftsMMOC1tRpM/aunXr5LUAOXv77bfD9sknn4StW7dulThORfTr1y9sNa1fPeWUU8J23nnnlT4TjcN6661X6rrXXnut9DO32WabsKVWAb7yyithS32OG0Lqs3HkkUeGrUOHDmHr27dv2O6+++7aHYyfZNasWWG79957w3b00UfX/WEqJDV7DB48OGy333578r6pdbCTJ0+u8Vx4sw8AAADZMewDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZqze+5FJkyaFLbWqZNlll03e97bbbgtbauXG9ttvH7Z27dqF7cMPPwzbscceG7Zx48aFrSbffvtt6WsBmroZM2aE7Z///GfYVlxxxeR9n3zyybC99957YVt55ZWT9438+te/DtvAgQNL3bMoiuLUU08N2wsvvBC2Rx99tPQzqT9rrrlmqevuvPPO0s88+eSTw5ZaAzZmzJjSz6xv33//fdhS/3ZDhgwJW9mfDVRG6r9jaqVlamYpiqK49dZbS58p0rlz57D94Q9/CNv6668ftq233jr5zAsuuCBsu+++e/Ja/jdv9gEAACAzhn0AAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADITFV1dXV1rb6wqqrSZ2lwqVUtZ599dthOPPHE5H1r+U9cZ/bYY4+wjRo1qh5PAoumvj87Kc3hZyC0aNEibL/61a/CduaZZybvu+6664btrbfeCltq7a2Vr43H2LFjwzZ48OCw9e/fP2zPPPNM8pmp75tVV101bKkVYRdeeGHymY3JNddcE7YDDjggbHfddVfYdtppp0U6E81X69atw3b++eeH7Ygjjij9zE022SRs48ePL33fpqK2fyN7sw8AAACZMewDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZlo29AEak7lz54Yttarl7rvvTt73H//4R9hWXnnlmg/2X1gDBkBdW7BgQdjuueeesM2cOTN539TvyV/+8pdhO/zww8N2wQUXJJ9J/UmtgKrUCtWbbropbEceeWTYmsMK4ob470Hz9v3334ft1FNPDdtaa62VvO+AAQPCtvPOO4etOazeqy1v9gEAACAzhn0AAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADIjNV7dWDChAnJ/sADD4St7Oq91OqU1Dqi+fPnh23MmDGlzgJA7Gc/+1nYalpZ11TUtObo7LPPDttFF10UtiFDhoTN6r2mYdasWWH7/PPPS9/3nHPOKdWA+vXNN9+ErabPamqG2mabbcLWrl27UufJkTf7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGTG6r160Lt377B9+eWXYTvllFPCdvHFF4etR48eYTvjjDPCdu+994atKIpi7ty5yQ51raqqKmyp9ZNQ35Zffvmw3XPPPWE79NBDk/d97rnnSp+pMbnjjjvCdv7554etY8eOYevSpUvYpk6dWqtzUXmp/4bbb7992C655JIKnCYf66+/fqnrXnrppTo+CZT32GOPJfvEiRPDtsEGG4StW7duYXvnnXdqPlhGvNkHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDOGfQAAAMhMy4Y+QHOw5pprhu3mm28O29VXXx22KVOmhG3s2LFhW2uttcL25z//OWxFURRHHnlkskPOqqurk72qqqqeTkJjdPrpp4ct9XN39dVXT973ueeeK32mxiT1O+vSSy8N2zHHHBO2nj17hm3q1Km1Ohd147777gvb4MGDw/aHP/whbJdccskinSkHqe//1N+Wqd9Xr7322iKdiZ9u0KBBYRs3blw9nqTxWbBgQbLPnz+/1H1XXnnlsL3zzjul7tlUebMPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGas3qsDffr0Sfb27duHbcaMGaWe+cADD4Tt8MMPD1tqnd+uu+6afOZZZ50VtunTpyevhdzVtJovYmVfHmbPnl3qumOPPTbZW7duHbbLL7+81DMbm06dOjX0EVhEn3/+edhSP+O6dOkStl122SX5zNGjR9d8sCYgtZrzxBNPDFvq3zX1N9ndd99du4NRZwYMGBC2gQMHhu3444+vwGmalvfeey9s/fv3r8eTNF3e7AMAAEBmDPsAAACQGcM+AAAAZMawDwAAAJkx7AMAAEBmDPsAAACQGav36sD666+f7IsvvnjYKrF269prrw3bjjvuGLZBgwYl77vvvvuGbcSIETUfDCBTqdWku+22W9h++ctfJu977rnnhm3nnXcO22233Ra2559/Pmxvvvlm2BYsWBC2hQsXhq0oiqJdu3Zh23777cP20Ucfhe2NN95IPpP6k1qP9dlnn4UttXrvhhtuSD5ztdVWC9v5558ftrlz5ybvW8Zii8XvztZee+3ktXfddVfYll122bCl1r3edNNNyWdSv1JrIp999tmwpeaL1O+GoiiKF198MWzffvtt2ObMmZO8byT1GWjZMh43l19++eR9U6sJU5+B1M+k5sabfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyU1Wd2lvw4y+swIq4XBx66KHJfvnll4dt3LhxYRs8eHDpM0X69+8ftqeeeip5bWp9Trdu3UqfCepaLX+sNQp+tubvqquuCttBBx1UjydZNKnfEVOmTEleu+2224Zt6aWXDts777wTtprWFtI4pFZrvfLKK2FLfV8URfrn/MSJE8M2derUsJVd17X66quHLfW9vygeeuihsKXWfX7zzTeVOA4JqdWjqdV7qe+rRfk7J7Vi9f333w9b6u+V1JrxTp06ha2m1ZStWrUK29tvvx22DTbYIGy5fAZq+z3gzT4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmbF6rw706tUr2f/1r3+FrUWLFmF7+OGHw/bSSy/VfLD/Yp999gnbiiuumLx25MiRYTvwwANLnQcqobGt3vPzs3lbaqmlwnbcccclrz3qqKNK3bcpefnll8N28sknhy31O5KmoU+fPmEbNWpU8tru3bvX9XGSUj/HF+V3zueffx62G2+8MWxnnHFG2L7//vvS56F+lf0M1Pf3f1FU7jNQVmqmufXWW+vxJA3D6j0AAABopgz7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBmr9+rB448/HrZNN920Hk+S9u233yZ7akXUVVddVdfHgYqo1HoYPyOphA033DBsRxxxRNj22muvShwn9MILLyR7agXtqaeeGrYvvvii9Jlo2mpaLZlaPbfmmmuGrUOHDmFLrTPr0qVL2CZOnBi2Y489NmxFURQffvhh2D755JPkteQt9RlYb731ktf+5je/CVvqM7DLLruErRKr92r63XHeeeeFbezYsaWemQur9wAAAKCZMuwDAABAZgz7AAAAkBnDPgAAAGTGsA8AAACZMewDAABAZqzeqwcdO3YM2z333BO2fv361flZpk+fHratt946ee2rr75ax6cBAADgp7B6DwAAAJopwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmamqruWSvqqqqkqfBQAAAEio5QjvzT4AAADkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkpmVDHwAAAH6KF154IWwdO3YM2yabbBK2zz77bFGOBNDoeLMPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGas3gOARdSiRYuwnXfeeWGbNm1a8r4XX3xx2BYuXFjzwSBT8+bNC1uvXr3C9utf/zpsV1xxxaIcCfgvOnfuHLbZs2cnr50zZ05dH6fZ8WYfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADIjGEfAAAAMmPYBwAAgMxUVVdXV9fqC6uqKn0WAGiS+vfvH7Ynn3yy9H1XXXXVsL3//vul7wtN3aBBg8J2zz33hG3UqFFh23333RfpTJCz7bbbLmybbbZZ2I4++uiwffDBB8lnPvvss2E77LDDwtYcVvbVcoT3Zh8AAAByY9gHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzLRs6AMQO/HEE8N28sknh61jx45h++qrr0rdsyiK4oorrkh2+G9WWGGFZP/oo4/Cllr5ecwxx4Rto402CtuGG24Ytn79+oVtypQpYYOll166oY/QoNZee+1kP+KII0rdd/jw4WFL/ewgf2VXT6Z+znfp0iVsU6dOLfU8aAgtW8YjXt++fcO26667hu3QQw8N29y5c8P22muvhW3MmDFhK4qi+Pjjj8O22GLeWdeGfyUAAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADIjGEfAAAAMlNVXV1dXasvTKzAIq1NmzZhu+mmm8I2ePDgsKX+e6Ra6j93Td8KvXr1CtvkyZOT19J8PfPMM8meWpP37LPPlrqurNTzNt544zp/Hk1L6mf5gw8+GLZF+d5ZddVVw1Z29Vgl3HbbbcmeWuc0e/bssKVWZb7zzjs1H4xstW/fPmwTJ04M2y9+8YuwnXTSSWE7//zza3cwqAep7+OiKIrddtstbKmVptOmTQvbBRdcELbUCj0zQmXUcoT3Zh8AAAByY9gHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzLRs6AM0B5dccknYdtxxx7ClVioMGjQobK+99lrYnnzyybD17NkzbEVRFBdffHHYdt555+S15G2FFVYI26KsyEtdO2XKlLA999xzYUuto6nEOj/yscoqq4St7Hq9p59+Otk//fTTUvethG7duoVtwIABpe971FFHhc16PSJff/112KZPn17qnj169Ch7HKhzyyyzTNief/755LVz5swJ28yZM8M2atSosL300kthS627TPnoo4+SfdiwYWFLrXaeMWNGqfPkyJt9AAAAyIxhHwAAADJj2AcAAIDMGPYBAAAgM4Z9AAAAyIxhHwAAADJj9V4d2GCDDZI9teorZd68eWG7//77S93zsMMOC9u4ceOS1/bu3TtsLVvG30rz58+v+WA0aTWtTimre/fuYUut3kvZfffdw3bbbbeF7Zhjjkned8SIEaXOQ/M2derUZP/uu+/q6SQ1O/jgg8PWuXPn5LWpVU+pdU4AOdtvv/3Cduqpp4ZtySWXTN439Td76vdO6r4dOnQI2/HHHx+222+/PWw12XPPPcM2evTosA0ZMiRskydPLn2epsibfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzMTL0am1oUOHJvv06dPD1r59+7B99tlnpc8UefDBB8NW0z7nn//852G78cYbw7bXXnvVfDAavQ033LAi991jjz3CNmXKlDp/3rPPPlvqumOOOSbZR4wYUeq+NG8jR45s6CPU2vLLL1/62jfeeKNUgzImTpwYtv79+4dtgw02qMRxaAZat24dtosvvjhs++67b9jatm0btpp+d3z44YfJHvnhhx/CNmvWrLDdcMMNpZ5Xk+uuuy5sL774Ytiuv/76sI0bNy5sF110UdgWLlwYtsbMm30AAADIjGEfAAAAMmPYBwAAgMwY9gEAACAzhn0AAADIjGEfAAAAMmP1Xh146qmnkv3Pf/5z2P71r3+F7YMPPih9psjSSy8dtpYty387vPLKK6WvpWm4/fbbS103atSoity3rEqs84OyOnfuXO/PHDJkSNjatWsXtl122aUSx4E699prr5W6bq211gpbr169wvb++++Xeh5NS4sWLcI2duzYsG299dZhe/3118N2wgknhO3hhx8OW3OR+rc74ogjwvaPf/wjbNOmTQtbpdYLVpo3+wAAAJAZwz4AAABkxrAPAAAAmTHsAwAAQGYM+wAAAJAZwz4AAABkxuq9OrAoqxjeeuutsA0cODBsl156adiefvrpsKVWJ7Vu3TpsRVEUCxYsCFtTXUdB7a2wwgqlrjvuuOPq+CQN4+OPP27oI5ChK6+8MtnPO++8On9mly5dwlZdXV3nz4P69uWXX5a6LrVarV+/fmGzei8fiy0Wvwc999xzw5b6m/25554L2/bbbx+2mTNnho20SZMmhe30008P26233lrqnq+++mqtztUQvNkHAACAzBj2AQAAIDOGfQAAAMiMYR8AAAAyY9gHAACAzBj2AQAAIDNW7zWw1Aq9q666KmyHHXZY2A4//PCwLcpapbPPPjtsU6dOLX1fmoY99tgjbB999FHYpkyZUonj1LuNNtqooY9AA/v888/DdtNNN4Vtn332CdsSSyyRfGbnzp1rPlgTMGHChIY+As3IPffcE7b58+eHrWXL+M/iX/7yl4t0JpqGU045JWzHH3982O66666w7bTTTot0JupWaoVeVVVV2DbZZJOwWb0HAAAA1BvDPgAAAGTGsA8AAACZMewDAABAZgz7AAAAkBnDPgAAAGSmqrqWu9hSqwgor0WLFmHbddddw/brX/86bH369AnbiiuuGLY33ngjbEVRFFtuuWXYUiupoKlI/Th89tlnk9duvPHGdX0cMrHvvvuGbauttkpem/pZX9YjjzwStv333z9s48ePD1vv3r2Tz3zqqafCtummmyavhbqU+v7fYostwpb6/h8wYMAinYnGI/Wzqm/fvmHr169f2F566aVF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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# show the true data\n", + "fig, axes = plt.subplots(nrows=5, ncols=5, figsize=(10, 10))\n", + "\n", + "for i in range(5):\n", + " for j in range(5):\n", + " axes[i][j].imshow(eval_dataset[i*5 +j].cpu().squeeze(0), cmap='gray')\n", + " axes[i][j].axis('off')\n", + "plt.tight_layout(pad=0.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualizing interpolations" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "interpolations = trained_model.interpolate(eval_dataset[:5].to(device), eval_dataset[5:10].to(device), granularity=10).detach().cpu()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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ERERkYXhy76IiIiIiIjIwvBlX0RERERERGRh7Dj13n57H7brCa+qOvbYY6fyaaedNtRde+21w/WVV145lenvf+yxx6bynXfeOdT9/Oc/n8r0x3Ye5U32jHQapK+S3u48F+GUU04Z6i699NLh+pxzzpnKbKsnnnhiKj/00ENDXecRX4pnfB1vcXq7eS5C6nHCCScMdWeeeeZwnXrRl//8889P5WeeeWaoe/PNN6fy3NNW7pTOX08/fc5RPIsizwrJMytYV1X10Y9+dCpnn6+qeu2116YyzxjJMy04HjZ1TurGQ9XY5l0d5/1jjjlmKlNHfk/OdezX77777lTO9aKq6re//e1U3tT2J6vOm+C6kOR8leWqUY915nbeP8/W4Tk7S1kjklV6ZL9nH+zmstQj+3HV4elx83v5PLlvOohzdvabdcZHpwe/J9cTrgnUo0uXm2NgieOB7JYeJMfVKj268ZFaLWXPtA7dHqr7LLVKHanHOv18088Y8S/7IiIiIiIiIgvDl30RERERERGRhbHtMP6DCFvYaZhyhu5/7WtfG+r+/M//fLg++eSTp3KGiVdVPf3001OZ4TcZNsLwEoafdOE4cw7P6cJkGWqZGjC94QUXXDCV/+zP/myo+/znPz9c57995JFHhroMDWebd2Gg6+hx0OE5nVWiCyHurCwnnnjiUJd6sP2vuuqqLe/5wAMPDHXZdi+//PJQlxowVKoLV5tb++c1+xGvM5yS4fhph6C16BOf+MRUTltRVdXZZ589XGcYGq0sd91111Sm5SLDllfNOQetQdLN+wwL53VaIHI8VI1tfuqppw51eU1by/HHHz9cZwrQxx9/fKh78MEHpzK1ytStXWjn3OhCK9nnuQ6kBYVzUurRWYlogaHmaV956qmnhrrUJy16VVXvvPPOVKYec6YLzWefT/sW6zkGzjrrrKnMOSjHAMPvef3iiy9O5eeee26oy/3Vk08+OdSl7WWTQsq590g92P4cA1l/+umnD3WpB8dHznPZj6sOT1GcGrz00ktb1r3wwgtD3S9/+cupPOc9K6EeOV9wLucYyPq0l1aNeuQ7RNU4Jl955ZWh7qc//elwne2cNryqcezwe3IfMOf1gnTz1XHHHTfUsV1POumkqXzJJZcMdWecccYRP1c1ttWhQ4eGulyHq8Yxke8bq+py3purHv5lX0RERERERGRh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C4N6zPnchHXY7hhgG6fvm2eK8DwSnpWR5NkHTBOYPnym0+t8+Zs0VtaZk7Jfs03zXASm7GMq1zwLgG2VqULTv181njnC82A6X/5u6aFnX0REREREROQoxJd9ERERERERkYVhGL+IiIgsijnbjub8bLtJF5q+09+8F221yXqsszffDT32I5XuUvVg+Pl2bUekC80/2vVYZfVKOj1Yt9O04Htha1mnr/wz/mVfREREREREZGH4si8iIiIiIiKyMHzZFxEREREREVkYf7L6IyIiIttnkzx+RwNHox5Hw2+cO5ua9muTWKdd87NznhPm/Gyr6J61833P+TfP+dnIOmcY0Ou+3TMtDvr37+QMPf+yLyIiIiIiIrIwfNkXERERERERWRiG8YuIyK5y0GFuMqIe80I9RLbG8TEvjhY9ut85pzbYybP4l30RERERERGRheHLvoiIiIiIiMjC8GVfREREREREZGFs27P/vve9b7jOlAVz8jLMHaZM+OM//pf/b2EaiO7f8Vo9dofUo2tH9dgf1GNeqMe82G4KHvXYH9RjXmS7sh27NFs5z1UtU4+D+B3qsTVz+x2dHnNOS7dbzPl3mHpPRERERERERHzZFxEREREREVkavuyLiIiIiIiILIxte/bpX5izn2HOsN12y3ukHrvDbnmP1GN3UI950Z0rsg7qsTvsVjuqx+6gHvNit/Jmq8fuoB6bwzpn8qjH/rKT9vYv+yIiIiIiIiILw5d9ERERERERkYWx7TB+2RsMf5kX6jEv1ENElsZOUifJ3qEe80I95oV6zAtT74mIiIiIiIiIL/siIiIiIiIiS8OXfREREREREZGFcdR79rfrfaB3uPt3e+EzXuf+m4x6zIud6rHfHC16JHNOf6Me6nHQzFkPprD84z/27y4HydE4PubMnMaqqMfcMPWeiIiIiIiIiPiyLyIiIiIiIrI0dhzGn2FOexXi0YVSbTfMap3wOP6O/Lfdb2QdQ/T2IwRmTnp0n1ulR/c7urps81W/f4l6rDNW8vp973vftu/Bft3V5fV+jIdV88Gc9cgxsVt6/OEPfxjq8pq/Xz3GutRglR4J9UioRzc+9oK569HVdXp0z8o27+rWWT92g73SY51/t1M9/uRP/mXbuEqPvGabb7duDiHEu6EH6fY3nR7cQ62jR/bzuemx3fma7JYe63y20+NP//RPpzL1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KenafD9C6peixyp2mtp4u3W7xdGiR7JJqULlYFGPebETPfzLvoiIiIiIiMjC8GVfREREREREZGH4si8iIiIiIiKyMDbGs995WdOjX1X10Y9+dCqfeuqpQ91pp502XJ900klTmb78t99+eyo/99xzQ1369Duv5ioO2hu1U6hHXtNLnD78E088caj7+Mc/PlyffPLJU5le77feemsqv/DCC0Ndfpb+2KXq0fl2sq7z5fMMC+qTevB+P/3pT6fyiy++ONT97Gc/m8rreMR32v5zT13VeVff//73T2WeYZFzWVXV8ccfP5Wp67vvvjuVX3/99aGOZyokR7uHn3rk2sK5jGdafOQjH5nK1CPPTcjxUHX43JbshYd/VYrNOZNrC9uY61COpe6MEZ5pkWNgP85Q2OQxtl0fftWoF88fyXZeZ83eDz02aXwkq56721Pv9NyK/dBjk8dLRzeW9jIN27ostf075nymhan3RERERERERMSXfREREREREZGlcaBh/OuEgDEk78Mf/vBUZqhrhuafeeaZQ91FF100XGeY8vPPPz/UZahfl76M4WkMj8pwNYbMbpeDSC22zm/O8FaGIp9wwglT+ayzzhrqrrzyyuH69NNPn8oM1X/88cen8muvvTbU5fOs0qML2dwuc0j11qWZynBWhh4fe+yxU5nj49prrx2uzzjjjKn8yiuvDHWPPfbYVE7LS9WYGpFtvI4e2a7btS3w3+0XXTrQLv1kZzvi+EjbC0P1n3nmmanM1JQZNr4qZDX1WCeccjdCEneT7nm6lHk5f6Vtoqrq3HPPHa5zbmOofs5R7Ndpueieu6rXY7vMISx5u6l0u7ks1/2qca2vGrXL9J9VVb/4xS+mclrCqg4fLx1duPN2mYMeCZ+nGzs5f6U2VYev/VnfpTZObarGvdeqPdNuzC1z04PsdC7jvjm16/Y+HDt5zX+3H3N91z8Pem0h3Vy26nqre9DmwuvkoNvjoEPc15nLVl1vVccx0KXSnQv+ZV9ERERERERkYfiyLyIiIiIiIrIwfNkXERERERERWRiz8ux3Kanoy8/r9E1WVZ199tlT+brrrhvq0hNetX1vGL1P6cvoPIb8LFMupRfqoNO/rKMHvZN5zXRuqceNN9441F1wwQVbPg894nn/Y445ZqjLtqMe1C51pvdpHW/zXrNKj/xdbI/0TnLspE+felx2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6sH3w9SH1rscS0wvnnrw3WQu+Jd9ERERERERkYXhy76IiIiIiIjIwvBlX0RERERERGRhHKhnn3Qeftal34i+MfowuntsN8VM55deqkem+130gqX/jr6xvKYPpvPMdGcGdOktlnJmwiq6lFTpZaXvOD1F9MdSn6znPdIrxnscjXok7Lvp2e7OMMhzKaoOT2mYqaz4PTkndulA1WOcP1iX6wm99vTs33XXXVveM+fETvOlrh/rkO3BNaGb53j+Raaf5DrEsZXkPXmP7nmWSrYB2zF/P+vYzx988MGpfMwxxwx1qV23h2N7H4165G/s9rddysKqqkcffXQqM+1Y7guYWoz6yL/Q6cGzDtiOeYYV0/J1qd66c8mOxvHRke1BPTh/5blHHB85J/JMi/ye/dCj63Nb4QgWERERERERWRi+7IuIiIiIiIgsjFmF8ZMuHU9e7ySkYd1/e9ChMHMIzdmu5YEh9l0qq1UhL1vRpavaj7aZgx5Jl1qsS2nJNCEMa8qwwC68tQsbJ3vRVnPTo7MksW1yvLzyyitDHUOPP/zhD0/lLmVcl95vP1KLzU0P0oXRZx3D9hnWf+KJJ05lzm2pOXVM7Y7G+Yp0a33WMT3VvffeO1y/8cYbU5lzW9qOmHaM6au2uv9uMXc9km6+YkrkDNuvGtOHcb5KDTjvdemrdqut1rF7zokufRvn/ccee2y4zhRu/J5MFcp5j3PbXpB6dPbOudE9G+eVp556ari+5557pjL7XKY25vqR4+q9vP90bKoe66SoPnTo0HCd6wktMWm54LyXcyKtGruV+rvTYzv4l30RERERERGRheHLvoiIiIiIiMjC8GVfREREREREZGH80T9t05y0V76QOTFnH92cn22voPdlv335HXy2OXuYyDpjOT/b/eb98IF3LEWPVSmO8rNd2qvuTAuyF1ptsh757KvSuqYGnR5d6r11tNopS9WDvspMrcS69IxTj/Rcduef7BarUtbNmW4OYoqwTFHFtFfpn00PMq+7NLu7xVL1YIqwj3zkI1OZeuQ5CTxfIbWiHnvRVhy7vOec6fTIMxOqqo477rip3OnBMxPyetV5SbuxnixVjzwDqWo8g4e/OfXgWQypB88F6M6g2SnsK+wDR8K/7IuIiIiIiIgsDF/2RURERERERBbGtsP4u5DqpTDnUPlVoddzelaSz77Oc8459HTOfWUVO9Vjp6GO+9FWS9WjG/e7FVqnHiM573TpJglDmBmavBWmQuzp9Oj2JQxhzvDKddpjt9adTU31RnaqB0OYM/S106NLpfVe2NTUYmSnejCEebvp9bq0su+Fo12PtFhU9Xp044N67HS/txQ9ut+/W3ps9R1VfSrybr/X1VGP7YxB/7IvIiIiIiIisjB82RcRERERERFZGL7si4iIiIiIiCyMbXv2RURERERERGQz8C/7IiIiIiIiIgvDl30RERERERGRheHLvoiIiIiIiMjC8GVfREREREREZGH4si8iIiIiIiKyMHzZFxEREREREVkYvuyLiIiIiIiILAxf9kVEREREREQWhi/7IiIiIiIiIgvj/we+J/b9lKOjegAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# show interpolations\n", + "fig, axes = plt.subplots(nrows=5, ncols=10, figsize=(10, 5))\n", + "\n", + "for i in range(5):\n", + " for j in range(10):\n", + " axes[i][j].imshow(interpolations[i, j].cpu().squeeze(0), cmap='gray')\n", + " axes[i][j].axis('off')\n", + "plt.tight_layout(pad=0.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "95022f601a219c6b6d093149c9a9b9a061a4446d3680d89cef8a1f82970031f2" + }, + "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.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/src/pythae/models/__init__.py b/src/pythae/models/__init__.py index ba45191e..40284ac6 100755 --- a/src/pythae/models/__init__.py +++ b/src/pythae/models/__init__.py @@ -24,6 +24,7 @@ from .disentangled_beta_vae import DisentangledBetaVAE, DisentangledBetaVAEConfig from .factor_vae import FactorVAE, FactorVAEConfig from .hvae import HVAE, HVAEConfig +from .hrq_vae import HRQVAE, HRQVAEConfig from .info_vae import INFOVAE_MMD, INFOVAE_MMD_Config from .iwae import IWAE, IWAEConfig from .miwae import MIWAE, MIWAEConfig @@ -96,4 +97,6 @@ "MIWAEConfig", "PIWAE", "PIWAEConfig", + "HRQVAE", + "HRQVAEConfig", ] diff --git a/src/pythae/models/auto_model/auto_config.py b/src/pythae/models/auto_model/auto_config.py index 3a2d55a2..fae89d18 100644 --- a/src/pythae/models/auto_model/auto_config.py +++ b/src/pythae/models/auto_model/auto_config.py @@ -60,6 +60,11 @@ def from_json_file(cls, json_path): model_config = HVAEConfig.from_json_file(json_path) + elif config_name == "HRQVAEConfig": + from ..hrq_vae import HRQVAEConfig + + model_config = HRQVAEConfig.from_json_file(json_path) + elif config_name == "INFOVAE_MMD_Config": from ..info_vae import INFOVAE_MMD_Config diff --git a/src/pythae/models/auto_model/auto_model.py b/src/pythae/models/auto_model/auto_model.py index 7d875c0d..d103ac29 100644 --- a/src/pythae/models/auto_model/auto_model.py +++ b/src/pythae/models/auto_model/auto_model.py @@ -74,6 +74,11 @@ def load_from_folder(cls, dir_path: str): model = HVAE.load_from_folder(dir_path=dir_path) + elif model_name == "HRQVAEConfig": + from ..hrq_vae import HRQVAE + + model = HRQVAE.load_from_folder(dir_path=dir_path) + elif model_name == "INFOVAE_MMD_Config": from ..info_vae import INFOVAE_MMD diff --git a/src/pythae/models/hrq_vae/__init__.py b/src/pythae/models/hrq_vae/__init__.py new file mode 100755 index 00000000..770b4a93 --- /dev/null +++ b/src/pythae/models/hrq_vae/__init__.py @@ -0,0 +1,19 @@ +"""This module is the implementation of the Vector Quantized VAE proposed in +(https://arxiv.org/abs/1711.00937). + +Available samplers +------------------- + +Normalizing flows sampler to come. + +.. autosummary:: + ~pythae.samplers.GaussianMixtureSampler + ~pythae.samplers.MAFSampler + ~pythae.samplers.IAFSampler + :nosignatures: +""" + +from .hrq_vae_config import HRQVAEConfig +from .hrq_vae_model import HRQVAE + +__all__ = ["HRQVAE", "HRQVAEConfig"] diff --git a/src/pythae/models/hrq_vae/hrq_vae_config.py b/src/pythae/models/hrq_vae/hrq_vae_config.py new file mode 100755 index 00000000..51aa0131 --- /dev/null +++ b/src/pythae/models/hrq_vae/hrq_vae_config.py @@ -0,0 +1,37 @@ +from pydantic.dataclasses import dataclass +from typing import Optional + +from ..ae import AEConfig + + +@dataclass +class HRQVAEConfig(AEConfig): + r""" + Hierarchical Residual Quantization VAE model config config class + + Parameters: + input_dim (tuple): The input_data dimension. + latent_dim (int): The latent space dimension. Default: 10. + num_embedding (int): The number of embedding points. Default: 64 + num_levels (int): Depth of hierarchy. Default: 4 + kl_weight (float): Weighting of the KL term. Default: 0.1 + init_scale (float): Magnitude of the embedding initialisation, should roughly match the encoder. Default: 1.0 + init_decay_weight (float): Factor by which the magnitude of each successive levels is multiplied. Default: 1.5 + norm_loss_weight (float): Weighting of the norm loss term. Default: 0.5 + norm_loss_scale (float): Scale for the norm loss. Default: 1.5 + temp_schedule_gamma (float): Decay constant for the Gumbel temperature - will be (epoch/gamma). Default: 33.333 + depth_drop_rate (float): Probability of dropping each level during training. Default: 0.1 + """ + + num_embeddings: int = 64 + num_levels: int = 4 + kl_weight: float = 0.1 + init_scale: float = 1.0 + init_decay_weight: float = 0.5 + norm_loss_weight: Optional[float] = 0.5 + norm_loss_scale: float = 1.5 + temp_schedule_gamma: float = 33.333 + depth_drop_rate: float = 0.1 + + def __post_init__(self): + super().__post_init__() diff --git a/src/pythae/models/hrq_vae/hrq_vae_model.py b/src/pythae/models/hrq_vae/hrq_vae_model.py new file mode 100755 index 00000000..59459335 --- /dev/null +++ b/src/pythae/models/hrq_vae/hrq_vae_model.py @@ -0,0 +1,128 @@ +from typing import Optional, Any, Dict + +import torch +import torch.nn.functional as F + +from ...data.datasets import BaseDataset +from ..ae import AE +from ..base.base_utils import ModelOutput +from ..nn import BaseDecoder, BaseEncoder +from .hrq_vae_config import HRQVAEConfig +from .hrq_vae_utils import HierarchicalResidualQuantizer + + +class HRQVAE(AE): + r""" + Hierarchical Residual Quantization-VAE model. Introduced in https://aclanthology.org/2022.acl-long.178/ (Hosking et al., ACL 2022) + + Args: + model_config (HRQVAEConfig): The Variational Autoencoder configuration setting the main + parameters of the model. + + encoder (BaseEncoder): An instance of BaseEncoder (inheriting from `torch.nn.Module` which + plays the role of encoder. This argument allows you to use your own neural networks + architectures if desired. If None is provided, a simple Multi Layer Preception + (https://en.wikipedia.org/wiki/Multilayer_perceptron) is used. Default: None. + + decoder (BaseDecoder): An instance of BaseDecoder (inheriting from `torch.nn.Module` which + plays the role of encoder. This argument allows you to use your own neural networks + architectures if desired. If None is provided, a simple Multi Layer Preception + (https://en.wikipedia.org/wiki/Multilayer_perceptron) is used. Default: None. + + """ + + def __init__( + self, + model_config: HRQVAEConfig, + encoder: Optional[BaseEncoder] = None, + decoder: Optional[BaseDecoder] = None, + ): + AE.__init__(self, model_config=model_config, encoder=encoder, decoder=decoder) + + self._set_quantizer(model_config) + + self.model_name = "HRQVAE" + + def _set_quantizer(self, model_config): + if model_config.input_dim is None: + raise AttributeError( + "No input dimension provided !" + "'input_dim' parameter of HRQVAEConfig instance must be set to 'data_shape' where " + "the shape of the data is (C, H, W ..). Unable to set quantizer." + ) + + x = torch.randn((2,) + self.model_config.input_dim) + z = self.encoder(x).embedding + if len(z.shape) == 2: + z = z.reshape(z.shape[0], 1, 1, -1) + + z = z.permute(0, 2, 3, 1) + + self.model_config.embedding_dim = z.shape[-1] + + self.quantizer = HierarchicalResidualQuantizer(model_config=model_config) + + def forward(self, inputs: Dict[str, Any], **kwargs) -> ModelOutput: + """ + The VAE model + + Args: + inputs (dict): A dict of samples + + Returns: + ModelOutput: An instance of ModelOutput containing all the relevant parameters + + """ + + x = inputs["data"] + uses_ddp = kwargs.pop("uses_ddp", False) + epoch = kwargs.pop("epoch", 0) + + encoder_output = self.encoder(x) + + embeddings = encoder_output.embedding + + reshape_for_decoding = False + + if len(embeddings.shape) == 2: + embeddings = embeddings.reshape(embeddings.shape[0], 1, 1, -1) + reshape_for_decoding = True + + embeddings = embeddings.permute(0, 2, 3, 1) + + quantizer_output = self.quantizer(embeddings, epoch=epoch, uses_ddp=uses_ddp) + + quantized_embed = quantizer_output.quantized_vector + + if reshape_for_decoding: + quantized_embed = quantized_embed.reshape(embeddings.shape[0], -1) + + recon_x = self.decoder(quantized_embed).reconstruction + + loss, recon_loss, hrq_loss = self.loss_function(recon_x, x, quantizer_output) + + output = ModelOutput( + loss=loss, + recon_loss=recon_loss, + hrq_loss=hrq_loss, + recon_x=recon_x, + z=quantized_embed, + z_orig=quantizer_output.z_orig, + quantized_indices=quantizer_output.quantized_indices, + probs=quantizer_output.probs, + ) + + return output + + def loss_function(self, recon_x, x, quantizer_output): + recon_loss = F.mse_loss( + recon_x.reshape(x.shape[0], -1), x.reshape(x.shape[0], -1), reduction="none" + ).sum(dim=-1) + + hrq_loss = quantizer_output.loss + + return ( + (recon_loss + hrq_loss).mean(dim=0), + recon_loss.mean(dim=0), + hrq_loss.mean(dim=0), + ) diff --git a/src/pythae/models/hrq_vae/hrq_vae_utils.py b/src/pythae/models/hrq_vae/hrq_vae_utils.py new file mode 100755 index 00000000..346c68ea --- /dev/null +++ b/src/pythae/models/hrq_vae/hrq_vae_utils.py @@ -0,0 +1,153 @@ +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F +from math import sqrt + +from ..base.base_utils import ModelOutput +from .hrq_vae_config import HRQVAEConfig + + +class HierarchicalResidualQuantizer(nn.Module): + def __init__(self, model_config: HRQVAEConfig): + nn.Module.__init__(self) + + self.model_config = model_config + + self.embedding_dim = model_config.embedding_dim + self.num_embeddings = model_config.num_embeddings + self.num_levels = model_config.num_levels + + self.embeddings = nn.ModuleList( + [ + nn.Embedding(self.num_embeddings, self.embedding_dim) + for hix in range(self.num_levels) + ] + ) + + init_scale = model_config.init_scale + init_decay_weight = model_config.init_decay_weight + for hix, embedding in enumerate(self.embeddings): + scale = init_scale * init_decay_weight**hix / sqrt(self.embedding_dim) + embedding.weight.data.uniform_(-1.0 * scale, scale) + + # Normalise onto sphere + embedding.weight.data = ( + embedding.weight.data + / torch.linalg.vector_norm(embedding.weight, dim=1, keepdim=True) + * init_scale + * init_decay_weight**hix + ) + + def forward(self, z: torch.Tensor, epoch: int, uses_ddp: bool = False): + if uses_ddp: + raise Exception("HRQVAE doesn't currently support DDP :(") + + input_shape = z.shape + + z = z.reshape(-1, self.embedding_dim) + + loss = torch.zeros(z.shape[0]).to(z.device) + + resid_error = z + + quantized = [] + codes = [] + all_probs = [] + + for head_ix, embedding in enumerate(self.embeddings): + if head_ix > 0: + resid_error = z - torch.sum(torch.cat(quantized, dim=1), dim=1) + + distances = -1.0 * ( + torch.sum(resid_error**2, dim=-1, keepdim=True) + + torch.sum(embedding.weight**2, dim=-1) + - 2 * torch.matmul(resid_error, embedding.weight.T) + ) + + gumbel_sched_weight = torch.exp( + -torch.tensor(float(epoch)) + / float(self.model_config.temp_schedule_gamma * 1.5**head_ix) + ) + gumbel_temp = max(gumbel_sched_weight, 0.5) + + if self.training: + + sample_onehot = F.gumbel_softmax( + distances, tau=gumbel_temp, hard=True, dim=-1 + ) + else: + indices = torch.argmax(distances, dim=-1) + sample_onehot = F.one_hot( + indices, num_classes=self.num_embeddings + ).float() + + probs = F.softmax(distances / gumbel_temp, dim=-1) + + # KL loss + prior = ( + torch.ones_like(distances).detach() + / torch.ones_like(distances).sum(-1, keepdim=True).detach() + ) + kl_loss = torch.nn.KLDivLoss(reduction="none") + kl = kl_loss(nn.functional.log_softmax(distances, dim=-1), prior).sum( + dim=-1 + ) + loss += kl * self.model_config.kl_weight + + # quantization + this_quantized = sample_onehot @ embedding.weight + this_quantized = this_quantized.reshape_as(z) + + quantized.append(this_quantized.unsqueeze(-2)) + codes.append(torch.argmax(sample_onehot, dim=-1).unsqueeze(-1)) + all_probs.append(probs.unsqueeze(-2)) + + quantized = torch.cat(quantized, dim=-2) + quantized_indices = torch.cat(codes, dim=-1) + all_probs = torch.cat(all_probs, dim=-2) + + # Calculate the norm loss + if self.model_config.norm_loss_weight is not None: + upper_norms = torch.linalg.vector_norm(quantized[:, :-1, :], dim=-1) + lower_norms = torch.linalg.vector_norm(quantized[:, 1:, :], dim=-1) + norm_loss = ( + torch.max( + lower_norms / upper_norms * self.model_config.norm_loss_scale, + torch.ones_like(lower_norms), + ) + - 1.0 + ) ** 2 + + loss += norm_loss.mean(dim=1) * self.model_config.norm_loss_weight + + # Depth drop out + if self.training: + + drop_dist = torch.distributions.Bernoulli( + 1 - self.model_config.depth_drop_rate + ) + + mask = drop_dist.sample(sample_shape=(*quantized.shape[:-1], 1)) + + mask = torch.cumprod(mask, dim=1).to(quantized.device) + quantized = quantized * mask + + quantized = quantized.sum(dim=-2).reshape(*input_shape) + quantized = quantized.permute(0, 3, 1, 2) + + loss = loss.reshape(input_shape[0], -1).mean(dim=1) + + quantized_indices = quantized_indices.reshape( + *input_shape[:-1], self.num_levels + ) + + output = ModelOutput( + z_orig=z, + quantized_vector=quantized, + quantized_indices=quantized_indices, + loss=loss, + probs=all_probs, + ) + + return output diff --git a/src/pythae/models/nn/benchmarks/mnist/resnets.py b/src/pythae/models/nn/benchmarks/mnist/resnets.py index 4176f5a9..d7afc0ea 100644 --- a/src/pythae/models/nn/benchmarks/mnist/resnets.py +++ b/src/pythae/models/nn/benchmarks/mnist/resnets.py @@ -578,6 +578,117 @@ def forward(self, x: torch.Tensor, output_layer_levels: List[int] = None): return output +class Encoder_ResNet_HRQVAE_MNIST(BaseEncoder): + """ + A ResNet encoder suited for MNIST and VQ- or HRQ- VAE models. It differs from the VQVAE ResNet in that it outputs only a single embedding vector. + + It can be built as follows: + + .. code-block:: + + >>> from pythae.models.nn.benchmarks.mnist import Encoder_ResNet_HRQVAE_MNIST + >>> from pythae.models import HRQVAEConfig + >>> model_config = HRQVAEConfig(input_dim=(1, 28, 28), latent_dim=16) + >>> encoder = Encoder_ResNet_HRQVAE_MNIST(model_config) + + + and then passed to a :class:`pythae.models` instance + + >>> from pythae.models import HRQVAE + >>> model = HRQVAE(model_config=model_config, encoder=encoder) + >>> model.encoder == encoder + ... True + + .. note:: + + Please note that this encoder is only suitable for Autoencoder based models since it only + outputs the embeddings of the input data under the key `embedding`. + + .. code-block:: + + >>> import torch + >>> input = torch.rand(2, 1, 28, 28) + >>> out = encoder(input) + >>> out.embedding.shape + ... torch.Size([2, 16, 1, 1]) + + """ + + def __init__(self, args: BaseAEConfig): + BaseEncoder.__init__(self) + + self.input_dim = (1, 28, 28) + self.latent_dim = args.latent_dim + self.n_channels = 1 + + layers = nn.ModuleList() + + layers.append(nn.Sequential(nn.Conv2d(self.n_channels, 64, 4, 2, padding=1))) + + layers.append(nn.Sequential(nn.Conv2d(64, 128, 4, 2, padding=1))) + + layers.append(nn.Sequential(nn.Conv2d(128, 128, 3, 2, padding=1))) + + layers.append( + nn.Sequential( + ResBlock(in_channels=128, out_channels=32), + ResBlock(in_channels=128, out_channels=32), + ) + ) + + # Additional Conv layer to squeeze down to a single embedding + # layers.append(nn.Sequential(nn.Conv2d(128, 128, 4, 1, padding=0))) + + self.layers = layers + self.depth = len(layers) + + self.pre_qantized = nn.Conv2d(128, self.latent_dim, 4, 1) + + def forward(self, x: torch.Tensor, output_layer_levels: List[int] = None): + """Forward method + + Args: + x (torch.Tensor): A batch of inputs. + output_layer_levels (List[int]): The levels of the layers where the outputs are + extracted. If None, the last layer's output is returned. Default: None. + + Returns: + ModelOutput: An instance of ModelOutput containing the embeddings of the input data + under the key `embedding`. Optional: The outputs of the layers specified in + `output_layer_levels` arguments are available under the keys `embedding_layer_i` where + i is the layer's level.""" + output = ModelOutput() + + max_depth = self.depth + + if output_layer_levels is not None: + assert all( + self.depth >= levels > 0 or levels == -1 + for levels in output_layer_levels + ), ( + f"Cannot output layer deeper than depth ({self.depth})." + f"Got ({output_layer_levels})." + ) + + if -1 in output_layer_levels: + max_depth = self.depth + else: + max_depth = max(output_layer_levels) + + out = x + + for i in range(max_depth): + out = self.layers[i](out) + + if output_layer_levels is not None: + if i + 1 in output_layer_levels: + output[f"embedding_layer_{i+1}"] = out + if i + 1 == self.depth: + output["embedding"] = self.pre_qantized(out) + + return output + + class Decoder_ResNet_AE_MNIST(BaseDecoder): """ A ResNet decoder suited for MNIST and Autoencoder-based @@ -884,3 +995,122 @@ def forward(self, z: torch.Tensor, output_layer_levels: List[int] = None): output["reconstruction"] = out return output + + +class Decoder_ResNet_HRQVAE_MNIST(BaseDecoder): + """ + A ResNet decoder suited for MNIST and VQ- or HRQ- VAE models. It differs from the VQVAE ResNet in that it expects only a single embedding vector as input. + + .. code-block:: + + >>> from pythae.models.nn.benchmarks.mnist import Decoder_ResNet_HRQVAE_MNIST + >>> from pythae.models import HRQVAEConfig + >>> model_config = HRQVAEConfig(input_dim=(1, 28, 28), latent_dim=16) + >>> decoder = Decoder_ResNet_HRQVAE_MNIST(model_config) + + + and then passed to a :class:`pythae.models` instance + + >>> from pythae.models import HRQVAE + >>> model = HRQVAE(model_config=model_config, decoder=decoder) + >>> model.decoder == decoder + ... True + + .. note:: + + Please note that this decoder is suitable for **all** models. + + .. code-block:: + + >>> import torch + >>> input = torch.randn(2, 16, 1, 1) + >>> out = decoder(input) + >>> out.reconstruction.shape + ... torch.Size([2, 1, 28, 28]) + """ + + def __init__(self, args: BaseAEConfig): + BaseDecoder.__init__(self) + + self.input_dim = (1, 28, 28) + self.latent_dim = args.latent_dim + self.n_channels = 1 + + layers = nn.ModuleList() + + layers.append(nn.ConvTranspose2d(self.latent_dim, 128, 4, 1)) + + layers.append(nn.ConvTranspose2d(128, 128, 3, 2, padding=1)) + + layers.append( + nn.Sequential( + ResBlock(in_channels=128, out_channels=32), + ResBlock(in_channels=128, out_channels=32), + nn.ReLU(), + ) + ) + + layers.append( + nn.Sequential( + nn.ConvTranspose2d(128, 64, 3, 2, padding=1, output_padding=1), + nn.ReLU(), + ) + ) + + layers.append( + nn.Sequential( + nn.ConvTranspose2d( + 64, self.n_channels, 3, 2, padding=1, output_padding=1 + ), + nn.Sigmoid(), + ) + ) + + self.layers = layers + self.depth = len(layers) + + def forward(self, z: torch.Tensor, output_layer_levels: List[int] = None): + """Forward method + + Args: + z (torch.Tensor): A batch of embeddings. + output_layer_levels (List[int]): The levels of the layers where the outputs are + extracted. If None, the last layer's output is returned. Default: None. + + Returns: + ModelOutput: An instance of ModelOutput containing the reconstruction of the latent code + under the key `reconstruction`. Optional: The outputs of the layers specified in + `output_layer_levels` arguments are available under the keys `reconstruction_layer_i` + where i is the layer's level. + """ + output = ModelOutput() + + max_depth = self.depth + + if output_layer_levels is not None: + assert all( + self.depth >= levels > 0 or levels == -1 + for levels in output_layer_levels + ), ( + f"Cannot output layer deeper than depth ({self.depth})." + f"Got ({output_layer_levels})" + ) + + if -1 in output_layer_levels: + max_depth = self.depth + else: + max_depth = max(output_layer_levels) + + out = z + + for i in range(max_depth): + out = self.layers[i](out) + + if output_layer_levels is not None: + if i + 1 in output_layer_levels: + output[f"reconstruction_layer_{i+1}"] = out + + if i + 1 == self.depth: + output["reconstruction"] = out + + return output diff --git a/tests/test_HRQVAE.py b/tests/test_HRQVAE.py new file mode 100755 index 00000000..262dc715 --- /dev/null +++ b/tests/test_HRQVAE.py @@ -0,0 +1,760 @@ +import os +from copy import deepcopy + +import pytest +import torch +from pydantic import ValidationError + +from pythae.customexception import BadInheritanceError +from pythae.models import HRQVAE, AutoModel, HRQVAEConfig +from pythae.models.base.base_utils import ModelOutput +from pythae.models.vq_vae.vq_vae_utils import Quantizer, QuantizerEMA +from pythae.pipelines import GenerationPipeline, TrainingPipeline +from pythae.samplers import PixelCNNSamplerConfig +from pythae.trainers import BaseTrainer, BaseTrainerConfig +from tests.data.custom_architectures import ( + Decoder_AE_Conv, + Encoder_AE_Conv, + NetBadInheritance, +) + +PATH = os.path.dirname(os.path.abspath(__file__)) + + +@pytest.fixture(params=[HRQVAEConfig(), HRQVAEConfig(latent_dim=4)]) +def model_configs_no_input_dim(request): + return request.param + + +@pytest.fixture( + params=[ + HRQVAEConfig( + input_dim=(1, 28, 28), latent_dim=16, num_embeddings=10 + ), # ! Needs squared latent_dim ! + HRQVAEConfig( + input_dim=(1, 28, 28), + latent_dim=16, + num_embeddings=10, + num_levels = 8, + kl_weight = 0.01, + init_scale = 1.0, + init_decay_weight = 0.5, + norm_loss_weight = 0.5, + norm_loss_scale = 1.5, + temp_schedule_gamma=10, + ), + ] +) +def model_configs(request): + return request.param + + +@pytest.fixture +def custom_encoder(model_configs): + return Encoder_AE_Conv(model_configs) + + +@pytest.fixture +def custom_decoder(model_configs): + return Decoder_AE_Conv(model_configs) + + +class Test_Model_Building: + @pytest.fixture() + def bad_net(self): + return NetBadInheritance() + + def test_build_model(self, model_configs): + model = HRQVAE(model_configs) + assert all( + [ + model.input_dim == model_configs.input_dim, + model.latent_dim == model_configs.latent_dim, + ] + ) + + + def build_quantizer(self, model_configs): + model = HRQVAE(model_configs) + + if model.use_ema: + assert isinstance(model.quantizer, QuantizerEMA) + + else: + assert isinstance(model.quantizer, Quantizer) + + def test_raises_bad_inheritance(self, model_configs, bad_net): + with pytest.raises(BadInheritanceError): + model = HRQVAE(model_configs, encoder=bad_net) + + with pytest.raises(BadInheritanceError): + model = HRQVAE(model_configs, decoder=bad_net) + + def test_raises_no_input_dim( + self, model_configs_no_input_dim, custom_encoder, custom_decoder + ): + with pytest.raises(AttributeError): + model = HRQVAE(model_configs_no_input_dim) + + with pytest.raises(AttributeError): + model = HRQVAE(model_configs_no_input_dim, encoder=custom_encoder) + + with pytest.raises(AttributeError): + model = HRQVAE(model_configs_no_input_dim, decoder=custom_decoder) + + def test_build_custom_arch(self, model_configs, custom_encoder, custom_decoder): + model = HRQVAE(model_configs, encoder=custom_encoder, decoder=custom_decoder) + + assert model.encoder == custom_encoder + assert not model.model_config.uses_default_encoder + assert model.decoder == custom_decoder + assert not model.model_config.uses_default_decoder + + model = HRQVAE(model_configs, encoder=custom_encoder) + + assert model.encoder == custom_encoder + assert not model.model_config.uses_default_encoder + assert model.model_config.uses_default_decoder + + model = HRQVAE(model_configs, decoder=custom_decoder) + + assert model.model_config.uses_default_encoder + assert model.decoder == custom_decoder + assert not model.model_config.uses_default_decoder + + +class Test_Model_Saving: + def test_default_model_saving(self, tmpdir, model_configs): + tmpdir.mkdir("dummy_folder") + dir_path = dir_path = os.path.join(tmpdir, "dummy_folder") + + model = HRQVAE(model_configs) + + model.state_dict()["encoder.layers.0.0.weight"][0] = 0 + + model.save(dir_path=dir_path) + + assert set(os.listdir(dir_path)) == set( + ["model_config.json", "model.pt", "environment.json"] + ) + + # reload model + model_rec = AutoModel.load_from_folder(dir_path) + + # check configs are the same + assert model_rec.model_config.__dict__ == model.model_config.__dict__ + + assert all( + [ + torch.equal(model_rec.state_dict()[key], model.state_dict()[key]) + for key in model.state_dict().keys() + ] + ) + + def test_custom_encoder_model_saving(self, tmpdir, model_configs, custom_encoder): + tmpdir.mkdir("dummy_folder") + dir_path = dir_path = os.path.join(tmpdir, "dummy_folder") + + model = HRQVAE(model_configs, encoder=custom_encoder) + + model.state_dict()["encoder.layers.0.0.weight"][0] = 0 + + model.save(dir_path=dir_path) + + assert set(os.listdir(dir_path)) == set( + ["model_config.json", "model.pt", "encoder.pkl", "environment.json"] + ) + + # reload model + model_rec = AutoModel.load_from_folder(dir_path) + + # check configs are the same + assert model_rec.model_config.__dict__ == model.model_config.__dict__ + + assert all( + [ + torch.equal(model_rec.state_dict()[key], model.state_dict()[key]) + for key in model.state_dict().keys() + ] + ) + + def test_custom_decoder_model_saving(self, tmpdir, model_configs, custom_decoder): + tmpdir.mkdir("dummy_folder") + dir_path = dir_path = os.path.join(tmpdir, "dummy_folder") + + model = HRQVAE(model_configs, decoder=custom_decoder) + + model.state_dict()["encoder.layers.0.0.weight"][0] = 0 + + model.save(dir_path=dir_path) + + assert set(os.listdir(dir_path)) == set( + ["model_config.json", "model.pt", "decoder.pkl", "environment.json"] + ) + + # reload model + model_rec = AutoModel.load_from_folder(dir_path) + + # check configs are the same + assert model_rec.model_config.__dict__ == model.model_config.__dict__ + + assert all( + [ + torch.equal(model_rec.state_dict()[key], model.state_dict()[key]) + for key in model.state_dict().keys() + ] + ) + + def test_full_custom_model_saving( + self, tmpdir, model_configs, custom_encoder, custom_decoder + ): + tmpdir.mkdir("dummy_folder") + dir_path = dir_path = os.path.join(tmpdir, "dummy_folder") + + model = HRQVAE(model_configs, encoder=custom_encoder, decoder=custom_decoder) + + model.state_dict()["encoder.layers.0.0.weight"][0] = 0 + + model.save(dir_path=dir_path) + + assert set(os.listdir(dir_path)) == set( + [ + "model_config.json", + "model.pt", + "encoder.pkl", + "decoder.pkl", + "environment.json", + ] + ) + + # reload model + model_rec = AutoModel.load_from_folder(dir_path) + + # check configs are the same + assert model_rec.model_config.__dict__ == model.model_config.__dict__ + + assert all( + [ + torch.equal(model_rec.state_dict()[key], model.state_dict()[key]) + for key in model.state_dict().keys() + ] + ) + + def test_raises_missing_files( + self, tmpdir, model_configs, custom_encoder, custom_decoder + ): + tmpdir.mkdir("dummy_folder") + dir_path = dir_path = os.path.join(tmpdir, "dummy_folder") + + model = HRQVAE(model_configs, encoder=custom_encoder, decoder=custom_decoder) + + model.state_dict()["encoder.layers.0.0.weight"][0] = 0 + + model.save(dir_path=dir_path) + + os.remove(os.path.join(dir_path, "decoder.pkl")) + + # check raises decoder.pkl is missing + with pytest.raises(FileNotFoundError): + model_rec = AutoModel.load_from_folder(dir_path) + + os.remove(os.path.join(dir_path, "encoder.pkl")) + + # check raises encoder.pkl is missing + with pytest.raises(FileNotFoundError): + model_rec = AutoModel.load_from_folder(dir_path) + + os.remove(os.path.join(dir_path, "model.pt")) + + # check raises encoder.pkl is missing + with pytest.raises(FileNotFoundError): + model_rec = AutoModel.load_from_folder(dir_path) + + os.remove(os.path.join(dir_path, "model_config.json")) + + # check raises encoder.pkl is missing + with pytest.raises(FileNotFoundError): + model_rec = AutoModel.load_from_folder(dir_path) + + +class Test_Model_forward: + @pytest.fixture + def demo_data(self): + data = torch.load(os.path.join(PATH, "data/mnist_clean_train_dataset_sample"))[ + : + ] + return data # This is an extract of 3 data from MNIST (unnormalized) used to test custom architecture + + @pytest.fixture + def vae(self, model_configs, demo_data): + model_configs.input_dim = tuple(demo_data["data"][0].shape) + return HRQVAE(model_configs) + + def test_model_train_output(self, vae, demo_data): + vae.train() + + out = vae(demo_data) + + assert isinstance(out, ModelOutput) + + assert set( + ["loss", "recon_loss", "hrq_loss", "recon_x", "z", "z_orig", "quantized_indices", "probs"] + ) == set(out.keys()) + + print(out.z.shape, demo_data['data'].shape) + + assert out.z.shape[0] == demo_data["data"].shape[0] + assert out.recon_x.shape == demo_data["data"].shape + + +class Test_Model_interpolate: + @pytest.fixture( + params=[ + torch.rand(3, 2, 3, 1), + torch.rand(3, 2, 2), + torch.load(os.path.join(PATH, "data/mnist_clean_train_dataset_sample"))[:][ + "data" + ], + ] + ) + def demo_data(self, request): + return request.param + + @pytest.fixture() + def granularity(self): + return int(torch.randint(1, 10, (1,))) + + @pytest.fixture + def ae(self, model_configs, demo_data): + model_configs.input_dim = tuple(demo_data[0].shape) + return HRQVAE(model_configs) + + def test_interpolate(self, ae, demo_data, granularity): + with pytest.raises(AssertionError): + ae.interpolate(demo_data, demo_data[1:], granularity) + + interp = ae.interpolate(demo_data, demo_data, granularity) + + assert tuple(interp.shape) == ( + demo_data.shape[0], + granularity, + ) + (demo_data.shape[1:]) + + +class Test_Model_reconstruct: + @pytest.fixture( + params=[ + torch.rand(3, 2, 3, 1), + torch.rand(3, 2, 2), + torch.load(os.path.join(PATH, "data/mnist_clean_train_dataset_sample"))[:][ + "data" + ], + ] + ) + def demo_data(self, request): + return request.param + + @pytest.fixture + def ae(self, model_configs, demo_data): + model_configs.input_dim = tuple(demo_data[0].shape) + return HRQVAE(model_configs) + + def test_reconstruct(self, ae, demo_data): + recon = ae.reconstruct(demo_data) + assert tuple(recon.shape) == demo_data.shape + + +@pytest.mark.slow +class Test_HRQVAETraining: + @pytest.fixture + def train_dataset(self): + return torch.load(os.path.join(PATH, "data/mnist_clean_train_dataset_sample")) + + @pytest.fixture( + params=[BaseTrainerConfig(num_epochs=3, steps_saving=2, learning_rate=1e-5)] + ) + def training_configs(self, tmpdir, request): + tmpdir.mkdir("dummy_folder") + dir_path = os.path.join(tmpdir, "dummy_folder") + request.param.output_dir = dir_path + return request.param + + @pytest.fixture( + params=[ + torch.rand(1), + torch.rand(1), + torch.rand(1), + torch.rand(1), + torch.rand(1), + ] + ) + def vae(self, model_configs, custom_encoder, custom_decoder, request): + # randomized + + alpha = request.param + + if alpha < 0.25: + model = HRQVAE(model_configs) + + elif 0.25 <= alpha < 0.5: + model = HRQVAE(model_configs, encoder=custom_encoder) + + elif 0.5 <= alpha < 0.75: + model = HRQVAE(model_configs, decoder=custom_decoder) + + else: + model = HRQVAE(model_configs, encoder=custom_encoder, decoder=custom_decoder) + + return model + + @pytest.fixture + def trainer(self, vae, train_dataset, training_configs): + trainer = BaseTrainer( + model=vae, + train_dataset=train_dataset, + eval_dataset=train_dataset, + training_config=training_configs, + ) + + trainer.prepare_training() + + return trainer + + def test_vae_train_step(self, trainer): + start_model_state_dict = deepcopy(trainer.model.state_dict()) + + step_1_loss = trainer.train_step(epoch=1) + + step_1_model_state_dict = deepcopy(trainer.model.state_dict()) + + # check that weights were updated + assert not all( + [ + torch.equal(start_model_state_dict[key], step_1_model_state_dict[key]) + for key in start_model_state_dict.keys() + ] + ) + + def test_vae_eval_step(self, trainer): + start_model_state_dict = deepcopy(trainer.model.state_dict()) + + step_1_loss = trainer.eval_step(epoch=1) + + step_1_model_state_dict = deepcopy(trainer.model.state_dict()) + + # check that weights were not updated + assert all( + [ + torch.equal(start_model_state_dict[key], step_1_model_state_dict[key]) + for key in start_model_state_dict.keys() + ] + ) + + def test_vae_predict_step(self, trainer, train_dataset): + start_model_state_dict = deepcopy(trainer.model.state_dict()) + + inputs, recon, generated = trainer.predict(trainer.model) + + step_1_model_state_dict = deepcopy(trainer.model.state_dict()) + + # check that weights were not updated + assert all( + [ + torch.equal(start_model_state_dict[key], step_1_model_state_dict[key]) + for key in start_model_state_dict.keys() + ] + ) + + assert inputs.cpu() in train_dataset.data + assert recon.shape == inputs.shape + assert generated.shape == inputs.shape + + def test_vae_main_train_loop(self, trainer): + start_model_state_dict = deepcopy(trainer.model.state_dict()) + + trainer.train() + + step_1_model_state_dict = deepcopy(trainer.model.state_dict()) + + # check that weights were updated + assert not all( + [ + torch.equal(start_model_state_dict[key], step_1_model_state_dict[key]) + for key in start_model_state_dict.keys() + ] + ) + + def test_checkpoint_saving(self, vae, trainer, training_configs): + dir_path = training_configs.output_dir + + # Make a training step + step_1_loss = trainer.train_step(epoch=1) + + model = deepcopy(trainer.model) + optimizer = deepcopy(trainer.optimizer) + + trainer.save_checkpoint(dir_path=dir_path, epoch=0, model=model) + + checkpoint_dir = os.path.join(dir_path, "checkpoint_epoch_0") + + assert os.path.isdir(checkpoint_dir) + + files_list = os.listdir(checkpoint_dir) + + assert set(["model.pt", "optimizer.pt", "training_config.json"]).issubset( + set(files_list) + ) + + # check pickled custom decoder + if not vae.model_config.uses_default_decoder: + assert "decoder.pkl" in files_list + + else: + assert not "decoder.pkl" in files_list + + # check pickled custom encoder + if not vae.model_config.uses_default_encoder: + assert "encoder.pkl" in files_list + + else: + assert not "encoder.pkl" in files_list + + model_rec_state_dict = torch.load(os.path.join(checkpoint_dir, "model.pt"))[ + "model_state_dict" + ] + + assert all( + [ + torch.equal( + model_rec_state_dict[key].cpu(), model.state_dict()[key].cpu() + ) + for key in model.state_dict().keys() + ] + ) + + # check reload full model + model_rec = AutoModel.load_from_folder(os.path.join(checkpoint_dir)) + + assert all( + [ + torch.equal( + model_rec.state_dict()[key].cpu(), model.state_dict()[key].cpu() + ) + for key in model.state_dict().keys() + ] + ) + + assert type(model_rec.encoder.cpu()) == type(model.encoder.cpu()) + assert type(model_rec.decoder.cpu()) == type(model.decoder.cpu()) + + optim_rec_state_dict = torch.load(os.path.join(checkpoint_dir, "optimizer.pt")) + + assert all( + [ + dict_rec == dict_optimizer + for (dict_rec, dict_optimizer) in zip( + optim_rec_state_dict["param_groups"], + optimizer.state_dict()["param_groups"], + ) + ] + ) + + assert all( + [ + dict_rec == dict_optimizer + for (dict_rec, dict_optimizer) in zip( + optim_rec_state_dict["state"], optimizer.state_dict()["state"] + ) + ] + ) + + def test_checkpoint_saving_during_training(self, vae, trainer, training_configs): + # + target_saving_epoch = training_configs.steps_saving + + dir_path = training_configs.output_dir + + model = deepcopy(trainer.model) + + trainer.train() + + training_dir = os.path.join( + dir_path, f"HRQVAE_training_{trainer._training_signature}" + ) + assert os.path.isdir(training_dir) + + checkpoint_dir = os.path.join( + training_dir, f"checkpoint_epoch_{target_saving_epoch}" + ) + + assert os.path.isdir(checkpoint_dir) + + files_list = os.listdir(checkpoint_dir) + + # check files + assert set(["model.pt", "optimizer.pt", "training_config.json"]).issubset( + set(files_list) + ) + + # check pickled custom decoder + if not vae.model_config.uses_default_decoder: + assert "decoder.pkl" in files_list + + else: + assert not "decoder.pkl" in files_list + + # check pickled custom encoder + if not vae.model_config.uses_default_encoder: + assert "encoder.pkl" in files_list + + else: + assert not "encoder.pkl" in files_list + + model_rec_state_dict = torch.load(os.path.join(checkpoint_dir, "model.pt"))[ + "model_state_dict" + ] + + assert not all( + [ + torch.equal(model_rec_state_dict[key], model.state_dict()[key]) + for key in model.state_dict().keys() + ] + ) + + def test_final_model_saving(self, vae, trainer, training_configs): + dir_path = training_configs.output_dir + + trainer.train() + + model = deepcopy(trainer._best_model) + + training_dir = os.path.join( + dir_path, f"HRQVAE_training_{trainer._training_signature}" + ) + assert os.path.isdir(training_dir) + + final_dir = os.path.join(training_dir, f"final_model") + assert os.path.isdir(final_dir) + + files_list = os.listdir(final_dir) + + assert set(["model.pt", "model_config.json", "training_config.json"]).issubset( + set(files_list) + ) + + # check pickled custom decoder + if not vae.model_config.uses_default_decoder: + assert "decoder.pkl" in files_list + + else: + assert not "decoder.pkl" in files_list + + # check pickled custom encoder + if not vae.model_config.uses_default_encoder: + assert "encoder.pkl" in files_list + + else: + assert not "encoder.pkl" in files_list + + # check reload full model + model_rec = AutoModel.load_from_folder(os.path.join(final_dir)) + + assert all( + [ + torch.equal( + model_rec.state_dict()[key].cpu(), model.state_dict()[key].cpu() + ) + for key in model.state_dict().keys() + ] + ) + + assert type(model_rec.encoder.cpu()) == type(model.encoder.cpu()) + assert type(model_rec.decoder.cpu()) == type(model.decoder.cpu()) + + def test_vae_training_pipeline(self, vae, train_dataset, training_configs): + dir_path = training_configs.output_dir + + # build pipeline + pipeline = TrainingPipeline(model=vae, training_config=training_configs) + + # Launch Pipeline + pipeline( + train_data=train_dataset.data, # gives tensor to pipeline + eval_data=train_dataset.data, # gives tensor to pipeline + ) + + model = deepcopy(pipeline.trainer._best_model) + + training_dir = os.path.join( + dir_path, f"HRQVAE_training_{pipeline.trainer._training_signature}" + ) + assert os.path.isdir(training_dir) + + final_dir = os.path.join(training_dir, f"final_model") + assert os.path.isdir(final_dir) + + files_list = os.listdir(final_dir) + + assert set(["model.pt", "model_config.json", "training_config.json"]).issubset( + set(files_list) + ) + + # check pickled custom decoder + if not vae.model_config.uses_default_decoder: + assert "decoder.pkl" in files_list + + else: + assert not "decoder.pkl" in files_list + + # check pickled custom encoder + if not vae.model_config.uses_default_encoder: + assert "encoder.pkl" in files_list + + else: + assert not "encoder.pkl" in files_list + + # check reload full model + model_rec = AutoModel.load_from_folder(os.path.join(final_dir)) + + assert all( + [ + torch.equal( + model_rec.state_dict()[key].cpu(), model.state_dict()[key].cpu() + ) + for key in model.state_dict().keys() + ] + ) + + assert type(model_rec.encoder.cpu()) == type(model.encoder.cpu()) + assert type(model_rec.decoder.cpu()) == type(model.decoder.cpu()) + + +class Test_HRQVAE_Generation: + @pytest.fixture + def train_data(self): + return torch.load( + os.path.join(PATH, "data/mnist_clean_train_dataset_sample") + ).data + + @pytest.fixture() + def ae_model(self): + return HRQVAE(HRQVAEConfig(input_dim=(1, 28, 28), latent_dim=4)) + + @pytest.fixture(params=[PixelCNNSamplerConfig()]) + def sampler_configs(self, request): + return request.param + + @pytest.mark.skip(reason="Sampling not currently supported for HRQVAE") + def test_fits_in_generation_pipeline(self, ae_model, sampler_configs, train_data): + pipeline = GenerationPipeline(model=ae_model, sampler_config=sampler_configs) + gen_data = pipeline( + num_samples=11, + batch_size=7, + output_dir=None, + return_gen=True, + train_data=train_data, + eval_data=train_data, + training_config=BaseTrainerConfig(num_epochs=1), + ) + + assert gen_data.shape[0] == 11