Introduction et initialisation¶

Objectifs et approche¶

L'objectif de ce TP est de développer un réseau de neuronnes convolutifs pour détecter des obstacles à partir d'un dataset d'images.

Nous étudierons l’influence de différents hyperparamètres, comme le nombre de couches convolutionnelles, la taille des filtres et le taux d’apprentissage, sur la performance du modèle.

Notre analyse inclura principalement la visualisation des courbes d’apprentissage (accuracy et loss), ainsi que la matrice de confusion pour mesurer la qualité des prédictions.

Configurations initiales¶

Nous commençons par importer les librairies utiles.

In [2]:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.regularizers import l2
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

import matplotlib.pyplot as plt
import numpy as np
import math
from sklearn.metrics import confusion_matrix, classification_report

import itertools

Nous récupérons ensuite le dataset, chargeons et on préparons les données.

In [3]:
# Charger et préparer les données
path='dataset'
train_dir = f'{path}/train'
val_dir = f'{path}/test'
datagen = ImageDataGenerator(rescale=1./255)
train_generator = datagen.flow_from_directory(
    train_dir, target_size=(64, 64), 
    batch_size=32, 
    class_mode='categorical',
    shuffle=False)
val_generator = datagen.flow_from_directory(
    val_dir, target_size=(64, 64), 
    batch_size=32,
    class_mode='categorical',
    shuffle=False)

# Définir le modèle CNN
model = Sequential([
    Conv2D(32, (3, 3), activation='relu', input_shape=(64, 64, 3)),
    MaxPooling2D(pool_size=(2, 2)),
    Conv2D(64, (3, 3), activation='relu'),
    MaxPooling2D(pool_size=(2, 2)),
    Flatten(),
    Dense(128, activation='relu'),
    Dense(10, activation='softmax')])
Found 3132 images belonging to 10 classes.
Found 432 images belonging to 10 classes.
/Users/alaaeddineahriz/.pyenv/versions/3.10.16/lib/python3.10/site-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)

Analyse du dataset¶

Nous examinons maintenant le jeu de données. Nous commençons par sélectionner aléatoirement 10 images que nous affichons accompagnées de leurs étiquettes. Cette visualisation permet d’observer la diversité des exemples et d’identifier d’éventuelles similitudes visuelles entre les catégories.

In [3]:
# Afficher des exemples d'images d'entraînement
def show_batch_samples(generator, title=None):
    plt.figure(figsize=(15, 8))
    
    # Récupérer un batch d'images
    images, labels = next(generator)
    
    # Convertir les labels one-hot en indices de classe
    labels = np.argmax(labels, axis=1)
    
    # Afficher 10 images (une par classe si possible)
    for i in range(min(10, len(images))):
        plt.subplot(2, 5, i+1)
        plt.imshow(images[i])
        plt.title(f"Classe: {list(generator.class_indices.keys())[labels[i]]}")
        plt.axis('off')
    
    if title:
        plt.suptitle(title)
    plt.tight_layout()
    plt.show()

show_batch_samples(train_generator, "Exemples d'images d'entraînement")
No description has been provided for this image

Par la suite, nous représentons graphiquement la distribution des classes pour les ensembles d’entraînement et de validation. Cette répartition des données injectées au modèle facilite l’interprétation des résultats. Elle permettrait notamment d'expliquer d’éventuels écarts aux résultats attendus pour la matrice de confusion.

In [4]:
# Aficher la distribution des classes
def plot_class_distribution(generator, title="Distribution des classes"):
    # Obtenir les nombres d'images par classe
    class_counts = [0] * len(generator.class_indices)
    for i in range(len(generator.classes)):
        class_counts[generator.classes[i]] += 1
    
    # Récupérer les noms des classes
    class_names = list(generator.class_indices.keys())
    
    # Création du graphique
    plt.figure(figsize=(12, 6))
    plt.bar(class_names, class_counts)
    plt.xlabel('Classes')
    plt.ylabel('Nombre d\'images')
    plt.title(title)
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()
    
    # Afficher les statistiques
    print("Nombre total d'images:", sum(class_counts))
    print("Répartition par classe:")
    for i, (name, count) in enumerate(zip(class_names, class_counts)):
        print(f"  Classe {i} ({name}): {count} images ({count/sum(class_counts)*100:.1f}%)")

# Afficher la distribution des classes d'entraînement et de validation
plot_class_distribution(train_generator, "Distribution des classes - Données d'entraînement")
plot_class_distribution(val_generator, "Distribution des classes - Données de validation")
No description has been provided for this image
Nombre total d'images: 3132
Répartition par classe:
  Classe 0 (chair): 383 images (12.2%)
  Classe 1 (door): 191 images (6.1%)
  Classe 2 (fence): 167 images (5.3%)
  Classe 3 (garbage_bin): 158 images (5.0%)
  Classe 4 (obstacle): 332 images (10.6%)
  Classe 5 (plant): 115 images (3.7%)
  Classe 6 (pothole): 676 images (21.6%)
  Classe 7 (stairs): 444 images (14.2%)
  Classe 8 (table): 152 images (4.9%)
  Classe 9 (vehicle): 514 images (16.4%)
No description has been provided for this image
Nombre total d'images: 432
Répartition par classe:
  Classe 0 (chair): 24 images (5.6%)
  Classe 1 (door): 51 images (11.8%)
  Classe 2 (fence): 12 images (2.8%)
  Classe 3 (garbage_bin): 17 images (3.9%)
  Classe 4 (obstacle): 91 images (21.1%)
  Classe 5 (plant): 24 images (5.6%)
  Classe 6 (pothole): 30 images (6.9%)
  Classe 7 (stairs): 60 images (13.9%)
  Classe 8 (table): 33 images (7.6%)
  Classe 9 (vehicle): 90 images (20.8%)

L’analyse de la distribution des classes dans le dataset révèle un déséquilibre marqué entre les classes dans les ensembles d’entraînement et de validation. Dans le jeu d’entraînement, certaines classes telles que "pothole" (21,6%), "vehicle" (16,4%) et "stairs" (14,2%) sont surreprésentées, tandis que des classes comme "plant" (3,7%), "fence" (5,3%) ou "garbage_bin (5,0%) figurent en minorité. Le jeu de validation présente une répartition similaire, bien que quelques écarts subsistent (par exemple, "door" passe de 6,1% en entraînement à 11,8% en validation).

Ces disparités peuvent influencer la matrice de confusion : le modèle est susceptible d’obtenir de meilleures performances sur les classes les mieux représentées, tandis que les classes rares pourraient être confondues, entraînant des précisions et des rappels faibles. La variation relative entre les distributions d’entraînement et de validation pourrait également contribuer à des erreurs de classification spécifiques.

Fonctions utiles¶

On définit ici les fonctions utiles à l'analyse des résultats des modèles, notamment :

  • l'évolution de l'accuracy et de la loss en fonction des epochs pour l'entraînement et la validation
  • la matrice de confusion
In [19]:
# Visualiser les courbes d'apprentissage (accuracy et loss)
def plot_learning_curves(history, title="Courbes d'apprentissage"):
    # Récupérer l'historique d'entraînement
    acc = history.history['accuracy']
    val_acc = history.history['val_accuracy']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(acc) + 1)
    
    # Créer les graphiques
    plt.figure(figsize=(12, 5))
    
    # Graphique d'accuracy
    plt.subplot(1, 2, 1)
    plt.plot(epochs, acc, 'b-', label='Accuracy entraînement')
    plt.plot(epochs, val_acc, 'r-', label='Accuracy validation')
    plt.title('Accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    
    # Graphique de loss
    plt.subplot(1, 2, 2)
    plt.plot(epochs, loss, 'b-', label='Loss entraînement')
    plt.plot(epochs, val_loss, 'r-', label='Loss validation')
    plt.title('Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()
    
    plt.suptitle(title)
    plt.tight_layout()
    plt.show()
In [20]:
# Visualiser les courbes d'apprentissage pour une approche spécifique
def plot_training_curves(history, title=""):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
    
    # Accuracy
    ax1.plot(history.history['accuracy'])
    ax1.plot(history.history['val_accuracy'])
    ax1.set_title(f'Accuracy - {title}')
    ax1.set_ylabel('Accuracy')
    ax1.set_xlabel('Epoch')
    ax1.legend(['Train', 'Validation'], loc='lower right')
    
    # Loss
    ax2.plot(history.history['loss'])
    ax2.plot(history.history['val_loss'])
    ax2.set_title(f'Loss - {title}')
    ax2.set_ylabel('Loss')
    ax2.set_xlabel('Epoch')
    ax2.legend(['Train', 'Validation'], loc='upper right')
    
    plt.tight_layout()
    plt.show()
    
    # Afficher le learning rate si disponible
    if 'lr' in history.history:
        plt.figure(figsize=(10, 5))
        plt.plot(history.history['lr'])
        plt.title(f'Learning Rate - {title}')
        plt.ylabel('Learning Rate')
        plt.xlabel('Epoch')
        plt.yscale('log')
        plt.grid(True)
        plt.show()
In [21]:
#Afficher la matrice de confusion
def plot_confusion_matrix(cm, classes, title='Matrice de confusion', cmap=plt.cm.Blues):
    plt.figure(figsize=(10, 8))
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)
    
    # Afficher les valeurs dans les cellules
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], 'd'),
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")
    
    plt.tight_layout()
    plt.ylabel('Classe réelle')
    plt.xlabel('Classe prédite')
    plt.show()

def evaluate_model(model, generator, title="Évaluation du modèle"):
    # Réinitialiser le générateur
    generator.reset()
    
    # Prédire les classes
    Y_pred = model.predict(generator)
    y_pred = np.argmax(Y_pred, axis=1)
    
    # Classes réelles
    y_true = generator.classes
    
    # Noms des classes
    class_names = list(generator.class_indices.keys())
    
    # Calculer et afficher la matrice de confusion
    cm = confusion_matrix(y_true, y_pred)
    plot_confusion_matrix(cm, class_names, title=f"Matrice de confusion - {title}")
    
    # Afficher le rapport de classification
    print(f"Rapport de classification - {title}")
    print(classification_report(y_true, y_pred, target_names=class_names))
In [7]:
# Visualiser les résultats pour tous les learning rates
def plot_learning_rate_comparison(results):
    learning_rates = list(results.keys())
    accuracies = [results[lr]['val_accuracy'] for lr in learning_rates]
    losses = [results[lr]['val_loss'] for lr in learning_rates]
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
    
    # Graphique d'accuracy
    ax1.semilogx(learning_rates, accuracies, 'o-')
    ax1.set_title('Validation Accuracy vs Learning Rate')
    ax1.set_xlabel('Learning Rate (log scale)')
    ax1.set_ylabel('Validation Accuracy')
    ax1.grid(True)
    
    # Graphique de loss
    ax2.semilogx(learning_rates, losses, 'o-')
    ax2.set_title('Validation Loss vs Learning Rate')
    ax2.set_xlabel('Learning Rate (log scale)')
    ax2.set_ylabel('Validation Loss')
    ax2.grid(True)
    
    plt.tight_layout()
    plt.show()
    
    # Trouver le meilleur learning rate
    best_lr = min(results.items(), key=lambda x: x[1]['val_loss'])[0]
    best_accuracy = results[best_lr]['val_accuracy']
    best_loss = results[best_lr]['val_loss']
    
    print(f"\nMeilleur learning rate: {best_lr}")
    print(f"Meilleure accuracy: {best_accuracy:.4f}")
    print(f"Meilleure loss: {best_loss:.4f}")
    
    return best_lr

Modèle CNN de base¶

Nous commmençons ici par compiler et entraîner le modèle de base tel que donné dans le sujet. Ensuite, nous l'évaluons en affichant l'évolution de l'accuracy et de la loss pour les sets d'entraînement et de validation. Cette étape initiale nous permettra d'établir une référence (benchmark) pour comparer les performances des modèles plus complexes que nous développerons par la suite.

Définition du modèle¶

In [10]:
# Définition du modèle CNN de base
def create_base_model():
    model = Sequential([
        Conv2D(32, (3, 3), activation='relu', input_shape=(64, 64, 3)),
        MaxPooling2D(pool_size=(2, 2)),
        Conv2D(64, (3, 3), activation='relu', padding='same'),
        MaxPooling2D(pool_size=(2, 2)),
        Flatten(),
        Dense(128, activation='relu'),
        Dense(10, activation='softmax')
    ])
    
    # Compilation du modèle
    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',  # Corrigé pour classification multiclasse
        metrics=['accuracy']
    )
    
    return model

# Création du modèle de base
base_model = create_base_model()

