diff --git a/brain_tumor_dataset_preparation.ipynb b/brain_tumor_dataset_preparation.ipynb index 3a1c0ac..88c1967 100644 --- a/brain_tumor_dataset_preparation.ipynb +++ b/brain_tumor_dataset_preparation.ipynb @@ -1,791 +1,738 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "GXh66ZKmLUA1" + }, + "source": [ + "# Brain Tumor Detection - Dataset Preparation\n", + "\n", + "## Include necessary libraries\n", + "\n", + "We'll need the following libraries to prepare our dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "A3XcXuKJDjT0" + }, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'google.colab'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[1], line 10\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mh5py\u001b[39;00m\n\u001b[0;32m 9\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mcv2\u001b[39;00m\n\u001b[1;32m---> 10\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mgoogle\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcolab\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpatches\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m cv2_imshow\n\u001b[0;32m 11\u001b[0m get_ipython()\u001b[38;5;241m.\u001b[39mrun_line_magic(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmatplotlib\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124minline\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", + "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'google.colab'" + ] + } + ], + "source": [ + "import os\n", + "import PIL\n", + "import zipfile\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import pickle\n", + "import h5py\n", + "import cv2\n", + "from google.colab.patches import cv2_imshow\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting opencv-python\n", + " Downloading opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl (38.6 MB)\n", + " ---------------------------------------- 38.6/38.6 MB 3.8 MB/s eta 0:00:00\n", + "Requirement already satisfied: numpy>=1.17.3 in d:\\new folder (3)\\lib\\site-packages (from opencv-python) (1.23.5)\n", + "Installing collected packages: opencv-python\n", + "Successfully installed opencv-python-4.9.0.80\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "pip install opencv-python\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "YLifrnvcc7OG" + }, + "source": [ + "Import Google Drive, where we'll be storing our dataset so we can use them later. (Persistent preparation of Dataset)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { "colab": { - "name": "brain_tumor_dataset_preparation.ipynb", - "provenance": [], - "collapsed_sections": [], - "machine_shape": "hm" - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "accelerator": "GPU" + "base_uri": "https://localhost:8080/", + "height": 122 + }, + "colab_type": "code", + "id": "jH6JT7YRDr-d", + "outputId": "03762fcf-7912-4311-dcd6-7784f229f81d" + }, + "outputs": [], + "source": [ + "from google.colab import drive\n", + "drive.mount('/content/drive')" + ] }, - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "GXh66ZKmLUA1", - "colab_type": "text" - }, - "source": [ - "# Brain Tumor Detection - Dataset Preparation\n", - "\n", - "## Include necessary libraries\n", - "\n", - "We'll need the following libraries to prepare our dataset." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "A3XcXuKJDjT0", - "colab_type": "code", - "colab": {} - }, - "source": [ - "import os\n", - "import PIL\n", - "import zipfile\n", - "import numpy as np\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "import pickle\n", - "import h5py\n", - "import cv2\n", - "from google.colab.patches import cv2_imshow\n", - "%matplotlib inline" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "YLifrnvcc7OG", - "colab_type": "text" - }, - "source": [ - "Import Google Drive, where we'll be storing our dataset so we can use them later. (Persistent preparation of Dataset)." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "jH6JT7YRDr-d", - "colab_type": "code", - "outputId": "03762fcf-7912-4311-dcd6-7784f229f81d", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 122 - } - }, - "source": [ - "from google.colab import drive\n", - "drive.mount('/content/drive')" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&response_type=code&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly\n", - "\n", - "Enter your authorization code:\n", - "··········\n", - "Mounted at /content/drive\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "CjbuxQHPLk4e", - "colab_type": "text" - }, - "source": [ - "## Extract the Dataset" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_oS26YfYdLqI", - "colab_type": "text" - }, - "source": [ - "Dataset taken from [this link](https://figshare.com/articles/brain_tumor_dataset/1512427), which consists of brain tumor images belonging to 3 classes of tumor along with other details present in .mat format.\n", - "\n", - "Create a folder in Google Drive under Colab Notebooks folder named dataset, where we'll be extracting our main zip file." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "raq86BibDuAp", - "colab_type": "code", - "colab": {} - }, - "source": [ - "if not os.path.exists('/content/drive/My Drive/Colab Notebooks/dataset'):\n", - " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "c18E9qkwdUos", - "colab_type": "text" - }, - "source": [ - "Extract the dataset .zip file" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "TyWcKMdTFY9L", - "colab_type": "code", - "colab": {} - }, - "source": [ - "with zipfile.ZipFile('/content/drive/My Drive/1512427.zip') as zf:\n", - " zip_dir = zf.namelist()[0]\n", - " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "2hNITFpzdZnJ", - "colab_type": "text" - }, - "source": [ - "List the extracted files" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "39Id1fqCGVGI", - "colab_type": "code", - "outputId": "cce6fd82-ccf1-43e9-bf46-f06243a3512a", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 68 - } - }, - "source": [ - "!ls '/content/drive/My Drive/Colab Notebooks/dataset/'" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "brainTumorDataPublic_1533-2298.zip brainTumorDataPublic_767-1532.zip\n", - "brainTumorDataPublic_1-766.zip\t cvind.mat\n", - "brainTumorDataPublic_2299-3064.zip README.txt\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "OdP3WEIGdcAl", - "colab_type": "text" - }, - "source": [ - "Let's go through the README file for the information about the dataset" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "JosjvwpBG8u2", - "colab_type": "code", - "outputId": "bcf5a0ff-0a3e-484b-9e17-f0bfd606ed41", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 595 - } - }, - "source": [ - "!cat '/content/drive/My Drive/Colab Notebooks/dataset/README.txt'" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "This brain tumor dataset containing 3064 T1-weighted contrast-inhanced images\r\n", - "from 233 patients with three kinds of brain tumor: meningioma (708 slices), \r\n", - "glioma (1426 slices), and pituitary tumor (930 slices). Due to the file size\r\n", - "limit of repository, we split the whole dataset into 4 subsets, and achive \r\n", - "them in 4 .zip files with each .zip file containing 766 slices.The 5-fold\r\n", - "cross-validation indices are also provided.\r\n", - "\r\n", - "-----\r\n", - "This data is organized in matlab data format (.mat file). Each file stores a struct\r\n", - "containing the following fields for an image:\r\n", - "\r\n", - "cjdata.label: 1 for meningioma, 2 for glioma, 3 for pituitary tumor\r\n", - "cjdata.PID: patient ID\r\n", - "cjdata.image: image data\r\n", - "cjdata.tumorBorder: a vector storing the coordinates of discrete points on tumor border.\r\n", - "\t\tFor example, [x1, y1, x2, y2,...] in which x1, y1 are planar coordinates on tumor border.\r\n", - "\t\tIt was generated by manually delineating the tumor border. So we can use it to generate\r\n", - "\t\tbinary image of tumor mask.\r\n", - "cjdata.tumorMask: a binary image with 1s indicating tumor region\r\n", - "\r\n", - "-----\r\n", - "This data was used in the following paper:\r\n", - "1. Cheng, Jun, et al. \"Enhanced Performance of Brain Tumor Classification via Tumor Region Augmentation\r\n", - "and Partition.\" PloS one 10.10 (2015).\r\n", - "2. Cheng, Jun, et al. \"Retrieval of Brain Tumors by Adaptive Spatial Pooling and Fisher Vector \r\n", - "Representation.\" PloS one 11.6 (2016). Matlab source codes are available on github \r\n", - "https://github.com/chengjun583/brainTumorRetrieval\r\n", - "\r\n", - "-----\r\n", - "Jun Cheng\r\n", - "School of Biomedical Engineering\r\n", - "Southern Medical University, Guangzhou, China\r\n", - "Email: chengjun583@qq.com\r\n", - "\r\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7i2xmtdzdhsZ", - "colab_type": "text" - }, - "source": [ - "Extract all the 4 zip files of brain tumor, consisting .mat files" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "9zFst3tAGee7", - "colab_type": "code", - "colab": {} - }, - "source": [ - "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_1-766.zip') as zf:\n", - " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set1')\n", - " zip_dir = zf.namelist()[0]\n", - " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set1')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "code", - "metadata": { - "id": "MTHUf8u9HW_p", - "colab_type": "code", - "colab": {} - }, - "source": [ - "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_767-1532.zip') as zf:\n", - " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set2')\n", - " zip_dir = zf.namelist()[0]\n", - " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set2')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "code", - "metadata": { - "id": "wRrlwqZDHkHk", - "colab_type": "code", - "colab": {} - }, - "source": [ - "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_1533-2298.zip') as zf:\n", - " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set3')\n", - " zip_dir = zf.namelist()[0]\n", - " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set3')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "code", - "metadata": { - "id": "NZ-1TF46IEQM", - "colab_type": "code", - "colab": {} - }, - "source": [ - "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_2299-3064.zip') as zf:\n", - " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set4')\n", - " zip_dir = zf.namelist()[0]\n", - " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set4')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QvrMSXPWdrRD", - "colab_type": "text" - }, - "source": [ - "Move the .mat files to a new folder named imageData" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "DeaEY6MgITdz", - "colab_type": "code", - "colab": {} - }, - "source": [ - "os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/imageData')" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "code", - "metadata": { - "id": "rIkcEF5CI5AV", - "colab_type": "code", - "colab": {} - }, - "source": [ - "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set1/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'\n", - "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set2/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'\n", - "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set3/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'\n", - "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set4/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ih-0xfeoagQ_", - "colab_type": "text" - }, - "source": [ - "## Prepare the Dataset" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "J0860tP8dz6h", - "colab_type": "text" - }, - "source": [ - "Let's look at the things that we have in a single .mat file. Display the images & contents present inside it." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "mzE6qQ8RPCPC", - "colab_type": "code", - "colab": {} - }, - "source": [ - "arrays = {}\n", - "img = None" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "code", - "metadata": { - "id": "jjebDTJlMF_x", - "colab_type": "code", - "outputId": "5ebc712f-9bc8-452f-eda4-9a7acd22b5e3", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 785 - } - }, - "source": [ - "with h5py.File('/content/drive/My Drive/Colab Notebooks/dataset/imageData/1.mat', 'r') as f:\n", - " for key in f.keys():\n", - " print(key)\n", - " for item in f.items():\n", - " print(item)\n", - " for key, val in f['cjdata'].items():\n", - " print(key, val)\n", - " img = f['cjdata']['image']\n", - " label = f['cjdata']['label'][0][0]\n", - " tumorBorder = f['cjdata']['tumorBorder'][0]\n", - " mask = f['cjdata']['tumorMask']\n", - " fig = plt.figure(2)\n", - " img = np.array(img, dtype=np.float32)\n", - " img = img/127.5 - 1\n", - " mask = np.array(mask, dtype=np.float32)\n", - " mask = mask/127.5 - 1\n", - " plt.axis('off')\n", - " plt.imshow(img, cmap='gray')\n", - " print(\"Image shape: \", img.shape)\n", - " print(\"Label\", label)\n", - " print(\"Coords: \", tumorBorder)\n", - " print(\"Mask shape: \", mask.shape)\n", - " fig = plt.figure(3)\n", - " plt.axis('off')\n", - " plt.imshow(mask, cmap='gray')" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "cjdata\n", - "('cjdata', )\n", - "PID \n", - "image \n", - "label \n", - "tumorBorder \n", - "tumorMask \n", - "Image shape: (512, 512)\n", - "Label 1.0\n", - "Coords: [267.61524501 231.37568058 277.83666062 248.10163339 289.91651543\n", - " 250.8892922 305.71324864 253.676951 318.72232305 249.9600726\n", - " 321.50998185 237.88021779 354.96188748 234.16333938 367.97096189\n", - " 227.65880218 380.9800363 210.93284936 372.61705989 195.13611615\n", - " 363.32486388 177.48094374 350.31578947 176.55172414 336.37749546\n", - " 183.98548094 317.79310345 196.06533575 305.71324864 207.21597096\n", - " 295.49183303 217.43738657 288.98729583 222.08348457 273.19056261\n", - " 223.94192377 268.54446461 228.58802178]\n", - "Mask shape: (512, 512)\n" - ], - "name": "stdout" - }, - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOcAAADnCAYAAADl9EEgAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0\ndHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOx912/c2Xn285teOZ1VJEVKVNmVvNri\nbbYR2wk2jhMDTgLEMJBcJAgQOAjgmyQ3yT+Qm9zmKsh/4BiJHcAJ4NjO2ttXqy0qS4oSe5nh9F6/\nC37Py3eOzlCSw7X14eMBBI5mfuWUtzxvOe9xBoMBTttpO21PXnP9ujtw2k7babO3U+Y8baftCW2n\nzHnaTtsT2k6Z87Sdtie0nTLnaTttT2jzHPej4zinrtzTdto+4zYYDBzb96ea87Sdtie0nTLnaTtt\nT2g7Zc7Tdtqe0HbKnKfttD2h7ZQ5T9tpe0LbKXOettP2hLZT5jxtp+0JbafMedpO2xPaTpnztJ22\nJ7SdMudpO21PaDtlztN22p7Qdsqcp+20PaHtlDlP22l7Qtspc5620/aEtlPmPG2n7Qltp8x52k7b\nE9pOmfO0nbYntJ0y52k7bU9oO2XO03bantB2ypyn7bQ9oe2UOU/baXtC2ylznrbT9oS2U+Y8baft\nCW3H1q09bf//NpfLhcFgAMc5LKk6GAzkO7bBYIDTU+o+u3bKnL/mRuK3/d9xnCEGcRwHLpcL/X5/\n5P39fh8ul0s+8xlsZDCT8QaDAdxut1zn8/nkPYPBAL1eD36/H71eT67v9Xpwu93o9XpyHf+yr4PB\nwNoPs/+jmNx2nzlHfL4es9mf/xfbKXMe00wiMBnBbCRK/ddkBv0cTTSaqPQ9ZDT+v9/vw+12o9vt\nyvculwtutxv9fl+uMZ/vcrmEkHX/ODaPxyNM5jgOOp2OvNPj8cj3ZOBer4d+vy/95vM005u/UbDo\n8ek+HMdE5rzqd3COOE49L/q5D3u+vk/Pk15fjR5szz5JQfD/DXOahG8jBmoBcwGAB7WC+dkkds2E\nJmHZpPuo9/BaEh2/c7lc0lcAwiim1uSz9HttgoMaUd+v+6ObyVScVwAiNPS1o7SjSdxagOn5sj3D\nnHv217xn1Dv1u8zn6bnT95iwXo/fHI+tacTzKO3/aeY04Z6ecD0RNonL+0wiNZ9v2lj6nWY/TMa3\n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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - }, - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOcAAADnCAYAAADl9EEgAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0\ndHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAADeklEQVR4nO3cMY7iQABFQbya+1+5N0JCiGVhsOln\nuyoDkk6efssCljHGBej5M/sAwGPihChxQpQ4IUqcEPXz7MNlWTzKhY2NMZZH71tOiBInRIkTosQJ\nUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqc\nECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LE\nCVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFK\nnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROi\nxAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDgh\nSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSZ9wY4zLGmH0MJviZfQAeuw/y+npZlhnHYQLL\nuTOW9DzEuVMCPT5x7pgVPTZxQpQ4IUqcECVOiBJn0LsPeTwUOiZxxgiNK3GGCJNb4owQJvfEGSBM\nHhHnZGuFKfDjEedEguIZcU4iTP5HnAfhd57HI84JrCavEOeXCZNXifOLtgrTlfaYxPklFpN3+YOv\nL1g7TEt5DpZzZ4R5HuLcmOssvyXODQmTT4hzR1xpz0WcG7GafEqcGxAmaxDnyoTJWsQJUeKEKHGu\nyJWWNYkTosS5EqvJ2sQJUeKEKHHuhK/unY84IUqcO2A1z0mcECXOlSzLssnCWc3zEufKtoqU8xHn\nRj4NVOT4970N3cb1zjeIRMnlYjm/5tUlFCZX4vyyZ/EJk1uutRPcX3dFySOWczJh8i/ihChxQpQ4\nIUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJ\nE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKU\nOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTopYxxuwzAA9YTogSJ0SJE6LECVHi\nhChxQtRfgS5uvHRLkD0AAAAASUVORK5CYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wzAhqm61eApj", - "colab_type": "text" - }, - "source": [ - "A single .mat file consists of:\n", - "\n", - "* **PID:** Patient ID for that particular tumor image. This is not a necessary feature.\n", - "* **image:** Image of the brain tumor\n", - "* **label:** Label between 1-3 denoting what kind of tumor is present\n", - "* **tumorBorder:** Coordinates of the tumor present in the image\n", - "* **tumorMask:** Masked version of the tumor.\n", - "\n", - "We'll need the image, label, tumorBorder, tumorMask which are the necessary features that'll help us to detect brain tumors." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ewh0lu2Ue1XN", - "colab_type": "text" - }, - "source": [ - "Create new folders named the following:\n", - "\n", - "* **new_dataset:** We'll be using this folder to store our images, labels, mask and borders\n", - "* **bt_images:** We'll store the full brain tumor image in this folder\n", - "* **bt_mask:** We'll store the mask images of the brain tumor in this folder" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "0HaGLuvS5h_m", - "colab_type": "code", - "colab": {} - }, - "source": [ - "os.mkdir('/content/drive/My Drive/Colab Notebooks/new_dataset')\n", - "os.mkdir('/content/drive/My Drive/Colab Notebooks/new_dataset/bt_images') " - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4VNjICqWfSok", - "colab_type": "text" - }, - "source": [ - "Create empty lists for storing labels\n", - "\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "07LqMUXA5BCx", - "colab_type": "code", - "colab": {} - }, - "source": [ - "labels = []\n" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "t_bJP9dOfY6q", - "colab_type": "text" - }, - "source": [ - "Save images of brain tumor, masks and store labels and borders in their respective lists iteratively." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "RYFvHS3Xew7Z", - "colab_type": "code", - "outputId": "192f4640-0b14-45dc-ee35-5db44fe24b7b", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 265 - } - }, - "source": [ - "filename = None\n", - "\n", - "for filename in range(1, 3065):\n", - " with h5py.File('/content/drive/My Drive/Colab Notebooks/dataset/imageData/{}.mat'.format(filename), 'r') as f:\n", - " img = f['cjdata']['image']\n", - " label = f['cjdata']['label'][0][0]\n", - " \n", - " \n", - " labels.append(int(label))\n", - " \n", - " border.append(coord)\n", - " img = np.array(img, dtype=np.float32)\n", - " \n", - " plt.axis('off')\n", - " plt.imsave(\"/content/drive/My Drive/Colab Notebooks/new_dataset/bt_images/{}.jpg\".format(filename), img, cmap='gray')\n", - " \n", - " \n", - "print(\"{} files successfully saved\".format(filename))" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "3064 files successfully saved\n" - ], - "name": "stdout" - }, - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAV0AAADnCAYAAAC9roUQAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0\ndHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAADKUlEQVR4nO3UMQEAIAzAMMC/5+GiHCQKenXPzAKg\ncV4HAPzEdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZ\nLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDI\ndAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFC\npgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQ\nMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2A\nkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkC\nhEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwX\nIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6\nACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHT\nBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZ\nLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDI\ndAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFC\npgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQ\nMl2AkOkChEwXIGS6ACHTBQiZLkDIdAFCpgsQMl2AkOkChEwXIHQBcjcEy3+fc28AAAAASUVORK5C\nYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7WQf_HBUfiQP", - "colab_type": "text" - }, - "source": [ - "Convert the Python lists to a Numpy arrays" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "zb8znQzg655F", - "colab_type": "code", - "colab": {} - }, - "source": [ - "label_names = np.array(labels, dtype=np.int64)\n" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "fm6Hemi0fnkR", - "colab_type": "text" - }, - "source": [ - "Check if the array has the right shape & length." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "B5LnQrPE7cqs", - "colab_type": "code", - "outputId": "77b56eb5-a946-4c0b-d4ce-1f56a7b5ea74", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - } - }, - "source": [ - "label_names.shape" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "(3064,)" - ] - }, - "metadata": { - "tags": [] - }, - "execution_count": 18 - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "AzBZl4FifsaC", - "colab_type": "text" - }, - "source": [ - "Store the labels and tumor border (coordinates) as a pickle file, which can be loaded whenever we want to use it." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "9UbCgJIZ7DSQ", - "colab_type": "code", - "colab": {} - }, - "source": [ - "pickle_out = open(\"/content/drive/My Drive/Colab Notebooks/new_dataset/labels.pickle\",\"wb\") \n", - "pickle.dump(label_names, pickle_out)\n", - "pickle_out.close() " - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "qsKkbR5TdLbl", - "colab_type": "text" - }, - "source": [ - "Create an empty list named 'training_data' in which we'll store our images and their respective labels as arrays" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "U8WjocXu9cB9", - "colab_type": "code", - "colab": {} - }, - "source": [ - "training_data = []\n", - "img = None\n", - "label = None\n", - "i = None" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "c3TtXcNpdNVV", - "colab_type": "text" - }, - "source": [ - "Read the images from bt_images folder from Google Drive and convert it to RGB images and store it along with their respective labels in the training_data list." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "SICvCO6rdBLQ", - "colab_type": "code", - "colab": {} - }, - "source": [ - "for i in range(1, 3065):\n", - " img = cv2.imread(\"/content/drive/My Drive/Colab Notebooks/new_dataset/bt_images/{}.jpg\".format(i), cv2.IMREAD_GRAYSCALE)\n", - " img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n", - " img = cv2.resize(img, (512, 512))\n", - " label = label_names[i-1]\n", - " training_data.append([img, label])\n", - "\n", - "print(\"shape: {} label: {} | {} samples successfully preprocessed\".format(img.shape, label, i))\n", - "\n", - "pickle_out = open(\"/content/drive/My Drive/Colab Notebooks/new_dataset/training_data.pickle\",\"wb\") \n", - "pickle.dump(training_data, pickle_out)\n", - "pickle_out.close()" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "P0vHOhwWdR_V", - "colab_type": "text" - }, - "source": [ - "Store the training_data list as a pickle file" - ] + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "CjbuxQHPLk4e" + }, + "source": [ + "## Extract the Dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "_oS26YfYdLqI" + }, + "source": [ + "Dataset taken from [this link](https://figshare.com/articles/brain_tumor_dataset/1512427), which consists of brain tumor images belonging to 3 classes of tumor along with other details present in .mat format.\n", + "\n", + "Create a folder in Google Drive under Colab Notebooks folder named dataset, where we'll be extracting our main zip file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "raq86BibDuAp" + }, + "outputs": [], + "source": [ + "if not os.path.exists('/content/drive/My Drive/Colab Notebooks/dataset'):\n", + " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "c18E9qkwdUos" + }, + "source": [ + "Extract the dataset .zip file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "TyWcKMdTFY9L" + }, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: '/content/drive/My Drive/1512427.zip'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[43mzipfile\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mZipFile\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m/content/drive/My Drive/1512427.zip\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m zf:\n\u001b[0;32m 2\u001b[0m zip_dir \u001b[38;5;241m=\u001b[39m zf\u001b[38;5;241m.\u001b[39mnamelist()[\u001b[38;5;241m0\u001b[39m]\n\u001b[0;32m 3\u001b[0m zf\u001b[38;5;241m.\u001b[39mextractall(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m/content/drive/My Drive/Colab Notebooks/dataset\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", + "File \u001b[1;32mD:\\New folder (3)\\lib\\zipfile.py:1249\u001b[0m, in \u001b[0;36mZipFile.__init__\u001b[1;34m(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps)\u001b[0m\n\u001b[0;32m 1247\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m 1248\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m-> 1249\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfp \u001b[38;5;241m=\u001b[39m \u001b[43mio\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfilemode\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 1250\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m:\n\u001b[0;32m 1251\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m filemode \u001b[38;5;129;01min\u001b[39;00m modeDict:\n", + "\u001b[1;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/content/drive/My Drive/1512427.zip'" + ] } - ] + ], + "source": [ + "with zipfile.ZipFile('/content/drive/My Drive/1512427.zip') as zf:\n", + " zip_dir = zf.namelist()[0]\n", + " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "2hNITFpzdZnJ" + }, + "source": [ + "List the extracted files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 68 + }, + "colab_type": "code", + "id": "39Id1fqCGVGI", + "outputId": "cce6fd82-ccf1-43e9-bf46-f06243a3512a" + }, + "outputs": [], + "source": [ + "!ls '/content/drive/My Drive/Colab Notebooks/dataset/'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "OdP3WEIGdcAl" + }, + "source": [ + "Let's go through the README file for the information about the dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 595 + }, + "colab_type": "code", + "id": "JosjvwpBG8u2", + "outputId": "bcf5a0ff-0a3e-484b-9e17-f0bfd606ed41" + }, + "outputs": [], + "source": [ + "!cat '/content/drive/My Drive/Colab Notebooks/dataset/README.txt'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "7i2xmtdzdhsZ" + }, + "source": [ + "Extract all the 4 zip files of brain tumor, consisting .mat files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "9zFst3tAGee7" + }, + "outputs": [], + "source": [ + "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_1-766.zip') as zf:\n", + " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set1')\n", + " zip_dir = zf.namelist()[0]\n", + " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set1')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "MTHUf8u9HW_p" + }, + "outputs": [], + "source": [ + "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_767-1532.zip') as zf:\n", + " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set2')\n", + " zip_dir = zf.namelist()[0]\n", + " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set2')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "wRrlwqZDHkHk" + }, + "outputs": [], + "source": [ + "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_1533-2298.zip') as zf:\n", + " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set3')\n", + " zip_dir = zf.namelist()[0]\n", + " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set3')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "NZ-1TF46IEQM" + }, + "outputs": [], + "source": [ + "with zipfile.ZipFile('/content/drive/My Drive/Colab Notebooks/dataset/brainTumorDataPublic_2299-3064.zip') as zf:\n", + " os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/bt_set4')\n", + " zip_dir = zf.namelist()[0]\n", + " zf.extractall('/content/drive/My Drive/Colab Notebooks/dataset/bt_set4')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "QvrMSXPWdrRD" + }, + "source": [ + "Move the .mat files to a new folder named imageData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "DeaEY6MgITdz" + }, + "outputs": [], + "source": [ + "os.mkdir('/content/drive/My Drive/Colab Notebooks/dataset/imageData')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "rIkcEF5CI5AV" + }, + "outputs": [], + "source": [ + "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set1/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'\n", + "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set2/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'\n", + "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set3/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'\n", + "!mv /content/drive/'My Drive'/'Colab Notebooks'/dataset/bt_set4/*.mat '/content/drive/My Drive/Colab Notebooks/dataset/imageData/'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "Ih-0xfeoagQ_" + }, + "source": [ + "## Prepare the Dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "J0860tP8dz6h" + }, + "source": [ + "Let's look at the things that we have in a single .mat file. Display the images & contents present inside it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "mzE6qQ8RPCPC" + }, + "outputs": [], + "source": [ + "arrays = {}\n", + "img = None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 785 + }, + "colab_type": "code", + "id": "jjebDTJlMF_x", + "outputId": "5ebc712f-9bc8-452f-eda4-9a7acd22b5e3" + }, + "outputs": [], + "source": [ + "with h5py.File('/content/drive/My Drive/Colab Notebooks/dataset/imageData/1.mat', 'r') as f:\n", + " for key in f.keys():\n", + " print(key)\n", + " for item in f.items():\n", + " print(item)\n", + " for key, val in f['cjdata'].items():\n", + " print(key, val)\n", + " img = f['cjdata']['image']\n", + " label = f['cjdata']['label'][0][0]\n", + " tumorBorder = f['cjdata']['tumorBorder'][0]\n", + " mask = f['cjdata']['tumorMask']\n", + " fig = plt.figure(2)\n", + " img = np.array(img, dtype=np.float32)\n", + " img = img/127.5 - 1\n", + " mask = np.array(mask, dtype=np.float32)\n", + " mask = mask/127.5 - 1\n", + " plt.axis('off')\n", + " plt.imshow(img, cmap='gray')\n", + " print(\"Image shape: \", img.shape)\n", + " print(\"Label\", label)\n", + " print(\"Coords: \", tumorBorder)\n", + " print(\"Mask shape: \", mask.shape)\n", + " fig = plt.figure(3)\n", + " plt.axis('off')\n", + " plt.imshow(mask, cmap='gray')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "wzAhqm61eApj" + }, + "source": [ + "A single .mat file consists of:\n", + "\n", + "* **PID:** Patient ID for that particular tumor image. This is not a necessary feature.\n", + "* **image:** Image of the brain tumor\n", + "* **label:** Label between 1-3 denoting what kind of tumor is present\n", + "* **tumorBorder:** Coordinates of the tumor present in the image\n", + "* **tumorMask:** Masked version of the tumor.\n", + "\n", + "We'll need the image, label, tumorBorder, tumorMask which are the necessary features that'll help us to detect brain tumors." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "ewh0lu2Ue1XN" + }, + "source": [ + "Create new folders named the following:\n", + "\n", + "* **new_dataset:** We'll be using this folder to store our images, labels, mask and borders\n", + "* **bt_images:** We'll store the full brain tumor image in this folder\n", + "* **bt_mask:** We'll store the mask images of the brain tumor in this folder" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "0HaGLuvS5h_m" + }, + "outputs": [], + "source": [ + "os.mkdir('/content/drive/My Drive/Colab Notebooks/new_dataset')\n", + "os.mkdir('/content/drive/My Drive/Colab Notebooks/new_dataset/bt_images') " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "4VNjICqWfSok" + }, + "source": [ + "Create empty lists for storing labels\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "07LqMUXA5BCx" + }, + "outputs": [], + "source": [ + "labels = []\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "t_bJP9dOfY6q" + }, + "source": [ + "Save images of brain tumor, masks and store labels and borders in their respective lists iteratively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 265 + }, + "colab_type": "code", + "id": "RYFvHS3Xew7Z", + "outputId": "192f4640-0b14-45dc-ee35-5db44fe24b7b" + }, + "outputs": [], + "source": [ + "filename = None\n", + "\n", + "for filename in range(1, 3065):\n", + " with h5py.File('/content/drive/My Drive/Colab Notebooks/dataset/imageData/{}.mat'.format(filename), 'r') as f:\n", + " img = f['cjdata']['image']\n", + " label = f['cjdata']['label'][0][0]\n", + " \n", + " \n", + " labels.append(int(label))\n", + " \n", + " border.append(coord)\n", + " img = np.array(img, dtype=np.float32)\n", + " \n", + " plt.axis('off')\n", + " plt.imsave(\"/content/drive/My Drive/Colab Notebooks/new_dataset/bt_images/{}.jpg\".format(filename), img, cmap='gray')\n", + " \n", + " \n", + "print(\"{} files successfully saved\".format(filename))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "7WQf_HBUfiQP" + }, + "source": [ + "Convert the Python lists to a Numpy arrays" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "zb8znQzg655F" + }, + "outputs": [], + "source": [ + "label_names = np.array(labels, dtype=np.int64)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "fm6Hemi0fnkR" + }, + "source": [ + "Check if the array has the right shape & length." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "colab_type": "code", + "id": "B5LnQrPE7cqs", + "outputId": "77b56eb5-a946-4c0b-d4ce-1f56a7b5ea74" + }, + "outputs": [], + "source": [ + "label_names.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "AzBZl4FifsaC" + }, + "source": [ + "Store the labels and tumor border (coordinates) as a pickle file, which can be loaded whenever we want to use it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "9UbCgJIZ7DSQ" + }, + "outputs": [], + "source": [ + "pickle_out = open(\"/content/drive/My Drive/Colab Notebooks/new_dataset/labels.pickle\",\"wb\") \n", + "pickle.dump(label_names, pickle_out)\n", + "pickle_out.close() " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "qsKkbR5TdLbl" + }, + "source": [ + "Create an empty list named 'training_data' in which we'll store our images and their respective labels as arrays" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "U8WjocXu9cB9" + }, + "outputs": [], + "source": [ + "training_data = []\n", + "img = None\n", + "label = None\n", + "i = None" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "c3TtXcNpdNVV" + }, + "source": [ + "Read the images from bt_images folder from Google Drive and convert it to RGB images and store it along with their respective labels in the training_data list." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "SICvCO6rdBLQ" + }, + "outputs": [], + "source": [ + "for i in range(1, 3065):\n", + " img = cv2.imread(\"/content/drive/My Drive/Colab Notebooks/new_dataset/bt_images/{}.jpg\".format(i), cv2.IMREAD_GRAYSCALE)\n", + " img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n", + " img = cv2.resize(img, (512, 512))\n", + " label = label_names[i-1]\n", + " training_data.append([img, label])\n", + "\n", + "print(\"shape: {} label: {} | {} samples successfully preprocessed\".format(img.shape, label, i))\n", + "\n", + "pickle_out = open(\"/content/drive/My Drive/Colab Notebooks/new_dataset/training_data.pickle\",\"wb\") \n", + "pickle.dump(training_data, pickle_out)\n", + "pickle_out.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "P0vHOhwWdR_V" + }, + "source": [ + "Store the training_data list as a pickle file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "machine_shape": "hm", + "name": "brain_tumor_dataset_preparation.ipynb", + "provenance": [] + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 1 } diff --git a/conversion/conversion.pytotfile.py b/conversion/conversion.pytotfile.py new file mode 100644 index 0000000..147fa54 --- /dev/null +++ b/conversion/conversion.pytotfile.py @@ -0,0 +1,77 @@ +import sys +import os +import torch +import torch.nn as nn +import torch.nn.functional as F +import onnx +from collections import OrderedDict +import tensorflow as tf +from torch.autograd import Variable +from onnx_tf.backend import prepare + +class MLP(nn.Module): + def __init__(self, input_dims, n_hiddens, n_class): + super(MLP, self).__init__() + assert isinstance(input_dims, int), 'Please provide int for input_dims' + self.input_dims = input_dims + current_dims = input_dims + layers = OrderedDict() + + if isinstance(n_hiddens, int): + n_hiddens = [n_hiddens] + else: + n_hiddens = list(n_hiddens) + for i, n_hidden in enumerate(n_hiddens): + layers['fc{}'.format(i+1)] = nn.Linear(current_dims, n_hidden) + layers['relu{}'.format(i+1)] = nn.ReLU() + layers['drop{}'.format(i+1)] = nn.Dropout(0.2) + current_dims = n_hidden + layers['out'] = nn.Linear(current_dims, n_class) + + self.model= nn.Sequential(layers) + print(self.model) + + def forward(self, input): + input = input.view(input.size(0), -1) + assert input.size(1) == self.input_dims + return self.model.forward(input) + +print("%s" % sys.argv[1]) +print("%s" % sys.argv[2]) + + +# Load the trained model from file +trained_dict = torch.load(sys.argv[1], map_location={'cuda:0': 'cpu'}) + +trained_model = MLP(784, [256, 256], 10) +trained_model.load_state_dict(trained_dict) + +if not os.path.exists("%s" % sys.argv[2]): + os.makedirs("%s" % sys.argv[2]) + +# Export the trained model to ONNX +dummy_input = Variable(torch.randn(1, 1, 28, 28)) # one black and white 28 x 28 picture will be the input to the model +torch.onnx.export(trained_model, dummy_input, "%s/mnist.onnx" % sys.argv[2]) + +# Load the ONNX file +model = onnx.load("%s/mnist.onnx" % sys.argv[2]) + +# Import the ONNX model to Tensorflow +tf_rep = prepare(model) + +# Input nodes to the model +print('inputs:', tf_rep.inputs) + +# Output nodes from the model +print('outputs:', tf_rep.outputs) + +# All nodes in the model +print('tensor_dict:') +print(tf_rep.tensor_dict) + +tf_rep.export_graph("%s/mnist.pb" % sys.argv[2]) + +converter = tf.lite.TFLiteConverter.from_frozen_graph( + "%s/mnist.pb" % sys.argv[2], tf_rep.inputs, tf_rep.outputs) +tflite_model = converter.convert() +open("%s/mnist.tflite" % sys.argv[2], "wb").write(tflite_model) \ No newline at end of file diff --git a/endpoint.py b/endpoint.py new file mode 100644 index 0000000..2148a6d --- /dev/null +++ b/endpoint.py @@ -0,0 +1,81 @@ +import os +from io import BytesIO +from torch import argmax, load +from torch import device as DEVICE +from torch.cuda import is_available +from torch.nn import Sequential, Linear, SELU, Dropout, LogSigmoid +from PIL import Image +from torchvision.transforms import Compose, ToTensor, Resize +from torchvision.models import resnet50 +from flask import Flask, jsonify, request + +app = Flask(__name__) +LABELS = ['None', 'Meningioma', 'Glioma', 'Pituitary'] + +device = "cuda" if is_available() else "cpu" + +resnet_model = resnet50(pretrained=True) + +# Freeze model parameters +for param in resnet_model.parameters(): + param.requires_grad = False + +# Modify the fully connected layer +n_inputs = resnet_model.fc.in_features +resnet_model.fc = Sequential(Linear(n_inputs, 2048), + SELU(), + Dropout(p=0.4), + Linear(2048, 2048), + SELU(), + Dropout(p=0.4), + Linear(2048, 4), + LogSigmoid()) + +# Enable gradients for the fully connected layer +for param in resnet_model.fc.parameters(): + param.requires_grad = True + +resnet_model.to(device) + +# Load the model weights +model_path = './models/bt_resnet50_model.pt' +resnet_model.load_state_dict(load(model_path, map_location=DEVICE(device))) +resnet_model.eval() + +def preprocess_image(image_bytes): + transform = Compose([Resize((512, 512)), ToTensor()]) + img = Image.open(BytesIO(image_bytes)) + return transform(img).unsqueeze(0) + +def get_prediction(image_bytes): + tensor = preprocess_image(image_bytes=image_bytes) + with torch.no_grad(): + y_hat = resnet_model(tensor.to(device)) + class_id = argmax(y_hat.data, dim=1) + return str(int(class_id)), LABELS[int(class_id)] + +@app.route('/predict', methods=['POST']) +def predict(): + if request.method == 'POST': + if 'file' not in request.files: + return jsonify({'error': 'No file part'}) + + file = request.files['file'] + + if file.filename == '': + return jsonify({'error': 'No selected file'}) + + img_bytes = file.read() + class_id, class_name = get_prediction(img_bytes) + return jsonify({'class_id': class_id, 'class_name': class_name}) + +@app.route('/') +def index(): + return 'Welcome to the Brain Tumor Classification API!' + +@app.route('/favicon.ico') +def favicon(): + return '', 204 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/images/1.jpg b/static/images/1.jpg new file mode 100644 index 0000000..e1caa8e Binary files /dev/null and b/static/images/1.jpg differ diff --git a/static/images/test1.jpg b/static/images/test1.jpg new file mode 100644 index 0000000..4cb4a4b Binary files /dev/null and b/static/images/test1.jpg differ diff --git a/static/images/test11.jpg b/static/images/test11.jpg new file mode 100644 index 0000000..1dd942f Binary files /dev/null and b/static/images/test11.jpg differ diff --git a/static/images/test4.jpg b/static/images/test4.jpg new file mode 100644 index 0000000..e276b11 Binary files /dev/null and b/static/images/test4.jpg differ diff --git a/static/images/test6.jpg b/static/images/test6.jpg deleted file mode 100644 index 86f728a..0000000 Binary files a/static/images/test6.jpg and /dev/null differ diff --git a/static/images/test7.jpg b/static/images/test7.jpg new file mode 100644 index 0000000..2dcaf6f Binary files /dev/null and b/static/images/test7.jpg differ diff --git a/static/images/test8.jpg b/static/images/test8.jpg new file mode 100644 index 0000000..93b8670 Binary files /dev/null and b/static/images/test8.jpg differ diff --git a/template/index.html b/template/index.html index 300e2cc..b842927 100644 --- a/template/index.html +++ b/template/index.html @@ -2,7 +2,7 @@ - neuralBlack + @@ -39,7 +39,7 @@
-

Made with love by

aksh-ai

+

Made with love

\ No newline at end of file diff --git a/template/pred.html b/template/pred.html index f1a7312..ef7eacf 100644 --- a/template/pred.html +++ b/template/pred.html @@ -2,7 +2,7 @@ - neuralBlack + @@ -35,7 +35,7 @@
-

Made with love by

aksh-ai

+

Made with love

\ No newline at end of file diff --git a/test.py b/test.py index d6b8b56..2bf0148 100644 --- a/test.py +++ b/test.py @@ -5,7 +5,7 @@ from PIL import Image from torchvision import transforms, models -device_name = "cuda:0:" if torch.cuda.is_available() else "cpu" +device_name = "cuda:0" if torch.cuda.is_available() else "cpu" device = torch.device(device_name) resnet_model = models.resnet50(pretrained=True) @@ -30,7 +30,14 @@ resnet_model.to(device) -resnet_model.load_state_dict(torch.load('models\\bt_resnet50_model.pt')) +#resnet_model.load_state_dict(torch.load('models\\bt_resnet50_model.pt')) +state_dict = torch.load('models\\bt_resnet50_model.pt') + +# Load the state dictionary into the model +resnet_model.load_state_dict(state_dict) + +# Print the model to verify if the weights are loaded correctly +# print(resnet_model) resnet_model.eval() diff --git a/torch_brain_tumor_classifier.ipynb b/torch_brain_tumor_classifier.ipynb index 4728ce6..a372d50 100644 --- a/torch_brain_tumor_classifier.ipynb +++ b/torch_brain_tumor_classifier.ipynb @@ -1,1816 +1,1829 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "43754Epan7bx" + }, + "source": [ + "# Brain Tumor Classification\n", + "\n", + "A Brain Tumor Classifier using fine-tuned ResNet50 Neural Network architecture with almost 99 % Accuracy achieved by applying the method of Transfer Learning.\n", + "\n", + "Given an MRI image of brain, classify the tumor into **Meningioma, Glioma, and Pitutary**, if present. \n", + "\n", + "Project done using Google Colab with follwing specifications:\n", + "\n", + "* Ubuntu 18.04 64-bit OS\n", + "* 12 GB DDR4 RAM\n", + "* 16 GB NVidia P100 GPU\n", + "* 40 GB of Non-Persistent Storage\n", + "\n", + "Refer [brain_tumor_dataset_preparation.ipynb](brain_tumor_dataset_preparation.ipynb) for dataset preparation & pre-processing that we did." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "s4k9AZlhoVgg" + }, + "source": [ + "## Import Necessary Libraries\n", + "\n", + "We'll be using the following libraries to complete our classification problem:\n", + "\n", + "* Numpy - For linear algebra operations \n", + "* Torch - Pytorch Deep Learning Framework\n", + "* Torch NN - Neural network class from Pytorch library\n", + "* Torch NN Functional - Functional Neural Network class from Pytorch library\n", + "* Torch Utils Data: DataLoader, Dataset - Dataset class used to create custom dataset class by subclassing it and DataLoader is used to laod data in batches using dataset class in real-time.\n", + "* Torchvision: Transforms, Models - Trochvision provides augmentation techniques using transforms class and transfer learning models are available in models class\n", + "* OS - To use Operating System methods\n", + "* Random - To set random seed at specific places where random operations take place just so it happens the same way everytime it is executed\n", + "* Pandas - To create DataFrame, CSV files, etc\n", + "* Time - To perform date time operations\n", + "* Seaborn - For sophisticated visualization\n", + "* Pickle - To save and load binary files of our training data\n", + "* Scikit-Learn - For evaluating our Classifier and for cross-validation split\n", + "* Matplotlib - To visualize images, losses and accuracy\n", + "* Google Colab Drive - To mount Google Drive so we can perform storage and loading operations using it\n", + "\n", + "To install the requirements, refer [requirements.txt](requirements.txt) file\n", + "\n", + " `pip3 install -r requirements.txt`\n", + " \n", + "Let's go ahead and import the required libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { "colab": { - "name": "brain_tumor_classifier_resnet_torch.ipynb", - "provenance": [], - "collapsed_sections": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "accelerator": "GPU" + "base_uri": "https://localhost:8080/", + "height": 52 + }, + "colab_type": "code", + "id": "ppY0E2oBcXNu", + "outputId": "523f44ab-6260-44fd-8e22-1ec57ddd7605" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.\n", + " import pandas.util.testing as tm\n" + ] + } + ], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "from torch.utils.data import DataLoader, Dataset\n", + "from torchvision import transforms, models\n", + "from torchvision.utils import make_grid\n", + "import os\n", + "import random\n", + "import numpy as np\n", + "import pandas as pd\n", + "import pickle\n", + "import time\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.metrics import confusion_matrix, classification_report, jaccard_similarity_score\n", + "from google.colab import drive " + ] }, - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "43754Epan7bx", - "colab_type": "text" - }, - "source": [ - "# Brain Tumor Classification\n", - "\n", - "A Brain Tumor Classifier using fine-tuned ResNet50 Neural Network architecture with almost 99 % Accuracy achieved by applying the method of Transfer Learning.\n", - "\n", - "Given an MRI image of brain, classify the tumor into **Meningioma, Glioma, and Pitutary**, if present. \n", - "\n", - "Project done using Google Colab with follwing specifications:\n", - "\n", - "* Ubuntu 18.04 64-bit OS\n", - "* 12 GB DDR4 RAM\n", - "* 16 GB NVidia P100 GPU\n", - "* 40 GB of Non-Persistent Storage\n", - "\n", - "Refer [brain_tumor_dataset_preparation.ipynb](brain_tumor_dataset_preparation.ipynb) for dataset preparation & pre-processing that we did." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "s4k9AZlhoVgg", - "colab_type": "text" - }, - "source": [ - "## Import Necessary Libraries\n", - "\n", - "We'll be using the following libraries to complete our classification problem:\n", - "\n", - "* Numpy - For linear algebra operations \n", - "* Torch - Pytorch Deep Learning Framework\n", - "* Torch NN - Neural network class from Pytorch library\n", - "* Torch NN Functional - Functional Neural Network class from Pytorch library\n", - "* Torch Utils Data: DataLoader, Dataset - Dataset class used to create custom dataset class by subclassing it and DataLoader is used to laod data in batches using dataset class in real-time.\n", - "* Torchvision: Transforms, Models - Trochvision provides augmentation techniques using transforms class and transfer learning models are available in models class\n", - "* OS - To use Operating System methods\n", - "* Random - To set random seed at specific places where random operations take place just so it happens the same way everytime it is executed\n", - "* Pandas - To create DataFrame, CSV files, etc\n", - "* Time - To perform date time operations\n", - "* Seaborn - For sophisticated visualization\n", - "* Pickle - To save and load binary files of our training data\n", - "* Scikit-Learn - For evaluating our Classifier and for cross-validation split\n", - "* Matplotlib - To visualize images, losses and accuracy\n", - "* Google Colab Drive - To mount Google Drive so we can perform storage and loading operations using it\n", - "\n", - "To install the requirements, refer [requirements.txt](requirements.txt) file\n", - "\n", - " `pip3 install -r requirements.txt`\n", - " \n", - "Let's go ahead and import the required libraries" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "ppY0E2oBcXNu", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "outputId": "523f44ab-6260-44fd-8e22-1ec57ddd7605" - }, - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "from torch.utils.data import DataLoader, Dataset\n", - "from torchvision import transforms, models\n", - "from torchvision.utils import make_grid\n", - "import os\n", - "import random\n", - "import numpy as np\n", - "import pandas as pd\n", - "import pickle\n", - "import time\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "from sklearn.model_selection import train_test_split\n", - "from sklearn.metrics import confusion_matrix, classification_report, jaccard_similarity_score\n", - "from google.colab import drive " - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.\n", - " import pandas.util.testing as tm\n" - ], - "name": "stderr" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "YyPnviwkoaRh", - "colab_type": "text" - }, - "source": [ - "Print Pytorch's version" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "uY8S0R76sj90", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "012126c7-b199-473a-d7c3-88a22c6eeb3b" - }, - "source": [ - "torch.__version__" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "'1.5.0+cu101'" - ] - }, - "metadata": { - "tags": [] - }, - "execution_count": 2 - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8uWrMvwCbmsw", - "colab_type": "text" - }, - "source": [ - "Check GPU" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "4NKtY3ESbmSl", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 372 - }, - "outputId": "e54e9295-b586-4ee6-ff51-70e63e9dd854" - }, - "source": [ - "!nvidia-smi" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Sat Jun 20 12:12:51 2020 \n", - "+-----------------------------------------------------------------------------+\n", - "| NVIDIA-SMI 450.36.06 Driver Version: 418.67 CUDA Version: 10.1 |\n", - "|-------------------------------+----------------------+----------------------+\n", - "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", - "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", - "| | | MIG M. |\n", - "|===============================+======================+======================|\n", - "| 0 Tesla P100-PCIE... Off | 00000000:00:04.0 Off | 0 |\n", - "| N/A 45C P0 39W / 250W | 0MiB / 16280MiB | 0% Default |\n", - "| | | ERR! |\n", - "+-------------------------------+----------------------+----------------------+\n", - " \n", - "+-----------------------------------------------------------------------------+\n", - "| Processes: |\n", - "| GPU GI CI PID Type Process name GPU Memory |\n", - "| ID ID Usage |\n", - "|=============================================================================|\n", - "| No running processes found |\n", - "+-----------------------------------------------------------------------------+\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mQ_bNRuloctt", - "colab_type": "text" - }, - "source": [ - "Import Google Drive for persistent storage of our training data, neural network model weights and other required files." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "SMpvVU6Tq2jy", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "daadc193-1cba-405d-d50d-06a05f3e8348" - }, - "source": [ - "drive.mount('/content/drive')" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "go-hqw0wokrN", - "colab_type": "text" - }, - "source": [ - "Empty GPU's memory/cache for training so we'd clear garbage values in it and more memory will be available" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "XOjZidblq5JL", - "colab_type": "code", - "colab": {} - }, - "source": [ - "torch.cuda.empty_cache()" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zjPKpYJTooMG", - "colab_type": "text" - }, - "source": [ - "## Custom Dataset Class\n", - "\n", - "Create a custom dataset class that augments each image into 4 different angles: 0, 45, 90, 120, 180, 270, 300, 330 degrees. Fuse it with Pytorch's DataLoader class so data can be loaded, augmented and trained in realtime instead of caching all training samples in memory for augmenting." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "V4PuME0qmFvZ", - "colab_type": "code", - "colab": {} - }, - "source": [ - "class BrainTumorDataset(Dataset):\n", - " def __init__(self, images, labels):\n", - " # images\n", - " self.X = images\n", - " # labels\n", - " self.y = labels\n", - " \n", - " # Transformation for converting original image array to an image and then convert it to a tensor\n", - " self.transform = transforms.Compose([transforms.ToPILImage(),\n", - " transforms.ToTensor()\n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -45 degrees and 45 degrees, and then convert it to a tensor\n", - " self.transform1 = transforms.Compose([\n", - " transforms.ToPILImage(), \n", - " transforms.RandomRotation(45),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -90 degrees and 90 degrees, and then convert it to a tensor\n", - " self.transform2 = transforms.Compose([\n", - " transforms.ToPILImage(),\n", - " transforms.RandomRotation(90),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -120 degrees and 120 degrees, and then convert it to a tensor\n", - " self.transform3 = transforms.Compose([\n", - " transforms.ToPILImage(),\n", - " transforms.RandomRotation(120),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -180 degrees and 180 degrees, and then convert it to a tensor\n", - " self.transform4 = transforms.Compose([\n", - " transforms.ToPILImage(),\n", - " transforms.RandomRotation(180),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -270 degrees and 270 degrees, and then convert it to a tensor\n", - " self.transform5 = transforms.Compose([\n", - " transforms.ToPILImage(),\n", - " transforms.RandomRotation(270),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -300 degrees and 300 degrees, and then convert it to a tensor\n", - " self.transform6 = transforms.Compose([\n", - " transforms.ToPILImage(),\n", - " transforms.RandomRotation(300),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " # Transformation for converting original image array to an image, rotate it randomly between -330 degrees and 330 degrees, and then convert it to a tensor\n", - " self.transform7 = transforms.Compose([\n", - " transforms.ToPILImage(),\n", - " transforms.RandomRotation(330),\n", - " transforms.ToTensor() \n", - " ])\n", - "\n", - " def __len__(self):\n", - " # return length of image samples\n", - " return len(self.X)\n", - "\n", - " def __getitem__(self, idx):\n", - " # perform transformations on one instance of X\n", - " # Original image as a tensor\n", - " data = self.transform(self.X[idx])\n", - "\n", - " # Augmented image at 45 degrees as a tensor\n", - " aug45 = self.transform1(self.X[idx])\n", - "\n", - " # Augmented image at 90 degrees as a tensor\n", - " aug90 = self.transform2(self.X[idx])\n", - "\n", - " # Augmented image at 120 degrees as a tensor\n", - " aug120 = self.transform3(self.X[idx])\n", - "\n", - " # Augmented image at 180 degrees as a tensor\n", - " aug180 = self.transform4(self.X[idx])\n", - "\n", - " # Augmented image at 270 degrees as a tensor\n", - " aug270 = self.transform5(self.X[idx])\n", - "\n", - " # Augmented image at 300 degrees as a tensor\n", - " aug300 = self.transform6(self.X[idx])\n", - "\n", - " # Augmented image at 330 degrees as a tensor\n", - " aug330 = self.transform7(self.X[idx]) \n", - " \n", - " # store the transformed images in a list\n", - " new_batch = [data, aug45, aug90, aug120, aug180, aug270, aug300, aug330]\n", - "\n", - " # one-hot encode the labels\n", - " labels = torch.zeros(4, dtype=torch.float32)\n", - " labels[int(self.y[idx])] = 1.0\n", - "\n", - " new_labels = [labels, labels, labels, labels, labels, labels, labels, labels]\n", - "\n", - " # 8 augmented images and corresponding labels per sample will be returned\n", - " return (torch.stack(new_labels), torch.stack(new_batch))" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zE6mTnC6pQRD", - "colab_type": "text" - }, - "source": [ - "## Load the Dataset\n", - "\n", - "* Load the **training_data.pickle** file. \n", - "\n", - "* Store the images and labels in separate lists called Xt & yt." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "trcS9hq8q7Dq", - "colab_type": "code", - "colab": {} - }, - "source": [ - "training_data = pickle.load(open('/content/drive/My Drive/new_dataset/training_data.pickle', 'rb'))" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "-FUX5SScHhEg", - "colab_type": "text" - }, - "source": [ - "Create empty lists for storing our data" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "hY6k--StlVAC", - "colab_type": "code", - "colab": {} - }, - "source": [ - "Xt = []\n", - "yt = []\n", - "features = None\n", - "labels = None\n", - "label = []" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "chU6n1FDHllX", - "colab_type": "text" - }, - "source": [ - "Store images in Xt and labels in yt iteratively" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "9gQx6_RtlwfE", - "colab_type": "code", - "colab": {} - }, - "source": [ - "for features,labels in training_data:\n", - " Xt.append(features)\n", - " yt.append(labels)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QOlU2ZoCpitc", - "colab_type": "text" - }, - "source": [ - "## Train Validation Test split\n", - "\n", - "Split the dataset for training using cross-validation method.\n", - "\n", - "* 70 % of images for training \n", - "* 15% of images for validating\n", - "* 15% of images for testing\n", - "\n", - "Set random seed and random_state to any arbitrary number, so the train_test_split happens the same way everytime the function is called." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "wxT6Znuul5Kq", - "colab_type": "code", - "colab": {} - }, - "source": [ - "# 70 % training, 15% validating, 15% testing\n", - "X_train, X_test, y_train, y_test = train_test_split(Xt, yt, test_size=0.3, shuffle=True) # 70% training, 30% testing\n", - "X_valid, X_test, y_valid, y_test = train_test_split(X_test, y_test, test_size=0.5, shuffle=True) # split testing set into 50% validation , 50% testing " - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BWlXaeaVqINY", - "colab_type": "text" - }, - "source": [ - "Empty the previously used lists and arrays to free up RAM / Cache" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "34qYiYRlJoXH", - "colab_type": "code", - "colab": {} - }, - "source": [ - "Xt = None\n", - "yt = None\n", - "features = None\n", - "labels = None\n", - "label = None\n", - "training_data = None " - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "oKlDZh7VqaJj", - "colab_type": "text" - }, - "source": [ - "Create training set, validation set and test set using our custom dataset class" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "rnTKHLR5mCeI", - "colab_type": "code", - "colab": {} - }, - "source": [ - "train_set = BrainTumorDataset(X_train, y_train)\n", - "valid_set = BrainTumorDataset(X_valid, y_valid)\n", - "test_set = BrainTumorDataset(X_test, y_test)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tfWO3u4YqiUP", - "colab_type": "text" - }, - "source": [ - "Print original number of samples in each set" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "MEL7oQ4Y8NIe", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 70 - }, - "outputId": "b4d33819-b7f1-4b5a-ce44-d7aa9c9d9b0c" - }, - "source": [ - "print(f\"Number of training samples: {len(X_train)}\")\n", - "print(f\"Number of validation samples: {len(X_valid)}\")\n", - "print(f\"Number of testing samples: {len(X_test)}\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Number of training samples: 2144\n", - "Number of validation samples: 460\n", - "Number of testing samples: 460\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "0udsS0Qqql3g", - "colab_type": "text" - }, - "source": [ - "Print augmented number of samples in each set" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "8PJu7hjEi0Ij", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 70 - }, - "outputId": "b9cb23ac-8182-4b4b-cd7f-d7b93670b615" - }, - "source": [ - "print(f\"Number of augmented training samples: {len(X_train) * 8}\")\n", - "print(f\"Number of augmented validation samples: {len(X_valid)* 8}\")\n", - "print(f\"Number of augmented testing samples: {len(X_test)* 8}\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Number of augmented training samples: 17152\n", - "Number of augmented validation samples: 3680\n", - "Number of augmented testing samples: 3680\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "S9XaKzTZqpW0", - "colab_type": "text" - }, - "source": [ - "Create a DataLoader for each set with batch size of 4 and shuffling enabled" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "-5Eil06jrRUT", - "colab_type": "code", - "colab": {} - }, - "source": [ - "train_gen = DataLoader(train_set, batch_size=4, shuffle=True, pin_memory=True, num_workers=8)\n", - "valid_gen = DataLoader(valid_set, batch_size=4, shuffle=True, pin_memory=True, num_workers=8)\n", - "test_gen = DataLoader(test_set, batch_size=10, shuffle=True, pin_memory=True, num_workers=8)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VE1aH9bJq1HD", - "colab_type": "text" - }, - "source": [ - "Get device to set the training to run on GPU or CPU later based on its availibility" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "ka_a8OAPtNHY", - "colab_type": "code", - "colab": {} - }, - "source": [ - "device_name = \"cuda:0:\" if torch.cuda.is_available() else \"cpu\"\n", - "device = torch.device(device_name)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eHncA4uwrAnB", - "colab_type": "text" - }, - "source": [ - "## Build the Model\n", - "\n", - "* Instantiate the transfer learning model using torchvision's models class.\n", - "\n", - "* RESNET50 is the CNN model that we're going to use by transfer learning.\n", - "\n", - "* Set all the pretrained weights to trainable by enabling every layer's parameters as true\n", - "\n", - "* Build the top layer by creating a custom output sequential layer and assign it to model's fc." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "Lq3_lCEttAJk", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "outputId": "feb12130-9d1d-4c44-d546-6327f266745a" - }, - "source": [ - "# instantiate transfer learning model\n", - "resnet_model = models.resnet50(pretrained=True)\n", - "\n", - "# set all paramters as trainable\n", - "for param in resnet_model.parameters():\n", - " param.requires_grad = True\n", - "\n", - "# get input of fc layer\n", - "n_inputs = resnet_model.fc.in_features\n", - "\n", - "# redefine fc layer / top layer/ head for our classification problem\n", - "resnet_model.fc = nn.Sequential(nn.Linear(n_inputs, 2048),\n", - " nn.SELU(),\n", - " nn.Dropout(p=0.4),\n", - " nn.Linear(2048, 2048),\n", - " nn.SELU(),\n", - " nn.Dropout(p=0.4),\n", - " nn.Linear(2048, 4),\n", - " nn.LogSigmoid())\n", - "\n", - "# set all paramters of the model as trainable\n", - "for name, child in resnet_model.named_children():\n", - " for name2, params in child.named_parameters():\n", - " params.requires_grad = True\n", - "\n", - "# set model to run on GPU or CPU absed on availibility\n", - "resnet_model.to(device)\n", - "\n", - "# print the trasnfer learning NN model's architecture\n", - "resnet_model" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "ResNet(\n", - " (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n", - " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n", - " (layer1): Sequential(\n", - " (0): Bottleneck(\n", - " (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " (downsample): Sequential(\n", - " (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " )\n", - " )\n", - " (1): Bottleneck(\n", - " (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (2): Bottleneck(\n", - " (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " )\n", - " (layer2): Sequential(\n", - " (0): Bottleneck(\n", - " (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " (downsample): Sequential(\n", - " (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", - " (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " )\n", - " )\n", - " (1): Bottleneck(\n", - " (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (2): Bottleneck(\n", - " (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (3): Bottleneck(\n", - " (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " )\n", - " (layer3): Sequential(\n", - " (0): Bottleneck(\n", - " (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " (downsample): Sequential(\n", - " (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", - " (1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " )\n", - " )\n", - " (1): Bottleneck(\n", - " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (2): Bottleneck(\n", - " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (3): Bottleneck(\n", - " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (4): Bottleneck(\n", - " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (5): Bottleneck(\n", - " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " )\n", - " (layer4): Sequential(\n", - " (0): Bottleneck(\n", - " (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " (downsample): Sequential(\n", - " (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", - " (1): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " )\n", - " )\n", - " (1): Bottleneck(\n", - " (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " (2): Bottleneck(\n", - " (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", - " (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", - " (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (relu): ReLU(inplace=True)\n", - " )\n", - " )\n", - " (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n", - " (fc): Sequential(\n", - " (0): Linear(in_features=2048, out_features=2048, bias=True)\n", - " (1): SELU()\n", - " (2): Dropout(p=0.4, inplace=False)\n", - " (3): Linear(in_features=2048, out_features=2048, bias=True)\n", - " (4): SELU()\n", - " (5): Dropout(p=0.4, inplace=False)\n", - " (6): Linear(in_features=2048, out_features=4, bias=True)\n", - " (7): LogSigmoid()\n", - " )\n", - ")" - ] - }, - "metadata": { - "tags": [] - }, - "execution_count": 17 - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_QdrEPESsKQi", - "colab_type": "text" - }, - "source": [ - "## Set Training Configuration\n", - "\n", - "* Set model's loss function as CrossEntropyLoss\n", - "\n", - "* Set SGD optimizer with 0.9 momentum and learning rate 3e-4 as the model's optimizer. According to many Deep learning experts and researchers such as [Andrej karpathy](https://github.com/karpathy) 3e-4 is a good learning rate choice.\n", - "\n", - "* Run the model for 10 total iterations\n", - "\n", - "* Create empty lists to store training losses, validation losses, training accuracies, and validation accuracies." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "cokrpXp3ud8_", - "colab_type": "code", - "colab": {} - }, - "source": [ - "# loss function\n", - "# if GPU is available set loss function to use GPU\n", - "criterion = nn.CrossEntropyLoss().to(device)\n", - "\n", - "# optimizer\n", - "optimizer = torch.optim.SGD(resnet_model.parameters(), momentum=0.9, lr=3e-4)\n", - "\n", - "# number of training iterations\n", - "epochs = 30\n", - "\n", - "# empty lists to store losses and accuracies\n", - "train_losses = []\n", - "test_losses = []\n", - "train_correct = []\n", - "test_correct = []" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7E_rg4eJs7Xq", - "colab_type": "text" - }, - "source": [ - "## Util function\n", - "\n", - "### Checkpoint Saver\n", - "A function to save the model using checkpoints based on best loss achieved during every iteration compared with previous iteration's loss. We'll load the checkpoint and resume training in case Colab's runtime get's disconnected due to inactivity or any other issues." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "MnUksQpFNxNK", - "colab_type": "code", - "colab": {} - }, - "source": [ - "def save_checkpoint(state, is_best, filename='/content/drive/My Drive/bt_resnet50_ckpt_v2.pth.tar'):\n", - " torch.save(state, filename)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ci1Lw3UgtU13", - "colab_type": "text" - }, - "source": [ - "## Train the model" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "0TWAlOEau6k7", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "outputId": "d1c0a256-466f-4420-c9ad-a1bf15630b97" - }, - "source": [ - "# set training start time\n", - "start_time = time.time()\n", - "\n", - "# set best_prec loss value as 2 for checkpoint threshold\n", - "best_prec1 = 2\n", - "\n", - "# empty batch variables\n", - "b = None\n", - "train_b = None\n", - "test_b = None\n", - "\n", - "# start training\n", - "for i in range(epochs):\n", - " # empty training correct and test correct counter as 0 during every iteration\n", - " trn_corr = 0\n", - " tst_corr = 0\n", - " \n", - " # set epoch's starting time\n", - " e_start = time.time()\n", - " \n", - " # train in batches\n", - " for b, (y, X) in enumerate(train_gen):\n", - " # set label as cuda if device is cuda\n", - " X, y = X.to(device), y.to(device)\n", - "\n", - " # forward pass image sample\n", - " y_pred = resnet_model(X.view(-1, 3, 512, 512))\n", - " # calculate loss\n", - " loss = criterion(y_pred.float(), torch.argmax(y.view(32, 4), dim=1).long())\n", - "\n", - " # get argmax of predicted tensor, which is our label\n", - " predicted = torch.argmax(y_pred, dim=1).data\n", - " # if predicted label is correct as true label, calculate the sum for samples\n", - " batch_corr = (predicted == torch.argmax(y.view(32, 4), dim=1)).sum()\n", - " # increment train correct with correcly predicted labels per batch\n", - " trn_corr += batch_corr\n", - " \n", - " # set optimizer gradients to zero\n", - " optimizer.zero_grad()\n", - " # back propagate with loss\n", - " loss.backward()\n", - " # perform optimizer step\n", - " optimizer.step()\n", - "\n", - " # set epoch's end time\n", - " e_end = time.time()\n", - " # print training metrics\n", - " print(f'Epoch {(i+1)} Batch {(b+1)*4}\\nAccuracy: {trn_corr.item()*100/(4*8*b):2.2f} % Loss: {loss.item():2.4f} Duration: {((e_end-e_start)/60):.2f} minutes') # 4 images per batch * 8 augmentations per image * batch length\n", - "\n", - " # some metrics storage for visualization\n", - " train_b = b\n", - " train_losses.append(loss)\n", - " train_correct.append(trn_corr)\n", - "\n", - " X, y = None, None\n", - "\n", - " # validate using validation generator\n", - " # do not perform any gradient updates while validation\n", - " with torch.no_grad():\n", - " for b, (y, X) in enumerate(valid_gen):\n", - " # set label as cuda if device is cuda\n", - " X, y = X.to(device), y.to(device)\n", - "\n", - " # forward pass image\n", - " y_val = resnet_model(X.view(-1, 3, 512, 512))\n", - "\n", - " # get argmax of predicted tensor, which is our label\n", - " predicted = torch.argmax(y_val, dim=1).data\n", - "\n", - " # increment test correct with correcly predicted labels per batch\n", - " tst_corr += (predicted == torch.argmax(y.view(32, 4), dim=1)).sum()\n", - "\n", - " # get loss of validation set\n", - " loss = criterion(y_val.float(), torch.argmax(y.view(32, 4), dim=1).long())\n", - " # print validation metrics\n", - " print(f'Validation Accuracy {tst_corr.item()*100/(4*8*b):2.2f} Validation Loss: {loss.item():2.4f}\\n')\n", - "\n", - " # if current validation loss is less than previous iteration's validatin loss create and save a checkpoint\n", - " is_best = loss < best_prec1\n", - " best_prec1 = min(loss, best_prec1)\n", - " save_checkpoint({\n", - " 'epoch': i + 1,\n", - " 'state_dict': resnet_model.state_dict(),\n", - " 'best_prec1': best_prec1,\n", - " }, is_best)\n", - "\n", - " # some metrics storage for visualization\n", - " test_b = b\n", - " test_losses.append(loss)\n", - " test_correct.append(tst_corr)\n", - "\n", - "# set total training's end time\n", - "end_time = time.time() - start_time \n", - "\n", - "# print training summary\n", - "print(\"\\nTraining Duration {:.2f} minutes\".format(end_time/60))\n", - "print(\"GPU memory used : {} kb\".format(torch.cuda.memory_allocated()))\n", - "print(\"GPU memory cached : {} kb\".format(torch.cuda.memory_cached()))" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Epoch 1 Batch 2144\n", - "Accuracy: 56.80 % Loss: 0.4113 Duration: 7.17 minutes\n", - "Validation Accuracy 69.44 Validation Loss: 1.3394\n", - "\n", - "Epoch 2 Batch 2144\n", - "Accuracy: 79.44 % Loss: 1.0226 Duration: 7.14 minutes\n", - "Validation Accuracy 87.03 Validation Loss: 1.3007\n", - "\n", - "Epoch 3 Batch 2144\n", - "Accuracy: 85.72 % Loss: 0.0820 Duration: 7.13 minutes\n", - "Validation Accuracy 85.75 Validation Loss: 0.2911\n", - "\n", - "Epoch 4 Batch 2144\n", - "Accuracy: 88.46 % Loss: 0.5032 Duration: 7.13 minutes\n", - "Validation Accuracy 89.91 Validation Loss: 0.0314\n", - "\n", - "Epoch 5 Batch 2144\n", - "Accuracy: 90.29 % Loss: 1.4266 Duration: 7.14 minutes\n", - "Validation Accuracy 92.57 Validation Loss: 0.3442\n", - "\n", - "Epoch 6 Batch 2144\n", - "Accuracy: 91.64 % Loss: 0.0373 Duration: 7.15 minutes\n", - "Validation Accuracy 90.90 Validation Loss: 0.0449\n", - "\n", - "Epoch 7 Batch 2144\n", - "Accuracy: 93.24 % Loss: 0.1904 Duration: 7.14 minutes\n", - "Validation Accuracy 93.64 Validation Loss: 0.0942\n", - "\n", - "Epoch 8 Batch 2144\n", - "Accuracy: 95.27 % Loss: 0.0215 Duration: 7.15 minutes\n", - "Validation Accuracy 95.94 Validation Loss: 0.0230\n", - "\n", - "Epoch 9 Batch 2144\n", - "Accuracy: 96.08 % Loss: 0.0065 Duration: 7.14 minutes\n", - "Validation Accuracy 92.82 Validation Loss: 0.0080\n", - "\n", - "Epoch 10 Batch 2144\n", - "Accuracy: 97.21 % Loss: 0.0347 Duration: 7.14 minutes\n", - "Validation Accuracy 94.19 Validation Loss: 0.1907\n", - "\n", - "Epoch 11 Batch 2144\n", - "Accuracy: 96.34 % Loss: 0.0050 Duration: 7.13 minutes\n", - "Validation Accuracy 96.85 Validation Loss: 0.1216\n", - "\n", - "Epoch 12 Batch 2144\n", - "Accuracy: 97.03 % Loss: 0.1557 Duration: 7.14 minutes\n", - "Validation Accuracy 95.34 Validation Loss: 0.0142\n", - "\n", - "Epoch 13 Batch 2144\n", - "Accuracy: 97.79 % Loss: 0.0323 Duration: 7.13 minutes\n", - "Validation Accuracy 97.18 Validation Loss: 0.0280\n", - "\n", - "Epoch 14 Batch 2144\n", - "Accuracy: 98.07 % Loss: 0.0075 Duration: 7.14 minutes\n", - "Validation Accuracy 96.93 Validation Loss: 0.0036\n", - "\n", - "Epoch 15 Batch 2144\n", - "Accuracy: 98.29 % Loss: 0.0080 Duration: 7.13 minutes\n", - "Validation Accuracy 96.79 Validation Loss: 0.0067\n", - "\n", - "Epoch 16 Batch 2144\n", - "Accuracy: 98.33 % Loss: 0.3111 Duration: 7.13 minutes\n", - "Validation Accuracy 97.89 Validation Loss: 0.2079\n", - "\n", - "Epoch 17 Batch 2144\n", - "Accuracy: 98.46 % Loss: 0.0145 Duration: 7.14 minutes\n", - "Validation Accuracy 97.64 Validation Loss: 0.0079\n", - "\n", - "Epoch 18 Batch 2144\n", - "Accuracy: 99.09 % Loss: 0.0176 Duration: 7.13 minutes\n", - "Validation Accuracy 97.51 Validation Loss: 0.0313\n", - "\n", - "Epoch 19 Batch 2144\n", - "Accuracy: 98.92 % Loss: 0.0012 Duration: 7.14 minutes\n", - "Validation Accuracy 96.24 Validation Loss: 0.0181\n", - "\n", - "Epoch 20 Batch 2144\n", - "Accuracy: 99.42 % Loss: 0.0017 Duration: 7.14 minutes\n", - "Validation Accuracy 97.75 Validation Loss: 0.0065\n", - "\n", - "Epoch 21 Batch 2144\n", - "Accuracy: 99.52 % Loss: 0.0013 Duration: 7.13 minutes\n", - "Validation Accuracy 97.59 Validation Loss: 0.0034\n", - "\n", - "Epoch 22 Batch 2144\n", - "Accuracy: 99.85 % Loss: 0.0014 Duration: 7.14 minutes\n", - "Validation Accuracy 98.27 Validation Loss: 0.0242\n", - "\n", - "Epoch 23 Batch 2144\n", - "Accuracy: 99.39 % Loss: 0.0474 Duration: 7.13 minutes\n", - "Validation Accuracy 97.81 Validation Loss: 0.1353\n", - "\n", - "Epoch 24 Batch 2144\n", - "Accuracy: 99.81 % Loss: 0.2521 Duration: 7.13 minutes\n", - "Validation Accuracy 98.19 Validation Loss: 0.2287\n", - "\n", - "Epoch 25 Batch 2144\n", - "Accuracy: 99.65 % Loss: 0.0075 Duration: 7.13 minutes\n", - "Validation Accuracy 98.71 Validation Loss: 0.0178\n", - "\n", - "Epoch 26 Batch 2144\n", - "Accuracy: 99.85 % Loss: 0.0745 Duration: 7.13 minutes\n", - "Validation Accuracy 98.33 Validation Loss: 0.0034\n", - "\n", - "Epoch 27 Batch 2144\n", - "Accuracy: 100.05 % Loss: 0.0002 Duration: 7.13 minutes\n", - "Validation Accuracy 98.79 Validation Loss: 0.0046\n", - "\n", - "Epoch 28 Batch 2144\n", - "Accuracy: 100.01 % Loss: 0.0014 Duration: 7.13 minutes\n", - "Validation Accuracy 98.66 Validation Loss: 0.1200\n", - "\n", - "Epoch 29 Batch 2144\n", - "Accuracy: 99.86 % Loss: 0.0452 Duration: 7.13 minutes\n", - "Validation Accuracy 97.94 Validation Loss: 0.0074\n", - "\n", - "Epoch 30 Batch 2144\n", - "Accuracy: 100.08 % Loss: 0.0005 Duration: 7.13 minutes\n", - "Validation Accuracy 98.88 Validation Loss: 0.0016\n", - "\n", - "\n", - "Training Duration 231.13 minutes\n", - "GPU memory used : 490444800 kb\n", - "GPU memory cached : 16034824192 kb\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "U5hiXkZkwW-W", - "colab_type": "code", - "colab": {} - }, - "source": [ - "torch.cuda.empty_cache()" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9Rzl2f2kteb8", - "colab_type": "text" - }, - "source": [ - "## Save the model\n", - "\n", - "Save the model after the training is completed" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "K4yzcJMmwPnY", - "colab_type": "code", - "colab": {} - }, - "source": [ - "torch.save(resnet_model.state_dict(), '/content/drive/My Drive/bt_resnet50_model.pt')" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Q-2R8Yputlvh", - "colab_type": "text" - }, - "source": [ - "## Evaluation" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "hPEBr7E1tobU", - "colab_type": "text" - }, - "source": [ - "Print the validation accuracy of the model calculated using validation set during training " - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "4w0EuNLQvmNV", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "685ce068-21f1-4419-c278-555cde282b40" - }, - "source": [ - "print(f'Validation accuracy: {test_correct[-1].item()*100/(test_b*8*4):.2f}%')" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Validation accuracy: 98.88%\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EnauSi8Dt04I", - "colab_type": "text" - }, - "source": [ - "Plot the loss graph" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "TttSopdFvtr0", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 294 - }, - "outputId": "6bc80323-bb7c-4a70-eae5-544ed8c363fb" - }, - "source": [ - "plt.plot(train_losses, label='Training loss')\n", - "plt.plot(test_losses, label='Validation loss')\n", - "plt.title('Loss Metrics')\n", - "plt.ylabel('Loss')\n", - "plt.xlabel('Epochs')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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81mxYgV2u53S0lKWLl2Kw+Hgk08+4Wc/+xlvvfVWs/fk5uayePFiysrKGDp0KHfeeWezOfnr169n69at9OrVi6lTp7J8+XJycnL43ve+x5IlS+jfvz9z5sxps30PPfQQ48aNY8GCBXz22WfcfPPNbNiwgccff5ynn36aqVOnUl5eTmxsLM8++ywXXnghP//5z/F6vVRWVob999ERnbbOQCnVG5gFnE0bwUApdTtwO0Dfvn2tb1wnKqwwhokkGAjRtmuvvRa73Q5ASUkJt9xyCzt37kQpRW1ty0Otl156KS6XC5fLRffu3Tly5AiZmZmNrjn99NPrj2VnZ7N3717cbjcDBgyon78/Z84cnn322aDtW7ZsWX1AOueccygoKKC0tJSpU6dy3333ccMNN3DVVVeRmZnJxIkTufXWW6mtreXKK68kOzu7Q3834erMRWdPAA9orX1tTYXSWj8LPAuQk5NzUk+zkWEi0dW15xu8VRISEur//Mtf/pKzzz6bd955h7179zJjxowW3+Nyuer/bLfbqatr/v9aKNd0xE9/+lMuvfRSFi1axNSpU/noo4+YPn06S5Ys4f3332fu3Lncd9993HzzzRF9bjCdOZsoB5ivlNoLXAPMU0pd2Ynt6RIKKmSYSIj2KCkpoXfv3gC8+OKLEb//0KFD2b17N3v37gXg9ddfb/M906ZN49VXXwWMXES3bt1ISkpi165djB49mgceeICJEyeSm5vLvn37OO2007jtttv47ne/y7p16yL+MwTTaT0DrXX9Wmml1IvAf7TWCzqrPV1FoQQDIdrlJz/5CbfccguPPvool156acTvHxcXx7x587joootISEhg4sS2U51mwnrMmDHEx8fz0ksvAfDEE0+wePFibDYbI0eO5OKLL2b+/Pn8/ve/x+l04na7efnllyP+MwSjrFrcpJT6FzAD6AYcAR4CnABa6782ufZFjGDQ5tTSnJwcfbJublNd62XYLz8EYM7pffnfq0Z3couEMGzfvp3hw4d3djM6XXl5OW63G601d911F4MHD+bee+/t7GY109K/l1Jqrda61Tm2Vs4majvV3nDtXKvacSIxh4hAegZCdEV///vfeemll/B4PIwbN47vfe97nd2kiJGqpV1IYXlAMJBFZ0J0Offee2+X7AlEwqlTjqKyEJb+AWrKOrslrSrwTyt1OWzSMxBC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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [], - "needs_background": "light" - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8L-_dup0uCl5", - "colab_type": "text" - }, - "source": [ - "Plot the accuracy graph\n", - "\n", - "* Training set - Total length of training samples divided by 100 for every trained sample\n", - "\n", - " * `int((2144 * 8)/100) = int(171.52) = 171`\n", - "\n", - "* Testing set - Total length of testing samples divided by 100 for every testing sample\n", - "\n", - " * `int((460 * 8)/100) = int(36.8) = 36`\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "YKzECNFZvx9u", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 312 - }, - "outputId": "7a995797-5511-4b7d-8227-ea9af093cc18" - }, - "source": [ - "plt.plot([t/171 for t in train_correct], label='Training accuracy')\n", - "plt.plot([t/36 for t in test_correct], label='Validation accuracy')\n", - "plt.title('Accuracy Metrics')\n", - "plt.ylabel('Accuracy')\n", - "plt.xlabel('Epochs')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "/pytorch/aten/src/ATen/native/BinaryOps.cpp:81: UserWarning: Integer division of tensors using div or / is deprecated, and in a future release div will perform true division as in Python 3. Use true_divide or floor_divide (// in Python) instead.\n" - ], - "name": "stderr" - }, - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [], - "needs_background": "light" - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nEKbGLWsIt6P", - "colab_type": "text" - }, - "source": [ - "Empty out training set and validation set to free up RAM / Cache" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "SsVJuIwych21", - "colab_type": "code", - "colab": {} - }, - "source": [ - "# resnet_model.load_state_dict(torch.load('/content/drive/My Drive/bt_resnet_torch.pt'))\n", - "train_gen = None\n", - "valid_gen = None\n", - "train_set = None\n", - "valid_set = None" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LfVhP2dmvdoi", - "colab_type": "text" - }, - "source": [ - "Set model to evaluation mode\n", - "\n", - "Calculate loss, correctly classified samples, predicted values, labels and store them in a list using test dataloader" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "WZJ7w9ztv1pb", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "e59c6347-59f8-42e8-aff4-c95e2567f495" - }, - "source": [ - "# set model to evaluation mode\n", - "resnet_model.eval()\n", - "\n", - "# perform no gradient updates\n", - "with torch.no_grad():\n", - " # soem metrics storage for visualization and analysis\n", - " correct = 0\n", - " test_loss = []\n", - " test_corr = []\n", - " labels = []\n", - " pred = []\n", - " # perform test set evaluation batch wise\n", - " for (y, X) in test_gen:\n", - " # set label to use CUDA if available\n", - " X, y = X.to(device), y.to(device)\n", - "\n", - " # append original labels\n", - " labels.append(torch.argmax(y.view(10 * 8, 4), dim=1).data)\n", - "\n", - " # perform forward pass\n", - " y_val = resnet_model(X.view(-1, 3, 512, 512))\n", - "\n", - " # get argmax of predicted values, which is our label\n", - " predicted = torch.argmax(y_val, dim=1).data\n", - " # append predicted label\n", - " pred.append(predicted)\n", - "\n", - " # calculate loss\n", - " loss = criterion(y_val.float(), torch.argmax(y.view(10 * 8, 4), dim=1).long())\n", - "\n", - " # increment correct with correcly predicted labels per batch\n", - " correct += (predicted == torch.argmax(y.view(10 * 8, 4), dim=1)).sum()\n", - "\n", - " # append correct samples labels and losses\n", - " test_corr.append(correct)\n", - " test_loss.append(loss)\n", - " \n", - "print(f\"Test Loss: {test_loss[-1].item():.4f}\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Test Loss: 0.0096\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "AqDm19ZSvyY2", - "colab_type": "text" - }, - "source": [ - "Print the test accuracy\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "HJm2yjzHbLlP", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "7b227ca6-3d4d-44cd-a030-a0e0cc8d744e" - }, - "source": [ - "print(f'Test accuracy: {test_corr[-1].item()*100/(460*8):.2f}%')" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Test accuracy: 99.32%\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "T6fxSb68v0uf", - "colab_type": "text" - }, - "source": [ - "Convert list of tensors to tensors" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "jDRv0IhThKhP", - "colab_type": "code", - "colab": {} - }, - "source": [ - "labels = torch.stack(labels)\n", - "pred = torch.stack(pred)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ps2QnU4ov4Sr", - "colab_type": "text" - }, - "source": [ - "Define ground-truth labels as a list" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "7BsASCMlL2_z", - "colab_type": "code", - "colab": {} - }, - "source": [ - "LABELS = ['Meningioma', 'Glioma', 'Pitutary']" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wubHPKqov61w", - "colab_type": "text" - }, - "source": [ - "Plot the confusion matrix" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "3cfoG8Q0gMKZ", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 387 - }, - "outputId": "95a96f4c-bf40-4efd-a0f2-31d5606c657a" - }, - "source": [ - "arr = confusion_matrix(pred.view(-1).cpu(), labels.view(-1).cpu())\n", - "df_cm = pd.DataFrame(arr, LABELS, LABELS)\n", - "plt.figure(figsize = (9,6))\n", - "sns.heatmap(df_cm, annot=True, fmt=\"d\", cmap='viridis')\n", - "plt.xlabel(\"Prediction\")\n", - "plt.ylabel(\"Target\")\n", - "plt.show()" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [], - "needs_background": "light" - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kHhrPo6Tv9Xc", - "colab_type": "text" - }, - "source": [ - "Print the classification report" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "MNWrpWVXL_pQ", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 230 - }, - "outputId": "4dd25c3d-45e9-4c8c-cf56-11c58a41cecd" - }, - "source": [ - "print(f\"Clasification Report\\n\\n{classification_report(pred.view(-1).cpu(), labels.view(-1).cpu())}\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Clasification Report\n", - "\n", - " precision recall f1-score support\n", - "\n", - " 1 0.98 1.00 0.99 895\n", - " 2 1.00 0.99 0.99 1715\n", - " 3 1.00 0.99 1.00 1070\n", - "\n", - " accuracy 0.99 3680\n", - " macro avg 0.99 0.99 0.99 3680\n", - "weighted avg 0.99 0.99 0.99 3680\n", - "\n" - ], - "name": "stdout" - } + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "YyPnviwkoaRh" + }, + "source": [ + "Print Pytorch's version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "colab_type": "code", + "id": "uY8S0R76sj90", + "outputId": "012126c7-b199-473a-d7c3-88a22c6eeb3b" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'1.5.0+cu101'" ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "N5xUvujSwBQc", - "colab_type": "text" - }, - "source": [ - "Print the Jaccard Similarity score / Index (Accuracy)" + }, + "execution_count": 2, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "torch.__version__" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "8uWrMvwCbmsw" + }, + "source": [ + "Check GPU" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 372 + }, + "colab_type": "code", + "id": "4NKtY3ESbmSl", + "outputId": "e54e9295-b586-4ee6-ff51-70e63e9dd854" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sat Jun 20 12:12:51 2020 \n", + "+-----------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 450.36.06 Driver Version: 418.67 CUDA Version: 10.1 |\n", + "|-------------------------------+----------------------+----------------------+\n", + "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", + "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", + "| | | MIG M. |\n", + "|===============================+======================+======================|\n", + "| 0 Tesla P100-PCIE... Off | 00000000:00:04.0 Off | 0 |\n", + "| N/A 45C P0 39W / 250W | 0MiB / 16280MiB | 0% Default |\n", + "| | | ERR! |\n", + "+-------------------------------+----------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=============================================================================|\n", + "| No running processes found |\n", + "+-----------------------------------------------------------------------------+\n" + ] + } + ], + "source": [ + "!nvidia-smi" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "mQ_bNRuloctt" + }, + "source": [ + "Import Google Drive for persistent storage of our training data, neural network model weights and other required files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "colab_type": "code", + "id": "SMpvVU6Tq2jy", + "outputId": "daadc193-1cba-405d-d50d-06a05f3e8348" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n" + ] + } + ], + "source": [ + "drive.mount('/content/drive')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "go-hqw0wokrN" + }, + "source": [ + "Empty GPU's memory/cache for training so we'd clear garbage values in it and more memory will be available" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "XOjZidblq5JL" + }, + "outputs": [], + "source": [ + "torch.cuda.empty_cache()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "zjPKpYJTooMG" + }, + "source": [ + "## Custom Dataset Class\n", + "\n", + "Create a custom dataset class that augments each image into 4 different angles: 0, 45, 90, 120, 180, 270, 300, 330 degrees. Fuse it with Pytorch's DataLoader class so data can be loaded, augmented and trained in realtime instead of caching all training samples in memory for augmenting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "V4PuME0qmFvZ" + }, + "outputs": [], + "source": [ + "class BrainTumorDataset(Dataset):\n", + " def __init__(self, images, labels):\n", + " # images\n", + " self.X = images\n", + " # labels\n", + " self.y = labels\n", + " \n", + " # Transformation for converting original image array to an image and then convert it to a tensor\n", + " self.transform = transforms.Compose([transforms.ToPILImage(),\n", + " transforms.ToTensor()\n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -45 degrees and 45 degrees, and then convert it to a tensor\n", + " self.transform1 = transforms.Compose([\n", + " transforms.ToPILImage(), \n", + " transforms.RandomRotation(45),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -90 degrees and 90 degrees, and then convert it to a tensor\n", + " self.transform2 = transforms.Compose([\n", + " transforms.ToPILImage(),\n", + " transforms.RandomRotation(90),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -120 degrees and 120 degrees, and then convert it to a tensor\n", + " self.transform3 = transforms.Compose([\n", + " transforms.ToPILImage(),\n", + " transforms.RandomRotation(120),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -180 degrees and 180 degrees, and then convert it to a tensor\n", + " self.transform4 = transforms.Compose([\n", + " transforms.ToPILImage(),\n", + " transforms.RandomRotation(180),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -270 degrees and 270 degrees, and then convert it to a tensor\n", + " self.transform5 = transforms.Compose([\n", + " transforms.ToPILImage(),\n", + " transforms.RandomRotation(270),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -300 degrees and 300 degrees, and then convert it to a tensor\n", + " self.transform6 = transforms.Compose([\n", + " transforms.ToPILImage(),\n", + " transforms.RandomRotation(300),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " # Transformation for converting original image array to an image, rotate it randomly between -330 degrees and 330 degrees, and then convert it to a tensor\n", + " self.transform7 = transforms.Compose([\n", + " transforms.ToPILImage(),\n", + " transforms.RandomRotation(330),\n", + " transforms.ToTensor() \n", + " ])\n", + "\n", + " def __len__(self):\n", + " # return length of image samples\n", + " return len(self.X)\n", + "\n", + " def __getitem__(self, idx):\n", + " # perform transformations on one instance of X\n", + " # Original image as a tensor\n", + " data = self.transform(self.X[idx])\n", + "\n", + " # Augmented image at 45 degrees as a tensor\n", + " aug45 = self.transform1(self.X[idx])\n", + "\n", + " # Augmented image at 90 degrees as a tensor\n", + " aug90 = self.transform2(self.X[idx])\n", + "\n", + " # Augmented image at 120 degrees as a tensor\n", + " aug120 = self.transform3(self.X[idx])\n", + "\n", + " # Augmented image at 180 degrees as a tensor\n", + " aug180 = self.transform4(self.X[idx])\n", + "\n", + " # Augmented image at 270 degrees as a tensor\n", + " aug270 = self.transform5(self.X[idx])\n", + "\n", + " # Augmented image at 300 degrees as a tensor\n", + " aug300 = self.transform6(self.X[idx])\n", + "\n", + " # Augmented image at 330 degrees as a tensor\n", + " aug330 = self.transform7(self.X[idx]) \n", + " \n", + " # store the transformed images in a list\n", + " new_batch = [data, aug45, aug90, aug120, aug180, aug270, aug300, aug330]\n", + "\n", + " # one-hot encode the labels\n", + " labels = torch.zeros(4, dtype=torch.float32)\n", + " labels[int(self.y[idx])] = 1.0\n", + "\n", + " new_labels = [labels, labels, labels, labels, labels, labels, labels, labels]\n", + "\n", + " # 8 augmented images and corresponding labels per sample will be returned\n", + " return (torch.stack(new_labels), torch.stack(new_batch))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "zE6mTnC6pQRD" + }, + "source": [ + "## Load the Dataset\n", + "\n", + "* Load the **training_data.pickle** file. \n", + "\n", + "* Store the images and labels in separate lists called Xt & yt." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "trcS9hq8q7Dq" + }, + "outputs": [], + "source": [ + "training_data = pickle.load(open('/content/drive/My Drive/new_dataset/training_data.pickle', 'rb'))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "-FUX5SScHhEg" + }, + "source": [ + "Create empty lists for storing our data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "hY6k--StlVAC" + }, + "outputs": [], + "source": [ + "Xt = []\n", + "yt = []\n", + "features = None\n", + "labels = None\n", + "label = []" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "chU6n1FDHllX" + }, + "source": [ + "Store images in Xt and labels in yt iteratively" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "9gQx6_RtlwfE" + }, + "outputs": [], + "source": [ + "for features,labels in training_data:\n", + " Xt.append(features)\n", + " yt.append(labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "QOlU2ZoCpitc" + }, + "source": [ + "## Train Validation Test split\n", + "\n", + "Split the dataset for training using cross-validation method.\n", + "\n", + "* 70 % of images for training \n", + "* 15% of images for validating\n", + "* 15% of images for testing\n", + "\n", + "Set random seed and random_state to any arbitrary number, so the train_test_split happens the same way everytime the function is called." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "wxT6Znuul5Kq" + }, + "outputs": [], + "source": [ + "# 70 % training, 15% validating, 15% testing\n", + "X_train, X_test, y_train, y_test = train_test_split(Xt, yt, test_size=0.3, shuffle=True) # 70% training, 30% testing\n", + "X_valid, X_test, y_valid, y_test = train_test_split(X_test, y_test, test_size=0.5, shuffle=True) # split testing set into 50% validation , 50% testing " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "BWlXaeaVqINY" + }, + "source": [ + "Empty the previously used lists and arrays to free up RAM / Cache" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "34qYiYRlJoXH" + }, + "outputs": [], + "source": [ + "Xt = None\n", + "yt = None\n", + "features = None\n", + "labels = None\n", + "label = None\n", + "training_data = None " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "oKlDZh7VqaJj" + }, + "source": [ + "Create training set, validation set and test set using our custom dataset class" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "rnTKHLR5mCeI" + }, + "outputs": [], + "source": [ + "train_set = BrainTumorDataset(X_train, y_train)\n", + "valid_set = BrainTumorDataset(X_valid, y_valid)\n", + "test_set = BrainTumorDataset(X_test, y_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "tfWO3u4YqiUP" + }, + "source": [ + "Print original number of samples in each set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 70 + }, + "colab_type": "code", + "id": "MEL7oQ4Y8NIe", + "outputId": "b4d33819-b7f1-4b5a-ce44-d7aa9c9d9b0c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of training samples: 2144\n", + "Number of validation samples: 460\n", + "Number of testing samples: 460\n" + ] + } + ], + "source": [ + "print(f\"Number of training samples: {len(X_train)}\")\n", + "print(f\"Number of validation samples: {len(X_valid)}\")\n", + "print(f\"Number of testing samples: {len(X_test)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "0udsS0Qqql3g" + }, + "source": [ + "Print augmented number of samples in each set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 70 + }, + "colab_type": "code", + "id": "8PJu7hjEi0Ij", + "outputId": "b9cb23ac-8182-4b4b-cd7f-d7b93670b615" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of augmented training samples: 17152\n", + "Number of augmented validation samples: 3680\n", + "Number of augmented testing samples: 3680\n" + ] + } + ], + "source": [ + "print(f\"Number of augmented training samples: {len(X_train) * 8}\")\n", + "print(f\"Number of augmented validation samples: {len(X_valid)* 8}\")\n", + "print(f\"Number of augmented testing samples: {len(X_test)* 8}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "S9XaKzTZqpW0" + }, + "source": [ + "Create a DataLoader for each set with batch size of 4 and shuffling enabled" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "-5Eil06jrRUT" + }, + "outputs": [], + "source": [ + "train_gen = DataLoader(train_set, batch_size=4, shuffle=True, pin_memory=True, num_workers=8)\n", + "valid_gen = DataLoader(valid_set, batch_size=4, shuffle=True, pin_memory=True, num_workers=8)\n", + "test_gen = DataLoader(test_set, batch_size=10, shuffle=True, pin_memory=True, num_workers=8)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "VE1aH9bJq1HD" + }, + "source": [ + "Get device to set the training to run on GPU or CPU later based on its availibility" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "ka_a8OAPtNHY" + }, + "outputs": [], + "source": [ + "device_name = \"cuda:0:\" if torch.cuda.is_available() else \"cpu\"\n", + "device = torch.device(device_name)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "eHncA4uwrAnB" + }, + "source": [ + "## Build the Model\n", + "\n", + "* Instantiate the transfer learning model using torchvision's models class.\n", + "\n", + "* RESNET50 is the CNN model that we're going to use by transfer learning.\n", + "\n", + "* Set all the pretrained weights to trainable by enabling every layer's parameters as true\n", + "\n", + "* Build the top layer by creating a custom output sequential layer and assign it to model's fc." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "colab_type": "code", + "id": "Lq3_lCEttAJk", + "outputId": "feb12130-9d1d-4c44-d546-6327f266745a" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ResNet(\n", + " (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n", + " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n", + " (layer1): Sequential(\n", + " (0): Bottleneck(\n", + " (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " (downsample): Sequential(\n", + " (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " )\n", + " )\n", + " (1): Bottleneck(\n", + " (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (2): Bottleneck(\n", + " (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " )\n", + " (layer2): Sequential(\n", + " (0): Bottleneck(\n", + " (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " (downsample): Sequential(\n", + " (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", + " (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " )\n", + " )\n", + " (1): Bottleneck(\n", + " (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (2): Bottleneck(\n", + " (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (3): Bottleneck(\n", + " (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " )\n", + " (layer3): Sequential(\n", + " (0): Bottleneck(\n", + " (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " (downsample): Sequential(\n", + " (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", + " (1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " )\n", + " )\n", + " (1): Bottleneck(\n", + " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (2): Bottleneck(\n", + " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (3): Bottleneck(\n", + " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (4): Bottleneck(\n", + " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (5): Bottleneck(\n", + " (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " )\n", + " (layer4): Sequential(\n", + " (0): Bottleneck(\n", + " (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " (downsample): Sequential(\n", + " (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", + " (1): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " )\n", + " )\n", + " (1): Bottleneck(\n", + " (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " (2): Bottleneck(\n", + " (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", + " (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n", + " (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (relu): ReLU(inplace=True)\n", + " )\n", + " )\n", + " (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n", + " (fc): Sequential(\n", + " (0): Linear(in_features=2048, out_features=2048, bias=True)\n", + " (1): SELU()\n", + " (2): Dropout(p=0.4, inplace=False)\n", + " (3): Linear(in_features=2048, out_features=2048, bias=True)\n", + " (4): SELU()\n", + " (5): Dropout(p=0.4, inplace=False)\n", + " (6): Linear(in_features=2048, out_features=4, bias=True)\n", + " (7): LogSigmoid()\n", + " )\n", + ")" ] - }, - { - "cell_type": "code", - "metadata": { - "id": "WXiYK3JxMC3F", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 125 - }, - "outputId": "f4288b40-ded1-491d-b242-6c052b3dcbc6" - }, - "source": [ - "print(f\"Jaccard Index\\n\\n{round(jaccard_similarity_score(pred.view(-1).cpu(), labels.view(-1).cpu()), 2)}\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Jaccard Index\n", - "\n", - "0.99\n" - ], - "name": "stdout" - }, - { - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.6/dist-packages/sklearn/metrics/_classification.py:664: FutureWarning: jaccard_similarity_score has been deprecated and replaced with jaccard_score. It will be removed in version 0.23. This implementation has surprising behavior for binary and multiclass classification tasks.\n", - " FutureWarning)\n" - ], - "name": "stderr" - } + }, + "execution_count": 17, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "# instantiate transfer learning model\n", + "resnet_model = models.resnet50(pretrained=True)\n", + "\n", + "# set all paramters as trainable\n", + "for param in resnet_model.parameters():\n", + " param.requires_grad = True\n", + "\n", + "# get input of fc layer\n", + "n_inputs = resnet_model.fc.in_features\n", + "\n", + "# redefine fc layer / top layer/ head for our classification problem\n", + "resnet_model.fc = nn.Sequential(nn.Linear(n_inputs, 2048),\n", + " nn.SELU(),\n", + " nn.Dropout(p=0.4),\n", + " nn.Linear(2048, 2048),\n", + " nn.SELU(),\n", + " nn.Dropout(p=0.4),\n", + " nn.Linear(2048, 4),\n", + " nn.LogSigmoid())\n", + "\n", + "# set all paramters of the model as trainable\n", + "for name, child in resnet_model.named_children():\n", + " for name2, params in child.named_parameters():\n", + " params.requires_grad = True\n", + "\n", + "# set model to run on GPU or CPU absed on availibility\n", + "resnet_model.to(device)\n", + "\n", + "# print the trasnfer learning NN model's architecture\n", + "resnet_model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "_QdrEPESsKQi" + }, + "source": [ + "## Set Training Configuration\n", + "\n", + "* Set model's loss function as CrossEntropyLoss\n", + "\n", + "* Set SGD optimizer with 0.9 momentum and learning rate 3e-4 as the model's optimizer. According to many Deep learning experts and researchers such as [Andrej karpathy](https://github.com/karpathy) 3e-4 is a good learning rate choice.\n", + "\n", + "* Run the model for 10 total iterations\n", + "\n", + "* Create empty lists to store training losses, validation losses, training accuracies, and validation accuracies." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "cokrpXp3ud8_" + }, + "outputs": [], + "source": [ + "# loss function\n", + "# if GPU is available set loss function to use GPU\n", + "criterion = nn.CrossEntropyLoss().to(device)\n", + "\n", + "# optimizer\n", + "optimizer = torch.optim.SGD(resnet_model.parameters(), momentum=0.9, lr=3e-4)\n", + "\n", + "# number of training iterations\n", + "epochs = 30\n", + "\n", + "# empty lists to store losses and accuracies\n", + "train_losses = []\n", + "test_losses = []\n", + "train_correct = []\n", + "test_correct = []" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "7E_rg4eJs7Xq" + }, + "source": [ + "## Util function\n", + "\n", + "### Checkpoint Saver\n", + "A function to save the model using checkpoints based on best loss achieved during every iteration compared with previous iteration's loss. We'll load the checkpoint and resume training in case Colab's runtime get's disconnected due to inactivity or any other issues." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "MnUksQpFNxNK" + }, + "outputs": [], + "source": [ + "def save_checkpoint(state, is_best, filename='/content/drive/My Drive/bt_resnet50_ckpt_v2.pth.tar'):\n", + " torch.save(state, filename)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "Ci1Lw3UgtU13" + }, + "source": [ + "## Train the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "colab_type": "code", + "id": "0TWAlOEau6k7", + "outputId": "d1c0a256-466f-4420-c9ad-a1bf15630b97" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1 Batch 2144\n", + "Accuracy: 56.80 % Loss: 0.4113 Duration: 7.17 minutes\n", + "Validation Accuracy 69.44 Validation Loss: 1.3394\n", + "\n", + "Epoch 2 Batch 2144\n", + "Accuracy: 79.44 % Loss: 1.0226 Duration: 7.14 minutes\n", + "Validation Accuracy 87.03 Validation Loss: 1.3007\n", + "\n", + "Epoch 3 Batch 2144\n", + "Accuracy: 85.72 % Loss: 0.0820 Duration: 7.13 minutes\n", + "Validation Accuracy 85.75 Validation Loss: 0.2911\n", + "\n", + "Epoch 4 Batch 2144\n", + "Accuracy: 88.46 % Loss: 0.5032 Duration: 7.13 minutes\n", + "Validation Accuracy 89.91 Validation Loss: 0.0314\n", + "\n", + "Epoch 5 Batch 2144\n", + "Accuracy: 90.29 % Loss: 1.4266 Duration: 7.14 minutes\n", + "Validation Accuracy 92.57 Validation Loss: 0.3442\n", + "\n", + "Epoch 6 Batch 2144\n", + "Accuracy: 91.64 % Loss: 0.0373 Duration: 7.15 minutes\n", + "Validation Accuracy 90.90 Validation Loss: 0.0449\n", + "\n", + "Epoch 7 Batch 2144\n", + "Accuracy: 93.24 % Loss: 0.1904 Duration: 7.14 minutes\n", + "Validation Accuracy 93.64 Validation Loss: 0.0942\n", + "\n", + "Epoch 8 Batch 2144\n", + "Accuracy: 95.27 % Loss: 0.0215 Duration: 7.15 minutes\n", + "Validation Accuracy 95.94 Validation Loss: 0.0230\n", + "\n", + "Epoch 9 Batch 2144\n", + "Accuracy: 96.08 % Loss: 0.0065 Duration: 7.14 minutes\n", + "Validation Accuracy 92.82 Validation Loss: 0.0080\n", + "\n", + "Epoch 10 Batch 2144\n", + "Accuracy: 97.21 % Loss: 0.0347 Duration: 7.14 minutes\n", + "Validation Accuracy 94.19 Validation Loss: 0.1907\n", + "\n", + "Epoch 11 Batch 2144\n", + "Accuracy: 96.34 % Loss: 0.0050 Duration: 7.13 minutes\n", + "Validation Accuracy 96.85 Validation Loss: 0.1216\n", + "\n", + "Epoch 12 Batch 2144\n", + "Accuracy: 97.03 % Loss: 0.1557 Duration: 7.14 minutes\n", + "Validation Accuracy 95.34 Validation Loss: 0.0142\n", + "\n", + "Epoch 13 Batch 2144\n", + "Accuracy: 97.79 % Loss: 0.0323 Duration: 7.13 minutes\n", + "Validation Accuracy 97.18 Validation Loss: 0.0280\n", + "\n", + "Epoch 14 Batch 2144\n", + "Accuracy: 98.07 % Loss: 0.0075 Duration: 7.14 minutes\n", + "Validation Accuracy 96.93 Validation Loss: 0.0036\n", + "\n", + "Epoch 15 Batch 2144\n", + "Accuracy: 98.29 % Loss: 0.0080 Duration: 7.13 minutes\n", + "Validation Accuracy 96.79 Validation Loss: 0.0067\n", + "\n", + "Epoch 16 Batch 2144\n", + "Accuracy: 98.33 % Loss: 0.3111 Duration: 7.13 minutes\n", + "Validation Accuracy 97.89 Validation Loss: 0.2079\n", + "\n", + "Epoch 17 Batch 2144\n", + "Accuracy: 98.46 % Loss: 0.0145 Duration: 7.14 minutes\n", + "Validation Accuracy 97.64 Validation Loss: 0.0079\n", + "\n", + "Epoch 18 Batch 2144\n", + "Accuracy: 99.09 % Loss: 0.0176 Duration: 7.13 minutes\n", + "Validation Accuracy 97.51 Validation Loss: 0.0313\n", + "\n", + "Epoch 19 Batch 2144\n", + "Accuracy: 98.92 % Loss: 0.0012 Duration: 7.14 minutes\n", + "Validation Accuracy 96.24 Validation Loss: 0.0181\n", + "\n", + "Epoch 20 Batch 2144\n", + "Accuracy: 99.42 % Loss: 0.0017 Duration: 7.14 minutes\n", + "Validation Accuracy 97.75 Validation Loss: 0.0065\n", + "\n", + "Epoch 21 Batch 2144\n", + "Accuracy: 99.52 % Loss: 0.0013 Duration: 7.13 minutes\n", + "Validation Accuracy 97.59 Validation Loss: 0.0034\n", + "\n", + "Epoch 22 Batch 2144\n", + "Accuracy: 99.85 % Loss: 0.0014 Duration: 7.14 minutes\n", + "Validation Accuracy 98.27 Validation Loss: 0.0242\n", + "\n", + "Epoch 23 Batch 2144\n", + "Accuracy: 99.39 % Loss: 0.0474 Duration: 7.13 minutes\n", + "Validation Accuracy 97.81 Validation Loss: 0.1353\n", + "\n", + "Epoch 24 Batch 2144\n", + "Accuracy: 99.81 % Loss: 0.2521 Duration: 7.13 minutes\n", + "Validation Accuracy 98.19 Validation Loss: 0.2287\n", + "\n", + "Epoch 25 Batch 2144\n", + "Accuracy: 99.65 % Loss: 0.0075 Duration: 7.13 minutes\n", + "Validation Accuracy 98.71 Validation Loss: 0.0178\n", + "\n", + "Epoch 26 Batch 2144\n", + "Accuracy: 99.85 % Loss: 0.0745 Duration: 7.13 minutes\n", + "Validation Accuracy 98.33 Validation Loss: 0.0034\n", + "\n", + "Epoch 27 Batch 2144\n", + "Accuracy: 100.05 % Loss: 0.0002 Duration: 7.13 minutes\n", + "Validation Accuracy 98.79 Validation Loss: 0.0046\n", + "\n", + "Epoch 28 Batch 2144\n", + "Accuracy: 100.01 % Loss: 0.0014 Duration: 7.13 minutes\n", + "Validation Accuracy 98.66 Validation Loss: 0.1200\n", + "\n", + "Epoch 29 Batch 2144\n", + "Accuracy: 99.86 % Loss: 0.0452 Duration: 7.13 minutes\n", + "Validation Accuracy 97.94 Validation Loss: 0.0074\n", + "\n", + "Epoch 30 Batch 2144\n", + "Accuracy: 100.08 % Loss: 0.0005 Duration: 7.13 minutes\n", + "Validation Accuracy 98.88 Validation Loss: 0.0016\n", + "\n", + "\n", + "Training Duration 231.13 minutes\n", + "GPU memory used : 490444800 kb\n", + "GPU memory cached : 16034824192 kb\n" + ] + } + ], + "source": [ + "# set training start time\n", + "start_time = time.time()\n", + "\n", + "# set best_prec loss value as 2 for checkpoint threshold\n", + "best_prec1 = 2\n", + "\n", + "# empty batch variables\n", + "b = None\n", + "train_b = None\n", + "test_b = None\n", + "\n", + "# start training\n", + "for i in range(epochs):\n", + " # empty training correct and test correct counter as 0 during every iteration\n", + " trn_corr = 0\n", + " tst_corr = 0\n", + " \n", + " # set epoch's starting time\n", + " e_start = time.time()\n", + " \n", + " # train in batches\n", + " for b, (y, X) in enumerate(train_gen):\n", + " # set label as cuda if device is cuda\n", + " X, y = X.to(device), y.to(device)\n", + "\n", + " # forward pass image sample\n", + " y_pred = resnet_model(X.view(-1, 3, 512, 512))\n", + " # calculate loss\n", + " loss = criterion(y_pred.float(), torch.argmax(y.view(32, 4), dim=1).long())\n", + "\n", + " # get argmax of predicted tensor, which is our label\n", + " predicted = torch.argmax(y_pred, dim=1).data\n", + " # if predicted label is correct as true label, calculate the sum for samples\n", + " batch_corr = (predicted == torch.argmax(y.view(32, 4), dim=1)).sum()\n", + " # increment train correct with correcly predicted labels per batch\n", + " trn_corr += batch_corr\n", + " \n", + " # set optimizer gradients to zero\n", + " optimizer.zero_grad()\n", + " # back propagate with loss\n", + " loss.backward()\n", + " # perform optimizer step\n", + " optimizer.step()\n", + "\n", + " # set epoch's end time\n", + " e_end = time.time()\n", + " # print training metrics\n", + " print(f'Epoch {(i+1)} Batch {(b+1)*4}\\nAccuracy: {trn_corr.item()*100/(4*8*b):2.2f} % Loss: {loss.item():2.4f} Duration: {((e_end-e_start)/60):.2f} minutes') # 4 images per batch * 8 augmentations per image * batch length\n", + "\n", + " # some metrics storage for visualization\n", + " train_b = b\n", + " train_losses.append(loss)\n", + " train_correct.append(trn_corr)\n", + "\n", + " X, y = None, None\n", + "\n", + " # validate using validation generator\n", + " # do not perform any gradient updates while validation\n", + " with torch.no_grad():\n", + " for b, (y, X) in enumerate(valid_gen):\n", + " # set label as cuda if device is cuda\n", + " X, y = X.to(device), y.to(device)\n", + "\n", + " # forward pass image\n", + " y_val = resnet_model(X.view(-1, 3, 512, 512))\n", + "\n", + " # get argmax of predicted tensor, which is our label\n", + " predicted = torch.argmax(y_val, dim=1).data\n", + "\n", + " # increment test correct with correcly predicted labels per batch\n", + " tst_corr += (predicted == torch.argmax(y.view(32, 4), dim=1)).sum()\n", + "\n", + " # get loss of validation set\n", + " loss = criterion(y_val.float(), torch.argmax(y.view(32, 4), dim=1).long())\n", + " # print validation metrics\n", + " print(f'Validation Accuracy {tst_corr.item()*100/(4*8*b):2.2f} Validation Loss: {loss.item():2.4f}\\n')\n", + "\n", + " # if current validation loss is less than previous iteration's validatin loss create and save a checkpoint\n", + " is_best = loss < best_prec1\n", + " best_prec1 = min(loss, best_prec1)\n", + " save_checkpoint({\n", + " 'epoch': i + 1,\n", + " 'state_dict': resnet_model.state_dict(),\n", + " 'best_prec1': best_prec1,\n", + " }, is_best)\n", + "\n", + " # some metrics storage for visualization\n", + " test_b = b\n", + " test_losses.append(loss)\n", + " test_correct.append(tst_corr)\n", + "\n", + "# set total training's end time\n", + "end_time = time.time() - start_time \n", + "\n", + "# print training summary\n", + "print(\"\\nTraining Duration {:.2f} minutes\".format(end_time/60))\n", + "print(\"GPU memory used : {} kb\".format(torch.cuda.memory_allocated()))\n", + "print(\"GPU memory cached : {} kb\".format(torch.cuda.memory_cached()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "U5hiXkZkwW-W" + }, + "outputs": [], + "source": [ + "torch.cuda.empty_cache()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "9Rzl2f2kteb8" + }, + "source": [ + "## Save the model\n", + "\n", + "Save the model after the training is completed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "K4yzcJMmwPnY" + }, + "outputs": [], + "source": [ + "torch.save(resnet_model.state_dict(), '/content/drive/My Drive/bt_resnet50_model.pt')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "Q-2R8Yputlvh" + }, + "source": [ + "## Evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "hPEBr7E1tobU" + }, + "source": [ + "Print the validation accuracy of the model calculated using validation set during training " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "colab_type": "code", + "id": "4w0EuNLQvmNV", + "outputId": "685ce068-21f1-4419-c278-555cde282b40" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation accuracy: 98.88%\n" + ] + } + ], + "source": [ + "print(f'Validation accuracy: {test_correct[-1].item()*100/(test_b*8*4):.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "EnauSi8Dt04I" + }, + "source": [ + "Plot the loss graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 294 + }, + "colab_type": "code", + "id": "TttSopdFvtr0", + "outputId": "6bc80323-bb7c-4a70-eae5-544ed8c363fb" + }, + "outputs": [ + { + "data": { + "image/png": "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81mxYgV2u53S0lKWLl2Kw+Hgk08+4Wc/+xlvvfVWs/fk5uayePFiysrKGDp0KHfeeWezOfnr169n69at9OrVi6lTp7J8+XJycnL43ve+x5IlS+jfvz9z5sxps30PPfQQ48aNY8GCBXz22WfcfPPNbNiwgccff5ynn36aqVOnUl5eTmxsLM8++ywXXnghP//5z/F6vVRWVob999ERnbbOQCnVG5gFnE0bwUApdTtwO0Dfvn2tb1wnKqwwhokkGAjRtmuvvRa73Q5ASUkJt9xyCzt37kQpRW1ty0Otl156KS6XC5fLRffu3Tly5AiZmZmNrjn99NPrj2VnZ7N3717cbjcDBgyon78/Z84cnn322aDtW7ZsWX1AOueccygoKKC0tJSpU6dy3333ccMNN3DVVVeRmZnJxIkTufXWW6mtreXKK68kOzu7Q3834erMRWdPAA9orX1tTYXSWj8LPAuQk5NzUk+zkWEi0dW15xu8VRISEur//Mtf/pKzzz6bd955h7179zJjxowW3+Nyuer/bLfbqatr/v9aKNd0xE9/+lMuvfRSFi1axNSpU/noo4+YPn06S5Ys4f3332fu3Lncd9993HzzzRF9bjCdOZsoB5ivlNoLXAPMU0pd2Ynt6RIKKmSYSIj2KCkpoXfv3gC8+OKLEb//0KFD2b17N3v37gXg9ddfb/M906ZN49VXXwWMXES3bt1ISkpi165djB49mgceeICJEyeSm5vLvn37OO2007jtttv47ne/y7p16yL+MwTTaT0DrXX9Wmml1IvAf7TWCzqrPV1FoQQDIdrlJz/5CbfccguPPvool156acTvHxcXx7x587joootISEhg4sS2U51mwnrMmDHEx8fz0ksvAfDEE0+wePFibDYbI0eO5OKLL2b+/Pn8/ve/x+l04na7efnllyP+MwSjrFrcpJT6FzAD6AYcAR4CnABa6782ufZFjGDQ5tTSnJwcfbJublNd62XYLz8EYM7pffnfq0Z3couEMGzfvp3hw4d3djM6XXl5OW63G601d911F4MHD+bee+/t7GY109K/l1Jqrda61Tm2Vs4majvV3nDtXKvacSIxh4hAegZCdEV///vfeemll/B4PIwbN47vfe97nd2kiJGqpV1IYXlAMJBFZ0J0Offee2+X7AlEwqlTjqKyEJb+AWrKOrslrSrwTyt1OWzSMxBC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\n", + "text/plain": [ + "
" ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Qw0YkS8kbs8q", - "colab_type": "text" - }, - "source": [ - "We've got around 99% test accuracy which is really good. " + }, + "metadata": { + "needs_background": "light", + "tags": [] + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(train_losses, label='Training loss')\n", + "plt.plot(test_losses, label='Validation loss')\n", + "plt.title('Loss Metrics')\n", + "plt.ylabel('Loss')\n", + "plt.xlabel('Epochs')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "8L-_dup0uCl5" + }, + "source": [ + "Plot the accuracy graph\n", + "\n", + "* Training set - Total length of training samples divided by 100 for every trained sample\n", + "\n", + " * `int((2144 * 8)/100) = int(171.52) = 171`\n", + "\n", + "* Testing set - Total length of testing samples divided by 100 for every testing sample\n", + "\n", + " * `int((460 * 8)/100) = int(36.8) = 36`\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 312 + }, + "colab_type": "code", + "id": "YKzECNFZvx9u", + "outputId": "7a995797-5511-4b7d-8227-ea9af093cc18" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/pytorch/aten/src/ATen/native/BinaryOps.cpp:81: UserWarning: Integer division of tensors using div or / is deprecated, and in a future release div will perform true division as in Python 3. Use true_divide or floor_divide (// in Python) instead.\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "HOzZqnNq2EyF", - "colab_type": "text" - }, - "source": [ - "## Future Scopes\n", - "\n", - "* The model will be deployed using python flask server as a website, so anyone could an upload MRI image of brain and find out what kinda tumor is present (if any).\n", - "\n", - "* Tumor detection will be added in future to locate where the tumor is present in the given MRI image of the brain.\n", - "\n", - "* Will add more sophisticated regularization techniques to prevent slight overfitting and increase accuracy upto 100 %." + }, + "metadata": { + "needs_background": "light", + "tags": [] + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot([t/171 for t in train_correct], label='Training accuracy')\n", + "plt.plot([t/36 for t in test_correct], label='Validation accuracy')\n", + "plt.title('Accuracy Metrics')\n", + "plt.ylabel('Accuracy')\n", + "plt.xlabel('Epochs')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "nEKbGLWsIt6P" + }, + "source": [ + "Empty out training set and validation set to free up RAM / Cache" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "SsVJuIwych21" + }, + "outputs": [], + "source": [ + "# resnet_model.load_state_dict(torch.load('/content/drive/My Drive/bt_resnet_torch.pt'))\n", + "train_gen = None\n", + "valid_gen = None\n", + "train_set = None\n", + "valid_set = None" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "LfVhP2dmvdoi" + }, + "source": [ + "Set model to evaluation mode\n", + "\n", + "Calculate loss, correctly classified samples, predicted values, labels and store them in a list using test dataloader" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "colab_type": "code", + "id": "WZJ7w9ztv1pb", + "outputId": "e59c6347-59f8-42e8-aff4-c95e2567f495" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test Loss: 0.0096\n" + ] + } + ], + "source": [ + "# set model to evaluation mode\n", + "resnet_model.eval()\n", + "\n", + "# perform no gradient updates\n", + "with torch.no_grad():\n", + " # soem metrics storage for visualization and analysis\n", + " correct = 0\n", + " test_loss = []\n", + " test_corr = []\n", + " labels = []\n", + " pred = []\n", + " # perform test set evaluation batch wise\n", + " for (y, X) in test_gen:\n", + " # set label to use CUDA if available\n", + " X, y = X.to(device), y.to(device)\n", + "\n", + " # append original labels\n", + " labels.append(torch.argmax(y.view(10 * 8, 4), dim=1).data)\n", + "\n", + " # perform forward pass\n", + " y_val = resnet_model(X.view(-1, 3, 512, 512))\n", + "\n", + " # get argmax of predicted values, which is our label\n", + " predicted = torch.argmax(y_val, dim=1).data\n", + " # append predicted label\n", + " pred.append(predicted)\n", + "\n", + " # calculate loss\n", + " loss = criterion(y_val.float(), torch.argmax(y.view(10 * 8, 4), dim=1).long())\n", + "\n", + " # increment correct with correcly predicted labels per batch\n", + " correct += (predicted == torch.argmax(y.view(10 * 8, 4), dim=1)).sum()\n", + "\n", + " # append correct samples labels and losses\n", + " test_corr.append(correct)\n", + " test_loss.append(loss)\n", + " \n", + "print(f\"Test Loss: {test_loss[-1].item():.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "AqDm19ZSvyY2" + }, + "source": [ + "Print the test accuracy\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "colab_type": "code", + "id": "HJm2yjzHbLlP", + "outputId": "7b227ca6-3d4d-44cd-a030-a0e0cc8d744e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test accuracy: 99.32%\n" + ] + } + ], + "source": [ + "print(f'Test accuracy: {test_corr[-1].item()*100/(460*8):.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "T6fxSb68v0uf" + }, + "source": [ + "Convert list of tensors to tensors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "jDRv0IhThKhP" + }, + "outputs": [], + "source": [ + "labels = torch.stack(labels)\n", + "pred = torch.stack(pred)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "Ps2QnU4ov4Sr" + }, + "source": [ + "Define ground-truth labels as a list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "7BsASCMlL2_z" + }, + "outputs": [], + "source": [ + "LABELS = ['Meningioma', 'Glioma', 'Pitutary']" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "wubHPKqov61w" + }, + "source": [ + "Plot the confusion matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 387 + }, + "colab_type": "code", + "id": "3cfoG8Q0gMKZ", + "outputId": "95a96f4c-bf40-4efd-a0f2-31d5606c657a" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAggAAAFzCAYAAABb8fH8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+j8jraAAAgAElEQVR4nO3deZgU1b3/8fdnBlARFRFFBBWMaOIuIm5ZUOOeiMk1LknUGBPihonmRk28uS5JbtSYaBKN/lBQ1LjHBROXoLgmKqIiiMZIcGEQZBNwF5jv74+qkXaaGYZqqrun5/N6nnq66tSpOqdnGvo755w6RxGBmZmZWaG6SlfAzMzMqo8DBDMzMyviAMHMzMyKOEAwMzOzIg4QzMzMrIgDBDMzMyvSqdIVaEm/yy/y85dWZMBJT1W6CmbWToxtvFV5l9E4a4uSvqvqNvx37nXMqmoDBDMzs2rXSGNJ11dzM341183MzMwqxC0IZmZmGS2N0loQqvlLuJrrZmZmVtUaqd3hcg4QzMzMMip1DEI18xgEMzMzK+IWBDMzs4yW1vCKyA4QzMzMMvIYBDMzMyuy1AGCmZmZNVfLLQgepGhmZmZF3IJgZmaWkQcpmpmZWZHanQXBAYKZmVlmtTxI0WMQzMzMMloapW1tIWmUpNmSXmiWPlzSvyRNkXRhQfpPJU2V9LKk/QrS90/Tpko6c0XlugXBzMysul0DXApc25QgaU9gKLB9RHwkaYM0fSvgCGBrYCPgAUlbpJddBuwDNABPSxoTES+2VKgDBDMzs4zKMQYhIh6V1K9Z8gnA+RHxUZpndpo+FLgpTX9V0lRgcHpuakRMA5B0U5q3xQDBXQxmZmYZLUUlbSXYAviCpKckPSJp5zS9DzC9IF9DmtZSeovcgmBmZpZRY4ljFCUNA4YVJI2IiBFtuLQT0APYFdgZuEXSZqXVprgAMzMzq4A0GGhLQNBcA3B7RAQwXlIj0BOYAWxckK9vmkYr6cvlLgYzM7OMKtjFcCewJ0A6CLELMBcYAxwhaTVJ/YEBwHjgaWCApP6SupAMZBzTWgFuQTAzM8uoxC/5NpF0IzAE6CmpATgbGAWMSh99/Bg4Jm1NmCLpFpLBh0uAkyJiaXqfk4H7gXpgVERMaa1cBwhmZmYZNUb+AUJEHNnCqW+3kP9XwK+Wk34PcE9by3WAYGZmllE5WhAqxWMQzMzMrIhbEMzMzDJaWsN/ZztAMDMzy6gcYxAqxQGCmZlZRrU8BsEBgpmZWUZLo3a7GGr3nZmZmVlmbkEwMzPLqLGG/852gGBmZpaRxyCYmZlZEY9BMDMzsw7FLQhmZmYZNbqLwczMzJrzTIpmZmZWpJbHIDhAMDMzy6iWH3Os3XdmZmZmmbkFwczMLKOlXqzJzMzMmvMgRTMzMyvS6EGKZmZm1lwttyDU7jszMzOzzNyCYGZmlpEHKZqZmVmRWp4HwQGCmZlZRrU8k2LtvjMzMzPLzC0IZmZmGXk1R8vVcdvtxOGf25YAXp43h588dB8DN9yIs3YbQuf6el6Y8xanP3QfSyP4TPce/GbP/dl6/Q246KnHufL5CZWuvlVAXV0dlz19PnNnzOfnB59f6epYlRi03w6ceMmx1NXXce/IB7n5gjsrXaWa5y4Gy02vNbvxnW0H8tXbrme/m6+hTnUcPOBz/HavAxg+9q/sd/M1NLyziP/acmsAFnz0Iec8Po4rJzow6Mi+9sMDeeOlGZWuhlWRuro6hl96HD878Fd8b+tT2fOIPdjkc30rXa2at5S6krZqVt216yDq68TqnTpRL7FGp058sHgxi5c28urCtwF4vOE1DthsCwDmffA+k+bMYkljYyWrbBXUs08PdjlwIPeOfLDSVbEqsuXgzXlz6ixmvTqbJYuX8PDN/2D3oYMqXa2a1xgqaatmuXcxSDoI2BpYvSktIs7Lu9z24q333uXKiRP451HD+HDJEh6b/hp//c/LnLnbl9h2/V5MnvMWB262Bb27rVXpqlqVOOHiY7nyjOtZY63VV5zZOoyefXowp2HeJ8dzG+bz2V0GVLBG1t7l2oIg6QrgcGA4IOAbwKat5B8maYKkCe88/mSeVasaa3dZjX36b84Xrr+SXa69gq6dO3PIgM9xyti7+fkee3Ln17/Fu4s/pjGi0lW1KrDLQQNZMGchrzw7rdJVMTPK08UgaZSk2ZJeWM65H0sKST3TY0n6g6SpkiZJGliQ9xhJr6TbMSsqN+8WhN0jYjtJkyLiXEm/Be5tKXNEjABGAPS7/KIO8Y34+b6bMn3RQuZ/+AEA9017hZ027MOdr7zEYXfeBMAX+m5K/3V6VLKaViW23uOz7PbVQQw+YEe6rN6FrmuvwRnXDueCo/9Y6apZhc2dMZ/1+673yXHPvj2YO2NeK1fYqlCmxZquAS4Fri1MlLQxsC/wRkHyAcCAdNsFuBzYRVIP4GxgEBDAM5LGRMTbLRWa9zv7IH19X9JGwGKgd85ltitvvruIHXv1ZvVOSay2R99Nmfr2PNZboysAXerqOX7Hwfz5xYmVrKZViVE/u4FvbnI8R212Er868mImjnvBwYEB8PLTU+kzoDcb9tuATp07MeTwPXhijAcz520pKmlri4h4FJi/nFMXA6eTfOE3GQpcG4knge6SegP7AWMjYn4aFIwF9m+t3LxbEP4qqTvwG+BZkjdxVc5ltisTZ8/i3mn/5m+HHsWSCKbMeYsbX5zEj3f5PHtvuhmS+POUiTwxYzoA66/RlTGHHkW3Ll2ICL673U7sc9PVvLv44wq/EzOrpMaljVw6fCS/vu8s6urruP/qh3j9xYZKV6vmldqCIGkYMKwgaUTamr6i64YCMyLieelTgUYfYHrBcUOa1lJ6y2VEmfq2Ja0GrB4RC9uSv6N0MdjKGXDSU5Wugpm1E2Mbb839MYELXzygpO+q07e6t011lNQP+GtEbCOpK/AQsG9ELJT0GjAoIuZK+itwfkQ8nl73IHAGMITkO/iXafrPgQ8i4qKWysy1BUFSPXAQ0K+pLElExO/yLNfMzKwc2tpNsIp9BugPNLUe9AWelTQYmAFsXJC3b5o2gyRIKEx/uLVC8u5iuBv4EJgM+MF9MzOrKWUapPgpETEZ2KDpuFkLwhjgZEk3kQxSXBgRMyXdD/yfpHXTy/YFftpaOXkHCH0jYrucyzAzM6uIcky1LOlGkr/+e0pqAM6OiJEtZL8HOBCYCrwPHAsQEfMl/QJ4Os13XkQsb+DjJ/IOEO6VtG9E/D3ncszMzGpSRBy5gvP9CvYDOKmFfKOAUW0tN+8A4UngDkl1JI84iqT+a+dcrpmZWe68mmN2vwN2AyZHuR6XMDMzK5NaXs0x7wBhOvCCgwMzM6tF1b7gUinyDhCmAQ9Luhf4qCnRjzmamVktqPYlm0uRd4Dwarp1STczMzNrB3INECLiXABJ3dLjd/Msz8zMrJzcxZCRpG2A64Ae6fFc4OiImJJnuWZmZuXQ6C6GzEYAp0XEQwCShgBXArvnXK6ZmVnulroFIbM1m4IDgIh4WNKaOZdpZmZWFu5iyG5aumLUdenxt0mebDAzM7MqlneA8F3gXOD29PixNM3MzKzdq8RiTeWS91MMbwOn5FmGmZlZpVRoueeyyCVAkHRJRPxI0t1A0SyKEXFwHuWamZmVk8cgrLymMQcX5XR/MzMzy1EuAUJEPJO+PpLH/c3MzKqBxyBkJGkyxV0MC4EJwC8jYl6e5ZuZmeXJyz1ndy+wFLghPT4C6ArMAq4Bvppz+WZmZrnxREnZfTkiBhYcT5b0bEQMlPTtnMs2MzPLVS13MeT9zuolDW46kLQzUJ8eLsm5bDMzM8so7xaE7wGj0tUcBSwCvpdOt/zrnMs2MzPLlR9zzCginga2lbROeryw4PQteZZtZmaWNw9SzEjSasB/Af2ATlLyg4yI8/Is18zMrBzcgpDdXSSPNT4DfJRzWWZmZraK5B0g9I2I/XMuw8zMrCJq+SmGvAOEf0raNiIm51yOmZlZ2bmLIbvPA9+R9CpJF4OAiIjtci7XzMwsdx6kmN0BOd/fzMysYtyCsJIkrR0Ri4B38ri/mZmZ5SuvFoQbgK+QPL0Q8Kk2mAA2y6lcMzOzsqnlFoRchl9GxFfS1/4RsVn62rQ5ODAzs5rQGCppawtJoyTNlvRCQdpvJP1L0iRJd0jqXnDup5KmSnpZ0n4F6funaVMlnbmicnN/PkNSH0m7S/pi05Z3mWZmZuVQjgCBZPXj5lMGjAW2SQf9/xv4KYCkrUhWTt46veZPkuol1QOXkYwN3Ao4Ms3borxnUrwAOBx4kWTZZ0i6GB7Ns1wzM7NyKMdTDBHxqKR+zdL+XnD4JHBouj8UuCkiPgJelTQVaFo0cWpETAOQdFOa98WWys37KYZDgC3TipqZmdmq913g5nS/D0nA0KQhTQOY3ix9l9ZumneAMA3ojKdZNjOzGlTqIEVJw4BhBUkjImLESlx/FrAE+HNJFVmOvAOE94GJkh6kIEiIiFNyLtfMzCx3pQYIaTDQ5oCgkKTvkDwxuHdERJo8A9i4IFvfNI1W0pcr7wBhTLqZmZnVnEo95ihpf+B04EsR8X7BqTHADZJ+B2wEDADGk0w3MEBSf5LA4Ajgm62VkWuAEBGjJa0BbBIRL+dZlpmZWS2SdCMwBOgpqQE4m+SphdWAsZIAnoyI4yNiiqRbSAYfLgFOioil6X1OBu4H6oFRETGltXLzforhq8BFQBegv6QdgPMi4uA8yzUzMyuHcrQgRMSRy0ke2Ur+XwG/Wk76PcA9bS0373kQziF5vGIBQERMxLMomplZjYhQSVs1y3sMwuKIWJg2fzRpzLlMMzOzsvBqjtlNkfRNoF7SAOAU4J85l2lmZlYWXoshu+Ek0z1+BNwILAJ+lHOZZmZmVqK8n2J4Hzgr3czMzGpKtY8jKEUuAYKkVuc+8FMMZmZWC2q5iyGvFoTdSOZ8vhF4Cmp4FIeZmXVYbkFYeRsC+wBHkszU9DfgxhVNylBoi1Mm5FQ1a8/ue/P5SlfBqtB+G21f6SpYB1XLLQi5DFKMiKURcV9EHAPsCkwFHk5ncTIzM7Mql9sgRUmrAQeRtCL0A/4A3JFXeWZmZuX2yRJJNSivQYrXAtuQTOl4bkS8kEc5ZmZmleSJklbet4H3gB8CpxTMpCggImLtnMo1MzMrGw9SXEkRkfcETGZmZpajvKdaNjMzq1m1/BSDAwQzM7OMPEjRzMzMingMgpmZmRWp5QDBgwnNzMysiFsQzMzMMvIgRTMzMyviQYpmZmZWpJbHIDhAMDMzy6iWAwQPUjQzM7MibkEwMzPLqIaHIDhAMDMzy6qWuxgcIJiZmWVVw00IHoNgZmZmRdyCYGZmlpG7GMzMzKyIJ0oyMzOzIrXcguAxCGZmZlmFStvaQNIoSbMlvVCQ1kPSWEmvpK/rpumS9AdJUyVNkjSw4Jpj0vyvSDpmReU6QDAzM6tu1wD7N0s7E3gwIgYAD6bHAAcAA9JtGHA5JAEFcDawCzAYOLspqGiJAwQzM7OMIkrb2lZGPArMb5Y8FBid7o8GDilIvzYSTwLdJfUG9gPGRsT8iHgbGEtx0PEpDhDMzMyyitI2ScMkTSjYhrWx5F4RMTPdnwX0Svf7ANML8jWkaS2lt8iDFM3MzDIqdZBiRIwARpR4j5C0yp+ncAuCmZlZViW2IJTgrbTrgPR1dpo+A9i4IF/fNK2l9BY5QDAzM2t/xgBNTyIcA9xVkH50+jTDrsDCtCvifmBfSeumgxP3TdNa5C4GMzOzjMoxD4KkG4EhQE9JDSRPI5wP3CLpOOB14LA0+z3AgcBU4H3g2KSeMV/SL4Cn03znRUTzgY+f4gDBzMwsqzLMpBgRR7Zwau/l5A3gpBbuMwoY1dZyHSCYmZll5pkUzczMrANxC4KZmVlWXqzJzMzMijhAMDMzsyI1vJqjAwQzM7OM2rqeQnvkQYpmZmZWxC0IZmZmWdVwC4IDBDMzs6xqeAzCCrsYJF3QljQzM7OORlHaVs3aMgZhn+WkHbCqK2JmZtbuVG41x9y12MUg6QTgRGAzSZMKTq0F/CPvipmZmVnltDYG4QbgXuDXwJkF6e+saAUoMzOzDqEjjkGIiIUR8Vq6itTGwF4R8TpQJ6l/2WpoZmZWrTpiF0MTSWcDg4AtgauBLsD1wB75Vs3MzKzKVfmXfCnaMkjxa8DBwHsAEfEmyTgEMzMzq1FtmQfh44gIKXkgQ9KaOdfJzMysfejgLQi3SPp/QHdJ3wceAK7Mt1pmZmbtQKi0rYqtsAUhIi6StA+wiGQcwv9GxNjca9ZBnXblD9j1wIEsmL2IYTv+BICjfn4oBxy3FwvnLgJg1P/cxNP3TaxkNS0nZ50PDz8BPdaFu69J0k49B16bnuwvehfW7gZ3jEyOR1wPf7kH6urgrFPg84OT9Meegv/7IzQ2wqEHwfe/Ve53YpUwaL8dOPGSY6mrr+PekQ9y8wV3VrpKNa/aJzsqRZumWk4DAgcFZTB29COM+dP9nD7qpE+l3/77e7jt4r9WqFZWLoccAN/8Opz5f8vSLj5n2f4Fl0G3tJNv6mtwz7gkkJg9D757Gtx7fXLuF5fAyN9Cr/XhsB/AnnvA5v3K8x6sMurq6hh+6XGcse8vmNswn0vH/5onxkzgjZcaKl212lbDAUJbplp+R9KiZtt0SXdI2qwclexIJj/+L96Z/16lq2EVsvP20L2FIcARcN9DcNCXk+Nxj8OBe0GXLtC3N2zSBya9lGyb9IGNN4IunZM84x4v33uwythy8Oa8OXUWs16dzZLFS3j45n+w+9BBla6WtWNtGYNwCfAToA/QF/hvkkmUbgJG5Vc1K3TwiftxxbMXcNqVP6Bbd48T7YgmTIL1ekC/vsnxW3Nhww2Wne+1Psyem2zN09+aW966Wvn17NODOQ3zPjme2zCfnn3Wq2CNrL1rS4BwcET8v4h4JyIWRcQIYL+IuBlYd0UXS9pG0mGSjm7aSq51B3P3/xvLd7Y8hRN2OpP5Mxcw7DffrnSVrAL+9gActHela2FmhTr6Yk3vp1/wdel2GPBheq7Vt5dOsvTHdNsTuJBkToWW8g+TNEHShIbG/7TtHXQAC2YvpLExiAjuHTmOzw7avNJVsjJbsgQeeAwO2HNZWq+eMGv2suO35sAGPZOteXqvnuWrq1XG3BnzWb/vshaDnn17MHfGvFausFWihp9iaEuA8C3gKGA28Fa6/21JawAnr+DaQ4G9gVkRcSywPbBOS5kjYkREDIqIQX3rPtOW+ncIPTbs/sn+HofszGtTplewNlYJTzwD/Tf5dNfBnnskgxQ//hgaZsLrDbDd52Dbzyb7DTPh48VJnj0972nNe/npqfQZ0JsN+21Ap86dGHL4HjwxZkKlq1X7OupUy5LqgRMj4qstZFnR0KcPIqJR0hJJa5MEGRtnqGeH8dPrhrPdl7ZinZ5r8edXL+O6825juy9txWe235SI4K3X5vD7E6+qdDUtJz8+F8ZPhAULYcihcPKxyWOK94wr7l4Y0B/23xO+cgzU18PPf5S8AvzPj+B7/5085vj1A5O8VtsalzZy6fCR/Pq+s6irr+P+qx/i9Rf9BINlp4jWQxhJT0bErpluLv0J+BlwBPBj4F1gYtqa0Kp9Ox9R5bGVVcJ905+tdBWsCu230faVroJVobGNt+behr/Zxb8r6btq2qmnVW0/Q1vmQXhO0hjgVtL1GAAi4vYVXRgRJ6a7V0i6D1g7IiZlqqmZmVmVqfaBhqVoS4CwOjAP2KsgLYAVBggAkrYD+jWVJWnztgQXZmZmVa8jBwht6Q5oiaRRwHbAFKCx6Za0MbgwMzPr6CSdCnyP5PtzMnAs0JtkPqL1gGeAoyLiY0mrAdcCO5H8cX94RLyWpdwVBgiSVgeOA7YmaU0AICK+24b77xoRW2WpmJmZWdXLuQVBUh/gFGCriPhA0i0k4/oOBC6OiJskXUHyPX15+vp2RGwu6QjgAuDwLGW35THH64ANgf2AR0hmU3ynjfd/QpIDBDMzq0llmiipE7CGpE5AV2AmSbf/ben50cAh6f7Q9Jj0/N6SMg2EbDFASCsCsHlE/Bx4LyJGAwcBu7Tx/teSBAkvS5okabIkD1I0M7PaUOJESYUTBKbbsE/dPmIGcBHwBklgsJCkS2FBRCxJszWQLIdA+jo9vXZJmj/TnNutdTGMBwYCi9PjBZK2AWYBG7R41aeNJJlYaTLLxiCYmZnVhhK7GNLlC0a0dF7SuiStAv2BBSRPFO5fWqlt05anGEakFfwfYAzQDfh5G+8/JyLGZK2cmZlZB/dl4NWImAMg6XZgD6C7pE5pK0FfYEaafwbJhIQNaU/AOiSDFVdaawHCBpJOS/ebnmS4LH1t63KCz0m6Abgb+Kgp0Y85mplZLSjDPAhvALtK6gp8QLJ8wQTgIZLlDG4CjgHuSvOPSY+fSM+PixXNiNiC1gKEepLWguUNbmhrYWuQBAb7NrvWAYKZmbV/OQcIEfGUpNuAZ4ElwHMkXRJ/A26S9Ms0bWR6yUjgOklTgfkkTzxk0lqAMDMizst6YyhtDgUzM7NqV46ZFCPibODsZsnTgMHLyfsh8I1VUW5rjzmWPD+0pL6S7pA0O93+Iqlvqfc1MzOzfLUWIOzdyrm2upqkP2SjdLs7TTMzM2v/ani55xYDhIiYvwruv35EXB0RS9LtGmD9VXBfMzOzyuuIAcIqMk/StyXVp9u3yfi4hZmZWbUp00yKFZF3gPBd4DCSyZVmkjxy4YGLZmZmVa4tEyVlFhGvAwfnWYaZmZmterkECJJOj4gLJf2R5fSyRMQpeZRrZmZWVlXeTVCKvFoQXkpfJ+R0fzMzs4qr9nEEpcglQIiIu9PX0SvKa2Zm1m45QFg5ku6mlR9bRHhcgpmZtX8OEFbaRctJa/oxljxDo5mZmeUrrwChO9A3Ii4DkDSeZIKkAM7IqUwzM7OyquUxCHnNg3A6yRTLTboAg4AhwPE5lWlmZlZeNTyTYl4tCF0iYnrB8eMRMY9kZsU1cyrTzMysrNyCsPLWLTyIiJMLDr0Wg5mZWZXLK0B4StL3mydK+gEwPqcyzczMystdDCvtVOBOSd8Enk3TdgJWAw7JqUwzM7PyqvIv+VLkNVHSbGB3SXsBW6fJf4uIcXmUZ2ZmVgm1PAYh78WaxgEOCszMrDbVcICQ93LPZmZm1g7l2oJgZmZW02q4BcEBgpmZWUYeg2BmZmbFHCCYmZlZc7XcguBBimZmZlbELQhmZmZZ1XALggMEMzOzrBwgmJmZWXOqdAVy5DEIZmZmVsQBgpmZWVZlWM1RUndJt0n6l6SXJO0mqYeksZJeSV/XTfNK0h8kTZU0SdLArG/NAYKZmVlGitK2Nvo9cF9EfBbYHngJOBN4MCIGAA+mxwAHAAPSbRhwedb35gDBzMwsq5xbECStA3wRGAkQER9HxAJgKDA6zTYaOCTdHwpcG4knge6Semd5aw4QzMzMssq/i6E/MAe4WtJzkq6StCbQKyJmpnlmAb3S/T7A9ILrG9K0leYAwczMrEIkDZM0oWAb1ixLJ2AgcHlE7Ai8x7LuBAAiYiVGNLSdH3M0MzPLqNSpliNiBDCilSwNQENEPJUe30YSILwlqXdEzEy7EGan52cAGxdc3zdNW2luQTAzM8sq5y6GiJgFTJe0ZZq0N/AiMAY4Jk07Brgr3R8DHJ0+zbArsLCgK2KluAXBzMwsozIt1jQc+LOkLsA04FiSP/BvkXQc8DpwWJr3HuBAYCrwfpo3EwcIZmZmWZUhQIiIicCg5Zzaezl5AzhpVZTrLgYzMzMrUrUtCLF0aaWrYFVov422r3QVrAo1/GWbSlfBOqgydTFURNUGCGZmZlXPAYKZmZkVqeEAwWMQzMzMrIhbEMzMzDLyGAQzMzMr5gDBzMzMmlPUboTgAMHMzCyr2o0PPEjRzMzMirkFwczMLCMPUjQzM7NiDhDMzMysObcgmJmZWbEaDhA8SNHMzMyKuAXBzMwsI3cxmJmZWTEHCGZmZtZcLbcgeAyCmZmZFXELgpmZWVZei8HMzMyaq+UuBgcIZmZmWTlAMDMzs+bUWOka5MeDFM3MzKyIWxDMzMyycheDmZmZNedBimZmZlbMjzmamZlZc7XcguBBimZmZlbELQhmZmZZuQXBzMzMmlOUtrW5HKle0nOS/poe95f0lKSpkm6W1CVNXy09npqe75f1vTlAMDMzyyqitK3tfgi8VHB8AXBxRGwOvA0cl6YfB7ydpl+c5svEAYKZmVkVk9QXOAi4Kj0WsBdwW5plNHBIuj80PSY9v3eaf6U5QDAzM8uo1C4GScMkTSjYhi2nmEuA04GmiZ3XAxZExJL0uAHok+73AaYDpOcXpvlXmgcpmpmZZVXiIMWIGAGMaOm8pK8AsyPiGUlDSitt5ThAMDMzy6gM8yDsARws6UBgdWBt4PdAd0md0laCvsCMNP8MYGOgQVInYB1gXpaC3cVgZmaWVWOUtq1ARPw0IvpGRD/gCGBcRHwLeAg4NM12DHBXuj8mPSY9Py4i23SPDhDMzMzanzOA0yRNJRljMDJNHwmsl6afBpyZtQB3MZiZmWVVxomSIuJh4OF0fxoweDl5PgS+sSrKc4BgZmaWUS2vxeAAwczMLCuv5mhmZmbN1XILggcpmpmZWRG3IJiZmWVVwy0IDhDMzMwykscgmJmZWZHGFWdprzwGwczMzIq4BcHMzCwjdzGYmZlZsdqNDxwgmJmZZeYWBDMzM2uulidKcoBQ5dZcpyunXXkC/bbZGCK46LjLeenJf1e6WlZhg/bbgRMvOZa6+jruHfkgN19wZ6WrZDn65cCDGbLhFsz/6D0OfvByANbpvDq/G3wofbp2Z8b7Czh1/G0sWvwhADv33JSfbrs/nevqePvj9zn6sdGf3KsOceue32f2h+9wwhM3VuT9WPvgAKHKnXjJsUy4/zl+cdhv6dS5E6t17VLpKlmF1dXVMfzS4zhj318wt2E+l47/NU+MmcAbLzVUumqWkztfn8gN/xnP+YO+9kna97f4PE/MeZWr/v0PvrfFHnx/i8/z2ykPsFbn1fjf7Q9i2D+vZ+YHi+jRpeun7sBHqKgAABBdSURBVHXU5rsw7Z25dOu8WrnfRm2q4S4GP+ZYxbqu3ZVtv7gV944cB8CSxUt4b+H7Fa6VVdqWgzfnzamzmPXqbJYsXsLDN/+D3YcOqnS1LEcT5r3BgsUffCptr95bctfrzwNw1+vPs3fvLQH4St9teeDNl5j5wSIA5n+87P+MXquvxZd6DeC2154tU81rnxpL26pZrgGCpOGS1s2zjFrWu/8GLJyziJ+MOonLn7mQ0648ntW7Ourv6Hr26cGchnmfHM9tmE/PPutVsEZWCeut1o05H70LwJyP3mW91boB0K/beqzdZQ1Gf/4YbhvyfYZuvN0n1/x0u/25aMoDNNby0Ptyiyhtq2J5tyD0Ap6WdIuk/SWptcyShkmaIGlCQ0zLuWrVr75THQMG9ufuK+7nhJ1O58P3PuLwMw+pdLXMrApF+qVfrzq27t6b45+4ge/983pO+OwX6detB0M2HMD8j97jxQUzK1xTay9yDRAi4n+AAcBI4DvAK5L+T9JnWsg/IiIGRcSgvtosz6q1C3Ma5jOnYR7/Gj8VgEdve4IBO/rn0tHNnTGf9fsuazHo2bcHc2fMa+UKq0XzPnqX9dNWg/VX68b8j94DYNaHi3j8rf/wwdLFLPj4AybMfYMt196QHXtswp69t+SBfX/Ib3c+lF169ueCnb7WWhHWFlHiVsVyH4MQEQHMSrclwLrAbZIuzLvs9u7ttxYwZ/o8+m6xEQA77r0tr3sgWof38tNT6TOgNxv224BOnTsx5PA9eGLMhEpXy8ps3Kx/M3TT7QEYuun2jJv5cpI+82UGrrcx9RKr13diux59mPbOHC5+8UH2vO9ivvz33/Pjp2/jqbmvcsYzd1TyLdQERZS0VbNcn2KQ9EPgaGAucBXwk4hYLKkOeAU4Pc/ya8Flp4zip9efQqcunZg57S0u+u6fKl0lq7DGpY1cOnwkv77vLOrq67j/6od4/UUHjrXsokFfZ/D6/ejepSsP7X8ql770MFf9+3F+t/OhHLrpjrz5/kJOHX8rANPemcvjs//DnXudQBDc9tqzvPLOnAq/gxpW5V/ypVDk+OYknQNcHRGvL+fc5yLipZau3afuG7X7UzezVarhL9tUugpWhV762tmtjntbFfYdfF5J31V/H/+/udcxq9y6GCTVA0csLzgAaC04MDMzs8rKrYshIpZKelnSJhHxRl7lmJmZVUq1jyMoRd4zKa4LTJE0HnivKTEiDs65XDMzs/w5QMjs5znf38zMrHIcIGQTEY/keX8zM7OKqvLpkkuR91TLu0p6WtK7kj6WtFTSojzLNDMzs9Ll3cVwKXAEcCswiGROhC1yLtPMzKwsanmQYjlmUpwK1EfE0oi4Gtg/7zLNzMzKwos1Zfa+pC7AREkXSjq1DGWamZmVR84BgqSNJT0k6UVJU9IZipHUQ9JYSa+kr+um6ZL0B0lTJU2SNDDrW8v7y/qotIyTSR5z3Bj4es5lmpmZ1YolwI8jYitgV+AkSVsBZwIPRsQA4MH0GOAAkkUSBwDDgMuzFpx3gHBIRHwYEYsi4tyIOA34Ss5lmpmZlUfOLQgRMTMink333wFeAvoAQ4HRabbRwCHp/lDg2kg8CXSX1DvLW8s7QDhmOWnfyblMMzOz8mgsbZM0TNKEgm1YS0VJ6gfsCDwF9IqImempWUCvdL8PML3gsoY0baXl8hSDpCOBbwL9JY0pOLUWMD+PMs3MzMqt1KcYImIEMGKF5UjdgL8AP4qIRdKyNZ4iIiSt8hGPeT3m+E9gJtAT+G1B+jvApJzKNDMzK68yPIkgqTNJcPDniLg9TX5LUu+ImJl2IcxO02eQjPdr0jdNW2m5BAjpCo6vA7vlcX8zM7OOQElTwUjgpYj4XcGpMSTd+Oenr3cVpJ8s6SZgF2BhQVfESsl1oiRJ7wBN4VUXoDPwXkSsnWe5ZmZmZdGYewvCHiRPBE6WNDFN+xlJYHCLpONI/iA/LD13D3AgMBV4Hzg2a8F5r8WwVtN+GgUNJXlMw8zMrP3LuYshIh4H1MLpvZeTP4CTVkXZZZu0KH3k4k5gv3KVaWZmlqsankkx7y6GwkmR6kjWY/gwzzLNzMzKpsq/5EuR92JNXy3YXwK8RtLNYGZmZlUs7wDhqoj4R2GCpD1Y9jiGmZlZ+5X/IMWKyXsMwh/bmGZmZtb+RGNpWxXLaybF3YDdgfUlnVZwam2gPo8yzczMys5jEFZaF6Bbev+1CtIXAYfmVKaZmZmtInnNpPgI8Iika9JZFc3MzGpPDY9ByKuL4ZKI+BFw6fIWkIiIg/Mo18zMrKzcxbDSrktfL8rp/mZmZpXnAGGlTZH0I2BzYDIwMiKW5FSWmZlZZdRwgJDXY46jSWZNnAwcwKeXfDYzM7Mql1cLwlYRsS2ApJHA+JzKMTMzq5zG6p7LoBR5BQiLm3YiYkmykKOZmVmNqeEuhrwChO0lLUr3BayRHotkYce1cyrXzMysfBwgrJyI8GyJZmZW+2p4HoS812IwMzOzdijv1RzNzMxqVlT5gkulcIBgZmaWVQ13MThAMDMzy6qGByl6DIKZmZkVcQuCmZlZVp4oyczMzIrUcBeDAwQzM7OMwi0IZmZmVqSGWxA8SNHMzMyKuAXBzMwsK8+DYGZmZkU8k6KZmZk1FzXcguAxCGZmZllFY2lbG0jaX9LLkqZKOjPnd/QJBwhmZmZVSlI9cBlwALAVcKSkrcpRtrsYzMzMMipDF8NgYGpETAOQdBMwFHgx74IdIJiZmWWV/yDFPsD0guMGYJe8C4UqDhDGNt6qStehWkgaFhEjKl0Pqy7+XNjy+HNRXqV+V0kaBgwrSBpRLb8/j0FoH4atOIt1QP5c2PL4c9GORMSIiBhUsDUPDmYAGxcc903TcucAwczMrHo9DQyQ1F9SF+AIYEw5Cq7aLgYzM7OOLiKWSDoZuB+oB0ZFxJRylO0AoX2oiv4oqzr+XNjy+HNRYyLiHuCecperqOGVqMzMzCwbj0EwMzOzIg4QVoKkkHR9wXEnSXMk/bWEe94jqXvGawdJ+kPWsq2yJPWSdIOkaZKekfSEpK9JGtL0mZJ0cDmnVrXKkbRU0kRJL0i6VVLXwn/j6edi9zbcp5+kb+ZfY6t1DhBWznvANpLWSI/3ocTHTSLiwIhYkPHaCRFxSinlW2VIEnAn8GhEbBYRO5GMTu5bmC8ixkTE+ZWoo5XdBxGxQ0RsA3wMHN/s3/gQYIUBAtAPWKkAQZLHo1kRBwgr7x7goHT/SODGphOS1pQ0StJ4Sc9JGpqmf0fS7ZLuk/SKpAsLrnlNUs806n9J0pWSpkj6e1MgImlnSZPSvy5+I+mFNL3wL80eku5M8z0pabs0/RxJoyU9Jul1SV+XdKGkyWl9Oqf5/lfS0+lfLyPSLzDLz17AxxFxRVNCRLweEX8szJR+di5N9/tJGpf+jh+UtEmafo2ky9Pf+7T0czEq/TxdU3CvyyVNSD9f55bnbVpGjwGbN/0bl9QPOB44Nf1/4Avp7/3QpgskvZvung98Ic13avq5eUzSs+m2e5p/SJo+BnhR0nmSflRwv19J+mG53rBVHwcIK+8m4AhJqwPbAU8VnDsLGBcRg4E9gd9IWjM9twNwOLAtcLikwokvmgwALouIrYEFwH+l6VcDP4iIHYClLdTrXOC5iNgO+BlwbcG5z5B8IR0MXA88FBHbAh+wLNi5NCJ2Tv96WQP4yop/FFaCrYFnV/KaPwKj09/xn4HC7qV1gd2AU0mekb44LWNbSTukec6KiEEkn9svNQWRVl3Sv+YPACY3pUXEa8AVwMVpK8NjrdziTOCxNN/FwGxgn4gYSPJ/UOHnZiDww4jYAhgFHJ3WoY6kRet6rMNygLCSImISSRPekRQ/drIvcKakicDDwOrAJum5ByNiYUR8SLLIxqbLuf2rETEx3X8G6JeOT1grIp5I029ooWqfB65L6zgOWE/S2um5eyNiMcl/OPXAfWn65PS9AOwp6SlJk0mCia1b/CHYKifpMknPS3q6lWy7sez3fx3J77zJ3ZE8kjQZeCsiJkdEIzCFZb/jwyQ9CzxH8vsty4pw1mZrpP93TADeAEauovt2Bq5M/23fyqd/7+Mj4lX4JAiZJ2lHkv/LnouIeauoDtYOud8pmzHARSR9gusVpAv4r4h4uTCzpF2AjwqSlrL8n33zPGssJ08WHwFERKOkxbHs2dZGoFPaGvInYFBETJd0DklwY/mZwrIWIiLiJEk9Sb4csmj67DTy6c9R0++4P/DfwM4R8Xba9eDfcXX5IG0l/MQKevqWkP6Rl/7F36WFfKcCbwHbp/k/LDj3XrO8VwHfATYkaVGwDswtCNmMAs6NiMnN0u8Hhjf136eReEnSAYzvpEEGJM1+y/MY8K203CHA3IhY1MZimr4o5krqBhzaWmZbJcYBq0s6oSCt6wqu+SfLfv/fIvmdt9XaJF8GCyX1ImnCtvblHWCtguPXgJ3S/YNJWgqWl28dYGbaonQUSStiS+4A9gd2Jvn/zDowtyBkEBENfLofr8kvgEuASWlE/yqrpi//OJImwkbgEWDhcvKcA4ySNAl4HzimrTePiAWSrgReAGaRzP1tOYqIkHQIcLGk04E5JF/gZ7Ry2XDgakk/SfMfuxLlPS/pOeBfJEvH/iNz5a1S7gZuUzL4eThwJXCXpOdJug2bWgMmAUvT9GtIWgf/IunoZvmKRMTHkh4CFkRES+OdrIPwTIrtgKRuEfFuun8m0DsiPLrYzFap9A+bZ4FvRMQrla6PVZa7GNqHg9JHll4AvgD8stIVMrPaImkrYCrJgGoHB+YWBDMzMyvmFgQzMzMr4gDBzMzMijhAMDMzsyIOEMxyouWszlfCvT6Zd1/SVemAspbyfmrVP0nHp4+4mZm1mQMEs/wUrc5XeFIZV9CLiO9FxIutZBlCwap/EXFFRFzbcnYzs2IOEMzKo3B1vsIV9OqVrND5dLpK4w8gWQ5a0qWSXpb0ALBB040kPSxpULq/f7pC3/PpCo/9KF717xxJ/53m30HJqo+TJN0had2Ce16gZCXSf0v6Qll/OmZWdTyTolnOClbna1okayCwTUS8KmkYsDAidpa0GvAPSX8HdgS2JFlYpxfJAl+jmt13fZLZ9L6Y3qtHRMyXdAXwbkRclObbu+Cya4HhEfGIpPOAs4GmJX47RcRgSQem6V9e1T8LM2s/HCCY5adpdT5IWhBGkjT9f7KCHsmqeds1jS8gmTd/APBF4MZ0uts3JY1bzv13BR4tWI1vfmuVkbQO0D0iHkmTRpOs7tfk9vT1GZatAGlmHZQDBLP8tLQ6X+Fc+CL5i/7+ZvkOzL96RZpWgWxptVEz60A8BsGssu4HTpDUGUDSFpLWBB4FDk/HKPQG9lzOtU8CX0yXckZSjzS9+Wp+AETEQuDtgvEFR5Es/mVmVsR/JZhV1lUkzfnPpsuEzwEOIVl2dy+SsQdvAE80vzAi5qRjGG5PF9mZDexD8ap/hY4BrkgfuZzGSqwIaWYdi9diMDMzsyLuYjAzM7MiDhDMzMysiAMEMzMzK+IAwczMzIo4QDAzM7MiDhDMzMysiAMEMzMzK+IAwczMzIr8f6PoS6euiqP3AAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" ] + }, + "metadata": { + "needs_background": "light", + "tags": [] + }, + "output_type": "display_data" + } + ], + "source": [ + "arr = confusion_matrix(pred.view(-1).cpu(), labels.view(-1).cpu())\n", + "df_cm = pd.DataFrame(arr, LABELS, LABELS)\n", + "plt.figure(figsize = (9,6))\n", + "sns.heatmap(df_cm, annot=True, fmt=\"d\", cmap='viridis')\n", + "plt.xlabel(\"Prediction\")\n", + "plt.ylabel(\"Target\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "kHhrPo6Tv9Xc" + }, + "source": [ + "Print the classification report" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 230 + }, + "colab_type": "code", + "id": "MNWrpWVXL_pQ", + "outputId": "4dd25c3d-45e9-4c8c-cf56-11c58a41cecd" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Clasification Report\n", + "\n", + " precision recall f1-score support\n", + "\n", + " 1 0.98 1.00 0.99 895\n", + " 2 1.00 0.99 0.99 1715\n", + " 3 1.00 0.99 1.00 1070\n", + "\n", + " accuracy 0.99 3680\n", + " macro avg 0.99 0.99 0.99 3680\n", + "weighted avg 0.99 0.99 0.99 3680\n", + "\n" + ] } - ] -} \ No newline at end of file + ], + "source": [ + "print(f\"Clasification Report\\n\\n{classification_report(pred.view(-1).cpu(), labels.view(-1).cpu())}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "N5xUvujSwBQc" + }, + "source": [ + "Print the Jaccard Similarity score / Index (Accuracy)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 125 + }, + "colab_type": "code", + "id": "WXiYK3JxMC3F", + "outputId": "f4288b40-ded1-491d-b242-6c052b3dcbc6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Jaccard Index\n", + "\n", + "0.99\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.6/dist-packages/sklearn/metrics/_classification.py:664: FutureWarning: jaccard_similarity_score has been deprecated and replaced with jaccard_score. It will be removed in version 0.23. This implementation has surprising behavior for binary and multiclass classification tasks.\n", + " FutureWarning)\n" + ] + } + ], + "source": [ + "print(f\"Jaccard Index\\n\\n{round(jaccard_similarity_score(pred.view(-1).cpu(), labels.view(-1).cpu()), 2)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "Qw0YkS8kbs8q" + }, + "source": [ + "We've got around 99% test accuracy which is really good. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "HOzZqnNq2EyF" + }, + "source": [ + "## Future Scopes\n", + "\n", + "* The model will be deployed using python flask server as a website, so anyone could an upload MRI image of brain and find out what kinda tumor is present (if any).\n", + "\n", + "* Tumor detection will be added in future to locate where the tumor is present in the given MRI image of the brain.\n", + "\n", + "* Will add more sophisticated regularization techniques to prevent slight overfitting and increase accuracy upto 100 %." + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "name": "brain_tumor_classifier_resnet_torch.ipynb", + "provenance": [] + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +}