Recherche de site Web

Comment améliorer les performances grâce à l'apprentissage par transfert pour les réseaux de neurones d'apprentissage profond


Un avantage intéressant des réseaux de neurones d’apprentissage profond est qu’ils peuvent être réutilisés sur des problèmes connexes.

L'apprentissage par transfert fait référence à une technique de modélisation prédictive sur un problème différent mais en quelque sorte similaire qui peut ensuite être réutilisée en partie ou en totalité pour accélérer la formation et améliorer les performances d'un modèle sur le problème qui nous intéresse.

Dans l'apprentissage profond, cela signifie réutiliser les poids dans une ou plusieurs couches d'un modèle de réseau pré-entraîné dans un nouveau modèle et soit garder les poids fixes, les affiner, soit adapter entièrement les poids lors de l'entraînement du modèle.

Dans ce tutoriel, vous découvrirez comment utiliser l'apprentissage par transfert pour améliorer les performances des réseaux de neurones d'apprentissage profond en Python avec Keras.

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

  • L'apprentissage par transfert est une méthode permettant de réutiliser un modèle formé sur un problème de modélisation prédictive connexe.
  • L'apprentissage par transfert peut être utilisé pour accélérer la formation de réseaux de neurones, soit sous la forme d'un schéma d'initialisation de poids, soit d'une méthode d'extraction de caractéristiques.
  • Comment utiliser l'apprentissage par transfert pour améliorer les performances d'un MLP pour un problème de classification multiclasse.

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.
  • Mise à jour de janvier 2020 : mise à jour pour les modifications apportées à l'API scikit-learn v0.22.

Présentation du didacticiel

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

  1. Qu’est-ce que l’apprentissage par transfert ?
  2. Problème de classification multiclasse des blobs
  3. Modèle de perceptron multicouche pour le problème 1
  4. Modèle MLP autonome pour le problème 2
  5. MLP avec apprentissage par transfert pour le problème 2
  6. Comparaison des modèles sur le problème 2

Qu’est-ce que l’apprentissage par transfert ?

L'apprentissage par transfert fait généralement référence à un processus dans lequel un modèle formé sur un problème est utilisé d'une manière ou d'une autre sur un deuxième problème connexe.

L'apprentissage par transfert et l'adaptation de domaine font référence à la situation dans laquelle ce qui a été appris dans un contexte (c'est-à-dire la distribution P1) est exploité pour améliorer la généralisation dans un autre contexte (par exemple la distribution P2).

— Page 536, Apprentissage profond, 2016.

En apprentissage profond, l'apprentissage par transfert est une technique par laquelle un modèle de réseau neuronal est d'abord formé sur un problème similaire au problème en cours de résolution. Une ou plusieurs couches du modèle formé sont ensuite utilisées dans un nouveau modèle formé sur le problème qui vous intéresse.

Ceci est généralement compris dans un contexte d’apprentissage supervisé, où l’entrée est la même mais la cible peut être de nature différente. Par exemple, nous pouvons en apprendre davantage sur un ensemble de catégories visuelles, telles que les chats et les chiens, dans le premier cadre, puis sur un autre ensemble de catégories visuelles, telles que les fourmis et les guêpes, dans le deuxième cadre.

— Page 536, Apprentissage profond, 2016.

L'apprentissage par transfert présente l'avantage de réduire le temps de formation d'un modèle de réseau neuronal et d'entraîner une erreur de généralisation plus faible.

Il existe deux approches principales pour mettre en œuvre l’apprentissage par transfert ; ils sont:

  • Initialisation du poids.
  • Extraction de fonctionnalités.

Les poids des couches réutilisées peuvent être utilisés comme point de départ du processus de formation et adaptés en réponse au nouveau problème. Cet usage traite l'apprentissage par transfert comme un type de schéma d'initialisation du poids. Cela peut être utile lorsque le premier problème associé contient beaucoup plus de données étiquetées que le problème qui vous intéresse et que la similitude dans la structure du problème peut être utile dans les deux contextes.

… l’objectif est de profiter des données du premier cadre pour en extraire des informations qui peuvent être utiles lors de l’apprentissage ou même pour faire directement des prédictions dans le second cadre.

— Page 538, Apprentissage profond, 2016.

Alternativement, les poids du réseau peuvent ne pas être adaptés en réponse au nouveau problème, et seules les nouvelles couches après les couches réutilisées peuvent être entraînées pour interpréter leur sortie. Cet usage traite l'apprentissage par transfert comme un type de schéma d'extraction de fonctionnalités. Un exemple de cette approche est la réutilisation de modèles de réseaux neuronaux à convolution profonde formés pour la classification de photos en tant qu'extracteurs de caractéristiques lors du développement de modèles de sous-titrage de photos.

Les variations de ces utilisations peuvent impliquer de ne pas entraîner initialement les poids du modèle sur le nouveau problème, mais ensuite d'affiner tous les poids du modèle appris avec un faible taux d'apprentissage.

Problème de classification multiclasse des blobs

Nous utiliserons un petit problème de classification multi-classes comme base pour démontrer l'apprentissage par transfert.

La classe scikit-learn fournit la fonction make_blobs() qui peut être utilisée pour créer un problème de classification multi-classe avec le nombre prescrit d'échantillons, de variables d'entrée, de classes et de variance des échantillons au sein d'une classe.

Nous pouvons configurer le problème pour avoir deux variables d'entrée (pour représenter les coordonnées x et y des points) et un écart type de 2,0 pour les points de chaque groupe. Nous utiliserons le même état aléatoire (graine pour le générateur de nombres pseudo-aléatoires) pour garantir que nous obtenons toujours les mêmes points de données.

# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=1)

Les résultats sont les éléments d'entrée et de sortie d'un ensemble de données que nous pouvons modéliser.

L'argument « random_state » peut varier pour donner différentes versions du problème (différents centres de cluster). Nous pouvons utiliser cela pour générer des échantillons à partir de deux problèmes différents : entraîner un modèle sur un problème et réutiliser les poids pour mieux apprendre un modèle pour un deuxième problème.

Plus précisément, nous ferons référence à random_state=1 comme problème 1 et à random_state=2 comme problème 2.

  • Problème 1. Problème de blobs avec deux variables d'entrée et trois classes avec l'argument random_state défini sur un.
  • Problème 2. Problème de blobs avec deux variables d'entrée et trois classes avec l'argument random_state défini sur deux.

Afin d'avoir une idée de la complexité du problème, nous pouvons tracer chaque point sur un nuage de points bidimensionnel et colorer chaque point par valeur de classe.

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

# plot of blobs multiclass classification problems 1 and 2
from sklearn.datasets import make_blobs
from numpy import where
from matplotlib import pyplot

# generate samples for blobs problem with a given random seed
def samples_for_seed(seed):
	# generate samples
	X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
	return X, y

# create a scatter plot of points colored by class value
def plot_samples(X, y, classes=3):
	# plot points for each class
	for i in range(classes):
		# select indices of points with each class label
		samples_ix = where(y == i)
		# plot points for this class with a given color
		pyplot.scatter(X[samples_ix, 0], X[samples_ix, 1])

# generate multiple problems
n_problems = 2
for i in range(1, n_problems+1):
	# specify subplot
	pyplot.subplot(210 + i)
	# generate samples
	X, y = samples_for_seed(i)
	# scatter plot of samples
	plot_samples(X, y)
# plot figure
pyplot.show()

L'exécution de l'exemple génère un échantillon de 1 000 exemples pour le problème 1 et le problème 2 et crée un nuage de points pour chaque échantillon, colorant les points de données en fonction de leur valeur de classe.

Cela fournit une bonne base pour l'apprentissage par transfert, car chaque version du problème a des données d'entrée similaires avec une échelle similaire, bien qu'avec des informations cibles différentes (par exemple, les centres de cluster).

Nous nous attendons à ce que les aspects d'un modèle adaptés à une version du problème des blobs (par exemple, le problème 1) soient utiles lors de l'ajustement d'un modèle sur une nouvelle version du problème des blobs (par exemple, le problème 2).

Modèle de perceptron multicouche pour le problème 1

Dans cette section, nous développerons un modèle Perceptron multicouche (MLP) pour le problème 1 et enregistrerons le modèle dans un fichier afin de pouvoir réutiliser les poids ultérieurement.

Tout d’abord, nous développerons une fonction pour préparer l’ensemble de données prêt pour la modélisation. Une fois la fonction make_blobs() appelée avec une graine aléatoire donnée (par exemple, une dans ce cas pour le problème 1), la variable cible doit être une variable codée à chaud afin que nous puissions développer un modèle qui prédit la probabilité qu'un échantillon donné appartienne à chacune des classes cibles.

Les échantillons préparés peuvent ensuite être divisés en deux, avec 500 exemples pour les ensembles de données d'entraînement et de test. La fonction samples_for_seed() ci-dessous implémente cela, en préparant l'ensemble de données pour une graine de nombre aléatoire donnée et en réajustant les ensembles d'entraînement et de test divisés en composants d'entrée et de sortie.

# prepare a blobs examples with a given random seed
def samples_for_seed(seed):
	# generate samples
	X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
	# one hot encode output variable
	y = to_categorical(y)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	return trainX, trainy, testX, testy

Nous pouvons appeler cette fonction pour préparer un ensemble de données pour le problème 1 comme suit.

# prepare data
trainX, trainy, testX, testy = samples_for_seed(1)

Ensuite, nous pouvons définir et ajuster un modèle sur l'ensemble de données de formation.

Le modèle attendra deux entrées pour les deux variables dans les données. Le modèle aura deux couches cachées avec cinq nœuds chacune et la fonction d'activation linéaire rectifiée. Deux couches ne sont probablement pas nécessaires pour cette fonction, même si nous souhaitons que le modèle apprenne une structure profonde que nous pouvons réutiliser dans toutes les instances de ce problème. La couche de sortie comporte trois nœuds, un pour chaque classe dans la variable cible et la fonction d'activation softmax.

# 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(3, activation='softmax'))

Étant donné que le problème est un problème de classification multi-classes, la fonction de perte d'entropie croisée catégorielle est minimisée et la descente de gradient stochastique avec le taux d'apprentissage par défaut et sans impulsion est utilisée pour apprendre le problème.

# compile model
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])

Le modèle est adapté pour 100 époques sur l'ensemble de données d'entraînement et l'ensemble de test est utilisé comme ensemble de données de validation pendant l'entraînement, évaluant les performances sur les deux ensembles de données à la fin de chaque époque afin que nous puissions tracer des courbes d'apprentissage.

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

La fonction fit_model() relie ces éléments ensemble, en prenant les ensembles de données d'entraînement et de test comme arguments et en renvoyant le modèle d'ajustement et l'historique d'entraînement.

# define and fit model on a training dataset
def fit_model(trainX, trainy, testX, testy):
	# 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(3, activation='softmax'))
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# fit model
	history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
	return model, history

Nous pouvons appeler cette fonction avec l'ensemble de données préparé pour obtenir un modèle d'ajustement et l'historique collecté pendant le processus de formation.

# fit model on train dataset
model, history = fit_model(trainX, trainy, testX, testy)

Enfin, nous pouvons résumer les performances du modèle.

La précision de la classification du modèle sur le train et les ensembles de test peut être évaluée.

# 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))

L'historique collecté pendant la formation peut être utilisé pour créer des tracés linéaires montrant à la fois la perte et la précision de classification du modèle sur le train et les ensembles de test au cours de chaque époque de formation, fournissant ainsi des courbes d'apprentissage.

# plot loss during training
pyplot.subplot(211)
pyplot.title('Loss')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
# plot accuracy during training
pyplot.subplot(212)
pyplot.title('Accuracy')
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()

La fonction summarize_model() ci-dessous implémente cela, en prenant le modèle d'ajustement, l'historique d'entraînement et l'ensemble de données comme arguments, en imprimant les performances du modèle et en créant un tracé des courbes d'apprentissage du modèle.

# summarize the performance of the fit model
def summarize_model(model, history, trainX, trainy, testX, testy):
	# 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 loss during training
	pyplot.subplot(211)
	pyplot.title('Loss')
	pyplot.plot(history.history['loss'], label='train')
	pyplot.plot(history.history['val_loss'], label='test')
	pyplot.legend()
	# plot accuracy during training
	pyplot.subplot(212)
	pyplot.title('Accuracy')
	pyplot.plot(history.history['accuracy'], label='train')
	pyplot.plot(history.history['val_accuracy'], label='test')
	pyplot.legend()
	pyplot.show()

Nous pouvons appeler cette fonction avec le modèle d'ajustement et les données préparées.

# evaluate model behavior
summarize_model(model, history, trainX, trainy, testX, testy)

À la fin de l'exécution, nous pouvons enregistrer le modèle dans un fichier afin de pouvoir le charger ultérieurement et l'utiliser comme base pour certaines expériences d'apprentissage par transfert.

Notez que l'enregistrement du modèle dans un fichier nécessite que la bibliothèque h5py soit installée. Cette bibliothèque peut être installée via pip comme suit :

sudo pip install h5py

Le modèle d'ajustement peut être enregistré en appelant la fonction save() sur le modèle.

# save model to file
model.save('model.h5')

En reliant ces éléments ensemble, l'exemple complet d'ajustement d'un MLP sur le problème 1, résumant les performances du modèle et enregistrant le modèle dans un fichier est répertorié ci-dessous.

# fit mlp model on problem 1 and save model to file
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
from matplotlib import pyplot

# prepare a blobs examples with a given random seed
def samples_for_seed(seed):
	# generate samples
	X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
	# one hot encode output variable
	y = to_categorical(y)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	return trainX, trainy, testX, testy

# define and fit model on a training dataset
def fit_model(trainX, trainy, testX, testy):
	# 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(3, activation='softmax'))
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# fit model
	history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
	return model, history

# summarize the performance of the fit model
def summarize_model(model, history, trainX, trainy, testX, testy):
	# 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 loss during training
	pyplot.subplot(211)
	pyplot.title('Loss')
	pyplot.plot(history.history['loss'], label='train')
	pyplot.plot(history.history['val_loss'], label='test')
	pyplot.legend()
	# plot accuracy during training
	pyplot.subplot(212)
	pyplot.title('Accuracy')
	pyplot.plot(history.history['accuracy'], label='train')
	pyplot.plot(history.history['val_accuracy'], label='test')
	pyplot.legend()
	pyplot.show()

# prepare data
trainX, trainy, testX, testy = samples_for_seed(1)
# fit model on train dataset
model, history = fit_model(trainX, trainy, testX, testy)
# evaluate model behavior
summarize_model(model, history, trainX, trainy, testX, testy)
# save model to file
model.save('model.h5')

L'exécution de l'exemple s'adapte et évalue les performances du modèle, en imprimant la précision de la classification 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, nous pouvons constater que le modèle a bien fonctionné sur le problème 1, atteignant une précision de classification d'environ 92 % sur les ensembles de données d'entraînement et de test.

Train: 0.916, Test: 0.920

Une figure est également créée résumant les courbes d'apprentissage du modèle, montrant à la fois la perte (en haut) et la précision (en bas) du modèle sur les ensembles de données de train (bleu) et de test (orange) à la fin de chaque époque de formation.

Votre tracé n’est peut-être pas identique, mais il est censé montrer le même comportement général. Sinon, essayez d'exécuter l'exemple plusieurs fois.

Dans ce cas, nous pouvons voir que le modèle a appris le problème assez rapidement et correctement, convergeant peut-être sur environ 40 époques et restant raisonnablement stable sur les deux ensembles de données.

Maintenant que nous avons vu comment développer un MLP autonome pour le problème 1 des blobs, nous pouvons envisager de faire de même pour le problème 2 qui peut être utilisé comme référence.

Modèle MLP autonome pour le problème 2

L'exemple de la section précédente peut être mis à jour pour adapter un modèle MLP au problème 2.

Il est important d'avoir d'abord une idée des performances et de la dynamique d'apprentissage sur le problème 2 pour un modèle autonome, car cela fournira une base de référence en matière de performances qui pourra être utilisée pour comparer à un modèle adapté au même problème en utilisant l'apprentissage par transfert.

Une seule modification est requise pour modifier l'appel à samples_for_seed() afin d'utiliser la graine du générateur de nombres pseudo-aléatoires de deux au lieu d'une.

# prepare data
trainX, trainy, testX, testy = samples_for_seed(2)

Par souci d’exhaustivité, l’exemple complet avec ce changement est répertorié ci-dessous.

# fit mlp model on problem 2 and save model to file
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
from matplotlib import pyplot

# prepare a blobs examples with a given random seed
def samples_for_seed(seed):
	# generate samples
	X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
	# one hot encode output variable
	y = to_categorical(y)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	return trainX, trainy, testX, testy

# define and fit model on a training dataset
def fit_model(trainX, trainy, testX, testy):
	# 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(3, activation='softmax'))
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# fit model
	history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
	return model, history

# summarize the performance of the fit model
def summarize_model(model, history, trainX, trainy, testX, testy):
	# 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 loss during training
	pyplot.subplot(211)
	pyplot.title('Loss')
	pyplot.plot(history.history['loss'], label='train')
	pyplot.plot(history.history['val_loss'], label='test')
	pyplot.legend()
	# plot accuracy during training
	pyplot.subplot(212)
	pyplot.title('Accuracy')
	pyplot.plot(history.history['accuracy'], label='train')
	pyplot.plot(history.history['val_accuracy'], label='test')
	pyplot.legend()
	pyplot.show()

# prepare data
trainX, trainy, testX, testy = samples_for_seed(2)
# fit model on train dataset
model, history = fit_model(trainX, trainy, testX, testy)
# evaluate model behavior
summarize_model(model, history, trainX, trainy, testX, testy)

L'exécution de l'exemple s'adapte et évalue les performances du modèle, en imprimant la précision de la classification 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, nous pouvons voir que le modèle a bien fonctionné sur le problème 2, mais pas aussi bien que sur le problème 1, atteignant une précision de classification d'environ 79 % sur les ensembles de données d'entraînement et de test.

Train: 0.794, Test: 0.794

Une figure est également créée résumant les courbes d’apprentissage du modèle. Votre tracé n’est peut-être pas identique, mais il est censé montrer le même comportement général. Sinon, essayez d'exécuter l'exemple plusieurs fois.

Dans ce cas, nous pouvons voir que le modèle a convergé plus lentement que ce que nous avons vu pour le problème 1 de la section précédente. Cela suggère que cette version du problème pourrait être légèrement plus complexe, du moins pour la configuration du modèle choisi.

Maintenant que nous disposons d'une base de données de performance et de dynamique d'apprentissage pour un MLP sur le problème 2, nous pouvons voir comment l'ajout de l'apprentissage par transfert affecte le MLP sur ce problème.

MLP avec apprentissage par transfert pour le problème 2

Le modèle adapté au problème 1 peut être chargé et les poids peuvent être utilisés comme poids initiaux pour un modèle adapté au problème 2.

Il s'agit d'un type d'apprentissage par transfert dans lequel l'apprentissage sur un problème différent mais connexe est utilisé comme type de schéma d'initialisation de poids.

Cela nécessite que la fonction fit_model() soit mise à jour pour charger le modèle et le réajuster sur les exemples du problème 2.

Le modèle enregistré dans 'model.h5' peut être chargé à l'aide de la fonction Keras load_model().

# load model
model = load_model('model.h5')

Une fois chargé, le modèle peut être compilé et ajusté normalement.

Le fit_model() mis à jour avec cette modification est répertorié ci-dessous.

# load and re-fit model on a training dataset
def fit_model(trainX, trainy, testX, testy):
	# load model
	model = load_model('model.h5')
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# re-fit model
	history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
	return model, history

Nous nous attendrions à ce qu'un modèle qui utilise les poids d'un modèle adapté à un problème différent mais connexe apprenne le problème peut-être plus rapidement en termes de courbe d'apprentissage et entraîne peut-être une erreur de généralisation plus faible, bien que ces aspects dépendent du choix de problèmes et modèle.

Par souci d’exhaustivité, l’exemple complet avec ce changement est répertorié ci-dessous.

# transfer learning with mlp model on problem 2
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
from keras.models import load_model
from matplotlib import pyplot

# prepare a blobs examples with a given random seed
def samples_for_seed(seed):
	# generate samples
	X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
	# one hot encode output variable
	y = to_categorical(y)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	return trainX, trainy, testX, testy

# load and re-fit model on a training dataset
def fit_model(trainX, trainy, testX, testy):
	# load model
	model = load_model('model.h5')
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# re-fit model
	history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
	return model, history

# summarize the performance of the fit model
def summarize_model(model, history, trainX, trainy, testX, testy):
	# 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 loss during training
	pyplot.subplot(211)
	pyplot.title('Loss')
	pyplot.plot(history.history['loss'], label='train')
	pyplot.plot(history.history['val_loss'], label='test')
	pyplot.legend()
	# plot accuracy during training
	pyplot.subplot(212)
	pyplot.title('Accuracy')
	pyplot.plot(history.history['accuracy'], label='train')
	pyplot.plot(history.history['val_accuracy'], label='test')
	pyplot.legend()
	pyplot.show()

# prepare data
trainX, trainy, testX, testy = samples_for_seed(2)
# fit model on train dataset
model, history = fit_model(trainX, trainy, testX, testy)
# evaluate model behavior
summarize_model(model, history, trainX, trainy, testX, testy)

L'exécution de l'exemple s'adapte et évalue les performances du modèle, en imprimant la précision de la classification 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, nous pouvons voir que le modèle a atteint une erreur de généralisation plus faible, atteignant une précision d'environ 81 % sur l'ensemble de données de test pour le problème 2 par rapport au modèle autonome qui a atteint une précision d'environ 79 %.

Train: 0.786, Test: 0.810

Une figure est également créée résumant les courbes d’apprentissage du modèle. Votre tracé n’est peut-être pas identique, mais il est censé montrer le même comportement général. Sinon, essayez d'exécuter l'exemple plusieurs fois.

Dans ce cas, nous pouvons voir que le modèle semble avoir une courbe d'apprentissage similaire, bien que nous constations des améliorations apparentes dans la courbe d'apprentissage pour l'ensemble de test (ligne orange) à la fois en termes de meilleures performances plus tôt (à partir de l'époque 20) et au-dessus des performances du modèle sur l’ensemble d’entraînement.

Nous n'avons examiné que des exécutions uniques d'un modèle MLP autonome et d'un MLP avec apprentissage par transfert.

Les algorithmes de réseaux neuronaux sont stochastiques, c'est pourquoi une moyenne des performances sur plusieurs exécutions est nécessaire pour voir si le comportement observé est réel ou s'il s'agit d'un hasard statistique.

Comparaison des modèles sur le problème 2

Afin de déterminer si l'utilisation de l'apprentissage par transfert pour le problème de classification multi-classes des blobs a un effet réel, nous devons répéter chaque expérience plusieurs fois et analyser les performances moyennes au fil des répétitions.

Nous comparerons les performances du modèle autonome formé sur le problème 2 à un modèle utilisant l'apprentissage par transfert, en moyenne sur 30 répétitions.

De plus, nous examinerons si le maintien des poids fixes dans certaines couches améliore les performances du modèle.

Le modèle formé sur le problème 1 comporte deux couches cachées. En gardant fixes la première ou la première et la deuxième couches cachées, les couches avec des poids non modifiables agiront comme un extracteur de caractéristiques et pourront fournir des fonctionnalités qui faciliteront l'apprentissage du problème 2, affectant la vitesse d'apprentissage et/ou la précision du modèle sur le terrain. ensemble de tests.

Dans un premier temps, nous allons simplifier la fonction fit_model() pour ajuster le modèle et supprimer tout historique d'entraînement afin de pouvoir nous concentrer sur la précision finale du modèle entraîné.

# define and fit model on a training dataset
def fit_model(trainX, trainy):
	# 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(3, activation='softmax'))
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# fit model
	model.fit(trainX, trainy, epochs=100, verbose=0)
	return model

Ensuite, nous pouvons développer une fonction qui adaptera à plusieurs reprises un nouveau modèle autonome au problème 2 sur l'ensemble de données d'entraînement et évaluera la précision sur l'ensemble de test.

La fonction eval_standalone_model() ci-dessous implémente cela, en prenant les ensembles d'entraînement et de test comme arguments ainsi que le nombre de répétitions et renvoie une liste de scores de précision pour les modèles sur l'ensemble de données de test.

# repeated evaluation of a standalone model
def eval_standalone_model(trainX, trainy, testX, testy, n_repeats):
	scores = list()
	for _ in range(n_repeats):
		# define and fit a new model on the train dataset
		model = fit_model(trainX, trainy)
		# evaluate model on test dataset
		_, test_acc = model.evaluate(testX, testy, verbose=0)
		scores.append(test_acc)
	return scores

Résumer la distribution des scores de précision renvoyés par cette fonction donnera une idée de la performance du modèle autonome choisi sur le problème 2.

# repeated evaluation of standalone model
standalone_scores = eval_standalone_model(trainX, trainy, testX, testy, n_repeats)
print('Standalone %.3f (%.3f)' % (mean(standalone_scores), std(standalone_scores)))

Ensuite, nous avons besoin d'une fonction équivalente pour évaluer un modèle à l'aide de l'apprentissage par transfert.

Dans chaque boucle, le modèle entraîné sur le problème 1 doit être chargé à partir du fichier, adapté à l'ensemble de données d'entraînement pour le problème 2, puis évalué sur l'ensemble de test pour le problème 2.

De plus, nous configurerons 0, 1 ou 2 des couches cachées du modèle chargé pour qu'elles restent fixes. Garder 0 couche cachée fixe signifie que tous les poids du modèle seront adaptés lors de l'apprentissage du problème 2, en utilisant l'apprentissage par transfert comme schéma d'initialisation des poids. Tandis que garder les deux (2) couches cachées fixes signifie que seule la couche de sortie du modèle sera adaptée pendant la formation, en utilisant l'apprentissage par transfert comme méthode d'extraction de fonctionnalités.

La fonction eval_transfer_model() ci-dessous implémente cela, en prenant les ensembles de données d'entraînement et de test pour le problème 2 comme arguments ainsi que le nombre de couches cachées dans le modèle chargé à conserver et le nombre de fois pour répéter l'opération. expérience.

La fonction renvoie une liste de scores de précision des tests et résumer cette distribution donnera une idée raisonnable de l'efficacité du modèle avec le type d'apprentissage par transfert choisi sur le problème 2.

# repeated evaluation of a model with transfer learning
def eval_transfer_model(trainX, trainy, testX, testy, n_fixed, n_repeats):
	scores = list()
	for _ in range(n_repeats):
		# load model
		model = load_model('model.h5')
		# mark layer weights as fixed or not trainable
		for i in range(n_fixed):
			model.layers[i].trainable = False
		# re-compile model
		model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
		# fit model on train dataset
		model.fit(trainX, trainy, epochs=100, verbose=0)
		# evaluate model on test dataset
		_, test_acc = model.evaluate(testX, testy, verbose=0)
		scores.append(test_acc)
	return scores

Nous pouvons appeler cette fonction à plusieurs reprises, en définissant n_fixed sur 0, 1, 2 dans une boucle et en résumant les performances au fur et à mesure ; Par exemple:

# repeated evaluation of transfer learning model, vary fixed layers
n_fixed = 3
for i in range(n_fixed):
	scores = eval_transfer_model(trainX, trainy, testX, testy, i, n_repeats)
	print('Transfer (fixed=%d) %.3f (%.3f)' % (i, mean(scores), std(scores)))

En plus de rapporter la moyenne et l'écart type de chaque modèle, nous pouvons collecter tous les scores et créer un diagramme en boîte et en moustaches pour résumer et comparer les distributions des scores du modèle.

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

# compare standalone mlp model performance to transfer learning
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
from keras.models import load_model
from matplotlib import pyplot
from numpy import mean
from numpy import std

# prepare a blobs examples with a given random seed
def samples_for_seed(seed):
	# generate samples
	X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
	# one hot encode output variable
	y = to_categorical(y)
	# split into train and test
	n_train = 500
	trainX, testX = X[:n_train, :], X[n_train:, :]
	trainy, testy = y[:n_train], y[n_train:]
	return trainX, trainy, testX, testy

# define and fit model on a training dataset
def fit_model(trainX, trainy):
	# 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(3, activation='softmax'))
	# compile model
	model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
	# fit model
	model.fit(trainX, trainy, epochs=100, verbose=0)
	return model

# repeated evaluation of a standalone model
def eval_standalone_model(trainX, trainy, testX, testy, n_repeats):
	scores = list()
	for _ in range(n_repeats):
		# define and fit a new model on the train dataset
		model = fit_model(trainX, trainy)
		# evaluate model on test dataset
		_, test_acc = model.evaluate(testX, testy, verbose=0)
		scores.append(test_acc)
	return scores

# repeated evaluation of a model with transfer learning
def eval_transfer_model(trainX, trainy, testX, testy, n_fixed, n_repeats):
	scores = list()
	for _ in range(n_repeats):
		# load model
		model = load_model('model.h5')
		# mark layer weights as fixed or not trainable
		for i in range(n_fixed):
			model.layers[i].trainable = False
		# re-compile model
		model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
		# fit model on train dataset
		model.fit(trainX, trainy, epochs=100, verbose=0)
		# evaluate model on test dataset
		_, test_acc = model.evaluate(testX, testy, verbose=0)
		scores.append(test_acc)
	return scores

# prepare data for problem 2
trainX, trainy, testX, testy = samples_for_seed(2)
n_repeats = 30
dists, dist_labels = list(), list()

# repeated evaluation of standalone model
standalone_scores = eval_standalone_model(trainX, trainy, testX, testy, n_repeats)
print('Standalone %.3f (%.3f)' % (mean(standalone_scores), std(standalone_scores)))
dists.append(standalone_scores)
dist_labels.append('standalone')

# repeated evaluation of transfer learning model, vary fixed layers
n_fixed = 3
for i in range(n_fixed):
	scores = eval_transfer_model(trainX, trainy, testX, testy, i, n_repeats)
	print('Transfer (fixed=%d) %.3f (%.3f)' % (i, mean(scores), std(scores)))
	dists.append(scores)
	dist_labels.append('transfer f='+str(i))

# box and whisker plot of score distributions
pyplot.boxplot(dists, labels=dist_labels)
pyplot.show()

L’exécution de l’exemple rapporte d’abord la moyenne et l’écart type de la précision de la classification sur l’ensemble de données de test pour chaque modèle.

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 le modèle autonome a atteint une précision d'environ 78 % sur le problème 2 avec un écart type important de 10 %. En revanche, nous pouvons constater que la diffusion de tous les modèles d’apprentissage par transfert est beaucoup plus petite, allant d’environ 0,05 % à 1,5 %.

