Convnet moderne, Squeezenet, Xception, con Keras e TPU

1. Panoramica

In questo lab imparerai a conoscere la moderna architettura convoluzionale e utilizzerai le tue conoscenze per implementare una conversione 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 saperne di più sul deep learning.

Questo lab è la parte 4 della sessione "Keras su TPU" Google Cloud. Puoi farlo nel seguente ordine o in modo indipendente.

ca8cc21f6838eccc.png

Obiettivi didattici

  • Per padroneggiare lo stile funzionale di Keras
  • a creare un modello usando l'architettura Squeezenet.
  • Per usare le TPU per un addestramento rapido e per iterare la tua 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 che non va in questo lab del codice, faccelo sapere. Il feedback può essere fornito tramite i problemi di GitHub [link per il feedback].

2. Guida rapida di Google Colaboratory

Questo lab utilizza Google Collaboratory e non richiede alcuna configurazione da parte tua. Colaboratory è una piattaforma di blocchi note online a scopo didattico. Offre l'addestramento senza costi di CPU, GPU e TPU.

688858c21e3beff2.png

Puoi aprire questo blocco note di esempio ed eseguire un paio di celle per acquisire familiarità con Colaboratory.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Seleziona un backend TPU

8832c6208c99687d.png

Nel menu Colab, seleziona Runtime > Modifica il tipo di runtime, quindi seleziona TPU. In questo codelab, utilizzerai una potente TPU (Tensor Processing Unit) supportata per l'addestramento con accelerazione hardware. La connessione al runtime avverrà automaticamente alla prima esecuzione oppure puoi utilizzare il pulsante nell'angolo in alto a destra.

Esecuzione di blocchi note

76d05caa8b4db6da.png

Esegui le celle una alla volta facendo clic su una cella e premendo Maiusc-Invio. Puoi anche eseguire l'intero blocco note con Runtime > Esegui tutto

Sommario

429f106990037ec4.png

Tutti i blocchi note hanno un sommario. Puoi aprirlo utilizzando la freccia nera a sinistra.

Celle nascoste

edc3dba45d26f12a.png

Alcune celle mostreranno solo il titolo. Si tratta di una funzionalità del blocco note specifica per Colab. Puoi fare doppio clic sopra per vedere il codice al loro interno, ma di solito non è molto interessante. In genere supportano o le funzioni di visualizzazione. Devi comunque eseguire queste celle per definire le funzioni all'interno.

Autenticazione

cdd4b41413100543.png

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 breve

f88cf6facfc70166.png

Codice per l'addestramento di un modello sulla TPU in Keras (e utilizza 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).

688858c21e3beff2.png

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 processore vettoriale classico a un'unità di moltiplicazione della matrice dedicata ed eccellono in qualsiasi attività in cui dominano le moltiplicazioni matriciali di grandi dimensioni, come le reti neurali.

8eb3e718b8e2ed08.png

Illustrazione: uno strato di rete neurale densa come una moltiplicazione della matrice, con un batch di otto immagini elaborate contemporaneamente attraverso la rete neurale. Esegui la moltiplicazione di una riga per colonna per verificare che venga effettivamente calcolata una somma ponderata di tutti i valori in pixel di un'immagine. Anche gli strati convoluzionali possono essere rappresentati come moltiplicazioni matriciali, anche se è un po' più complicato ( spiegazione qui, nella sezione 1).

L'hardware

MXU e VPU

Un core TPU v2 è composto 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. MXU, invece, funziona in un formato in virgola mobile a 16-32 bit a precisione mista.

7d68944718f76b18.png

Virgola mobile con precisione mista e bfloat16

MXU calcola le moltiplicazioni matriciali utilizzando gli input bfloat16 e gli output float32. Gli accumuli intermedi vengono eseguiti con precisione float32.

19c5fc432840c714.png

L'addestramento della rete neurale è in genere resistente al rumore introdotto da una precisione in virgola mobile ridotta. In alcuni casi il rumore aiuta persino 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 delle soluzioni, ma in genere è necessario un lavoro aggiuntivo per far funzionare float16.

Ecco perché Google ha introdotto il formato bfloat16 nelle TPU. bfloat16 è un float32 troncato con esattamente gli stessi bit di esponente e lo stesso intervallo 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

