Recherche de site Web

Comment créer un ensemble de modèles d'apprentissage profond dans Keras


L'apprentissage d'ensemble est une méthode qui combine les prédictions de plusieurs modèles.

Il est important dans l’apprentissage d’ensemble que les modèles qui composent l’ensemble soient bons, et qu’ils génèrent différentes erreurs de prédiction. Les prédictions qui sont bonnes à différents égards peuvent aboutir à une prédiction à la fois plus stable et souvent meilleure que les prédictions de n'importe quel modèle de membre individuel.

Une façon d'obtenir des différences entre les modèles consiste à entraîner chaque modèle sur un sous-ensemble différent des données d'entraînement disponibles. Les modèles sont naturellement entraînés sur différents sous-ensembles de données d'entraînement grâce à l'utilisation de méthodes de rééchantillonnage telles que la validation croisée et le bootstrap, conçues pour estimer les performances moyennes du modèle généralement sur des données invisibles. Les modèles utilisés dans ce processus d'estimation peuvent être combinés dans ce que l'on appelle un ensemble basé sur le rééchantillonnage, tel qu'un ensemble de validation croisée ou un ensemble d'agrégation bootstrap (ou d'ensachage).

Dans ce didacticiel, vous découvrirez comment développer une suite de différents ensembles basés sur le rééchantillonnage pour les modèles de réseaux neuronaux d'apprentissage profond.

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

  • Comment estimer les performances d'un modèle à l'aide de répartitions aléatoires et développer un ensemble à partir des modèles.
  • Comment estimer les performances à l'aide d'une validation croisée 10 fois et développer un ensemble de validation croisée.
  • Comment estimer les performances à l'aide du bootstrap et combiner des modèles à l'aide d'un ensemble d'ensachage.

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

Commençons.

  • Mise à jour en octobre 2019 : mise à jour pour Keras 2.3 et TensorFlow 2.0.
  • Mise à jour en 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. Ensembles de rééchantillonnage de données
  2. Problème de classification multi-classes
  3. Modèle de perceptron multicouche unique
  4. Ensemble de fractionnements aléatoires
  5. Ensemble de validation croisée
  6. Ensemble d'ensachage

Ensembles de rééchantillonnage de données

La combinaison des prédictions de plusieurs modèles peut aboutir à des prédictions plus stables et, dans certains cas, à des prédictions offrant de meilleures performances que n'importe lequel des modèles contributifs.

Les ensembles efficaces ont besoin de membres qui ne sont pas d'accord. Chaque membre doit avoir des compétences (par exemple, mieux performer que le hasard), mais idéalement, bien performer de différentes manières. Techniquement, nous pouvons dire que nous préférons que les membres de l’ensemble aient une faible corrélation dans leurs prédictions, ou erreurs de prédiction.

Une approche pour encourager les différences entre les ensembles consiste à utiliser le même algorithme d'apprentissage sur différents ensembles de données de formation. Ceci peut être réalisé en rééchantillonnant à plusieurs reprises un ensemble de données de formation qui est à son tour utilisé pour former un nouveau modèle. Plusieurs modèles sont ajustés en utilisant des perspectives légèrement différentes sur les données d'entraînement et, à leur tour, génèrent différentes erreurs et souvent des prédictions plus stables et meilleures lorsqu'elles sont combinées.

Nous pouvons généralement appeler ces méthodes des ensembles de rééchantillonnage de données.

L'un des avantages de cette approche est que des méthodes de rééchantillonnage peuvent être utilisées sans utiliser tous les exemples de l'ensemble de données de formation. Tous les exemples qui ne sont pas utilisés pour ajuster le modèle peuvent être utilisés comme ensemble de données de test pour estimer l'erreur de généralisation de la configuration du modèle choisi.

Il existe trois méthodes de rééchantillonnage populaires que nous pourrions utiliser pour créer un ensemble de rééchantillonnage : ils sont:

  • Répartitions aléatoires. L'ensemble de données est échantillonné à plusieurs reprises avec une répartition aléatoire des données en ensembles d'entraînement et de test.
  • Validation croisée k-fold. L'ensemble de données est divisé en k plis de taille égale, k modèles sont formés et chaque pli a la possibilité d'être utilisé comme ensemble d'exclusion où le modèle est formé sur tous les plis restants.
  • Agrégation Bootstrap. Des échantillons aléatoires sont collectés avec remplacement et des exemples non inclus dans un échantillon donné sont utilisés comme ensemble de test.

La méthode d’ensemble de rééchantillonnage la plus largement utilisée est peut-être l’agrégation bootstrap, plus communément appelée bagging. Le rééchantillonnage avec remplacement permet plus de différence dans l'ensemble de données d'entraînement, biaisant le modèle et, par conséquent, entraînant plus de différence entre les prédictions des modèles résultants.

Le rééchantillonnage des modèles d'ensemble émet des hypothèses spécifiques sur votre projet :

  • Qu'une estimation robuste des performances du modèle sur des données invisibles est nécessaire ; sinon, une seule répartition train/test peut être utilisée.
  • Qu’il existe un potentiel d’amélioration des performances en utilisant un ensemble de modèles ; dans le cas contraire, un modèle unique adapté à toutes les données disponibles peut être utilisé.
  • Que le coût de calcul nécessaire à l'ajustement de plusieurs modèles de réseau neuronal sur un échantillon de l'ensemble de données de formation n'est pas prohibitif ; sinon, toutes les ressources devraient être consacrées à l’adaptation d’un modèle unique.

Les modèles de réseaux neuronaux sont remarquablement flexibles, par conséquent l'amélioration des performances fournie par un ensemble de rééchantillonnage n'est pas toujours possible étant donné que les modèles individuels formés sur toutes les données disponibles peuvent être si performants.

En tant que tel, le point idéal pour utiliser un ensemble de rééchantillonnage est le cas où il existe une exigence d'une estimation robuste des performances et où plusieurs modèles peuvent être adaptés pour calculer l'estimation, mais il existe également une exigence pour un (ou plusieurs) des modèles créés lors de l'estimation des performances pour être utilisés comme modèle final (par exemple, un nouveau modèle final ne peut pas être adapté à toutes les données d'entraînement disponibles).

Maintenant que nous sommes familiarisés avec les méthodes de rééchantillonnage d’ensemble, nous pouvons examiner un exemple d’application de chaque méthode tour à tour.

Problème de classification multi-classes

Nous utiliserons un petit problème de classification multi-classes comme base pour démontrer un modèle de rééchantillonnage d'ensembles.

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 utilisons ce problème avec 1 000 exemples, avec des 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 du générateur de nombres pseudo-aléatoires) pour garantir que nous obtenons toujours les mêmes 1 000 points.

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

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

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.

# scatter plot of blobs dataset
from sklearn.datasets import make_blobs
from matplotlib import pyplot
from pandas import DataFrame
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# scatter plot, dots colored by class value
df = DataFrame(dict(x=X[:,0], y=X[:,1], label=y))
colors = {0:'red', 1:'blue', 2:'green'}
fig, ax = pyplot.subplots()
grouped = df.groupby('label')
for key, group in grouped:
    group.plot(ax=ax, kind='scatter', x='x', y='y', label=key, color=colors[key])
pyplot.show()

L’exécution de l’exemple crée un nuage de points de l’ensemble de données. On voit que l'écart type de 2,0 signifie que les classes ne sont pas linéairement séparables (séparables par une ligne) provoquant de nombreux points ambigus.

Ceci est souhaitable car cela signifie que le problème n'est pas trivial et permettra à un modèle de réseau neuronal de trouver de nombreuses solutions candidates différentes « assez bonnes », ce qui entraîne une variance élevée.

Modèle de perceptron multicouche unique

Nous définirons un réseau neuronal Multilayer Perceptron, ou MLP, qui apprend raisonnablement bien le problème.

Le problème est un problème de classification multi-classes, et nous le modéliserons en utilisant une fonction d'activation softmax sur la couche de sortie. Cela signifie que le modèle prédira un vecteur à 3 éléments avec la probabilité que l'échantillon appartienne à chacune des 3 classes. Par conséquent, la première étape consiste à coder à chaud les valeurs de classe.

y = to_categorical(y)

Ensuite, nous devons diviser l'ensemble de données en ensembles de formation et de test. Nous utiliserons l'ensemble de tests à la fois pour évaluer les performances du modèle et pour tracer ses performances pendant l'entraînement avec une courbe d'apprentissage. Nous utiliserons 90 % des données pour la formation et 10 % pour l’ensemble de tests.

Nous choisissons une grande division car il s'agit d'un problème bruyant et qu'un modèle performant nécessite autant de données que possible pour apprendre la fonction de classification complexe.

# split into train and test
n_train = int(0.9 * X.shape[0])
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

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

Le modèle attendra des échantillons avec deux variables d'entrée. Le modèle comporte alors une seule couche cachée à 50 nœuds et une fonction d'activation linéaire rectifiée, puis une couche de sortie à 3 nœuds pour prédire la probabilité de chacune des 3 classes, et une fonction d'activation softmax.

Étant donné que le problème est multi-classe, nous utiliserons la fonction de perte d'entropie croisée catégorique pour optimiser le modèle et la saveur Adam efficace de la descente de gradient stochastique.

# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

Le modèle est adapté à 50 époques de formation et nous évaluerons le modèle à chaque époque sur l'ensemble de test, en utilisant l'ensemble de test comme ensemble de validation.

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

À la fin de l'exécution, nous évaluerons les performances du modèle sur le train et sur les ensembles de test.

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

Enfin, nous tracerons les courbes d'apprentissage de la précision du modèle sur chaque époque d'entraînement sur les ensembles de données d'entraînement et de test.

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

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

# develop an mlp for blobs dataset
from sklearn.datasets import make_blobs
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# one hot encode output variable
y = to_categorical(y)
# split into train and test
n_train = int(0.9 * X.shape[0])
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=50, 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))
# learning curves of model accuracy
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 final 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 le modèle a atteint une précision d'environ 83 % sur l'ensemble de données d'entraînement et d'environ 86 % de précision sur l'ensemble de données de test.

La division choisie de l'ensemble de données en ensembles d'entraînement et de test signifie que l'ensemble de test est petit et n'est pas représentatif du problème plus large. À leur tour, les performances sur l’ensemble de test ne sont pas représentatives du modèle ; dans ce cas, il s’agit d’un biais optimiste.

Train: 0.830, Test: 0.860

Un tracé linéaire est également créé montrant les courbes d'apprentissage pour la précision du modèle sur le train et les ensembles de test au cours de chaque époque de formation.

Nous pouvons voir que le modèle présente un ajustement raisonnablement stable.

Ensemble de fractionnements aléatoires

L’instabilité du modèle et le petit ensemble de données de test signifient que nous ne savons pas vraiment dans quelle mesure ce modèle fonctionnera sur les nouvelles données en général.

Nous pouvons essayer une méthode de rééchantillonnage simple consistant à générer à plusieurs reprises de nouvelles divisions aléatoires de l'ensemble de données dans des ensembles d'entraînement et de test et à adapter de nouveaux modèles. Le calcul de la moyenne des performances du modèle pour chaque division donnera une meilleure estimation de l’erreur de généralisation du modèle.

Nous pouvons ensuite combiner plusieurs modèles formés sur les divisions aléatoires en espérant que les performances de l'ensemble seront probablement plus stables et meilleures que le modèle unique moyen.

Nous générerons 10 fois plus de points d'échantillonnage à partir du domaine problématique et les conserverons en tant qu'ensemble de données invisibles. L'évaluation d'un modèle sur cet ensemble de données beaucoup plus vaste sera utilisée comme proxy ou comme estimation beaucoup plus précise de l'erreur de généralisation d'un modèle pour ce problème.

Cet ensemble de données supplémentaire n'est pas un ensemble de données de test. Techniquement, c'est pour les besoins de cette démonstration, mais nous prétendons que ces données ne sont pas disponibles au moment de la formation du modèle.

# generate 2d classification dataset
dataX, datay = make_blobs(n_samples=55000, centers=3, n_features=2, cluster_std=2, random_state=2)
X, newX = dataX[:5000, :], dataX[5000:, :]
y, newy = datay[:5000], datay[5000:]

Nous disposons donc désormais de 5 000 exemples pour entraîner notre modèle et estimer ses performances générales. Nous disposons également de 50 000 exemples que nous pouvons utiliser pour mieux nous rapprocher de la véritable performance générale d'un modèle unique ou d'un ensemble.

Ensuite, nous avons besoin d'une fonction pour ajuster et évaluer un modèle unique sur un ensemble de données d'entraînement et renvoyer les performances du modèle d'ajustement sur un ensemble de données de test. Nous avons également besoin du modèle adapté pour pouvoir l’utiliser dans le cadre d’un ensemble. La fonction évalue_model() ci-dessous implémente ce comportement.

# evaluate a single mlp model
def evaluate_model(trainX, trainy, testX, testy):
	# encode targets
	trainy_enc = to_categorical(trainy)
	testy_enc = to_categorical(testy)
	# define model
	model = Sequential()
	model.add(Dense(50, input_dim=2, activation='relu'))
	model.add(Dense(3, activation='softmax'))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
	# fit model
	model.fit(trainX, trainy_enc, epochs=50, verbose=0)
	# evaluate the model
	_, test_acc = model.evaluate(testX, testy_enc, verbose=0)
	return model, test_acc

Ensuite, nous pouvons créer des divisions aléatoires de l'ensemble de données d'entraînement et ajuster et évaluer les modèles sur chaque division.

Nous pouvons utiliser la fonction train_test_split() de la bibliothèque scikit-learn pour créer une division aléatoire d'un ensemble de données en ensembles d'entraînement et de test. Il prend les tableaux X et y comme arguments et « test_size » spécifie la taille de l'ensemble de données de test en termes de pourcentage. Nous utiliserons 10 % des 5 000 exemples comme test.

Nous pouvons ensuite appeler évalue_model() pour ajuster et évaluer un modèle. La précision et le modèle renvoyés peuvent ensuite être ajoutés aux listes pour une utilisation ultérieure.

Dans cet exemple, nous limiterons le nombre de divisions, et donc le nombre de modèles d’ajustement, à 10.

# multiple train-test splits
n_splits = 10
scores, members = list(), list()
for _ in range(n_splits):
	# split data
	trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.10)
	# evaluate model
	model, test_acc = evaluate_model(trainX, trainy, testX, testy)
	print('>%.3f' % test_acc)
	scores.append(test_acc)
	members.append(model)

Après avoir ajusté et évalué les modèles, nous pouvons estimer les performances attendues d'un modèle donné avec la configuration choisie pour le domaine.

# summarize expected performance
print('Estimated Accuracy %.3f (%.3f)' % (mean(scores), std(scores)))

Nous ne savons pas combien de modèles seront utiles dans l’ensemble. Il est probable qu’il y aura un point de rendement décroissant, après lequel l’ajout de nouveaux membres ne modifiera plus la performance de l’ensemble.

Néanmoins, nous pouvons évaluer différentes tailles d'ensemble de 1 à 10 et tracer leurs performances sur l'ensemble de données invisibles.

Nous pouvons également évaluer chaque modèle sur l'ensemble de données retenu et calculer la moyenne de ces scores pour obtenir une bien meilleure approximation des performances réelles du modèle choisi sur le problème de prédiction.

# evaluate different numbers of ensembles on hold out set
single_scores, ensemble_scores = list(), list()
for i in range(1, n_splits+1):
	ensemble_score = evaluate_n_members(members, i, newX, newy)
	newy_enc = to_categorical(newy)
	_, single_score = members[i-1].evaluate(newX, newy_enc, verbose=0)
	print('> %d: single=%.3f, ensemble=%.3f' % (i, single_score, ensemble_score))
	ensemble_scores.append(ensemble_score)
	single_scores.append(single_score)

Enfin, nous pouvons comparer et calculer une estimation plus robuste des performances générales d'un modèle moyen sur le problème de prédiction, puis tracer les performances de la taille de l'ensemble avec précision sur l'ensemble de données d'exclusion.

# plot score vs number of ensemble members
print('Accuracy %.3f (%.3f)' % (mean(single_scores), std(single_scores)))
x_axis = [i for i in range(1, n_splits+1)]
pyplot.plot(x_axis, single_scores, marker='o', linestyle='None')
pyplot.plot(x_axis, ensemble_scores, marker='o')
pyplot.show()

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

# random-splits mlp ensemble on blobs dataset
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
from numpy import mean
from numpy import std
import numpy
from numpy import array
from numpy import argmax

# evaluate a single mlp model
def evaluate_model(trainX, trainy, testX, testy):
	# encode targets
	trainy_enc = to_categorical(trainy)
	testy_enc = to_categorical(testy)
	# define model
	model = Sequential()
	model.add(Dense(50, input_dim=2, activation='relu'))
	model.add(Dense(3, activation='softmax'))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
	# fit model
	model.fit(trainX, trainy_enc, epochs=50, verbose=0)
	# evaluate the model
	_, test_acc = model.evaluate(testX, testy_enc, verbose=0)
	return model, test_acc

# make an ensemble prediction for multi-class classification
def ensemble_predictions(members, testX):
	# make predictions
	yhats = [model.predict(testX) for model in members]
	yhats = array(yhats)
	# sum across ensemble members
	summed = numpy.sum(yhats, axis=0)
	# argmax across classes
	result = argmax(summed, axis=1)
	return result

# evaluate a specific number of members in an ensemble
def evaluate_n_members(members, n_members, testX, testy):
	# select a subset of members
	subset = members[:n_members]
	# make prediction
	yhat = ensemble_predictions(subset, testX)
	# calculate accuracy
	return accuracy_score(testy, yhat)

# generate 2d classification dataset
dataX, datay = make_blobs(n_samples=55000, centers=3, n_features=2, cluster_std=2, random_state=2)
X, newX = dataX[:5000, :], dataX[5000:, :]
y, newy = datay[:5000], datay[5000:]
# multiple train-test splits
n_splits = 10
scores, members = list(), list()
for _ in range(n_splits):
	# split data
	trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.10)
	# evaluate model
	model, test_acc = evaluate_model(trainX, trainy, testX, testy)
	print('>%.3f' % test_acc)
	scores.append(test_acc)
	members.append(model)
# summarize expected performance
print('Estimated Accuracy %.3f (%.3f)' % (mean(scores), std(scores)))
# evaluate different numbers of ensembles on hold out set
single_scores, ensemble_scores = list(), list()
for i in range(1, n_splits+1):
	ensemble_score = evaluate_n_members(members, i, newX, newy)
	newy_enc = to_categorical(newy)
	_, single_score = members[i-1].evaluate(newX, newy_enc, verbose=0)
	print('> %d: single=%.3f, ensemble=%.3f' % (i, single_score, ensemble_score))
	ensemble_scores.append(ensemble_score)
	single_scores.append(single_score)
# plot score vs number of ensemble members
print('Accuracy %.3f (%.3f)' % (mean(single_scores), std(single_scores)))
x_axis = [i for i in range(1, n_splits+1)]
pyplot.plot(x_axis, single_scores, marker='o', linestyle='None')
pyplot.plot(x_axis, ensemble_scores, marker='o')
pyplot.show()

L'exécution de l'exemple ajuste et évalue d'abord 10 modèles sur 10 divisions aléatoires différentes de l'ensemble de données en ensembles d'entraînement et de test.

À partir de ces scores, nous estimons que l'ajustement moyen du modèle sur l'ensemble de données atteindra une précision d'environ 83 % avec un écart type d'environ 1,9 %.

>0.816
>0.836
>0.818
>0.806
>0.814
>0.824
>0.830
>0.848
>0.868
>0.858
Estimated Accuracy 0.832 (0.019)