# Résumé du modèle
base_model.summary()
Model: "sequential_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_2 (Conv2D)               │ (None, 62, 62, 32)     │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 31, 31, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 31, 31, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 15, 15, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_1 (Flatten)             │ (None, 14400)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 128)            │     1,843,328 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (Dense)                 │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 1,864,010 (7.11 MB)
 Trainable params: 1,864,010 (7.11 MB)
 Non-trainable params: 0 (0.00 B)
In [11]:
# Entraîner le modèle
base_history = base_model.fit(
    train_generator,
    validation_data=val_generator,
    epochs=30,
    verbose=1
)

# Évaluer le modèle
loss, accuracy = base_model.evaluate(val_generator)
print(f'Loss: {loss:.4f}, Accuracy: {accuracy:.4f}')
Epoch 1/30
/Users/alaaeddineahriz/.pyenv/versions/3.10.16/lib/python3.10/site-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
  self._warn_if_super_not_called()
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 49ms/step - accuracy: 0.1203 - loss: 2.5208 - val_accuracy: 0.0694 - val_loss: 2.2605
Epoch 2/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.2950 - loss: 2.2403 - val_accuracy: 0.2083 - val_loss: 2.2033
Epoch 3/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 43ms/step - accuracy: 0.2751 - loss: 2.1920 - val_accuracy: 0.2199 - val_loss: 2.1398
Epoch 4/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 44ms/step - accuracy: 0.2630 - loss: 2.1683 - val_accuracy: 0.2384 - val_loss: 2.0760
Epoch 5/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 44ms/step - accuracy: 0.3144 - loss: 1.9357 - val_accuracy: 0.2245 - val_loss: 2.0235
Epoch 6/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 44ms/step - accuracy: 0.3893 - loss: 1.8013 - val_accuracy: 0.2685 - val_loss: 1.9126
Epoch 7/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 44ms/step - accuracy: 0.4410 - loss: 1.6239 - val_accuracy: 0.1852 - val_loss: 2.0894
Epoch 8/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 46ms/step - accuracy: 0.4769 - loss: 1.5949 - val_accuracy: 0.3287 - val_loss: 1.9095
Epoch 9/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.4698 - loss: 1.4651 - val_accuracy: 0.4977 - val_loss: 1.4930
Epoch 10/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 46ms/step - accuracy: 0.5185 - loss: 1.3446 - val_accuracy: 0.5787 - val_loss: 1.3516
Epoch 11/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.6499 - loss: 1.0240 - val_accuracy: 0.5417 - val_loss: 1.3316
Epoch 12/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 46ms/step - accuracy: 0.7092 - loss: 0.8443 - val_accuracy: 0.6227 - val_loss: 1.1701
Epoch 13/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 46ms/step - accuracy: 0.8052 - loss: 0.5975 - val_accuracy: 0.6134 - val_loss: 1.2105
Epoch 14/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 48ms/step - accuracy: 0.8376 - loss: 0.4759 - val_accuracy: 0.6273 - val_loss: 1.2674
Epoch 15/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 47ms/step - accuracy: 0.8529 - loss: 0.4199 - val_accuracy: 0.6088 - val_loss: 1.2443
Epoch 16/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.9271 - loss: 0.2729 - val_accuracy: 0.6574 - val_loss: 1.0781
Epoch 17/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.9533 - loss: 0.1743 - val_accuracy: 0.6713 - val_loss: 1.2189
Epoch 18/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 46ms/step - accuracy: 0.9673 - loss: 0.1282 - val_accuracy: 0.6806 - val_loss: 1.2874
Epoch 19/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.9917 - loss: 0.0571 - val_accuracy: 0.6528 - val_loss: 1.6690
Epoch 20/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 45ms/step - accuracy: 0.9849 - loss: 0.0675 - val_accuracy: 0.6690 - val_loss: 1.4887
Epoch 21/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 46ms/step - accuracy: 0.9985 - loss: 0.0204 - val_accuracy: 0.6505 - val_loss: 1.6301
Epoch 22/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 4s 46ms/step - accuracy: 0.9972 - loss: 0.0190 - val_accuracy: 0.6736 - val_loss: 1.6749
Epoch 23/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 51ms/step - accuracy: 0.9998 - loss: 0.0089 - val_accuracy: 0.6736 - val_loss: 1.6519
Epoch 24/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 51ms/step - accuracy: 1.0000 - loss: 0.0074 - val_accuracy: 0.6898 - val_loss: 1.6545
Epoch 25/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 6s 60ms/step - accuracy: 1.0000 - loss: 0.0038 - val_accuracy: 0.6806 - val_loss: 1.8003
Epoch 26/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 6s 56ms/step - accuracy: 1.0000 - loss: 0.0030 - val_accuracy: 0.6736 - val_loss: 1.8212
Epoch 27/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 6s 57ms/step - accuracy: 1.0000 - loss: 0.0020 - val_accuracy: 0.6829 - val_loss: 1.8082
Epoch 28/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 5s 54ms/step - accuracy: 1.0000 - loss: 0.0018 - val_accuracy: 0.6782 - val_loss: 1.8018
Epoch 29/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 6s 62ms/step - accuracy: 1.0000 - loss: 0.0017 - val_accuracy: 0.6852 - val_loss: 1.8815
Epoch 30/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 8s 83ms/step - accuracy: 1.0000 - loss: 0.0015 - val_accuracy: 0.6782 - val_loss: 1.9056
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 39ms/step - accuracy: 0.6828 - loss: 1.8055
Loss: 1.9056, Accuracy: 0.6782

Résultats¶

In [12]:
# Visualiser les courbes d'apprentissage du modèle de base
plot_learning_curves(base_history, "Courbes d'apprentissage - Modèle de base")
No description has been provided for this image

En analysant les courbes d’apprentissage, nous constatons que l’accuracy de la validation diverge rapidement de celle de l’entraînement, indiquant un surapprentissage précoce. De même, la loss de validation s’écarte notablement de celle mesurée pendant l’entraînement.

In [13]:
# Évaluer le modèle de base avec la matrice de confusion et le rapport de classification
evaluate_model(base_model, val_generator, "Modèle de base")
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 35ms/step
No description has been provided for this image
Rapport de classification - Modèle de base
              precision    recall  f1-score   support

       chair       0.33      0.75      0.46        24
        door       0.79      0.75      0.77        51
       fence       0.62      0.42      0.50        12
 garbage_bin       0.60      0.71      0.65        17
    obstacle       0.86      0.54      0.66        91
       plant       0.95      0.79      0.86        24
     pothole       0.63      0.90      0.74        30
      stairs       0.61      0.75      0.67        60
       table       0.68      0.70      0.69        33
     vehicle       0.77      0.63      0.70        90

    accuracy                           0.68       432
   macro avg       0.68      0.69      0.67       432
weighted avg       0.73      0.68      0.69       432

La matrice de confusion présente déjà une diagonale bien définie, traduisant des résultats satisfaisants. Certaines classes, notamment « vehicle », « obstacle », « stairs », « door » et « pothole », sont nettement mieux reconnues que les autres, ce qui s’explique principalement par la distribution déséquilibrée du dataset (d'entraînement et de validation) où ces classes sont surreprésentées

Une analyse par classe, effectuée sur le rapport de classification, révèle elle aussi des performances contrastées :

  • La classe « pothole » affiche le meilleur recall (0,90) : 90 % des nids-de-poule sont correctement identifiés, bien que la précision soit moyenne (0,63) en raison d’un nombre important de faux positifs.
  • La classe « plant » présente quant à elle la meilleure précision (0,95) avec un recall acceptable (0,79).
  • La classe « obstacle » montre en revanche une bonne précision (0,6) mais un recall médiocre (0,54), indiquant que le modèle omet fréquemment cette classe.
  • Enfin, la classe « chair » a la précision la plus faible (0,33). Elle est donc souvent confondue avec d'autres classes.

Globalement, l’accuracy atteint 0,68, signifiant que 68 % des prédictions sont correctes. La moyenne pondérée du f1-score (0,69) dépasse légèrement la moyenne macro (0,67) ce qui souligne, une fois encore, l’influence du déséquilibre des classes sur la précision du modèle.

Nous allons donc tester différentes optimisations de notre modèle afin d'avoir de meilleurs résultats.

Expérimentation - Nombre de couches convolutives¶

À cette étape, nous cherchons à évaluer l’impact du nombre de couches convolutives sur la performance de notre réseau de neuronnes. La fonction create_model_with_layers(num_conv_layers) construit un modèle contenant n couches convolutives (n allant de 1 à 5). Plus précisement :

  • Nous commençons par une couche Conv2D (32 filtres, noyau 3×3) suivie d’un max pooling.
  • Au moyen d'une boucle on ajoute ensuite, pour chaque couche supplémentaire, une paire Conv2D/MaxPooling2D, doublant le nombre de filtres à chaque fois (limité à 256).
  • Nous terminons le réseau par deux couches d'activation. Une couche fully connected de 128 neurones et une couche de sortie softmax, adaptée à la classification multiclasse (10 dans notre cas).

Pour différentes configurations (1 à 5 couches), nous entraînons chaque modèle sur 15 époques, puis comparons leurs performances en affichant un graphique de l’accuracy de validation. Enfin nous comparons les courbes d’apprentissage du meilleur modèle par rapport à celles du modèle de base (benchmark).

Définition du modèle à n couches convolutives¶

In [14]:
def create_model_with_layers(num_conv_layers):
    model = Sequential()
    
    # Première couche
    model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(64, 64, 3)))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    
    # Ajouter des couches convolutives
    filters = 64
    for _ in range(1, num_conv_layers):
        model.add(Conv2D(filters, (3, 3), padding='same', activation='relu'))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        filters = min(filters * 2, 256)  # Augmenter le nombre de filtres, max 256
    
    # Couches fully connected
    model.add(Flatten())
    model.add(Dense(128, activation='relu'))
    model.add(Dense(10, activation='softmax'))
    
    # Compiler le modèle
    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    
    return model
In [ ]:
# Tester différents nombres de couches
layer_results = {}
for num_layers in [1,2,3]:
    print(f"\nEntraînement du modèle avec {num_layers} couches convolutives")
    
    # Créer et entraîner le modèle
    model = create_model_with_layers(num_layers)
    history = model.fit(
        train_generator,
        validation_data=val_generator,
        epochs=15,
        #callbacks=[early_stopping],
        verbose=1
    )
    
    # Évaluer le modèle
    val_loss, val_acc = model.evaluate(val_generator)
    
    # Stocker les résultats
    layer_results[num_layers] = {
        'model': model,
        'history': history,
        'val_accuracy': val_acc,
        'val_loss': val_loss
    }
    
    print(f"Modèle avec {num_layers} couches: Loss = {val_loss:.4f}, Accuracy = {val_acc:.4f}")
Entraînement du modèle avec 1 couches convolutives
Epoch 1/15
/Users/alaaeddineahriz/.pyenv/versions/3.10.16/lib/python3.10/site-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 89ms/step - accuracy: 0.1499 - loss: 5.3390 - val_accuracy: 0.0556 - val_loss: 2.2643
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 94ms/step - accuracy: 0.1014 - loss: 2.2568 - val_accuracy: 0.0903 - val_loss: 2.2268
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 92ms/step - accuracy: 0.2729 - loss: 2.1177 - val_accuracy: 0.0625 - val_loss: 2.2049
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 107ms/step - accuracy: 0.1922 - loss: 2.1547 - val_accuracy: 0.1343 - val_loss: 2.4176
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 110ms/step - accuracy: 0.3255 - loss: 1.9668 - val_accuracy: 0.1134 - val_loss: 2.1940
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 122ms/step - accuracy: 0.2535 - loss: 2.0272 - val_accuracy: 0.2616 - val_loss: 2.0733
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 102ms/step - accuracy: 0.3826 - loss: 1.7720 - val_accuracy: 0.2685 - val_loss: 2.0102
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 97ms/step - accuracy: 0.4001 - loss: 1.7705 - val_accuracy: 0.2338 - val_loss: 1.9280
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 98ms/step - accuracy: 0.4020 - loss: 1.7362 - val_accuracy: 0.2500 - val_loss: 2.1009
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 94ms/step - accuracy: 0.4268 - loss: 1.7197 - val_accuracy: 0.3426 - val_loss: 1.8467
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 89ms/step - accuracy: 0.5075 - loss: 1.4405 - val_accuracy: 0.2731 - val_loss: 1.8622
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 92ms/step - accuracy: 0.4990 - loss: 1.4343 - val_accuracy: 0.3264 - val_loss: 1.7648
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 102ms/step - accuracy: 0.5188 - loss: 1.3581 - val_accuracy: 0.3819 - val_loss: 1.6997
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 100ms/step - accuracy: 0.6114 - loss: 1.1927 - val_accuracy: 0.4120 - val_loss: 1.7432
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 92ms/step - accuracy: 0.5732 - loss: 1.2456 - val_accuracy: 0.4282 - val_loss: 1.6047
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 52ms/step - accuracy: 0.3977 - loss: 1.6934
Modèle avec 1 couches: Loss = 1.6047, Accuracy = 0.4282

