{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-output":true,"_kg_hide-input":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"root_jpeg = '/kaggle/input/siim-isic-melanoma-classification/jpeg/'\ntrain_path_jpeg = root_jpeg + 'train/'\ntest_path_jpeg = root_jpeg + 'test/'\n\ndcm_root = '/kaggle/input/siim-isic-melanoma-classification/'\ndcm_train =  dcm_root +'train/'\ndcm_test = dcm_root + 'test/'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# import required libraries\nimport numpy as np\n\n\nimport PIL\nfrom PIL import Image\n\n# plotly libraries\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport plotly.graph_objs as go\nfrom plotly.offline import iplot, init_notebook_mode\nimport plotly.express as px\nimport plotly.io as pio\nimport seaborn as sns\nimport matplotlib\n\nimport pydicom as dicom\n\nfrom scipy.stats import norm\nimport random\n\nimport cv2\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# <center>1. Basic Data Exploration<center>","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"#read train data\ntrain_df = pd.read_csv('/kaggle/input/siim-isic-melanoma-classification/train.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.shape[0], train_df.patient_id.nunique()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There are a total of 33,126 records for 2,056 unique patients.\n\nAs a first step let's take a look at the total number of benign vs malignant cases.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"benign_malig = train_df.groupby('benign_malignant').agg(count=('benign_malignant','count'))\nbenign_malig.reset_index(inplace=True, drop=False)\n\nfig,ax = plt.subplots(figsize=(8,4))\nax = sns.barplot(x='count',y='benign_malignant',data=benign_malig.sort_values(by='count', ascending=False), \n                 palette = {'benign':'#27AE60','malignant':'#E74C3C'})\nax.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))\nplt.title('Benign vs Malignant')\nplt.xlabel('')\nplt.ylabel('')\nplt.tight_layout()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As expected the the number of benign cases are exponentially higher than the malignant cases, a perfect example of an imbalanced dataset. Now, let's explore the patient information a bit more.","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"male_female = train_df.groupby('sex').agg(count=('patient_id','nunique'))\nmale_female.reset_index(inplace=True, drop=False)\n\nfig,ax = plt.subplots(figsize=(8,4))\nax = sns.barplot(x ='count', y = 'sex',data=male_female,palette={'male':'#3498DB','female':'#E74C3C'})\nplt.xlabel('', fontsize=20)\nplt.ylabel('')\nplt.yticks(fontsize=20)\nplt.xticks(fontsize=15)\nplt.tight_layout()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The male to female distribution is pretty uniform. Now let's look at the age distribution for the patients.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"age_distribution = train_df.groupby('patient_id').agg(sex=('sex','first'),age=('age_approx','first'))\nage_distribution.reset_index(inplace=True, drop=False)\n\nfig = plt.figure(figsize = (18,4))\nax = fig.add_subplot(1, 3, 1)\nax = sns.distplot(age_distribution['age'],fit=norm, color='#1ABC9C')\nplt.title('Age Distribution - Overall')\n\nax = fig.add_subplot(1, 3, 2)\nax = sns.distplot(age_distribution[age_distribution.sex == 'male']['age'],fit=norm, color='#3498DB')\nplt.title('Age Distribution - Males')\n\nax = fig.add_subplot(1, 3, 3)\nax = sns.distplot(age_distribution[age_distribution.sex == 'female']['age'],fit=norm, color='#E74C3C')\nplt.title('Age Distribution - Females')\n\nplt.tight_layout()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig,ax = plt.subplots(figsize=(18,4))\nax = sns.boxplot(x ='age', y = 'sex',data=age_distribution,palette={'male':'#3498DB','female':'#E74C3C'})\nplt.xlabel('Age', fontsize=20)\nplt.ylabel('')\nplt.yticks(fontsize=20)\nplt.tight_layout()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The overall age distribution has a near-normal distriburtion. The median age for males is ~55 and for females it is ~50. May be the age could be a factor for determining if the lesion is a cancer.\n\nNow let look at the site of the lesions.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"site = train_df.groupby('anatom_site_general_challenge').agg(count=('anatom_site_general_challenge','count'))\nsite.reset_index(inplace=True, drop=False)\n\nfig,ax = plt.subplots(figsize=(10,6))\nax = sns.barplot(x='count',y='anatom_site_general_challenge',data=site.sort_values(by='count', ascending=False), color = '#2C3E50')\nax.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))\nplt.title('Lesion Count by Site')\nplt.xlabel('')\nplt.ylabel('')\nplt.tight_layout()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Looks like most of the lesions identifed were in the torso followed by the lower extremity regions. Very few lesions were found on the palms/soles and oral and genital areas. \n\nWe will also quickly look at the diagnosis column.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"diagnosis = train_df.groupby('diagnosis').agg(count=('diagnosis','count'))\ndiagnosis.reset_index(inplace=True, drop=False)\n\nfig,ax = plt.subplots(figsize=(10,6))\nax = sns.barplot(x='count',y='diagnosis',data=diagnosis.sort_values(by='count', ascending=False), color = '#5DADE2')\nax.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))\nplt.title('Diagnosis')\nplt.xlabel('')\nplt.ylabel('')\nplt.tight_layout()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Approximately 80% of the disgnosis is unknown. Very few have been classified as nevus (nothing but moles) or melanoma. However, I don't think this is important for the analysis, since we are more focussed on predicting whether a given lesion is benign or malignant.\n\nNow, let's open some images and take a look at those.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# <center>2. Images<center>","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# get the list of image names\nimages_list = train_df.image_name.values","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_image(images,title=None):\n    fig = plt.figure(figsize = (20,12))\n    for i in range(1,10):\n        ax = fig.add_subplot(3, 3, i)\n        img = np.random.choice(images)\n        image_path = os.path.join(train_path_jpeg,img +'.jpg')\n        image = Image.open(image_path)\n        ax.imshow(image)\n    plt.suptitle(title, fontsize=15)    \n    plt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_image(images_list)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"These are some of the first observations:\n* The images are different sizes \n* Some images are circular as well. Need to figure out how to handle those.\n* The images vary in color, both the background and the lesions. Augmentations can be useful here. This is mainly because of the skin tones.\n* Some of the lesions are under 'hair' and these could impact predictions.\n\nLets look at some of the benign and malignant lesions individually. ","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"benign_list = train_df[train_df.target == 0].image_name.values\nmalignant_list = train_df[train_df.target == 1].image_name.values","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Images with lesions that are benign.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_image(benign_list, title='Benign')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Images with lesions that are malignant.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_image(malignant_list, title='Malignant')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now that we have looked at the actual images, lets look at the DCM images. I am curious to know what these are since I have never worked with those. \n\nA DCM file is an image file saved in the Digital Imaging and Communications in Medicine (DICOM) image format. It stores a medical image, such as a CT scan or ultrasound.\n\nThe DICOM format was created by the National Electrical Manufacturers Association (NEMA) as a standard for distributing and viewing medical images, such as MRIs, CT scans, and ultrasound images. You can open DICOM files with a variety of programs for Windows, macOS, and Linux, such as XnViewMP, GIMP, and MeVisLab.\n\nWe can use the pydicom package to view these files.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_dcm(images):\n    fig = plt.figure(figsize = (20,12))\n    for i in range(1,4):\n        ax = fig.add_subplot(1, 3, i)\n        img = np.random.choice(images)\n        image_path = os.path.join(dcm_train,img +'.dcm')\n        ds = dicom.dcmread(image_path)\n        ax.imshow(ds.pixel_array)   \n        plt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_dcm(images_list)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Looks like the dcm images may be more useful since they enhance the the lesions. But just maybe that we picked the right images :-)\n\nAs stated earlier we have about 33,127 images to play with. However, we can always use more data. That is where augmentations will come into play.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# <center>3. Image Augmentation<center>\n    \n Deep neural networks need a lot of data to be effective. That is where image augmentation comes into play. It is the process of creating more images from the existing training data by applying transformations. These include, but not limited to, flips, adding blur, increase sharpness and more. Some of these are very helpful to increase the accuracy of the models.\n\nThere is a fantastic library called 'Albumentations' that helps in creating augmenations quickly and effectively within a few lines of code. The github link is [here](https://github.com/albumentations-team/albumentations) and is a really good resource for beginners. The library was created by Kaggle Grandmasters and has helped win Kaggle competitions.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import albumentations as A\nfrom albumentations import (\n    HorizontalFlip, IAAPerspective, ShiftScaleRotate, CLAHE, RandomRotate90,\n    Transpose, ShiftScaleRotate, Blur, OpticalDistortion, GridDistortion, HueSaturationValue,\n    IAAAdditiveGaussianNoise, GaussNoise, MotionBlur, MedianBlur, RandomBrightnessContrast, IAAPiecewiseAffine,\n    IAASharpen, IAAEmboss, Flip, OneOf, Compose,VerticalFlip,BboxParams,Rotate, ChannelShuffle, RandomRain)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def aug_show(image,aug,title=None):\n    fig = plt.figure(figsize = (20,12))\n    image_path = os.path.join(train_path_jpeg,image +'.jpg')\n    image = Image.open(image_path)\n    \n    ax = fig.add_subplot(1, 2, 1)\n    ax.imshow(image)\n    \n    aug_image = aug(image=image)['image']\n    ax = fig.add_subplot(1, 2, 2)\n    ax.imshow(image)\n    \n    plt.suptitle(title, fontsize=15)    \n    plt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"BOX_COLOR = (255, 0, 0)\nTEXT_COLOR = (255, 255, 255)\n\ndef visualize_bbox(img, bbox, color=BOX_COLOR, thickness=2, **kwargs):\n    #height, width = img.shape[:2]\n\n    x_min, y_min, w, h = bbox\n    x_min, x_max, y_min, y_max = int(x_min), int(x_min + w), int(y_min), int(y_min + h)\n    \n    cv2.rectangle(img, (x_min, y_min), (x_max, y_max), color=color, thickness=thickness)\n    return img\n\ndef visualize_titles(img, bbox, title, color=BOX_COLOR, thickness=2, font_thickness = 2, font_scale=0.35, **kwargs):\n    #height, width = img.shape[:2]\n    x_min, y_min, w, h = bbox\n    x_min, x_max, y_min, y_max = int(x_min), int(x_min + w), int(y_min), int(y_min + h)\n    \n    ((text_width, text_height), _) = cv2.getTextSize(title, cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_thickness)\n    cv2.rectangle(img, (x_min, y_min - int(1.3 * text_height)), (x_min + text_width, y_min), BOX_COLOR, -1)\n    cv2.putText(img, title, (x_min, y_min - int(0.3 * text_height)), cv2.FONT_HERSHEY_SIMPLEX, font_scale, TEXT_COLOR,\n                font_thickness, lineType=cv2.LINE_AA)\n    return img\n\n\ndef augment_and_show(aug, image, mask=None, bboxes=[], categories=[], category_id_to_name=[], filename=None, title=None,\n                     font_scale_orig=0.35, \n                     font_scale_aug=0.35, show_title=True, **kwargs):\n\n    augmented = aug(image=image, mask=mask, bboxes=bboxes, category_id=categories)\n\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    image_aug = cv2.cvtColor(augmented['image'], cv2.COLOR_BGR2RGB)\n\n    for bbox in bboxes:\n        visualize_bbox(image, bbox, **kwargs)\n\n    for bbox in augmented['bboxes']:\n        visualize_bbox(image_aug, bbox, **kwargs)\n\n    if show_title:\n        for bbox,cat_id in zip(bboxes, categories):\n            visualize_titles(image, bbox, category_id_to_name[cat_id], font_scale=font_scale_orig, **kwargs)\n        for bbox,cat_id in zip(augmented['bboxes'], augmented['category_id']):\n            visualize_titles(image_aug, bbox, category_id_to_name[cat_id], font_scale=font_scale_aug, **kwargs)\n\n    \n    if mask is None:\n        f, ax = plt.subplots(1, 2, figsize=(16, 8))\n        \n        ax[0].imshow(image)\n        ax[0].set_title('Original image')\n        \n        ax[1].imshow(image_aug)\n        ax[1].set_title(title)\n    else:\n        f, ax = plt.subplots(2, 2, figsize=(16, 16))\n        \n        if len(mask.shape) != 3:\n            mask = label2rgb(mask, bg_label=0)            \n            mask_aug = label2rgb(augmented['mask'], bg_label=0)\n        else:\n            mask = cv2.cvtColor(mask, cv2.COLOR_BGR2RGB)\n            mask_aug = cv2.cvtColor(augmented['mask'], cv2.COLOR_BGR2RGB)\n            \n        ax[0, 0].imshow(image)\n        ax[0, 0].set_title('Original image')\n        \n        ax[0, 1].imshow(image_aug)\n        ax[0, 1].set_title('Augmented image')\n        \n        ax[1, 0].imshow(mask, interpolation='nearest')\n        ax[1, 0].set_title('Original mask')\n\n        ax[1, 1].imshow(mask_aug, interpolation='nearest')\n        ax[1, 1].set_title('Augmented mask')\n\n    f.tight_layout()\n    if filename is not None:\n        f.savefig(filename)\n        \n    return augmented['image'], augmented['mask'], augmented['bboxes']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"random.seed(42)\nimage_id = 'ISIC_2245325'\nimage = cv2.imread(os.path.join(train_path_jpeg,image_id +'.jpg'))\n\nflip = A.Compose([VerticalFlip(p=1)],p=1)\n\nr = augment_and_show(flip, image, title='Vertical Flip')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"strong = A.Compose([\n    A.ChannelShuffle(p=1),\n], p=1)\n\nr = augment_and_show(strong, image, title='Channel Shuffle')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"light = A.Compose([\n    A.RandomBrightnessContrast(p=1),    \n    A.RandomGamma(p=1),    \n    A.CLAHE(p=1),    \n], p=1)\n\nr = augment_and_show(light, image, title='Adjust Contrast')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Some of these augmentations like saturation and contrast may be useful.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# <center>4. Baseline Model<center>\n    \nFor this problem we will use the latest state of the art EfficientNet model which is 8.4x smaller and 6.1x faster and has achieved very high accuracy on ImageNet. Compared to other models achieving similar ImageNet accuracy, EfficientNet is much smaller. For example, the ResNet50 model has 23,534,592 parameters in total, and even though, it still underperforms the smallest EfficientNet, which only takes 5,330,564 parameters in total.\n\n![](https://gitcdn.xyz/cdn/Tony607/blog_statics/36894ad880dc3e645513efc36cc070c4cd0d3d7c/images/efficientnet/size_vs_accuracy.png)\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# install efficient net\n!pip install -q efficientnet","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"from keras_preprocessing.image import ImageDataGenerator\n\nimport efficientnet.tfkeras as efnt\n\nfrom tensorflow.keras import models\nfrom tensorflow.keras import layers\nimport tensorflow as tf","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 4.1 Data Prep\n\nIt is worth noting that our dataset contains multiple images for each patient. This could be the case, for example, when a patient has taken multiple pictures at different times. In our data splitting, we need to ensure that the split is done on the patient level so that there is no data \"leakage\" between the train, validation, and test datasets.\n\nWe will use set intersections to check those and make sure there is no leakage.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# read data\ndf = pd.read_csv('/kaggle/input/siim-isic-melanoma-classification/train.csv')\n\n# get unique ids of patients and shuffle\nunique_ids = df['patient_id'].unique()\nrandom.shuffle(unique_ids)\n\n# create a list for training and validation id's  - we will use 20% of the data for validation\ntrain_ids = unique_ids[:int(0.8 * len(unique_ids))]\nvalid_ids = unique_ids[int(0.8 * len(unique_ids)):]\n\n# check if there are same patient id's both in training and validation sets\nprint ('Number of Repeating ids:' ,len(set(train_ids).intersection(set(valid_ids))))\n\n# get the train and validation df's\ntrain_df = df[df['patient_id'].isin(train_ids)]\nvalid_df = df[df['patient_id'].isin(valid_ids)]\n\nprint('Train Data Size: ',train_df.shape[0])\nprint('Validation Data Size: ',valid_df.shape[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Great! We do not have any data leakage since the repeating  id's are 0.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"* ## 4.2 Preparing Images\n\nWith our dataset splits ready, we can now proceed with setting up our model to consume them. For this we will use the off-the-shelf ImageDataGenerator class from the Keras framework, which allows us to build a \"generator\" for images specified in a dataframe. This class also provides support for basic data augmentation such as random horizontal flipping of images.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# add jpg extensions for filename\ntrain_df.loc[:,'image_name'] = train_df['image_name']+'.jpg'\nvalid_df.loc[:,'image_name'] = valid_df['image_name'] + '.jpg'\n\n# convert target column to string to input into image generator\ntrain_df['target'] = train_df['target'].astype(str)\nvalid_df['target'] = valid_df['target'].astype(str)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We will also define some variables that we will use in the future.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"\nbatch_size = 48\n\nwidth = 512\nheight = 512\nepochs = 10\nNUM_TRAIN = train_df.shape[0]\nNUM_TEST = valid_df.shape[0]\ndropout_rate = 0.2\ninput_shape = (height, width, 3)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Since we will be using the flow_from_dataframe function, it will be useful to define a few key parameters:\n* dataframe - this is the dataframe we prepared in the previous section (train_df, valid_df)\n* x_col - this is the image name column with the .jpg extension\n* y_col - this is the target column (has to be converted to string for binary class mode\n* class_mode - this is binary, since we have only two classes. For more than 2 classes this will be 'categorical'\n* target size - required size for the image - in this case I have reduced by 0.5, so 512 x 512\n* batch_size - number of images to be loaded at a time - too large a batch size can cause poor generalization. Typical values are 32 or less. This is a parameter that we can play around with. ","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# we will use some in-built augmentations for now\ntrain_datagen=ImageDataGenerator(rescale=1./255, rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,zoom_range=0.2)\n\ntrain_generator=train_datagen.flow_from_dataframe(dataframe=train_df, directory=train_path_jpeg, \n                                            x_col=\"image_name\", y_col=\"target\", class_mode=\"binary\", \n                                            target_size=(height,width), batch_size=batch_size)\n\n# The validation datatset should not be augmented!\nvalid_datagen = ImageDataGenerator(rescale=1.0 / 255)\n\nvalid_generator=valid_datagen.flow_from_dataframe(dataframe=valid_df, directory=train_path_jpeg, \n                                            x_col=\"image_name\", y_col=\"target\", class_mode=\"binary\", \n                                            target_size=(height,width), batch_size=batch_size)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 4.3 Model Development\n\nAs stated earlier we will use the pre-trained EfficentNet B0 model as the baseline. The EfficientNet is built for ImageNet classification contains 1000 classes labels. For our dataset, we only have 2, which means the last few layers for classification is not useful for us. They can be excluded while loading the model by specifying the include_top argument to False, and this applies to other ImageNet models made available in Keras applications as well. ","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# load the pretrained model. Set include_top to false since we have only 2 classes\nconv_base = efnt.EfficientNetB0(weights=\"imagenet\", include_top=False, input_shape=input_shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We will create our own classification layers stack on top of the EfficientNet convolutional base model. We adapt GlobalMaxPooling2D to convert 4D the (batch_size, rows, cols, channels) tensor into 2D tensor with shape (batch_size, channels). GlobalMaxPooling2D results in a much smaller number of features compared to the Flatten layer, which effectively reduces the number of parameters. ","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"model = models.Sequential()\nmodel.add(conv_base)\nmodel.add(layers.GlobalMaxPooling2D(name=\"gap\"))\n\nif dropout_rate > 0:\n    model.add(layers.Dropout(dropout_rate, name=\"dropout_out\"))\n    \nmodel.add(layers.Dense(2, activation=\"softmax\", name=\"fc_out\"))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"To keep the convolutional base's weight untouched, we will freeze it, otherwise, the representations previously learned from the ImageNet dataset will be destroyed.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"conv_base.trainable = False","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now that we have all the pieces in place the last step is to compile the model and run. We will use the Binary Cross Entropy fopr the loss and the adam opimizer. We may need to define other loss functions to address the class imbalance and will do that in later stages.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"model.compile(\n    loss=tf.keras.losses.BinaryCrossentropy(label_smoothing = 0.1),\n    optimizer='adam',\n    metrics=[\"binary_crossentropy\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#history = model.fit_generator(\n #   train_generator,\n  #  steps_per_epoch=20,  # typically the len(train_data)/batch_size. Used a small number for demo.\n  #  epochs=3,            # this also has to be tuned. Using a small number just for demo and saving up GPU time\n  #  validation_data=valid_generator,\n  #  validation_steps=20,\n  #  verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"os.makedirs(\"./models\", exist_ok=True)\nmodel.save('./models/melanoma_base.h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"loss = history.history['loss']\nval_loss = history.history['val_loss']\n\nepochs_x = range(len(loss))\n\nplt.plot(epochs_x, loss, 'bo', label='Training loss')\nplt.plot(epochs_x, val_loss, 'b', label='Validation loss')\nplt.title('Training and validation loss')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There are plenty of other things we can do as below:  \n1)  Add your custom network on top of an already trained base network.  \n2)  Freeze the base network.  \n3)  Train the part we added  \n4)  Unfreeze some layers in the base network.  \n5)  Jointly train both these layers and the part we added.  \n6)  Ensembling.  \n7)  Combining the image classifier along with a metadata classifier.  \n\nI will be trying a few of those. This is just the begining!","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}