Recherche de site Web

Comment résoudre le problème des dégradés qui disparaissent à l'aide de ReLU


Le problème des gradients de disparition est un exemple de comportement instable que vous pouvez rencontrer lors de la formation d'un réseau neuronal profond.

Il décrit la situation dans laquelle un réseau de rétroaction multicouche profond ou un réseau neuronal récurrent est incapable de propager des informations de gradient utiles depuis l'extrémité de sortie du modèle vers les couches proches de l'extrémité d'entrée du modèle.

Le résultat est l’incapacité générale des modèles comportant de nombreuses couches à apprendre sur un ensemble de données donné, ou pour les modèles comportant de nombreuses couches de converger prématurément vers une mauvaise solution.

De nombreux correctifs et solutions de contournement ont été proposés et étudiés, tels que des schémas alternatifs d'initialisation du poids, un pré-entraînement non supervisé, un entraînement par couche et des variations sur la descente de gradient. Le changement le plus courant est peut-être l’utilisation de la fonction d’activation linéaire rectifiée qui est devenue la nouvelle fonction par défaut, au lieu de la fonction d’activation tangente hyperbolique qui était la fonction par défaut à la fin des années 1990 et dans les années 2000.

Dans ce didacticiel, vous découvrirez comment diagnostiquer un problème de gradient de disparition lors de la formation d'un modèle de réseau neuronal et comment le résoudre à l'aide d'une fonction d'activation alternative et d'un schéma d'initialisation du poids.

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

  • Le problème des gradients de disparition limite le développement de réseaux de neurones profonds dotés de fonctions d'activation classiquement populaires telles que la tangente hyperbolique.
  • Comment réparer un réseau neuronal profond Perceptron multicouche pour la classification à l'aide de l'initialisation du poids ReLU et He.
  • Comment utiliser TensorBoard pour diagnostiquer un problème de gradient en voie de disparition et confirmer l'impact de ReLU pour améliorer le flux des gradients à travers le modèle.

Démarrez votre projet avec mon nouveau livre Better Deep Learning, comprenant des tutoriels étape par étape 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 cinq parties ; ils sont:

  1. Problème de disparition des dégradés
  2. Problème de classification binaire à deux cercles
  3. Modèle de perceptron multicouche pour le problème de deux cercles
  4. Modèle MLP plus profond avec ReLU pour le problème de deux cercles
  5. Examiner la taille moyenne du dégradé pendant l'entraînement

Problème de disparition des dégradés

Les réseaux de neurones sont entraînés par descente de gradient stochastique.

Cela implique d'abord de calculer l'erreur de prédiction commise par le modèle et d'utiliser l'erreur pour estimer un gradient utilisé pour mettre à jour chaque poids dans le réseau afin que moins d'erreurs soient commises la prochaine fois. Ce gradient d'erreur se propage vers l'arrière à travers le réseau, de la couche de sortie à la couche d'entrée.

Il est souhaitable de former des réseaux de neurones comportant de nombreuses couches, car l'ajout de plusieurs couches augmente la capacité du réseau, le rendant capable d'apprendre un vaste ensemble de données de formation et de représenter efficacement des fonctions de cartographie plus complexes, des entrées aux sorties.

Un problème avec les réseaux de formation comportant de nombreuses couches (par exemple les réseaux de neurones profonds) est que le gradient diminue considérablement à mesure qu'il se propage vers l'arrière dans le réseau. L'erreur peut être si petite au moment où elle atteint les couches proches de l'entrée du modèle qu'elle peut avoir très peu d'effet. En tant que tel, ce problème est appelé problème des « gradients de disparition ».

Les gradients qui disparaissent rendent difficile de savoir dans quelle direction les paramètres doivent évoluer pour améliorer la fonction de coût…

— Page 290, Apprentissage profond, 2016.

En fait, le gradient d’erreur peut être instable dans les réseaux neuronaux profonds et non seulement disparaître, mais également exploser, le gradient augmentant de façon exponentielle à mesure qu’il se propage vers l’arrière dans le réseau. C’est ce qu’on appelle le problème du « gradient explosif ».

Le terme gradient de disparition fait référence au fait que dans un réseau à action directe (FFN), le signal d'erreur rétropropagé diminue (ou augmente) généralement de manière exponentielle en fonction de la distance par rapport à la couche finale.

