{"cells":[{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://user-images.githubusercontent.com/33928040/105023234-b5ed5900-5a70-11eb-9b3f-0f64afc99038.jpeg\"  width=\"1000\" height=\"200\">\n\n\n<h1><center>↫ Transfer Learning ↬</center></h1>"},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"pip install git+https://github.com/abhishekkrthakur/wtfml.git","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"!pip install pretrainedmodels albumentations","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"from IPython.core.display import HTML\n\n# import Source Code Pro font\ndisplay(HTML(\"\"\"\n<style>\n@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap');\n</style>\n\"\"\"))\n\ndef css_styling():\n    styles = open(\"../input/sloth-styling-css/alerts.css\", \"r\").read()\n    return HTML(\"<style>\"+styles+\"</style>\")\ncss_styling()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 1. Introduction\n\nHello everyone, in this notebook I want to share how I used <span style=\"background:#F2F2F2; font-weight:bold; color:#000000\">PyTorch</span> for *transfer learning* for classifying dogs and cats. The code template is inspired by <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://www.youtube.com/watch?v=WaCFd-vL4HA\">this</a></span> YouTube tutorial. We will be using `se_resnext50_32x4d` model from the `pretrainedmodels` package.\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Learning Objectives</span>📙</h2>\n\n<ol>\n    <li>How to use PyTorch for transfer learning?</li>\n    <li>How to use joblib to parallelize resizing of images?</li>\n</ol>\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Prerequisite</span>🚨</h2>\n\nHere are a few prerequisites. One should know:\n\n<ol>\n    <li>How to code in Python,</li>\n    <li>The basics of ANN and CNN, and</li>\n    <li>The basics of PyTorch</li>\n</ol>\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Data</span>💡</h2>\n\nWe are going to make use of the <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition\">Dogs vs Cats</a></span> dataset.\n\nThe dataset contains dogs and cats images and our goal is to make a model that will classify images accurately."},{"metadata":{},"cell_type":"markdown","source":"# 2. Importing Libraries & Reading Data\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Imports</span>🔛</h2>\n\nLet us see all the required libraries we are going to use in this notebook:\n\n<div class=\"alert success-alert\">\n  <p>NumPy🧮: for numerical computation.</p>\n  <p>Pandas🐼: for data manipulation and analysis.</p>\n  <p>Matplotlib📊: for creating visualizations.</p>\n  <p>Joblib⚓: for providing lightweight pipelining in Python.</p>\n  <p>Pillow🛌: for opening, manipulating, and saving many different image file formats.</p>\n  <p>Tqdm🆒: for making smart progress meter.</p>\n  <p>PyTorch🔦: for building artificial neural network.</p>\n  <p>Scikit-Learn🤖: provides tools for predictive data analysis.</p>\n  <p>Pretrainedmodels💪: provides pretrained ConvNets for pytorch.</p>\n  <p>Albumentations🤸: for image augmentation</p>\n  <p>WTFML🚀: for providing flexibility while using PyTorch.</p>\n</div>\n\n<br>\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Datasets</span>💽</h2>\n\nWe have two datasets files:\n    \n<ol>\n    <li><b>train.zip</b>: contains images that we will use for training and evaluation.</li>\n    <li><b>test.zip</b>: contains images that we will use for submission.</li>\n</ol>\n\n> Note📌: There are no null values in either of the data set.\n\n<b>👇 Version Number and Summary of Datasets</b>"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib as mpl\nimport matplotlib.pyplot as plt\nfrom matplotlib import rcParams\n\nimport sys, os, joblib, PIL, shutil\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nimport sklearn\nfrom sklearn import metrics\n\nimport pretrainedmodels, albumentations, wtfml\nfrom wtfml.data_loaders.image.classification import ClassificationDataset\nfrom wtfml.utils import EarlyStopping\nfrom wtfml.engine import Engine\n\n# default font-family\nrcParams[\"font.family\"] = \"serif\"\n\n# set a default random seed\nRANDOM_SEED = 42\n\n# set pandas options\npd.set_option(\"display.max_columns\", None)\n\n# version used\nprint(f\"🐍: {sys.version}\")\nprint()\nprint(\"⟿ Version🔗:\")\nprint()\nprint(f\"    NumPy version: {np.__version__}\")\nprint()\nprint(f\"    Pandas version: {pd.__version__}\")\nprint()\nprint(f\"    Matplotlib version: {mpl.__version__}\")\nprint()\nprint(f\"    Joblib version: {joblib.__version__}\")\nprint()\nprint(f\"    Pillow version: {PIL.__version__}\")\nprint()\nprint(f\"    PyTorch version: {torch.__version__}\")\nprint()\nprint(f\"    Scikit-Learn version: {sklearn.__version__}\")\nprint()\nprint(f\"    PreTrainedModels version: {pretrainedmodels.__version__}\")\nprint()\nprint(f\"    Albumentations version: {albumentations.__version__}\")\nprint()\nprint(f\"    WTFML version: {wtfml.__version__}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Unzip The Data\n\n* Since our train and test images are present inside a zip file, so our first step is to unzip all the images.\n\n* We will unzip images to location: <span style=\"background:#F2F2F2; font-weight:normal; color:#000000\">kaggle/temp</span>.\n\n<b>✅ Click the toggle button on the right to access the code.</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"!unzip ../input/dogs-vs-cats-redux-kernels-edition/test.zip -d /kaggle/temp/\n!unzip ../input/dogs-vs-cats-redux-kernels-edition/train.zip -d /kaggle/temp/","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Navigating The Directory Tree</span>🌴</h2>\n\n* As we have unzipped the images to their respective directories, let us see the images present inside our train directory.\n\n> Note📌: The present working directory is <span style=\"background:#F2F2F2; font-weight:normal; color:#000000\">kaggle/working</span>, and the images are saved in the location <span style=\"background:#F2F2F2; font-weight:normal; color:#000000\">kaggle/temp/</span>\n\n<b>👇 Current Working Directory and Number of Train Images</b>"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# train and test path\ntrain_path = \"../temp/train\"\ntest_path = \"../temp/test\"\n\n# total number of files present\nprint(f\"The total number of images present: {len(os.listdir(train_path))}\")\n\n# total number of dogs and cats images\nprint(f\"    ⟿ Total images of dogs: {len([file for file in os.listdir(train_path) if file[0:3] == 'dog'])}\")\nprint(f\"    ⟿ Total images of cats: {len([file for file in os.listdir(train_path) if file[0:3] == 'cat'])}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Visualizing The Images</span>🆒</h2>\n\n* Let's now define a function that will plot images for dogs and cats.\n\n* The function will take a `value` (i.e. dog or cat or both), `path` (where the images are stored), and `title` (the title of the plot). It will plot 12 random images based on the `value` argument.\n\n<b>👇 Dogs Images In Train-Directory</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def plot_images(value, path, title):\n    \"\"\"\n    Function to plot dogs/cats images.\n    \n    Args:\n        value (str): either dog or cat or both.\n        path (str): where images ae present.\n        title (str, optional): title of the plot.\n    \n    Returns:\n        figure.Figure: figure object.\n        axes.Axes: axes object.\n    \"\"\"\n    # create subplots\n    fig, axes = plt.subplots(\n        nrows = 3, ncols=4, facecolor=\"#FFFFFF\",\n        figsize=(8,6), dpi=450\n    )\n    \n    # all images for given value\n    if value != \"both\":\n        images = [file for file in os.listdir(path) if file[0:3] == value]\n    else:\n        images = [file for file in os.listdir(path)]\n        \n    # select random numbers for indices\n    random_index = np.random.randint(0, len(images) - 1, 12)\n\n    # select 24 dogs images based on random_index\n    select_images = np.array(images)[random_index]\n    \n    # traverse the axes and plot the images\n    for count, ax in tqdm(enumerate(fig.get_axes()), desc=\"Plotting Images\", total=len(select_images)):\n        # set facecolor\n        ax.set_facecolor(\"#FFFFFF\")\n        \n        # read the image\n        img = plt.imread(f\"{path}/{select_images[count]}\")\n        \n        # plot the image\n        ax.imshow(img)\n        \n        # tidy axis\n        ax.axis(\"off\")\n    \n    # configure layout\n    plt.tight_layout(pad=0.5)\n\n    # add figure title\n    fig.text(0.5, 1.07, title, fontsize=20, color=\"#121212\", fontstyle=\"italic\", ha=\"center\")\n    \n    fig.subplots_adjust(hspace=0, wspace=0)\n        \n    return fig, axes","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# plot dogs images\nfig, ax = plot_images(\"dog\", train_path, \"Woof! Woof!\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Cats Images In Train-Directory</b>"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# plot cats images\nfig, ax = plot_images(\"cat\", train_path, \"Meow! Meow!\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Split The Data\n\n* There are `25000` images present inside the *train* directory. We are going to make three dataset out of all `25000` images:\n    \n    * `train-set`: our training set will include `22000` images (11000 dogs & 11000 cats).\n    \n    * `valid-set`: our validation set will include `1500` images (750 dogs & 750 cats).\n    \n    * `test-set`: our test set will include `1500` images (750 dogs & 750 cats).\n    \n* Here, we will prepare three lists which will contain image names for train, valid and test set.\n\n<b>👇 Splits and Number of Images</b>"},{"metadata":{},"cell_type":"markdown","source":""},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# set random seed for numpy\nnp.random.seed(RANDOM_SEED)\n\n# all dogs and cats images\ndogs_images = sorted(np.array([file for file in os.listdir(train_path) if file[:3] == \"dog\"]))\ncats_images = sorted(np.array([file for file in os.listdir(train_path) if file[:3] == \"cat\"]))\n\n# shuffle both the array\nnp.random.shuffle(dogs_images)\nnp.random.shuffle(cats_images)\n\n# start and end indices for train, valid and test sets\ntrain_start, train_end = 0, 11000\nvalid_start, valid_end = 11000, 11750\ntest_start, test_end = 11750, 12501\n\n# make train-set, valid-set and test-set\ntrain_set = dogs_images[train_start: train_end] + cats_images[train_start: train_end]\nvalid_set = dogs_images[valid_start: valid_end] + cats_images[valid_start: valid_end]\ntest_set = dogs_images[test_start: test_end] + cats_images[test_start: test_end]\n\n# shuffle all the data sets\nnp.random.shuffle(train_set)\nnp.random.shuffle(valid_set)\nnp.random.shuffle(test_set)\n\nprint(\"⟿ For Dogs:\")\nprint(f\"    Number of images in train-set: {len(dogs_images[train_start: train_end])}\")\nprint(f\"    Number of images in valid-set: {len(dogs_images[valid_start: valid_end])}\")\nprint(f\"    Number of images in test-set:  {len(dogs_images[test_start: test_end])}\")\nprint()\nprint(\"⟿ For Cats:\")\nprint(f\"    Number of images in train-set: {len(cats_images[train_start: train_end])}\")\nprint(f\"    Number of images in valid-set: {len(cats_images[valid_start: valid_end])}\")\nprint(f\"    Number of images in test-set:  {len(cats_images[test_start: test_end])}\")\nprint()\nprint(\"⟿ Final Datasets:\")\nprint(f\"    Length of train-set: {len(train_set)}\")\nprint(f\"    Length of valid-set: {len(valid_set)}\")\nprint(f\"    Length of test-set:  {len(test_set)}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Resize Images\n\n* By default the `se_resnext50_32x4d` model takes input image <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://github.com/Cadene/pretrained-models.pytorch/blob/master/pretrainedmodels/models/senet.py#L60\">size</a></span> as `224x224`.\n\n* Since, the image size in our dataset is not *224x224*, we now will resize the images to the required size. To speed up the task we are going to make use of `joblib` to *parallelize* the process.\n\n* We are also going to make *four* new directories `train_set`, `valid_set`, `test_set` and `kaggle_test_set` containing resized images from `train` and `test` directories, and will delete the `train` and `test` directories we had initially.\n\n<b>✅ Click the toggle button on the right to access the resize-function.</b>"},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"def resize_image(image_path, output_dir, resize):\n    \"\"\"\n    Function to resize one image.\n    \n    Args:\n        image_path (str): path to the image.\n        output_dir (str): directory where final image will be stored.\n        resize (tuple): required shape of the image.\n    \"\"\"\n    # fetch the base name of the image\n    base_name = os.path.basename(image_path)\n    \n    # set the output path\n    out_path = os.path.join(output_dir, base_name)\n    \n    # open the image using pillow\n    image = PIL.Image.open(image_path)\n    \n    # resize the image\n    image = image.resize(\n        (resize[0], resize[1]), resample=PIL.Image.BILINEAR\n    )\n    \n    # save the image\n    image.save(out_path)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Resizing Images From train Directory</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# make three directories - for train, valid and test\nos.mkdir(\"../temp/train_set\")\nos.mkdir(\"../temp/valid_set\")\nos.mkdir(\"../temp/test_set\")\n\n# resize the train-set images\njoblib.Parallel(n_jobs=12)(\n    joblib.delayed(resize_image)(\n        f\"{train_path}/{i}\", \"../temp/train_set\", (224, 224)\n    ) for _, i in tqdm(enumerate(train_set))\n)\n\n# resize the valid-set images\njoblib.Parallel(n_jobs=12)(\n    joblib.delayed(resize_image)(\n        f\"{train_path}/{i}\", \"../temp/valid_set\", (224, 224)\n    ) for _, i in tqdm(enumerate(valid_set))\n)\n\n# resize the test-set images\njoblib.Parallel(n_jobs=12)(\n    joblib.delayed(resize_image)(\n        f\"{train_path}/{i}\", \"../temp/test_set\", (224, 224)\n    ) for _, i in tqdm(enumerate(test_set))\n)\n\n# remove the train directory\nshutil.rmtree(train_path)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Resizing Images From test Directory</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# make directory for kaggle-test-set\nos.mkdir(\"../temp/kaggle_test_set\")\n\n# fetch image names\nkaggle_images = os.listdir(test_path)\n\n# resize the test-set images\njoblib.Parallel(n_jobs=12)(\n    joblib.delayed(resize_image)(\n        f\"{test_path}/{i}\", \"../temp/kaggle_test_set\", (224, 224)\n    ) for _, i in tqdm(enumerate(kaggle_images))\n)\n\n# remove test1 directory\nshutil.rmtree(test_path)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Resized Images From train_set Directory</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# plot images\nfig, ax = plot_images(\"both\", \"../temp/train_set\", \"Images in train-set\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Final Directories & Images Inside Them</b>"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"dirs = os.listdir(\"../temp\")\n\nprint(f\"Directries present: {dirs}\")\nprint()\n\nprint(\"⟿ Images Present:\")\n\nfor dir_ in dirs:\n    print(f\"    In {dir_}: {len(os.listdir(f'../temp/{dir_}'))}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Using Pre-Trained Model\n\n* We here are using `se_resnext50_32x4d` model from `pretrainedmodels` package. This model represents <span style=\"background:#F2F2F2; font-weight:normal; color:black\">SE-ResNet</span> architecture and is trained on the <span style=\"background:#F2F2F2; font-weight:normal; color:black\">Imagenet</span> dataset.\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">SENet</span>🔝</h2>\n\n* *SENet* is the winning architecture in the ILSVRC 2017 challenge.\n\n* SENet stands for <span style=\"background:#F2F2F2; font-weight:bold; color:black\">Squeeze-and-Excitation Network</span>. This architecture extends other architectures such as `Inception Networks` and `Residual Networks` and boosts their performance.\n\n* The extended versions of `Inception Networks` and `ResNets` are called `SE-inception` and `SE-ResNet` respectively.\n\n* The boost comes from the fact that a SENet adds a small neural network, called an *SE block*, to every unit in the original architecture (i.e. every inception module or every residual unit).\n\n<center>\n<img src=\"https://user-images.githubusercontent.com/33928040/107626693-307e5280-6c84-11eb-82d6-23309d49e856.jpeg\" height=500 width=1000>\n</center>\n\n<br>\n\n* A SE block analyzes the output of the unit it is attached to, focusing exclusively on the depth dimension, and learns which features are mostly active together. It then uses this information to recalibrate the feature maps.\n\n* For example, an SE block may learn that mouth, noses and eyes usually appear together in the pictures: if you see a mouth and a nose, you should expect to see eyes as well.\n\n* So, if the block sees a strong activation in the mouth and nose feature maps, but only mild activation in the eye feature map, it will boost the eye feature map (more accurately, it will reduce irrelevant feature maps).\n\n* If the eyes were somewhat confused with something else, this feature map recalibration will help resolve the ambiguity.\n\n<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">se_resnext50_32x4d</span>🏅</h2>\n\n* We are going to use the <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://github.com/Cadene/pretrained-models.pytorch/blob/master/pretrainedmodels/models/senet.py#L347\">features</a></span> method defined in `SENet` class in `pretrainedmodels` package. This method returns `2048` feature values.\n\n* We will then feed these `2048` feature values to our output layer which outputs a single floating number ranging between 0 to 1. (i.e. we will use `sigmoid` as our activation function for the output layer, `0` is `cat` and `1` is `dog`.\n\n* To compute the loss we are using `Binary Cross Entropy` loss metric.\n\n<b>👇 Download Required Model</b>"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_kg_hide-output":false},"cell_type":"code","source":"class SEResNext50_32x4d(nn.Module):\n    \"\"\"\n    class for our neural network architecture.\n    \"\"\"\n    \n    def __init__(self, pretrained=\"imagenet\"):\n        \"\"\"\n        Function to init object of the class.\n        \n        Args:\n            pretrained (str): trained on which dataset.\n        \"\"\"\n        # access the methods and features of parent class\n        super(SEResNext50_32x4d, self).__init__()\n        \n        # create a base model\n        self.model = pretrainedmodels.__dict__[\n            \"se_resnext50_32x4d\"\n        ](pretrained=pretrained)\n        \n        self.out = nn.Linear(\n            in_features=2048, out_features=1\n        )\n        self.sigmoid = nn.Sigmoid()\n        \n    def forward(self, image, targets):\n        \"\"\"\n        Forwad propagation.\n        \n        Args:\n            image (torch.tensor): batch of images.\n            targets (torch.tensor): images-targets.\n        \n        Returns:\n            float: final output.\n        \"\"\"\n        batch_size, _, _, _ = image.shape\n        \n        # get features\n        x = self.model.features(image)\n        \n        # perform adaptive pooling\n        x = F.adaptive_avg_pool2d(x, 1)\n        \n        # reshape\n        x = x.reshape(batch_size, -1)\n        \n        # get the output\n        x = self.out(x)\n        output = self.sigmoid(x)\n        \n        # compute loss\n        loss = nn.BCELoss()(\n            output, targets.reshape(-1, 1).type_as(output)\n        )\n        \n        return output, loss\n\nmodel = SEResNext50_32x4d()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Setting Default Values</span>📋</h2>\n\n* We are going to use `50 epochs` and `batch-size` of `12`.\n\n* Mean and Standard Deviation are used for normalizing the images, and for `se_resnext50_32x4d` mean and std can be found <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://github.com/Cadene/pretrained-models.pytorch/blob/master/pretrainedmodels/models/senet.py#L60\">here</a></span>.\n\n* Since, Kaggle provides us with GPU so we are going to pass `device` as `\"cuda\"`.\n\n<b>👇 Default Values</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# default values\nDEVICE = \"cuda\"\nEPOCHS = 50\nBATCH_SIZE = 32\n\n\nmean = (0.485, 0.456, 0.406)\nstd = (0.229, 0.224, 0.225)\n\nprint(\"⟿ Default Values\")\nprint(f\"    Device: {DEVICE}\")\nprint(f\"    Epochs: {EPOCHS}\")\nprint(f\"    Batch-Size: {BATCH_SIZE}\")\nprint()\nprint(\"⟿ Normalize Images\")\nprint(f\"    Mean: {mean}\")\nprint(f\"    Standard-Deviation: {std}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Kaggle GPU Information</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"!nvidia-smi","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Let Us Train\n\n* We will perform the following steps while training our model:\n    \n    * Use `albumentations` for making required normalization and augmentations.\n    \n    * Making `DataLoader` for train and valid set images using `wtfml` and `PyTorch`.\n    \n    * Initializing `optimizer` and `learning rate scheduler` using `PyTorch`.\n    \n    * Using `wtfml` to initialize `EarlyStopping` and `Engine` class oject. (To know more about what Engine is see this <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://www.kaggle.com/slothfulwave612/pytorch-building-a-digit-recognizer#5.-Building-Artificial-Neural-Network\">implementation</a></span>)\n    \n    * Finally iterate over epochs: train, validate and save the model.\n    \n<b>✅ Click the toggle button on the right to access the training function.</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def run_training(model, train_set, valid_set):\n    \"\"\"\n    Function to run the training process.\n    \n    Args:\n        model (__main__.SEResNext50_32x4d): object of the model.\n        train_set (list): list of train-image-names.\n        valid_set (list): list of valid-image-names.\n    \"\"\"\n    # path to train, validation and test data\n    train_data_path = \"../temp/train_set/\"\n    valid_data_path = \"../temp/valid_set/\"\n    \n    # path where model is saved\n    model_path = \"../temp/model.bin\"\n    \n    # augmentation\n    aug = albumentations.Compose(\n        [\n            albumentations.Normalize(\n                mean, std, max_pixel_value=255.0, always_apply=True\n            ),\n            albumentations.augmentations.transforms.Flip(p=0.11)\n        ]\n    )\n    \n    # get the image path and target for train-set\n    train_images = [train_data_path + image for image in train_set]\n    train_targets = [1 if image[0:3] == \"dog\" else 0 for image in train_set]\n\n    # get the image path and target for validation-set\n    valid_images = [valid_data_path + image for image in valid_set]\n    valid_targets = [1 if image[0:3] == \"dog\" else 0 for image in valid_set]\n    \n    # create ClassificationLoader object for train-images\n    train_dataset = ClassificationDataset(\n        image_paths=train_images, targets=train_targets,\n        resize=None, augmentations=aug\n    )\n    \n    # create DataLoader for train-images\n    train_loader = torch.utils.data.DataLoader(\n        train_dataset, batch_size=BATCH_SIZE,\n        shuffle=True, num_workers=4\n    )\n    \n    # create ClassificationLoader object for valid-images\n    valid_dataset = ClassificationDataset(\n        image_paths=valid_images, targets=valid_targets,\n        resize=None, augmentations=aug\n    )\n    \n    # create DataLoader for valid-images\n    valid_loader = torch.utils.data.DataLoader(\n        valid_dataset, batch_size=BATCH_SIZE,\n        shuffle=False, num_workers=4\n    )\n    \n    # transfer to device\n    model.to(DEVICE)\n    \n    # init optimizer\n    optimizer = torch.optim.Adam(\n        model.parameters(), lr=1e-4\n    )\n    \n    # init scheduler\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n        optimizer, patience=3, mode=\"max\"\n    )\n    \n    # init EarlyStopping object\n    es = EarlyStopping(patience=5, mode=\"max\")\n    \n    # init Engine object\n    engine = Engine(\n        model, optimizer, DEVICE\n    )\n    \n    for epoch in range(EPOCHS):\n        # train the model\n        train_loss = engine.train(train_loader)\n        \n        # evaluate on validation set\n        valid_loss, predictions = engine.evaluate(\n            valid_loader, True\n        )\n        \n        # fetch predictions\n        predictions = np.vstack(\n            ([tensor.cpu().numpy() for tensor in predictions])\n        ).ravel()\n        predictions = np.vstack((predictions)).ravel()\n        predictions = [1 if value >= 0.5 else 0 for value in predictions]\n        \n        # get accuracy\n        accuracy = metrics.accuracy_score(\n            valid_targets, predictions\n        )\n        \n        # update the parameter values\n        scheduler.step(accuracy)\n        \n        print(f\"Epoch={epoch}, Accuracy={accuracy}\")\n        \n        # check for early stopping\n        es(accuracy, model, model_path)\n        \n        if es.early_stop:\n            # early stop when required\n            print(\"Early Stopping\")\n            break\n\nrun_training(model, train_set, valid_set)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Load The Model</span>📥</h2>\n\n* The best performing model was giving aroung `99.4%` accuracy on the validation set.\n\n* Since we have saved the best performing model during the training process, we can now load it to use it for checking the scores in the test set.\n\n<b>✅ Click the toggle button on the right to access the code.</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"# load the best model\nbest_model = SEResNext50_32x4d()\nbest_model.load_state_dict(torch.load(\"../temp/model.bin\"))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Test Scores\n\n* We will use the best performing model on our test set to check the final accuracy. \n\n* We again have to perform a few steps as we did in the training function to get our test-score.\n\n<b>👇 Accuracy on Test Set</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def eval_test(model, test_set, test_path, test_targets=None, get_accuracy=True):\n    # transfer model to GPU\n    model.to(\"cuda\")\n    \n    # init Engine object\n    engine = Engine(\n        model, None, DEVICE\n    )\n\n    # augmentation\n    aug = albumentations.Compose(\n        [\n            albumentations.Normalize(\n                mean, std, max_pixel_value=255.0, always_apply=True\n            )\n        ]\n    )\n\n    # get the image path and target for validation-set\n    test_images = [test_path + image for image in test_set]\n    \n    if test_targets is None:\n        test_targets = [1 if image[0:3] == \"dog\" else 0 for image in test_set]\n\n    # create ClassificationLoader object for test-images\n    test_dataset = ClassificationDataset(\n        image_paths=test_images, targets=test_targets, \n        resize=None, augmentations=aug\n    )\n\n    # create DataLoader for test-images\n    test_loader = torch.utils.data.DataLoader(\n        test_dataset, batch_size=BATCH_SIZE,\n        shuffle=False, num_workers=4\n    )\n\n    # check on test set\n    predictions = engine.predict(test_loader)  \n        \n    # fetch probability\n    predictions = np.vstack(\n        ([tensor.cpu().numpy() for tensor in predictions])\n    ).ravel()\n    \n    if get_accuracy:\n        # get predictions\n        predictions = [1 if value >= 0.5 else 0 for value in predictions]\n        \n        # compute accuracy\n        accuracy = metrics.accuracy_score(\n            test_targets, predictions\n        )\n\n        print(f\"Accuracy on test-set: {accuracy}\")\n        return\n        \n    return predictions\n\neval_test(best_model, test_set, \"../temp/test_set/\", test_targets=None, get_accuracy=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Final Submission</span>🏁</h2>\n\n* We will use the function defined above to get the predictions for the `kaggle_test_set` images.\n\n* At last we will create the required dataframe containing `image-id` and it's corresponding `probability`.\n\n<b>👇 Prediction on kaggle-set.</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"preds = eval_test(\n    best_model, kaggle_images, \"../temp/kaggle_test_set/\", test_targets=[0] * len(kaggle_images), get_accuracy=False\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<b>👇 Saving The Dataframe</b>"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# make dataframe\ndf = pd.DataFrame(\n    {\n        \"id\": [int(image[:-4]) for image in kaggle_images], \n        \"label\": preds\n    }\n)\n\n# sort values\ndf = df.sort_values(by=\"id\").reset_index(drop=True)\n\n# save the dataframe\ndf.to_csv(\"predictions.csv\", index=False)\n\nprint(\"Saved!!!\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"So, that's everything I want to share through this notebook. I hope you have enjoyed and learnt something, and if you like my efforts do give an upvote to this notebook and share it.\n\nThank you for your presence.\n\n<h3><span style=\"background:#F3E9D9; font-weight:normal; color:black; font-family: 'Source Code Pro'; monospace\">Footnote</span>📍</h3>\n\n<ol>\n    <li>The code template is inspired by <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://www.kaggle.com/abhishek\">Abhisekh Thakur</a></span>.</li>\n    <li>The notebook theme is inspired by <span style=\"background:#F2F2F2; font-weight:normal; color:black\"><a href=\"https://www.kaggle.com/andradaolteanu\">Andrada Olteanu</a></span>.</li>\n</ol>"}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}