Overfit e underfit

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza l'origine su GitHub Scarica quaderno

Come sempre, il codice in questo esempio utilizzerà l'API tf.keras , di cui puoi saperne di più nella guida di TensorFlow Keras .

In entrambi gli esempi precedenti , classificazione del testo e previsione dell'efficienza del carburante , abbiamo visto che l'accuratezza del nostro modello sui dati di convalida avrebbe raggiunto il picco dopo l'addestramento per un certo numero di epoche, per poi ristagnare o iniziare a diminuire.

In altre parole, il nostro modello si adatterebbe ai dati di addestramento. Imparare a gestire l'overfitting è importante. Sebbene sia spesso possibile ottenere un'elevata precisione sul set di addestramento , ciò che vogliamo veramente è sviluppare modelli che si generalizzino bene a un set di test (o dati che non hanno mai visto prima).

L'opposto di overfitting è underfitting . L'underfitting si verifica quando c'è ancora spazio per migliorare i dati del treno. Ciò può accadere per una serie di motivi: se il modello non è abbastanza potente, è eccessivamente regolamentato o semplicemente non è stato addestrato abbastanza a lungo. Ciò significa che la rete non ha appreso i modelli rilevanti nei dati di addestramento.

Se ti alleni troppo a lungo, il modello inizierà a sovraadattarsi e imparerà modelli dai dati di allenamento che non si generalizzano ai dati del test. Dobbiamo trovare un equilibrio. Capire come allenarsi per un numero appropriato di epoche come esploreremo di seguito è un'abilità utile.

Per prevenire l'overfitting, la soluzione migliore è utilizzare dati di allenamento più completi. Il set di dati dovrebbe coprire l'intera gamma di input che il modello dovrebbe gestire. Ulteriori dati possono essere utili solo se riguardano casi nuovi e interessanti.

Un modello addestrato su dati più completi si generalizzerà naturalmente meglio. Quando ciò non è più possibile, la prossima soluzione migliore è utilizzare tecniche come la regolarizzazione. Questi impongono vincoli alla quantità e al tipo di informazioni che il tuo modello può memorizzare. Se una rete può permettersi di memorizzare solo un piccolo numero di pattern, il processo di ottimizzazione la costringerà a concentrarsi sui pattern più importanti, che hanno maggiori possibilità di generalizzare bene.

In questo quaderno, esploreremo diverse tecniche di regolarizzazione comuni e le utilizzeremo per migliorare un modello di classificazione.

Impostare

Prima di iniziare, importa i pacchetti necessari:

import tensorflow as tf

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

print(tf.__version__)
2.8.0-rc1
!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)

Il set di dati di Higgs

L'obiettivo di questo tutorial non è fare fisica delle particelle, quindi non soffermarti sui dettagli del set di dati. Contiene 11.000.000 di esempi, ciascuno con 28 funzioni e un'etichetta di classe binaria.

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 [==============================] - 123s 0us/step
2816417792/2816407858 [==============================] - 123s 0us/step
FEATURES = 28

La classe tf.data.experimental.CsvDataset può essere utilizzata per leggere i record CSV direttamente da un file gzip senza alcuna fase di decompressione intermedia.

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

Quella classe del lettore csv restituisce un elenco di scalari per ogni record. La seguente funzione ricomprime quell'elenco di scalari in una coppia (feature_vector, label).

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

TensorFlow è più efficiente quando si opera su grandi batch di dati.

Quindi, invece di riconfezionare ogni riga individualmente, crea un nuovo Dataset di dati che accetta batch di 10000 esempi, applica la funzione pack_row a ciascun batch e quindi suddivide i batch in record individuali:

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

Dai un'occhiata ad alcuni dei record di questo nuovo packed_ds .

Le caratteristiche non sono perfettamente normalizzate, ma questo è sufficiente per questo tutorial.

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

Per mantenere questo tutorial relativamente breve, usa solo i primi 1000 campioni per la convalida e i successivi 10.000 per la formazione:

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

I metodi Dataset.skip e Dataset.take rendono facile.

Allo stesso tempo, usa il metodo Dataset.cache per assicurarti che il caricatore non debba rileggere i dati dal file ad ogni epoca:

validate_ds = packed_ds.take(N_VALIDATION).cache()
train_ds = packed_ds.skip(N_VALIDATION).take(N_TRAIN).cache()
train_ds
<CacheDataset element_spec=(TensorSpec(shape=(28,), dtype=tf.float32, name=None), TensorSpec(shape=(), dtype=tf.float32, name=None))>

Questi set di dati restituiscono singoli esempi. Utilizzare il metodo .batch per creare batch di dimensioni appropriate per l'addestramento. Prima di dosare ricordarsi anche di .shuffle e .repeat il training set.

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

Dimostrare l'overfitting

Il modo più semplice per prevenire l'overfitting è iniziare con un modello piccolo: un modello con un numero ridotto di parametri apprendibili (determinato dal numero di strati e dal numero di unità per strato). Nell'apprendimento profondo, il numero di parametri apprendibili in un modello viene spesso definito "capacità" del modello.

Intuitivamente, un modello con più parametri avrà più "capacità di memorizzazione" e quindi sarà in grado di apprendere facilmente una perfetta mappatura simile a un dizionario tra i campioni di addestramento e i loro target, una mappatura senza alcun potere di generalizzazione, ma questo sarebbe inutile quando si fanno previsioni su dati mai visti.

Tienilo sempre a mente: i modelli di deep learning tendono ad adattarsi bene ai dati di training, ma la vera sfida è la generalizzazione, non l'adattamento.

D'altra parte, se la rete ha risorse di memorizzazione limitate, non sarà in grado di apprendere la mappatura così facilmente. Per ridurre al minimo la sua perdita, dovrà imparare rappresentazioni compresse che hanno un potere predittivo maggiore. Allo stesso tempo, se rimpicciolisci il tuo modello, avrà difficoltà a adattarsi ai dati di allenamento. C'è un equilibrio tra "capacità eccessiva" e "capacità insufficiente".

Sfortunatamente, non esiste una formula magica per determinare la giusta dimensione o architettura del tuo modello (in termini di numero di livelli o dimensione giusta per ogni livello). Dovrai sperimentare utilizzando una serie di architetture diverse.

Per trovare una dimensione del modello appropriata, è meglio iniziare con relativamente pochi livelli e parametri, quindi iniziare ad aumentare le dimensioni dei livelli o aggiungere nuovi livelli fino a quando non vedi rendimenti decrescenti sulla perdita di convalida.

Inizia con un modello semplice utilizzando solo layers.Dense come linea di base, quindi crea versioni più grandi e confrontale.

Procedura di formazione

Molti modelli si allenano meglio se si riduce gradualmente il tasso di apprendimento durante l'allenamento. Usa optimizers.schedules per ridurre il tasso di apprendimento nel tempo:

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)

Il codice precedente imposta un schedules.InverseTimeDecay per ridurre iperbolicamente la velocità di apprendimento a 1/2 della velocità di base a 1000 epoche, 1/3 a 2000 epoche e così via.

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

Ciascun modello in questo tutorial utilizzerà la stessa configurazione di addestramento. Quindi impostali in modo riutilizzabile, a partire dall'elenco delle richiamate.

L'addestramento per questo tutorial viene eseguito per molte brevi epoche. Per ridurre il rumore di registrazione, utilizzare tfdocs.EpochDots che stampa semplicemente un file . per ogni epoca e un set completo di metriche ogni 100 epoche.

Successivamente include callbacks.EarlyStopping per evitare tempi di formazione lunghi e non necessari. Nota che questo callback è impostato per monitorare val_binary_crossentropy , non val_loss . Questa differenza sarà importante in seguito.

Utilizzare callbacks.TensorBoard per generare registri TensorBoard per l'addestramento.

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

Allo stesso modo, ogni modello utilizzerà le stesse impostazioni Model.compile e Model.fit :

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

Modello minuscolo

Inizia addestrando un modello:

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
_________________________________________________________________

Epoch: 0, accuracy:0.4961,  binary_crossentropy:0.7294,  loss:0.7294,  val_accuracy:0.4840,  val_binary_crossentropy:0.7200,  val_loss:0.7200,  
....................................................................................................
Epoch: 100, accuracy:0.5931,  binary_crossentropy:0.6279,  loss:0.6279,  val_accuracy:0.5860,  val_binary_crossentropy:0.6288,  val_loss:0.6288,  
....................................................................................................
Epoch: 200, accuracy:0.6157,  binary_crossentropy:0.6178,  loss:0.6178,  val_accuracy:0.6200,  val_binary_crossentropy:0.6134,  val_loss:0.6134,  
....................................................................................................
Epoch: 300, accuracy:0.6370,  binary_crossentropy:0.6086,  loss:0.6086,  val_accuracy:0.6220,  val_binary_crossentropy:0.6055,  val_loss:0.6055,  
....................................................................................................
Epoch: 400, accuracy:0.6522,  binary_crossentropy:0.6008,  loss:0.6008,  val_accuracy:0.6260,  val_binary_crossentropy:0.5997,  val_loss:0.5997,  
....................................................................................................
Epoch: 500, accuracy:0.6513,  binary_crossentropy:0.5946,  loss:0.5946,  val_accuracy:0.6480,  val_binary_crossentropy:0.5911,  val_loss:0.5911,  
....................................................................................................
Epoch: 600, accuracy:0.6636,  binary_crossentropy:0.5894,  loss:0.5894,  val_accuracy:0.6390,  val_binary_crossentropy:0.5898,  val_loss:0.5898,  
....................................................................................................
Epoch: 700, accuracy:0.6696,  binary_crossentropy:0.5852,  loss:0.5852,  val_accuracy:0.6530,  val_binary_crossentropy:0.5870,  val_loss:0.5870,  
....................................................................................................
Epoch: 800, accuracy:0.6706,  binary_crossentropy:0.5824,  loss:0.5824,  val_accuracy:0.6590,  val_binary_crossentropy:0.5850,  val_loss:0.5850,  
....................................................................................................
Epoch: 900, accuracy:0.6709,  binary_crossentropy:0.5796,  loss:0.5796,  val_accuracy:0.6680,  val_binary_crossentropy:0.5831,  val_loss:0.5831,  
....................................................................................................
Epoch: 1000, accuracy:0.6780,  binary_crossentropy:0.5769,  loss:0.5769,  val_accuracy:0.6530,  val_binary_crossentropy:0.5851,  val_loss:0.5851,  
....................................................................................................
Epoch: 1100, accuracy:0.6735,  binary_crossentropy:0.5752,  loss:0.5752,  val_accuracy:0.6620,  val_binary_crossentropy:0.5807,  val_loss:0.5807,  
....................................................................................................
Epoch: 1200, accuracy:0.6759,  binary_crossentropy:0.5729,  loss:0.5729,  val_accuracy:0.6620,  val_binary_crossentropy:0.5792,  val_loss:0.5792,  
....................................................................................................
Epoch: 1300, accuracy:0.6849,  binary_crossentropy:0.5716,  loss:0.5716,  val_accuracy:0.6450,  val_binary_crossentropy:0.5859,  val_loss:0.5859,  
....................................................................................................
Epoch: 1400, accuracy:0.6790,  binary_crossentropy:0.5695,  loss:0.5695,  val_accuracy:0.6700,  val_binary_crossentropy:0.5776,  val_loss:0.5776,  
....................................................................................................
Epoch: 1500, accuracy:0.6824,  binary_crossentropy:0.5681,  loss:0.5681,  val_accuracy:0.6730,  val_binary_crossentropy:0.5761,  val_loss:0.5761,  
....................................................................................................
Epoch: 1600, accuracy:0.6828,  binary_crossentropy:0.5669,  loss:0.5669,  val_accuracy:0.6690,  val_binary_crossentropy:0.5766,  val_loss:0.5766,  
....................................................................................................
Epoch: 1700, accuracy:0.6874,  binary_crossentropy:0.5657,  loss:0.5657,  val_accuracy:0.6600,  val_binary_crossentropy:0.5774,  val_loss:0.5774,  
....................................................................................................
Epoch: 1800, accuracy:0.6845,  binary_crossentropy:0.5655,  loss:0.5655,  val_accuracy:0.6780,  val_binary_crossentropy:0.5752,  val_loss:0.5752,  
....................................................................................................
Epoch: 1900, accuracy:0.6837,  binary_crossentropy:0.5644,  loss:0.5644,  val_accuracy:0.6790,  val_binary_crossentropy:0.5753,  val_loss:0.5753,  
....................................................................................................
Epoch: 2000, accuracy:0.6853,  binary_crossentropy:0.5632,  loss:0.5632,  val_accuracy:0.6780,  val_binary_crossentropy:0.5753,  val_loss:0.5753,  
....................................................................................................
Epoch: 2100, accuracy:0.6871,  binary_crossentropy:0.5625,  loss:0.5625,  val_accuracy:0.6670,  val_binary_crossentropy:0.5769,  val_loss:0.5769,  
...................................

