Surajustement et sous-ajustement

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Comme toujours, le code dans cet exemple utilisera la tf.keras API, que vous pouvez en savoir plus sur la tensorflow Guide Keras .

Dans les deux cas du précédent examples- texte classifiant et prédire l' efficacité énergétique - nous avons vu que la précision de notre modèle sur les données de validation culminerait après la formation d'un certain nombre d'époques, et alors stagner ou commencer à diminuer.

En d' autres termes, notre modèle serait surajuster aux données de formation. Apprendre à gérer le surapprentissage est important. Bien qu'il soit souvent possible d'obtenir une grande précision sur l'ensemble de la formation, ce que nous voulons vraiment est de développer des modèles qui généralisent bien à un ensemble de tests (ou les données qu'ils ont jamais vus auparavant).

L'opposé de surajustement est underfitting. Le sous-apprentissage se produit lorsqu'il y a encore place à l'amélioration des données du train. Cela peut se produire pour un certain nombre de raisons : si le modèle n'est pas assez puissant, est trop régularisé ou n'a tout simplement pas été entraîné assez longtemps. Cela signifie que le réseau n'a pas appris les modèles pertinents dans les données d'apprentissage.

Cependant, si vous vous entraînez trop longtemps, le modèle commencera à surajuster et à apprendre des modèles à partir des données d'entraînement qui ne se généraliseront pas aux données de test. Nous devons trouver un équilibre. Comprendre comment s'entraîner pour un nombre approprié d'époques, comme nous l'explorerons ci-dessous, est une compétence utile.

Pour éviter le surapprentissage, la meilleure solution consiste à utiliser des données d'entraînement plus complètes. L'ensemble de données doit couvrir toute la gamme d'entrées que le modèle est censé gérer. Des données supplémentaires ne peuvent être utiles que si elles couvrent des cas nouveaux et intéressants.

Un modèle entraîné sur des données plus complètes se généralisera naturellement mieux. Lorsque cela n'est plus possible, la meilleure solution suivante consiste à utiliser des techniques telles que la régularisation. Celles-ci imposent des contraintes sur la quantité et le type d'informations que votre modèle peut stocker. Si un réseau ne peut se permettre de mémoriser qu'un petit nombre de motifs, le processus d'optimisation le forcera à se concentrer sur les motifs les plus importants, qui ont de meilleures chances de bien se généraliser.

Dans ce cahier, nous allons explorer plusieurs techniques de régularisation courantes et les utiliser pour améliorer un modèle de classification.

Installer

Avant de commencer, importez les packages nécessaires :

import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import regularizers

print(tf.__version__)
2.5.0
!pip install git+https://github.com/tensorflow/docs

import tensorflow_docs as tfdocs
import tensorflow_docs.modeling
import tensorflow_docs.plots
from  IPython import display
from matplotlib import pyplot as plt

import numpy as np

import pathlib
import shutil
import tempfile
logdir = pathlib.Path(tempfile.mkdtemp())/"tensorboard_logs"
shutil.rmtree(logdir, ignore_errors=True)

L'ensemble de données de Higgs

Le but de ce tutoriel n'est pas de faire de la physique des particules, donc ne vous attardez pas sur les détails du jeu de données. Il contient 11 000 000 d'exemples, chacun avec 28 fonctionnalités, et une étiquette de classe binaire.

gz = tf.keras.utils.get_file('HIGGS.csv.gz', 'http://mlphysics.ics.uci.edu/data/higgs/HIGGS.csv.gz')
Downloading data from http://mlphysics.ics.uci.edu/data/higgs/HIGGS.csv.gz
2816409600/2816407858 [==============================] - 148s 0us/step
FEATURES = 28

La tf.data.experimental.CsvDataset classe peut être utilisé pour lire les enregistrements csv directement à partir d' un fichier gzip sans étape de décompression intermédiaire.

ds = tf.data.experimental.CsvDataset(gz,[float(),]*(FEATURES+1), compression_type="GZIP")

Cette classe de lecteur csv renvoie une liste de scalaires pour chaque enregistrement. La fonction suivante reconditionne cette liste de scalaires dans une paire (feature_vector, label).

def pack_row(*row):
  label = row[0]
  features = tf.stack(row[1:],1)
  return features, label

TensorFlow est plus efficace lorsqu'il fonctionne sur de gros lots de données.

Ainsi , au lieu de remballer chaque ligne individuellement faire une nouvelle Dataset qui prend des lots de 10000-exemples, applique la pack_row fonction à chaque lot, et se divise ensuite les lots sauvegarder dans les dossiers individuels:

packed_ds = ds.batch(10000).map(pack_row).unbatch()

Jetez un oeil à quelques - uns des dossiers de cette nouvelle packed_ds .

Les fonctionnalités ne sont pas parfaitement normalisées, mais cela est suffisant pour ce tutoriel.

for features,label in packed_ds.batch(1000).take(1):
  print(features[0])
  plt.hist(features.numpy().flatten(), bins = 101)
tf.Tensor(
[ 0.8692932  -0.6350818   0.22569026  0.32747006 -0.6899932   0.75420225
 -0.24857314 -1.0920639   0.          1.3749921  -0.6536742   0.9303491
  1.1074361   1.1389043  -1.5781983  -1.0469854   0.          0.65792954
 -0.01045457 -0.04576717  3.1019614   1.35376     0.9795631   0.97807616
  0.92000484  0.72165745  0.98875093  0.87667835], shape=(28,), dtype=float32)

png

Pour garder ce didacticiel relativement court, utilisez uniquement les 1000 premiers échantillons pour la validation et les 10 000 suivants pour la formation :

N_VALIDATION = int(1e3)
N_TRAIN = int(1e4)
BUFFER_SIZE = int(1e4)
BATCH_SIZE = 500
STEPS_PER_EPOCH = N_TRAIN//BATCH_SIZE

Les Dataset.skip et Dataset.take méthodes font facilement.

En même temps, utilisez la Dataset.cache méthode pour faire en sorte que le chargeur n'a pas besoin de relire les données du fichier de chaque époque:

validate_ds = packed_ds.take(N_VALIDATION).cache()
train_ds = packed_ds.skip(N_VALIDATION).take(N_TRAIN).cache()
train_ds
<CacheDataset shapes: ((28,), ()), types: (tf.float32, tf.float32)>

Ces ensembles de données renvoient des exemples individuels. Utilisez la .batch méthode pour créer des lots d'une taille appropriée pour la formation. Avant Batching me souviens aussi de .shuffle et .repeat l'ensemble de la formation.

validate_ds = validate_ds.batch(BATCH_SIZE)
train_ds = train_ds.shuffle(BUFFER_SIZE).repeat().batch(BATCH_SIZE)

Démontrer le surapprentissage

Le moyen le plus simple d'éviter le surapprentissage est de commencer avec un petit modèle : un modèle avec un petit nombre de paramètres pouvant être appris (qui est déterminé par le nombre de couches et le nombre d'unités par couche). En apprentissage profond, le nombre de paramètres pouvant être appris dans un modèle est souvent appelé « capacité » du modèle.

