Keras e convnet moderne, su TPU

1. Panoramica

In questo lab imparerai a creare, addestrare e ottimizzare le tue reti neurali convoluzionali da zero con Keras e Tensorflow 2. Ora bastano pochi minuti per farlo grazie alla potenza delle TPU. Esplorerai anche diversi approcci, dal transfer learning molto semplice alle architetture convoluzionali moderne come Squeezenet. Questo lab include spiegazioni teoriche sulle reti neurali ed è un buon punto di partenza per gli sviluppatori che vogliono imparare a utilizzare il deep learning.

Leggere articoli sul deep learning può essere difficile e confuso. Diamo un'occhiata pratica alle architetture moderne delle reti neurali convoluzionali.

ca8cc21f6838eccc.png

Obiettivi didattici

  • Per utilizzare Keras e le TPU (Tensor Processing Unit) in modo da creare più rapidamente i tuoi modelli personalizzati.
  • Utilizzare l'API tf.data.Dataset e il formato TFRecord per caricare i dati di addestramento in modo efficiente.
  • Per barare 😈, utilizza il transfer learning anziché creare i tuoi modelli.
  • Per utilizzare gli stili di modello sequenziale e funzionale di Keras.
  • Per creare il tuo classificatore Keras con uno strato softmax e la perdita di entropia incrociata.
  • Per ottimizzare il modello con una buona scelta di livelli con convoluzione.
  • Esplorare le moderne idee di architettura delle convnet come moduli, pooling medio globale e così via.
  • Per creare una convnet moderna e semplice utilizzando l'architettura Squeezenet.

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. Puoi eseguirlo da un Chromebook. Apri il file di seguito ed esegui le celle per acquisire familiarità con i blocchi note di Colab.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Seleziona un backend TPU

8832c6208c99687d.png

Nel menu Colab, seleziona Runtime > Cambia tipo di runtime, quindi seleziona TPU. In questo lab di codice 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 "Connetti" 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 notebook 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 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

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

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 utilizzeremo le TPU per creare e ottimizzare un classificatore di fiori a velocità interattive (minuti per esecuzione di 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 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. 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

L'unità MXU calcola le moltiplicazioni di matrici utilizzando input bfloat16 e 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 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. Questo, insieme al fatto che le TPU calcolano le moltiplicazioni di matrici con precisione mista con input bfloat16, ma output float32, significa che, in genere, non sono necessarie modifiche al codice per trarre vantaggio dai miglioramenti delle prestazioni della 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 del cuore 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 di matrici 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, si programma questo prodotto scalare in un "core" GPU e lo si esegue su tutti i "core" disponibili in parallelo per provare a calcolare contemporaneamente ogni valore 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. Sono così piccole che una TPU può implementarne 16.000 in un'unità MXU 128 x 128 ed elaborare questa moltiplicazione di matrici in un'unica operazione.

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 le 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 scalari vengono calcolati in un'unità di calcolo MXU, le somme intermedie passano semplicemente da un'unità di calcolo adiacente all'altra. Non è necessario archiviarli e recuperarli nella/dalla memoria o persino in un file di registro. Il risultato finale è che l'architettura dell'array sistolica TPU presenta un vantaggio significativo in termini di densità e potenza, oltre a un vantaggio di velocità non trascurabile rispetto a una GPU, durante il calcolo delle moltiplicazioni di matrici.

Cloud TPU

Quando richiedi un "Cloud TPU v2" sulla piattaforma Google Cloud, ricevi una macchina virtuale (VM) con una scheda TPU collegata tramite PCI. La scheda TPU ha quattro chip TPU dual-core. Ogni core TPU è dotato di una VPU (Unità di elaborazione vettoriale) e di un'unità di moltiplicazione a matrice (MXU) 128 x 128. In genere, questa "Cloud TPU" viene connessa tramite la rete alla VM che l'ha richiesta. Il quadro completo sarà quindi simile a questo:

dfce5522ed644ece.png

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 (high-performance computing) che può farle apparire come un acceleratore molto grande. Google li chiama pod e possono includere fino a 512 core TPU v2 o 2048 core TPU v3.

2ec1e0d341e7fc34.jpeg

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.

d97b9cc5d40fdb1d.gif

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 con dimensioni dei batch elevate

La dimensione ideale del batch per le TPU è di 128 elementi di dati per core TPU, ma l'hardware può già mostrare un buon utilizzo a partire 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 XLA esplicitamente nella catena di compilazione.

edce61112cd57972.png

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 oppure chiama TPUClusterResolver 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 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'utilizzo 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. Caricamento dati in corso…

c0ecb860e4cad0a9.jpeg cc4781a7739c49ae.jpeg 81236b00f8bbf39e.jpeg 961e2228974076bb.jpeg 7517dc163bdffcd5.jpeg 96392df4767f566d.png

Lavoreremo con un set di dati di immagini di fiori. L'obiettivo è imparare a classificarli in 5 tipi di fiori. Il caricamento dei dati viene eseguito utilizzando l'API tf.data.Dataset. Innanzitutto, conosciamo l'API.

Pratico

Apri il seguente blocco note, esegui le celle (Maiusc-Invio) e segui le istruzioni ogni volta che vedi l'etichetta "OPERAZIONE RICHIESTA".

c3df49e90e5a654f.png Fun with tf.data.Dataset (playground).ipynb

Ulteriori informazioni

Informazioni sul set di dati "flowers"

Il set di dati è organizzato in 5 cartelle. Ogni cartella contiene fiori di un solo tipo. Le cartelle sono denominate girasoli, margherite, tarassaco, tulipani e rose. I dati sono ospitati in un bucket pubblico su Google Cloud Storage. Estratto:

gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg

Perché tf.data.Dataset?

Keras e TensorFlow accettano i set di dati in tutte le loro funzioni di addestramento e valutazione. Una volta caricati i dati in un set di dati, l'API offre tutte le funzionalità comuni utili per i dati di addestramento delle reti neurali:

dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training

Puoi trovare suggerimenti sul rendimento e best practice per i set di dati in questo articolo. La documentazione di riferimento è disponibile qui.

Nozioni di base su tf.data.Dataset

I dati di solito si trovano in più file, qui le immagini. Puoi creare un set di dati di nomi file chiamando:

filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.

Devi quindi "mappare" una funzione su ciascun nome file, che in genere carica e decodifica il file in dati effettivi in memoria:

def decode_jpeg(filename):
  bits = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  return image

image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)

Per eseguire l'iterazione su un set di dati:

for data in my_dataset:
  print(data)

Set di dati di tuple

Nell'apprendimento supervisionato, un set di dati di addestramento è in genere costituito da coppie di dati di addestramento e risposte corrette. Per consentire questo, la funzione di decodifica può restituire tuple. Avrai quindi un set di dati di tuple e le tuple verranno restituite quando esegui l'iterazione. I valori restituiti sono tensori TensorFlow pronti per essere utilizzati dal modello. Puoi chiamare .numpy() per visualizzare i valori non elaborati:

def decode_jpeg_and_label(filename):
  bits = tf.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  label = ... # extract flower name from folder name
  return image, label

image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs 

for image, label in dataset:
  print(image.numpy().shape, label.numpy())

Conclusione:caricare le immagini una alla volta è lento!

Man mano che esegui l'iterazione su questo set di dati, noterai che puoi caricare circa 1-2 immagini al secondo. Troppo lento! Gli acceleratori hardware che utilizzeremo per l'addestramento possono sostenere molte volte questa frequenza. Vai alla prossima sezione per vedere come raggiungere questo obiettivo.

Soluzione

Ecco il blocco note della soluzione. Se non riesci a procedere, puoi utilizzarla.

c3df49e90e5a654f.png Fun with tf.data.Dataset (solution).ipynb

Argomenti trattati

  • 🤔 tf.data.Dataset.list_files
  • 🤔 tf.data.Dataset.map
  • 🤔 Set di dati di tuple
  • 😀 l'iterazione tramite i set di dati

Esamina mentalmente questo elenco di controllo.

5. Caricamento rapido dei dati

Gli acceleratori hardware TPU (Tensor Processing Unit) che utilizzeremo in questo lab sono molto veloci. Spesso la sfida è fornire i dati abbastanza velocemente da tenerli occupati. Google Cloud Storage (GCS) è in grado di supportare un throughput molto elevato, ma come per tutti i sistemi di archiviazione sul cloud, l'avvio di una connessione comporta alcuni trasferimenti di rete. Pertanto, non è ideale memorizzare i dati sotto forma di migliaia di singoli file. Li raggruppaamo in un numero inferiore di file e utilizziamo la potenza di tf.data.Dataset per leggere da più file in parallelo.

Read-through

Il codice che carica i file immagine, ne ridimensiona le dimensioni in base a una dimensione comune e poi li archivia in 16 file TFRecord è nel seguente notebook. Leggilo rapidamente. Non è necessario eseguirla, poiché per il resto del codelab verranno forniti dati correttamente formattati in TFRecord.

c3df49e90e5a654f.png Flower pictures to TFRecords.ipynb

Layout di dati ideale per una velocità effettiva ottimale di GCS

Formato file TFRecord

Il formato di file preferito da TensorFlow per l'archiviazione dei dati è il formato TFRecord basato su protobuf. Andrebbero bene anche altri formati di serializzazione, ma puoi caricare un set di dati direttamente dai file TFRecord scrivendo:

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

Per ottenere prestazioni ottimali, ti consigliamo di utilizzare il codice più complesso riportato di seguito per leggere da più file TFRecord contemporaneamente. Questo codice leggerà da N file in parallelo e ignorerà l'ordine dei dati a favore della velocità di lettura.

AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

Scheda di riferimento di TFRecord

In TFRecord è possibile memorizzare tre tipi di dati: stringhe di byte (elenco di byte), interi a 64 bit e numeri in virgola mobile a 32 bit. Vengono sempre archiviati come elenchi, un singolo elemento di dati sarà un elenco di dimensione 1. Puoi utilizzare le seguenti funzioni helper per archiviare i dati in TFRecord.

scrittura di stringhe di byte

# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))

scrittura di numeri interi

def _int_feature(list_of_ints): # int64
  return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))

scrivere messaggi mobili

def _float_feature(list_of_floats): # float32
  return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))

scrivendo un TFRecord, usando gli assistenti indicati sopra

# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
  feature = {
    "image": _bytestring_feature([my_img_bytes]), # one image in the list
    "class": _int_feature([my_class]),            # one class in the list
    "size": _int_feature([my_height, my_width]),  # fixed length (2) list of ints
    "float_data": _float_feature(my_floats)       # variable length  list of floats
  }
  tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
  out_file.write(tf_record.SerializeToString())

Per leggere i dati dai TFRecord, devi prima dichiarare il layout dei record archiviati. Nella dichiarazione, puoi accedere a qualsiasi campo denominato come elenco di lunghezza fissa o elenco di lunghezza variabile:

leggi dai TFRecord

def read_tfrecord(data):
  features = {
    # tf.string = byte string (not text string)
    "image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
    "class": tf.io.FixedLenFeature([], tf.int64),  # shape [] means scalar, i.e. a single item
    "size": tf.io.FixedLenFeature([2], tf.int64),  # two integers
    "float_data": tf.io.VarLenFeature(tf.float32)  # a variable number of floats
  }

  # decode the TFRecord
  tf_record = tf.io.parse_single_example(data, features)

  # FixedLenFeature fields are now ready to use
  sz = tf_record['size']

  # Typical code for decoding compressed images
  image = tf.io.decode_jpeg(tf_record['image'], channels=3)

  # VarLenFeature fields require additional sparse.to_dense decoding
  float_data = tf.sparse.to_dense(tf_record['float_data'])

  return image, sz, float_data

# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)

Snippet di codice utili:

Lettura di singoli elementi di dati

tf.io.FixedLenFeature([], tf.string)   # for one byte string
tf.io.FixedLenFeature([], tf.int64)    # for one int
tf.io.FixedLenFeature([], tf.float32)  # for one float

leggere elenchi di elementi a dimensioni fisse

tf.io.FixedLenFeature([N], tf.string)   # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64)    # list of N ints
tf.io.FixedLenFeature([N], tf.float32)  # list of N floats

Lettura di un numero variabile di elementi di dati

tf.io.VarLenFeature(tf.string)   # list of byte strings
tf.io.VarLenFeature(tf.int64)    # list of ints
tf.io.VarLenFeature(tf.float32)  # list of floats

Una funzione VarLenFeature restituisce un vettore sparso ed è necessario un passaggio aggiuntivo dopo la decodifica di TFRecord:

dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])

È anche possibile avere campi facoltativi nei TFRecord. Se specifichi un valore predefinito durante la lettura di un campo, viene restituito il valore predefinito anziché un errore se il campo non è presente.

tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional

Argomenti trattati

  • 🤔 partizionamento orizzontale dei file di dati per un rapido accesso da GCS
  • 😓 come scrivere TFRecord. (Hai già dimenticato la sintassi? Non preoccuparti, aggiungi questa pagina ai preferiti come promemoria.
  • 🤔 Caricamento di un set di dati da TFRecord utilizzando TFRecordDataset

Dedica qualche istante a leggere questo elenco di controllo.

6. [INFO] Classificatore di rete neurale 101

In sintesi

Se conosci già tutti i termini in grassetto del 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 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. È costituito da "neuroni" disposti in strati. Il primo strato elabora i dati di input e alimenta gli output in altri strati. È chiamato "denso" perché ogni neurone è collegato a tutti i neuroni dello strato precedente.

c21bae6dade487bc.png

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 verrà migliorata in seguito.

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". Inizialmente, le ponderazioni e il bias sono sconosciuti. Verranno inizializzate in modo casuale e "apprese" addestrando la rete neurale con molti dati noti.

644f4213a4ee70e5.png

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 classica funzione di attivazione RELU. Nell'ultimo livello, però, vogliamo calcolare 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à.

ef0d98c0952c262d.png d51252f75894479e.gif

Perdita di 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 questa funzione di errore o "loss":

7bdf8753d20617fb.png

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 delle ponderazioni, dei bias, dei pixel dell'immagine di addestramento e della sua classe nota.

Se calcoliamo le derivate parziali dell'entropia di crociera rispetto a tutti i pesi e a tutti i bias, otteniamo un "gradiente", calcolato per una determinata immagine, etichetta e valore attuale di pesi 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. Poi ripetiamo la stessa cosa più volte utilizzando i batch successivi di immagini e etichette di addestramento, in un ciclo 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 del gradiente2.png

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. Vuoi dire che abbiamo trovato un 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.

52e824fe4716c4a0.png

Illustrazione: un punto di attacco. La pendenza è 0, ma non è un minimo in tutte le direzioni. (Attribuzione dell'immagine Wikimedia: By Nicoguaro - Own work, CC BY 3.0)

La soluzione consiste nell'aggiungere un po' di 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 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 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

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 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 nelle categorie di classificazione.

tensore: 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.

7. Transfer Learning

Per un problema di classificazione delle immagini, gli strati densi probabilmente non saranno sufficienti. Dobbiamo conoscere i livelli convoluzionali e i molti modi in cui puoi organizzarli.

Ma possiamo anche usare una scorciatoia. Sono disponibili per il download reti neurali convoluzionali completamente addestrate. È possibile tagliare l'ultimo livello, la testa di classificazione softmax, e sostituirlo con il tuo. Tutti i pesi e i bias addestrati rimangono invariati, devi solo addestrare nuovamente il livello softmax che aggiungi. Questa tecnica è chiamata Transfer Learning e, sorprendentemente, funziona purché il set di dati su cui la rete neurale è preaddestrata sia "abbastanza simile" al tuo.

Hands-on

Apri il seguente blocco note, esegui le celle (Maiusc+Invio) e segui le istruzioni ogni volta che vedi l'etichetta "OPERAZIONE RICHIESTA".

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

Ulteriori informazioni

Il transfer learning consente di sfruttare sia le architetture di reti neurali convoluzionali avanzate sviluppate da famosi ricercatori sia il preaddestramento su un enorme set di dati di immagini. Nel nostro caso trasferiremo l'apprendimento da una rete addestrata su ImageNet, un database di immagini contenente molte piante e scene all'aperto, abbastanza vicino ai fiori.

b8fc1efd2001f072.png

Ilustrazione: utilizzo di una complessa rete neurale convoluzionale, già addestrata, come una scatola nera, per addestrare nuovamente solo la testa di classificazione. Questo è il trasferimento di apprendimento. Vedremo più avanti come funzionano queste complicate disposizione degli strati convoluzionali. Per il momento, il problema è qualcun altro.

Transfer learning in Keras

In Keras, puoi creare un'istanza per un modello preaddestrato dalla raccolta tf.keras.applications.*. MobileNet V2, ad esempio, è un'ottima architettura convoluzionale che garantisce dimensioni ragionevoli. Se selezioni include_top=False, ottieni il modello preaddestrato senza il livello softmax finale, in modo da poter aggiungere il tuo:

pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False

model = tf.keras.Sequential([
    pretrained_model,
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(5, activation='softmax')
])

Tieni presente anche l'impostazione pretrained_model.trainable = False. Blocca i pesi e i bias del modello preaddestrato in modo da addestrare solo lo strato softmax. In genere, sono coinvolti relativamente pochi pesi e l'operazione può essere eseguita rapidamente e senza richiedere un set di dati molto grande. Tuttavia, se hai molti dati, l'apprendimento tramite trasferimento può funzionare ancora meglio con pretrained_model.trainable = True. I pesi preaddestrati forniscono quindi valori iniziali eccellenti e possono comunque essere regolati dall'addestramento per adattarsi meglio al problema.

Infine, noterai il livello Flatten() inserito prima del livello softmax denso. Gli strati densi funzionano su vettori piatti di dati, ma non sappiamo se questo è ciò che restituisce il modello preaddestrato. Ecco perché dobbiamo appiattire. Nel prossimo capitolo, mentre approfondiremo le architetture convoluzionali, spiegheremo il formato dei dati restituito dagli strati convoluzionali.

Con questo approccio dovresti ottenere un'accuratezza del 75%.

Soluzione

Ecco il blocco note della soluzione. Se non riesci a procedere, puoi utilizzarla.

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

Argomenti trattati

  • 🤔 Come scrivere un classificatore in Keras
  • 🤓 configurato con un ultimo strato softmax e perdita di entropia incrociata
  • 🔍 Transfer Learning
  • 🤔 Addestramento del primo modello
  • 🧐 A seguito della perdita e della precisione durante l'addestramento

Esamina mentalmente questo elenco di controllo.

8. [INFO] Reti neurali convoluzionali

In breve

Se conosci già tutti i termini in grassetto del paragrafo successivo, puoi passare all'esercizio successivo. Se stai appena iniziando a utilizzare le reti neurali convoluzionali, continua a leggere.

convolutional.gif

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

688858c21e3beff2.png

Introduzione alle reti neurali convoluzionali

In un livello di una rete convoluzionale, un "neurone" esegue la somma ponderata dei pixel appena sopra di esso, solo in una piccola regione 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 in tutta l'immagine utilizzando gli stessi pesi. Ricorda che negli 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 filtro che utilizza un filtro di 4 x 4 x 3=48 pesi.

Tuttavia, 48 pesi non saranno sufficienti. Per aggiungere altri 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.

Screen Shot 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 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 sovrapporre e concatenare gli strati convoluzionali.

d1b557707bcd1cb9.png

Illustrazione: una rete neurale convoluzionale trasforma dei "cubi" di dati in altri "cubi" 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, 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:

4a61aaffb6cba3d1.png

Illustrazione: un classificatore di immagini che utilizza i livelli 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'])

9. La tua rete convoluzionale personalizzata

Hands-on

Creiamo e addestriamo una rete neurale convoluzionale da zero. L'utilizzo di una TPU ci consentirà di eseguire l'iterazione molto rapidamente. Apri il seguente blocco note, esegui le celle (Maiusc-Invio) e segui le istruzioni ogni volta che vedi l'etichetta "OPERAZIONE RICHIESTA".

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

L'obiettivo è battere l'accuratezza del 75% del modello Transfer Learning. Quel modello aveva un vantaggio, in quanto è stato preaddestrato su un set di dati di milioni di immagini mentre qui abbiamo solo immagini 3670. Riesci almeno a abbinarlo?

Ulteriori informazioni

Quanti livelli, quanto grande?

La scelta delle dimensioni dei livelli è più un'arte che una scienza. Devi trovare il giusto equilibrio tra un numero troppo basso e un numero eccessivo di parametri (ponderazioni e bias). Con un numero troppo ridotto di pesi, la rete neurale non può rappresentare la complessità delle forme dei fiori. Se sono troppe, possono essere soggette a "overfitting", ovvero specializzarsi nelle immagini di addestramento e non essere in grado di generalizzare. Con molti parametri, l'addestramento del modello è lento. In Keras, la funzione model.summary() mostra la struttura e il numero di parametri del modello:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 192, 192, 16)      448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 192, 192, 30)      4350      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 96, 96, 60)        16260     
_________________________________________________________________
 ... 
_________________________________________________________________
global_average_pooling2d (Gl (None, 130)               0         
_________________________________________________________________
dense (Dense)                (None, 90)                11790     
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 455       
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________

Un paio di suggerimenti:

  • La presenza di più livelli è ciò che rende efficaci le reti neurali "profonde". Per questo semplice problema del riconoscimento dei fiori, da 5 a 10 livelli ha senso.
  • Utilizza filtri di piccole dimensioni. In genere, i filtri 3 x 3 sono adatti ovunque.
  • Puoi utilizzare anche i filtri 1 x 1, che sono economici. Non "filtrano" nulla, ma calcolano combinazioni lineari di canali. Alternali con filtri veri. Scopri di più sulle "convoluzioni 1x1" nella sezione successiva.
  • Per un problema di classificazione come questo, esegui il sottocampionamento di frequente con i livelli di max pooling (o con le convolute con uno stride maggiore di 1). Non ti importa dove si trova il fiore, solo che si tratta di una rosa o di un dente di leone, quindi perdere le informazioni x e y non è importante e filtrare aree più piccole è più economico.
  • Il numero di filtri di solito diventa simile al numero di classi alla fine della rete (perché? vedi il trucco "pooling medio globale" di seguito). Se esegui la classificazione in centinaia di classi, aumenta progressivamente il numero di filtri in livelli consecutivi. Per il set di dati dei fiori con 5 classi, non sarebbe sufficiente filtrare con solo 5 filtri. Puoi utilizzare lo stesso numero di filtri nella maggior parte dei livelli, ad esempio 32, e diminuirlo verso la fine.
  • Gli strati densi finali sono costosi. Possono avere più pesi di tutti i livelli con convoluzione messi insieme. Ad esempio, anche con un output molto ragionevole dell'ultimo cubo di dati di 24 x 24 x 10 punti dati, un livello denso di 100 neuroni costerebbe 24 x 24 x 10 x 100 = 576.000 pesi. Cerca di essere ponderato o prova la sommatoria media globale (vedi di seguito).

Pooling medio globale

Invece di utilizzare un costoso strato denso alla fine di una rete neurale convoluzionale, puoi suddividere il "cubo" di dati in arrivo in tante parti quante sono le classi, calcolarne la media dei valori e trasmetterli attraverso una funzione di attivazione softmax. Questo modo di creare l'attributo principale di classificazione non ha costi. In Keras, la sintassi è tf.keras.layers.GlobalAveragePooling2D().

93240029f59df7c2.png

Soluzione

Ecco il blocco note della soluzione. Se non riesci a procedere, puoi utilizzarla.

c3df49e90e5a654f.png Keras_Flowers_TPU (solution).ipynb

Argomenti trattati

  • 🤔 Ho giocato con i livelli con convoluzione
  • 🤓 Ho fatto esperimenti con la somma massima, gli intervalli, la somma media globale e così via.
  • 😀 Eseguito l'iterazione su un modello reale in modo rapido su TPU

Esamina mentalmente questo elenco di controllo.

10. [INFO] Architetture convoluzionali moderne

In breve

7968830b57b708c0.png

Illustrazione: un "modulo" convoluzionale. Cosa posso fare in questo momento? 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)

688858c21e3beff2.png

Altri trucchi economici

Filtri piccoli 3 x 3

40a7b15fb7dbe75c.png

In questa illustrazione viene mostrato il risultato di due filtri 3 x 3 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 1x1?

fd7cac16f8ecb423.png

In termini matematici, una convezione "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 1x1 utilizzano meno pesi.

11. Squeezenet

Un modo semplice per mettere insieme queste idee è stato illustrato nel documento "Squeezenet". Gli autori suggeriscono un design del modulo di convoluzione molto semplice, utilizzando solo livelli di convoluzione 1x1 e 3x3.

1730ac375379269b.png

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 modificare il codice del modello in "stile funzionale" di Keras.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

Ulteriori informazioni

Per questo esercizio sarà utile definire una funzione di supporto 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.

Cose 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 massimi nella rete e anche con 1, 2 o 3 moduli di incendio consecutivi tra i livelli di pooling massimi.

Nei moduli di attivazione, il parametro "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. È ora di altri due trucchi economici.

Normalizzazione batch

La norma batch ti aiuterà a risolvere i problemi di convergenza che stai riscontrando. Nel prossimo workshop ci saranno spiegazioni dettagliate su questa tecnica. Per il momento, usala come un "magico" helper black-box aggiungendo questa riga dopo ogni strato convoluzionale della tua rete, inclusi i livelli all'interno della tua 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 di slancio deve essere ridotto dal valore predefinito di 0,99 a 0,9 perché il nostro 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:

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.

Ora dovrebbe essere possibile raggiungere un'accuratezza dell'80% in 35 epoche.

Soluzione

Ecco il blocco note della soluzione. Se non riesci a procedere, puoi utilizzarla.

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

Argomenti trattati

  • 🤔 Modelli "in stile funzionale" di Keras
  • 🤓 Architettura Squeezenet
  • 🤓 Aumentazione dei dati con tf.data.datset

Dedica qualche istante a leggere questo elenco di controllo.

12. Xception ottimizzato

Convoluzioni separabili

Di recente, un modo diverso di implementare i livelli con convoluzioni sta guadagnando popolarità: le convoluzioni separabili in base alla profondità. Lo so, è un po' complicato, 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 1x1", una serie di prodotti scalari che generano una somma ponderata dei canali filtrati. Con nuovi pesi ogni volta, vengono calcolate tutte le ricombinazioni ponderate dei canali in base alle necessità.

615720b803bf8dda.gif

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, con nuovi pesi ogni volta, ma nella pratica è raro.

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 delle ponderazioni per l'esempio sopra illustrato:

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

Livello 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ù efficienti dal punto di vista computazionale.

Hands-on

Riavvia dal blocco note "Transfer Learning", ma questa volta seleziona Xception come modello preaddestrato. Xception utilizza solo convolute separabili. Lascia addestrabili tutti i pesi. Eseguiremo la messa a punto dei pesi pre-addestrati sui nostri dati anziché utilizzare i livelli pre-addestrati come tali.

c3df49e90e5a654f.png 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 sulla messa a punto

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 la messa a punto di un modello, devi prestare attenzione al tasso di apprendimento e utilizzare una pianificazione del tasso di apprendimento con un periodo di applicazione graduale. Esempio:

9b1af213b2b36d47.png

Iniziare con un tasso di apprendimento standard interromperebbe i pesi preaddestrati del modello. Se li inizi a utilizzare gradualmente, li mantieni fino a quando il modello non li ha acquisiti 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 passerà 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. Se non riesci a procedere, puoi utilizzarla.

c3df49e90e5a654f.png 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.

13. Complimenti!

Hai creato la tua prima rete neurale convoluzionale moderna e l'hai addestrata con un'accuratezza superiore al 90%, ripetendo le esecuzioni di addestramento successive in pochi minuti grazie alle TPU.

TPU nella pratica

Le TPU e le GPU sono disponibili su Vertex AI di Google Cloud:

Infine, ci piace 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].

HR.png

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