Ora controlla come ha fatto il modello:

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

Modello piccolo

Per vedere se riesci a battere le prestazioni del modello piccolo, allena progressivamente alcuni modelli più grandi.

Prova due livelli nascosti con 16 unità ciascuno:

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
_________________________________________________________________

Epoch: 0, accuracy:0.4864,  binary_crossentropy:0.7769,  loss:0.7769,  val_accuracy:0.4930,  val_binary_crossentropy:0.7211,  val_loss:0.7211,  
....................................................................................................
Epoch: 100, accuracy:0.6386,  binary_crossentropy:0.6052,  loss:0.6052,  val_accuracy:0.6020,  val_binary_crossentropy:0.6177,  val_loss:0.6177,  
....................................................................................................
Epoch: 200, accuracy:0.6697,  binary_crossentropy:0.5829,  loss:0.5829,  val_accuracy:0.6310,  val_binary_crossentropy:0.6018,  val_loss:0.6018,  
....................................................................................................
Epoch: 300, accuracy:0.6838,  binary_crossentropy:0.5721,  loss:0.5721,  val_accuracy:0.6490,  val_binary_crossentropy:0.5940,  val_loss:0.5940,  
....................................................................................................
Epoch: 400, accuracy:0.6911,  binary_crossentropy:0.5656,  loss:0.5656,  val_accuracy:0.6430,  val_binary_crossentropy:0.5985,  val_loss:0.5985,  
....................................................................................................
Epoch: 500, accuracy:0.6930,  binary_crossentropy:0.5607,  loss:0.5607,  val_accuracy:0.6430,  val_binary_crossentropy:0.6028,  val_loss:0.6028,  
.........................

Modello medio

Ora prova 3 livelli nascosti con 64 unità ciascuno:

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

E addestra il modello usando gli stessi dati:

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
_________________________________________________________________

Epoch: 0, accuracy:0.5017,  binary_crossentropy:0.6840,  loss:0.6840,  val_accuracy:0.4790,  val_binary_crossentropy:0.6723,  val_loss:0.6723,  
....................................................................................................
Epoch: 100, accuracy:0.7173,  binary_crossentropy:0.5221,  loss:0.5221,  val_accuracy:0.6470,  val_binary_crossentropy:0.6111,  val_loss:0.6111,  
....................................................................................................
Epoch: 200, accuracy:0.7884,  binary_crossentropy:0.4270,  loss:0.4270,  val_accuracy:0.6390,  val_binary_crossentropy:0.7045,  val_loss:0.7045,  
..............................................................

Modello grande

Come esercizio, puoi creare un modello ancora più grande e vedere quanto velocemente inizia a sovraadattarsi. Quindi, aggiungiamo a questo benchmark una rete che ha molta più capacità, molto più di quanto il problema giustificherebbe:

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

E, ancora, addestra il modello utilizzando gli stessi dati:

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
_________________________________________________________________

Epoch: 0, accuracy:0.5145,  binary_crossentropy:0.7740,  loss:0.7740,  val_accuracy:0.4980,  val_binary_crossentropy:0.6793,  val_loss:0.6793,  
....................................................................................................
Epoch: 100, accuracy:1.0000,  binary_crossentropy:0.0020,  loss:0.0020,  val_accuracy:0.6600,  val_binary_crossentropy:1.8540,  val_loss:1.8540,  
....................................................................................................
Epoch: 200, accuracy:1.0000,  binary_crossentropy:0.0001,  loss:0.0001,  val_accuracy:0.6560,  val_binary_crossentropy:2.5293,  val_loss:2.5293,  
..........................

Tracciare le perdite di formazione e convalida

Le linee continue mostrano la perdita di addestramento e le linee tratteggiate mostrano la perdita di convalida (ricorda: una perdita di convalida inferiore indica un modello migliore).

Sebbene la costruzione di un modello più grande gli dia più potenza, se questa potenza non è vincolata in qualche modo può facilmente adattarsi al set di allenamento.