Nous évaluons ensuite les performances de chaque modèle sur l'ensemble de données invisibles et les performances d'ensembles de modèles de 1 à 10 modèles.

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.

A partir de ces scores, nous pouvons voir qu'une estimation plus précise de la performance d'un modèle moyen sur ce problème est d'environ 82 % et que la performance estimée est optimiste.

> 1: single=0.821, ensemble=0.821
> 2: single=0.821, ensemble=0.820
> 3: single=0.820, ensemble=0.820
> 4: single=0.820, ensemble=0.821
> 5: single=0.821, ensemble=0.821
> 6: single=0.820, ensemble=0.821
> 7: single=0.820, ensemble=0.821
> 8: single=0.820, ensemble=0.821
> 9: single=0.820, ensemble=0.820
> 10: single=0.820, ensemble=0.821
Accuracy 0.820 (0.000)

Une grande partie de la différence entre les scores de précision se situe en fractions de pourcentage.

Un graphique est créé montrant la précision de chaque modèle individuel sur l'ensemble de données invisibles sous forme de points bleus et les performances d'un ensemble avec un nombre donné de membres de 1 à 10 sous forme de ligne et de points orange.

Nous pouvons voir que l’utilisation d’un ensemble de 4 à 8 membres, au moins dans ce cas, donne une précision meilleure que la plupart des exécutions individuelles (la ligne orange est au-dessus de nombreux points bleus).

Le graphique montre que certains modèles individuels peuvent être plus performants qu'un ensemble de modèles (points bleus au-dessus de la ligne orange), mais nous ne sommes pas en mesure de choisir ces modèles. Ici, nous démontrons que sans données supplémentaires (par exemple l'ensemble de données hors échantillon), un ensemble de 4 à 8 membres donnera en moyenne de meilleures performances qu'un modèle de test de train sélectionné au hasard.

Plus de répétitions (par exemple 30 ou 100) peuvent entraîner une performance d'ensemble plus stable.

Ensemble de validation croisée

Un problème avec les divisions aléatoires répétées comme méthode de rééchantillonnage pour estimer les performances moyennes du modèle est qu'elle est optimiste.

Une approche conçue pour être moins optimiste et qui est par conséquent largement utilisée est la méthode de validation croisée k-fold.

La méthode est moins biaisée car chaque exemple de l'ensemble de données n'est utilisé qu'une seule fois dans l'ensemble de données de test pour estimer les performances du modèle, contrairement aux répartitions aléatoires des tests de train où un exemple donné peut être utilisé pour évaluer un modèle plusieurs fois.

La procédure comporte un seul paramètre appelé k qui fait référence au nombre de groupes dans lesquels un échantillon de données donné doit être divisé. La moyenne des scores de chaque modèle fournit une estimation moins biaisée des performances du modèle. Une valeur typique pour k est 10.

Étant donné que la formation des modèles de réseaux neuronaux est très coûteuse en termes de calcul, il est courant d'utiliser le modèle le plus performant lors de la validation croisée comme modèle final.

Alternativement, les modèles résultant du processus de validation croisée peuvent être combinés pour fournir un ensemble de validation croisée susceptible d'avoir de meilleures performances en moyenne qu'un modèle unique donné.

Nous pouvons utiliser la classe KFold de scikit-learn pour diviser l'ensemble de données en k plis. Il prend comme arguments le nombre de divisions, la nécessité ou non de mélanger l'échantillon, et la graine du générateur de nombres pseudo-aléatoires utilisé avant le mélange.

# prepare the k-fold cross-validation configuration
n_folds = 10
kfold = KFold(n_folds, True, 1)

Une fois la classe instanciée, elle peut être énumérée pour obtenir chaque division d'index dans l'ensemble de données pour les ensembles d'entraînement et de test.

# cross validation estimation of performance
scores, members = list(), list()
for train_ix, test_ix in kfold.split(X):
	# select samples
	trainX, trainy = X[train_ix], y[train_ix]
	testX, testy = X[test_ix], y[test_ix]
	# evaluate model
	model, test_acc = evaluate_model(trainX, trainy, testX, testy)
	print('>%.3f' % test_acc)
	scores.append(test_acc)
	members.append(model)

Une fois les scores calculés sur chaque pli, la moyenne des scores peut être utilisée pour rendre compte de la performance attendue de l'approche.

# summarize expected performance
print('Estimated Accuracy %.3f (%.3f)' % (mean(scores), std(scores)))

Maintenant que nous avons collecté les 10 modèles évalués sur les 10 plis, nous pouvons les utiliser pour créer un ensemble de validation croisée. Il semble intuitif d'utiliser les 10 modèles de l'ensemble, néanmoins, nous pouvons évaluer la précision de chaque sous-ensemble d'ensembles de 1 à 10 membres comme nous l'avons fait dans la section précédente.

L’exemple complet d’analyse de l’ensemble de validation croisée est répertorié ci-dessous.

# cross-validation mlp ensemble on blobs dataset
from sklearn.datasets import make_blobs
from sklearn.model_selection import KFold
from sklearn.metrics import accuracy_score
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
from numpy import mean
from numpy import std
import numpy
from numpy import array
from numpy import argmax

