{"cells":[{"metadata":{"trusted":true},"cell_type":"markdown","source":"# Melanoma Skin Cancer Classification\n","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"For this competition, given an image our job is to determine whether it's benign or malign. Later, we can use the different features from the csv file additionally for classification task.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import StratifiedKFold\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.layers import Dense, GlobalAveragePooling2D\nfrom keras.models import Model\nfrom keras.losses import binary_crossentropy\nfrom keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\nfrom keras.applications import Xception\nimport tensorflow.keras.backend as K\nfrom PIL import Image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"For this notebook, we'll be using jpeg images. Later, we can try tfrecords.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"input_path = '/kaggle/input/siim-isic-melanoma-classification'\n\ntrain_images_path = os.path.join(input_path, 'jpeg', 'train')\ntest_images_path = os.path.join(input_path, 'jpeg', 'test')\ntrain_df_path = os.path.join(input_path, 'train.csv')\ntest_df_path = os.path.join(input_path, 'test.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(train_df_path)\ntest_df = pd.read_csv(test_df_path)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# is the data balanced?\ntrain_df['target'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The dataset as imbalanced since it is related medical diagnosis. More no. of patients are getting recovered from the illness.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# % of benign and malign samples\nprint('% benign: {:.4f}'.format(sum(train_df['target'] == 0) / len(train_df)))\nprint('% malign: {:.4f}'.format(sum(train_df['target'] == 1) / len(train_df)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Only 1.7% patients have malign cancer.\n","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"Let's take a look at the sample photos of both classes.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_images(data, nrows=5, ncols=5, target=0):\n    data = data[data['target'] == target].sample(nrows * ncols)['image_name']\n    plt.figure(figsize=(nrows * 2.5, ncols * 2.5))\n    for idx, image_name in enumerate(data):\n        image = Image.open(os.path.join(train_images_path, image_name + '.jpg'))\n        plt.subplot(nrows, ncols, idx + 1)\n        plt.imshow(image)\n        plt.axis('off')\n    plt.show();","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# plot the benign images\nprint('Benign Samples')\nplot_images(train_df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# plot the malign images\nprint('Malign Samples')\nplot_images(train_df, target=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# prepare the data: (training_images, labels)\ntrain_images = train_df['image_name'].apply(lambda img_path: os.path.join(train_images_path, img_path + '.jpg')).values\ntest_images = test_df['image_name'].apply(lambda img_path: os.path.join(test_images_path, img_path + '.jpg')).values\n\ntrain_labels = train_df['target'].values","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# convert to dataframe for flow_from_dataframe\ntrain_data = pd.DataFrame({'image': train_images, 'target': train_labels})\ntest_data = pd.DataFrame({'image': test_images})\n\ntrain_data.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's define the image generator which does some preprocessing like normalizing, data augmentation and so on.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=180.0,\n    shear_range=2.0,\n    zoom_range=8.0,\n    width_shift_range=8.0,\n    height_shift_range=8.0,\n    horizontal_flip=True,\n    vertical_flip=True\n)\n\nval_datagen = ImageDataGenerator(rescale=1./255)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It's better to use focal loss rather than binary cross entropy since data is imbalanced.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# reference :~ https://www.kaggle.com/c/tgs-salt-identification-challenge/discussion/66146\ndef focal_loss(alpha=0.5, gamma=2.0):\n    def focal_crossentropy(y_true, y_pred):\n        # compute binary cross entropy loss\n        bce_loss = binary_crossentropy(y_true, y_pred)\n        \n        y_pred = K.clip(y_pred, K.epsilon(), 1 - K.epsilon())\n        p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)\n        \n        alpha_factor = y_true * alpha + (1 - y_true) * (1- alpha)\n        modulating_factor = K.pow((1 - p_t), gamma)\n        \n        # compute and return final loss\n        return K.mean(alpha_factor * modulating_factor * bce_loss, axis=-1)\n    return focal_crossentropy","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Using Xception model pre-trained on ImageNet dataset.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def create_model():\n    xception = Xception(weights='imagenet', include_top=False, input_shape=(229, 229, 3))\n    output =  GlobalAveragePooling2D()(xception.output)\n    output = Dense(1, activation='sigmoid')(output)\n    model = Model(inputs=xception.input, outputs=output)\n    return model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Training the Model using KFold as the data is imbalance.\n\n**Note**: This model was trained on google colab. Just specify the checkpoint directory path in the `Training` and `Prediction` block. I have also provided the model weights for 5 folds which you can directly use for predictions.\n","execution_count":null},{"metadata":{"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"# Training Block\nskf = StratifiedKFold(n_splits=5)\nn_epochs = 50\ntrain_bs = 32\nvalid_bs = 16\n\nfor idx, (train_idx, val_idx) in enumerate(skf.split(train_data['image'], train_data['target'])):\n\n    print('Fold: {:02d}'.format(idx))\n    print('='*16)\n    \n    # train and validation generator for image preprocessing\n    train_generator = train_datagen.flow_from_dataframe(train_data.iloc[train_idx], x_col='image', y_col='target',\n                                                        target_size=(224, 224), batch_size=train_bs, shuffle=True,\n                                                        class_mode='raw')\n    val_generator = val_datagen.flow_from_dataframe(train_data.iloc[val_idx], x_col='image', y_col='target',\n                                                    target_size=(224, 224), batch_size=valid_bs, shuffle=False,\n                                                    class_mode='raw')\n    \n    steps_per_epoch = len(train_generator)\n    validation_steps = len(val_generator)\n    \n    model = create_model()\n    \n    checkpoint_dir = 'PATH_TO_CHECKPOINT_DIR'\n    if not os.path.exists(checkpoint_dir):\n        os.makedirs(checkpoint_dir)\n    checkpoint_filename = 'model_kfolds_{:02d}.hdf5'.format(idx)\n    checkpoint_filepath = os.path.join(checkpoint_dir, checkpoint_filename)\n\n    # callbacks for model\n    early_cb = EarlyStopping(patience=5, mode='max')\n    reduce_lr_cb = ReduceLROnPlateau(patience=3, min_lr=0.001, mode='max')\n    checkpoint_cb = ModelCheckpoint(filepath=checkpoint_filepath, save_best_only=True, mode='max')\n    \n    # compile model\n    model.compile(loss=focal_loss(),\n                  optimizer=keras.optimizers.Adam(3e-4),\n                  metrics=[keras.metrics.AUC()],\n    )\n    \n    model.fit(train_generator, \n              steps_per_epoch=steps_per_epoch,\n              epochs=n_epochs, \n              validation_data=val_generator,\n              validation_steps=validation_steps,\n              callbacks=[checkpoint_cb, reduce_lr_cb, early_cb],\n    )\n\n    print()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Great, now we have trained our model. So let's see how well our model performs by predicting on test images.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Prediction Block\ncheckpoint_dir = '/kaggle/input/melanoma-contest-model-weights'\n\ntest_generator = val_datagen.flow_from_directory(os.path.join(test_images_path, '..'), classes=['test'], class_mode=None,\n                                                         target_size=(224, 224), shuffle=False)\n\nfilenames = test_generator.filenames\n\nfinal_predictions = np.zeros((test_data.shape[0], 1))\n\nfor filename in os.listdir(checkpoint_dir):\n    if filename.endswith('.hdf5'):\n        print('Loading {} for prediction'.format(filename))\n        \n        # define model\n        model = create_model()\n\n        # load the weights from checkpoint dir\n        model.load_weights(os.path.join(checkpoint_dir, filename))\n\n        # model predicts\n        predictions = model.predict(test_generator)\n\n        final_predictions += predictions\n\n        print()\n\nfinal_predictions = final_predictions / 5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample = pd.DataFrame({'image_name': filenames, 'target': final_predictions.ravel()})\nsample['image_name'] = sample['image_name'].apply(lambda img_path: os.path.basename(img_path).split('.')[0])\nsample.to_csv('submission.csv', index=False)\nsample.head()","execution_count":null,"outputs":[]},{"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}