{"cells":[{"metadata":{"papermill":{"duration":0.030876,"end_time":"2020-11-25T02:17:07.436041","exception":false,"start_time":"2020-11-25T02:17:07.405165","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# EfficientNet+Augmentation for Cassava Disease Classification using TF/Keras"},{"metadata":{"papermill":{"duration":0.028255,"end_time":"2020-11-25T02:17:07.493584","exception":false,"start_time":"2020-11-25T02:17:07.465329","status":"completed"},"tags":[]},"cell_type":"markdown","source":"This notebook presents a full pipeline to load the data, apply advanced data augmentation, train an EfficientNet and use the model to predict over the test images. Due to the original image size, we will randomly crop 300x300 images from original images in order to keep the highest image resolution possible. Previous versions of this notebook used resized images and the results were extremely poor in comparison (~0.42 accuracy)."},{"metadata":{"trusted":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 20GB 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":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2020-11-25T02:17:07.562206Z","iopub.status.busy":"2020-11-25T02:17:07.561468Z","iopub.status.idle":"2020-11-25T02:17:14.27188Z","shell.execute_reply":"2020-11-25T02:17:14.272812Z"},"papermill":{"duration":6.751504,"end_time":"2020-11-25T02:17:14.273048","exception":false,"start_time":"2020-11-25T02:17:07.521544","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom tqdm import tqdm\nfrom sklearn.utils import shuffle\nfrom sklearn.utils import class_weight\nfrom sklearn.preprocessing import minmax_scale\nimport random\nimport cv2\nfrom imgaug import augmenters as iaa\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras import layers\nfrom tensorflow.keras import Input\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, Flatten, Dropout, Activation\nfrom tensorflow.keras.layers import BatchNormalization, GlobalAveragePooling2D\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.layers import Dense, Dropout, Activation, Input, BatchNormalization, GlobalAveragePooling2D\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import EfficientNetB3\n\nfrom tensorflow.keras.layers.experimental.preprocessing import RandomCrop,CenterCrop, RandomRotation","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","execution":{"iopub.execute_input":"2020-11-25T02:17:14.363326Z","iopub.status.busy":"2020-11-25T02:17:14.362342Z","iopub.status.idle":"2020-11-25T02:17:14.789678Z","shell.execute_reply":"2020-11-25T02:17:14.790515Z"},"papermill":{"duration":0.475571,"end_time":"2020-11-25T02:17:14.790742","exception":false,"start_time":"2020-11-25T02:17:14.315171","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"img = Image.open(\"../input/cassava-leaf-disease-classification/train_images/3412658650.jpg\")\nplt.imshow(img)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.0566,"end_time":"2020-11-25T02:17:14.909535","exception":false,"start_time":"2020-11-25T02:17:14.852935","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# Prepare the training and validation data generators"},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:17:15.016255Z","iopub.status.busy":"2020-11-25T02:17:15.014446Z","iopub.status.idle":"2020-11-25T02:17:15.017056Z","shell.execute_reply":"2020-11-25T02:17:15.017552Z"},"papermill":{"duration":0.045986,"end_time":"2020-11-25T02:17:15.017674","exception":false,"start_time":"2020-11-25T02:17:14.971688","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"training_folder = '../input/cassava-leaf-disease-classification/train_images/'","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:17:15.09294Z","iopub.status.busy":"2020-11-25T02:17:15.091601Z","iopub.status.idle":"2020-11-25T02:17:15.160346Z","shell.execute_reply":"2020-11-25T02:17:15.161004Z"},"papermill":{"duration":0.109659,"end_time":"2020-11-25T02:17:15.161147","exception":false,"start_time":"2020-11-25T02:17:15.051488","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"samples_df = pd.read_csv(\"../input/cassava-leaf-disease-classification/train.csv\")\nsamples_df = shuffle(samples_df, random_state=42)\nsamples_df[\"label\"] = samples_df[\"label\"].astype(\"str\")\nsamples_df.head()","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.034044,"end_time":"2020-11-25T02:17:15.230228","exception":false,"start_time":"2020-11-25T02:17:15.196184","status":"completed"},"tags":[]},"cell_type":"markdown","source":"I keep 90% of the data provided for training and retain the other 10% for validation during my training process."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:17:15.307757Z","iopub.status.busy":"2020-11-25T02:17:15.307177Z","iopub.status.idle":"2020-11-25T02:17:15.311446Z","shell.execute_reply":"2020-11-25T02:17:15.310978Z"},"papermill":{"duration":0.044563,"end_time":"2020-11-25T02:17:15.311562","exception":false,"start_time":"2020-11-25T02:17:15.266999","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"training_percentage = 0.9\ntraining_item_count = int(len(samples_df)*training_percentage)\nvalidation_item_count = len(samples_df)-int(len(samples_df)*training_percentage)\ntraining_df = samples_df[:training_item_count]\nvalidation_df = samples_df[training_item_count:]","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.035277,"end_time":"2020-11-25T02:17:15.382273","exception":false,"start_time":"2020-11-25T02:17:15.346996","status":"completed"},"tags":[]},"cell_type":"markdown","source":"The following block of code goes through our training set and will extract the means and standard deviation of each band to apply standardisation on all future images fed to the model. This is one of the update for the notebook so I hope it will help improve the results!"},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:17:15.468483Z","iopub.status.busy":"2020-11-25T02:17:15.467301Z","iopub.status.idle":"2020-11-25T02:22:49.125733Z","shell.execute_reply":"2020-11-25T02:22:49.12518Z"},"papermill":{"duration":333.707381,"end_time":"2020-11-25T02:22:49.125883","exception":false,"start_time":"2020-11-25T02:17:15.418502","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"red_mean = []\ngreen_mean = []\nblue_mean = []\n\nred_std = []\ngreen_std = []\nblue_std = []\n\nwith tqdm(total=len(training_df)) as pbar:\n    for idx, row in training_df.iterrows():\n        pbar.update(1)\n        img = np.array(Image.open(training_folder+row.image_id))\n        red_mean.append(np.mean(img[:,:,0]))\n        green_mean.append(np.mean(img[:,:,1]))\n        blue_mean.append(np.mean(img[:,:,2]))\n        red_std.append(np.std(img[:,:,0]))\n        green_std.append(np.std(img[:,:,1]))\n        blue_std.append(np.std(img[:,:,2]))\n\ndataset_mean_rgb = [np.mean(red_mean), np.mean(green_mean), np.mean(blue_mean)]\ndataset_std_rgb = [np.mean(red_std), np.mean(green_std), np.mean(blue_std)]\n\nprint(dataset_mean_rgb)\nprint(dataset_std_rgb)","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:22:51.223975Z","iopub.status.busy":"2020-11-25T02:22:51.222103Z","iopub.status.idle":"2020-11-25T02:22:51.224688Z","shell.execute_reply":"2020-11-25T02:22:51.225271Z"},"papermill":{"duration":1.032671,"end_time":"2020-11-25T02:22:51.225447","exception":false,"start_time":"2020-11-25T02:22:50.192776","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def get_cropped_image(img_path, crop_size=300):\n    '''\n    Randomly select a 300x300 pixel area from the full image\n    '''\n    img = Image.open(img_path)\n    img_height, img_width = img.size\n    img = np.array(img)\n\n    y = random.randint(0,img_height-crop_size)\n    x = random.randint(0,img_width-crop_size)\n\n    cropped_img = img[x:x+crop_size , y:y+crop_size,:]\n    \n    return cropped_img\n\ndef standardise_channel(channel, mean, std):\n    return np.array((channel-mean)/std)\n\ndef standardise_image(image, dataset_mean, dataset_std):\n    standardised_image = np.transpose(np.array([standardise_channel(image[:,:,0], dataset_mean[0], dataset_std[0]),\n                                               standardise_channel(image[:,:,1], dataset_mean[1], dataset_std[1]), \n                                               standardise_channel(image[:,:,2], dataset_mean[2], dataset_std[2])]),\n                                     (1,2,0))\n    return standardised_image","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:22:53.487971Z","iopub.status.busy":"2020-11-25T02:22:53.48708Z","iopub.status.idle":"2020-11-25T02:22:53.73227Z","shell.execute_reply":"2020-11-25T02:22:53.732799Z"},"papermill":{"duration":1.491705,"end_time":"2020-11-25T02:22:53.732955","exception":false,"start_time":"2020-11-25T02:22:52.24125","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"cropped_img = get_cropped_image(\"../input/cassava-leaf-disease-classification/train_images/1951270318.jpg\")\nplt.imshow(cropped_img)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":1.019576,"end_time":"2020-11-25T02:22:55.926146","exception":false,"start_time":"2020-11-25T02:22:54.90657","status":"completed"},"tags":[]},"cell_type":"markdown","source":"I prepare the set of augmentations methods to use on the training data. Beside the traditional image flip and gaussian blur, I have also decided to play with the contrast and scale, as well as grayscale to help the model to focus on textures."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:22:57.98806Z","iopub.status.busy":"2020-11-25T02:22:57.986151Z","iopub.status.idle":"2020-11-25T02:22:57.988742Z","shell.execute_reply":"2020-11-25T02:22:57.989254Z"},"papermill":{"duration":1.037183,"end_time":"2020-11-25T02:22:57.989396","exception":false,"start_time":"2020-11-25T02:22:56.952213","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"augmentation = iaa.Sequential([\n    iaa.Fliplr(0.5),\n    iaa.Flipud(0.5),\n    iaa.Sometimes(0.2,\n                 iaa.GaussianBlur(sigma=(0,2))),\n    iaa.Sometimes(0.2,\n                 iaa.Grayscale(alpha=(0.0, 1.0))),\n    iaa.Sometimes(0.2,\n                 iaa.GammaContrast((0.5, 2.0))),\n    iaa.Affine(scale={\"x\": (1, 1.2), \"y\":(1, 1.2)})\n])","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":1.052704,"end_time":"2020-11-25T02:23:00.106094","exception":false,"start_time":"2020-11-25T02:22:59.05339","status":"completed"},"tags":[]},"cell_type":"markdown","source":"I implemented this custom generator to reproduce a `RandomCrop` function as I had issues with the Keras' implementation. In previous versions, I use to feed the entire image to the model. This custom generator also allows to integrate `imgaug` augmentation methods defined above."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:23:02.175542Z","iopub.status.busy":"2020-11-25T02:23:02.174747Z","iopub.status.idle":"2020-11-25T02:23:02.178643Z","shell.execute_reply":"2020-11-25T02:23:02.178164Z"},"papermill":{"duration":1.042434,"end_time":"2020-11-25T02:23:02.178756","exception":false,"start_time":"2020-11-25T02:23:01.136322","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def custom_generator(image_path_list, groundtruth_list, folder, batch_size=16, training_mode=True):\n    \n    while True:\n        for start in range(0, len(image_path_list), batch_size):\n            X_batch = []\n            Y_batch = []\n            end = min(start + batch_size, training_item_count)\n            \n            image_list = [get_cropped_image(folder+\"/\"+image_path) for image_path in image_path_list[start:end]]\n            \n            #only apply augmentation during training\n            if training_mode:\n                image_list = augmentation.augment_images(images=image_list)\n\n            #standardise the images\n            image_list = [standardise_image(image, dataset_mean_rgb, dataset_std_rgb) for image in image_list]\n            \n            X_batch = np.array(image_list)\n            Y_batch = tf.keras.utils.to_categorical(np.array(groundtruth_list[start:end]),5) \n\n            yield X_batch, Y_batch","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":1.332887,"end_time":"2020-11-25T02:23:04.596456","exception":false,"start_time":"2020-11-25T02:23:03.263569","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# Build the model"},{"metadata":{"papermill":{"duration":1.35091,"end_time":"2020-11-25T02:23:07.295196","exception":false,"start_time":"2020-11-25T02:23:05.944286","status":"completed"},"tags":[]},"cell_type":"markdown","source":"I am using an EfficientNetB3 on top of which I add some outputs layers to predict our 5 disease classes. All images are 300x300 pixels as the EfficientNetB3 was initially designed for this type of inputs. I decided to load the imagenet pretrained weights locally to keep the internet off (part of the requirements to submit a kernel to this competition)."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:23:09.567411Z","iopub.status.busy":"2020-11-25T02:23:09.56654Z","iopub.status.idle":"2020-11-25T02:23:18.81538Z","shell.execute_reply":"2020-11-25T02:23:18.814488Z"},"papermill":{"duration":10.320765,"end_time":"2020-11-25T02:23:18.815512","exception":false,"start_time":"2020-11-25T02:23:08.494747","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"batch_size = 16\nimage_size = 300\ninput_shape = (image_size, image_size, 3)\nclasses_to_predict = sorted(training_df.label.unique())\neffnet_layers = EfficientNetB3(weights=None, include_top=False, input_shape=input_shape)\neffnet_layers.load_weights(\"../input/efficientnetb3-notop/efficientnetb3_notop.h5\")\n\nfor layer in effnet_layers.layers:\n    layer.trainable = True\n\ndropout_dense_layer = 0.3\n\nmodel = Sequential()\nmodel.add(effnet_layers)\n    \nmodel.add(GlobalAveragePooling2D())\nmodel.add(Dense(256))\nmodel.add(BatchNormalization())\nmodel.add(Activation('relu'))\nmodel.add(Dropout(dropout_dense_layer))\n\nmodel.add(Dense(len(classes_to_predict), activation=\"softmax\"))\n    \nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:23:20.949943Z","iopub.status.busy":"2020-11-25T02:23:20.94931Z","iopub.status.idle":"2020-11-25T02:23:20.975587Z","shell.execute_reply":"2020-11-25T02:23:20.974684Z"},"papermill":{"duration":1.074777,"end_time":"2020-11-25T02:23:20.975689","exception":false,"start_time":"2020-11-25T02:23:19.900912","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"callbacks = [ReduceLROnPlateau(monitor='val_loss', patience=1, verbose=1, factor=0.5),\n             EarlyStopping(monitor='val_loss', patience=4),\n             ModelCheckpoint(filepath='best_model.h5', monitor='val_loss', save_best_only=True)]\n\nmodel.compile(loss=\"categorical_crossentropy\", optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4))","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:23:23.049585Z","iopub.status.busy":"2020-11-25T02:23:23.048693Z","iopub.status.idle":"2020-11-25T02:23:23.051627Z","shell.execute_reply":"2020-11-25T02:23:23.05208Z"},"papermill":{"duration":1.041096,"end_time":"2020-11-25T02:23:23.052209","exception":false,"start_time":"2020-11-25T02:23:22.011113","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"class_weights = class_weight.compute_class_weight(\"balanced\", classes_to_predict, training_df.label.values)\nclass_weights_dict = {i : class_weights[i] for i,label in enumerate(classes_to_predict)}","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T02:23:25.117718Z","iopub.status.busy":"2020-11-25T02:23:25.116934Z","iopub.status.idle":"2020-11-25T04:34:31.872693Z","shell.execute_reply":"2020-11-25T04:34:31.873477Z"},"papermill":{"duration":7867.790925,"end_time":"2020-11-25T04:34:31.873673","exception":false,"start_time":"2020-11-25T02:23:24.082748","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"history = model.fit_generator(custom_generator(training_df[\"image_id\"], training_df[\"label\"], training_folder, batch_size=batch_size, training_mode=True),\n                  steps_per_epoch = int(len(training_df)/batch_size),\n                  epochs = 20, \n                  validation_data=custom_generator(validation_df[\"image_id\"], validation_df[\"label\"], training_folder, batch_size=batch_size),\n                  validation_steps=int(len(validation_df)/batch_size),\n                  class_weight=class_weights_dict,\n                  callbacks=callbacks)","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":7.382867,"end_time":"2020-11-25T04:34:47.781981","exception":false,"start_time":"2020-11-25T04:34:40.399114","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# Verification of the training process"},{"metadata":{"papermill":{"duration":7.2499,"end_time":"2020-11-25T04:35:02.491874","exception":false,"start_time":"2020-11-25T04:34:55.241974","status":"completed"},"tags":[]},"cell_type":"markdown","source":"First, we will check that we perform on similar level on both the training and validation. The training curve will also tell us if we stopped training too early or may have overfitted in comparison to the validation data."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:35:17.878615Z","iopub.status.busy":"2020-11-25T04:35:17.87773Z","iopub.status.idle":"2020-11-25T04:35:18.063074Z","shell.execute_reply":"2020-11-25T04:35:18.063616Z"},"papermill":{"duration":7.561446,"end_time":"2020-11-25T04:35:18.063761","exception":false,"start_time":"2020-11-25T04:35:10.502315","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"plt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.title('Loss over epochs')\nplt.ylabel('Loss')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Validation'], loc='best')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":7.460053,"end_time":"2020-11-25T04:35:32.744398","exception":false,"start_time":"2020-11-25T04:35:25.284345","status":"completed"},"tags":[]},"cell_type":"markdown","source":"We load the best weights that were kept from the training phase. Just to check how our model is performing, we will attempt predictions over the validation set. This can help to highlight any classes that will be consistently miscategorised."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:35:48.948846Z","iopub.status.busy":"2020-11-25T04:35:48.947806Z","iopub.status.idle":"2020-11-25T04:35:48.952306Z","shell.execute_reply":"2020-11-25T04:35:48.952808Z"},"papermill":{"duration":7.500642,"end_time":"2020-11-25T04:35:48.952984","exception":false,"start_time":"2020-11-25T04:35:41.452342","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"os.listdir(\"../input/\")","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:36:03.75155Z","iopub.status.busy":"2020-11-25T04:36:03.746552Z","iopub.status.idle":"2020-11-25T04:36:04.065083Z","shell.execute_reply":"2020-11-25T04:36:04.064066Z"},"papermill":{"duration":7.889915,"end_time":"2020-11-25T04:36:04.065203","exception":false,"start_time":"2020-11-25T04:35:56.175288","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"model.load_weights(\"best_model.h5\")","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":7.877842,"end_time":"2020-11-25T04:36:19.371467","exception":false,"start_time":"2020-11-25T04:36:11.493625","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# Prediction on test images"},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:36:35.156057Z","iopub.status.busy":"2020-11-25T04:36:35.155383Z","iopub.status.idle":"2020-11-25T04:36:35.159886Z","shell.execute_reply":"2020-11-25T04:36:35.159334Z"},"papermill":{"duration":8.159149,"end_time":"2020-11-25T04:36:35.160024","exception":false,"start_time":"2020-11-25T04:36:27.000875","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def scan_over_image(img_path, crop_size=300):\n    '''\n    Will extract 300x300 images covering the whole original image\n    with some overlap between images\n    '''\n    \n    img = Image.open(img_path)\n    img_height, img_width = img.size\n    img = np.array(img)\n    \n    y = random.randint(0,img_height-crop_size)\n    x = random.randint(0,img_width-crop_size)\n\n    x_img_origins = [0,75,150,225,300]\n    y_img_origins = [0,125,250,375,500]\n    img_list = []\n    for x in x_img_origins:\n        for y in y_img_origins:\n            img_list.append(img[x:x+crop_size , y:y+crop_size,:])\n  \n    return img_list","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:36:50.960241Z","iopub.status.busy":"2020-11-25T04:36:50.959525Z","iopub.status.idle":"2020-11-25T04:36:57.061908Z","shell.execute_reply":"2020-11-25T04:36:57.062435Z"},"papermill":{"duration":14.061761,"end_time":"2020-11-25T04:36:57.062568","exception":false,"start_time":"2020-11-25T04:36:43.000807","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def display_samples(img_path):\n    '''\n    Display all 300x300 images extracted from original images\n    '''\n    \n    img_list = scan_over_image(img_path)\n    sample_number = len(img_list)\n    fig = plt.figure(figsize = (25,sample_number))\n    for i in range(0,sample_number):\n        ax = fig.add_subplot(5, 5, i+1)\n        ax.imshow(img_list[i])\n        ax.set_title(str(i))\n    plt.tight_layout()\n    plt.show()\n\ndisplay_samples(\"../input/cassava-leaf-disease-classification/train_images/3412658650.jpg\")","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":7.625809,"end_time":"2020-11-25T04:37:12.181153","exception":false,"start_time":"2020-11-25T04:37:04.555344","status":"completed"},"tags":[]},"cell_type":"markdown","source":"Previous versions of the model seemed to misclassify too many plants as healthy. As expected, if a disease is in its earlier stages, it will only affect a small portion of a plant. Therefore, instead of taking the most predicted diagnosis for an image, I have set a threshold and plants will only be consider \"healthy\" if 80% (7 areas out of 9) of an image is classified as such."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:37:27.816452Z","iopub.status.busy":"2020-11-25T04:37:27.81551Z","iopub.status.idle":"2020-11-25T04:37:27.822387Z","shell.execute_reply":"2020-11-25T04:37:27.826975Z"},"papermill":{"duration":7.556144,"end_time":"2020-11-25T04:37:27.827157","exception":false,"start_time":"2020-11-25T04:37:20.271013","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def predict_and_vote(image_filename, folder):\n    '''\n    Run the model over 9 local areas of the given image,\n    before making a decision depending on the most predicted\n    disease.\n    '''\n    local_image_list = scan_over_image(folder+image_filename)\n    local_image_list = [standardise_image(image, dataset_mean_rgb, dataset_std_rgb) for image in local_image_list]\n    predictions = model.predict(np.array(local_image_list))\n    prediction_list = [np.argmax(pred) for pred in predictions]\n    \n    #percentage of healthy areas to call a plant healthy\n    healthy_threshold=0.7\n    if prediction_list.count(4)>int(len(prediction_list)*healthy_threshold):\n        final_prediction = 4\n    else:\n        #keep the predictions regarding diseases and keep the highest sum overall\n        final_prediction = int(np.argmax(np.sum(predictions[:,:4], axis=0)))\n    return final_prediction","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:37:43.910726Z","iopub.status.busy":"2020-11-25T04:37:43.908914Z","iopub.status.idle":"2020-11-25T04:37:43.91156Z","shell.execute_reply":"2020-11-25T04:37:43.912046Z"},"papermill":{"duration":8.329439,"end_time":"2020-11-25T04:37:43.912168","exception":false,"start_time":"2020-11-25T04:37:35.582729","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def run_predictions_over_image_list(image_list, folder):\n    predictions = []\n    with tqdm(total=len(image_list)) as pbar:\n        for image_filename in image_list:\n            pbar.update(1)\n            predictions.append(predict_and_vote(image_filename, folder))\n    return predictions","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":7.542287,"end_time":"2020-11-25T04:37:59.602959","exception":false,"start_time":"2020-11-25T04:37:52.060672","status":"completed"},"tags":[]},"cell_type":"markdown","source":"I first test all the functions for the prediction pipeline on the validation set as we have little visibility over the test set."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:38:14.924803Z","iopub.status.busy":"2020-11-25T04:38:14.924134Z","iopub.status.idle":"2020-11-25T04:46:10.447493Z","shell.execute_reply":"2020-11-25T04:46:10.446996Z"},"papermill":{"duration":483.168631,"end_time":"2020-11-25T04:46:10.447607","exception":false,"start_time":"2020-11-25T04:38:07.278976","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"validation_df[\"results\"] = run_predictions_over_image_list(validation_df[\"image_id\"], training_folder)","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:46:27.207843Z","iopub.status.busy":"2020-11-25T04:46:27.207218Z","iopub.status.idle":"2020-11-25T04:46:28.048848Z","shell.execute_reply":"2020-11-25T04:46:28.047582Z"},"papermill":{"duration":9.211948,"end_time":"2020-11-25T04:46:28.048984","exception":false,"start_time":"2020-11-25T04:46:18.837036","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"!cat ../input/cassava-leaf-disease-classification/label_num_to_disease_map.json","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:46:46.054557Z","iopub.status.busy":"2020-11-25T04:46:46.053639Z","iopub.status.idle":"2020-11-25T04:46:46.059301Z","shell.execute_reply":"2020-11-25T04:46:46.059771Z"},"papermill":{"duration":8.737563,"end_time":"2020-11-25T04:46:46.059896","exception":false,"start_time":"2020-11-25T04:46:37.322333","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"validation_df[:20]","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:47:02.886522Z","iopub.status.busy":"2020-11-25T04:47:02.876394Z","iopub.status.idle":"2020-11-25T04:47:03.117416Z","shell.execute_reply":"2020-11-25T04:47:03.116898Z"},"papermill":{"duration":8.743135,"end_time":"2020-11-25T04:47:03.117524","exception":false,"start_time":"2020-11-25T04:46:54.374389","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"true_positives = 0\nprediction_distribution_per_class = {\"0\":{\"0\": 0, \"1\": 0, \"2\":0, \"3\":0, \"4\":0},\n                                     \"1\":{\"0\": 0, \"1\": 0, \"2\":0, \"3\":0, \"4\":0},\n                                     \"2\":{\"0\": 0, \"1\": 0, \"2\":0, \"3\":0, \"4\":0},\n                                     \"3\":{\"0\": 0, \"1\": 0, \"2\":0, \"3\":0, \"4\":0},\n                                     \"4\":{\"0\": 0, \"1\": 0, \"2\":0, \"3\":0, \"4\":0}}\nnumber_of_images = len(validation_df)\nfor idx, pred in validation_df.iterrows():\n    if int(pred[\"label\"]) == pred.results:\n        true_positives+=1\n    prediction_distribution_per_class[pred[\"label\"]][str(pred.results)] += 1\nprint(\"accuracy: {}%\".format(true_positives/number_of_images*100))","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:47:20.157021Z","iopub.status.busy":"2020-11-25T04:47:20.15639Z","iopub.status.idle":"2020-11-25T04:47:20.161566Z","shell.execute_reply":"2020-11-25T04:47:20.162054Z"},"papermill":{"duration":8.162039,"end_time":"2020-11-25T04:47:20.162186","exception":false,"start_time":"2020-11-25T04:47:12.000147","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"prediction_distribution_per_class","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":9.119976,"end_time":"2020-11-25T04:47:37.617321","exception":false,"start_time":"2020-11-25T04:47:28.497345","status":"completed"},"tags":[]},"cell_type":"markdown","source":"We can also have a better understanding of where this new model misclassifies diseases by plotting a heatmap from the results. Each row in this heatmap is normalised to highlight the classification distribution per disease without being bothered by the fact that the dataset is imbalanced."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:47:54.787102Z","iopub.status.busy":"2020-11-25T04:47:54.786194Z","iopub.status.idle":"2020-11-25T04:47:55.70063Z","shell.execute_reply":"2020-11-25T04:47:55.701197Z"},"papermill":{"duration":9.1771,"end_time":"2020-11-25T04:47:55.701367","exception":false,"start_time":"2020-11-25T04:47:46.524267","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"heatmap_df = pd.DataFrame(columns={\"groundtruth\",\"prediction\",\"value\"})\nfor key in prediction_distribution_per_class.keys():\n    for pred_key in prediction_distribution_per_class[key].keys():\n        value = prediction_distribution_per_class[key][pred_key]/validation_df.query(\"label==@key\").count()[0]\n        heatmap_df = heatmap_df.append({\"groundtruth\":key,\"prediction\":pred_key,\"value\":value}, ignore_index=True)   \n\nheatmap = heatmap_df.pivot(index='groundtruth', columns='prediction', values='value')\nsns.heatmap(heatmap,cmap=\"Blues\")","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:48:12.757548Z","iopub.status.busy":"2020-11-25T04:48:12.754371Z","iopub.status.idle":"2020-11-25T04:48:12.761304Z","shell.execute_reply":"2020-11-25T04:48:12.762838Z"},"papermill":{"duration":8.872537,"end_time":"2020-11-25T04:48:12.763028","exception":false,"start_time":"2020-11-25T04:48:03.890491","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"test_folder = '../input/cassava-leaf-disease-classification/test_images/'\nsubmission_df = pd.DataFrame(columns={\"image_id\",\"label\"})\nsubmission_df[\"image_id\"] =  os.listdir(test_folder)\nsubmission_df[\"label\"] = 0","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:48:29.749495Z","iopub.status.busy":"2020-11-25T04:48:29.748724Z","iopub.status.idle":"2020-11-25T04:48:30.111218Z","shell.execute_reply":"2020-11-25T04:48:30.110184Z"},"papermill":{"duration":8.752879,"end_time":"2020-11-25T04:48:30.111338","exception":false,"start_time":"2020-11-25T04:48:21.358459","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"submission_df[\"label\"] = run_predictions_over_image_list(submission_df[\"image_id\"], test_folder)","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:48:47.955633Z","iopub.status.busy":"2020-11-25T04:48:47.954809Z","iopub.status.idle":"2020-11-25T04:48:47.958512Z","shell.execute_reply":"2020-11-25T04:48:47.959121Z"},"papermill":{"duration":8.88238,"end_time":"2020-11-25T04:48:47.959263","exception":false,"start_time":"2020-11-25T04:48:39.076883","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"submission_df","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-25T04:49:04.657084Z","iopub.status.busy":"2020-11-25T04:49:04.656444Z","iopub.status.idle":"2020-11-25T04:49:04.972471Z","shell.execute_reply":"2020-11-25T04:49:04.971833Z"},"papermill":{"duration":8.706199,"end_time":"2020-11-25T04:49:04.972604","exception":false,"start_time":"2020-11-25T04:48:56.266405","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"submission_df.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":8.813184,"end_time":"2020-11-25T04:49:22.288174","exception":false,"start_time":"2020-11-25T04:49:13.47499","status":"completed"},"tags":[]},"cell_type":"markdown","source":"### Thanks for reading this notebook! If you found this notebook helpful, please give it an upvote. It is always greatly appreciated!"}],"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}