La MXU implementa le moltiplicazioni matriciali nell'hardware utilizzando un cosiddetto "array sistolica" un'architettura in cui gli elementi dei 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 base di una moltiplicazione matriciale è un prodotto scalare tra una linea di una matrice e una colonna dell'altra (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" GPU. e poi eseguirla su quanti più "core" disponibili in parallelo per provare a calcolare contemporaneamente ogni valore della matrice risultante. Se la matrice risultante è grande 128x128, ciò richiederebbe 128x128=16K "core" la disponibilità, 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 nella MXU: solo bfloat16 x bfloat16 => float32 moltiplicatori, 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.

f1b283fc45966717.gif

Illustrazione: l'array sistolica MXU. Gli elementi di calcolo sono accumulatori multipli. 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. Viene lasciato come esercizio all'utente per verificare che man mano che i dati fluiscono attraverso l'array, si ottiene il risultato della moltiplicazione matriciale che esce dal 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 un " Cloud TPU v2" su Google Cloud, hai 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. Questa "Cloud TPU" è quindi generalmente connesso tramite la rete alla VM che lo ha richiesto. Il quadro completo sarà quindi simile a questo:

dfce5522ed644ece.png

Illustrazione: la tua VM con una rete "Cloud TPU" collegata alla rete acceleratore. "Cloud TPU" è composta da una VM con una scheda TPU PCI collegata a 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.

2ec1e0d341e7fc34.jpeg

Illustrazione: un pod TPU v3. Rack e schede TPU collegate 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.

d97b9cc5d40fdb1d.gif

Illustrazione: sincronizzazione dei gradienti durante l'addestramento utilizzando l'algoritmo All-Reduce sulla rete HPC mesh toroidale 2-D 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 automaticamente suddivisi in 8 core e eseguiti su 8 core della TPU.

da534407825f01e3.png

Per ulteriori suggerimenti sulle prestazioni, consulta la Guida alle prestazioni TPU. Per dimensioni dei batch molto grandi, potrebbe essere necessaria un'attenzione particolare in alcuni modelli. 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 esegue il grafico di calcolo definito dal tuo programma TensorFlow. Un compilatore chiamato XLA (Accelerated Linear Algebra compiler) trasforma il grafico 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.

edce61112cd57972.png

Illustrazione: per l'esecuzione su TPU, il grafico di calcolo definito dal programma Tensorflow viene prima tradotto in una rappresentazione XLA (Accelerated Linear Algebra compiler), quindi compilato da XLA nel 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, Deep Learning VM create tramite l'utilità "ctpu up"). Questi sistemi sanno dove si trova la loro TPU grazie a una variabile di ambiente TPU_NAME. Se crei una TPU manualmente, imposta l'ambiente TPU_NAME var. sulla VM da cui la utilizzi o chiama TPUClusterResolver con parametri espliciti: TPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy è la parte che implementa la distribuzione e "all-Reduce" di sincronizzazione dei gradienti.
  • La strategia viene applicata attraverso un ambito. Il modello deve essere definito all'interno della strategia scope().
  • La funzione tpu_model.fit prevede un oggetto tf.data.Dataset per l'input per l'addestramento delle TPU.

Attività comuni di portabilità delle 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 alle prestazioni TPU puoi trovare strumenti per individuare i colli di bottiglia dei dati e altri suggerimenti per le prestazioni.
  • I numeri int8 o int16 vengono trattati come int32. La TPU non ha un hardware intero che opera su meno di 32 bit.
  • Alcune operazioni di TensorFlow non sono supportate. L'elenco è disponibile qui. La buona notizia è che questo limite si applica solo al codice di addestramento, ovvero al passaggio in avanti e indietro attraverso il modello. Puoi comunque utilizzare tutte le operazioni TensorFlow nella pipeline di input dei dati perché verranno eseguite sulla CPU.
  • tf.py_func non è supportato sulla TPU.

4. [INFO] Classificatore di rete neurale 101

In breve

Se conosci già tutti i termini in grassetto del paragrafo successivo, puoi passare all'esercizio successivo. Se hai appena iniziato il deep learning, ti diamo il benvenuto e continua a leggere.

Per i modelli creati 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, ... )

688858c21e3beff2.png

Rete neurale densa

Questa è la rete neurale più semplice per classificare le immagini. È fatta di "neuroni" disposte in strati. Il primo strato elabora i dati di input e alimenta gli output in altri strati. È chiamato "denso" perché ogni neurone è connesso a tutti i neuroni dello strato precedente.

c21bae6dade487bc.png

Puoi inserire un'immagine in una rete di questo tipo, suddividendo i valori RGB di tutti i suoi pixel in un vettore lungo e utilizzandolo come input. Non è la tecnica migliore per il riconoscimento delle immagini, ma verrà migliorata in seguito.

