{ "cells": [ { "cell_type": "markdown", "metadata": { "colab_type": "text", "execution": {}, "id": "view-in-github" }, "source": [ "\"Open   \"Open" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Data Augmentation in image classification models\n", "\n", "**By Neuromatch Academy**\n", "\n", "__Content creators:__ [Jama Hussein Mohamud](https://engmubarak48.github.io/jmohamud/index.html), [Alex Hernandez-Garcia](https://alexhernandezgarcia.github.io/)\n", "\n", "__Production editors:__ Spiros Chavlis, Saeed Salehi\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "----\n", "# Objective\n", "\n", "Data augmentation refers to synthetically increasing the amount of training data by transforming the existing training examples. Data augmentation has been shown to be a very useful technique, especially in computer vision applications. However, there are multiple ways of performing data augmentation and it is yet to be understood which transformations are more effective and why, and how data augmentation interacts with other techniques. In fact, it is common to see different augmentation schemes and setups in different papers. For example, there are perceptually possible image transformations (related to human visual perception), simple synthetic transformations such as cutout, more artificial transformations such as mixup that even transform the class labels, among many others. \n", "\n", "In this notebook, we will show how to train deep neural networks for image classification with data augmentation and analyse the results." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Setup\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install dependencies\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Install dependencies\n", "!pip install pandas --quiet" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# imports\n", "import os\n", "import csv\n", "import multiprocessing\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "import torch\n", "import torch.nn as nn\n", "import torch.optim as optim\n", "import torch.nn.functional as F\n", "import torch.backends.cudnn as cudnn\n", "from torch.autograd import Variable\n", "\n", "import torchvision\n", "import torchvision.transforms as transforms" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set random seed\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Executing `set_seed(seed=seed)` you are setting the seed\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Set random seed\n", "\n", "# @markdown Executing `set_seed(seed=seed)` you are setting the seed\n", "\n", "# for DL its critical to set the random seed so that students can have a\n", "# baseline to compare their results to expected results.\n", "# Read more here: https://pytorch.org/docs/stable/notes/randomness.html\n", "\n", "# Call `set_seed` function in the exercises to ensure reproducibility.\n", "import random\n", "import torch\n", "\n", "def set_seed(seed=None, seed_torch=True):\n", " if seed is None:\n", " seed = np.random.choice(2 ** 32)\n", " random.seed(seed)\n", " np.random.seed(seed)\n", " if seed_torch:\n", " torch.manual_seed(seed)\n", " torch.cuda.manual_seed_all(seed)\n", " torch.cuda.manual_seed(seed)\n", " torch.backends.cudnn.benchmark = False\n", " torch.backends.cudnn.deterministic = True\n", "\n", " print(f'Random seed {seed} has been set.')\n", "\n", "# In case that `DataLoader` is used\n", "def seed_worker(worker_id):\n", " worker_seed = torch.initial_seed() % 2**32\n", " np.random.seed(worker_seed)\n", " random.seed(worker_seed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set device (GPU or CPU)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Set device (GPU or CPU)\n", "\n", "# inform the user if the notebook uses GPU or CPU.\n", "\n", "def set_device():\n", " device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", " if device != \"cuda\":\n", " print(\"WARNING: For this notebook to perform best, \"\n", " \"if possible, in the menu under `Runtime` -> \"\n", " \"`Change runtime type.` select `GPU` \")\n", " else:\n", " print(\"GPU is enabled in this notebook.\")\n", "\n", " return device" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Random seed 2021 has been set.\n", "GPU is enabled in this notebook.\n" ] }, { "data": { "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" }, "text/plain": [ "'cuda'" ] }, "execution_count": 5, "metadata": { "tags": [] }, "output_type": "execute_result" } ], "source": [ "set_seed(seed=2021)\n", "set_device()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "### Training hyperparameters\n", "\n", "**Note:** We have reduced the number of epochs, `end_epochs`. The value was set to 200. Please, change it back and run the code." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# hyper-parameters\n", "use_cuda = torch.cuda.is_available()\n", "alpha = 1 # alpha for mixup augmentation\n", "best_acc = 0 # best test accuracy\n", "start_epoch = 0 # start from epoch 0 or last checkpoint epoch\n", "batch_size = 128\n", "end_apochs = 15 # Please change this to 200\n", "base_learning_rate = 0.1\n", "cutout = True # True/False if you want to use cutout augmentation\n", "mixup = False # True/False if you want to use mixup augmentation\n", "n_holes = 1 # number of holes to cut out from image for cutout\n", "length = 16 # length of the holes for cutout augmentation\n", "torchvision_transforms = False # True/False if you want use torchvision augmentations" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Augmentation" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Cutout\n", "Randomly mask out one or more patches from an image." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " `Cutout` Augmentation class\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @markdown `Cutout` Augmentation class\n", "\n", "class Cutout(object):\n", " \"\"\"\n", " code from: https://github.com/uoguelph-mlrg/Cutout\n", "\n", " Randomly mask out one or more patches from an image.\n", " Args:\n", " n_holes (int): Number of patches to cut out of each image.\n", " length (int): The length (in pixels) of each square patch.\n", " \"\"\"\n", " def __init__(self, n_holes, length):\n", " self.n_holes = n_holes\n", " self.length = length\n", "\n", " def __call__(self, img):\n", " \"\"\"\n", " Args:\n", " img (Tensor): Tensor image of size (C, H, W).\n", " Returns:\n", " Tensor: Image with n_holes of dimension length x length cut out of it.\n", " \"\"\"\n", " h = img.size(1)\n", " w = img.size(2)\n", "\n", " mask = np.ones((h, w), np.float32)\n", "\n", " for n in range(self.n_holes):\n", " y = np.random.randint(h)\n", " x = np.random.randint(w)\n", "\n", " y1 = np.clip(y - self.length // 2, 0, h)\n", " y2 = np.clip(y + self.length // 2, 0, h)\n", " x1 = np.clip(x - self.length // 2, 0, w)\n", " x2 = np.clip(x + self.length // 2, 0, w)\n", "\n", " mask[y1: y2, x1: x2] = 0.\n", "\n", " mask = torch.from_numpy(mask)\n", " mask = mask.expand_as(img)\n", " img = img * mask\n", "\n", " return img" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Mixup\n", "\n", "Mixup is a data augmentation technique that combines pairs of examples via a convex combination of the images and the labels. Given images $x_i$ and $x_j$ with labels $y_i$ and $y_j$, respectively, and $\\lambda \\in [0, 1]$, mixup creates a new image $\\hat{x}$ with label $\\hat{y}$ the following way:\n", "\n", "\\begin{align}\n", "\\hat{x} &= \\lambda x_i + (1 - \\lambda) x_j \\\\\n", "\\hat{y} &= \\lambda y_i + (1 - \\lambda) y_j\n", "\\end{align}\n", "\n", "You may check the [original paper](https://arxiv.org/abs/1710.09412) and [code repository](https://github.com/hongyi-zhang/mixup)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " `mixup_data` Augmentation function\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @markdown `mixup_data` Augmentation function\n", "\n", "def mixup_data(x, y, alpha=1.0, use_cuda=True):\n", " '''Compute the mixup data. Return mixed inputs, pairs of targets, and lambda\n", " - https://github.com/hongyi-zhang/mixup\n", " '''\n", " if alpha > 0.:\n", " lam = np.random.beta(alpha, alpha)\n", " else:\n", " lam = 1.\n", " batch_size = x.size()[0]\n", " if use_cuda:\n", " index = torch.randperm(batch_size).cuda()\n", " else:\n", " index = torch.randperm(batch_size)\n", "\n", " mixed_x = lam * x + (1 - lam) * x[index, :]\n", " y_a, y_b = y, y[index]\n", "\n", " return mixed_x, y_a, y_b, lam" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Data" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Datasets\n", "\n", "We will start using CIFAR-10 data set from PyTorch, but with small tweaks we can get any other data we are interested in. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Download and prepare Data\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "==> Preparing data...\n", "Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./CIFAR10/cifar-10-python.tar.gz\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "3a3a8eff5d924100831e7e7cd0202361", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(FloatProgress(value=0.0, max=170498071.0), HTML(value='')))" ] }, "metadata": { "tags": [] }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Extracting ./CIFAR10/cifar-10-python.tar.gz to ./CIFAR10\n", "Files already downloaded and verified\n" ] } ], "source": [ "# @markdown Download and prepare Data\n", "print('==> Preparing data...')\n", "def percentageSplit(full_dataset, percent=0.0):\n", " set1_size = int(percent * len(full_dataset))\n", " set2_size = len(full_dataset) - set1_size\n", " final_dataset, _ = torch.utils.data.random_split(full_dataset,\n", " [set1_size, set2_size])\n", " return final_dataset\n", "\n", "# CIFAR100 normalizing\n", "# mean = [0.5071, 0.4866, 0.4409]\n", "# std = [0.2673, 0.2564, 0.2762]\n", "\n", "# CIFAR10 normalizing\n", "mean = (0.4914, 0.4822, 0.4465)\n", "std = (0.2023, 0.1994, 0.2010)\n", "\n", "# torchvision transforms\n", "transform_train = transforms.Compose([])\n", "if torchvision_transforms:\n", " transform_train.transforms.append(transforms.RandomCrop(32, padding=4))\n", " transform_train.transforms.append(transforms.RandomHorizontalFlip())\n", "\n", "transform_train.transforms.append(transforms.ToTensor())\n", "transform_train.transforms.append(transforms.Normalize(mean, std))\n", "if cutout:\n", " transform_train.transforms.append(Cutout(n_holes=n_holes, length=length))\n", "\n", "transform_test = transforms.Compose([\n", " transforms.ToTensor(),\n", " transforms.Normalize(mean, std),\n", "])\n", "\n", "trainset = torchvision.datasets.CIFAR10(\n", " root='./CIFAR10', train=True, download=True,\n", " transform=transform_train)\n", "\n", "testset = torchvision.datasets.CIFAR10(\n", " root='./CIFAR10', train=False, download=True,\n", " transform=transform_test)" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "#### CIFAR-10\n", "\n", "CIFAR-10 is a data set of 50,000 colour (RGB) training images and 10,000 test images, of size 32 x 32 pixels. Each image is labelled as 1 of 10 possible classes: \n", "```\n", "'plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'\n", "```\n", "The data set is stored as a custom `torchvision.datasets.cifar.CIFAR` object. You can check some of its properties with the following code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Object type: \n", "Training data shape: (50000, 32, 32, 3)\n", "Test data shape: (10000, 32, 32, 3)\n", "Number of classes: 10\n" ] } ], "source": [ "print(f\"Object type: {type(trainset)}\")\n", "print(f\"Training data shape: {trainset.data.shape}\")\n", "print(f\"Test data shape: {testset.data.shape}\")\n", "print(f\"Number of classes: {np.unique(trainset.targets).shape[0]}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "size of the new trainset: 50000\n" ] } ], "source": [ "# choose percentage from the trainset. set percent = 1.0 to use the whole train data\n", "percent = 1.0\n", "trainset = percentageSplit(trainset, percent = percent)\n", "print(f\"size of the new trainset: {len(trainset)}\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Data loaders\n", "\n", "A dataloader is an optimized data iterator that provides functionality for efficient shuffling, transformation and batching of the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "----> number of workers: 4\n" ] } ], "source": [ "# Dataloader\n", "num_workers = multiprocessing.cpu_count()\n", "\n", "print(f'----> number of workers: {num_workers}')\n", "\n", "trainloader = torch.utils.data.DataLoader(\n", " trainset, batch_size=batch_size, shuffle=True, num_workers=num_workers)\n", "testloader = torch.utils.data.DataLoader(\n", " testset, batch_size=batch_size, shuffle=False, num_workers=num_workers)" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Visualization\n", "\n", "To visualize some of the augmentations, make sure you set to ```True``` their corresponding flags in the hyperparameters section" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# get batch of data\n", "batch_X, batch_Y = next(iter(trainloader))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "def plot_mixed_images(images):\n", " inv_normalize = transforms.Normalize(\n", " mean= [-m/s for m, s in zip(mean, std)],\n", " std= [1/s for s in std]\n", " )\n", " inv_PIL = transforms.ToPILImage()\n", " fig = plt.figure(figsize=(10, 8))\n", " for i in range(1, len(images) + 1):\n", " image = images[i-1]\n", " ax = fig.add_subplot(1, 4, i)\n", " inv_tensor = inv_normalize(image).cpu()\n", " ax.imshow(inv_PIL(inv_tensor))\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Mixup Visualization\n", "if mixup:\n", " alpha = 0.9\n", " mixed_x, y_a, y_b, lam = mixup_data(batch_X, batch_Y,\n", " alpha=alpha, use_cuda=use_cuda)\n", " plot_mixed_images(mixed_x[:4])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light", "tags": [] }, "output_type": "display_data" } ], "source": [ "# Mixup Visualization\n", "if cutout:\n", " plot_mixed_images(batch_X[:4])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Torchvision Visualization\n", "if torchvision_transforms:\n", " plot_mixed_images(batch_X[:4])" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Model" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Architecture: ResNet\n", "\n", "ResNet is a family of network architectures whose main property is that the network is organised as a stack of _residual blocks_. Residual blocks consist of a stack of layers whose output is added the input, making a _shortcut connection_.\n", "\n", "See the [original paper](https://arxiv.org/abs/1512.03385) for more details.\n", "\n", "ResNet is just a popular choice out of many others, but data augmentation works well in general. We just picked ResNet for illustration purposes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ResNet model in PyTorch\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @markdown ResNet model in PyTorch\n", "\n", "class BasicBlock(nn.Module):\n", " \"\"\"ResNet in PyTorch.\n", " Reference:\n", " [1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun\n", " Deep Residual Learning for Image Recognition.\n", " arXiv:1512.03385\n", " \"\"\"\n", " expansion = 1\n", "\n", " def __init__(self, in_planes, planes, stride=1):\n", " super(BasicBlock, self).__init__()\n", " self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n", " self.bn1 = nn.BatchNorm2d(planes)\n", " self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)\n", " self.bn2 = nn.BatchNorm2d(planes)\n", "\n", " self.shortcut = nn.Sequential()\n", " if stride != 1 or in_planes != self.expansion*planes:\n", " self.shortcut = nn.Sequential(\n", " nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),\n", " nn.BatchNorm2d(self.expansion*planes)\n", " )\n", "\n", " def forward(self, x):\n", " out = F.relu(self.bn1(self.conv1(x)))\n", " out = self.bn2(self.conv2(out))\n", " out += self.shortcut(x)\n", " out = F.relu(out)\n", " return out\n", "\n", "class Bottleneck(nn.Module):\n", " expansion = 4\n", "\n", " def __init__(self, in_planes, planes, stride=1):\n", " super(Bottleneck, self).__init__()\n", " self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)\n", " self.bn1 = nn.BatchNorm2d(planes)\n", " self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n", " self.bn2 = nn.BatchNorm2d(planes)\n", " self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False)\n", " self.bn3 = nn.BatchNorm2d(self.expansion*planes)\n", "\n", " self.shortcut = nn.Sequential()\n", " if stride != 1 or in_planes != self.expansion*planes:\n", " self.shortcut = nn.Sequential(\n", " nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),\n", " nn.BatchNorm2d(self.expansion*planes)\n", " )\n", "\n", " def forward(self, x):\n", " out = F.relu(self.bn1(self.conv1(x)))\n", " out = F.relu(self.bn2(self.conv2(out)))\n", " out = self.bn3(self.conv3(out))\n", " out += self.shortcut(x)\n", " out = F.relu(out)\n", " return out\n", "\n", "\n", "class ResNet(nn.Module):\n", " def __init__(self, block, num_blocks, num_classes=10):\n", " super(ResNet, self).__init__()\n", " self.in_planes = 64\n", "\n", " self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n", " self.bn1 = nn.BatchNorm2d(64)\n", " self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)\n", " self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)\n", " self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)\n", " self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)\n", " self.linear = nn.Linear(512*block.expansion, num_classes)\n", "\n", " def _make_layer(self, block, planes, num_blocks, stride):\n", " strides = [stride] + [1]*(num_blocks-1)\n", " layers = []\n", " for stride in strides:\n", " layers.append(block(self.in_planes, planes, stride))\n", " self.in_planes = planes * block.expansion\n", " return nn.Sequential(*layers)\n", "\n", " def forward(self, x):\n", " out = F.relu(self.bn1(self.conv1(x)))\n", " out = self.layer1(out)\n", " out = self.layer2(out)\n", " out = self.layer3(out)\n", " out = self.layer4(out)\n", " out = F.avg_pool2d(out, 4)\n", " out = out.view(out.size(0), -1)\n", " out = self.linear(out)\n", " return out\n", "\n", "\n", "def ResNet18():\n", " return ResNet(BasicBlock, [2, 2, 2, 2])\n", "\n", "\n", "def ResNet34():\n", " return ResNet(BasicBlock, [3, 4, 6, 3])\n", "\n", "\n", "def ResNet50():\n", " return ResNet(Bottleneck, [3, 4, 6, 3])" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Model setup and test" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-----> verify if model is run on random data\n", "model loaded\n", "Using 1 GPUs.\n", "Using CUDA..\n" ] } ], "source": [ "# load the Model\n", "net = ResNet18()\n", "print('-----> verify if model is run on random data')\n", "y = net(Variable(torch.randn(1,3,32,32)))\n", "print('model loaded')\n", "\n", "result_folder = './results/'\n", "if not os.path.exists(result_folder):\n", " os.makedirs(result_folder)\n", "\n", "logname = result_folder + net.__class__.__name__ + '_' + '.csv'\n", "\n", "if use_cuda:\n", " net.cuda()\n", " net = torch.nn.DataParallel(net)\n", " print('Using', torch.cuda.device_count(), 'GPUs.')\n", " cudnn.benchmark = True\n", " print('Using CUDA..')" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Training" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Loss function and Optimizer\n", "\n", "We use the cross entropy loss, commonly used for classification, and stochastic gradient descent (SGD) as optimizer, with momentum and weight decay." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# optimizer and criterion\n", "\n", "def mixup_criterion(y_a, y_b, lam):\n", " '''\n", " - Mixup criterion\n", " - https://github.com/hongyi-zhang/mixup\n", " '''\n", " return lambda criterion, pred: lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)\n", "\n", "criterion = nn.CrossEntropyLoss() # only for test data\n", "optimizer = optim.SGD(net.parameters(), lr=base_learning_rate, momentum=0.9, weight_decay=1e-4)" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Train and test loops" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Training & Test functions\n", "def train(epoch, alpha, use_cuda=False):\n", " print('\\nEpoch: %d' % epoch)\n", " net.train()\n", " train_loss = 0\n", " correct = 0\n", " total = 0\n", " for batch_idx, (inputs, targets) in enumerate(trainloader):\n", " if use_cuda:\n", " inputs, targets = inputs.cuda(), targets.cuda()\n", " optimizer.zero_grad()\n", " if mixup:\n", " # generate mixed inputs, two one-hot label vectors and mixing coefficient\n", " inputs, targets_a, targets_b, lam = mixup_data(inputs, targets, alpha, use_cuda)\n", " inputs, targets_a, targets_b = Variable(inputs), Variable(targets_a), Variable(targets_b)\n", " outputs = net(inputs)\n", " loss_func = mixup_criterion(targets_a, targets_b, lam)\n", " loss = loss_func(criterion, outputs)\n", " else:\n", " inputs, targets = Variable(inputs), Variable(targets)\n", " outputs = net(inputs)\n", " loss = criterion(outputs, targets)\n", "\n", " loss.backward()\n", " optimizer.step()\n", "\n", " train_loss += loss.item()\n", " _, predicted = torch.max(outputs.data, 1)\n", " total += targets.size(0)\n", " if mixup:\n", " correct += lam * predicted.eq(targets_a.data).cpu().sum() + (1 - lam) * predicted.eq(targets_b.data).cpu().sum()\n", " else:\n", " correct += predicted.eq(targets.data).cpu().sum()\n", "\n", " if batch_idx % 500 == 0:\n", " print(batch_idx, len(trainloader), 'Loss: %.3f | Acc: %.3f%% (%d/%d)'\n", " % (train_loss/(batch_idx+1), 100.*correct/total, correct, total))\n", " return (train_loss/batch_idx, 100.*correct/total)\n", "\n", "\n", "def test(epoch, use_cuda=False):\n", " global best_acc\n", " net.eval()\n", " test_loss = 0\n", " correct = 0\n", " total = 0\n", " with torch.no_grad():\n", " for batch_idx, (inputs, targets) in enumerate(testloader):\n", " if use_cuda:\n", " inputs, targets = inputs.cuda(), targets.cuda()\n", " # inputs, targets = Variable(inputs, volatile=True), Variable(targets)\n", " outputs = net(inputs)\n", " loss = criterion(outputs, targets)\n", "\n", " test_loss += loss.item()\n", " _, predicted = torch.max(outputs.data, 1)\n", " total += targets.size(0)\n", " correct += predicted.eq(targets.data).cpu().sum()\n", "\n", " if batch_idx % 200 == 0:\n", " print(batch_idx, len(testloader), 'Loss: %.3f | Acc: %.3f%% (%d/%d)'\n", " % (test_loss/(batch_idx+1), 100.*correct/total, correct, total))\n", "\n", " # Save checkpoint.\n", " acc = 100.*correct/total\n", " if acc > best_acc:\n", " best_acc = acc\n", " checkpoint(acc, epoch)\n", " return (test_loss/batch_idx, 100.*correct/total)" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "### Auxiliary functions\n", "\n", "* `checkpoint()`: Store checkpoints of the model\n", "* `adjust_learning_rate()`: Decreases the learning rate (learning rate decay) at certain epochs of training." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " `checkpoint` and `adjust_learning_rate` functions\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @markdown `checkpoint` and `adjust_learning_rate` functions\n", "def checkpoint(acc, epoch):\n", " # Save checkpoint.\n", " print('Saving..')\n", " state = {\n", " 'net': net.state_dict(),\n", " 'acc': acc,\n", " 'epoch': epoch,\n", " 'rng_state': torch.get_rng_state()\n", " }\n", " if not os.path.isdir('checkpoint'):\n", " os.mkdir('checkpoint')\n", " torch.save(state, './checkpoint/ckpt.t7')\n", "\n", "\n", "def adjust_learning_rate(optimizer, epoch):\n", " \"\"\"decrease the learning rate at 100 and 150 epoch\"\"\"\n", " lr = base_learning_rate\n", " if epoch <= 9 and lr > 0.1:\n", " # warm-up training for large minibatch\n", " lr = 0.1 + (base_learning_rate - 0.1) * epoch / 10.\n", " if epoch >= 100:\n", " lr /= 10\n", " if epoch >= 150:\n", " lr /= 10\n", " for param_group in optimizer.param_groups:\n", " param_group['lr'] = lr" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Epoch: 0\n", "0 391 Loss: 2.443 | Acc: 10.938% (14/128)\n", "0 79 Loss: 1.531 | Acc: 46.094% (59/128)\n", "Saving..\n", "Epoch: 0 | train acc: 31.604000091552734 | test acc: 44.2599983215332\n", "\n", "Epoch: 1\n", "0 391 Loss: 1.619 | Acc: 39.844% (51/128)\n", "0 79 Loss: 1.199 | Acc: 60.156% (77/128)\n", "Saving..\n", "Epoch: 1 | train acc: 47.03200149536133 | test acc: 54.41999816894531\n", "\n", "Epoch: 2\n", "0 391 Loss: 1.301 | Acc: 53.906% (69/128)\n", "0 79 Loss: 1.013 | Acc: 61.719% (79/128)\n", "Saving..\n", "Epoch: 2 | train acc: 56.257999420166016 | test acc: 62.599998474121094\n", "\n", "Epoch: 3\n", "0 391 Loss: 1.036 | Acc: 64.062% (82/128)\n", "0 79 Loss: 0.909 | Acc: 69.531% (89/128)\n", "Saving..\n", "Epoch: 3 | train acc: 62.43199920654297 | test acc: 65.6500015258789\n", "\n", "Epoch: 4\n", "0 391 Loss: 0.839 | Acc: 68.750% (88/128)\n", "0 79 Loss: 0.859 | Acc: 70.312% (90/128)\n", "Saving..\n", "Epoch: 4 | train acc: 67.0 | test acc: 69.08999633789062\n", "\n", "Epoch: 5\n", "0 391 Loss: 0.922 | Acc: 64.844% (83/128)\n", "0 79 Loss: 0.660 | Acc: 76.562% (98/128)\n", "Saving..\n", "Epoch: 5 | train acc: 70.1259994506836 | test acc: 72.52999877929688\n", "\n", "Epoch: 6\n", "0 391 Loss: 0.833 | Acc: 65.625% (84/128)\n", "0 79 Loss: 0.616 | Acc: 78.125% (100/128)\n", "Saving..\n", "Epoch: 6 | train acc: 73.45999908447266 | test acc: 73.45999908447266\n", "\n", "Epoch: 7\n", "0 391 Loss: 0.686 | Acc: 75.000% (96/128)\n", "0 79 Loss: 0.533 | Acc: 81.250% (104/128)\n", "Saving..\n", "Epoch: 7 | train acc: 75.99600219726562 | test acc: 75.91000366210938\n", "\n", "Epoch: 8\n", "0 391 Loss: 0.626 | Acc: 78.125% (100/128)\n", "0 79 Loss: 0.458 | Acc: 82.031% (105/128)\n", "Saving..\n", "Epoch: 8 | train acc: 78.42400360107422 | test acc: 79.11000061035156\n", "\n", "Epoch: 9\n", "0 391 Loss: 0.465 | Acc: 85.938% (110/128)\n", "0 79 Loss: 0.465 | Acc: 87.500% (112/128)\n", "Saving..\n", "Epoch: 9 | train acc: 80.72599792480469 | test acc: 80.37000274658203\n", "\n", "Epoch: 10\n", "0 391 Loss: 0.509 | Acc: 81.250% (104/128)\n", "0 79 Loss: 0.523 | Acc: 79.688% (102/128)\n", "Epoch: 10 | train acc: 82.16400146484375 | test acc: 79.25\n", "\n", "Epoch: 11\n", "0 391 Loss: 0.423 | Acc: 82.031% (105/128)\n", "0 79 Loss: 0.610 | Acc: 78.125% (100/128)\n", "Epoch: 11 | train acc: 83.96199798583984 | test acc: 79.68000030517578\n", "\n", "Epoch: 12\n", "0 391 Loss: 0.221 | Acc: 89.844% (115/128)\n", "0 79 Loss: 0.467 | Acc: 82.812% (106/128)\n", "Saving..\n", "Epoch: 12 | train acc: 85.61799621582031 | test acc: 80.88999938964844\n", "\n", "Epoch: 13\n", "0 391 Loss: 0.427 | Acc: 85.938% (110/128)\n", "0 79 Loss: 0.522 | Acc: 82.812% (106/128)\n", "Saving..\n", "Epoch: 13 | train acc: 87.21199798583984 | test acc: 81.54000091552734\n", "\n", "Epoch: 14\n", "0 391 Loss: 0.216 | Acc: 93.750% (120/128)\n", "0 79 Loss: 0.386 | Acc: 86.719% (111/128)\n", "Epoch: 14 | train acc: 88.08000183105469 | test acc: 81.44999694824219\n" ] } ], "source": [ "# start training\n", "if not os.path.exists(logname):\n", " with open(logname, 'w') as logfile:\n", " logwriter = csv.writer(logfile, delimiter=',')\n", " logwriter.writerow(['epoch', 'train loss', 'train acc',\n", " 'test loss', 'test acc'])\n", "\n", "for epoch in range(start_epoch, end_apochs):\n", " adjust_learning_rate(optimizer, epoch)\n", " train_loss, train_acc = train(epoch, alpha, use_cuda=use_cuda)\n", " test_loss, test_acc = test(epoch, use_cuda=use_cuda)\n", " with open(logname, 'a') as logfile:\n", " logwriter = csv.writer(logfile, delimiter=',')\n", " logwriter.writerow([epoch, train_loss, train_acc.item(),\n", " test_loss, test_acc.item()])\n", " print(f'Epoch: {epoch} | train acc: {train_acc} | test acc: {test_acc}')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
epochtrain losstrain acctest losstest acc
001.93213031.6040001.53523344.259998
111.44686347.0320011.26277954.419998
221.21251856.2579991.06959362.599998
331.05185062.4319990.99647665.650002
440.92813167.0000000.89835469.089996
\n", "
" ], "text/plain": [ " epoch train loss train acc test loss test acc\n", "0 0 1.932130 31.604000 1.535233 44.259998\n", "1 1 1.446863 47.032001 1.262779 54.419998\n", "2 2 1.212518 56.257999 1.069593 62.599998\n", "3 3 1.051850 62.431999 0.996476 65.650002\n", "4 4 0.928131 67.000000 0.898354 69.089996" ] }, "execution_count": 24, "metadata": { "tags": [] }, "output_type": "execute_result" } ], "source": [ "# plot results\n", "results = pd.read_csv('/content/results/ResNet_.csv', sep=',')\n", "results.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average test Accuracy over 15 epochs: 72.0\n", "best test accuraccy over 15 epochs: 81.54000091552734\n" ] } ], "source": [ "train_accuracy = results['train acc'].values\n", "test_accuracy = results['test acc'].values\n", "\n", "print(f\"Average test Accuracy over {end_apochs} epochs: {sum(test_accuracy)//len(test_accuracy)}\")\n", "print(f\"best test accuraccy over {end_apochs} epochs: {max(test_accuracy)}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light", "tags": [] }, "output_type": "display_data" } ], "source": [ "figureName = 'WithMixUp' # change figure name\n", "\n", "plt.figure(figsize=(9, 6))\n", "plt.plot(results['epoch'].values, train_accuracy, label='train')\n", "plt.plot(results['epoch'].values, test_accuracy, label='test')\n", "plt.xlabel('Number of epochs')\n", "plt.ylabel('Accuracy')\n", "plt.title(f'Train/Test Accuracy curve for {end_apochs} epochs')\n", "plt.savefig(f'/content/results/{figureName}.png')\n", "plt.legend()\n", "plt.show()" ] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [ "MHZXiV5ZsWXq" ], "include_colab_link": true, "machine_shape": "hm", "name": "data_augmentation", "provenance": [], "toc_visible": true }, "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }