Un solo spazio per le immagini e le parole#
Le mille categorie di ImageNet, il dataset su cui si è addestrata mezza storia della visione artificiale, contengono circa centoventi razze di cane e nessuna classe «persona» (ci sono uno «sposo», un «giocatore di baseball» e un «sommozzatore», ma l’essere umano in quanto tale non è una categoria). Quell’assenza è la conseguenza di come nasce un classificatore: qualcuno decide una lista, qualcun altro etichetta milioni di immagini secondo quella lista, e il modello impara a rispondere sempre alla stessa domanda, quale delle mille. Fuori da quell’elenco non esiste niente. Un tram non esiste, una radiografia non esiste, e «un gatto nero che salta sul muro» non esiste nemmeno come domanda: è una frase, non una classe.
La via d’uscita standard è il transfer learning, costruito nella sezione sulla classificazione: si prende una rete pre-addestrata, si toglie la testa (l’ultimo strato, quello che sceglie fra le classi), se ne monta una nuova con tante uscite quante sono le proprie classi e la si addestra su esempi etichettati a mano. Funziona, e resta il modo normale di costruire un classificatore quando le classi sono poche e stabili. Ma il conto si paga ogni volta che l’elenco cambia: una classe in più vuole immagini nuove, etichette nuove, un addestramento nuovo e un modello nuovo da mettere in servizio. Quello che il sistema sa dire viene deciso una volta per tutte, prima di partire.
La domanda, allora, è se si possa costruire un modello a cui le classi si dicano a parole, nel momento in cui servono. La risposta comincia da un cambio di domanda.
Non «che cosa è», ma «quale di queste»#
Prima dell’idea serve un attrezzo, e il libro lo ha già costruito. Nel capitolo sul linguaggio abbiamo visto che una parola si può scrivere come un vettore, cioè una fila di numeri, e che quelle file di numeri formano una mappa del significato: gatto e felino finiscono vicini, gatto e mercoledì lontanissimi, e quanto due cose siano vicine lo dice un solo numero fra \(-1\) e \(+1\): più è alto, più le due cose si somigliano. Lo spazio che serve qui è quella mappa, con un’aggiunta: dentro non ci vanno soltanto le parole, ci vanno anche le fotografie. La foto di un gatto nero su un muro deve finire più vicina alla frase «un gatto nero su un muro» di quanto sia a «una scodella di minestra». Più vicina, si badi, non sovrapposta: è una differenza che tornerà a farsi sentire alla fine della sezione.
L’idea, resa celebre da CLIP [RKH+21] nel 2021, è di addestrare due reti separate, un encoder di immagini e un encoder di testo, a scrivere le loro uscite su quell’unica mappa (in gergo: nello stesso spazio vettoriale). Il compito diventa appaiare invece che assegnare un’etichetta. Dato un mucchietto di immagini e il mucchietto mescolato delle loro didascalie, il modello deve dire chi va con chi.
Fig. 18.2 La matrice è il compito. Ogni riga porta con sé una risposta giusta e tante sbagliate, e sono queste ultime, gratuite e numerose, a fare il grosso del lavoro.#
Su un tavolo ci sono quattro fotografie e, in disordine, quattro didascalie ritagliate dal giornale. Nessuno ti dice che cosa raffigurano le foto: ti si chiede solo di appaiarle. Il gioco sembra più povero di «riconosci il soggetto», e invece chiede la stessa cosa per vie traverse, perché per appaiare bene devi comunque aver capito che nella prima foto c’è un gatto su un muro e che quella didascalia parla di un gatto su un muro.
Il vantaggio è che questo gioco non ha bisogno di nessuno che prepari le risposte. Le didascalie esistono già: ogni immagine pubblicata sul web arriva con del testo attaccato, la frase sotto la foto, la descrizione alternativa che serve a chi non vede, il titolo del prodotto in un catalogo. Sono coppie già appaiate, gratis, a milioni: per addestrare CLIP ne sono state raccolte quattrocento milioni. Nessuno le ha etichettate, nessuno ha deciso una lista di categorie. È supervisione, ma naturale: viene dal fatto che gli esseri umani, quando pubblicano un’immagine, ci scrivono accanto che cosa c’è.
Formalmente si apprendono due funzioni, \(f_{\text{img}}\) e \(f_{\text{txt}}\), che portano rispettivamente un’immagine e una sequenza di token in un unico spazio di rappresentazione \(\mathbb{R}^d\) (in CLIP, tramite una proiezione lineare posta in cima a ciascun encoder). L’encoder visivo è una CNN o un Vision Transformer [DBK+21]; quello testuale è un Transformer con maschera causale, da cui si preleva la rappresentazione dell’ultimo token. Le uscite vengono normalizzate,
dove \(\tilde{\mathbf{I}}_i\) è l’immagine \(i\)-esima del batch (il tensore grezzo, quello che l’overview chiamava \(\mathbf{I}\)), \(\tilde{\mathbf{T}}_j\) la sequenza di token della didascalia \(j\)-esima e \(\mathbf{I}_i, \mathbf{T}_j \in \mathbb{R}^d\) i due embedding. La notazione è quella del paper di CLIP, in cui \(\mathbf{I}\) e \(\mathbf{T}\) denotano gli embedding normalizzati e non i dati grezzi: nelle formule di CLIP \(\mathbf{I}_i\) è un vettore, non un reticolo di pixel. È una deroga dichiarata alla convenzione del libro, che riserva il maiuscolo grassetto alle matrici: il maiuscolo qui viene dal paper, e a fare il lavoro resta il grassetto, che dice che l’oggetto ha più di una componente. Il \(T\) tondo che si incontra altrove nel capitolo (il numero di token di un prompt) è invece un conteggio. I due embedding vivono così sulla sfera unitaria: il loro prodotto scalare \(\langle \mathbf{I}_i, \mathbf{T}_j \rangle\) è esattamente il coseno dell’angolo fra i due, un numero in \([-1, 1]\).
Il compito di pretesto è una classificazione a \(B\) vie definita dal batch stesso: data l’immagine \(i\), indovinare quale delle \(B\) didascalie presenti sia la sua. Non c’è alcuna ontologia fissata a priori, e il «vocabolario» delle descrizioni è aperto quanto la lingua. È un caso di apprendimento auto-supervisionato di famiglia contrastiva, quella che il capitolo sui world model metterà accanto alla generativa e alla predittiva nello spazio latente: si impara una geometria, avvicinando ciò che va insieme e allontanando ciò che non va insieme.
Adesso che il gioco è chiaro, la griglia di Fig. 18.2 dice perché conti tanto la dimensione del batch, cioè quante coppie si mettono sul tavolo insieme a ogni passo di addestramento. Con \(B\) coppie ogni riga porta una risposta giusta e \(B - 1\) sbagliate: raddoppiare il batch raddoppia le alternative sbagliate che ogni immagine deve scartare, e l’esame si fa più difficile. Quanto costi tenere il tavolo grande lo vediamo fra poco, perché è il vincolo che decide la forma di tutto il metodo.
Fig. 18.3 Due encoder, una mappa sola. Le somiglianze di un gruppo di \(B\) coppie formano una tabella \(B \times B\): sulla diagonale gli abbinamenti giusti, in tutte le altre caselle quelli sbagliati. Il disegno a sinistra è uno schema: quanto le due frecce siano davvero vicine lo misureremo più avanti, ed è meno di quel che sembra.#
La Fig. 18.3 mostra la struttura che ne esce, ed è tutta la sezione in un disegno. Le due reti (ciascuna è una pila di strati, cioè una torre) non si scambiano niente durante il calcolo, e si incontrano solo alla fine, in un prodotto scalare. Sono due torri separate, come quelle del recupero che tengono le domande da una parte e i passaggi dall’altra; nel Transformer che traduce, invece, la torre che scrive consulta a ogni piano quella che legge. Le caselle fuori dalla diagonale hanno un nome, i negativi: sono gli abbinamenti sbagliati che il caso ha messo insieme nello stesso batch.
L’esame si fa in due sensi#
A questo punto serve una funzione di costo che dica al modello che cosa fare di quella tabella (in inglese si chiama loss, ed è il nome che si sente più spesso; le due parole indicano la stessa cosa). La richiesta è semplice da enunciare: i numeri sulla diagonale devono salire, tutti gli altri scendere. Il modo di ottenerlo è la cross-entropy, che misura quanto si paga caro sbagliare una domanda a risposta multipla; e qui la domanda a risposta multipla se la costruisce il batch da solo.
Guarda la griglia della figura una riga alla volta. La prima riga è un’interrogazione a risposta multipla: «ecco l’immagine numero uno, quale delle quattro didascalie è la sua?». Il modello risponde con quattro numeri, e la risposta giusta è sempre la prima cella, quella sulla diagonale. Il costo misura quanto la risposta giusta è stata considerata probabile: se il modello le dà il 90% di fiducia paga pochissimo, se le dà il 25% (come tirando a caso fra quattro) paga parecchio.
I quattro numeri sono percentuali di fiducia e insieme fanno cento, quindi quello che manca alla didascalia giusta se l’è preso qualcun altro, e quasi sempre è la didascalia sbagliata che le somiglia di più. È su quella che il modello lavora, mentre le due che non c’entravano niente le lascia stare.
Poi si rifà lo stesso identico esame guardando le colonne: «ecco la didascalia numero uno, quale delle quattro immagini descrive?». Le due interrogazioni non sono la stessa cosa, perché una didascalia potrebbe essere la più adatta a una foto senza che quella foto sia la più adatta a lei. Si fanno entrambe e si fa la media: da qui l’aggettivo simmetrica che si attacca a questa loss.
La forma generale è la InfoNCE, introdotta da van den Oord e colleghi per il contrastive predictive coding [vdOLV18]:
dove \(\mathbf{u}\) è l’ancora, \(\mathbf{v}^{+}\) il suo positivo, \(\mathbf{v}_1, \dots, \mathbf{v}_B\) l’insieme dei candidati (il positivo più \(B-1\) negativi), \(s(\cdot, \cdot)\) una misura di compatibilità e \(\tau > 0\) la temperatura. È, letteralmente, una cross-entropy su un problema di classificazione a \(B\) vie in cui la classe corretta è «il positivo». (Nel testo originale la compatibilità è una funzione di punteggio qualsiasi; la \(\tau\) esplicita è della variante su similarità coseno, quella che CLIP adotta, e che qui useremo sempre.)
In CLIP l’ancora è un embedding di immagine, i candidati sono le \(B\) didascalie del batch e la compatibilità è il coseno. Per la direzione immagine → testo:
e simmetricamente, scorrendo la colonna \(i\) invece della riga \(i\), per la direzione testo → immagine:
La loss finale è la media delle due:
Qui \(B\) è la dimensione del batch (la lettera \(N\) è già impegnata a contare le tessere di un’immagine), \(\mathbf{I}_i\) e \(\mathbf{T}_j\) gli embedding normalizzati, \(\langle \mathbf{I}_i, \mathbf{T}_j \rangle\) la loro similarità coseno e \(\tau\) la temperatura. Si noti che il numeratore è lo stesso nelle due direzioni (la coppia vera \((i,i)\)) e a cambiare è solo l’insieme rispetto a cui si normalizza: le didascalie a parità di immagine, oppure le immagini a parità di didascalia. I gradienti alzano il coseno della diagonale e abbassano quelli fuori diagonale, con un’intensità che dipende da quanto ciascun negativo è già vicino: è la proprietà, tipica della softmax, di occuparsi soprattutto dei concorrenti credibili.
Quattro coppie, fatte a mano#
Adesso i numeri, perché la temperatura, la manopola che amplifica le differenze fra le somiglianze prima di trasformarle in percentuali (e amplifica tanto più quanto più è piccola, perché nella formula divide), fa una differenza che a parole non si apprezza. Prendiamo un batch minuscolo, \(B = 4\): quattro immagini e le loro quattro didascalie. Nella tabella delle somiglianze le righe \(\mathbf{I}_1 \dots \mathbf{I}_4\) sono le quattro immagini, le colonne \(\mathbf{T}_1 \dots \mathbf{T}_4\) le quattro didascalie, e ogni cella dice quanto quell’immagine e quella didascalia si somigliano, su una scala che va da \(-1\) (agli antipodi) a \(+1\) (nello stesso punto esatto); in grassetto le quattro coppie vere. I valori sono plausibili per un modello a metà addestramento (le coppie vere intorno a \(0{,}3\), le altre fra \(0\) e \(0{,}15\)):
somiglianza |
\(\mathbf{T}_1\) |
\(\mathbf{T}_2\) |
\(\mathbf{T}_3\) |
\(\mathbf{T}_4\) |
|---|---|---|---|---|
\(\mathbf{I}_1\) |
0,30 |
0,10 |
0,05 |
0,02 |
\(\mathbf{I}_2\) |
0,08 |
0,28 |
0,12 |
0,04 |
\(\mathbf{I}_3\) |
0,04 |
0,15 |
0,32 |
0,09 |
\(\mathbf{I}_4\) |
0,06 |
0,03 |
0,10 |
0,26 |
Nella prima riga la coppia giusta somiglia \(0{,}30\), la migliore delle sbagliate \(0{,}10\). Differenze piccole, e il mestiere della temperatura è decidere quanto pesano: è una manopola che amplifica le differenze fra i punteggi prima di trasformarli in percentuali di fiducia, ed è girata all’incontrario: più il suo numero è basso, più amplifica.
Con la temperatura di partenza di CLIP, che è bassa (\(0{,}07\)), quel piccolo vantaggio viene ingigantito, e i passaggi si possono rifare con una calcolatrice. Primo: si divide ogni somiglianza per la temperatura, cioè per \(0{,}07\), che è come moltiplicarla per quattordici e rotti: la riga diventa \(4{,}29\), poi \(1{,}43\), \(0{,}71\) e \(0{,}29\). Secondo: quei numeri si trasformano in fiducia con l’esponenziale, il tasto \(e^x\), che gonfia i grandi molto più dei piccoli: \(4{,}29\) diventa \(73\) mentre \(1{,}43\) diventa appena \(4{,}2\) (poi \(2{,}0\) e \(1{,}3\)). Terzo: si guarda che fetta è ciascuno del totale, che è poco più di \(80\): alla coppia giusta ne vanno \(73\), cioè il 91% della fiducia.
Il costo della riga si ricava da quella fetta con il logaritmo naturale (il tasto \(\ln\), che disfa quello che fa l’esponenziale), cambiato di segno perché venga un numero positivo: più alta è la fetta, più basso è il costo. Al 91% vale circa \(0{,}1\); se il modello tirasse a caso, dando il 25% a ciascuna delle quattro, varrebbe \(1{,}386\). Facendo la media sulle quattro righe, e poi anche sulle colonne, il costo complessivo è \(0{,}148\).
Ora portiamo la manopola da \(0{,}07\) a \(0{,}5\), senza toccare una sola somiglianza. Siccome è girata all’incontrario, alzarne il numero riduce l’amplificazione, che infatti quasi sparisce: alla coppia giusta va il 35% della fiducia e alle tre sbagliate poco meno, fra il 20 e il 24. Il costo sale a \(1{,}082\); per confronto, tirare a caso fra quattro didascalie costerebbe \(1{,}386\). Stessa tabella, stesso ordine corretto: con la manopola alta si paga quasi quanto tirando a caso, con quella bassa un decimo.
Con la temperatura di partenza di CLIP, \(\tau = 0{,}07\), le similarità coseno della prima riga diventano logit dividendo per \(\tau\): \(0{,}30/0{,}07 = 4{,}29\), poi \(1{,}43\), \(0{,}71\) e \(0{,}29\). Esponenziando si ottengono \(72{,}7\), \(4{,}17\), \(2{,}04\) e \(1{,}33\), la cui somma è \(80{,}2\); le probabilità sono quindi \(0{,}906\), \(0{,}052\), \(0{,}025\) e \(0{,}017\), e il costo della riga è \(-\log 0{,}906 = 0{,}099\) (i logaritmi qui sono naturali, come vuole la forma esponenziale della softmax). Ripetendo per le altre tre righe e mediando, la loss in direzione immagine → testo vale \(0{,}147\); quella sulle colonne \(0{,}148\); la loss simmetrica \(0{,}148\).
Ora rifacciamo il conto senza cambiare una sola similarità, solo alzando la temperatura a \(\tau = 0{,}5\). Dividendo per \(0{,}5\) ed esponenziando come prima, la prima riga diventa \(0{,}351\), \(0{,}235\), \(0{,}213\), \(0{,}201\) di fiducia: la coppia giusta è ancora in testa, ma di un soffio, e la loss simmetrica sale a \(1{,}082\). Per confronto, un modello che tirasse a caso fra quattro didascalie pagherebbe \(\log 4 = 1{,}386\). Con \(\tau = 0{,}5\) questa matrice, che pure è ordinata correttamente, costa quasi quanto tirare a caso; con \(\tau = 0{,}07\) ne costa circa un decimo.
La temperatura e il batch#
Quel confronto dice una cosa importante: la temperatura non è un parametro cosmetico, decide quanto piccole differenze di somiglianza diventino grandi differenze di fiducia, e quindi che cosa il modello si sforzi di correggere.
La temperatura decide quanto l’esaminatore distingue. Un esaminatore mite (temperatura alta) dà a tutti voti quasi uguali: che la risposta giusta fosse nettamente davanti alle altre o appena appaiata, il voto cambia pochissimo, e allora non hai nessun motivo di allargare quel vantaggio. Un esaminatore severo (temperatura bassa) amplifica ogni differenza: essere appena davanti vale molto, essere appena dietro costa moltissimo, e il modello viene spinto ad allargare il margine. Nei conti di prima lo stesso identico compito costava \(0{,}15\) con l’esaminatore severo, che quel piccolo vantaggio l’ha visto e premiato, e \(1{,}08\) con quello mite, che non se n’è nemmeno accorto.
Questa severità non la sceglie chi progetta: è un numero che il modello impara insieme a tutto il resto, come i pesi. E siccome, sulle coppie già messe in ordine giusto, abbassarla fa scendere il costo da sola, senza che il modello abbia imparato niente, le si mette un fondo sotto il quale non può andare. CLIP parte da \(0{,}07\) e finisce l’addestramento appoggiato a quel fondo, a \(0{,}01\), sette volte più severo di come era partito. Lasciato libero, l’esaminatore diventa il più duro che il regolamento gli consente.
C’è poi un secondo ingrediente altrettanto poco appariscente: quante didascalie sbagliate ci sono nel mucchio. Indovinare fra quattro è facile, e un modello che sbaglia poco impara poco. Indovinare fra trentamila è tutta un’altra cosa, e trentamila è esattamente l’ordine di grandezza che CLIP usa: quanto è grande il mucchio decide la difficoltà dell’esame.
Il mucchio grande, però, costa, e costa in un modo storto. Le caselle da riempire sono \(B\) righe per \(B\) colonne: se le coppie raddoppiano, le caselle diventano quattro volte tante, e vanno tenute tutte insieme sotto gli occhi, perché per dare le percentuali di una riga bisogna avere davanti la riga intera. Trentamila coppie in memoria a una macchina sola non ci stanno: il lavoro si spezza fra centinaia di schede grafiche, e a ogni passo i pezzi vanno radunati e poi ridistribuiti. E la forbice è brutta: raddoppiando il mucchio l’ingombro per prepararlo diventa quattro volte tanto, mentre la difficoltà dell’esame cresce pianissimo, come il logaritmo. È per questo che i mucchi molto grandi costano tanto e rendono poco.
In CLIP \(\tau\) è appresa. In pratica il parametro ottimizzato è il logaritmo del fattore di scala \(1/\tau\), così che la scala resti positiva senza vincoli espliciti; lo si inizializza al valore corrispondente a \(\tau = 0{,}07\) e si impedisce alla scala di superare \(100\), perché l’ottimizzazione tenderebbe altrimenti a farla crescere senza freno (una temperatura che tende a zero rende la loss arbitrariamente piccola sulle coppie già ordinate bene, e instabile il gradiente: nel lavoro originale il tetto è motivato proprio dall’instabilità osservata in addestramento). L’effetto di \(\tau\) sulla distribuzione dei pesi è quello visto nei conti: al calare della temperatura la softmax si fa più piccata e la penalità si concentra sui negativi difficili, quelli con coseno vicino a quello del positivo. Il \(0{,}07\) è un punto di partenza e non un regime di esercizio: nel modello pubblicato la scala appresa sta appoggiata al tetto, cioè \(\tau = 1/100 = 0{,}01\), sette volte più piccata di come è partita. L’ottimizzazione, lasciata libera, va a sbattere contro il vincolo e ci resta; ci servirà fra poco, quando si tratterà di capire perché due nuvole di punti non si avvicinano mai.
Il secondo parametro strutturale è \(B\). Il denominatore della InfoNCE somma sui candidati del batch: i negativi sono il batch, non un insieme costruito a parte. Con \(B\) piccolo il compito è banale (la baseline casuale è \(\log B\), e con \(B = 4\) vale \(1{,}39\)) e il segnale di apprendimento è povero; al crescere di \(B\) il compito diventa un ago in un pagliaio e il gradiente informa molto di più. CLIP addestra con batch da \(32\,768\) coppie, distribuiti su centinaia di GPU. Il prezzo è la struttura stessa della loss: la matrice di similarità è \(B \times B\), il suo costo cresce con il quadrato del batch, e la normalizzazione della softmax richiede che ogni riga veda tutte le colonne, quindi che gli embedding di tutti i dispositivi vengano radunati insieme a ogni passo. Torneremo su questo punto fra poco, perché è esattamente il vincolo che una variante successiva scioglie.
La loss in dieci righe#
Tradotta in PyTorch, tutta la sezione sta in una funzione. Gli embedding arrivano dai due encoder come due matrici \((B, d)\), cioè \(B\) righe (una per elemento del batch) lunghe \(d\) numeri ciascuna; il resto è normalizzazione, un prodotto matriciale e due cross-entropy.
import torch
from torch import nn
import torch.nn.functional as F
# la temperatura si impara: il parametro e' log(1/tau), cosi' la scala,
# che si ottiene esponenziando, e' positiva per costruzione
logit_scale = nn.Parameter(torch.tensor(1 / 0.07).log())
def loss_contrastiva(emb_img, emb_txt, logit_scale):
"""emb_img, emb_txt: due tensori (B, d), una riga per elemento del batch."""
# 1. sulla sfera unitaria: il prodotto scalare diventa un coseno
I = F.normalize(emb_img, dim=-1)
T = F.normalize(emb_txt, dim=-1)
# 2. matrice B x B dei coseni, riscalata dalla temperatura (con il tetto)
scala = logit_scale.exp().clamp(max=100.0)
logits = scala * (I @ T.t())
# 3. la risposta giusta e' sempre sulla diagonale: 0, 1, 2, ... B-1
bersagli = torch.arange(len(I), device=I.device)
# 4. una cross-entropy sulle righe, una sulle colonne, e si media
perdita_i2t = F.cross_entropy(logits, bersagli)
perdita_t2i = F.cross_entropy(logits.t(), bersagli)
return (perdita_i2t + perdita_t2i) / 2
# Gli stessi conti fatti a mano poco fa, rifatti dalla libreria: si parte
# direttamente dalla tabella delle somiglianze, saltando i due encoder.
somiglianze = torch.tensor([[0.30, 0.10, 0.05, 0.02],
[0.08, 0.28, 0.12, 0.04],
[0.04, 0.15, 0.32, 0.09],
[0.06, 0.03, 0.10, 0.26]])
bersagli = torch.arange(4)
for tau in (0.07, 0.5):
logits = somiglianze / tau
perdita = (F.cross_entropy(logits, bersagli)
+ F.cross_entropy(logits.t(), bersagli)) / 2
print(f"tau = {tau}: loss simmetrica = {perdita:.3f}")
# tau = 0.07: loss simmetrica = 0.148
# tau = 0.5: loss simmetrica = 1.082
Le ultime righe rifanno i conti della tabella delle somiglianze: la stessa matrice, la stessa loss, solo la temperatura cambiata. Quei due numeri, \(0{,}148\) e \(1{,}082\), si possono così ritrovare invece che crederli sulla parola.
Cosa non c’è: nessuna etichetta, nessun numero di classi, nessuna testa di classificazione. L’unica informazione supervisionata è l’ordine delle righe, cioè il fatto che la didascalia \(i\) stava sotto l’immagine \(i\).
Un solo spazio, due quartieri#
Conviene tornare sulla parola «vicino», perché presa alla lettera inganna. Fin qui si è detto che una foto e la sua didascalia finiscono vicine sulla mappa. Vicine quanto? La risposta è stata misurata su un modello CLIP pubblico, e non è quella che ci si aspetta.
Prendiamo ottanta fotografie, otto per ciascuno di dieci soggetti (aerei, gatti, cavalli, navi e così via), più quaranta didascalie, diamole a un modello CLIP pubblico e misuriamo tutte le vicinanze. Una fotografia somiglia alla didascalia che il modello stesso sceglie per lei circa \(0{,}3\), e a una fotografia qualunque, che con lei non c’entra niente, circa \(0{,}76\). Ogni foto è molto più vicina a una foto estranea che alla frase che la descrive.
Non è un guasto, e il meccanismo funziona lo stesso: sulle stesse ottanta immagini il classificatore scritto a parole (le dieci categorie diventano dieci frasi, e si tiene la più vicina) indovina quasi nove volte su dieci. Funziona perché il confronto che conta è sempre «questa foto, con quale delle dieci frasi va meglio?», e mai «foto contro frase, in assoluto». Fra le frasi la graduatoria è giusta, ed è tutto quello che serve.
La mappa, insomma, è una sola, ma ci sono due quartieri: le fotografie da una parte, le frasi dall’altra, e i due gruppi non si toccano mai. Basta una riga tracciata una volta sola per dire di ogni punto, senza sbagliarne nemmeno uno su centoventi, se è una foto o una frase. L’addestramento non ha mai chiesto ai due quartieri di mescolarsi: ha chiesto che, nel quartiere delle frasi e soltanto lì, quella giusta stesse davanti a tutte le altre. E quello lo ottiene benissimo.
I due quartieri, per giunta, erano separati fin dal primo giorno. Due reti appena costruite scrivono già in angoli diversi della mappa, prima ancora di essere addestrate, e niente le obbliga poi a traslocare. Anzi, con l’esaminatore severo di CLIP, spostare a mano un quartiere sopra l’altro fa salire il costo, non scendere.
Ne segue una regola pratica: il numero di somiglianza fra una foto e una frase non si confronta con quello fra due foto. Sono due righelli con lo zero in posti diversi, e chi fissa una soglia guardando i secondi e la applica ai primi sbaglia tutte le volte.
Il fenomeno ha un nome, modality gap, e una descrizione sistematica in Liang
e colleghi [LZK+22], che trovano le due modalità immerse «a
distanza di braccio» nello spazio che condividono. I numeri che seguono vengono da clip-vit-base-patch32, da ottanta fotografie
di CIFAR-10 (otto per ciascuna delle dieci classi) e da quaranta didascalie
generiche, quattro stampi diversi (del tipo a photo of a {classe}) applicati
a ciascuna delle dieci classi. La distanza fra i due
centroidi vale \(\approx 1{,}1\) (gli embedding stanno sulla
sfera unitaria, dove il massimo possibile è \(2\)); il coseno medio della coppia
migliore è \(\approx 0{,}3\) contro \(\approx 0{,}76\) fra due immagini qualunque;
e proiettando tutto sulla direzione che unisce i due centroidi le due nuvole non
si sovrappongono per niente, tanto che una regressione logistica risponde
«immagine o testo?» con accuratezza \(1{,}000\) in validazione incrociata. Sulle
stesse ottanta immagini, e con un solo prompt per classe, la classificazione
zero-shot a dieci vie ne prende circa nove su dieci: il divario non le
impedisce di
funzionare, ed è il punto.
Le cifre fini dipendono dalle scelte appena elencate (quali otto immagini per
classe, quali stampi: nessun seme le fissa), ed è la ragione per cui qui si
leggono arrotondate; e quel
\(0{,}76\) è alto perché le immagini di CIFAR-10 sono \(32 \times 32\) e si
somigliano fra loro più di quanto si somiglino fotografie a piena risoluzione:
su queste il valore scende, senza che la forbice si chiuda. L’ampiezza del
divario dipende dunque dal modello e dai dati; la sua esistenza no.
Che non si chiuda è proprio ciò che l’ottimizzazione chiede, e conviene dire con precisione che cosa chiede. La InfoNCE non contiene nessun termine che premi la vicinanza fra le due modalità in assoluto: vincola soltanto l’ordine e i margini dentro ogni riga e dentro ogni colonna. Quel che il minimo chiede è che la coppia vera stia davanti alle altre della sua riga, con il margine più largo possibile; «le due nuvole sovrapposte» non compare da nessuna parte, né come richiesta né come conseguenza. Il divario nasce per giunta già all’inizializzazione (è l’effetto cono: una rete profonda non addestrata concentra le proprie uscite in un cono stretto, e due reti diverse danno due coni diversi), e la temperatura bassa di cui si è parlato lo difende invece di chiuderlo: forzando a mano la sovrapposizione, alla temperatura originale la perdita aumenta.
Due conseguenze per chi costruisce. La prima: coseni cross-modali e coseni intra-modali vivono su scale diverse, non si confrontano fra loro e non si mescolano in un’unica soglia. La seconda: le operazioni che presuppongono uno spazio omogeneo (il centroide fra un’immagine e un testo, un \(k\)-means su vettori misti, una soglia assoluta di appartenenza) restituiscono risultati che sembrano sensati e non lo sono. Quel che è lecito, ed è quanto basta a tutto il resto della sezione, è l’\(\arg\max\) dentro una modalità sola.
Il classificatore che si scrive a parole#
Finito l’addestramento, il modello sa fare una cosa sola: dire quanto un’immagine e un testo si somigliano. Ma quella cosa sola, usata bene, produce un classificatore che nessuno ha addestrato.
Vuoi distinguere gatti, cani e tram? Non serve raccogliere foto né riaddestrare niente. Scrivi tre frasi: «una foto di un gatto», «una foto di un cane», «una foto di un tram». Le passi all’encoder di testo, che ti dà tre file di numeri. Passi la tua immagine all’encoder di immagini, che te ne dà una. Guardi a quale delle tre è più vicina, cioè calcoli quel numero fra \(-1\) e \(+1\) tre volte e tieni il più alto. Se domani ti serve anche «una foto di un vaporetto», aggiungi una riga di testo: il classificatore è cresciuto di una classe in un secondo, senza una sola immagine di vaporetto.
Questo si chiama zero-shot, «a zero esempi», ed è la stessa identica operazione di prima, l’abbinare, usata con didascalie che ti sei scritto da solo. Le tre file di numeri sono i pesi del classificatore, e di solito i pesi si stimano a poco a poco su migliaia di foto etichettate; qui li scrive l’encoder di testo, leggendo una frase. Il fenomeno che ha colpito tutti nel 2021 è che il classificatore scritto a parole, senza aver visto nemmeno una delle immagini etichettate di ImageNet (sono 1,28 milioni), ci prendeva quanto la ResNet-50 che su quelle immagini si era addestrata.
C’è però una stranezza: il risultato cambia a seconda di come scrivi la frase. «Una foto di un gatto» funziona meglio della sola parola «gatto», perché il modello ha imparato dalle didascalie del web, che sono frasi: una parola secca gli arriva in una lingua un po’ diversa da quella su cui si è allenato. E poi c’è l’ambiguità. «Gru» da sola può essere l’uccello o la macchina da cantiere, mentre «una foto di una gru, l’uccello» chiude la questione. Sistemare la frase vale, su ImageNet, poco più di un punto di risposte giuste in più.
L’altro accorgimento sta nel non fidarsi di una formulazione sola. Della stessa classe si scrivono ottanta frasi diverse («una foto di un gatto», «un primo piano di un gatto», «una foto sfocata di un gatto»), si fa la media delle ottanta file di numeri e si usa quella: le stranezze di ciascuna si annullano a vicenda e resta quello che le ottanta hanno in comune, il concetto. Vale altri tre punti e mezzo, e non costa niente, perché la media si fa una volta sola e prima di guardare qualunque fotografia.
Dato un insieme di classi candidate \(\{c_1, \dots, c_K\}\), si costruisce per
ciascuna un prompt (per esempio una foto di un {c_k}), lo si passa
nell’encoder di testo e si normalizza, ottenendo \(\mathbf{T}_1, \dots, \mathbf{T}_K\). La
predizione per un’immagine con embedding \(\mathbf{I}\) è
L’osservazione strutturale, fatta nel paper originale [RKH+21], è che questa è letteralmente una classificazione lineare: la matrice \([\mathbf{T}_1; \dots; \mathbf{T}_K] \in \mathbb{R}^{K \times d}\) è una matrice di pesi, e l’encoder di testo si comporta come una rete che genera i pesi del classificatore a partire da una descrizione, invece di stimarli per discesa del gradiente su esempi etichettati. Cambiare l’insieme delle classi significa rigenerare quella matrice, un’operazione che costa una forward pass per classe.
Due fenomeni rendono la scelta del prompt non neutrale. Il primo è la
polisemia: un’etichetta isolata non disambigua i suoi sensi (l’italiano
«gru» copre l’uccello e la macchina da cantiere, e nei dataset di visione casi
simili sono la norma), mentre un contesto testuale lo fa. Il secondo è uno
scarto di distribuzione: nel corpus di pre-addestramento il testo appaiato
a un’immagine è quasi sempre una frase, quindi un input costituito da una sola
parola cade in una regione poco frequentata dello spazio testuale. Il rimedio,
un template fisso come A photo of a {label}., vale nel paper originale un
guadagno di \(1{,}3\) punti su ImageNet, e mediare gli embedding di ottanta
template diversi (una forma di ensembling che, essendo fatta sui vettori e non
sulle predizioni, non costa nulla in inferenza) ne aggiunge altri \(3{,}5\).
La stessa geometria dà il recupero cross-modale: si indicizzano gli embedding di un archivio di immagini e si interroga l’indice con l’embedding di una frase, prendendo i \(k\) più vicini; oppure il contrario, cercando la didascalia più adatta a un’immagine. Ricerca semantica di immagini, deduplicazione, filtraggio di corpora enormi: sono tutti lo stesso prodotto scalare. E il text encoder così addestrato è riusabile altrove: è lui, congelato, a tradurre il prompt in vettori dentro Stable Diffusion, come vedremo nel capitolo sui modelli di diffusione.
Sì o no, una casella alla volta#
Il vincolo lasciato in sospeso quando si parlava del mucchio nasce tutto dalla forma dell’esame. Per dare le percentuali di una riga bisogna avere sotto gli occhi la riga intera, cioè tutte le didascalie del gruppo; e se il gruppo è spalmato su duecento schede grafiche, ogni passo di addestramento comincia radunando i risultati di tutte e finisce ridistribuendoli. È un costo che cresce con il batch, proprio mentre il metodo chiede batch grandi.
SigLIP [ZMKB23] cambia una cosa sola, e la cambia alla radice: smette di trattare la riga come una domanda a risposta multipla e tratta ogni casella come una domanda a sé, con risposta sì o no.
Invece di «ecco l’immagine numero uno, quale delle quattro didascalie è la sua?», a ogni casella della tabella si fa una domanda indipendente: «voi due andate insieme, sì o no?». Sedici domandine al posto delle otto interrogazioni di prima, quattro sulle righe e quattro sulle colonne.
Il guadagno è che per rispondere a una non serve sapere niente delle altre. Nessuno deve più radunare la riga intera, il lavoro si può spezzare in pezzi che viaggiano per conto proprio, e soprattutto cade l’obbligo del mucchio enorme: l’esame a scelta multipla, per essere difficile, il mucchio grande lo pretendeva; una domanda sì-o-no si regge da sé. Non che il mucchio grande faccia male: semplicemente smette di essere obbligatorio.
Un guaio però c’è, ed è di proporzioni. In una tabella di quattro per quattro le caselle da «sì» sono quattro e quelle da «no» dodici; con un mucchio da quattromila coppie diventano quattromila «sì» contro quasi sedici milioni di «no». Chi rispondesse «no» a tutto avrebbe quasi sempre ragione senza aver imparato niente, e le prime ore di addestramento se ne andrebbero tutte a scoprire questa sciocchezza. Il rimedio è dirgliela in partenza: si regala al modello la conoscenza che «no» è la risposta di gran lunga più frequente, così il tempo lo può spendere sul resto.
«Questa immagine e questa didascalia vanno insieme?» è un problema di classificazione binaria, e la funzione che gli corrisponde non è la softmax ma la sigmoide:
dove \(\sigma\) è la funzione logistica, \(t\) il fattore di scala appreso (lo stesso ruolo di \(1/\tau\), parametrizzato anche qui come esponenziale di un parametro libero), \(b\) un bias appreso e \(z_{ij}\) l’etichetta binaria della cella. Si noti la normalizzazione: la somma corre su tutte le \(B^2\) celle, ma il divisore è \(B\), così che la loss conti il costo per elemento del batch e non per cella. Il bias serve a un problema che la softmax non aveva: in un batch le celle negative sono \(B^2 - B\) contro \(B\) positive, uno sbilanciamento feroce, e senza un \(b\) inizializzato molto negativo (nel lavoro originale a \(-10\)) le prime iterazioni si consumerebbero tutte a correggerlo, invece che a imparare.[1]
La conseguenza pratica è che ogni pezzo del conto, cioè ogni casella, dipende da una coppia sola. Non c’è più niente da normalizzare su tutto il batch, il calcolo si può spezzare in blocchi che si scambiano gli embedding a turno, e soprattutto la qualità dell’addestramento smette di dipendere dall’avere un batch enorme. Sul proprio impianto gli autori misurano due soglie: sotto le sedicimila coppie il metodo a domande sì-o-no stacca di parecchio quello a scelta multipla, e oltre le trentaduemila nessuno dei due guadagna più molto. Sono i numeri di quelle prove, non costanti di natura, e a un altro modello su altri dati verranno diversi; quello che non dipende dai numeri è la direzione, cioè che alla dimensione del batch viene tolto il ruolo di prerequisito. È lo stesso allineamento, ottenuto togliendo un vincolo invece di aggiungere un pezzo.
Conviene registrare anche un risultato di metodo, arrivato negli stessi mesi di CLIP. ALIGN [JYX+21] ha addestrato le stesse due reti su oltre un miliardo di coppie prese dal web così com’è, senza i costosi passaggi di pulizia con cui di solito si prepara un archivio di immagini. Molte di quelle didascalie con la loro fotografia c’entrano poco o niente; il messaggio, che sono gli autori stessi a formulare, è che quando le coppie sono così tante la sciatteria di ciascuna pesa meno. Ripulire l’archivio non è un prerequisito del metodo.
Uno spazio allineato non è uno spazio che capisce#
Qui finisce la parte in cui tutto funziona meglio del previsto, e comincia quella che spiega perché non è bastato.
Torniamo alla frase che apriva la sezione: «un gatto nero che salta sul muro». Un modello contrastivo la riconosce benissimo se la foto contiene un gatto, del nero e un muro. Ma proviamo a chiedergli di distinguere «il gatto sotto il tappeto» da «il tappeto sotto il gatto», o «il gatto insegue il cane» da «il cane insegue il gatto»: le due frasi contengono le stesse identiche parole, e le due immagini gli stessi oggetti. È qui che il meccanismo mostra il fondo.
Il fenomeno è stato reso visibile da Winoground [TJB+22], un insieme di quattrocento esempi costruiti a mano apposta: due immagini e due didascalie fatte esattamente delle stesse parole in ordine diverso, con il compito di appaiarle correttamente. Si misura in tre modi: scegliere la didascalia giusta per ciascuna delle due immagini, scegliere l’immagine giusta per ciascuna delle due didascalie, e riuscire in tutte e quattro le scelte insieme. Le prime due misure chiedono di indovinare entrambe le volte fra due possibilità, quindi tirando a caso si prende il 25%; la terza chiede le prime due insieme, e a caso si prende un sesto, cioè il 16,7%, e non un sedicesimo: le due misure precedenti leggono gli stessi quattro punteggi, quindi non sono indipendenti e non si moltiplicano fra loro. Perché tornino tutte e quattro le scelte, le due coppie giuste devono stare davanti a entrambe le sbagliate, e questo capita in quattro dei ventiquattro ordinamenti possibili. Il risultato, enunciato dagli autori, è che nessuno dei modelli provati fa molto meglio del caso; sulle due misure più difficili, cioè scegliere l’immagine giusta e riuscire in tutte e quattro le scelte insieme, sono tutti sotto il livello del caso, il che non è sfortuna: vuol dire che qualcosa li spinge sistematicamente verso la risposta sbagliata.[2] È la misura di un limite che riguarda la famiglia, non una classifica fra prodotti.
La ragione è strutturale, e sta nel gioco stesso che abbiamo descritto: il modello impara a distinguere la sua didascalia dalle altre del gruppo (didascalie di immagini prese a caso), e non a descrivere quello che vede. Per vincere, quasi sempre, basta indovinare quali oggetti compaiono nella foto: se le altre parlano di un tramonto, di una bicicletta e di una scodella di minestra, riconoscere «gatto» e «muro» è più che sufficiente, e capire chi sta sopra chi non porta nessun vantaggio. La strada più economica verso un costo basso è trattare la didascalia come un sacco di concetti, e l’ottimizzazione, che è pigra per mestiere, la prende. La sintassi, le relazioni spaziali, il conteggio, la negazione (togliere un «senza» da una didascalia le rovescia il significato e non la sposta quasi per niente sulla mappa) sono i primi a rimanere fuori.
Che sia davvero il gioco a produrre quel comportamento, e non un difetto delle reti, lo si è dimostrato con un esperimento di una semplicità disarmante [YBK+23]: si prendono le didascalie di un archivio, se ne mescolano le parole, si rifà la ricerca per immagini, e il risultato non peggiora. Se l’ordine si può buttare via senza pagare pegno, l’ordine il compito non lo chiedeva. Lo stesso lavoro mostra anche il rovescio, che è la parte utile: aggiungendo al mucchio, come didascalie sbagliate, la didascalia giusta con le parole rimescolate, la stessa identica rete impara l’ordine. Il limite stava in quello che le si chiedeva di distinguere, non nell’architettura.
Due precisazioni, per onestà. La prima è che quegli esempi, scelti a mano perché siano difficili, lo sono anche per altre ragioni (alcuni chiedono conoscenza del mondo, altri sono visivamente ostici [DBC+22]), e quindi quel che misurano è il saper mettere insieme i pezzi di una frase (la composizionalità) più qualcos’altro. Il fenomeno è solido, la sua quantificazione esatta lo è meno. La seconda è che un limite parallelo viene dalla forma della rappresentazione: un’intera immagine finisce in una sola fila di qualche centinaio di numeri, e una fila sola non può portare insieme la scena, la posizione di ogni oggetto e il testo scritto su un cartello. Il dettaglio fine e i documenti sono un problema a parte, e li affronta la sezione sulla risoluzione.
Da qui in avanti il capitolo prova a superare entrambi i limiti nello stesso modo: smettere di chiedere a un prodotto scalare di rappresentare la relazione fra un’immagine e una frase, e mettere al suo posto un modello di linguaggio che legge l’immagine token per token. Come si innestano gli occhi su un modello che sa solo leggere è la prossima sezione.
Da ricordare
Un classificatore a elenco chiuso sa dire soltanto le voci del suo elenco, e aggiungerne una costa foto nuove, etichette nuove e un addestramento nuovo. Il gioco delle quattro foto e delle quattro didascalie da appaiare sostituisce la domanda «che cosa è questo?» con «quale di queste didascalie è la sua?».
Nessuno prepara le risposte: le didascalie sono già attaccate alle immagini del web, e per addestrare CLIP ne sono state raccolte quattrocento milioni.
Il gioco si fa in due sensi, per righe e per colonne, e si fa la media. Due numeri decidono quanto è severo: la manopola che amplifica le differenze fra le somiglianze prima di trasformarle in percentuali, e che amplifica tanto più quanto più è piccola, e quante didascalie sbagliate ci sono nel mucchio, perché indovinare fra quattro è facile e indovinare fra trentamila no.
Il regalo che ne esce: per costruire un classificatore bastano tre frasi scritte a mano, e domani la quarta si aggiunge in un secondo, senza una sola fotografia. Conta però come si scrive la frase, e conviene scriverne ottanta e fare la media.
Nella variante «sì o no» si smette di chiedere «quale di queste quattro» e si chiede a ogni casella «voi due andate insieme?»: nessuno deve più radunare tutta la riga, e il mucchio enorme non serve più.
Immagini e parole finiscono sulla stessa mappa, ma in due quartieri separati: per una foto la didascalia giusta è la più vicina fra tutte le frasi, e questo basta a farla vincere, ma nessuna frase le è mai vicina quanto le è vicina una fotografia qualunque. I numeri si confrontano fra pari, mai una foto con una frase in assoluto.
Appaiare non è capire: al gioco si vince riconoscendo gli oggetti, quindi «il gatto sotto il tappeto» e «il tappeto sotto il gatto» restano indistinguibili. È il motivo per cui esistono le sezioni che seguono.
Da ricordare
Un classificatore chiuso sa dire solo le classi su cui è stato addestrato, e aggiungerne una costa rietichettatura e riaddestramento. L’addestramento contrastivo immagine-testo [RKH+21] sostituisce la domanda «che cosa è» con «quale di queste didascalie è la sua».
La supervisione è naturale: le coppie immagine-didascalia esistono già sul web (quattrocento milioni per CLIP), nessuno le etichetta e nessuna lista di categorie viene decisa in anticipo.
La loss è una InfoNCE simmetrica [vdOLV18]: embedding normalizzati, matrice \(B \times B\) di coseni divisi per la temperatura \(\tau\), cross-entropy sulle righe e sulle colonne con la diagonale come risposta corretta.
\(\tau\) e \(B\) sono parte del metodo, non dell’implementazione: la temperatura è appresa, e a fine addestramento la scala \(1/\tau\) sta appoggiata al suo tetto, cioè \(\tau\) al suo valore minimo, \(0{,}01\). Decide quanto la distribuzione è piccata, e i negativi vengono dal batch, quindi un batch piccolo rende il compito troppo facile. A crescere con \(B^2\) non è il calcolo, che accanto ai due encoder è trascurabile, ma la memoria della matrice. E la normalizzazione di riga obbliga a un all-gather degli embedding fra i dispositivi a ogni passo.
Le due modalità restano in due regioni disgiunte dello spazio condiviso, il modality gap [LZK+22]: il contrastivo ottimizza un ordinamento dentro il batch, e di quell’ordinamento la sovrapposizione delle due nuvole non fa parte, tanto che forzarla a mano fa salire la perdita invece di abbassarla. Conseguenza operativa: un coseno cross-modale non si confronta con un coseno intra-modale.
La classificazione zero-shot è una conseguenza, non una funzione in più: si scrive una didascalia per classe e si prende la più simile. La forma del prompt conta perché il modello ha imparato su frasi, non su parole isolate. La stessa geometria dà il recupero di immagini per descrizione.
SigLIP [ZMKB23] sostituisce la softmax di riga con una sigmoide per coppia: niente normalizzazione globale, niente raduno degli embedding fra le GPU, buon addestramento anche con batch piccoli. ALIGN [JYX+21] mostra che il metodo regge un miliardo di coppie raccolte dal web senza curatela.
Allineare non è capire: la loss premia il riconoscimento degli oggetti e non le relazioni fra loro, e il modello si comporta in buona parte come un sacco di concetti. Winoground [TJB+22] rende visibile il fallimento, e l’esperimento delle didascalie con le parole mescolate [YBK+23] ne isola la causa (il recupero non peggiora) e mostra che con negativi permutati la stessa rete impara l’ordine: il limite è dell’obiettivo, non dell’architettura. Da qui le architetture delle sezioni successive.