Intuitivement, un modèle avec plus de paramètres aura plus de "capacité de mémorisation" et sera donc capable d'apprendre facilement un mapping parfait de type dictionnaire entre les échantillons d'apprentissage et leurs cibles, un mapping sans aucun pouvoir de généralisation, mais cela serait inutile pour faire des prédictions sur des données inédites.

Gardez toujours cela à l'esprit : les modèles d'apprentissage en profondeur ont tendance à bien s'adapter aux données d'entraînement, mais le vrai défi est la généralisation, pas l'ajustement.

En revanche, si le réseau dispose de ressources de mémorisation limitées, il ne pourra pas apprendre la cartographie aussi facilement. Pour minimiser sa perte, il devra apprendre des représentations compressées qui ont plus de pouvoir prédictif. En même temps, si vous rendez votre modèle trop petit, il aura du mal à s'adapter aux données d'entraînement. Il y a un équilibre entre "trop ​​de capacité" et "pas assez de capacité".

Malheureusement, il n'y a pas de formule magique pour déterminer la bonne taille ou l'architecture de votre modèle (en termes de nombre de couches, ou la bonne taille pour chaque couche). Vous devrez expérimenter en utilisant une série d'architectures différentes.

Pour trouver une taille de modèle appropriée, il est préférable de commencer avec relativement peu de couches et de paramètres, puis de commencer à augmenter la taille des couches ou à ajouter de nouvelles couches jusqu'à ce que vous voyiez des retours décroissants sur la perte de validation.

Commencez par un modèle simple en utilisant uniquement layers.Dense comme base de référence, puis créer des versions plus grandes, et de les comparer.

Procédure de formation

De nombreux modèles s'entraînent mieux si vous réduisez progressivement le taux d'apprentissage pendant l'entraînement. Utilisez optimizers.schedules pour réduire le taux d'apprentissage au fil du temps:

lr_schedule = tf.keras.optimizers.schedules.InverseTimeDecay(
  0.001,
  decay_steps=STEPS_PER_EPOCH*1000,
  decay_rate=1,
  staircase=False)

