Recherche de site Web

Comment utiliser la mise à l'échelle des données pour améliorer la stabilité et les performances du modèle d'apprentissage profond


Les réseaux neuronaux d'apprentissage profond apprennent à mapper les entrées aux sorties à partir d'exemples dans un ensemble de données de formation.

Les poids du modèle sont initialisés à de petites valeurs aléatoires et mis à jour via un algorithme d'optimisation en réponse aux estimations d'erreur sur l'ensemble de données d'entraînement.

Compte tenu de l'utilisation de petits poids dans le modèle et de l'utilisation de l'erreur entre les prédictions et les valeurs attendues, l'échelle des entrées et des sorties utilisée pour entraîner le modèle constitue un facteur important. Les variables d'entrée non mises à l'échelle peuvent entraîner un processus d'apprentissage lent ou instable, tandis que les variables cibles non mises à l'échelle sur les problèmes de régression peuvent entraîner des gradients explosifs provoquant l'échec du processus d'apprentissage.

La préparation des données implique l'utilisation de techniques telles que la normalisation et la standardisation pour redimensionner les variables d'entrée et de sortie avant de former un modèle de réseau neuronal.

Dans ce didacticiel, vous découvrirez comment améliorer la stabilité du réseau neuronal et les performances de modélisation en mettant à l'échelle les données.

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

  • La mise à l'échelle des données est une étape de prétraitement recommandée lorsque vous travaillez avec des réseaux neuronaux d'apprentissage profond.
  • La mise à l'échelle des données peut être obtenue en normalisant ou en standardisant les variables d'entrée et de sortie à valeur réelle.
  • Comment appliquer la standardisation et la normalisation pour améliorer les performances d'un modèle Perceptron multicouche sur un problème de modélisation prédictive de régression.

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.

Présentation du didacticiel

Ce didacticiel est divisé en six parties ; ils sont:

  1. L'ampleur de vos données est importante
  2. Méthodes de mise à l'échelle des données
  3. Problème de modélisation prédictive de régression
  4. Perceptron multicouche avec données non mises à l'échelle
  5. Perceptron multicouche avec variables de sortie mises à l'échelle
  6. Perceptron multicouche avec variables d'entrée mises à l'échelle

L'ampleur de vos données est importante

Les modèles de réseaux neuronaux d'apprentissage profond apprennent un mappage entre des variables d'entrée et une variable de sortie.

Ainsi, l’échelle et la distribution des données tirées du domaine peuvent être différentes pour chaque variable.

Les variables d'entrée peuvent avoir des unités différentes (par exemple, pieds, kilomètres et heures), ce qui, à son tour, peut signifier que les variables ont des échelles différentes.

Les différences d’échelles entre les variables d’entrée peuvent accroître la difficulté du problème modélisé. Un exemple de ceci est que de grandes valeurs d'entrée (par exemple, une répartition de centaines ou de milliers d'unités) peuvent donner lieu à un modèle qui apprend de grandes valeurs de poids. Un modèle avec des valeurs de poids élevées est souvent instable, ce qui signifie qu'il peut souffrir de mauvaises performances lors de l'apprentissage et d'une sensibilité aux valeurs d'entrée, ce qui entraîne une erreur de généralisation plus élevée.

L'une des formes de prétraitement les plus courantes consiste en une simple mise à l'échelle linéaire des variables d'entrée.

— Page 298, Réseaux de neurones pour la reconnaissance de formes, 1995.

Une variable cible avec un large éventail de valeurs peut, à son tour, entraîner des valeurs de gradient d'erreur élevées, entraînant une modification spectaculaire des valeurs de poids, rendant le processus d'apprentissage instable.

La mise à l'échelle des variables d'entrée et de sortie est une étape critique dans l'utilisation de modèles de réseaux neuronaux.

En pratique, il est presque toujours avantageux d'appliquer des transformations de prétraitement aux données d'entrée avant qu'elles ne soient présentées à un réseau. De même, les sorties du réseau sont souvent post-traitées pour donner les valeurs de sortie requises.

— Page 296, Réseaux de neurones pour la reconnaissance de formes, 1995.

Mise à l'échelle des variables d'entrée

Les variables d'entrée sont celles que le réseau prend sur la couche d'entrée ou visible afin de faire une prédiction.

Une bonne règle de base est que les variables d'entrée doivent être de petites valeurs, probablement comprises entre 0 et 1 ou standardisées avec une moyenne nulle et un écart type de un.

La nécessité d'une mise à l'échelle des variables d'entrée dépend des spécificités de votre problème et de chaque variable.

Vous pouvez avoir une séquence de quantités comme entrées, telles que des prix ou des températures.

Si la distribution de la quantité est normale, alors elle doit être standardisée, sinon les données doivent être normalisées. Cela s'applique si la plage de valeurs de quantité est grande (10s, 100s, etc.) ou petite (0,01, 0,0001).

Si les valeurs des quantités sont petites (proches de 0-1) et que la distribution est limitée (par exemple, écart type proche de 1), vous pourrez peut-être vous en sortir sans mettre à l'échelle les données.

Les problèmes peuvent être complexes et il peut ne pas être clair comment mettre à l’échelle au mieux les données d’entrée.

En cas de doute, normalisez la séquence de saisie. Si vous disposez des ressources, explorez la modélisation avec les données brutes, les données standardisées et les données normalisées et voyez s'il existe une différence bénéfique dans les performances du modèle résultant.

Si les variables d’entrée sont combinées linéairement, comme dans un MLP [Multilayer Perceptron], alors il est rarement strictement nécessaire de standardiser les entrées, du moins en théorie. […] Cependant, il existe diverses raisons pratiques pour lesquelles la standardisation des intrants peut accélérer la formation et réduire les risques de rester coincé dans les optima locaux.

— Dois-je normaliser/standardiser/redimensionner les données ? FAQ sur les réseaux neuronaux

Mise à l'échelle des variables de sortie

La variable de sortie est la variable prédite par le réseau.

Vous devez vous assurer que l'échelle de votre variable de sortie correspond à l'échelle de la fonction d'activation (fonction de transfert) sur la couche de sortie de votre réseau.

Si votre fonction d'activation de sortie a une plage de [0,1], vous devez évidemment vous assurer que les valeurs cibles se situent dans cette plage. Mais il est généralement préférable de choisir une fonction d’activation de sortie adaptée à la répartition des cibles plutôt que de forcer vos données à se conformer à la fonction d’activation de sortie.

— Dois-je normaliser/standardiser/redimensionner les données ? FAQ sur les réseaux neuronaux

Si votre problème est un problème de régression, alors le résultat sera une valeur réelle.

Ceci est mieux modélisé avec une fonction d’activation linéaire. Si la distribution de la valeur est normale, vous pouvez alors standardiser la variable de sortie. Sinon, la variable de sortie peut être normalisée.

Méthodes de mise à l'échelle des données

Il existe deux types de mise à l'échelle de vos données que vous pouvez envisager : la normalisation et la standardisation.

Ces deux éléments peuvent être réalisés à l’aide de la bibliothèque scikit-learn.

Normalisation des données

La normalisation est un redimensionnement des données à partir de la plage d'origine afin que toutes les valeurs soient comprises entre 0 et 1.

La normalisation nécessite que vous connaissiez ou soyez capable d'estimer avec précision les valeurs minimales et maximales observables. Vous pourrez peut-être estimer ces valeurs à partir de vos données disponibles.

Une valeur est normalisée comme suit :

y = (x - min) / (max - min)

Où les valeurs minimale et maximale se rapportent à la valeur x en cours de normalisation.

Par exemple, pour un ensemble de données, nous pourrions estimer les valeurs observables minimales et maximales à 30 et -10. Nous pouvons alors normaliser n’importe quelle valeur, comme 18,8, comme suit :

y = (x - min) / (max - min)
y = (18.8 - (-10)) / (30 - (-10))
y = 28.8 / 40
y = 0.72

Vous pouvez voir que si une valeur x fournie est en dehors des limites des valeurs minimale et maximale, la valeur résultante ne sera pas comprise entre 0 et 1. Vous pouvez vérifier ces observations avant à faire des prédictions et soit les supprimer de l'ensemble de données, soit les limiter aux valeurs maximales ou minimales prédéfinies.

Vous pouvez normaliser votre ensemble de données à l'aide de l'objet scikit-learn MinMaxScaler.

Les bonnes pratiques d'utilisation avec MinMaxScaler et d'autres techniques de mise à l'échelle sont les suivantes :

  • Ajustez le scaler à l'aide des données d'entraînement disponibles. Pour la normalisation, cela signifie que les données d'entraînement seront utilisées pour estimer les valeurs minimales et maximales observables. Cela se fait en appelant la fonction fit().
  • Appliquer l'échelle aux données d'entraînement. Cela signifie que vous pouvez utiliser les données normalisées pour entraîner votre modèle. Cela se fait en appelant la fonction transform().
  • Appliquer l'échelle aux données à venir. Cela signifie que vous pouvez préparer de nouvelles données futures sur lesquelles vous souhaitez faire des prédictions.

L'échelle par défaut de MinMaxScaler consiste à redimensionner les variables dans la plage [0,1], bien qu'une échelle préférée puisse être spécifiée via l'argument « feature_range » et spécifier un tuple. y compris le min et le max pour toutes les variables.

# create scaler
scaler = MinMaxScaler(feature_range=(-1,1))

Si nécessaire, la transformation peut être inversée. Ceci est utile pour reconvertir les prédictions dans leur échelle d'origine à des fins de création de rapports ou de traçage. Cela peut être fait en appelant la fonction inverse_transform().

L'exemple ci-dessous fournit une démonstration générale de l'utilisation de MinMaxScaler pour normaliser les données.

# demonstrate data normalization with sklearn
from sklearn.preprocessing import MinMaxScaler
# load data
data = ...
# create scaler
scaler = MinMaxScaler()
# fit scaler on data
scaler.fit(data)
# apply transform
normalized = scaler.transform(data)
# inverse transform
inverse = scaler.inverse_transform(normalized)

Vous pouvez également effectuer l'ajustement et la transformation en une seule étape à l'aide de la fonction fit_transform() ; Par exemple:

# demonstrate data normalization with sklearn
from sklearn.preprocessing import MinMaxScaler
# load data
data = ...
# create scaler
scaler = MinMaxScaler()
# fit and transform in one step
normalized = scaler.fit_transform(data)
# inverse transform
inverse = scaler.inverse_transform(normalized)

Standardisation des données

La normalisation d'un ensemble de données implique de redimensionner la distribution des valeurs de sorte que la moyenne des valeurs observées soit de 0 et l'écart type soit de 1. Cela est parfois appelé « blanchiment ».

Cela peut être considéré comme une soustraction de la valeur moyenne ou un centrage des données.

Tout comme la normalisation, la standardisation peut être utile, voire requise dans certains algorithmes d'apprentissage automatique, lorsque vos données comportent des valeurs d'entrée avec des échelles différentes.

La normalisation suppose que vos observations correspondent à une distribution gaussienne (courbe en cloche) avec une moyenne et un écart type bien comportés. Vous pouvez toujours standardiser vos données si cette attente n’est pas satisfaite, mais vous risquez de ne pas obtenir de résultats fiables.

La normalisation nécessite que vous connaissiez ou soyez capable d'estimer avec précision la moyenne et l'écart type des valeurs observables. Vous pourrez peut-être estimer ces valeurs à partir de vos données d'entraînement.

Une valeur est standardisée comme suit :

y = (x - mean) / standard_deviation

Où la moyenne est calculée comme :

mean = sum(x) / count(x)

Et le standard_deviation est calculé comme :

standard_deviation = sqrt( sum( (x - mean)^2 ) / count(x))

Nous pouvons estimer une moyenne de 10 et un écart type d’environ 5. En utilisant ces valeurs, nous pouvons normaliser la première valeur de 20,7 comme suit :

y = (x - mean) / standard_deviation
y = (20.7 - 10) / 5
y = (10.7) / 5
y = 2.14

Les estimations de la moyenne et de l'écart type d'un ensemble de données peuvent être plus robustes aux nouvelles données que le minimum et le maximum.

Vous pouvez standardiser votre ensemble de données à l'aide de l'objet scikit-learn StandardScaler.

# demonstrate data standardization with sklearn
from sklearn.preprocessing import StandardScaler
# load data
data = ...
# create scaler
scaler = StandardScaler()
# fit scaler on data
scaler.fit(data)
# apply transform
standardized = scaler.transform(data)
# inverse transform
inverse = scaler.inverse_transform(standardized)

Vous pouvez également effectuer l'ajustement et la transformation en une seule étape à l'aide de la fonction fit_transform() ; Par exemple:

# demonstrate data standardization with sklearn
from sklearn.preprocessing import StandardScaler
# load data
data = ...
# create scaler
scaler = StandardScaler()
# fit and transform in one step
standardized = scaler.fit_transform(data)
# inverse transform
inverse = scaler.inverse_transform(standardized)

Problème de modélisation prédictive de régression

Un problème de modélisation prédictive de régression consiste à prédire une quantité à valeur réelle.

Nous pouvons utiliser un générateur de problèmes de régression standard fourni par la bibliothèque scikit-learn dans la fonction make_regression(). Cette fonction générera des exemples à partir d'un simple problème de régression avec un nombre donné de variables d'entrée, de bruit statistique et d'autres propriétés.

Nous utiliserons cette fonction pour définir un problème comportant 20 fonctionnalités d’entrée ; 10 des fonctionnalités seront significatives et 10 ne seront pas pertinentes. Au total, 1 000 exemples seront générés aléatoirement. Le générateur de nombres pseudo-aléatoires sera corrigé pour garantir que nous obtenons les mêmes 1 000 exemples à chaque fois que le code est exécuté.

# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)

Chaque variable d'entrée a une distribution gaussienne, tout comme la variable cible.

Nous pouvons le démontrer en créant des histogrammes de certaines des variables d'entrée et de la variable de sortie.

# regression predictive modeling problem
from sklearn.datasets import make_regression
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# histograms of input variables
pyplot.subplot(211)
pyplot.hist(X[:, 0])
pyplot.subplot(212)
pyplot.hist(X[:, 1])
pyplot.show()
# histogram of target variable
pyplot.hist(y)
pyplot.show()

L’exécution de l’exemple crée deux figures.

Le premier montre les histogrammes des deux premières des vingt variables d'entrée, montrant que chacune a une distribution de données gaussienne.

La deuxième figure montre un histogramme de la variable cible, montrant une plage beaucoup plus large pour la variable par rapport aux variables d'entrée et, encore une fois, une distribution de données gaussienne.

Maintenant que nous avons un problème de régression que nous pouvons utiliser comme base pour l’enquête, nous pouvons développer un modèle pour le résoudre.

Perceptron multicouche avec données non mises à l'échelle

Nous pouvons développer un modèle Multilayer Perceptron (MLP) pour le problème de régression.

Un modèle sera démontré sur les données brutes, sans aucune mise à l'échelle des variables d'entrée ou de sortie. Nous nous attendons à ce que les performances du modèle soient généralement médiocres.

La première étape consiste à diviser les données en ensembles d’entraînement et de test afin que nous puissions ajuster et évaluer un modèle. Nous générerons 1 000 exemples à partir du domaine et diviserons l'ensemble de données en deux, en utilisant 500 exemples pour les ensembles de données d'entraînement et de test.

# 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 pouvons définir un modèle MLP. Le modèle attendra 20 entrées dans les 20 variables d'entrée du problème.

Une seule couche cachée sera utilisée avec 25 nœuds et une fonction d'activation linéaire rectifiée. La couche de sortie comporte un nœud pour la variable cible unique et une fonction d'activation linéaire pour prédire directement les valeurs réelles.

# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))

La fonction de perte d'erreur quadratique moyenne sera utilisée pour optimiser le modèle et l'algorithme d'optimisation de descente de gradient stochastique sera utilisé avec la configuration par défaut raisonnable d'un taux d'apprentissage de 0,01 et d'un élan de 0,9.

# compile model
model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))

Le modèle sera adapté à 100 époques de formation et l'ensemble de test sera utilisé comme ensemble de validation, évalué à la fin de chaque époque de formation.

L'erreur quadratique moyenne est calculée sur les ensembles de données d'entraînement et de test à la fin de la formation pour avoir une idée de la façon dont le modèle a appris le problème.

# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)

Enfin, les courbes d'apprentissage de l'erreur quadratique moyenne sur le train et les ensembles de test à la fin de chaque époque d'entraînement sont représentées graphiquement à l'aide de tracés linéaires, fournissant des courbes d'apprentissage pour avoir une idée de la dynamique du modèle tout en apprenant le problème.

# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()

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

# mlp with unscaled data for the regression problem
from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# 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(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))
# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()

L'exécution de l'exemple ajuste le modèle et calcule l'erreur quadratique moyenne sur le train et les ensembles de test.

Dans ce cas, le modèle est incapable d’apprendre le problème, ce qui entraîne des prédictions de valeurs NaN. Les poids du modèle ont explosé pendant l'entraînement en raison des erreurs très importantes et, par conséquent, des gradients d'erreur calculés pour les mises à jour des poids.

Train: nan, Test: nan

Cela démontre qu'à tout le moins, une certaine mise à l'échelle des données est requise pour la variable cible.

Un tracé linéaire de l'historique d'entraînement est créé mais ne montre rien car le modèle aboutit presque immédiatement à une erreur quadratique moyenne de NaN.

Perceptron multicouche avec variables de sortie mises à l'échelle

Le modèle MLP peut être mis à jour pour mettre à l'échelle la variable cible.

La réduction de l'échelle de la variable cible réduira, à son tour, la taille du gradient utilisé pour mettre à jour les poids et aboutira à un modèle et à un processus de formation plus stables.

Compte tenu de la distribution gaussienne de la variable cible, une méthode naturelle pour redimensionner la variable serait de standardiser la variable. Cela nécessite d'estimer la moyenne et l'écart type de la variable et d'utiliser ces estimations pour effectuer le redimensionnement.

La meilleure pratique consiste à estimer la moyenne et l'écart type de l'ensemble de données d'entraînement et à utiliser ces variables pour mettre à l'échelle l'ensemble de données d'entraînement et de test. Ceci afin d'éviter toute fuite de données pendant le processus d'évaluation du modèle.

Les transformateurs scikit-learn s'attendent à ce que les données d'entrée soient des matrices de lignes et de colonnes, par conséquent les tableaux 1D de la variable cible devront être remodelés en tableaux 2D avant les transformations.

# reshape 1d arrays to 2d arrays
trainy = trainy.reshape(len(trainy), 1)
testy = testy.reshape(len(trainy), 1)

Nous pouvons ensuite créer et appliquer le StandardScaler pour redimensionner la variable cible.

# created scaler
scaler = StandardScaler()
# fit scaler on training dataset
scaler.fit(trainy)
# transform training dataset
trainy = scaler.transform(trainy)
# transform test dataset
testy = scaler.transform(testy)

Le redimensionnement de la variable cible signifie que l'estimation des performances du modèle et le tracé des courbes d'apprentissage calculeront un MSE en unités carrées de la variable mise à l'échelle plutôt qu'en unités carrées de l'échelle d'origine. Cela peut rendre difficile l’interprétation de l’erreur dans le contexte du domaine.

En pratique, il peut être utile d'estimer les performances du modèle en inversant d'abord la transformation sur la variable cible de l'ensemble de données de test et sur les prédictions du modèle, puis en estimant les performances du modèle à l'aide de l'erreur quadratique moyenne sur les données non mises à l'échelle. Ceci est laissé en exercice au lecteur.

L'exemple complet de standardisation de la variable cible du MLP sur le problème de régression est répertorié ci-dessous.

# mlp with scaled outputs on the regression problem
from sklearn.datasets import make_regression
from sklearn.preprocessing import StandardScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# reshape 1d arrays to 2d arrays
trainy = trainy.reshape(len(trainy), 1)
testy = testy.reshape(len(trainy), 1)
# created scaler
scaler = StandardScaler()
# fit scaler on training dataset
scaler.fit(trainy)
# transform training dataset
trainy = scaler.transform(trainy)
# transform test dataset
testy = scaler.transform(testy)
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))
# plot loss during training
pyplot.title('Loss / Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()

L'exécution de l'exemple ajuste le modèle et calcule l'erreur quadratique moyenne sur le train et les ensembles 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, le modèle semble apprendre le problème et atteint une erreur quadratique moyenne proche de zéro, au moins à trois décimales près.

Train: 0.003, Test: 0.007

Un tracé linéaire de l'erreur quadratique moyenne sur l'ensemble de données de train (bleu) et de test (orange) sur chaque époque d'entraînement est créé.

Dans ce cas, nous pouvons voir que le modèle apprend rapidement à mapper efficacement les entrées aux sorties pour le problème de régression et obtient de bonnes performances sur les deux ensembles de données au cours de l'exécution, sans surajustement ni sous-ajustement de l'ensemble de données d'entraînement.

Il peut être intéressant de répéter cette expérience, de normaliser la variable cible et de comparer les résultats.

Perceptron multicouche avec variables d'entrée mises à l'échelle

Nous avons vu que la mise à l'échelle des données peut stabiliser le processus de formation lors de l'ajustement d'un modèle de régression avec une variable cible largement répandue.

Il est également possible d'améliorer la stabilité et les performances du modèle en mettant à l'échelle les variables d'entrée.

Dans cette section, nous allons concevoir une expérience pour comparer les performances de différentes méthodes de mise à l'échelle pour les variables d'entrée.

Les variables d'entrée ont également une distribution de données gaussienne, comme la variable cible, nous nous attendons donc à ce que la normalisation des données soit la meilleure approche. Ce n'est pas toujours le cas.

Nous pouvons comparer les performances des variables d'entrée non mises à l'échelle aux modèles adaptés aux variables d'entrée standardisées et normalisées.

La première étape consiste à définir une fonction pour créer les mêmes 1 000 échantillons de données, les diviser en ensembles d’entraînement et de test et appliquer les méthodes de mise à l’échelle des données spécifiées via les arguments d’entrée. La fonction get_dataset() ci-dessous implémente cela, nécessitant que le scaler soit fourni pour les variables d'entrée et cible et renvoie les ensembles de données d'entraînement et de test divisés en composants d'entrée et de sortie prêts à entraîner et à évaluer un modèle.

# prepare dataset with input and output scalers, can be none
def get_dataset(input_scaler, output_scaler):
	# generate dataset
	X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	# scale inputs
	if input_scaler is not None:
		# fit scaler
		input_scaler.fit(trainX)
		# transform training dataset
		trainX = input_scaler.transform(trainX)
		# transform test dataset
		testX = input_scaler.transform(testX)
	if output_scaler is not None:
		# reshape 1d arrays to 2d arrays
		trainy = trainy.reshape(len(trainy), 1)
		testy = testy.reshape(len(trainy), 1)
		# fit scaler on training dataset
		output_scaler.fit(trainy)
		# transform training dataset
		trainy = output_scaler.transform(trainy)
		# transform test dataset
		testy = output_scaler.transform(testy)
	return trainX, trainy, testX, testy

Ensuite, nous pouvons définir une fonction pour ajuster un modèle MLP sur un ensemble de données donné et renvoyer l'erreur quadratique moyenne du modèle d'ajustement sur l'ensemble de données de test.

La fonction evaluate_model() ci-dessous implémente ce comportement.

# fit and evaluate mse of model on test set
def evaluate_model(trainX, trainy, testX, testy):
	# define model
	model = Sequential()
	model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
	model.add(Dense(1, activation='linear'))
	# compile model
	model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))
	# fit model
	model.fit(trainX, trainy, epochs=100, verbose=0)
	# evaluate the model
	test_mse = model.evaluate(testX, testy, verbose=0)
	return test_mse

Les réseaux de neurones sont formés à l'aide d'un algorithme d'apprentissage stochastique. Cela signifie que le même modèle adapté aux mêmes données peut entraîner des performances différentes.

Nous pouvons résoudre ce problème dans notre expérience en répétant l'évaluation de chaque configuration de modèle, dans ce cas un choix de mise à l'échelle des données, plusieurs fois et en rapportant les performances comme moyenne des scores d'erreur pour toutes les exécutions. Nous répéterons chaque analyse 30 fois pour garantir que la moyenne est statistiquement robuste.

