![]() | ![]() | ![]() | ![]() |
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)
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')
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)
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]')
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)
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)
È 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)
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.