Tomato Leaf Disease Classifier using TensorFlow 2.0
In this tutorial, we will learn how to classify images of tomato leaves that are infected or not by using transfer learning from a pre-trained network with the help of TensorFlow in Python.
A pre-trained model is a saved network that was previously trained on a large dataset, typically on a large-scale image classification task. You either use the pre-trained model as it is or use transfer learning to customize this model to a given task. In our case, it is tomato image classification.
You will follow the general machine learning workflow.
- First of all, we examine and understand the data.
- Second Build an input pipeline, in this case, we are using Keras ImageDataGenerator.
- The next step is to Compose the model
- Load in the pre-trained base model (and pretrained weights)
- Stack the classification layers on top
- Train the model
- The final step is to Evaluate model
Loading All Required Python Library
Import all the libraries required for Image Classification.
import tensorflow as tf from tensorflow.keras.layers import Input, Lambda, Dense, Flatten,GlobalAveragePooling2D,MaxPooling2D from tensorflow.keras.models import Model from tensorflow.keras.applications.inception_v3 import InceptionV3 from tensorflow.keras.applications.inception_v3 import preprocess_input from tensorflow.keras.preprocessing import image from tensorflow.keras.preprocessing.image import ImageDataGenerator,load_img from tensorflow.keras.models import Sequential import numpy as np from glob import glob import matplotlib.pyplot as plt
Reading Data
Let’s read the train and test data.
train='/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train' test='/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/valid'
Check the number of folders in Train
Now we check the number of folders that are available on the train using glob library.
# useful for getting number of output classes folders = glob('/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/*') folders
Output
['/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Early_blight', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Bacterial_spot', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Late_blight', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___healthy', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Target_Spot', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Spider_mites Two-spotted_spider_mite', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Tomato_mosaic_virus', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Leaf_Mold', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Tomato_Yellow_Leaf_Curl_Virus', '/content/drive/MyDrive/Data/New Plant Diseases Dataset(Augmented)/train/Tomato___Septoria_leaf_spot']
As you can see that there are 10 folders inside the train folder.
Importing Data into TensorFlow Object
Here we are taking the batch size of 32 and image size off 224*224 i.e height, width.
- Count number of images in train folder with tf.keras.utils.image_dataset_from_directory().
BATCH_SIZE = 32 IMG_SIZE = (224, 224) train_t = tf.keras.utils.image_dataset_from_directory(train, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)
Output
As you can see there are 18365 images of tomatoes in the training folder.
Found 18365 files belonging to 10 classes.
- Count number of images in test folder with tf.keras.utils.image_dataset_from_directory().
test_t = tf.keras.utils.image_dataset_from_directory(test, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)
Output
Found 4585 files belonging to 10 classes.
As you can see there are 4585 images are available in the test.
Check Train Classes
Here we are checking the class names that belong to the train.
names1=train_t.class_names names1
Output
['Tomato___Bacterial_spot', 'Tomato___Early_blight', 'Tomato___Late_blight', 'Tomato___Leaf_Mold', 'Tomato___Septoria_leaf_spot', 'Tomato___Spider_mites Two-spotted_spider_mite', 'Tomato___Target_Spot', 'Tomato___Tomato_Yellow_Leaf_Curl_Virus', 'Tomato___Tomato_mosaic_virus', 'Tomato___healthy']
Now Check Batch of Image
Let’s check the batch size of a single image.
for image_batch, labels_batch in train_t.take(1): print(image_batch.shape) print(labels_batch.numpy())
Output
(32, 224, 224, 3) [8 1 6 3 9 6 6 2 5 4 5 0 9 3 2 6 2 1 9 9 7 8 7 7 1 3 3 2 0 8 1 8]
The image_batch is a tensor of the shape (32,224,224,3). This is a batch of 32 images of shape 224*224*3 (the last dimension refers to color channels RGB). The label_batch is a tensor of the shape (32,), these are corresponding labels to the 32 images.
You can call .numpy() on the image_batch and labels_batch tensors to convert them to a NumPy.ndarray.
Visualize some Random Image
Show the first nine images and labels from the training set:
fig=plt.figure(figsize=(10,10)) for img,label in train_t.take(1): for i in range(9): fig.add_subplot(3,3,i+1),plt.imshow(img[i].numpy().astype("uint8")) plt.title(names1[label[i]]) plt.axis("off")
Since the original database does not have a test set, we will create it. To do so, find out how many data collections are available in the verification set using tf.data.experimental.cardinality, and submit 20% of them to the test set.
val_batches = tf.data.experimental.cardinality(test_t) test_dataset = test_t.take(val_batches // 5) validation_dataset = test_t.skip(val_batches // 5) print('Number of validation batches: %d' % tf.data.experimental.cardinality(validation_dataset)) print('Number of test batches: %d' % tf.data.experimental.cardinality(test_dataset))
Output
Number of validation batches: 116 Number of test batches: 28
Configuring Dataset for Performance
Use buffered prefetching to upload images to disk without I / O being blocked.
cache: – read the image to disk and next time you need the image will keep the image in memory to improve performance.
prefetch: – if the GPU is busy downloading it will upload images using the CPU which leads to increased performance.
AUTOTUNE = tf.data.AUTOTUNE train_dataset = train_t.prefetch(buffer_size=AUTOTUNE) validation_dataset = test_t.prefetch(buffer_size=AUTOTUNE) test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)
Apply Data Augmentation
Used to create some more random sample of training data which include rotation and horizontal flip of images
data_augmentation = tf.keras.Sequential([ tf.keras.layers.RandomFlip('horizontal'), tf.keras.layers.RandomRotation(0.2), ])
Model Building
Stages Include
- Train the model
- Exclude the last layer in Inception V3 with include_top=Falsd
- Freezing layer in the network with trainable =False
- Compile & Fit the model.
First, instantiate an Inception V3 model pre-loaded with weights trained on ImageNet. By specifying the include_top=False argument, you load a network that doesn’t include the classification layers at the top, which is ideal for feature extraction.
IMG_SHAPE = IMG_SIZE + (3,) base_model = InceptionV3(input_shape=IMG_SHAPE, weights='imagenet', include_top=False) base_model.summary()
Output
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5 87916544/87910968 [==============================] - 1s 0us/step 87924736/87910968 [==============================] - 1s 0us/step Model: "inception_v3"
It is important to establish a convolutional base before assembling and training the model. Freezing (by setting layer. trainable = False) prevents weight loss in a given layer from being repeated during training. Inception V3 has many layers, so setting the entire model’s trainable flag to False will freeze all of them.
When you set it, the BatchNormalization layer will run in inference mode, and will not update its mean and variance statistics.
#freeze network base_model.trainable = False base_model.summary()
Now We Create a model by combining data augmentation, re-scaling, base_model, and feature extraction layers using the Keras Functional API. As mentioned earlier, use training = False as our model contains the BatchNormalization layer.
inputs = base_model.input x = data_augmentation(inputs) x = preprocess_input(x) x = base_model(x, training=False) global_average_layer = tf.keras.layers.GlobalAveragePooling2D() x=global_average_layer(x) x = tf.keras.layers.Dropout(0.2)(x) outputs=Dense(len(folders), activation='softmax')(x) model = Model(inputs, outputs) model.summary()
Let’s display the architecture of the model so far:
Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 224, 224, 3)] 0 sequential (Sequential) (None, 224, 224, 3) 0 tf.math.truediv (TFOpLambda (None, 224, 224, 3) 0 ) tf.math.subtract (TFOpLambd (None, 224, 224, 3) 0 a) inception_v3 (Functional) (None, 5, 5, 2048) 21802784 global_average_pooling2d (G (None, 2048) 0 lobalAveragePooling2D) dropout (Dropout) (None, 2048) 0 dense (Dense) (None, 10) 20490 ================================================================= Total params: 21,823,274 Trainable params: 20,490 Non-trainable params: 21,802,784 _________________________________________________________________
Compiling the Model
- Select optimizer = Adam(Momentum+RMS Prop) as it is most used by experts.
- Select loss= Sparse Categorical.
- Select metrics= ‘accuracy’ (can choose any other metrics like precision, recall, f1-score, etc depending on the problem).
model.compile( optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), metrics=['accuracy'] )
Fitting the Model
- In the .fit() parameter we are passing our train dataset, validation dataset, and then passing epoch.
- Epoch: It means several passes of the entire dataset. In our case, we are passing 10 epochs.
history = model.fit(train_dataset, epochs=10, validation_data=validation_dataset)
Output
The main aim is here to decrease the loss & improve the accuracy. The below code gives us information about training loss and validation loss, training accuracy, and validation accuracy for every epoch.
Epoch 1/10 574/574 [==============================] - 3030s 5s/step - loss: 1.0469 - accuracy: 0.6454 - val_loss: 0.7922 - val_accuracy: 0.7337 Epoch 2/10 574/574 [==============================] - 121s 210ms/step - loss: 0.7154 - accuracy: 0.7546 - val_loss: 0.6552 - val_accuracy: 0.7725 Epoch 3/10 574/574 [==============================] - 121s 210ms/step - loss: 0.6475 - accuracy: 0.7764 - val_loss: 0.6101 - val_accuracy: 0.7911 Epoch 4/10 574/574 [==============================] - 121s 210ms/step - loss: 0.6167 - accuracy: 0.7879 - val_loss: 0.5976 - val_accuracy: 0.7945 Epoch 5/10 574/574 [==============================] - 123s 212ms/step - loss: 0.5826 - accuracy: 0.7995 - val_loss: 0.5615 - val_accuracy: 0.8083 Epoch 6/10 574/574 [==============================] - 121s 210ms/step - loss: 0.5795 - accuracy: 0.7985 - val_loss: 0.5892 - val_accuracy: 0.8015 Epoch 7/10 574/574 [==============================] - 121s 210ms/step - loss: 0.5586 - accuracy: 0.8057 - val_loss: 0.5836 - val_accuracy: 0.7945 Epoch 8/10 574/574 [==============================] - 121s 210ms/step - loss: 0.5573 - accuracy: 0.8076 - val_loss: 0.5435 - val_accuracy: 0.8107 Epoch 9/10 574/574 [==============================] - 122s 211ms/step - loss: 0.5466 - accuracy: 0.8107 - val_loss: 0.5628 - val_accuracy: 0.7976 Epoch 10/10 574/574 [==============================] - 122s 211ms/step - loss: 0.5567 - accuracy: 0.8058 - val_loss: 0.5680 - val_accuracy: 0.8044
Visualize Train loss, accuracy vs Valid loss, accuracy
Create plots of loss and accuracy on the training and validation sets:
acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] plt.figure(figsize=(8, 8)) plt.subplot(2, 1, 1) plt.plot(acc, label='Training Accuracy') plt.plot(val_acc, label='Validation Accuracy') plt.legend(loc='lower right') plt.ylabel('Accuracy') plt.ylim([min(plt.ylim()),1]) plt.title('Training and Validation Accuracy') plt.subplot(2, 1, 2) plt.plot(loss, label='Training Loss') plt.plot(val_loss, label='Validation Loss') plt.legend(loc='upper right') plt.ylabel('Cross Entropy') plt.ylim([0,1.0]) plt.title('Training and Validation Loss') plt.xlabel('epoch') plt.show()
Check Results of Actual vs Predicted
Now visualize the results that the Inception model predicted concerning Actual value.
def predict(model, img): img_array = tf.keras.preprocessing.image.img_to_array(images[i].numpy()) img_array = tf.expand_dims(img_array, 0) predictions = model.predict(img_array) predicted_class = names1[np.argmax(predictions[0])] confidence = round(100 * (np.max(predictions[0])), 2) return predicted_class, confidence plt.figure(figsize=(15, 15)) for images, labels in test_dataset.take(1): for i in range(9): ax = plt.subplot(3, 3, i + 1) plt.imshow(images[i].numpy().astype("uint8")) predicted_class, confidence = predict(model, images[i].numpy()) actual_class = names1[labels[i]] plt.title(f"Actual: {actual_class},\n Predicted: {predicted_class}.\n Confidence: {confidence}%") plt.axis("off")
Leave a Reply