<jupyter_start><jupyter_text>**Plant pathology classification**The project concerns the recognition of apple leaf diseases based on their photos. The leaves are classified as "healthy", "multiple diseases", "scab" and "rust". This is a multiclass classification problem. In this approach we used a transfer learning with pre-trained MobileNet V2 model to recognition of leaves diseases. In our problem we will check what accuracy we are able to achieve by using transfer learning method. **Transfer learning** is a machine learning technique where a model trained on one task is re-purposed on a second related task.  An important application of transfer learning is when the dataset is small then one can use a pre-trained model on similar images and can easily achieve high performance.MobileNet V2 is a classification model developed by Google. This model is pre-trained on the ImageNet dataset, a large dataset consisting of 1.4M images and 1000 classes. The model architecture utilizes an inverted residual structure where the input and output of the residual blocks are thin bottleneck layers. It also uses lightweight convolutions to filter features in the expansion layer. Finally it removes non-linearities in the narrow layers.**Data**The data comes from kaggle competition (Plant Pathology 2020 - FGVC7). **Import libraries and data**<jupyter_code>import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt 
import os
import cv2
from PIL import Image

from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE

from keras.preprocessing.image import img_to_array
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout

import warnings
warnings.filterwarnings('ignore')<jupyter_output><empty_output><jupyter_text>Read data:<jupyter_code>cd '/content/drive/My Drive/moje pliki/AS_zad'
images_path = '/content/drive/My Drive/moje pliki/AS_zad/images'
train = pd.read_csv('/content/drive/My Drive/moje pliki/AS_zad/train.csv')
test = pd.read_csv('/content/drive/My Drive/moje pliki/AS_zad/train.csv')<jupyter_output><empty_output><jupyter_text>**Data analysis**The dataset contains csv. files divided into training and test ones and a folder of photos in jpg format.The data include photos of the apple leaves describing their diseases. There are 4 classes: healthy, rust, scub and multiple diseases.<jupyter_code>train.head()
#shape of data
print(train.shape)
print(test.shape)<jupyter_output>(1821, 5)
(1821, 5)<jupyter_text>Number of photos in each class:<jupyter_code>number_of_images = ['healthy', 'multiple_diseases', 'rust', 'scab']

print(train[number_of_images].sum())<jupyter_output>healthy              516
multiple_diseases     91
rust                 622
scab                 592
dtype: int64<jupyter_text>Distribution of individual classes on the charts:<jupyter_code>train[number_of_images].sum().plot.bar()
plt.xlabel('Classes')
plt.ylabel('Counts')<jupyter_output><empty_output><jupyter_text>After first observations we can see that we have 1,821 images with 4 classes in the training data and the same amount of test data. We can also see that the data is out of balance. The fewest images are in the category of multiple diseases.   **Data preparation**We divide the data into training and test sets and change the image size to 224.<jupyter_code>IMG_SIZE = 224

train_images = []

for name in train['image_id']:
  path = os.path.join(images_path, name) + ".jpg"
  image = cv2.imread(path)
  img = cv2.resize(image,(IMG_SIZE,IMG_SIZE),interpolation=cv2.INTER_AREA)
  train_images.append(img)

test_images = []

for name in test['image_id']:
  path = os.path.join(images_path, name) + ".jpg"
  image = cv2.imread(path)
  img = cv2.resize(image,(IMG_SIZE,IMG_SIZE),interpolation=cv2.INTER_AREA)
  test_images.append(img)
len(train_images), len(test_images)<jupyter_output><empty_output><jupyter_text>Visualization of images:<jupyter_code>#train set
f, ax = plt.subplots(1, 4, figsize=(12,12))
for i in range(4):
  ax[i].imshow(train_images[i])<jupyter_output><empty_output><jupyter_text>Then we need to write the data into array and rescale all the images. The RGB channel values need to be standardized, which are in the range of [0, 255], so that they are in the range of [0, 1].Train set:<jupyter_code>X_train = np.ndarray(shape=(len(train_images), IMG_SIZE, IMG_SIZE, 3), dtype = np.float32)

