1. Panoramica
In questo lab imparerai l'architettura convoluzionale moderna e utilizzerai le tue conoscenze per implementare una rete convoluzionale semplice ma efficace chiamata "squeezenet".
Questo lab include le spiegazioni teoriche necessarie sulle reti neurali convoluzionali ed è un buon punto di partenza per gli sviluppatori che vogliono imparare a utilizzare il deep learning.
Questo lab è la Parte 4 della serie "Keras on TPU". Puoi farlo nel seguente ordine oppure in modo indipendente.
- Pipeline di dati a velocità TPU: tf.data.Dataset e TFRecords
- Il tuo primo modello Keras con il transfer learning
- Reti neurali convoluzionali con Keras e TPU
- [QUESTO LABORA] Convnet moderne, Squeezenet, Xception, con Keras e TPU
Obiettivi didattici
- Per padroneggiare lo stile funzionale di Keras
- a creare un modello usando l'architettura Squeezenet.
- Utilizzare le TPU per eseguire l'addestramento in modo rapido e eseguire l'iterazione dell'architettura
- a implementare l'aumento dei dati con tf.data.dataset
- Per ottimizzare un modello di grandi dimensioni preaddestrato (Xception) su TPU
Feedback
Se noti qualcosa di strano in questo lab, non esitare a comunicarcelo. I feedback possono essere inviati tramite i problemi di GitHub [link al feedback].
2. Guida rapida di Google Colaboratory
Questo lab utilizza Google Collaboratory e non richiede alcuna configurazione da parte tua. Colaboratory è una piattaforma di notebook online per scopi didattici. Offre l'addestramento senza costi di CPU, GPU e TPU.
Puoi aprire questo notebook di esempio ed eseguire un paio di celle per familiarizzare con Colaboratory.
Seleziona un backend TPU
Nel menu di Colab, seleziona Runtime > Cambia tipo di runtime e poi TPU. In questo codelab utilizzerai una potente TPU (Tensor Processing Unit) supportata per l'addestramento con accelerazione hardware. Il collegamento al runtime viene eseguito automaticamente alla prima esecuzione oppure puoi utilizzare il pulsante "Connetti" nell'angolo in alto a destra.
Esecuzione del notebook
Esegui le celle una alla volta facendo clic su una cella e utilizzando Maiusc-Invio. Puoi anche eseguire l'intero blocco note con Runtime > Esegui tutto.
Sommario
Tutti i blocchi note hanno un sommario. Puoi aprirlo utilizzando la freccia nera a sinistra.
Celle nascoste
Alcune celle mostreranno solo il titolo. Si tratta di una funzionalità del blocco note specifica per Colab. Puoi fare doppio clic per visualizzare il codice al loro interno, ma in genere non è molto interessante. In genere, sono funzioni di supporto o di visualizzazione. Devi comunque eseguire queste celle per definire le funzioni all'interno.
Autenticazione
Colab può accedere ai tuoi bucket Google Cloud Storage privati, a condizione che tu esegua l'autenticazione con un account autorizzato. Lo snippet di codice riportato sopra attiverà un processo di autenticazione.
3. [INFO] Che cosa sono le Tensor Processing Unit (TPU)?
In sintesi
Il codice per l'addestramento di un modello su TPU in Keras (e il fallback su GPU o CPU se non è disponibile una TPU):
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
Oggi useremo le TPU per creare e ottimizzare un classificatore di fiori a velocità interattive (minuti per addestramento).
Perché le TPU?
Le GPU moderne sono organizzate in base a "core" programmabili, un'architettura molto flessibile che consente loro di gestire una varietà di attività come rendering 3D, deep learning, simulazioni fisiche e così via. Le TPU, invece, abbinano un classico processore vettoriale a un'unità di moltiplicazione della matrice dedicata e eccellono in qualsiasi attività in cui dominano le moltiplicazioni di matrici di grandi dimensioni, come le reti neurali.
Illustrazione: un livello di rete neurale denso come moltiplicazione di matrici, con un batch di otto immagini elaborate contemporaneamente dalla rete neurale. Esegui la moltiplicazione di una riga per una colonna per verificare che venga effettivamente eseguita una somma ponderata di tutti i valori dei pixel di un'immagine. Anche i livelli convoluzionali possono essere rappresentati come moltiplicazioni di matrici, anche se è un po' più complicato ( spiegazione qui, nella sezione 1).
L'hardware
MXU e VPU
Un core TPU v2 è costituito da un'unità Matrix Multiply (MXU) che esegue moltiplicazioni matriciali e da una Vector Processing Unit (VPU) per tutte le altre attività, come attivazioni, softmax, ecc. La VPU gestisce i calcoli in float32 e int32. L'unità MXU, invece, opera in un formato a virgola mobile con precisione mista di 16-32 bit.
Numeri in virgola mobile con precisione mista e bfloat16
MXU calcola le moltiplicazioni matriciali utilizzando gli input bfloat16 e gli output float32. Le accumulazioni intermedie vengono eseguite con precisione float32.
L'addestramento della rete neurale è in genere resistente al rumore introdotto da una precisione in virgola mobile ridotta. In alcuni casi, il rumore aiuta addirittura l'ottimizzatore a convergere. La precisione in virgola mobile a 16 bit è stata tradizionalmente utilizzata per accelerare i calcoli, ma i formati float16 e float32 hanno intervalli molto diversi. La riduzione della precisione da float32 a float16 solitamente si verifica in overflow e underflow. Esistono soluzioni, ma in genere è necessario un lavoro aggiuntivo per far funzionare float16.
Questo è il motivo per cui Google ha introdotto il formato bfloat16 nelle TPU. bfloat16 è un float32 troncato con esattamente gli stessi bit e lo stesso intervallo esponente di float32. Ciò, aggiunto al fatto che le TPU calcolano le moltiplicazioni delle matrici in precisione mista con gli input bfloat16 ma con gli output float32, e ciò significa che, in genere, non sono necessarie modifiche al codice per trarre vantaggio dai guadagni in termini di prestazioni derivanti dalla precisione ridotta.
Array di sistolica
L'MXU implementa le moltiplicazioni di matrici in hardware utilizzando una cosiddetta architettura "array sistolica" in cui gli elementi di dati fluiscono attraverso un array di unità di calcolo hardware. In medicina, "sistolica" si riferisce alle contrazioni cardiache e al flusso sanguigno, qui al flusso di dati.
L'elemento di base di una moltiplicazione di matrici è un prodotto scalare tra una riga di una matrice e una colonna dell'altra matrice (vedi l'illustrazione nella parte superiore di questa sezione). Per una moltiplicazione matriciale Y=X*W, un elemento del risultato sarebbe:
Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]
Su una GPU, questo prodotto scalare viene programmato in un "core" della GPU e poi eseguito su tutti i "core" disponibili in parallelo per provare a calcolare contemporaneamente tutti i valori della matrice risultante. Se la matrice risultante è grande 128x128, ciò richiederebbe la disponibilità dei "core" 128x128=16K, cosa che in genere non è possibile. Le GPU più grandi hanno circa 4000 core. Una TPU, invece, utilizza il minimo indispensabile di hardware per le unità di calcolo nell'unità MXU: solo moltiplicatori-accumulatori bfloat16 x bfloat16 => float32
, nient'altro. Queste sono così piccole che una TPU può implementarne 16K in un MXU di 128x128 ed elaborare questa moltiplicazione della matrice in una volta sola.
Illustrazione: l'array sistolica MXU. Gli elementi di calcolo sono moltiplicatori-accumulatori. I valori di una matrice vengono caricati nell'array (punti rossi). I valori dell'altra matrice fluiscono attraverso l'array (punti grigi). Le linee verticali propagano i valori verso l'alto. Le linee orizzontali propagano somme parziali. Spetta all'utente verificare che, man mano che i dati passano attraverso l'array, il risultato della moltiplicazione della matrice venga visualizzato sul lato destro.
Inoltre, mentre i prodotti scalare vengono calcolati in un MXU, le somme intermedie si limitano a passare tra le unità di calcolo adiacenti. Non è necessario archiviarli e recuperarli nella/dalla memoria o persino in un file di registro. Il risultato finale è che l'architettura dell'array di sistolica TPU ha un significativo vantaggio in termini di densità e potenza, oltre a un vantaggio in termini di velocità non trascurabile rispetto a una GPU, quando si calcolano le moltiplicazioni delle matrici.
Cloud TPU
Quando richiedi una "Cloud TPU v2" su Google Cloud Platform, ottieni una macchina virtuale (VM) con una scheda TPU collegata al PCI. La scheda TPU ha quattro chip TPU dual-core. Ogni core TPU è dotato di una VPU (Vector Processing Unit) e di una MXU (MatriX moltiplicazione) 128 x 128. In genere, questa "Cloud TPU" viene connessa tramite la rete alla VM che l'ha richiesta. Il quadro completo è il seguente:
Illustrazione: la VM con un acceleratore "Cloud TPU" collegato alla rete. La "Cloud TPU" stessa è composta da una VM con una scheda TPU collegata su PCI con quattro chip TPU dual-core.
Pod TPU
Nei data center di Google, le TPU sono collegate a un'interconnessione HPC (computing ad alte prestazioni) che può farle apparire come un unico acceleratore molto grande. Google li chiama pod e possono comprendere fino a 512 core TPU v2 o 2048 core TPU v3.
Illustrazione: un pod TPU v3. Schede e rack TPU connessi tramite interconnessione HPC.
Durante l'addestramento, i gradienti vengono scambiati tra i core TPU utilizzando l'algoritmo all-Reduce ( qui è una buona spiegazione di all-Reduce). Il modello in fase di addestramento può sfruttare l'hardware eseguendo l'addestramento su batch di grandi dimensioni.
Illustrazione: sincronizzazione delle derive durante l'addestramento mediante l'algoritmo all-reduce sulla rete HPC a mesh toroidale 2D di Google TPU.
Il software
Addestramento di grandi dimensioni del batch
La dimensione del batch ideale per le TPU è di 128 elementi di dati per core TPU, ma l'hardware può già mostrare un buon utilizzo da 8 elementi di dati per core TPU. Ricorda che una Cloud TPU ha 8 core.
In questo codelab utilizzeremo l'API Keras. In Keras, il batch specificato è la dimensione globale del batch per l'intera TPU. I batch verranno suddivisi automaticamente in 8 ed eseguiti sugli 8 core della TPU.
Per ulteriori suggerimenti sul rendimento, consulta la guida al rendimento delle TPU. Per batch di dimensioni molto grandi, in alcuni modelli potrebbe essere necessaria una particolare attenzione. Per ulteriori dettagli, consulta LARSOptimizer.
Dietro le quinte: XLA
I programmi TensorFlow definiscono i grafici di calcolo. La TPU non esegue direttamente il codice Python, ma il grafo di calcolo definito dal programma TensorFlow. Sotto il cofano, un compilatore chiamato XLA (accelerated Linear Algebra compiler) trasforma il grafo di TensorFlow dei nodi di calcolo in codice macchina TPU. Questo compilatore esegue anche molte ottimizzazioni avanzate sul codice e sul layout della memoria. La compilazione avviene automaticamente quando il lavoro viene inviato alla TPU. Non è necessario includere in modo esplicito XLA nella catena di build.
Illustrazione: per l'esecuzione su TPU, il grafo di calcolo definito dal programma Tensorflow viene prima tradotto in una rappresentazione XLA (compiler di algebra lineare accelerata) e poi compilato da XLA in codice macchina TPU.
Utilizzo delle TPU in Keras
Le TPU sono supportate tramite l'API Keras a partire da TensorFlow 2.1. Il supporto Keras funziona su TPU e pod di TPU. Ecco un esempio che funziona su TPU, GPU e CPU:
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
In questo snippet di codice:
TPUClusterResolver().connect()
trova la TPU sulla rete. Funziona senza parametri sulla maggior parte dei sistemi Google Cloud (job AI Platform, Colaboratory, Kubeflow, VM di deep learning creati tramite l'utilità "ctpu up"). Questi sistemi sanno dove si trova la TPU grazie a una variabile di ambiente TPU_NAME. Se crei una TPU manualmente, imposta la variabile di ambiente TPU_NAME sulla VM da cui la utilizzi o chiamaTPUClusterResolver
con parametri espliciti:TPUClusterResolver(tp_uname, zone, project)
TPUStrategy
è la parte che implementa la distribuzione e l'algoritmo di sincronizzazione del gradiente "all-reduce".- La strategia viene applicata tramite un ambito. Il modello deve essere definito all'interno di scope().
- La funzione
tpu_model.fit
prevede un oggetto tf.data.Dataset per l'input per l'addestramento delle TPU.
Attività comuni di porting su TPU
- Sebbene esistano molti modi per caricare i dati in un modello TensorFlow, per le TPU è necessario l'uso dell'API
tf.data.Dataset
. - Le TPU sono molto veloci e l'importazione dei dati spesso diventa un collo di bottiglia quando vengono eseguite. Nella guida al rendimento delle TPU sono disponibili strumenti che puoi utilizzare per rilevare i colli di bottiglia dei dati e altri suggerimenti per il rendimento.
- I numeri int8 o int16 vengono trattati come int32. La TPU non dispone di hardware intero che operi su meno di 32 bit.
- Alcune operazioni di Tensorflow non sono supportate. L'elenco è disponibile qui. La buona notizia è che questa limitazione si applica solo al codice di addestramento, ovvero al passaggio in avanti e all'indietro nel modello. Puoi comunque utilizzare tutte le operazioni TensorFlow nella pipeline di input dei dati perché verranno eseguite sulla CPU.
tf.py_func
non è supportato su TPU.
4. [INFO] Introduzione ai classificatori di reti neurali
In breve
Se conosci già tutti i termini in grassetto nel paragrafo successivo, puoi passare all'esercizio successivo. Se stai appena iniziando a utilizzare il deep learning, ti diamo il benvenuto e ti invitiamo a continuare a leggere.
Per i modelli costruiti come sequenza di strati, Keras offre l'API Sequential. Ad esempio, un classificatore di immagini che utilizza tre strati densi può essere scritto in Keras come:
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
tf.keras.layers.Dense(500, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])
# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy']) # % of correct answers
# train the model
model.fit(dataset, ... )
Rete neurale densa
Questa è la rete neurale più semplice per classificare le immagini. È fatta di "neuroni" disposti in strati. Il primo strato elabora i dati di input e alimenta gli output in altri strati. È chiamato "densa" perché ogni neurone è connesso a tutti i neuroni dello strato precedente.
Puoi inserire un'immagine in una rete di questo tipo appiattando i valori RGB di tutti i suoi pixel in un vettore lungo e utilizzandoli come input. Non è la tecnica migliore per il riconoscimento delle immagini, ma la miglioreremo in futuro.
Neuroni, attivazioni, RELU
Un "neurone" calcola una somma ponderata di tutti i suoi input, aggiunge un valore chiamato "bias" e alimenta il risultato tramite una cosiddetta "funzione di attivazione". Le ponderazioni e i bias sono inizialmente sconosciuti. Verranno inizializzati in modo casuale e "appresi" addestrando la rete neurale su molti dati noti.
La funzione di attivazione più utilizzata è chiamata RELU per Rectified Linear Unit. Si tratta di una funzione molto semplice, come puoi vedere nel grafico qui sopra.
Attivazione softmax
La rete sopra termina con un livello di 5 neuroni perché stiamo classificando i fiori in 5 categorie (rosa, tulipano, tarassaco, margherita, girasole). I neuroni negli strati intermedi vengono attivati utilizzando la funzione di attivazione RELU classica. Nell'ultimo strato, però, vogliamo calcolare i numeri compresi tra 0 e 1 che rappresentano la probabilità che questo fiore sia una rosa, un tulipano e così via. A questo scopo, utilizzeremo una funzione di attivazione chiamata "softmax".
L'applicazione di softmax a un vettore viene eseguita prendendo l'esponenziale di ogni elemento e poi normalizzando il vettore, in genere utilizzando la norma L1 (somma dei valori assoluti) in modo che i valori sommati diano 1 e possano essere interpretati come probabilità.
Perdita di entropia incrociata
Ora che la nostra rete neurale produce previsioni dalle immagini di input, dobbiamo misurarne la qualità, ovvero la distanza tra ciò che ci dice la rete e le risposte corrette, spesso chiamate "etichette". Ricorda che abbiamo etichette corrette per tutte le immagini del set di dati.
Qualsiasi distanza andrebbe bene, ma per i problemi di classificazione la cosiddetta "distanza di entropia incrociata" è la più efficace. Chiameremo questa funzione di errore o "loss":
Discesa del gradiente
"Addestrare" la rete neurale significa in realtà utilizzare immagini ed etichette di addestramento per regolare ponderazioni e bias in modo da minimizzare la funzione di perdita di entropia incrociata. Ecco come funziona.
L'entropia incrociata è una funzione di pesi, bias e pixel dell'immagine di addestramento e della relativa classe nota.
Se calcoliamo le derivate parziali dell'entropia incrociata relativamente a tutte le ponderazioni e a tutti i bias, otteniamo un "gradiente", calcolato per una determinata immagine, etichetta e valore attuale di ponderazioni e bias. Ricorda che possiamo avere milioni di ponderazioni e bias, quindi il calcolo del gradiente richiede molto lavoro. Fortunatamente, TensorFlow lo fa per noi. La proprietà matematica di un gradiente è che punta "verso l'alto". Poiché vogliamo andare dove l'entropia incrociata è bassa, andiamo nella direzione opposta. Aggiorniamo i pesi e i bias in base a una frazione del gradiente. Facciamo quindi la stessa cosa ancora e ancora usando i batch successivi di immagini ed etichette di addestramento, in un loop di addestramento. Si spera che questo converge in un punto in cui l'entropia incrociata è minima, anche se nulla garantisce che questo minimo sia unico.
Mini-batching e momentum
Puoi calcolare il gradiente su una sola immagine di esempio e aggiornare immediatamente i pesi e i bias, ma farlo su un batch di, ad esempio, 128 immagini fornisce un gradiente che rappresenta meglio i vincoli imposti da diverse immagini di esempio ed è quindi probabile che converga verso la soluzione più velocemente. La dimensione del mini-batch è un parametro regolabile.
Questa tecnica, a volte chiamata "discesa del gradiente stocastico", ha un altro vantaggio più pragmatico: lavorare con i batch significa anche lavorare con matrici più grandi, che in genere sono più facili da ottimizzare su GPU e TPU.
La convergenza può essere comunque un po' caotica e può persino arrestarsi se il vettore del gradiente è costituito da tutti gli zeri. Significa che abbiamo trovato il minimo? Non sempre. Un componente di gradiente può essere pari a zero per un valore minimo o massimo. Con un vettore gradiente con milioni di elementi, se sono tutti uguali a zero, la probabilità che ogni zero corrisponda a un minimo e nessuno a un punto massimo è piuttosto ridotta. In uno spazio di molte dimensioni, i punti di inserimento sono abbastanza comuni e non vogliamo fermarli.
Illustrazione: un punto di attacco. Il gradiente è 0 ma non è il minimo in tutte le direzioni. (Attribuzione dell'immagine Wikimedia: By Nicoguaro - Own work, CC BY 3.0)
La soluzione è aggiungere slancio all'algoritmo di ottimizzazione, in modo che possa superare i punti di sella senza fermarsi.
Glossario
batch o mini-batch: l'addestramento viene sempre eseguito su batch di dati di addestramento ed etichette. In questo modo favorirai la convergenza dell'algoritmo. La dimensione "batch" è in genere la prima dimensione dei tensori di dati. Ad esempio, un tensore di forma [100, 192, 192, 3] contiene 100 immagini di 192 x 192 pixel con tre valori per pixel (RGB).
Perdita di entropia incrociata: una funzione di perdita speciale spesso utilizzata nei classificatori.
strato denso: uno strato di neuroni in cui ogni neurone è collegato a tutti i neuroni dello strato precedente.
features: gli input di una rete neurale vengono a volte chiamati "features". L'arte di capire quali parti di un set di dati (o combinazioni di parti) alimentare in una rete neurale per ottenere buone previsioni si chiama "feature engineering".
labels: un altro nome per "classi" o risposte corrette in un problema di classificazione supervisionata
tasso di apprendimento: frazione del gradiente in base alla quale le ponderazioni e i bias vengono aggiornati a ogni iterazione del loop di addestramento.
logits: gli output di uno strato di neuroni prima dell'applicazione della funzione di attivazione sono chiamati "logit". Il termine deriva dalla "funzione logistica", nota anche come "funzione sigmoide", che era la funzione di attivazione più utilizzata. "Output dei neuroni prima della funzione logistica" è stato abbreviato in "logit".
loss: la funzione di errore che confronta gli output della rete neurale con le risposte corrette
neurone: calcola la somma ponderata dei suoi input, aggiunge un bias e alimenta il risultato tramite una funzione di attivazione.
Codifica one-hot: la classe 3 su 5 è codificata come un vettore di 5 elementi, tutti zeri tranne il terzo che è 1.
relu: unità lineare rettificata. Una funzione di attivazione molto diffusa per i neuroni.
sigmoid: un'altra funzione di attivazione molto utilizzata in passato ed ancora utile in casi speciali.
softmax: una funzione di attivazione speciale che agisce su un vettore, aumenta la differenza tra il componente più grande e tutti gli altri, e normalizza il vettore in modo che abbia una somma pari a 1 in modo che possa essere interpretato come un vettore di probabilità. Utilizzato come ultimo passaggio nei classificatori.
tensore: un "tensore" è simile a una matrice, ma con un numero arbitrario di dimensioni. Un tensore monodimensionale è un vettore. Un tensore di 2 dimensioni è una matrice. Poi si possono avere tensori con 3, 4, 5 o più dimensioni.
5. [INFO] Reti neurali convoluzionali
In breve
Se conosci già tutti i termini in grassetto nel paragrafo successivo, puoi passare all'esercizio successivo. Se stai appena iniziando a utilizzare le reti neurali convoluzionali, continua a leggere.
Illustrazione: applicazione di due filtri successivi composti ciascuno da 4x4x3=48 pesi apprendibili.
Ecco come appare una semplice rete neurale convoluzionale in Keras:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
Nozioni di base sulle reti neurali convoluzionali
In un livello di una rete convoluzionale, un "neurone" esegue una somma ponderata dei pixel appena sopra, in una piccola regione solo dell'immagine. Aggiunge un bias e alimenta la somma tramite una funzione di attivazione, proprio come farebbe un neurone in un normale livello denso. Questa operazione viene poi ripetuta nell'intera immagine utilizzando gli stessi pesi. Ricorda che, in strati densi, ogni neurone aveva i propri pesi. In questo caso, un singolo "patch" di pesi scorre sull'immagine in entrambe le direzioni (una "convoluzione"). L'output ha tanti valori quanti sono i pixel dell'immagine (è necessario un po' di spaziatura ai bordi). Si tratta di un'operazione di filtraggio, che utilizza un filtro di 4x4x3=48 pesi.
Tuttavia, 48 pesi non saranno sufficienti. Per aggiungere più gradi di libertà, ripetiamo la stessa operazione con un nuovo insieme di pesi. Viene prodotta una nuova serie di output del filtro. Chiamiamolo un "canale" di uscite per analogia con i canali R,G,B nell'immagine di ingresso.
I due (o più) insiemi di pesi possono essere sommati come un unico tensore aggiungendo una nuova dimensione. Questo ci fornisce la forma generica del tensore dei pesi per un livello di convoluzione. Poiché il numero di canali di input e di output sono parametri, possiamo iniziare a impilare e a concatenare i livelli con convoluzione.
Illustrazione: una rete neurale convoluzionale trasforma dei "cubi" di dati in altri "cubi" di dati.
Convoluzioni con stride, pooling massimo
Eseguendo le convoluzioni con passo 2 o 3, è possibile anche ridurre il cubo di dati risultante nelle sue dimensioni orizzontali. Esistono due modi comuni per farlo:
- Convoluzione con passo: un filtro scorrevole come sopra, ma con un passo > 1
- Pooling massimo: finestra scorrevole applicando l'operazione MAX (tipicamente su patch 2x2, ripetute ogni 2 pixel)
Illustrazione: se si fa scorrere la finestra di calcolo di 3 pixel, si riducono i valori di output. Le convoluzioni con stride o il pooling massimo (max su una finestra 2x2 che scorre con uno stride di 2) sono un modo per ridurre il cubo di dati nelle dimensioni orizzontali.
Classificatore convolzionale
Infine, colleghiamo un'unità di classificazione appiattando l'ultimo cubo di dati e alimentandolo tramite un livello denso attivato da softmax. Un tipico classificatore convoluzionale può avere il seguente aspetto:
Illustrazione: un classificatore di immagini che utilizza i livelli convoluzionali e softmax. Utilizza filtri 3x3 e 1x1. I livelli maxpool prendono il massimo di gruppi di punti dati 2x2. La testa di classificazione è implementata con un livello denso con attivazione softmax.
In Keras
Lo stack convoluzionale illustrato sopra può essere scritto in Keras in questo modo:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
6. [NUOVE INFORMAZIONI] Architetture convoluzionali moderne
In sintesi
Illustrazione: un "modulo" convoluzionale. Qual è la soluzione migliore a questo punto? Uno strato max-pool seguito da uno strato convoluzionale 1x1 o da una diversa combinazione di strati ? Provali tutti, concatena i risultati e lascia che sia la rete a decidere. A destra: l'architettura convoluzionale " inception" che utilizza questi moduli.
In Keras, per creare modelli in cui il flusso di dati può ramificarsi, devi utilizzare lo stile del modello "funzionale". Ecco un esempio:
l = tf.keras.layers # syntax shortcut
y = l.Conv2D(filters=32, kernel_size=3, padding='same',
activation='relu', input_shape=[192, 192, 3])(x) # x=input image
# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation
# many more layers ...
# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)
Altri trucchi economici
Filtri 3x3 piccoli
In questa illustrazione si può vedere il risultato di due filtri 3x3 consecutivi. Prova a risalire ai punti dati che hanno contribuito al risultato: questi due filtri 3 x 3 consecutivi calcolano una combinazione di una regione 5 x 5. Non è esattamente la stessa combinazione che calcolerebbe un filtro 5x5, ma vale la pena provare perché due filtri 3x3 consecutivi sono più economici di un singolo filtro 5x5.
Convoluzioni 1x1?
In termini matematici, una convoluzione "1x1" è una moltiplicazione per una costante, un concetto non molto utile. Nelle reti neurali convoluzionali, tuttavia, ricorda che il filtro viene applicato a un cubo di dati, non solo a un'immagine 2D. Pertanto, un filtro "1x1" calcola una somma pesata di una colonna di dati 1x1 (vedi illustrazione) e, mentre lo scorri sui dati, ottieni una combinazione lineare dei canali dell'input. Questo è davvero utile. Se consideri i canali come i risultati di singole operazioni di filtraggio, ad esempio un filtro per "orecchie a punta", un altro per "baffi" e un terzo per "occhi a fessura", un livello convoluzionale "1x1" calcolerà più possibili combinazioni lineari di queste caratteristiche, che potrebbero essere utili quando cerchi un "gatto". Inoltre, i livelli 1 x 1 utilizzano meno ponderazioni.
7. Squeezenet
Un modo semplice per realizzare queste idee è stato presentato nel documento "Squeezenet". Gli autori suggeriscono un design di modulo convoluzionale molto semplice, utilizzando solo strati convoluzionali 1x1 e 3x3.
Illustrazione: architettura di squeezenet basata su "moduli antincendio". Alternano un livello 1x1 che "comprime" i dati in entrata nella dimensione verticale, seguito da due livelli con convoluzione 1x1 e 3x3 paralleli che "espandeno" di nuovo la profondità dei dati.
Hands-on
Continua nel notebook precedente e crea una rete neurale convoluzionale ispirata a SqueezeNet. Dovrai cambiare il codice del modello nello "stile funzionale" Keras.
Keras_Flowers_TPU (playground).ipynb
Ulteriori informazioni
In questo esercizio è utile definire una funzione helper per un modulo Squeezenet:
def fire(x, squeeze, expand):
y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
return tf.keras.layers.concatenate([y1, y3])
# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
return lambda x: fire(x, squeeze, expand)
# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)
Questa volta l'obiettivo è raggiungere un'accuratezza dell'80%.
Da provare
Inizia con un singolo livello convoluzionale, poi segui con "fire_modules
", alternandoli con i livelli MaxPooling2D(pool_size=2)
. Puoi sperimentare da 2 a 4 livelli di pooling massimi nella rete e anche con 1, 2 o 3 moduli di incendio consecutivi tra i livelli di pooling massimi.
Nei moduli di fuoco, in genere il parametro "squeeze" deve essere inferiore al parametro "expand". Questi parametri sono in realtà numeri di filtri. In genere, possono variare da 8 a 196. Puoi fare esperimenti con architetture in cui il numero di filtri aumenta gradualmente all'interno della rete o con architetture semplici in cui tutti i moduli di esecuzione hanno lo stesso numero di filtri.
Ecco un esempio:
x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB
y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, y)
A questo punto, potresti notare che i tuoi esperimenti non stanno andando così bene e che l'obiettivo di accuratezza dell'80% sembra lontano. È ora di altri due trucchi economici.
Normalizzazione batch
La norma batch ti aiuterà a risolvere i problemi di convergenza che stai riscontrando. Verranno fornite spiegazioni dettagliate su questa tecnica nel prossimo workshop. Per il momento, utilizzala come una scatola nera "magica" aggiungendo questa riga dopo ogni livello convoluzionale nella rete, inclusi i livelli all'interno della funzione fire_module:
y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context
Il parametro momentum deve essere ridotto dal suo valore predefinito di 0,99 a 0,9 perché il set di dati è piccolo. Per il momento non preoccuparti di questo dettaglio.
Aumento dei dati
Puoi ottenere un paio di punti percentuali in più integrando i dati con semplici trasformazioni, come le inversioni sinistra-destra delle modifiche di saturazione:
Questa operazione è molto facile in TensorFlow con l'API tf.data.Dataset. Definisci una nuova funzione di trasformazione per i dati:
def data_augment(image, label):
image = tf.image.random_flip_left_right(image)
image = tf.image.random_saturation(image, lower=0, upper=2)
return image, label
Quindi utilizzalo nella trasformazione finale dei dati (cella "set di dati di addestramento e convalida", funzione "get_batched_dataset"):
dataset = dataset.repeat() # existing line
# insert this
if augment_data:
dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line
Non dimenticare di rendere facoltativa l'aumento dei dati e di aggiungere il codice necessario per assicurarti che solo il set di dati di addestramento venga aumentato. Non ha senso aumentare il set di dati di convalida.
Un'accuratezza dell'80% in 35 epoche dovrebbe ora essere raggiungibile.
Soluzione
Ecco il blocco note della soluzione. Puoi utilizzarlo se non riesci a procedere.
Keras_Flowers_TPU_squeezenet.ipynb
Argomenti trattati
- 🤔 Modelli di "stile funzionale" Keras
- 🤓 Architettura di Squeezenet
- 🤓 Aumento dei dati con tf.data.datset
Esamina mentalmente questo elenco di controllo.
8. Xception perfezionata
Convoluzioni separabili
Un modo diverso di implementare gli strati convoluzionali stava guadagnando popolarità di recente: le convoluzioni separabili in profondità. Lo so, è un boccone, ma il concetto è abbastanza semplice. Sono implementati in TensorFlow e Keras come tf.keras.layers.SeparableConv2D
.
Una convezione separabile esegue anche un filtro sull'immagine, ma utilizza un insieme distinto di pesi per ciascun canale dell'immagine di input. Segue una "convoluzione 1 x 1", una serie di prodotti di tipo "dot" che genera una somma ponderata dei canali filtrati. Con nuovi pesi ogni volta, vengono calcolate tutte le ricombinazioni ponderate dei canali in base alle necessità.
Illustrazione: convoluzioni separabili. Fase 1: convoluzioni con un filtro separato per ciascun canale. Fase 2: ricostruzioni lineari dei canali. Ripetito con un nuovo set di ponderazioni fino a raggiungere il numero desiderato di canali di uscita. Anche la fase 1 può essere ripetuta, ogni volta con nuovi pesi, ma in pratica accade raramente.
Le convoluzioni separabili vengono utilizzate nelle architetture delle reti convoluzionali più recenti: MobileNetV2, Xception, EfficientNet. A proposito, MobileNetV2 è quello che utilizzavi in precedenza per il Transfer Learning.
Sono più economiche delle convoluzioni normali e si sono dimostrate altrettanto efficaci nella pratica. Ecco il conteggio del peso per l'esempio illustrato sopra:
Livello convoluzionale: 4 x 4 x 3 x 5 = 240
Strato convoluzionale separabile: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
Lasciamo al lettore il compito di calcolare il numero di moltiplicazioni necessarie per applicare ogni stile di livello convoluzionale in modo simile. Le convoluzioni separabili sono più piccole e molto più efficienti dal punto di vista computazionale.
Hands-on
Riavvia dal notebook di esercitazione "transfer learning", ma questa volta seleziona Xception come modello preaddestrato. L'Xception utilizza solo convoluzioni separabili. Lascia addestrabili tutti i pesi. Ottimizzeremo i pesi preaddestrati sui nostri dati invece di utilizzare gli strati preaddestrati come tali.
Keras Flowers transfer learning (playground).ipynb
Obiettivo: accuratezza > 95% (no, è possibile!)
Questo è l'esercizio finale, richiede un po' più di codice e lavoro di data science.
Ulteriori informazioni sul perfezionamento
Xception è disponibile nei modelli preaddestrati standard in tf.keras.application.* Non dimenticare di lasciare addestrabili tutti i pesi questa volta.
pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
include_top=False)
pretrained_model.trainable = True
Per ottenere buoni risultati durante l'ottimizzazione di un modello, devi prestare attenzione al tasso di apprendimento e utilizzare una programmazione del tasso di apprendimento con un periodo iniziale. Esempio:
Iniziare con un tasso di apprendimento standard interromperebbe i pesi preaddestrati del modello. Un avvio progressivo consente di conservarle finché il modello non si è agganciato ai tuoi dati e non è in grado di modificarli in modo sensato. Dopo la rampa, puoi continuare con un tasso di apprendimento costante o in decadimento esponenziale.
In Keras, il tasso di apprendimento viene specificato tramite un callback in cui puoi calcolare il tasso di apprendimento appropriato per ogni epoca. Keras trasmetterà il tasso di apprendimento corretto all'ottimizzatore per ogni epoca.
def lr_fn(epoch):
lr = ...
return lr
lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)
model.fit(..., callbacks=[lr_callback])
Soluzione
Ecco il blocco note della soluzione. Puoi utilizzarlo se non riesci a procedere.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
Argomenti trattati
- 🤔 Convoluzione separabile in profondità
- 🤓 Programmazione dei tassi di apprendimento
- 😈 Ottimizzazione fine di un modello preaddestrato.
Dedica qualche istante a leggere questo elenco di controllo.
9. Complimenti!
Hai creato la tua prima rete neurale convoluzionale moderna e l'hai addestrata con un'accuratezza superiore al 90%, eseguendo l'iterazione sull'addestramento successivo in pochi minuti grazie alle TPU. Con questo si concludono i 4 "codelab di Keras su TPU":
- Pipeline di dati a velocità TPU: tf.data.Dataset e TFRecords
- Il tuo primo modello Keras, con Transfer Learning
- Reti neurali convoluzionali con Keras e TPU
- [QUESTO LABORA] Convnet moderne, Squeezenet, Xception, con Keras e TPU
TPU nella pratica
TPU e GPU sono disponibili su Cloud AI Platform:
- Su Deep Learning VM
- In AI Platform Notebooks
- Nei job di AI Platform Training
Infine, ci piacerebbe ricevere feedback. Facci sapere se noti qualcosa di strano in questo lab o se ritieni che debba essere migliorato. I feedback possono essere inviati tramite i problemi di GitHub [link al feedback].
|