Recherche de site Web

Comment améliorer la robustesse du modèle d'apprentissage profond en ajoutant du bruit


L'ajout de bruit à un modèle de réseau neuronal sous-contraint avec un petit ensemble de données d'entraînement peut avoir un effet régularisant et réduire le surapprentissage.

Keras prend en charge l'ajout de bruit gaussien via une couche distincte appelée couche GaussianNoise. Cette couche peut être utilisée pour ajouter du bruit à un modèle existant.

Dans ce tutoriel, vous découvrirez comment ajouter du bruit aux modèles d'apprentissage profond dans Keras afin de réduire le surajustement et d'améliorer la généralisation des modèles.

Après avoir terminé ce tutoriel, vous saurez :

  • Du bruit peut être ajouté à un modèle de réseau neuronal via la couche GaussianNoise.
  • Le GaussianNoise peut être utilisé pour ajouter du bruit aux valeurs d'entrée ou entre les couches cachées.
  • Comment ajouter une couche GaussianNoise afin de réduire le surajustement dans un modèle Perceptron multicouche pour la classification.

Démarrez votre projet avec mon nouveau livre Better Deep Learning, comprenant des tutoriels pas à pas et les fichiers code source Python pour tous les exemples .

Commençons.

  • Mise à jour en octobre 2019 : mise à jour pour Keras 2.3 et TensorFlow 2.0.

Présentation du didacticiel

Ce didacticiel est divisé en trois parties : ils sont:

  1. Régularisation du bruit à Keras
  2. Régularisation du bruit dans les modèles
  3. Étude de cas sur la régularisation du bruit

Régularisation du bruit à Keras

Keras prend en charge l'ajout de bruit aux modèles via la couche GaussianNoise.

Il s'agit d'un calque qui ajoutera du bruit aux entrées d'une forme donnée. Le bruit a une moyenne nulle et nécessite qu'un écart type du bruit soit spécifié comme paramètre. Par exemple:

# import noise layer
from keras.layers import GaussianNoise
# define noise layer
layer = GaussianNoise(0.1)

La sortie du calque aura la même forme que l'entrée, la seule modification étant l'ajout de bruit aux valeurs.

Régularisation du bruit dans les modèles

Le GaussianNoise peut être utilisé de différentes manières avec un modèle de réseau neuronal.

Premièrement, il peut être utilisé comme couche d’entrée pour ajouter directement du bruit aux variables d’entrée. Il s'agit de l'utilisation traditionnelle du bruit comme méthode de régularisation dans les réseaux de neurones.

Vous trouverez ci-dessous un exemple de définition d'une couche GaussianNoise comme couche d'entrée pour un modèle qui prend 2 variables d'entrée.

...
model.add(GaussianNoise(0.01, input_shape=(2,)))
...

Du bruit peut également être ajouté entre les calques cachés du modèle. Compte tenu de la flexibilité de Keras, le bruit peut être ajouté avant ou après l'utilisation de la fonction d'activation. Il peut être plus judicieux de l'ajouter avant l'activation ; néanmoins, les deux options sont possibles.

Vous trouverez ci-dessous un exemple de couche GaussianNoise qui ajoute du bruit à la sortie linéaire d'une couche Dense avant une fonction d'activation linéaire rectifiée (ReLU), peut-être une utilisation plus appropriée du bruit entre les couches cachées.

...
model.add(Dense(32))
model.add(GaussianNoise(0.1))
model.add(Activation('relu'))
model.add(Dense(32))
...

Du bruit peut également être ajouté après la fonction d'activation, un peu comme si vous utilisiez une fonction d'activation bruyante. L'un des inconvénients de cette utilisation est que les valeurs résultantes peuvent être hors de portée de ce que la fonction d'activation peut normalement fournir. Par exemple, une valeur avec du bruit ajouté peut être inférieure à zéro, alors que la fonction d'activation relu ne produira que des valeurs égales ou supérieures à 0.

...
model.add(Dense(32, activation='reu'))
model.add(GaussianNoise(0.1))
model.add(Dense(32))
...

Voyons comment la régularisation du bruit peut être utilisée avec certains types de réseaux courants.

Régularisation du bruit MLP

L'exemple ci-dessous ajoute du bruit entre deux couches denses entièrement connectées.

# example of noise between fully connected layers
from keras.layers import Dense
from keras.layers import GaussianNoise
from keras.layers import Activation
...
model.add(Dense(32))
model.add(GaussianNoise(0.1))
model.add(Activation('relu'))
model.add(Dense(1))
...

