{"cells":[{"metadata":{},"cell_type":"markdown","source":"<h2><center>Detect diabetic retinopathy to stop blindness before it's too late</center></h2>\n<center><img src=\"https://raw.githubusercontent.com/dimitreOliveira/MachineLearning/master/Kaggle/APTOS%202019%20Blindness%20Detection/aux_img.png\"></center>\n##### Image source: http://cceyemd.com/diabetes-and-eye-exams/"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Helper libraries\nimport tensorflow\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport cv2\nimport os\n%matplotlib inline\nprint(tensorflow.__version__)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Read in the training and test data"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv('../input/aptos2019-blindness-detection/train.csv')\ntrain_df['id_code'] = train_df['id_code'].apply(lambda x:x+'.png')\ntrain_df['diagnosis'] = train_df['diagnosis'].astype(str)\ntest_df = pd.read_csv('../input/aptos2019-blindness-detection/test.csv')\ntest_df['id_code'] = test_df['id_code'].apply(lambda x:x+'.png')\n\nnum_classes = train_df['diagnosis'].nunique()\ndiag_text = ['Normal', 'Mild', 'Moderate', 'Severe', 'Proliferative']","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Look at some raw images"},{"metadata":{"trusted":true},"cell_type":"code","source":"def display_raw_images(df, columns = 4, rows = 3):\n    fig=plt.figure(figsize = (5 * columns, 4 * rows))\n    for i in range(columns * rows):\n        image_name = df.loc[i,'id_code']\n        image_id = df.loc[i,'diagnosis']\n        img = cv2.imread(f'../input/aptos2019-blindness-detection/train_images/{image_name}')[...,[2, 1, 0]]\n        fig.add_subplot(rows, columns, i + 1)\n        plt.title(diag_text[int(image_id)])\n        plt.imshow(img)\n    plt.tight_layout()\n\ndisplay_raw_images(train_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Graph out the class frequency"},{"metadata":{"trusted":true},"cell_type":"code","source":"unique, counts = np.unique(train_df['diagnosis'], return_counts=True)\nplt.bar(unique, counts)\nplt.title('Class Frequency')\nplt.xlabel('Class')\nplt.ylabel('Frequency')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Calculate class weights to help with training on the unbalanced data set.[](http://) "},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.utils import class_weight\n\nsklearn_class_weights = class_weight.compute_class_weight(\n               'balanced',\n                np.unique(train_df['diagnosis']), \n                train_df['diagnosis'])\n\nprint(sklearn_class_weights)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Load a model"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint, Callback\nfrom tensorflow.keras.layers import Dense, Dropout, GlobalAveragePooling2D\nfrom tensorflow.keras.applications import DenseNet121, ResNet50, InceptionV3, Xception\nfrom tensorflow.keras.models import Model, Sequential\nfrom tensorflow.keras.optimizers import Adam \n\ndef create_resnet50_model(input_shape, n_out):\n    base_model = ResNet50(weights = None,\n                          include_top = False,\n                          input_shape = input_shape)\n    \n    base_model.load_weights('../input/keras-pretrained-models/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5')\n    model = Sequential()\n    model.add(base_model)\n    model.add(GlobalAveragePooling2D())\n    model.add(Dropout(0.5))\n    model.add(Dense(2048, activation = 'relu'))\n    model.add(Dropout(0.5))    \n    model.add(Dense(n_out, activation = 'sigmoid'))\n    return model\n\ndef create_inception_v3_model(input_shape, n_out):\n    base_model = InceptionV3(weights = None,\n                             include_top = False,\n                             input_shape = input_shape)\n    base_model.load_weights('../input/keras-pretrained-models/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5')\n    model = Sequential()\n    model.add(base_model)\n    model.add(GlobalAveragePooling2D())\n    model.add(Dropout(0.5))\n    model.add(Dense(2048, activation = 'relu'))\n    model.add(Dropout(0.5))    \n    model.add(Dense(n_out, activation = 'sigmoid'))\n    return model\n\ndef create_xception_model(input_shape, n_out):\n    base_model = Xception(weights = None,\n                             include_top = False,\n                             input_shape = input_shape)\n    base_model.load_weights('../input/keras-pretrained-models/xception_weights_tf_dim_ordering_tf_kernels_notop.h5')\n    model = Sequential()\n    model.add(base_model)\n    model.add(GlobalAveragePooling2D())\n    model.add(Dropout(0.5))\n    model.add(Dense(2048, activation = 'relu'))\n    model.add(Dropout(0.5))    \n    model.add(Dense(n_out, activation = 'sigmoid'))\n    return model\n\ndef create_densenet121_model(input_shape, n_out):\n    base_model = DenseNet121(weights = None,\n                             include_top = False,\n                             input_shape = input_shape)\n    base_model.load_weights('../input/densenet-keras/DenseNet-BC-121-32-no-top.h5')\n    model = Sequential()\n    model.add(base_model)\n    model.add(GlobalAveragePooling2D())\n    model.add(Dropout(0.5))\n    model.add(Dense(2048, activation = 'relu'))\n    model.add(Dropout(0.5))    \n    model.add(Dense(n_out, activation = 'sigmoid'))\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#IMAGE_HEIGHT = 224\n#IMAGE_WIDTH = 224\n#model = create_resnet50_model(input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 3), n_out = num_classes)\n#model = create_densenet121_model(input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 3), n_out = num_classes)\n\nIMAGE_HEIGHT = 299\nIMAGE_WIDTH = 299\n#model = create_inception_v3_model(input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 3), n_out = num_classes)\nmodel = create_xception_model(input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 3), n_out = num_classes)\n\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"PRETRAINED_MODEL = '../input/pretrained_blindness_detector/blindness_detector.h5'\n\nif (os.path.exists(PRETRAINED_MODEL)):\n  print('Restoring model from ' + PRETRAINED_MODEL)\n  model.load_weights(PRETRAINED_MODEL)\nelse:\n  print('No pretrained model found. Using fresh model.')\n\ncurrent_epoch = 0","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Preprocess the data"},{"metadata":{},"cell_type":"markdown","source":"#### Crop and improve lighting condition using Ben Graham's preprocessing method\nSee: https://www.kaggle.com/ratthachat/aptos-updated-preprocessing-ben-s-cropping"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tqdm import tqdm_notebook as tqdm\n\ndef crop_image_from_gray(img, tol = 7):\n    if img.ndim == 2:\n        mask = img > tol\n        return img[np.ix_(mask.any(1),mask.any(0))]\n    elif img.ndim == 3:\n        gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        mask = gray_img > tol        \n        check_shape = img[:,:,0][np.ix_(mask.any(1),mask.any(0))].shape[0]\n        if (check_shape == 0): # image is too dark so that we crop out everything,\n            return img # return original image\n        else:\n            img1 = img[:,:,0][np.ix_(mask.any(1),mask.any(0))]\n            img2 = img[:,:,1][np.ix_(mask.any(1),mask.any(0))]\n            img3 = img[:,:,2][np.ix_(mask.any(1),mask.any(0))]\n            img = np.stack([img1,img2,img3],axis = -1)\n        return img\n\ndef preprocess_image(image_path, sigmaX = 10):\n    image = cv2.imread(image_path)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    image = crop_image_from_gray(image)\n    image = cv2.resize(image, (IMAGE_HEIGHT, IMAGE_WIDTH))\n    image = cv2.addWeighted(image, 4, cv2.GaussianBlur(image, (0,0), sigmaX), -4, 128)        \n    return image\n\nprint(\"Preprocessing training images...\")\nx_train = np.empty((train_df.shape[0], IMAGE_HEIGHT, IMAGE_WIDTH, 3), dtype = np.uint8)\nfor i, image_id in enumerate(tqdm(train_df['id_code'])):\n    x_train[i, :, :, :] = preprocess_image(f'../input/aptos2019-blindness-detection/train_images/{image_id}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Look at some preprocessed images"},{"metadata":{"trusted":true},"cell_type":"code","source":"def display_preprocessed_images(df, columns = 4, rows = 3):\n    fig=plt.figure(figsize = (5 * columns, 4 * rows))\n    for i in range(columns * rows):\n        image_name = df.loc[i,'id_code']\n        image_id = df.loc[i,'diagnosis']\n        img = x_train[i]\n        fig.add_subplot(rows, columns, i + 1)\n        plt.title(diag_text[int(image_id)])\n        plt.imshow(img)\n    plt.tight_layout()\n\ndisplay_preprocessed_images(train_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Change target to a multi-label problem so a class encompasses all the classes before it.\nsee: https://arxiv.org/abs/0704.1028"},{"metadata":{"trusted":true},"cell_type":"code","source":"y_train = pd.get_dummies(train_df['diagnosis']).values\ny_train_multi = np.empty(y_train.shape, dtype = y_train.dtype)\ny_train_multi[:, 4] = y_train[:, 4]\n\nfor i in range(3, -1, -1):\n    y_train_multi[:, i] = np.logical_or(y_train[:, i], y_train_multi[:, i + 1])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Split into training and validation"},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\nx_train, x_val, y_train, y_val = train_test_split(\n    x_train, y_train_multi, \n    test_size = 0.20, \n    random_state = 2006\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Setup training data generator with augmentation"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nTRAIN_DATA_ROOT = '../input/aptos2019-blindness-detection/train_images'\nTEST_DATA_ROOT  = '../input/aptos2019-blindness-detection/test_images'\n\nBATCH_SIZE = 16\n\ntrain_datagen = ImageDataGenerator(\n    rotation_range = 360, \n    horizontal_flip = True, \n#    vertical_flip = True,\n    zoom_range = [0.98, 1.02], \n    width_shift_range = 0.01,\n    height_shift_range = 0.01)\n\ntrain_generator = train_datagen.flow(\n    x_train, \n    y_train,\n    batch_size = BATCH_SIZE, \n    shuffle = True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Train the clasifier head"},{"metadata":{"trusted":true},"cell_type":"code","source":"WARMUP_EPOCHS = 2\nWARMUP_LEARNING_RATE = 1e-3\n\nfor layer in model.layers:\n    layer.trainable = False\n\nfor i in range(-5, 0):\n    model.layers[i].trainable = True\n\nmodel.compile(optimizer = Adam(lr = WARMUP_LEARNING_RATE),\n              loss = 'binary_crossentropy',  \n              metrics = ['accuracy'])\n\nwarmup_history = model.fit_generator(generator = train_generator,\n#                              class_weight = sklearn_class_weights,\n                              steps_per_epoch = train_generator.n // train_generator.batch_size,\n                              validation_data = (x_val, y_val),\n                              epochs = WARMUP_EPOCHS,\n                              use_multiprocessing = True,\n                              workers = 4,                                     \n                              verbose = 1).history","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Fine-tune the whole model"},{"metadata":{"trusted":true},"cell_type":"code","source":"FINETUNING_EPOCHS = 20\nFINETUNING_LEARNING_RATE = 1e-4\n\nfor layer in model.layers:\n    layer.trainable = True\n\nmodel.compile(optimizer = Adam(lr = FINETUNING_LEARNING_RATE), \n              loss = 'binary_crossentropy',\n              metrics = ['accuracy'])\n\ncheckpoint = ModelCheckpoint(\n    'blindness_detector_best.h5', \n    monitor = 'val_acc',  \n    save_best_only = True, \n    save_weights_only = True,\n    verbose = 1)\n\nrlrop = ReduceLROnPlateau(\n    monitor = 'val_loss', \n    patience = 3, \n    factor = 0.5, \n    min_lr = 1e-6, \n    verbose = 1)\n\nstopping = EarlyStopping(\n    monitor = 'val_acc', \n    patience = 8, \n    restore_best_weights = True, \n    verbose = 1)\n\nfinetune_history = model.fit_generator(generator = train_generator,\n#                              class_weight = sklearn_class_weights,\n                              steps_per_epoch = train_generator.n // train_generator.batch_size,\n                              validation_data = (x_val, y_val),\n                              epochs = FINETUNING_EPOCHS,\n                              callbacks = [checkpoint, rlrop, stopping],         \n                              use_multiprocessing = True,\n                              workers = 4,\n                              verbose = 1).history","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plot learning curves"},{"metadata":{"trusted":true},"cell_type":"code","source":"training_accuracy = warmup_history['acc'] + finetune_history['acc']\nvalidation_accuracy = warmup_history['val_acc'] + finetune_history['val_acc']\ntraining_loss = warmup_history['loss'] + finetune_history['loss']\nvalidation_loss = warmup_history['val_loss'] + finetune_history['val_loss']\n\nplt.figure(figsize = (8, 8))\nplt.subplot(2, 1, 1)\nplt.plot(training_accuracy, label = 'Training Accuracy')\nplt.plot(validation_accuracy, label = 'Validation Accuracy')\nplt.legend(loc = 'lower right')\nplt.ylabel('Accuracy')\nplt.ylim([min(plt.ylim()), 1])\nplt.title('Training and Validation Accuracy')\n\nplt.subplot(2, 1, 2)\nplt.plot(training_loss, label = 'Training Loss')\nplt.plot(validation_loss, label = 'Validation Loss')\nplt.legend(loc = 'upper right')\nplt.ylabel('Cross Entropy')\nplt.ylim([0, 1.0])\nplt.title('Training and Validation Loss')\nplt.xlabel('epoch')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Evaluate the model"},{"metadata":{},"cell_type":"markdown","source":"### Get validation predictions from the final model"},{"metadata":{"trusted":true},"cell_type":"code","source":"validation_predictions_raw = model.predict(x_val)\nvalidation_predictions = validation_predictions_raw > 0.5\nvalidation_predictions = validation_predictions.astype(int).sum(axis=1) - 1\nvalidation_truth = y_val.sum(axis=1) - 1","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Plot some metrics"},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import classification_report, confusion_matrix, cohen_kappa_score\n\ndef plot_confusion_matrix(cm, target_names, title = 'Confusion matrix', cmap = plt.cm.Blues):\n    plt.grid(False)\n    plt.imshow(cm, interpolation = 'nearest', cmap = cmap)\n    plt.title(title)\n    plt.colorbar()\n    tick_marks = np.arange(len(target_names))\n    plt.xticks(tick_marks, target_names, rotation = 90)\n    plt.yticks(tick_marks, target_names)\n    plt.tight_layout()\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n\nnp.set_printoptions(precision = 2)\ncm = confusion_matrix(validation_truth, validation_predictions)\ncm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n\nplot_confusion_matrix(cm = cm, target_names = diag_text)\nplt.show()\n\nprint('Confusion Matrix')\nprint(cm)\n\nprint('Classification Report')\nprint(classification_report(validation_truth, validation_predictions, target_names = diag_text))\n\nprint(\"Validation Cohen Kappa score: %.3f\" % cohen_kappa_score(validation_predictions, validation_truth, weights = 'quadratic'))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Look at some predictions from the validation set"},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_image(prediction_array, predicted_label, true_label, img):\n    plt.grid(False)\n    plt.xticks([])\n    plt.yticks([])\n    plt.imshow(img, cmap = plt.cm.binary)\n    if predicted_label == true_label:\n        color = 'blue'\n    else:\n        color = 'red'\n    plt.xlabel(\"{} {:2.0f}% ({})\".format(diag_text[predicted_label], 100 * np.max(prediction_array), diag_text[true_label]), color = color)\n\ndef plot_prediction(prediction_array, predicted_label, true_label):\n    plt.grid(False)\n    plt.xticks([])\n    plt.yticks([])\n    thisplot = plt.bar(range(5), prediction_array, color = \"#777777\")\n    plt.ylim([0, 1]) \n    thisplot[predicted_label].set_color('red')\n    thisplot[true_label].set_color('blue')\n  \n# Plot some validation images, their predicted label, and the true label\n# Color correct predictions in blue, incorrect predictions in red\nplt.figure(figsize=(24, 6))\nnum_cols = 4\nnum_rows = 4\nfor i in range(num_rows * num_cols):\n    plt.subplot(num_rows, 2 * num_cols, 2 * i + 1)\n    plot_image(validation_predictions_raw[i], validation_predictions[i], validation_truth[i], x_val[i])\n    plt.subplot(num_rows, 2 * num_cols, 2 * i + 2)\n    plot_prediction(validation_predictions_raw[i], validation_predictions[i], validation_truth[i])\nplt.show() ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Make some predictions "},{"metadata":{},"cell_type":"markdown","source":"### Preprocess the test images"},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train = None\nx_val = None\nprint(\"Preprocessing test images...\")\n!mkdir 'test_images_preprocessed/'\nfor i, image_id in enumerate(tqdm(test_df['id_code'])):\n    image = preprocess_image(f'../input/aptos2019-blindness-detection/test_images/{image_id}')    \n    cv2.imwrite(f'./test_images_preprocessed/{image_id}', image)\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_datagen = ImageDataGenerator()\ntest_generator = test_datagen.flow_from_dataframe(\n    dataframe = test_df,\n    directory = \"./test_images_preprocessed/\",\n    x_col = \"id_code\",\n    target_size = (IMAGE_HEIGHT, IMAGE_WIDTH),\n    batch_size = 1,\n    shuffle = False,\n    class_mode = None)\n\ny_test = model.predict_generator(test_generator) > 0.5\ny_test = y_test.astype(int).sum(axis = 1) - 1","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Check out the class distribution in the predicitons compared to the traing data"},{"metadata":{"trusted":true},"cell_type":"code","source":"unique, counts = np.unique(y_test, return_counts = True)\nplt.bar(unique, counts)\n\nunique, counts = np.unique(validation_truth, return_counts = True)\nplt.bar(unique, counts)\n\nplt.title('Class Frequency Training and Predictions')\nplt.xlabel('Class')\nplt.ylabel('Frequency')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Generate a submission"},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df = pd.read_csv('../input/aptos2019-blindness-detection/test.csv')\nsubmission_df['diagnosis'] = y_test\nsubmission_df.to_csv('submission.csv', index = False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!rm -rf /kaggle/working/test_images_preprocessed/","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}