Logo Zéphyrnet

Comprendre le surapprentissage dans les ConvNets

Date :

Introduction

Le surajustement dans les ConvNets est un défi dans l'apprentissage profond et les réseaux de neurones, où un modèle apprend trop des données d'entraînement, ce qui entraîne de mauvaises performances sur les nouvelles données. Ce phénomène est particulièrement répandu dans les architectures neuronales complexes, qui peuvent modéliser des relations complexes. La résolution du surapprentissage dans convnet est cruciale pour créer des modèles de réseaux neuronaux fiables. Cet article fournit un guide pour comprendre et atténuer le surapprentissage, en examinant les causes profondes telles que la complexité du modèle, les données d'entraînement limitées et les fonctionnalités bruyantes. Il aborde également les techniques permettant d'éviter le surajustement, telles que les stratégies d'augmentation des données et les méthodes de régularisation.

Je recommanderais de lire ces articles pour une compréhension de base de surapprentissage, sous-apprentissage ainsi que compromis entre biais et variance.

Apprendre les objectifs

  • Comprendre les causes, les conséquences et les scénarios de surapprentissage dans ConvNets.
  • Interprétez les courbes d'apprentissage pour détecter le surajustement et le sous-ajustement dans les modèles de réseaux neuronaux.
  • Apprenez diverses techniques pour atténuer le surapprentissage, telles que l'arrêt anticipé, l'abandon, la normalisation par lots, la régularisation et l'augmentation des données.
  • Mettre en œuvre ces techniques en utilisant TensorFlow et Keras pour former les ConvNets sur l'ensemble de données CIFAR-10.
  • Analyser l'impact de différentes techniques sur les performances et la généralisation du modèle.

Table des matières

Scénarios courants de surajustement dans ConvNet

Examinons quelques scénarios courants de surajustement dans ConvNet :

Scénario 1 : Modèle très complexe avec des données insuffisantes

L’utilisation d’un modèle très complexe, tel qu’un réseau neuronal profond, sur un petit ensemble de données peut conduire à un surapprentissage. Le modèle peut mémoriser les exemples de formation au lieu d'apprendre le modèle général. Par exemple, entraîner un réseau neuronal profond avec seulement quelques centaines d’images pour une tâche complexe comme la reconnaissance d’images pourrait conduire à un surapprentissage.

Conséquence

Le modèle peut très bien fonctionner sur les données d'entraînement, mais ne parvient pas à se généraliser à de nouvelles données invisibles, ce qui entraîne de mauvaises performances dans les applications du monde réel.

Comment résoudre ce problème?

Obtenez plus de données de formation, effectuez une augmentation d'image pour généraliser notre ensemble de données. Commencez avec un modèle moins complexe et si la capacité est inférieure, augmentez la complexité. 

Scénario 2 : entraînement excessif

Entraîner continuellement un modèle pendant trop d'époques peut conduire à un surapprentissage. Comme le modèle voit les données d'entraînement à plusieurs reprises, il peut commencer à les mémoriser plutôt que d'apprendre les modèles sous-jacents.

Conséquence

Les performances du modèle peuvent stagner, voire se dégrader sur des données invisibles, à mesure qu'il devient de plus en plus spécialisé dans l'ensemble d'entraînement.

Comment résoudre ce problème?

Utilisez un arrêt anticipé pour éviter que le modèle ne soit surajusté et enregistrez le meilleur modèle. 

Scénario 3 : Ignorer la régularisation

Des techniques de régularisation, telles que la régularisation L1 ou L2, sont utilisées pour éviter le surajustement en pénalisant les modèles complexes. Ignorer ou mal régler les paramètres de régularisation peut conduire à un surajustement.

Conséquence

Le modèle peut devenir trop complexe et ne pas se généraliser correctement à de nouvelles données, ce qui entraîne de mauvaises performances en dehors de l'ensemble de formation.

Comment résoudre ce problème?

Implémenter la régularisation, la validation croisée et le réglage des paramètres Hyper. 

Quelle est la capacité du modèle ?

La capacité d'un modèle fait référence à la taille et à la complexité des modèles qu'il est capable d'apprendre. Pour les réseaux de neurones, cela dépendra en grande partie du nombre de neurones dont ils disposent et de la manière dont ils sont connectés entre eux. S'il apparaît que votre réseau ne dispose pas suffisamment de données, vous devriez essayer d'augmenter sa capacité.

