{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\nfrom plotly.subplots import make_subplots\nimport plotly.graph_objs as go\nimport copy\nimport os\nimport torch\nfrom PIL import Image\nfrom PIL import Image, ImageDraw\nfrom torch.utils.data import Dataset\nimport torchvision.transforms as transforms\nfrom torch.utils.data import random_split\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nimport torch.nn as nn\nfrom torchvision import utils\n%matplotlib inline","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":false,"execution":{"iopub.status.busy":"2022-03-04T20:46:27.650558Z","iopub.execute_input":"2022-03-04T20:46:27.650922Z","iopub.status.idle":"2022-03-04T20:46:29.329125Z","shell.execute_reply.started":"2022-03-04T20:46:27.650852Z","shell.execute_reply":"2022-03-04T20:46:29.328349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# --- Notebook Theme (codes from @vivek468 & @sonalisingh1411) ---\nfrom IPython.core.display import display, HTML, Javascript\n\ncolor_map = ['#176BA0', '#19AADE']\n\nprompt = color_map[-1]\nmain_color = color_map[0]\nstrong_main_color = color_map[1]\ncustom_colors = [strong_main_color, main_color]\n\ncss_file = '''\ndiv #notebook {\nbackground-color: white;\nline-height: 20px;\n}\n\n#notebook-container {\n%s\nmargin-top: 2em;\npadding-top: 2em;\nborder-top: 4px solid %s;\n-webkit-box-shadow: 0px 0px 8px 2px rgba(224, 212, 226, 0.5);\n    box-shadow: 0px 0px 8px 2px rgba(224, 212, 226, 0.5);\n}\n\ndiv .input {\nmargin-bottom: 1em;\n}\n\n.rendered_html h1, .rendered_html h2, .rendered_html h3, .rendered_html h4, .rendered_html h5, .rendered_html h6 {\ncolor: %s;\nfont-weight: 600;\n}\n\ndiv.input_area {\nborder: none;\n    background-color: %s;\n    border-top: 2px solid %s;\n}\n\ndiv.input_prompt {\ncolor: %s;\n}\n\ndiv.output_prompt {\ncolor: %s; \n}\n\ndiv.cell.selected:before, div.cell.selected.jupyter-soft-selected:before {\nbackground: %s;\n}\n\ndiv.cell.selected, div.cell.selected.jupyter-soft-selected {\n    border-color: %s;\n}\n\n.edit_mode div.cell.selected:before {\nbackground: %s;\n}\n\n.edit_mode div.cell.selected {\nborder-color: %s;\n\n}\n'''\n\ndef to_rgb(h): \n    return tuple(int(h[i:i+2], 16) for i in [0, 2, 4])\n\nmain_color_rgba = 'rgba(%s, %s, %s, 0.1)' % (to_rgb(main_color[1:]))\nopen('notebook.css', 'w').write(css_file % ('width: 95%;', main_color, main_color, main_color_rgba, main_color,  main_color, prompt, main_color, main_color, main_color, main_color))\n\ndef nb(): \n    return HTML(\"<style>\" + open(\"notebook.css\", \"r\").read() + \"</style>\")\nnb()","metadata":{"_kg_hide-input":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"![](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/8cc1eeaa-4046-4c4a-ae93-93d656f68688/df00ola-99e02df1-49d7-4adc-b752-5df91e7bf39a.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzhjYzFlZWFhLTQwNDYtNGM0YS1hZTkzLTkzZDY1NmY2ODY4OFwvZGYwMG9sYS05OWUwMmRmMS00OWQ3LTRhZGMtYjc1Mi01ZGY5MWU3YmYzOWEuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.dy3Alobc9fOX3Xp1jt4Mm2gGchyTXOv4jn8NXN3eqn8)\n\n# <b>1 <span style='color:#5D2ECC'>|</span> INTRODUCTION</b>\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.1 | THE NEED</b></p>\n</div>\n\n- Microscopic evaluation of histopathalogic stained tissue & its subsequent digitalisation is now a more feasible due to the advances in slide scanning technology, as well a reduction in digital storage cost in recent years.\n- There are certain advantages that come with such digitalised pathology; including remote diagnosis, instant archival access & simplified procedure of consultations with expert pathologists.\n- Digitalised Analysis based on Deep Learning has shown potential benefits as a potential diagnosis tool & strategy. \n- [Gulshan et al](https://jamanetwork.com/journals/jama/fullarticle/2588763) and [Esteva et al](https://pubmed.ncbi.nlm.nih.gov/28117445/) demonstrated the <b>potential of deep learning for diabetic retinopathy screening</b> and <b>skin lesion classification</b>, respectively.\n- An essential task performed by pathologist; accurate breast cancer staging. \n- Assessment of the extent of cancer spread by histopathological analysis of sentinel axillary lymph nodes (SLNs) is an essential part of breast cancer staging process.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.2 | PROBLEM STATEMENT</b></p>\n</div>\n\n- The sensitivity of SLN assessment by pathologists, however, is not optimal. A retrospective study showed that pathology review by experts changed the nodal status in 24% of patients.\n- SLN assessment is <b>tedious</b> and <b>time-consuming</b>. It has been shown that deep learning algorithms could identify metastases in SLN slides with 100% sensitivity, whereas 40% of the slides without metastases could be identified as such.\n- This could result in a <b>significant reduction in the workload</b> of pathologists.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.3 | STUDY AIM</b></p>\n</div>\n\n- The aim of this study was to investigate the potential of using [Pytorch's](https://pytorch.org) Deep Learning module for the <b>detection of metastases</b> in SLN slides and compare them with the predefined pathologist diagnosis.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.4 | PLAYGROUND PREDICTION COMPETITION</b></p>\n</div>\n\n#### **<span style='color:#5D2ECC'>OVERVIEW</span>**\n\nIn this competition, you must create an algorithm to identify metastatic cancer in small image patches taken from larger digital pathology scans. The data for this competition is a slightly modified version of the PatchCamelyon (PCam) benchmark dataset (the original PCam dataset contains duplicate images due to its probabilistic sampling, however, the version presented on Kaggle does not contain duplicates).\n\nPCam is highly interesting for both its size, simplicity to get started on, and approachability. In the authors' words:\n\n> [PCam] packs the clinically-relevant task of metastasis detection into a straight-forward binary image classification task, akin to CIFAR-10 and MNIST. Models can easily be trained on a single GPU in a couple hours, and achieve competitive scores in the Camelyon16 tasks of tumor detection and whole-slide image diagnosis. Furthermore, the balance between task-difficulty and tractability makes it a prime suspect for fundamental machine learning research on topics as active learning, model uncertainty, and explainability.\n\n#### **<span style='color:#5D2ECC'>SUBMISSION FORMAT</span>**\n\n> For each id in the test set, you must predict a probability that center 32x32px region of a patch contains at least one pixel of tumor tissue. The file should contain a header and have the following format: \n\nid,label <br>\n<code>0b2ea2a822ad23fdb1b5dd26653da899fbd2c0d5,0</code>","metadata":{}},{"cell_type":"code","source":"!pip install torchsummary","metadata":{"_kg_hide-output":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-04T20:46:29.330654Z","iopub.execute_input":"2022-03-04T20:46:29.330922Z","iopub.status.idle":"2022-03-04T20:46:38.633544Z","shell.execute_reply.started":"2022-03-04T20:46:29.330889Z","shell.execute_reply":"2022-03-04T20:46:38.632739Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We have a table for identification, <code>id</code> & subsequent expert label diagnosis <code>label</code>","metadata":{}},{"cell_type":"code","source":"labels_df = pd.read_csv('/kaggle/input/histopathologic-cancer-detection/train_labels.csv')\nlabels_df.head()","metadata":{"execution":{"iopub.status.busy":"2022-03-04T20:46:38.635808Z","iopub.execute_input":"2022-03-04T20:46:38.63609Z","iopub.status.idle":"2022-03-04T20:46:39.230978Z","shell.execute_reply.started":"2022-03-04T20:46:38.636052Z","shell.execute_reply":"2022-03-04T20:46:39.230277Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# No duplicate ids found\nlabels_df[labels_df.duplicated(keep=False)]","metadata":{"execution":{"iopub.status.busy":"2022-03-04T20:46:39.232738Z","iopub.execute_input":"2022-03-04T20:46:39.232992Z","iopub.status.idle":"2022-03-04T20:46:39.309554Z","shell.execute_reply.started":"2022-03-04T20:46:39.232958Z","shell.execute_reply":"2022-03-04T20:46:39.308732Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Definitely not as one sides as was expected, but the dataset still favours non malignant, normal cases (13k) compared to malignant cases (8.9k)","metadata":{}},{"cell_type":"code","source":"labels_df['label'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-03-04T20:46:39.310866Z","iopub.execute_input":"2022-03-04T20:46:39.31118Z","iopub.status.idle":"2022-03-04T20:46:39.320952Z","shell.execute_reply.started":"2022-03-04T20:46:39.311144Z","shell.execute_reply":"2022-03-04T20:46:39.320056Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.5 | DATA SAMPLES</b></p>\n</div>\n\n- Let's also visualise the dataset images, outlined with the colour of normal <b>cases (0) (green)</b> & <b>malignant cases (1) (red)</b>\n- We can note that its quite a challenge to distinguish whether an image should be classified as __malignant__ or not, simply from an inspection, an expert evaluation is quite beneficial, but it is likely a very time consuming procedure as indicated in the introduction.","metadata":{}},{"cell_type":"code","source":"# data is stored here\nimgpath =\"/kaggle/input/histopathologic-cancer-detection/train/\"\nmalignant = labels_df.loc[labels_df['label']==1]['id'].values # get the ids of malignant cases\nnormal = labels_df.loc[labels_df['label']==0]['id'].values # get the ids of the normal cases\n\nnrows,ncols=6,15\nfig,ax = plt.subplots(nrows,ncols,figsize=(15,6))\nplt.subplots_adjust(wspace=0, hspace=0) \nfor i,j in enumerate(malignant[:nrows*ncols]):\n    fname = os.path.join(imgpath ,j +'.tif')\n    img = Image.open(fname)\n    idcol = ImageDraw.Draw(img)\n    idcol.rectangle(((0,0),(95,95)),outline='red')\n    plt.subplot(nrows, ncols, i+1) \n    plt.imshow(np.array(img))\n    plt.axis('off')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-04T20:46:45.656645Z","iopub.execute_input":"2022-03-04T20:46:45.657247Z","iopub.status.idle":"2022-03-04T20:46:49.569321Z","shell.execute_reply.started":"2022-03-04T20:46:45.657206Z","shell.execute_reply":"2022-03-04T20:46:49.56857Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.rcParams['figure.figsize'] = (15, 6)\nplt.subplots_adjust(wspace=0, hspace=0)\n\nnrows,ncols=6,15\nfor i,j in enumerate(normal[:nrows*ncols]):\n    fname = os.path.join(imgpath ,j +'.tif')\n    img = Image.open(fname)\n    idcol = ImageDraw.Draw(img)\n    idcol.rectangle(((0,0),(95,95)),outline='green')\n    plt.subplot(nrows, ncols, i+1) \n    plt.imshow(np.array(img))\n    plt.axis('off')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-03T19:36:53.961576Z","iopub.execute_input":"2022-03-03T19:36:53.96181Z","iopub.status.idle":"2022-03-03T19:36:58.244137Z","shell.execute_reply.started":"2022-03-03T19:36:53.961781Z","shell.execute_reply":"2022-03-03T19:36:58.243363Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>2 <span style='color:#5D2ECC'>|</span> CREATING A CUSTOM DATASET</b>\n\n- Typically, we'd store the data in Pandas/Numpy formats, dealing with a large dataset, we can utilise PyTorch's efficient data management approach.\n- Let's create a custom <code>Dataset</code> class by subclassing the __Pytorch Dataset__ class.\n- We need two essential fuctions <code>__len__</code> & <code>__getitem__</code> in our custom class.","metadata":{}},{"cell_type":"code","source":"# fix torch random seed\ntorch.manual_seed(0)\n\nclass pytorch_data(Dataset):\n    \n    def __init__(self, data_dir, transform,data_type=\"train\"):      \n    \n        # Get Image File Names\n        cdm_data=os.path.join(data_dir,data_type)  # directory of files\n        file_names = os.listdir(cdm_data) # get list of images in that directory\n        self.full_filenames = [os.path.join(cdm_data, f) for f in file_names]   # get the full path to images\n        \n        # Get Labels\n        labels_data=os.path.join(data_dir,\"train_labels.csv\") \n        labels_df=pd.read_csv(labels_data)\n        labels_df.set_index(\"id\", inplace=True) # set data frame index to id\n        # obtained labels from df\n        self.labels = [labels_df.loc[filename[:-4]].values[0] for filename in file_names] \n        self.transform = transform\n      \n    def __len__(self):\n        return len(self.full_filenames) # size of dataset\n      \n    def __getitem__(self, idx):\n        # open image, apply transforms and return with label\n        image = Image.open(self.full_filenames[idx])  # Open Image with PIL\n        image = self.transform(image) # Apply Specific Transformation to Image\n        return image, self.labels[idx]","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:36:58.245452Z","iopub.execute_input":"2022-03-03T19:36:58.245837Z","iopub.status.idle":"2022-03-03T19:36:58.260948Z","shell.execute_reply.started":"2022-03-03T19:36:58.245801Z","shell.execute_reply":"2022-03-03T19:36:58.260021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# define transformation that converts a PIL image into PyTorch tensors.\nimport torchvision.transforms as transforms\ndata_transformer = transforms.Compose([transforms.ToTensor(),\n                                       transforms.Resize((46,46))])","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:36:58.262695Z","iopub.execute_input":"2022-03-03T19:36:58.26323Z","iopub.status.idle":"2022-03-03T19:36:58.269901Z","shell.execute_reply.started":"2022-03-03T19:36:58.263186Z","shell.execute_reply":"2022-03-03T19:36:58.269027Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define an object of the custom dataset for the train folder.\ndata_dir = '/kaggle/input/histopathologic-cancer-detection/'\nimg_dataset = pytorch_data(data_dir, data_transformer, \"train\") # Histopathalogic images","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:36:58.273508Z","iopub.execute_input":"2022-03-03T19:36:58.273953Z","iopub.status.idle":"2022-03-03T19:37:22.713813Z","shell.execute_reply.started":"2022-03-03T19:36:58.273918Z","shell.execute_reply":"2022-03-03T19:37:22.713057Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# load an example tensor\nimg,label=img_dataset[10]\nprint(img.shape,torch.min(img),torch.max(img))","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:22.715091Z","iopub.execute_input":"2022-03-03T19:37:22.715333Z","iopub.status.idle":"2022-03-03T19:37:22.796103Z","shell.execute_reply.started":"2022-03-03T19:37:22.715302Z","shell.execute_reply":"2022-03-03T19:37:22.795341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>3 <span style='color:#5D2ECC'>|</span> SPLITTING THE DATASET</b>\n\n- Among the training set, we need to evaluate the model on validation datasets to track the model's performance during training.\n- Let's use 20% of img_dataset as the validation dataset & use the rest as the training set.","metadata":{}},{"cell_type":"code","source":"len_img=len(img_dataset)\nlen_train=int(0.8*len_img)\nlen_val=len_img-len_train\n\n# Split Pytorch tensor\ntrain_ts,val_ts=random_split(img_dataset,\n                             [len_train,len_val]) # random split 80/20\n\nprint(\"train dataset size:\", len(train_ts))\nprint(\"validation dataset size:\", len(val_ts))","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:22.797144Z","iopub.execute_input":"2022-03-03T19:37:22.797377Z","iopub.status.idle":"2022-03-03T19:37:22.825009Z","shell.execute_reply.started":"2022-03-03T19:37:22.797345Z","shell.execute_reply":"2022-03-03T19:37:22.824317Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# getting the torch tensor image & target variable\nii=-1\nfor x,y in train_ts:\n    print(x.shape,y)\n    ii+=1\n    if(ii>5):\n        break","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:22.826211Z","iopub.execute_input":"2022-03-03T19:37:22.826441Z","iopub.status.idle":"2022-03-03T19:37:22.928525Z","shell.execute_reply.started":"2022-03-03T19:37:22.826409Z","shell.execute_reply":"2022-03-03T19:37:22.927856Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import plotly.express as px\n\ndef plot_img(x,y):\n\n    npimg = x.numpy() # convert tensor to numpy array\n    npimg_tr=np.transpose(npimg, (1,2,0)) # Convert to H*W*C shape\n    fig = px.imshow(npimg_tr)\n    fig.update_layout(coloraxis_showscale=False,title=str(y_grid_train))\n    fig.update_xaxes(showticklabels=False)\n    fig.update_layout(template='plotly_white',height=200);fig.update_layout(margin={\"r\":0,\"t\":60,\"l\":0,\"b\":0})\n    fig.update_layout(title={'text': str(y),'y':0.9,'x':0.5,'xanchor': 'center','yanchor': 'top'})\n    \n    fig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-03T19:37:22.929714Z","iopub.execute_input":"2022-03-03T19:37:22.930212Z","iopub.status.idle":"2022-03-03T19:37:26.211913Z","shell.execute_reply.started":"2022-03-03T19:37:22.930175Z","shell.execute_reply":"2022-03-03T19:37:26.211153Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training Subset Examples \nSome examples from our training data subset, with corresponding labels.","metadata":{}},{"cell_type":"code","source":"import plotly.express as px\n\n# Create grid of sample images \ngrid_size=8\nrnd_inds=np.random.randint(0,len(train_ts),grid_size)\nprint(\"image indices:\",rnd_inds)\n\nx_grid_train=[train_ts[i][0] for i in rnd_inds]\ny_grid_train=[train_ts[i][1] for i in rnd_inds]\n\nx_grid_train=utils.make_grid(x_grid_train, nrow=4, padding=2)\nprint(x_grid_train.shape)\n    \nplot_img(x_grid_train,y_grid_train)","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:26.213141Z","iopub.execute_input":"2022-03-03T19:37:26.2134Z","iopub.status.idle":"2022-03-03T19:37:27.109123Z","shell.execute_reply.started":"2022-03-03T19:37:26.213367Z","shell.execute_reply":"2022-03-03T19:37:27.108434Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Validation Dataset Examples\nSome examples from the validation subset, with corresponding labels.","metadata":{}},{"cell_type":"code","source":"grid_size=8\nrnd_inds=np.random.randint(0,len(val_ts),grid_size)\nprint(\"image indices:\",rnd_inds)\nx_grid_val=[val_ts[i][0] for i in range(grid_size)]\ny_grid_val=[val_ts[i][1] for i in range(grid_size)]\n\nx_grid_val=utils.make_grid(x_grid_val, nrow=4, padding=2)\nprint(x_grid_val.shape)\n\nplot_img(x_grid_val,y_grid_val)","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:27.110312Z","iopub.execute_input":"2022-03-03T19:37:27.110634Z","iopub.status.idle":"2022-03-03T19:37:27.300969Z","shell.execute_reply.started":"2022-03-03T19:37:27.110596Z","shell.execute_reply":"2022-03-03T19:37:27.29863Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>4 <span style='color:#5D2ECC'>|</span> TRANSFORMING THE DATA</b>\n\n#### **<span style='color:#5D2ECC'>IMAGE AUGMENTATIONS</span>**\n\n- Among with pretrained models, image __transformation__ and __image augmentation__ are generally considered to be an essential parts of constructing deep learning models.\n- Using image transformations, we can expand our dataset or resize and normalise it to achieve better model performance.\n- Typical transformations include __horizontal__,__vertical flipping__, __rotation__, __resizing__.\n- We can use various image transformations for our binary classification model without making label changes; we can flip/rotate a __malignant__ image but it will remain the same, __malignant__.\n- We can use the torchvision module to perform image transformations during the training process.\n\n#### **<span style='color:#5D2ECC'>TRAINING DATA AUGMENTATIONS</span>**\n- transforms.RandomHorizontalFlip(p=0.5): Flips the image horizontally with the probability of 0.5\n- transforms.RandomVerticalFlip(p=0.5) : Flips the image vertically  \" \n- transforms.RandomRotation(45) : Rotates the images in the range of (-45,45) degrees.\n- transforms.RandomResizedCrop(96,scale=(0.8,1.0),ratio=(1.0,1.0)) : Randomly square crops the image in the range of [72,96], followed by a resize to 96x96, which is the original pixel size of our image data.\n- transforms.ToTensor() : Converts to Tensor & Normalises as shown above already.","metadata":{}},{"cell_type":"code","source":"# Define the following transformations for the training dataset\ntr_transf = transforms.Compose([\n#     transforms.Resize((40,40)),\n    transforms.RandomHorizontalFlip(p=0.5), \n    transforms.RandomVerticalFlip(p=0.5),  \n    transforms.RandomRotation(45),         \n#     transforms.RandomResizedCrop(50,scale=(0.8,1.0),ratio=(1.0,1.0)),\n    transforms.ToTensor()])","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:27.302275Z","iopub.execute_input":"2022-03-03T19:37:27.302539Z","iopub.status.idle":"2022-03-03T19:37:27.307737Z","shell.execute_reply.started":"2022-03-03T19:37:27.302505Z","shell.execute_reply":"2022-03-03T19:37:27.306787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# For the validation dataset, we don't need any augmentation; simply convert images into tensors\nval_transf = transforms.Compose([\n    transforms.ToTensor()])\n\n# After defining the transformations, overwrite the transform functions of train_ts, val_ts\ntrain_ts.transform=tr_transf\nval_ts.transform=val_transf","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:27.308937Z","iopub.execute_input":"2022-03-03T19:37:27.309688Z","iopub.status.idle":"2022-03-03T19:37:27.31842Z","shell.execute_reply.started":"2022-03-03T19:37:27.309633Z","shell.execute_reply":"2022-03-03T19:37:27.317708Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>5 <span style='color:#5D2ECC'>|</span> CREATING DATALOADERS</b>\n\n- Ready to create a PyTorch Dataloader. If we don't use __Dataloaders__, we have to write code to loop over datasets & extract a data batch; automated.\n- We need to define a __batch_size__ : The number of images extracted from the dataset each iteration","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader\ntrain_dl = DataLoader(train_ts, batch_size=32, shuffle=True)\nval_dl = DataLoader(val_ts, batch_size=32, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:27.3196Z","iopub.execute_input":"2022-03-03T19:37:27.320012Z","iopub.status.idle":"2022-03-03T19:37:27.327615Z","shell.execute_reply.started":"2022-03-03T19:37:27.319976Z","shell.execute_reply":"2022-03-03T19:37:27.326939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for x,y in train_dl:\n    print(x.shape,y)\n    break","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:27.328782Z","iopub.execute_input":"2022-03-03T19:37:27.329152Z","iopub.status.idle":"2022-03-03T19:37:27.764603Z","shell.execute_reply.started":"2022-03-03T19:37:27.329116Z","shell.execute_reply":"2022-03-03T19:37:27.762888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>6 <span style='color:#5D2ECC'>|</span> CNN BINARY CLASSIFICATION MODEL</b>\n\n- Model is comprised of __four CNN__ & __two fully connected__ layers.\n- After each convolution layer, there is a pooling layer, implementation in PyTorch.\n- Pooling layers are used after each convolution layer as well.","metadata":{}},{"cell_type":"code","source":"# Function to calculate the output size of a CNN layer\n# before making it an input into the linear layer\n\ndef findConv2dOutShape(hin,win,conv,pool=2):\n    # get conv arguments\n    kernel_size=conv.kernel_size\n    stride=conv.stride\n    padding=conv.padding\n    dilation=conv.dilation\n\n    hout=np.floor((hin+2*padding[0]-dilation[0]*(kernel_size[0]-1)-1)/stride[0]+1)\n    wout=np.floor((win+2*padding[1]-dilation[1]*(kernel_size[1]-1)-1)/stride[1]+1)\n\n    if pool:\n        hout/=pool\n        wout/=pool\n    return int(hout),int(wout)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-03T19:37:27.766122Z","iopub.execute_input":"2022-03-03T19:37:27.766392Z","iopub.status.idle":"2022-03-03T19:37:27.773133Z","shell.execute_reply.started":"2022-03-03T19:37:27.766355Z","shell.execute_reply":"2022-03-03T19:37:27.772147Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch.nn as nn\nimport torch.nn.functional as F\n\nclass Network(nn.Module):\n    \n    # Network Initialisation\n    def __init__(self, params):\n        \n        super(Network, self).__init__()\n    \n        Cin,Hin,Win=params[\"shape_in\"]\n        init_f=params[\"initial_filters\"] \n        num_fc1=params[\"num_fc1\"]  \n        num_classes=params[\"num_classes\"] \n        self.dropout_rate=params[\"dropout_rate\"] \n        \n        # Convolution Layers\n        self.conv1 = nn.Conv2d(Cin, init_f, kernel_size=3)\n        h,w=findConv2dOutShape(Hin,Win,self.conv1)\n        self.conv2 = nn.Conv2d(init_f, 2*init_f, kernel_size=3)\n        h,w=findConv2dOutShape(h,w,self.conv2)\n        self.conv3 = nn.Conv2d(2*init_f, 4*init_f, kernel_size=3)\n        h,w=findConv2dOutShape(h,w,self.conv3)\n        self.conv4 = nn.Conv2d(4*init_f, 8*init_f, kernel_size=3)\n        h,w=findConv2dOutShape(h,w,self.conv4)\n        \n        # compute the flatten size\n        self.num_flatten=h*w*8*init_f\n        self.fc1 = nn.Linear(self.num_flatten, num_fc1)\n        self.fc2 = nn.Linear(num_fc1, num_classes)\n\n    def forward(self,X):\n        \n        # Convolution & Pool Layers\n        X = F.relu(self.conv1(X)); X = F.max_pool2d(X, 2, 2)\n        X = F.relu(self.conv2(X)); X = F.max_pool2d(X, 2, 2)\n        X = F.relu(self.conv3(X));X = F.max_pool2d(X, 2, 2)\n        X = F.relu(self.conv4(X));X = F.max_pool2d(X, 2, 2)\n\n        X = X.view(-1, self.num_flatten)\n        \n        X = F.relu(self.fc1(X))\n        X=F.dropout(X, self.dropout_rate)\n        X = self.fc2(X)\n        return F.log_softmax(X, dim=1)\n\n# Neural Network Predefined Parameters\nparams_model={\n        \"shape_in\": (3,46,46), \n        \"initial_filters\": 8,    \n        \"num_fc1\": 100,\n        \"dropout_rate\": 0.25,\n        \"num_classes\": 2}\n\n# Create instantiation of Network class\ncnn_model = Network(params_model)\n\n# define computation hardware approach (GPU/CPU)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = cnn_model.to(device)","metadata":{"_kg_hide-input":false,"execution":{"iopub.status.busy":"2022-03-03T19:37:27.774599Z","iopub.execute_input":"2022-03-03T19:37:27.774875Z","iopub.status.idle":"2022-03-03T19:37:30.984225Z","shell.execute_reply.started":"2022-03-03T19:37:27.774842Z","shell.execute_reply":"2022-03-03T19:37:30.983465Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>7 <span style='color:#5D2ECC'>|</span> DEFINING A LOSS FUNCTION</b>\n\n- Loss Functions are one of the key pieces of an effective deep learning solution.\n- Pytorch uses <code>loss functions</code> to determine how it will update the network to reach the desired solution.\n- The standard loss function for classification tasks is __cross entropy loss__ or __logloss__\n- When defining a loss function, we need to consider, the number of model outputs and their activation functions.\n- For binary classification tasks, we can choose one or two outputs.\n- It is recommended to use __log_softmax__ as it is easier to expand to multiclass classification; PyTorch combines the log and softmax operations into one function, due to numerical stability and speed.","metadata":{}},{"cell_type":"code","source":"loss_func = nn.NLLLoss(reduction=\"sum\")","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:30.985341Z","iopub.execute_input":"2022-03-03T19:37:30.985593Z","iopub.status.idle":"2022-03-03T19:37:30.990942Z","shell.execute_reply.started":"2022-03-03T19:37:30.985561Z","shell.execute_reply":"2022-03-03T19:37:30.99005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>8 <span style='color:#5D2ECC'>|</span> DEFINING AN OPTIMISER</b>\n\n- Training the network involves passing data through the network, using the loss function to determine the difference b/w the prediction & true value. Which is then followed by using of that information to update the weights of the network in an attempt to make the loss function return as small of a loss as possible. __Performing updates on the neural network, an optimiser is used__.\n- The <code>torch.optim</code> contains implementations of common optimisers\n- The optimiser will hold the current state and will update the parameters based on the computed gradients\n- For binary classification taskss, __SGD__, __Adam__ Optimisers are commonly used, let's use the latter here.","metadata":{}},{"cell_type":"code","source":"from torch import optim\nopt = optim.Adam(cnn_model.parameters(), lr=3e-4)\nlr_scheduler = ReduceLROnPlateau(opt, mode='min',factor=0.5, patience=20,verbose=1)","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:30.992028Z","iopub.execute_input":"2022-03-03T19:37:30.992719Z","iopub.status.idle":"2022-03-03T19:37:31.003637Z","shell.execute_reply.started":"2022-03-03T19:37:30.992682Z","shell.execute_reply":"2022-03-03T19:37:31.003005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>9 <span style='color:#5D2ECC'>|</span> TRAINING & EVALUATION</b>","metadata":{}},{"cell_type":"code","source":"''' Helper Functions'''\n\n# Function to get the learning rate\ndef get_lr(opt):\n    for param_group in opt.param_groups:\n        return param_group['lr']\n\n# Function to compute the loss value per batch of data\ndef loss_batch(loss_func, output, target, opt=None):\n    \n    loss = loss_func(output, target) # get loss\n    pred = output.argmax(dim=1, keepdim=True) # Get Output Class\n    metric_b=pred.eq(target.view_as(pred)).sum().item() # get performance metric\n    \n    if opt is not None:\n        opt.zero_grad()\n        loss.backward()\n        opt.step()\n\n    return loss.item(), metric_b\n\n# Compute the loss value & performance metric for the entire dataset (epoch)\ndef loss_epoch(model,loss_func,dataset_dl,check=False,opt=None):\n    \n    run_loss=0.0 \n    t_metric=0.0\n    len_data=len(dataset_dl.dataset)\n\n    # internal loop over dataset\n    for xb, yb in dataset_dl:\n        # move batch to device\n        xb=xb.to(device)\n        yb=yb.to(device)\n        output=model(xb) # get model output\n        loss_b,metric_b=loss_batch(loss_func, output, yb, opt) # get loss per batch\n        run_loss+=loss_b        # update running loss\n\n        if metric_b is not None: # update running metric\n            t_metric+=metric_b\n\n        # break the loop in case of sanity check\n        if check is True:\n            break\n    \n    loss=run_loss/float(len_data)  # average loss value\n    metric=t_metric/float(len_data) # average metric value\n    \n    return loss, metric","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-03T19:37:31.004974Z","iopub.execute_input":"2022-03-03T19:37:31.005223Z","iopub.status.idle":"2022-03-03T19:37:31.01716Z","shell.execute_reply.started":"2022-03-03T19:37:31.00519Z","shell.execute_reply":"2022-03-03T19:37:31.016347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_val(model, params,verbose=False):\n    \n    # Get the parameters\n    epochs=params[\"epochs\"]\n    loss_func=params[\"f_loss\"]\n    opt=params[\"optimiser\"]\n    train_dl=params[\"train\"]\n    val_dl=params[\"val\"]\n    check=params[\"check\"]\n    lr_scheduler=params[\"lr_change\"]\n    weight_path=params[\"weight_path\"]\n    \n    loss_history={\"train\": [],\"val\": []} # history of loss values in each epoch\n    metric_history={\"train\": [],\"val\": []} # histroy of metric values in each epoch\n    best_model_wts = copy.deepcopy(model.state_dict()) # a deep copy of weights for the best performing model\n    best_loss=float('inf') # initialize best loss to a large value\n    \n    # main loop\n    for epoch in range(epochs):\n        \n        ''' Get the Learning Rate '''\n        current_lr=get_lr(opt)\n        if(verbose):\n            print('Epoch {}/{}, current lr={}'.format(epoch, epochs - 1, current_lr))\n        \n        ''' Train the Model on the Training Set '''\n        model.train()\n        train_loss, train_metric=loss_epoch(model,loss_func,train_dl,check,opt)\n\n        ''' Collect loss and metric for training dataset ''' \n        loss_history[\"train\"].append(train_loss)\n        metric_history[\"train\"].append(train_metric)\n        \n        ''' Evaluate model on validation dataset '''\n        model.eval()\n        with torch.no_grad():\n            val_loss, val_metric=loss_epoch(model,loss_func,val_dl,check)\n        \n        # store best model\n        if val_loss < best_loss:\n            best_loss = val_loss\n            best_model_wts = copy.deepcopy(model.state_dict())\n            \n            # store weights into a local file\n            torch.save(model.state_dict(), weight_path)\n            if(verbose):\n                print(\"Copied best model weights!\")\n        \n        # collect loss and metric for validation dataset\n        loss_history[\"val\"].append(val_loss)\n        metric_history[\"val\"].append(val_metric)\n        \n        # learning rate schedule\n        lr_scheduler.step(val_loss)\n        if current_lr != get_lr(opt):\n            if(verbose):\n                print(\"Loading best model weights!\")\n            model.load_state_dict(best_model_wts) \n\n        if(verbose):\n            print(f\"train loss: {train_loss:.6f}, dev loss: {val_loss:.6f}, accuracy: {100*val_metric:.2f}\")\n            print(\"-\"*10) \n\n    # load best model weights\n    model.load_state_dict(best_model_wts)\n        \n    return model, loss_history, metric_history","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:31.018671Z","iopub.execute_input":"2022-03-03T19:37:31.01898Z","iopub.status.idle":"2022-03-03T19:37:31.033681Z","shell.execute_reply.started":"2022-03-03T19:37:31.018944Z","shell.execute_reply":"2022-03-03T19:37:31.032897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create Parameter Dictionary\nparams_train={\n  \"train\": train_dl,\n    \"val\": val_dl,\n \"epochs\": 30,\n \"optimiser\": optim.Adam(cnn_model.parameters(),lr=3e-4),\n \"lr_change\": ReduceLROnPlateau(opt,\n                                mode='min',\n                                factor=0.5,\n                                patience=20,verbose=0),\n \"f_loss\": nn.NLLLoss(reduction=\"sum\"),\n \"weight_path\": \"weights.pt\",\n \"check\": True, \n}\n\n# train and validate the model w/ Sanity Check\ncnn_model,loss_hist,metric_hist=train_val(cnn_model,params_train)","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:31.035164Z","iopub.execute_input":"2022-03-03T19:37:31.035426Z","iopub.status.idle":"2022-03-03T19:37:50.861931Z","shell.execute_reply.started":"2022-03-03T19:37:31.035393Z","shell.execute_reply":"2022-03-03T19:37:50.86114Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Train-Validation Progress\nepochs=params_train[\"epochs\"]\n\nfig = make_subplots(rows=1, cols=2,subplot_titles=['lost_hist','metric_hist'])\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=loss_hist[\"train\"],name='loss_hist[\"train\"]'),row=1, col=1)\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=loss_hist[\"val\"],name='loss_hist[\"val\"]'),row=1, col=1)\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=metric_hist[\"train\"],name='metric_hist[\"train\"]'),row=1, col=2)\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=metric_hist[\"val\"],name='metric_hist[\"val\"]'),row=1, col=2)\nfig.update_layout(template='plotly_white');fig.update_layout(margin={\"r\":0,\"t\":60,\"l\":0,\"b\":0},height=300)\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-03T19:37:50.865959Z","iopub.execute_input":"2022-03-03T19:37:50.866273Z","iopub.status.idle":"2022-03-03T19:37:50.939087Z","shell.execute_reply.started":"2022-03-03T19:37:50.866234Z","shell.execute_reply":"2022-03-03T19:37:50.93844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"params_train={\n \"train\": train_dl,\"val\": val_dl,\n \"epochs\": 50,\n \"optimiser\": optim.Adam(cnn_model.parameters(),\n                         lr=3e-4),\n \"lr_change\": ReduceLROnPlateau(opt,\n                                mode='min',\n                                factor=0.5,\n                                patience=20,\n                                verbose=0),\n \"f_loss\": nn.NLLLoss(reduction=\"sum\"),\n \"weight_path\": \"weights.pt\",\n \"check\": False, \n}\n\n''' Actual Train / Evaluation of CNN Model '''\n# train and validate the model\ncnn_model,loss_hist,metric_hist=train_val(cnn_model,params_train)","metadata":{"execution":{"iopub.status.busy":"2022-03-03T19:37:50.945376Z","iopub.execute_input":"2022-03-03T19:37:50.945625Z","iopub.status.idle":"2022-03-03T19:38:15.056698Z","shell.execute_reply.started":"2022-03-03T19:37:50.945591Z","shell.execute_reply":"2022-03-03T19:38:15.055879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Train-Validation Progress\nepochs=params_train[\"epochs\"]\n\nfig = make_subplots(rows=1, cols=2,subplot_titles=['lost_hist','metric_hist'])\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=loss_hist[\"train\"],name='loss_hist[\"train\"]'),row=1, col=1)\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=loss_hist[\"val\"],name='loss_hist[\"val\"]'),row=1, col=1)\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=metric_hist[\"train\"],name='metric_hist[\"train\"]'),row=1, col=2)\nfig.add_trace(go.Scatter(x=[*range(1,epochs+1)], y=metric_hist[\"val\"],name='metric_hist[\"val\"]'),row=1, col=2)\nfig.update_layout(template='plotly_white');fig.update_layout(margin={\"r\":0,\"t\":60,\"l\":0,\"b\":0},height=300)\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-03-03T19:38:15.057958Z","iopub.execute_input":"2022-03-03T19:38:15.058207Z","iopub.status.idle":"2022-03-03T19:38:15.118161Z","shell.execute_reply.started":"2022-03-03T19:38:15.058175Z","shell.execute_reply":"2022-03-03T19:38:15.117516Z"},"trusted":true},"execution_count":null,"outputs":[]}]}