RSVP per il tuo evento TensorFlow Everywhere locale oggi!
Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Overfit e underfit

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub Scarica taccuino

Come sempre, il codice in questo esempio utilizzerà l'API tf.keras , di cui puoi trovare ulteriori informazioni nella guida di TensorFlow Keras .

In entrambi gli esempi precedenti, la classificazione del testo e la 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 eccessivamente 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 visto prima).

L'opposto dell'overfitting è l' underfitting . L'underfitting si verifica quando c'è ancora spazio per migliorare i dati del treno. Ciò può accadere per una serie di ragioni: se il modello non è abbastanza potente, è troppo regolarizzato 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, tuttavia, il modello inizierà a sovradimensionarsi e apprendere modelli dai dati di allenamento che non si generalizzano ai dati di test. Dobbiamo trovare un equilibrio. Capire come allenarsi per un numero appropriato di epoche come esploreremo di seguito è un'abilità utile.

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

Un modello addestrato su dati più completi si generalizzerà naturalmente meglio. Quando ciò non è più possibile, la prossima soluzione migliore è usare 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 modelli, il processo di ottimizzazione la costringerà a concentrarsi sui modelli più importanti, che hanno maggiori possibilità di generalizzare bene.

In questo taccuino, esploreremo diverse tecniche di regolarizzazione comuni e le useremo 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.4.0

!pip install -q 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 funzionalità 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 [==============================] - 120s 0us/step

FEATURES = 28

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

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 funzione seguente ricompila 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 reimballare ogni riga individualmente, crea un nuovo Dataset che accetta batch di 10000 esempi, applica la funzione pack_row a ciascun batch e quindi suddivide i batch in singoli record:

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

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

Le funzionalità 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 10000 per l'addestramento:

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, utilizza il metodo Dataset.cache per assicurarti che il caricatore non debba rileggere i dati dal file in ogni epoca:

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

Questi set di dati restituiscono singoli esempi. Utilizzare il metodo .batch per creare batch di dimensioni appropriate per l'addestramento. Prima di .shuffle batch, 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 overfitting

Il modo più semplice per prevenire l'overfitting è iniziare con un modello piccolo: un modello con un numero ridotto di parametri apprendibili (che è 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 ei loro obiettivi, una mappatura senza alcun potere di generalizzazione, ma ciò sarebbe inutile quando si fanno previsioni su dati mai visti prima.

Tienilo sempre a mente: i modelli di deep learning tendono ad essere bravi ad adattarsi ai dati di formazione, 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 facilmente la mappatura. Per ridurre al minimo la sua perdita, dovrà apprendere rappresentazioni compresse che hanno un maggiore potere predittivo. Allo stesso tempo, se rendi il tuo modello troppo piccolo, avrà difficoltà ad adattarsi ai dati di allenamento. Esiste 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 la giusta dimensione 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 la dimensione dei livelli o aggiungere nuovi livelli fino a quando non si vedono rendimenti decrescenti sulla perdita di convalida.

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

Procedura di formazione

Molti modelli si allenano meglio se riduci gradualmente il tasso di apprendimento durante l'allenamento. Utilizza 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 sopra imposta unaschedules.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

Ogni modello in questo tutorial utilizzerà la stessa configurazione di addestramento. Quindi impostali in modo riutilizzabile, iniziando con l'elenco delle richiamate.

La formazione per questo tutorial dura molte epoche brevi. Per ridurre il rumore di registrazione usa tfdocs.EpochDots che stampa semplicemente un file . per ogni epoca e un set completo di metriche ogni 100 epoche.

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

Usa 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0027s vs `on_train_batch_end` time: 0.0064s). Check your callbacks.

Epoch: 0, accuracy:0.5142,  binary_crossentropy:0.7823,  loss:0.7823,  val_accuracy:0.5230,  val_binary_crossentropy:0.7322,  val_loss:0.7322,  
....................................................................................................
Epoch: 100, accuracy:0.5933,  binary_crossentropy:0.6284,  loss:0.6284,  val_accuracy:0.5730,  val_binary_crossentropy:0.6271,  val_loss:0.6271,  
....................................................................................................
Epoch: 200, accuracy:0.6176,  binary_crossentropy:0.6135,  loss:0.6135,  val_accuracy:0.5950,  val_binary_crossentropy:0.6167,  val_loss:0.6167,  
....................................................................................................
Epoch: 300, accuracy:0.6411,  binary_crossentropy:0.6011,  loss:0.6011,  val_accuracy:0.6230,  val_binary_crossentropy:0.6052,  val_loss:0.6052,  
....................................................................................................
Epoch: 400, accuracy:0.6542,  binary_crossentropy:0.5943,  loss:0.5943,  val_accuracy:0.6300,  val_binary_crossentropy:0.6003,  val_loss:0.6003,  
....................................................................................................
Epoch: 500, accuracy:0.6569,  binary_crossentropy:0.5903,  loss:0.5903,  val_accuracy:0.6470,  val_binary_crossentropy:0.5949,  val_loss:0.5949,  
....................................................................................................
Epoch: 600, accuracy:0.6631,  binary_crossentropy:0.5872,  loss:0.5872,  val_accuracy:0.6550,  val_binary_crossentropy:0.5927,  val_loss:0.5927,  
....................................................................................................
Epoch: 700, accuracy:0.6673,  binary_crossentropy:0.5846,  loss:0.5846,  val_accuracy:0.6690,  val_binary_crossentropy:0.5905,  val_loss:0.5905,  
....................................................................................................
Epoch: 800, accuracy:0.6743,  binary_crossentropy:0.5824,  loss:0.5824,  val_accuracy:0.6710,  val_binary_crossentropy:0.5890,  val_loss:0.5890,  
....................................................................................................
Epoch: 900, accuracy:0.6739,  binary_crossentropy:0.5815,  loss:0.5815,  val_accuracy:0.6630,  val_binary_crossentropy:0.5888,  val_loss:0.5888,  
....................................................................................................
Epoch: 1000, accuracy:0.6748,  binary_crossentropy:0.5791,  loss:0.5791,  val_accuracy:0.6610,  val_binary_crossentropy:0.5881,  val_loss:0.5881,  
....................................................................................................
Epoch: 1100, accuracy:0.6763,  binary_crossentropy:0.5774,  loss:0.5774,  val_accuracy:0.6710,  val_binary_crossentropy:0.5875,  val_loss:0.5875,  
....................................................................................................
Epoch: 1200, accuracy:0.6767,  binary_crossentropy:0.5763,  loss:0.5763,  val_accuracy:0.6630,  val_binary_crossentropy:0.5869,  val_loss:0.5869,  
....................................................................................................
Epoch: 1300, accuracy:0.6833,  binary_crossentropy:0.5750,  loss:0.5750,  val_accuracy:0.6550,  val_binary_crossentropy:0.5878,  val_loss:0.5878,  
....................................................................................................
Epoch: 1400, accuracy:0.6766,  binary_crossentropy:0.5737,  loss:0.5737,  val_accuracy:0.6710,  val_binary_crossentropy:0.5865,  val_loss:0.5865,  
....................................................................................................
Epoch: 1500, accuracy:0.6806,  binary_crossentropy:0.5727,  loss:0.5727,  val_accuracy:0.6630,  val_binary_crossentropy:0.5864,  val_loss:0.5864,  
....................................................................................................
Epoch: 1600, accuracy:0.6807,  binary_crossentropy:0.5717,  loss:0.5717,  val_accuracy:0.6720,  val_binary_crossentropy:0.5859,  val_loss:0.5859,  
....................................................................................................
Epoch: 1700, accuracy:0.6823,  binary_crossentropy:0.5707,  loss:0.5707,  val_accuracy:0.6570,  val_binary_crossentropy:0.5872,  val_loss:0.5872,  
..........

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, addestra 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0028s vs `on_train_batch_end` time: 0.0112s). Check your callbacks.