Entraînement du modèle avec 2 couches convolutives
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 99ms/step - accuracy: 0.0941 - loss: 2.5512 - val_accuracy: 0.0694 - val_loss: 2.2739
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 100ms/step - accuracy: 0.2541 - loss: 2.2013 - val_accuracy: 0.0694 - val_loss: 2.4715
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 111ms/step - accuracy: 0.2539 - loss: 2.1075 - val_accuracy: 0.1968 - val_loss: 2.1632
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 126ms/step - accuracy: 0.2872 - loss: 2.0559 - val_accuracy: 0.2292 - val_loss: 2.0910
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 110ms/step - accuracy: 0.2660 - loss: 2.0183 - val_accuracy: 0.2361 - val_loss: 2.1477
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 111ms/step - accuracy: 0.3168 - loss: 1.9616 - val_accuracy: 0.1551 - val_loss: 2.1047
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 98ms/step - accuracy: 0.4168 - loss: 1.7562 - val_accuracy: 0.4074 - val_loss: 1.7689
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 103ms/step - accuracy: 0.4605 - loss: 1.6193 - val_accuracy: 0.3958 - val_loss: 1.7715
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 104ms/step - accuracy: 0.4889 - loss: 1.5139 - val_accuracy: 0.3796 - val_loss: 1.7838
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 102ms/step - accuracy: 0.5664 - loss: 1.2352 - val_accuracy: 0.4537 - val_loss: 1.6314
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 96ms/step - accuracy: 0.6470 - loss: 1.0530 - val_accuracy: 0.5694 - val_loss: 1.3123
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 101ms/step - accuracy: 0.7540 - loss: 0.7563 - val_accuracy: 0.5972 - val_loss: 1.2626
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 98ms/step - accuracy: 0.7983 - loss: 0.6553 - val_accuracy: 0.5880 - val_loss: 1.4577
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 90ms/step - accuracy: 0.7598 - loss: 0.6916 - val_accuracy: 0.6389 - val_loss: 1.2038
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 8s 86ms/step - accuracy: 0.8756 - loss: 0.4034 - val_accuracy: 0.6713 - val_loss: 1.1510
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 36ms/step - accuracy: 0.6420 - loss: 1.1646
Modèle avec 2 couches: Loss = 1.1510, Accuracy = 0.6713

Entraînement du modèle avec 3 couches convolutives
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 100ms/step - accuracy: 0.1498 - loss: 2.3759 - val_accuracy: 0.0694 - val_loss: 2.3318
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 99ms/step - accuracy: 0.2221 - loss: 2.2024 - val_accuracy: 0.0764 - val_loss: 2.2646
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 99ms/step - accuracy: 0.2239 - loss: 2.2322 - val_accuracy: 0.0718 - val_loss: 2.2728
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 102ms/step - accuracy: 0.2295 - loss: 2.1535 - val_accuracy: 0.1759 - val_loss: 2.2270
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 98ms/step - accuracy: 0.3162 - loss: 2.1433 - val_accuracy: 0.1782 - val_loss: 2.1681
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 101ms/step - accuracy: 0.3530 - loss: 2.0046 - val_accuracy: 0.2569 - val_loss: 2.1137
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 109ms/step - accuracy: 0.2689 - loss: 2.0517 - val_accuracy: 0.1759 - val_loss: 2.2289
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 127ms/step - accuracy: 0.3786 - loss: 1.8130 - val_accuracy: 0.2037 - val_loss: 2.0636
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 113ms/step - accuracy: 0.2662 - loss: 1.9937 - val_accuracy: 0.0764 - val_loss: 2.5577
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 115ms/step - accuracy: 0.3870 - loss: 1.9486 - val_accuracy: 0.3889 - val_loss: 1.7742
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 113ms/step - accuracy: 0.4092 - loss: 1.7308 - val_accuracy: 0.3588 - val_loss: 1.8263
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 118ms/step - accuracy: 0.4617 - loss: 1.6095 - val_accuracy: 0.3981 - val_loss: 1.7080
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 119ms/step - accuracy: 0.5019 - loss: 1.4629 - val_accuracy: 0.4421 - val_loss: 1.6233
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 99ms/step - accuracy: 0.5167 - loss: 1.3942 - val_accuracy: 0.4468 - val_loss: 1.6256
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 103ms/step - accuracy: 0.5836 - loss: 1.2169 - val_accuracy: 0.5463 - val_loss: 1.3340
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 64ms/step - accuracy: 0.5655 - loss: 1.3264
Modèle avec 3 couches: Loss = 1.3340, Accuracy = 0.5463

Entraînement du modèle avec 4 couches convolutives
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 143ms/step - accuracy: 0.1601 - loss: 2.2876 - val_accuracy: 0.0694 - val_loss: 2.2665
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.2086 - loss: 2.1968 - val_accuracy: 0.2083 - val_loss: 2.2664
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 137ms/step - accuracy: 0.1406 - loss: 2.1899 - val_accuracy: 0.0694 - val_loss: 2.2549
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 137ms/step - accuracy: 0.2144 - loss: 2.1575 - val_accuracy: 0.0556 - val_loss: 2.4432
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 139ms/step - accuracy: 0.2293 - loss: 2.2342 - val_accuracy: 0.1389 - val_loss: 2.2664
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 134ms/step - accuracy: 0.1312 - loss: 2.2109 - val_accuracy: 0.0764 - val_loss: 2.2572
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 138ms/step - accuracy: 0.2474 - loss: 2.1652 - val_accuracy: 0.0764 - val_loss: 2.2522
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 133ms/step - accuracy: 0.2079 - loss: 2.1788 - val_accuracy: 0.0694 - val_loss: 2.2970
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 140ms/step - accuracy: 0.2385 - loss: 2.1730 - val_accuracy: 0.0856 - val_loss: 2.2076
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 152ms/step - accuracy: 0.2596 - loss: 2.1278 - val_accuracy: 0.1250 - val_loss: 2.1706
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 149ms/step - accuracy: 0.2300 - loss: 2.0461 - val_accuracy: 0.0903 - val_loss: 2.2771
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 142ms/step - accuracy: 0.3231 - loss: 1.9933 - val_accuracy: 0.2384 - val_loss: 2.0646
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 143ms/step - accuracy: 0.3619 - loss: 1.9711 - val_accuracy: 0.1389 - val_loss: 2.1544
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 141ms/step - accuracy: 0.2874 - loss: 2.0588 - val_accuracy: 0.2037 - val_loss: 2.0917
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 145ms/step - accuracy: 0.4207 - loss: 1.7468 - val_accuracy: 0.2431 - val_loss: 2.0432
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 69ms/step - accuracy: 0.2556 - loss: 2.1511
Modèle avec 4 couches: Loss = 2.0432, Accuracy = 0.2431

Entraînement du modèle avec 5 couches convolutives
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 18s 172ms/step - accuracy: 0.1215 - loss: 2.3137 - val_accuracy: 0.0694 - val_loss: 2.2748
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 166ms/step - accuracy: 0.0709 - loss: 2.1397 - val_accuracy: 0.0694 - val_loss: 2.2639
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 165ms/step - accuracy: 0.3502 - loss: 2.1968 - val_accuracy: 0.0694 - val_loss: 2.2392
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 157ms/step - accuracy: 0.2529 - loss: 2.1269 - val_accuracy: 0.0694 - val_loss: 2.2407
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 159ms/step - accuracy: 0.2320 - loss: 2.1797 - val_accuracy: 0.1389 - val_loss: 2.2187
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 17s 169ms/step - accuracy: 0.2938 - loss: 2.0810 - val_accuracy: 0.0694 - val_loss: 2.2392
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 17s 172ms/step - accuracy: 0.1742 - loss: 2.1437 - val_accuracy: 0.1343 - val_loss: 2.2083
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 17s 177ms/step - accuracy: 0.2891 - loss: 2.1009 - val_accuracy: 0.1481 - val_loss: 2.1829
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 141ms/step - accuracy: 0.2664 - loss: 2.0813 - val_accuracy: 0.1343 - val_loss: 2.1788
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 143ms/step - accuracy: 0.2733 - loss: 2.0630 - val_accuracy: 0.1620 - val_loss: 2.1810
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 140ms/step - accuracy: 0.3456 - loss: 1.9401 - val_accuracy: 0.0833 - val_loss: 2.2288
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 142ms/step - accuracy: 0.3284 - loss: 1.9999 - val_accuracy: 0.2245 - val_loss: 2.0804
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 138ms/step - accuracy: 0.3322 - loss: 1.9792 - val_accuracy: 0.2685 - val_loss: 2.0731
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 129ms/step - accuracy: 0.3343 - loss: 1.8784 - val_accuracy: 0.3032 - val_loss: 1.9331
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 122ms/step - accuracy: 0.3578 - loss: 1.8037 - val_accuracy: 0.2037 - val_loss: 2.0253
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 41ms/step - accuracy: 0.2281 - loss: 2.1056
Modèle avec 5 couches: Loss = 2.0253, Accuracy = 0.2037

Résultats pour le modèle à n couches convolutives¶

In [16]:
# Afficher un graphique comparatif
plt.figure(figsize=(10, 6))
layers = list(layer_results.keys())
accuracies = [results['val_accuracy'] for results in layer_results.values()]

plt.bar(layers, accuracies)
plt.xlabel('Nombre de couches convolutives')
plt.ylabel('Accuracy de validation')
plt.title('Impact du nombre de couches convolutives sur la performance')
plt.xticks(layers)
plt.ylim(0, 1)
for i, v in enumerate(accuracies):
    plt.text(i + 1, v + 0.02, f"{v:.4f}", ha='center')
plt.tight_layout()
plt.show()

# Visualiser les courbes d'apprentissage du meilleur modèle
best_layer_count = max(layer_results, key=lambda k: layer_results[k]['val_accuracy'])
print(f"Le meilleur modèle a {best_layer_count} couches convolutives")
plot_learning_curves(layer_results[best_layer_count]['history'], 
                    f"Courbes d'apprentissage - Modèle avec {best_layer_count} couches")

# Visualiser les courbes d'apprentissage du modèle de base
# plot_learning_curves(base_history, "Courbes d'apprentissage - Modèle de base")

# Évaluation du meilleur modèle
best_cnn_model = layer_results[best_layer_count]['model']
evaluate_model(best_cnn_model, val_generator, f"Modèle avec {best_layer_count} couches")
best_cnn_model.summary()
best_cnn_model.save("best_cnn_model.h5")
No description has been provided for this image
Le meilleur modèle a 2 couches convolutives
No description has been provided for this image
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 38ms/step
No description has been provided for this image
Rapport de classification - Modèle avec 2 couches
              precision    recall  f1-score   support

       chair       0.39      0.58      0.47        24
        door       0.66      0.76      0.71        51
       fence       0.67      0.33      0.44        12
 garbage_bin       0.71      0.71      0.71        17
    obstacle       0.75      0.53      0.62        91
       plant       0.84      0.67      0.74        24
     pothole       0.63      0.87      0.73        30
      stairs       0.69      0.70      0.69        60
       table       0.82      0.42      0.56        33
     vehicle       0.67      0.83      0.74        90

    accuracy                           0.67       432
   macro avg       0.68      0.64      0.64       432
weighted avg       0.69      0.67      0.67       432