Neuroni, attivazioni, RELU

Un "neurone" calcola una somma ponderata di tutti gli input e aggiunge un valore chiamato "bias" e fornisce il risultato attraverso una cosiddetta "funzione di attivazione". Le ponderazioni e i bias sono inizialmente sconosciuti. Verranno inizializzate in modo casuale e "apprese" addestrando la rete neurale con molti dati noti.

644f4213a4ee70e5.png

La funzione di attivazione più diffusa è denominata RELU per l'unità lineare rettificata. Si tratta di una funzione molto semplice, come puoi vedere nel grafico in alto.

Attivazione di softmax

La rete precedente termina con uno strato a 5 neuroni perché classifichiamo i fiori in 5 categorie (rosa, tulipano, dente di leone, margherita, girasole). I neuroni negli strati intermedi vengono attivati utilizzando la classica funzione di attivazione RELU. 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 della funzione softmax su un vettore viene eseguita prendendo l'esponenziale di ciascun elemento e quindi normalizzando il vettore, tipicamente usando la norma L1 (somma dei valori assoluti) in modo che i valori sommano 1 e possano essere interpretati come probabilità.

ef0d98c0952c262d.png d51252f75894479e.gif

Perdita con entropia incrociata

Ora che la nostra rete neurale produce previsioni a partire dalle immagini di input, dobbiamo misurare la loro qualità, cioè la distanza tra ciò che ci dice la rete e le risposte corrette, spesso chiamate "etichette". Ricorda che abbiamo le etichette corrette per tutte le immagini nel set di dati.

Qualsiasi distanza andrebbe bene, ma per i problemi di classificazione la cosiddetta "distanza di entropia incrociata" è la più efficace. Chiameremo questo errore o "perdita" :

7bdf8753d20617fb.png

Discesa del gradiente

"Addestramento" la rete neurale prevede in realtà l'utilizzo di 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 delle ponderazioni, dei bias, dei pixel dell'immagine di addestramento e della sua 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 al posto nostro. La proprietà matematica di un gradiente è che punta verso l'alto. Poiché vogliamo andare dove l'entropia incrociata è bassa, andiamo nella direzione opposta. Aggiorniamo le ponderazioni e i bias di 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.

discesa gradiente2.png

Mini-batching e momentum

Puoi calcolare il gradiente solo su un'immagine di esempio e aggiornare immediatamente le ponderazioni e i bias, ma farlo per un batch di immagini con 128 formati, ad esempio, fornisce un gradiente che rappresenta meglio i vincoli imposti da diverse immagini di esempio e ha quindi la probabilità di convergere verso la soluzione più velocemente. La dimensione del mini-batch è un parametro regolabile.

Questa tecnica, a volte chiamata "discesa stocastica del gradiente" ha un altro vantaggio più pragmatico: lavorare con i batch significa anche lavorare con matrici più grandi, che di solito 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 gradiente può essere pari a zero al minimo o al massimo. Con un vettore gradiente con milioni di elementi, se sono tutti zeri, la probabilità che ogni zero corrisponda a un minimo e nessuno di questi a un punto massimo è piuttosto piccola. In uno spazio di molte dimensioni, i punti di inserimento sono abbastanza comuni e non vogliamo fermarli.

52e824fe4716c4a0.png

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 ed etichette di addestramento. In questo modo favorirai la convergenza dell'algoritmo. Il "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 con entropia incrociata: una funzione di perdita speciale spesso usata 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 sono a volte chiamati "caratteristiche". 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 le risposte corrette in un problema di classificazione supervisionato

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" anche noto come "funzione sigmoidea" che era la funzione di attivazione più diffusa. "Output del neurone prima della funzione logistica" è stato abbreviato in "logits".

loss: la funzione di errore che confronta gli output della rete neurale con le risposte corrette

neuron: calcola la somma ponderata dei suoi input, aggiunge un bias e fornisce il risultato attraverso 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 popolare ma che è 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 nelle categorie di classificazione.

tensor: un "tensore" è simile a una matrice, ma con un numero arbitrario di dimensioni. Un tensore monodimensionale è un vettore. Un tensore bidimensionale è 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 del paragrafo successivo, puoi passare all'esercizio successivo. Se hai appena iniziato con le reti neurali convoluzionali, continua a leggere.

convolutional.gif

Illustrazione: filtrare un'immagine con due filtri successivi composti da 4 x 4 x 3=48 pesi utilizzabili ciascuno.

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

688858c21e3beff2.png

Nozioni di base sulle reti neurali convoluzionali