# evaluate a single mlp model
def evaluate_model(trainX, trainy, testX, testy):
	# encode targets
	trainy_enc = to_categorical(trainy)
	testy_enc = to_categorical(testy)
	# define model
	model = Sequential()
	model.add(Dense(50, input_dim=2, activation='relu'))
	model.add(Dense(3, activation='softmax'))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
	# fit model
	model.fit(trainX, trainy_enc, epochs=50, verbose=0)
	# evaluate the model
	_, test_acc = model.evaluate(testX, testy_enc, verbose=0)
	return model, test_acc

# make an ensemble prediction for multi-class classification
def ensemble_predictions(members, testX):
	# make predictions
	yhats = [model.predict(testX) for model in members]
	yhats = array(yhats)
	# sum across ensemble members
	summed = numpy.sum(yhats, axis=0)
	# argmax across classes
	result = argmax(summed, axis=1)
	return result

# evaluate a specific number of members in an ensemble
def evaluate_n_members(members, n_members, testX, testy):
	# select a subset of members
	subset = members[:n_members]
	# make prediction
	yhat = ensemble_predictions(subset, testX)
	# calculate accuracy
	return accuracy_score(testy, yhat)

# generate 2d classification dataset
dataX, datay = make_blobs(n_samples=55000, centers=3, n_features=2, cluster_std=2, random_state=2)
X, newX = dataX[:5000, :], dataX[5000:, :]
y, newy = datay[:5000], datay[5000:]
# prepare the k-fold cross-validation configuration
n_folds = 10
kfold = KFold(n_folds, True, 1)
# cross validation estimation of performance
scores, members = list(), list()
for train_ix, test_ix in kfold.split(X):
	# select samples
	trainX, trainy = X[train_ix], y[train_ix]
	testX, testy = X[test_ix], y[test_ix]
	# evaluate model
	model, test_acc = evaluate_model(trainX, trainy, testX, testy)
	print('>%.3f' % test_acc)
	scores.append(test_acc)
	members.append(model)
# summarize expected performance
print('Estimated Accuracy %.3f (%.3f)' % (mean(scores), std(scores)))
# evaluate different numbers of ensembles on hold out set
single_scores, ensemble_scores = list(), list()
for i in range(1, n_folds+1):
	ensemble_score = evaluate_n_members(members, i, newX, newy)
	newy_enc = to_categorical(newy)
	_, single_score = members[i-1].evaluate(newX, newy_enc, verbose=0)
	print('> %d: single=%.3f, ensemble=%.3f' % (i, single_score, ensemble_score))
	ensemble_scores.append(ensemble_score)
	single_scores.append(single_score)
# plot score vs number of ensemble members
print('Accuracy %.3f (%.3f)' % (mean(single_scores), std(single_scores)))
x_axis = [i for i in range(1, n_folds+1)]
pyplot.plot(x_axis, single_scores, marker='o', linestyle='None')
pyplot.plot(x_axis, ensemble_scores, marker='o')
pyplot.show()

L'exécution de l'exemple imprime d'abord les performances de chacun des 10 modèles sur chacun des plis de la validation croisée.

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.

La performance moyenne de ces modèles est d'environ 82 %, ce qui semble moins optimiste que l'approche de répartition aléatoire utilisée dans la section précédente.

>0.834
>0.870
>0.818
>0.806
>0.836
>0.804
>0.820
>0.830
>0.828
>0.822
Estimated Accuracy 0.827 (0.018)

Ensuite, chacun des modèles enregistrés est évalué sur l'ensemble d'exclusions invisible.

La moyenne de ces scores est également d'environ 82 %, soulignant que, au moins dans ce cas, l'estimation par validation croisée de la performance générale du modèle était raisonnable.

> 1: single=0.819, ensemble=0.819
> 2: single=0.820, ensemble=0.820
> 3: single=0.820, ensemble=0.820
> 4: single=0.821, ensemble=0.821
> 5: single=0.820, ensemble=0.821
> 6: single=0.821, ensemble=0.821
> 7: single=0.820, ensemble=0.820
> 8: single=0.819, ensemble=0.821
> 9: single=0.820, ensemble=0.821
> 10: single=0.820, ensemble=0.821
Accuracy 0.820 (0.001)

Un graphique de la précision d'un modèle unique (points bleus) et de la taille de l'ensemble par rapport à la précision (ligne orange) est créé.

Comme dans l’exemple précédent, la véritable différence entre les performances des modèles se situe en fractions de pourcentage dans la précision du modèle.

La ligne orange montre qu’à mesure que le nombre de membres augmente, la précision de l’ensemble augmente jusqu’à un point de rendement décroissant.

Nous pouvons voir que, au moins dans ce cas, l'utilisation de quatre modèles ou plus adaptés lors de la validation croisée dans un ensemble donne de meilleures performances que presque tous les modèles individuels.

Nous pouvons également voir qu’une stratégie par défaut consistant à utiliser tous les modèles de l’ensemble serait efficace.

Ensemble d'ensachage