Model: "sequential_3"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_5 (Conv2D)               │ (None, 62, 62, 32)     │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_5 (MaxPooling2D)  │ (None, 31, 31, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_6 (Conv2D)               │ (None, 31, 31, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_6 (MaxPooling2D)  │ (None, 15, 15, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_3 (Flatten)             │ (None, 14400)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_6 (Dense)                 │ (None, 128)            │     1,843,328 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_7 (Dense)                 │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 5,592,032 (21.33 MB)
 Trainable params: 1,864,010 (7.11 MB)
 Non-trainable params: 0 (0.00 B)
 Optimizer params: 3,728,022 (14.22 MB)
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

Sur la graph de comparaison des valeurs d'accuracy pour les différents modèles, on remarque que l'ajout de trois couches convolutives améliore légèrement les performances (accuracy) du modèle. Contre-intuitivement, l’ajout d’une quatrième couche entraîne une diminution drastique des performances, celles-ci étant divisées par cinq.

En analysant les courbes de performance (accuracy et loss) du modèle à trois couches convolutives, nous observons des résultats nettement supérieurs par rapport à ceux du modèle initial : l’évolution des courbes de validation (accuracy et loss) suit bien celle des courbes d’entraînement. Nous pouvons donc espérer, avec davantage d'optimisations et d'apprentissage (plus d'époques), converger vers des valeurs de performance à la validation proches de celles obtenues à l'entraînement.

Enfin, la matrice de confusion présente une diagonale légèrement mieux définie. Toutefois, certaines classes, qui n’étaient pas faussement prédites auparavant, le deviennent désormais, comme c’est le cas pour « vehicle » et « stairs ». Cet écart pourrait être dû au fait que les deux modèles n’aient pas été entraînés sur le même nombre d’époques (20 contre 15). Malgré cela, nous retenons cette optimisation et expérimentons avec davantage d’hyperparamètres (taille des noyaux prochainement).

À terme, nous réentraînerons le modèle optimisé et le modèle de base sur un même nombre d’époques afin de les comparer de manière équitable.

Expérimentation - Taille des kernels¶

À partir des résultats de l’expérimentation précédente, nous avons sélectionné le modèle optimal doté de 3 couches convolutives. Nous allons maintenant déterminer la taille de filtres la plus adaptée.

Cette étape vise à équilibrer la capacité d’extraction des caractéristiques et la complexité du modèle, en évaluant l’impact de différentes tailles de filtres sur la performance globale.

Définition du modèle pour différentes tailles des filtres¶

In [ ]:
def create_model_with_kernel_size(kernel_size):
    model = Sequential([
        Conv2D(32, kernel_size, activation='relu', input_shape=(64, 64, 3)),
        MaxPooling2D(pool_size=(2, 2)),
        Conv2D(64, kernel_size, padding='same', activation='relu'),
        MaxPooling2D(pool_size=(2, 2)),
        Flatten(),
        Dense(128, activation='relu'),
        Dense(10, activation='softmax')
    ])
    
    # Compiler le modèle
    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    
    return model
In [ ]:
# Tester différentes tailles de kernel
kernel_results = {}
for kernel_size in [(3, 3), (5, 5), (7, 7)]:
    print(f"\nEntraînement du modèle avec kernel de taille {kernel_size}")
    
    # Créer et entraîner le modèle
    model = create_model_with_kernel_size(kernel_size)
    history = model.fit(
        train_generator,
        validation_data=val_generator,
        epochs=15,
        verbose=1
    )
    
    # Évaluer le modèle
    val_loss, val_acc = model.evaluate(val_generator)
    
    # Stocker les résultats
    kernel_results[str(kernel_size)] = {
        'model': model,
        'history': history,
        'val_accuracy': val_acc,
        'val_loss': val_loss
    }
    
    print(f"Modèle avec kernel {kernel_size}: Loss = {val_loss:.4f}, Accuracy = {val_acc:.4f}")
Entraînement du modèle avec kernel de taille (3, 3)
/Users/alaaeddineahriz/.pyenv/versions/3.10.16/lib/python3.10/site-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 92ms/step - accuracy: 0.1295 - loss: 2.8854 - val_accuracy: 0.0880 - val_loss: 2.2491
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 8s 85ms/step - accuracy: 0.1485 - loss: 2.2266 - val_accuracy: 0.2083 - val_loss: 2.2367
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 8s 85ms/step - accuracy: 0.1354 - loss: 2.2722 - val_accuracy: 0.2222 - val_loss: 2.1132
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 90ms/step - accuracy: 0.2670 - loss: 2.0985 - val_accuracy: 0.1829 - val_loss: 2.1108
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 87ms/step - accuracy: 0.3333 - loss: 1.9164 - val_accuracy: 0.2407 - val_loss: 2.0075
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 9s 88ms/step - accuracy: 0.3337 - loss: 1.8296 - val_accuracy: 0.2269 - val_loss: 1.9670
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 8s 85ms/step - accuracy: 0.3541 - loss: 1.7418 - val_accuracy: 0.3287 - val_loss: 1.8034
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 102ms/step - accuracy: 0.4850 - loss: 1.4601 - val_accuracy: 0.3704 - val_loss: 1.7494
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 112ms/step - accuracy: 0.5613 - loss: 1.3494 - val_accuracy: 0.4977 - val_loss: 1.4983
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 109ms/step - accuracy: 0.5947 - loss: 1.1855 - val_accuracy: 0.4884 - val_loss: 1.5114
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 106ms/step - accuracy: 0.6775 - loss: 0.9581 - val_accuracy: 0.5718 - val_loss: 1.3642
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 106ms/step - accuracy: 0.7056 - loss: 0.8845 - val_accuracy: 0.5718 - val_loss: 1.3076
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 105ms/step - accuracy: 0.7255 - loss: 0.8172 - val_accuracy: 0.6042 - val_loss: 1.2654
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 106ms/step - accuracy: 0.8054 - loss: 0.5902 - val_accuracy: 0.6343 - val_loss: 1.1929
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 10s 104ms/step - accuracy: 0.8340 - loss: 0.5108 - val_accuracy: 0.6505 - val_loss: 1.1308
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 57ms/step - accuracy: 0.5810 - loss: 1.3219
Modèle avec kernel (3, 3): Loss = 1.1308, Accuracy = 0.6505

Entraînement du modèle avec kernel de taille (5, 5)
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 153ms/step - accuracy: 0.1083 - loss: 2.3685 - val_accuracy: 0.0694 - val_loss: 2.2727
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 160ms/step - accuracy: 0.1390 - loss: 2.2749 - val_accuracy: 0.0694 - val_loss: 2.3044
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 149ms/step - accuracy: 0.2600 - loss: 2.1548 - val_accuracy: 0.2083 - val_loss: 2.2328
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 148ms/step - accuracy: 0.2113 - loss: 2.2087 - val_accuracy: 0.0741 - val_loss: 2.2534
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 149ms/step - accuracy: 0.2418 - loss: 2.2458 - val_accuracy: 0.0718 - val_loss: 2.3265
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 143ms/step - accuracy: 0.2413 - loss: 2.2437 - val_accuracy: 0.0972 - val_loss: 2.2187
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 154ms/step - accuracy: 0.2340 - loss: 2.0878 - val_accuracy: 0.1019 - val_loss: 2.3139
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 159ms/step - accuracy: 0.2432 - loss: 2.1708 - val_accuracy: 0.1829 - val_loss: 2.1323
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 161ms/step - accuracy: 0.2577 - loss: 2.1066 - val_accuracy: 0.2477 - val_loss: 2.1221
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 166ms/step - accuracy: 0.3718 - loss: 1.7821 - val_accuracy: 0.2963 - val_loss: 1.9548
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 155ms/step - accuracy: 0.4369 - loss: 1.6948 - val_accuracy: 0.4144 - val_loss: 1.7439
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 158ms/step - accuracy: 0.4460 - loss: 1.5325 - val_accuracy: 0.3657 - val_loss: 1.8167
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 160ms/step - accuracy: 0.4957 - loss: 1.4877 - val_accuracy: 0.5231 - val_loss: 1.5338
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 158ms/step - accuracy: 0.5784 - loss: 1.2266 - val_accuracy: 0.5023 - val_loss: 1.5285
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 157ms/step - accuracy: 0.6089 - loss: 1.1273 - val_accuracy: 0.5347 - val_loss: 1.3703
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 67ms/step - accuracy: 0.5467 - loss: 1.3546
Modèle avec kernel (5, 5): Loss = 1.3703, Accuracy = 0.5347

Entraînement du modèle avec kernel de taille (7, 7)
Epoch 1/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 26s 251ms/step - accuracy: 0.0911 - loss: 2.4139 - val_accuracy: 0.2130 - val_loss: 2.2822
Epoch 2/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 24s 241ms/step - accuracy: 0.1045 - loss: 2.3952 - val_accuracy: 0.2500 - val_loss: 2.1901
Epoch 3/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 28s 282ms/step - accuracy: 0.1692 - loss: 2.2498 - val_accuracy: 0.0972 - val_loss: 2.2333
Epoch 4/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 27s 271ms/step - accuracy: 0.3157 - loss: 2.1060 - val_accuracy: 0.1620 - val_loss: 2.1730
Epoch 5/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 26s 261ms/step - accuracy: 0.2648 - loss: 2.1396 - val_accuracy: 0.1505 - val_loss: 2.1963
Epoch 6/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 25s 256ms/step - accuracy: 0.1769 - loss: 2.1377 - val_accuracy: 0.1551 - val_loss: 2.1755
Epoch 7/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 24s 243ms/step - accuracy: 0.2669 - loss: 2.1124 - val_accuracy: 0.1481 - val_loss: 2.1465
Epoch 8/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 23s 237ms/step - accuracy: 0.2961 - loss: 2.0015 - val_accuracy: 0.1759 - val_loss: 2.1604
Epoch 9/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 23s 236ms/step - accuracy: 0.3378 - loss: 1.9252 - val_accuracy: 0.2384 - val_loss: 2.0825
Epoch 10/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 23s 235ms/step - accuracy: 0.3096 - loss: 1.9549 - val_accuracy: 0.2384 - val_loss: 2.0561
Epoch 11/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 21s 216ms/step - accuracy: 0.3251 - loss: 1.8843 - val_accuracy: 0.2546 - val_loss: 1.9610
Epoch 12/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 23s 232ms/step - accuracy: 0.3663 - loss: 1.7756 - val_accuracy: 0.2083 - val_loss: 2.0938
Epoch 13/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 23s 236ms/step - accuracy: 0.4344 - loss: 1.6228 - val_accuracy: 0.3426 - val_loss: 1.9144
Epoch 14/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 23s 230ms/step - accuracy: 0.3962 - loss: 1.6566 - val_accuracy: 0.2986 - val_loss: 1.9383
Epoch 15/15
98/98 ━━━━━━━━━━━━━━━━━━━━ 22s 228ms/step - accuracy: 0.5060 - loss: 1.4001 - val_accuracy: 0.3843 - val_loss: 1.8554
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 72ms/step - accuracy: 0.2855 - loss: 2.1278
Modèle avec kernel (7, 7): Loss = 1.8554, Accuracy = 0.3843

Résultats pour différentes tailles de noyaux¶

In [19]:
# Afficher un graphique comparatif
plt.figure(figsize=(10, 6))
kernels = list(kernel_results.keys())
accuracies = [results['val_accuracy'] for results in kernel_results.values()]

plt.bar(kernels, accuracies)
plt.xlabel('Taille des kernels')
plt.ylabel('Accuracy de validation')
plt.title('Impact de la taille des kernels sur la performance')
plt.ylim(0, 1)
for i, v in enumerate(accuracies):
    plt.text(i, v + 0.02, f"{v:.4f}", ha='center')
plt.tight_layout()
plt.show()

# Visualiser les courbes d'apprentissage du meilleur modèle
best_kernel_size = max(kernel_results, key=lambda k: kernel_results[k]['val_accuracy'])
print(f"Le meilleur modèle a des kernels de taille {best_kernel_size}")

plot_learning_curves(kernel_results[best_kernel_size]['history'], 
                    f"Courbes d'apprentissage - Modèle avec kernels {best_kernel_size}")

# Évaluation du meilleur modèle
best_kernel_model = kernel_results[best_kernel_size]['model']
evaluate_model(best_kernel_model, val_generator, f"Modèle avec {best_kernel_size} couches")

# Résumé du modèle
best_kernel_model.summary()
best_kernel_model.save("best_kernel_model.h5")
No description has been provided for this image
Le meilleur modèle a des kernels de taille (3, 3)
No description has been provided for this image
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 54ms/step
No description has been provided for this image
Rapport de classification - Modèle avec (3, 3) couches
              precision    recall  f1-score   support

       chair       0.47      0.33      0.39        24
        door       0.64      0.67      0.65        51
       fence       0.56      0.42      0.48        12
 garbage_bin       0.48      0.82      0.61        17
    obstacle       0.78      0.49      0.60        91
       plant       0.74      0.71      0.72        24
     pothole       0.59      0.77      0.67        30
      stairs       0.65      0.75      0.70        60
       table       0.64      0.70      0.67        33
     vehicle       0.68      0.74      0.71        90

    accuracy                           0.65       432
   macro avg       0.62      0.64      0.62       432
weighted avg       0.66      0.65      0.65       432

Model: "sequential_7"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_19 (Conv2D)              │ (None, 62, 62, 32)     │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_19 (MaxPooling2D) │ (None, 31, 31, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_20 (Conv2D)              │ (None, 31, 31, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_20 (MaxPooling2D) │ (None, 15, 15, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_7 (Flatten)             │ (None, 14400)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_14 (Dense)                │ (None, 128)            │     1,843,328 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_15 (Dense)                │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 5,592,032 (21.33 MB)
 Trainable params: 1,864,010 (7.11 MB)
 Non-trainable params: 0 (0.00 B)
 Optimizer params: 3,728,022 (14.22 MB)
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

Le noyau de taille (3,3) offre des performances nettement supérieures à celles des noyaux de tailles (5,5) et (7,7), atteignant jusqu’à deux fois plus de précision. Les courbes de performance obtenues sont sensiblement similaires à celles issues de l’optimisation du nombre de couches convolutives, ce qui est cohérent puisque les hyper-paramètres (taille du noyau et nombre de couches) restent identiques.

La diagonale de la matrice de confusion est cependant légèrement moins bien définie. On observe une inversion inhabituelle des mauvaises prédictions entre les classes « stairs » et « vehicle ». Alors qu’auparavant le modèle confondait les escaliers avec les véhicules, il tend maintenant à classer les véhicules comme des escaliers. Étrange !

Expérimentation - Learning Rate¶

Nous étudions ici l’impact du taux d’apprentissage sur les performances du modèle en conservant la meilleure architecture identifiée jusqu’à présent. Le même modèle est entraîné et comparé pour différents taux d’apprentissage, en maintenant ce paramètre fixe durant l’ensemble de l’apprentissage. Il serait pertinent d’examiner ultérieurement les résultats obtenus avec un taux d’apprentissage dynamique.

Définition du modèle pour différents learning rates statiques¶

In [ ]:
def create_model_lr(learning_rate):
    model = Sequential([
        Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(64, 64, 3)),
        MaxPooling2D(pool_size=(2, 2)),
        Conv2D(64, (3, 3), padding='same', activation='relu'),
        MaxPooling2D(pool_size=(2, 2)),
        Flatten(),
        Dense(128, activation='relu'),
        Dense(10, activation='softmax')
    ])
    
    # Compiler avec le learning rate spécifié
    optimizer = Adam(learning_rate=learning_rate)
    model.compile(
        optimizer=optimizer,
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    
    return model

# Définir une fonction pour obtenir un early stopping callback
def get_early_stopping():
    return EarlyStopping(
        monitor='val_loss',
        patience=5,
        restore_best_weights=True,
        verbose=1
    )
In [ ]:
# Tester différents learning rates (approche statique)
def test_learning_rates():
    learning_rates = [0.0001, 0.0005, 0.001, 0.005, 0.01]
    results = {}
    
    for lr in learning_rates:
        print(f"\n===== Test avec learning rate = {lr} =====")
        
        model = create_model_lr(learning_rate=lr)
        
        # Early stopping pour éviter l'overfitting
        early_stopping = get_early_stopping()
        
        # Entraîner le modèle
        history = model.fit(
            train_generator,
            validation_data=val_generator,
            epochs=20,  # Nombre d'époques maximal
            callbacks=[early_stopping],
            verbose=1
        )
        
        # Évaluer le modèle
        val_loss, val_acc = model.evaluate(val_generator)
        
        # Stocker les résultats
        results[lr] = {
            'model': model,
            'history': history,
            'val_accuracy': val_acc,
            'val_loss': val_loss,
            'epochs_trained': len(history.history['loss'])
        }
        
        print(f"Learning rate {lr}: Loss = {val_loss:.4f}, Accuracy = {val_acc:.4f}, Epochs: {len(history.history['loss'])}")
    
    return results
In [22]:
# Exécuter les tests
lr_results = test_learning_rates()

# Loop through each learning rate result and save the corresponding model
for lr, res in lr_results.items():
    filename = f"model_lr_{lr}.h5"
    res['model'].save(filename)
    print(f"Model with learning rate {lr} saved as {filename}.")
===== Test avec learning rate = 0.0001 =====
/Users/alaaeddineahriz/.pyenv/versions/3.10.16/lib/python3.10/site-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Epoch 1/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 17s 151ms/step - accuracy: 0.1704 - loss: 3.3230 - val_accuracy: 0.1620 - val_loss: 2.7824
Epoch 2/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 150ms/step - accuracy: 0.4374 - loss: 1.6767 - val_accuracy: 0.2037 - val_loss: 3.4913
Epoch 3/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 143ms/step - accuracy: 0.6269 - loss: 1.1975 - val_accuracy: 0.2523 - val_loss: 3.5326
Epoch 4/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 141ms/step - accuracy: 0.6938 - loss: 0.9259 - val_accuracy: 0.3356 - val_loss: 2.4516
Epoch 5/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 136ms/step - accuracy: 0.8343 - loss: 0.5627 - val_accuracy: 0.4537 - val_loss: 1.9238
Epoch 6/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.8662 - loss: 0.4460 - val_accuracy: 0.5208 - val_loss: 1.5259
Epoch 7/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.9640 - loss: 0.1966 - val_accuracy: 0.5000 - val_loss: 1.5864
Epoch 8/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 133ms/step - accuracy: 0.9703 - loss: 0.1535 - val_accuracy: 0.5787 - val_loss: 1.4588
Epoch 9/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.9974 - loss: 0.0644 - val_accuracy: 0.5602 - val_loss: 1.5156
Epoch 10/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 11s 107ms/step - accuracy: 1.0000 - loss: 0.0419 - val_accuracy: 0.5718 - val_loss: 1.4630
Epoch 11/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 127ms/step - accuracy: 1.0000 - loss: 0.0237 - val_accuracy: 0.6065 - val_loss: 1.4172
Epoch 12/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 130ms/step - accuracy: 1.0000 - loss: 0.0197 - val_accuracy: 0.6227 - val_loss: 1.4499
Epoch 13/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 126ms/step - accuracy: 1.0000 - loss: 0.0126 - val_accuracy: 0.6157 - val_loss: 1.4965
Epoch 14/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 121ms/step - accuracy: 1.0000 - loss: 0.0107 - val_accuracy: 0.6088 - val_loss: 1.5076
Epoch 15/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 127ms/step - accuracy: 1.0000 - loss: 0.0092 - val_accuracy: 0.6065 - val_loss: 1.4499
Epoch 16/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 128ms/step - accuracy: 1.0000 - loss: 0.0077 - val_accuracy: 0.6157 - val_loss: 1.5004
Epoch 16: early stopping
Restoring model weights from the end of the best epoch: 11.
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 53ms/step - accuracy: 0.5733 - loss: 1.4518
Learning rate 0.0001: Loss = 1.4172, Accuracy = 0.6065, Epochs: 16

===== Test avec learning rate = 0.0005 =====
Epoch 1/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 128ms/step - accuracy: 0.1134 - loss: 6.4255 - val_accuracy: 0.1181 - val_loss: 9.4316
Epoch 2/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 124ms/step - accuracy: 0.3027 - loss: 2.0765 - val_accuracy: 0.1273 - val_loss: 11.4659
Epoch 3/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 125ms/step - accuracy: 0.3884 - loss: 1.8369 - val_accuracy: 0.1620 - val_loss: 11.6740
Epoch 4/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 127ms/step - accuracy: 0.4696 - loss: 1.5878 - val_accuracy: 0.2153 - val_loss: 5.6966
Epoch 5/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 130ms/step - accuracy: 0.5516 - loss: 1.3455 - val_accuracy: 0.3148 - val_loss: 2.6742
Epoch 6/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 129ms/step - accuracy: 0.6494 - loss: 0.9692 - val_accuracy: 0.4167 - val_loss: 1.8378
Epoch 7/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 120ms/step - accuracy: 0.7389 - loss: 0.7430 - val_accuracy: 0.4491 - val_loss: 1.9165
Epoch 8/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 131ms/step - accuracy: 0.8398 - loss: 0.4609 - val_accuracy: 0.5069 - val_loss: 1.7431
Epoch 9/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.9139 - loss: 0.2490 - val_accuracy: 0.5417 - val_loss: 1.8147
Epoch 10/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 139ms/step - accuracy: 0.9693 - loss: 0.1229 - val_accuracy: 0.5787 - val_loss: 1.6374
Epoch 11/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 20s 134ms/step - accuracy: 0.9888 - loss: 0.0567 - val_accuracy: 0.5556 - val_loss: 2.0349
Epoch 12/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 139ms/step - accuracy: 0.9922 - loss: 0.0379 - val_accuracy: 0.5741 - val_loss: 1.7004
Epoch 13/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 142ms/step - accuracy: 0.9954 - loss: 0.0199 - val_accuracy: 0.5579 - val_loss: 1.8122
Epoch 14/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 136ms/step - accuracy: 0.9970 - loss: 0.0151 - val_accuracy: 0.5532 - val_loss: 1.8933
Epoch 15/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 137ms/step - accuracy: 0.9998 - loss: 0.0072 - val_accuracy: 0.5509 - val_loss: 1.9329
Epoch 15: early stopping
Restoring model weights from the end of the best epoch: 10.
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 67ms/step - accuracy: 0.5806 - loss: 1.5877
Learning rate 0.0005: Loss = 1.6374, Accuracy = 0.5787, Epochs: 15

===== Test avec learning rate = 0.001 =====
Epoch 1/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 138ms/step - accuracy: 0.1156 - loss: 10.9948 - val_accuracy: 0.2014 - val_loss: 6.5068
Epoch 2/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 134ms/step - accuracy: 0.2777 - loss: 2.0430 - val_accuracy: 0.1389 - val_loss: 16.5722
Epoch 3/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.3464 - loss: 1.8431 - val_accuracy: 0.1829 - val_loss: 15.6984
Epoch 4/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 134ms/step - accuracy: 0.4587 - loss: 1.5847 - val_accuracy: 0.2407 - val_loss: 7.0959
Epoch 5/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 134ms/step - accuracy: 0.5158 - loss: 1.4822 - val_accuracy: 0.2407 - val_loss: 3.6278
Epoch 6/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 140ms/step - accuracy: 0.5900 - loss: 1.2306 - val_accuracy: 0.3032 - val_loss: 2.4170
Epoch 7/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 134ms/step - accuracy: 0.5912 - loss: 1.1531 - val_accuracy: 0.1875 - val_loss: 3.6372
Epoch 8/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 12s 126ms/step - accuracy: 0.6593 - loss: 0.9371 - val_accuracy: 0.3356 - val_loss: 2.4382
Epoch 9/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 131ms/step - accuracy: 0.7741 - loss: 0.6766 - val_accuracy: 0.4444 - val_loss: 1.8359
Epoch 10/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 137ms/step - accuracy: 0.8294 - loss: 0.4678 - val_accuracy: 0.5069 - val_loss: 1.8505
Epoch 11/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 139ms/step - accuracy: 0.8620 - loss: 0.3643 - val_accuracy: 0.4560 - val_loss: 2.0720
Epoch 12/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 138ms/step - accuracy: 0.9369 - loss: 0.1982 - val_accuracy: 0.4838 - val_loss: 2.7952
Epoch 13/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.9562 - loss: 0.1297 - val_accuracy: 0.4861 - val_loss: 2.6397
Epoch 14/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 132ms/step - accuracy: 0.9779 - loss: 0.0712 - val_accuracy: 0.5532 - val_loss: 2.3878
Epoch 14: early stopping
Restoring model weights from the end of the best epoch: 9.
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 63ms/step - accuracy: 0.4451 - loss: 1.7743
Learning rate 0.001: Loss = 1.8359, Accuracy = 0.4444, Epochs: 14

===== Test avec learning rate = 0.005 =====
Epoch 1/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 147ms/step - accuracy: 0.1460 - loss: 43.6402 - val_accuracy: 0.1227 - val_loss: 3.5458
Epoch 2/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 131ms/step - accuracy: 0.1712 - loss: 2.2769 - val_accuracy: 0.1273 - val_loss: 7.6646
Epoch 3/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.2649 - loss: 2.2302 - val_accuracy: 0.1088 - val_loss: 4.7536
Epoch 4/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 136ms/step - accuracy: 0.2449 - loss: 2.1543 - val_accuracy: 0.0810 - val_loss: 2.6122
Epoch 5/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 140ms/step - accuracy: 0.1708 - loss: 2.1814 - val_accuracy: 0.0741 - val_loss: 2.2628
Epoch 6/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 137ms/step - accuracy: 0.1695 - loss: 2.1722 - val_accuracy: 0.0694 - val_loss: 2.2657
Epoch 7/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.2178 - loss: 2.1839 - val_accuracy: 0.0694 - val_loss: 2.2693
Epoch 8/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 136ms/step - accuracy: 0.2239 - loss: 2.1460 - val_accuracy: 0.0694 - val_loss: 2.2697
Epoch 9/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.2413 - loss: 2.1332 - val_accuracy: 0.0718 - val_loss: 2.2725
Epoch 10/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 133ms/step - accuracy: 0.1970 - loss: 2.1340 - val_accuracy: 0.0694 - val_loss: 2.2741
Epoch 10: early stopping
Restoring model weights from the end of the best epoch: 5.
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 62ms/step - accuracy: 0.0538 - loss: 2.3350
Learning rate 0.005: Loss = 2.2628, Accuracy = 0.0741, Epochs: 10

===== Test avec learning rate = 0.01 =====
Epoch 1/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 135ms/step - accuracy: 0.1496 - loss: 78.3532 - val_accuracy: 0.0694 - val_loss: 2.2682
Epoch 2/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 140ms/step - accuracy: 0.1330 - loss: 2.2308 - val_accuracy: 0.0856 - val_loss: 2.2430
Epoch 3/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 140ms/step - accuracy: 0.2035 - loss: 2.1699 - val_accuracy: 0.0741 - val_loss: 2.2482
Epoch 4/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 142ms/step - accuracy: 0.2424 - loss: 2.1507 - val_accuracy: 0.0718 - val_loss: 2.2562
Epoch 5/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 137ms/step - accuracy: 0.2883 - loss: 2.1112 - val_accuracy: 0.0694 - val_loss: 2.2664
Epoch 6/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 135ms/step - accuracy: 0.2442 - loss: 2.1126 - val_accuracy: 0.0694 - val_loss: 2.2749
Epoch 7/20
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 131ms/step - accuracy: 0.2026 - loss: 2.1062 - val_accuracy: 0.0718 - val_loss: 2.2774
Epoch 7: early stopping
Restoring model weights from the end of the best epoch: 2.
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 66ms/step - accuracy: 0.0594 - loss: 2.3115
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 
Learning rate 0.01: Loss = 2.2430, Accuracy = 0.0856, Epochs: 7
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 
Model with learning rate 0.0001 saved as model_lr_0.0001.h5.
Model with learning rate 0.0005 saved as model_lr_0.0005.h5.
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 
Model with learning rate 0.001 saved as model_lr_0.001.h5.
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 
Model with learning rate 0.005 saved as model_lr_0.005.h5.
Model with learning rate 0.01 saved as model_lr_0.01.h5.

Résultats pour les différents learning rates statiques¶

In [23]:
# Afficher un graphique comparatif
plt.figure(figsize=(10, 6))
lrs = list(lr_results.keys())
accuracies = [results['val_accuracy'] for results in lr_results.values()]

plt.bar([str(lr) for lr in lrs], accuracies)
plt.xlabel('Learning Rate')
plt.ylabel('Accuracy de validation')
plt.title('Impact du learning rate sur la performance')
plt.ylim(0, 1)
for i, v in enumerate(accuracies):
    plt.text(i, v + 0.02, f"{v:.4f}", ha='center')
plt.tight_layout()
plt.show()

# Visualiser l'évolution de l'accuracy et de la loss de validatoin en fonction du learning rate
plot_learning_rate_comparison(lr_results)

# Visualiser les courbes d'apprentissage du meilleur modèle
best_lr = max(lr_results, key=lambda k: lr_results[k]['val_accuracy'])
print(f"Le meilleur modèle a un learning rate de {best_lr}")
plot_learning_curves(lr_results[best_lr]['history'], 
                    f"Courbes d'apprentissage - Modèle avec learning rate {best_lr}")

# Évaluation du meilleur modèle
best_lr_model = lr_results[best_lr]['model']
evaluate_model(best_lr_model, val_generator, f"Modèle avec {best_lr} couches")

# Résumé et sauvegarde du modèle
best_lr_model.summary()
best_lr_model.save("best_lr_model.h5")
No description has been provided for this image
No description has been provided for this image
Meilleur learning rate: 0.0001
Meilleure accuracy: 0.6065
Meilleure loss: 1.4172
Le meilleur modèle a un learning rate de 0.0001
No description has been provided for this image
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 63ms/step
No description has been provided for this image
Rapport de classification - Modèle avec 0.0001 couches
              precision    recall  f1-score   support

       chair       0.28      0.54      0.37        24
        door       0.74      0.55      0.63        51
       fence       0.50      0.42      0.45        12
 garbage_bin       0.58      0.65      0.61        17
    obstacle       0.77      0.55      0.64        91
       plant       0.76      0.79      0.78        24
     pothole       0.53      0.57      0.55        30
      stairs       0.49      0.65      0.56        60
       table       0.70      0.58      0.63        33
     vehicle       0.67      0.68      0.67        90

    accuracy                           0.61       432
   macro avg       0.60      0.60      0.59       432
weighted avg       0.64      0.61      0.61       432

Model: "sequential_10"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_25 (Conv2D)              │ (None, 64, 64, 32)     │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization             │ (None, 64, 64, 32)     │           128 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_25 (MaxPooling2D) │ (None, 32, 32, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_26 (Conv2D)              │ (None, 32, 32, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_1           │ (None, 32, 32, 64)     │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_26 (MaxPooling2D) │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_10 (Flatten)            │ (None, 16384)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_20 (Dense)                │ (None, 128)            │     2,097,280 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_21 (Dense)                │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 6,354,656 (24.24 MB)
 Trainable params: 2,118,154 (8.08 MB)
 Non-trainable params: 192 (768.00 B)
 Optimizer params: 4,236,310 (16.16 MB)
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

Nous remarquons ici que le learning rate influence directement les performances du modèle. Pour des valeurs faibles, comprises entre $10^{-3}$ et $10^{-4}$, l’accuracy se stabilise autour de 58 %. En revanche, en augmentant le taux d’apprentissage à $5 \times 10^{-3}$, la précision chute drastiquement (divisée par plus de 5) et la loss s’envole. Ce constat indique qu’un learning rate trop élevé perturbe le processus d’optimisation, empêchant le modèle de converger correctement et menant à une divergence des poids.

Pour un learning rate de $5 \times 10^{-4}$, la matrice de confusion est nettement améliorée, avec une diagonale mieux définie et une reconnaissance globale plus satisfaisante des classes. Cependant, certaines classes restent problématiques (valeurs non négligeables écartés de la diagonale). Le rapport de classification révèle que la classe « chair » présente une faible précision (0,24) malgré un recall élevé (0,67), signe d’une confusion marquée avec d’autres classes. De même, la classe « stairs » affiche une précision de 0,45 et un recall de 0,63, tandis que la classe « obstacle » se distingue par une précision de 0,69 et un recall de 0,52. La classe « vehicle », bien que mieux reconnue avec une précision de 0,79 et un recall de 0,62, ne parvient pas à compenser les difficultés observées pour les autres catégories.

Ainsi, bien que le learning rate de $5 \times 10^{-4}$ améliore globalement la performance du modèle en termes de clarté de la matrice de confusion, il subsiste des axes d’amélioration pour optimiser la reconnaissance des classes les plus problématiques.

Enfin, étant donné que les performances obtenues pour les trois premiers learning rates testés sont suffisamment proches, nous optons pour le plus élevé ($10^{-3}$). Ce choix permettra de gagner en temps de calcul. Exceptionnellement, et merci Kaggle GPUs, nous relançons un entraînement sur un nombre d’époques supérieur en utilisant la meilleure architecture retenue jusqu’ici et le learning rate ayant fourni les meilleurs résultats.

Modèle final¶

In [24]:
# Entraîner un modèle final avec le meilleur learning rate et plus d'époques
best_lr = 0.0001
def train_final_model(best_lr):
    print(f"\n===== Entraînement du modèle final avec learning rate = {best_lr} =====")
    
    # Créer le modèle avec le meilleur learning rate
    final_model = create_model_lr(learning_rate=best_lr)
    
    # Early stopping
    # early_stopping = get_early_stopping()
    
    # Entraîner le modèle
    final_history = final_model.fit(
        train_generator,
        validation_data=val_generator,
        epochs=30,  # Plus d'époques pour le modèle final
        # callbacks=[early_stopping],
        verbose=1
    )
    
    # Évaluer le modèle
    val_loss, val_acc = final_model.evaluate(val_generator)
    print(f"Modèle final: Loss = {val_loss:.4f}, Accuracy = {val_acc:.4f}")
    
    return final_model, final_history
In [25]:
# Entraîner le modèle final avec le meilleur learning rate
final_model, final_history = train_final_model(best_lr)
===== Entraînement du modèle final avec learning rate = 0.0001 =====
/Users/alaaeddineahriz/.pyenv/versions/3.10.16/lib/python3.10/site-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Epoch 1/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 135ms/step - accuracy: 0.1943 - loss: 3.1343 - val_accuracy: 0.2755 - val_loss: 2.1893
Epoch 2/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 18s 182ms/step - accuracy: 0.4916 - loss: 1.4550 - val_accuracy: 0.2361 - val_loss: 2.6681
Epoch 3/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 159ms/step - accuracy: 0.6360 - loss: 1.1078 - val_accuracy: 0.2616 - val_loss: 2.5709
Epoch 4/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 160ms/step - accuracy: 0.7308 - loss: 0.8291 - val_accuracy: 0.3542 - val_loss: 2.0962
Epoch 5/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 158ms/step - accuracy: 0.8397 - loss: 0.5081 - val_accuracy: 0.4537 - val_loss: 1.7081
Epoch 6/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 159ms/step - accuracy: 0.9254 - loss: 0.3189 - val_accuracy: 0.4769 - val_loss: 1.5919
Epoch 7/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 18s 186ms/step - accuracy: 0.9655 - loss: 0.1984 - val_accuracy: 0.5301 - val_loss: 1.4882
Epoch 8/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 166ms/step - accuracy: 0.9915 - loss: 0.0968 - val_accuracy: 0.5301 - val_loss: 1.4596
Epoch 9/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 161ms/step - accuracy: 0.9991 - loss: 0.0496 - val_accuracy: 0.5463 - val_loss: 1.4659
Epoch 10/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 158ms/step - accuracy: 0.9982 - loss: 0.0367 - val_accuracy: 0.5556 - val_loss: 1.4896
Epoch 11/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 158ms/step - accuracy: 0.9995 - loss: 0.0245 - val_accuracy: 0.5671 - val_loss: 1.4452
Epoch 12/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 14s 139ms/step - accuracy: 1.0000 - loss: 0.0181 - val_accuracy: 0.5648 - val_loss: 1.5006
Epoch 13/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 153ms/step - accuracy: 1.0000 - loss: 0.0140 - val_accuracy: 0.5671 - val_loss: 1.4611
Epoch 14/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 164ms/step - accuracy: 1.0000 - loss: 0.0112 - val_accuracy: 0.5625 - val_loss: 1.5361
Epoch 15/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 167ms/step - accuracy: 1.0000 - loss: 0.0105 - val_accuracy: 0.5718 - val_loss: 1.5250
Epoch 16/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 17s 169ms/step - accuracy: 1.0000 - loss: 0.0084 - val_accuracy: 0.5694 - val_loss: 1.5453
Epoch 17/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 17s 173ms/step - accuracy: 1.0000 - loss: 0.0071 - val_accuracy: 0.5787 - val_loss: 1.5312
Epoch 18/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 161ms/step - accuracy: 1.0000 - loss: 0.0062 - val_accuracy: 0.5741 - val_loss: 1.5238
Epoch 19/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 160ms/step - accuracy: 1.0000 - loss: 0.0056 - val_accuracy: 0.5741 - val_loss: 1.5890
Epoch 20/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 155ms/step - accuracy: 1.0000 - loss: 0.0048 - val_accuracy: 0.5741 - val_loss: 1.5478
Epoch 21/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 156ms/step - accuracy: 1.0000 - loss: 0.0043 - val_accuracy: 0.5648 - val_loss: 1.5676
Epoch 22/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 153ms/step - accuracy: 1.0000 - loss: 0.0042 - val_accuracy: 0.5949 - val_loss: 1.5157
Epoch 23/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 149ms/step - accuracy: 1.0000 - loss: 0.0034 - val_accuracy: 0.5787 - val_loss: 1.5830
Epoch 24/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 13s 131ms/step - accuracy: 1.0000 - loss: 0.0033 - val_accuracy: 0.5694 - val_loss: 1.5954
Epoch 25/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 167ms/step - accuracy: 1.0000 - loss: 0.0029 - val_accuracy: 0.5787 - val_loss: 1.5812
Epoch 26/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 164ms/step - accuracy: 1.0000 - loss: 0.0026 - val_accuracy: 0.5741 - val_loss: 1.5884
Epoch 27/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 16s 161ms/step - accuracy: 1.0000 - loss: 0.0023 - val_accuracy: 0.5903 - val_loss: 1.5799
Epoch 28/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 153ms/step - accuracy: 1.0000 - loss: 0.0022 - val_accuracy: 0.5741 - val_loss: 1.6256
Epoch 29/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 156ms/step - accuracy: 1.0000 - loss: 0.0021 - val_accuracy: 0.5833 - val_loss: 1.6339
Epoch 30/30
98/98 ━━━━━━━━━━━━━━━━━━━━ 15s 156ms/step - accuracy: 1.0000 - loss: 0.0018 - val_accuracy: 0.5856 - val_loss: 1.5907
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 82ms/step - accuracy: 0.5715 - loss: 1.6253
Modèle final: Loss = 1.5907, Accuracy = 0.5856
In [26]:
# Visualiser les courbes d'apprentissage du meilleur modèle
plot_learning_curves(final_history, 
                    f"Courbes d'apprentissage du meilleur modèle")

# Évaluation du meilleur modèle
evaluate_model(final_model, val_generator, f"Évaluation du meilleur modèle")

# Résumé et sauvegarde du modèle
final_model.summary()
final_model.save("final_model.h5")
No description has been provided for this image
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 81ms/step
No description has been provided for this image
Rapport de classification - Évaluation du meilleur modèle
              precision    recall  f1-score   support

       chair       0.30      0.67      0.42        24
        door       0.76      0.55      0.64        51
       fence       0.50      0.58      0.54        12
 garbage_bin       0.86      0.71      0.77        17
    obstacle       0.74      0.46      0.57        91
       plant       0.72      0.75      0.73        24
     pothole       0.55      0.57      0.56        30
      stairs       0.45      0.63      0.53        60
       table       0.81      0.39      0.53        33
     vehicle       0.61      0.69      0.65        90

    accuracy                           0.59       432
   macro avg       0.63      0.60      0.59       432
weighted avg       0.64      0.59      0.59       432

Model: "sequential_15"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_35 (Conv2D)              │ (None, 64, 64, 32)     │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_10          │ (None, 64, 64, 32)     │           128 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_35 (MaxPooling2D) │ (None, 32, 32, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_36 (Conv2D)              │ (None, 32, 32, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_11          │ (None, 32, 32, 64)     │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_36 (MaxPooling2D) │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_15 (Flatten)            │ (None, 16384)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_30 (Dense)                │ (None, 128)            │     2,097,280 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_31 (Dense)                │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 6,354,656 (24.24 MB)
 Trainable params: 2,118,154 (8.08 MB)
 Non-trainable params: 192 (768.00 B)
 Optimizer params: 4,236,310 (16.16 MB)
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

Après avoir entraîné notre modèle avec les meilleurs paramètres identifiés jusqu’à présent (3 couches convolutives, noyaux de taille (3,3) et learning rate à 0.0005), nous analysons les résultats obtenus.

Les courbes d’accuracy et de loss révèlent un écart marqué entre l’entraînement et la validation. L’accuracy sur l’ensemble d’entraînement atteint presque 100 % alors que celle de validation se stabilise autour de 58–62 %. Parallèlement, la loss d’entraînement chute presque à zéro tandis que la loss de validation demeure élevée (entre 1.5 et 1.7). Ce comportement est caractéristique d’un surapprentissage. Ce comportement suggère un surapprentissage : le modèle mémorise presque parfaitement les données d’entraînement mais généralise moins bien aux données de validation. Pour y remédier, nous pourrons mettre en place des mécanismes de normalisation par batch ou en ajouter des couches de dropout.

Par ailleurs, nous pouvons intégrer un mécanisme d’early stopping afin de limiter le nombre d’époques dès lors que la performance du modèle cesse de s'améliorer. Nous y gagnerions de précieuses minutes (voire heures) de calculs non nécessaires.

En analysant les performances par classe, nous notons des disparités importantes. La matrice de confusion et le rapport de classification montrent une bonne reconnaissance des classes telles que « vehicle » (recall de 69 %) et « garbage_bin » (recall de 71 %), tandis que des classes comme « table » (39 % de recall) et « obstacle » (46 % de recall) sont moins bien détectées. La classe « chair » présente une précision particulièrement faible (0.30), indiquant de fréquentes erreurs de prédiction. Par ailleurs, le modèle confond régulièrement certaines classes, « obstacle » étant parfois interprété à tort comme « vehicle » (17 cas) ou « stairs » (16 cas), et « door » est souvent confondu avec « stairs » (8 cas).

Le rapport de classification met aussi en lumière un déséquilibre marqué dans la répartition des données tel que remarqué au début. Les classes majoritaires, telles que « obstacle » (91 échantillons) et « vehicle » (90), dominent l’ensemble de données, alors que les classes minoritaires, comme « fence » (12 échantillons) et « garbage_bin » (17 échantillons), sont sous-représentées. Ce déséquilibre contribue aux performances inégales et accentue les difficultés de généralisation de notre modèle.

Enfin, l’architecture est très complexe. Le résumé du modèle indique que notre couche dense comportant plus de 2 millions de paramètres. Ce grand nombre de paramètre pourrait facilier la mémorisation des données d’entraînement, renforcer le surapprentissage et expliquer nos résultats.

En somme, l’écart significatif entre les performances (loss et accuracy) d’entraînement et de validation, les disparités de reconnaissance entre classes, les confusions récurrentes, le déséquilibre des données et la complexité (peut être exessive) du modèle expliquent l'accuracy globale limitée (environ 59 %). Des ajustements, tant sur le plan de la régularisation que sur celui de la conception du modèle (architecture) et du rééquilibrage des classes, peuvent s'avérer utiles pour améliorer les capacités de généralisation. Nous explorerons dans la suite certaines de ses optimisations.

Analyse visuelles des erreurs de prédiction¶

In [28]:
def analyze_errors(model, generator):
    # Réinitialiser le générateur
    generator.reset()
    
    # Collecter toutes les images et prédictions
    all_images = []
    all_true_labels = []
    batch_count = 0
    max_batches = len(generator)
    
    for i in range(max_batches):
        batch_images, batch_labels = next(generator)
        if i == 0:  # Garder le premier batch pour l'analyse
            all_images = batch_images
            all_true_labels = batch_labels
        batch_count += 1
    
    # Prédictions du modèle
    predictions = model.predict(all_images)
    predicted_classes = np.argmax(predictions, axis=1)
    true_classes = np.argmax(all_true_labels, axis=1)
    
    # Identifier les erreurs
    errors = np.where(predicted_classes != true_classes)[0]
    
    # Afficher quelques exemples d'erreurs
    plt.figure(figsize=(15, 10))
    n_errors = min(10, len(errors))
    
    class_names = list(generator.class_indices.keys())
    
    for i in range(n_errors):
        idx = errors[i]
        plt.subplot(2, 5, i+1)
        plt.imshow(all_images[idx])
        plt.title(f"Réel: {class_names[true_classes[idx]]}\nPrédit: {class_names[predicted_classes[idx]]}")
        plt.axis('off')
    
    plt.tight_layout()
    plt.suptitle('Analyse des erreurs de classification')
    plt.subplots_adjust(top=0.9)
    plt.show()
    
    # Afficher des statistiques sur les erreurs
    print(f"Nombre total d'images analysées: {len(all_images)}")
    print(f"Nombre d'erreurs: {len(errors)} ({len(errors)/len(all_images)*100:.2f}%)")

# Analyser les erreurs du modèle optimisé
analyze_errors(final_model, val_generator)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 220ms/step
No description has been provided for this image
Nombre total d'images analysées: 32
Nombre d'erreurs: 11 (34.38%)

Exploration de nouvelles optimisations¶

Dans cette section, le TP étant terminé, nous explorons des optimisations supplémentaires afin d’affiner davantage les performances du modèle. Nous conservons l’utilisation de filtres de taille (3, 3) et ajoutons une troisième couche convolutive pour enrichir l’extraction des caractéristiques.

Pour essayer de limiter le surapprentissage, nous ajoutons des couches de dropout après chaque couche convolutive. Cela devrait, théoriquement, améliorer la capacité de généralisation du modèle en atténuant le risque d’apprentissage de patterns spécifiques.

Par ailleurs, nous réduisons davantage le learning rate et optons pour un learning rate dynamique. Cette approche, en adaptant le taux d’apprentissage au cours de la formation, devrait permettre d’équilibrer la vitesse de convergence et la stabilité de l’entraînement.

Voyons voir ce que ça donne.

In [ ]:
def new_opt_model():
    model = Sequential([
        # Premier bloc convolutional - augmenter les filtres
        Conv2D(64, (3, 3), activation='relu', padding='same', input_shape=(64, 64, 3)),
        Conv2D(64, (3, 3), activation='relu', padding='same'),  # Couche supplémentaire
        MaxPooling2D(pool_size=(2, 2)),
        Dropout(0.25),
        
        # Deuxième bloc convolutional - plus profond
        Conv2D(128, (3, 3), padding='same', activation='relu'),
        Conv2D(128, (3, 3), padding='same', activation='relu'),  # Couche supplémentaire
        MaxPooling2D(pool_size=(2, 2)),
        Dropout(0.3),
        
        # Troisième bloc convolutional - réactivé et amélioré
        Conv2D(256, (3, 3), padding='same', activation='relu'),
        MaxPooling2D(pool_size=(2, 2)),
        Dropout(0.4),
        
        # Partie fully connected - plus de capacité
        Flatten(),
        Dense(128, activation='relu', kernel_regularizer=l2(0.001)),  # Plus grand, avec régularisation
        Dropout(0.5),
        Dense(10, activation='softmax')
    ])
    
    # Compiler avec un learning rate encore plus faible
    optimizer = Adam(learning_rate=0.00005)  # Réduit de 0.0001 à 0.00005
    model.compile(
        optimizer=optimizer,
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    
    return model
In [ ]:
def train_new_optimized_model():
    print("\n===== Entraînement du modèle optimisé v2 =====")
    
    # Créer le modèle optimisé
    opt_model = new_opt_model()
    
    # Ajouter des callbacks
    early_stopping = EarlyStopping(
        monitor='val_accuracy',  # Surveiller l'accuracy au lieu de la loss
        patience=10,
        restore_best_weights=True,
        verbose=1
    )
    
    reduce_lr = ReduceLROnPlateau(
        monitor='val_loss',
        factor=0.2,
        patience=5,
        min_lr=1e-6,
        verbose=1
    )
    
    # Entraîner le modèle avec plus d'époques
    opt_history = opt_model.fit(
        train_generator,
        validation_data=val_generator,
        epochs=50,  # Plus d'époques avec early stopping
        callbacks=[early_stopping, reduce_lr],
        verbose=1
    )
    
    # Évaluer le modèle
    val_loss, val_acc = opt_model.evaluate(val_generator)
    print(f"Modèle optimisé v2: Loss = {val_loss:.4f}, Accuracy = {val_acc:.4f}")
    
    return opt_model, opt_history
In [ ]:
opt_mode, opt_history = train_new_optimized_model()
===== Entraînement du modèle optimisé v2 =====
Epoch 1/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 32s 318ms/step - accuracy: 0.1426 - loss: 2.5540 - val_accuracy: 0.0694 - val_loss: 2.5137 - learning_rate: 5.0000e-05
Epoch 2/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 34s 342ms/step - accuracy: 0.2861 - loss: 2.3918 - val_accuracy: 0.0694 - val_loss: 2.4881 - learning_rate: 5.0000e-05
Epoch 3/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 38s 388ms/step - accuracy: 0.1850 - loss: 2.4100 - val_accuracy: 0.0694 - val_loss: 2.4675 - learning_rate: 5.0000e-05
Epoch 4/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 52s 534ms/step - accuracy: 0.1520 - loss: 2.4290 - val_accuracy: 0.0694 - val_loss: 2.4443 - learning_rate: 5.0000e-05
Epoch 5/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 49s 499ms/step - accuracy: 0.2617 - loss: 2.3113 - val_accuracy: 0.0694 - val_loss: 2.4343 - learning_rate: 5.0000e-05
Epoch 6/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 47s 481ms/step - accuracy: 0.2344 - loss: 2.3572 - val_accuracy: 0.0694 - val_loss: 2.4126 - learning_rate: 5.0000e-05
Epoch 7/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 49s 497ms/step - accuracy: 0.1872 - loss: 2.3277 - val_accuracy: 0.0694 - val_loss: 2.4102 - learning_rate: 5.0000e-05
Epoch 8/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 47s 484ms/step - accuracy: 0.1541 - loss: 2.3293 - val_accuracy: 0.0694 - val_loss: 2.3898 - learning_rate: 5.0000e-05
Epoch 9/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 47s 477ms/step - accuracy: 0.1569 - loss: 2.3706 - val_accuracy: 0.0764 - val_loss: 2.3677 - learning_rate: 5.0000e-05
Epoch 10/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 47s 484ms/step - accuracy: 0.2014 - loss: 2.3155 - val_accuracy: 0.1620 - val_loss: 2.3286 - learning_rate: 5.0000e-05
Epoch 11/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 54s 548ms/step - accuracy: 0.2037 - loss: 2.2800 - val_accuracy: 0.1204 - val_loss: 2.3085 - learning_rate: 5.0000e-05
Epoch 12/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 52s 530ms/step - accuracy: 0.2428 - loss: 2.2430 - val_accuracy: 0.2106 - val_loss: 2.2718 - learning_rate: 5.0000e-05
Epoch 13/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 48s 489ms/step - accuracy: 0.3278 - loss: 2.1337 - val_accuracy: 0.2454 - val_loss: 2.2229 - learning_rate: 5.0000e-05
Epoch 14/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 47s 482ms/step - accuracy: 0.2801 - loss: 2.1902 - val_accuracy: 0.2824 - val_loss: 2.1313 - learning_rate: 5.0000e-05
Epoch 15/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 48s 487ms/step - accuracy: 0.3417 - loss: 2.0611 - val_accuracy: 0.2847 - val_loss: 2.1536 - learning_rate: 5.0000e-05
Epoch 16/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 49s 497ms/step - accuracy: 0.3127 - loss: 2.1324 - val_accuracy: 0.3009 - val_loss: 2.0838 - learning_rate: 5.0000e-05
Epoch 17/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 48s 484ms/step - accuracy: 0.3808 - loss: 2.0263 - val_accuracy: 0.2778 - val_loss: 2.1019 - learning_rate: 5.0000e-05
Epoch 18/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 48s 492ms/step - accuracy: 0.4089 - loss: 1.9003 - val_accuracy: 0.3519 - val_loss: 1.9982 - learning_rate: 5.0000e-05
Epoch 19/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 48s 484ms/step - accuracy: 0.3658 - loss: 1.9032 - val_accuracy: 0.3403 - val_loss: 1.9938 - learning_rate: 5.0000e-05
Epoch 20/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 47s 480ms/step - accuracy: 0.3991 - loss: 1.8861 - val_accuracy: 0.3194 - val_loss: 2.0116 - learning_rate: 5.0000e-05
Epoch 21/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 45s 461ms/step - accuracy: 0.3934 - loss: 1.8641 - val_accuracy: 0.3449 - val_loss: 1.9293 - learning_rate: 5.0000e-05
Epoch 22/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 39s 397ms/step - accuracy: 0.4547 - loss: 1.6887 - val_accuracy: 0.3519 - val_loss: 1.9183 - learning_rate: 5.0000e-05
Epoch 23/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 34s 346ms/step - accuracy: 0.4815 - loss: 1.6974 - val_accuracy: 0.3819 - val_loss: 1.8652 - learning_rate: 5.0000e-05
Epoch 24/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 34s 351ms/step - accuracy: 0.3727 - loss: 1.8802 - val_accuracy: 0.3796 - val_loss: 1.8210 - learning_rate: 5.0000e-05
Epoch 25/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 36s 371ms/step - accuracy: 0.4787 - loss: 1.6747 - val_accuracy: 0.4306 - val_loss: 1.7884 - learning_rate: 5.0000e-05
Epoch 26/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 38s 390ms/step - accuracy: 0.4585 - loss: 1.6344 - val_accuracy: 0.4236 - val_loss: 1.8159 - learning_rate: 5.0000e-05
Epoch 27/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 42s 432ms/step - accuracy: 0.4290 - loss: 1.7392 - val_accuracy: 0.4144 - val_loss: 1.7877 - learning_rate: 5.0000e-05
Epoch 28/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 419ms/step - accuracy: 0.5001 - loss: 1.5843 - val_accuracy: 0.4745 - val_loss: 1.7297 - learning_rate: 5.0000e-05
Epoch 29/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 414ms/step - accuracy: 0.4938 - loss: 1.5556 - val_accuracy: 0.4306 - val_loss: 1.8162 - learning_rate: 5.0000e-05
Epoch 30/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 45s 464ms/step - accuracy: 0.5208 - loss: 1.5316 - val_accuracy: 0.4468 - val_loss: 1.7182 - learning_rate: 5.0000e-05
Epoch 31/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 48s 488ms/step - accuracy: 0.4898 - loss: 1.5817 - val_accuracy: 0.4329 - val_loss: 1.7394 - learning_rate: 5.0000e-05
Epoch 32/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 420ms/step - accuracy: 0.5072 - loss: 1.5546 - val_accuracy: 0.4630 - val_loss: 1.6574 - learning_rate: 5.0000e-05
Epoch 33/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 40s 410ms/step - accuracy: 0.4957 - loss: 1.5579 - val_accuracy: 0.4699 - val_loss: 1.6134 - learning_rate: 5.0000e-05
Epoch 34/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 39s 400ms/step - accuracy: 0.4938 - loss: 1.5345 - val_accuracy: 0.4931 - val_loss: 1.5748 - learning_rate: 5.0000e-05
Epoch 35/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 39s 402ms/step - accuracy: 0.5268 - loss: 1.4692 - val_accuracy: 0.4421 - val_loss: 1.6724 - learning_rate: 5.0000e-05
Epoch 36/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 419ms/step - accuracy: 0.5273 - loss: 1.4641 - val_accuracy: 0.4769 - val_loss: 1.5848 - learning_rate: 5.0000e-05
Epoch 37/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 42s 427ms/step - accuracy: 0.5333 - loss: 1.4451 - val_accuracy: 0.4815 - val_loss: 1.6668 - learning_rate: 5.0000e-05
Epoch 38/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 415ms/step - accuracy: 0.5726 - loss: 1.3345 - val_accuracy: 0.4630 - val_loss: 1.6782 - learning_rate: 5.0000e-05
Epoch 39/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 414ms/step - accuracy: 0.5282 - loss: 1.4255 - val_accuracy: 0.5417 - val_loss: 1.4884 - learning_rate: 5.0000e-05
Epoch 40/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 40s 406ms/step - accuracy: 0.5977 - loss: 1.2698 - val_accuracy: 0.5162 - val_loss: 1.5401 - learning_rate: 5.0000e-05
Epoch 41/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 36s 368ms/step - accuracy: 0.5525 - loss: 1.3557 - val_accuracy: 0.5347 - val_loss: 1.4945 - learning_rate: 5.0000e-05
Epoch 42/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 35s 356ms/step - accuracy: 0.5420 - loss: 1.3965 - val_accuracy: 0.5324 - val_loss: 1.4536 - learning_rate: 5.0000e-05
Epoch 43/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 42s 434ms/step - accuracy: 0.6016 - loss: 1.2759 - val_accuracy: 0.5602 - val_loss: 1.4164 - learning_rate: 5.0000e-05
Epoch 44/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 42s 426ms/step - accuracy: 0.5966 - loss: 1.2579 - val_accuracy: 0.5347 - val_loss: 1.5394 - learning_rate: 5.0000e-05
Epoch 45/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 38s 390ms/step - accuracy: 0.6115 - loss: 1.2357 - val_accuracy: 0.5417 - val_loss: 1.4022 - learning_rate: 5.0000e-05
Epoch 46/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 39s 396ms/step - accuracy: 0.5710 - loss: 1.3355 - val_accuracy: 0.5648 - val_loss: 1.4249 - learning_rate: 5.0000e-05
Epoch 47/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 40s 411ms/step - accuracy: 0.6152 - loss: 1.2065 - val_accuracy: 0.5162 - val_loss: 1.4956 - learning_rate: 5.0000e-05
Epoch 48/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 422ms/step - accuracy: 0.6089 - loss: 1.2337 - val_accuracy: 0.5486 - val_loss: 1.4316 - learning_rate: 5.0000e-05
Epoch 49/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 414ms/step - accuracy: 0.6328 - loss: 1.1425 - val_accuracy: 0.5556 - val_loss: 1.3632 - learning_rate: 5.0000e-05
Epoch 50/50
98/98 ━━━━━━━━━━━━━━━━━━━━ 41s 419ms/step - accuracy: 0.6262 - loss: 1.1563 - val_accuracy: 0.5023 - val_loss: 1.5762 - learning_rate: 5.0000e-05
Restoring model weights from the end of the best epoch: 46.
14/14 ━━━━━━━━━━━━━━━━━━━━ 2s 106ms/step - accuracy: 0.5125 - loss: 1.5014
Modèle optimisé v2: Loss = 1.4249, Accuracy = 0.5648
In [ ]:
plot_learning_curves(opt_history, "Courbes d'apprentissage - Modèle optimisé par ajout de dropout")
evaluate_model(opt_mode, val_generator, f"Évaluation du modèle optimisé")
No description has been provided for this image
14/14 ━━━━━━━━━━━━━━━━━━━━ 1s 80ms/step
No description has been provided for this image
Rapport de classification - Évaluation du modèle optimisé
              precision    recall  f1-score   support

       chair       0.23      0.58      0.33        24
        door       0.64      0.31      0.42        51
       fence       0.36      0.33      0.35        12
 garbage_bin       0.48      0.88      0.62        17
    obstacle       0.66      0.49      0.57        91
       plant       0.56      0.58      0.57        24
     pothole       0.52      0.87      0.65        30
      stairs       0.56      0.48      0.52        60
       table       0.77      0.52      0.62        33
     vehicle       0.74      0.71      0.72        90

    accuracy                           0.56       432
   macro avg       0.55      0.58      0.54       432
weighted avg       0.61      0.56      0.57       432

Après avoir optimisé notre modèle par l'ajout de dropout, nous observons des améliorations significatives dans le comportement d'apprentissage et les performances générales. Les courbes d'apprentissage montrent une progression plus équilibrée entre les données d'entraînement et de validation. L'accuracy d'entraînement atteint environ 57% en fin d'apprentissage, tandis que l'accuracy de validation se stabilise autour de 52-56%. Cet écart réduit entre les deux courbes, comparé aux modèles précédents (sans mécanismes de régularisation), indique que le dropout a effectivement permis de limiter le surapprentissage. De même, les courbes de loss suivent une trajectoire descendante cohérente, avec la loss de validation restant légèrement supérieure à celle d'entraînement, ce qui est attendu et souhaitable.

La matrice de confusion révèle des performances variables selon les classes. Certaines catégories sont particulièrement bien reconnues :

  • "vehicle" présente la meilleure performance avec 64 prédictions correctes sur 90 échantillons (71% de recall)
  • "pothole" affiche également un bon recall (87%)
  • "garbage_bin" est bien identifié (88% de recall)

En revanche, d'autres classes montrent des difficultés persistantes :

  • "fence" n'est correctement identifiée que dans 4 cas sur 12 (33%)
  • "door" présente un recall faible (31%)
  • "chair" est fréquemment confondue avec d'autres classes, notamment "obstacle" (13 cas)

Le rapport de classification confirme ces observations avec une accuracy globale de 56%. Les F1-scores varient considérablement : excellent pour "vehicle" (0.72), satisfaisant pour "pothole" (0.65) et faible pour "fence" (0.35) et "chair" (0.33). Ces écarts s'expliquent en partie par le déséquilibre des données : "obstacle" et "vehicle" dominent avec respectivement 91 et 90 échantillons, tandis que "fence" ne compte que 12 échantillons.

Les confusions les plus fréquentes concernent des objets partageant des caractéristiques visuelles similaires :

  • "obstacle" est parfois confondu avec "chair" (13 erreurs)
  • "door" est confondue avec "chair" (9 cas) et "stairs" (7 cas)
  • "stairs" et "vehicle" présentent des confusions réciproques

Bien que le modèle atteigne une accuracy de 56%, les moyennes macro (0.54) et pondérée (0.57) des F1-scores suggèrent une performance modérée mais équilibrée. L'ajout de dropout a manifestement amélioré la capacité de généralisation, comme en témoigne la convergence des courbes d'apprentissage.

Pour améliorer davantage les performances, nous pourrions envisager :

  • L'augmentation des données pour les classes minoritaires comme vu en TIP (projet CV&ML du S3)
  • L'ajustement des poids selon l'inverse de la fréquence des classes
  • L'exploration d'architectures plus adaptées ou le transfert learning
  • La mise en place d'une stratégie d'ensemble combinant plusieurs modèles

Ces optimisations permettraient potentiellement de renforcer la robustesse du modèle face aux classes moins représentées et d'améliorer sa capacité de discrimination entre classes visuellement similaires.