In uno strato di una rete convoluzionale, un "neurone" esegue una somma ponderata dei pixel appena sopra, in una piccola area solo dell'immagine. Aggiunge un bias e alimenta la somma attraverso una funzione di attivazione, proprio come farebbe un neurone in uno strato denso regolare. Questa operazione viene poi ripetuta in tutta l'immagine utilizzando gli stessi pesi. Ricorda che, in strati densi, ogni neurone aveva i propri pesi. In questo caso, una singola "patch" di pesi scorre sull'immagine in entrambe le direzioni (una "convoluzione"). L'output ha tanti valori quanti sono i pixel dell'immagine (è però necessaria una spaziatura interna 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. Questo produce un nuovo insieme di output di filtro. Chiamiamolo "canale" di uscite per analogia con i canali R,G,B nell'immagine di ingresso.

Screenshot 2016-07-29 at 16.02.37.png

I due (o più) insiemi di pesi possono essere sommati come un unico tensore aggiungendo una nuova dimensione. Questo ci dà la forma generica del tensore delle ponderazioni per uno strato convoluzionale. Poiché il numero di canali di input e di output sono parametri, possiamo iniziare a sovrapporre e concatenare gli strati convoluzionali.

d1b557707bcd1cb9.png

Illustrazione: una rete neurale convoluzionale trasforma i "cubi" di dati in altri "cubi" quantità di dati.

Convoluzioni allungate, max pooling

Eseguendo le convoluzioni con passo 2 o 3, è possibile anche ridurre il cubo di dati risultante nelle sue dimensioni orizzontali. Ci sono due modi comuni per farlo:

  • Convoluzione allungata: un filtro scorrevole come sopra, ma con passo >1
  • Pooling massimo: finestra scorrevole applicando l'operazione MAX (tipicamente su patch 2x2, ripetute ogni 2 pixel)

2b2d4263bb8470b.gif

Illustrazione: se si fa scorrere la finestra di calcolo di 3 pixel, si riducono i valori di output. Le convoluzioni striate o il massimo pooling (max su una finestra 2x2 che scorre di un passo di 2) sono un modo per ridurre il cubo di dati nelle dimensioni orizzontali.

Cclassificatore onvoluzionale

Infine, aggiungiamo una testa di classificazione appiattindo l'ultimo cubo di dati e alimentandolo attraverso uno strato denso attivato dalla tecnologia softmax. Un tipico classificatore convoluzionale può avere il seguente aspetto:

4a61aaffb6cba3d1.png

Illustrazione: un classificatore di immagini che utilizza strati convoluzionali e softmax. Utilizza filtri 3x3 e 1x1. I livelli maxpool prendono il numero massimo di gruppi di punti dati 2x2. L'intestazione di classificazione è implementata con uno strato 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 breve

7968830b57b708c0.png

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: la colonna " inception" un'architettura convoluzionale utilizzando questi moduli.

In Keras, per creare modelli in cui il flusso di dati può ramificarsi in entrata e in uscita, è necessario utilizzare il lo stile del modello. 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)

688858c21e3beff2.png

Altri trucchi economici

Filtri 3x3 piccoli

40a7b15fb7dbe75c.png

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 3x3 consecutivi calcolano una combinazione di una regione 5x5. 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 1 x 1?

fd7cac16f8ecb423.png

In termini matematici, un "1 x 1" di convoluzione è 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. Di conseguenza, un valore di "1 x 1" Calcola una somma ponderata di una colonna di dati 1x1 (vedi illustrazione) e, scorrendo la colonna tra i dati, si otterrà una combinazione lineare dei canali di input. Questo è davvero utile. Se pensi ai canali come al risultato di singole operazioni di filtro, ad esempio un filtro per "orecchie a punta", un altro per "baffi" e una terza per "occhi spaccati" seguito da "1 x 1" lo strato convoluzionale calcolerà più possibili combinazioni lineari di queste caratteristiche, il che potrebbe essere utile quando si cerca un "gatto". Inoltre, i livelli 1 x 1 utilizzano meno ponderazioni.

7. Connettore

Un modo semplice per realizzare queste idee è stato presentato nel "Squeezenet" cartaceo. Gli autori suggeriscono una progettazione di moduli convoluzionali molto semplice, utilizzando solo strati convoluzionali 1x1 e 3x3.

1730ac375379269b.png

Illustrazione: architettura di squeezenet basata su "moduli antincendio". Alterna uno strato 1 x 1 che "schiaccia" i dati in entrata nella dimensione verticale seguiti da due strati convoluzionali paralleli 1x1 e 3x3 che si "espandono" la profondità dei dati.