Vous pouvez augmenter la capacité d'un réseau soit en l'élargissant (plus d'unités aux couches existantes), soit en le rendant plus profond (en ajoutant plus de couches). Les réseaux plus larges ont plus de facilité à apprendre des relations plus linéaires, tandis que les réseaux plus profonds préfèrent les relations plus non linéaires. Ce qui est mieux dépend simplement de l'ensemble de données.

Interprétation des courbes d'apprentissage

Keras offre la possibilité d'enregistrer des rappels lors de la formation d'un modèle d'apprentissage en profondeur. L'un des rappels par défaut enregistrés lors de la formation de tous les modèles d'apprentissage profond est le rappel de l'historique. Il enregistre les mesures de formation pour chaque époque. Cela inclut la perte et l'exactitude (pour les problèmes de classification) ainsi que la perte et l'exactitude de l'ensemble de données de validation, le cas échéant.

L'objet historique est renvoyé par les appels à la fonction fit() utilisée pour entraîner le modèle. Les métriques sont stockées dans un dictionnaire dans le membre d'historique de l'objet renvoyé.

Par exemple, vous pouvez répertorier les métriques collectées dans un objet d'historique à l'aide de l'extrait de code suivant après l'entraînement d'un modèle :

# list all data in history
print(history.history.keys())

Sortie :

['précision', 'perte', 'val_accuracy', 'val_loss']

Type d'informations

Vous pourriez considérer les informations contenues dans les données d’entraînement comme étant de deux sortes :

  • signal: Le signal est la partie qui généralise, la partie qui peut aider notre modèle à faire des prédictions à partir de nouvelles données.
  • Bruit: Le bruit est la partie qui n'est vraie que pour les données d'entraînement ; le bruit est constitué de toutes les fluctuations aléatoires provenant des données du monde réel ou de tous les modèles accidentels et non informatifs qui ne peuvent pas réellement aider le modèle à faire des prédictions. Le bruit est que la pièce peut sembler utile mais ne l'est vraiment pas.

Lorsque nous entraînons un modèle, nous traçons la perte sur l'ensemble d'entraînement, époque par époque. À cela, nous ajouterons également un tracé des données de validation. Ces tracés sont appelés courbes d’apprentissage. Pour former efficacement des modèles d’apprentissage profond, nous devons être capables de les interpréter.

Courbes d'apprentissage

Dans la figure ci-dessus, nous pouvons voir que la perte de formation diminue à mesure que les époques augmentent, mais la perte de validation diminue au début et augmente à mesure que le modèle commence à capturer le bruit présent dans l'ensemble de données. Nous allons maintenant voir comment éviter le surajustement dans les ConvNets grâce à diverses techniques. 

Méthodes pour éviter le surapprentissage

Maintenant que nous avons vu quelques scénarios et comment interpréter les courbes d'apprentissage pour détecter le surapprentissage. Voyons quelques méthodes pour éviter le surajustement dans un réseau de neurones :

Méthode 1 : utiliser plus de données

Augmenter la taille de votre ensemble de données peut aider le modèle à mieux généraliser, car il dispose d'exemples plus diversifiés dont vous pouvez tirer des leçons. Le modèle trouvera des modèles importants présents dans l'ensemble de données et ignorera le bruit car le modèle se rend compte que ces modèles spécifiques (bruit) ne sont pas présents dans l'ensemble de l'ensemble de données.

Méthode 2 : arrêt anticipé

L'arrêt anticipé est une technique utilisée pour éviter le surajustement en surveillant les performances du modèle sur un ensemble de validation pendant la formation. La formation est arrêtée lorsque les performances de l'ensemble de validation commencent à se dégrader, indiquant que le modèle commence à être surajusté. En règle générale, un ensemble de validation distinct est utilisé pour surveiller les performances, et la formation est arrêtée lorsque les performances ne se sont pas améliorées pendant un nombre spécifié d'époques.

Sous-apprentissage et surapprentissage

Méthode 3 : abandon

Nous savons que le surapprentissage est causé par l'apprentissage par le réseau de modèles parasites (bruit) dans les données d'entraînement. Pour reconnaître ces modèles fallacieux, un réseau s’appuie souvent sur des combinaisons de poids très spécifiques, une sorte de « conspiration » de poids. Étant si spécifiques, ils ont tendance à être fragiles : supprimez-en un et le complot s’effondre.

C’est l’idée derrière le décrochage. Pour briser ces conspirations, nous supprimons de manière aléatoire une fraction des unités d'entrée d'une couche à chaque étape de la formation, ce qui rend beaucoup plus difficile pour le réseau d'apprendre ces modèles parasites dans les données de formation. Au lieu de cela, il doit rechercher des modèles généraux et plus larges, dont les modèles de pondération ont tendance à être plus robustes. 

On pourrait également considérer le décrochage scolaire comme la création d’une sorte d’ensemble de réseaux. Les prédictions ne seront plus faites par un seul grand réseau, mais par un comité de réseaux plus petits. Les membres du comité ont tendance à commettre différents types d’erreurs, mais ont en même temps raison, ce qui rend le comité dans son ensemble meilleur que n’importe quel individu. (Si vous connaissez les forêts aléatoires en tant qu'ensemble d'arbres de décision, c'est la même idée.)

Surapprentissage dans les ConvNets

Méthode 4 : normalisation par lots

La prochaine méthode spéciale que nous examinerons effectue une « normalisation par lots » (ou « batchnorm »), qui peut aider à corriger un entraînement lent ou instable.

Avec les réseaux de neurones, c'est généralement une bonne idée de mettre toutes vos données sur une échelle commune, peut-être avec quelque chose comme StandardScaler ou MinMaxScaler de scikit-learn. La raison en est que SGD modifiera la pondération du réseau proportionnellement à l'ampleur de l'activation produite par les données. Les fonctionnalités qui ont tendance à produire des activations de tailles très différentes peuvent conduire à un comportement d'entraînement instable.

Maintenant, s'il est bon de normaliser les données avant qu'elles n'entrent dans le réseau, il serait peut-être préférable de les normaliser également à l'intérieur du réseau ! En fait, nous disposons d’un type spécial de couche capable de faire cela, la couche de normalisation par lots. Une couche de normalisation par lots examine chaque lot au fur et à mesure de son arrivée, normalisant d'abord le lot avec sa propre moyenne et son propre écart type, puis plaçant également les données sur une nouvelle échelle avec deux paramètres de redimensionnement pouvant être entraînés. Batchnorm, en effet, effectue une sorte de redimensionnement coordonné de ses entrées.

Le plus souvent, batchnorm est ajouté pour faciliter le processus d’optimisation (bien qu’il puisse parfois également améliorer les performances de prédiction). Les modèles avec batchnorm ont tendance à avoir besoin de moins d’époques pour terminer la formation. De plus, batchnorm peut également résoudre divers problèmes qui peuvent provoquer le « blocage » de la formation. Pensez à ajouter la normalisation par lots à vos modèles, surtout si vous rencontrez des difficultés lors de la formation.

Méthode 5 : Régularisation L1 et L2

Les régularisations L1 et L2 sont des techniques utilisées pour éviter le surajustement en pénalisant les poids importants dans le réseau neuronal. La régularisation L1 ajoute un terme de pénalité à la fonction de perte proportionnelle à la valeur absolue des poids. Cela encourage la rareté des pondérations et peut conduire à une sélection de fonctionnalités. La régularisation L2, également connue sous le nom de décroissance des poids, ajoute un terme de pénalité proportionnel au carré des poids à la fonction de perte. Cela évite que les poids ne deviennent trop gros et favorise une répartition plus uniforme des poids.

Le choix entre la régularisation L1 et L2 dépend souvent du problème spécifique et des propriétés souhaitées du modèle.

Avoir des valeurs élevées pour la régularisation L1/L2 empêchera le modèle d'apprendre rapidement et atteindra un plateau d'apprentissage, ce qui entraînera un sous-ajustement du modèle. 

Méthode 6 : Augmentation des données

La meilleure façon d’améliorer les performances d’un modèle d’apprentissage automatique est de l’entraîner sur davantage de données. Plus le modèle doit apprendre d’exemples, mieux il sera capable de reconnaître quelles différences dans les images sont importantes et lesquelles ne le sont pas. Plus de données aident le modèle à mieux généraliser.

Un moyen simple d’obtenir davantage de données consiste à utiliser celles dont vous disposez déjà. Si nous pouvons transformer les images de notre ensemble de données de manière à préserver la classe (exemple : classification MNIST Digit si nous essayons d'augmenter 6, il sera difficile de distinguer entre 6 et 9), nous pouvons apprendre à notre classificateur à ignorer ce type de transformations. Par exemple, le fait qu'une voiture soit orientée à gauche ou à droite sur une photo ne change rien au fait qu'il s'agit d'une voiture et non d'un camion. Ainsi, si nous augmentons nos données d'entraînement avec des images inversées, notre classificateur apprendra que « gauche ou droite » est une différence qu'il doit ignorer.

Et c'est toute l'idée derrière l'augmentation des données : ajoutez de fausses données supplémentaires qui ressemblent raisonnablement aux données réelles et votre classificateur s'améliorera. 

N'oubliez pas que la clé pour éviter le surajustement est de vous assurer que votre modèle généralise bien. Vérifiez toujours les performances de votre modèle sur un ensemble de validation, pas seulement sur l'ensemble d'entraînement.

Implémentation des méthodes ci-dessus avec des données

Explorons les étapes de mise en œuvre des méthodes ci-dessus :

Étape 1 : Chargement des bibliothèques nécessaires

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import ModelCheckpoint
import keras
from keras.preprocessing import image
from keras import models, layers, regularizers
from tqdm import tqdm
import warnings
warnings.filterwarnings(action='ignore')

Étape 2 : chargement de l'ensemble de données et prétraitement

#Here all the images are in the form of a numpy array
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train = x_train / 255.0
x_test = x_test / 255.0

Étape 3 : Ensemble de données d'apprentissage

x_train.shape, y_train.shape, x_test.shape, y_test.shape 

Sortie :

Sortie
np.unique(y_train)

Sortie :

Sortie
#These labels are in the order and taken from the documentaion
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck']

Étape 4 : Visualisation de l'image à partir de l'ensemble de données

def show_image(IMG_INDEX):
    plt.imshow(x_train[20] ,cmap=plt.cm.binary)
    plt.xlabel(class_names[y_train[IMG_INDEX][0]])
    plt.show()
show_image(20)
Surapprentissage dans les ConvNets
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))
model.summary()

Initialisons maintenant les hyper paramètres et le modèle de compilation avec l'optimiseur, la fonction de perte et la métrique d'évaluation.


train_hyperparameters_config={'optim':keras.optimizers.Adam(learning_rate=0.001),
                             'epochs':20,
                              'batch_size':16
                             }
model.compile(optimizer=train_hyperparameters_config['optim'],
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=['accuracy'])

Étape 6 : Modèle de formation

history = model.fit(x_train, y_train, 
                        epochs=train_hyperparameters_config['epochs'], 
                        batch_size=train_hyperparameters_config['batch_size'], 
                        verbose=1,
                        validation_data=(x_test, y_test))

Étape 7 : évaluer le modèle

Ceux-ci nous indiqueront les informations contenues dans l'objet historique et nous les utiliserons pour créer nos courbes d'informations.

print(history.history.keys()) 
def learning_curves(history):
# Plotting Accuracy
    plt.figure(figsize=(14, 5))  # Adjust the figure size as needed
    plt.subplot(1, 2, 1)  # Subplot with 1 row, 2 columns, and index 1
    plt.plot(history.history['accuracy'], label='train_accuracy', marker='s', markersize=4)
    plt.plot(history.history['val_accuracy'], label='val_accuracy', marker='*', markersize=4)
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy')
    plt.legend(loc='lower right')

    # Plotting Loss
    plt.subplot(1, 2, 2)  # Subplot with 1 row, 2 columns, and index 2
    plt.plot(history.history['loss'], label='train_loss', marker='s', markersize=4)
    plt.plot(history.history['val_loss'], label='val_loss', marker='*', markersize=4)
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.legend(loc='lower right')

    plt.show()
learning_curves(history)
Surapprentissage dans les ConvNets

D'après les courbes, nous pouvons voir que la précision de la validation atteint un plateau après la 4ème époque et que le modèle commence à capturer le bruit. Par conséquent, nous mettrons en œuvre un arrêt anticipé pour éviter le surajustement du modèle et restaurerons les meilleurs poids basés sur val_loss. Nous utiliserons val_loss pour surveiller l'arrêt précoce alors que notre réseau neuronal tente de réduire les pertes à l'aide d'optimiseurs. L'exactitude et la précision de la validation dépendent du seuil (une probabilité de séparer les classes – généralement 0.5 pour la classification binaire), donc si notre ensemble de données est déséquilibré, ce serait une perte dont nous devrions nous inquiéter dans la plupart des cas. 

Étape 8 : Mise en œuvre de l'arrêt anticipé

Puisque nous ne craignons pas un surajustement de notre modèle, un arrêt précoce évitera que notre modèle ne se produise. C'est un bon choix de choisir un nombre d'époques plus élevé et une patience appropriée. Nous allons maintenant utiliser la même architecture de modèle et nous entraîner avec un rappel d'arrêt anticipé. 

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))
model.summary()

# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)

def model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config):
    model.compile(optimizer=train_hyperparameters_config['optim'],
                      loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                      metrics=['accuracy'])
    ht = model.fit(x_train, y_train, 
                            epochs=train_hyperparameters_config['epochs'], 
                            batch_size=train_hyperparameters_config['batch_size'],
                            callbacks=[callback],
                            verbose=1,
                            validation_data=(x_test, y_test))
    return ht

ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
learning_curves(ht)
Surapprentissage dans les ConvNets

Pour connaître nos meilleurs poids que le modèle a pris. 

print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

Étape 9 : Augmentation de la complexité du modèle

Étant donné que notre modèle ne fonctionne pas bien et est sous-adapté car il n'est pas capable de capturer suffisamment de données. Nous devrions augmenter la complexité de notre modèle et l'évaluer. 

model = models.Sequential()

model.add(layers.Conv2D(128, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
Sortie

Nous pouvons constater une augmentation des paramètres totaux. Cela aiderait à trouver des relations plus complexes dans notre modèle. Remarque : Notre ensemble de données est composé d’images 32 X 32 ; ce sont des images relativement petites. Par conséquent, l'utilisation de modèles plus complexes au début entraînera sûrement un surajustement du modèle, c'est pourquoi nous avons tendance à augmenter lentement la complexité de notre modèle.

# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)

learning_curves(ht)
Surapprentissage dans les ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

À partir des graphiques ci-dessus, nous pouvons clairement dire que le modèle est surajusté, nous utiliserons donc une autre méthode appelée Normalisation par abandon et normalisation par lots.

Étape 10 : Utilisation des couches d'abandon et des couches de normalisation par lots

model = models.Sequential()

model.add(layers.Conv2D(128, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.3))

model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.3))

