{"cells":[{"metadata":{},"cell_type":"markdown","source":"**Version 3**: \n- Added the sklearn cohen_kappa_score metric (quadratic)\n- Added early stopping\n- Added a csv logger\n\n<hr>"},{"metadata":{},"cell_type":"markdown","source":"**Diabetic Retinopathy Analyzer**<br>\nby Marsh [ @vbookshelf ]<br>\n30 June 2019"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"http://dr.test.woza.work/assets/blind.jpg\" width=\"400\"></img>"},{"metadata":{},"cell_type":"markdown","source":"## Introduction"},{"metadata":{},"cell_type":"markdown","source":"Diabetic Retinopathy (DR) is the fastest growing cause of preventable blindness. All people with diabetes are at risk. They need to be screened once a year. \n\nThis screening involves taking a picture of the back of the eye. The picture is called a fundus photo. It's taken using a special camera. An eye doctor then diagnoses this image. In many parts of the world there's a shortage of eye doctors. As a result, in India about 45% of people suffer some form of vision loss before the disease is detected.\n\nIt's now possible to take fundus photos using a cellphone camera.<br>\nhttps://www.jove.com/video/55958/smartphone-fundus-photography\n\nWhy not also use that same phone to automatically diagnose the photo?\n\nThe objective of this notebook is to build a binary classifier that can detect diabetic retinopathy on a fundus image. This model has been deployed online as a tensorflow.js web app. It can be easily accessed from anywhere where there's an internet connection.\n\nFundus images can be quite large, as can be seen by the size of the images in this competition. The good thing about tensorflow.js is that there's no need to upload images. The model runs in the browser and all processing is done locally on the user's computer or mobile phone. \n\nWe will use a pre-trained MobileNet model. MobileNet was developed by Google to be small and fast. This makes it ideal for web use. Its performance metrics are close to larger models like Inception and VGG.\n\n> Live Prototype Web App<br>\n> http://dr.test.woza.work/\n> \n> Github<br>\nhttps://github.com/vbookshelf/Diabetic-Retinopathy-Analyzer\n\nFor best results please use the Chrome browser when accessing the app. In other browsers the app may freeze. The javascript, html and css code is available on github. \n\nLet's get started..."},{"metadata":{"trusted":true},"cell_type":"markdown","source":"<hr>"},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\n\nimport cv2\n\nfrom skimage.io import imread, imshow\nfrom skimage.transform import resize\n\nfrom PIL import Image\n\nimport tensorflow\nfrom tensorflow.keras.layers import Dense, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.metrics import categorical_crossentropy\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.callbacks import (EarlyStopping, ReduceLROnPlateau, \n                                        ModelCheckpoint, CSVLogger)\nfrom tensorflow.keras.metrics import categorical_accuracy, top_k_categorical_accuracy\n\nfrom sklearn.utils import shuffle\nfrom sklearn.metrics import confusion_matrix\nfrom sklearn.model_selection import train_test_split\nimport itertools\nimport shutil\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\n# Don't Show Warning Messages\nimport warnings\nwarnings.filterwarnings('ignore')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"os.listdir('../input')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"IMAGE_HEIGHT = 224\nIMAGE_WIDTH = 224\nIMAGE_CHANNELS = 3","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = pd.read_csv('../input/train.csv')\n\n\nprint(df_train.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Create a new column called file_name"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Add a file_name column to df_train and df_test\n\ndef create_fname(x):\n    \n    fname = str(x) + '.png'\n    \n    return fname\n\ndf_train['file_name'] = df_train['id_code'].apply(create_fname)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Check the target distribution"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check the target distribution\ndf_train['diagnosis'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Create Binary Targets"},{"metadata":{"trusted":true},"cell_type":"code","source":"def binary_target(x):\n    if x != 0:\n        return 1\n    else:\n        return x\n    \ndf_train['binary_target'] = df_train['diagnosis'].apply(binary_target)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check the target distribution\n\ndf_train['binary_target'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Balance the target distribution"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_0 = df_train[df_train['binary_target'] == 0]\ndf_1 = df_train[df_train['binary_target'] == 1].sample(len(df_0), random_state=101)\n\n\ndf_data = pd.concat([df_0, df_1], axis=0).reset_index(drop=True)\n\ndf_data = shuffle(df_data)\n\nprint(df_data.shape)\n\ndf_data.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check the new target distribution\n\ndf_data['binary_target'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Train Test Split"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train, df_val = train_test_split(df_data, test_size=0.1, random_state=101)\n\nprint(df_train.shape)\nprint(df_val.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# check the train set target distribution\ndf_train['binary_target'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# check the train set target distribution\ndf_val['binary_target'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## Create the directory structure"},{"metadata":{},"cell_type":"markdown","source":"In these folders we will store the resized images that will later be fed into the generators. Keras needs this directory structure in order for the generators to work."},{"metadata":{},"cell_type":"markdown","source":"**Key**\n> 0 = No Diabetic Retinopathy<br>\n> 1 = Has Diabetic Retinopathy"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Create a new directory\nbase_dir = 'base_dir'\nos.mkdir(base_dir)\n\n\n#[CREATE FOLDERS INSIDE THE BASE DIRECTORY]\n\n# now we create sub folders inside 'base_dir':\n\n# train_dir\n    # a_0\n    # b_1\n\n# val_dir\n    # a_0\n    # b_1\n\n\n# create a path to 'base_dir' to which we will join the names of the new folders\n# train_dir\ntrain_dir = os.path.join(base_dir, 'train_dir')\nos.mkdir(train_dir)\n\n# val_dir\nval_dir = os.path.join(base_dir, 'val_dir')\nos.mkdir(val_dir)\n\n\n# [CREATE FOLDERS INSIDE THE TRAIN, VALIDATION AND TEST FOLDERS]\n# Inside each folder we create seperate folders for each class\n\n# create new folders inside train_dir\na_0 = os.path.join(train_dir, 'a_0')\nos.mkdir(a_0)\nb_1 = os.path.join(train_dir, 'b_1')\nos.mkdir(b_1)\n\n\n# create new folders inside val_dir\na_0 = os.path.join(val_dir, 'a_0')\nos.mkdir(a_0)\nb_1 = os.path.join(val_dir, 'b_1')\nos.mkdir(b_1)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check that the folders exist\nos.listdir('base_dir')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Transfer the Images into the Folders"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Set the file_name as the index in df_data\ndf_data.set_index('file_name', inplace=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get a list of train and val images\ntrain_list = list(df_train['file_name'])\n\n# ============================\n# Transfer the train images\n# ============================\n\nfor fname in train_list:\n    \n    label = df_data.loc[fname,'binary_target']\n    \n    if label == 0:\n        sub_folder = 'a_0'\n        # source path to image\n        src = os.path.join('../input/train_images', fname)\n        # destination path to image\n        dst = os.path.join(train_dir, sub_folder, fname)\n        \n        image = cv2.imread(src)\n        image = cv2.resize(image, (IMAGE_HEIGHT, IMAGE_WIDTH))\n        cv2.imwrite(dst, image)\n        # save the image at the destination\n        # save the image using PIL\n        #result = Image.fromarray(image.astype(np.uint8))\n        #result.save(dst)\n        # copy the image from the source to the destination\n        #shutil.copyfile(src, dst)\n        \n        \n    if label == 1:\n        sub_folder = 'b_1'\n        # source path to image\n        src = os.path.join('../input/train_images', fname)\n        # destination path to image\n        dst = os.path.join(train_dir, sub_folder, fname)\n        \n        image = cv2.imread(src)\n        image = cv2.resize(image, (IMAGE_HEIGHT, IMAGE_WIDTH))\n        cv2.imwrite(dst, image)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ============================\n# Transfer the val images\n# ============================\n\n# Get a list of train and val images\nval_list = list(df_val['file_name'])\n\nfor fname in val_list:\n    \n    label = df_data.loc[fname,'binary_target']\n    \n    if label == 0:\n        sub_folder = 'a_0'\n        # source path to image\n        src = os.path.join('../input/train_images', fname)\n        # destination path to image\n        dst = os.path.join(val_dir, sub_folder, fname)\n        \n        image = cv2.imread(src)\n        image = cv2.resize(image, (IMAGE_HEIGHT, IMAGE_WIDTH))\n        cv2.imwrite(dst, image)\n        \n        \n    if label == 1:\n        sub_folder = 'b_1'\n        # source path to image\n        src = os.path.join('../input/train_images', fname)\n        # destination path to image\n        dst = os.path.join(val_dir, sub_folder, fname)\n        \n        image = cv2.imread(src)\n        image = cv2.resize(image, (IMAGE_HEIGHT, IMAGE_WIDTH))\n        cv2.imwrite(dst, image)\n\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check how many images are in the train sub folders\n\nprint(len(os.listdir('base_dir/train_dir/a_0')))\nprint(len(os.listdir('base_dir/train_dir/b_1')))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check how many images are in the val sub folders\n\nprint(len(os.listdir('base_dir/val_dir/a_0')))\nprint(len(os.listdir('base_dir/val_dir/b_1')))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## Set Up the Generators"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_path = 'base_dir/train_dir'\nval_path = 'base_dir/val_dir'\n\nnum_train_samples = len(df_train)\nnum_val_samples = len(df_val)\ntrain_batch_size = 5\nval_batch_size = 5\n\n# Get the number of train and val steps\ntrain_steps = np.ceil(num_train_samples / train_batch_size)\nval_steps = np.ceil(num_val_samples / val_batch_size)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Pre-process the input images in the same way as the ImageNet images \n# were pre-processed when they were used to train MobileNet.\ndatagen = ImageDataGenerator(\n    preprocessing_function= \\\n    tensorflow.keras.applications.mobilenet.preprocess_input)\n\ntrain_gen = datagen.flow_from_directory(train_path,\n                                            target_size=(IMAGE_HEIGHT,IMAGE_WIDTH),\n                                            batch_size=train_batch_size)\n\nval_gen = datagen.flow_from_directory(val_path,\n                                            target_size=(IMAGE_HEIGHT,IMAGE_WIDTH),\n                                            batch_size=val_batch_size)\n\n# Note: shuffle=False causes the test dataset to not be shuffled\n# We are only going to use this to make a prediction on the val set. That's\n# why the path is set as val_path\ntest_gen = datagen.flow_from_directory(val_path,\n                                            target_size=(IMAGE_HEIGHT,IMAGE_WIDTH),\n                                            batch_size=1,\n                                            shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## MobileNet Pre-trained Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# create a copy of a mobilenet model\n\nmobile = tensorflow.keras.applications.mobilenet.MobileNet()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"mobile.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# The layers are set up as a list.\n\ntype(mobile.layers)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# How many layers does MobileNet have?\nlen(mobile.layers)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# CREATE THE MODEL ARCHITECTURE\n\n# Exclude the last 5 layers of the above model.\n# This will include all layers up to and including global_average_pooling2d_1\nx = mobile.layers[-6].output\n\n# Create a new dense layer for predictions\n# 2 corresponds to the number of classes\nx = Dropout(0.25)(x)\npredictions = Dense(2, activation='softmax')(x)\n\n# inputs=mobile.input selects the input layer, outputs=predictions refers to the\n# dense layer we created above.\n\nmodel = Model(inputs=mobile.input, outputs=predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"model.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# We need to choose how many layers we actually want to be trained.\n\n# Here we are freezing the weights of all layers except the\n# last 23 layers in the new model.\n# The last 23 layers of the model will be trained.\n\nfor layer in model.layers[:-23]:\n    layer.trainable = False","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Train the Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get the labels that are associated with each index\nprint(val_gen.class_indices)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Add weights to try to make the model more sensitive to some classes.\n# The dictionary is ordered as per the above output.\n\n# Here the weights are set to 1 so this is not affecting the model.\n# These weights can be changed later, if needed.\n\nclass_weights={\n    0: 1.0, # Class 0\n    1: 1.0, # Class 1\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"model.compile(Adam(lr=0.01), loss='categorical_crossentropy', \n              metrics=[categorical_accuracy])\n\n\nfilepath = \"model.h5\"\ncheckpoint = ModelCheckpoint(filepath, monitor='val_categorical_accuracy', verbose=1, \n                             save_best_only=True, mode='max')\n\n\nreduce_lr = ReduceLROnPlateau(monitor='val_categorical_accuracy', factor=0.5, patience=2, \n                                   verbose=1, mode='max', min_lr=0.00001)\n\nearly_stopper = EarlyStopping(monitor=\"val_categorical_accuracy\", \n                      mode=\"max\", \n                      patience=7)\n\ncsv_logger = CSVLogger(filename='training_log.csv',\n                       separator=',',\n                       append=False)\n                              \n                              \ncallbacks_list = [checkpoint, reduce_lr, early_stopper, csv_logger]\n\nhistory = model.fit_generator(train_gen, steps_per_epoch=train_steps, \n                              class_weight=class_weights,\n                    validation_data=val_gen,\n                    validation_steps=val_steps,\n                    epochs=100, verbose=1,\n                   callbacks=callbacks_list)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Get the best epoch from the training log"},{"metadata":{"trusted":true},"cell_type":"code","source":"# check that the training_log.csv file has been created\n!ls","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# load the training log\ndf = pd.read_csv('training_log.csv')\n\n# we are monitoring val_loss\nbest_acc = df['val_categorical_accuracy'].max()\n\n# display the row with the best accuracy\ndf[df['val_categorical_accuracy'] == best_acc]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## Evaluate the model using the val set"},{"metadata":{"trusted":true},"cell_type":"code","source":"# get the metric names so we can use evaulate_generator\nmodel.metrics_names","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Note: evaluate_generator appears to work when using tensorflow.keras but\n# it gives wrong results when using ordinary Keras. This could be a bug.\n\n# Here the best epoch will be used.\nmodel.load_weights('model.h5')\n\nval_loss, val_categorical_accuracy = \\\nmodel.evaluate_generator(test_gen, \n                        steps=len(df_val))\n\nprint('val_loss:', val_loss)\nprint('val_categorical_accuracy:', val_categorical_accuracy)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plot the Training Curves"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# display the loss and accuracy curves\n\nimport matplotlib.pyplot as plt\n\nacc = history.history['categorical_accuracy']\nval_acc = history.history['val_categorical_accuracy']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nepochs = range(1, len(acc) + 1)\n\nplt.plot(epochs, loss, 'bo', label='Training loss')\nplt.plot(epochs, val_loss, 'b', label='Validation loss')\nplt.title('Training and validation loss')\nplt.legend()\nplt.figure()\n\nplt.plot(epochs, acc, 'bo', label='Training cat acc')\nplt.plot(epochs, val_acc, 'b', label='Validation cat acc')\nplt.title('Training and validation cat accuracy')\nplt.legend()\nplt.figure()\n\n\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Confusion Matrix"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get the labels of the test images.\n\ntest_labels = test_gen.classes\n\n# We need these to plot the confusion matrix.\ntest_labels","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Print the label associated with each class\ntest_gen.class_indices","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# make a prediction on the val data\npredictions = model.predict_generator(test_gen, steps=len(df_val), verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions.shape","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Source: Scikit Learn website\n# http://scikit-learn.org/stable/auto_examples/\n# model_selection/plot_confusion_matrix.html#sphx-glr-auto-examples-model-\n# selection-plot-confusion-matrix-py\n\n\ndef plot_confusion_matrix(cm, classes,\n                          normalize=False,\n                          title='Confusion matrix',\n                          cmap=plt.cm.Blues):\n    \"\"\"\n    This function prints and plots the confusion matrix.\n    Normalization can be applied by setting `normalize=True`.\n    \"\"\"\n    if normalize:\n        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n        print(\"Normalized confusion matrix\")\n    else:\n        print('Confusion matrix, without normalization')\n\n    print(cm)\n\n    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n    plt.title(title)\n    plt.colorbar()\n    tick_marks = np.arange(len(classes))\n    plt.xticks(tick_marks, classes, rotation=45)\n    plt.yticks(tick_marks, classes)\n\n    fmt = '.2f' if normalize else 'd'\n    thresh = cm.max() / 2.\n    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n        plt.text(j, i, format(cm[i, j], fmt),\n                 horizontalalignment=\"center\",\n                 color=\"white\" if cm[i, j] > thresh else \"black\")\n\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n    plt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_labels.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# argmax returns the index of the max value in a row\ncm = confusion_matrix(test_labels, predictions.argmax(axis=1))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_gen.class_indices","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define the labels of the class indices. These need to match the \n# order shown above.\ncm_plot_labels = ['0', '1']\n\nplot_confusion_matrix(cm, cm_plot_labels, title='Confusion Matrix')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Classification Report"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get the index of the class with the highest probability score\ny_pred = np.argmax(predictions, axis=1)\n\n# Get the labels of the test images.\ny_true = test_gen.classes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import classification_report\n\n# Generate a classification report\nreport = classification_report(y_true, y_pred, target_names=cm_plot_labels)\n\nprint(report)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> **Recall** = Given a class, will the classifier be able to detect it?<br>\n> **Precision** = Given a class prediction from a classifier, how likely is it to be correct?<br>\n> **F1 Score** = The harmonic mean of the recall and precision. Essentially, it punishes extreme values."},{"metadata":{},"cell_type":"markdown","source":"## Cohen Kappa Score (Quadratic)"},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import cohen_kappa_score\n\ncohen_kappa = cohen_kappa_score(y_true, y_pred, weights='quadratic')\n\ncohen_kappa","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Results\n\nBased on the above scores performance looks surprisingly good. It appears that diabetic retinopathy has a distinctive pattern that the model can easily detect. Classifying the severity of diabetic retinopathy may prove to be more of a challenge but simply detecting that it exists is quite easy. \n\nDetection can be easily automated, as the app demonstrates. There is also a high level of confidence that the diagnosis that the app ouputs is in fact correct. It currently processes one image at a time but it can be modified to diagnose multiple images in seconds. This can help doctors to triage patients and help speed up their diagnostic workflows. \n\nMaybe this could also evolve into a free mobile phone based tool that people with diabetes can use at home - to check themselves for diabetic retinopathy in the same way that they monitor their blood glucose levels."},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Delete the image data directory we created to prevent a Kaggle error.\n# Kaggle allows a max of 500 files to be saved.\n\nshutil.rmtree('base_dir')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## Convert the Model to Tensorflow.js"},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"# Install tensorflowjs.\n# Don't use the latest version. Instead install version 1.1.2\n\n# --ignore-installed is added to fix an error.\n\n!pip install tensorflowjs==1.1.2 --ignore-installed","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Use the command line conversion tool to convert the model\n\n!tensorflowjs_converter --input_format keras model.h5 tfjs/model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# check that the folder containing the tfjs model files has been created\n!ls","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Helpful Resources\n\n- Excellent tutorial series by deeplizard on how to use Mobilenet with Tensorflow.js<br>\nIt explains how to build and deploy a tfjs web app.<br>\nhttps://www.youtube.com/watch?v=HEQDRWMK6yY\n\n- Tensorflow.js gallery of projects<br>\nhttps://github.com/tensorflow/tfjs/blob/master/GALLERY.md\n\n- Some practical tfjs related lessons I've learned are listed on the readme page of this repo:<br>\nhttps://github.com/vbookshelf/Skin-Lesion-Analyzer\n\n- Google video discussing their work on diabetic retinopathy<br>\nhttps://www.youtube.com/watch?v=JzB7yS9t1YE&feature=youtu.be&t=261\n\n- Video about taking fundus images using a mobile phone camera<br>\nhttps://www.jove.com/video/55958/smartphone-fundus-photography"},{"metadata":{},"cell_type":"markdown","source":"## Citations\n\n- MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications<br>\nAndrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, Weijun Wang, Tobias Weyand, Marco Andreetto, Hartwig Adam<br>\nhttps://arxiv.org/abs/1704.04861\n\n- Image by cdd20 from Pixabay"},{"metadata":{},"cell_type":"markdown","source":"## Conclusion\n\nThank you for reading."}],"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}