Régularisation du bruit CNN

L'exemple ci-dessous ajoute du bruit après une couche de pooling dans un réseau convolutif.

# example of noise for a CNN
from keras.layers import Dense
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import GaussianNoise
...
model.add(Conv2D(32, (3,3)))
model.add(Conv2D(32, (3,3)))
model.add(MaxPooling2D())
model.add(GaussianNoise(0.1))
model.add(Dense(1))
...

Régularisation des abandons RNN

L'exemple ci-dessous ajoute du bruit entre une couche récurrente LSTM et une couche Dense entièrement connectée.

# example of noise between LSTM and fully connected layers
from keras.layers import Dense
from keras.layers import Activation
from keras.layers import LSTM
from keras.layers import GaussianNoise
...
model.add(LSTM(32))
model.add(GaussianNoise(0.5))
model.add(Activation('relu'))
model.add(Dense(1))
...

Maintenant que nous avons vu comment ajouter du bruit aux modèles de réseaux neuronaux, examinons une étude de cas consistant à ajouter du bruit à un modèle de surajustement pour réduire les erreurs de généralisation.

Étude de cas sur la régularisation du bruit

Dans cette section, nous montrerons comment utiliser la régularisation du bruit pour réduire le surajustement d'un MLP sur un simple problème de classification binaire.

Cet exemple fournit un modèle pour appliquer la régularisation du bruit à votre propre réseau neuronal pour les problèmes de classification et de régression.

Problème de classification binaire

Nous utiliserons un problème de classification binaire standard qui définit deux cercles d'observations concentriques bidimensionnels, un demi-cercle pour chaque classe.

Chaque observation a deux variables d'entrée avec la même échelle et une valeur de sortie de classe de 0 ou 1. Cet ensemble de données est appelé l'ensemble de données « cercles » en raison de la forme des observations dans chaque classe une fois tracées.

Nous pouvons utiliser la fonction make_circles() pour générer des observations à partir de ce problème. Nous ajouterons du bruit aux données et amorcerons le générateur de nombres aléatoires afin que les mêmes échantillons soient générés à chaque fois que le code est exécuté.

# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)

Nous pouvons tracer l'ensemble de données où les deux variables sont prises comme coordonnées x et y sur un graphique et la valeur de classe est prise comme couleur de l'observation.

L'exemple complet de génération de l'ensemble de données et de son tracé est répertorié ci-dessous.

# generate two circles dataset
from sklearn.datasets import make_circles
from matplotlib import pyplot
from pandas import DataFrame
# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)
# scatter plot, dots colored by class value
df = DataFrame(dict(x=X[:,0], y=X[:,1], label=y))
colors = {0:'red', 1:'blue'}
fig, ax = pyplot.subplots()
grouped = df.groupby('label')
for key, group in grouped:
    group.plot(ax=ax, kind='scatter', x='x', y='y', label=key, color=colors[key])
pyplot.show()

L’exécution de l’exemple crée un nuage de points montrant la forme des cercles concentriques des observations dans chaque classe. On peut voir le bruit dans la dispersion des points rendant les cercles moins évidents.

C'est un bon problème de test car les classes ne peuvent pas être séparées par une ligne, par ex. ne sont pas linéairement séparables, nécessitant une méthode non linéaire telle qu'un réseau neuronal pour être traitées.

Nous n'avons généré que 100 échantillons, ce qui est petit pour un réseau de neurones, ce qui offre la possibilité de surajuster l'ensemble de données d'entraînement et d'avoir une erreur plus élevée sur l'ensemble de données de test, un bon cas d'utilisation de la régularisation. De plus, les échantillons comportent du bruit, ce qui donne au modèle la possibilité d’apprendre des aspects des échantillons qui ne se généralisent pas.

Perceptron multicouche surajusté

Nous pouvons développer un modèle MLP pour résoudre ce problème de classification binaire.

Le modèle aura une couche cachée avec plus de nœuds que ce qui est nécessaire pour résoudre ce problème, offrant ainsi la possibilité de surajuster. Nous entraînerons également le modèle plus longtemps que nécessaire pour garantir le surajustement du modèle.

Avant de définir le modèle, nous diviserons l'ensemble de données en ensembles d'entraînement et de test, en utilisant 30 exemples pour entraîner le modèle et 70 pour évaluer les performances du modèle d'ajustement.

# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)
# split into train and test
n_train = 30
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

Ensuite, nous pouvons définir le modèle.

