Cassava Disease Detection | Python

Cassava disease affects the overall growth of the plants and their tubers depending on the level of infection. In this article, we propose a very unique deep learning method using Transfer Learning via EfficientNet resulting in an overall acc of 89.15% and 88.45% for training and testing of the detection of the Cassava Disease in Python. This network once used on plants will thus help the plants to grow strong and healthy with early diagnosis if cassava disease is there in the plant.

The article consists of:

  • Downloading Dependencies
  • Importing Libraries
  • Data Generation
  • Building Model
  • Compilation and Training
  • Plotting of the results

Happy Reading!

INSTALLATION

Installations (via pip command) are essential for the execution of this project

!pip install pydot
!pip install pydotplus
!pip install graphviz

IMPORT

Importing necessary libraries.

import os
import glob
import random
import shutil
import warnings
import json
import itertools
import numpy as np
import pandas as pd
from collections import Counter

from keras.models import 
from keras.layers import
from keras.optimizers import 
from keras.callbacks import 
from tensorflow.keras.applications import 
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns

import keras
from keras.preprocessing.image import ImageDataGenerator
import tensorflow as tf
from PIL import Image

from sklearn.model_selection import train_test_split

# Defining the working directories
work_dir = '../input/cassava-leaf-disease-classification/'
os.listdir(work_dir) 
train_path = '/kaggle/input/cassava-leaf-disease-classification/train_images'

LOAD DATA

Checking the frequency of the labels. Import the JSON file with labels. Then, defining the working dataset thereby generating the training and testing sets.

data = pd.read_csv(work_dir + 'train.csv')
print(data['label'].value_counts()) 

with open(work_dir + 'label_num_to_disease_map.json') as f:
    real_labels = json.load(f)
    real_labels = {int(k):v for k,v in real_labels.items()}
    
data['class_name'] = data['label'].map(real_labels)
real_labels

train, test = train_test_split(data, test_size = 0.05, random_state = 42, stratify = data['class_name'])
3    13158
4     2577
2     2386
1     2189
0     1087
Name: label, dtype: int64
{0: 'Cassava Bacterial Blight (CBB)',
 1: 'Cassava Brown Streak Disease (CBSD)',
 2: 'Cassava Green Mottle (CGM)',
 3: 'Cassava Mosaic Disease (CMD)',
 4: 'Healthy'}

DATA GENERATION

In this section, data augmentation, pre-processing, and data generation for training. Data augmentation tackles the less dataset issue and thereby saving pre-processing time by using augmentation techniques. Pre-processing includes normalizing the dataset for efficient feature extraction while mapping. Thus, all the pre-processed and augmented images are stored for training purposes.

datagen_train = ImageDataGenerator(
    preprocessing_function = tf.keras.applications.efficientnet.preprocess_input,
    rotation_range = 40,
    width_shift_range = 0.2,
    height_shift_range = 0.2,
    shear_range = 0.2,
    zoom_range = 0.2,
    horizontal_flip = True,
    vertical_flip = True,
    fill_mode = 'nearest',
)

datagen_val = ImageDataGenerator(
    preprocessing_function = tf.keras.applications.efficientnet.preprocess_input,
)

train_set = datagen_train.flow_from_dataframe(
    train,
    directory=train_path,
    seed=42,
    x_col='image_id',
    y_col='class_name',
    target_size = size,
    class_mode='categorical',
    interpolation='nearest',
    shuffle = True,
    batch_size = BATCH_SIZE,
)

test_set = datagen_val.flow_from_dataframe(
    test,
    directory=train_path,
    seed=42,
    x_col='image_id',
    y_col='class_name',
    target_size = size,
    class_mode='categorical',
    interpolation='nearest',
    shuffle=True,
    batch_size=BATCH_SIZE,    
)
Found 20327 validated image filenames belonging to 5 classes.
Found 1070 validated image filenames belonging to 5 classes.

BUILD MODEL

Initialize the model with the input shape. Built using Global Average Pooling (GAP), flatten, and dense ReLU activators along with Dropout layers and concluding the network with a softmax layer.

GAP: Reduces the spatial dimension of the layers and resulting in a smoother feature map.

Flatten: Reducing the size of the vector

ReLU: Activation function which directly outputs the input if positive and zeroes if negative.

Dropout: Dropping of random neurons in layers thereby preventing overfitting error.

def create_model():
    
    model = Sequential()
    # initialize the model with input shape
    model.add(
        EfficientNetB3(
            input_shape = (IMG_SIZE, IMG_SIZE, 3), 
            include_top = False,
            weights='imagenet',
            drop_connect_rate=0.6,
        )
    )
    model.add(GlobalAveragePooling2D())
    model.add(Flatten())
    model.add(Dense(
        256, 
        activation='relu', 
        bias_regularizer=tf.keras.regularizers.L1L2(l1=0.01, l2=0.001)
    ))
    model.add(Dropout(0.5))
    model.add(Dense(n_CLASS, activation = 'softmax'))
    
    return model