Epoch: 0, accuracy:0.5037,  binary_crossentropy:0.9699,  loss:0.9699,  val_accuracy:0.4950,  val_binary_crossentropy:0.7648,  val_loss:0.7648,  
....................................................................................................
Epoch: 100, accuracy:0.6141,  binary_crossentropy:0.6173,  loss:0.6173,  val_accuracy:0.6000,  val_binary_crossentropy:0.6165,  val_loss:0.6165,  
....................................................................................................
Epoch: 200, accuracy:0.6532,  binary_crossentropy:0.5935,  loss:0.5935,  val_accuracy:0.6430,  val_binary_crossentropy:0.6023,  val_loss:0.6023,  
....................................................................................................
Epoch: 300, accuracy:0.6762,  binary_crossentropy:0.5797,  loss:0.5797,  val_accuracy:0.6540,  val_binary_crossentropy:0.5987,  val_loss:0.5987,  
....................................................................................................
Epoch: 400, accuracy:0.6843,  binary_crossentropy:0.5725,  loss:0.5725,  val_accuracy:0.6570,  val_binary_crossentropy:0.6005,  val_loss:0.6005,  
.............................................................................

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 utilizzando 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0026s vs `on_train_batch_end` time: 0.0107s). Check your callbacks.

Epoch: 0, accuracy:0.4897,  binary_crossentropy:0.7502,  loss:0.7502,  val_accuracy:0.4590,  val_binary_crossentropy:0.7012,  val_loss:0.7012,  
....................................................................................................
Epoch: 100, accuracy:0.7040,  binary_crossentropy:0.5396,  loss:0.5396,  val_accuracy:0.6760,  val_binary_crossentropy:0.5956,  val_loss:0.5956,  
....................................................................................................
Epoch: 200, accuracy:0.7784,  binary_crossentropy:0.4380,  loss:0.4380,  val_accuracy:0.6560,  val_binary_crossentropy:0.6714,  val_loss:0.6714,  
.......................................................................

Modello grande

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

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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0035s vs `on_train_batch_end` time: 0.0129s). Check your callbacks.

Epoch: 0, accuracy:0.4901,  binary_crossentropy:0.8508,  loss:0.8508,  val_accuracy:0.5540,  val_binary_crossentropy:0.6800,  val_loss:0.6800,  
....................................................................................................
Epoch: 100, accuracy:1.0000,  binary_crossentropy:0.0025,  loss:0.0025,  val_accuracy:0.6480,  val_binary_crossentropy:1.7541,  val_loss:1.7541,  
....................................................................................................
Epoch: 200, accuracy:1.0000,  binary_crossentropy:0.0002,  loss:0.0002,  val_accuracy:0.6530,  val_binary_crossentropy:2.4090,  val_loss:2.4090,  
....................

Traccia le perdite di addestramento 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).

Mentre costruire un modello più grande gli conferisce più potenza, se questo potere non è limitato 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 supera i dati più rapidamente. 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 traccia e si confrontano le metriche di convalida con le metriche di addestramento.

  • È normale che ci sia una piccola differenza.
  • Se entrambe le metriche si muovono nella stessa direzione, va tutto bene.
  • Se la metrica di convalida inizia a ristagnare mentre la metrica di formazione 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 i log di TensorBoard durante l'addestramento.

Apri un visualizzatore TensorBoard incorporato in un blocco note:


# Load the TensorBoard notebook extension
%load_ext tensorboard

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

È possibile visualizzare i risultati di una precedente esecuzione di questo blocco appunti su TensorBoard.dev .

TensorBoard.dev è un'esperienza gestita per l'hosting, il monitoraggio e la condivisione di esperimenti ML 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 log 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 allenamento dal modello "Tiny" sopra, da utilizzare come riferimento per il confronto.

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

Aggiungi la regolarizzazione del peso