La fonction repeated_evaluation() ci-dessous implémente cela, en prenant le scaler pour les variables d'entrée et de sortie comme arguments, en évaluant un modèle 30 fois avec ces scalers, en imprimant les scores d'erreur en cours de route et en renvoyant une liste des valeurs calculées. scores d’erreur de chaque exécution.

# evaluate model multiple times with given input and output scalers
def repeated_evaluation(input_scaler, output_scaler, n_repeats=30):
	# get dataset
	trainX, trainy, testX, testy = get_dataset(input_scaler, output_scaler)
	# repeated evaluation of model
	results = list()
	for _ in range(n_repeats):
		test_mse = evaluate_model(trainX, trainy, testX, testy)
		print('>%.3f' % test_mse)
		results.append(test_mse)
	return results

Enfin, nous pouvons exécuter l'expérience et évaluer le même modèle sur le même ensemble de données de trois manières différentes :

  • Pas de mise à l'échelle des entrées, sorties standardisées.
  • Entrées normalisées, sorties standardisées.
  • Entrées standardisées, sorties standardisées.

La moyenne et l'écart type de l'erreur pour chaque configuration sont signalés, puis des diagrammes en boîte et en moustaches sont créés pour résumer les scores d'erreur pour chaque configuration.

# unscaled inputs
results_unscaled_inputs = repeated_evaluation(None, StandardScaler())
# normalized inputs
results_normalized_inputs = repeated_evaluation(MinMaxScaler(), StandardScaler())
# standardized inputs
results_standardized_inputs = repeated_evaluation(StandardScaler(), StandardScaler())
# summarize results
print('Unscaled: %.3f (%.3f)' % (mean(results_unscaled_inputs), std(results_unscaled_inputs)))
print('Normalized: %.3f (%.3f)' % (mean(results_normalized_inputs), std(results_normalized_inputs)))
print('Standardized: %.3f (%.3f)' % (mean(results_standardized_inputs), std(results_standardized_inputs)))
# plot results
results = [results_unscaled_inputs, results_normalized_inputs, results_standardized_inputs]
labels = ['unscaled', 'normalized', 'standardized']
pyplot.boxplot(results, labels=labels)
pyplot.show()

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

# compare scaling methods for mlp inputs on regression problem
from sklearn.datasets import make_regression
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
from numpy import mean
from numpy import std

# prepare dataset with input and output scalers, can be none
def get_dataset(input_scaler, output_scaler):
	# generate dataset
	X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	# scale inputs
	if input_scaler is not None:
		# fit scaler
		input_scaler.fit(trainX)
		# transform training dataset
		trainX = input_scaler.transform(trainX)
		# transform test dataset
		testX = input_scaler.transform(testX)
	if output_scaler is not None:
		# reshape 1d arrays to 2d arrays
		trainy = trainy.reshape(len(trainy), 1)
		testy = testy.reshape(len(trainy), 1)
		# fit scaler on training dataset
		output_scaler.fit(trainy)
		# transform training dataset
		trainy = output_scaler.transform(trainy)
		# transform test dataset
		testy = output_scaler.transform(testy)
	return trainX, trainy, testX, testy

# fit and evaluate mse of model on test set
def evaluate_model(trainX, trainy, testX, testy):
	# define model
	model = Sequential()
	model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
	model.add(Dense(1, activation='linear'))
	# compile model
	model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))
	# fit model
	model.fit(trainX, trainy, epochs=100, verbose=0)
	# evaluate the model
	test_mse = model.evaluate(testX, testy, verbose=0)
	return test_mse

# evaluate model multiple times with given input and output scalers
def repeated_evaluation(input_scaler, output_scaler, n_repeats=30):
	# get dataset
	trainX, trainy, testX, testy = get_dataset(input_scaler, output_scaler)
	# repeated evaluation of model
	results = list()
	for _ in range(n_repeats):
		test_mse = evaluate_model(trainX, trainy, testX, testy)
		print('>%.3f' % test_mse)
		results.append(test_mse)
	return results

