{"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","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Read in the 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')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Look at some data"},{"metadata":{"trusted":true},"cell_type":"code","source":"diag_text = ['Normal', 'Mild', 'Moderate', 'Severe', 'Proliferative']\n\ndef display_samples(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_samples(train_df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from tqdm import tqdm_notebook as tqdm\n\nIMAGE_HEIGHT = 224\nIMAGE_WIDTH = 224\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...\")\n!mkdir -p 'train_images_preprocessed'\nfor i, image_id in enumerate(tqdm(train_df['id_code'])):\n    image = preprocess_image(f'../input/aptos2019-blindness-detection/train_images/{image_id}')\n    cv2.imwrite(f'./train_images_preprocessed/{image_id}', image)    \n    \n   ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Setup data generators with augmentation"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nnum_classes = train_df['diagnosis'].nunique()\n\nTRAIN_DATA_ROOT = './train_images_preprocessed/'\nTEST_DATA_ROOT  = './test_images_preprocessed/'\n\nBATCH_SIZE = 16\n\ntrain_datagen = ImageDataGenerator(\n    rescale = 1/255, \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    validation_split = 0.20)\n\nvalidation_datagen = ImageDataGenerator(\n    rescale = 1/255, \n    validation_split = 0.20)\n\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe = train_df,\n    directory = TRAIN_DATA_ROOT,\n    x_col = 'id_code',\n    y_col = 'diagnosis',\n    batch_size = BATCH_SIZE, \n    shuffle = True,\n    class_mode = 'categorical',\n    target_size = (IMAGE_HEIGHT, IMAGE_WIDTH),\n    subset = 'training')\n\nvalidation_generator = validation_datagen.flow_from_dataframe(\n    dataframe = train_df,\n    directory = TRAIN_DATA_ROOT,\n    x_col = 'id_code',\n    y_col = 'diagnosis',\n    batch_size = BATCH_SIZE, \n    shuffle = True,\n    class_mode = 'categorical', \n    target_size = (IMAGE_HEIGHT, IMAGE_WIDTH),\n    subset = 'validation')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Check out the class frequency"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Use the line below to show inline in a notebook\n%matplotlib inline\n\nunique, counts = np.unique(train_generator.classes, return_counts=True)\nplt.bar(unique, counts)\n\nunique, counts = np.unique(validation_generator.classes, return_counts=True)\nplt.bar(unique, counts)\n\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_generator.classes), \n                train_generator.classes)\nprint(sklearn_class_weights)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.layers import Dense, Dropout, GlobalAveragePooling2D, Input\nfrom tensorflow.keras.applications import DenseNet121, ResNet50\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    base_model.load_weights('../input/resnet50/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(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(n_out, activation = 'sigmoid'))\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#model = create_resnet50_model(input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 3), n_out = num_classes)\nmodel = create_densenet121_model(input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 3), n_out = num_classes)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"PRETRAINED_MODEL = '../input/pretrained_blindness_detector/blindness_detector.h5'\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":"## 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(-3, 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 = validation_generator,\n                              validation_steps = validation_generator.n // validation_generator.batch_size,\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 = 'categorical_crossentropy',\n              metrics = ['accuracy'])\n\nstopping = EarlyStopping(\n    monitor = 'val_loss', \n    mode = 'min', \n    patience = 5, \n    restore_best_weights = True, \n    verbose = 1)\n\nrlrop = ReduceLROnPlateau(\n    monitor = 'val_loss', \n    mode = 'min', \n    patience =  8, \n    factor = 0.5, \n    min_lr = 1e-6, \n    verbose = 1)\n\ncheckpoint = ModelCheckpoint(\n    'blindness_detector_best.h5', \n    monitor = 'val_acc',  \n    save_best_only = True, \n    save_weights_only = False,\n    mode = 'max',\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 = validation_generator,\n                              validation_steps = validation_generator.n // validation_generator.batch_size,\n                              epochs = FINETUNING_EPOCHS,\n                              callbacks = [stopping, rlrop, checkpoint],\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":{"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\nvalidation_generator.reset()\n    \nY_pred = model.predict_generator(validation_generator, \n                                 steps = validation_generator.n // validation_generator.batch_size + 1)\ny_pred = np.argmax(Y_pred, axis = 1)\n\nnp.set_printoptions(precision = 2)\ncm = confusion_matrix(validation_generator.classes, y_pred)\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_generator.classes, y_pred, target_names = diag_text))\n\nprint(\"Train Cohen Kappa score: %.3f\" % cohen_kappa_score(y_pred, validation_generator.classes, 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, true_label, img):\n    predicted_label = np.argmax(prediction_array) \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, 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    predicted_label = np.argmax(prediction_array)\n    thisplot[predicted_label].set_color('red')\n    thisplot[true_label].set_color('blue')\n  \nlabel_names = sorted(validation_generator.class_indices.items(), key = lambda pair:pair[1])\nlabel_names = np.array([key.title() for key, value in label_names])\n\nfor image_batch, label_batch in validation_generator:\n  break\n\npredictions = model.predict(image_batch)\nprediction_labels = label_names[np.argmax(predictions, axis = -1)]\n\n# Plot the first X test 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 = validation_generator.batch_size // num_cols\nfor i in range(num_rows * num_cols):\n    true_label = np.argmax(label_batch[i]);\n    plt.subplot(num_rows, 2 * num_cols, 2 * i + 1)\n    plot_image(predictions[i], true_label, image_batch[i])\n    plt.subplot(num_rows, 2 * num_cols, 2 * i + 2)\n    plot_prediction(predictions[i], true_label)\nplt.show() ","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(train_generator.classes, return_counts=True)\nplt.bar(unique, counts)\n\nunique, counts = np.unique(y_pred, 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":"!rm -rf /kaggle/working/train_images_preprocessed/\n\nprint(\"Preprocessing test images...\")\n!mkdir -p '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    \ntest_datagen = ImageDataGenerator(rescale = 1./255)\n\ntest_generator = test_datagen.flow_from_dataframe(  \n        dataframe = test_df,\n        directory = TEST_DATA_ROOT,\n        x_col = 'id_code',\n        target_size = (IMAGE_HEIGHT, IMAGE_WIDTH),\n        batch_size = 1,\n        shuffle = False,\n        class_mode = None)    \n    \npredict = model.predict_generator(test_generator, steps = len(test_generator.filenames))\nresults=pd.DataFrame({'id_code':test_generator.filenames,\n                      'diagnosis':np.argmax(predict, axis = 1)})\nresults['id_code'] = results['id_code'].map(lambda x: str(x)[:-4])\nresults.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}