Deep Learning With Python-1-237 It
Deep Learning With Python-1-237 It
François Chollet
MANNINA
Apprendimento profondo con Python
Apprendiment
o profondo
con Python
SECONDA EDIZIONE
FRANÇOIS CHOLLET
MANIFEST
AZIONE
ISOLA DI SHELTER
Per informazioni e ordini online di questo e altri libri di Manning, visitare il sito
www.manning.com. L'editore offre sconti su questo libro se ordinato in quantità. Per ulteriori
informazioni, contattare
Ufficio vendite speciali
Manning Publications Co.
20 Baldwin Road
Casella postale 761
Shelter Island, NY 11964
Email: [email protected]
Molte delle denominazioni utilizzate da produttori e venditori per distinguere i loro prodotti
sono rivendicate come marchi di fabbrica. Nei casi in cui tali denominazioni compaiono nel
libro e Manning Publications era a conoscenza di una rivendicazione di marchio, le
denominazioni sono state stampate in maiuscolo o tutto maiuscolo.
Riconoscendo l'importanza di preservare ciò che è stato scritto, la politica di Manning prevede che
i libri che pubblichiamo siano stampati su carta priva di acidi, e facciamo del nostro meglio a
questo scopo.
Riconoscendo anche la nostra responsabilità di preservare le risorse del nostro pianeta, i libri
Manning sono stampati su carta riciclata per almeno il 15% e lavorata senza l'uso di cloro
elementare.
L'autore e l'editore hanno fatto ogni sforzo per assicurare che le informazioni contenute in
questo libro fossero corrette al momento della stampa. L'autore e l'editore non si assumono e
declinano ogni responsabilità nei confronti di terzi per eventuali perdite, danni o disservizi
causati da errori od omissioni, siano essi dovuti a negligenza, incidente o qualsiasi altra causa, o
all'uso delle informazioni qui contenute.
ISBN: 9781617296864
Stampato negli Stati Uniti d'America
A mio figlio Sylvain: spero che un giorno leggerai questo libro!
contenuti
sintetici
1 Cos'è il deep learning? 1
2 Gli elementi matematici delle reti
neurali 26
3 Introduzione a Keras e TensorFlow 68
4 Iniziare con le reti neurali: Classificazione e
regressione 95
5 Fondamenti di apprendimento automatico 121
6 Il flusso di lavoro universale dell'apprendimento automatico
153
7 Lavorare con Keras: Un'immersione profonda 172
8 Introduzione all'apprendimento
profondo per la visione
artificiale 201
9 Apprendimento profondo avanzato per la visione artificiale
238
10 Apprendimento profondo per le serie temporali 280
11 Apprendimento profondo per il testo 309
12 Apprendimento profondo generativo 364
13 Migliori pratiche per il mondo reale 412
14 Conclusioni 431
vii
contenuti
prefazione xvii
ringraziamenti xix
questo libro xx
l'autore xxiii
sull'illustrazione di copertina xxiv
reti neurali14
Metodi kernel 14 ■
learning24 Durerà? 24
Mondo reale
esempi di tensori di dati 35 Dati ■ 35 Timeseriesdati o
■
Il gradiente 51 ■
Discesa stocastica del gradiente 52 ■
3 modello66
Colaboratory
73
3.5 Primi passi con TensorFlow 75
CONTENUT xi
Tensori e variabili Icostanti 76 Operazioni con i tensori: Fare
■
4
■
addestramento93
Avvolte 113
4.3 Prevedere i prezzi delle case: Un esempio di regressione 113
Il set di dati sui prezzi delle abitazioni di Boston 114 ■
valutazione 137
5.3 Migliorare l'adattamento del modello 138
CONTENUT xiii
Regolazione dei Iparametri chiave della discesa del
gradiente138 Sfruttamento di migliori
■
successo 160
6.2 Sviluppare un modello 161
Preparare i dati 161 Scegliere un protocollo di
■
modello165
6.3 Distribuire il modello 165
Spiegare il proprio lavoro agli stakeholder e definire le
aspettative 165 Spedire un modello di inferenza 166 ■
modello 170
lavoro185
7.3 Utilizzo dei cicli di formazione e valutazione integrati 185
Scrivere le proprie metriche186 Usare i callback 187 ■
personalizzato 198
strato 316
11.3 Due approcci per rappresentare gruppi di parole:
Insiemi e sequenze 319
Preparazione dei dati delle recensioni di film IMDB 320 ■
CONTENUT xvii
I
Elaborazione delle parole come insieme: L'approccio bag-of-
words 322 Elaborare le parole come sequenza: L'approccio del
■
Autoencoder variazionali393
Implementazione di un VAE con Keras
396 Conclusione 401
■
avversaria408 Conclusione410
■
14 Conclusioni 431
14.1 Concetti chiave in rassegna 432
Vari approcci all'IA 432 Cosa rende speciale il deep learningnel
■
automatico ■
Le principali architetture di rete 436 Lo ■
indice 469
CONTENUT xxi
I
prefazione
Se avete preso in mano questo libro, probabilmente siete consapevoli degli
straordinari progressi che il deep learning ha rappresentato per il campo
dell'intelligenza artificiale nel recente passato. Siamo passati da una visione
computerizzata e da un'elaborazione del linguaggio naturale quasi inutilizzabili a
sistemi altamente performanti implementati su scala nei prodotti che utilizzate ogni
giorno. Le conseguenze di questo improvviso progresso si estendono a quasi tutti i
settori. Stiamo già applicando l'apprendimento profondo a un'incredibile gamma di
problemi importanti in ambiti diversi come l'imaging medico, l'agricoltura, la guida
autonoma, l'istruzione, la prevenzione di disastri e la produzione.
Tuttavia, credo che il deep learning sia ancora agli albori. Finora ha realizzato solo
una piccola parte del suo potenziale. Con il tempo, si farà strada in tutti i problemi in cui
può essere utile, una trasformazione che avverrà nell'arco di diversi decenni.
Per iniziare a distribuire la tecnologia di deep learning a tutti i problemi che
potrebbe risolvere, dobbiamo renderla accessibile al maggior numero possibile di
persone, compresi i non esperti - persone che non sono ricercatori o studenti laureati.
Affinché l'apprendimento profondo raggiunga il suo pieno potenziale, dobbiamo
democratizzarlo radicalmente. Oggi credo che ci troviamo al culmine di una
transizione storica, in cui l'apprendimento profondo sta uscendo dai laboratori
accademici e dai dipartimenti di ricerca e sviluppo delle grandi aziende tecnologiche
per diventare una parte onnipresente della cassetta degli attrezzi di ogni
sviluppatore, non diversamente dalla traiettoria dello sviluppo web alla fine degli
anni Novanta. Oggi quasi tutti possono creare un sito web o un'applicazione web per
la propria azienda o comunità, di un tipo che nel 1998 avrebbe richiesto un piccolo
team di ingegneri specializzati. In un futuro non così lontano, chiunque abbia un'idea
e competenze di base di codifica sarà in grado di costruire applicazioni intelligenti
che imparano dai dati.
xvii
xviii PREFAZI
ONE
Quando ho rilasciato la prima versione del framework di deep learning Keras nel
marzo 2015, la democratizzazione dell'IA non era quello che avevo in mente.
Facevo ricerca sull'apprendimento automatico da diversi anni e avevo costruito
Keras per aiutarmi con i miei esperimenti. Ma dal 2015, centinaia di migliaia di
nuovi arrivati sono entrati nel campo del deep learning; molti di loro hanno scelto
Keras come strumento di elezione. Mentre osservavo decine di persone intelligenti
utilizzare Keras in modi inaspettati e potenti, ho iniziato a preoccuparmi
profondamente dell'accessibilità e della democratizzazione dell'IA. Mi sono reso
conto che più diffondiamo queste tecnologie, più diventano utili e preziose. L'accessibilità
è diventata presto un obiettivo esplicito nello sviluppo di Keras e, in pochi anni, la
comunità di sviluppatori di Keras ha raggiunto risultati fantastici su questo fronte.
Abbiamo messo il deep learning nelle mani di centinaia di migliaia di persone, che a
loro volta lo utilizzano per risolvere problemi che fino a poco tempo fa erano
ritenuti irrisolvibili.
Il libro che avete in mano è un altro passo avanti per rendere il deep learning
accessibile al maggior numero di persone possibile. Keras ha sempre avuto bisogno di
un corso di accompagnamento che coprisse contemporaneamente i fondamenti del deep
learning, le migliori pratiche di deep learning e i modelli di utilizzo di Keras. Nel 2016 e
nel 2017 ho fatto del mio meglio per produrre un corso di questo tipo, che è diventato la
prima edizione di questo libro, pubblicato nel dicembre 2017. È diventato rapidamente
un best seller sull'apprendimento automatico che ha venduto oltre 50.000 copie ed è
stato tradotto in 12 lingue.
Tuttavia, il campo del deep learning avanza rapidamente. Dalla pubblicazione
della prima edizione, si sono verificati molti sviluppi importanti: il rilascio di
TensorFlow 2, la crescente popolarità dell'architettura Transformer e altro ancora.
Così, alla fine del 2019, ho deciso di aggiornare il mio libro. Inizialmente pensavo,
piuttosto ingenuamente, che avrebbe presentato circa il 50% di nuovi contenuti e
che avrebbe finito per essere più o meno della stessa lunghezza della prima
edizione. In pratica, dopo due anni di lavoro, è risultato più lungo di un terzo, con
circa il 75% di contenuti nuovi. Più che un aggiornamento, è un libro
completamente nuovo.
L'ho scritto con l'obiettivo di rendere i concetti alla base del deep learning e la
loro implementazione il più possibile accessibili. Per farlo, non ho dovuto ridurre
nulla al minimo: credo fermamente che non ci siano idee difficili
nell'apprendimento profondo. Spero che questo libro vi sia utile e che vi consenta di
iniziare a costruire applicazioni intelligenti e di risolvere i problemi che vi stanno a
cuore.
Riconoscimenti
Prima di tutto, vorrei ringraziare la comunità di Keras per aver reso possibile questo
libro. Negli ultimi sei anni, Keras è cresciuto fino a contare centinaia di
collaboratori open source e oltre un milione di utenti. I vostri contributi e feedback
hanno trasformato Keras in quello che è oggi.
Su una nota più personale, vorrei ringraziare mia moglie per il suo infinito
supporto durante lo sviluppo di Keras e la stesura di questo libro.
Vorrei anche ringraziare Google per aver sostenuto il progetto Keras. È stato
fantastico vedere Keras adottato come API di alto livello di TensorFlow.
Un'integrazione fluida tra Keras e TensorFlow è di grande beneficio sia per gli utenti di
TensorFlow che per quelli di Keras e rende il deep learning accessibile a tutti.
Voglio ringraziare le persone di Manning che hanno reso possibile questo libro:
l'editore Marjan Bace e tutti i membri del team editoriale e di produzione, tra cui
Michael Stephens, Jennifer Stout, Aleksandar Dragosavljevic´ e molti altri che
hanno lavorato dietro le quinte.
Un sentito ringraziamento va ai revisori tecnici: Billy O'Callaghan, Christian
Weisstanner, Conrad Taylor, Daniela Zapata Riesco, David Jacobs, Edmon Begoli,
Edmund Ronald PhD, Hao Liu, Jared Duncan, Kee Nam, Ken Fricklas, Kjell Jansson,
Milan Šarenac, Nguyen Cao, Nikos Kanakaris, Oliver Korten, Raushan Jha, Sayak Paul,
Sergio Govoni, Shashank Polasa, Todd Cook e Viton Vitanis e tutte le altre persone che
ci hanno inviato un feedback sulla bozza d e l libro.
Dal punto di vista tecnico, un ringraziamento particolare va a Frances
Buontempo, che ha svolto il ruolo di redattore tecnico del libro, e a Karsten
Strøbæk, che ha svolto il ruolo di correttore tecnico del libro.
xix
Informazioni su
questo libro
Questo libro è stato scritto per tutti coloro che desiderano esplorare l'apprendimento
profondo da zero o ampliare la loro comprensione del deep learning. Che siate
ingegneri di machine learning, sviluppatori di software o studenti universitari,
queste pagine vi saranno utili.
Esplorerete l'apprendimento profondo in modo accessibile, iniziando in modo
semplice per poi arrivare a tecniche all'avanguardia. Troverete in questo libro un
equilibrio tra intuizione, teoria e pratica. Evita la notazione matematica, preferendo
spiegare le idee fondamentali dell'apprendimento automatico e dell'apprendimento
profondo attraverso frammenti di codice dettagliati e modelli mentali intuitivi.
Imparerete grazie ad abbondanti esempi di codice che includono ampi commenti,
consigli pratici e semplici spiegazioni di alto livello di tutto ciò che è necessario sapere
per iniziare a utilizzare l'apprendimento profondo per risolvere i problemi più
comuni.
Gli esempi di codice utilizzano il framework di deep learning Python Keras e
TensorFlow 2 c o m e motore numerico. Essi dimostrano le moderne best practice di
Keras e TensorFlow 2 al 2021.
Dopo aver letto questo libro, avrete una solida conoscenza di cosa sia il deep
learning, di quando sia applicabile e di quali siano i suoi limiti. Conoscerete il flusso
di lavoro standard per affrontare e risolvere i problemi di apprendimento automatico
e saprete come affrontare i problemi più comuni. Sarete in grado di utilizzare Keras
per affrontare problemi reali che spaziano dalla computer vision all'elaborazione del
linguaggio naturale: classificazione di immagini, segmentazione di immagini,
previsione di serie temporali, classificazione di testi, traduzione automatica,
generazione di testi e altro ancora.
xx
SU QUESTO LIBRO xxi
libro, porre domande tecniche e ricevere aiuto dall'autore e da altri utenti. Per
accedere al forum, visitare https://livebook.manning.com/#!/book/deep-learning-
with-python-second-edition/discussion. Per saperne di più sui forum di Manning e
sulle regole di condotta, visitare https://livebook.manning.com/#!/discussion.
L'impegno di Manning nei confronti dei lettori è quello di fornire un luogo in cui
possa svolgersi un dialogo significativo tra i singoli lettori e tra i lettori e l'autore.
Non si tratta di un impegno a una partecipazione specifica da parte dell'autore, il cui
contributo al forum rimane volontario (e non retribuito). Vi suggeriamo di provare a
porre all'autore qualche domanda impegnativa, per evitare che il suo interesse si
allontani! Il forum e l'archivio delle discussioni precedenti saranno accessibili dal
sito dell'editore finché il libro sarà in stampa.
sull'autore
FRANÇOIS CHOLLET è il creatore di Keras, uno dei
framework di deep learning più utilizzati. Attualmente è
ingegnere soft ware presso Google, dove dirige il team Keras.
Inoltre, si occupa di ricerca sull'astrazione, sul ragionamento e
su come ottenere una maggiore generalità nell'intelligenza
artificiale.
xxiii
sull'illustrazione di copertina
La figura sulla copertina di Deep Learning with Python, seconda edizione, è intitolata
"Abito di una dama persiana nel 1568". L'illustrazione è tratta da A Collection of the
Dresses of Different Nations, Ancient and Modern (quattro volumi) di Thomas Jefferys,
Londra, pubblicato tra il 1757 e il 1772. Sul frontespizio si legge che si tratta di incisioni
su rame colorate a mano e arricchite con gomma arabica.
Thomas Jefferys (1719-1771) era chiamato "Geografo di Re Giorgio III". È stato un
cartografo inglese che ha rappresentato il principale fornitore di mappe del suo
tempo. Incise e stampò mappe per il governo e altri enti ufficiali e produsse
un'ampia gamma di mappe commerciali e atlanti, soprattutto del Nord America. Il suo
lavoro di cartografo suscitò l'interesse per le usanze locali delle terre da lui rilevate e
mappate, che sono brillantemente esposte in questa collezione. Il fascino per le terre
lontane e i viaggi di piacere erano fenomeni relativamente nuovi alla fine del XVIII
secolo e collezioni come questa erano molto popolari, in quanto introducevano sia il
turista che il viaggiatore in poltrona agli abitanti di altri Paesi.
La diversità dei disegni nei volumi di Jefferys parla in modo vivido dell'unicità e
dell'individualità delle nazioni del mondo circa 200 anni fa. Da allora i codici di
abbigliamento sono cambiati e la diversità per regione e paese, così ricca all'epoca, è
svanita. Oggi è spesso difficile distinguere gli abitanti di un continente da quelli di un
altro. Forse, cercando di vederla in modo ottimistico, abbiamo barattato una diversità
culturale e visiva con una vita personale più varia o una vita intellettuale e tecnica più
varia e interessante.
In un'epoca in cui è difficile distinguere un libro di informatica da un altro, Manning
mette in risalto l'inventiva e l'iniziativa del settore informatico con copertine di libri
basate sulla ricca diversità della vita regionale di due secoli fa, riportata in vita dalle
immagini di Jefferys.
xxiv
Che cos'è
l'apprendimento
profondo?
Negli ultimi anni, l'intelligenza artificiale (AI) è stata oggetto di un intenso clamore
mediatico. L'apprendimento automatico, l'apprendimento profondo e l'IA vengono
citati in innumerevoli articoli, spesso al di fuori di pubblicazioni dedicate alla
tecnologia. Ci viene promesso un futuro di chatbot intelligenti, auto a guida
autonoma e assistenti virtuali, un futuro a volte dipinto in una luce cupa e altre volte
come utopico, in cui i posti di lavoro umani scarseggeranno e la maggior parte delle
attività economiche sarà gestita da robot o agenti AI. Per un futuro o un attuale
praticante dell'apprendimento automatico, è importante essere in grado di riconoscere
il segnale in mezzo al rumore, in modo da poter distinguere gli sviluppi che
cambieranno il mondo dai comunicati stampa esagerati. Il nostro futuro è in gioco, ed
è un futuro in cui avete un ruolo attivo: dopo aver letto questo libro, sarete uno di
coloro che svilupperanno i sistemi di intelligenza artificiale. Affrontiamo quindi
queste domande: Che cosa ha raggiunto finora il deep learning? Quanto è
significativo? Dove siamo diretti? È il caso di credere al clamore?
Questo capitolo fornisce un contesto essenziale sull'intelligenza artificiale,
l'apprendimento automatico e l'apprendimento profondo.
1
2 CAPITOLO 1 Cos'è il deep learning?
Artificiale
intelligenza
Apprend
imento
automat
ico
Profon
do
apprend
imento
Figura 1.1 Intelligenza artificiale,
apprendimento automatico e
apprendimento profondo
Lo studio procederà sulla base della congettura che ogni aspetto dell'apprendimento o di
qualsiasi altra caratteristica dell'intelligenza possa essere descritto in linea di principio
in modo così preciso da poter essere simulato da una macchina. Si cercherà di capire
come far sì che le macchine utilizzino il linguaggio, formino astrazioni e concetti,
risolvano tipi di problemi ora riservati agli esseri umani e migliorino se stesse.
Riteniamo che un gruppo di scienziati accuratamente selezionati possa compiere un
progresso significativo in uno o più di questi problemi lavorando insieme per un'estate.
Alla fine dell'estate, il workshop si concluse senza aver risolto completamente l'enigma
che si era proposto di indagare. Ciononostante, vi parteciparono molte persone che
sarebbero diventate pionieri del settore e mise in moto una rivoluzione intellettuale che
dura ancora oggi.
In sintesi, l'IA può essere descritta come lo sforzo di automatizzare i compiti intellettuali
normalmente svolti dagli esseri umani. In quanto tale, l'IA è un campo generale che
comprende l'apprendimento automatico e l'apprendimento profondo, ma che include
anche molti altri approcci che potrebbero non comportare alcun apprendimento. Si
consideri che fino agli anni '80, la maggior parte dei libri di testo sull'IA non
Intelligenza artificiale, apprendimento automatico e 3
apprendimento
menzionava affatto il termineprofondo
"apprendimento".
4 CAPITOLO 1 Cos'è il deep learning?
tutti! I primi programmi di scacchi, per esempio, prevedevano solo regole codificate da
professionisti e non si qualificavano come apprendimento automatico. In realtà, per
molto tempo, la maggior parte degli esperti ha creduto che l'intelligenza artificiale di
livello umano potesse essere raggiunta facendo sì che i programmatori realizzassero a
mano un insieme sufficientemente ampio di regole esplicite per manipolare la
conoscenza memorizzata in database espliciti. Questo approccio è noto come IA
simbolica. È stato il paradigma dominante nell'IA dagli anni Cinquanta alla fine degli
anni Ottanta e ha raggiunto la massima popolarità durante il boom dei sistemi esperti
degli anni Ottanta.
Sebbene l'IA simbolica si sia dimostrata adatta a risolvere problemi logici e ben
definiti, come il gioco degli scacchi, si è rivelata intrattabile nel trovare regole esplicite
per risolvere problemi più complessi e confusi, come la classificazione delle immagini,
il riconoscimento del parlato o la traduzione del linguaggio naturale. Un nuovo
approccio è nato per prendere il posto dell'IA simbolica: l'apprendimento automatico.
Il Motore Analitico non ha alcuna pretesa di dare origine a qualcosa. Può fare tutto
ciò che noi sappiamo ordinargli di fare.
disponibile ciò che già conosciamo.
Anche con 178 anni di prospettiva storica, l'osservazione di Lady Lovelace rimane
sorprendente. Un computer di uso generale potrebbe "dare origine" a qualcosa, o
sarebbe sempre destinato a eseguire in modo noioso i processi che gli esseri umani
comprendono appieno? Potrebbe mai essere capace di un pensiero originale? Potrebbe
imparare dall'esperienza? Potrebbe mostrare creatività?
La sua osservazione fu in seguito citata dal pioniere dell'IA Alan Turing come
"l'obiettivo di Lady Lovelace" nel suo storico articolo del 1950 "Computing Machinery
and Intelligence",1 che introdusse il test di Turing e i concetti chiave che avrebbero dato
forma all'IA.2 Turing
1
A.M. Turing, "Macchine da calcolo e intelligenza", Mind 59, no. 236 (1950): 433-460.
Intelligenza artificiale, apprendimento automatico e 5
2 Sebbene il test di Turingapprendimento profondo
sia stato talvolta interpretato come un test letterale - un obiettivo che il campo dell'IA
dovrebbe prefiggersi di raggiungere - Turing lo intendeva semplicemente come un dispositivo concettuale in
una discussione filosofica sulla natura della cognizione.
6 CAPITOLO 1 Cos'è il deep learning?
Dati Programmazio
Risposte
sulle ne classica
rego
le
Risp Apprend
Regole Figura 1.2 Apprendimento
oste ai imento
automat
automatico: un nuovo
dati ico paradigma di programmazione
Sebbene l'apprendimento automatico abbia iniziato a fiorire solo negli anni '90, è
diventato rapidamente il sottocampo più popolare e di maggior successo dell'IA, una
tendenza guidata dalla disponibilità di hardware più veloce e di set di dati più grandi.
L'apprendimento automatico è correlato alla statistica matematica, ma si differenzia
dalla statistica per diversi aspetti importanti, nello stesso senso in cui la medicina è
correlata alla chimica ma non può essere ridotta a quest'ultima, poiché la medicina si
occupa di sistemi distinti con proprietà distinte. A differenza della statistica,
l'apprendimento automatico tende a trattare insiemi di dati grandi e complessi (come un
insieme di milioni di immagini, ciascuna composta da decine di migliaia di pixel) per i
quali l'analisi statistica classica, come l'analisi bayesiana, non sarebbe praticabile. Di
conseguenza, l'apprendimento automatico, e in particolare l'apprendimento profondo,
presenta relativamente poca teoria matematica, forse troppo poca, ed è
fondamentalmente una disciplina ingegneristica. A differenza della fisica teorica o della
matematica, l'apprendimento automatico è un campo molto pratico, guidato da risultati
empirici e profondamente dipendente dai progressi del software e dell'hardware.
Per definire il deep learning e capire la differenza tra l'apprendimento profondo e altri
approcci di apprendimento automatico, è necessario innanzitutto avere un'idea di
cosa fanno gli algoritmi di apprendimento automatico. Abbiamo appena affermato
che l'apprendimento automatico scopre le regole per l'esecuzione di un
8 CAPITOLO 1 Cos'è il deep learning?
attività di elaborazione dei dati, con esempi di ciò che ci si aspetta. Quindi, per fare
apprendimento automatico, abbiamo bisogno di tre cose:
◾ Punti di dati in ingresso: ad esempio, se il compito è il riconoscimento
vocale, questi punti di dati possono essere file audio di persone che parlano.
Se il compito è il tagging di immagini, potrebbero essere immagini.
◾ Esempi di output atteso - In un compito di riconoscimento vocale, questi
potrebbero essere trascrizioni generate dall'uomo di file audio. In un compito di
immagine, gli output attesi potrebbero essere tag come "cane", "gatto" e così via.
◾ Un modo per misurare se l'algoritmo sta facendo un buon lavoro - È necessario
per determinare la distanza tra l'uscita attuale dell'algoritmo e l'uscita prevista. La
misurazione viene utilizzata come segnale di feedback per regolare il
funzionamento dell'algoritmo. Questa fase di regolazione si chiama
apprendimento.
Un modello di apprendimento automatico trasforma i dati in ingresso in output
significativi, un processo che viene "appreso" grazie all'esposizione a esempi noti di
input e output. Il problema centrale dell'apprendimento automatico e
dell'apprendimento profondo è quindi quello di trasformare i dati in modo
significativo: in altre parole, imparare rappresentazioni utili dei dati in ingresso,
rappresentazioni che ci avvicinino all'output previsto.
Prima di andare avanti: cos'è una rappresentazione? Si tratta di un modo diverso di
guardare i dati, di rappresentarli o di codificarli. Per esempio, un'immagine a colori
può essere codificata nel formato RGB (rosso-verde-blu) o nel formato HSV (tinta-
saturazione-valore): sono due rappresentazioni diverse degli stessi dati. Alcuni
compiti che possono essere difficili con una rappresentazione possono diventare
facili con un'altra. Per esempio, il compito "selezionare tutti i pixel rossi
nell'immagine" è più semplice nel formato RGB, mentre "rendere l'immagine meno
satura" è più semplice nel formato HSV. I modelli di apprendimento automatico si
occupano di trovare rappresentazioni appropriate per i loro dati di ingresso,
trasformazioni dei dati che li rendono più adatti al compito da svolgere.
Rendiamo questo concetto concreto. Si considerino un asse y
x, un asse y e alcuni punti rappresentati dalle loro coordinate
nel sistema (x, y), come mostrato nella figura 1.3.
Come si può vedere, abbiamo alcuni punti bianchi e alcuni
punti neri. Supponiamo di voler sviluppare un algoritmo in
grado di prendere le coordinate (x, y) di un punto e di indicare
se è probabile che quel punto sia nero o bianco. In questo caso,
x
◾ Gli input sono le coordinate dei nostri punti.
◾ I risultati attesi sono i colori dei nostri punti.
Figura 1.3 Alcuni
◾ Un modo per misurare se il nostro algoritmo sta dati di esempio
facendo un buon lavoro potrebbe essere, ad esempio, la
percentuale di punti classificati correttamente.
Abbiamo bisogno di una nuova rappresentazione dei dati che separi in modo netto i
punti bianchi da quelli neri. Una trasformazione che potremmo utilizzare, tra le
tante possibili, è una modifica delle coordinate, illustrata nella figura 1.4.
Intelligenza artificiale, apprendimento automatico e 9
apprendimento profondo
1: Dati grezzi 2: Variazione di 3: Migliore rappresentazione
y
y coordinate y
x x
In questo nuovo sistema di coordinate, si può dire che le coordinate dei nostri punti
sono una nuova rappresentazione dei nostri dati. Ed è una buona rappresentazione!
Con questa rappresentazione, il problema della classificazione bianco/nero può
essere espresso come una semplice regola: "I punti neri sono tali che x > 0" o "I
punti bianchi sono tali che x < 0". Questa nuova rappresentazione, combinata con
questa semplice regola, risolve perfettamente il problema della classificazione.
In questo caso abbiamo definito il cambio di coordinate a mano: abbiamo usato
la nostra intelligenza umana per trovare una rappresentazione appropriata dei dati.
Questo va bene per un problema così semplice, ma si potrebbe fare lo stesso se il
compito fosse quello di clas- sificare immagini di cifre scritte a mano? Potreste scrivere
trasformazioni di immagine esplicite ed eseguibili al computer che illuminino la
differenza tra un 6 e un 8, tra un 1 e un 7, in tutti i tipi di grafie diverse?
Questo è possibile fino a un certo punto. Le regole basate su rappresentazioni
delle cifre come il "numero di anelli chiusi" o gli istogrammi verticali e orizzontali
dei pixel possono fare un lavoro decente per distinguere le cifre scritte a mano. Ma
trovare a mano tali rappresentazioni utili è un lavoro duro e, come si può immaginare, il
sistema basato su regole che ne risulta è fragile, un incubo da mantenere. Ogni volta che
ci si imbatte in un nuovo esempio di scrittura che infrange le regole accuratamente
studiate, è necessario aggiungere nuove trasformazioni di dati e nuove regole,
tenendo conto della loro interazione con ogni regola precedente.
Probabilmente starete pensando: se questo processo è così doloroso, potremmo
automatizzarlo? E se provassimo a cercare sistematicamente diverse serie di
rappresentazioni dei dati generate automaticamente e regole basate su di esse,
identificando quelle valide utilizzando come feedback la percentuale di cifre
classificate correttamente in un certo set di dati di sviluppo? In questo caso si
tratterebbe di apprendimento automatico. L'apprendimento, nel contesto
dell'apprendimento automatico, descrive un processo di ricerca automatica di
trasformazioni dei dati che producono rappresentazioni utili di alcuni dati, guidate
da alcuni segnali-rappresentazioni di feedback che si prestano a regole più semplici
per risolvere il compito in questione.
Queste trasformazioni possono essere cambiamenti di coordinate (come nel
nostro esempio di classificazione delle coordinate 2D), o prendere un istogramma di
pixel e contare i cicli (come nel nostro esempio di classificazione delle cifre), ma
possono anche essere proiezioni lineari, traslazioni, operazioni non lineari (come
"selezionare tutti i punti tali che x > 0") e così via.
10 CAPITOLO 1 Cos'è il deep learning?
Gli algoritmi di apprendimento automatico di solito non sono creativi nel trovare queste
trasformazioni, ma si limitano a cercare in un insieme predefinito di operazioni,
chiamato spazio delle ipotesi. Per esempio, lo spazio di tutte le possibili trasformazioni
delle coordinate sarebbe il nostro spazio di ipotesi nell'esempio di classificazione delle
coordinate 2D.
Ecco cos'è, in sintesi, l'apprendimento automatico: la ricerca di rappresentazioni
e regole utili su alcuni dati di input, all'interno di uno spazio predefinito di
possibilità, utilizzando la guida di un segnale di feedback. Questa semplice idea
consente di risolvere una gamma straordinariamente ampia di compiti intellettuali,
dal riconoscimento vocale alla guida autonoma.
Ora che avete capito cosa intendiamo per apprendimento, diamo un'occhiata a ciò che rende
speciale sull'apprendimento profondo.
Come si può vedere nella figura 1.6, la rete trasforma l'immagine della cifra in
rappresentazioni sempre più diverse dall'immagine originale e sempre più informative
sul risultato finale. Si può pensare a una rete profonda come a un processo di
distillazione delle informazioni a più stadi, in cui le informazioni passano attraverso
filtri successivi e ne escono sempre più purificate (cioè, utili rispetto a qualche
compito).
Strato 4
rappresentazio
ni (output
finale)
Original 0
e 1
ingres 2
so 3
4
5
6
7
8
9
Strato 1Strato 2Strato 3Strato
4
Figura 1.6 Rappresentazioni dei dati apprese da un modello di classificazione delle cifre
Ecco cos'è tecnicamente l'apprendimento profondo: un modo a più stadi per imparare le
rappresentazioni dei dati. È un'idea semplice ma, come si è visto, meccanismi molto
semplici, sufficientemente scalati, possono finire per sembrare magici.
Ingresso X
Strato
Pesi
Obiettivo: (trasformazione dei
trovare i valori dati)
giusti per
questi pesi Strato
Pesi
(trasformazione dei
dati)
Previsioni
Y' Figura 1.7 Una rete neurale è
parametrizzata dai suoi pesi.
Per controllare qualcosa, bisogna prima essere in grado di osservarla. Per controllare
l'output di una rete neurale, bisogna essere in grado di misurare quanto questo output si
allontana da quello previsto. Questo è il compito della funzione di perdita della rete,
talvolta chiamata anche funzione obiettivo o funzione di costo. La funzione di perdita
prende le previsioni della rete e l'obiettivo vero (ciò che si desiderava che la rete
producesse) e calcola un punteggio di distanza, che indica quanto bene la rete ha fatto su
questo specifico esempio (vedi figura 1.8).
Ingresso X
Strato
Pesi
(trasformazione dei
dati)
Strato
Pesi
(trasformazione dei
dati)
Funzione di
perdita
Figura 1.8 Una funzione
di perdita misura la qualità
Punteggio
di perdita dell'output della rete.
14 CAPITOLO 1 Cos'è il deep learning?
Ingresso X
Strato
Pesi
(trasformazione dei
dati)
Strato
Pesi
(trasformazione dei
dati)
Inizialmente, ai pesi della rete vengono assegnati valori casuali, quindi la rete si
limita a implementare una serie di trasformazioni casuali. Naturalmente, il suo
output è molto lontano da quello che dovrebbe essere idealmente, e il punteggio di
perdita è di conseguenza molto alto. Ma a ogni esempio che la rete elabora, i pesi
vengono aggiustati un po' nella direzione corretta e il punteggio di perdita diminuisce.
Questo è il ciclo di addestramento che, ripetuto un numero sufficiente di volte (in genere
decine di iterazioni su migliaia di esempi), produce valori di pesi che minimizzano la
funzione di perdita. Una rete con una perdita minima è una rete per la quale le uscite sono
il più vicino possibile agli obiettivi: una rete addestrata. Ancora una volta, si tratta
di un meccanismo semplice che, una volta scalato, finisce per sembrare magico.
Stiamo ancora esplorando la portata di ciò che l'apprendimento profondo può fare. Abbiamo
iniziato ad applicarlo con grande successo a un'ampia varietà di problemi che si
pensava fossero impossibili da risolvere solo pochi anni fa: trascrivere
automaticamente le decine di migliaia di antichi manoscritti conservati nell'Archivio
Apostolico Vaticano, rilevare e classificare le malattie delle piante nei campi usando un
semplice smartphone, assistere oncologi o radiologi nell'interpretazione dei dati di
imaging medico, prevedere disastri naturali come inondazioni, uragani o persino
terremoti, e così via. Con ogni pietra miliare, ci avviciniamo a un'era in cui il deep
learning ci assiste in ogni attività e in ogni campo dello sforzo umano: scienza,
medicina, produzione, energia, trasporti, sviluppo di software, agricoltura e persino
creazione artistica.
3
Vladimir Vapnik e Corinna Cortes, "Support-Vector Networks", Machine Learning 20, no. 3 (1995): 273-297.
4
Vladimir Vapnik e Alexey Chervonenkis, "A Note on One Class of Perceptrons", Automation and Remote Con- trol
25 (1964).
Prima del deep learning: Breve storia dell'apprendimento 17
automatico
fase chiamata massimizzazione del margine. Ciò consente al margine di
generalizzare bene a nuovi campioni al di fuori del set di dati di addestramento.
La tecnica di mappare i dati in una rappresentazione ad alta dimensione che semplifica il
problema della classificazione può sembrare buona sulla carta, ma in pratica è spesso
computazionalmente intrattabile. È qui che entra in gioco il trucco del kernel (l'idea
chiave da cui prendono il nome i metodi kernel). Ecco il succo: per trovare buoni
iperpiani decisionali nel nuovo spazio di rappresentazione, non è necessario calcolare
esplicitamente le coordinate dei punti nel nuovo spazio; è sufficiente calcolare la
distanza tra coppie di punti in quello spazio, cosa che può essere fatta in modo efficiente
utilizzando una funzione kernel. Una funzione kernel è un'operazione computabile che
mappa due punti qualsiasi dello spazio iniziale alla distanza tra questi punti nello spazio
di rappresentazione di destinazione, evitando completamente il calcolo esplicito della
nuova rappresentazione. Le funzioni kernel sono tipicamente create a mano piuttosto
che apprese dai dati; n e l caso di un SVM, viene appreso solo l'iperpiano di
separazione.
All'epoca in cui furono sviluppate, le SVM mostravano prestazioni
all'avanguardia su semplici problemi di classificazione ed erano uno dei pochi
metodi di apprendimento automatico supportati da un'ampia teoria e suscettibili di
una seria analisi matematica, che le rendeva ben comprensibili e facilmente
interpretabili. Grazie a queste utili proprietà, le SVM sono diventate estremamente
popolari nel settore per molto tempo.
Ma le SVM si sono dimostrate difficili da scalare su grandi insiemi di dati e non
hanno fornito buoni risultati per problemi percettivi come la classificazione delle
immagini. Essendo un metodo superficiale, l'applicazione di una SVM a problemi
percettivi richiede prima l'estrazione manuale delle rappresentazioni utili (una fase
chiamata feature engineering), che è difficile e fragile. Per esempio, se si vuole usare una
SVM per classificare le cifre scritte a mano, non si può partire dai pixel grezzi; si
devono prima trovare rappresentazioni utili che rendano il problema più trattabile,
come gli istogrammi dei pixel di cui ho parlato prima.
Dati di ingresso
Domand
a
Categoria
Domand Domand
a a
Keras
LuceGBM
XGBoost
PyTorch
TensorFlow
Scikit-learn
Fastai
Caffè
0 10 20 30 40
Numero di competizioni
Profondo Classico
Figura 1.12 Strumenti di apprendimento automatico utilizzati dai migliori team su Kaggle
24 CAPITOLO 1 Cos'è il deep learning?
Non si tratta solo di campioni della competizione. Kaggle conduce anche un sondaggio
annuale tra i professionisti dell'apprendimento automatico e della scienza dei dati di
tutto il mondo. Con decine di migliaia di partecipanti, questo sondaggio è una delle fonti
più affidabili sullo stato del settore. La Figura 1.13 mostra la percentuale di utilizzo di
diversi framework software per l'apprendimento automatico.
Scikit-learn 82.8%
TensorFlow 50.5%
Keras 50.5%
Xgboost 48.4%
PyTorch 30.9%
LuceGBM 26.1%
Caret 14.1%
Catboost 13.7%
Profeta 10%
Fast.ai 7.5%
Tidymodels 7.2%
H2O3 6%
MXNet 2.1%
Altro 3.7%
Nessuno 3.2%
JAX 0.7%
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%
Figura 1.13 Utilizzo di strumenti nel settore dell'apprendimento automatico e della scienza dei dati (Fonte:
www.kaggle.com/ kaggle-survey-2020)
Dal 2016 al 2020, l'intero settore dell'apprendimento automatico e della scienza dei
dati è stato dominato da questi due approcci: deep learning e gradient boosted trees.
In particolare, il gradient boosted trees viene utilizzato per problemi in cui sono
disponibili dati strutturati, mentre il deep learning viene utilizzato per problemi
percettivi come la classificazione delle immagini.
Gli utenti di alberi a gradiente amplificato tendono a usare Scikit-learn,
XGBoost o LightGBM. Nel frattempo, la maggior parte dei praticanti
dell'apprendimento profondo utilizza Keras, spesso in combinazione con
Prima del deep learning: Breve storia dell'apprendimento 25
automatico
il suo framework madre TensorFlow. Il punto in comune di questi strumenti è che sono
tutti librerie Python: Python è di gran lunga il linguaggio più utilizzato per
l'apprendimento automatico e la scienza dei dati.
Queste sono le due tecniche con cui dovreste avere maggiore familiarità per
avere successo nell'apprendimento automatico applicato oggi: gli alberi a gradiente
potenziato, per i problemi di apprendimento superficiale, e l'apprendimento
profondo, per i problemi percettivi. In termini tecnici, ciò significa che dovrete
conoscere Scikit-learn, XGBoost e Keras, le tre librerie che attualmente dominano le
competizioni Kaggle. Con questo libro in mano, siete già un grande passo avanti.
1.3.1 Hardware
Tra il 1990 e il 2010, le CPU di serie sono diventate più veloci di circa 5.000 volte. Di
conseguenza, oggi è possibile eseguire piccoli modelli di deep learning sul proprio
laptop, mentre 25 anni fa sarebbe stato impossibile farlo.
Ma i tipici modelli di deep learning utilizzati nella computer vision o nel
riconoscimento vocale richiedono ordini di grandezza superiori alla potenza di
calcolo che il vostro laptop è in grado di fornire. Nel corso degli anni 2000, aziende
come NVIDIA e AMD hanno investito miliardi di dollari nello sviluppo di chip
veloci e massicciamente paralleli (unità di elaborazione grafica, o GPU) per
alimentare la grafica di videogiochi sempre più fotorealistici - supercomputer economici e
monouso progettati per eseguire il rendering di scene 3D complesse sullo schermo in
tempo reale. Questo investimento si è rivelato vantaggioso per la comunità
scientifica quando, nel 2007, NVIDIA ha lanciato CUDA
26 CAPITOLO 1 Cos'è il deep learning?
(https://developer.nvidia.com/about-cuda), un'interfaccia di programmazione che
consente di gestire i dati in tempo reale.
Perché l'apprendimento 21
profondo? Perché ora?
per la sua linea di GPU. Un piccolo numero di GPU ha iniziato a sostituire cluster
massicci di CPU in varie applicazioni altamente parallelizzabili, a partire dalla
modellazione fisica. Anche le reti neurali profonde, costituite per lo più da molte piccole
moltiplicazioni di matrici, sono altamente parallelizzabili e intorno al 2011 alcuni
ricercatori hanno iniziato a scrivere implementazioni CUDA di reti neurali: Dan
Ciresan6 e Alex Krizhevsky7 sono stati tra i primi.
È successo che il mercato dei giochi ha sovvenzionato il supercalcolo per la
prossima generazione di applicazioni di intelligenza artificiale. A volte, le grandi
cose iniziano come giochi. Oggi, la NVIDIA Titan RTX, una GPU che costava 2.500
dollari alla fine del 2019, è in grado di offrire un picco di 16 teraFLOPS in singola
precisione (16 trilioni di operazioni float32 al secondo). Si tratta di una potenza di
calcolo circa 500 volte superiore a quella del super computer più veloce al mondo del
1990, l'Intel Touchstone Delta. Su una Titan RTX, ci vogliono solo poche ore per
addestrare un modello ImageNet del tipo che avrebbe vinto la gara ILSVRC nel
2012 o 2013. Nel frattempo, le grandi aziende addestrano i modelli di deep learning
su cluster di centinaia di GPU.
Inoltre, l'industria del deep learning si sta spostando oltre le GPU e sta
investendo in chip sempre più specializzati ed efficienti per l'apprendimento
profondo. Nel 2016, in occasione della convention annuale I/O, Google ha rivelato il
progetto Tensor Processing Unit (TPU): un nuovo design di chip sviluppato da zero
per eseguire reti neurali profonde in modo significativamente più veloce e molto più
efficiente dal punto di vista energetico rispetto alle GPU top di gamma. Oggi, nel
2020, la terza iterazione della scheda TPU rappresenta 420 teraFLOPS di potenza di
calcolo. Si tratta di un valore 10.000 volte superiore a quello dell'Intel Touchstone
Delta del 1990.
Queste schede TPU sono progettate per essere assemblate in configurazioni su larga
scala, chiamate "pod". Un pod (1024 schede TPU) raggiunge un picco di 100
petaFLOPS. Per avere un'idea della scala, si tratta di circa il 10% della potenza di
calcolo di picco dell'attuale più grande supercomputer, l'IBM Summit dell'Oak Ridge
National Lab, che consiste in 27.000 GPU NVIDIA e raggiunge un picco di circa 1,1
exaFLOPS.
1.3.2 Dati
L'intelligenza artificiale è talvolta annunciata come la nuova rivoluzione industriale.
Se il deep learning è la macchina a vapore di questa rivoluzione, i dati sono il suo
carbone: la materia prima che alimenta le nostre macchine intelligenti, senza la
quale nulla sarebbe possibile. Per quanto riguarda i dati, oltre al progresso
esponenziale dell'hardware di archiviazione negli ultimi 20 anni (seguendo la legge di
Moore), il cambiamento di gioco è stato l'ascesa di Internet, che ha reso possibile la
raccolta e la distribuzione di serie di dati molto grandi per l'apprendimento automatico. Oggi
le grandi aziende lavorano con dataset di immagini, video e linguaggio naturale che
non avrebbero potuto essere raccolti senza internet. I tag delle immagini generate
dagli utenti su Flickr, ad esempio
6 Si veda "Flexible, High Performance Convolutional Neural Networks for Image Classification", Proceedings of
the 22nd International Joint Conference on Artificial Intelligence (2011), www.ijcai.org/Proceedings/11/Papers/
22 CAPITOLO 1 Cos'è il deep learning?
210.pdf.
7
Si veda "ImageNet Classification with Deep Convolutional Neural Networks", Advances in Neural Information
Pro- cessing Systems 25 (2012), http://mng.bz/2286.
Perché l'apprendimento 23
profondo? Perché ora?
sono stati un tesoro di dati per la computer vision. Lo stesso vale per i video di
YouTube. E Wikipedia è un set di dati fondamentale per l'elaborazione del linguaggio
naturale.
Se c'è un set di dati che ha catalizzato l'ascesa del deep learning, è il set di dati
ImageNet, composto da 1,4 milioni di immagini annotate a mano con 1.000 categorie di
immagini (una categoria per immagine). Ma ciò che rende ImageNet speciale non sono
solo le sue grandi dimensioni, ma anche la competizione annuale ad esso associata.8
Come dimostra Kaggle dal 2010, le competizioni pubbliche sono un modo
eccellente per motivare ricercatori e ingegneri a spingersi oltre. Avere dei
benchmark comuni che i ricercatori competono per battere ha aiutato molto l'ascesa
del deep learning, evidenziandone il successo rispetto agli approcci classici
dell'apprendimento automatico.
1.3.3 Algoritmi
Oltre all'hardware e ai dati, fino alla fine degli anni 2000 mancava un modo
affidabile per addestrare reti neurali molto profonde. Di conseguenza, le reti neurali
erano ancora piuttosto basse, utilizzando solo uno o due strati di rappresentazioni; di
conseguenza, non erano in grado di competere con metodi più raffinati e poco
profondi, come le SVM e le foreste casuali. Il problema principale era quello della
propagazione del gradiente attraverso strati profondi. Il segnale di feedback utilizzato per
addestrare le reti neurali si affievoliva con l'aumentare del numero di strati.
La situazione è cambiata intorno al 2009-2010 con l'avvento di alcuni semplici ma
importanti miglioramenti algoritmici che hanno permesso una migliore propagazione del
gradiente:
◾ Migliori funzioni di attivazione per gli strati neurali
◾ Migliori schemi di inizializzazione dei pesi, a partire dal preaddestramento dei livelli,
poi rapidamente abbandonato.
◾ Migliori schemi di ottimizzazione, come RMSProp e Adam
16
Israel
e
14 UE
12
Cina
10
8
Israel
e
UE
6
Cina
UE
4 ST
ATI
ST UN
2 ST ST ATI ITI
ATI ATI UN
ST UN UN ITI
ST ST ATI ITI ITI
0 ATI ATI UN
UN
2011 UN
2012 2013
ITI 2014 2015 2016 2017
ITI ITI
Figura 1.14 Stima OCSE degli investimenti totali in startup AI (Fonte: http://mng.bz/zGN6)
Nel 2011, poco prima che il deep learning salisse alla ribalta, il totale degli
investimenti di venture capital nell'IA a livello mondiale era inferiore a un miliardo
di dollari, destinato quasi esclusivamente ad applicazioni pratiche di approcci di
apprendimento automatico poco profondi. Nel 2015, la cifra era salita a oltre 5
miliardi di dollari e nel 2017 a un'incredibile cifra di 16 miliardi di dollari. In questi
anni sono state lanciate centinaia di startup che hanno cercato di capitalizzare il
successo del deep learning. Nel frattempo, grandi aziende tecnologiche come
Google, Amazon e Microsoft hanno investito in dipartimenti di ricerca interni per
importi che probabilmente superano il flusso di denaro dei venture capital.
L'apprendimento automatico, e in particolare l'apprendimento profondo, è diventato
centrale nella strategia di produzione di questi giganti tecnologici. Alla fine del 2015, il
CEO di Google Sundar Pichai ha dichiarato: "L'apprendimento automatico è un modo
fondamentale e trasformativo con cui stiamo ripensando il modo in cui siamo
26 CAPITOLO 1 Cos'è il deep learning?
per tutto. Lo stiamo applicando in modo ponderato a tutti i nostri prodotti, che si
tratti di ricerca, annunci, YouTube o Play. Siamo ancora agli inizi, ma ci vedrete applicare
in modo sistematico il machine learning in tutte queste aree". 9
Grazie a questa ondata di investimenti, il numero di persone che lavorano sul deep
learning è passato da poche centinaia a decine di migliaia in meno di 10 anni e i
progressi della ricerca hanno raggiunto un ritmo frenetico.
1.3.6 Durerà?
C'è qualcosa di speciale nelle reti neurali profonde che le rende l'approccio "giusto"
su cui le aziende devono investire e i ricercatori devono accorrere? Oppure il deep
learning è solo una moda che potrebbe non durare? Utilizzeremo ancora le reti
neurali profonde tra 20 anni?
L'apprendimento profondo ha diverse proprietà che giustificano il suo status di
rivoluzione dell'IA ed è destinato a rimanere. Forse tra due decenni non useremo più le
reti neurali, ma qualsiasi cosa useremo erediterà direttamente dal moderno deep
learning e dai suoi concetti fondamentali. Queste importanti proprietà possono
essere suddivise in tre categorie:
◾ Semplicità: l'apprendimento approfondito elimina la necessità di ingegnerizzare
le funzionalità, sostituendo pipeline complesse, fragili e pesanti dal punto di vista
ingegneristico con modelli semplici e addestrabili end-to-end, che in genere
vengono costruiti utilizzando solo cinque o sei operazioni tensoriali diverse.
◾ Scalabilità: l'apprendimento profondo è altamente adattabile alla parallelizzazione su
GPU o TPU, in modo da poter sfruttare appieno la legge di Moore. Inoltre, i
modelli di deep learning vengono addestrati iterando su piccoli lotti di dati, il
che consente di addestrarli su insiemi di dati di dimensioni arbitrarie. (L'unico
collo di bottiglia è la quantità di potenza di calcolo parallelo disponibile che,
grazie alla legge di Moore, è una barriera in rapida evoluzione).
◾ Versatilità e riutilizzabilità - A differenza di molti approcci precedenti
all'apprendimento automatico, i modelli di deep learning possono essere
Perché l'apprendimento 27
profondo? Perché
addestrati su dati aggiuntivi senza ora?
dover ripartire da zero.
I blocchi
matematici
delle reti neurali
26
Un primo sguardo a una rete 27
neurale
Dopo aver letto questo capitolo, avrete una comprensione intuitiva della teoria
matematica che sta alla base del deep learning e sarete pronti per iniziare a
immergervi in Keras e TensorFlow nel capitolo 3.
Le immagini sono codificate come array NumPy e le etichette sono array di cifre, da
0 a 9. Le immagini e le etichette hanno una corrispondenza uno a uno. Le immagini
e le etichette hanno una corrispondenza uno-a-uno.
Esaminiamo i dati di allenamento:
>>> train_images.shape
(60000, 28, 28)
>>> len(train_labels)
60000
>>> addestramento_labelle
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
>>> test_images.shape
(10000, 28, 28)
>>> len(test_labels)
10000
>>> test_labels
array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)
Il flusso di lavoro sarà il seguente: Per prima cosa, alimenteremo la rete neurale con i
dati di addestramento, train_images e train_labels. La rete imparerà quindi ad
associare immagini ed etichette. Infine, chiederemo alla rete di produrre previsioni per
test_images e verificheremo se queste previsioni corrispondono alle etichette di
test_labels.
Costruiamo la rete - ancora una volta, ricordate che non ci si aspetta che
comprendiate tutto di questo esempio.
L'elemento centrale delle reti neurali è il livello. Si può pensare a un livello come a un filtro
per i dati: alcuni dati entrano e ne escono in una forma più utile. In particolare, i
livelli estraggono rappresentazioni dai dati in essi immessi, auspicabilmente
rappresentazioni più significative per il problema in questione. La maggior parte
dell'apprendimento profondo consiste nel concatenare semplici livelli che implementano
una forma di distillazione progressiva dei dati. Un modello di deep learning è come un
setaccio per l'elaborazione dei dati, costituito da una successione di filtri sempre più
raffinati: gli strati.
In questo caso, il nostro modello consiste in una sequenza di due strati Dense, che
sono strati neurali densamente connessi (detti anche fully connected). Il secondo (e
ultimo) strato è uno strato di classificazione softmax a 10 vie, il che significa che
restituirà un array di 10 punteggi di probabilità (con somma a 1). Ogni punteggio sarà la
probabilità che l'immagine della cifra corrente appartenga a una delle 10 classi di cifre.
Un primo sguardo a una rete 29
neurale
Per rendere il modello pronto per l'addestramento, dobbiamo scegliere altre tre
cose come parte della fase di compilazione:
◾ Un ottimizzatore: il meccanismo attraverso il quale il modello si aggiorna in base ai
dati di addestramento che vede, in modo da migliorare le sue prestazioni.
◾ Una funzione di perdita: il modo in cui il modello sarà in grado di misurare le
proprie prestazioni sui dati di addestramento e quindi di orientarsi nella giusta
direzione.
◾ Metriche da monitorare durante l'addestramento e il test: in questo caso, ci
interesserà solo l'accuratezza (la frazione di immagini classificate correttamente).
Lo scopo esatto della funzione di perdita e dell'ottimizzatore sarà chiarito nei
prossimi due capitoli.
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
Ora siamo pronti ad addestrare il modello, cosa che in Keras viene fatta tramite una chiamata
alla funzione
metodo fit(): adattiamo il modello ai dati di addestramento.
Ogni numero dell'indice i in tale matrice corrisponde alla probabilità che l'immagine della
cifra
test_digits[0] appartiene alla classe i.
Questa prima cifra di prova ha il punteggio di probabilità più alto (0,99999106, quasi
1) con l'indice 7, quindi, secondo il nostro modello, deve essere un 7:
>>> previsioni[0].argmax()
7
>>> previsioni[0][7]
0.99999106
>>> test_labels[0]
7
In media, quanto è bravo il nostro modello a classificare queste cifre mai viste prima?
Verifichiamo calcolando l'accuratezza media sull'intero set di test.
L'accuratezza del set di test risulta essere del 97,8%, un valore nettamente inferiore
all'accuratezza del set di addestramento (98,9%). Questo divario tra l'accuratezza
dell'addestramento e l'accuratezza del test è un esempio di overfitting: il fatto che i
modelli di apprendimento automatico tendono a ottenere risultati peggiori sui nuovi
dati rispetto ai dati di addestramento. L'overfitting è un argomento centrale del
capitolo 3.
Questo conclude il nostro primo esempio: avete appena visto come si può
costruire e addestrare una rete neurale per classificare le cifre scritte a mano in
meno di 15 righe di codice Python. In questo capitolo e nel prossimo, entreremo nel
dettaglio di ogni pezzo mobile che abbiamo appena visto e chiariremo cosa succede
dietro le quinte. Impareremo a conoscere i tensori, gli oggetti che memorizzano i
dati nel modello; le operazioni sui tensori, che costituiscono gli strati; e la discesa
del gradiente, che consente al modello di imparare dagli esempi di addestramento.
Rappresentazioni di dati per reti neurali 31
Questo vettore ha cinque voci ed è quindi chiamato vettore a 5 dimensioni. Non bisogna
confondere un vettore 5D con un tensore 5D! Un vettore 5D ha un solo asse e ha cinque
dimensioni lungo il suo asse, mentre un tensore 5D ha cinque assi (e può avere un
numero qualsiasi di dimensioni lungo ogni asse). La dimensionalità può indicare sia il
numero di voci lungo un asse specifico (come nel caso del nostro vettore 5D) sia il
numero di assi di un tensore (come il tensore 5D), il che a volte può creare confusione.
In quest'ultimo caso, è tecnicamente più corretto parlare di un tensore di rango 5 (il
rango di un tensore è il numero di assi), ma la notazione ambigua di tensore 5D è
comune comunque.
32 CAPITOLO 2 Gli elementi matematici delle reti neurali
Le voci del primo asse sono chiamate righe e le voci del secondo asse sono chiamate
colonne. Nell'esempio precedente, [5, 78, 2, 34, 0] è la prima riga di x e [5, 6,
7] è la prima colonna.
◾ Tipo di dati (solitamente chiamato dtype nelle librerie Python): è il tipo di dati
contenuti nel tensore; ad esempio, il tipo di un tensore può essere float16,
float32, float64, uint8 e così via. In TensorFlow è probabile che si
incontrino anche tensori di tipo stringa.
Per rendere il tutto più concreto, riprendiamo i dati elaborati nell'esempio di
MNIST. Innanzitutto, carichiamo il set di dati MNIST:
>>> train_images.ndim
3
>>> train_images.shape
(60000, 28, 28)
>>> train_images.dtype
uint8
>>> train_labels[4]
9
In generale, è possibile selezionare fette tra due indici qualsiasi lungo ciascun asse
del tensore. Ad esempio, per selezionare 14 × 14 pixel nell'angolo inferiore destro di
tutte le immagini, si procede in questo modo:
È anche possibile utilizzare indici negativi. Come gli indici negativi negli elenchi
Python, indicano una posizione relativa alla fine dell'asse corrente. Per ritagliare le
immagini in patch di 14 × 14 pixel centrate al centro, si procede in questo modo:
batch = train_images[:128]
batch = train_images[128:256]
E l'ennesimo lotto:
n = 3
batch = train_images[128 * n:128 * (n + 1)]
Caratteristiche
Campio
ni Figura 2.3 Un tensore di dati
Tempi di serie temporali di rango 3
L'asse del tempo è sempre il secondo asse (asse di indice 1) per convenzione. Vediamo
alcuni esempi:
◾ Un set di dati sui prezzi delle azioni. Ogni minuto, memorizziamo il prezzo
corrente del titolo, il prezzo più alto dell'ultimo minuto e il prezzo più basso
dell'ultimo minuto. In questo modo, ogni minuto è codificato come un vettore 3D,
un'intera giornata di trading è codificata come una matrice di forma (390, 3)
(ci sono 390 minuti in una giornata di trading) e 250 giorni di dati possono essere
memorizzati in un tensore di rango 3 di forma (250, 390, 3). In questo caso,
ogni campione corrisponde a un giorno di dati.
◾ Un insieme di tweet, in cui ogni tweet viene codificato come una sequenza di 280
caratteri su un alfabeto di 128 caratteri unici. In questo contesto, ogni carattere
può essere codificato come un vettore binario di dimensione 128 (un vettore di tutti
zeri, tranne una voce 1 all'indice corrispondente al carattere). Quindi ogni tweet
può essere codificato come un tensore di rango 2 di forma (280, 128) e un set
di dati di 1 milione di tweet può essere memorizzato in un tensore di forma
(1000000, 280, 128).
Rappresentazioni di dati per reti neurali 37
Canali colore
Altezza
Campioni
Figura 2.4 Un
Larghezza tensore di dati di
immagine di rango
4
Il tipo di tensore era float32, ogni valore sarebbe stato memorizzato in 32 bit,
quindi il tensore avrebbe rappresentato 405 MB. Pesante! I video che si incontrano nella
vita reale sono molto più leggeri, perché non sono memorizzati in float32 e sono
tipicamente compressi da un grande fattore (come nel formato MPEG).
keras.layers.Dense(512, attivazione="relu")
Questo strato può essere interpretato come una funzione che prende in input una
matrice e restituisce un'altra matrice, una nuova rappresentazione del tensore di
input. In particolare, la funzione è la seguente (dove W è una matrice e b è un
vettore, entrambi attributi dello strato):
output = relu(dot(input, W) + b)
In base allo stesso principio, è possibile eseguire moltiplicazioni, sottrazioni e così via.
In pratica, quando si ha a che fare con gli array NumPy, queste operazioni sono
disponibili come funzioni integrate NumPy ben ottimizzate, che a loro volta delegano il
lavoro pesante a un'implementazione di Basic Linear Algebra Subprograms (BLAS). I
BLAS sono routine di manipolazione dei tensori di basso livello, altamente parallele ed
efficienti, che vengono tipicamente implementate
in Fortran o C.
Quindi, in NumPy, è possibile eseguire la seguente operazione element-wise e sarà
velocissima:
tempo di importazione
x = np.random.random((20, 100))
y = np.random.random((20, 100))
t0 = time.time()
per _ in range(1000):
z = x + y
z = np.maximum(z, 0.)
print("Preso: {0:.2f} s".format(time.time() - t0))
t0 = time.time()
per _ in range(1000):
z = naive_add(x, y)
z = naive_relu(z)
print("Preso: {0:.2f} s".format(time.time() - t0))
40 CAPITOLO 2 Gli elementi matematici delle reti neurali
Allo stesso modo, quando si esegue il codice TensorFlow su una GPU, le operazioni
elementari vengono eseguite tramite implementazioni CUDA completamente
vettoriali, in grado di utilizzare al meglio l'architettura del chip GPU altamente par-
ticolare.
2.3.2 Trasmissione
La nostra precedente implementazione ingenua di naive_add supporta solo l'aggiunta di
tensori di rango 2 con forme identiche. Ma nel livello Dense introdotto in
precedenza, abbiamo aggiunto un tensore di rango 2 con un vettore. Cosa succede
con l'addizione quando le forme dei due tensori da aggiungere sono diverse?
Quando è possibile, e se non ci sono ambiguità, il tensore più piccolo viene
trasmesso in modo che corrisponda alla forma del tensore più grande. La trasmissione
consiste in due fasi:
1 Al tensore più piccolo vengono aggiunti degli assi (chiamati assi di trasmissione)
che corrispondono all'ndim del tensore più grande.
2 Il tensore più piccolo viene ripetuto accanto a questi nuovi assi per ottenere la
forma completa del tensore più grande.
Vediamo un esempio concreto. Consideriamo X con forma (32, 10) e y con forma
(10,):
X è una matrice casuale
di forma (32, 10).
importare numpy come np
X = np.random.random((32, y è un vettore
10)) y = casuale di forma
np.random.random((10,)) (10,).
Per prima cosa, aggiungiamo un primo asse vuoto a y, la cui forma diventa (1, 10):
Poi, ripetiamo y 32 volte lungo questo nuovo asse, in modo da ottenere un tensore Y
con forma (32, 10), dove Y[i, :] == y per i in range(0, 32):
tensore di ingresso.
42 CAPITOLO 2 Gli elementi matematici delle reti neurali
per j in range(x.shape[1]):
x[i, j] += y[j]
restituire x
x = np.random.random((32,))
y = np.random.random((32,))
z = np.dot(x, y)
z = x - y
Avrete notato che il prodotto del punto tra due vettori è uno scalare e che solo i
vettori con lo stesso numero di elementi sono compatibili per un prodotto del punto.
È anche possibile eseguire il prodotto di punti tra una matrice x e un vettore y, che
restituisce un vettore in cui i coefficienti sono i prodotti di punti tra y e le righe di x. Si
implementa come segue:
Gli ingranaggi delle reti neurali: Operazioni 43
tensoriali
def naive_matrix_vector_dot(x, y):
assert len(x.shape) == 2 assert
x è una matrice NumPy.
len(y.shape) == 1 assert
y è un vettore NumPy.
x.shape[1] == y.shape[0] z =
np.zeros(x.shape[0]) La prima dimensione di
per i in range(x.shape[0]): x deve essere uguale alla
per j in range(x.shape[1]): dimensione 0 di y!
z[i] += x[i, j] * y[j]
restituire z Questa operazione restituisce
un vettore di 0 con la stessa
forma di y.
Si noti che non appena uno dei due tensori ha un ndim maggiore di 1, dot non è più
simmetrico, il che significa che dot(x, y) non è uguale a dot(y, x).
Naturalmente, il prodotto di punti si generalizza a tensori con un numero arbitrario
di assi. L'applicazione più comune è il prodotto di punti tra due matrici. È possibile
prendere il prodotto di punti di due matrici x e y (dot(x, y)) se e solo se x.shape[1]
== y.shape[0]. Il risultato è una matrice con forma (x.shape[0], y.shape[1]),
dove i coefficienti sono i prodotti vettoriali tra le righe di x e le colonne di y. Ecco
l'implementazione ingenua:
y.shape:
(b, c)
x-y=z
b Colonna di
y
x.shape: z.shape:
(a, b) (a, c)
a
z [ i, j ] Figura 2.5 Diagramma
Riga di x matriciale del prodotto a
punti
E così via.
A = [0.5, 1]
1 A [0.5, 1] 1 A [0.5, 1]
1 1
A+B
1 A
B
1
Figura 2.8 Interpretazione
geometrica della somma di due
vettori
Fattore orizzontale x
Fattore verticale + y
K Fattore verticale
K Figura 2.9
Traslazione 2D
Fattore orizzontale come
addizione
vettoriale
cos(theta) -sin(theta) x
sin(theta) cos(theta) y
K K Theta
Figura 2.10 Rotazione
2D (in senso antiorario)
Gli ingranaggi delle reti neurali: Operazioni 47
tensoriali
K
10 x
0 -0.5 y
Figura 2.11
Scala 2D come
prodotto di
punti
W-x+b
K Figura 2.12
Trasformazione affine nel
piano
relu(W - x + b)
K Figura 2.13
Trasformazione affine
seguita da attivazione
della relu
output = relu(dot(input, W) + b)
In questa espressione, W e b sono tensori che sono attributi dello strato. Sono chiamati
i pesi o i parametri addestrabili dello strato (rispettivamente gli attributi kernel e bias).
Questi pesi contengono le informazioni apprese dal modello grazie all'esposizione ai
dati di addestramento.
Inizialmente, queste matrici di pesi vengono riempite con piccoli valori casuali (una fase
chiamata
inizializzazione casuale). Naturalmente, non c'è motivo di aspettarsi che
relu(dot(input, W))
+ b), quando W e b sono casuali, produrrà qualsiasi rappresentazione utile. Le
rappresentazioni risultanti sono prive di significato, ma sono un punto di partenza. Il
passo successivo è quello di regolare gradualmente questi pesi, sulla base di un segnale
di feedback. Questo aggiustamento graduale, chiamato anche addestramento, è
l'apprendimento di cui si occupa l'apprendimento automatico.
Questo avviene all'interno di quello che viene chiamato ciclo di allenamento, che
funziona come segue. Ripetete questi passaggi in un ciclo, finché la perdita non sembra
sufficientemente bassa:
1 Disegnare un gruppo di campioni di allenamento, x, e i corrispondenti target, y_true.
2 Eseguireil modello su x (una fase chiamata forward pass) per ottenere le previsioni,
y_pred.
3 Calcolo della perdita del modello sul lotto, una misura del disallineamento tra
y_pred e y_true.
4 Aggiornare tutti i pesi del modello in modo da ridurre leggermente la perdita su
questo lotto.
Alla fine si otterrà un modello che ha una perdita molto bassa sui dati di addestramento: una
bassa discrepanza tra le previsioni, y_pred, e gli obiettivi previsti, y_true. Il modello ha
"imparato" a mappare i suoi input verso gli obiettivi corretti. Da lontano può
sembrare una magia, ma quando si riduce il tutto a passi elementari, si scopre che è
semplice.
Il passo 1 sembra abbastanza facile: basta il codice di I/O. I passi 2 e 3 non sono
altro che l'applicazione di una manciata di operazioni sui tensori, per cui si potrebbero
implementare questi passi solo sulla base di quanto appreso nella sezione precedente.
La parte difficile è il passo 4: l'aggiornamento dei pesi del modello. Dato un
Gli ingranaggi delle reti neurali: Operazioni 53
coefficiente di pesotensoriali
individuale nel modello, come si può calcolare se il coefficiente
deve essere aumentato o diminuito e di quanto?
Una soluzione ingenua potrebbe essere quella di congelare tutti i pesi del modello,
tranne quello del coefficiente di rischio considerato, e provare diversi valori per questo
coefficiente. Diciamo che
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 49
il valore iniziale del coefficiente è 0,3. Dopo il forward pass su un batch di dati, la
perdita del modello sul batch è di 0,5. Se si modifica il valore del coefficiente a 0,35 e si
esegue nuovamente il forward pass, la perdita aumenta a 0,6. Se si modifica il valore del
coefficiente a 0,35 e si esegue nuovamente il forward pass, la perdita aumenta a 0,6. Ma
se si abbassa il coefficiente a 0,25, la perdita aumenta. Se invece si abbassa il
coefficiente a 0,25, la perdita scende a 0,4. In questo caso, sembra che l'aggiornamento
del coefficiente di -0,05 contribuisca a minimizzare la perdita. Questa operazione
dovrebbe essere ripetuta per tutti i coefficienti del modello.
Ma un approccio di questo tipo sarebbe terribilmente inefficiente, perché
bisognerebbe calcolare due passaggi in avanti (che sono costosi) per ogni singolo
coefficiente (di cui ce ne sono molti, di solito migliaia e a volte fino a milioni).
Fortunatamente, esiste un approccio molto migliore: la discesa del gradiente.
La discesa del gradiente è la tecnica di ottimizzazione che alimenta le moderne
reti neurali. Ecco il nocciolo della questione. Tutte le funzioni utilizzate nei nostri
modelli (come il punto o il +) trasformano il loro input in modo fluido e continuo:
se si guarda a z = x + y, ad esempio, una piccola variazione di y si traduce in
una piccola variazione di z, e se si conosce la direzione della variazione di y, si può
dedurre la direzione della variazione di z. Matematicamente si direbbe che queste
funzioni sono differenziabili. Se si concatenano tali funzioni, la funzione più grande che
si ottiene è ancora differenziabile. In particolare, questo vale per la funzione che
mappa i coefficienti del modello alla perdita del modello su un gruppo di dati: una
piccola variazione dei coefficienti del modello si traduce in una piccola e
prevedibile variazione del valore della perdita. Ciò consente di utilizzare un
operatore matematico chiamato gradiente per descrivere come varia la perdita
quando si spostano i coefficienti del modello in direzioni diverse. Se si calcola questo
gradiente, è possibile utilizzarlo per spostare i coefficienti (tutti insieme in un unico
aggiornamento, anziché uno alla volta) in una direzione che riduce la perdita.
Se sapete già cosa significa differenziabile e cosa è un gradiente, potete passare alla
sezione 2.4.3. Altrimenti, le due sezioni seguenti vi aiuteranno a capire questi concetti.
Altrimenti, le due sezioni seguenti vi aiuteranno a comprendere questi concetti.
y = f(x)
y
Figura 2.15 Un continuo,
x funzione liscia
Poiché la funzione è continua, una piccola variazione di x può comportare solo una
piccola variazione di y: questa è l'intuizione alla base della continuità.
50 CAPITOLO 2 Gli elementi matematici delle reti neurali
Supponiamo di aumentare x di un piccolo fattore, epsilon_x: ciò si traduce in una
piccola variazione di epsilon_y in y, come mostrato nella figura 2.16.
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 51
epsilon_y
Inoltre, poiché la funzione è liscia (la sua curva non presenta angoli bruschi), quando
epsilon_x è abbastanza piccolo, intorno a un certo punto p, è possibile approssimare
f come una funzione lineare di pendenza a, in modo che epsilon_y diventi a *
epsilon_x:
Lineare locale
approssimazion f, con
e di
pendenza a
y y = f(x)
Supponiamo che il valore attuale di W sia W0. Allora la derivata di f nel punto W0 è un
tensore grad(valore_perdita, W0), con la stessa forma di W, dove ogni coefficiente
grad(valore_perdita, W0)[i, j] indica la direzione e l'entità della variazione del
valore_perdita che si osserva modificando W0[i, j]. Il tensore
grad(loss_value, W0) è il gradiente della funzione f(W) = loss_value in W0,
chiamato anche "gradiente del loss_value rispetto a W nell'intorno di W0".
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 53
Derivate parziali
L'operazione tensoriale grad(f(W), W) (che prende in ingresso una matrice W)
può essere espressa come una combinazione di funzioni scalari,
grad_ij(f(W), w_ij), ognuna delle quali restituisce la derivata di loss_value
= f(W) rispetto al coefficiente W[i, j] di W, assumendo che tutti gli altri
coefficienti siano costanti. grad_ij è chiamata la derivata parziale di f rispetto a
W[i, j].
3 Calcolare la perdita del modello sul lotto, una misura della mancata
corrispondenza tra y_pred e y_true.
4 Calcolare il gradiente della perdita rispetto ai parametri del modello (questo è il
cosiddetto backward pass).
5 Spostare i parametri un po' nella direzione opposta al gradiente, ad esempio W -=
learning_rate * gradient, riducendo così un po' la perdita sul batch. Il
tasso di apprendimento (learning_rate in questo caso) è un fattore scalare
che modula la "velocità" del processo di discesa del gradiente.
Abbastanza facile! Quello che abbiamo appena descritto si chiama mini-batch stochastic
gradient descent (mini-batch SGD). Il termine stocastico si riferisce al fatto che ogni
lotto di dati viene estratto a caso (stocastico è un sinonimo scientifico di casuale). La
Figura 2.18 illustra ciò che accade in 1D, quando il modello ha un solo parametro e si
dispone di un solo campione di allenamento.
t=1
t=2
t=3
Punto di
partenza
45
40
35
30
25
20
15
10
5
Inoltre, esistono diverse varianti di SGD che si differenziano per il fatto di tenere conto
degli aggiornamenti precedenti dei pesi nel calcolo del prossimo aggiornamento dei
pesi, anziché limitarsi a considerare il valore attuale dei gradienti. Esiste, ad esempio,
l'SGD con il metodo del momen- to, così come l'Adagrad, l'RMSprop e molti altri. Tali
varianti sono note come metodi di ottimizzazione o ottimizzatori. In particolare, merita
attenzione il concetto di momento, utilizzato in molte di queste varianti. Il momentum
affronta due problemi con SGD: la velocità di convergenza e i minimi locali. Si
consideri la figura 2.20, che mostra la curva di una perdita in funzione di un parametro
del modello.
Valor
e di
perdit
a
Minimo
locale
Minimo
globale
Come si può notare, intorno a un certo valore del parametro si verifica un minimo locale:
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 57
in quel punto, spostandosi verso sinistra la perdita aumenterebbe, ma anche spostandosi
verso destra.
58 CAPITOLO 2 Gli elementi matematici delle reti neurali
Il calcolo ci dice che una tale catena di funzioni può essere derivata utilizzando la
seguente identità, detta regola della catena.
Consideriamo due funzioni f e g, nonché la funzione composta fg tale che
fg(x) == f(g(x)):
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 59
def fg(x):
x1 = g(x)
y = f(x1)
return y
def fghj(x):
x1 = j(x)
x2 = h(x1)
x
x3 = g(x2)
y = f(x3)
return y
w *
x1
b +
x2
y_true perd
ita
Impostiamo valori concreti per i "nodi di ingresso" del grafo, c i o è l'ingresso x, il target
y_true, w e b. Propagheremo questi valori a tutti i nodi d e l grafo, dall'alto verso il
basso, fino a raggiungere loss_val. Questo è il passaggio in avanti (vedi figura 2.23).
Ora "invertiamo" il grafico: per ogni spigolo del grafico che va da A a B, creiamo
uno spigolo opposto da B ad A e chiediamoci: quanto varia B al variare di A? Ovvero,
qual è il grad(B, A)? Annoteremo ogni bordo invertito con questo valore. Questo
grafico inverso rappresenta il passaggio all'indietro (vedi figura 2.24).
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 61
2
x
3
w *
x1 = 6
1
b +
x2 = 7
4
y_true perd
ita
2
x
3 grad(x1, w) = 2
w *
x2grad(loss_val, x2) = 1
4
y_true perdita
2
x
3 grad(x1, w) = 2
w *
x2grad(loss_val, x2) = 1
4
y_true abs_diff
valore_perdita
Applicando la regola della catena al nostro grafico, otteniamo ciò che stavamo cercando:
◾ grad(loss_val, w) = 1 * 1 * 2 = 2
◾ grad(loss_val, b) = 1 * 1 = 1
Il motore delle reti neurali: Ottimizzazione basata sul gradiente 63
NOTA Se ci sono più percorsi che collegano i due nodi di interesse, a e b, nel
grafo a ritroso, otterremmo grad(b, a) sommando i contributi di tutti i
percorsi.
Ingresso X
Strato
Pesi
(trasformazione dei
dati)
Strato
Pesi
(trasformazione dei
dati)
Ora si capisce che le immagini in ingresso sono memorizzate in tensori NumPy, qui
formattati come tensori float32 di forma (60000, 784) (dati di allenamento) e
(10000, 784) (dati di test) rispettivamente.
Questo era il nostro modello:
model = keras.Sequential([
layers.Dense(512, activation="relu"),
layers.Dense(10, activation="softmax")
])
Ora si capisce che questo modello consiste in una catena di due strati Dense, che ogni
strato applica alcune semplici operazioni tensoriali ai dati di ingresso e che queste
operazioni coinvolgono i tensori peso. I tensori dei pesi, che sono attributi degli
strati, sono il luogo in cui risiede la conoscenza del modello.
Questa era la fase di compilazione del modello:
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
Ora si capisce cosa succede quando si chiama fit: il modello inizierà a iterare sui
dati di addestramento in mini-batch di 128 campioni, per 5 volte (ogni iterazione su
tutti i dati di addestramento è chiamata epoch). Per ogni lotto, il modello calcola il
gradiente della perdita rispetto ai pesi (utilizzando l'algoritmo di retropropagazione,
che deriva dalla regola della catena nel calcolo) e sposta i pesi nella direzione che
ridurrà il valore della perdita per questo lotto.
Dopo queste 5 epoche, il modello avrà eseguito 2.345 aggiornamenti del gradiente
(469 per epoca) e la perdita del modello sarà sufficientemente bassa da consentirgli di
classificare le cifre scritte a mano con elevata precisione.
A questo punto, sapete già la maggior parte di ciò che c'è da sapere sulle reti
neurali. Dimostriamolo reimplementando una versione semplificata del primo
esempio "da zero" in TensorFlow, passo dopo passo.
Riprendendo il nostro primo 63
esempio
2.5.1 Reimplementare da zero il nostro primo esempio in TensorFlow
Cosa dimostra meglio una comprensione completa e non ambigua se non
l'implementazione di ogni cosa da zero? Naturalmente, il significato di "da zero" è
relativo: non reimplementeremo le operazioni tensoriali di base e non
implementeremo la retropropagazione. Ma andremo a un livello così basso che non
useremo quasi nessuna funzionalità di Keras.
Non preoccupatevi se non capite ancora tutti i dettagli di questo esempio. Il
prossimo capitolo approfondirà l'API di TensorFlow. Per ora, cercate di seguire il
succo di ciò che sta accadendo: l'intento di questo esempio è quello di aiutarvi a
cristallizzare la comprensione della matematica del deep learning utilizzando
un'implementazione concreta. Andiamo!
UNA SEMPLICE CLASSE DENSA
Si è appreso in precedenza che il livello Dense implementa la seguente trasformazione
dell'input, dove W e b sono parametri del modello e l'attivazione è una funzione
elementare (di solito relu, ma sarebbe softmax per l'ultimo livello):
Implementiamo una semplice classe Python, NaiveDense, che crea due variabili
TensorFlow, W e b, ed espone un metodo call () che applica la trasformazione
precedente.
classe NaiveSequential:
def init (self, layers):
self.layers = layers
@proprietà
def pesi(self):
pesi = []
per layer in self.layers:
pesi += layer.weights
restituire i pesi
modello = NaiveSequential([
NaiveDense(input_size=28 * 28, output_size=512, attivazione=tf.nn.relu),
NaiveDense(input_size=512, output_size=10, attivazione=tf.nn.softmax)
])
assert len(model.weights) == 4
UN GENERATORE DI BATCH
Quindi, abbiamo bisogno di un modo per iterare sui dati MNIST in mini-batch. È facile:
Importazione di matematica
classe BatchGenerator:
def init (self, images, labels, batch_size=128):
assert len(immagini) ==
len(etichette) self.index = 0
self.images = immagini
self.labels = etichette
self.batch_size = batch_size
self.num_batches = math.ceil(len(images) / batch_size)
def next(self):
images = self.images[self.index : self.index + self.batch_size]
labels = self.labels[self.index : self.index + self.batch_size]
self.index += self.batch_size
restituire immagini, etichette
Come già sapete, lo scopo del passo di "aggiornamento dei pesi" (rappresentato dalla
funzione update_weights) è quello di spostare i pesi di "un po'" in una direzione che
riduca la perdita su questo batch. L'entità dello spostamento è determinata dal "tasso di
apprendimento", in genere una piccola quantità. Il modo più semplice per implementare
questa funzione update_weights è sottrarre gradiente * learning_rate da ogni
peso:
tasso_di_apprendimento = 1e-3
In pratica, non si implementa quasi mai una fase di aggiornamento del peso come
questa a mano. Si userebbe invece un'istanza di Optimizer di Keras, come questa:
= optimizers.SGD(learning_rate=1e-3)
predictions = model(test_images)
predictions = predictions.numpy()
Chiamando .numpy() su
predicted_labels = np.argmax(predictions, un tensore TensorFlow lo si
axis=1) matches = predicted_labels == converte in un tensore
test_labels print(f "accuracy: NumPy.
{matches.mean():.2f}")
Tutto fatto! Come si può vedere, è un bel po' di lavoro fare "a mano" ciò che si può
fare in poche righe di codice Keras. Ma dopo aver seguito questi passaggi, ora dovreste
avere una comprensione cristallina di ciò che accade all'interno di una rete neurale
quando si chiama fit(). Questo modello mentale di basso livello di ciò che il vostro
codice fa dietro le quinte vi permetterà di sfruttare meglio le funzioni di alto livello
dell'API di Keras.
Sintesi
◾ I tensori costituiscono la base dei moderni sistemi di apprendimento automatico.
Sono disponibili in diverse varianti di tipo, grado e forma.
◾ È possibile manipolare i tensori numerici tramite operazioni sui tensori (come
l'addizione, il prodotto di tensori o la moltiplicazione elementare), che
possono essere interpretate come codifica di trasformazioni geometriche. In
generale, tutto ciò che riguarda il deep learning è suscettibile di
un'interpretazione geometrica.
◾ I modelli di apprendimento profondo sono costituiti da catene di semplici
operazioni tensoriali, parametrizzate da pesi, che sono a loro volta tensori. I pesi
di un modello sono il luogo in cui viene memorizzata la sua "conoscenza".
◾ Apprendere significa trovare un insieme di valori per i pesi del modello che
minimizzi una funzione di perdita per un dato insieme di campioni di dati di
addestramento e i loro corrispondenti obiettivi.
Sintesi 67
Questo capitolo ha lo scopo di fornire tutto ciò che serve per iniziare a fare deep
learning nella pratica. Vi presenterò rapidamente Keras (https://keras.io) e
TensorFlow (https://tensorflow.org), gli strumenti di deep learning basati su
Python che utilizzeremo nel corso del libro. Scoprirete come impostare uno
spazio di lavoro per il deep learning, con TensorFlow, Keras e il supporto delle
GPU. Infine, sulla base del primo contatto con Keras e TensorFlow nel capitolo 2,
rivedremo i componenti fondamentali delle reti neurali e come si traducono nelle
API di Keras e TensorFlow.
Alla fine di questo capitolo, sarete pronti per passare alle applicazioni pratiche e
reali, che inizieranno nel capitolo 4.
68
Cos'è Keras? 69
Sviluppo dell'apprendimento
Keras profondo: livelli, modelli,
ottimizzatori, perdite, metriche...
Si notano due pulsanti nella barra degli strumenti: + Codice e + Testo. Servono
rispettivamente a creare celle di codice Python e celle di testo di annotazione. Dopo aver
inserito il codice in una cella di codice, premere Maiuscole-Invio per eseguirlo (vedere
figura 3.3).
In una cella di testo è possibile utilizzare la sintassi Markdown (vedi figura 3.4). Se
si preme Maiuscole-Invio su una cella di testo, questa viene visualizzata.
Le celle di testo sono utili per dare una struttura leggibile ai vostri quaderni:
usatele per annotare il codice con titoli di sezioni e lunghi paragrafi di spiegazione o
per incorporare figure. I blocchi note sono pensati per essere un'esperienza
multimediale!
74 CAPITOLO 3 Introduzione a Keras e TensorFlow
Una differenza significativa tra gli array NumPy e i tensori TensorFlow è che i tensori
Tensor-Flow non sono assegnabili: sono costanti. Per esempio, in NumPy, si può fare
quanto segue.
Lo stato di una variabile può essere modificato tramite il suo metodo assign, come segue.
a = tf.ones((2, 2))
b = tf.square(a) Prendete la piazza.
c = tf.sqrt(a) Prendere la radice quadrata.
d = b + c
Aggiungere due tensori (in modo elementare).
e = tf.matmul(a, b)
e *= d Prendiamo il prodotto di due
tensori (come discusso nel
Moltiplica due
capitolo 2).
tensori (in
modo
elementare).
È importante notare che ciascuna delle operazioni precedenti viene eseguita al volo: in
qualsiasi m o m e n t o è possibile stampare il risultato corrente, proprio come in NumPy.
Questa viene chiamata esecuzione "eager".
input_var = tf.Variabile(valore_iniziale=3.)
con tf.GradientTape() come tape:
risultato = tf.square(input_var)
gradiente = tape.gradient(risultato, input_var)
Primi passi con 79
TensorFlow
Questo è il metodo più comunemente usato per recuperare i gradienti della perdita di un
modello rispetto ai suoi pesi: gradienti = tape.gradient(loss, weights).
L'abbiamo visto in azione nel capitolo 2.
Finora abbiamo visto solo il caso in cui i tensori in ingresso a tape.gradient() erano
variabili TensorFlow. In realtà è possibile che questi input siano qualsiasi tensore
arbitrario. Tuttavia, per impostazione predefinita, vengono tracciate solo le variabili
addestrabili. Con un tensore costante, è necessario contrassegnarlo manualmente come
tracciato chiamando tape.watch() su di esso.
input_const = tf.constant(3.)
con tf.GradientTape() come tape:
tape.watch(input_const)
risultato = tf.square(input_const)
gradiente = tape.gradient(risultato, input_const)
Listato 3.12 Utilizzo di nastri di gradienti annidati per calcolare gradienti del secondo ordine
tempo = tf.Variabile(0.)
con tf.GradientTape() come outer_tape: Utilizziamo il nastro
con tf.GradientTape() come inner_tape: esterno per calcolare il
posizione = 4,9 * tempo ** 2 gradiente del gradiente
velocità = inner_tape.gradient(posizione, del nastro interno.
tempo) accelerazione = Naturalmente la risposta è
4,9 * 2 = 9,8.
outer_tape.gradient(velocità, tempo)
Generiamo le etichette di destinazione corrispondenti, una matrice di zeri e di uni della forma
(2000, 1), dove target[i, 0] è 0 se input[i] appartiene alla classe 0 (e viceversa).
Ora creiamo un classificatore lineare che impari a separare questi due blob. Un
classificatore lineare è una trasformazione affine (predizione = W - input + b)
addestrata per minimizzare il quadrato della differenza tra le predizioni e gli obiettivi.
Come vedrete, si tratta di un esempio molto più semplice rispetto a quello di una rete
neurale giocattolo a due strati, visto alla fine del capitolo 2. Tuttavia, questa volta
dovrete essere in grado di capire tutto ciò che riguarda il codice, riga per riga. Tuttavia,
questa volta dovreste essere in grado di capire tutto del codice, riga per riga.
Creiamo le nostre variabili, W e b, inizializzate rispettivamente con valori casuali e
con zeri.
def model(input):
restituire tf.matmul(ingressi, W) + b
Poiché il nostro classificatore lineare opera su ingressi 2D, W è in realtà solo due coefficienti
scalari, w1 e w2: W = [[w1], [w2]]. Nel frattempo, b è un singolo coefficiente scalare.
Di conseguenza, per un dato punto di ingresso [x, y], il suo valore di predizione è
predizione = [[w1], [w2]] - [x, y] + b = w1 * x + w2 * y + b.
Il seguente elenco mostra la nostra funzione di perdita.
82 CAPITOLO 3 Introduzione a Keras e TensorFlow
La fase successiva è quella di addestramento, che riceve alcuni dati di addestramento e aggiorna i
pesi W
e b in modo da ridurre al minimo la perdita di dati.
previsioni = modello(input)
plt.scatter(input[:, 0], input[:, 1], c=predictions[:, 0] > 0.5)
plt.show()
84 CAPITOLO 3 Introduzione a Keras e TensorFlow
modello, visualizzato
come una linea
86 CAPITOLO 3 Introduzione a Keras e TensorFlow
Questo è il vero scopo di un classificatore lineare: trovare i parametri di una linea (o, in
spazi più dimensionali, di un iperpiano) che separa nettamente due classi di dati.
Nella prossima sezione, tratteremo in dettaglio lo scopo di questi metodi build() e call().
metodi. Non preoccupatevi se non avete ancora capito tutto!
Una volta istanziato, un livello come questo può essere usato come una funzione,
prendendo in input un tensore TensorFlow:
dinamicamente per adattarsi alla forma del livello in arrivo. Per esempio, supponiamo di
scrivere quanto segue:
Anatomia di una rete neurale: Comprendere le API di base di 87
Keras
from tensorflow.keras import models
from tensorflow.keras import layers
model = models.Sequential([
layers.Dense(32, activation="relu"),
layers.Dense(32)
])
Gli strati non h a n n o ricevuto alcuna informazione sulla forma dei loro input, ma
hanno dedotto automaticamente la forma dei primi i n p u t c h e hanno visto.
Nella versione giocattolo del livello Dense che abbiamo implementato nel capitolo
2 (che abbiamo chiamato NaiveDense), dovevamo passare esplicitamente al
costruttore la dimensione di ingresso del livello per poter creare i suoi pesi. Questo non
è l'ideale, perché porterebbe a modelli come questo, in cui ogni nuovo livello deve
conoscere la forma del livello precedente:
modello = NaiveSequential([
NaiveDense(input_size=784, output_size=32, attivazione="relu"),
NaiveDense(input_size=32, output_size=64, attivazione="relu"),
NaiveDense(input_size=64, output_size=32, attivazione="relu"),
NaiveDense(input_size=32, output_size=10, attivazione="softmax")
])
Sarebbe ancora peggio se le regole utilizzate da uno strato per produrre la forma in
uscita fossero complesse. Per esempio, cosa succederebbe se il nostro strato restituisse
output di forma (batch, input_size * 2 se input_size % 2 == 0 altrimenti
input_size * 3)?
Se dovessimo reimplementare il nostro livello NaiveDense come un livello Keras
in grado di fare inferenza automatica sulle forme, sarebbe simile al precedente livello
SimpleDense (vedi listato 3.22), con i suoi metodi build() e call().
In SimpleDense, non creiamo più i pesi nel costruttore come nell'esempio
Naive-Dense, ma li creiamo in un metodo di creazione dello stato dedicato,
build(), che riceve come argomento la prima forma di input vista dallo strato. Il
metodo build() viene richiamato automaticamente la prima volta che il livello viene
chiamato (tramite il suo metodo
metodo call ()). In effetti, questo è il motivo per cui abbiamo definito il calcolo in
un metodo call() separato, anziché direttamente nel metodo call (). Il metodo
call () del livello base si presenta schematicamente come segue:
Con l'inferenza automatica della forma, il nostro esempio precedente diventa semplice e
pulito:
model = keras.Sequential([
SimpleDense(32, attivazione="relu"),
SimpleDense(64, attivazione="relu"),
88 CAPITOLO 3 Introduzione a Keras e TensorFlow
SimpleDense(32, attivazione="relu"),
SimpleDense(10, attivazione="softmax")
])
Si noti che l'inferenza automatica della forma non è l'unica cosa che la classe Layer
gestisce il metodo call (). Si occupa di molte altre cose, in particolare
dell'instradamento tra l'esecuzione eager e quella a grafo (un concetto che si
apprenderà nel capitolo 7) e del mascheramento degli ingressi (di cui si parlerà nel
capitolo 11). Per ora, ricordate: quando implementate i vostri livelli, mettete il
passaggio in avanti nel metodo call().
La topologia della rete può essere molto complessa. Ad esempio, la figura 3.9
mostra la topologia del grafo dei livelli di un trasformatore, un'architettura comune
progettata per elaborare dati di testo.
In genere esistono due modi per costruire tali modelli in Keras: si può sottoclasse
direttamente la classe Model, oppure si può utilizzare l'API funzionale, che consente di
fare di più con meno codice. Tratteremo entrambi gli approcci nel capitolo 7.
La topologia di un modello definisce uno spazio di ipotesi. Forse ricorderete che nel
capitolo 1 abbiamo descritto l'apprendimento automatico come la ricerca di
rappresentazioni utili di alcuni dati di ingresso, all'interno di uno spazio predefinito di
possibilità, utilizzando la guida di un segnale di feedback. Scegliendo una topologia di
rete, si vincola lo spazio delle possibilità (spazio ipotetico) a una serie specifica di
operazioni tensoriali, che mappano i dati di ingresso in quelli di uscita. Si cercherà
quindi un buon insieme di valori per i tensori dei pesi coinvolti in queste operazioni
tensoriali.
Per imparare dai dati, è necessario fare delle ipotesi su di essi. Queste ipotesi
definiscono ciò che può essere appreso. Per questo motivo, la struttura dello spazio
delle ipotesi - l'architettura del modello - è estremamente importante. Essa codifica
le ipotesi che si fanno sul problema, la conoscenza preliminare da cui parte il
modello. Per esempio, se si lavora su un problema di classificazione a due classi con un
modello costituito da un singolo strato denso senza attivazione (una trasformazione affine
pura), si assume che le due classi siano linearmente separabili.
Scegliere la giusta architettura di rete è più un'arte che una scienza e, sebbene
esistano alcune best practice e principi su cui fare affidamento, solo la pratica può
aiutare a diventare un corretto architetto di reti neurali. I prossimi capitoli
insegneranno
Anatomia di una rete neurale: Comprendere le API di base di 89
Keras
StratoNormalizzazi
one
Denso
Denso
StratoNormalizzazi
one
StratoNormalizzazi
one
+
Denso +
MultiHeadAttention
Denso
StratoNormalizzazio StratoNormalizzazio
ne ne
+ +
MultiHeadAttention MultiHeadAttention
Figura 3.9 L'architettura di Transformer (trattata nel capitolo 11). Qui c'è molto da fare.
Nei prossimi capitoli si cercherà di capire meglio.
L'utente potrà conoscere i principi espliciti per la costruzione delle reti neurali e
sviluppare l'intuizione di ciò che funziona o non funziona per problemi specifici. Si
acquisirà una solida intuizione su quali tipi di architetture di modelli funzionano per
diversi tipi di problemi, su come costruire queste reti nella pratica, su come
scegliere la giusta configurazione di apprendimento e su come modificare un
modello fino a ottenere i risultati desiderati.
◾ Ottimizzatore: determina il modo in cui la rete verrà aggiornata in base alla funzione
di perdita. Implementa una variante specifica della discesa stocastica del
gradiente (SGD).
◾ Metriche: le misure di successo che si desidera monitorare durante l'addestramento e
la valutazione, come l'accuratezza della classificazione. A differenza della perdita,
l'addestramento non ottimizza direttamente queste metriche. Pertanto, non è
necessario che le metriche siano differenziabili.
Dopo aver scelto la perdita, l'ottimizzatore e le metriche, si possono usare i metodi
integrati compile() e fit() per iniziare l'addestramento del modello. In alternativa,
è possibile scrivere i propri cicli di addestramento personalizzati, che verranno illustrati
nel capitolo 7. È un lavoro molto più impegnativo! Per ora, diamo un'occhiata a
compile() e fit().
Il metodo compile() configura il processo di addestramento, che è già stato
introdotto nel primo esempio di rete neurale nel capitolo 2. Prende gli argomenti
ottimizzatore, perdita e metrica (un elenco):
Definire un classificatore lineare. Specificare l'ottimizzatore
the
model = per nome: RMSprop
keras.Sequential([keras.layers.Dense(1)]) (non fa distinzione
model.compile(optimizer="rmsprop",
tra maiuscole e
minuscole).
loss="mean_squared_error",
metrics=["accuracy"]) Specificare la
Specificare un elenco di perdita per
metriche: in questo nome: errore
caso, solo l'accuratezza. quadratico
medio.
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=keras.losses.MeanSquaredError(),
metrics=[keras.metrics.BinaryAccuracy()])
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-4),
loss=my_custom_loss,
metrics=[my_custom_metric_1, my_custom_metric_2])
◾ Adagrad
◾ ecc.
Sconfitte:
◾ Crossentropia categorica
◾ SparseCategoricalCrossentropy
◾ Crossentropia binaria
◾ Errore quadratico medio
◾ KLDivergenza
◾ CosenoSimilarità
◾ ecc.
Metriche:
◾ Precisione categoriale
◾ Precisione sparseCategoriali
◾ Precisione binaria
◾ AUC
◾ Precisione
◾ Richiamo
◾ ecc.
Nel corso di questo libro, vedrete applicazioni concrete di molte di queste opzioni.
>>> storia.storia
{"binary_accuracy": [0.855, 0.9565, 0.9555, 0.95, 0.951],
"perdita": [0.6573270302042366,
0.07434618508815766,
0.07687718723714351,
0.07412414988875389,
0.07617757616937161]}
Per tenere sotto controllo l'andamento del modello su nuovi dati, è prassi riservare
un sottoinsieme dei dati di addestramento come dati di validazione: non si addestrerà il
modello su questi dati, ma li si utilizzerà per calcolare un valore di perdita e un valore di
metrica. Per farlo, si utilizza l'argomento validation_data in fit(). Come i dati
di addestramento, i dati di validazione possono essere passati come array NumPy o
come oggetto TensorFlow Dataset.
model = keras.Sequential([keras.layers.Dense(1)])
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=0.1),
loss=keras.losses.MeanSquaredError(),
Per evitare di avere
metrics=[keras.metrics.BinaryAccuracy()])
campioni di una sola
classe nei dati di
indices_permutation = np.random.permutation(len(inputs)) validazione, mescolare
shuffled_inputs = inputs[indices_permutation] gli input e i target
shuffled_targets = targets[indices_permutation] utilizzando una
permutazione casuale
num_validation_samples = int(0.3 * len(inputs)) degli indici.
val_inputs = shuffled_inputs[:num_validation_samples]
val_targets = shuffled_targets[:num_validation_samples] Riservare il 30% degli
training_inputs = shuffled_inputs[num_validation_samples:] input e dei target di
training_targets = shuffled_targets[num_validation_samples:] formazione per la
model.fit( convalida (escluderemo
questi campioni dalla
formazione e li
riserveremo alla
convalida).
training_inputs, Dati di addestramento, calcolare la convalida
training_targets, utilizzati per aggiornare i pesi perdita e metriche).
epochs=5, del modello.
batch_size=16, Dati di convalida, utilizzati solo
validation_data=(val_inputs, val_targets) per monitorare la
) perdita di convalida e le
metriche
Il valore della perdita sui dati di validazione è chiamato "perdita di validazione", per
distinguerlo dalla "perdita di addestramento". È essenziale tenere rigorosamente separati
i dati di addestramento da quelli di convalida: lo scopo della convalida è monitorare se
ciò che il modello sta imparando è effettivamente utile su nuovi dati. Se uno qualsiasi
dei dati di convalida è stato visto dal modello durante l'addestramento, la perdita e le
metriche di convalida saranno errate.
Si noti che se si desidera calcolare la perdita e la metrica di convalida al termine
dell'addestramento, è possibile richiamare il metodo evaluate():
evaluate() itera in batch (di dimensione batch_size) sui dati passati e restituisce
un elenco di scalari, dove la prima voce è la perdita di validazione e le voci successive
sono le metriche di validazione. Se il modello non ha metriche, viene restituita solo la
perdita di validazione (anziché un elenco).
Sintesi 93
Tuttavia, questo processerà tutti gli input in new_inputs in una volta sola, il che potrebbe
non essere fattibile se si stanno esaminando molti dati (in particolare, potrebbe
richiedere più memoria di quanta ne abbia la GPU).
Un modo migliore per fare inferenza è usare il metodo predict(). Questo metodo itera
i dati in piccoli lotti e restituisce un array NumPy di previsioni. E a differenza di
(), può anche elaborare oggetti TensorFlow Dataset.
Prende un array di
predictions = model.predict(new_inputs, batch_size=128) NumPy o un Dataset e
restituisce un array di
NumPy.
Per il momento, questo è tutto ciò che occorre sapere sui modelli di Keras. Siete
pronti per passare alla soluzione di problemi reali di apprendimento automatico con
Keras nel prossimo capitolo.
Sintesi
◾ TensorFlow è un framework per l'elaborazione numerica che può essere
eseguito su CPU, GPU o TPU. È in grado di calcolare automaticamente il
gradiente di qualsiasi espressione differenziabile, può essere distribuito su
molti dispositivi e può esportare i programmi in vari runtime esterni, persino
in JavaScript.
◾ Keras è l'API standard per l'apprendimento profondo con TensorFlow. È quella
che utilizzeremo in questo libro.
◾ Gli oggetti chiave di TensorFlow includono i tensori, le variabili, le
96 CAPITOLO 3 Introduzione a Keras e TensorFlow
Questo capitolo è stato pensato per iniziare a utilizzare le reti neurali per risolvere
problemi reali. Si consolideranno le conoscenze acquisite nei capitoli 2 e 3 e si
applicherà quanto appreso a tre nuovi compiti che coprono i tre casi d'uso più comuni
delle reti neurali: classificazione binaria, classificazione multiclasse e regressione
scalare:
◾ Classificazione delle recensioni di film come positive o negative (classificazione
binaria)
◾ Classificazione dei fili di notizie per argomento (classificazione multiclasse)
◾ Stima del prezzo di una casa, dati i dati immobiliari (regressione scalare)
Questi esempi saranno il vostro primo contatto con i flussi di lavoro end-to-end
dell'apprendimento automatico: verrete introdotti alla pre-elaborazione dei dati,
ai principi di base dell'architettura dei modelli e alla valutazione dei modelli.
95
96 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
Alla fine di questo capitolo, sarete in grado di utilizzare le reti neurali per gestire
semplici compiti di classificazione e regressione su dati vettoriali. Sarete quindi pronti per
iniziare a costruire una comprensione dell'apprendimento automatico basata su
principi e teorie nel capitolo 5.
Classificare le recensioni di film: Un esempio di 97
classificazione binaria
>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1
Poiché ci limitiamo alle 10.000 parole più frequenti, l'indice delle parole non
supererà le 10.000 unità:
>>> x_train[0]
array([ 0., 1., 1., ..., 0., 0., 0.])
100 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
y_train = np.asarray(train_labels).astype("float32")
y_test = np.asarray(test_labels).astype("float32")
Ora i dati sono pronti per essere inseriti in una rete neurale.
model = keras.Sequential([
layers.Dense(16, attivazione="relu"),
layers.Dense(16, attivazione="relu"),
layers.Dense(1,
attivazione="sigmoid")
])
Il primo argomento passato a ogni strato denso è il numero di unità dello strato: la
dimensionalità dello spazio di rappresentazione dello strato. Si ricorda dai capitoli 2 e 3
che ciascuno di questi strati densi con attivazione relu- gativa implementa la
seguente catena di operazioni tensoriali:
output = relu(dot(input, W) + b)
Classificare le recensioni di film: Un esempio di 101
classificazione binaria
Avere 16 unità significa che la matrice dei pesi W avrà forma (input_dimension, 16):
il prodotto del punto con W proietterà i dati di input su uno spazio di
rappresentazione a 16 dimensioni (e poi si aggiungerà il vettore bias b e si
applicherà l'operazione relu). La dimensionalità dello spazio di rappresentazione
può essere intesa intuitivamente come "quanta libertà si concede al modello
nell'apprendimento delle rappresentazioni interne". Avere più unità (uno spazio di
rappresentazione più alto) permette al modello di apprendere rappresentazioni più
complesse, ma rende il modello più costoso dal punto di vista computazionale e può
portare all'apprendimento di modelli indesiderati (modelli che migliorano le
prestazioni sui dati di addestramento ma non sui dati di test).
Gli strati intermedi utilizzano la funzione di attivazione relu, mentre lo strato
finale utilizza un'attivazione sigmoide per produrre una probabilità (un punteggio
compreso tra 0 e 1 che indica la probabilità che il campione abbia l'obiettivo "1", ovvero
la probabilità che l'esame sia positivo). Una relu (unità lineare rettificata) è una
funzione che ha lo scopo di azzerare i valori negativi (si veda la figura 4.2), mentre una
sigmoide "schiaccia" i valori arbitrari nell'intervallo [0, 1] (si veda la figura 4.3),
producendo qualcosa che può essere interpretato come una probabilità.
con modelli che producono probabilità. L'entropia incrociata è una quantità del campo
della teoria dell'informazione che misura la distanza tra le distribuzioni di probabilità o,
in questo caso, tra la distribuzione della verità e le vostre previsioni.
Per quanto riguarda la scelta dell'ottimizzatore, sceglieremo rmsprop, che di solito
è una buona scelta predefinita per quasi tutti i problemi.
Classificare le recensioni di film: Un esempio di 103
classificazione binaria
Ecco il passaggio in cui configuriamo il modello con l'ottimizzatore rmsprop e la
funzione di perdita binary_crossentropy. Si noti che monitoreremo anche
l'accuratezza durante l'addestramento.
model.compile(optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"])
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
Ora alleneremo il modello per 20 epoche (20 iterazioni su tutti i campioni dei dati di
addestramento) in mini-batch di 512 campioni. Allo stesso tempo, monitoreremo la
perdita e l'accuratezza sui 10.000 campioni che abbiamo separato. Per farlo, passiamo i
dati di validazione come argomento validation_data.
storia = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
Con la CPU, questo richiede meno di 2 secondi per ogni epoch: l'addestramento termina
in 20 secondi. Alla fine di ogni epoca, si verifica una leggera pausa mentre il modello
calcola la perdita e l'accuratezza sui 10.000 campioni dei dati di convalida.
Si noti che la chiamata a model.fit() restituisce un oggetto History, come si è
visto nel capitolo 3. Questo oggetto ha un membro history, che è un dizionario contenente i
dati di ogni cosa accaduta durante l'addestramento. Questo oggetto ha un membro
history, che è un dizionario contenente dati su ogni cosa accaduta durante
l'addestramento. V e d i a m o l o :
Il dizionario contiene quattro voci: una per ogni metrica monitorata durante la
formazione e la validazione. Nei due listati seguenti, usiamo Matplotlib per tracciare la
perdita di addestramento e quella di validazione (vedi figura 4.4), nonché l'accuratezza
di addestramento e quella di validazione (vedi figura 4.5). Si noti che i risultati
potrebbero variare leggermente a causa di una diversa inizializzazione casuale del
modello.
plt.clf()
Cancella la figura
acc = history_dict["accuracy"]
val_acc =
history_dict["val_accuracy"]
plt.plot(epochs, acc, "bo", label="Acc di addestramento")
plt.plot(epochs, val_acc, "b", label="Acc di validazione")
plt.title("Accuratezza di addestramento e validazione")
plt.xlabel("Epoche")
plt.ylabel("Precisione
") plt.legend()
plt.show()
model = keras.Sequential([
layers.Dense(16, activation="relu"),
layers.Dense(16, activation="relu"),
106 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
layers.Dense(1, attivazione="sigmoide")
])
model.compile(optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"])
model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)
>>> model.predict(x_test)
array([[ 0.98006207]
[ 0.99758697]
[ 0.99975556]
...,
[ 0.82167041]
[ 0.02885115]
[ 0.65371346]], dtype=float32)
Come si può vedere, il modello è sicuro per alcuni campioni (0,99 o più, o 0,01 o
meno) ma meno sicuro per altri (0,6, 0,4).
>>> len(train_data)
8982
>>> len(test_data)
2246
Come per le recensioni di IMDB, ogni esempio è un elenco di numeri interi (indici di parole):
>>> train_data[10]
[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979,
3554, 14, 46, 4689, 4329, 86, 61, 3499, 4795, 14, 61, 451, 4329, 17, 12]
word_index = reuters.get_word_index()
reverse_word_index = dict(
[(valore, chiave) per (chiave, valore) in word_index.items()])
decoded_newswire = " ".join(
[reverse_word_index.get(i - 3, "?") per i in train_data[0]])
Si noti che gli indici sono sfalsati di 3 perché 0, 1 e 2 sono indici riservati per
"padding", "inizio sequenza" e "sconosciuto".
L'etichetta associata a un esempio è un numero intero compreso tra 0 e 45, un indice di
argomento:
>>> train_labels[10]
3
Per vettorializzare le etichette, ci sono due possibilità: si può lanciare l'elenco delle
etichette come un tensore intero, oppure si può usare la codifica one-hot. La codifica
one-hot è un formato ampiamente utilizzato per i dati categoriali, chiamato anche
codifica categoriale. In questo caso, la codifica one-hot delle etichette consiste
nell'incorporare ogni etichetta come un vettore tutto zero con un 1 al posto dell'indice
dell'etichetta. Il seguente elenco mostra un esempio.
model = keras.Sequential([
layers.Dense(64, attivazione="relu"),
layers.Dense(64, attivazione="relu"),
layers.Dense(46,
attivazione="softmax")
])
model.compile(optimizer="rmsprop",
loss="categorical_crossentropy",
metrics=["accuracy"])
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = y_train[:1000]
partial_y_train = y_train[1000:]
storia = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
loss = history.history["loss"]
val_loss =
history.history["val_loss"] epochs =
range(1, len(loss) + 1)
plt.plot(epochs, loss, "bo", label="Training loss")
plt.plot(epochs, val_loss, "b", label="Validation loss")
plt.title("Training and validation loss")
plt.xlabel("Epochs")
plt.ylabel("Perdit
a") plt.legend()
plt.show()
model = keras.Sequential([
layers.Dense(64, activation="relu"),
112 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
layers.Dense(64, attivazione="relu"),
layers.Dense(46, attivazione="softmax")
])
model.compile(optimizer="rmsprop",
loss="categorical_crossentropy",
metrics=["accuracy"])
model.fit(x_train,
y_train,
epochs=9,
batch_size=512)
risultati = model.evaluate(x_test, y_test)
>>> risultati
[0.9565213431445807, 0.79697239536954589]
previsioni = model.predict(x_test)
>>> previsioni[0].shape
(46,)
>>> np.sum(predictions[0])
1.0
Classificare i telegiornali: Un esempio di classificazione 113
multiclasse
La voce più grande è la classe prevista, quella con la probabilità più alta:
>>> np.argmax(previsioni[0])
4
y_train = np.array(train_labels)
y_test = np.array(test_labels)
L'unica cosa che cambierebbe in questo approccio è la scelta della funzione di perdita.
La funzione di perdita utilizzata nel listato 4.21, categorical_crossentropy, prevede
che le etichette seguano una codifica categoriale. Con etichette intere, si dovrebbe usare
sparse_categorical_ crossentropy:
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model = keras.Sequential([
layers.Dense(64, attivazione="relu"),
layers.Dense(4, attivazione="relu"),
layers.Dense(46,
attivazione="softmax")
])
model.compile(optimizer="rmsprop",
loss="categorical_crossentropy",
metrics=["accuracy"])
model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=128,
validation_data=(x_val, y_val))
Il modello raggiunge ora un'accuratezza di convalida del 71% circa, con un calo
assoluto dell'8%. Questo calo è dovuto principalmente al fatto che stiamo cercando di
comprimere molte informazioni (abbastanza
Prevedere i prezzi delle case: Un esempio di 113
regressione
informazioni per recuperare gli iperpiani di separazione di 46 classi) in uno spazio
intermedio troppo poco dimensionale. Il modello è in grado di stipare la maggior
parte delle informazioni necessarie in queste rappresentazioni quadridimensionali,
ma non tutte.
4.2.9 Conclusione
Ecco cosa si deve dedurre da questo esempio:
◾ Se si cerca di classificare i punti di dati tra N classi, il modello deve terminare
con un livello Dense di dimensione N.
◾ In un problema di classificazione multiclasse a etichetta singola, il modello
dovrebbe terminare con un'attivazione softmax in modo da produrre una
distribuzione di probabilità sulle N classi di uscita.
◾ La crossentropia categoriale è quasi sempre la funzione di perdita da
utilizzare per questi problemi. Essa minimizza la distanza tra le distribuzioni
di probabilità prodotte dal modello e la vera distribuzione degli obiettivi.
◾ Esistono due modi per gestire le etichette nella classificazione multiclasse:
– Codificare le etichette tramite una codifica categoriale (nota anche come
codifica one-hot) e utilizzare la categorical_crossentropy come funzione
di perdita.
– Codificare le etichette come numeri interi e utilizzare la funzione di perdita
sparse_categoriche_cross- entropia
◾ Se dovete classificare i dati in un gran numero di categorie, dovete evitare di
creare colli di bottiglia informativi nel vostro modello a causa di livelli
intermedi troppo piccoli.
Esaminiamo i dati:
>>> train_data.shape
(404, 13)
>>> test_data.shape
(102, 13)
Come si può vedere, abbiamo 404 campioni di addestramento e 102 campioni di test,
ciascuno con 13 caratteristiche numeriche, come il tasso di criminalità pro capite, il
numero medio di stanze per abitazione, l'accessibilità alle autostrade e così via.
Gli obiettivi sono i valori mediani delle abitazioni occupate dai proprietari, in migliaia di dollari:
>>> addestramento_obiettivi
[ 15.2, 42.3, 50. ... 19.4, 19.4, 29.1]
I prezzi sono in genere compresi tra 10.000 e 50.000 dollari. Se vi sembrano prezzi
bassi, r i c o r d a t e che si trattava della metà degli anni '70 e che questi prezzi non
sono stati adeguati all'inflazione.
media =
116 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
train_data.mean(axis=0)
train_data -= media
Prevedere i prezzi delle case: Un esempio di 117
regressione
std = train_data.std(axis=0)
train_data /= std
test_data -= media
test_data /= std
Si noti che le quantità utilizzate per normalizzare i dati di test sono calcolate con i
dati di addestramento. Nel flusso di lavoro non si deve mai usare una quantità
calcolata sui dati di test, anche per una cosa semplice come la normalizzazione dei
dati.
def build_model():
model = keras.Sequential([
Poiché dobbiamo istanziare lo
layers.Dense(64, stesso modello più volte,
activation="relu"), usiamo una funzione per
layers.Dense(64, costruirlo.
activation="relu"), layers.Dense(1)
])
model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"])
restituire il modello
Il modello termina con una singola unità e nessuna attivazione (sarà uno strato
lineare). Questa è una configurazione tipica per la regressione scalare (una
regressione in cui si cerca di prevedere un singolo valore continuo). L'applicazione
di una funzione di attivazione limiterebbe l'intervallo di valori che l'output può
assumere; per esempio, se si applicasse una funzione di attivazione sigmoide all'ultimo
strato, il modello potrebbe imparare a prevedere solo valori compresi tra 0 e 1. Qui,
poiché l'ultimo strato è puramente lineare, il modello è libero di imparare a
prevedere valori in qualsiasi intervallo.
Si noti che il modello viene compilato con la funzione di perdita mse - errore
quadratico medio, il quadrato della differenza tra le previsioni e gli obiettivi. Si tratta di
una funzione di perdita ampiamente utilizzata per i problemi di regressione.
Durante l'addestramento monitoriamo anche una nuova metrica: l'errore assoluto medio
(MAE). È il valore assoluto della differenza tra le previsioni e gli obiettivi. Ad esempio,
un MAE di 0,5 su questo problema significa che le previsioni sono sbagliate in media di
500 dollari.
per essere molto piccolo (per esempio, circa 100 esempi). Di conseguenza, i
punteggi di validazione potrebbero cambiare molto a seconda dei punti dati scelti
per la validazione e di quelli scelti per l'addestramento: i punteggi di validazione
Prevedere i prezzi delle case: Un esempio di 119
regressione
potrebbe avere un'elevata varianza rispetto allo split di validazione. Questo ci
impedirebbe d i valutare in modo affidabile il nostro modello.
La pratica migliore in queste situazioni è quella di utilizzare la convalida incrociata K-fold
(vedi figura 4.8).
Punteggi
Piegatura 1 Convalida Formazione Formazione
o di
convalida
#1
Punteggi Punteggio
Piegatura 2
o di finale:
convalida medio
Formazione Convalida Formazione
#2
Piegatura 3
Punteggi
Formazione Formazione Convalida
o di
Figura 4.8 Convalida incrociata K-fold con convalida
#3
K=3
k = 4
num_val_samples = len(train_data) //
k num_epochs = 100
Prepara i
all_scores = [] dati di convalida:
per i in range(k): dati dalla
print(f "Elaborazione della piega partizione #k
#{i}")
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
partial_train_data = np.concatenate(
Prepara i dati di
[train_data[:i * num_val_samples], addestramento: i dati di
train_data[(i + 1) * num_val_samples:]], tutte le altre partizioni.
axis=0)
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
Costruisce il modello Keras
(già compilato)
train_targets[(i + 1) * num_val_samples:]],
axis=0)
modello = build_model()
Addestra il
model.fit(partial_train_data, partial_train_targets, modello (in
modalità
silenziosa,
verbose = 0)
epochs=num_epochs, batch_size=16, verbose=0)
120 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
num_epoche = 500
Prepara i
all_mae_histories = [] dati di convalida:
for i in range(k): dati dalla
print(f "Elaborazione della partizione #k
piega #{i}")
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) *
num_val_samples]
partial_train_data = np.concatenate(
Prepara i dati di
[train_data[:i * num_val_samples], addestramento: i
train_data[(i + 1) * num_val_samples:]], dati di tutte le altre
axis=0) partizioni.
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
train_targets[(i + 1) * Costruisce il
num_val_samples:]], axis=0) modello Keras
modello = build_model() (già compilato)
history = model.fit(partial_train_data, partial_train_targets,
Addestra il
validation_data=(val_data, val_targets), modello
epochs=num_epochs, batch_size=16, verbose=0) (in modalità
mae_history = history.history["val_mae"] silenziosa,
all_mae_histories.append(mae_history) verbose=0)
Possiamo quindi calcolare la media dei punteggi MAE per epoche per tutte
le pieghe.
Listato 4.28 Creazione della storia dei punteggi medi successivi della validazione K-fold
media_mae_storia = [
np.mean([x[i] per x in all_mae_histories]) per i in range(num_epochs)]
La lettura del grafico può risultare un po' difficile a causa di un problema di scala: il
MAE di convalida per le prime epoche è nettamente superiore ai valori successivi.
Tralasciamo i primi 10 punti di dati, che hanno una scala diversa dal resto della curva.
truncated_mae_history = media_mae_history[10:]
plt.plot(range(1, len(truncated_mae_history) + 1), truncated_mae_history)
plt.xlabel("Epoche")
plt.ylabel("Validazione MAE")
plt.show()
Come si può vedere nella figura 4.10, il MAE della validazione smette di migliorare
significativamente dopo 120-140 epoche (questo numero include le 10 epoche che
abbiamo omesso). Oltre questo punto, inizia l'overfitting.
Una volta terminata la regolazione degli altri parametri del modello (oltre al
numero di epoche, si potrebbe anche regolare la dimensione degli strati intermedi),
si può addestrare un modello di produzione finale su tutti i dati di addestramento,
con i parametri migliori, e poi esaminare le sue prestazioni sui dati di test.
Figura 4.10 MAE della validazione per epoch, esclusi i primi 10 punti di dati
>>> test_mae_score
2.4642276763916016
Siamo ancora indietro di poco meno di 2.500 dollari. È un miglioramento! Come per i
due compiti precedenti, si può provare a variare il numero di strati del modello o il
numero di unità per strato, per vedere se si riesce a ottenere un errore di prova
inferiore.
Si prevede che la prima casa dell'insieme di test abbia un prezzo di circa 10.000 dollari.
4.3.6 Conclusione
Ecco cosa si deve dedurre da questo esempio di regressione scalare:
◾ La regressione viene eseguita utilizzando funzioni di perdita diverse da quelle
utilizzate per la classificazione. L'errore quadratico medio (MSE) è una funzione
di perdita comunemente utilizzata per la regressione.
124 CAPITOLO 4 Come iniziare con le reti neurali: Classificazione e regressione
Sintesi
◾ I tre tipi più comuni di compiti di apprendimento automatico su dati vettoriali
sono la classificazione binaria, la classificazione multiclasse e la regressione
scalare.
– Le sezioni "Conclusione" all'inizio del capitolo riassumono i punti importanti
appresi per ciascun compito.
– La regressione utilizza funzioni di perdita e metriche di valutazione diverse
rispetto alla classificazione.
◾ Di solito è necessario preelaborare i dati grezzi prima di inserirli in una rete neurale.
◾ Quando i dati hanno caratteristiche con intervalli diversi, scalare ogni
caratteristica in modo indipendente come parte della preelaborazione.
◾ Con il progredire dell'addestramento, le reti neurali finiscono per sovra-adattarsi e
ottenere risultati peggiori su dati mai visti prima.
◾ Se non s i dispone di molti dati di addestramento, si può utilizzare un modello
piccolo con solo uno o due livelli intermedi, per evitare un grave overfitting.
◾ Se i dati sono suddivisi in molte categorie, si possono creare dei colli di bottiglia
se i livelli intermedi sono troppo piccoli.
◾ Quando si lavora con pochi dati, la validazione K-fold può aiutare a valutare in
modo affidabile il modello.
Fondamenti
di apprendimento
automatico
Dopo i tre esempi pratici del capitolo 4, dovreste aver iniziato a capire come
affrontare i problemi di classificazione e regressione con le reti neurali e avete
visto il problema centrale dell'apprendimento automatico: l'overfitting. Questo
capitolo formalizzerà alcune delle vostre nuove intuizioni sull'apprendimento
automatico in un solido quadro concettuale, evidenziando l'importanza di una
valutazione accurata dei modelli e l'equilibrio tra addestramento e
generalizzazione.
121
122 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
Dopo poche epoche, le prestazioni su dati mai visti hanno iniziato a divergere da
quelle sui dati di addestramento, che migliorano sempre con il progredire
dell'addestramento. I modelli hanno iniziato ad adattarsi eccessivamente. L'overfitting si
verifica in ogni problema di apprendimento automatico.
Il problema fondamentale dell'apprendimento automatico è la tensione tra
ottimizzazione e generalizzazione. L'ottimizzazione si riferisce al processo di
regolazione di un modello per ottenere le migliori prestazioni possibili sui dati di
addestramento (l'apprendimento nell'apprendimento automatico), mentre la
generalizzazione si riferisce alle prestazioni del modello addestrato su dati che non
ha mai visto prima. L'obiettivo del gioco è ottenere una buona generalizzazione,
naturalmente, ma non si può controllare la generalizzazione; si può solo adattare il
modello ai dati di addestramento. Se lo si fa troppo bene, si verifica un overfitting e
la generalizzazione ne risente.
Ma cosa causa l'overfitting? Come possiamo ottenere una buona generalizzazione?
Curva di
Perdi formazione
ta Curva di
valor Underfitting
validazione
e
Overfitting
Adattame
nto
robusto
Tempo di
formazione
Cosa sono questi? Non lo so nemmeno io. Ma fanno tutti parte dell'insieme di
addestramento MNIST. La cosa peggiore, tuttavia, è avere input perfettamente validi
che finiscono con l'essere etichettati in modo errato, come quelli della figura 5.3.
CARATTERISTICHE AMBIGUE
Non tutto il rumore dei dati deriva da imprecisioni: anche i dati perfettamente puliti e
ben etichettati possono essere rumorosi quando il problema comporta incertezza e
ambiguità. Nei compiti di classificazione, spesso accade che alcune regioni dello spazio
delle caratteristiche di input siano associate a più classi contemporaneamente.
Supponiamo che si stia sviluppando un modello che prende l'immagine di una banana e
predice se la banana è acerba, matura o marcia. Queste categorie non hanno confini
oggettivi, quindi la stessa immagine potrebbe essere classificata come acerba o matura
da diversi etichettatori umani. Allo stesso modo, molti problemi implicano la casualità.
Si possono usare i dati sulla pressione atmosferica per prevedere se domani pioverà, ma
le stesse misure possono essere seguite a volte dalla pioggia e a volte da un cielo sereno,
con una certa probabilità.
Un modello potrebbe adattarsi in modo eccessivo a tali dati probabilistici, essendo
troppo fiducioso su regioni ambigue dello spazio delle caratteristiche, come nella figura
5.5. Un adattamento più robusto ignorerebbe i singoli punti di dati e guarderebbe al
quadro generale.
Area di incertezza
Figura 5.5 Adattamento robusto e overfitting per un'area ambigua dello spazio delle caratteristiche
126 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
CARATTERISTICHE RARE E CORRELAZIONI SPURIE
Se avete visto solo due gatti soriani arancioni nella vostra vita, ed entrambi e r a n o
terribilmente antisociali, potreste dedurre che i gatti soriani arancioni sono generalmente
inclini ad essere antisociali. Si tratta di un overfitting: se foste stati esposti a una più
ampia varietà di gatti, compresi quelli arancioni, avreste imparato che il colore del gatto
non è ben correlato al carattere.
Allo stesso modo, i modelli di apprendimento automatico addestrati su insiemi
di dati che includono valori di caratteristiche rare sono altamente suscettibili di
overfitting. In un compito di classificazione del sentiment, se la parola "cherimoya"
(un frutto originario delle Ande) compare solo in un testo nei dati di addestramento
e questo testo è negativo nel sentiment, un modello mal regolarizzato potrebbe
attribuire un peso molto elevato a questa parola e classificare sempre come negativi
i nuovi testi che menzionano le cherimoya, mentre, oggettivamente, non c'è nulla di
negativo nelle cherimoya.1
È importante notare che non è necessario che un valore di caratteristica si
presenti solo un paio di volte per portare a correlazioni spurie. Si consideri una
parola che ricorre in 100 campioni nei dati di addestramento e che è associata a un
sentimento positivo il 54% delle volte e a un sentimento negativo il 46% delle volte.
Questa differenza potrebbe essere un puro caso statistico, ma è probabile che il modello
impari a sfruttare questa caratteristica per la sua attività di classificazione. Questa è una
delle fonti più comuni di overfitting.
Ecco un esempio lampante. Prendiamo MNIST. Creare un nuovo set di addestramento
concatenando 784 dimensioni di rumore bianco alle 784 dimensioni esistenti dei dati,
in modo che metà dei dati sia ora costituita da rumore. Per confronto, creare un set
di dati equivalente concatenando 784 dimensioni di rumore. La nostra
concatenazione di caratteristiche senza senso non influisce affatto sul contenuto
informativo dei dati: stiamo solo aggiungendo qualcosa. L'accuratezza della
classificazione umana non sarebbe affatto influenzata da queste trasformazioni.
train_images_with_noise_channels = np.concatenate(
[train_images, np.random.random((len(train_images), 784))], axis=1)
train_images_with_zeros_channels = np.concatenate(
[train_images, np.zeros((len(train_images), 784))], axis=1)
1 Mark Twain lo definì addirittura "il frutto più delizioso conosciuto dagli uomini".
Generalizzazione: L'obiettivo 127
dell'apprendimento automatico
Listato 5.2 Addestramento dello stesso modello su dati MNIST con canali di rumore o c a n a l i tutti a zero
da tensorflow importa keras
da tensorflow.keras importa strati
def get_model():
model = keras.Sequential([
layers.Dense(512, attivazione="relu"),
layers.Dense(10,
attivazione="softmax")
])
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
restituire il modello
model = get_model()
history_noise = model.fit(
train_images_with_noise_channels, train_labels,
epochs=10,
batch_size=128,
validation_split=0.2)
model = get_model()
history_zeros = model.fit(
train_images_with_zeros_channels, train_labels,
epochs=10,
batch_size=128,
validation_split=0.2)
selezione prima dell'addestramento. Limitare i dati di IMDB alle 10.000 parole più
comuni era una forma grezza di selezione delle caratteristiche, ad esempio. Il modo
tipico di selezionare le caratteristiche è quello di calcolare un punteggio di utilità per
ogni caratteristica disponibile - una misura di quanto sia informativa la caratteristica
rispetto al compito, come l' informazione reciproca tra la caratteristica e le etichette - e
mantenere solo le caratteristiche che sono al di sopra di una certa soglia. In questo modo
si filtrano i canali di rumore bianco dell'esempio precedente.
Listato 5.4 Adattamento di un modello MNIST con etichette rimescolate in modo casuale
random_train_labels = train_labels[:]
np.random.shuffle(random_train_labels)
model = keras.Sequential([
layers.Dense(512, activation="relu"),
Generalizzazione: L'obiettivo 129
dell'apprendimento automatico
layers.Dense(10, attivazione="softmax")
])
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.fit(train_images, random_train_labels,
epochs=100,
batch_size=128,
validation_split=0.2)
In realtà, non è nemmeno necessario farlo con i dati MNIST: basta generare input di
rumore bianco ed etichette casuali. Si potrebbe adattare un modello anche a questo,
purché abbia abbastanza parametri. Il modello finirebbe per memorizzare gli input
specifici, proprio come un dizionario Python.
Se questo è il caso, allora come mai i modelli di deep learning generalizzano? Non
dovrebbero semplicemente imparare una mappatura ad hoc tra gli input e gli obiettivi
dell'addestramento, come una sorta di dettatura? Che aspettativa abbiamo che questa
mappatura funzioni per i nuovi input?
Come si evince, la natura della generalizzazione nell'apprendimento profondo ha poco a
che fare con
con i modelli di deep learning stessi, e molto a che fare con la struttura delle
informazioni nel mondo reale. Diamo un'occhiata a ciò che sta realmente accadendo.
L'IPOTESI DEL MANIFOLD
L'input di un classificatore MNIST (prima della preelaborazione) è una matrice 28
× 28 di numeri interi compresi tra 0 e 255. Il numero totale di possibili valori di
input è quindi 256 alla potenza di 784, molto più grande del numero di atomi
dell'universo. Il numero totale di possibili valori di input è quindi 256 alla potenza
di 784, molto più grande del numero di atomi dell'universo. Tuttavia, pochissimi di
questi input assomigliano a campioni MNIST validi: le cifre effettivamente scritte a
mano sono soltanto
occupano un piccolo sottospazio dello spazio genitore di tutte le possibili matrici 28 × 28 uint8.
Cosa è
Inoltre, questo sottospazio non è solo un insieme di punti sparsi a caso nello spazio
genitore: è altamente strutturato.
In primo luogo, il sottospazio delle cifre valide scritte a mano è continuo: se si
prende un campione e lo si modifica leggermente, sarà ancora riconoscibile come la
stessa cifra scritta a mano. Inoltre, tutti i campioni nel sottospazio valido sono collegati
da percorsi lisci che attraversano il sottospazio. Ciò significa che se si prendono due
cifre MNIST casuali A e B, esiste una sequenza di immagini "intermedie" che
trasformano A in B, in modo che due cifre concrete siano molto vicine tra loro (vedi
figura 5.7). Forse ci saranno alcune forme ambigue vicino al confine tra due classi, ma
anche queste forme sembrerebbero comunque molto simili a cifre.
In termini tecnici, si potrebbe dire che le cifre scritte a mano formano un collettore
all'interno dello spazio delle possibili matrici 28 × 28 uint8. È una parola grossa, ma il
concetto è piuttosto intuitivo. Un "manifold" è un sottospazio di dimensioni inferiori di
un qualche spazio genitore che è
localmente simile a uno spazio lineare (euclideo). Per esempio, una curva liscia nel
piano è un manifold 1D all'interno di uno spazio 2D, perché per ogni punto della curva è
130 CAPITOLO 5 Fondamenti dell'apprendimento
possibile tracciare una tangente (la curva può essere approssimata da una linea in ogni
automatico
punto). Una superficie liscia in uno spazio 3D è un manifesto 2D. E così via.
Generalizzazione: L'obiettivo 131
dell'apprendimento automatico
Figura 5.7 Diverse cifre MNIST si trasformano gradualmente l'una nell'altra, mostrando che lo spazio
delle cifre scritte a mano forma un "collettore". Questa immagine è stata generata usando il codice
del capitolo 12.
Più in generale, l'ipotesi del collettore sostiene che tutti i dati naturali si trovano su un
collettore a bassa dimensionalità all'interno dello spazio ad alta dimensione in cui sono
codificati. È un'affermazione piuttosto forte sulla struttura dell'informazione
nell'universo. Per quanto ne sappiamo, è accurata ed è il motivo per cui il deep learning
funziona. È vero per le cifre MNIST, ma anche per i volti umani, la morfologia degli
alberi, i suoni della voce umana e persino il linguaggio naturale.
L'ipotesi del manifesto implica che
◾ I modelli di apprendimento automatico devono adattarsi solo a sottospazi
relativamente semplici, a bassa dimensione e altamente strutturati all'interno del
loro spazio potenziale di input (mani- ficazioni latenti).
◾ All'interno di uno di questi collettori, è sempre possibile interpolare tra due input,
cioè trasformarne uno in un altro attraverso un percorso continuo lungo il quale
tutti i punti cadono sul collettore.
La capacità di interpolare tra i campioni è la chiave per comprendere la
generalizzazione nel deep learning.
L'INTERPOLAZIONE COME FONTE DI GENERALIZZAZIONE
Se si lavora con punti di dati che possono essere interpolati, si può iniziare a dare un
senso a punti mai visti prima mettendoli in relazione con altri punti che si trovano
vicini sul manifold. In altre parole, è possibile dare un senso alla totalità dello spazio
utilizzando solo un campione dello spazio. È possibile utilizzare l'interpolazione per
riempire gli spazi vuoti.
Si noti che l'interpolazione sul manifold latente è diversa dall'interpolazione
lineare nello spazio genitore, come illustrato nella figura 5.8. Ad esempio, la media
dei pixel tra due cifre MNIST di solito non è una cifra valida.
È importante sottolineare che, sebbene l'apprendimento profondo raggiunga la
generalizzazione attraverso l'interpolazione su un'approssimazione appresa del
manifold dei dati, sarebbe un errore pensare che l'interpolazione sia tutto ciò che
riguarda la generalizzazione. È la punta dell'iceberg. L'interpolazione può solo
aiutare a dare un senso a cose molto simili a quelle già viste:
132 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
Interpolazione del
collettore (punto
intermedio
sul manifold latente)
Figura 5.8 Differenza tra
interpolazione lineare e
interpolazione sul manifold latente.
Ogni punto del manifold latente di
Interpolazione lineare cifre è una cifra valida, ma la media
(media nello spazio di codifica) di due cifre di solito non lo è.
Ulteriore addestramento:
Prima Inizio un adattamento robusto si Stato finale: il modello si
dell'addestrame dell'addestramento: il ottiene, in modo adatta in modo eccessivo ai
nto: il modello si modello viene transitorio, nel processo di dati di addestramento,
avvia gradualmente morphing del modello dal raggiungendo una perdita di
con uno stato iniziale si muove verso un migliore suo stato iniziale a quello addestramento perfetta.
casuale. adattamento. finale.
Tempo di prova:
Tempo di prova: prestazioni del modello
prestazioni del overfit
modello robusto su su nuovi punti di dati
nuovi punti di dati
Figura 5.10 Passaggio da un modello casuale a un modello overfit e raggiungimento di un adattamento robusto come stato
intermedio
Lo spostamento lungo la curva appresa dal modello a quel punto si avvicinerà allo
spostamento lungo l'effettivo manifold latente dei dati; in questo modo, il modello
sarà in grado di dare un senso a input mai visti prima attraverso l'interpolazione tra
gli input di addestramento.
Oltre al fatto banale che hanno una potenza rappresentativa sufficiente, ci sono
alcune proprietà dei modelli di apprendimento profondo che li rendono
particolarmente adatti all'apprendimento di manifold latenti:
◾ I modelli di apprendimento profondo implementano una mappatura liscia e
134 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
continua dai loro ingressi alle loro uscite. Deve essere liscia e continua perché
deve essere necessariamente differenziabile (altrimenti non si potrebbe fare la
discesa del gradiente).
Generalizzazione: L'obiettivo 135
dell'apprendimento automatico
Questa levigatezza aiuta ad approssimare i collettori latenti, che seguono le
stesse proprietà.
◾ I modelli di apprendimento profondo tendono a essere strutturati in modo da
rispecchiare la "forma" delle informazioni presenti nei dati di addestramento
(tramite i priori di architettura). Questo è particolarmente il caso dei modelli
di elaborazione delle immagini (discussi nei capitoli 8 e 9) e dei modelli di
elaborazione delle sequenze (capitolo 10). Più in generale, le reti neurali
profonde strutturano le loro rappresentazioni apprese in modo gerarchico e
modulare, che richiama il modo in cui sono organizzati i dati naturali.
I DATI DI FORMAZIONE SONO FONDAMENTALI
Sebbene il deep learning sia effettivamente adatto all'apprendimento manifold, il
potere di generalizzare è più una conseguenza della struttura naturale dei dati che
una conseguenza di qualsiasi proprietà del modello. La generalizzazione è possibile solo se
i dati formano un insieme in cui i punti possono essere interpolati. Quanto più le
caratteristiche sono informative e meno rumorose, tanto più sarà possibile
generalizzare, poiché lo spazio di input sarà più semplice e meglio strutturato. La
cura dei dati e l'ingegneria delle caratteristiche sono essenziali per la
generalizzazione.
Inoltre, poiché l'apprendimento profondo si basa sull'adattamento delle curve, per
ottenere buone prestazioni un modello deve essere addestrato su un campionamento
denso del suo spazio di input. Un "campionamento denso" in questo contesto significa
che i dati di addestramento devono coprire densamente la totalità del manifold dei dati
di input (vedi figura 5.11). Ciò è particolarmente vero in prossimità dei confini
decisionali. Con un campionamento sufficientemente denso, diventa possibile dare un
senso ai nuovi input interpolando i dati di addestramento passati senza dover ricorrere al
buon senso, al ragionamento astratto o alla conoscenza esterna del mondo, tutte cose a
cui i modelli di apprendimento automatico non hanno accesso.
Campionamento Campionamento
sparso: il modello denso: il modello
appreso non appreso
corrisponde allo approssima bene
spazio latente e porta lo spazio latente, e
a un'interpolazione l'interpolazione
errata. porta alla generalizzazione.
Set di
Set di formazione convalida
per l'uscita
Listato 5.5 Convalida dell'holdout (notare che le etichette sono state omesse per semplicità)
10000 np.random.shuffle(dati)
num_validazione_campioni =
136 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
Di solito è
opportuno
rimescolare i
dati.
Valutazione dei modelli di 137
apprendimento automatico
validation_data = data[:num_validation_samples]
Definisce la Definisce l'insieme di
convalida training_data = data[num_validation_samples:]
set model = get_model() addestramento
model.fit(training_data, ...)
validation_score = model.evaluate(validation_data, ...) Addestra un modello
sull'insieme di dati
dati di addestramento e la
valuta sui dati di
convalida.
Punteggi
Piegatura 1 Convalida Formazione Formazione
o di
convalida
#1
f
i
n
a
l
e
:
m
e
d
i
o
Valutazione dei modelli di 139
apprendimento automatico
Listato 5.6 Validazione incrociata K-fold (si noti che le etichette sono omesse per semplicità)
k = 3
num_validation_samples = len(data) // k
np.random.shuffle(data)
validation_scores = [] Seleziona la
per fold in range(k): partizione dei
validation_data = dati[num_validation_samples * fold: dati di
num_validation_samples * (fold + convalida
1)] training_data = np.concatenate(
dati[:num_validation_samples * fold], Crea un nuovo file
dati[num_validation_samples * (fold + 1):]) istanza del modello (non
model = get_model() addestrato)
model.fit(training_data, ...)
validation_score = model.evaluate(validation_data, ...) Punteggio di convalida:
validation_scores.append(validation_score) media dei punteggi di
validation_score = np.average(validation_scores) convalida delle k pieghe.
model = get_model()
model.fit(dati, ...) Addestra il
test_score = model.evaluate(test_data, ...) modello finale su
tutti i dati non di
Utilizza il resto dei dati come dati di
test disponibili.
addestramento. Si noti che l'operatore +
rappresenta la concatenazione di
elenchi, non la somma.
Listato 5.7 Addestramento di un modello MNIST con un tasso di apprendimento erroneamente alto
model = keras.Sequential([
layers.Dense(512, attivazione="relu"),
layers.Dense(10,
attivazione="softmax")
])
model.compile(optimizer=keras.optimizers.RMSprop(1.),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.fit(train_images, train_labels,
epochs=10,
Migliorare 139
l'adattamento del
modello
batch_size=128,
validation_split=0.2)
model = keras.Sequential([
layers.Dense(512, attivazione="relu"),
layers.Dense(10,
attivazione="softmax")
])
model.compile(optimizer=keras.optimizers.RMSprop(1e-2),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.fit(train_images, train_labels,
epochs=10,
batch_size=128,
validation_split=0.2)
Figura 5.14 Effetto dell'insufficiente capacità del modello sulle curve di perdita
La curva di validazione ora appare esattamente come dovrebbe: il modello si adatta velocemente
e inizia a fare overfitting dopo 8 epoch (vedi figura 5.15).
Migliorare 143
l'adattamento del
modello
Dati
grezzi:
griglia di
pixel
model = keras.Sequential([
layers.Dense(4, attivazione="relu"),
layers.Dense(4, attivazione="relu"),
layers.Dense(1,
attivazione="sigmoid")
])
model.compile(optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"])
history_smaller_model = model.fit(
train_data, train_labels,
epochs=20, batch_size=512, validation_split=0.4)
La Figura 5.17 mostra un confronto delle perdite di convalida del modello originale e
del modello più piccolo.
Figura 5.17 Modello originale vs. modello più piccolo sulla classificazione delle recensioni di IMDB
Migliorare la 149
generalizzazione
Come si può notare, il modello più piccolo inizia l'overfitting più tardi rispetto al
modello di riferimento (dopo sei epoche anziché quattro) e le sue prestazioni si
degradano più lentamente una volta iniziato l'overfitting.
Ora, aggiungiamo al nostro benchmark un modello che ha una capacità molto
maggiore, molto più di quanto il problema giustifichi. Sebbene sia normale lavorare
con modelli che sono significativamente sovraparametrizzati per ciò che stanno
cercando di apprendere, può sicuramente esistere una capacità di memorizzazione
eccessiva. Saprete che il vostro modello è troppo grande se inizia subito a fare
overfitting e se la sua curva di perdita di convalida appare irregolare con un'alta
varianza (sebbene le metriche di convalida irregolari possano anche essere un
sintomo dell'utilizzo di un processo di convalida inaffidabile, come una divisione di
convalida troppo piccola).
model = keras.Sequential([
layers.Dense(512,
attivazione="relu"),
layers.Dense(512,
attivazione="relu"), layers.Dense(1,
attivazione="sigmoid")
])
model.compile(optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"])
history_larger_model = model.fit(
train_data, train_labels,
epochs=20, batch_size=512, validation_split=0.4)
La Figura 5.18 mostra come si comporta il modello più grande rispetto al modello di
riferimento.
Figura 5.18 Modello originale vs. modello molto più grande sulla
classificazione delle recensioni di IMDB
150 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
Il modello più grande inizia a fare overfitting quasi subito, dopo una sola epoch, e lo fa
in modo molto più grave. Anche la sua perdita di convalida è più rumorosa. La perdita
di addestramento si avvicina a zero molto rapidamente. Maggiore è la capacità del
modello, più rapidamente riesce a modellare i dati di formazione (con conseguente
bassa perdita di formazione), ma più è suscettibile all'overfiting (con conseguente
grande differenza tra la perdita di formazione e quella di validazione).
AGGIUNTA DELLA REGOLARIZZAZIONE DEL PESO
Forse conoscete il principio del rasoio di Occam: date due spiegazioni per qualcosa, la
spiegazione più probabile è quella più semplice, quella che fa meno ipotesi. Questa idea
si applica anche ai modelli appresi dalle reti neurali: dati alcuni dati di addestramento e
un'architettura di rete, più serie di valori di peso (più modelli) potrebbero spiegare i dati.
I modelli più semplici hanno meno probabilità di adattarsi eccessivamente rispetto a
quelli complessi.
Un modello semplice in questo contesto è un modello in cui la distribuzione dei valori
dei parametri ha un'entropia minore (o un modello con meno parametri, come si è
visto nella sezione precedente). Pertanto, un modo comune per mitigare l'overfitting
è quello di porre dei vincoli alla complessità di un modello, obbligando i suoi pesi
ad assumere solo valori piccoli, il che rende la distribuzione dei valori dei pesi più
regolare. Si tratta della cosiddetta regolarizzazione dei pesi, che si ottiene aggiungendo
alla funzione di perdita del modello un costo associato alla presenza di pesi grandi.
Questo costo è di due tipi:
◾ Regolarizzazione L1: il costo aggiunto è proporzionale al valore assoluto dei
coefficienti di peso (la norma L1 dei pesi).
◾ Regolarizzazione L2: il costo aggiunto è proporzionale al quadrato del valore dei
coefficienti dei pesi (la norma L2 dei pesi). La regolarizzazione L2 è chiamata anche
decadimento dei pesi nel contesto delle reti neurali. Non l a s c i a t e v i ingannare dal
nome diverso: il decadimento dei pesi è matematicamente identico alla
regolarizzazione L2.
In Keras, la regolarizzazione del peso viene aggiunta passando istanze di
regolarizzazione del peso ai livelli come argomenti di parole chiave. Aggiungiamo la
regolarizzazione del peso L2 al nostro modello iniziale di classificazione delle
recensioni di film.
Nell'elenco precedente, l2(0,002) significa che ogni coefficiente nella matrice dei pesi
del livello aggiungerà 0,002 * valore_del_coefficiente_di_peso ** 2 alla
perdita totale del modello. Poiché questa penalità viene aggiunta solo al momento
dell'addestramento, la perdita di questo modello sarà molto più alta al momento
dell'addestramento che al momento del test.
La Figura 5.19 mostra l'impatto della penalità di regolarizzazione L2. Come si
può notare, il modello con la regolarizzazione L2 è diventato molto più resistente
all'overfitting rispetto al modello di riferimento, anche se entrambi i modelli hanno
lo stesso numero di parametri.
Figura 5.19 Effetto della regolarizzazione del peso L2 sulla perdita di convalida
In alternativa alla regolarizzazione L2, è possibile utilizzare uno dei seguenti regolarizzatori
di peso di Keras.
Si noti che la regolarizzazione dei pesi è più tipicamente utilizzata per i modelli di
deep learning più piccoli. I modelli di deep learning di grandi dimensioni tendono a
essere talmente iperparametrizzati che l'imposizione di vincoli sui valori dei pesi non
ha un grande impatto sulla capacità del modello e sulla generalizzazione. In questi
casi, si preferisce una tecnica di regolarizzazione diversa: il dropout.
152 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
AGGIUNTA DI UN ABBANDONO
Il dropout è una delle tecniche di regolarizzazione più efficaci e più utilizzate per le
reti neurali; è stato sviluppato da Geoff Hinton e dai suoi studenti dell'Università di
Toronto. Il dropout, applicato a uno strato, consiste nell'eliminare in modo casuale
(azzerando) un certo numero di caratteristiche di uscita dello strato durante
l'addestramento. Supponiamo che un dato strato restituisca normalmente un vettore
[0.2, 0.5, 1.3, 0.8, 1.1] per un dato campione di input durante
l'addestramento. Dopo l'applicazione del dropout, questo vettore avrà alcune voci
nulle distribuite a caso: per esempio, [0, 0,5, 1,3, 0, 1,1]. Il tasso di abbandono è
la frazione delle caratteristiche che vengono azzerate; di solito è impostato tra 0,2 e
0,5. Al momento del test, nessuna unità viene eliminata; i valori di uscita dello
strato vengono invece ridotti di un fattore pari al tasso di abbandono, per
compensare il fatto che sono attive più unità rispetto al momento
dell'addestramento.
Si consideri una matrice NumPy contenente l'output di uno strato, layer_output,
di forma (batch_size, features). Al momento dell'addestramento, azzeriamo a caso
una frazione dei valori della matrice:
Questa tecnica può sembrare strana e arbitraria. Perché dovrebbe aiutare a ridurre
l'overfiting? Hinton dice di essersi ispirato, tra l'altro, a un meccanismo di
Migliorare la 153
generalizzazione
prevenzione delle frodi utilizzato dalle banche. Per dirla con le sue parole: "Sono
andato nella mia banca. I cassieri continuavano a cambiare e ho chiesto a uno di
loro perché. Mi ha risposto che non lo sapeva, ma che venivano spostati spesso.
154 CAPITOLO 5 Fondamenti dell'apprendimento
automatico
Ho pensato che doveva essere perché per riuscire a frodare la banca era necessaria la
collaborazione tra i dipendenti. Questo mi ha fatto capire che la rimozione casuale di un
sottoinsieme diverso di neuroni per ogni esempio avrebbe impedito le cospirazioni e
quindi ridotto l'over fitting". L'idea di base è che l'introduzione di rumore nei valori di
uscita di uno strato può interrompere modelli casuali che non sono significativi (ciò che
Hinton definisce cospirazioni), che il modello inizierà a memorizzare in assenza di
rumore.
In Keras, è possibile introdurre il dropout in un modello tramite il livello Dropout,
che viene applicato all'output del livello immediatamente precedente. Aggiungiamo due
livelli di dropout al modello IMDB per vedere come riducono l'overfitting.
model = keras.Sequential([
layers.Dense(16,
activation="relu"),
layers.Dropout(0.5),
layers.Dense(16,
activation="relu"),
layers.Dropout(0.5),
layers.Dense(1, attivazione="sigmoide")
])
model.compile(optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"])
history_dropout = model.fit(
train_data, train_labels,
epochs=20, batch_size=512, validation_split=0.4)
Sintesi
◾ Lo scopo di un modello di apprendimento automatico è quello di generalizzare,
ossia di ottenere risultati accurati su input mai visti prima. È più difficile di
quanto sembri.
◾ Una rete neurale profonda ottiene la generalizzazione apprendendo un
modello parametrico in grado di interpolare con successo tra i campioni di
addestramento: si può dire che il modello abbia appreso il "manifold latente" dei
dati di addestramento. Per questo motivo, i modelli di apprendimento
profondo possono dare un senso solo a input molto simili a quelli che hanno
visto durante l'addestramento.
◾ Il problema fondamentale dell'apprendimento automatico è la tensione tra
ottimizzazione e generalizzazione: per ottenere la generalizzazione, è necessario
innanzitutto ottenere un buon adattamento ai dati di addestramento, ma il
miglioramento dell'adattamento del modello ai dati di addestramento inizierà
inevitabilmente a danneggiare la generalizzazione dopo un po'. Ogni singola best
practice di deep learning si occupa di gestire questa tensione.
◾ La capacità di generalizzazione dei modelli di deep learning deriva dal fatto
che essi riescono a imparare ad approssimare il manifold latente dei loro dati
e possono quindi dare un senso ai nuovi input tramite interpolazione.
◾ È essenziale essere in grado di valutare accuratamente il potere di
generalizzazione del modello mentre lo si sta sviluppando. Avete a
disposizione una serie di metodi di valutazione, dalla semplice convalida in
attesa alla convalida incrociata K-fold e alla convalida incrociata K-fold iterata
con rimescolamento. Ricordate di tenere sempre un set di test completamente
separato per la valutazione finale del modello, poiché potrebbero verificarsi
fughe di informazioni dai dati di convalida al modello.
◾ Quando si inizia a lavorare su un modello, l'obiettivo è innanzitutto quello di
ottenere un modello che abbia un certo potere di generalizzazione e che sia in
grado di adattarsi in modo eccessivo. Le migliori pratiche per raggiungere
questo obiettivo includono la regolazione del tasso di apprendimento e della
dimensione del batch, l'utilizzo di priori architettonici migliori, l'aumento
della capacità del modello o semplicemente un addestramento più lungo.
◾ Quando il modello inizia ad adattarsi eccessivamente, l'obiettivo passa al
miglioramento della generalizzazione attraverso la regolarizzazione del modello. È
possibile ridurre la capacità del modello, aggiungere la regolarizzazione del dropout
o del peso e utilizzare l'arresto anticipato. Naturalmente, un set di dati più
grande o migliore è sempre il modo migliore per aiutare un modello a
156 CAPITOLO 5 Fondamenti dell'apprendimento
generalizzare. automatico
Il flusso di lavoro
universale
dell'apprendimento
automatico
Gli esempi precedenti presuppongono che si disponga già di un set di dati etichettati
da cui partire e che si possa iniziare immediatamente ad addestrare un modello. Nel
mondo reale, spesso non è così. Non si parte da un set di dati, ma da un problema.
Immaginate di avviare il vostro negozio di consulenza sull'apprendimento
automatico. Vi costituite, mettete su un sito web di lusso, informate la vostra rete
di contatti. I progetti iniziano ad arrivare:
◾ Un motore di ricerca fotografico personalizzato per un social network di
condivisione di immagini: digitate "matrimonio" e recuperate tutte le foto
che avete scattato ai matrimoni, senza bisogno di tag manuali.
◾ Segnalazione di spam e contenuti testuali offensivi tra i post di un'app di
chat in erba.
◾ Costruire un sistema di raccomandazione musicale per gli utenti di una radio
online.
◾ Rilevare le frodi con le carte di credito per un sito di e-commerce.
153
154 CAPITOLO 6 Il flusso di lavoro universale
dell'apprendimento automatico
◾ Previsione del tasso di clic degli annunci display per decidere quale annuncio
proporre a un determinato utente in un determinato momento.
◾ Segnalazione di biscotti anomali sul nastro trasportatore di una linea di produzione di biscotti.
◾ Utilizzo di immagini satellitari per prevedere la posizione di siti archeologici ancora
sconosciuti.
Nota sull'etica
A volte vi vengono proposti progetti eticamente discutibili, come "costruire
un'intelligenza artificiale che valuti l'affidabilità di una persona da una foto del suo
volto". Innanzitutto, la validità del progetto è in dubbio: non è chiaro perché
l'affidabilità dovrebbe essere riflessa sul volto di una persona. In secondo luogo,
un compito del genere apre la porta a ogni tipo di problema etico. Raccogliere un
set di dati per questo compito equivale a registrare i pregiudizi delle persone che
etichettano le immagini. I modelli che verrebbero addestrati su questi dati non
farebbero altro che codificare questi stessi pregiudizi in un algoritmo black-box che
darebbe loro una sottile patina di legittimità. In una società largamente analfabeta
come la nostra, "l'algoritmo dell'IA ha detto che questa persona non è affidabile"
sembra stranamente avere più peso e obiettività di "John Smith ha detto che
questa persona non è affidabile", nonostante il primo sia un'approssimazione
appresa del secondo. Il vostro modello riciclerebbe e renderebbe operativi su scala
gli aspetti peggiori del giudizio umano, con effetti negativi sulla vita delle persone
reali.
La tecnologia non è mai neutrale. Se il vostro lavoro ha un impatto sul mondo,
questo impatto ha una direzione morale: le scelte tecniche sono anche scelte
etiche. Siate sempre consapevoli dei valori che volete che il vostro lavoro
sostenga.
Al giorno d'oggi, ogni sondaggio telefonico tiene conto dei bias di campionamento.
Questo non significa che i bias di campionamento siano un ricordo del passato nei
sondaggi politici, tutt'altro. Ma a differenza del 1948, i sondaggisti ne sono
consapevoli e prendono provvedimenti per correggerli.
164 CAPITOLO 6 Il flusso di lavoro universale
dell'apprendimento automatico
6.1.3 Comprendere i dati
È una pratica piuttosto scorretta trattare un set di dati come una scatola nera. Prima di
iniziare l'addestramento delle modalità, è necessario esplorare e visualizzare i dati per
ottenere informazioni su ciò che li rende predittivi, in modo da informare
l'ingegnerizzazione delle funzionalità e individuare potenziali problemi.
◾ Se i vostri dati includono immagini o testo in linguaggio naturale, guardate
direttamente alcuni campioni (e le loro etichette).
◾ Se i dati contengono caratteristiche numeriche, è una buona idea tracciare
l'istogramma dei valori delle caratteristiche per avere un'idea della gamma di
valori assunti e della frequenza dei diversi valori.
◾ Se i dati includono informazioni sulla posizione, tracciateli su una mappa.
Emerge qualche schema chiaro?
◾ In alcuni campioni mancano valori per alcune caratteristiche? In tal caso, è
necessario gestire questo problema quando si preparano i dati (vedremo come
farlo nella prossima sezione).
◾ Se il vostro compito è un problema di classificazione, stampate il numero di
istanze di ciascuna classe nei vostri dati. Le classi sono rappresentate in modo
approssimativo? In caso contrario, è necessario tenere conto di questo squilibrio.
◾ Verificare la presenza di target leaking: la presenza di caratteristiche nei dati che
forniscono informazioni sui target e che potrebbero non essere disponibili in
produzione. Se si sta addestrando un modello sulle cartelle cliniche per prevedere
se una persona sarà trattata per il cancro in futuro, e le cartelle includono la
caratteristica "a questa persona è stato diagnosticato il cancro", allora i target
sono stati fatti trapelare artificialmente nei dati. Chiedetevi sempre: ogni
caratteristica dei vostri dati è disponibile nella stessa forma in produzione?
x -=
Assumendo che x sia una
x.media(asse=0) x matrice di dati 2D di forma
/= x.std(asse=0) (campioni, caratteristiche)
(continua)
Scegliere la giusta funzione di attivazione e perdita dell'ultimo strato per il proprio modello
Tipo di problema Attivazione dell'ultimo Funzione di perdita
strato
Classificazione binaria sigmoide binario_crossentropia
multilabel e
Per la maggior parte dei problemi, esistono modelli esistenti da cui partire. Non siete
la prima persona che cerca di costruire un rilevatore di spam, un motore di
raccomandazione musicale o un classificatore di immagini. Assicuratevi di fare una
ricerca sui precedenti per identificare le tecniche di ingegneria delle caratteristiche e
le architetture dei modelli che hanno maggiori probabilità di ottenere buoni risultati
nel vostro compito. Si noti che non è sempre possibile raggiungere la potenza
statistica. Se non si riesce a battere una linea di base semplice dopo aver provato
diverse architetture ragionevoli, è possibile che la risposta alla domanda che si sta
ponendo non sia presente nei dati di input. Ricordate che state
fare due ipotesi:
◾ Si ipotizza che i risultati possano essere previsti in base agli input.
◾ Si ipotizza che i dati disponibili siano sufficientemente informativi per
imparare la relazione tra input e output.
Può darsi che queste ipotesi siano false, nel qual caso bisogna tornare al tavolo da disegno.
Sintesi
◾ Quando si intraprende un nuovo progetto di apprendimento automatico,
occorre innanzitutto definire il problema da affrontare:
– Comprendete il contesto più ampio di ciò che vi state prefiggendo: qual è
l'obiettivo finale e quali sono i vincoli?
– Raccogliere e annotare un set di dati; assicurarsi di comprendere a fondo i dati.
– Scegliete come misurare il successo del vostro problema: quali metriche
monitorerete sui dati di convalida?
◾ Una volta compreso il problema e una volta che si dispone di un set di dati
appropriato, è necessario sviluppare un modello:
– Preparare i dati.
– Scegliete il vostro protocollo di valutazione: convalida di holdout? Convalida
K-fold? Quale porzione di dati utilizzare per la convalida?
– Raggiungere la potenza statistica: battere una semplice linea di base.
– Scalare: sviluppare un modello in grado di adattarsi a più persone.
Sintesi 171
Ora avete un po' di esperienza con Keras: conoscete il modello sequenziale, i livelli
densi e le API integrate per l'addestramento, la valutazione e l'inferenza:
compile(), fit(), evaluate() e predict(). Nel capitolo 3 avete anche imparato
a ereditare dalla classe Layer per creare livelli personalizzati e a usare il Tensor
Flow GradientTape perimplementare un ciclo di addestramento passo-passo.
Nei prossimi capitoli ci occuperemo di computer vision, previsione delle serie
temporali, elaborazione del linguaggio naturale e deep learning generativo.
Queste applicazioni complesse richiederanno molto di più di un'architettura
sequenziale e del ciclo predefinito fit(). Perciò, per prima cosa, vi
trasformeremo in un esperto di Keras! In questo capitolo, otterrete una panoramica
completa dei modi principali per lavorare con le API di Keras: tutto ciò che è possibile
fare con le API di Keras.
Sintesi 173
172
Diversi modi per costruire modelli 173
Keras
per gestire i casi d'uso avanzati dell'apprendimento profondo che incontrerete in
seguito.
API funzionale
+ strati
personalizzati Sottoclasse:
API API funzionale + metriche scrivere tutto
sequenziale + strati personalizzate da zero
+ strati incorporati + perdite
incorporati personalizzate
+ ...
Figura 7.1 Divulgazione progressiva della complessità per la costruzione del modello
174 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
7.2.1 Il modello sequenziale
Il modo più semplice per costruire un modello Keras è utilizzare il modello
sequenziale, che già conosciamo.
model = keras.Sequential([
layers.Dense(64, attivazione="relu"),
layers.Dense(10,
attivazione="softmax")
])
Si noti che è possibile costruire lo stesso modello in modo incrementale tramite il metodo
add(), che è simile al metodoappend() di un elenco Python.
model = keras.Sequential()
model.add(layers.Dense(64, activation="relu"))
model.add(layers.Dense(10, activation="softmax"))
Nel capitolo 4 si è visto che i livelli vengono costruiti (cioè creano i loro pesi) solo quando
vengono chiamati per la prima v o l t a . Questo perché la forma dei pesi dei livelli dipende
dalla forma del loro input: finché non si conosce la forma dell'input, non possono
essere creati.
Per questo motivo, il modello sequenziale precedente non ha pesi (listato 7.3)
finché non lo si chiama effettivamente su alcuni dati o non si chiama il suo metodo
build() con una forma di input (listato 7.4).
Listato 7.3 I modelli che non sono ancora stati costruiti non hanno pesi
A quel punto, il
>>> model.weights Il modello non è ancora stato costruito.
ValueError: I pesi per il modello sequenziale_1 non sono ancora stati creati.
Dopo che il modello è stato costruito, è possibile visualizzarne il contenuto tramite il metodo
summary(), utile per il debug.
Diversi modi per costruire modelli 175
Keras
>>> model.summary()
Modello:
"sequenziale_2"
Si tratta di un flusso di debug piuttosto comune quando si ha a che fare con livelli
che trasformano i loro input in modi complessi, come i livelli convoluzionali che si
impareranno a conoscere nel capitolo 8.
Questo oggetto input contiene informazioni sulla forma e sul tipo di dati che il
modello elaborerà:
Il modello elabora lotti in cui ogni campione ha
>>> input.shape forma (3,). Il numero di campioni per lotto è
(None, 3) variabile (indicato dalla dimensione del lotto
None).
>>> input.dtype
float32 Questi batch avranno il tipo
float32.
Un oggetto di questo tipo viene chiamato tensore simbolico. Non contiene dati reali, ma
codifica le specifiche dei tensori di dati reali che il modello vedrà quando lo si usa. Sta
per tensori di dati futuri.
Successivamente, abbiamo creato un livello e lo abbiamo richiamato sull'input:
Tutti i livelli di Keras possono essere chiamati sia su tensori reali di dati sia su
questi tensori simbolici. In quest'ultimo caso, restituiscono un nuovo tensore
simbolico, con informazioni aggiornate su forma e tipo:
>>>.features.shape
(None, 64)
Dopo aver ottenuto gli output finali, abbiamo istanziato il modello specificando i
suoi input e output nel costruttore Model:
>>> model.summary()
Modello:
"funzionale_1"
vocabolario_dimensioni
= 10000 Combina le caratteristiche in
num_tags = 100 ingresso in un unico
num_dipartimenti = 4 tensore, le caratteristiche,
concatenandole.
Defini title = keras.Input(shape=(vocabulary_size,), name="title")
re gli text_body = keras.Input(shape=(vocabulary_size,), name="text_body")
input tags = keras.Input(shape=(num_tags,), name="tags")
del
model features = layers.Concatenate()([title, text_body, tags])
lo. features = layers.Dense(64, activation="relu")(features)
num_samples = 1280
Se non si vuole fare affidamento sull'ordine degli input (ad esempio, perché si
hanno molti input o output), si possono anche sfruttare i nomi dati agli oggetti
Input e ai livelli di output e passare i dati tramite dizionari.
Listato 7.11 Addestramento di un modello fornendo i detti degli array di input e di target
model.compile(optimizer="rmsprop",
loss={"priorità": "mean_squared_error", "department":
"categorical_crossentropy"},
metriche={"priorità": ["errore_assoluto_medio"], "dipartimento":
["accuracy"]})
model.fit({"title": title_data, "text_body": text_body_data,
"tags": tags_data},
{"priorità": priority_data, "reparto": department_data},
epochs=1)
model.evaluate({"title": title_data, "text_body": text_body_data,
"tags": tags_data},
{"priorità": priority_data, "dipartimento": department_data})
priority_preds, department_preds = model.predict(
{"title": title_data, "text_body": text_body_data, "tags": tags_data})
Diversi modi per costruire modelli 181
Keras
LA POTENZA DELL'API FUNZIONALE: ACCESSO ALLA CONNETTIVITÀ DI LIVELLO
Un modello funzionale è una struttura di dati a grafo esplicita. In questo modo è
possibile ispezionare il modo in cui gli strati sono collegati e riutilizzare i nodi
precedenti del grafo (che sono le uscite degli strati) come parte di nuovi modelli. Inoltre,
si adatta perfettamente al "modello mentale" che la maggior parte dei ricercatori utilizza
quando pensa a una rete neurale profonda: un grafo di strati. Ciò consente due
importanti casi d'uso: la visualizzazione del modello e l'estrazione di caratteristiche.
Visualizziamo la connettività del modello appena definito (la topologia del
modello). È possibile tracciare un modello funzionale come un grafico con l'utilità
plot_model() (vedere figura 7.2).
keras.utils.plot_model(model, "ticket_classifier.png")
keras.utils.plot_model(
model, "ticket_classifier_with_shape_info.png", show_shapes=True)
Figura 7.3 Grafico del modello con l'aggiunta delle informazioni sulla forma
182 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
Il "None" nelle forme del tensore rappresenta la dimensione del lotto: questo
modello consente lotti di qualsiasi dimensione.
L'accesso alla connettività dei livelli significa anche che è possibile ispezionare e
riutilizzare i singoli nodi (chiamate di livello) nel grafo. La proprietà model.layers
fornisce l'elenco dei livelli che compongono il modello e per ogni livello è possibile
interrogare layer.input e layer.output.
>>> model.layers
[<tensorflow.python.keras.engine.input_layer.InputLayer at 0x7fa963f9d358>,
<tensorflow.python.keras.engine.input_layer.InputLayer at 0x7fa963f9d2e8>,
<tensorflow.python.keras.engine.input_layer.InputLayer at 0x7fa963f9d470>,
<tensorflow.python.keras.layers.merge.Concatenate at 0x7fa963f9d860>,
<tensorflow.python.keras.layers.core.Dense at 0x7fa964074390>,
<tensorflow.python.keras.layers.core.Dense at 0x7fa963f9d898>,
<tensorflow.python.keras.layers.core.Dense at 0x7fa963f95470>]
>>> model.layers[3].input
[<tf.Tensor "title:0" shape=(None, 10000) dtype=float32>,
<tf.Tensor "text_body:0" shape=(None, 10000) dtype=float32>,
<tf.Tensor "tags:0" shape=(None, 100) dtype=float32>]
>>> model.layers[3].output
<tf.Tensor "concatenate/concat:0" shape=(None, 20100) dtype=float32>
Listato 7.13 Creare un nuovo modello riutilizzando gli output dei livelli intermedi
strati[4] è il nostro intermedio
caratteristiche = model.layers[4].output Strato denso
difficulty = layers.Dense(3, activation="softmax", name="difficulty")(features)
new_model = keras.Model(
inputs=[title, text_body, tags],
output=[priorità, reparto, difficoltà])
keras.utils.plot_model(
new_model, "updated_ticket_classifier.png", show_shapes=True)
Diversi modi per costruire modelli 183
Keras
classe CustomerTicketModel(keras.Model):
Non
def init (self, num_departments): dimenticate di
super(). init () chiamare il
costruttore
super()!
self.concat_layer = layers.Concatenate() Definire i
self.mixing_layer = layers.Dense(64, activation="relu") sottolivelli
self.priority_scorer = layers.Dense(1, nel file
activation="sigmoid") self.department_classifier = costruttore.
layers.Dense(
num_dipartimenti, attivazione="softmax")
Una volta definito il modello, è possibile istanziarlo. Si noti che il modello creerà i suoi
pesi solo la prima volta che viene chiamato su alcuni dati, come le sottoclassi di Layer:
model = CustomerTicketModel(num_departments=4)
Fin qui, tutto sembra molto simile alla sottoclasse di Layer, un flusso di lavoro che
abbiamo incontrato nel capitolo 3. Qual è la differenza tra una sottoclasse di Layer e
una sottoclasse di Model? È semplice: un "livello" è un blocco di costruzione che si usa
per creare modelli, mentre un "modello" è l'oggetto di livello superiore che verrà
effettivamente addestrato, esportato per l'inferenza, ecc. In breve, un modello ha i
metodi fit(), evaluate() e predict(). I livelli no. A parte questo, le due classi
sono praticamente identiche. (Un'altra differenza è la possibilità di salvare un modello
in un file su disco, di cui parleremo tra qualche paragrafo).
È possibile compilare e addestrare una sottoclasse Model proprio come una sottoclasse
Sequential o Functional.
modello: La struttura di ciò che viene passato come
argomento di perdita e metrica deve
model.compile(optimizer="rmsprop", corrispondere esattamente a ciò che viene
restituito da call()- in questo caso, un elenco di
due elementi.
loss=["mean_squared_error", "categorical_crossentropy"],
metrics=["mean_absolute_error"], ["accuracy"]])
model.fit({"title": title_data,
"text_body": text_body_data,
"tags": tags_data},
[dati_priorità, dati_reparto], La struttura dell'obiettivo
epochs=1) deve corrispondere esattamente
model.evaluate({"title": title_data, a ciò che viene restituito dal
"text_body": text_body_data, metodo call(), in questo caso
"tags": tags_data}, un elenco di due elementi.
[dati_priorità, dati_reparto])
priority_preds, department_preds = model.predict({"title": title_data,
"text_body": text_body_data,
La struttura dei dati di input deve "tags": tags_data})
corrispondere esattamente a quella prevista
dal metodo call(): in questo caso, un dict con le
chiavi title, text_body e tag.
quantità di logica del modello, il che significa che la superficie di errore potenziale è
molto più ampia. Di conseguenza, dovrete fare più lavoro di debug. Si sta sviluppando
un nuovo oggetto Python, non solo unendo i mattoncini LEGO.
186 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
Anche i modelli funzionali e quelli sottoclassificati hanno una natura
sostanzialmente diversa. Un modello funzionale è una struttura di dati esplicita, un grafo
di livelli che è possibile visualizzare, ispezionare e modificare. Un modello sottoclassato
è un pezzo di bytecode, una classe Python con un metodo call() che contiene codice
grezzo. Questa è la fonte della flessibilità del flusso di lavoro di sottoclasse: si può
codificare qualsiasi funzionalità si desideri, ma introduce nuove limitazioni. Ad
esempio, poiché il modo in cui i livelli sono collegati tra loro è nascosto nel corpo del
metodo call(), non è possibile accedere a tali informazioni. Chiamando sum() non
si visualizza la connettività dei livelli e non si può tracciare la topologia del modello con
plot_model(). Allo stesso modo, se si dispone di un modello sottoclassificato, non è
possibile accedere ai nodi del grafo dei livelli per eseguire l'estrazione delle
caratteristiche, perché semplicemente non esiste un grafo. Una volta
il modello viene istanziato, il suo passaggio in avanti diventa una scatola nera completa.
classe Classificatore(keras.Model):
input = keras.Input(shape=(3,))
features = layers.Dense(64, activation="relu")(input)
outputs = Classifier(num_classes=10)(features)
model = keras.Model(input=input, output=output)
input = keras.Input(shape=(64,))
outputs = layers.Dense(1, activation="sigmoid")(inputs)
Diversi modi per costruire modelli 187
Keras
binary_classifier = keras.Model(inputs=inputs, outputs=outputs)
Utilizzo di cicli di formazione e valutazione 185
integrati
classe MyModel(keras.Model):
model = MyModel()
Listato 7.18 Implementazione di una metrica personalizzata mediante sottoclasse della classe Metrica
Utilizzo di cicli di formazione e valutazione 187
integrati
def result(self):
return tf.sqrt(self.mse_sum / tf.cast(self.total_samples, tf.float32))
Nel frattempo, è necessario esporre un modo per resettare lo stato della metrica
senza doverla ricostituire: ciò consente di utilizzare gli stessi oggetti metrici in
diverse epoche di addestramento o in entrambe le fasi di addestramento e
valutazione. Lo si fa con il metodo reset_state():
def reset_state(self):
self.mse_sum.assign(0.)
self.total_samples.assign(0)
Le metriche personalizzate possono essere utilizzate proprio come quelle integrate. Proviamo la
nostra metrica:
model = get_mnist_model()
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy", RootMeanSquaredError()])
model.fit(train_images, train_labels,
epochs=3,
validation_data=(val_images, val_labels))
test_metrics = model.evaluate(test_images, test_labels)
Ora è possibile vedere la barra di avanzamento di fit() che mostra l'RMSE del modello.
keras.callbacks.ModelCheckpoint
keras.callbacks.EarlyStopping
keras.callbacks.LearningRateScheduler
keras.callbacks.ReduceLROnPlateau
keras.callbacks.CSVLogger
]
model = get_mnist_model()
model.compile(optimizer="rmsprop", L'accuratezza viene
loss="sparse_categorical_crossentropy", monitorata, quindi
metrics=["accuracy"]) dovrebbe far parte
model.fit(train_images, train_labels,
delle metriche del
modello.
epochs=10,
callbacks=callbacks_list, Si noti che, poiché il callback
validation_data=(val_images, val_labels)) monitorerà la perdita di
convalida e la
per ottenere l'accuratezza della convalida, è
necessario passare validation_data alla
chiamata a fit().
Si noti che è sempre possibile salvare i modelli manualmente dopo l'addestramento, basta
richiamare il comando
model.save('my_checkpoint_path'). Per ricaricare il modello salvato, basta usare
model = keras.models.load_model("checkpoint_path.keras")
on_epoch_end(epoch, logs)
on_batch_begin(batch, logs) Chiamato alla fine
on_batch_end(batch, logs)
di ogni epoca
on_train_begin(logs) Richiamato subito prima
on_train_end(logs) dell'elaborazione di ogni lotto
Richiamato subito
dopo l'elaborazione di
ogni lotto
Chiamata alla fine Chiamata
di formazione all'inizio della
formazione
192 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
Tutti questi metodi sono chiamati con un argomento log, che è un dizionario
contenente informazioni sul batch, sull'epoch o sull'allenamento precedente, sulle
metriche di allenamento e di validazione e così via. Anche i metodi on_epoch_* e
on_batch_* hanno come primo argomento (un intero) l'indice dell'epoca o del batch.
Ecco un semplice esempio che salva un elenco di valori di perdita per lotto
durante l'addestramento e salva un grafico di questi valori alla fine di ogni epoca.
classe LossHistory(keras.callbacks.Callback):
def on_train_begin(self, logs):
self.per_batch_losses = []
model = get_mnist_model()
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.fit(train_images, train_labels,
epochs=10,
callbacks=[LossHistory()],
validation_data=(val_images, val_labels))
Figura 7.5 L'output del callback personalizzato per il tracciamento della cronologia
potenti le vostre idee. Keras vi aiuta a passare dall'idea all'esperimento nel minor
tempo possibile e le GPU veloci possono aiutarvi a passare dall'esperimento al
risultato il più rapidamente possibile. Ma che dire dell'elaborazione dei risultati
dell'esperimento? È qui che entra in gioco Tensor Board (vedi figura 7.6).
Idea
Visualizzazio Apprendiment
ne o profondo
framework: framework:
TensorBoard Keras
Risultati Esperiment
o
GPU, TPU
Figura 7.6 Il ciclo del progresso
Se si monitorano più informazioni rispetto alla sola perdita finale del modello, si
può sviluppare una visione più chiara di ciò che il modello fa e non fa, e si possono
fare progressi più rapidamente.
194 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
Il modo più semplice per usare TensorBoard con un modello Keras e il metodo fit()
è usare il callback keras.callbacks.TensorBoard.
Nel c a s o più semplice, basta specificare dove si vuole che il callback scriva i log e il
gioco è fatto:
model = get_mnist_model()
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
tensorboard = keras.callbacks.TensorBoard(
log_dir="/full_path_to_your_log_dir",
)
model.fit(train_images, train_labels,
epochs=10,
validation_data=(val_images, val_labels),
callbacks=[tensorboard])
%load_ext tensorboard
%tensorboard --logdir /full_path_to_your_log_dir
Figura 7.7 TensorBoard può essere utilizzato per monitorare facilmente le metriche di formazione e valutazione.
categoria. Esistono altre configurazioni in cui non sono presenti obiettivi espliciti, come
l'apprendimento generazionale (di cui parleremo nel capitolo 12), l'apprendimento auto-
supervisionato (in cui gli obiettivi sono ottenuti dagli input) e l'apprendimento per
rinforzo (in cui l'apprendimento è guidato da "ricompense" occasionali, come
l'addestramento di un cane). Anche se si sta facendo un normale apprendimento
supervisionato, come ricercatore si potrebbe voler aggiungere qualche novità che
richiede una flessibilità di basso livello.
Ogni volta che ci si trova in una situazione in cui la funzione integrata fit()
non è sufficiente, è necessario scrivere la propria logica di allenamento
personalizzata. Nei capitoli 2 e 3 abbiamo già visto semplici esempi di cicli di
addestramento di basso livello. Come promemoria, il contenuto di un tipico ciclo di
addestramento è simile a questo:
1 Eseguire il passaggio in avanti (calcolare l'output del modello) all'interno di un
nastro di gradienti per ottenere un valore di perdita per il lotto di dati corrente.
2 Recuperare i gradienti della perdita rispetto ai pesi del modello.
3 Aggiornare i pesi del modello in modo da ridurre il valore di perdita sul lotto di
dati corrente.
194 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
Questi passaggi vengono ripetuti per tutti i lotti necessari. Questo è essenzialmente ciò
che fit() fa sotto il cofano. In questa sezione imparerete a reimplementare fit()
da zero, il che vi fornirà tutte le conoscenze necessarie per scrivere qualsiasi algoritmo
di addestramento che vi venga in mente.
Esaminiamo i dettagli.
Potrebbe anche essere necessario tracciare la media di un valore scalare, come la perdita
del modello. È possibile farlo tramite la metrica keras.metrics.Mean:
values = [0, 1, 2, 3, 4]
mean_tracker = keras.metrics.Mean()
for value in values:
mean_tracker.update_state(valore)
print(f "Media dei valori: {mean_tracker.result():.2f}")
def reset_metrics():
per metrica in metriche:
metrica.reset_state()
loss_tracking_metric.reset_state()
A questo punto, possiamo definire il nostro ciclo di addestramento completo. Si noti che
usiamo un oggetto tf.data.Dataset per trasformare i nostri dati NumPy in un
iteratore che itera sui dati in batch di dimensione 32.
training_dataset = tf.data.Dataset.from_tensor_slices(
(train_images, train_labels))
training_dataset = training_dataset.batch(32)
epochs = 3
per epoch in range(epochs):
reset_metrics()
per input_batch, target_batch in training_dataset: logs
= train_step(input_batch, target_batch)
print(f "Risultati alla fine dell'epoca {epoch}")
per chiave, valore in logs.items():
print(f"...{chiave}: {valore:.4f}")
Ed ecco il ciclo di valutazione: un semplice ciclo for che richiama ripetutamente una
funzione test_step(), che elabora un singolo lotto di dati. La funzione
test_step() è solo un sottoinsieme della logica di train_step(). Omette il
codice che si occupa dell'aggiornamento dei pesi del modello, cioè tutto ciò che
coinvolge il GradientTape e l'ottimizzatore.
@tf.function
Questa è
def test_step(input, target): l'unica linea
predictions = model(inputs, che è
training=False) loss = loss_fn(targets, cambiata.
predictions)
logs = {}
per metrica in metrica:
metrica.update_state(target, predictions)
logs["val_" + metric.name] = metrica.result()
loss_tracking_metric.update_state(loss)
logs["val_loss"] = loss_tracking_metric.result()
return logs
Con la CPU Colab, il ciclo di valutazione è passato da 1,80 s a soli 0,8 s. Molto più
veloce!
Ricordare che, mentre si esegue il debug del codice, è preferibile eseguirlo in
modalità eager, senza alcun decoratore @tf.function. In questo modo è più facile
individuare i bug. Una volta che il codice funziona e si vuole renderlo più veloce,
aggiungere un decoratore @tf.function al passo di addestramento e al passo di
valutazione o a qualsiasi altra funzione critica per le prestazioni.
loss_tracker.update_state(loss)
Aggiorniamo la
return {"loss": loss_tracker.result()} metrica del loss
tracker che tiene
@proprietà traccia della
media
della perdita.
def metrics(self): Qui vanno elencate
restituire tutte le metriche La perdita media viene
[loss_tracker] che si desidera restituita interrogando la
azzerare tra le varie metrica del loss tracker.
epoche.
model.compile(optimizer=keras.optimizers.RMSprop())
model.fit(train_images, train_labels, epochs=3)
classe CustomModel(keras.Model):
def train_step(self, data):
input, target = data Calcolare la
con tf.GradientTape() come nastro: perdita
predizioni = self(input, training=True) tramite
loss = self.compiled_loss(targets, predictions) perdita di
gradients = tape.gradient(loss, model.trainable_weights) self.compiled_.
Scrivere i propri cicli di formazione e 203
valutazione
optimizer.apply_gradients(zip(gradients, model.trainable_weights))
self.compiled_metrics.update_state(targets, predictions)
return {m.name: m.result() for m in self.metrics}
Aggiornare le metriche del Restituisce un dict che
modello tramite mappa i nomi delle metriche
self.compiled_metrics. al loro valore attuale.
Proviamo:
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=keras.losses.SparseCategoricalCrossentropy(),
metrics=[keras.metrics.SparseCategoricalAccuracy()])
model.fit(train_images, train_labels, epochs=3)
Sono state molte informazioni, ma ora ne sapete abbastanza per usare Keras per fare
quasi tutto.
Sintesi
◾ Keras offre uno spettro di flussi di lavoro diversi, basati sul principio della
divulgazione progressiva della complessità. Tutti interagiscono senza problemi tra
loro.
◾ È possibile costruire modelli tramite la classe Sequential, tramite l'API
Functional o tramite una sottoclasse della classe Model. Nella maggior parte dei
casi, si utilizzerà l'API Functional.
◾ Il modo più semplice per addestrare e valutare un modello è attraverso i metodi predefiniti
fit() e
valutare().
◾ Le callback di Keras forniscono un modo semplice per monitorare i modelli durante la
chiamata a
fit() e agire automaticamente in base allo stato del modello.
◾ Si può anche avere il pieno controllo di ciò che fa fit() sovrascrivendo il
metodo train_ step().
◾ Oltre a fit(), è possibile scrivere i propri cicli di addestramento interamente da
zero. Questo è utile per i ricercatori che implementano nuovi algoritmi di
addestramento.
204 CAPITOLO 7 Lavorare con Keras:
Un'immersione profonda
Introduzion
e
all'apprendimento
profondo per la
visione artificiale
Questo capitolo tratta
◾ Capire le reti neurali convoluzionali (convnet)
◾ Utilizzo dell'aumento dei dati per ridurre
l'overfitting
◾ Utilizzo di una convnet preaddestrata
per l'estrazione delle caratteristiche
◾ Messa a punto di una convnet preaddestrata
La visione artificiale è la prima e più grande storia di successo del deep learning.
Ogni giorno si interagisce con i modelli di visione profonda: attraverso Google Foto, la
ricerca di immagini su Google, YouTube, i filtri video nelle app delle fotocamere, i
software OCR e molto altro. Questi modelli sono anche al centro di ricerche
all'avanguardia nel campo della guida autonoma, della robotica, della diagnosi
medica assistita dall'intelligenza artificiale, dei sistemi di cassa autonomi e
persino dell'agricoltura autonoma.
La computer vision è il dominio problematico che ha portato all'ascesa
iniziale del deep learning tra il 2011 e il 2015. Un tipo di modello di deep
learning chiamato reti neurali convoluzionali ha iniziato a ottenere risultati
straordinariamente buoni nelle competizioni di classificazione delle immagini in
quel periodo, dapprima con Dan Ciresan che ha vinto due competizioni di nicchia
(la competizione di riconoscimento dei caratteri cinesi ICDAR 2011 e la IJCNN
201
202 CAPITOLO 8 Introduzione all'apprendimento profondo per la
visione artificiale
2011, il concorso tedesco per il riconoscimento dei segnali stradali) e poi, più in
particolare, nell'autunno 2012, con la vittoria del gruppo di Hinton nella sfida di alto
profilo ImageNet per il r i c o n o s c i m e n t o visivo su larga scala. Molti altri risultati
promettenti hanno iniziato a emergere rapidamente in altre attività di computer
vision.
È interessante notare che questi primi successi non sono stati sufficienti a rendere il
deep learning mainstream all'epoca: ci sono voluti alcuni anni. La comunità di ricerca
sulla computer vision aveva investito molti anni in metodi diversi dalle reti neurali e
non e r a pronta ad abbandonarli solo perché c'era un nuovo arrivato. Nel 2013 e nel
2014, il deep learning si scontrava ancora con un forte scetticismo da parte di molti
ricercatori senior di computer vision. Solo nel 2016 è diventato finalmente dominante.
Ricordo di aver esortato un mio ex professore, nel febbraio 2014, a passare al deep
learning. "È la prossima grande cosa!" Gli dicevo. "Beh, forse è solo una moda", mi
rispose. Nel 2016, tutto il suo laboratorio si occupava di deep learning. Non si può
fermare un'idea il cui momento è arrivato.
Questo capitolo introduce le reti neurali convoluzionali, note anche come reti
convettive, il tipo di modello di apprendimento profondo che oggi viene utilizzato quasi
universalmente nelle applicazioni di computer vision. Imparerete ad applicare le reti
convoluzionali ai problemi di classificazione delle immagini, in particolare a quelli che
coinvolgono piccoli insiemi di dati di addestramento, che sono il caso d'uso più comune
se non siete una grande azienda tecnologica.
>>> model.summary()
Modello: "modello"
Si può notare che l'output di ogni livello Conv2D e MaxPooling2D è un tensore di rango
3 di forma (altezza, larghezza, canali). Le dimensioni di larghezza e altezza
tendono a ridursi man mano che si scende in profondità nel modello. Il numero di canali
è controllato d a l primo argomento passato ai livelli Conv2D (32, 64 o 128).
Dopo l'ultimo strato Conv2D, si ottiene un output di forma (3, 3, 128) - una mappa
di caratteristiche 3 × 3 di 128 canali. Il passo successivo consiste nell'inserire questo
output in un classificatore densamente connesso, come quelli che già conosciamo: una
pila di livelli densi. Questi classificatori elaborano vettori, che sono 1D, mentre
l'output attuale è un tensore di rango 3. Per colmare il divario, appiattiamo la mappa
delle caratteristiche su 128 canali. Per colmare il divario, appiattiamo gli output 3D in
1D con un livello Flatten prima di aggiungere i livelli Dense.
Infine, effettuiamo una classificazione a 10 vie, quindi l'ultimo livello ha 10 uscite e
un'attivazione softmax.
Ora addestriamo la convnet sulle cifre MNIST. Riutilizzeremo gran parte del codice
dell'esempio MNIST nel capitolo 2. Poiché stiamo eseguendo una classificazione a 10
vie con un'uscita softmax, useremo la perdita categorica crossentropy e, poiché le nostre
etichette sono numeri interi, useremo la versione rada,
sparse_categorical_crossentropy.
204 CAPITOLO 8 Introduzione all'apprendimento profondo per la
visione artificiale
"gatt
o"
Filtro
singolo
Larghezza Altezza
3 × 3 patch di ingresso
Prodotto di
punti con il
kernel
Profon
dità di Toppe trasformate
uscita
Figura 8.5 Posizioni valide delle patch 3 × 3 in una mappa di caratteristiche di ingresso 5 × 5
e colonne su ogni lato della mappa delle caratteristiche di input, in modo da poter
inserire finestre di convoluzione al centro intorno a ogni tile di input. Per una finestra
3 × 3, si aggiunge una colonna a destra, una colonna a sinistra, una riga in alto e una
riga in basso. Per una finestra 5 × 5, si aggiungono due righe (vedi figura 8.6).
ecc.
2
1
1 2
3 4
3 4
L'uso dello stride 2 significa che la larghezza e l'altezza della mappa delle
caratteristiche sono sottocampionate di un fattore 2 (oltre alle modifiche indotte
dagli effetti dei bordi). Le convoluzioni con stride sono raramente utilizzate nei modelli
di classificazione, ma sono utili per alcuni tipi di modelli, come si vedrà nel prossimo
capitolo.
Nei modelli di classificazione, al posto degli strides, si tende a utilizzare l'operazione
di max-pooling per ricampionare le mappe di caratteristiche, che abbiamo visto in
azione nel nostro primo esempio di convnet. Analizziamola in modo più approfondito.
Listato 8.5 Una convnet strutturata in modo errato a cui mancano i livelli di max-pooling
>>> model_no_max_pool.summary()
Modello: "modello_1"
Tuttavia, l'accesso all'API è limitato agli utenti di Kaggle, quindi per poter eseguire
il comando di pre-cessione è necessario prima autenticarsi. Il pacchetto kaggle
cercherà le credenziali di accesso in un file JSON situato in ~/.kaggle/kaggle.json.
Creiamo questo file.