Une limitation des divisions aléatoires et de la validation croisée k fois du point de vue de l'apprentissage d'ensemble est que les modèles sont très similaires.

La méthode bootstrap est une technique statistique permettant d'estimer des quantités concernant une population en faisant la moyenne des estimations de plusieurs petits échantillons de données.

Il est important de noter que les échantillons sont construits en extrayant les observations d’un grand échantillon de données une par une et en les renvoyant à l’échantillon de données une fois qu’elles ont été choisies. Cela permet à une observation donnée d’être incluse plus d’une fois dans un petit échantillon donné. Cette approche de l’échantillonnage est appelée échantillonnage avec remise.

La méthode peut être utilisée pour estimer les performances des modèles de réseaux neuronaux. Les exemples non sélectionnés dans un échantillon donné peuvent être utilisés comme ensemble de tests pour estimer les performances du modèle.

Le bootstrap est une méthode robuste pour estimer les performances du modèle. Elle souffre un peu d'un biais optimiste, mais est souvent presque aussi précise que la validation croisée k fois dans la pratique.

L'avantage pour l'apprentissage d'ensemble est que chaque modèle est que chaque échantillon de données est biaisé, permettant à un exemple donné d'apparaître plusieurs fois dans l'échantillon. Cela signifie à son tour que les modèles formés sur ces échantillons seront biaisés, notamment de différentes manières. Le résultat peut être des prédictions d’ensemble plus précises.

Généralement, l'utilisation de la méthode bootstrap dans l'apprentissage d'ensemble est appelée agrégation bootstrap ou bagging.

Nous pouvons utiliser la fonction resample() de scikit-learn pour sélectionner un sous-échantillon avec remplacement. La fonction prend un tableau à sous-échantillonner et la taille du rééchantillonnage comme arguments. Nous effectuerons la sélection dans les indices de lignes que nous pourrons à leur tour utiliser pour sélectionner des lignes dans les tableaux X et y.

La taille de l'échantillon sera de 4 500, soit 90 % des données, bien que l'ensemble de tests puisse être supérieur à 10 % car, compte tenu du recours au rééchantillonnage, plus de 500 exemples peuvent avoir été laissés non sélectionnés.

# multiple train-test splits
n_splits = 10
scores, members = list(), list()
for _ in range(n_splits):
	# select indexes
	ix = [i for i in range(len(X))]
	train_ix = resample(ix, replace=True, n_samples=4500)
	test_ix = [x for x in ix if x not in train_ix]
	# select data
	trainX, trainy = X[train_ix], y[train_ix]
	testX, testy = X[test_ix], y[test_ix]
	# evaluate model
	model, test_acc = evaluate_model(trainX, trainy, testX, testy)
	print('>%.3f' % test_acc)
	scores.append(test_acc)
	members.append(model)

Il est courant d'utiliser des modèles de surajustement simples, comme des arbres de décision non élagués, lors de l'utilisation d'une stratégie d'apprentissage d'ensemble par bagging.

De meilleures performances peuvent être observées avec des réseaux neuronaux sur-contraints et sur-ajustés. Néanmoins, nous utiliserons le même MLP des sections précédentes dans cet exemple.

De plus, il est courant de continuer à ajouter des membres de l'ensemble dans l'ensachage jusqu'à ce que les plateaux d'ensemble soient atteints, car l'ensachage ne surajuste pas l'ensemble de données. Nous limiterons à nouveau le nombre de membres à 10 comme dans les exemples précédents.

L'exemple complet d'agrégation bootstrap pour estimer les performances du modèle et l'apprentissage d'ensemble avec un Perceptron multicouche est répertorié ci-dessous.

# bagging mlp ensemble on blobs dataset
from sklearn.datasets import make_blobs
from sklearn.utils import resample
from sklearn.metrics import accuracy_score
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
from numpy import mean
from numpy import std
import numpy
from numpy import array
from numpy import argmax

# evaluate a single mlp model
def evaluate_model(trainX, trainy, testX, testy):
	# encode targets
	trainy_enc = to_categorical(trainy)
	testy_enc = to_categorical(testy)
	# define model
	model = Sequential()
	model.add(Dense(50, input_dim=2, activation='relu'))
	model.add(Dense(3, activation='softmax'))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
	# fit model
	model.fit(trainX, trainy_enc, epochs=50, verbose=0)
	# evaluate the model
	_, test_acc = model.evaluate(testX, testy_enc, verbose=0)
	return model, test_acc

# make an ensemble prediction for multi-class classification
def ensemble_predictions(members, testX):
	# make predictions
	yhats = [model.predict(testX) for model in members]
	yhats = array(yhats)
	# sum across ensemble members
	summed = numpy.sum(yhats, axis=0)
	# argmax across classes
	result = argmax(summed, axis=1)
	return result

# evaluate a specific number of members in an ensemble
def evaluate_n_members(members, n_members, testX, testy):
	# select a subset of members
	subset = members[:n_members]
	# make prediction
	yhat = ensemble_predictions(subset, testX)
	# calculate accuracy
	return accuracy_score(testy, yhat)