for i, img in enumerate(train_images):
  X_train[i] = img_to_array(img)
  X_train[i] = train_images[i]

X_train = X_train/255

print(X_train.shape)
y_train = train.drop(columns=['image_id'])
y_train = np.array(y_train.values)
y_train.shape<jupyter_output><empty_output><jupyter_text>The same transformation for test set:<jupyter_code>X_test = np.ndarray(shape=(len(test_images), IMG_SIZE, IMG_SIZE, 3), dtype = np.float32)

for i, img in enumerate(test_images):
  X_test[i] = img_to_array(img)
  X_test[i] = test_images[i]

X_test = X_test/255

print(X_test.shape)
y_test = test.drop(columns=['image_id'])
y_test = np.array(y_test.values)<jupyter_output><empty_output><jupyter_text>**Division of data into test and validation data**We divide the data into a testing and validation sets by use train_test_split method in sklearn library. We split them into 50% of testing and validation data.<jupyter_code>print(X_train.shape)
print(X_test.shape)
X_test, X_val, y_test, y_val = train_test_split(X_test ,y_test, test_size=0.5, random_state=42)

X_test.shape, X_val.shape<jupyter_output><empty_output><jupyter_text>Final shapes of our data:<jupyter_code>print(X_train.shape)
print(X_test.shape)
print(X_val.shape)<jupyter_output>(1821, 224, 224, 3)
(910, 224, 224, 3)
(911, 224, 224, 3)<jupyter_text>Since the dataset is not balanced we have to create a balanced dataset for each class. The results give the same number of samples in multiple classes.To get a balanced dataset we will use the SMOTE library:<jupyter_code>smote = SMOTE(random_state=42)

X_train, y_train = smote.fit_resample(X_train.reshape((-1, img_size*img_size*3)), y_train)

X_train = X_train.reshape((-1, img_size, img_size, 3))
X_train.shape, y_train.shape<jupyter_output><empty_output><jupyter_text>**Creating the model**We build model by using transfer learning with pre-trained MobileNet V2 model. First we create the base MobileNetV2 model:<jupyter_code>IMG_SHAPE = (224,224,3)

base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                               include_top=False, 
                                               weights='imagenet')<jupyter_output>Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5
9412608/9406464 [==============================] - 0s 0us/step
9420800/9406464 [==============================] - 0s 0us/step<jupyter_text>In this point we are instantiating a model where the classification layers will depend on the last layer before the flatten operation. Next we freeze the convolutional layers to use the base model as a feature extractor:<jupyter_code>base_model.trainable = False<jupyter_output><empty_output><jupyter_text>**Model 1** The model 1 consists of Global Average Pooling 2D layer, then we added two Dense layers with one Dropout layer as well.<jupyter_code>model = tf.keras.Sequential([base_model,
                             tf.keras.layers.GlobalAveragePooling2D(),
                             tf.keras.layers.Dense(32, activation="relu"),
                             tf.keras.layers.Dropout(0.2),
                             tf.keras.layers.Dense(4, activation="softmax")                                     
                             ])
model.compile(optimizer = 'adam', 
              loss = 'categorical_crossentropy',
              metrics = ['accuracy'])<jupyter_output><empty_output><jupyter_text>We train the model for 10 epoch:<jupyter_code>history = model.fit(X_train, y_train,
                    batch_size = 32,
                    epochs = 10,
                    validation_data=(X_val, y_val),
                    verbose=1)
def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.plot(history.history['val_'+string])
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.legend([string, 'val_'+string])
  plt.show()<jupyter_output><empty_output><jupyter_text>The plots of loss and accuracy on the training and validation sets:<jupyter_code>plot_graphs(history, "accuracy")