model.add(layers.Dense(10, activation='softmax'))
model.summary()
Sortie
# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
learning_curves(ht)
Surapprentissage dans les ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

À partir des graphiques d'apprentissage, nous pouvons voir que le modèle est surajusté même avec des couches de normalisation par lots et d'abandon. Donc au lieu d’augmenter la complexité mais d’augmenter le nombre de filtres. Nous ajouterions plus de couches de convolution pour extraire plus de fonctionnalités.

Étape 11 : Augmentation des couches de convolution

Diminuez le paramètre pouvant être entraîné mais augmentez les couches de convolution pour extraire plus de fonctionnalités.

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.4))

model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.5))

model.add(layers.Dense(10, activation='softmax'))

model.summary()
# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
sortie
learning_curves(ht)
Surapprentissage dans les ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

À partir des résultats et de la courbe d'apprentissage ci-dessus, nous pouvons déduire que le modèle a très bien fonctionné et a évité le surajustement. La précision de la formation et la précision de la validation sont très proches. Dans ce scénario, nous n'aurons pas besoin de méthodes supplémentaires pour réduire le surapprentissage. Mais nous explorerons la régularisation L1/L2. 

Étape 12 : Utilisation de la régularisation L1/L2

from tensorflow.keras import regularizers

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', kernel_regularizer=regularizers.l1(0.0005)))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same', kernel_regularizer=regularizers.l2(0.0005)))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.4))

