Classification des maladies de la feuille avec une méthode de Deep Learning¶
Clément BOULAY - juillet 2021 (update : février 2022)
import jupyterthemes
!jt -tf latosans
1 - Objectif¶
Nous disposons d'une base de données contenant 238 images de feuille. Parmi ces feuilles, certaines sont malades. Nous souhaitons, étant donnée une image de feuille nouvelle, déterminer si elle est malade, et le cas échéant classifier sa maladie (parmi 5 possibilités, en considérant la possibilité que la feuille ne soit pas malade : classe "Normal"). Pour ce faire, nous allons utiliser un réseau de neurones construit autour de plusieurs couches de convolution.
2 - Découverte des données¶
# Librairies natives
import os
import random
import warnings
warnings.filterwarnings("ignore")
# Libraires importées
import keras
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import PIL
import tensorflow as tf
from tensorflow.keras import activations
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, Conv2D, MaxPooling2D, MaxPooling1D
from tensorflow.keras.models import Sequential
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.utils import to_categorical
Using TensorFlow backend.
FOLDER = "data" # where data is stored
diseases = ["Bacteria", "Fungi", "Nematodes", "Normal", "Virus"] # classes
number_samples = 0
for disease in diseases:
number_samples += len(os.listdir(FOLDER + "/" + disease))
print("Nombre d'images dans la base de données : ", number_samples)
Nombre d'images dans la base de données : 238
# représentation des images
plt.figure(figsize = (20, 10))
for k in range(1, len(diseases) + 1):
disease_displayed = diseases[k - 1]
path = os.path.join(FOLDER, disease_displayed)
im = plt.imread(os.path.join(path, os.listdir(path)[0]))
plt.subplot(2, 3, k)
plt.title(f"Feuille présentant la maladie {disease_displayed} :")
plt.imshow(im)
On identifie clairement la feuille saine, ainsi que celle qui appartient à la classe "Virus" : elle possède des marques brunes qui s'étirent sur tout son long. Pour les trois autres maladies, les différences sont plus ténues. Pour la classe "Fungi" (champignon), des zones brunes plutôt localisées semblent être caractéristiques. Entre "Bactérie' et "Nematodes" toutefois, peu de critères différenciants à ce stade.
# Préparation des données pour le réseau de neurones
SIZE = (100, 100)
def transform_data () : # convertion des données en ndarray et réduction de leur taille
data = []
for disease in diseases:
i = 0
images_disease = os.listdir(os.path.join(FOLDER, disease))
for im_name in images_disease:
i += 1
im = PIL.Image.open(os.path.join(FOLDER, disease, im_name)).resize(SIZE)
im_array = np.array(im, dtype = "uint8") # codée sur 8 bits
data.append([im_array, disease])
print("{} images sont labelisées {}.".format(i, disease))
return data
data = transform_data()
49 images sont labelisées Bacteria. 50 images sont labelisées Fungi. 49 images sont labelisées Nematodes. 40 images sont labelisées Normal. 50 images sont labelisées Virus.
print(f"Les images sont de {data[0][0].shape[0]} par {data[0][0].shape[1]} pixels et sur {data[0][0].shape[2]} canaux colorés.")
Les images sont de 100 par 100 pixels et sur 3 canaux colorés.
np.random.shuffle(data) # mélange des données pour éviter les biais, possible ici car pas des séries temporelles
# vérification : plot la 1ère image qui n'est effectivement pas la même
plt.imshow(data[0][0])
<matplotlib.image.AxesImage at 0x1a63fe98470>
# Data Augmentation en utilisant un objet dédié par Keras
aug = ImageDataGenerator(rotation_range = 180)
# for labels encoding
label_correspondance = {}
for i, disease in enumerate(diseases):
label_correspondance[disease] = i
# Création du jeu d'entraînement (train et validation)
X, y = [], []
for array, label in data:
X.append(array)
y.append(label)
for i in range(len(y)):
y[i] = int(label_correspondance[y[i]])
X, y = np.array(X), np.array(y).reshape(-1, 1)
X = X/255.0
print(X.shape, y.shape)
(238, 100, 100, 3) (238, 1)
y = to_categorical(y, num_classes = 5) # transforme les labels en vecteurs : 1 devient (1, 0, 0, 0, 0)
3. Construction du réseau¶
# Build the neural network
input_shape = X.shape[1:]
print("Entrée du réseau :", input_shape)
model1 = Sequential()
model1.add(Conv2D(filters = 1, kernel_size = 3, input_shape = input_shape))
print("Sortie de la couche de convolution :", model1.output_shape)
model1.add(MaxPooling2D(pool_size = (2, 2)))
print("Sortie de la couche de Pooling :", model1.output_shape)
model1.add(Flatten())
print("Sortie de la couche de Flatten :", model1.output_shape)
model1.add(Dense(5, activation = "softmax"))
print("Sortie du réseau :", model1.output_shape)
Entrée du réseau : (100, 100, 3) Sortie de la couche de convolution : (None, 98, 98, 1) Sortie de la couche de Pooling : (None, 49, 49, 1) Sortie de la couche de Flatten : (None, 2401) Sortie du réseau : (None, 5)
# Compile the model (accuracy = ratio de bonnes classifications)
model1.compile(
loss = "categorical_crossentropy", # fonction de perte de classification multi-classes
optimizer = "adam",
metrics = ["accuracy"])
La fonction de perte "entropie croisée" est définie comme suit :
$\mathcal{L} = - \sum_{i = 1}^5 y_i$ ln$(\hat{y}_i)$
où $y_i = 1$ si l'individu est effectivement de la classe $i$, 0 sinon et $\hat{y}_i$ correspond à la prédiction du modèle pour la classe $i$ (probabilité). Comme nos individus sont tous classifiés par exactement une maladie - notons la $i^*$ -, nous avons :
$\mathcal{L} = - $ln$(\hat{y}_{i^*})$
Remarquons que le logarithme n'est pas défini pour une prédiction du modèle nulle sur la classe réelle de l'échantillon (si $\hat{y}_{i^*} = 0$). Ainsi, il est nécessaire d'utiliser, dans le réseau, l'activation softmax, qui est définie comme suit :
$g : R^5 \rightarrow R^5, g(x1, x2, ..., x5)_j = \frac{exp(x_j)}{\sum_{i = 1}^5 exp(z_i)}$
afin qu'aucune valeur de prédiction ne soit strictement égale à 0.
4. Entraînement¶
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
X_train.shape, X_test.shape
((190, 100, 100, 3), (48, 100, 100, 3))
EPOCHS = 70
history = model1.fit(
x = aug.flow(X_train, y_train, batch_size = 3), # utilisation des données augmentées
validation_data = (X_test, y_test),
batch_size = 3,
epochs = EPOCHS,
verbose = 1)
Epoch 1/70 64/64 [==============================] - 2s 35ms/step - loss: 1.5766 - acc: 0.2316 - val_loss: 1.4043 - val_acc: 0.5417 Epoch 2/70 64/64 [==============================] - 1s 22ms/step - loss: 1.4276 - acc: 0.3895 - val_loss: 1.3359 - val_acc: 0.5000 Epoch 3/70 64/64 [==============================] - 2s 24ms/step - loss: 1.3412 - acc: 0.4053 - val_loss: 1.2674 - val_acc: 0.4167 Epoch 4/70 64/64 [==============================] - 1s 23ms/step - loss: 1.2937 - acc: 0.4053 - val_loss: 1.2506 - val_acc: 0.4375 Epoch 5/70 64/64 [==============================] - 1s 23ms/step - loss: 1.2619 - acc: 0.3895 - val_loss: 1.2824 - val_acc: 0.4792 Epoch 6/70 64/64 [==============================] - 1s 22ms/step - loss: 1.2438 - acc: 0.4053 - val_loss: 1.2540 - val_acc: 0.4167 Epoch 7/70 64/64 [==============================] - 2s 24ms/step - loss: 1.2031 - acc: 0.4474 - val_loss: 1.2405 - val_acc: 0.4167 Epoch 8/70 64/64 [==============================] - 2s 24ms/step - loss: 1.1885 - acc: 0.4684 - val_loss: 1.2547 - val_acc: 0.3750: 0s - loss: 1.2461 - - ETA: 0s - loss: 1.1727 - acc: 0.46 Epoch 9/70 64/64 [==============================] - 1s 23ms/step - loss: 1.1659 - acc: 0.4842 - val_loss: 1.3275 - val_acc: 0.4583 Epoch 10/70 64/64 [==============================] - 1s 22ms/step - loss: 1.2090 - acc: 0.4632 - val_loss: 1.2196 - val_acc: 0.4167 Epoch 11/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1537 - acc: 0.5105 - val_loss: 1.2222 - val_acc: 0.4375 Epoch 12/70 64/64 [==============================] - 1s 23ms/step - loss: 1.2017 - acc: 0.4895 - val_loss: 1.2418 - val_acc: 0.4375 Epoch 13/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1656 - acc: 0.5053 - val_loss: 1.2236 - val_acc: 0.4375 Epoch 14/70 64/64 [==============================] - 2s 23ms/step - loss: 1.1467 - acc: 0.5211 - val_loss: 1.1754 - val_acc: 0.4583 Epoch 15/70 64/64 [==============================] - 2s 26ms/step - loss: 1.1350 - acc: 0.5158 - val_loss: 1.2359 - val_acc: 0.4167 Epoch 16/70 64/64 [==============================] - 2s 30ms/step - loss: 1.1732 - acc: 0.4632 - val_loss: 1.2276 - val_acc: 0.4167 Epoch 17/70 64/64 [==============================] - 2s 27ms/step - loss: 1.1757 - acc: 0.4842 - val_loss: 1.2065 - val_acc: 0.4167 Epoch 18/70 64/64 [==============================] - 2s 30ms/step - loss: 1.1108 - acc: 0.4947 - val_loss: 1.1553 - val_acc: 0.4583 Epoch 19/70 64/64 [==============================] - 2s 24ms/step - loss: 1.0941 - acc: 0.5368 - val_loss: 1.1698 - val_acc: 0.4583 Epoch 20/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0968 - acc: 0.5368 - val_loss: 1.1649 - val_acc: 0.4375 Epoch 21/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1156 - acc: 0.4895 - val_loss: 1.2023 - val_acc: 0.3750 Epoch 22/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0766 - acc: 0.5263 - val_loss: 1.1688 - val_acc: 0.4375 Epoch 23/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1286 - acc: 0.5526 - val_loss: 1.1485 - val_acc: 0.4167 Epoch 24/70 64/64 [==============================] - 1s 23ms/step - loss: 1.1702 - acc: 0.4895 - val_loss: 1.1664 - val_acc: 0.3958 Epoch 25/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1084 - acc: 0.5368 - val_loss: 1.2915 - val_acc: 0.4583 Epoch 26/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1148 - acc: 0.5053 - val_loss: 1.1972 - val_acc: 0.3958 Epoch 27/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0679 - acc: 0.5474 - val_loss: 1.1966 - val_acc: 0.3750 Epoch 28/70 64/64 [==============================] - 2s 24ms/step - loss: 1.0883 - acc: 0.5211 - val_loss: 1.2958 - val_acc: 0.4583 Epoch 29/70 64/64 [==============================] - 2s 25ms/step - loss: 1.0570 - acc: 0.5263 - val_loss: 1.2528 - val_acc: 0.4375 Epoch 30/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0560 - acc: 0.5684 - val_loss: 1.2492 - val_acc: 0.4167 Epoch 31/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0842 - acc: 0.5421 - val_loss: 1.2194 - val_acc: 0.4375 Epoch 32/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1521 - acc: 0.5211 - val_loss: 1.1909 - val_acc: 0.4167 Epoch 33/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1171 - acc: 0.5316 - val_loss: 1.1391 - val_acc: 0.4583 Epoch 34/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0784 - acc: 0.5105 - val_loss: 1.1841 - val_acc: 0.4792 Epoch 35/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0875 - acc: 0.5105 - val_loss: 1.1400 - val_acc: 0.5000 Epoch 36/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0836 - acc: 0.5263 - val_loss: 1.1594 - val_acc: 0.5833 Epoch 37/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0523 - acc: 0.5895 - val_loss: 1.0860 - val_acc: 0.5000 Epoch 38/70 64/64 [==============================] - 1s 22ms/step - loss: 1.1391 - acc: 0.5105 - val_loss: 1.1023 - val_acc: 0.5208 Epoch 39/70 64/64 [==============================] - 2s 26ms/step - loss: 1.1106 - acc: 0.4895 - val_loss: 1.1216 - val_acc: 0.5208 Epoch 40/70 64/64 [==============================] - 2s 26ms/step - loss: 1.0415 - acc: 0.5474 - val_loss: 1.1208 - val_acc: 0.4792 Epoch 41/70 64/64 [==============================] - 2s 26ms/step - loss: 1.0173 - acc: 0.5842 - val_loss: 1.1160 - val_acc: 0.4375 Epoch 42/70 64/64 [==============================] - 2s 24ms/step - loss: 1.0712 - acc: 0.5368 - val_loss: 1.1361 - val_acc: 0.4375 Epoch 43/70 64/64 [==============================] - 2s 24ms/step - loss: 1.0325 - acc: 0.5579 - val_loss: 1.0947 - val_acc: 0.5208 Epoch 44/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0157 - acc: 0.5526 - val_loss: 1.1161 - val_acc: 0.6250 Epoch 45/70 64/64 [==============================] - 2s 24ms/step - loss: 1.0127 - acc: 0.6053 - val_loss: 1.3836 - val_acc: 0.4792 Epoch 46/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0965 - acc: 0.5316 - val_loss: 1.1163 - val_acc: 0.4792 Epoch 47/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0132 - acc: 0.5684 - val_loss: 1.1136 - val_acc: 0.5625 Epoch 48/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0016 - acc: 0.6105 - val_loss: 1.1270 - val_acc: 0.5625 Epoch 49/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0437 - acc: 0.5632 - val_loss: 1.0755 - val_acc: 0.4583 Epoch 50/70 64/64 [==============================] - 2s 26ms/step - loss: 1.0695 - acc: 0.5211 - val_loss: 1.0672 - val_acc: 0.6042 Epoch 51/70 64/64 [==============================] - 2s 24ms/step - loss: 1.0289 - acc: 0.6053 - val_loss: 1.0574 - val_acc: 0.5625 Epoch 52/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0495 - acc: 0.5737 - val_loss: 1.0655 - val_acc: 0.5417 Epoch 53/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0208 - acc: 0.5526 - val_loss: 1.0599 - val_acc: 0.5833 Epoch 54/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0127 - acc: 0.5737 - val_loss: 1.0403 - val_acc: 0.5417 Epoch 55/70 64/64 [==============================] - 1s 23ms/step - loss: 0.9969 - acc: 0.6211 - val_loss: 1.1071 - val_acc: 0.5417 Epoch 56/70 64/64 [==============================] - 1s 23ms/step - loss: 0.9831 - acc: 0.5789 - val_loss: 1.0827 - val_acc: 0.5833 Epoch 57/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0391 - acc: 0.5842 - val_loss: 1.0557 - val_acc: 0.5625 Epoch 58/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0078 - acc: 0.5789 - val_loss: 1.0285 - val_acc: 0.5208 Epoch 59/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0338 - acc: 0.5789 - val_loss: 1.0545 - val_acc: 0.4583 Epoch 60/70 64/64 [==============================] - 2s 25ms/step - loss: 0.9949 - acc: 0.5895 - val_loss: 1.1199 - val_acc: 0.5000 Epoch 61/70 64/64 [==============================] - 2s 26ms/step - loss: 1.0280 - acc: 0.5579 - val_loss: 1.1217 - val_acc: 0.5417 Epoch 62/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0137 - acc: 0.5789 - val_loss: 1.0489 - val_acc: 0.5625
Epoch 63/70 64/64 [==============================] - 1s 23ms/step - loss: 1.0201 - acc: 0.5842 - val_loss: 1.1206 - val_acc: 0.5417 Epoch 64/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0406 - acc: 0.5421 - val_loss: 1.1156 - val_acc: 0.5417 Epoch 65/70 64/64 [==============================] - 1s 22ms/step - loss: 0.9496 - acc: 0.5895 - val_loss: 1.0551 - val_acc: 0.5833 Epoch 66/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0339 - acc: 0.5842 - val_loss: 1.1254 - val_acc: 0.4583 Epoch 67/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0083 - acc: 0.5368 - val_loss: 1.0450 - val_acc: 0.5208 Epoch 68/70 64/64 [==============================] - 1s 22ms/step - loss: 0.9835 - acc: 0.6421 - val_loss: 1.0769 - val_acc: 0.5625 Epoch 69/70 64/64 [==============================] - 1s 22ms/step - loss: 1.0205 - acc: 0.5737 - val_loss: 1.0686 - val_acc: 0.5208 Epoch 70/70 64/64 [==============================] - 1s 22ms/step - loss: 0.9852 - acc: 0.5737 - val_loss: 1.0538 - val_acc: 0.5000
plt.figure(figsize = (15, 5))
plt.plot(range(EPOCHS), history.history["acc"], marker = "x")
plt.plot(range(EPOCHS), history.history["val_acc"], marker = "x")
plt.title("Précisions (entraînement et validation)")
plt.ylabel("Précision (accuracy)")
plt.xlabel("Epoque d'entraînement")
plt.xticks(ticks = range(0, EPOCHS, 5), labels = range(1, EPOCHS + 1, 5))
plt.legend(["Jeu d'entraînement", "Jeu de validation"], loc = "upper left")
plt.show()
Il semble que malgré les époques d'apprentissage, le modèle stagne à 50% de précision sur le jeu de validation. On peut donc penser qu'il n'apprend pas à classifier correctement certains échantillons, possiblement ceux évoqués plus haut.
5. Analyse du modèle¶
layer_names = [layer.name for layer in model1.layers]
layer_names
['conv2d_1', 'max_pooling2d_1', 'flatten_1', 'dense_1']
model1.layers
[<tensorflow.python.keras.layers.convolutional.Conv2D at 0x1a63fdf4470>, <tensorflow.python.keras.layers.pooling.MaxPooling2D at 0x1a63ffae940>, <tensorflow.python.keras.layers.core.Flatten at 0x1a63ffae588>, <tensorflow.python.keras.layers.core.Dense at 0x1a63ffc5fd0>]
layer_outputs = [layer.output for layer in model1.layers]
feature_map_model = tf.keras.models.Model(model1.input, layer_outputs)
ims_to_fm = []
plt.figure(figsize = (20, 10))
for k in range(5):
path_to_folder = os.path.join(FOLDER, diseases[k])
ims = os.listdir(path_to_folder)
im = PIL.Image.open(os.path.join(path_to_folder, ims[10])).resize(SIZE) # get 10th image of each folder
im = np.array(im)
ims_to_fm.append(im)
plt.subplot(2, 3, k+1)
plt.title(f"Image de la classe {diseases[k]}")
plt.imshow(im)
Résultats pour chacune des classes après la 1ère couche de convolution. On repère bien les pixels plus clairs (attention, ils sont standardisés !) sur la classe virus. Les contours sont également bien identifiés mais ne sont pas très utiles, à part à la classification des "Fungi" (la maladie attaque le contour des feuilles). Les zones brunes des "Nematodes" sont bien visibles aussi. Le dégradé causée par les bactéries (classe 1, 1ère image) permettra aussi sans doute d'aider à la classification.
feature_maps = [feature_map_model.predict(input_im.reshape(1, 100, 100, 3)) for input_im in ims_to_fm]
conv_feature_map = [feature_maps[k][1] for k in range(5)]
conv_feature_map = [conv_feature_map[k].reshape((49, 49)) for k in range(5)]
conv_feature_map = [(conv_feature_map[k] - conv_feature_map[k].min())/(conv_feature_map[k].max() - conv_feature_map[k].min()) for k in range(5)] # min-max scaling
for k in range(5):
conv_feature_map[k] *= 255 # from [0, 1.0] to [0, 255.0]
plt.figure(figsize = (20, 10))
for k in range(5):
plt.subplot(2, 3, k+1)
plt.title(f"Image de la classe {diseases[k]}")
plt.imshow(conv_feature_map[k])
Que se passe-t-il après la couche de Pooling ?
pooling_feature_map = [feature_maps[k][2] for k in range(5)]
pooling_feature_map = [pooling_feature_map[k].reshape((49, 49)) for k in range(5)]
pooling_feature_map = [((pooling_feature_map[k] - pooling_feature_map[k].min())/(pooling_feature_map[k].max() - pooling_feature_map[k].min()))*255 for k in range(5)]
plt.figure(figsize = (20, 10))
for k in range(5):
plt.subplot(2, 3, k+1)
plt.title(f"Image de la classe {diseases[k]}")
plt.imshow(pooling_feature_map[k])
Peu d'effet ! Il faudrait ajouter plus de couches pour permettre au réseau de retirer des détails plus fins des images et ne pas mettre de couche de Pooling aussi tôt (plutôt après quelques couches de convolution).
6. Réseau multi-couches pour la convolution¶
Noter qu'étant donné le peu de données dont nous disposons (240 environ), cela n'a pas de sens de construire un réseau trop profond (il ne faut pas que le nombre de paramètres soit supérieur au nombre de données disponible).
# Build the neural network
input_shape = X.shape[1:]
print("Entrée du réseau :", input_shape) # 100x100x3
model2 = Sequential()
model2.add(Conv2D(filters = 3,
kernel_size = 5, # un grand noyau est bon ici car peu de différence des données sur les bords d'images
input_shape = input_shape,
strides = (3, 3))) # pas de padding car même commentaire sur les bords
print("Sortie de la 1ère couche de convolution :", model2.output_shape) # 32x32x3
model2.add(Conv2D(filters = 1,
kernel_size = 3, # on diminue la taille du noyau pour commencer à tenir compte des bords
input_shape = model2.output_shape,
padding = "same")) # il faut commencer à garder les bords
print("Sortie de la 2ème couche de convolution :", model2.output_shape)
model2.add(MaxPooling2D(pool_size = (3, 3)))
print("Sortie de la couche de Pooling :", model2.output_shape)
model2.add(Dropout(0.2)) # éviter l'overfitting
model2.add(Flatten())
print("Sortie de la couche de Flatten :", model2.output_shape)
model2.add(Dense(5, activation = "softmax")) # softmax important car entropie croisée comme fonction de perte !
print("Sortie du réseau :", model2.output_shape)
Entrée du réseau : (100, 100, 3) Sortie de la 1ère couche de convolution : (None, 32, 32, 3) Sortie de la 2ème couche de convolution : (None, 32, 32, 1) Sortie de la couche de Pooling : (None, 10, 10, 1) Sortie de la couche de Flatten : (None, 100) Sortie du réseau : (None, 5)
model2.summary()
Model: "sequential_9" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_15 (Conv2D) (None, 32, 32, 3) 228 _________________________________________________________________ conv2d_16 (Conv2D) (None, 32, 32, 1) 28 _________________________________________________________________ max_pooling2d_8 (MaxPooling2 (None, 10, 10, 1) 0 _________________________________________________________________ dropout_4 (Dropout) (None, 10, 10, 1) 0 _________________________________________________________________ flatten_6 (Flatten) (None, 100) 0 _________________________________________________________________ dense_6 (Dense) (None, 5) 505 ================================================================= Total params: 761 Trainable params: 761 Non-trainable params: 0 _________________________________________________________________
Ce réseau est déjà beaucoup trop gros compte-tenu des données disponibles.
model2.compile(
loss = "categorical_crossentropy",
optimizer = "adam",
metrics = ["accuracy"])
EPOCHS = 40
history = model2.fit(
X_train,
y_train,
batch_size = 1, # petit batch size car pas beaucoup de données
validation_data = (X_test, y_test),
epochs = EPOCHS,
verbose = 1)
Train on 190 samples, validate on 48 samples Epoch 1/40 190/190 [==============================] - 2s 8ms/sample - loss: 1.6397 - acc: 0.2000 - val_loss: 1.5889 - val_acc: 0.2292 Epoch 2/40 190/190 [==============================] - 1s 6ms/sample - loss: 1.5806 - acc: 0.2737 - val_loss: 1.6192 - val_acc: 0.2708 Epoch 3/40 190/190 [==============================] - 1s 6ms/sample - loss: 1.5402 - acc: 0.3474 - val_loss: 1.4892 - val_acc: 0.2917 Epoch 4/40 190/190 [==============================] - 1s 6ms/sample - loss: 1.3848 - acc: 0.4737 - val_loss: 1.3697 - val_acc: 0.5000 Epoch 5/40 190/190 [==============================] - 1s 6ms/sample - loss: 1.1844 - acc: 0.5632 - val_loss: 1.0667 - val_acc: 0.6042 Epoch 6/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.9043 - acc: 0.6895 - val_loss: 0.8915 - val_acc: 0.6875 Epoch 7/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.8561 - acc: 0.6526 - val_loss: 0.8695 - val_acc: 0.7083 Epoch 8/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.7209 - acc: 0.7368 - val_loss: 0.8155 - val_acc: 0.7083 Epoch 9/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.6179 - acc: 0.8158 - val_loss: 0.8306 - val_acc: 0.7083 Epoch 10/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.6071 - acc: 0.7684 - val_loss: 0.7735 - val_acc: 0.7708 Epoch 11/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.5755 - acc: 0.7947 - val_loss: 0.7577 - val_acc: 0.7500 Epoch 12/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.4708 - acc: 0.8579 - val_loss: 0.7458 - val_acc: 0.7500 Epoch 13/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.5235 - acc: 0.8316 - val_loss: 0.7225 - val_acc: 0.7708 Epoch 14/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.4296 - acc: 0.8474 - val_loss: 0.7366 - val_acc: 0.7500 Epoch 15/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.3746 - acc: 0.8842 - val_loss: 0.6967 - val_acc: 0.7708 Epoch 16/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.3547 - acc: 0.8789 - val_loss: 0.9579 - val_acc: 0.6667 Epoch 17/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.3211 - acc: 0.9000 - val_loss: 0.7519 - val_acc: 0.7708 Epoch 18/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.3308 - acc: 0.8789 - val_loss: 0.7373 - val_acc: 0.7500 Epoch 19/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.3365 - acc: 0.8842 - val_loss: 0.6703 - val_acc: 0.7708 Epoch 20/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.2948 - acc: 0.8947 - val_loss: 0.6495 - val_acc: 0.7292 Epoch 21/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.3456 - acc: 0.8579 - val_loss: 0.6829 - val_acc: 0.7708 Epoch 22/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.2947 - acc: 0.9000 - val_loss: 0.7416 - val_acc: 0.7500 Epoch 23/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.2488 - acc: 0.9211 - val_loss: 0.7106 - val_acc: 0.7500 Epoch 24/40 190/190 [==============================] - 2s 8ms/sample - loss: 0.2734 - acc: 0.9053 - val_loss: 0.7194 - val_acc: 0.7708 Epoch 25/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.2434 - acc: 0.9000 - val_loss: 0.7033 - val_acc: 0.7292 Epoch 26/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.2170 - acc: 0.9211 - val_loss: 0.7595 - val_acc: 0.7708 Epoch 27/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1852 - acc: 0.9421 - val_loss: 0.9058 - val_acc: 0.7083 Epoch 28/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.2164 - acc: 0.9158 - val_loss: 0.6870 - val_acc: 0.7708 Epoch 29/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.2708 - acc: 0.8947 - val_loss: 0.8095 - val_acc: 0.7292 Epoch 30/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.2136 - acc: 0.9316 - val_loss: 0.8141 - val_acc: 0.7083 Epoch 31/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1941 - acc: 0.9263 - val_loss: 0.6985 - val_acc: 0.7708 Epoch 32/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1980 - acc: 0.9368 - val_loss: 0.9589 - val_acc: 0.7708 Epoch 33/40 190/190 [==============================] - 1s 7ms/sample - loss: 0.1875 - acc: 0.9368 - val_loss: 0.7088 - val_acc: 0.7500 Epoch 34/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1669 - acc: 0.9526 - val_loss: 0.7437 - val_acc: 0.7708 Epoch 35/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1590 - acc: 0.9421 - val_loss: 0.7735 - val_acc: 0.7917 Epoch 36/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1885 - acc: 0.9368 - val_loss: 0.7961 - val_acc: 0.7708 Epoch 37/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1406 - acc: 0.9526 - val_loss: 0.7780 - val_acc: 0.8125 Epoch 38/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1103 - acc: 0.9684 - val_loss: 0.7493 - val_acc: 0.7708 Epoch 39/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1613 - acc: 0.9579 - val_loss: 0.7838 - val_acc: 0.7917 Epoch 40/40 190/190 [==============================] - 1s 6ms/sample - loss: 0.1704 - acc: 0.9368 - val_loss: 0.8776 - val_acc: 0.7500
plt.figure(figsize = (15, 5))
plt.plot(range(EPOCHS), history.history["acc"], marker = "x")
plt.plot(range(EPOCHS), history.history["val_acc"], marker = "x")
plt.title("Précisions (entraînement et validation)")
plt.ylabel("Précision (accuracy)")
plt.xlabel("Epoque d'entraînement")
plt.xticks(ticks = range(0, EPOCHS, 5), labels = range(1, EPOCHS + 1, 5))
plt.legend(["Jeu d'entraînement", "Jeu de validation"], loc = "upper left")
plt.show()
7.Amélioration du réseau - application d'un modèle de type Light AlexNet¶
On tente ici d'appliquer une structure de réseau de convolution remarquable, celle d'AlexNet (Alex Krizhevsky a conçu ce réseau durant son doctorat). La structure initiale du réseau est allégée pour réduire le temps de calcul (sur CPU dans mon cas).
# Light AlexNet-like network
input_shape = X.shape[1:]
print("Entrée du réseau type Light AlexNet :", input_shape) # 100x100x3
model2 = Sequential()
model2.add(Conv2D(
filters = 3,
kernel_size = 4,
strides = (2, 2),
activation = activations.relu,
input_shape = input_shape))
print("Sortie de la 1ère couche de convolution :", model2.output_shape) # x49x49x8
model2.add(MaxPooling2D(
pool_size = (4, 4),
strides = (2, 2)))
print("Sortie de la 1ère couche de MaxPooling :", model2.output_shape) # 23x23x8
model2.add(Conv2D(
filters = 3,
kernel_size = 4,
strides = (1, 1),
activation = activations.relu,
padding = "same"))
print("Sortie de la 2ème couche de convolution :", model2.output_shape) # 23x23x32
model2.add(MaxPooling2D(
pool_size = (3, 3),
strides = (2, 2)))
print("Sortie de la 2ème couche de MaxPooling :", model2.output_shape) # 11x11x32
model2.add(Conv2D(
filters = 3, # facteur 1.5 comme dans la version original d'AlexNet
kernel_size = 3,
strides = (1, 1),
activation = activations.relu,
padding = "same"))
print("Sortie de la 3ème couche de convolution :", model2.output_shape) # 11x11x48
model2.add(Conv2D(
filters = 3,#48,
kernel_size = 3,
strides = (1, 1),
activation = activations.relu,
padding = "same"))
print("Sortie de la 4ème couche de convolution :", model2.output_shape) # 11x11x48
model2.add(Conv2D(
filters = 3,#32,
kernel_size = 3,
strides = (1, 1),
activation = activations.relu,
padding = "same"))
print("Sortie de la 5ème couche de convolution :", model2.output_shape) # 11x11x32
model2.add(MaxPooling2D(
pool_size = (3, 3),
strides = (2, 2),
padding = "same"))
print("Sortie de la 3ème couche de MaxPooling :", model2.output_shape) # 6x6x32
model2.add(Flatten())
print("Sortie de la couche de Flatten :", model2.output_shape) # 1152
model2.add(Dropout(0.5))
model2.add(Dense(
108,
activation = activations.relu))
print("Sortie de la 1ère couche dense :", model2.output_shape)
model2.add(Dense(
54,
activation = activations.relu))
print("Sortie de la 2ème couche dense :", model2.output_shape)
model2.add(Dense(
27,
activation = activations.relu))
print("Sortie de la 4ème couche dense :", model2.output_shape)
model2.add(Dense(
5,
activation = activations.relu))
print("Sortie de la dernière couche dense :", model2.output_shape)
Entrée du réseau type Light AlexNet : (100, 100, 3) Sortie de la 1ère couche de convolution : (None, 49, 49, 3) Sortie de la 1ère couche de MaxPooling : (None, 23, 23, 3) Sortie de la 2ème couche de convolution : (None, 23, 23, 3) Sortie de la 2ème couche de MaxPooling : (None, 11, 11, 3) Sortie de la 3ème couche de convolution : (None, 11, 11, 3) Sortie de la 4ème couche de convolution : (None, 11, 11, 3) Sortie de la 5ème couche de convolution : (None, 11, 11, 3) Sortie de la 3ème couche de MaxPooling : (None, 6, 6, 3) Sortie de la couche de Flatten : (None, 108) Sortie de la 1ère couche dense : (None, 108) Sortie de la 2ème couche dense : (None, 54) Sortie de la 4ème couche dense : (None, 27) Sortie de la dernière couche dense : (None, 5)
Noter que None représente le Batch Size à définir.
model2.compile(
loss = "categorical_crossentropy",
optimizer = "adam",
metrics = ["accuracy"])
EPOCHS = 10
history = model2.fit(
X_train,
y_train,
batch_size = 5,
validation_data = (X_test, y_test),
epochs = EPOCHS,
verbose = 0)
plt.figure(figsize = (15, 5))
plt.plot(range(EPOCHS), history.history["acc"], marker = "x")
plt.plot(range(EPOCHS), history.history["val_acc"], marker = "x")
plt.title("Précisions (entraînement et validation)")
plt.ylabel("Précision (accuracy)")
plt.xlabel("Epoque d'entraînement")
plt.xticks(ticks = range(0, EPOCHS, 5), labels = range(1, EPOCHS + 1, 5))
plt.legend(["Jeu d'entraînement", "Jeu de validation"], loc = "upper left")
plt.show()
Un exemple d'exécution¶
On considère une image en particulier :
im = Image("test.jpg")
display(im)
im = PIL.Image.open("test.jpg").resize(SIZE)
data = np.array(im, dtype = "uint8")
data.shape
(100, 100, 3)
model1.input_shape
(None, 100, 100, 3)
model1.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 98, 98, 1) 28 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 49, 49, 1) 0 _________________________________________________________________ flatten (Flatten) (None, 2401) 0 _________________________________________________________________ dense (Dense) (None, 5) 12010 ================================================================= Total params: 12,038 Trainable params: 12,038 Non-trainable params: 0 _________________________________________________________________
data = data/255.0
data = data.reshape(-1, 100, 100, 3)
data.shape
(1, 100, 100, 3)
model1.predict(data)
array([[6.4149761e-01, 1.0553817e-02, 1.3960915e-03, 3.4655020e-01, 2.4056558e-06]], dtype=float32)
Différence entre "Nématodes" et "Bactéries"¶
for dis in ["Nematodes", "Bacteria"]:
print(dis)
images = os.listdir(os.path.join(FOLDER, dis))
for i in range(5):
im = Image(os.path.join(FOLDER, dis) + "/" + images[i])
display(im)
Nematodes
Bacteria