In questo esempio, in genere, solo il modello "Tiny" riesce a evitare del tutto l'overfitting e ciascuno dei modelli più grandi si adatta più rapidamente ai dati. Questo diventa così grave per il modello "large" che è necessario cambiare la trama su una scala logaritmica per vedere davvero cosa sta succedendo.

Ciò è evidente se si tracciano e si confrontano le metriche di convalida con le metriche di addestramento.

  • È normale che ci sia una piccola differenza.
  • Se entrambe le metriche si stanno muovendo nella stessa direzione, va tutto bene.
  • Se la metrica di convalida inizia a ristagnare mentre la metrica di allenamento continua a migliorare, probabilmente sei vicino all'overfitting.
  • Se la metrica di convalida sta andando nella direzione sbagliata, il modello è chiaramente overfitting.
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

Visualizza in TensorBoard

Tutti questi modelli hanno scritto registri TensorBoard durante l'allenamento.

Aprire un visualizzatore TensorBoard incorporato all'interno di un notebook:

#docs_infra: no_execute

# Load the TensorBoard notebook extension
%load_ext tensorboard

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

È possibile visualizzare i risultati di un'esecuzione precedente di questo notebook su TensorBoard.dev .

TensorBoard.dev è un'esperienza gestita per l'hosting, il monitoraggio e la condivisione di esperimenti di machine learning con tutti.

È anche incluso in un <iframe> per comodità:

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

Se desideri condividere i risultati di TensorBoard, puoi caricare i registri su TensorBoard.dev copiando quanto segue in una cella di codice.

tensorboard dev upload --logdir  {logdir}/sizes

Strategie per prevenire l'overfitting

Prima di entrare nel contenuto di questa sezione, copia i registri di formazione dal modello "Tiny" sopra, da utilizzare come base per il confronto.

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

Aggiungi la regolarizzazione del peso

Potresti avere familiarità con il principio del rasoio di Occam: date due spiegazioni per qualcosa, la spiegazione più probabile che sia corretta è quella "più semplice", quella che fa il minor numero di ipotesi. Questo vale anche per i modelli appresi dalle reti neurali: dati alcuni dati di addestramento e un'architettura di rete, esistono più insiemi di valori di peso (modelli multipli) che potrebbero spiegare i dati e i modelli più semplici hanno meno probabilità di adattarsi a quelli complessi.

Un "modello semplice" in questo contesto è un modello in cui la distribuzione dei valori dei parametri ha meno entropia (o un modello con meno parametri del tutto, come abbiamo visto nella sezione precedente). Pertanto, un modo comune per mitigare l'overfitting è porre vincoli alla complessità di una rete forzando i suoi pesi solo ad assumere valori piccoli, il che rende più "regolare" la distribuzione dei valori di peso. Questa è chiamata "regolarizzazione del peso" e viene eseguita aggiungendo alla funzione di perdita della rete un costo associato all'avere pesi elevati. Questo costo è disponibile in due gusti:

  • Regolarizzazione L1 , dove il costo aggiunto è proporzionale al valore assoluto dei coefficienti dei pesi (ovvero a quella che viene chiamata la "norma L1" dei pesi).

  • Regolarizzazione L2 , dove il costo aggiunto è proporzionale al quadrato del valore dei coefficienti di peso (ovvero a quella che viene chiamata la "norma L2" al quadrato dei pesi). La regolarizzazione L2 è anche chiamata decadimento del peso nel contesto delle reti neurali. Non lasciarti confondere dal nome diverso: il decadimento del peso è matematicamente esattamente lo stesso della regolarizzazione L2.

La regolarizzazione L1 spinge i pesi esattamente verso lo zero incoraggiando un modello sparso. La regolarizzazione L2 penalizzerà i parametri dei pesi senza renderli scarsi poiché la penalità va a zero per pesi piccoli, uno dei motivi per cui L2 è più comune.

In tf.keras , la regolarizzazione del peso viene aggiunta passando le istanze del regolarizzatore del peso ai livelli come argomenti di parole chiave. Aggiungiamo ora la regolarizzazione del peso 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
_________________________________________________________________