model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu', kernel_regularizer=regularizers.l1_l2(0.0005, 0.0005)))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(10, activation='softmax'))

model.summary()
# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 7,
    'epochs': 70,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
learning_curves(ht)
Surapprentissage dans les ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

Nous pouvons maintenant voir que la régularisation L1/L2, même après avoir utilisé un faible score de pénalité de 0.0001, a rendu notre modèle sous-adapté de 4 %. Il est donc conseillé d’utiliser toutes les méthodes ensemble avec prudence. Comme la normalisation et la régularisation par lots affectent le modèle de la même manière, nous n'aurions pas besoin de régularisation L1/L2. 

Étape 13 : Augmentation des données

Nous utiliserons ImageDataGenerator de tensorflow keras.

# creates a data generator object that transforms images
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')

# pick an image to transform
test_img = x_train[20]
img = image.img_to_array(test_img)  # convert image to numpy arry
img = img.reshape((1,) + img.shape)  # reshape image

i = 0

for batch in datagen.flow(img, save_prefix='test', save_format='jpeg'):  # this loops runs forever until we break, saving images to current directory with specified prefix
    plt.figure(i)
    plot = plt.imshow(image.img_to_array(batch[0]))
    i += 1
    if i > 4:  # show 4 images
        break

plt.show()
Surapprentissage dans les ConvNets