plot_graphs(history, "loss")<jupyter_output><empty_output><jupyter_text>Prediction for model 1:<jupyter_code>loss, accuracy = model.evaluate(X_train,y_train, verbose=False)
print("Training Accuracy: {:.4f}".format(accuracy))
loss, accuracy = model.evaluate(X_test,y_test, verbose=False)
print("Testing Accuracy:  {:.4f}".format(accuracy))
loss, accuracy = model.evaluate(X_test,y_test, verbose=False)
print("Testing loss:  {:.4f}".format(loss))<jupyter_output>Testing loss:  0.2598<jupyter_text>It looks like the model performed quite well. The training and validation accuracy is increasing together and the same is in the training and validation loss. On the test data we achieved 93% accuracy. However we will check if we can improve its accuracy in next model. **Model 2**In model two we change a bit architecture of our model to check if we achieve a better results.<jupyter_code>model2 = tf.keras.Sequential([base_model,
                             tf.keras.layers.GlobalAveragePooling2D(),
                             tf.keras.layers.Dense(64, activation="relu"),
                             tf.keras.layers.Dropout(0.2),
                             tf.keras.layers.Dense(4, activation="softmax")                                     
                             ])
model2.compile(optimizer = 'adam', 
               loss = 'categorical_crossentropy',
               metrics = ['accuracy'])<jupyter_output><empty_output><jupyter_text>We train the model for 10 epoch:<jupyter_code>history2 = model2.fit(X_train, y_train,
                      batch_size = 32,
                      epochs = 10,
                      validation_data=(X_val, y_val),
                      verbose=1)<jupyter_output>Epoch 1/10
78/78 [==============================] - 93s 1s/step - loss: 0.9394 - accuracy: 0.6246 - val_loss: 0.6376 - val_accuracy: 0.7703
Epoch 2/10
78/78 [==============================] - 89s 1s/step - loss: 0.6218 - accuracy: 0.7741 - val_loss: 0.5649 - val_accuracy: 0.7923
Epoch 3/10
78/78 [==============================] - 90s 1s/step - loss: 0.5049 - accuracy: 0.8111 - val_loss: 0.4349 - val_accuracy: 0.8484
Epoch 4/10
78/78 [==============================] - 90s 1s/step - loss: 0.4196 - accuracy: 0.8497 - val_loss: 0.4011 - val_accuracy: 0.8582
Epoch 5/10
78/78 [==============================] - 90s 1s/step - loss: 0.3679 - accuracy: 0.8674 - val_loss: 0.3076 - val_accuracy: 0.9022
Epoch 6/10
78/78 [==============================] - 90s 1s/step - loss: 0.3167 - accuracy: 0.8850 - val_loss: 0.2720 - val_accuracy: 0.9220
Epoch 7/10
78/78 [==============================] - 90s 1s/step - loss: 0.2801 - accuracy: 0.9059 - val_loss: 0.2597 - val_accuracy: 0.9121
Epoch 8/10
78/78 [==[...]<jupyter_text>The plots of loss and accuracy on the training and validation sets:<jupyter_code>plot_graphs(history2, "accuracy")
plot_graphs(history2, "loss")
loss, accuracy = model2.evaluate(X_train,y_train, verbose=False)
print("Training Accuracy: {:.4f}".format(accuracy))
loss, accuracy = model2.evaluate(X_test,y_test, verbose=False)
print("Testing Accuracy:  {:.4f}".format(accuracy))
loss, accuracy = model2.evaluate(X_test,y_test, verbose=False)
print("Testing loss:  {:.4f}".format(loss))
#save the model
model2.save("plant_model_tf.h5")<jupyter_output><empty_output><jupyter_text>In this model we achived a better result than in first one. The training graphs show that the model was able to converge. The accuracy of the test set was at the level of 96% and was also better compared to the model 1.  **Summary**For our analysis we have used a pretrained MobileNetV2 model to resolve our image classification problem. From above analysis we can see that the second model is better than first one. By virtue the transfer learning and by a little bit of architecture changes  it was possible to obtain a model with better accuracy.<jupyter_code><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11831416391860372, 'fraction_numerical': 0.036113568728384136, 'mean_word_length': 4.259729272419627, 'pattern_counts': {'":': 0, '<': 83, '<?xml version=': 0, '>': 83, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '424251', 'license': 'MIT', 'n_tokens_mistral': 4259, 'n_tokens_neox': 3883, 'n_words': 1139, 'path': 'plant_tf.ipynb', 'repo_name': 'aniass/Plant-pathology', 'size': '12433'}