def get_optimizer():
  return tf.keras.optimizers.Adam(lr_schedule)

Le code ci - dessus établit un schedules.InverseTimeDecay pour diminuer hyperbolique le taux d'apprentissage à 1/2 de la vitesse de base à 1000 époques, 3.1 à 2000 époques et ainsi de suite.

step = np.linspace(0,100000)
lr = lr_schedule(step)
plt.figure(figsize = (8,6))
plt.plot(step/STEPS_PER_EPOCH, lr)
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Epoch')
_ = plt.ylabel('Learning Rate')

png

Chaque modèle de ce didacticiel utilisera la même configuration d'entraînement. Configurez-les donc de manière réutilisable, en commençant par la liste des rappels.

La formation pour ce didacticiel s'étend sur de nombreuses périodes courtes. Pour réduire l'exploitation forestière utiliser le bruit des tfdocs.EpochDots qui imprime simplement . pour chaque époque, et un ensemble complet de métriques toutes les 100 époques.

Suivant comprennent callbacks.EarlyStopping pour éviter les longues et les temps de formation inutiles. Notez que ce rappel est réglé pour surveiller la val_binary_crossentropy , pas le val_loss . Cette différence sera importante plus tard.

Utilisez callbacks.TensorBoard pour générer des journaux TensorBoard pour la formation.

def get_callbacks(name):
  return [
    tfdocs.modeling.EpochDots(),
    tf.keras.callbacks.EarlyStopping(monitor='val_binary_crossentropy', patience=200),
    tf.keras.callbacks.TensorBoard(logdir/name),
  ]

De même , chaque modèle utilisera les mêmes Model.compile et Model.fit paramètres:

def compile_and_fit(model, name, optimizer=None, max_epochs=10000):
  if optimizer is None:
    optimizer = get_optimizer()
  model.compile(optimizer=optimizer,
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=[
                  tf.keras.losses.BinaryCrossentropy(
                      from_logits=True, name='binary_crossentropy'),
                  'accuracy'])

  model.summary()

  history = model.fit(
    train_ds,
    steps_per_epoch = STEPS_PER_EPOCH,
    epochs=max_epochs,
    validation_data=validate_ds,
    callbacks=get_callbacks(name),
    verbose=0)
  return history

Petit modèle

Commencez par entraîner un modèle :

tiny_model = tf.keras.Sequential([
    layers.Dense(16, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(1)
])
size_histories = {}
size_histories['Tiny'] = compile_and_fit(tiny_model, 'sizes/Tiny')
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 16)                464       
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 17        
=================================================================
Total params: 481
Trainable params: 481
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0031s vs `on_train_batch_begin` time: 0.0346s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0031s vs `on_train_batch_end` time: 0.0125s). Check your callbacks.

Epoch: 0, accuracy:0.5093,  binary_crossentropy:0.7681,  loss:0.7681,  val_accuracy:0.5000,  val_binary_crossentropy:0.7207,  val_loss:0.7207,  
....................................................................................................
Epoch: 100, accuracy:0.6058,  binary_crossentropy:0.6244,  loss:0.6244,  val_accuracy:0.5700,  val_binary_crossentropy:0.6322,  val_loss:0.6322,  
....................................................................................................
Epoch: 200, accuracy:0.6220,  binary_crossentropy:0.6124,  loss:0.6124,  val_accuracy:0.5990,  val_binary_crossentropy:0.6202,  val_loss:0.6202,  
....................................................................................................
Epoch: 300, accuracy:0.6388,  binary_crossentropy:0.6045,  loss:0.6045,  val_accuracy:0.6150,  val_binary_crossentropy:0.6114,  val_loss:0.6114,  
....................................................................................................
Epoch: 400, accuracy:0.6475,  binary_crossentropy:0.5976,  loss:0.5976,  val_accuracy:0.6270,  val_binary_crossentropy:0.6012,  val_loss:0.6012,  
....................................................................................................
Epoch: 500, accuracy:0.6579,  binary_crossentropy:0.5917,  loss:0.5917,  val_accuracy:0.6390,  val_binary_crossentropy:0.5929,  val_loss:0.5929,  
....................................................................................................
Epoch: 600, accuracy:0.6662,  binary_crossentropy:0.5878,  loss:0.5878,  val_accuracy:0.6410,  val_binary_crossentropy:0.5890,  val_loss:0.5890,  
....................................................................................................
Epoch: 700, accuracy:0.6664,  binary_crossentropy:0.5847,  loss:0.5847,  val_accuracy:0.6670,  val_binary_crossentropy:0.5865,  val_loss:0.5865,  
....................................................................................................
Epoch: 800, accuracy:0.6709,  binary_crossentropy:0.5822,  loss:0.5822,  val_accuracy:0.6460,  val_binary_crossentropy:0.5896,  val_loss:0.5896,  
....................................................................................................
Epoch: 900, accuracy:0.6772,  binary_crossentropy:0.5793,  loss:0.5793,  val_accuracy:0.6540,  val_binary_crossentropy:0.5880,  val_loss:0.5880,  
...................