Il s'agit de quatre images augmentées et d'une image originale.

# Create an instance of the ImageDataGenerator
datagen = ImageDataGenerator(
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

# Create an iterator for the data generator
data_generator = datagen.flow(x_train, y_train, batch_size=32)

# Create empty lists to store the augmented images and labels
augmented_images = []
augmented_labels = []

# Loop over the data generator and append the augmented data to the lists
num_batches = len(x_train) // 32
progress_bar = tqdm(total=num_batches, desc="Augmenting data", unit="batch")

for i in range(num_batches):
    batch_images, batch_labels = next(data_generator)
    augmented_images.append(batch_images)
    augmented_labels.append(batch_labels)
    progress_bar.update(1)

progress_bar.close()

# Convert the lists to NumPy arrays
augmented_images = np.concatenate(augmented_images, axis=0)
augmented_labels = np.concatenate(augmented_labels, axis=0)

# Combine the original and augmented data
x_train_augmented = np.concatenate((x_train, augmented_images), axis=0)
y_train_augmented = np.concatenate((y_train, augmented_labels), axis=0)
sortie

Nous avons utilisé la bibliothèque tqdm pour connaître la progression de notre augmentation.

x_train_augmented.shape, y_train_augmented.shape
Sortie

Ceci est notre ensemble de données après augmentation. Utilisons maintenant cet ensemble de données et entraînons notre modèle.

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.4))

model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.5))

model.add(layers.Dense(10, activation='softmax'))

model.summary()

# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 10,
    'epochs': 70,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)

ht=model_train(model, x_train_augmented, y_train_augmented, x_test, y_test, train_hyperparameters_config)


learning_curves(ht)
Surapprentissage dans les ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

On peut constater que le modèle est plus généralisé et une diminution des pertes. Nous avons également une meilleure précision de validation. Par conséquent, l’augmentation des données a augmenté la précision de notre modèle. 

Conclusion

Le surajustement est un problème courant dans l’apprentissage profond, en particulier avec les architectures de réseaux neuronaux complexes comme les ConvNets. Les praticiens peuvent éviter le surajustement dans les ConvNets en comprenant ses causes profondes et en reconnaissant les scénarios dans lesquels il se produit. Des techniques telles que l'arrêt anticipé, l'abandon, la normalisation des lots, la régularisation et l'augmentation des données peuvent aider à atténuer ce problème. La mise en œuvre de ces techniques sur l'ensemble de données CIFAR-10 a montré des améliorations significatives dans la généralisation et les performances du modèle. La maîtrise de ces techniques et la compréhension de leurs principes peuvent conduire à des modèles de réseaux neuronaux robustes et fiables.

Foire aux Questions

T1. Qu’est-ce que le surapprentissage et pourquoi est-ce un problème en apprentissage profond ? 

R. Le surajustement se produit lorsqu'un modèle apprend trop bien les données d'entraînement, y compris leur bruit et leurs modèles non pertinents, ce qui entraîne de mauvaises performances sur de nouvelles données invisibles. C’est un problème car les modèles surajustés ne parviennent pas à généraliser efficacement, ce qui limite leur utilité pratique.

Q2. Comment puis-je détecter le surapprentissage dans mon modèle de réseau neuronal ?

R. Vous pouvez détecter le surajustement dans les ConvNets en interprétant les courbes d'apprentissage, qui tracent les mesures de formation et de validation (par exemple, perte, précision) au fil des époques. Si les métriques de validation cessent de s'améliorer ou commencent à se dégrader alors que les métriques de formation continuent de s'améliorer, c'est un signe de surapprentissage.

Q3. Qu’est-ce qu’un arrêt précoce et comment aide-t-il à prévenir le surapprentissage ?

A. L'arrêt anticipé est une technique qui surveille les performances du modèle sur un ensemble de validation pendant la formation et arrête le processus de formation lorsque les performances sur l'ensemble de validation commencent à se dégrader, indiquant un surajustement. Cela permet d'éviter le surajustement du modèle en arrêtant l'entraînement au bon moment.

Q4. Comment l’augmentation des données aide-t-elle à atténuer le surapprentissage ? 

A. L'augmentation des données est le processus de génération de nouvelles données de formation synthétiques en appliquant des transformations (par exemple, retournement, rotation, mise à l'échelle) aux données existantes. Cela aide le modèle à mieux généraliser en l'exposant à des exemples plus diversifiés, réduisant ainsi le risque de surajustement dans les ConvNets aux données de formation limitées.

spot_img

Dernières informations

spot_img