TensorFlow, Keras e deep learning, senza un dottorato di ricerca

1. Panoramica

Questo tutorial è stato aggiornato per TensorFlow 2.2.

74f6fbd758bf19e6.png

In questo codelab imparerai a creare e addestrare una rete neurale che riconosce le cifre scritte a mano. Man mano che migliori la tua rete neurale per raggiungere una precisione del 99%, scoprirai anche gli strumenti del mestiere che i professionisti del deep learning utilizzano per addestrare i loro modelli in modo efficiente.

Questo codelab utilizza il set di dati MNIST, una raccolta di 60.000 cifre etichettate che ha tenuto occupate generazioni di dottorandi per quasi due decenni. Risolvi il problema con meno di 100 righe di codice Python / TensorFlow.

Obiettivi didattici

  • Che cos'è una rete neurale e come addestrarla
  • Come creare una rete neurale di base a un livello utilizzando tf.keras
  • Come aggiungere altri livelli
  • Come impostare una pianificazione del tasso di apprendimento
  • Come creare reti neurali convoluzionali
  • Come utilizzare le tecniche di regolarizzazione: abbandono, normalizzazione batch
  • Che cos'è l'overfitting

Che cosa ti serve

Solo un browser. Questo workshop può essere eseguito interamente con Google Colaboratory.

Feedback

Comunicaci se noti qualcosa di strano in questo lab o se ritieni che debba essere migliorato. Gestiamo i feedback tramite le segnalazioni di GitHub [link al feedback].

2. Guida rapida di Google Colaboratory

Questo lab utilizza Google Colaboratory e non richiede alcuna configurazione da parte tua. Puoi eseguirlo da un Chromebook. Apri il file riportato di seguito ed esegui le celle per acquisire familiarità con i blocchi note Colab.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Ulteriori istruzioni riportate di seguito:

Seleziona un backend GPU

hsy7H7O5qJNvKcRnHRiZoyh0IznlzmrO60wR1B6pqtfdc8Ie7gLsXC0f670zsPzGsNy3QAJuZefYv9CwTHmjiMyywG2pTpnMCE6Slkf3K1BeVmfpsYVw6omItm1ZneqdE31F8re-dA

Nel menu Colab, seleziona Runtime > Cambia tipo di runtime e poi seleziona GPU. La connessione al runtime avverrà automaticamente alla prima esecuzione oppure puoi utilizzare il pulsante "Connetti" nell'angolo in alto a destra.

Esecuzione del notebook

evlBKSO15ImjocdEcsIo8unzEe6oDGYnKFe8CoHS_7QiP3sDbrs2jB6lbyitEtE7Gt_1UsCdU5dJA-_2IgBWh9ofYf4yVDE740PwJ6kiQwuXNOLkgktzzf0E_k5VN5mq29ZXI5wb7Q

Esegui le celle una alla volta facendo clic su una cella e utilizzando Maiusc+Invio. Puoi anche eseguire l'intero notebook con Runtime > Esegui tutto.

Sommario

OXeYYbtKdLCNnw_xovSMeMwSdD7CL_w25EfhnpRhhhO44bYp3zZpU72J5tKaSuo8wpas0GK5B2sTBlIMiFmdGxFRQ9NmwJ7JIRYy5XtpWKQCPdxQVRPy_0J_LshGIKjtw8P9fXozaA

Tutti i notebook hanno un indice. Puoi aprirlo utilizzando la freccia nera a sinistra.

Celle nascoste

GXTbXUO8xpPFKiGc6Q-cFwFHxHvOa105hHg3vk77EDpStyhU4AQMN3FYenbiBusHXUSk-yGXbRDcK-Cwx18XbDtyqB5WRr3_2jhnLvFxW8a7H_4cGvVDKrEMto_QxhfTeO0hwmrfng

Alcune celle mostreranno solo il titolo. Questa è una funzionalità specifica dei notebook di Colab. Puoi fare doppio clic per visualizzare il codice all'interno, ma di solito non è molto interessante. In genere, funzioni di supporto o visualizzazione. Devi comunque eseguire queste celle per definire le funzioni al loro interno.

3. Addestrare una rete neurale

Per prima cosa, osserveremo l'addestramento di una rete neurale. Apri il blocco note riportato di seguito ed esegui tutte le celle. Non prestare ancora attenzione al codice, inizieremo a spiegarlo più avanti.

c3df49e90e5a654f.png keras_01_mnist.ipynb

Mentre esegui il blocco note, concentrati sulle visualizzazioni. Vedi di seguito le spiegazioni.

Dati di addestramento

Abbiamo un set di dati di cifre scritte a mano che sono state etichettate in modo da sapere cosa rappresenta ogni immagine, ovvero un numero compreso tra 0 e 9. Nel notebook vedrai un estratto:

ad83f98e56054737.png

La rete neurale che creeremo classifica le cifre scritte a mano nelle 10 classi (0, …, 9). Lo fa in base a parametri interni che devono avere un valore corretto affinché la classificazione funzioni correttamente. Questo "valore corretto" viene appreso tramite un processo di addestramento che richiede un "set di dati etichettato" con immagini e le risposte corrette associate.

Come facciamo a sapere se la rete neurale addestrata funziona bene o meno? Utilizzare il set di dati di addestramento per testare la rete sarebbe un imbroglio. Ha già visto questo set di dati più volte durante l'addestramento ed è sicuramente molto performante. Abbiamo bisogno di un altro set di dati etichettato, mai visto durante l'addestramento, per valutare le prestazioni "reali" della rete. Si chiama "set di dati di convalida".

Addestramento

Man mano che l'addestramento procede, un batch di dati di addestramento alla volta, i parametri interni del modello vengono aggiornati e il modello diventa sempre più bravo a riconoscere le cifre scritte a mano. Puoi visualizzarlo nel grafico di addestramento:

3f7b405649301ea.png

A destra, l'accuratezza è semplicemente la percentuale di cifre riconosciute correttamente. Aumenta man mano che l'addestramento procede, il che è positivo.

A sinistra, possiamo vedere la "perdita". Per guidare l'addestramento, definiremo una funzione "loss", che rappresenta il grado di riconoscimento delle cifre da parte del sistema, e cercheremo di ridurla al minimo. Come puoi vedere, la perdita diminuisce sia sui dati di addestramento che su quelli di convalida man mano che l'addestramento procede: questo è un buon segno. Significa che la rete neurale sta imparando.

L'asse X rappresenta il numero di "epoche" o iterazioni nell'intero set di dati.

Previsioni

Una volta addestrato, il modello può essere utilizzato per riconoscere i numeri scritti a mano. La visualizzazione successiva mostra il rendimento su alcune cifre eseguite con caratteri locali (prima riga) e poi sulle 10.000 cifre del set di dati di convalida. La classe prevista viene visualizzata sotto ogni cifra, in rosso se è errata.

c0699216ba0effdb.png

Come puoi vedere, questo modello iniziale non è molto buono, ma riconosce comunque correttamente alcune cifre. La sua precisione di convalida finale è di circa il 90%, il che non è male per il modello semplificato con cui stiamo iniziando,ma significa comunque che mancano 1000 cifre di convalida su 10.000. Sono molte di più di quelle che possono essere visualizzate, motivo per cui sembra che tutte le risposte siano errate (rosse).

Tensori

I dati vengono archiviati in matrici. Un'immagine in scala di grigi di 28 x 28 pixel si inserisce in una matrice bidimensionale di 28 x 28. Per un'immagine a colori, però, abbiamo bisogno di più dimensioni. Ci sono tre valori di colore per pixel (rosso, verde, blu), quindi sarà necessaria una tabella tridimensionale con dimensioni [28, 28, 3]. Per memorizzare un batch di 128 immagini a colori, è necessaria una tabella quadridimensionale con dimensioni [128, 28, 28, 3].

Queste tabelle multidimensionali sono chiamate "tensori" e l'elenco delle loro dimensioni è la loro "forma".

4. [INFO]: neural networks 101

In breve

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

witch.png

Per i modelli creati come sequenza di livelli, Keras offre l'API Sequential. Ad esempio, un classificatore di immagini che utilizza tre livelli densi può essere scritto in Keras come segue:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28, 1]),
    tf.keras.layers.Dense(200, activation="relu"),
    tf.keras.layers.Dense(60, activation="relu"),
    tf.keras.layers.Dense(10, activation='softmax') # classifying into 10 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

Un singolo livello denso

Le cifre scritte a mano nel set di dati MNIST sono immagini in scala di grigi di 28 x 28 pixel. L'approccio più semplice per classificarli è utilizzare i 28 x 28=784 pixel come input per una rete neurale a un livello.

Screen Shot 2016-07-26 at 12.32.24.png

Ogni "neurone" in una rete neurale esegue una somma ponderata di tutti i suoi input, aggiunge una costante chiamata "bias" e poi alimenta il risultato attraverso una "funzione di attivazione" non lineare. Le "ponderazioni" e i "bias" sono parametri che verranno determinati tramite l'addestramento. All'inizio vengono inizializzati con valori casuali.

L'immagine sopra rappresenta una rete neurale a un livello con 10 neuroni di output, poiché vogliamo classificare le cifre in 10 classi (da 0 a 9).

Con una moltiplicazione di matrici

Ecco come un livello di rete neurale, che elabora una raccolta di immagini, può essere rappresentato da una moltiplicazione di matrici:

matmul.gif

Utilizzando la prima colonna dei pesi nella matrice dei pesi W, calcoliamo la somma ponderata di tutti i pixel della prima immagine. Questa somma corrisponde al primo neurone. Utilizzando la seconda colonna di pesi, eseguiamo la stessa operazione per il secondo neurone e così via fino al decimo neurone. Possiamo quindi ripetere l'operazione per le restanti 99 immagini. Se chiamiamo X la matrice contenente le nostre 100 immagini, tutte le somme ponderate per i nostri 10 neuroni, calcolate su 100 immagini, sono semplicemente X.W, una moltiplicazione di matrici.

Ogni neurone deve ora aggiungere il proprio bias (una costante). Poiché abbiamo 10 neuroni, abbiamo 10 costanti di bias. Chiameremo questo vettore di 10 valori b. Deve essere aggiunto a ogni riga della matrice calcolata in precedenza. Utilizzando un po' di magia chiamata "trasmissione", scriveremo questa formula con un semplice segno più.

Infine, applichiamo una funzione di attivazione, ad esempio "softmax" (spiegata di seguito) e otteniamo la formula che descrive una rete neurale a un livello, applicata a 100 immagini:

Screen Shot 2016-07-26 at 16.02.36.png

In Keras

Con librerie di reti neurali di alto livello come Keras, non sarà necessario implementare questa formula. Tuttavia, è importante capire che un livello di rete neurale è solo un insieme di moltiplicazioni e addizioni. In Keras, un livello denso verrebbe scritto come segue:

tf.keras.layers.Dense(10, activation='softmax')

Approfondisci

È banale concatenare i livelli della rete neurale. Il primo livello calcola le somme ponderate dei pixel. Gli strati successivi calcolano le somme ponderate degli output degli strati precedenti.

fba0638cc213a29.png

L'unica differenza, a parte il numero di neuroni, sarà la scelta della funzione di attivazione.

