{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Saving and Loading Models\n",
    "\n",
    "In this notebook, I'll show you how to save and load models with PyTorch. This is important because you'll often want to load previously trained models to use in making predictions or to continue training on new data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'retina'\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import torch\n",
    "from torch import nn\n",
    "from torch import optim\n",
    "import torch.nn.functional as F\n",
    "from torchvision import datasets, transforms\n",
    "\n",
    "import helper\n",
    "import fc_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz\n",
      "Processing...\n",
      "Done!\n"
     ]
    }
   ],
   "source": [
    "# Define a transform to normalize the data\n",
    "transform = transforms.Compose([transforms.ToTensor(),\n",
    "                                transforms.Normalize((0.5,), (0.5,))])\n",
    "# Download and load the training data\n",
    "trainset = datasets.FashionMNIST('F_MNIST_data/', download=True, train=True, transform=transform)\n",
    "trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)\n",
    "\n",
    "# Download and load the test data\n",
    "testset = datasets.FashionMNIST('F_MNIST_data/', download=True, train=False, transform=transform)\n",
    "testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we can see one of the images."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 5724 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7fa4f2708b00>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 233,
       "width": 233
      },
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "image, label = next(iter(trainloader))\n",
    "helper.imshow(image[0,:]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Train a network\n",
    "\n",
    "To make things more concise here, I moved the model architecture and training code from the last part to a file called `fc_model`. Importing this, we can easily create a fully-connected network with `fc_model.Network`, and train the network using `fc_model.train`. I'll use this model (once it's trained) to demonstrate how we can save and load models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the network, define the criterion and optimizer\n",
    "\n",
    "model = fc_model.Network(784, 10, [512, 256, 128])\n",
    "criterion = nn.NLLLoss()\n",
    "optimizer = optim.Adam(model.parameters(), lr=0.001)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 1/2..  Training Loss: 1.710..  Test Loss: 0.976..  Test Accuracy: 0.679\n",
      "Epoch: 1/2..  Training Loss: 1.043..  Test Loss: 0.760..  Test Accuracy: 0.713\n",
      "Epoch: 1/2..  Training Loss: 0.864..  Test Loss: 0.675..  Test Accuracy: 0.737\n",
      "Epoch: 1/2..  Training Loss: 0.780..  Test Loss: 0.635..  Test Accuracy: 0.749\n",
      "Epoch: 1/2..  Training Loss: 0.787..  Test Loss: 0.617..  Test Accuracy: 0.770\n",
      "Epoch: 1/2..  Training Loss: 0.755..  Test Loss: 0.590..  Test Accuracy: 0.774\n",
      "Epoch: 1/2..  Training Loss: 0.717..  Test Loss: 0.583..  Test Accuracy: 0.773\n",
      "Epoch: 1/2..  Training Loss: 0.650..  Test Loss: 0.565..  Test Accuracy: 0.778\n",
      "Epoch: 1/2..  Training Loss: 0.661..  Test Loss: 0.561..  Test Accuracy: 0.790\n",
      "Epoch: 1/2..  Training Loss: 0.637..  Test Loss: 0.560..  Test Accuracy: 0.796\n",
      "Epoch: 1/2..  Training Loss: 0.586..  Test Loss: 0.529..  Test Accuracy: 0.799\n",
      "Epoch: 1/2..  Training Loss: 0.627..  Test Loss: 0.525..  Test Accuracy: 0.805\n",
      "Epoch: 1/2..  Training Loss: 0.602..  Test Loss: 0.521..  Test Accuracy: 0.809\n",
      "Epoch: 1/2..  Training Loss: 0.635..  Test Loss: 0.522..  Test Accuracy: 0.810\n",
      "Epoch: 1/2..  Training Loss: 0.606..  Test Loss: 0.502..  Test Accuracy: 0.810\n",
      "Epoch: 1/2..  Training Loss: 0.582..  Test Loss: 0.522..  Test Accuracy: 0.805\n",
      "Epoch: 1/2..  Training Loss: 0.584..  Test Loss: 0.507..  Test Accuracy: 0.822\n",
      "Epoch: 1/2..  Training Loss: 0.545..  Test Loss: 0.500..  Test Accuracy: 0.818\n",
      "Epoch: 1/2..  Training Loss: 0.599..  Test Loss: 0.489..  Test Accuracy: 0.826\n",
      "Epoch: 1/2..  Training Loss: 0.585..  Test Loss: 0.498..  Test Accuracy: 0.816\n",
      "Epoch: 1/2..  Training Loss: 0.546..  Test Loss: 0.482..  Test Accuracy: 0.821\n",
      "Epoch: 1/2..  Training Loss: 0.527..  Test Loss: 0.477..  Test Accuracy: 0.828\n",
      "Epoch: 1/2..  Training Loss: 0.563..  Test Loss: 0.482..  Test Accuracy: 0.820\n",
      "Epoch: 2/2..  Training Loss: 0.558..  Test Loss: 0.473..  Test Accuracy: 0.830\n",
      "Epoch: 2/2..  Training Loss: 0.525..  Test Loss: 0.492..  Test Accuracy: 0.821\n",
      "Epoch: 2/2..  Training Loss: 0.534..  Test Loss: 0.469..  Test Accuracy: 0.830\n",
      "Epoch: 2/2..  Training Loss: 0.548..  Test Loss: 0.483..  Test Accuracy: 0.827\n",
      "Epoch: 2/2..  Training Loss: 0.559..  Test Loss: 0.471..  Test Accuracy: 0.825\n",
      "Epoch: 2/2..  Training Loss: 0.534..  Test Loss: 0.469..  Test Accuracy: 0.831\n",
      "Epoch: 2/2..  Training Loss: 0.568..  Test Loss: 0.473..  Test Accuracy: 0.823\n",
      "Epoch: 2/2..  Training Loss: 0.551..  Test Loss: 0.463..  Test Accuracy: 0.829\n",
      "Epoch: 2/2..  Training Loss: 0.566..  Test Loss: 0.453..  Test Accuracy: 0.832\n",
      "Epoch: 2/2..  Training Loss: 0.513..  Test Loss: 0.446..  Test Accuracy: 0.835\n",
      "Epoch: 2/2..  Training Loss: 0.572..  Test Loss: 0.476..  Test Accuracy: 0.821\n",
      "Epoch: 2/2..  Training Loss: 0.511..  Test Loss: 0.447..  Test Accuracy: 0.833\n",
      "Epoch: 2/2..  Training Loss: 0.554..  Test Loss: 0.449..  Test Accuracy: 0.833\n",
      "Epoch: 2/2..  Training Loss: 0.502..  Test Loss: 0.468..  Test Accuracy: 0.832\n",
      "Epoch: 2/2..  Training Loss: 0.507..  Test Loss: 0.451..  Test Accuracy: 0.834\n",
      "Epoch: 2/2..  Training Loss: 0.502..  Test Loss: 0.454..  Test Accuracy: 0.840\n",
      "Epoch: 2/2..  Training Loss: 0.536..  Test Loss: 0.444..  Test Accuracy: 0.839\n",
      "Epoch: 2/2..  Training Loss: 0.503..  Test Loss: 0.447..  Test Accuracy: 0.837\n",
      "Epoch: 2/2..  Training Loss: 0.542..  Test Loss: 0.446..  Test Accuracy: 0.840\n",
      "Epoch: 2/2..  Training Loss: 0.549..  Test Loss: 0.438..  Test Accuracy: 0.838\n",
      "Epoch: 2/2..  Training Loss: 0.510..  Test Loss: 0.461..  Test Accuracy: 0.830\n",
      "Epoch: 2/2..  Training Loss: 0.544..  Test Loss: 0.448..  Test Accuracy: 0.837\n",
      "Epoch: 2/2..  Training Loss: 0.496..  Test Loss: 0.448..  Test Accuracy: 0.843\n"
     ]
    }
   ],
   "source": [
    "fc_model.train(model, trainloader, testloader, criterion, optimizer, epochs=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Saving and loading networks\n",
    "\n",
    "As you can imagine, it's impractical to train a network every time you need to use it. Instead, we can save trained networks then load them later to train more or use them for predictions.\n",
    "\n",
    "The parameters for PyTorch networks are stored in a model's `state_dict`. We can see the state dict contains the weight and bias matrices for each of our layers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Our model: \n",
      "\n",
      " Network(\n",
      "  (hidden_layers): ModuleList(\n",
      "    (0): Linear(in_features=784, out_features=512, bias=True)\n",
      "    (1): Linear(in_features=512, out_features=256, bias=True)\n",
      "    (2): Linear(in_features=256, out_features=128, bias=True)\n",
      "  )\n",
      "  (output): Linear(in_features=128, out_features=10, bias=True)\n",
      "  (dropout): Dropout(p=0.5)\n",
      ") \n",
      "\n",
      "The state dict keys: \n",
      "\n",
      " odict_keys(['hidden_layers.0.weight', 'hidden_layers.0.bias', 'hidden_layers.1.weight', 'hidden_layers.1.bias', 'hidden_layers.2.weight', 'hidden_layers.2.bias', 'output.weight', 'output.bias'])\n"
     ]
    }
   ],
   "source": [
    "print(\"Our model: \\n\\n\", model, '\\n')\n",
    "print(\"The state dict keys: \\n\\n\", model.state_dict().keys())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The simplest thing to do is simply save the state dict with `torch.save`. For example, we can save it to a file `'checkpoint.pth'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.save(model.state_dict(), 'checkpoint.pth')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can load the state dict with `torch.load`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "odict_keys(['hidden_layers.0.weight', 'hidden_layers.0.bias', 'hidden_layers.1.weight', 'hidden_layers.1.bias', 'hidden_layers.2.weight', 'hidden_layers.2.bias', 'output.weight', 'output.bias'])\n"
     ]
    }
   ],
   "source": [
    "state_dict = torch.load('checkpoint.pth')\n",
    "print(state_dict.keys())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And to load the state dict in to the network, you do `model.load_state_dict(state_dict)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.load_state_dict(state_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seems pretty straightforward, but as usual it's a bit more complicated. Loading the state dict works only if the model architecture is exactly the same as the checkpoint architecture. If I create a model with a different architecture, this fails."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "ename": "RuntimeError",
     "evalue": "Error(s) in loading state_dict for Network:\n\tWhile copying the parameter named \"hidden_layers.0.weight\", whose dimensions in the model are torch.Size([400, 784]) and whose dimensions in the checkpoint are torch.Size([512, 784]).\n\tWhile copying the parameter named \"hidden_layers.0.bias\", whose dimensions in the model are torch.Size([400]) and whose dimensions in the checkpoint are torch.Size([512]).\n\tWhile copying the parameter named \"hidden_layers.1.weight\", whose dimensions in the model are torch.Size([200, 400]) and whose dimensions in the checkpoint are torch.Size([256, 512]).\n\tWhile copying the parameter named \"hidden_layers.1.bias\", whose dimensions in the model are torch.Size([200]) and whose dimensions in the checkpoint are torch.Size([256]).\n\tWhile copying the parameter named \"hidden_layers.2.weight\", whose dimensions in the model are torch.Size([100, 200]) and whose dimensions in the checkpoint are torch.Size([128, 256]).\n\tWhile copying the parameter named \"hidden_layers.2.bias\", whose dimensions in the model are torch.Size([100]) and whose dimensions in the checkpoint are torch.Size([128]).\n\tWhile copying the parameter named \"output.weight\", whose dimensions in the model are torch.Size([10, 100]) and whose dimensions in the checkpoint are torch.Size([10, 128]).",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mRuntimeError\u001b[0m                              Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-10-d859c59ebec0>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfc_model\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNetwork\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m784\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m400\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m200\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m100\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;31m# This will throw an error because the tensor sizes are wrong!\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload_state_dict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstate_dict\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m/opt/conda/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36mload_state_dict\u001b[0;34m(self, state_dict, strict)\u001b[0m\n\u001b[1;32m    719\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merror_msgs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    720\u001b[0m             raise RuntimeError('Error(s) in loading state_dict for {}:\\n\\t{}'.format(\n\u001b[0;32m--> 721\u001b[0;31m                                self.__class__.__name__, \"\\n\\t\".join(error_msgs)))\n\u001b[0m\u001b[1;32m    722\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    723\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mparameters\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mRuntimeError\u001b[0m: Error(s) in loading state_dict for Network:\n\tWhile copying the parameter named \"hidden_layers.0.weight\", whose dimensions in the model are torch.Size([400, 784]) and whose dimensions in the checkpoint are torch.Size([512, 784]).\n\tWhile copying the parameter named \"hidden_layers.0.bias\", whose dimensions in the model are torch.Size([400]) and whose dimensions in the checkpoint are torch.Size([512]).\n\tWhile copying the parameter named \"hidden_layers.1.weight\", whose dimensions in the model are torch.Size([200, 400]) and whose dimensions in the checkpoint are torch.Size([256, 512]).\n\tWhile copying the parameter named \"hidden_layers.1.bias\", whose dimensions in the model are torch.Size([200]) and whose dimensions in the checkpoint are torch.Size([256]).\n\tWhile copying the parameter named \"hidden_layers.2.weight\", whose dimensions in the model are torch.Size([100, 200]) and whose dimensions in the checkpoint are torch.Size([128, 256]).\n\tWhile copying the parameter named \"hidden_layers.2.bias\", whose dimensions in the model are torch.Size([100]) and whose dimensions in the checkpoint are torch.Size([128]).\n\tWhile copying the parameter named \"output.weight\", whose dimensions in the model are torch.Size([10, 100]) and whose dimensions in the checkpoint are torch.Size([10, 128])."
     ]
    }
   ],
   "source": [
    "# Try this\n",
    "model = fc_model.Network(784, 10, [400, 200, 100])\n",
    "# This will throw an error because the tensor sizes are wrong!\n",
    "model.load_state_dict(state_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This means we need to rebuild the model exactly as it was when trained. Information about the model architecture needs to be saved in the checkpoint, along with the state dict. To do this, you build a dictionary with all the information you need to compeletely rebuild the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoint = {'input_size': 784,\n",
    "              'output_size': 10,\n",
    "              'hidden_layers': [each.out_features for each in model.hidden_layers],\n",
    "              'state_dict': model.state_dict()}\n",
    "\n",
    "torch.save(checkpoint, 'checkpoint.pth')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the checkpoint has all the necessary information to rebuild the trained model. You can easily make that a function if you want. Similarly, we can write a function to load checkpoints. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_checkpoint(filepath):\n",
    "    checkpoint = torch.load(filepath)\n",
    "    model = fc_model.Network(checkpoint['input_size'],\n",
    "                             checkpoint['output_size'],\n",
    "                             checkpoint['hidden_layers'])\n",
    "    model.load_state_dict(checkpoint['state_dict'])\n",
    "    \n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network(\n",
      "  (hidden_layers): ModuleList(\n",
      "    (0): Linear(in_features=784, out_features=400, bias=True)\n",
      "    (1): Linear(in_features=400, out_features=200, bias=True)\n",
      "    (2): Linear(in_features=200, out_features=100, bias=True)\n",
      "  )\n",
      "  (output): Linear(in_features=100, out_features=10, bias=True)\n",
      "  (dropout): Dropout(p=0.5)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "model = load_checkpoint('checkpoint.pth')\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "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.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
