{"cells":[{"metadata":{},"cell_type":"markdown","source":"<div class=\"jumbotron\">\n  <h1 class=\"display-4\">SIIM-ISIC Melanoma - Analysis, EDA and model </h1>\n  <img src=\"https://www.usnews.com/dims4/USNEWS/d124ccf/2147483647/thumbnail/640x420/quality/85/?url=http%3A%2F%2Fmedia.beam.usnews.com%2Ff2%2Fc0%2F26d7b74b44faad0a3d9f3dced483%2F190816hotnanomedicine-stock.jpg\" alt=\"Melanoma challenge\" width=\"800\"/>\n  <hr class=\"my-4\">\n  <p class=\"lead\">Hello everyone,\n    In this notebook, we will carry out the EDA of the <b>SIIM-ISIC Melanoma Classification challenge</b>. We shall first study why this is an important topic of research and what's at stake in this competition. We will then carry out an exploratory data analysis. Finally, I will show you how to setup a running pipeline with PyTorch so that you can quickly make experiments.</p>\n  <hr class=\"my-4\">\n  <p>I'll continuously update this notebook over the next few weeks.</p>\n  <hr class=\"my-4\">\n  <p>I'd like to give credit to:\n      <ul class=\"lead\">\n          <li>Credit goes to Marco Vasquez for his HTML notebook.</li>\n      </ul>\n  </p>\n  <hr class=\"my-4\">\n  <p>Sources:\n      <ul class=\"lead\">\n          <li><a href=\"https://www.cancer.gov/about-cancer/understanding/what-is-cancer\">https://www.cancer.gov/about-cancer/understanding/what-is-cancer</a></li>\n          <li><a href=\"https://www.skincancer.org/skin-cancer-information/\">https://www.skincancer.org/skin-cancer-information/</a></li>\n          <li><a href=\"https://www.skincancer.org/skin-cancer-information/melanoma/the-stages-of-melanoma/\">https://www.skincancer.org/skin-cancer-information/melanoma/the-stages-of-melanoma/</a></li>\n          <li><a href=\"https://www.skincancer.org/skin-cancer-information/melanoma/melanoma-warning-signs-and-images/\"></a>https://www.skincancer.org/skin-cancer-information/melanoma/melanoma-warning-signs-and-images/</li>\n      </ul>\n  </p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<div class=\"list-group\" id=\"list-tab\" role=\"tablist\">\n  <h3 class=\"list-group-item list-group-item-action active\" data-toggle=\"list\"  role=\"tab\" aria-controls=\"home\">Table of Contents</h3>\n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#what\" role=\"tab\" aria-controls=\"profile\">1. What is a melanoma?<span class=\"badge badge-primary badge-pill\">1</span></a>\n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#why\" role=\"tab\" aria-controls=\"messages\">2. Why is this competition important? <span class=\"badge badge-primary badge-pill\">2</span></a>\n  <a class=\"list-group-item list-group-item-action\"  data-toggle=\"list\" href=\"#evalmetric\" role=\"tab\" aria-controls=\"settings\">3. Evaluation metric<span class=\"badge badge-primary badge-pill\">3</span></a>\n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#overview\" role=\"tab\" aria-controls=\"settings\">4. EDA - Overview<span class=\"badge badge-primary badge-pill\">4</span></a> \n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#meta\" role=\"tab\" aria-controls=\"settings\">5. EDA - Meta-features<span class=\"badge badge-primary badge-pill\">5</span></a> \n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#dist\" role=\"tab\" aria-controls=\"settings\">6. EDA - Target distribution<span class=\"badge badge-primary badge-pill\">6</span></a> \n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#corr\" role=\"tab\" aria-controls=\"settings\">7. EDA - Correlation<span class=\"badge badge-primary badge-pill\">7</span></a> \n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#viz\" role=\"tab\" aria-controls=\"settings\">8. EDA - Image visualization<span class=\"badge badge-primary badge-pill\">8</span></a> \n  <a class=\"list-group-item list-group-item-action\" data-toggle=\"list\" href=\"#starter\" role=\"tab\" aria-controls=\"settings\">9. Starter model - EfficientNet B1<span class=\"badge badge-primary badge-pill\">9</span></a>  \n</div>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"what\">1. What is a melanoma?</h1>\n\n<h3>1.1. A quick recap': what cancer actually is...</h3>\n\n<p>Cancer is the name given to a <b>collection of related diseases</b>. In all types of cancer, some of the body’s cells begin to <b>divide without stopping and spread into surrounding tissues</b>.<br>\n<br>\nCancer can start almost anywhere in the human body, which is made up of trillions of cells. Normally, human cells grow and divide to form new cells as the body needs them. When cells grow old or become damaged, they die, and new cells take their place.<br>\n<br>\n<img src=\"https://www.cancer.gov/sites/g/files/xnrzdm211/files/styles/cgov_article/public/cgov_contextual_image/2019-06/1-how-does-cancer-form.jpg?h=b48714fe&itok=fc2eMUvr\" alt=\"Cancer cell development\" width=400/>\n    \n<br>\nWhen cancer develops, however, this orderly process breaks down. As cells become more and more abnormal, <b>old or damaged cells survive when they should die</b>, and new cells form when they are not needed. These extra cells can divide without stopping and may form growths called <b>tumors</b>.</p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h3>1.2. Skin cancer and melanoma</h3>\n\n<p> As stated above, cancer can develop in any part of the body. Skin cancer is one of the most wide-spread cancer found in global population.<br><br>\n\nSkin cancer is the out-of-control growth of abnormal cells in the <b>epidermis</b>, the outermost skin layer, caused by <b>unrepaired DNA damage that triggers mutations</b>. These mutations lead the skin cells to multiply rapidly and <b>form malignant tumors</b>. The main types of skin cancer are basal cell carcinoma (BCC), squamous cell carcinoma (SCC), <b>melanoma</b> and Merkel cell carcinoma (MCC).<br><br>\n\n<b>As you have guessed, for this competition, we will be interested in detecting melanoma.</b><br><br>\n\nThe two main causes of skin cancer are the <b>sun’s harmful ultraviolet (UV) rays</b> and the use of <b>UV tanning machines</b>. Often, the doctor may even detect the growth at a precancerous stage, before it has become a full-blown skin cancer or penetrated below the surface of the skin.\n\n</p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h3>1.3. Melanoma, in 4 questions</h3>\n\n<img src=\"https://www.verywellhealth.com/thmb/GmfzGuCPokTF14Dk06EaWvvROzo=/3125x2084/filters:no_upscale():max_bytes(150000):strip_icc()/what-is-melanoma-514215_final-01-3b091d9a68074ba7b5a1cb6d8287cf92.png\" width=500 alt=\"4 types of melanoma\"/><br><br>\n\n<p>\n    \n<b>What is it?</b> Melanoma is a cancer that develops from melanocytes, the skin cells that produce melanin pigment, which gives skin its color.<br><br>\n\n<b>Where is it usually found?</b> Melanomas often resemble moles and sometimes may arise from them. They can be found on any area of the body, even in areas that are not typically exposed to the sun.<br><br>\n\n<b>How many people get it?</b> In 2019, more than 192,000 new cases of melanoma are expected to occur in the U.S., about 96,000 of which will be invasive.<br></br>\n\n<b>How serious is it?</b> Melanoma is the most dangerous of the three most common forms of skin cancer. Melanomas can be curable when caught and treated early. In 2019, melanoma is projected to cause about 7,200 deaths.<br><br>\n    \n</p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"why\">2. Why is this competition important?</h1>\n    \n> Skin cancer is the most prevalent type of cancer. Melanoma, specifically, is responsible for 75% of skin cancer deaths, despite being the least common skin cancer. The American Cancer Society estimates over 100,000 new melanoma cases will be diagnosed in 2020. It's also expected that almost 7,000 people will die from the disease. As with other cancers, early and accurate detection—potentially aided by data science—can make treatment more effective.\n\n<p>As stated above, this disease can be deadly and an early and accurate diagnosis is critical to enhance the probability of survival of the patient.</p>\n\n> In this competition, you’ll identify melanoma in images of skin lesions. In particular, you’ll use images within the same patient and determine which are likely to represent a melanoma. Using patient-level contextual information may help the development of image analysis tools, which could better support clinical dermatologists.\n\n<p>This competition aims to classify skin moles into melanomas or simple moles. To do so, you'll have to develop a model that takes into account <b><i>contextual</i></b> information about the patient.</p>\n   ","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"evalmetric\">3. Evaluation metric</h1>\n    \n<p>The evaluation metric is the ROC-AUC score as expected in medical imaging competition, and in healthcare competition broadly speaking. Since the dataset is likely to be highly imbalanced (more non-cancerous moles than cancerous ones) we want a metric that takes into account this imbalance.<br><br>\n    \nFor more information on Recall, Precision, False Positive, True Negatives and ROC-AUC score, please check the links hereafter:\n<ul>\n    <li>https://www.healthnewsreview.org/toolkit/tips-for-understanding-studies/understanding-medical-tests-sensitivity-specificity-and-positive-predictive-value/</li>\n    <li>https://www.split.io/glossary/false-positive-rate/</li>\n    <li>https://towardsdatascience.com/understanding-auc-roc-curve-68b2303cc9c5</li>\n<ul>\n</p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"overview\">4. EDA - Overview</h1>\n\n<p>Before everything, let's take a moment to appreciate the fact that the competition organizers have provided us with tfrecords file to use Tensorflow Data API and TPU. A lot of formats are available namely, TFRecords, JPEG or DCM.</p>","execution_count":null},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport cv2\nfrom PIL import Image\n\nimport numpy as np\nimport pandas as pd\n\nimport glob","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"DATA_ROOT_PATH = \"../input/siim-isic-melanoma-classification/\"\nTRAIN_CSV = DATA_ROOT_PATH + \"train.csv\"\nTEST_CSV = DATA_ROOT_PATH + \"test.csv\"\n\ntrain_df = pd.read_csv(TRAIN_CSV, na_values=['unknown'])\ntest_df = pd.read_csv(TEST_CSV)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>Let's first have a quick loop at the overall look of the data. How many samples we have? Do we have meta-features?","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"trn_len_df = len(train_df)\ntst_len_df = len(test_df)\nprint(f\"There are {trn_len_df} images in the training set\")\nprint(f\"There are {tst_len_df} images in the test set\")","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"test_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We learn:<p>\n<ul>\n    <li>Patients can have multiple samples. <b>We need to make sure while training our model to prevent any form of data leakage. For that, we need to make sure a patient can't have samples in the training and validation sets at the same time. Otherwise, we introduce a form of data leakage.</b> Indeed, in production, such a model wouldn't have prior knowledge of a patient's moles.</li>\n    <li>There are indeed meta-features to feed our model with.</li>\n</ul>\n\n<p>Let's investigate a bit further what are the meta-features and their distribution.</p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"meta\">5. EDA - Meta-features</h1>\n\n<p>Before analyzing the distribution of the meta-features, let's first discover what the meta-features correspond to.</p>\n\n<ul>\n    <li><b>image_name</b>: the filename of the image/TFRecord.</li>\n    <li><b>patient_id</b>: the unique identifier of the patient.</li>\n    <li><b>sex</b>: the patient sex. (When unknown, will be blank)</li>\n    <li><b>age_approx</b>: an approximation of the patient age.</li>\n    <li><b>anatom_site_general_challenge</b>: a location of the image.</li>\n    <li><b>diagnosis</b>: indicator of malignancy of imaged lesion.</li>\n    <li><b>benign_malignant</b>: binarized version of the target variable.</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# NaN values\n\nnan_stats = train_df.isna().sum() / len(train_df) * 100\n\nstats = pd.DataFrame({\n    'columns': train_df.columns,\n    'NaN statistics (in %)': nan_stats\n})\n\nstats = stats.sort_values(by=['NaN statistics (in %)'], ascending=False)\n\nstats = stats.reset_index(drop=True)\nstats.head(5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li>There are very few NaN values in the dataset.</li>\n    <li>The diagnosis column is mostly NaN values since most of the values are unknown.</li>\n    <li>For the anatomic site, we can create an additional cateory called <i>unknown</i>.</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Patient distribution\nprint(f\"There are {train_df['patient_id'].nunique()} unique patients for {len(train_df)} images in the training set.\")\nprint(f\"There are {test_df['patient_id'].nunique()} unique patients for {len(test_df)} images in the training set.\")\n\nfig, ax = plt.subplots(1, 2, figsize=(15, 10))\n\nsns.countplot(train_df['patient_id'], ax=ax[0])\nax[0].set_title('Patient distribution in the training set')\n\nsns.countplot(test_df['patient_id'], ax=ax[1])\nax[1].set_title('Patient distribution in the test set')\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li>There are not a lot of patients in the dataset actually.</li>\n    <li>The distribution of patient images is skewed with some patients having more than 40 images in the dataset. We will really need to make sure that we don't overfit to some patients' images.</li>\n    <li>The training set and test set have roughly the same distribution with some patients contributing with more than 40 pictures.</li>\n</ul>\n\n<p>Let's see if some patients are present in the training and the test set at the same time.</p>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"trn_patients = set(train_df['patient_id'])\ntst_patients = set(test_df['patient_id'])\n\ninter_patients = len(trn_patients.intersection(tst_patients))\n\nprint(f'There are {inter_patients} common patients in the training and test sets.')","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n\nsns.countplot(train_df['sex'], ax=ax[0])\nax[0].set_title(\"Sex distribution in the training set\")\n\nsns.countplot(test_df['sex'], ax=ax[1])\nax[1].set_title(\"Sex distribution in the test set\")\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li>In both sets, there are more male pictures than female ones. We'll see later if the sex correlates with the target.</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n\nsns.distplot(train_df['age_approx'], ax=ax[0])\nax[0].set_title(\"Age distribution in the training set\")\n\nsns.distplot(test_df['age_approx'], ax=ax[1])\nax[1].set_title(\"Age distribution in the test set\")\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li>Both sets have the same age distribution.</li>\n    <li>Oddly enough, even though the images have been collected from 2056 patients in the training set and 690 in the test set, the age is highly concentrated since there exist only 18 different ages in the test set.</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n\nchart = sns.countplot(train_df['anatom_site_general_challenge'], ax=ax[0])\nax[0].set_title(\"Anatomical site in the training set\")\nchart.set_xticklabels(chart.get_xticklabels(), rotation=45)\n\nchart2 = sns.countplot(test_df['anatom_site_general_challenge'], ax=ax[1])\nax[1].set_title(\"Anatomical site in the test set\")\nchart2.set_xticklabels(chart2.get_xticklabels(), rotation=45)\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li>Most of the images come from the torso and the extremities (feet, hand, arm...).</li>\n    <li>Again the test set has the same distribution as the training set.</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"chart3 = sns.countplot(train_df['diagnosis'])\nchart3.set_xticklabels(chart3.get_xticklabels(), rotation=45)\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li><b>Most of the values are NaN. However, they are not displayed in the graph.</b></li>\n    <li>Again the distribution is mostly made of unknown and nevus values.</li>\n    <li>This information is not available in the test set.</li>\n</ul>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"dist\">6. EDA - Target distribution</h1>","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"print(f\"There are {len(train_df[train_df['target'] == 0])} negative labels.\")\nprint(f\"There are {len(train_df[train_df['target'] == 1])} positive labels.\")\n\nsns.countplot(train_df['target'])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>As expected the data is highly imbalanced like in most medical datasets. We will need to come up with some forms of tricks to prevent a model from always predicting negative. Example: weighted loss, ...</p>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"corr\">7. EDA - Correlation</h1>\n\n<p>Now that we have a better understanding of the feature distributions, let's see how continuous and categorical variables correlate with the target.</p>\n\n<b>Continuous variable:</b>\n<ul>\n    <li>Age</li>\n</ul>\n\n<b>Categorical variables:</b>\n<ul>\n    <li>Sex</li>\n    <li>Anatomical site</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig = plt.figure(figsize=(7,5))\nax = sns.countplot(x=\"target\", hue=\"sex\", data=train_df)\n\nfor p in ax.patches:\n    height = p.get_height()\n    ax.text(p.get_x() + p.get_width()/2, height+10, '{:1.2f}%'.format(100*height/len(train_df)), ha=\"center\")","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig = plt.figure(figsize=(7,5))\nax = sns.countplot(x=\"target\", hue=\"anatom_site_general_challenge\", data=train_df)\n\nfor p in ax.patches:\n    height = p.get_height()\n    ax.text(p.get_x() + p.get_width()/2, height+15, '{:1.2f}%'.format(100*height/len(train_df)), ha=\"center\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"viz\">8. EDA - Image visualization</h1>\n\n<p>Now, let's visualize some images from the training and the test sets to see any noticeable differences betweeen both sets.</p>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Training set\n\nimg_names = glob.glob('../input/siim-isic-melanoma-classification/jpeg/train/*.jpg')\n\nfig, ax = plt.subplots(4, 4, figsize=(20, 20))\n\nfor i in range(16):\n    x = i // 4\n    y = i % 4\n    \n    path = img_names[i]\n    image_id = path.split(\"/\")[5][:-4]\n    \n    target = train_df.loc[train_df['image_name'] == image_id, 'target'].tolist()[0]\n    \n    img = Image.open(path)\n    \n    ax[x, y].imshow(img)\n    ax[x, y].axis('off')\n    ax[x, y].set_title(f'ID: {image_id}, Target: {target}')\n\nfig.suptitle(\"Training set samples\", fontsize=15)","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Test set\n\nimg_names = glob.glob('../input/siim-isic-melanoma-classification/jpeg/test/*.jpg')\n\nfig, ax = plt.subplots(4, 4, figsize=(20, 20))\n\nfor i in range(16):\n    x = i // 4\n    y = i % 4\n    \n    path = img_names[i]\n    image_id = path.split(\"/\")[5][:-4]\n\n    img = Image.open(path)\n    \n    ax[x, y].imshow(img)\n    ax[x, y].axis('off')\n    ax[x, y].set_title(f'ID: {image_id}')\n\nfig.suptitle(\"Test set samples\", fontsize=15)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p>We note that:</p>\n<ul>\n    <li><b>The pictures seem to be in high quality for the training and test sets.</b></li>\n    <li>Some might have obstacles or noises such as hairs or artifacts such as ('mm' or graduatations).</li>\n</ul>","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"<h1 id=\"starter\">9. Starter model</h1>\n\n<p>We can draw some conclusions from the EDA for the modelling part.</p>\n\n<ul>\n    <li>First, we need to make sure that no patients is simultaneously in the training and validation set at the same time.</li>\n    <li>Second, since the data is highly imbalanced, we do need to implement some sort of counter mechanism like a weighted loss/focal loss.</li>\n</ul>","execution_count":null},{"metadata":{"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"!curl https://raw.githubusercontent.com/pytorch/xla/master/contrib/scripts/env-setup.py -o pytorch-xla-env-setup.py","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"!python pytorch-xla-env-setup.py --version nightly --apt-packages libomp5 libopenblas-dev","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"!pip install -q efficientnet_pytorch","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import os, re, random, gc\nfrom tqdm import tqdm\n\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom torch.optim import Adam, lr_scheduler\nfrom torch.utils.data.sampler import WeightedRandomSampler\n\n#import pretrainedmodels\nfrom efficientnet_pytorch import EfficientNet\n\nimport albumentations\nfrom PIL import Image\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import roc_auc_score\n\nimport torch_xla.core.xla_model as xm\nimport torch_xla.distributed.parallel_loader as pl\nimport torch_xla.distributed.xla_multiprocessing as xmp","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.1. Config</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"WIDTH = 224\nHEIGHT = 224\nTRAIN_BATCH_SIZE = 128\nVALID_BATCH_SIZE = 128\nEPOCHS = 10\nLR = 1e-3\nFOLDS = 5\nSEED = 0\nVERBOSE_STEP = 1\nTRAIN_CSV = '/kaggle/input/siim-isic-melanoma-classification/train.csv'\nSUBMISSION_CSV = '/kaggle/input/siim-isic-melanoma-classification/sample_submission.csv'\n\nMEAN = (0.485, 0.456, 0.406)\nSTD = (0.229, 0.224, 0.225)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.2. Utils</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\n\nseed_everything(SEED)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class AverageMeter:\n    def __init__(self):\n        self.reset()\n    \n    def reset(self):\n        self.val = 0\n        self.avg = 0\n        self.sum = 0\n        self.count = 0\n    \n    def update(self, val, n=1):\n        self.val = val\n        self.sum += val * n\n        self.count += n\n        self.avg = self.sum / self.count","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.3. Dataset</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class MelanomaDataset:\n    def __init__(self, image_paths, targets, resize=True, augmentations=None):\n        self.image_paths = image_paths\n        self.targets = targets\n        self.augmentations = augmentations\n        self.resize = resize\n    \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, item):\n        image = Image.open(self.image_paths[item])\n        targets = self.targets[item]\n        \n        if self.resize:\n            image = image.resize(\n                (WIDTH, HEIGHT), resample=Image.BILINEAR\n            )\n        \n        image = np.array(image)\n        \n        if self.augmentations is not None:\n            augmented = self.augmentations(image=image)\n            image = augmented['image']\n        \n        image = np.transpose(image, (2, 0, 1)).astype(np.float32)\n        \n        return {\n            'image': torch.tensor(image, dtype=torch.float),\n            'targets': torch.tensor(targets, dtype=torch.long),\n        }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(TRAIN_CSV)\ndf['image_name'] = df['image_name'].apply(lambda x: f'../input/siic-isic-224x224-images/train/{x}.png')\ndf.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Generating folds\n\nkf = KFold(FOLDS, random_state=SEED)\ndf = df.sample(frac=1).reset_index(drop=True)\n\nfor f, (_, val_index) in enumerate(kf.split(df, df)):\n    df.loc[val_index, 'kfold'] = f\n    \nprint(df['kfold'].value_counts())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Deriving weights for sampler\ndef generate_weights(df):\n    B = 0.5\n\n    C = np.array([B, (1 - B)])*2\n    ones = len(df.query('target == 1'))\n    zeros = len(df.query('target == 0'))\n\n    weightage_fn = {0: C[1]/zeros, 1: C[0]/ones}\n    return [weightage_fn[target] for target in df.target]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_transforms = albumentations.Compose([\n    albumentations.ShiftScaleRotate(p=0.9),\n    albumentations.CLAHE(p=0.5),\n    albumentations.HorizontalFlip(p=0.5),\n    albumentations.VerticalFlip(p=0.5),\n    albumentations.RandomBrightnessContrast(p=0.9),\n    albumentations.Normalize(mean=MEAN, std=STD, always_apply=True)\n])\n\ntest_transforms = albumentations.Compose([\n    albumentations.Normalize(mean=MEAN, std=STD, always_apply=True)\n])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.4. Model</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class MelanomaModel(nn.Module):\n    def __init__(self):\n        super(MelanomaModel, self).__init__()\n        \n        self.encoder = EfficientNet.from_pretrained(\"efficientnet-b1\")\n        self.dropout = nn.Dropout(0.3)\n        self.head = nn.Linear(1280, 1)\n    \n    def forward(self, image):\n        batch_size, _, _, _ = image.shape\n        \n        x = self.encoder.extract_features(image)\n        x = F.adaptive_avg_pool2d(x, 1).reshape(batch_size, -1)\n        \n        x = self.dropout(x)\n        logit = self.head(x)\n        \n        return logit","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.5. Engine</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, alpha=1, gamma=2, logits=False, reduce=True):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.logits = logits\n        self.reduce = reduce\n\n    def forward(self, inputs, targets):\n        if self.logits:\n            BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=None)\n        else:\n            BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=None)\n        pt = torch.exp(-BCE_loss)\n        F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss\n\n        if self.reduce:\n            return torch.mean(F_loss)\n        else:\n            return F_loss","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def loss_fn(outputs, targets):\n    return FocalLoss(logits=True)(outputs, targets)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def train_fn(data_loader, model, optimizer, device, scheduler=None):\n    model.train()\n    \n    losses = AverageMeter()\n\n    tk0 = tqdm(data_loader, total=len(data_loader))\n    \n    for bi, d in enumerate(tk0):\n        images = d['image']\n        targets = d['targets']\n        \n        images = images.to(device, dtype=torch.float)\n        targets = targets.to(device, dtype=torch.long)\n\n        model.zero_grad()\n        outputs = model(images)\n        targets = targets.view(-1, 1).type_as(outputs)\n        \n        loss = loss_fn(outputs, targets)\n        loss.backward()\n        xm.optimizer_step(optimizer, barrier=True)\n        \n        if scheduler:\n            scheduler.step()\n        \n        losses.update(loss.item(), images.size(0))\n        \n        tk0.set_postfix(loss=losses.avg)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def eval_fn(data_loader, model, device):\n    model.eval()\n    \n    losses = AverageMeter()\n    final_preds = []\n    \n    with torch.no_grad():\n        tk0 = tqdm(data_loader, total=len(data_loader))\n        \n        for bi, d in enumerate(tk0):\n            images = d['image']\n            targets = d['targets']\n\n            images = images.to(device, dtype=torch.float)\n            targets = targets.to(device, dtype=torch.long)\n\n            outputs = model(images)\n            targets = targets.view(-1, 1).type_as(outputs)\n            \n            loss = loss_fn(outputs, targets)\n            losses.update(loss.item(), images.size(0))\n            \n            final_preds.extend(outputs.cpu().detach().numpy().tolist())\n        \n    return losses.avg, final_preds","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def run_fold(fold):\n    device = xm.xla_device()\n    model = MelanomaModel().to(device)\n    best_auc = 0\n    \n    # Selecting fold\n    train_df = df[df['kfold'] != fold].reset_index(drop=True)\n    valid_df = df[df['kfold'] == fold].reset_index(drop=True)\n    \n    weights = generate_weights(train_df)\n        \n    # Loading data\n    \n    train_dataset = MelanomaDataset(\n        image_paths=train_df['image_name'],\n        targets=train_df['target'],\n        resize=True,\n        augmentations=train_transforms,\n    )\n\n    valid_dataset = MelanomaDataset(\n        image_paths=valid_df['image_name'],\n        targets=valid_df['target'],\n        resize=True,\n        augmentations=test_transforms,\n     )\n\n    train_sampler = WeightedRandomSampler(weights, len(train_df))\n\n    train_loader = torch.utils.data.DataLoader(\n        train_dataset, \n        batch_size=TRAIN_BATCH_SIZE, \n        sampler=train_sampler,\n        num_workers=8\n    )\n\n    valid_loader = torch.utils.data.DataLoader(\n        valid_dataset, \n        batch_size=VALID_BATCH_SIZE, \n        shuffle=False, \n        num_workers=8\n    )\n    \n    # Optimizer and scheduler\n    \n    num_train_steps = int(len(train_df) / TRAIN_BATCH_SIZE * EPOCHS)\n    optimizer = torch.optim.Adam(model.parameters(), lr=LR)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n        optimizer,\n        T_max=num_train_steps,\n        eta_min=1e-6\n    )\n    \n    # Training loop\n    \n    for epoch in range(EPOCHS):\n        train_fn(train_loader, model, optimizer, device=device, scheduler=scheduler)\n        loss, y_pred = eval_fn(valid_loader, model, device=device)\n        \n        y_pred = np.array(y_pred)\n        val_auc = roc_auc_score(valid_df['target'].values, y_pred)\n        \n        xm.master_print(f\"Epoch = {epoch}, val_loss = {loss}, val_auc = {val_auc}\")\n        \n        if val_auc > best_auc:\n            xm.save(model.state_dict(), f\"model_{fold}.bin\")\n            xm.master_print('Validation score improved ({} --> {}). Saving model!'.format(best_auc, val_auc))\n            best_auc = val_auc","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.6. Training</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"run_fold(0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"run_fold(1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"run_fold(2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"run_fold(3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"run_fold(4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.7. Inference</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class MelanomaDataset:\n    def __init__(self, image_paths, resize=True, augmentations=None):\n        self.image_paths = image_paths\n        self.augmentations = augmentations\n        self.resize = resize\n    \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, item):\n        image = Image.open(self.image_paths[item])\n        \n        if self.resize:\n            image = image.resize(\n                (WIDTH, HEIGHT), resample=Image.BILINEAR\n            )\n        \n        image = np.array(image)\n        \n        if self.augmentations is not None:\n            augmented = self.augmentations(image=image)\n            image = augmented['image']\n        \n        image = np.transpose(image, (2, 0, 1)).astype(np.float32)\n        \n        return {\n            'image': torch.tensor(image, dtype=torch.float),\n        }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.read_csv(TEST_CSV)\ntest_df['image_name'] = test_df['image_name'].apply(lambda x: f'../input/siic-isic-224x224-images/test/{x}.png')\ntest_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_aug_transforms = albumentations.Compose([\n    \n    albumentations.ShiftScaleRotate(p=0.9),\n    \n    albumentations.OneOf([\n        \n        albumentations.CLAHE(p=0.5),\n        albumentations.HueSaturationValue(p=0.5),\n        \n    ]),\n    \n    albumentations.OneOf([\n    \n        albumentations.HorizontalFlip(p=0.5),\n        albumentations.VerticalFlip(p=0.5),\n        \n    ]),\n    \n    albumentations.RandomBrightnessContrast(p=0.9),\n    \n    albumentations.Normalize(mean=MEAN, std=STD, always_apply=True)\n    \n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_transforms = albumentations.Compose([\n    albumentations.Normalize(mean=MEAN, std=STD, always_apply=True)\n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict(data_loader, model, device):\n    model.eval()\n    \n    final_preds = []\n    \n    with torch.no_grad():\n        tk0 = tqdm(data_loader, total=len(data_loader))\n        \n        for bi, d in enumerate(tk0):\n            images = d['image']\n            \n            images = images.to(device, dtype=torch.float)\n\n            outputs = model(images)\n                        \n            final_preds.extend(outputs.cpu().detach().numpy().tolist())\n        \n    return final_preds","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dataset = MelanomaDataset(\n    image_paths=test_df['image_name'],\n    resize=True,\n    augmentations=test_transforms,\n)\n\ntest_loader = torch.utils.data.DataLoader(\n    test_dataset, \n    batch_size=VALID_BATCH_SIZE, \n    shuffle=False, \n    num_workers=8\n)\n\n\ntest_aug_dataset = MelanomaDataset(\n    image_paths=test_df['image_name'],\n    resize=True,\n    augmentations=test_aug_transforms,\n)\n\ntest_aug_loader = torch.utils.data.DataLoader(\n    test_aug_dataset,\n    batch_size=VALID_BATCH_SIZE,\n    shuffle=False,\n    num_workers=8\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"MODEL_PATHS = [f'model_{fold}.bin' for fold in range(5)]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"device = xm.xla_device()\npredictions = []\n\nfor path in MODEL_PATHS:\n    model = MelanomaModel().to(device)\n    model.load_state_dict(torch.load(path))\n    \n    preds = predict(test_loader, model, device)\n    preds_aug = predict(test_aug_loader, model, device)\n    \n    preds = np.array(preds)\n    preds_aug = np.array(preds_aug)\n    \n    final_preds = np.mean([preds, preds_aug], axis=0)\n    \n    predictions.append(final_preds)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<h2>9.8. Submission</h2>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def sigmoid(x):\n    return 1 / (1 + np.exp(-x))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = np.array(predictions)\npredictions = np.mean(predictions, axis=0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = sigmoid(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub_df = pd.read_csv(SUBMISSION_CSV)\nsub_df['target'] = predictions","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub_df.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}