Funzioni di attivazione: relu, softmax e sigmoide

In genere, utilizzeresti la funzione di attivazione "relu" per tutti i livelli tranne l'ultimo. L'ultimo livello di un classificatore utilizzerebbe l'attivazione "softmax".

644f4213a4ee70e5.png

Anche in questo caso, un "neurone" calcola una somma ponderata di tutti i suoi input, aggiunge un valore chiamato "bias" e inserisce il risultato nella funzione di attivazione.

La funzione di attivazione più popolare è chiamata "RELU", acronimo di Rectified Linear Unit. È una funzione molto semplice, come puoi vedere nel grafico sopra.

La funzione di attivazione tradizionale nelle reti neurali era la "sigmoide", ma è stato dimostrato che la "relu" ha proprietà di convergenza migliori quasi ovunque ed è ora preferita.

41fc82288c4aff5d.png

Attivazione Softmax per la classificazione

L'ultimo livello della nostra rete neurale ha 10 neuroni perché vogliamo classificare le cifre scritte a mano in 10 classi (0-9). Deve restituire 10 numeri compresi tra 0 e 1 che rappresentano la probabilità che questa cifra sia 0, 1, 2 e così via. Per questo, nell'ultimo livello 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 dividendolo per la sua norma "L1" (ovvero la somma dei valori assoluti) in modo che i valori normalizzati sommati diano 1 e possano essere interpretati come probabilità.

L'output dell'ultimo livello, prima dell'attivazione, viene talvolta chiamato "logits". Se questo vettore è L = [L0, L1, L2, L3, L4, L5, L6, L7, L8, L9], allora:

ef0d98c0952c262d.png d51252f75894479e.gif

Perdita di entropia incrociata

Ora che la nostra rete neurale produce previsioni dalle immagini di input, dobbiamo misurare la loro qualità, ovvero 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 del set di dati.

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

6dbba1bce3cadc36.png

Discesa del gradiente

"Addestrare" la rete neurale significa utilizzare immagini e etichette di addestramento per regolare pesi e bias in modo da ridurre al minimo la funzione di perdita di entropia incrociata. Ecco come funziona.

L'entropia incrociata è una funzione di pesi, bias, pixel dell'immagine di addestramento e della sua classe nota.

Se calcoliamo le derivate parziali dell'entropia incrociata 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 pesi e bias, quindi il calcolo del gradiente sembra un'attività molto impegnativa. Fortunatamente, TensorFlow lo fa per noi. La proprietà matematica di un gradiente è che punta "verso l'alto". Poiché vogliamo andare dove l'entropia incrociata è bassa, andiamo nella direzione opposta. Aggiorniamo pesi e bias di una frazione del gradiente. Quindi, ripetiamo la stessa operazione più e più volte utilizzando i batch successivi di immagini e etichette di addestramento, in un ciclo di addestramento. Si spera che converga in un punto in cui l'entropia incrociata sia minima, anche se nulla garantisce che questo minimo sia univoco.

gradient descent2.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 e quindi è più probabile che converga più rapidamente verso la soluzione. 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 e queste sono generalmente più facili da ottimizzare su GPU e TPU.

La convergenza può comunque essere un po' caotica e può persino interrompersi se il vettore gradiente è tutto zero. Significa che abbiamo trovato un minimo? Non sempre. Un componente gradiente può essere zero su un minimo o un massimo. Con un vettore gradiente con milioni di elementi, se sono tutti zero, la probabilità che ogni zero corrisponda a un minimo e nessuno a un punto massimo è piuttosto bassa. In uno spazio con molte dimensioni, i punti di sella sono piuttosto comuni e non vogliamo fermarci.

cc544924671fa208.png