Maintenant, vérifiez comment le modèle a fait :

plotter = tfdocs.plots.HistoryPlotter(metric = 'binary_crossentropy', smoothing_std=10)
plotter.plot(size_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Petit modèle

Pour voir si vous pouvez battre les performances du petit modèle, entraînez progressivement des modèles plus grands.

Essayez deux couches cachées avec 16 unités chacune :

small_model = tf.keras.Sequential([
    # `input_shape` is only required here so that `.summary` works.
    layers.Dense(16, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(16, activation='elu'),
    layers.Dense(1)
])
size_histories['Small'] = compile_and_fit(small_model, 'sizes/Small')
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_2 (Dense)              (None, 16)                464       
_________________________________________________________________
dense_3 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 17        
=================================================================
Total params: 753
Trainable params: 753
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0030s vs `on_train_batch_begin` time: 0.0258s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0030s vs `on_train_batch_end` time: 0.0176s). Check your callbacks.

Epoch: 0, accuracy:0.4757,  binary_crossentropy:0.7130,  loss:0.7130,  val_accuracy:0.4630,  val_binary_crossentropy:0.7012,  val_loss:0.7012,  
....................................................................................................
Epoch: 100, accuracy:0.6295,  binary_crossentropy:0.6092,  loss:0.6092,  val_accuracy:0.6120,  val_binary_crossentropy:0.6145,  val_loss:0.6145,  
....................................................................................................
Epoch: 200, accuracy:0.6575,  binary_crossentropy:0.5879,  loss:0.5879,  val_accuracy:0.6520,  val_binary_crossentropy:0.5976,  val_loss:0.5976,  
....................................................................................................
Epoch: 300, accuracy:0.6758,  binary_crossentropy:0.5774,  loss:0.5774,  val_accuracy:0.6610,  val_binary_crossentropy:0.5958,  val_loss:0.5958,  
....................................................................................................
Epoch: 400, accuracy:0.6830,  binary_crossentropy:0.5698,  loss:0.5698,  val_accuracy:0.6690,  val_binary_crossentropy:0.5949,  val_loss:0.5949,  
....................................................................................................
Epoch: 500, accuracy:0.6873,  binary_crossentropy:0.5650,  loss:0.5650,  val_accuracy:0.6720,  val_binary_crossentropy:0.5930,  val_loss:0.5930,  
....................................................................................................
Epoch: 600, accuracy:0.6923,  binary_crossentropy:0.5600,  loss:0.5600,  val_accuracy:0.6570,  val_binary_crossentropy:0.5946,  val_loss:0.5946,  
......................................................

Moyen modèle

Essayez maintenant 3 couches cachées avec 64 unités chacune :

medium_model = tf.keras.Sequential([
    layers.Dense(64, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(64, activation='elu'),
    layers.Dense(64, activation='elu'),
    layers.Dense(1)
])

Et entraînez le modèle en utilisant les mêmes données :

size_histories['Medium']  = compile_and_fit(medium_model, "sizes/Medium")
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_5 (Dense)              (None, 64)                1856      
_________________________________________________________________
dense_6 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_7 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 65        
=================================================================
Total params: 10,241
Trainable params: 10,241
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_begin` time: 0.0251s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_end` time: 0.0189s). Check your callbacks.

Epoch: 0, accuracy:0.5026,  binary_crossentropy:0.6944,  loss:0.6944,  val_accuracy:0.4740,  val_binary_crossentropy:0.6830,  val_loss:0.6830,  
....................................................................................................
Epoch: 100, accuracy:0.7164,  binary_crossentropy:0.5242,  loss:0.5242,  val_accuracy:0.6490,  val_binary_crossentropy:0.6316,  val_loss:0.6316,  
....................................................................................................
Epoch: 200, accuracy:0.7919,  binary_crossentropy:0.4224,  loss:0.4224,  val_accuracy:0.6480,  val_binary_crossentropy:0.7022,  val_loss:0.7022,  
.......................................

Grand modèle

À titre d'exercice, vous pouvez créer un modèle encore plus grand et voir à quelle vitesse il commence à se suradapter. Ensuite, ajoutons à ce benchmark un réseau qui a beaucoup plus de capacité, bien plus que le problème ne le justifierait :

large_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu', input_shape=(FEATURES,)),
    layers.Dense(512, activation='elu'),
    layers.Dense(512, activation='elu'),
    layers.Dense(512, activation='elu'),
    layers.Dense(1)
])