— Initialisation de marche aléatoire pour la formation de réseaux à réaction très approfondie, 2014.

La disparition des gradients est un problème particulier avec les réseaux neuronaux récurrents, car la mise à jour du réseau implique de dérouler le réseau pour chaque pas de temps d'entrée, créant ainsi un réseau très profond qui nécessite des mises à jour de poids. Un réseau neuronal récurrent modeste peut avoir de 200 à 400 pas de temps d'entrée, ce qui donne conceptuellement un réseau très profond.

Le problème des gradients de disparition peut se manifester dans un Perceptron multicouche par un taux d'amélioration lent d'un modèle pendant l'entraînement et peut-être une convergence prématurée, par ex. la formation continue n’entraîne aucune amélioration supplémentaire. En inspectant les changements apportés aux poids pendant l'entraînement, nous verrions plus de changements (c'est-à-dire plus d'apprentissage) se produire dans les couches les plus proches de la couche de sortie et moins de changements se produire dans les couches proches de la couche d'entrée.

Il existe de nombreuses techniques qui peuvent être utilisées pour réduire l'impact du problème des gradients de disparition pour les réseaux neuronaux à action directe, notamment les schémas d'initialisation de poids alternatifs et l'utilisation de fonctions d'activation alternatives.

Différentes approches de formation de réseaux profonds (à la fois anticipées et récurrentes) ont été étudiées et appliquées [dans le but de résoudre les gradients en voie de disparition], telles que la pré-formation, une meilleure mise à l'échelle initiale aléatoire, de meilleures méthodes d'optimisation, des architectures spécifiques, une initialisation orthogonale, etc.

— Initialisation de marche aléatoire pour la formation de réseaux à réaction très approfondie, 2014.

Dans ce didacticiel, nous examinerons de plus près l'utilisation d'un schéma d'initialisation de poids alternatif et d'une fonction d'activation pour permettre la formation de modèles de réseaux neuronaux plus profonds.

Problème de classification binaire à deux cercles

Comme base de notre exploration, nous utiliserons un problème très simple de classification à deux classes ou binaire.

La classe scikit-learn fournit la fonction make_circles() qui peut être utilisée pour créer un problème de classification binaire avec le nombre d'échantillons et le bruit statistique prescrits.

Chaque exemple a deux variables d'entrée qui définissent les coordonnées x et y du point sur un plan bidimensionnel. Les points sont disposés en deux cercles concentriques (ils ont le même centre) pour les deux classes.

Le nombre de points dans l'ensemble de données est spécifié par un paramètre dont la moitié sera tirée de chaque cercle. Du bruit gaussien peut être ajouté lors de l'échantillonnage des points via l'argument « noise » qui définit l'écart type du bruit, où 0,0 indique l'absence de bruit ou de points tirés exactement des cercles. La graine du générateur de nombres pseudo-aléatoires peut être spécifiée via l'argument « random_state » qui permet d'échantillonner exactement les mêmes points à chaque fois que la fonction est appelée.

L'exemple ci-dessous génère 1 000 exemples à partir des deux cercles avec du bruit et une valeur de 1 pour amorcer le générateur de nombres pseudo-aléatoires.

# generate circles
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)

Nous pouvons créer un graphique de l'ensemble de données, en traçant les coordonnées x et y des variables d'entrée (X) et en colorant chaque point par la classe valeur (0 ou 1).

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

# scatter plot of the circles dataset with points colored by class
from sklearn.datasets import make_circles
from numpy import where
from matplotlib import pyplot
# generate circles
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# select indices of points with each class label
for i in range(2):
	samples_ix = where(y == i)
	pyplot.scatter(X[samples_ix, 0], X[samples_ix, 1], label=str(i))
pyplot.legend()
pyplot.show()

L'exécution de l'exemple crée un tracé montrant les 1 000 points de données générés avec la valeur de classe de chaque point utilisée pour colorer chaque point.

Nous pouvons voir que les points de la classe 0 sont bleus et représentent le cercle extérieur, et les points de la classe 1 sont orange et représentent le cercle intérieur.