Illustrazione: un punto di sella. Il gradiente è 0, ma non è un minimo in tutte le direzioni. (Attribuzione immagine Wikimedia: di 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, l'algoritmo può convergere. La dimensione "batch" è in genere la prima dimensione dei tensori di dati. Ad esempio, un tensore di forma [100, 192, 192, 3] contiene 100 immagini di 192 x 192 pixel con tre valori per pixel (RGB).

Perdita di entropia incrociata: una funzione di perdita speciale spesso utilizzata nei classificatori.

Strato denso: uno strato di neuroni in cui ogni neurone è collegato a tutti i neuroni dello strato precedente.

Caratteristiche: gli input di una rete neurale a volte vengono chiamati "caratteristiche". L'arte di capire quali parti di un set di dati (o combinazioni di parti) inserire in una rete neurale per ottenere buone previsioni è chiamata "ingegneria delle funzionalità".

Etichette: un altro nome per "classi" o risposte corrette in un problema di classificazione supervisionata

Tasso di apprendimento: frazione del gradiente in base alla quale vengono aggiornati pesi e bias a ogni iterazione del ciclo di addestramento.

Logit: gli output di un livello di neuroni prima dell'applicazione della funzione di attivazione sono chiamati "logit". Il termine deriva dalla "funzione logistica", nota anche come "funzione sigmoide", che in passato era la funzione di attivazione più popolare. "Neuron outputs before logistic function" è stato abbreviato in "logits".

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

Neurone: calcola la somma ponderata dei suoi input, aggiunge un bias e trasmette il risultato tramite una funzione di attivazione.

Codifica one-hot: la classe 3 su 5 viene codificata come un vettore di 5 elementi, tutti pari a zero tranne il terzo, che è pari a 1.

relu: unità lineare rettificata. Una funzione di attivazione popolare per i neuroni.

sigmoid: un'altra funzione di attivazione che era molto popolare e 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 la somma sia pari a 1, in modo che possa essere interpretato come un vettore di probabilità. Utilizzato come ultimo passaggio nei classificatori.

Tensore: un "tensore" è come una matrice, ma con un numero arbitrario di dimensioni. Un tensore unidimensionale è un vettore. Un tensore bidimensionale è una matrice. Poi puoi avere tensori con 3, 4, 5 o più dimensioni.

5. Esaminiamo il codice

Torniamo al notebook dello studio e questa volta leggiamo il codice.

c3df49e90e5a654f.png keras_01_mnist.ipynb

Esaminiamo tutte le celle di questo notebook.

Cella "Parametri"

Qui vengono definiti le dimensioni del batch, il numero di epoche di addestramento e la posizione dei file di dati. I file di dati sono ospitati in un bucket Google Cloud Storage (GCS), motivo per cui il loro indirizzo inizia con gs://

Cella "Importazioni"

Qui vengono importate tutte le librerie Python necessarie, tra cui TensorFlow e matplotlib per le visualizzazioni.

Cella "visualization utilities [RUN ME]****"

Questa cella contiene codice di visualizzazione non interessante. È compresso per impostazione predefinita, ma puoi aprirlo e dare un'occhiata al codice quando hai tempo facendo doppio clic.

Cella "tf.data.Dataset: parse files and prepare training and validation datasets"

Questa cella ha utilizzato l'API tf.data.Dataset per caricare il set di dati MNIST dai file di dati. Non è necessario dedicare troppo tempo a questa cella. Se ti interessa l'API tf.data.Dataset, ecco un tutorial che la spiega: Pipeline di dati a velocità TPU. Per ora, le nozioni di base sono:

Le immagini e le etichette (risposte corrette) del set di dati MNIST sono archiviate in record a lunghezza fissa in 4 file. I file possono essere caricati con la funzione di record fisso dedicata:

imagedataset = tf.data.FixedLengthRecordDataset(image_filename, 28*28, header_bytes=16)

Ora abbiamo un set di dati di byte di immagini. Devono essere decodificati in immagini. Definiamo una funzione per farlo. L'immagine non viene compressa, quindi la funzione non deve decodificare nulla (decode_raw non fa praticamente nulla). L'immagine viene quindi convertita in valori a rappresentazione in virgola mobile compresi tra 0 e 1. Potremmo rimodellarla qui come immagine 2D, ma in realtà la manteniamo come array piatto di pixel di dimensioni 28 x 28 perché è ciò che si aspetta il nostro livello denso iniziale.

def read_image(tf_bytestring):
    image = tf.io.decode_raw(tf_bytestring, tf.uint8)
    image = tf.cast(image, tf.float32)/256.0
    image = tf.reshape(image, [28*28])
    return image

Applichiamo questa funzione al set di dati utilizzando .map e otteniamo un set di dati di immagini:

imagedataset = imagedataset.map(read_image, num_parallel_calls=16)

Eseguiamo lo stesso tipo di lettura e decodifica per le etichette e .zip immagini ed etichette insieme:

dataset = tf.data.Dataset.zip((imagedataset, labelsdataset))

Ora abbiamo un set di dati di coppie (immagine, etichetta). Questo è ciò che si aspetta il nostro modello. Non siamo ancora pronti per utilizzarlo nella funzione di addestramento:

dataset = dataset.cache()
dataset = dataset.shuffle(5000, reshuffle_each_iteration=True)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

L'API tf.data.Dataset dispone di tutte le funzioni di utilità necessarie per preparare i set di dati:

.cache memorizza nella cache il set di dati nella RAM. Si tratta di un set di dati molto piccolo, quindi funzionerà. .shuffle lo riproduce in ordine casuale con un buffer di 5000 elementi. È importante che i dati di addestramento siano ben mischiati. .repeat esegue il loop del set di dati. Lo addestreremo più volte (più epoche). .batch raggruppa più immagini ed etichette in un mini-batch. Infine, .prefetch può utilizzare la CPU per preparare il batch successivo mentre il batch corrente viene addestrato sulla GPU.

Il set di dati di convalida viene preparato in modo simile. Ora possiamo definire un modello e utilizzare questo set di dati per addestrarlo.

Cella "Modello Keras"

Tutti i nostri modelli saranno sequenze lineari di livelli, quindi possiamo utilizzare lo stile tf.keras.Sequential per crearli. Inizialmente qui è un singolo strato denso. Ha 10 neuroni perché stiamo classificando le cifre scritte a mano in 10 classi. Utilizza l'attivazione "softmax" perché è l'ultimo livello di un classificatore.

Un modello Keras deve anche conoscere la forma dei suoi input. tf.keras.layers.Input può essere utilizzato per definirlo. Qui, i vettori di input sono vettori piatti di valori dei pixel di lunghezza 28*28.

model = tf.keras.Sequential(
  [
    tf.keras.layers.Input(shape=(28*28,)),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model.compile(optimizer='sgd',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# print model layers
model.summary()

# utility callback that displays training curves
plot_training = PlotTraining(sample_rate=10, zoom=1)

La configurazione del modello viene eseguita in Keras utilizzando la funzione model.compile. Qui utilizziamo l'ottimizzatore di base 'sgd' (Stochastic Gradient Descent). Un modello di classificazione richiede una funzione di perdita di entropia incrociata, chiamata 'categorical_crossentropy' in Keras. Infine, chiediamo al modello di calcolare la metrica 'accuracy', ovvero la percentuale di immagini classificate correttamente.

Keras offre l'utilità model.summary() molto utile che stampa i dettagli del modello che hai creato. Il tuo gentile istruttore ha aggiunto l'utilità PlotTraining (definita nella cella "Utilità di visualizzazione") che mostrerà varie curve di allenamento durante l'allenamento.

Cella "Addestra e convalida il modello"

È qui che avviene l'addestramento, chiamando model.fit e passando sia i set di dati di addestramento che di convalida. Per impostazione predefinita, Keras esegue un round di convalida al termine di ogni epoca.

model.fit(training_dataset, steps_per_epoch=steps_per_epoch, epochs=EPOCHS,
          validation_data=validation_dataset, validation_steps=1,
          callbacks=[plot_training])

In Keras è possibile aggiungere comportamenti personalizzati durante l'addestramento utilizzando i callback. È così che è stato implementato il grafico di addestramento con aggiornamento dinamico per questo workshop.

Cella "Visualizza previsioni"

Una volta addestrato il modello, possiamo ottenere previsioni chiamando model.predict():

probabilities = model.predict(font_digits, steps=1)
predicted_labels = np.argmax(probabilities, axis=1)

Qui abbiamo preparato un insieme di cifre stampate eseguite con caratteri locali, come test. Ricorda che la rete neurale restituisce un vettore di 10 probabilità dal suo "softmax" finale. Per ottenere l'etichetta, dobbiamo scoprire qual è la probabilità più alta. np.argmax della libreria numpy.

Per capire perché è necessario il parametro axis=1, ricorda che abbiamo elaborato un batch di 128 immagini e pertanto il modello restituisce 128 vettori di probabilità. La forma del tensore di output è [128, 10]. Calcoliamo l'argmax tra le 10 probabilità restituite per ogni immagine, quindi axis=1 (il primo asse è 0).

Questo semplice modello riconosce già il 90% delle cifre. Non male, ma ora migliorerai notevolmente.

396c54ef66fad27f.png

6. Aggiungere livelli

godeep.png

Per migliorare l'accuratezza del riconoscimento, aggiungeremo altri strati alla rete neurale.

Screen Shot 2016-07-27 at 15.36.55.png

Manteniamo softmax come funzione di attivazione nell'ultimo livello perché è quella che funziona meglio per la classificazione. Nei livelli intermedi, tuttavia, utilizzeremo la funzione di attivazione più classica: la sigmoide:

41fc82288c4aff5d.png

Ad esempio, il modello potrebbe avere il seguente aspetto (non dimenticare le virgole, tf.keras.Sequential accetta un elenco di livelli separati da virgole):

model = tf.keras.Sequential(
  [
      tf.keras.layers.Input(shape=(28*28,)),
      tf.keras.layers.Dense(200, activation='sigmoid'),
      tf.keras.layers.Dense(60, activation='sigmoid'),
      tf.keras.layers.Dense(10, activation='softmax')
  ])

Guarda il "riepilogo" del modello. Ora ha almeno 10 volte più parametri. Dovrebbe essere 10 volte migliore. Ma per qualche motivo non è così…

5236f91ba6e07d85.png

Anche le perdite sembrano essere aumentate vertiginosamente. Si è verificato un problema.

7. Attenzione speciale per le reti profonde

Hai appena sperimentato le reti neurali, come venivano progettate negli anni'80 e'90. Non sorprende che abbiano abbandonato l'idea, dando il via al cosiddetto "inverno dell'AI". Infatti, man mano che aggiungi strati, le reti neurali hanno sempre più difficoltà a convergere.

Si è scoperto che le reti neurali profonde con molti strati (20, 50, persino 100 oggi) possono funzionare molto bene, a condizione che vengano utilizzati un paio di trucchi matematici per farle convergere. La scoperta di questi semplici trucchi è uno dei motivi della rinascita del deep learning negli anni 2010.

Attivazione ReLU

relu.png

La funzione di attivazione sigmoidea è in realtà piuttosto problematica nelle reti profonde. Comprime tutti i valori tra 0 e 1 e, se lo fai ripetutamente, gli output dei neuroni e i relativi gradienti possono scomparire completamente. È stato menzionato per motivi storici, ma le reti moderne utilizzano la ReLU (Rectified Linear Unit), che ha questo aspetto:

1abce89f7143a69c.png

La funzione ReLU, invece, ha una derivata pari a 1, almeno sul lato destro. Con l'attivazione ReLU, anche se i gradienti provenienti da alcuni neuroni possono essere pari a zero, ce ne saranno sempre altri che forniscono un gradiente diverso da zero e l'addestramento può continuare a un buon ritmo.

Un ottimizzatore migliore

In spazi con un numero molto elevato di dimensioni come questo, in cui abbiamo circa 10.000 pesi e bias, i "punti di sella" sono frequenti. Questi sono punti che non sono minimi locali, ma in cui il gradiente è comunque zero e l'ottimizzatore di discesa del gradiente rimane bloccato. TensorFlow dispone di una gamma completa di ottimizzatori disponibili, inclusi alcuni che funzionano con una certa inerzia e superano in sicurezza i punti di sella.

Inizializzazioni casuali

L'arte di inizializzare i bias dei pesi prima dell'addestramento è un campo di ricerca a sé stante, con numerosi articoli pubblicati sull'argomento. Puoi dare un'occhiata a tutti gli inizializzatori disponibili in Keras qui. Fortunatamente, Keras esegue l'operazione giusta per impostazione predefinita e utilizza l'inizializzatore 'glorot_uniform', che è il migliore in quasi tutti i casi.

Non devi fare nulla, perché Keras fa già la cosa giusta.

NaN ???

La formula dell'entropia incrociata include un logaritmo e log(0) non è un numero (NaN, un errore numerico, se preferisci). L'input dell'entropia incrociata può essere 0? L'input proviene dalla funzione softmax, che è essenzialmente un'esponenziale e un'esponenziale non è mai zero. Quindi siamo al sicuro.

Davvero? Nel bellissimo mondo della matematica, saremmo al sicuro, ma nel mondo dei computer, exp(-150), rappresentato in formato float32, è ZERO e l'entropia incrociata si arresta in modo anomalo.

Fortunatamente, non devi fare nulla neanche qui, poiché Keras si occupa di questo e calcola la funzione softmax seguita dall'entropia incrociata in modo particolarmente attento per garantire la stabilità numerica ed evitare i temuti NaN.

Operazione riuscita?

e1521c9dd936d9bc.png

Ora dovresti raggiungere un'accuratezza del 97%. L'obiettivo di questo workshop è superare di gran lunga il 99%, quindi continuiamo.

Se non riesci ad andare avanti, ecco la soluzione a questo punto:

c3df49e90e5a654f.png keras_02_mnist_dense.ipynb

8. Decadimento del tasso di apprendimento

Forse possiamo provare ad allenarci più velocemente? Il tasso di apprendimento predefinito nell'ottimizzatore Adam è 0,001. Proviamo ad aumentarlo.

Andare più veloce non sembra aiutare molto e tutto questo rumore?

d4fd66346d7c480e.png

Le curve di addestramento sono molto rumorose e guarda entrambe le curve di convalida: saltano su e giù. Ciò significa che stiamo andando troppo veloci. Potremmo tornare alla velocità precedente, ma c'è un modo migliore.

slow down.png

La soluzione migliore è iniziare rapidamente e ridurre il tasso di apprendimento in modo esponenziale. In Keras, puoi farlo con il callback tf.keras.callbacks.LearningRateScheduler.

Codice utile per il copia e incolla:

# lr decay function
def lr_decay(epoch):
  return 0.01 * math.pow(0.6, epoch)

# lr schedule callback
lr_decay_callback = tf.keras.callbacks.LearningRateScheduler(lr_decay, verbose=True)

# important to see what you are doing
plot_learning_rate(lr_decay, EPOCHS)

Non dimenticare di utilizzare il lr_decay_callback che hai creato. Aggiungilo all'elenco dei callback in model.fit:

model.fit(...,  callbacks=[plot_training, lr_decay_callback])

L'impatto di questa piccola modifica è spettacolare. Noti che la maggior parte del rumore è scomparsa e che l'accuratezza del test è ora superiore al 98% in modo costante.

8c1ae90976c4a0c1.png

9. Dropout, overfitting

Il modello sembra convergere bene ora. Cerchiamo di andare ancora più a fondo.

Ti è stato utile?

e36c09a3088104c6.png

Non proprio, l'accuratezza è ancora bloccata al 98% e guarda la perdita di convalida. Sta salendo. L'algoritmo di apprendimento funziona solo sui dati di addestramento e ottimizza la perdita di addestramento di conseguenza. Non vede mai i dati di convalida, quindi non sorprende che dopo un po' il suo lavoro non abbia più effetto sulla perdita di convalida, che smette di diminuire e a volte addirittura rimbalza verso l'alto.

Ciò non influisce immediatamente sulle capacità di riconoscimento nel mondo reale del modello, ma impedisce di eseguire molte iterazioni ed è generalmente un segno che l'addestramento non ha più un effetto positivo.

dropout.png

Questa disconnessione è solitamente chiamata "overfitting" e, quando la noti, puoi provare ad applicare una tecnica di regolarizzazione chiamata "dropout". La tecnica di dropout disattiva neuroni casuali a ogni iterazione di addestramento.

Ha funzionato?

43fd33801264743f.png

Il rumore ricompare (come previsto, dato il funzionamento dell'interruzione). La perdita di convalida non sembra più aumentare, ma è complessivamente superiore rispetto a quando non viene utilizzato il dropout. e l'accuratezza della convalida è diminuita leggermente. Si tratta di un risultato piuttosto deludente.

Sembra che il dropout non sia la soluzione corretta o forse l'overfitting è un concetto più complesso e alcune delle sue cause non sono risolvibili con un "dropout".

Che cos'è l'"overfitting"? L'overfitting si verifica quando una rete neurale apprende "male", in un modo che funziona per gli esempi di addestramento, ma non altrettanto bene sui dati reali. Esistono tecniche di regolarizzazione come il dropout che possono forzare l'apprendimento in modo migliore, ma l'overfitting ha anche radici più profonde.

overfitting.png

L'overfitting di base si verifica quando una rete neurale ha troppi gradi di libertà per il problema in questione. Immagina di avere così tanti neuroni che la rete può memorizzare tutte le immagini di addestramento e riconoscerle tramite il riconoscimento di pattern. Non funzionerebbe affatto con i dati reali. Una rete neurale deve essere in qualche modo vincolata in modo da essere costretta a generalizzare ciò che apprende durante l'addestramento.

Se hai pochissimi dati di addestramento, anche una piccola rete può impararli a memoria e vedrai un "overfitting". In generale, per addestrare le reti neurali sono sempre necessari molti dati.

Infine, se hai fatto tutto a regola d'arte, hai sperimentato diverse dimensioni della rete per assicurarti che i suoi gradi di libertà siano vincolati, hai applicato il dropout e hai eseguito l'addestramento su molti dati, potresti comunque essere bloccato a un livello di prestazioni che nulla sembra in grado di migliorare. Ciò significa che la tua rete neurale, nella sua forma attuale, non è in grado di estrarre ulteriori informazioni dai tuoi dati, come nel nostro caso.

Ricordi come utilizziamo le nostre immagini, compresse in un unico vettore? È stata una pessima idea. Le cifre scritte a mano sono composte da forme e abbiamo scartato le informazioni sulle forme quando abbiamo appiattito i pixel. Tuttavia, esiste un tipo di rete neurale che può sfruttare le informazioni sulla forma: le reti convoluzionali. Proviamoli.

Se non riesci ad andare avanti, ecco la soluzione a questo punto:

c3df49e90e5a654f.png keras_03_mnist_dense_lrdecay_dropout.ipynb

10. [INFO] convolutional networks

In breve

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

convolutional.gif

Illustrazione: filtro di un'immagine con due filtri successivi composti ciascuno da 48 pesi apprendibili di 4x4x3.

Ecco come appare una semplice rete neurale convoluzionale in Keras:

model = tf.keras.Sequential([
    tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(kernel_size=3, filters=12, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=24, strides=2, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=32, strides=2, activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

688858c21e3beff2.png

In un livello di una rete convoluzionale, un "neurone" esegue una somma ponderata dei pixel appena sopra, 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 sull'intera immagine utilizzando gli stessi pesi. Ricorda che negli strati densi ogni neurone aveva i propri pesi. Qui, una singola "patch" di pesi scorre sull'immagine in entrambe le direzioni (una "convoluzione"). L'output ha tanti valori quanti sono i pixel nell'immagine (anche se è necessario un po' di padding ai bordi). Si tratta di un'operazione di filtraggio. Nell'illustrazione precedente, utilizza un filtro di 4x4x3=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 generata una nuova serie di output del filtro. Chiamiamolo "canale " di output per analogia con i canali R, G e B nell'immagine di input.

Screen Shot 2016-07-29 at 16.02.37.png

I due (o più) insiemi di pesi possono essere riassunti in un unico tensore aggiungendo una nuova dimensione. In questo modo otteniamo la forma generica del tensore dei pesi per un livello convoluzionale. Poiché il numero di canali di input e output sono parametri, possiamo iniziare ad accumulare e concatenare i livelli convoluzionali.

d1b557707bcd1cb9.png

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

Convoluzioni con passo, max pooling

Eseguendo le convoluzioni con uno stride di 2 o 3, possiamo anche ridurre il cubo di dati risultante nelle sue dimensioni orizzontali. Esistono due modi comuni per farlo:

  • Convoluzione con passo: un filtro scorrevole come sopra, ma con un passo > 1
  • Max pooling: una finestra scorrevole che applica l'operazione MAX (in genere su patch 2x2, ripetute ogni 2 pixel)

2b2d4263bb8470b.gif

Illustrazione: se si sposta la finestra di calcolo di 3 pixel, si ottengono meno valori di output. Le convoluzioni con passo o il max pooling (massimo in una finestra 2x2 che scorre con un passo di 2) sono un modo per ridurre il cubo di dati nelle dimensioni orizzontali.

Il livello finale

Dopo l'ultimo livello convoluzionale, i dati si trovano sotto forma di "cubo". Esistono due modi per alimentarlo attraverso l'ultimo strato denso.

Il primo consiste nell'appiattire il cubo di dati in un vettore e poi inserirlo nel livello softmax. A volte, puoi persino aggiungere un livello denso prima del livello softmax. Questa operazione tende a essere costosa in termini di numero di pesi. Un livello denso alla fine di una rete convoluzionale può contenere più della metà dei pesi dell'intera rete neurale.

Anziché utilizzare un costoso livello denso, possiamo anche dividere il "cubo" di dati in entrata in tante parti quante sono le classi, calcolare la media dei loro valori e inserirli in una funzione di attivazione softmax. Questo modo di creare l'intestazione di classificazione costa 0 pesi. In Keras esiste un livello per questo: tf.keras.layers.GlobalAveragePooling2D().

a44aa392c7b0e32a.png

Passa alla sezione successiva per creare una rete convoluzionale per il problema in questione.

11. Una rete convoluzionale

Creiamo una rete convoluzionale per il riconoscimento delle cifre scritte a mano. Utilizzeremo tre livelli convoluzionali nella parte superiore, il nostro tradizionale livello di lettura softmax nella parte inferiore e li collegheremo con un livello completamente connesso:

e1a214a170957da1.png

Nota che il secondo e il terzo livello convoluzionale hanno uno stride di due, il che spiega perché il numero di valori di output scende da 28x28 a 14x14 e poi a 7x7.

Scriviamo il codice Keras.

È necessaria un'attenzione particolare prima del primo livello convoluzionale. Infatti, si aspetta un "cubo" di dati 3D, ma il nostro set di dati è stato finora configurato per layer densi e tutti i pixel delle immagini sono appiattiti in un vettore. Dobbiamo rimodellarli in immagini 28x28x1 (1 canale per le immagini in scala di grigi):

tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1))

Puoi utilizzare questa linea anziché il livello tf.keras.layers.Input che avevi finora.

In Keras, la sintassi per un livello convoluzionale attivato da "relu" è:

140f80336b0e653b.png

tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu')

Per una convoluzione con stride, scriveresti:

tf.keras.layers.Conv2D(kernel_size=6, filters=24, padding='same', activation='relu', strides=2)

Per appiattire un cubo di dati in un vettore in modo che possa essere utilizzato da un livello denso:

tf.keras.layers.Flatten()

Per il livello denso, la sintassi è rimasta invariata:

tf.keras.layers.Dense(200, activation='relu')

Il tuo modello ha superato la barriera di accuratezza del 99%? Ci sei quasi… ma guarda la curva di perdita di convalida. Tutto questo ti è familiare?

ecc5972814885226.png

Controlla anche le previsioni. Per la prima volta, dovresti vedere che la maggior parte delle 10.000 cifre di test ora vengono riconosciute correttamente. Rimangono solo circa 4 righe e mezzo di rilevamenti errati (circa 110 cifre su 10.000)

37e4cbd3f390c89e.png

Se non riesci ad andare avanti, ecco la soluzione a questo punto:

c3df49e90e5a654f.png keras_04_mnist_convolutional.ipynb

12. Dropout di nuovo

L'addestramento precedente mostra chiari segni di overfitting (e non raggiunge comunque il 99% di accuratezza). Vuoi provare di nuovo a uscire?

Com'è andata questa volta?

63e0cc982cee2030.png

Sembra che questa volta l'abbandono sia andato a buon fine. La perdita di convalida non aumenta più e l'accuratezza finale dovrebbe essere superiore al 99%. Complimenti!

La prima volta che abbiamo provato ad applicare il dropout, abbiamo pensato di avere un problema di overfitting, quando in realtà il problema risiedeva nell'architettura della rete neurale. Non potevamo andare oltre senza i livelli convoluzionali e il dropout non poteva fare nulla al riguardo.

Questa volta, sembra che l'overfitting sia stata la causa del problema e il dropout abbia effettivamente aiutato. Ricorda che ci sono molti fattori che possono causare una discrepanza tra le curve di perdita di addestramento e convalida, con un aumento della perdita di convalida. L'overfitting (troppi gradi di libertà, utilizzati male dalla rete) è solo uno di questi. Se il set di dati è troppo piccolo o l'architettura della rete neurale non è adeguata, potresti notare un comportamento simile nelle curve di perdita, ma il dropout non sarà utile.

13. Normalizzazione batch

oggEbikl2I6_sOo7FlaX2KLdNeaYhJnVSS8GyG8FHXid75PVJX73CRiOynwpMZpLZq6_xAy69wgyez5T-ZlpuC2XSlcmjk7oVcOzefKKTFhTEoLO3kljz2RDyKcaFtHvtTey-I4VpQ

Infine, proviamo ad aggiungere la normalizzazione batch.

Questa è la teoria, in pratica, ricorda solo un paio di regole:

Per ora seguiamo le regole e aggiungiamo un livello di normalizzazione batch a ogni livello della rete neurale, tranne l'ultimo. Non aggiungerlo all'ultimo livello "softmax". Non sarebbe utile.

# Modify each layer: remove the activation from the layer itself.
# Set use_bias=False since batch norm will play the role of biases.
tf.keras.layers.Conv2D(..., use_bias=False),
# Batch norm goes between the layer and its activation.
# The scale factor can be turned off for Relu activation.
tf.keras.layers.BatchNormalization(scale=False, center=True),
# Finish with the activation.
tf.keras.layers.Activation('relu'),

Qual è l'accuratezza attuale?

ea48193334c565a1.png

Con qualche modifica (BATCH_SIZE=64, parametro di decadimento del tasso di apprendimento 0,666, tasso di dropout sul livello densità 0,3) e un po' di fortuna, puoi raggiungere il 99,5%. Gli aggiustamenti del tasso di apprendimento e del dropout sono stati eseguiti seguendo le "best practice" per l'utilizzo della normalizzazione batch:

  • La normalizzazione batch aiuta le reti neurali a convergere e di solito consente di eseguire l'addestramento più rapidamente.
  • Batch norm è un regolarizzatore. In genere puoi ridurre la quantità di dropout che utilizzi o anche non utilizzarlo affatto.

Il notebook della soluzione ha una sessione di addestramento del 99,5%:

c3df49e90e5a654f.png keras_05_mnist_batch_norm.ipynb

14. Addestramento nel cloud su hardware potente: AI Platform

d7d0282e687bdad8.png

Troverai una versione del codice pronta per il cloud nella cartella mlengine su GitHub, insieme alle istruzioni per eseguirlo su Google Cloud AI Platform. Prima di poter eseguire questa parte, devi creare un account Google Cloud e attivare la fatturazione. Le risorse necessarie per completare il lab dovrebbero costare meno di un paio di dollari (supponendo 1 ora di tempo di addestramento su una GPU). Per preparare il tuo account:

  1. Crea un progetto Google Cloud ( http://cloud.google.com/console).
  2. Abilita la fatturazione.
  3. Installa gli strumenti a riga di comando GCP ( GCP SDK qui).
  4. Crea un bucket Google Cloud Storage (inseriscilo nella regione us-central1). Verrà utilizzato per preparare il codice di addestramento e archiviare il modello addestrato.
  5. Abilita le API necessarie e richiedi le quote necessarie (esegui il comando di addestramento una volta e dovresti ricevere messaggi di errore che ti indicano cosa abilitare).

15. Complimenti!

Hai creato la tua prima rete neurale e l'hai addestrata fino a raggiungere una precisione del 99%. Le tecniche apprese durante il percorso non sono specifiche del set di dati MNIST, ma sono ampiamente utilizzate quando si lavora con le reti neurali. Come regalo di addio, ecco la scheda "riassunto" del lab, in versione cartoon. Puoi utilizzarlo per ricordare ciò che hai imparato:

cliffs notes tensorflow lab.png

Passaggi successivi

  • Dopo le reti completamente connesse e convoluzionali, dovresti dare un'occhiata alle reti neurali ricorrenti.
  • Per eseguire l'addestramento o l'inferenza nel cloud su un'infrastruttura distribuita, Google Cloud fornisce AI Platform.
  • Infine, ci piacerebbe ricevere un tuo feedback. Comunicaci se noti qualcosa di strano in questo lab o se ritieni che debba essere migliorato. Gestiamo i feedback tramite le segnalazioni di GitHub [link al feedback].

HR.png

Martin Görner ID small.jpgL'autore: Martin GörnerTwitter: @martin_gorner

Copyright di tutte le immagini di cartoni animati in questo lab: alexpokusay / 123RF stock photos