Et, encore une fois, entraînez le modèle en utilisant les mêmes données :

size_histories['large'] = compile_and_fit(large_model, "sizes/large")
Model: "sequential_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_9 (Dense)              (None, 512)               14848     
_________________________________________________________________
dense_10 (Dense)             (None, 512)               262656    
_________________________________________________________________
dense_11 (Dense)             (None, 512)               262656    
_________________________________________________________________
dense_12 (Dense)             (None, 512)               262656    
_________________________________________________________________
dense_13 (Dense)             (None, 1)                 513       
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_begin` time: 0.0237s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0033s vs `on_train_batch_end` time: 0.0182s). Check your callbacks.

Epoch: 0, accuracy:0.5116,  binary_crossentropy:0.7680,  loss:0.7680,  val_accuracy:0.5440,  val_binary_crossentropy:0.6753,  val_loss:0.6753,  
....................................................................................................
Epoch: 100, accuracy:1.0000,  binary_crossentropy:0.0021,  loss:0.0021,  val_accuracy:0.6610,  val_binary_crossentropy:1.8058,  val_loss:1.8058,  
....................................................................................................
Epoch: 200, accuracy:1.0000,  binary_crossentropy:0.0001,  loss:0.0001,  val_accuracy:0.6500,  val_binary_crossentropy:2.4712,  val_loss:2.4712,  
.........................

Tracer les pertes de formation et de validation

Les lignes pleines montrent la perte de formation et les lignes pointillées montrent la perte de validation (rappelez-vous : une perte de validation plus faible indique un meilleur modèle).

Bien que la construction d'un modèle plus grand lui donne plus de puissance, si cette puissance n'est pas limitée d'une manière ou d'une autre, elle peut facilement s'adapter à l'ensemble d'entraînement.

Dans cet exemple, généralement, seule la "Tiny" modèle parvient à éviter surajustement tout à fait, et chacun des modèles plus grands surajuster les données plus rapidement. Cela devient si grave pour le "large" modèle que vous avez besoin de changer le tracé à une échelle logarithmique pour voir vraiment ce qui se passe.

Cela est évident si vous tracez et comparez les métriques de validation aux métriques d'entraînement.

  • C'est normal qu'il y ait une petite différence.
  • Si les deux métriques évoluent dans la même direction, tout va bien.
  • Si la métrique de validation commence à stagner alors que la métrique d'entraînement continue de s'améliorer, vous êtes probablement proche du surapprentissage.
  • Si la métrique de validation va dans la mauvaise direction, le modèle est clairement surajusté.
plotter.plot(size_histories)
a = plt.xscale('log')
plt.xlim([5, max(plt.xlim())])
plt.ylim([0.5, 0.7])
plt.xlabel("Epochs [Log Scale]")
Text(0.5, 0, 'Epochs [Log Scale]')

png

Afficher dans TensorBoard

Ces modèles ont tous écrit des journaux TensorBoard pendant la formation.

Ouvrez une visionneuse TensorBoard intégrée dans un bloc-notes :


# Load the TensorBoard notebook extension
%load_ext tensorboard

# Open an embedded TensorBoard viewer
%tensorboard --logdir {logdir}/sizes

Vous pouvez afficher les résultats d'une exécution précédente de ce bloc - notes sur TensorBoard.dev .

TensorBoard.dev est une expérience gérée pour l'hébergement, le suivi et le partage d'expériences de ML avec tout le monde.

Il est également inclus dans un <iframe> pour plus de commodité:

display.IFrame(
    src="https://tensorboard.dev/experiment/vW7jmmF9TmKmy3rbheMQpw/#scalars&_smoothingWeight=0.97",
    width="100%", height="800px")

Si vous souhaitez partager TensorBoard les résultats que vous pouvez télécharger les journaux à TensorBoard.dev en copiant ce qui suit dans un code-cellule.

tensorboard dev upload --logdir  {logdir}/sizes

Stratégies pour éviter le surapprentissage

Avant d' entrer dans le contenu de cette section copier les journaux de formation de la "Tiny" modèle ci - dessus, à utiliser comme base de comparaison.

shutil.rmtree(logdir/'regularizers/Tiny', ignore_errors=True)
shutil.copytree(logdir/'sizes/Tiny', logdir/'regularizers/Tiny')
PosixPath('/tmp/tmp_tm13yei/tensorboard_logs/regularizers/Tiny')
regularizer_histories = {}
regularizer_histories['Tiny'] = size_histories['Tiny']

Ajouter une régularisation de poids

Vous connaissez peut-être le principe du rasoir d'Occam : étant donné deux explications pour quelque chose, l'explication la plus susceptible d'être correcte est la plus "simple", celle qui fait le moins d'hypothèses. Cela s'applique également aux modèles appris par les réseaux de neurones : étant donné certaines données d'entraînement et une architecture de réseau, il existe plusieurs ensembles de valeurs de pondération (plusieurs modèles) qui pourraient expliquer les données, et les modèles plus simples sont moins susceptibles de surajuster que les modèles complexes.

Un « modèle simple » dans ce contexte est un modèle où la distribution des valeurs des paramètres a moins d'entropie (ou un modèle avec moins de paramètres au total, comme nous l'avons vu dans la section ci-dessus). Ainsi, un moyen courant d'atténuer le surapprentissage consiste à imposer des contraintes sur la complexité d'un réseau en forçant ses poids à ne prendre que de petites valeurs, ce qui rend la distribution des valeurs de poids plus « régulière ». C'est ce qu'on appelle la « régularisation des poids », et cela se fait en ajoutant à la fonction de perte du réseau un coût associé au fait d'avoir des poids importants. Ce coût se décline en deux saveurs :

  • Régularisation L1 , où le coût supplémentaire est proportionnelle à la valeur absolue des coefficients de poids ( par exemple à ce qu'on appelle la « norme L1 » des poids).

  • Régularisation L2 , où le coût supplémentaire est proportionnelle au carré de la valeur des coefficients de poids ( par exemple à ce qu'on appelle le carré « L2 norme » des poids). La régularisation L2 est également appelée perte de poids dans le contexte des réseaux de neurones. Ne laissez pas le nom différent vous embrouiller : la décroissance du poids est mathématiquement exactement la même que la régularisation L2.

La régularisation L1 pousse les poids vers exactement zéro, encourageant un modèle clairsemé. La régularisation L2 pénalisera les paramètres de poids sans les rendre clairsemés puisque la pénalité passe à zéro pour les petits poids - une des raisons pour lesquelles L2 est plus courant.

En tf.keras , la régularisation de poids est ajouté par le passage des instances de régularisateur de poids aux couches comme arguments de mots clés. Ajoutons maintenant la régularisation du poids L2.

l2_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001),
                 input_shape=(FEATURES,)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(1)
])

regularizer_histories['l2'] = compile_and_fit(l2_model, "regularizers/l2")
Model: "sequential_4"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_14 (Dense)             (None, 512)               14848     
_________________________________________________________________
dense_15 (Dense)             (None, 512)               262656    
_________________________________________________________________
dense_16 (Dense)             (None, 512)               262656    
_________________________________________________________________
dense_17 (Dense)             (None, 512)               262656    
_________________________________________________________________
dense_18 (Dense)             (None, 1)                 513       
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0038s vs `on_train_batch_begin` time: 0.0242s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0038s vs `on_train_batch_end` time: 0.0199s). Check your callbacks.

Epoch: 0, accuracy:0.5059,  binary_crossentropy:0.7720,  loss:2.2831,  val_accuracy:0.4620,  val_binary_crossentropy:0.7035,  val_loss:2.1321,  
....................................................................................................
Epoch: 100, accuracy:0.6490,  binary_crossentropy:0.5996,  loss:0.6228,  val_accuracy:0.6270,  val_binary_crossentropy:0.5898,  val_loss:0.6131,  
....................................................................................................
Epoch: 200, accuracy:0.6737,  binary_crossentropy:0.5826,  loss:0.6061,  val_accuracy:0.6680,  val_binary_crossentropy:0.5857,  val_loss:0.6096,  
....................................................................................................
Epoch: 300, accuracy:0.6842,  binary_crossentropy:0.5748,  loss:0.5993,  val_accuracy:0.6840,  val_binary_crossentropy:0.5754,  val_loss:0.5998,  
....................................................................................................
Epoch: 400, accuracy:0.6934,  binary_crossentropy:0.5620,  loss:0.5862,  val_accuracy:0.6690,  val_binary_crossentropy:0.5825,  val_loss:0.6066,  
.....................................................................................

l2(0.001) signifie que chaque coefficient dans la matrice de poids de la couche ajoutera 0.001 * weight_coefficient_value**2 à la perte totale du réseau.

Voilà pourquoi nous surveillons la binary_crossentropy directement. Parce qu'il n'a pas ce composant de régularisation mélangé.

Ainsi, même "Large" modèle avec une L2 peine de régularisation est bien meilleur:

plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Comme vous pouvez le voir, le "L2" modèle régularisé est maintenant beaucoup plus compétitif avec le la "Tiny" modèle. Ce "L2" modèle est beaucoup plus résistant à la overfitting que "Large" modèle qu'il était fondé sur malgré le même nombre de paramètres.

Plus d'informations

Il y a deux choses importantes à noter à propos de ce type de régularisation.

Tout d' abord: si vous écrivez votre propre boucle de formation, alors vous devez être sûr de demander au modèle de ses pertes de régularisation.

result = l2_model(features)
regularization_loss=tf.add_n(l2_model.losses)

Deuxièmement: Cette mise en œuvre fonctionne en ajoutant les pénalités de poids à la perte du modèle, puis d' appliquer une procédure d'optimisation standard après cela.

Il existe une deuxième approche qui, à la place, n'exécute l'optimiseur que sur la perte brute, puis lors de l'application de l'étape calculée, l'optimiseur applique également une certaine perte de poids. Ce « découplé poids Decay » est vu dans optimiseurs comme optimizers.FTRL et optimizers.AdamW .

Ajouter un décrochage

L'abandon est l'une des techniques de régularisation les plus efficaces et les plus couramment utilisées pour les réseaux de neurones, développée par Hinton et ses étudiants de l'Université de Toronto.

L'explication intuitive de l'abandon est que, étant donné que les nœuds individuels du réseau ne peuvent pas compter sur la sortie des autres, chaque nœud doit générer des fonctionnalités utiles en soi.

L'abandon, appliqué à une couche, consiste à "abandonner" aléatoirement (c'est-à-dire mis à zéro) un certain nombre de caractéristiques de sortie de la couche pendant l'entraînement. Disons qu'une couche donnée aurait normalement renvoyé un vecteur [0.2, 0.5, 1.3, 0.8, 1.1] pour un échantillon d'entrée donné pendant l'apprentissage ; après avoir appliqué la suppression, ce vecteur aura quelques entrées nulles distribuées au hasard, par exemple [0, 0,5, 1,3, 0, 1,1].

Le « taux d'abandon » est la fraction des caractéristiques qui sont mises à zéro ; il est généralement fixé entre 0,2 et 0,5. Au moment du test, aucune unité n'est abandonnée, et à la place, les valeurs de sortie de la couche sont réduites d'un facteur égal au taux d'abandon, de manière à compenser le fait que plus d'unités sont actives qu'au moment de l'entraînement.

En tf.keras vous pouvez introduire le décrochage dans un réseau via la couche Dropout, qui obtient appliquée à la sortie de la couche juste avant.

Ajoutons deux couches Dropout dans notre réseau pour voir à quel point elles réussissent à réduire le surapprentissage :

dropout_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu', input_shape=(FEATURES,)),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(1)
])

regularizer_histories['dropout'] = compile_and_fit(dropout_model, "regularizers/dropout")
Model: "sequential_5"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_19 (Dense)             (None, 512)               14848     
_________________________________________________________________
dropout (Dropout)            (None, 512)               0         
_________________________________________________________________
dense_20 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_1 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_21 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_2 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_22 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_3 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_23 (Dense)             (None, 1)                 513       
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_begin` time: 0.0241s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_end` time: 0.0208s). Check your callbacks.

Epoch: 0, accuracy:0.5060,  binary_crossentropy:0.7949,  loss:0.7949,  val_accuracy:0.5140,  val_binary_crossentropy:0.6710,  val_loss:0.6710,  
....................................................................................................
Epoch: 100, accuracy:0.6623,  binary_crossentropy:0.5950,  loss:0.5950,  val_accuracy:0.6840,  val_binary_crossentropy:0.5723,  val_loss:0.5723,  
....................................................................................................
Epoch: 200, accuracy:0.6897,  binary_crossentropy:0.5559,  loss:0.5559,  val_accuracy:0.6800,  val_binary_crossentropy:0.5971,  val_loss:0.5971,  
....................................................................................................
Epoch: 300, accuracy:0.7202,  binary_crossentropy:0.5114,  loss:0.5114,  val_accuracy:0.6800,  val_binary_crossentropy:0.5984,  val_loss:0.5984,  
...............................................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Il est clair de ce complot que ces deux approches de régularisation améliorer le comportement du "Large" modèle. Mais cela ne battait encore pas même le "Tiny" de base.

Ensuite, essayez-les tous les deux, ensemble, et voyez si cela fonctionne mieux.

Combiné L2 + abandon

combined_model = tf.keras.Sequential([
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu', input_shape=(FEATURES,)),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(1)
])

regularizer_histories['combined'] = compile_and_fit(combined_model, "regularizers/combined")
Model: "sequential_6"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_24 (Dense)             (None, 512)               14848     
_________________________________________________________________
dropout_4 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_25 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_5 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_26 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_6 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_27 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_7 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_28 (Dense)             (None, 1)                 513       
=================================================================
Total params: 803,329
Trainable params: 803,329
Non-trainable params: 0
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_begin` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_begin` time: 0.0245s). Check your callbacks.
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_end` time: 0.0214s). Check your callbacks.