Cette différence dans les écarts types des scores de précision des tests montre la stabilité que l'apprentissage par transfert peut apporter au modèle, réduisant ainsi la variance des performances du modèle final introduit via l'algorithme d'apprentissage stochastique.

En comparant la précision moyenne des tests des modèles, nous pouvons voir que l'apprentissage par transfert utilisant le modèle comme schéma d'initialisation de poids (fixe=0) a abouti à de meilleures performances que le modèle autonome avec une précision d'environ 80 %.

Garder toutes les couches cachées fixes (fixed=2) et les utiliser comme schéma d'extraction de fonctionnalités entraînait des performances moyennes inférieures à celles du modèle autonome. Cela suggère que l’approche est trop restrictive dans ce cas.

Fait intéressant, nous observons de meilleures performances lorsque la première couche cachée reste fixe (fixed=1) et que la deuxième couche cachée est adaptée au problème avec une précision de classification des tests d'environ 81 %. Cela suggère que dans ce cas, le problème bénéficie à la fois des propriétés d’extraction de caractéristiques et d’initialisation de poids de l’apprentissage par transfert.

Il peut être intéressant de voir comment les résultats de cette dernière approche se comparent au même modèle où les poids de la deuxième couche cachée (et peut-être de la couche de sortie) sont réinitialisés avec des nombres aléatoires. Cette comparaison démontrerait si les propriétés d'extraction de caractéristiques de l'apprentissage par transfert seules ou si les propriétés d'extraction de caractéristiques et d'initialisation de poids sont bénéfiques.

Standalone 0.787 (0.101)
Transfer (fixed=0) 0.805 (0.004)
Transfer (fixed=1) 0.817 (0.005)
Transfer (fixed=2) 0.750 (0.014)

Une figure est créée montrant quatre diagrammes en boîtes et moustaches. L'encadré montre les 50 % du milieu de chaque distribution de données, la ligne orange montre la médiane et les points montrent les valeurs aberrantes.

Le diagramme en boîte du modèle autonome montre un certain nombre de valeurs aberrantes, ce qui indique qu'en moyenne, le modèle fonctionne bien, mais il est possible qu'il fonctionne très mal.

À l’inverse, nous constatons que le comportement des modèles avec apprentissage par transfert est plus stable, montrant une répartition des performances plus serrée.

Rallonges

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

  • Expérience inversée. Entraînez et enregistrez un modèle pour le problème 2 et voyez s'il peut être utile lors de son utilisation pour l'apprentissage par transfert sur le problème 1.
  • Ajouter un calque caché. Mettez à jour l'exemple pour conserver les deux couches cachées fixes, mais ajoutez une nouvelle couche cachée avec des poids initialisés de manière aléatoire après les couches fixes avant la couche de sortie et comparez les performances.
  • Initialiser les calques de manière aléatoire. Mettez à jour l'exemple pour initialiser de manière aléatoire les poids de la deuxième couche cachée et de la couche de sortie et comparer les performances.

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'apprentissage par transfert pour le Deep Learning
  • Comment développer un générateur de légendes de photos d'apprentissage profond à partir de zéro

Papiers

  • Apprentissage profond des représentations pour l'apprentissage non supervisé et par transfert, 2011.
  • Adaptation de domaine pour la classification des sentiments à grande échelle : une approche d'apprentissage en profondeur, 2011.
  • Apprendre la nième chose est-il plus facile que d'apprendre la première ?, 1996.

Livres

  • Section 15.2 Apprentissage par transfert et adaptation de domaine, Deep Learning, 2016.

Articles

  • Apprentissage par transfert, Wikipédia

Résumé

Dans ce didacticiel, vous avez découvert comment utiliser l'apprentissage par transfert pour améliorer les performances des réseaux de neurones d'apprentissage profond en Python avec Keras.

Concrètement, vous avez appris :

  • L'apprentissage par transfert est une méthode permettant de réutiliser un modèle formé sur un problème de modélisation prédictive connexe.
  • L'apprentissage par transfert peut être utilisé pour accélérer la formation de réseaux de neurones, soit sous la forme d'un schéma d'initialisation de poids, soit d'une méthode d'extraction de caractéristiques.
  • Comment utiliser l'apprentissage par transfert pour améliorer les performances d'un MLP pour un problème de classification multiclasse.

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

Articles connexes