{"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":"markdown","source":"# <center>Diabetic Retinopathy Detection using PyTorch<center>","metadata":{}},{"cell_type":"markdown","source":"<img src = 'https://raw.githubusercontent.com/dimitreOliveira/APTOS2019BlindnessDetection/master/Assets/banner.png' >","metadata":{}},{"cell_type":"markdown","source":"### What is Diabetic Retinopathy?\nDiabetic Retinopathy is a condition that can cause Vision loss and Blindness in the people who have Diabetes. It affects the blood vessels in the Retina. It is one of the leading cause of Blindness across the world. Diabetic retinopathy may not have any symptoms at first, but finding it early can help you take steps to protect your vision. \n### Problem Statement :\nThe problem is to perform Image Classification. Given an image (fundus photographs), You have to predict which class does the given image belongs to. The Classes to predict are :\n*  0 - Normal <br>\n*  1 - Mild <br>\n*  2 - Moderate <br>\n*  3 - Severe <br>\n*  4 - Proliferative <br>","metadata":{}},{"cell_type":"markdown","source":"<img src = 'https://repository-images.githubusercontent.com/195603342/63983100-b4a6-11e9-846c-99b9465f7b3b'>","metadata":{}},{"cell_type":"markdown","source":"## Import Libraries","metadata":{}},{"cell_type":"code","source":"import pandas as pd #For reading csv files.\nimport numpy as np \nimport matplotlib.pyplot as plt #For plotting.\n\nimport PIL.Image as Image #For working with image files.\n\n#Importing torch\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom torch.utils.data import Dataset,DataLoader #For working with data.\n\nfrom torchvision import models,transforms #For pretrained models,image transformations.","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:10.545575Z","iopub.execute_input":"2022-03-09T10:11:10.545907Z","iopub.status.idle":"2022-03-09T10:11:12.212233Z","shell.execute_reply.started":"2022-03-09T10:11:10.545879Z","shell.execute_reply":"2022-03-09T10:11:12.211204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #Use GPU if it's available or else use CPU.\nprint(device) #Prints the device we're using.","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:12.214313Z","iopub.execute_input":"2022-03-09T10:11:12.214759Z","iopub.status.idle":"2022-03-09T10:11:12.312879Z","shell.execute_reply.started":"2022-03-09T10:11:12.214718Z","shell.execute_reply":"2022-03-09T10:11:12.311463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## EDA","metadata":{}},{"cell_type":"markdown","source":"I am using only the data from https://www.kaggle.com/c/aptos2019-blindness-detection/data. If you've more computing resources, you can also use the data from https://www.kaggle.com/c/diabetic-retinopathy-detection/data.","metadata":{}},{"cell_type":"code","source":"path = \"/kaggle/input/aptos2019-blindness-detection/\"\n\ntrain_df = pd.read_csv(f\"{path}train.csv\")\nprint(f'No.of.training_samples: {len(train_df)}')\n\ntest_df = pd.read_csv(f'{path}test.csv')\nprint(f'No.of.testing_samples: {len(test_df)}')","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:12.317236Z","iopub.execute_input":"2022-03-09T10:11:12.318065Z","iopub.status.idle":"2022-03-09T10:11:12.344862Z","shell.execute_reply.started":"2022-03-09T10:11:12.318018Z","shell.execute_reply":"2022-03-09T10:11:12.343511Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Histogram of label counts.\ntrain_df.diagnosis.hist()\nplt.xticks([0,1,2,3,4])\nplt.grid(False)\nplt.show() ","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:12.346957Z","iopub.execute_input":"2022-03-09T10:11:12.347676Z","iopub.status.idle":"2022-03-09T10:11:12.559547Z","shell.execute_reply.started":"2022-03-09T10:11:12.347632Z","shell.execute_reply":"2022-03-09T10:11:12.558171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#As you can see,the data is imbalanced.\n#So we've to calculate weights for each class,which can be used in calculating loss.\n\nfrom sklearn.utils import class_weight #For calculating weights for each class.\nclass_weights = class_weight.compute_class_weight(class_weight='balanced',classes=np.array([0,1,2,3,4]),y=train_df['diagnosis'].values)\nclass_weights = torch.tensor(class_weights,dtype=torch.float).to(device)\n \nprint(class_weights) #Prints the calculated weights for the classes.","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:12.561037Z","iopub.execute_input":"2022-03-09T10:11:12.561563Z","iopub.status.idle":"2022-03-09T10:11:18.837663Z","shell.execute_reply.started":"2022-03-09T10:11:12.561517Z","shell.execute_reply":"2022-03-09T10:11:18.836499Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#For getting a random image from our training set.\nnum = int(np.random.randint(0,len(train_df)-1,(1,))) #Picks a random number.\nsample_image = (f'{path}train_images/{train_df[\"id_code\"][num]}.png')#Image file.\nsample_image = Image.open(sample_image) \nplt.imshow(sample_image)\nplt.axis('off')\nplt.title(f'Class: {train_df[\"diagnosis\"][num]}') #Class of the random image.\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:18.839483Z","iopub.execute_input":"2022-03-09T10:11:18.840224Z","iopub.status.idle":"2022-03-09T10:11:19.531408Z","shell.execute_reply.started":"2022-03-09T10:11:18.840171Z","shell.execute_reply":"2022-03-09T10:11:19.530184Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preprocess the Data","metadata":{}},{"cell_type":"code","source":"class dataset(Dataset): # Inherits from the Dataset class.\n    '''\n    dataset class overloads the __init__, __len__, __getitem__ methods of the Dataset class. \n    \n    Attributes :\n        df:  DataFrame object for the csv file.\n        data_path: Location of the dataset.\n        image_transform: Transformations to apply to the image.\n        train: A boolean indicating whether it is a training_set or not.\n    '''\n    \n    def __init__(self,df,data_path,image_transform=None,train=True): # Constructor.\n        super(Dataset,self).__init__() #Calls the constructor of the Dataset class.\n        self.df = df\n        self.data_path = data_path\n        self.image_transform = image_transform\n        self.train = train\n        \n    def __len__(self):\n        return len(self.df) #Returns the number of samples in the dataset.\n    \n    def __getitem__(self,index):\n        image_id = self.df['id_code'][index]\n        image = Image.open(f'{self.data_path}/{image_id}.png') #Image.\n        if self.image_transform :\n            image = self.image_transform(image) #Applies transformation to the image.\n        \n        if self.train :\n            label = self.df['diagnosis'][index] #Label.\n            return image,label #If train == True, return image & label.\n        \n        else:\n            return image #If train != True, return image.\n            ","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:19.532984Z","iopub.execute_input":"2022-03-09T10:11:19.533401Z","iopub.status.idle":"2022-03-09T10:11:19.543556Z","shell.execute_reply.started":"2022-03-09T10:11:19.533351Z","shell.execute_reply":"2022-03-09T10:11:19.542244Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_transform = transforms.Compose([transforms.Resize([512,512]),\n                                      transforms.ToTensor(),\n                                      transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]) #Transformations to apply to the image.\ndata_set = dataset(train_df,f'{path}train_images',image_transform=image_transform)\n\n#Split the data_set so that valid_set contains 0.1 samples of the data_set. \ntrain_set,valid_set = torch.utils.data.random_split(data_set,[3302,360])","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:19.545398Z","iopub.execute_input":"2022-03-09T10:11:19.546237Z","iopub.status.idle":"2022-03-09T10:11:19.563618Z","shell.execute_reply.started":"2022-03-09T10:11:19.546195Z","shell.execute_reply":"2022-03-09T10:11:19.562412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataloader = DataLoader(train_set,batch_size=32,shuffle=True) #DataLoader for train_set.\nvalid_dataloader = DataLoader(valid_set,batch_size=32,shuffle=False) #DataLoader for validation_set.","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:19.565998Z","iopub.execute_input":"2022-03-09T10:11:19.566733Z","iopub.status.idle":"2022-03-09T10:11:19.572753Z","shell.execute_reply.started":"2022-03-09T10:11:19.566658Z","shell.execute_reply":"2022-03-09T10:11:19.571424Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Build the Model","metadata":{}},{"cell_type":"code","source":"model = models.alexnet(pretrained=True)\nmodel.classifier[6]= torch.nn.Linear(4096,5)\nprint(model)","metadata":{"execution":{"iopub.status.busy":"2022-03-09T10:11:34.668473Z","iopub.execute_input":"2022-03-09T10:11:34.668914Z","iopub.status.idle":"2022-03-09T10:11:55.485687Z","shell.execute_reply.started":"2022-03-09T10:11:34.668859Z","shell.execute_reply":"2022-03-09T10:11:55.482045Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = model.to(device) #Moves the model to the device.","metadata":{"execution":{"iopub.status.busy":"2022-03-01T12:46:56.30618Z","iopub.execute_input":"2022-03-01T12:46:56.306552Z","iopub.status.idle":"2022-03-01T12:46:56.344421Z","shell.execute_reply.started":"2022-03-01T12:46:56.30651Z","shell.execute_reply":"2022-03-01T12:46:56.343669Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Create functions for Training & Validation","metadata":{}},{"cell_type":"code","source":"def train(dataloader,model,loss_fn,optimizer):\n    '''\n    train function updates the weights of the model based on the\n    loss using the optimizer in order to get a lower loss.\n    \n    Args :\n         dataloader: Iterator for the batches in the data_set.\n         model: Given an input produces an output by multiplying the input with the model weights.\n         loss_fn: Calculates the discrepancy between the label & the model's predictions.\n         optimizer: Updates the model weights.\n         \n    Returns :\n         Average loss per batch which is calculated by dividing the losses for all the batches\n         with the number of batches.\n    '''\n\n    model.train() #Sets the model for training.\n    \n    total = 0\n    correct = 0\n    running_loss = 0\n    \n    for batch,(x,y) in enumerate(dataloader): #Iterates through the batches.\n        \n        output = model(x.to(device)) #model's predictions.\n        loss   = loss_fn(output,y.to(device)) #loss calculation.\n       \n        running_loss += loss.item()\n        \n        total        += y.size(0)\n        predictions   = output.argmax(dim=1).cpu().detach() #Index for the highest score for all the samples in the batch.\n        correct      += (predictions == y.cpu().detach()).sum().item() #No.of.cases where model's predictions are equal to the label.\n        \n        optimizer.zero_grad() #Gradient values are set to zero.\n        loss.backward() #Calculates the gradients.\n        optimizer.step() #Updates the model weights.\n             \n    \n    avg_loss = running_loss/len(dataloader) # Average loss for a single batch\n    \n    print(f'\\nTraining Loss = {avg_loss:.6f}',end='\\t')\n    print(f'Accuracy on Training set = {100*(correct/total):.6f}% [{correct}/{total}]') #Prints the Accuracy.\n    \n    return avg_loss","metadata":{"execution":{"iopub.status.busy":"2022-03-01T12:46:56.345843Z","iopub.execute_input":"2022-03-01T12:46:56.346211Z","iopub.status.idle":"2022-03-01T12:46:56.354402Z","shell.execute_reply.started":"2022-03-01T12:46:56.346175Z","shell.execute_reply":"2022-03-01T12:46:56.353415Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def validate(dataloader,model,loss_fn):\n    '''\n    validate function calculates the average loss per batch and the accuracy of the model's predictions.\n    \n    Args :\n         dataloader: Iterator for the batches in the data_set.\n         model: Given an input produces an output by multiplying the input with the model weights.\n         loss_fn: Calculates the discrepancy between the label & the model's predictions.\n    \n    Returns :\n         Average loss per batch which is calculated by dividing the losses for all the batches\n         with the number of batches.\n    '''\n    \n    model.eval() #Sets the model for evaluation.\n    \n    total = 0\n    correct = 0\n    running_loss = 0\n    \n    with torch.no_grad(): #No need to calculate the gradients.\n        \n        for x,y in dataloader:\n            \n            output        = model(x.to(device)) #model's output.\n            loss          = loss_fn(output,y.to(device)).item() #loss calculation.\n            running_loss += loss\n            \n            total        += y.size(0)\n            predictions   = output.argmax(dim=1).cpu().detach()\n            correct      += (predictions == y.cpu().detach()).sum().item()\n            \n    avg_loss = running_loss/len(dataloader) #Average loss per batch.      \n    \n    print(f'\\nValidation Loss = {avg_loss:.6f}',end='\\t')\n    print(f'Accuracy on Validation set = {100*(correct/total):.6f}% [{correct}/{total}]') #Prints the Accuracy.\n    \n    return avg_loss","metadata":{"execution":{"iopub.status.busy":"2022-03-01T12:46:56.355791Z","iopub.execute_input":"2022-03-01T12:46:56.356123Z","iopub.status.idle":"2022-03-01T12:46:56.366106Z","shell.execute_reply.started":"2022-03-01T12:46:56.356087Z","shell.execute_reply":"2022-03-01T12:46:56.365392Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Optimize the Model","metadata":{}},{"cell_type":"code","source":"def optimize(train_dataloader,valid_dataloader,model,loss_fn,optimizer,nb_epochs):\n    '''\n    optimize function calls the train & validate functions for (nb_epochs) times.\n    \n    Args :\n        train_dataloader: DataLoader for the train_set.\n        valid_dataloader: DataLoader for the valid_set.\n        model: Given an input produces an output by multiplying the input with the model weights.\n        loss_fn: Calculates the discrepancy between the label & the model's predictions.\n        optimizer: Updates the model weights.\n        nb_epochs: Number of epochs.\n        \n    Returns :\n        Tuple of lists containing losses for all the epochs.\n    '''\n    #Lists to store losses for all the epochs.\n    train_losses = []\n    valid_losses = []\n\n    for epoch in range(nb_epochs):\n        print(f'\\nEpoch {epoch+1}/{nb_epochs}')\n        print('-------------------------------')\n        train_loss = train(train_dataloader,model,loss_fn,optimizer) #Calls the train function.\n        train_losses.append(train_loss)\n        valid_loss = validate(valid_dataloader,model,loss_fn) #Calls the validate function.\n        valid_losses.append(valid_loss)\n    \n    print('\\nTraining has completed!')\n    \n    return train_losses,valid_losses","metadata":{"execution":{"iopub.status.busy":"2022-03-01T12:46:56.367312Z","iopub.execute_input":"2022-03-01T12:46:56.367886Z","iopub.status.idle":"2022-03-01T12:46:56.376549Z","shell.execute_reply.started":"2022-03-01T12:46:56.367849Z","shell.execute_reply":"2022-03-01T12:46:56.37566Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss_fn   = nn.CrossEntropyLoss(weight=class_weights) #CrossEntropyLoss with class_weights.\noptimizer = torch.optim.SGD(model.parameters(),lr=0.01) \nnb_epochs = 60\n#Call the optimize function.\ntrain_losses, valid_losses = optimize(train_dataloader,valid_dataloader,model,loss_fn,optimizer,nb_epochs)","metadata":{"execution":{"iopub.status.busy":"2022-03-01T12:46:56.377928Z","iopub.execute_input":"2022-03-01T12:46:56.378375Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Plot the graph of train_losses & valid_losses against nb_epochs.\nepochs = range(nb_epochs)\nplt.plot(epochs, train_losses, 'g', label='Training loss')\nplt.plot(epochs, valid_losses, 'b', label='validation loss')\nplt.title('Training and Validation loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Save the model\ntorch.save(model,'CNN_for_DR.pth')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Testing the model","metadata":{}},{"cell_type":"code","source":"test_set = dataset(test_df,f'{path}test_images',image_transform = image_transform,train = False )\n\ntest_dataloader = DataLoader(test_set, batch_size=32, shuffle=False) #DataLoader for test_set.","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def test(dataloader,model):\n    '''\n    test function predicts the labels given an image batches.\n    \n    Args :\n         dataloader: DataLoader for the test_set.\n         model: Given an input produces an output by multiplying the input with the model weights.\n         \n    Returns :\n         List of predicted labels.\n    '''\n    \n    model.eval() #Sets the model for evaluation.\n    \n    labels = [] #List to store the predicted labels.\n    \n    with torch.no_grad():\n        \n        for batch,x in enumerate(dataloader):\n            \n            output = model(x.to(device))\n            \n            predictions = output.argmax(dim=1).cpu().detach().tolist() #Predicted labels for an image batch.\n            labels.extend(predictions)\n                \n    print('Testing has completed')\n            \n    return labels                ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels = test(test_dataloader,model) #Calls the test function.","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Making submissions","metadata":{}},{"cell_type":"code","source":"labels = np.array(labels)\nsubmission_df = pd.DataFrame({'id_code':test_df['id_code'],'diagnosis':labels}) #DataFrame with id_code's and predicted labels.\n\nsubmission_df.to_csv('submission.csv',index=False) #csv file for submission.\nprint(\"Your submission was successfully saved!\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can able to increase the accuracy of the model by various ways, like increasing the dataset size, increasing the model complexity, using ensemble models, and increasing the number of epochs.\n\nIf you've found any mistakes, or any idea for improvement, or can't able to understand a line of code, then comment down below. I can help you out.","metadata":{}}]}