La couche cachée utilise 500 nœuds dans la couche cachée et la fonction d'activation linéaire rectifiée. Une fonction d'activation sigmoïde est utilisée dans la couche de sortie afin de prédire les valeurs de classe de 0 ou 1. Le modèle est optimisé à l'aide de la fonction de perte d'entropie croisée binaire, adaptée aux problèmes de classification binaire et de la version efficace d'Adam de descente de gradient.

# define model
model = Sequential()
model.add(Dense(500, input_dim=2, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

Le modèle défini est ensuite adapté aux données d'entraînement pour 4 000 époques et à la taille de lot par défaut de 32.

Nous utiliserons également l'ensemble de données de test comme ensemble de données de validation.

# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)

Nous pouvons évaluer les performances du modèle sur l'ensemble de données de test et rapporter le résultat.

# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))

Enfin, nous tracerons les performances du modèle sur le train et sur l'ensemble de test à chaque époque.

Si le modèle surajuste effectivement l'ensemble de données d'entraînement, nous nous attendons à ce que le tracé linéaire de précision sur l'ensemble d'entraînement continue d'augmenter et que l'ensemble de test augmente puis diminue à nouveau à mesure que le modèle apprend le bruit statistique dans l'ensemble de données d'entraînement.

# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()

Nous pouvons relier toutes ces pièces ensemble ; l'exemple complet est répertorié ci-dessous.

# mlp overfit on the two circles dataset
from sklearn.datasets import make_circles
from keras.layers import Dense
from keras.models import Sequential
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)
# split into train and test
n_train = 30
trainX, testX = X[: n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(500, input_dim=2, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()

L’exécution de l’exemple rapporte les performances du modèle sur les ensembles de données d’entraînement et de test.

Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.

Nous pouvons voir que le modèle a de meilleures performances sur l'ensemble de données d'entraînement que sur l'ensemble de données de test, un signe possible de surajustement.

Train: 1.000, Test: 0.757

Une figure est créée montrant des tracés linéaires de la précision du modèle sur le train et les ensembles de test.

Nous pouvons voir la forme attendue d'un modèle de surajustement où la précision des tests augmente jusqu'à un certain point puis recommence à diminuer.

MLP avec bruit de couche d'entrée

L'ensemble de données est défini par des points qui ont une quantité contrôlée de bruit statistique.

Néanmoins, comme l’ensemble de données est petit, nous pouvons ajouter du bruit supplémentaire aux valeurs d’entrée. Cela aura pour effet de créer davantage d’échantillons ou de rééchantillonner le domaine, rendant artificiellement la structure de l’espace d’entrée plus lisse. Cela peut rendre le problème plus facile à apprendre et améliorer les performances de généralisation.

Nous pouvons ajouter une couche GaussianNoise comme couche d'entrée. La quantité de bruit doit être faible. Étant donné que les valeurs d'entrée sont comprises dans la plage [0, 1], nous ajouterons du bruit gaussien avec une moyenne de 0,0 et un écart type de 0,01, choisis arbitrairement.

# define model
model = Sequential()
model.add(GaussianNoise(0.01, input_shape=(2,)))
model.add(Dense(500, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

L’exemple complet avec ce changement est répertorié ci-dessous.

# mlp overfit on the two circles dataset with input noise
from sklearn.datasets import make_circles
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import GaussianNoise
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)
# split into train and test
n_train = 30
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(GaussianNoise(0.01, input_shape=(2,)))
model.add(Dense(500, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()

L’exécution de l’exemple rapporte les performances du modèle sur les ensembles de données d’entraînement et de test.

Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.

Dans ce cas, nous pouvons constater une légère amélioration des performances du modèle sur l'ensemble de données de test, sans impact négatif sur l'ensemble de données d'entraînement.

Train: 1.000, Test: 0.771

Nous voyons clairement l'impact du bruit ajouté sur l'évaluation du modèle lors de la formation, comme représenté graphiquement sur le tracé linéaire. Le bruit conditionne la précision du modèle lors de ses sauts pendant l'entraînement, probablement en raison du bruit introduisant des points qui entrent en conflit avec les points réels de l'ensemble de données d'entraînement.

Peut-être qu’un écart type du bruit d’entrée plus faible serait plus approprié.

Le modèle montre toujours une tendance à être surajusté, avec une augmentation puis une diminution de la précision des tests au fil des époques d'entraînement.

MLP avec bruit de couche cachée

Une approche alternative pour ajouter du bruit aux valeurs d’entrée consiste à ajouter du bruit entre les couches cachées.

Cela peut être fait en ajoutant du bruit à la sortie linéaire de la couche (somme pondérée) avant que la fonction d'activation ne soit appliquée, dans ce cas une fonction d'activation linéaire rectifiée. Nous pouvons également utiliser un écart type plus grand pour le bruit, car le modèle est moins sensible au bruit à ce niveau étant donné les poids vraisemblablement plus élevés dus au surajustement. Nous utiliserons un écart type de 0,1, là encore choisi arbitrairement.

# define model
model = Sequential()
model.add(Dense(500, input_dim=2))
model.add(GaussianNoise(0.1))
model.add(Activation('relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

L'exemple complet avec du bruit gaussien entre les couches cachées est répertorié ci-dessous.

# mlp overfit on the two circles dataset with hidden layer noise
from sklearn.datasets import make_circles
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Activation
from keras.layers import GaussianNoise
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)
# split into train and test
n_train = 30
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(500, input_dim=2))
model.add(GaussianNoise(0.1))
model.add(Activation('relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()

L’exécution de l’exemple rapporte les performances du modèle sur les ensembles de données d’entraînement et de test.

Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.

Dans ce cas, nous pouvons constater une augmentation marquée des performances du modèle sur l’ensemble de test de tenue.

# Train: 0.967, Test: 0.814

Nous pouvons également voir sur le tracé linéaire de la précision sur les époques d'entraînement que le modèle ne semble plus montrer les propriétés de surajustement.

Nous pouvons également expérimenter et ajouter du bruit après que les sorties de la première couche cachée soient passées par la fonction d'activation.

# define model
model = Sequential()
model.add(Dense(500, input_dim=2, activation='relu'))
model.add(GaussianNoise(0.1))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

L’exemple complet est répertorié ci-dessous.

# mlp overfit on the two circles dataset with hidden layer noise (alternate)
from sklearn.datasets import make_circles
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import GaussianNoise
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=100, noise=0.1, random_state=1)
# split into train and test
n_train = 30
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(500, input_dim=2, activation='relu'))
model.add(GaussianNoise(0.1))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()

L’exécution de l’exemple rapporte les performances du modèle sur les ensembles de données d’entraînement et de test.

Remarque : Vos résultats peuvent varier en raison de la nature stochastique de l'algorithme ou de la procédure d'évaluation, ou des différences de précision numérique. Pensez à exécuter l’exemple plusieurs fois et comparez le résultat moyen.

Étonnamment, nous constatons peu de différence dans les performances du modèle.

Train: 0.967, Test: 0.814

Encore une fois, nous pouvons voir sur le graphique linéaire de précision sur les époques d'entraînement que le modèle ne montre plus de signe de surajustement.

Rallonges

Cette section répertorie quelques idées pour étendre le didacticiel que vous souhaiterez peut-être explorer.

  • Évaluation répétée. Mettez à jour l'exemple pour utiliser une évaluation répétée du modèle avec et sans bruit et signalez les performances sous forme de moyenne et d'écart type au fil des répétitions.
  • Écart type de recherche de grille. Développez une recherche par grille afin de découvrir la quantité de bruit qui aboutit de manière fiable au modèle le plus performant.
  • Entrée et bruit caché. Mettez à jour l'exemple pour introduire du bruit au niveau des couches d'entrée et cachées du modèle.

Si vous explorez l’une de ces extensions, j’aimerais le savoir.

Lectures complémentaires

Cette section fournit plus de ressources sur le sujet si vous souhaitez approfondir.

  • API des régularisateurs Keras
  • API des couches principales Keras
  • API des couches convolutives Keras
  • API des couches récurrentes Keras
  • API de bruit Keras
  • API sklearn.datasets.make_circles

Résumé

Dans ce didacticiel, vous avez découvert comment ajouter du bruit aux modèles d'apprentissage profond dans Keras afin de réduire le surajustement et d'améliorer la généralisation des modèles.

Concrètement, vous avez appris :

  • Du bruit peut être ajouté à un modèle de réseau neuronal via la couche GaussianNoise.
  • Le GaussianNoise peut être utilisé pour ajouter du bruit aux valeurs d'entrée ou entre les couches cachées.
  • Comment ajouter une couche GaussianNoise afin de réduire le surajustement dans un modèle Perceptron multicouche pour la classification.

Avez-vous des questions ?
Posez vos questions dans les commentaires ci-dessous et je ferai de mon mieux pour y répondre.

Articles connexes