# unscaled inputs
results_unscaled_inputs = repeated_evaluation(None, StandardScaler())
# normalized inputs
results_normalized_inputs = repeated_evaluation(MinMaxScaler(), StandardScaler())
# standardized inputs
results_standardized_inputs = repeated_evaluation(StandardScaler(), StandardScaler())
# summarize results
print('Unscaled: %.3f (%.3f)' % (mean(results_unscaled_inputs), std(results_unscaled_inputs)))
print('Normalized: %.3f (%.3f)' % (mean(results_normalized_inputs), std(results_normalized_inputs)))
print('Standardized: %.3f (%.3f)' % (mean(results_standardized_inputs), std(results_standardized_inputs)))
# plot results
results = [results_unscaled_inputs, results_normalized_inputs, results_standardized_inputs]
labels = ['unscaled', 'normalized', 'standardized']
pyplot.boxplot(results, labels=labels)
pyplot.show()

L’exécution de l’exemple imprime l’erreur quadratique moyenne pour chaque modèle exécuté en cours de route.

Après que chacune des trois configurations a été évaluée 30 fois chacune, les erreurs moyennes pour chacune sont rapportées.

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, comme nous l'espérions, la mise à l'échelle des variables d'entrée aboutit à un modèle avec de meilleures performances. De manière inattendue, de meilleures performances sont observées en utilisant des entrées normalisées au lieu d’entrées standardisées. Cela peut être lié au choix de la fonction d'activation linéaire rectifiée dans la première couche cachée.

...
>0.010
>0.012
>0.005
>0.008
>0.008
Unscaled: 0.007 (0.004)
Normalized: 0.001 (0.000)
Standardized: 0.008 (0.004)

Une figure avec trois diagrammes en boîte et moustaches est créée, résumant la répartition des scores d'erreur pour chaque configuration.

Les graphiques montrent qu'il y avait peu de différence entre les distributions des scores d'erreur pour les variables d'entrée non mises à l'échelle et standardisées, et que les variables d'entrée normalisées se traduisent par de meilleures performances et une distribution plus stable ou plus serrée des scores d'erreur.

Ces résultats soulignent qu'il est important d'expérimenter et de confirmer les résultats des méthodes de mise à l'échelle des données plutôt que de supposer qu'un système de préparation de données donné fonctionnera mieux en fonction de la distribution observée des données.

Rallonges

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

  • Normaliser la variable cible. Mettez à jour l'exemple et normalisez au lieu de standardiser la variable cible et comparez les résultats.
  • Mise à l'échelle comparée pour la variable cible. Mettez à jour l'exemple pour comparer la normalisation et la normalisation de la variable cible à l'aide d'expériences répétées et comparer les résultats.
  • Autres échelles. Mettez à jour l'exemple pour évaluer d'autres échelles min/max lors de la normalisation et de la comparaison des performances, par ex. [-1, 1] et [0,0, 0,5].

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

  • Comment mettre à l'échelle les données pour les réseaux de mémoire à long terme en Python
  • Comment faire évoluer les données d'apprentissage automatique à partir de zéro avec Python
  • Comment normaliser et standardiser les données de séries chronologiques en Python
  • Comment préparer vos données pour l'apprentissage automatique en Python avec Scikit-Learn

Livres

  • Section 8.2 Normalisation et codage des entrées, Neural Networks for Pattern Recognition, 1995.

API

  • API sklearn.datasets.make_regression
  • API sklearn.preprocessing.MinMaxScaler
  • API sklearn.preprocessing.StandardScaler

Articles

  • Dois-je normaliser/standardiser/redimensionner les données ? FAQ sur les réseaux neuronaux

Résumé

Dans ce didacticiel, vous avez découvert comment améliorer la stabilité du réseau neuronal et les performances de modélisation en mettant à l'échelle les données.

Concrètement, vous avez appris :

  • La mise à l'échelle des données est une étape de prétraitement recommandée lorsque vous travaillez avec des réseaux neuronaux d'apprentissage profond.
  • La mise à l'échelle des données peut être obtenue en normalisant ou en standardisant les variables d'entrée et de sortie à valeur réelle.
  • Comment appliquer la standardisation et la normalisation pour améliorer les performances d'un modèle Perceptron multicouche sur un problème de modélisation prédictive de régression.

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

Articles connexes