Potresti avere familiarità con il principio Razor di Occam: date due spiegazioni per qualcosa, la spiegazione più probabile è quella "più semplice", quella che fa il minor numero di supposizioni. Questo vale anche per i modelli appresi dalle reti neurali: dati alcuni dati di addestramento e un'architettura di rete, ci sono più insiemi di valori di pesi (più modelli) che potrebbero spiegare i dati, e i modelli più semplici hanno meno probabilità di overfit rispetto 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 complessivamente, come abbiamo visto nella sezione precedente). Quindi un modo comune per mitigare l'overfitting è quello di porre vincoli alla complessità di una rete costringendo i suoi pesi a prendere solo valori piccoli, il che rende la distribuzione dei valori di peso più "regolare". Questa si chiama "regolarizzazione del peso" e si ottiene aggiungendo alla funzione di perdita della rete un costo associato all'avere grandi pesi. Questo costo è disponibile in due gusti:

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

  • Regolarizzazione L2 , dove il costo aggiunto è proporzionale al quadrato del valore dei coefficienti dei pesi (cioè 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 identico alla regolarizzazione L2.

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

In tf.keras , la regolarizzazione del peso viene aggiunta passando istanze di regolarizzazione 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0034s vs `on_train_batch_end` time: 0.0134s). Check your callbacks.

Epoch: 0, accuracy:0.5162,  binary_crossentropy:0.7512,  loss:2.2423,  val_accuracy:0.4830,  val_binary_crossentropy:0.6906,  val_loss:2.0808,  
....................................................................................................
Epoch: 100, accuracy:0.6586,  binary_crossentropy:0.5961,  loss:0.6196,  val_accuracy:0.6590,  val_binary_crossentropy:0.5953,  val_loss:0.6188,  
....................................................................................................
Epoch: 200, accuracy:0.6736,  binary_crossentropy:0.5817,  loss:0.6037,  val_accuracy:0.6610,  val_binary_crossentropy:0.5796,  val_loss:0.6015,  
....................................................................................................
Epoch: 300, accuracy:0.6840,  binary_crossentropy:0.5771,  loss:0.6019,  val_accuracy:0.6770,  val_binary_crossentropy:0.5868,  val_loss:0.6124,  
....................................................................................................
Epoch: 400, accuracy:0.6926,  binary_crossentropy:0.5668,  loss:0.5924,  val_accuracy:0.6670,  val_binary_crossentropy:0.5748,  val_loss:0.6004,  
....................................................................................................
Epoch: 500, accuracy:0.6991,  binary_crossentropy:0.5550,  loss:0.5824,  val_accuracy:0.6890,  val_binary_crossentropy:0.5743,  val_loss:0.6018,  
....................................................................................................
Epoch: 600, accuracy:0.7073,  binary_crossentropy:0.5475,  loss:0.5758,  val_accuracy:0.6800,  val_binary_crossentropy:0.5734,  val_loss:0.6016,  
....................................................................................................
Epoch: 700, accuracy:0.7139,  binary_crossentropy:0.5381,  loss:0.5666,  val_accuracy:0.6770,  val_binary_crossentropy:0.5848,  val_loss:0.6132,  
....................................................................................................
Epoch: 800, accuracy:0.7169,  binary_crossentropy:0.5354,  loss:0.5645,  val_accuracy:0.6920,  val_binary_crossentropy:0.5835,  val_loss:0.6126,  
...........

l2(0.001) significa che ogni coefficiente nella matrice del peso del layer 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 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 con il modello "Tiny" . Questo modello "L2" è anche molto più resistente all'overfitting rispetto al modello "Large" 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 assicurarti 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 poi mentre applica il passo calcolato l'ottimizzatore applica anche un certo decadimento del peso. Questo "decadimento del peso disaccoppiato" è visibile in ottimizzatori come optimizers.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 per il dropout è che, poiché i singoli nodi della rete non possono fare affidamento sull'output degli altri, ogni nodo deve produrre funzionalità che sono utili per conto proprio.

L'abbandono, applicato a un livello, consiste nel "eliminare" in modo casuale (ovvero impostato a zero) una serie di caratteristiche di output del livello durante l'allenamento. Diciamo che un dato livello normalmente avrebbe 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 funzionalità che vengono azzerate; di solito è impostato tra 0,2 e 0,5. In fase di test, nessuna unità viene eliminata e invece i valori di output del livello vengono ridotti di un fattore pari al tasso di abbandono, in modo da bilanciare il fatto che più unità sono attive rispetto al momento dell'addestramento.

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