leaf_model = create_model()

SUMMARY + FLOWCHART

Summary of the built model in the previous section and the flowchart representing the layers of the built model for the ease of visualization.

leaf_model.summary()
Downloading data from https://storage.googleapis.com/keras-applications/efficientnetb3_notop.h5
43941888/43941136 [==============================] - 1s 0us/step
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
efficientnetb3 (Functional)  (None, 15, 15, 1536)      10783535  
_________________________________________________________________
global_average_pooling2d (Gl (None, 1536)              0         
_________________________________________________________________
flatten (Flatten)            (None, 1536)              0         
_________________________________________________________________
dense (Dense)                (None, 256)               393472    
_________________________________________________________________
dropout (Dropout)            (None, 256)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 1285      
=================================================================
Total params: 11,178,292
Trainable params: 11,090,989
Non-trainable params: 87,303
_________________________________________________________________
keras.utils.plot_model(leaf_model)

flowchart

BUILDING THE TRAIN FUNCTION

In this function, various built parameters are set. They are:

  • Building the loss function
  • Compiling of the model with the loss function built before
  • Stop training when validation loss has stopped decreasing for 3 epochs using EarlyStopping()
  • Then, save the model with the minimum validation loss using ModelCheckpoint()
  • Reducing the learning rate once learning stagnates by ReduceLROnPlateau()
  • Fit the model for training
  • Then, saving the best-trained model
EPOCHS = 15
STEP_SIZE_TRAIN = train_set.n // train_set.batch_size
STEP_SIZE_TEST = test_set.n // test_set.batch_size
def model_fit():
    leaf_model = create_model()
    
    loss = tf.keras.losses.CategoricalCrossentropy(
        from_logits = False,
        label_smoothing=0.0001,
        name='categorical_crossentropy'
    )
    
    leaf_model.compile(
        optimizer = Adam(learning_rate = 1e-3),
        loss = loss, #'categorical_crossentropy'
        metrics = ['categorical_accuracy']
    )
    
    es = EarlyStopping(
        monitor='val_loss', 
        mode='min', 
        patience=3,
        restore_best_weights=True, 
        verbose=1,
    )
    
    checkpoint_cb = ModelCheckpoint(
        "Cassava_best_model.h5",
        save_best_only=True,
        monitor='val_loss',
        mode='min',
    )
    
    reduce_lr = ReduceLROnPlateau(
        monitor='val_loss',
        factor=0.2,
        patience=2,
        min_lr=1e-6,
        mode='min',
        verbose=1,
    )
    
    history = leaf_model.fit(
        train_set,
        validation_data=test_set,
        epochs=EPOCHS,
        batch_size=BATCH_SIZE,
        steps_per_epoch=STEP_SIZE_TRAIN,
        validation_steps=STEP_SIZE_TEST,
        callbacks=[es, checkpoint_cb, reduce_lr],
    )
    
    leaf_model.save('Cassava_model'+'.h5')  
    
    return history

TRAINING

Calling of the training function mentioned in the previous section. Then viewing the categorical acc of the training and validation.

try:
    final_model = keras.models.load_model('Cassava_model.h5')
except Exception as e:
    with tf.device('/GPU:0'):
        results = model_fit()
    print('Train Categorical Accuracy: ', max(results.history['categorical_accuracy']))
    print('Test Categorical Accuracy: ', max(results.history['val_categorical_accuracy']))
Epoch 1/15
1355/1355 [==============================] - 1598s 1s/step - loss: 0.8348 - categorical_accuracy: 0.7084 - val_loss: 0.5775 - val_categorical_accuracy: 0.8047
Epoch 2/15
1355/1355 [==============================] - 1498s 1s/step - loss: 0.5562 - categorical_accuracy: 0.8133 - val_loss: 0.4261 - val_categorical_accuracy: 0.8469
Epoch 3/15
1355/1355 [==============================] - 1497s 1s/step - loss: 0.5079 - categorical_accuracy: 0.8307 - val_loss: 0.4411 - val_categorical_accuracy: 0.8629
Epoch 4/15
1355/1355 [==============================] - 1498s 1s/step - loss: 0.4983 - categorical_accuracy: 0.8354 - val_loss: 0.4200 - val_categorical_accuracy: 0.8601
Epoch 5/15
1355/1355 [==============================] - 1503s 1s/step - loss: 0.4610 - categorical_accuracy: 0.8451 - val_loss: 0.4732 - val_categorical_accuracy: 0.8319
Epoch 6/15
1355/1355 [==============================] - 1490s 1s/step - loss: 0.4485 - categorical_accuracy: 0.8507 - val_loss: 0.4100 - val_categorical_accuracy: 0.8667
Epoch 7/15
1355/1355 [==============================] - 1481s 1s/step - loss: 0.4374 - categorical_accuracy: 0.8547 - val_loss: 0.4215 - val_categorical_accuracy: 0.8648
Epoch 8/15
1355/1355 [==============================] - 1489s 1s/step - loss: 0.4246 - categorical_accuracy: 0.8580 - val_loss: 0.4096 - val_categorical_accuracy: 0.8667
Epoch 9/15
1355/1355 [==============================] - 1492s 1s/step - loss: 0.4274 - categorical_accuracy: 0.8562 - val_loss: 0.4047 - val_categorical_accuracy: 0.8714
Epoch 10/15
1355/1355 [==============================] - 1495s 1s/step - loss: 0.4098 - categorical_accuracy: 0.8630 - val_loss: 0.3682 - val_categorical_accuracy: 0.8704
Epoch 11/15
1355/1355 [==============================] - 1494s 1s/step - loss: 0.4129 - categorical_accuracy: 0.8605 - val_loss: 0.4354 - val_categorical_accuracy: 0.8629
Epoch 12/15
1355/1355 [==============================] - 1483s 1s/step - loss: 0.3872 - categorical_accuracy: 0.8714 - val_loss: 0.4067 - val_categorical_accuracy: 0.8563

Epoch 00012: ReduceLROnPlateau reducing learning rate to 0.00020000000949949026.
Epoch 13/15
1355/1355 [==============================] - 1489s 1s/step - loss: 0.3499 - categorical_accuracy: 0.8795 - val_loss: 0.3547 - val_categorical_accuracy: 0.8845
Epoch 14/15
1355/1355 [==============================] - 1487s 1s/step - loss: 0.3162 - categorical_accuracy: 0.8911 - val_loss: 0.3440 - val_categorical_accuracy: 0.8836
Epoch 15/15
1355/1355 [==============================] - 1489s 1s/step - loss: 0.3211 - categorical_accuracy: 0.8879 - val_loss: 0.3483 - val_categorical_accuracy: 0.8808
Train Categorical Accuracy:  0.8915911912918091
Test Categorical Accuracy:  0.8845070600509644

PLOT RESULTS

The plotting of the results of the trained model for visualization and analysis sake. Thus, the acc with respect to the epochs and the Loss with respect to the epochs is studied in this plot.

def trai_test_plot(acc, test_acc, loss, test_loss):
    
    fig, (ax1, ax2) = plt.subplots(1,2, figsize= (15,10))
    fig.suptitle("Model's metrics comparisson", fontsize=20)

    ax1.plot(range(1, len(acc) + 1), acc)
    ax1.plot(range(1, len(test_acc) + 1), test_acc)
    ax1.set_title('History of Accuracy', fontsize=15)
    ax1.set_xlabel('Epochs', fontsize=15)
    ax1.set_ylabel('Accuracy', fontsize=15)
    ax1.legend(['training', 'validation'])


    ax2.plot(range(1, len(loss) + 1), loss)
    ax2.plot(range(1, len(test_loss) + 1), test_loss)
    ax2.set_title('History of Loss', fontsize=15)
    ax2.set_xlabel('Epochs', fontsize=15)
    ax2.set_ylabel('Loss', fontsize=15)
    ax2.legend(['training', 'validation'])
    plt.show()
    

trai_test_plot(
    results.history['categorical_accuracy'],
    results.history['val_categorical_accuracy'],
    results.history['loss'],
    results.history['val_loss']
)

plot

FINAL THOUGHTS

In this article, Cassava disease detection using EfficientNet is successfully done. And we have built GAP, dropout, flatten, and activation functions over the EfficientNet (TL). EfficientNet is a very different network that concentrates on uniform scaling of the feature maps with uniform depth, width, and resolution using a simple but efficient coefficient. Then customized loss functions for improving the quality of the network. Then, training of the network and visualization using plotting of the model. A training acc of 89.15% and test acc of 88.45 for the trained model is obtained with only 15 epochs of training.

The source code for the cassava disease detection can be found and downloaded from here.

The dataset for the net can be found in kaggle.

To learn different DL methods, you can refer to my blogs:

[1]. Image classification of Bird species (Used a VGG16 net)

[2]. Salt Identification (Used UNets fused with a Residual net)

[3]. Pneumonia X-Ray Detection (Without Transfer Learning)

References:

[1]. EfficientNet: Rethinking Model Scaling for CNN 

To learn from my other ML blogs, click here.

Thank you. Hope this article was helpful!

Leave a Reply

Your email address will not be published. Required fields are marked *