Epoch: 0, accuracy:0.5126,  binary_crossentropy:0.7481,  loss:2.2415,  val_accuracy:0.4950,  val_binary_crossentropy:0.6707,  val_loss:2.0653,  
....................................................................................................
Epoch: 100, accuracy:0.6625,  binary_crossentropy:0.5945,  loss:0.6173,  val_accuracy:0.6400,  val_binary_crossentropy:0.5871,  val_loss:0.6100,  
....................................................................................................
Epoch: 200, accuracy:0.6690,  binary_crossentropy:0.5864,  loss:0.6079,  val_accuracy:0.6650,  val_binary_crossentropy:0.5856,  val_loss:0.6076,  
....................................................................................................
Epoch: 300, accuracy:0.6790,  binary_crossentropy:0.5762,  loss:0.5976,  val_accuracy:0.6550,  val_binary_crossentropy:0.5881,  val_loss:0.6095,  
....................................................................................................
Epoch: 400, accuracy:0.6843,  binary_crossentropy:0.5697,  loss:0.5920,  val_accuracy:0.6650,  val_binary_crossentropy:0.5878,  val_loss:0.6101,  
....................................................................................................
Epoch: 500, accuracy:0.6897,  binary_crossentropy:0.5651,  loss:0.5907,  val_accuracy:0.6890,  val_binary_crossentropy:0.5798,  val_loss:0.6055,  
....................................................................................................
Epoch: 600, accuracy:0.6945,  binary_crossentropy:0.5610,  loss:0.5864,  val_accuracy:0.6820,  val_binary_crossentropy:0.5772,  val_loss:0.6026,  
..........................................................

l2(0.001) significa che ogni coefficiente nella matrice di peso del livello aggiungerà 0.001 * weight_coefficient_value**2 alla perdita totale della rete.

Ecco perché stiamo monitorando direttamente binary_crossentropy . Perché non ha questo componente di regolarizzazione mescolato.

Quindi, lo stesso modello "Large" con una penalità di regolarizzazione L2 si comporta molto meglio:

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

png

Come puoi vedere, il modello regolarizzato "L2" è ora molto più competitivo rispetto al modello "Tiny" . Questo modello "L2" è anche molto più resistente all'overfitting rispetto al modello "Large" su cui era basato pur avendo lo stesso numero di parametri.

Ulteriori informazioni

Ci sono due cose importanti da notare su questo tipo di regolarizzazione.

Primo: se stai scrivendo il tuo ciclo di allenamento, devi essere sicuro di chiedere al modello le sue perdite di regolarizzazione.

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

Secondo: questa implementazione funziona aggiungendo le penalità di peso alla perdita del modello e quindi applicando una procedura di ottimizzazione standard.

C'è un secondo approccio che invece esegue l'ottimizzatore solo sulla perdita grezza, e quindi mentre applica il passaggio calcolato l'ottimizzatore applica anche un decadimento del peso. Questo "decadimento del peso disaccoppiato" è visto in optimizers.FTRL come optimizationrs.FTRL e optimizers.AdamW .

Aggiungi abbandono

Il dropout è una delle tecniche di regolarizzazione più efficaci e più comunemente utilizzate per le reti neurali, sviluppata da Hinton e dai suoi studenti dell'Università di Toronto.

La spiegazione intuitiva dell'abbandono è che, poiché i singoli nodi della rete non possono fare affidamento sull'output degli altri, ogni nodo deve produrre funzionalità utili di per sé.

Il dropout, applicato a un livello, consiste nell'"eliminare" in modo casuale (cioè impostato su zero) un certo numero di funzioni di output del livello durante l'allenamento. Diciamo che un dato livello avrebbe normalmente restituito un vettore [0.2, 0.5, 1.3, 0.8, 1.1] per un dato campione di input durante l'addestramento; dopo aver applicato il dropout, questo vettore avrà poche voci zero distribuite casualmente, ad esempio [0, 0.5, 1.3, 0, 1.1].

Il "tasso di abbandono" è la frazione delle caratteristiche che vengono azzerate; di solito è impostato tra 0,2 e 0,5. Al momento del test, nessuna unità viene eliminata e invece i valori di output del livello vengono ridimensionati di un fattore pari al tasso di abbandono, in modo da bilanciare il fatto che sono attive più unità rispetto al tempo di addestramento.

In tf.keras puoi introdurre il dropout in una rete tramite il livello Dropout, che viene applicato all'output del livello subito prima.

Aggiungiamo due livelli di abbandono nella nostra rete per vedere come si comportano bene nel ridurre l'overfitting:

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
_________________________________________________________________