Attività pratica

Continua con il blocco note precedente e crea una rete neurale convoluzionale ispirata a squeezenet. Dovrai cambiare il codice del modello nello "stile funzionale" Keras.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

Informazioni aggiuntive

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 l'80% di precisione.

Da provare

Inizia con un singolo livello convoluzionale, poi segui "fire_modules", alternando con MaxPooling2D(pool_size=2) livelli. Puoi sperimentare da 2 a 4 livelli di pooling massimo nella rete e anche con 1, 2 o 3 moduli di incendio consecutivi tra i livelli di pooling massimi.

Nei moduli incendi, la tecnica "squeeze" in genere deve essere inferiore al parametro "expand" . Questi parametri sono in realtà numeri di filtri. In genere, possono variare da 8 a 196. È possibile sperimentare architetture in cui il numero di filtri aumenta gradualmente attraverso la rete o architetture semplici in cui tutti i moduli di incendio 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 gli esperimenti non vanno molto bene e che l'obiettivo dell'accuratezza dell'80% sembra remoto. Un altro paio di trucchetti economici.

Normalizzazione batch

La norma batch ti aiuterà a risolvere i problemi di convergenza che stai riscontrando. Ci saranno spiegazioni dettagliate su questa tecnica nel prossimo workshop. Per il momento, usala come una "magia" in scatola nera. helper aggiungendo questa riga dopo ogni livello convoluzionale della tua 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. Non preoccuparti di questo dettaglio per ora.

Aumento dei dati

Otterrai un altro paio di punti percentuali aumentando i dati con semplici trasformazioni come le capovolgimenti da sinistra a destra dei cambiamenti di saturazione:

4ed2958e09b487ca.png

ad795b70334e0d6b.png

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 facoltativo l'aumento dei dati e di aggiungere il codice necessario per assicurarti che solo il set di dati di addestramento sia 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 utilizzarla se non riesci a proseguire.

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

Argomenti trattati

  • 🤔 "stile funzionale" di Keras di base
  • 🤓 Architettura di Squeezenet
  • 🤓 Aumento dei dati con tf.data.datset

Dedica qualche istante a leggere 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.

Anche una convoluzione separabile esegue un filtro sull'immagine, ma utilizza un insieme distinto di ponderazioni 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 esigenze.

615720b803bf8dda.gif

Illustrazione: convoluzioni separabili. Fase 1: convoluzioni con un filtro separato per ciascun canale. Fase 2: ricombinazioni lineari di 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, con nuovi pesi ogni volta, ma nella pratica è raro.

Le convoluzioni separabili sono utilizzate nelle più recenti architetture di reti convoluzionali: MobileNetV2, Xception, EfficientNet. A proposito, MobileNetV2 è quello che utilizzavi in precedenza per il Transfer Learning.

Sono più economici delle convoluzioni normali e si sono dimostrate altrettanto efficaci nella pratica. Ecco il conteggio delle ponderazioni per l'esempio sopra illustrato:

Strato convoluzionale: 4 x 4 x 3 x 5 = 240

Strato convoluzionale separabile: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63

Viene lasciato come esercizio che il lettore può calcolare rispetto al numero di moltiplicazioni necessarie per applicare ogni stile di scale di strati convoluzionali in modo simile. Le convoluzioni separabili sono più piccole e molto più efficaci dal punto di vista computazionale.

Attività pratica

Riavviare da "Transfer Learning" ma questa volta scegli 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.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

Obiettivo: precisione > 95% (no, davvero, è 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:

9b1af213b2b36d47.png

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 attraverso un callback in cui è possibile 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 utilizzarla se non riesci a proseguire.

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

Argomenti trattati

  • 🤔 Convoluzione separabile in profondità
  • 🤓 Programmazione dei tassi di apprendimento
  • 🔍 Perfezionare 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%, ripetendo l'addestramento successivo in pochi minuti grazie alle TPU. Con questo si concludeno i 4 "codelab su Keras su TPU":

TPU nella pratica

TPU e GPU sono disponibili su Cloud AI Platform:

Infine, ci piacerebbe ricevere feedback. Facci sapere se noti qualcosa che non va in questo lab o se pensi che dovrebbe essere migliorato. Il feedback può essere fornito tramite i problemi di GitHub [link per il feedback].

HR.png

ID Martin Görner small.jpg
L'autore: Martin Görner
Twitter: @martin_gorner

Tensorflow logo.jpg
www.tensorflow.org