# generate 2d classification dataset
dataX, datay = make_blobs(n_samples=55000, centers=3, n_features=2, cluster_std=2, random_state=2)
X, newX = dataX[:5000, :], dataX[5000:, :]
y, newy = datay[:5000], datay[5000:]
# multiple train-test splits
n_splits = 10
scores, members = list(), list()
for _ in range(n_splits):
	# select indexes
	ix = [i for i in range(len(X))]
	train_ix = resample(ix, replace=True, n_samples=4500)
	test_ix = [x for x in ix if x not in train_ix]
	# select data
	trainX, trainy = X[train_ix], y[train_ix]
	testX, testy = X[test_ix], y[test_ix]
	# evaluate model
	model, test_acc = evaluate_model(trainX, trainy, testX, testy)
	print('>%.3f' % test_acc)
	scores.append(test_acc)
	members.append(model)
# summarize expected performance
print('Estimated Accuracy %.3f (%.3f)' % (mean(scores), std(scores)))
# evaluate different numbers of ensembles on hold out set
single_scores, ensemble_scores = list(), list()
for i in range(1, n_splits+1):
	ensemble_score = evaluate_n_members(members, i, newX, newy)
	newy_enc = to_categorical(newy)
	_, single_score = members[i-1].evaluate(newX, newy_enc, verbose=0)
	print('> %d: single=%.3f, ensemble=%.3f' % (i, single_score, ensemble_score))
	ensemble_scores.append(ensemble_score)
	single_scores.append(single_score)
# plot score vs number of ensemble members
print('Accuracy %.3f (%.3f)' % (mean(single_scores), std(single_scores)))
x_axis = [i for i in range(1, n_splits+1)]
pyplot.plot(x_axis, single_scores, marker='o', linestyle='None')
pyplot.plot(x_axis, ensemble_scores, marker='o')
pyplot.show()

L'exécution de l'exemple imprime les performances du modèle sur les exemples inutilisés pour chaque échantillon d'amorçage.

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, les performances attendues du modèle sont moins optimistes que les répartitions aléatoires des tests de train et sont peut-être assez similaires aux résultats de la validation croisée k fois.

>0.829
>0.820
>0.830
>0.821
>0.831
>0.820
>0.834
>0.815
>0.829
>0.827
Estimated Accuracy 0.825 (0.006)

Peut-être en raison de la procédure d'échantillonnage bootstrap, nous constatons que les performances réelles de chaque modèle sont un peu moins bonnes sur l'ensemble de données invisibles beaucoup plus vaste.

Ceci était prévisible compte tenu du biais introduit par l’échantillonnage avec remplacement du bootstrap.

> 1: single=0.819, ensemble=0.819
> 2: single=0.818, ensemble=0.820
> 3: single=0.820, ensemble=0.820
> 4: single=0.818, ensemble=0.821
> 5: single=0.819, ensemble=0.820
> 6: single=0.820, ensemble=0.820
> 7: single=0.820, ensemble=0.820
> 8: single=0.819, ensemble=0.820
> 9: single=0.820, ensemble=0.820
> 10: single=0.819, ensemble=0.820
Accuracy 0.819 (0.001)

Le tracé linéaire créé est encourageant.

Nous constatons qu'après environ quatre membres, l'ensemble en sac obtient de meilleures performances sur l'ensemble de données d'exclusion que n'importe quel modèle individuel. Sans aucun doute, étant donné les performances moyennes légèrement inférieures des différents modèles.

Rallonges

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

  • Modèle Unique. Comparez les performances de chaque ensemble à un modèle formé sur toutes les données disponibles.
  • Taille de l'ensemble du CV. Expérimentez avec des tailles d'ensemble plus grandes et plus petites pour l'ensemble de validation croisée et comparez leurs performances.
  • Limite d'ensemble d'ensachage. Augmentez le nombre de membres dans l’ensemble d’ensachage pour trouver le point de rendement décroissant.

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 à la validation croisée k-fold
  • Une introduction douce à la méthode Bootstrap
  • Comment implémenter l'ensachage à partir de zéro avec Python

Papiers

  • Ensembles de réseaux neuronaux, validation croisée et apprentissage actif, 1995.

API

  • Premiers pas avec le modèle Keras Sequential
  • API des couches principales Keras
  • API scipy.stats.mode
  • API numpy.argmax
  • API sklearn.datasets.make_blobs
  • API sklearn.model_selection.train_test_split
  • API sklearn.model_selection.KFold
  • API sklearn.utils.resample

Résumé

Dans ce didacticiel, vous avez découvert comment développer une suite de différents ensembles basés sur le rééchantillonnage pour les modèles de réseaux neuronaux d'apprentissage profond.

Concrètement, vous avez appris :

  • Comment estimer les performances d'un modèle à l'aide de répartitions aléatoires et développer un ensemble à partir des modèles.
  • Comment estimer les performances à l'aide d'une validation croisée 10 fois et développer un ensemble de validation croisée.
  • Comment estimer les performances à l'aide du bootstrap et combiner des modèles à l'aide d'un ensemble d'ensachage.

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

Articles connexes