Aggiungiamo due livelli di Dropout nella nostra rete per vedere come riescono a 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
_________________________________________________________________
WARNING:tensorflow:Callback method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0036s vs `on_train_batch_end` time: 0.0141s). Check your callbacks.

Epoch: 0, accuracy:0.4944,  binary_crossentropy:0.8070,  loss:0.8070,  val_accuracy:0.5630,  val_binary_crossentropy:0.6919,  val_loss:0.6919,  
....................................................................................................
Epoch: 100, accuracy:0.6579,  binary_crossentropy:0.5956,  loss:0.5956,  val_accuracy:0.6780,  val_binary_crossentropy:0.5794,  val_loss:0.5794,  
....................................................................................................
Epoch: 200, accuracy:0.6878,  binary_crossentropy:0.5569,  loss:0.5569,  val_accuracy:0.6760,  val_binary_crossentropy:0.5896,  val_loss:0.5896,  
....................................................................................................
Epoch: 300, accuracy:0.7189,  binary_crossentropy:0.5084,  loss:0.5084,  val_accuracy:0.6960,  val_binary_crossentropy:0.6009,  val_loss:0.6009,  
....................
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])
(0.5, 0.7)

png

È chiaro da questo grafico 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.

Forcellino combinato L2 +

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_end` is slow compared to the batch time (batch time: 0.0040s vs `on_train_batch_end` time: 0.0151s). Check your callbacks.

Epoch: 0, accuracy:0.5052,  binary_crossentropy:0.8050,  loss:0.9634,  val_accuracy:0.4700,  val_binary_crossentropy:0.6965,  val_loss:0.8544,  
....................................................................................................
Epoch: 100, accuracy:0.6432,  binary_crossentropy:0.6061,  loss:0.6349,  val_accuracy:0.6440,  val_binary_crossentropy:0.5868,  val_loss:0.6154,  
....................................................................................................
Epoch: 200, accuracy:0.6633,  binary_crossentropy:0.5916,  loss:0.6159,  val_accuracy:0.6420,  val_binary_crossentropy:0.5788,  val_loss:0.6031,  
....................................................................................................
Epoch: 300, accuracy:0.6685,  binary_crossentropy:0.5828,  loss:0.6107,  val_accuracy:0.6900,  val_binary_crossentropy:0.5632,  val_loss:0.5911,  
....................................................................................................
Epoch: 400, accuracy:0.6719,  binary_crossentropy:0.5808,  loss:0.6102,  val_accuracy:0.6880,  val_binary_crossentropy:0.5620,  val_loss:0.5914,  
....................................................................................................
Epoch: 500, accuracy:0.6776,  binary_crossentropy:0.5739,  loss:0.6050,  val_accuracy:0.6880,  val_binary_crossentropy:0.5539,  val_loss:0.5851,  
....................................................................................................
Epoch: 600, accuracy:0.6856,  binary_crossentropy:0.5669,  loss:0.6006,  val_accuracy:0.6960,  val_binary_crossentropy:0.5493,  val_loss:0.5830,  
....................................................................................................
Epoch: 700, accuracy:0.6833,  binary_crossentropy:0.5648,  loss:0.5996,  val_accuracy:0.6980,  val_binary_crossentropy:0.5430,  val_loss:0.5778,  
....................................................................................................
Epoch: 800, accuracy:0.6943,  binary_crossentropy:0.5594,  loss:0.5955,  val_accuracy:0.6930,  val_binary_crossentropy:0.5412,  val_loss:0.5772,  
....................................................................................................
Epoch: 900, accuracy:0.6966,  binary_crossentropy:0.5558,  loss:0.5932,  val_accuracy:0.7060,  val_binary_crossentropy:0.5419,  val_loss:0.5793,  
.................................................................................................
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 registri di TensorBoard.

Per aprire un visualizzatore di tensorboard incorporato in un blocco note, copia quanto segue in una cella di codice:

%tensorboard --logdir {logdir}/regularizers

È possibile visualizzare i risultati di una precedente esecuzione di questo blocco appunti 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

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

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

Due importanti approcci 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.