{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"},{"sourceId":1236170,"sourceType":"datasetVersion","datasetId":708434}],"dockerImageVersionId":30636,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom albumentations import Compose, Flip, CropAndPad, Transpose\nimport os\nfrom tqdm.auto import tqdm\nimport cv2","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-03-03T07:11:20.698981Z","iopub.execute_input":"2024-03-03T07:11:20.699604Z","iopub.status.idle":"2024-03-03T07:11:23.790569Z","shell.execute_reply.started":"2024-03-03T07:11:20.699568Z","shell.execute_reply":"2024-03-03T07:11:23.789024Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/melanoma224/train.csv')\ntrain.head()","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:23.792212Z","iopub.execute_input":"2024-03-03T07:11:23.793688Z","iopub.status.idle":"2024-03-03T07:11:23.935825Z","shell.execute_reply.started":"2024-03-03T07:11:23.793654Z","shell.execute_reply":"2024-03-03T07:11:23.934904Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Umbalanced dataset","metadata":{}},{"cell_type":"code","source":"plt.subplot(1,2,1)\ntrain['target'].hist()\nplt.subplot(1,2,2)\ntrain['age_approx'].hist(bins=len(train['age_approx'].unique()))\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:23.937035Z","iopub.execute_input":"2024-03-03T07:11:23.937722Z","iopub.status.idle":"2024-03-03T07:11:24.359694Z","shell.execute_reply.started":"2024-03-03T07:11:23.937687Z","shell.execute_reply":"2024-03-03T07:11:24.35878Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"imgs = os.listdir('/kaggle/input/melanoma224/jpeg224/train')","metadata":{"execution":{"iopub.status.busy":"2024-03-02T14:03:59.661138Z","iopub.execute_input":"2024-03-02T14:03:59.661499Z","iopub.status.idle":"2024-03-02T14:04:00.049187Z","shell.execute_reply.started":"2024-03-02T14:03:59.661471Z","shell.execute_reply":"2024-03-02T14:04:00.048306Z"}}},{"cell_type":"markdown","source":"# One hot encoding for metadata","metadata":{}},{"cell_type":"code","source":"from sklearn.preprocessing import OneHotEncoder\n\ncols = [\"sex\", \"age_approx\", \"anatom_site_general_challenge\"]\nselected_cols = train[cols]\noh_encoder = OneHotEncoder()\noh_encoded = oh_encoder.fit_transform(selected_cols)\ncategories = oh_encoder.categories_","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:24.362539Z","iopub.execute_input":"2024-03-03T07:11:24.362854Z","iopub.status.idle":"2024-03-03T07:11:24.410687Z","shell.execute_reply.started":"2024-03-03T07:11:24.362828Z","shell.execute_reply":"2024-03-03T07:11:24.409474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_columns = oh_encoder.get_feature_names_out(cols)\ntrain_clean = pd.DataFrame(oh_encoded.toarray(), columns=new_columns)\ntrain_clean.head()","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:24.412444Z","iopub.execute_input":"2024-03-03T07:11:24.413008Z","iopub.status.idle":"2024-03-03T07:11:24.473314Z","shell.execute_reply.started":"2024-03-03T07:11:24.412967Z","shell.execute_reply":"2024-03-03T07:11:24.4724Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train set and validation set","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n_, val = train_test_split(train, test_size=0.33, shuffle=True, random_state=42, stratify=train['target'])","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:24.475293Z","iopub.execute_input":"2024-03-03T07:11:24.475867Z","iopub.status.idle":"2024-03-03T07:11:24.518346Z","shell.execute_reply.started":"2024-03-03T07:11:24.475833Z","shell.execute_reply":"2024-03-03T07:11:24.517175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# PyTorch baseline","metadata":{}},{"cell_type":"code","source":"import torch\nimport torchvision\nfrom skimage import io\nfrom fastprogress import master_bar, progress_bar\n\ntorch.__version__","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:24.519777Z","iopub.execute_input":"2024-03-03T07:11:24.520654Z","iopub.status.idle":"2024-03-03T07:11:27.96302Z","shell.execute_reply.started":"2024-03-03T07:11:24.520615Z","shell.execute_reply":"2024-03-03T07:11:27.962071Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = 'cuda' if torch.cuda.is_available() else 'cpu'\ndevice","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:27.964253Z","iopub.execute_input":"2024-03-03T07:11:27.964755Z","iopub.status.idle":"2024-03-03T07:11:28.002568Z","shell.execute_reply.started":"2024-03-03T07:11:27.964713Z","shell.execute_reply":"2024-03-03T07:11:28.001609Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train['path'] = [f'/kaggle/input/melanoma224/jpeg224/train/{img}.jpg' for img in train['image_name']]\nval['path'] = [f'/kaggle/input/melanoma224/jpeg224/train/{img}.jpg' for img in val['image_name']]","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:28.003751Z","iopub.execute_input":"2024-03-03T07:11:28.004034Z","iopub.status.idle":"2024-03-03T07:11:28.031109Z","shell.execute_reply.started":"2024-03-03T07:11:28.00401Z","shell.execute_reply":"2024-03-03T07:11:28.030253Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Pytorch dataset","metadata":{}},{"cell_type":"code","source":"def hair_remove(image):\n    # convert image to grayScale\n    grayScale = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)\n    \n    # kernel for morphologyEx\n    kernel = cv2.getStructuringElement(1,(17,17))\n    \n    # apply MORPH_BLACKHAT to grayScale image\n    blackhat = cv2.morphologyEx(grayScale, cv2.MORPH_BLACKHAT, kernel)\n    \n    # apply thresholding to blackhat\n    _,threshold = cv2.threshold(blackhat,10,255,cv2.THRESH_BINARY)\n    \n    # inpaint with original image and threshold image\n    final_image = cv2.inpaint(image,threshold,1,cv2.INPAINT_TELEA)\n    return final_image\n","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:28.03219Z","iopub.execute_input":"2024-03-03T07:11:28.032443Z","iopub.status.idle":"2024-03-03T07:11:28.037954Z","shell.execute_reply.started":"2024-03-03T07:11:28.032421Z","shell.execute_reply":"2024-03-03T07:11:28.037046Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def remove_shade(image, ref_image):\n    # Flat Field Correction\n    ratio = cv2.divide(ref_image, image)\n    corrected = cv2.divide(image, ratio)\n\n    # Homomorphic Filtering\n    dst = cv2.detailEnhance(corrected)\n\n    # Histogram Equalization\n    img_yuv = cv2.cvtColor(dst, cv2.COLOR_BGR2YUV)\n    img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])\n    img_output = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)\n\n    return img_output","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def remove_glare(image):\n    # Step 1: Grayscale Conversion\n    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n\n    # Step 2: Gaussian Blur\n    blurred = cv2.GaussianBlur(gray, (5, 5), 0)\n\n    # Step 3: Thresholding\n    _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)\n\n    # Step 4: Contour Detection\n    contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n\n    # Step 5: Glare Removal\n    for contour in contours:\n        area = cv2.contourArea(contour)\n        if area < 1000:\n            cv2.drawContours(binary, [contour], -1, 0, -1)\n\n    # Step 6: Return Modified Image\n    return cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Dataset(torch.utils.data.Dataset):\n    def __init__(self, images, meta, labels=None, train=True, cache=False, trans=None):\n        self.cache = cache\n        self.images = [self.load_img(img) for img in tqdm(images)] if cache else images\n        meta = oh_encoder.transform(meta).toarray()\n        self.meta = torch.tensor(meta).float()\n        self.train = train\n        self.trans = trans\n        if train: self.labels = [torch.tensor([label]).float() for label in labels]\n    \n    def __len__(self):\n        return len(self.images)\n    \n    def load_img(self, img):\n        image =  io.imread(img)\n        return hair_remove(image)\n    \n    def __getitem__(self, ix):    \n        img = self.images[ix] if self.cache else self.load_img(self.images[ix])\n        if self.trans:\n            img = self.trans(image=img)['image']\n        if self.train:\n            return torch.from_numpy(img), self.meta[ix], self.labels[ix]\n        return torch.from_numpy(img), self.meta[ix]\n\n","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:28.038973Z","iopub.execute_input":"2024-03-03T07:11:28.039234Z","iopub.status.idle":"2024-03-03T07:11:28.048233Z","shell.execute_reply.started":"2024-03-03T07:11:28.039212Z","shell.execute_reply":"2024-03-03T07:11:28.04742Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trans = Compose([\n    Flip(p=0.6), Transpose(), CropAndPad(p=0.2, percent=-0.1),\n])\n\ndataset = {\n    'train': Dataset(train['path'], train[cols], train['target'], cache=True, trans=trans),\n    'val': Dataset(val['path'], val[cols], val['target'], cache=True, trans=trans),\n}","metadata":{"execution":{"iopub.status.busy":"2024-03-03T07:11:28.051288Z","iopub.execute_input":"2024-03-03T07:11:28.051797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Sample of the images","metadata":{}},{"cell_type":"code","source":"import random\n\nr, c = 3, 5\nfig = plt.figure(figsize=(2*c, 2*r))\nfor _r in range(r):\n    for _c in range(c):\n        plt.subplot(r, c, _r*c + _c + 1)\n        ix = random.randint(0, len(dataset['train']) - 1)\n        img, meta, label = dataset['train'][ix]\n        plt.imshow(img)\n        plt.title(f'{ix} {label.item()}')\n        plt.axis('off')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Building the model\n","metadata":{}},{"cell_type":"code","source":"class NN(torch.nn.Module):\n    def __init__(self, inputs=29):\n        super().__init__()\n        # encoder for image\n        resnet = torchvision.models.resnet101(weights=True)\n        for param in resnet.parameters():\n            param.requires_grad = False \n        self.encoder = torch.nn.Sequential(*list(resnet.children())[:-1]) \n        \n        # encoder for metadata\n        self.meta_encoder = torch.nn.Sequential(\n            torch.nn.Linear(inputs, 100),\n            torch.nn.ReLU(inplace=True),\n            torch.nn.Linear(100, 3),\n            torch.nn.ReLU(inplace=True))\n        \n        # head. el head del backbone\n        self.head = torch.nn.Linear(2048+3, 1)\n        \n    def forward(self, x, y):\n        B, H, W, C = x.shape\n        x = (x / 255.).float().view(B, C, H, W)\n        x = self.encoder(x)\n        x = x.view(B, -1) # reshape\n        y = self.meta_encoder(y)\n        z = torch.cat([x, y], -1)\n        z = self.head(z)\n        \n        return z","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Pytorch dataloader","metadata":{}},{"cell_type":"code","source":"BATCH_SIZE = 64\n\ndataloader = {\n    'train': torch.utils.data.DataLoader(dataset['train'], batch_size=BATCH_SIZE, shuffle=True),\n    'val': torch.utils.data.DataLoader(dataset['val'], batch_size=BATCH_SIZE),\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## AUC error metric","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import roc_auc_score\n\ndef AUC(outputs, labels):\n    outputs = torch.sigmoid(outputs)\n    outputs = outputs.detach().cpu().numpy()\n    labels = labels.detach().cpu().numpy()\n    auc = roc_auc_score(labels, outputs)\n    return auc","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training the model","metadata":{}},{"cell_type":"code","source":"model = NN().to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=0.01)\ncriterion = torch.nn.BCEWithLogitsLoss() # this loss apply sigmoid itself, and it's better mathematically\n\nepochs = 12\nvalidation_steps = 15\n\nmb = master_bar(range(1, epochs+1))\nbest_auc = 0\n\n\nfor epoch in mb:\n    train_loss = []\n    model.train() \n    \n    for batch in progress_bar(dataloader['train'], parent=mb):\n        imgs, meta, labels = batch\n        imgs, meta, labels = imgs.to(device), meta.to(device), labels.to(device)\n        \n        outputs = model(imgs, meta)\n        \n        optimizer.zero_grad()\n        \n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step() \n        train_loss.append(loss.item())\n        mb.child.comment = f'loss: {np.mean(train_loss):.5f}'\n        \n    val_loss = []\n    model.eval() # evaluation mode\n    validation_step = 0\n    val_outputs = torch.tensor([])\n    val_targets = torch.tensor([])\n    with torch.no_grad():\n        for batch in progress_bar(dataloader['val'], parent=mb):\n            imgs, meta, labels = batch\n            imgs, meta, labels = imgs.to(device), meta.to(device), labels.to(device)\n            outputs = model(imgs, meta)\n            \n            loss = criterion(outputs, labels)\n            val_loss.append(loss.item())\n            mb.child.comment = f'val_loss: {np.mean(val_loss):.5f}'\n            val_outputs = torch.cat([val_outputs, outputs.cpu()])\n            val_targets = torch.cat([val_targets, labels.cpu()])\n            validation_step += 1\n            if validation_step > validation_steps:\n                break\n                \n    auc = AUC(val_outputs, val_targets)\n    if auc > best_auc:\n        best_auc = auc\n        torch.save(model, 'model.pth') # saving the model\n        \n    mb.write(f'epoch: {epoch} | train_loss: {np.mean(train_loss):.5f} | epoch: {epoch} | val_loss: {np.mean(val_loss):.5f} | auc_loss: {auc:.5f}')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading testset","metadata":{}},{"cell_type":"code","source":"test = pd.read_csv('/kaggle/input/melanoma224/test.csv')\ntest['path'] = [f'/kaggle/input/melanoma224/jpeg224/test/{img}.jpg' for img in test['image_name']]\n\ntest.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_dataset = Dataset(test['path'], test[cols], train=False)\ntest_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=200)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Inference","metadata":{}},{"cell_type":"code","source":"model_save = torch.load('/kaggle/working/model.pth')\nmodel_save.to(device)\nmodel_save.eval()\npreds = torch.tensor([]).to(device)\n\n# we dont need to calculate grads during testing\nwith torch.no_grad():\n    for imgs, meta in progress_bar(test_dataloader):\n        imgs, meta = imgs.to(device), meta.to(device)\n        outputs = model(imgs, meta)\n        outputs = torch.sigmoid(outputs)\n        preds = torch.cat([preds, outputs.view(-1)])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission = pd.DataFrame({'image_name': test['image_name'].values, 'target': preds.cpu().numpy()})\nsubmission","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}