{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":11848,"databundleVersionId":862157,"sourceType":"competition"}],"dockerImageVersionId":30733,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \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":{"execution":{"iopub.status.busy":"2024-06-05T15:18:13.149756Z","iopub.execute_input":"2024-06-05T15:18:13.150393Z","iopub.status.idle":"2024-06-05T15:18:21.614556Z","shell.execute_reply.started":"2024-06-05T15:18:13.150362Z","shell.execute_reply":"2024-06-05T15:18:21.613480Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_df = pd.read_csv('/kaggle/input/histopathologic-cancer-detection/train_labels.csv')\nprint(labels_df.head().to_markdown())","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:21.616358Z","iopub.execute_input":"2024-06-05T15:18:21.616931Z","iopub.status.idle":"2024-06-05T15:18:22.034439Z","shell.execute_reply.started":"2024-06-05T15:18:21.616906Z","shell.execute_reply":"2024-06-05T15:18:22.033475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.listdir('/kaggle/input/histopathologic-cancer-detection/')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.035673Z","iopub.execute_input":"2024-06-05T15:18:22.036025Z","iopub.status.idle":"2024-06-05T15:18:22.043389Z","shell.execute_reply.started":"2024-06-05T15:18:22.035985Z","shell.execute_reply":"2024-06-05T15:18:22.042562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_df.shape","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.046054Z","iopub.execute_input":"2024-06-05T15:18:22.046321Z","iopub.status.idle":"2024-06-05T15:18:22.052912Z","shell.execute_reply.started":"2024-06-05T15:18:22.046299Z","shell.execute_reply":"2024-06-05T15:18:22.052013Z"},"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":"2024-06-05T15:18:22.054399Z","iopub.execute_input":"2024-06-05T15:18:22.054843Z","iopub.status.idle":"2024-06-05T15:18:22.160675Z","shell.execute_reply.started":"2024-06-05T15:18:22.054795Z","shell.execute_reply":"2024-06-05T15:18:22.159699Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Target feature class balance","metadata":{}},{"cell_type":"code","source":"#distribuition of class\nlabel_counts = labels_df['label'].value_counts()\nlabel_counts","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.162242Z","iopub.execute_input":"2024-06-05T15:18:22.162687Z","iopub.status.idle":"2024-06-05T15:18:22.182341Z","shell.execute_reply.started":"2024-06-05T15:18:22.162632Z","shell.execute_reply":"2024-06-05T15:18:22.181391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create a bar chart\nplt.figure(figsize=(8, 6))\nlabel_counts.plot(kind='bar', color=['lightblue', 'lightgreen']) \nplt.title('Distribution of Cell Labels')\nplt.xlabel('Cell Label')\nplt.ylabel('Count')\nplt.grid(axis='y', linestyle='--', alpha=0.5)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.183468Z","iopub.execute_input":"2024-06-05T15:18:22.183785Z","iopub.status.idle":"2024-06-05T15:18:22.516674Z","shell.execute_reply.started":"2024-06-05T15:18:22.183758Z","shell.execute_reply":"2024-06-05T15:18:22.515705Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Dataset Preview \nLet's also visualise the dataset images:\n\n- non-malignant cases (0) (outligned with green colour)\n- malignant cases (1) (outligned with red colour)\nWe can note that its quite a challenge to distinguish whether an image should be classified as malignant or non-malignant simply from an inspection\n","metadata":{}},{"cell_type":"code","source":"imgpath =\"/kaggle/input/histopathologic-cancer-detection/train/\" # training data is stored in this folder\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\nprint('normal ids')\nprint(normal[0:3],'\\n')\n\nprint('malignant ids')\nprint(malignant[0:3])","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.518086Z","iopub.execute_input":"2024-06-05T15:18:22.518458Z","iopub.status.idle":"2024-06-05T15:18:22.539778Z","shell.execute_reply.started":"2024-06-05T15:18:22.518424Z","shell.execute_reply":"2024-06-05T15:18:22.538852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_fig(ids,title,nrows=5,ncols=15):\n\n    fig,ax = plt.subplots(nrows,ncols,figsize=(18,6))\n    plt.subplots_adjust(wspace=0, hspace=0) \n    for i,j in enumerate(ids[: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='white')\n        plt.subplot(nrows, ncols, i+1) \n        plt.imshow(np.array(img))\n        plt.axis('off')\n\n    plt.suptitle(title, y=0.94)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.540794Z","iopub.execute_input":"2024-06-05T15:18:22.541099Z","iopub.status.idle":"2024-06-05T15:18:22.548827Z","shell.execute_reply.started":"2024-06-05T15:18:22.541075Z","shell.execute_reply":"2024-06-05T15:18:22.547757Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_fig(malignant,'Malignant cases')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:22.553244Z","iopub.execute_input":"2024-06-05T15:18:22.553515Z","iopub.status.idle":"2024-06-05T15:18:26.538236Z","shell.execute_reply.started":"2024-06-05T15:18:22.553495Z","shell.execute_reply":"2024-06-05T15:18:26.536053Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_fig(normal,'Non-Malignant Cases')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:26.539590Z","iopub.execute_input":"2024-06-05T15:18:26.539930Z","iopub.status.idle":"2024-06-05T15:18:30.629083Z","shell.execute_reply.started":"2024-06-05T15:18:26.539901Z","shell.execute_reply":"2024-06-05T15:18:30.628019Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(labels_df)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:30.630316Z","iopub.execute_input":"2024-06-05T15:18:30.630617Z","iopub.status.idle":"2024-06-05T15:18:30.636629Z","shell.execute_reply.started":"2024-06-05T15:18:30.630592Z","shell.execute_reply":"2024-06-05T15:18:30.635745Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"raw","source":"## Data preparation\n### Custom dataset class\nLet's create a custom dataset class by subclassing Pytorch dataset class:\n- We need just two essential functions \\___len___ and \\___getitem___ in out custom class\n\nTo speed up the training process we're only using 4000 samples for the entire dataset","metadata":{}},{"cell_type":"code","source":"torch.manual_seed(0)\n\nclass pytorch_data(Dataset):\n    \n    def __init__(self, data_dir, transform, data_type = 'train'):\n        # get image file names\n        cdm_data = os.path.join(data_dir, data_type) \n        \n        file_names = os.listdir(cdm_data) \n        idx_choose = np.random.choice(np.arange(len(file_names)),4000, replace = False).tolist()\n        file_names_sample = [file_names[x] for x in idx_choose]\n        \n        self.full_filenames = [os.path.join(cdm_data, f) for f in file_names_sample]\n        \n        # get labels from df\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)\n        self.labels = [labels_df.loc[filename[:-4]].values[0] for filename in file_names_sample]\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.full_filenames)\n    \n    def __getitem__(self, idx):\n        # open image, apply transform and return with label\n        image = Image.open(self.full_filenames[idx]) # opens image with PIL\n        image = self.transform(image) # apply specific transformation to image\n        return image, self.labels[idx]\n    ","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:30.637879Z","iopub.execute_input":"2024-06-05T15:18:30.638202Z","iopub.status.idle":"2024-06-05T15:18:30.655128Z","shell.execute_reply.started":"2024-06-05T15:18:30.638176Z","shell.execute_reply":"2024-06-05T15:18:30.654127Z"},"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":"2024-06-05T15:18:30.656302Z","iopub.execute_input":"2024-06-05T15:18:30.656629Z","iopub.status.idle":"2024-06-05T15:18:30.667135Z","shell.execute_reply.started":"2024-06-05T15:18:30.656601Z","shell.execute_reply":"2024-06-05T15:18:30.666211Z"},"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":"2024-06-05T15:18:30.668238Z","iopub.execute_input":"2024-06-05T15:18:30.668526Z","iopub.status.idle":"2024-06-05T15:18:51.788812Z","shell.execute_reply.started":"2024-06-05T15:18:30.668503Z","shell.execute_reply":"2024-06-05T15:18:51.787752Z"},"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":"2024-06-05T15:18:51.790190Z","iopub.execute_input":"2024-06-05T15:18:51.790568Z","iopub.status.idle":"2024-06-05T15:18:51.967534Z","shell.execute_reply.started":"2024-06-05T15:18:51.790537Z","shell.execute_reply":"2024-06-05T15:18:51.966583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Solitting the dataset\n### random_split\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 the img_dataset for validation and use the rest as the training set, so we have a 80/20 split.","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, [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":"2024-06-05T15:18:51.968941Z","iopub.execute_input":"2024-06-05T15:18:51.969340Z","iopub.status.idle":"2024-06-05T15:18:51.980630Z","shell.execute_reply.started":"2024-06-05T15:18:51.969307Z","shell.execute_reply":"2024-06-05T15:18:51.979749Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# getting the torch sensor and 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":"2024-06-05T15:18:51.981844Z","iopub.execute_input":"2024-06-05T15:18:51.982293Z","iopub.status.idle":"2024-06-05T15:18:52.073205Z","shell.execute_reply.started":"2024-06-05T15:18:51.982259Z","shell.execute_reply":"2024-06-05T15:18:52.072230Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training subset example\n- Some examples from our training set with corresponding labels","metadata":{}},{"cell_type":"code","source":"import plotly.express as px\n\ndef plot_img(x,y,title=None):\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(template='plotly_white')\n    fig.update_layout(title=title,height=300,margin={'l':10,'r':20,'b':10})\n    fig.show()","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:52.074438Z","iopub.execute_input":"2024-06-05T15:18:52.074718Z","iopub.status.idle":"2024-06-05T15:18:52.695130Z","shell.execute_reply.started":"2024-06-05T15:18:52.074695Z","shell.execute_reply":"2024-06-05T15:18:52.694011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create grid of sample images \ngrid_size=30\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=10, padding=2)\nprint(x_grid_train.shape)\n    \nplot_img(x_grid_train,y_grid_train,'Training Subset Examples')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:52.696447Z","iopub.execute_input":"2024-06-05T15:18:52.696819Z","iopub.status.idle":"2024-06-05T15:18:55.439576Z","shell.execute_reply.started":"2024-06-05T15:18:52.696785Z","shell.execute_reply":"2024-06-05T15:18:55.438410Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"grid_size=30\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=10, padding=2)\nprint(x_grid_val.shape)\n\nplot_img(x_grid_val,y_grid_val,'Validation Dataset Preview')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:55.441025Z","iopub.execute_input":"2024-06-05T15:18:55.441369Z","iopub.status.idle":"2024-06-05T15:18:55.976856Z","shell.execute_reply.started":"2024-06-05T15:18:55.441340Z","shell.execute_reply":"2024-06-05T15:18:55.975872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Image Augmentation Definitions¶\n### IMAGE AUGMENTATIONS\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### TRAINING DATA AUGMENTATIONS\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":"2024-06-05T15:18:55.978017Z","iopub.execute_input":"2024-06-05T15:18:55.978303Z","iopub.status.idle":"2024-06-05T15:18:55.983515Z","shell.execute_reply.started":"2024-06-05T15:18:55.978278Z","shell.execute_reply":"2024-06-05T15:18:55.982444Z"},"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":"2024-06-05T15:18:55.984893Z","iopub.execute_input":"2024-06-05T15:18:55.985228Z","iopub.status.idle":"2024-06-05T15:18:55.994304Z","shell.execute_reply.started":"2024-06-05T15:18:55.985204Z","shell.execute_reply":"2024-06-05T15:18:55.993345Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# The subset can also have transform attribute (if we asign)\ntrain_ts.transform","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:55.995562Z","iopub.execute_input":"2024-06-05T15:18:55.995826Z","iopub.status.idle":"2024-06-05T15:18:56.011575Z","shell.execute_reply.started":"2024-06-05T15:18:55.995804Z","shell.execute_reply":"2024-06-05T15:18:56.010699Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Creating Dataloaders\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\n\n# Training DataLoader\ntrain_dl = DataLoader(train_ts,\n                      batch_size=32, \n                      shuffle=True)\n\n# Validation DataLoader\nval_dl = DataLoader(val_ts,\n                    batch_size=32,\n                    shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:56.012888Z","iopub.execute_input":"2024-06-05T15:18:56.013314Z","iopub.status.idle":"2024-06-05T15:18:56.021992Z","shell.execute_reply.started":"2024-06-05T15:18:56.013281Z","shell.execute_reply":"2024-06-05T15:18:56.021159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# check samples\nfor x,y in train_dl:\n    print(x.shape,y)\n    break","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:56.023143Z","iopub.execute_input":"2024-06-05T15:18:56.023477Z","iopub.status.idle":"2024-06-05T15:18:56.411938Z","shell.execute_reply.started":"2024-06-05T15:18:56.023450Z","shell.execute_reply":"2024-06-05T15:18:56.411002Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define Binary Classifier\n- Model is comprised of:\n    - four CNN Conv2D layers with a pooling layer max_pool2D added between each layer\n    - Two fully connected layers fc, with a dropout layer between the two layers\n    - log_softmax is used as the activation function for the final layer of the binary classifier\n- PyTorch allows us to create a custom class with nn.Module","metadata":{}},{"cell_type":"code","source":"def 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)\n\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n# Neural Network\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)); \n        X = F.max_pool2d(X, 2, 2)\n        X = F.relu(self.conv2(X))\n        X = F.max_pool2d(X, 2, 2)\n        X = F.relu(self.conv3(X))\n        X = F.max_pool2d(X, 2, 2)\n        X = F.relu(self.conv4(X))\n        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)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:56.413754Z","iopub.execute_input":"2024-06-05T15:18:56.414160Z","iopub.status.idle":"2024-06-05T15:18:56.430134Z","shell.execute_reply.started":"2024-06-05T15:18:56.414122Z","shell.execute_reply":"2024-06-05T15:18:56.429147Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 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":{"execution":{"iopub.status.busy":"2024-06-05T15:18:56.435513Z","iopub.execute_input":"2024-06-05T15:18:56.436030Z","iopub.status.idle":"2024-06-05T15:18:56.694901Z","shell.execute_reply.started":"2024-06-05T15:18:56.435998Z","shell.execute_reply":"2024-06-05T15:18:56.694099Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install torchsummary","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:18:56.696041Z","iopub.execute_input":"2024-06-05T15:18:56.696344Z","iopub.status.idle":"2024-06-05T15:19:12.308292Z","shell.execute_reply.started":"2024-06-05T15:18:56.696319Z","shell.execute_reply":"2024-06-05T15:19:12.307136Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchsummary import summary\nsummary(cnn_model, input_size=(3, 46, 46),device=device.type)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:19:12.309881Z","iopub.execute_input":"2024-06-05T15:19:12.310309Z","iopub.status.idle":"2024-06-05T15:19:13.679984Z","shell.execute_reply.started":"2024-06-05T15:19:12.310273Z","shell.execute_reply":"2024-06-05T15:19:13.678901Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loss Function Definition\n- Loss Functions are one of the key pieces of an effective deep learning solution.\n- Pytorch uses loss functions 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":"2024-06-05T15:19:13.681409Z","iopub.execute_input":"2024-06-05T15:19:13.681685Z","iopub.status.idle":"2024-06-05T15:19:13.686366Z","shell.execute_reply.started":"2024-06-05T15:19:13.681662Z","shell.execute_reply":"2024-06-05T15:19:13.685478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Optimiser Definition\n- Training the network involves passing data through the network:\n    - Using the **loss function** to _determine the difference between the prediction & true value_\n    - Which is then followed by using of that info to _update the weights_ of the network\n    - 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 `torch.optim` contains implementations of common optimisers\n- The **optimiser** will _hold the current state and will update the parameters based on the computed gradients</mark>_\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":"2024-06-05T15:19:13.687677Z","iopub.execute_input":"2024-06-05T15:19:13.688066Z","iopub.status.idle":"2024-06-05T15:19:13.699531Z","shell.execute_reply.started":"2024-06-05T15:19:13.688041Z","shell.execute_reply":"2024-06-05T15:19:13.698558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training Model\n### Helper functions\n\nThe main training loop function train_val will utiliser three functions:\n\n- `get_lr` : get the learning rate as it is adjusted\n- `loss_batch` : get the loss value for the particular batch\n- `loss_epoch` : get the entire loss for an epoch iteration","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,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    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":{"execution":{"iopub.status.busy":"2024-06-05T15:19:13.700873Z","iopub.execute_input":"2024-06-05T15:19:13.702620Z","iopub.status.idle":"2024-06-05T15:19:13.714354Z","shell.execute_reply.started":"2024-06-05T15:19:13.702593Z","shell.execute_reply":"2024-06-05T15:19:13.713531Z"},"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}","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:19:13.716484Z","iopub.execute_input":"2024-06-05T15:19:13.716801Z","iopub.status.idle":"2024-06-05T15:19:13.738010Z","shell.execute_reply.started":"2024-06-05T15:19:13.716776Z","shell.execute_reply":"2024-06-05T15:19:13.737076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Main training function\n\ntrain_val is the main function used to train the model on the training set train_dl & evaluate on the validation set val_dl\n\nFUNCTION INPUTS\nThe function requires\n\nPyTorch Classifier , cnn_model (we visualised in section 7)\nTraining parameter dictionary params_train (which contains both hyperparameters & input data)\nFUNCTION PARAMETER DICTIONARY\nThe parameter dictionary requires:\n\nNumber of training iterations,epochs\nTraining & validation data loaders, train_dl, val_dl\nOptimiser & loss function, opt & loss_func\nLearning rate adjustor (on the fly) lr_change\nPOST TRAINING OUTPUT\ntrain_val returns:\n\nThe best performing model on the validation dataset\nThe Loss per iteration\nThe Evaluation Metric per iteration (which is accuracy)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:14:39.505436Z","iopub.execute_input":"2024-06-05T15:14:39.505949Z","iopub.status.idle":"2024-06-05T15:14:39.516879Z","shell.execute_reply.started":"2024-06-05T15:14:39.505912Z","shell.execute_reply":"2024-06-05T15:14:39.515280Z"}}},{"cell_type":"code","source":"from tqdm.notebook import trange, tqdm\n\ndef 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    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    ''' Train Model n_epochs '''\n    \n    for epoch in tqdm(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        '''\n        \n        Train Model Process\n        \n        '''\n        \n        model.train()\n        train_loss, train_metric = loss_epoch(model,loss_func,train_dl,opt)\n\n        # collect losses\n        loss_history[\"train\"].append(train_loss)\n        metric_history[\"train\"].append(train_metric)\n        \n        '''\n        \n        Evaluate Model Process\n        \n        '''\n        \n        model.eval()\n        with torch.no_grad():\n            val_loss, val_metric = loss_epoch(model,loss_func,val_dl)\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":"2024-06-05T15:19:13.739531Z","iopub.execute_input":"2024-06-05T15:19:13.739905Z","iopub.status.idle":"2024-06-05T15:19:13.753687Z","shell.execute_reply.started":"2024-06-05T15:19:13.739872Z","shell.execute_reply":"2024-06-05T15:19:13.752510Z"},"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(),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}\n\n''' Actual Train / Evaluation of CNN Model '''\n# train and validate the model\n\ncnn_model,loss_hist,metric_hist=train_val(cnn_model,params_train)","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:19:13.755013Z","iopub.execute_input":"2024-06-05T15:19:13.756166Z","iopub.status.idle":"2024-06-05T15:27:58.276334Z","shell.execute_reply.started":"2024-06-05T15:19:13.756135Z","shell.execute_reply":"2024-06-05T15:27:58.275262Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import seaborn as sns; sns.set(style='whitegrid')\n\nepochs=params_train[\"epochs\"]\n\nfig,ax = plt.subplots(1,2,figsize=(12,5))\n\nsns.lineplot(x=[*range(1,epochs+1)],y=loss_hist[\"train\"],ax=ax[0],label='loss_hist[\"train\"]')\nsns.lineplot(x=[*range(1,epochs+1)],y=loss_hist[\"val\"],ax=ax[0],label='loss_hist[\"val\"]')\nsns.lineplot(x=[*range(1,epochs+1)],y=metric_hist[\"train\"],ax=ax[1],label='metric_hist[\"train\"]')\nsns.lineplot(x=[*range(1,epochs+1)],y=metric_hist[\"val\"],ax=ax[1],label='metric_hist[\"val\"]')\nplt.title('Convergence History')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:28:36.718764Z","iopub.execute_input":"2024-06-05T15:28:36.719474Z","iopub.status.idle":"2024-06-05T15:28:38.479674Z","shell.execute_reply.started":"2024-06-05T15:28:36.719439Z","shell.execute_reply":"2024-06-05T15:28:38.478696Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class pytorchdata_test(Dataset):\n    \n    def __init__(self, data_dir, transform,data_type=\"train\"):\n        \n        path2data = os.path.join(data_dir,data_type)\n        filenames = os.listdir(path2data)\n        self.full_filenames = [os.path.join(path2data, f) for f in filenames]\n        \n        # labels are in a csv file named train_labels.csv\n        csv_filename=\"sample_submission.csv\"\n        path2csvLabels=os.path.join(data_dir,csv_filename)\n        labels_df=pd.read_csv(path2csvLabels)\n        \n        # set data frame index to id\n        labels_df.set_index(\"id\", inplace=True)\n        \n        # obtain labels from data frame\n        self.labels = [labels_df.loc[filename[:-4]].values[0] for filename in filenames]\n        self.transform = transform       \n        \n    def __len__(self):\n        # return size of dataset\n        return len(self.full_filenames)\n    \n    def __getitem__(self, idx):\n        # open image, apply transforms and return with label\n        image = Image.open(self.full_filenames[idx]) # PIL image\n        image = self.transform(image)\n        return image, self.labels[idx]\n","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:28:45.873038Z","iopub.execute_input":"2024-06-05T15:28:45.873399Z","iopub.status.idle":"2024-06-05T15:28:45.883492Z","shell.execute_reply.started":"2024-06-05T15:28:45.873365Z","shell.execute_reply":"2024-06-05T15:28:45.882377Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:28:49.625113Z","iopub.execute_input":"2024-06-05T15:28:49.625520Z","iopub.status.idle":"2024-06-05T15:28:50.652738Z","shell.execute_reply.started":"2024-06-05T15:28:49.625493Z","shell.execute_reply":"2024-06-05T15:28:50.651462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls '/kaggle/input/histopathologic-cancer-detection/test' | head -n 5","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:28:53.419812Z","iopub.execute_input":"2024-06-05T15:28:53.420234Z","iopub.status.idle":"2024-06-05T15:28:57.215662Z","shell.execute_reply.started":"2024-06-05T15:28:53.420201Z","shell.execute_reply":"2024-06-05T15:28:57.214713Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# load any model weights for the model\ncnn_model.load_state_dict(torch.load('weights.pt'))","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:29:00.381851Z","iopub.execute_input":"2024-06-05T15:29:00.382309Z","iopub.status.idle":"2024-06-05T15:29:00.396006Z","shell.execute_reply.started":"2024-06-05T15:29:00.382273Z","shell.execute_reply":"2024-06-05T15:29:00.394984Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_dir = '/kaggle/input/histopathologic-cancer-detection/'\n\ndata_transformer = transforms.Compose([transforms.ToTensor(),\n                                       transforms.Resize((46,46))])\n\nimg_dataset_test = pytorchdata_test(data_dir,data_transformer,data_type=\"test\")\nprint(len(img_dataset_test), 'samples found')","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:29:04.394032Z","iopub.execute_input":"2024-06-05T15:29:04.395379Z","iopub.status.idle":"2024-06-05T15:29:06.822238Z","shell.execute_reply.started":"2024-06-05T15:29:04.395345Z","shell.execute_reply":"2024-06-05T15:29:06.821175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def inference(model,dataset,device,num_classes=2):\n    \n    len_data=len(dataset)\n    y_out=torch.zeros(len_data,num_classes) # initialize output tensor on CPU\n    y_gt=np.zeros((len_data),dtype=\"uint8\") # initialize ground truth on CPU\n    model=model.to(device) # move model to device\n    \n    with torch.no_grad():\n        for i in tqdm(range(len_data)):\n            x,y=dataset[i]\n            y_gt[i]=y\n            y_out[i]=model(x.unsqueeze(0).to(device))\n\n    return y_out.numpy(),y_gt            ","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:29:09.819216Z","iopub.execute_input":"2024-06-05T15:29:09.820085Z","iopub.status.idle":"2024-06-05T15:29:09.826477Z","shell.execute_reply.started":"2024-06-05T15:29:09.820053Z","shell.execute_reply":"2024-06-05T15:29:09.825420Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_test_out,_ = inference(cnn_model,img_dataset_test, device)  ","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:29:12.872456Z","iopub.execute_input":"2024-06-05T15:29:12.873268Z","iopub.status.idle":"2024-06-05T15:41:54.800262Z","shell.execute_reply.started":"2024-06-05T15:29:12.873234Z","shell.execute_reply":"2024-06-05T15:41:54.799172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# class predictions 0,1\ny_test_pred=np.argmax(y_test_out,axis=1)\nprint(y_test_pred.shape)\nprint(y_test_pred[0:5])","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:42:11.679716Z","iopub.execute_input":"2024-06-05T15:42:11.680622Z","iopub.status.idle":"2024-06-05T15:42:11.687984Z","shell.execute_reply.started":"2024-06-05T15:42:11.680586Z","shell.execute_reply":"2024-06-05T15:42:11.686743Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# probabilities of predicted selection\n# return F.log_softmax(x, dim=1) ie.\npreds = np.exp(y_test_out[:, 1])\nprint(preds.shape)\nprint(preds[0:5])","metadata":{"execution":{"iopub.status.busy":"2024-06-05T15:42:14.403042Z","iopub.execute_input":"2024-06-05T15:42:14.403412Z","iopub.status.idle":"2024-06-05T15:42:14.410306Z","shell.execute_reply.started":"2024-06-05T15:42:14.403385Z","shell.execute_reply":"2024-06-05T15:42:14.409287Z"},"trusted":true},"execution_count":null,"outputs":[]}]}