{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Introduction\nThis notebook is created by Britt Gerritsen and Kika Banning and is part of the Cassava leaf diseas classification competition. \n\nAs the second-largest provider of carbohydrates in Africa, cassava is a key food security crop grown by smallholder farmers because it can withstand harsh conditions. At least 80% of household farms in Sub-Saharan Africa grow this starchy root, but viral diseases are major sources of poor yields. With the help of data science, it may be possible to identify common diseases so they can be treated. Our dataset consists of 21,367 labeled images collected during a regular survey in Uganda. Our goal is to classify each cassava image into four disease categories or a fifth category indicating a healthy leaf. With our help, farmers may be able to quickly identify diseased plants, potentially saving their crops before they inflict irreparable damage.\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":0.039382,"end_time":"2020-11-19T21:45:23.042097","exception":false,"start_time":"2020-11-19T21:45:23.002715","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Set up environment\n## Load libraries","metadata":{"papermill":{"duration":0.037375,"end_time":"2020-11-19T21:45:23.192515","exception":false,"start_time":"2020-11-19T21:45:23.15514","status":"completed"},"tags":[]}},{"cell_type":"code","source":"!pip install --upgrade tensorflow","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:34:13.153326Z","iopub.execute_input":"2021-12-24T12:34:13.153913Z","iopub.status.idle":"2021-12-24T12:35:51.460494Z","shell.execute_reply.started":"2021-12-24T12:34:13.153877Z","shell.execute_reply":"2021-12-24T12:35:51.459009Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# System related imports\nimport math, re, os, warnings\n\n# Data manipulation\nimport numpy as np\nimport pandas as pd\nimport json\nimport shutil\n\n# Visualization imports\nimport matplotlib.pyplot as plt\nfrom matplotlib import gridspec\n\n# Deep learning framework\nimport tensorflow as tf\nfrom kaggle_datasets import KaggleDatasets\nfrom tensorflow import keras\nfrom functools import partial\nfrom sklearn.model_selection import train_test_split\nimport tensorflow_hub as hub\nfrom tensorflow import keras\nfrom tensorflow.keras import preprocessing\nfrom tensorflow.keras.layers.experimental import preprocessing\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.callbacks import EarlyStopping\ntf.config.optimizer.set_jit(True)\nfrom tensorflow.keras.preprocessing import image_dataset_from_directory\nfrom tensorflow.keras import regularizers\nimport cv2\n\n# Validation\nfrom sklearn import metrics\n\n# Check if tensorflow is updated\nprint(\"Tensorflow version \" + tf.__version__)","metadata":{"papermill":{"duration":6.890298,"end_time":"2020-11-19T21:45:30.119979","exception":false,"start_time":"2020-11-19T21:45:23.229681","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2021-12-24T12:40:48.557852Z","iopub.execute_input":"2021-12-24T12:40:48.558288Z","iopub.status.idle":"2021-12-24T12:40:52.971506Z","shell.execute_reply.started":"2021-12-24T12:40:48.558252Z","shell.execute_reply":"2021-12-24T12:40:52.970080Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Set Matplotlib defaults\nplt.rc('figure', autolayout=True)\nplt.rc('axes', labelweight='bold', labelsize='large',\n       titleweight='bold', titlesize=18, titlepad=10)\nplt.rc('image', cmap='magma')\nwarnings.filterwarnings(\"ignore\") ","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:40:56.155192Z","iopub.execute_input":"2021-12-24T12:40:56.155652Z","iopub.status.idle":"2021-12-24T12:40:56.162493Z","shell.execute_reply.started":"2021-12-24T12:40:56.155614Z","shell.execute_reply":"2021-12-24T12:40:56.161295Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Place images in the right folders\n\nBecause we are unable to manipulate the images from the input folders directly, we will transfer them to new folders in the output, sorted by their class. ","metadata":{}},{"cell_type":"code","source":"# Create training and validation folder\nos.mkdir('/kaggle/working/train_data/')\nos.mkdir('/kaggle/working/valid_data/')\n\n# Open file with labels \ndataset = pd.read_csv(\"../input/cassava-leaf-disease-classification/train.csv\")\n\n# Split training images in training and validation images\ntraining_data, validation_data = train_test_split(dataset, test_size=0.33)\n\ntraining_file_names = list(training_data['image_id'].values) \ntraining_img_labels = list(training_data['label'].values) \nvalidation_file_names = list(validation_data['image_id'].values) \nvalidation_img_labels = list(validation_data['label'].values) \n\n# Create folders of labels\nfolders_to_be_created = np.unique(list(dataset['label']))\n\n# Create folders for training and validation images\nfor new_path in folders_to_be_created: \n    if not os.path.exists(\".//\" + str(new_path)):\n        train_map = os.path.join('/kaggle/working/train_data/', str(new_path))\n        valid_map = os.path.join('/kaggle/working/valid_data/', str(new_path))\n        os.makedirs(train_map)\n        os.makedirs(valid_map)\n        \nfolders = folders_to_be_created.copy() \n\n# Set source and destination\nsource = \"../input/cassava-leaf-disease-classification/train_images\"\ntraining_destination = '/kaggle/working/train_data'\nvalidation_destination = '/kaggle/working/valid_data'\n\n# Places training images in the train folders   \nfor f in range(len(training_file_names)): \n    tr_current_img = training_file_names[f] \n    tr_current_label = training_img_labels[f] \n    src = os.path.join(source, tr_current_img)\n    dst = os.path.join(training_destination, str(tr_current_label))\n    shutil.copy(src, dst)\n    \n# Places validation images in the validation folders    \nfor f in range(len(validation_file_names)): \n    va_current_img = validation_file_names[f] \n    va_current_label = validation_img_labels[f] \n    src = os.path.join(source, va_current_img)\n    dst = os.path.join(validation_destination, str(va_current_label))\n    shutil.copy(src, dst)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:40:58.911679Z","iopub.execute_input":"2021-12-24T12:40:58.912174Z","iopub.status.idle":"2021-12-24T12:43:24.400582Z","shell.execute_reply.started":"2021-12-24T12:40:58.912108Z","shell.execute_reply":"2021-12-24T12:43:24.398934Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Exploratory data analysis (EDA)\n\nWe want to take do some initial investigations on the data so as to discover patterns and to spot anomalies with the help of summary statistics and graphical representations.","metadata":{}},{"cell_type":"markdown","source":"### Explore disease types\n\nWe first map the label number to the different disease types.","metadata":{}},{"cell_type":"code","source":"with open('/kaggle/input/cassava-leaf-disease-classification/label_num_to_disease_map.json') as f:\n    mapping = json.loads(f.read())\n    print(mapping)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:31:45.572763Z","iopub.execute_input":"2021-12-24T16:31:45.573233Z","iopub.status.idle":"2021-12-24T16:31:45.586388Z","shell.execute_reply.started":"2021-12-24T16:31:45.573194Z","shell.execute_reply":"2021-12-24T16:31:45.585366Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualise disease types\n\nWe adapted the code from Manu Siddhartha's notebook for our visualisations. (Siddhartha, M. (2021, July). *Cassava Leaf Disease Classification EfficientNet*. Kaggle https://www.kaggle.com/sid321axn/cassava-leaf-disease-classification-efficientnet)","metadata":{}},{"cell_type":"code","source":"def visualize(img_list):\n    rows = 1\n    cols = 3\n\n    plt.figure(figsize=(18, 10))\n\n    for i in range(rows*cols):\n        plt.subplot(10/cols+1, cols, i+1)\n        r = np.random.randint(len(img_list))\n        img_path = \"/kaggle/input/cassava-leaf-disease-classification/train_images/\" + str(img_list[r])\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        plt.xticks([])\n        plt.yticks([])\n        plt.title(str(img_list[r]))\n        plt.imshow(img)\n       \n\n    plt.tight_layout()\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:31:47.926331Z","iopub.execute_input":"2021-12-24T16:31:47.926915Z","iopub.status.idle":"2021-12-24T16:31:47.937727Z","shell.execute_reply.started":"2021-12-24T16:31:47.926880Z","shell.execute_reply":"2021-12-24T16:31:47.936276Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### '0': 'Cassava Bacterial Blight (CBB)","metadata":{}},{"cell_type":"code","source":"cbb_df = dataset[dataset['label'].isin([0])]\ncbb_img_list = list(dataset['image_id'])\n\nvisualize(cbb_img_list)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:35.096318Z","iopub.execute_input":"2021-12-24T12:18:35.096663Z","iopub.status.idle":"2021-12-24T12:18:35.632142Z","shell.execute_reply.started":"2021-12-24T12:18:35.096633Z","shell.execute_reply":"2021-12-24T12:18:35.631329Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### '1': 'Cassava Brown Streak Disease (CBSD)'","metadata":{}},{"cell_type":"code","source":"cbb_df = dataset[dataset['label'].isin([1])]\ncbb_img_list = list(dataset['image_id'])\n\nvisualize(cbb_img_list)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:36.502718Z","iopub.execute_input":"2021-12-24T12:18:36.503358Z","iopub.status.idle":"2021-12-24T12:18:36.876538Z","shell.execute_reply.started":"2021-12-24T12:18:36.503319Z","shell.execute_reply":"2021-12-24T12:18:36.875615Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### '2': 'Cassava Green Mottle (CGM)'","metadata":{}},{"cell_type":"code","source":"cbb_df = dataset[dataset['label'].isin([2])]\ncbb_img_list = list(dataset['image_id'])\n\nvisualize(cbb_img_list)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:41.238667Z","iopub.execute_input":"2021-12-24T12:18:41.239048Z","iopub.status.idle":"2021-12-24T12:18:41.616286Z","shell.execute_reply.started":"2021-12-24T12:18:41.239015Z","shell.execute_reply":"2021-12-24T12:18:41.615082Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### '3': 'Cassava Mosaic Disease (CMD)'","metadata":{}},{"cell_type":"code","source":"cbb_df = dataset[dataset['label'].isin([3])]\ncbb_img_list = list(dataset['image_id'])\n\nvisualize(cbb_img_list)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:44.831553Z","iopub.execute_input":"2021-12-24T12:18:44.831923Z","iopub.status.idle":"2021-12-24T12:18:45.219578Z","shell.execute_reply.started":"2021-12-24T12:18:44.831889Z","shell.execute_reply":"2021-12-24T12:18:45.218787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### '4': 'Healthy'","metadata":{}},{"cell_type":"code","source":"cbb_df = dataset[dataset['label'].isin([4])]\ncbb_img_list = list(dataset['image_id'])\n\nvisualize(cbb_img_list)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:48.991686Z","iopub.execute_input":"2021-12-24T12:18:48.992086Z","iopub.status.idle":"2021-12-24T12:18:49.385783Z","shell.execute_reply.started":"2021-12-24T12:18:48.992052Z","shell.execute_reply":"2021-12-24T12:18:49.384789Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Look at the target distribution","metadata":{}},{"cell_type":"markdown","source":"In the plot below, the amount of unique images per category is shown. We concluded that there were substantially more images in class 3 which shows the Cassava Mosaic Disease (CMD).","metadata":{}},{"cell_type":"code","source":"dataset['label'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:53.575652Z","iopub.execute_input":"2021-12-24T12:18:53.575999Z","iopub.status.idle":"2021-12-24T12:18:53.591299Z","shell.execute_reply.started":"2021-12-24T12:18:53.575971Z","shell.execute_reply":"2021-12-24T12:18:53.589725Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ax = dataset['label'].value_counts().plot(kind='bar', title = 'Image count per disease type')\nax.set_xlabel(\"Disease types\")\nax.set_ylabel(\"Image counts\")","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:18:56.105067Z","iopub.execute_input":"2021-12-24T12:18:56.105818Z","iopub.status.idle":"2021-12-24T12:18:56.284342Z","shell.execute_reply.started":"2021-12-24T12:18:56.105778Z","shell.execute_reply":"2021-12-24T12:18:56.283231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Balance the dataset\n\nWe discovered that class 3 has much more training samples than the other classes, therefore we decided to remove a portion of the images in this class for both the training and validation dataset. \n\nAnother option was customizing our weights for training in de loss function, however this option was less functional and resulted in a lower accuracy and a peculiar loss curve. ","metadata":{}},{"cell_type":"code","source":"# Check how much images are in class 3 compared to other classes\npath, dirs, files = next(os.walk(\"/kaggle/working/train_data/3\"))\nprint(len(files))\n\npath, dirs, files = next(os.walk(\"/kaggle/working/valid_data/3\"))\nprint(len(files))\n\n# training photo's removal\nfilenames = os.listdir(\"/kaggle/working/train_data/3\")\n\nfor i in filenames[:7000]:\n    os.remove(\"/kaggle/working/train_data/3/\" + i) \n    \n# validation photo's removal\nfilenames_valid = os.listdir(\"/kaggle/working/valid_data/3\")\n\nfor i in filenames_valid[:3500]:\n    os.remove(\"/kaggle/working/valid_data/3/\" + i) \n\n# Check if it worked\npath, dirs, files = next(os.walk(\"/kaggle/working/train_data/3\"))\nprint(len(files))\n\npath, dirs, files = next(os.walk(\"/kaggle/working/valid_data/3\"))\nprint(len(files))\n\n# Calculate weights for each class\n#total_images = 8452 + 1580 + 1479 + 706 + 1718\n#weight_0 = 1 / (706/total_images)\n#weight_1 = 1 / (1479/total_images)\n#weight_2 = 1 / (1580/total_images)\n#weight_3 = 1 / (8452/total_images)\n#weight_4 = 1 / (1718/total_images)\n    ","metadata":{"execution":{"iopub.status.busy":"2021-12-24T12:44:05.212860Z","iopub.execute_input":"2021-12-24T12:44:05.213316Z","iopub.status.idle":"2021-12-24T12:44:05.831272Z","shell.execute_reply.started":"2021-12-24T12:44:05.213271Z","shell.execute_reply":"2021-12-24T12:44:05.830475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Set up variables\nWe'll set up some of our variables and functions for our notebook here. \n\n#### Variables\n* `BATCH_SIZE`: The amount of data included in each sub-epoch weight change. A larger batch size means a degradation in model quality and thus less ability to generalize. We therefore chose a batch size of 64. The downside of a smaller batch size is that it takes more time to run. (Keshar, N. S., Mudigere, D., Nocedal, J., Smelyanskiy, M., & Tang, P. T. P. (2016, Sep 17). *On large-batch training for deep learning: Generalization gap and sharp minima.* Arxiv. https://arxiv.org/abs/1609.04836) \n* `IMAGE_SIZE`: Dimensions of the images in the dataset in pixels. Due to memory and speed considerations, we picked an image_size of 128 x 128. \n* `CLASSES`: Four disease categories and a fifth category indicating a healthy leaf.\n* `EPOCHS`: The number of times the whole training dataset is passed through the model. We chose 30 epochs because from multiple training it appeared that around 30 the maximum accuracy was reached. \n\n#### Functions\n* `early_stopping`: we make an early stopping function which we can implement as a callback in the model.fit() to prevent overfitting. ","metadata":{}},{"cell_type":"code","source":"AUTOTUNE = tf.data.experimental.AUTOTUNE\nBATCH_SIZE = 64\nIMAGE_SIZE = [128, 128]\nCLASSES = ['0', '1', '2', '3', '4']\nEPOCHS = 30\n\nearly_stopping = EarlyStopping(\n    min_delta=0.01,\n    patience=15, \n    restore_best_weights=True,\n    monitor = 'val_sparse_categorical_accuracy', \n    mode = 'max',\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T15:43:26.400303Z","iopub.execute_input":"2021-12-24T15:43:26.400729Z","iopub.status.idle":"2021-12-24T15:43:26.407693Z","shell.execute_reply.started":"2021-12-24T15:43:26.400695Z","shell.execute_reply":"2021-12-24T15:43:26.406611Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load training and validation datasets \n\nFor tweaking of the parameters we used smaller training and validation sets.","metadata":{}},{"cell_type":"code","source":"# Load training and validation sets\nds_train_ = image_dataset_from_directory(\n    '/kaggle/working/train_data',\n    labels='inferred',\n    label_mode='int',\n    image_size=IMAGE_SIZE, \n    interpolation='nearest',\n    batch_size=BATCH_SIZE,  \n    shuffle=True,\n    )\n\nds_valid_ = image_dataset_from_directory(\n    '/kaggle/working/valid_data',\n    labels='inferred',\n    label_mode='int',\n    image_size=IMAGE_SIZE,\n    interpolation='nearest',\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n)\n\n# Data pipeline\ndef convert_to_float(image, label):\n    image = tf.image.convert_image_dtype(image, dtype=tf.float32)\n    return image, label\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE\nds_train = (\n    ds_train_\n    .map(convert_to_float)\n    .cache()\n    .prefetch(buffer_size=AUTOTUNE)\n)\nds_valid = (\n    ds_valid_\n    .map(convert_to_float)\n    .cache()\n    .prefetch(buffer_size=AUTOTUNE)\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T15:43:31.701273Z","iopub.execute_input":"2021-12-24T15:43:31.701938Z","iopub.status.idle":"2021-12-24T15:43:32.365782Z","shell.execute_reply.started":"2021-12-24T15:43:31.701893Z","shell.execute_reply":"2021-12-24T15:43:32.364750Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Building the model\n\n## Computer vision model with a self-defined base\n\nOur model is a convolutional neural network model with a self-defined base constisting of two blocks of convolutional and pooling layers, and a head consisting of a flatten layer and 4 dense layers. We chose not to build a model with a pre-trained base because this gave us memory problems. We did try several of them: VGG-16, ResNet50, Inceptionv3, and EfficientNetB0.\n\n### Model specifications\n#### Preprocessing:\n* `input_shape = [128,128,3]`: we chose to reduce the image size to reduce training time while still keeping enough image information.\n* `layers.Rescaling(1./255)`: we rescale the input in the [0, 255] range to be in the [0, 1] range.\n* `preprocessing`: we add data augmentation so that we have more data to train the model on. We only use a few, so that the color remains, because color is a feature for distinguishing the classes. \n\n#### Base:\n* The base consists of 4 equal blocks of two convolutional layers and a max-pooling layer.\n* `layers.BatchNormalization(renorm=True)`: applies a transformation that maintains the mean output close to 0 and the output standard deviation close to 1.\n* `layers.Dropout(0.2 and 0.3)`: randomly sets input units to 0 with a frequency of 0.2 at each step during training time, which helps prevent overfitting. Inputs not set to 0 are scaled up by 1/(1 - rate) such that the sum over all inputs is unchanged. (Baldi, P., & Sadowski, P. J. (2013). Understanding dropout. In *Advances in neural information processing systems* (pp. 2814–2822)).\n* `kernel_size`: a kernel size of 3 is adviced. (Sahoo, S. (2018, August 19). *Deciding optimal kernel size for CNN*. Towards Data Science. https://towardsdatascience.com/deciding-optimal-filter-size-for-cnns-d6f7b56f9363)\n)\n* The chosen values for the parameters rendered the highest accuracy. They are determined by trial-and-error.\n\n#### Head:\n* Our head consists of a flatten layer and 2 dense layers with dropout (0.3).\n* `layers.Flatten()` flattens the input while not affecting the batch size.\n* `regulizer`: here we use a L2 regulizer, which is adviced for image classification tasks. (Chollet, F. (2017). *Overfit and underfit*. Tensorflow. https://www.tensorflow.org/tutorials/keras/overfit_and_underfit)\n* The final layer consists of 5 nodes, corresponding to the amount of classes, and a `softmax` activation function. This function is used for multi-class predictions. The sum of all outputs generated by softmax is 1.","metadata":{}},{"cell_type":"code","source":"model = keras.Sequential([\n    layers.InputLayer(input_shape=[128, 128, 3]),\n    layers.Rescaling(1./255),\n    layers.Dropout(0.2),\n    \n    # Data Augmentation\n    preprocessing.RandomFlip(mode='horizontal'), \n    preprocessing.RandomRotation(factor=0.1),\n    preprocessing.RandomFlip(mode='vertical'), \n    \n    # Block One\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=64, \n                  kernel_size=3,\n                  activation='relu',\n                  padding='same',\n                  strides = (2,2)),\n    layers.Conv2D(filters=64, \n                  kernel_size=3,\n                  activation='relu',\n                  padding='same',\n                  strides = (2,2)),\n    layers.MaxPool2D(pool_size=2,\n                     strides=2,\n                     padding='same'),\n    layers.Dropout(0.2), \n    \n    # Block Two\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=128,\n                  kernel_size=3,\n                  activation='relu',\n                  padding='same', \n                  strides = (2,2)),\n    layers.Conv2D(filters=128,\n                  kernel_size=3,\n                  activation='relu',\n                  padding='same', \n                  strides = (2,2)),\n    layers.MaxPool2D(pool_size=2,\n                     strides=2,\n                     padding='same'),\n    layers.Dropout(0.2),\n    \n    # Block Three\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=128,\n                  kernel_size=3,\n                  activation='relu', \n                  padding='same',\n                  strides = (2,2)),\n    layers.Conv2D(filters=128,\n                  kernel_size=3,\n                  activation='relu',\n                  padding='same',\n                  strides = (2,2)),\n    layers.MaxPool2D(pool_size=2,\n                     strides=2,\n                     padding='same'),\n    layers.Dropout(0.2),\n    \n    # Block four\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=256,\n                  kernel_size=3,\n                  activation='relu', \n                  padding='same',\n                  strides = (2,2)),\n    layers.Conv2D(filters=256,\n                  kernel_size=3,\n                  activation='relu',\n                  padding='same',\n                  strides = (2,2)),\n    layers.MaxPool2D(pool_size=2,\n                     strides=2,\n                     padding='same'),\n    layers.Dropout(0.2),\n\n    # Head\n    layers.BatchNormalization(renorm=True),\n    layers.Flatten(),\n    layers.Dense(128, activation='relu', kernel_regularizer=regularizers.l2(0.001)),\n    layers.Dropout(0.3),\n    layers.Dense(128, activation='relu', kernel_regularizer=regularizers.l2(0.001)),\n    layers.Dropout(0.3),\n    layers.Dense(len(CLASSES), activation='softmax'),\n])","metadata":{"execution":{"iopub.status.busy":"2021-12-24T15:43:37.483967Z","iopub.execute_input":"2021-12-24T15:43:37.484389Z","iopub.status.idle":"2021-12-24T15:43:37.904232Z","shell.execute_reply.started":"2021-12-24T15:43:37.484330Z","shell.execute_reply":"2021-12-24T15:43:37.903042Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"With model.summary() we'll see a printout of each of our layers, their corresponding shape, as well as the associated number of parameters.","metadata":{"papermill":{"duration":1.26977,"end_time":"2020-11-19T22:04:49.3763","exception":false,"start_time":"2020-11-19T22:04:48.10653","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:35:05.495678Z","iopub.execute_input":"2021-12-24T16:35:05.496140Z","iopub.status.idle":"2021-12-24T16:35:05.514941Z","shell.execute_reply.started":"2021-12-24T16:35:05.496100Z","shell.execute_reply":"2021-12-24T16:35:05.513946Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train the model\n\n### Training specifications:\n* `optimizer = 'adam'`: we chose Adam as an optimizer because it is computationally efficient, requires little memory, and is well suited for problems that are large in terms of data or parameters or both.\n* We are using `sparse_categorical_crossentropy` as our loss function and `sparse_categorical_accuracy` as our performance metric, because we did _not_ one-hot encode our labels. The four disease categories and the fifth category indicating a healthy leaf are mutually exclusive (e.g. each image belongs to one of the classes). (Author Unknown, (Date unknown). *Sparse Categorical Crossentropy Class*. Keras. https://keras.io/api/losses/probabilistic_losses/#sparsecategoricalcrossentropy-class)","metadata":{"papermill":{"duration":0.174404,"end_time":"2020-11-19T21:48:49.513099","exception":false,"start_time":"2020-11-19T21:48:49.338695","status":"completed"},"tags":[]}},{"cell_type":"code","source":"optimizer = tf.keras.optimizers.Adam(lr=0.0001)\n\nmodel.compile(\n    optimizer = optimizer,\n    loss='sparse_categorical_crossentropy',\n    metrics=['sparse_categorical_accuracy'], \n)\n\nhistory = model.fit(\n    ds_train,\n    validation_data=ds_valid,\n    epochs=EPOCHS,\n    batch_size = BATCH_SIZE,\n    callbacks=[early_stopping],\n    verbose=1\n    #class_weight = {0: weight_0, \n                    #1: weight_1, \n                    #2: weight_2, \n                    #3: weight_3, \n                    #4: weight_4, \n                    #}\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T15:43:45.045579Z","iopub.execute_input":"2021-12-24T15:43:45.045993Z","iopub.status.idle":"2021-12-24T16:26:14.492194Z","shell.execute_reply.started":"2021-12-24T15:43:45.045953Z","shell.execute_reply":"2021-12-24T16:26:14.491068Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Evaluating our model\nThe model is evaluated by the loss function and the accuracy of the training and the validation set. ","metadata":{"papermill":{"duration":1.245239,"end_time":"2020-11-19T22:04:54.493139","exception":false,"start_time":"2020-11-19T22:04:53.2479","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# loss and accuracy graph\nhistory_frame = pd.DataFrame(history.history)\nhistory_frame.loc[:, ['loss', 'val_loss']].plot()\nhistory_frame.loc[:, ['sparse_categorical_accuracy', 'val_sparse_categorical_accuracy']].plot();","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:26:19.973252Z","iopub.execute_input":"2021-12-24T16:26:19.974374Z","iopub.status.idle":"2021-12-24T16:26:20.392154Z","shell.execute_reply.started":"2021-12-24T16:26:19.974248Z","shell.execute_reply":"2021-12-24T16:26:20.391124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It is visible here that we reach an accuracy of ±0.4 at maximum. Additionally, no under or overfitting is visible here. ","metadata":{}},{"cell_type":"code","source":"# Make predictions\npredictions = model.predict(ds_valid)\nprint(predictions)\n\n# Count individual counts per class\nvalues, counts = np.unique([CLASSES[np.argmax(predictions[i,])] for i in range(len(predictions))], return_counts=True)\nprint(values, counts)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The values per category show that the model predicts class 4 for most of the images. Also, class 0 is identified least, which might be explained by the fact that class 0 had the least training images.  ","metadata":{}},{"cell_type":"code","source":"# Calculate Cohen Kappa score to determine how far away from 'chance' we are\nclasss = [0, 1, 2, 3, 4]\n\ntarget = np.concatenate([label for example, label in ds_valid])\n\nmetrics.cohen_kappa_score([classs[np.argmax(predictions[i,])] for i in range(len(predictions))], target)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:27:00.639934Z","iopub.execute_input":"2021-12-24T16:27:00.640646Z","iopub.status.idle":"2021-12-24T16:27:00.709482Z","shell.execute_reply.started":"2021-12-24T16:27:00.640587Z","shell.execute_reply":"2021-12-24T16:27:00.708400Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The Cohen Kappa is a value between 0 and 1 and it indicates how much this model classifies away from just chance. A high Cohen Kappa indicates very far away from chance. Here a value of 0.21 is calculated, which means this model is not very far away from chance unfortunately. ","metadata":{}},{"cell_type":"markdown","source":"# Making predictions on testset\nNow that we've trained our model we can use it to make predictions on the test image. \n\nWe adapted the code from Aayush Mishraa's notebook for our visualisations. (Mishraa, A. (2021). *Cassava Leaf Disease 69% Acc [Simple CNN approach]*. Kaggle. https://www.kaggle.com/aayushmishra1512/cassava-leaf-disease-69-acc-simple-cnn-approach)","metadata":{"papermill":{"duration":1.326243,"end_time":"2020-11-19T22:05:02.628032","exception":false,"start_time":"2020-11-19T22:05:01.301789","status":"completed"},"tags":[]}},{"cell_type":"code","source":"preds = []\nss = pd.read_csv('../input/cassava-leaf-disease-classification/sample_submission.csv')\n\nfor image in ss.image_id:\n    img = tf.keras.preprocessing.image.load_img('../input/cassava-leaf-disease-classification/test_images/' + image)\n    img = tf.keras.preprocessing.image.img_to_array(img)\n    img = tf.keras.preprocessing.image.smart_resize(img, (128, 128))\n    img = tf.reshape(img, (-1, 128, 128, 3))\n    prediction = model.predict(img/255)\n    preds.append(np.argmax(prediction))\n\nsubmission = pd.DataFrame({'image_id': ss.image_id, 'label': preds})\nsubmission","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:44:40.713830Z","iopub.execute_input":"2021-12-24T16:44:40.714221Z","iopub.status.idle":"2021-12-24T16:44:41.002794Z","shell.execute_reply.started":"2021-12-24T16:44:40.714187Z","shell.execute_reply":"2021-12-24T16:44:41.001707Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Creating a submission file\nNow that we've trained a model and made predictions we're ready to submit to the competition.","metadata":{"papermill":{"duration":1.271799,"end_time":"2020-11-19T22:05:24.759257","exception":false,"start_time":"2020-11-19T22:05:23.487458","status":"completed"},"tags":[]}},{"cell_type":"code","source":"submission.to_csv('submission.csv', index = False)","metadata":{"execution":{"iopub.status.busy":"2021-12-24T16:45:10.348441Z","iopub.execute_input":"2021-12-24T16:45:10.349023Z","iopub.status.idle":"2021-12-24T16:45:10.356282Z","shell.execute_reply.started":"2021-12-24T16:45:10.348969Z","shell.execute_reply":"2021-12-24T16:45:10.355403Z"},"trusted":true},"execution_count":null,"outputs":[]}]}