Epoch: 0, accuracy:0.5056,  binary_crossentropy:0.8201,  loss:0.9784,  val_accuracy:0.5130,  val_binary_crossentropy:0.6691,  val_loss:0.8269,  
....................................................................................................
Epoch: 100, accuracy:0.6409,  binary_crossentropy:0.6052,  loss:0.6362,  val_accuracy:0.6670,  val_binary_crossentropy:0.5831,  val_loss:0.6139,  
....................................................................................................
Epoch: 200, accuracy:0.6673,  binary_crossentropy:0.5893,  loss:0.6147,  val_accuracy:0.6880,  val_binary_crossentropy:0.5666,  val_loss:0.5920,  
....................................................................................................
Epoch: 300, accuracy:0.6724,  binary_crossentropy:0.5814,  loss:0.6092,  val_accuracy:0.6850,  val_binary_crossentropy:0.5638,  val_loss:0.5916,  
....................................................................................................
Epoch: 400, accuracy:0.6791,  binary_crossentropy:0.5764,  loss:0.6061,  val_accuracy:0.6960,  val_binary_crossentropy:0.5536,  val_loss:0.5832,  
....................................................................................................
Epoch: 500, accuracy:0.6750,  binary_crossentropy:0.5722,  loss:0.6037,  val_accuracy:0.6760,  val_binary_crossentropy:0.5583,  val_loss:0.5899,  
....................................................................................................
Epoch: 600, accuracy:0.6818,  binary_crossentropy:0.5651,  loss:0.5989,  val_accuracy:0.6940,  val_binary_crossentropy:0.5422,  val_loss:0.5761,  
....................................................................................................
Epoch: 700, accuracy:0.6882,  binary_crossentropy:0.5594,  loss:0.5943,  val_accuracy:0.6880,  val_binary_crossentropy:0.5436,  val_loss:0.5786,  
....................................................................................................
Epoch: 800, accuracy:0.6886,  binary_crossentropy:0.5567,  loss:0.5927,  val_accuracy:0.6960,  val_binary_crossentropy:0.5446,  val_loss:0.5807,  
....................................................................................................
Epoch: 900, accuracy:0.6994,  binary_crossentropy:0.5535,  loss:0.5907,  val_accuracy:0.6900,  val_binary_crossentropy:0.5463,  val_loss:0.5835,  
................................................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Ce modèle avec le "Combined" régularisation est évidemment le meilleur jusqu'à présent.

Afficher dans TensorBoard

Ces modèles ont également enregistré les journaux TensorBoard.

Pour ouvrir une visionneuse de tensorboard intégrée dans un bloc-notes, copiez ce qui suit dans une cellule de code :

%tensorboard --logdir {logdir}/regularizers

Vous pouvez afficher les résultats d'une exécution précédente de ce bloc - notes sur TensorDoard.dev .

Il est également inclus dans un <iframe> pour plus de commodité:

display.IFrame(
    src="https://tensorboard.dev/experiment/fGInKDo8TXes1z7HQku9mw/#scalars&_smoothingWeight=0.97",
    width = "100%",
    height="800px")

Cela a été téléchargé avec :

tensorboard dev upload --logdir  {logdir}/regularizers

Conclusion

Pour récapituler : voici les moyens les plus courants d'éviter le surapprentissage dans les réseaux de neurones :

  • Obtenez plus de données d'entraînement.
  • Réduire la capacité du réseau.
  • Ajouter une régularisation de poids.
  • Ajouter un décrochage.

Deux approches importantes non couvertes dans ce guide sont :

  • augmentation des données
  • normalisation des lots

N'oubliez pas que chaque méthode peut être utile à elle seule, mais que les combiner souvent peut être encore plus efficace.

# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.