Le bruit statistique des échantillons générés signifie qu'il y a un certain chevauchement de points entre les deux cercles, ajoutant une certaine ambiguïté au problème, le rendant non trivial. Ceci est souhaitable car un réseau de neurones peut choisir l'une des nombreuses solutions possibles pour classer les points entre les deux cercles et toujours commettre des erreurs.

Maintenant que nous avons défini un problème comme base de notre exploration, nous pouvons envisager de développer un modèle pour le résoudre.

Modèle de perceptron multicouche pour le problème de deux cercles

Nous pouvons développer un modèle Perceptron multicouche pour résoudre le problème des deux cercles.

Il s’agira d’un simple modèle de réseau neuronal à action directe, conçu comme on nous l’a enseigné à la fin des années 1990 et au début des années 2000.

Tout d’abord, nous allons générer 1 000 points de données à partir du problème des deux cercles et redimensionner les entrées dans la plage [-1, 1]. Les données se situent presque déjà dans cette fourchette, mais nous nous en assurerons.

Normalement, nous préparerions la mise à l'échelle des données à l'aide d'un ensemble de données d'entraînement et l'appliquerions à un ensemble de données de test. Pour simplifier les choses dans ce didacticiel, nous mettrons à l'échelle toutes les données ensemble avant de les diviser en ensembles d'entraînement et de test.

# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# scale input data to [-1,1]
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)

Ensuite, nous diviserons les données en ensembles d’entraînement et de test.

La moitié des données sera utilisée pour la formation et les 500 exemples restants seront utilisés comme ensemble de test. Dans ce didacticiel, l'ensemble de test servira également d'ensemble de données de validation afin que nous puissions avoir une idée des performances du modèle sur l'ensemble d'exclusion pendant la formation.

# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

Ensuite, nous définirons le modèle.

Le modèle aura une couche d'entrée avec deux entrées, pour les deux variables de l'ensemble de données, une couche cachée avec cinq nœuds et une couche de sortie avec un nœud utilisé pour prédire la probabilité de classe. La couche cachée utilisera la fonction d'activation tangente hyperbolique (tanh) et la couche de sortie utilisera la fonction d'activation logistique (sigmoïde) pour prédire la classe 0 ou la classe 1 ou quelque chose entre les deux.

L’utilisation de la fonction d’activation de la tangente hyperbolique dans les couches cachées était la meilleure pratique dans les années 1990 et 2000, avec des performances généralement meilleures que la fonction logistique lorsqu’elle était utilisée dans la couche cachée. C'était également une bonne pratique d'initialiser les pondérations du réseau à de petites valeurs aléatoires à partir d'une distribution uniforme. Ici, nous initialiserons les poids de manière aléatoire dans la plage [0,0, 1,0].

# define model
model = Sequential()
init = RandomUniform(minval=0, maxval=1)
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))

Le modèle utilise la fonction de perte d'entropie croisée binaire et est optimisé à l'aide d'une descente de gradient stochastique avec un taux d'apprentissage de 0,01 et une dynamique importante de 0,9.

# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])

Le modèle est entraîné pendant 500 époques d'entraînement et l'ensemble de données de test est évalué à la fin de chaque époque avec l'ensemble de données d'entraînement.

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

Une fois le modèle ajusté, il est évalué à la fois sur l'ensemble de données d'entraînement et de test et les scores de précision sont affichés.

# 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, la précision du modèle à chaque étape de la formation est représentée graphiquement sous forme de tracé linéaire, montrant la dynamique du modèle au fur et à mesure de son apprentissage du problème.

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

En reliant tout cela ensemble, l’exemple complet est répertorié ci-dessous.

# mlp for the two circles classification problem
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# scale input data to [-1,1]
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
init = RandomUniform(minval=0, maxval=1)
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, 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 training 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 correspond au modèle en quelques secondes seulement.

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 dans ce cas, le modèle a bien appris le problème, atteignant une précision d'environ 81,6 % sur les ensembles de données d'entraînement et de test.

Train: 0.816, Test: 0.816

Un tracé linéaire de la précision du modèle sur le train et les ensembles de test est créé, montrant l'évolution des performances au cours des 500 époques d'entraînement.

L'intrigue suggère, pour cette exécution, que les performances commencent à ralentir vers l'époque 300 avec une précision d'environ 80 % pour le train et les ensembles de test.

