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