During this compeitition the task was to classify various skin cancer images in one of the seven categories.
We performed EDA to check the distribution of the data by plotting various curves.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
df=pd.read_csv('data/train.csv')
dt=pd.read_csv('data/sample_submission.csv')
print("Missing Values")
print(df.isna().sum())
Missing Values
image 0
label 0
dtype: int64
No missing labels were found in the data!
The distribution histogram for train data is as follows:-
print(pd.value_counts(df['label']))
df.hist()
2 2026
1 1991
5 1522
4 1025
3 493
0 442
6 197
Name: label, dtype: int64
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x00000188F4DB0608>]],
dtype=object)
The following augmentations were applied in our approach:-
Applying random rotations to each of the images.
import cv2
from scipy import ndimage
imgs=[cv2.imread('data/data_images/train/train{}.jpg'.format(i)) for i in [2,26,51,57]]
rotated_images=[ndimage.rotate(img, np.random.random()*180) for img in imgs]
f, axarr = plt.subplots(2,2)
axarr[0,0].imshow(imgs[0])
axarr[0,1].imshow(imgs[1])
axarr[1,0].imshow(imgs[2])
axarr[1,1].imshow(imgs[3])
f, axarr = plt.subplots(2,2)
axarr[0,0].imshow(rotated_images[0])
axarr[0,1].imshow(rotated_images[1])
axarr[1,0].imshow(rotated_images[2])
axarr[1,1].imshow(rotated_images[3])
<matplotlib.image.AxesImage at 0x188f77d4d88>
Applying bounding rectangle to each image as some of the images have very less ROI(rest as black).
import cv2
f, axarr = plt.subplots(2,2)
axarr[0,0].imshow(imgs[0])
axarr[0,1].imshow(imgs[1])
axarr[1,0].imshow(imgs[2])
axarr[1,1].imshow(imgs[3])
<matplotlib.image.AxesImage at 0x188f9155d48>
def crop_and_resize_images(imgs, resize_size = 700):
cropped_imgs = []
for image in imgs:
img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img, 30, 255, cv2.THRESH_OTSU)
contours, _ = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[-2:]
idx = 0
ls_xmin = []
ls_ymin = []
ls_xmax = []
ls_ymax = []
for cnt in contours:
idx += 1
x,y,w,h = cv2.boundingRect(cnt)
ls_xmin.append(x)
ls_ymin.append(y)
ls_xmax.append(x + w)
ls_ymax.append(y + h)
xmin = min(ls_xmin)
ymin = min(ls_ymin)
xmax = max(ls_xmax)
ymax = max(ls_ymax)
roi = image[ymin:ymax,xmin:xmax]
resized_roi = cv2.resize(roi, (resize_size, resize_size))
cropped_imgs.append(resized_roi)
return cropped_imgs
f, axarr = plt.subplots(2,2)
cropped_imgs=crop_and_resize_images(imgs,700)
axarr[0,0].imshow(cropped_imgs[0])
axarr[0,1].imshow(cropped_imgs[1])
axarr[1,0].imshow(cropped_imgs[2])
axarr[1,1].imshow(cropped_imgs[3])
<matplotlib.image.AxesImage at 0x188fa83b988>
Applying random crop to the images.
def get_random_crop(image, crop_height=450, crop_width=450):
max_x = image.shape[1] - crop_width
max_y = image.shape[0] - crop_height
x = np.random.randint(0, max_x)
y = np.random.randint(0, max_y)
crop = image[y: y + crop_height, x: x + crop_width]
img=cv2.resize(crop,(700,700))
return img
cropped=[get_random_crop(img) for img in imgs]
f, axarr = plt.subplots(2,2)
axarr[0,0].imshow(cropped[0])
axarr[0,1].imshow(cropped[1])
axarr[1,0].imshow(cropped[2])
axarr[1,1].imshow(cropped[3])
<matplotlib.image.AxesImage at 0x188fbc20988>
Random noise addition in the images.
def add_random_noise(imgs,max_std=30):
noised=[]
for img in imgs:
img=img.astype('float32')
img[:,:,0]+=np.random.random(img.shape[:-1])*np.random.random()*max_std
img[:,:,1]+=np.random.random(img.shape[:-1])*np.random.random()*max_std
img[:,:,2]+=np.random.random(img.shape[:-1])*np.random.random()*max_std
img=img.astype('uint8')
noised.append(img)
return noised
noised_images=add_random_noise(imgs)
f, axarr = plt.subplots(2,2)
axarr[0,0].imshow(noised_images[0])
axarr[0,1].imshow(noised_images[1])
axarr[1,0].imshow(noised_images[2])
axarr[1,1].imshow(noised_images[3])
<matplotlib.image.AxesImage at 0x188fc32ed88>
Apply Cutmix augmentation for the dataset.
import numpy as np
import random
from torch.utils.data.dataset import Dataset
class CutMix(Dataset):
def __init__(self, dataset, num_classes, num_mix=1, beta=1., prob=1.0):
self.dataset = dataset
self.num_classes = num_classes
self.num_mix = num_mix
self.beta = beta
self.prob = prob
def __getitem__(self, index):
img, lb = self.dataset[index]
lb_onehot = [onehot(self.num_classes[i], lb[i]) for i in range(len(self.num_classes))]
for _ in range(self.num_mix):
r = np.random.rand(1)
if self.beta <= 0 or r > self.prob:
continue
# generate mixed sample
lam = np.random.beta(self.beta, self.beta)
rand_index = random.choice(range(len(self)))
img2, lb2 = self.dataset[rand_index]
lb2_onehot = [onehot(self.num_classes[i], lb2[i]) for i in range(len(self.num_classes))]
bbx1, bby1, bbx2, bby2 = rand_bbox(img.size(), lam)
img[:, bbx1:bbx2, bby1:bby2] = img2[:, bbx1:bbx2, bby1:bby2]
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (img.size()[-1] * img.size()[-2]))
for i in range(len(lb_onehot)):
lb_onehot[i] = lb_onehot[i] * lam + lb2_onehot[i] * (1. - lam)
return img, lb_onehot
def __len__(self):
return len(self.dataset)
def onehot(size, target):
vec = torch.zeros(size, dtype=torch.float32)
vec[target] = 1.
return vec
def rand_bbox(size, lam):
if len(size) == 4:
W = size[2]
H = size[3]
elif len(size) == 3:
W = size[1]
H = size[2]
else:
raise Exception
cut_rat = np.sqrt(1. - lam)
cut_w = np.int(W * cut_rat)
cut_h = np.int(H * cut_rat)
# uniform
cx = np.random.randint(W)
cy = np.random.randint(H)
bbx1 = np.clip(cx - cut_w // 2, 0, W)
bby1 = np.clip(cy - cut_h // 2, 0, H)
bbx2 = np.clip(cx + cut_w // 2, 0, W)
bby2 = np.clip(cy + cut_h // 2, 0, H)
return bbx1, bby1, bbx2, bby2
Our approach includes trying several different models iteratively. Some of which are described below:-
import torch
import torch.nn.functional as F
from torchvision import models
import pretrainedmodels
from efficientnet_pytorch import EfficientNet
Resnet-50
class Resnet_50(torch.nn.Module):
def __init__(self):
super().__init__()
model=models.resnet50(pretrained='imagenet')
self.layer1=model.conv1
self.layer2=model.bn1
self.layer3=model.relu
self.layer4=model.maxpool
self.layer5=model.layer1
self.layer6=model.layer2
self.layer7=model.layer3
self.layer8=model.layer4
self.layer9=model.avgpool
self.fc1=torch.nn.Linear(2048,7)
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
x=self.layer3(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=self.layer8(x)
x=self.layer9(x)
x=torch.flatten(x,1)
x=self.fc1(x)
return x
Resnet-101
class Resnet_101(torch.nn.Module):
def __init__(self):
super().__init__()
model=models.resnet101(pretrained='imagenet')
self.layer1=model.conv1
self.layer2=model.bn1
self.layer3=model.relu
self.layer4=model.maxpool
self.layer5=model.layer1
self.layer6=model.layer2
self.layer7=model.layer3
self.layer8=model.layer4
self.layer9=model.avgpool
self.fc1=torch.nn.Linear(2048,7)
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
x=self.layer3(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=self.layer8(x)
x=self.layer9(x)
x=torch.flatten(x,1)
x=self.fc1(x)
return x
Resnet-152
class Resnet_152(torch.nn.Module):
def __init__(self):
super().__init__()
model=models.resnet152(pretrained='imagenet')
self.layer1=model.conv1
self.layer2=model.bn1
self.layer3=model.relu
self.layer4=model.maxpool
self.layer5=model.layer1
self.layer6=model.layer2
self.layer7=model.layer3
self.layer8=model.layer4
self.layer9=model.avgpool
self.fc1=torch.nn.Linear(2048,7)
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
x=self.layer3(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=self.layer8(x)
x=self.layer9(x)
x=torch.flatten(x,1)
x=self.fc1(x)
return x
Densenet-121
class Densenet_121(torch.nn.Module):
def __init__(self):
model=models.densenet121(pretrained='imagenet')
self.layer1=model.features
self.fc1=torch.nn.Linear(1024,7)
def forward(self,x):
x=self.layer1(x)
x=torch.fllatten(x,1)
x=self.fc1(x)
return x
Densenet-169
class Densenet_169(torch.nn.Module):
def __init__(self):
model=models.densenet169(pretrained='imagenet')
self.layer1=model.features
self.fc1=torch.nn.Linear(1024,7)
def forward(self,x):
x=self.layer1(x)
x=torch.fllatten(x,1)
x=self.fc1(x)
return x
Densenet-201
class Densenet_201(torch.nn.Module):
def __init__(self):
model=models.densenet201(pretrained='imagenet')
self.layer1=model.features
self.fc1=torch.nn.Linear(1024,7)
def forward(self,x):
x=self.layer1(x)
x=torch.fllatten(x,1)
x=self.fc1(x)
return x
Efficientnet-b0
class Efficientnet_b0(torch.nn.Module):
def __init__(self):
super().__init__()
model=EfficientNet.from_name('efficientnet-b0')
self.layer1=model._conv_stem
self.layer2=model._bn0
self.layer3=model._blocks
self.layer4=model._conv_head
self.layer5=model._bn1
self.layer6=model._avg_pooling
self.layer7=model._dropout
self.layer8=torch.nn.Linear(1280,7)
self.layer9=model._swish
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
for layer in self.layer3:
x=layer(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=torch.flatten(x,1)
x=self.layer8(x)
x=self.layer9(x)
return x
Efficientnet-b1
class Efficientnet_b1(torch.nn.Module):
def __init__(self):
super().__init__()
model=EfficientNet.from_name('efficientnet-b1')
self.layer1=model._conv_stem
self.layer2=model._bn0
self.layer3=model._blocks
self.layer4=model._conv_head
self.layer5=model._bn1
self.layer6=model._avg_pooling
self.layer7=model._dropout
self.layer8=torch.nn.Linear(1280,7)
self.layer9=model._swish
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
for layer in self.layer3:
x=layer(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=torch.flatten(x,1)
x=self.layer8(x)
x=self.layer9(x)
return x
Efficientnet-b2
class Efficientnet_b2(torch.nn.Module):
def __init__(self):
super().__init__()
model=EfficientNet.from_name('efficientnet-b2')
self.layer1=model._conv_stem
self.layer2=model._bn0
self.layer3=model._blocks
self.layer4=model._conv_head
self.layer5=model._bn1
self.layer6=model._avg_pooling
self.layer7=model._dropout
self.layer8=torch.nn.Linear(1280,7)
self.layer9=model._swish
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
for layer in self.layer3:
x=layer(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=torch.flatten(x,1)
x=self.layer8(x)
x=self.layer9(x)
return x
Efficientnet-b3
class Efficientnet_b2(torch.nn.Module):
def __init__(self):
super().__init__()
model=EfficientNet.from_name('efficientnet-b3')
self.layer1=model._conv_stem
self.layer2=model._bn0
self.layer3=model._blocks
self.layer4=model._conv_head
self.layer5=model._bn1
self.layer6=model._avg_pooling
self.layer7=model._dropout
self.layer8=torch.nn.Linear(1280,7)
self.layer9=model._swish
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
for layer in self.layer3:
x=layer(x)
x=self.layer4(x)
x=self.layer5(x)
x=self.layer6(x)
x=self.layer7(x)
x=torch.flatten(x,1)
x=self.layer8(x)
x=self.layer9(x)
return x
Seresnet-50
class seresnet_50(torch.nn.Module):
def __init__(self):
super().__init__()
se_resnet50 = pretrainedmodels.se_resnet50(pretrained=None)
self.layer0 = se_resnet50.layer0
self.layer1 = se_resnet50.layer1
self.layer2 = se_resnet50.layer2
self.layer3 = se_resnet50.layer3
self.layer4 = se_resnet50.layer4
self.layer5 = torch.nn.Conv2d(2048, 256, (3, 3))
self.fc1 = torch.nn.Linear(4096, 7)
def forward(self, x):
x = self.layer0(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.layer5(x)
x = F.adaptive_avg_pool2d(x, (4, 4))
x = torch.flatten(x, 1)
x = self.fc1(x)
return x
Seresnext-50 32x4d
class seresnext_50_32x4d(torch.nn.Module):
def __init__(self):
super().__init__()
se_resnet50 = pretrainedmodels.se_resnext50_32x4d(pretrained=None)
self.layer0 = se_resnet50.layer0
self.layer1 = se_resnet50.layer1
self.layer2 = se_resnet50.layer2
self.layer3 = se_resnet50.layer3
self.layer4 = se_resnet50.layer4
self.layer5 = torch.nn.Conv2d(2048, 256, (3, 3))
self.fc1 = torch.nn.Linear(4096, 7)
def forward(self, x):
x = self.layer0(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.layer5(x)
x = F.adaptive_avg_pool2d(x, (4, 4))
x = torch.flatten(x, 1)
x = self.fc1(x)
return x
After training various models which are described above after appying various augmentations, we applied some further techniques to increase the metric as described below:-
-
Pseudo-labelling: In this technique, we predicted labels on test data and fed the test data into the model using predicted labels as output and trained the model further. After every epoch, we tested the model and updated the labels and trained again.
-
Test-time augmentation: In this technique, we used the above mentioned augmentations(except cutmix) to make about 15 augmentated images per original image(total 16 images). All the images are fed through models and majority voting is applied the various outputs of each model.
-
Schostic Weight Averaging (SWA): In this technique, we averaged the weights for each layer in every model for the whole training cycle.
-
Ensembling: In this technique, we used majority voting after testing and calculating final labels for each model.After this step, we saved and submitted the labels on compeitition submissions page.