Epoch: 0, accuracy:0.4961,  binary_crossentropy:0.8110,  loss:0.8110,  val_accuracy:0.5330,  val_binary_crossentropy:0.6900,  val_loss:0.6900,  
....................................................................................................
Epoch: 100, accuracy:0.6557,  binary_crossentropy:0.5961,  loss:0.5961,  val_accuracy:0.6710,  val_binary_crossentropy:0.5788,  val_loss:0.5788,  
....................................................................................................
Epoch: 200, accuracy:0.6871,  binary_crossentropy:0.5622,  loss:0.5622,  val_accuracy:0.6860,  val_binary_crossentropy:0.5856,  val_loss:0.5856,  
....................................................................................................
Epoch: 300, accuracy:0.7246,  binary_crossentropy:0.5121,  loss:0.5121,  val_accuracy:0.6820,  val_binary_crossentropy:0.5927,  val_loss:0.5927,  
............
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

È chiaro da questa trama che entrambi questi approcci di regolarizzazione migliorano il comportamento del modello "Large" . Ma questo ancora non batte nemmeno la linea di base "Tiny" .

Quindi provali entrambi, insieme, e vedi se funziona meglio.

Combinato L2 + drop out

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
_________________________________________________________________

Epoch: 0, accuracy:0.5090,  binary_crossentropy:0.8064,  loss:0.9648,  val_accuracy:0.4660,  val_binary_crossentropy:0.6877,  val_loss:0.8454,  
....................................................................................................
Epoch: 100, accuracy:0.6445,  binary_crossentropy:0.6050,  loss:0.6350,  val_accuracy:0.6630,  val_binary_crossentropy:0.5871,  val_loss:0.6169,  
....................................................................................................
Epoch: 200, accuracy:0.6660,  binary_crossentropy:0.5932,  loss:0.6186,  val_accuracy:0.6880,  val_binary_crossentropy:0.5722,  val_loss:0.5975,  
....................................................................................................
Epoch: 300, accuracy:0.6697,  binary_crossentropy:0.5818,  loss:0.6100,  val_accuracy:0.6900,  val_binary_crossentropy:0.5614,  val_loss:0.5895,  
....................................................................................................
Epoch: 400, accuracy:0.6749,  binary_crossentropy:0.5742,  loss:0.6046,  val_accuracy:0.6870,  val_binary_crossentropy:0.5576,  val_loss:0.5881,  
....................................................................................................
Epoch: 500, accuracy:0.6854,  binary_crossentropy:0.5703,  loss:0.6029,  val_accuracy:0.6970,  val_binary_crossentropy:0.5458,  val_loss:0.5784,  
....................................................................................................
Epoch: 600, accuracy:0.6806,  binary_crossentropy:0.5673,  loss:0.6015,  val_accuracy:0.6980,  val_binary_crossentropy:0.5453,  val_loss:0.5795,  
....................................................................................................
Epoch: 700, accuracy:0.6937,  binary_crossentropy:0.5583,  loss:0.5938,  val_accuracy:0.6870,  val_binary_crossentropy:0.5477,  val_loss:0.5832,  
....................................................................................................
Epoch: 800, accuracy:0.6911,  binary_crossentropy:0.5576,  loss:0.5947,  val_accuracy:0.7000,  val_binary_crossentropy:0.5446,  val_loss:0.5817,  
.......................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

Questo modello con la regolarizzazione "Combined" è ovviamente il migliore finora.

Visualizza in TensorBoard

Questi modelli hanno anche registrato i log TensorBoard.

Per aprire un visualizzatore di tensorboard incorporato all'interno di un notebook, copia quanto segue in una cella di codice:

%tensorboard --logdir {logdir}/regularizers

È possibile visualizzare i risultati di un'esecuzione precedente di questo notebook su TensorDoard.dev .

È anche incluso in un <iframe> per comodità:

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

Questo è stato caricato con:

tensorboard dev upload --logdir  {logdir}/regularizers

Conclusioni

Per ricapitolare: ecco i modi più comuni per prevenire l'overfitting nelle reti neurali:

  • Ottieni più dati di allenamento.
  • Ridurre la capacità della rete.
  • Aggiungi la regolarizzazione del peso.
  • Aggiungi abbandono.

Due approcci importanti non trattati in questa guida sono:

  • aumento dei dati
  • normalizzazione batch

Ricorda che ogni metodo può aiutare da solo, ma spesso combinarli può essere ancora più 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.