Maintenant que nous avons vu comment développer un MLP classique en utilisant la fonction d'activation tanh pour le problème des deux cercles, nous pouvons envisager de modifier le modèle pour avoir beaucoup plus de couches cachées.

Modèle MLP plus profond pour le problème des deux cercles

Traditionnellement, le développement de modèles Perceptron multicouches approfondis était un défi.

Les modèles profonds utilisant la fonction d'activation de la tangente hyperbolique ne s'entraînent pas facilement, et une grande partie de ces mauvaises performances est imputée au problème de gradient de disparition.

Nous pouvons tenter d’étudier cela en utilisant le modèle MLP développé dans la section précédente.

Le nombre de couches cachées peut être augmenté de 1 à 5 ; Par exemple:

# define model
init = RandomUniform(minval=0, maxval=1)
model = Sequential()
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))

Nous pouvons ensuite réexécuter l’exemple et examiner les résultats.

L’exemple complet du MLP plus profond est répertorié ci-dessous.

# deeper mlp for the two circles classification problem
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
init = RandomUniform(minval=0, maxval=1)
model = Sequential()
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, 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 training 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 imprime d’abord les performances du modèle d’ajustement 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 que les performances sont assez médiocres à la fois sur le train et sur les ensembles de test, atteignant une précision d'environ 50 %. Cela suggère que le modèle tel que configuré n'a pas pu apprendre le problème ni généraliser une solution.

Train: 0.530, Test: 0.468

Les tracés linéaires de la précision du modèle sur le train et les ensembles de test pendant la formation racontent une histoire similaire. Nous pouvons constater que les performances sont mauvaises et empirent à mesure que l’entraînement progresse.

Modèle MLP plus profond avec ReLU pour le problème de deux cercles

La fonction d'activation linéaire rectifiée a supplanté la fonction d'activation tangente hyperbolique en tant que nouvelle valeur par défaut préférée lors du développement de réseaux Perceptron multicouches, ainsi que d'autres types de réseaux tels que les CNN.

En effet, la fonction d'activation ressemble et agit comme une fonction linéaire, ce qui la rend plus facile à entraîner et moins susceptible de saturer, mais est en fait une fonction non linéaire, forçant les entrées négatives à la valeur 0. Elle est revendiquée comme une approche possible. pour résoudre le problème des gradients de disparition lors de la formation de modèles plus profonds.

Lorsque vous utilisez la fonction d'activation linéaire rectifiée (ou ReLU en abrégé), il est recommandé d'utiliser le schéma d'initialisation du poids He. Nous pouvons définir le MLP avec cinq couches cachées en utilisant l'initialisation ReLU et He, répertoriées ci-dessous.

# define model
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))

En reliant cela ensemble, l’exemple de code complet est répertorié ci-dessous.

# deeper mlp with relu for the two circles classification problem
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, 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 training 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 imprime 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 voir que ce petit changement a permis au modèle d'apprendre le problème, atteignant une précision d'environ 84 % sur les deux ensembles de données, surpassant ainsi le modèle monocouche utilisant la fonction d'activation tanh.

Train: 0.836, Test: 0.840

Un tracé linéaire de la précision du modèle sur le train et les ensembles de test sur les époques d'entraînement est également créé. L’intrigue montre une dynamique assez différente de ce que nous avons vu jusqu’à présent.

Le modèle semble apprendre rapidement le problème, convergeant vers une solution en 100 époques environ.

L'utilisation de la fonction d'activation ReLU nous a permis d'adapter un modèle beaucoup plus approfondi à ce problème simple, mais cette capacité ne s'étend pas à l'infini. Par exemple, augmenter le nombre de couches entraîne un apprentissage plus lent jusqu'à un point d'environ 20 couches où le modèle n'est plus capable d'apprendre le problème, du moins avec la configuration choisie.

Par exemple, vous trouverez ci-dessous un tracé linéaire de la précision de l'entraînement et du test du même modèle avec 15 couches cachées qui montre qu'il est toujours capable d'apprendre le problème.

Vous trouverez ci-dessous un tracé linéaire de la précision de l'entraînement et du test sur des époques avec le même modèle à 20 couches, montrant que la configuration n'est plus capable d'apprendre le problème.

Bien que l'utilisation de ReLU ait fonctionné, nous ne pouvons pas être sûrs que l'utilisation de la fonction tanh a échoué en raison de la disparition des gradients et que ReLU a réussi parce qu'il a surmonté ce problème.

Examiner la taille moyenne du dégradé pendant l'entraînement

Cette section suppose que vous utilisez le backend TensorFlow avec Keras. Si ce n'est pas le cas, vous pouvez ignorer cette section.

Dans le cas de l’utilisation de la fonction d’activation tanh, nous savons que le réseau a une capacité plus que suffisante pour apprendre le problème, mais l’augmentation du nombre de couches l’a empêché de le faire.

Il est difficile de diagnostiquer la disparition d’un gradient comme cause d’une mauvaise performance. Un signal possible consiste à revoir la taille moyenne du gradient par couche et par époque d’entraînement.

Nous nous attendrions à ce que les couches plus proches de la sortie aient un dégradé moyen plus grand que les couches plus proches de l'entrée.

Keras fournit le rappel TensorBoard qui peut être utilisé pour enregistrer les propriétés du modèle pendant l'entraînement, telles que le gradient moyen par couche. Ces statistiques peuvent ensuite être examinées à l'aide de l'interface TensorBoard fournie avec TensorFlow.

Nous pouvons configurer ce rappel pour enregistrer le gradient moyen par couche et par époque d'entraînement, puis garantir que le rappel est utilisé dans le cadre de l'entraînement du modèle.

# prepare callback
tb = TensorBoard(histogram_freq=1, write_grads=True)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0, callbacks=[tb])

Nous pouvons utiliser ce rappel pour étudier d'abord la dynamique des gradients dans l'ajustement profond du modèle à l'aide de la fonction d'activation tangente hyperbolique, puis comparer plus tard la dynamique au même ajustement de modèle à l'aide de la fonction d'activation linéaire rectifiée.

Tout d'abord, l'exemple complet du modèle MLP profond utilisant tanh et le rappel TensorBoard est répertorié ci-dessous.

# deeper mlp for the two circles classification problem with callback
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from keras.callbacks import TensorBoard
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
init = RandomUniform(minval=0, maxval=1)
model = Sequential()
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# prepare callback
tb = TensorBoard(histogram_freq=1, write_grads=True)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0, callbacks=[tb])

L'exécution de l'exemple crée un nouveau sous-répertoire « logs/ » avec un fichier contenant les statistiques enregistrées par le rappel lors de l'entraînement.

Nous pouvons consulter les statistiques dans l'interface Web TensorBoard. L'interface peut être démarrée à partir de la ligne de commande, nécessitant que vous spécifiiez le chemin complet de votre répertoire de journaux.

Par exemple, si vous exécutez le code dans un répertoire « /code », le chemin complet vers le répertoire des journaux sera « /code/logs/ ».

Vous trouverez ci-dessous la commande pour démarrer l'interface TensorBoard à exécuter sur votre ligne de commande (invite de commande). Assurez-vous de modifier le chemin d'accès à votre répertoire de journaux.

python -m tensorboard.main --logdir=/code/logs/

Ensuite, ouvrez votre navigateur Web et saisissez l'URL suivante :

  • http://localhost:6006

Si tout s'est bien passé, vous verrez l'interface Web TensorBoard.

Les tracés du gradient moyen par couche et par époque d'entraînement peuvent être consultés sous les onglets « Distributions » et « Histogrammes » de l'interface. Les tracés peuvent être filtrés pour afficher uniquement les dégradés des couches denses, à l'exclusion du biais, à l'aide du filtre de recherche « kernel_0_grad ».

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.

Tout d’abord, des tracés linéaires sont créés pour chacune des 6 couches (5 cachées, 1 sortie). Les noms des tracés indiquent la couche, où « dense_1 » indique la couche cachée après la couche d'entrée et « dense_6 » représente la couche de sortie.

Nous pouvons voir que la couche de sortie a beaucoup d'activité sur l'ensemble de l'exécution, avec des gradients moyens par époque d'environ 0,05 à 0,1. Nous pouvons également voir une activité dans la première couche cachée avec une plage similaire. Par conséquent, les dégradés parviennent jusqu'au premier calque masqué, mais le dernier calque et le dernier calque masqué voient l'essentiel de l'activité.

Nous pouvons collecter les mêmes informations à partir du MLP profond avec la fonction d'activation ReLU.

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

# deeper mlp with relu for the two circles classification problem with callback
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.callbacks import TensorBoard
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# prepare callback
tb = TensorBoard(histogram_freq=1, write_grads=True)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0, callbacks=[tb])

L'interface TensorBoard peut prêter à confusion si vous êtes nouveau dans ce domaine.

Pour simplifier les choses, supprimez le sous-répertoire « logs » avant d'exécuter ce deuxième exemple.

Une fois exécuté, vous pouvez démarrer l'interface TensorBoard de la même manière et y accéder via votre navigateur Web.

Les tracés du gradient moyen par couche et par époque d'entraînement montrent une histoire différente par rapport aux gradients du modèle profond avec tanh.

Nous pouvons voir que la première couche cachée voit plus de dégradés, de manière plus cohérente avec un écart plus grand, peut-être de 0,2 à 0,4, par opposition aux 0,05 et 0,1 observés avec tanh. Nous pouvons également voir que les calques cachés du milieu voient de grands dégradés.

La fonction d'activation ReLU permet à davantage de gradient de refluer à travers le modèle pendant l'entraînement, ce qui peut être à l'origine d'une amélioration des performances.

Rallonges

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

  • Initialisation du poids. Mettez à jour le MLP profond avec l'activation tanh pour utiliser l'initialisation du poids uniforme Xavier et rapporter les résultats.
  • Algorithme d'apprentissage. Mettez à jour le MLP profond avec l'activation tanh pour utiliser un algorithme d'apprentissage adaptatif tel qu'Adam et rapporter les résultats.
  • Changements de poids. Mettez à jour les exemples tanh et relu pour enregistrer et tracer la norme vectorielle L1 des poids du modèle à chaque époque en tant qu'indicateur de l'ampleur de la modification de chaque couche au cours de l'entraînement et comparer les résultats.
  • Profondeur du modèle d'étude. Créez une expérience en utilisant le MLP avec l'activation tanh et signalez les performances des modèles à mesure que le nombre de couches cachées passe de 1 à 10.
  • Augmenter la largeur. Augmentez le nombre de nœuds dans les couches cachées du MLP avec l'activation tanh de 5 à 25 et signalez les performances à mesure que le nombre de couches passe de 1 à 10.

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.

Messages

  • Une introduction douce à l'unité d'activation linéaire rectifiée (ReLU) pour les réseaux de neurones d'apprentissage profond

Papiers

  • Initialisation de marche aléatoire pour la formation de réseaux à réaction très approfondie, 2014.
  • Flux dégradé dans les réseaux récurrents : la difficulté d'apprendre les dépendances à long terme, 2001.

Livres

  • Section 8.2.5 Dépendances à long terme, Deep Learning, 2016.
  • Chapitre 5 Pourquoi les réseaux de neurones profonds sont-ils difficiles à former ?, Réseaux de neurones et apprentissage profond.

API

  • API d'initialisation du poids RandomUniform Keras
  • API d'optimisation SGD Keras
  • API de rappel TensorBoard Keras
  • Guide TensorFlow TensorBoard

Articles

  • Problème de gradient de disparition, Wikipédia.

Résumé

Dans ce didacticiel, vous avez découvert comment diagnostiquer un problème de gradient de disparition lors de la formation d'un modèle de réseau neuronal et comment le résoudre à l'aide d'une autre fonction d'activation et d'un schéma d'initialisation de poids.

Concrètement, vous avez appris :

  • Le problème des gradients de disparition limite le développement de réseaux de neurones profonds dotés de fonctions d'activation classiquement populaires telles que la tangente hyperbolique.
  • Comment réparer un réseau neuronal profond Perceptron multicouche pour la classification à l'aide de l'initialisation du poids ReLU et He.
  • Comment utiliser TensorBoard pour diagnostiquer un problème de gradient en voie de disparition et confirmer l'impact de ReLU pour améliorer le flux des gradients à travers le modèle.

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

Articles connexes