Salta ai contenuti

Vettori, spazi vettoriali, intuizione geometrica

La grammatica minima per parlare di embedding, attention e gradient descent senza barare.

Tutto quello che un LLM fa, sotto, è algebra lineare. Ogni token che il modello vede è prima un intero, poi diventa un vettore. Ogni passaggio dentro un transformer è una sequenza di moltiplicazioni tra matrici e vettori. La similarità tra due frasi che alimenta una pipeline di RAG (Retrieval Augmented Generation, recupero di documenti pertinenti per arricchire il contesto del modello) si calcola con un prodotto scalare. Il gradient descent che ha addestrato il modello è una sottrazione tra vettori, ripetuta miliardi di volte.

Un developer professionista non ha bisogno di diventare matematico per usare questi sistemi, ma ha bisogno di un livello operativo solido: capire cosa succede quando la documentazione di un’API parla di “embedding di dimensione 1536”, quando un vector database promette “fast cosine search”, quando un paper dice “the residual stream is a sum of contributions”. Senza vettori, queste frasi restano scatole nere. Con i vettori, diventano oggetti familiari su cui si può ragionare.

Il capitolo introduce il minimo indispensabile: cos’è un vettore, cos’è uno spazio vettoriale, come si misurano lunghezze e angoli, dove l’intuizione geometrica regge e dove si rompe. Chi ha già fatto un corso di algebra lineare può saltare ai paragrafi finali su anisotropia e concentrazione delle distanze. Chi non ne ha mai fatto uno troverà qui la base per i capitoli successivi della Parte IV.

Il calcolo vettoriale astratto come lo conosciamo nasce a metà dell’Ottocento, lontano dalle preoccupazioni delle macchine. Hermann Grassmann, matematico e linguista tedesco vissuto fra il 1809 e il 1877, pubblica nel 1844 Die lineale Ausdehnungslehre — letteralmente “la teoria delle estensioni lineari” — un libro che propone un calcolo astratto su oggetti che oggi chiameremmo vettori, in dimensione qualsiasi, scollegato dalla geometria fisica tridimensionale di Euclide. L’opera viene quasi ignorata per decenni: troppo astratta, scritta in modo difficile, fuori dal mainstream del tempo che vede la matematica come strumento per la fisica.

La sistemazione definitiva arriva da un matematico italiano. Giuseppe Peano, vissuto fra il 1858 e il 1932, nel Calcolo geometrico secondo l’Ausdehnungslehre di H. Grassmann (1888), capitolo IX, fissa per la prima volta gli assiomi moderni di “sistema lineare” — l’oggetto che oggi chiamiamo spazio vettoriale. Otto regole, scritte in modo riconoscibile da chiunque apra oggi un manuale di algebra lineare. Peano è anche l’inventore della notazione logica con cui le scriviamo.

Dal 1888 in avanti la sequenza è prevedibile. Hilbert estende a dimensione infinita per fare meccanica quantistica. Banach generalizza ulteriormente. Bourbaki, nel Novecento, sistematizza tutto con il rigore austero che è oggi lo standard. Per gli scopi di questo libro, però, basta restare sulla forma più concreta possibile: lo spazio R^n, cioè l’insieme delle liste ordinate di n numeri reali. È l’unico spazio vettoriale che compare di fatto nei modelli di machine learning che ci interessano.

vector as arrow vs list of numbers

Vale la pena tracciare il decoder di un altro nome che ricorrerà. William Rowan Hamilton, matematico e fisico irlandese vissuto fra il 1805 e il 1865, nel 1843 introduce i quaternioni — una struttura algebrica che generalizza i numeri complessi al 4D — e con essi la nozione di “vettore” in senso moderno. Hamilton coniò il termine stesso, dal latino vehere, “trasportare”. I suoi quaternioni hanno avuto fortune alterne in fisica, ma il vocabolario vettoriale che usiamo viene da lì. Grassmann arriva un anno dopo con un’astrazione molto più radicale; Peano la formalizza definitivamente quarantacinque anni più tardi.

Una nota di onestà storica. La parola “vettore” pre-esiste a Grassmann ed è stata usata in fisica (Hamilton, quaternioni, 1843) per indicare grandezze con modulo, direzione, verso. Quello che Grassmann e poi Peano fanno è separare il concetto dalla sua origine fisica: un vettore non è più necessariamente una freccia in R^3, è qualunque oggetto in qualunque insieme che obbedisca a otto regole. Questa è una filiazione documentata, non un’analogia: i testi moderni di algebra lineare citano esplicitamente Peano 1888 come la fonte assiomatica.

Tre angoli, tutti utili, da tenere insieme.

In R^2 — il piano cartesiano del liceo — un vettore è una freccia che parte dall’origine e arriva in un punto. La freccia ha tre attributi che si vedono a occhio: una lunghezza, una direzione (la retta su cui giace), un verso (da quale parte della retta punta). Sommare due vettori si fa con la regola del parallelogramma: si trasporta la coda del secondo sulla testa del primo, e il vettore somma è la diagonale del parallelogramma costruito così. Moltiplicare un vettore per un numero (uno scalare) significa stirarlo, contrarlo o invertirlo, lasciando la direzione invariata.

parallelogram rule for vector sum

Stesso quadro in R^3, lo spazio fisico ordinario. Tutto si vede ancora. Le frecce hanno tre coordinate, le somme si visualizzano, le lunghezze si misurano col Pitagora a tre dimensioni. È l’angolo geometrico, ed è quello con cui si costruiscono le intuizioni iniziali. Si rompe non appena si sale: nessuno “vede” R^768.

Un vettore in R^n è semplicemente una lista ordinata di n numeri reali, scritta come v = (v1, v2, ..., vn). Sommare due vettori della stessa dimensione si fa componente per componente: (u+v)i=ui+vi(u + v)_i = u_i + v_i. Moltiplicare per uno scalare a si fa sempre componente per componente: (av)i=avi(a · v)_i = a · v_i. Non c’è nessun parallelogramma da disegnare; ci sono solo addizioni e moltiplicazioni di numeri.

Questo è l’angolo che NumPy, PyTorch e ogni libreria numerica usano operativamente. Funziona identico in R^2, R^3, R^768, R^4096. Non chiede intuizione spaziale, chiede solo aritmetica. Il prezzo è che si perde il “vedere”: serve l’altro angolo come scaffold mentale.

Un vettore è una rappresentazione: un oggetto del mondo descritto da n attributi numerici. Una casa descritta da (metriquadri,nstanze,annocostruzione,distanzacentro)(metri_quadri, n_stanze, anno_costruzione, distanza_centro) è un vettore in R^4. Una parola dopo word2vec è un vettore in R^300 dove ogni componente codifica qualcosa del suo significato distribuzionale. Una immagine dopo un encoder è una sequenza di patch embedding. Un documento, una frase, un utente, una sessione: tutto può essere mappato in R^d.

Questa è la chiave per chi farà agent coding. La somma di due vettori-feature non è solo un’operazione algebrica, è una composizione semantica. Il fatto che king − man + woman produca un vettore vicino a queen negli embedding di word2vec — un risultato dimostrato da Mikolov e colleghi nel 2013 in Distributed Representations of Words and Phrases and their Compositionality, paper di NeurIPS — non è magia, è geometria. La stessa geometria che si fa al liceo, in 300 dimensioni invece che in 2.

I tre angoli vanno tenuti insieme. Il dev userà il terzo nel quotidiano, scivolerà al primo per intuizione, scenderà al secondo quando deve scrivere codice o leggere un paper. Trattarli come tre facce dello stesso oggetto è la differenza fra “uso embeddings” e “capisco cosa sto usando”.

Uno spazio vettoriale sui reali è un insieme V dotato di due operazioni: una somma V × V → V che a due elementi di V associa un terzo elemento di V, e una moltiplicazione per scalare R × V → V che a un numero reale e un elemento di V associa un nuovo elemento di V. Le due operazioni devono soddisfare otto assiomi:

  1. u + v = v + u (commutatività della somma)
  2. (u + v) + w = u + (v + w) (associatività della somma)
  3. esiste un elemento 0 in V tale che v + 0 = v per ogni v (vettore zero)
  4. per ogni v esiste -v tale che v + (-v) = 0 (esistenza dell’opposto)
  5. a(bv) = (ab)v (compatibilità della moltiplicazione)
  6. 1v=v1 · v = v (identità moltiplicativa)
  7. a(u + v) = au + av (distributività rispetto agli scalari)
  8. (a + b)v = av + bv (distributività rispetto ai vettori)

Non serve impararli a memoria. Serve sapere che esistono, e che sono il motivo per cui possiamo manipolare vettori con la stessa libertà con cui manipoliamo numeri: somma, sottrazione, riarrangiamenti, raccoglimento di fattori, tutto è legittimo perché gli assiomi lo garantiscono.

R^n con somma componente per componente e moltiplicazione per scalare è lo spazio vettoriale per eccellenza, ed è quello che incontreremo praticamente sempre. Ma vale la pena sapere che l’insieme dei polinomi di grado al più n, l’insieme delle funzioni continue su un intervallo, l’insieme delle sequenze convergenti — sono tutti spazi vettoriali. La definizione astratta cattura un pattern, non un oggetto specifico.

Dati k vettori v1, ..., vk di V e k scalari a1, ..., ak, la combinazione lineare è a1 v1 + a2 v2 + ... + ak vk. È una “ricetta” che mescola i vettori dati nelle proporzioni indicate dagli scalari.

L’insieme di tutte le combinazioni lineari possibili dei vettori v1, ..., vk si chiama span dei vettori e si scrive span(v1,...,vk)span(v1, ..., vk). Geometricamente, in R^3:

  • lo span di un vettore non nullo è la retta che lo contiene (passante per l’origine);
  • lo span di due vettori non paralleli è il piano che li contiene (passante per l’origine);
  • lo span di tre vettori non complanari è tutto R^3.

linear combination spans a plane

Vale la pena fermarsi un istante sul prodotto scalare letto come “quanto u e v condividono direzione”. Se decomponiamo u nelle sue componenti lungo la base canonica, u=sumuieiu = sum u_i e_i, e analogamente per v, allora uv=sumuiviu · v = sum u_i v_i non è altro che la somma dei prodotti delle coordinate. Ogni asse contribuisce indipendentemente. Questa indipendenza per asse è il motivo per cui il dot product si fattorizza così bene in operazioni vettoriali parallele su GPU: ogni thread può calcolare un termine, poi una riduzione li somma. È il pattern che rende attention computazionalmente trattabile su sequenze lunghe — anche se non è ancora il momento di entrare nei dettagli.

I vettori v1, ..., vk sono linearmente indipendenti se l’unica combinazione lineare che dà il vettore zero è quella con tutti i coefficienti nulli. In altre parole: nessun vettore della lista è esprimibile come combinazione degli altri. Sono “informazioni distinte”. Se invece esiste una combinazione non banale che dà zero, almeno uno dei vettori è ridondante e si dice che i vettori sono linearmente dipendenti.

Una base di V è un insieme {b1, ..., bn} linearmente indipendente il cui span è tutto V. Conseguenza: ogni vettore di V si scrive in modo unico come v = c1 b1 + c2 b2 + ... + cn bn. I numeri c1, ..., cn sono le coordinate di v rispetto alla base. Cambiare base significa cambiare il sistema di coordinate, non il vettore.

Teorema (dimensione invariante): tutte le basi di un dato spazio vettoriale finito-dimensionale hanno lo stesso numero di elementi. Questo numero, ben definito, si chiama dimensione dello spazio. Il teorema ha una prova classica (lemma di scambio di Steinitz) che non riportiamo: è una classe teorema, vale come fatto solido su cui costruire.

In R^n la base canonica è formata dai vettori e1=(1,0,...,0)e_1 = (1, 0, ..., 0), e2=(0,1,0,...,0)e_2 = (0, 1, 0, ..., 0), …, en=(0,...,0,1)e_n = (0, ..., 0, 1). È la base “ovvia” e quella rispetto a cui i vettori sono scritti per default: le coordinate rispetto alla base canonica coincidono con le componenti del vettore.

Il prodotto scalare o dot product in R^n è definito come

uv=u1v1+u2v2+...+unvn=sumi=1..nuiviu · v = u_1 v_1 + u_2 v_2 + ... + u_n v_n = sum_{i=1..n} u_i v_i.

L’operazione è semplice ma carica di significato. Geometricamente, il dot product misura quanto due vettori “puntano nella stessa direzione”, scalato dalle loro lunghezze. Algebricamente, è la generalizzazione naturale del prodotto fra numeri reali a oggetti multidimensionali. Statisticamente — sui vettori centrati — è proporzionale alla covarianza fra le loro componenti.

Tre proprietà fondamentali, tutte verificabili a mano dalla definizione:

  • Simmetria: uv=vuu · v = v · u.
  • Bilinearità: lineare in ciascun argomento, cioè (au+bu)v=a(uv)+b(uv)(a u + b u') · v = a (u · v) + b (u' · v) e analogamente sull’altro argomento.
  • Definita positività: vv0v · v ≥ 0, con uguaglianza solo se v è il vettore zero.

Il dot product è il pezzo di struttura aggiuntivo che trasforma uno spazio vettoriale “nudo” in uno spazio dove si possono misurare lunghezze e angoli.

La norma L2 o norma euclidea di un vettore è

v=sqrt(vv)=sqrt(v12+v22+...+vn2)||v|| = sqrt(v · v) = sqrt(v_1^2 + v_2^2 + ... + v_n^2).

In R^2 e R^3 coincide con la lunghezza geometrica della freccia, cioè con il teorema di Pitagora generalizzato a più dimensioni. In R^n è la sua definizione naturale.

La distanza euclidea fra due vettori è la norma della loro differenza:

d(u,v)=uvd(u, v) = ||u - v||.

Ci sono altre norme — L1 (somma dei valori assoluti delle componenti), L-infinito (massimo dei valori assoluti), p-norme generali — ma la L2 è quella che si comporta bene con il dot product e che fa da default. Una trattazione comparata è rimandata a norme-distanze.

In R^2 e R^3 si dimostra (e in R^n si definisce per estensione) che vale la relazione

uv=uvcos(theta)u · v = ||u|| · ||v|| · cos(theta),

dove theta è l’angolo fra i due vettori. Risolvendo rispetto al coseno:

cos(theta)=(uv)/(uv)cos(theta) = (u · v) / (||u|| · ||v||).

Questa quantità — la cosine similarity — misura solo l’allineamento, non la lunghezza dei vettori. Vale 1 quando i vettori sono paralleli e nello stesso verso, 0 quando sono perpendicolari (il dot product è nullo), -1 quando sono paralleli e di verso opposto. È bounded in [-1, 1] per la disuguaglianza di Cauchy-Schwarz, uvuv|u · v| ≤ ||u|| · ||v||, che è anche essa un teorema con prova classica.

cosine similarity geometry

Due vettori si dicono ortogonali se uv=0u · v = 0. In R^2 e R^3 corrisponde all’idea geometrica di “perpendicolare”. In R^n è una definizione, ma si comporta come l’idea geometrica suggerisce. Una base ortonormale è una base in cui i vettori sono a due a due ortogonali e tutti di norma 1. La base canonica di R^n è ortonormale: eiej=0e_i · e_j = 0 se iji ≠ j, eiei=1e_i · e_i = 1.

Un sottospazio di V è un sottoinsieme W che è esso stesso uno spazio vettoriale con le operazioni ereditate da V. Equivalentemente: W contiene il vettore zero ed è chiuso per somma e moltiplicazione per scalare. In R^3 i sottospazi sono esattamente: il punto origine, ogni retta passante per l’origine, ogni piano passante per l’origine, e tutto R^3. Niente altro.

Dato un vettore v non nullo, la proiezione ortogonale di u sulla direzione di v è

projv(u)=((uv)/(vv))vproj_v(u) = ((u · v) / (v · v)) · v.

dot product as projection

Geometricamente: è l’ombra di u sulla retta di v. Il residuo uprojv(u)u - proj_v(u) è ortogonale a v — è la componente di u che “esce” dalla direzione di v. Questa decomposizione u=projv(u)+(uprojv(u))u = proj_v(u) + (u - proj_v(u)) è uno strumento ovunque: la regressione lineare ai minimi quadrati risolve un problema di proiezione su un sottospazio, la PCA (Principal Component Analysis) trova iterativamente le direzioni su cui proiettare per massimizzare la varianza, e — vedremo nella Parte IX — l’attention dei transformer può essere letta come una proiezione modulata da un softmax.

Gram-Schmidt: come fabbricare una base ortonormale

Sezione intitolata “Gram-Schmidt: come fabbricare una base ortonormale”

Capita spesso di avere un insieme di vettori indipendenti ma non ortogonali, e di volere ottenere un insieme ortonormale con lo stesso span. L’algoritmo di Gram-Schmidt — dal nome di Jørgen Pedersen Gram (matematico danese, 1850–1916) e Erhard Schmidt (matematico tedesco, 1876–1959), anche se le idee precedono entrambi e risalgono a Laplace — fa esattamente questo. Dati v1,...,vkv_1, ..., v_k indipendenti, costruisce q1,...,qkq_1, ..., q_k ortonormali iterativamente:

  1. q1=v1/v1q_1 = v_1 / ||v_1||.
  2. Per i = 2, …, k: sottrae da v_i tutte le sue proiezioni sui q_j precedenti, poi normalizza. Cioè wi=visumj<i(viqj)qjw_i = v_i - sum_{j<i} (v_i · q_j) q_j, e qi=wi/wiq_i = w_i / ||w_i||.

L’idea è la stessa della proiezione ortogonale vista sopra, applicata in serie. Operativamente, Gram-Schmidt classico è instabile in floating point e si usa la sua variante “modified Gram-Schmidt” o, ancora meglio, decomposizioni QR via Householder. Il punto qui è concettuale: ortonormalizzare è un’operazione costruttiva, non un atto di fede. Se serve una base ortonormale, c’è una procedura per ottenerla.

Tre esempi eterogenei, dal calcolo a mano alla pipeline reale.

Prendiamo u = (3, 4) e v = (1, 2). Tutti i conti si fanno a mano in trenta secondi.

  • Norma di u: u=sqrt(32+42)=sqrt(9+16)=sqrt(25)=5||u|| = sqrt(3^2 + 4^2) = sqrt(9 + 16) = sqrt(25) = 5. (È il triangolo 3-4-5 di pitagorica memoria.)
  • Norma di v: v=sqrt(12+22)=sqrt(5)2.236||v|| = sqrt(1^2 + 2^2) = sqrt(5) ≈ 2.236.
  • Dot product: uv=31+42=3+8=11u · v = 3·1 + 4·2 = 3 + 8 = 11.
  • Distanza euclidea: uv=(2,2)=sqrt(4+4)=sqrt(8)2.828||u - v|| = ||(2, 2)|| = sqrt(4 + 4) = sqrt(8) ≈ 2.828.
  • Cosine similarity: cos(theta)=11/(52.236)0.984cos(theta) = 11 / (5 · 2.236) ≈ 0.984. Vicinissimo a 1: i due vettori puntano quasi nella stessa direzione.
  • Angolo: thetaarccos(0.984)10.3°theta ≈ arccos(0.984) ≈ 10.3°.
  • Proiezione di u su v: projv(u)=(11/5)(1,2)=(2.2,4.4)proj_v(u) = (11 / 5) · (1, 2) = (2.2, 4.4).

Tutto qui. Quando un modello in R^768 fa la stessa identica catena di operazioni 768 volte invece di 2, l’algebra è la stessa.

Verifichiamo a mano l’ortogonalità del residuo nella proiezione. Abbiamo projv(u)=(2.2,4.4)proj_v(u) = (2.2, 4.4). Il residuo è r=uprojv(u)=(32.2,44.4)=(0.8,0.4)r = u - proj_v(u) = (3 - 2.2, 4 - 4.4) = (0.8, -0.4). Calcoliamo rv=0.81+(0.4)2=0.80.8=0r · v = 0.8 · 1 + (-0.4) · 2 = 0.8 - 0.8 = 0. Esattamente zero, come la teoria prevede. Questo non è un caso: la proiezione è proprio la decomposizione di u nella sua “ombra su v” e nella sua componente “perpendicolare a v”, e per costruzione la seconda è ortogonale al primo. È lo stesso schema che la regressione lineare ai minimi quadrati implementa in dimensione qualsiasi.

Una piccola variante illuminante: sostituiamo u con il suo versore uhat=u/u=(0.6,0.8)u_hat = u / ||u|| = (0.6, 0.8) e v con vhat=v/v=(1/sqrt(5),2/sqrt(5))(0.447,0.894)v_hat = v / ||v|| = (1/sqrt(5), 2/sqrt(5)) ≈ (0.447, 0.894). Il dot product di due versori è direttamente la cosine similarity: uhatvhat=0.60.447+0.80.8940.984u_hat · v_hat = 0.6 · 0.447 + 0.8 · 0.894 ≈ 0.984, lo stesso numero di prima. Lavorare con vettori normalizzati semplifica i conti e fa coincidere dot product e cosine. È il motivo per cui molti vector database normalizzano internamente gli embedding all’ingestione.

Embedding finti, di dimensione 4 per leggibilità. In produzione la dimensione è tipicamente fra 384 e 4096; cambia il numero, non la sostanza.

import numpy as np
king = np.array([0.6, 0.8, 0.1, 0.3])
queen = np.array([0.5, 0.9, 0.2, 0.3])
apple = np.array([0.1, 0.0, 0.7, 0.6])
def cos_sim(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(cos_sim(king, queen)) # ~0.99 — concetti vicini
print(cos_sim(king, apple)) # ~0.40 — concetti distanti
print(cos_sim(queen, apple)) # ~0.42
# l'analogia di Mikolov, in piccolo
diff = king - np.array([0.55, 0.1, 0.1, 0.3]) # king - "man"
analogy = diff + np.array([0.45, 0.1, 0.1, 0.3]) # + "woman"
print(cos_sim(analogy, queen)) # alto, se gli embedding sono buoni

np.dot è il prodotto scalare, np.linalg.norm è la norma L2. Nient’altro. Su una libreria di embedding reale si chiama cosinesimilaritycosine_similarity o si delega a un vector store, ma sotto il cofano la funzione è esattamente quella scritta sopra.

Vale la pena notare cosa NON serve in questo esempio. Non serve invertire matrici, non servono autovalori, non servono derivate. Cosine similarity richiede solo le quattro operazioni aritmetiche e una radice quadrata. Eppure è il pezzo di matematica che fa funzionare la maggior parte dei sistemi di retrieval di produzione del 2026. Il rapporto fra semplicità degli ingredienti e complessità dei comportamenti emergenti è un tema ricorrente in tutto il libro.

Una pipeline RAG essenziale, ridotta all’osso:

  1. Ogni documento did_i del corpus viene trasformato in un vettore eie_i in R^d (per esempio d = 1536 con text-embedding-3-small di OpenAI, o d = 768 con un Sentence-BERT).
  2. Al runtime, la query dell’utente viene trasformata in un vettore q con lo stesso modello.
  3. Si cercano i top-k indici i tali che la cosine similarity cos(q,ei)cos(q, e_i) è massima, di solito tramite un indice ANN (Approximate Nearest Neighbor) come HNSW o IVF, perché la ricerca esatta su milioni di documenti sarebbe troppo lenta.
  4. I documenti corrispondenti vengono iniettati nel prompt del LLM, che risponde all’utente avendoli sotto gli occhi.

Da notare un dettaglio implementativo importante: il modello di embedding usato per il corpus deve essere identico a quello usato per le query. Mescolare modelli diversi produce vettori in spazi diversi che non sono confrontabili — il dot product fra un embedding text-embedding-3-small e un embedding Sentence-BERT è un numero che non significa nulla. Sembra ovvio, ma è uno dei bug più comuni in pipeline RAG mal scritte: durante una migrazione di modello, qualcuno reindicizza il corpus con il nuovo modello ma dimentica di aggiornare l’endpoint di query, oppure viceversa. Il sistema risponde, restituisce documenti, ma sono random. Non c’è nessun errore di runtime. Solo che la qualità delle risposte crolla in modo silenzioso.

Espandiamo un dettaglio del passo 3. Una ricerca esatta su un milione di documenti significa calcolare un milione di dot product per ogni query. In dimensione 1536, sono 106×15361.5×10910^6 × 1536 ≈ 1.5 × 10^9 moltiplicazioni e altrettante addizioni. Su una CPU moderna questo è dell’ordine del secondo per query — troppo lento per UX interattiva. Gli indici ANN — Approximate Nearest Neighbor — sacrificano un po’ di accuratezza (recall del 95–99% invece del 100%) per ottenere ricerche in millisecondi. HNSW (Hierarchical Navigable Small World, Malkov & Yashunin, 2018), IVF (Inverted File, Jégou et al., 2011), Product Quantization (Jégou, Douze, Schmid, 2010) sono le tre famiglie principali. Il capitolo ann-intro (in preparazione) entrerà nel dettaglio.

Il “magico” sta nell’embedding model — il fatto che un modello pre-addestrato su grandi corpora produca rappresentazioni in cui frasi semanticamente vicine finiscono in punti vicini di R^d. Ma una volta che gli embedding ci sono, il retrieval è algebra lineare di prima settimana: prodotti scalari, norme, ordinamento. Non c’è altro.

Diversi testi usano notazioni diverse, e mescolarle confonde. In questo libro:

  • I vettori sono in grassetto minuscolo quando il rendering lo permette: u, v, x. Quando il grassetto non si distingue, lasciamo solo la lettera in tondo. Le frecce sopra le lettere sono evitate, eccetto nelle figure.
  • Le matrici sono in maiuscolo in tondo: A, W, K. Le righe e le colonne si indicano con doppio indice o con notazione a slice (A[i, :], A[:, j]).
  • Gli scalari sono in minuscolo corsivo: a, c, eta.
  • Il prodotto scalare si scrive uvu · v o equivalentemente uTvu^T v (riga per colonna). I due sono identici per vettori reali; preferiamo il primo per leggibilità.
  • La norma L2 è scritta v||v||. Quando serve disambiguare v2||v||_2 con pedice esplicito.

Non è solo cosmesi. Quando un paper di transformer scrive softmax(QKT/sqrt(dk))Vsoftmax(QK^T / sqrt(d_k)) V, riconoscere che Q e K sono matrici, V è una matrice, e QKTQK^T è il batch dei dot product fra ogni query e ogni key, è la differenza fra leggere e capire.

Una rapida mappa di dove i vettori compaiono nei sistemi che useremo nel resto del libro.

  • Embedding di parole, frasi, documenti: word2vec, GloVe, fastText, Sentence-BERT, OpenAI text-embedding-*, Cohere, Voyage. Output: vettori in R^d con d fra 100 e 4096.
  • Vector database e RAG retrieval: Pinecone, Weaviate, Qdrant, Milvus, pgvector. Tutti operano su cosine similarity o dot product, con indici ANN per la scalabilità.
  • Classificatori softmax: l’ultimo layer di un classificatore neurale è una matrice W le cui righe sono “vettori prototipo” delle classi; il logit della classe k è il dot product fra l’attivazione e la riga k.
  • Clustering: k-means usa distanze euclidee. Spherical k-means e DBSCAN-with-cosine usano cosine. La scelta dipende da cosa significa “vicino” nei dati specifici.
  • PCA e riduzione dimensionale: le direzioni principali di un dataset sono autovettori della matrice di covarianza, e proiettare un dato su quelle direzioni è la stessa operazione vista sopra.
  • Gradient descent: il gradiente della loss rispetto ai parametri è un vettore in R^p (con p numero di parametri). Il passo di aggiornamento è thetat+1=thetatetagradtheta_{t+1} = theta_t - eta · grad, una sottrazione vettoriale. In un LLM moderno p è dell’ordine di 10910^9 o 101010^10: ogni passo di training muove un vettore in dieci miliardi di dimensioni di una piccola quantità in una direzione calcolata. La magia non è che funzioni, è che funzioni con una struttura algebrica così semplice.
  • Attention nei transformer: query, key, value sono vettori. Attention puntuale calcola softmax(qKT/sqrt(dk))Vsoftmax(q · K^T / sqrt(d_k)) V. Il cuore è il dot product, il resto è normalizzazione e somma pesata.
  • Steering vectors e activation engineering: nella ricerca recente (2023–2025) di mech interp si modifica il comportamento di un LLM aggiungendo o sottraendo specifici vettori alle attivazioni intermedie. È letteralmente algebra vettoriale applicata a uno stato interno del modello. Il capitolo steering-vectors (in preparazione) entrerà nei dettagli.
  • Diffusion models: il rumore aggiunto a un’immagine durante il forward process è un vettore gaussiano; il denoising stimato dalla rete è un altro vettore. Un campionamento di diffusion è una sequenza di sottrazioni vettoriali con un controllo di rumore.

Ogni voce di questa lista verrà ripresa nei capitoli successivi. Il punto qui non è elencare applicazioni in modo esaustivo, ma far notare che il vocabolario vettoriale ricorre, e che padroneggiarlo a questo livello dà accesso a tutte queste aree senza nuovi prerequisiti.

Riprendiamo il filo. Lo stesso vettore in R^d ammette quattro letture, e quando si fanno cose serie con i sistemi AI bisogna saper passare da una all’altra a richiesta:

  • Geometrica: una freccia in uno spazio con metrica e angoli.
  • Algebrica: un elemento di un insieme con due operazioni che soddisfano otto regole.
  • Computazionale: un buffer di n float in memoria, su cui agiscono kernel SIMD/GPU.
  • Semantica: una rappresentazione condensata di un oggetto del mondo, posizione in uno spazio di significato appreso.

Le quattro letture coincidono sull’oggetto matematico, ma illuminano facce diverse. La geometrica è dove nasce l’intuizione. L’algebrica è dove si dimostrano i teoremi. La computazionale è dove si scrive codice efficiente. La semantica è dove si capisce perché tutto questo serve. Un developer che agent-coda passa molte ore al giorno fra la terza e la quarta, ma ha bisogno della prima per non perdersi e della seconda per leggere paper.

L’intuizione 3D che ci accompagna fin qui è preziosa, ma alta dimensione e numerica reale la maltrattano in modo non ovvio. Ignorare questi limiti porta a scrivere codice che funziona “in teoria” ma esplode in produzione.

In R^768 nessuna intuizione visiva tiene davvero. Alcuni risultati controintuitivi che si dimostrano formalmente:

  • Il volume di una sfera unitaria si concentra in un guscio sottile vicino alla superficie: in R^d, la frazione di volume entro raggio 1ε1 - ε tende a zero esponenzialmente in d. Tutta la massa è “sulla buccia”.
  • Il volume di un cubo unitario si concentra negli angoli: la sfera inscritta nel cubo [-1, 1]^d ha volume che, rispetto al cubo, tende a zero come d cresce. Il cubo è quasi tutto angoli.
  • Coppie di vettori random sono quasi ortogonali: presi due vettori uniformi sulla sfera unitaria di R^d, il loro coseno ha media 0 e varianza dell’ordine di 1/d. In d alto, quasi tutte le coppie hanno coseno ≈ 0.

Questo ultimo fatto è la base teorica del lemma di Johnson-Lindenstrauss e del perché si possono “impilare” molti embedding in uno stesso spazio senza che si pestino i piedi: c’è quasi sempre una direzione libera disponibile.

Il risultato classico di Aggarwal, Hinneburg e Keim — On the Surprising Behavior of Distance Metrics in High Dimensional Space, ICDT 2001 — mostra che per molte distribuzioni di dati il rapporto

(maxid(q,xi)minid(q,xi))/minid(q,xi)(max_i d(q, x_i) - min_i d(q, x_i)) / min_i d(q, x_i)

tende a zero al crescere della dimensione. Tradotto: in alta dimensione, “il punto più vicino” e “il punto più lontano” da una query sono distanze quasi identiche. Il concetto stesso di “vicino” perde forza.

distance concentration in high dimension

Conseguenza pratica: in alta dimensione la euclidea diventa rumorosa, e la cosine similarity — che ignora le norme e guarda solo l’angolo — tende a discriminare meglio fra vettori semanticamente diversi. È il motivo per cui i vector database di default offrono ricerca per cosine, non per euclidea.

Sui vettori normalizzati a norma 1 (cioè sulla sfera unitaria), cosine similarity ed euclidea sono monotonamente equivalenti, perché vale l’identità

uv2=u2+v22(uv)=22(uv)=22cos(theta)||u - v||^2 = ||u||^2 + ||v||^2 - 2 (u · v) = 2 - 2 (u · v) = 2 - 2 cos(theta).

Cioè: ordinare per cosine decrescente equivale a ordinare per euclidea crescente, su vettori unitari. Ma se i vettori non sono normalizzati, le due metriche danno ranking diversi. Negli embedding di word2vec, per esempio, le parole frequenti hanno tipicamente norma maggiore: usare la euclidea privilegia parole rare in modi non semanticamente significativi, mentre la cosine annulla questo bias. Una buona pipeline normalizza esplicitamente o usa cosine.

Idealmente vorremmo che i nostri embedding fossero distribuiti isotropicamente — cioè uniformemente — sulla sfera unitaria, in modo che il coseno fra coppie random sia centrato su zero e tutta la varianza sia “informazione”. La realtà è diversa.

anisotropy of LLM embeddings

Kawin Ethayarajh, in How Contextual are Contextualized Word Representations? (EMNLP 2019), dimostra empiricamente che gli embedding di BERT, GPT-2 ed ELMo vivono in un cono stretto dell’iperspazio: la cosine similarity media fra coppie random non è 0 ma un valore positivo, tipicamente fra 0.3 e 0.6 a seconda del layer. Gao e colleghi, in Representation Degeneration Problem in Training Natural Language Models (ICLR 2019), spiegano una causa strutturale: il training con softmax tied embeddings spinge tutte le rappresentazioni verso una direzione comune.

Conseguenze: cosine similarity grezza fra embedding LLM è un segnale rumoroso, perché ha già un offset positivo “di base”. Per fare retrieval serio si applicano whitening, centratura, o si usano modelli appositi (Sentence-BERT, E5, BGE) che addestrano per isotropia. Oppure si normalizza la cosine sottraendo la media degli embedding del corpus.

Questa è una classe filiazione: il problema dell’anisotropia non è un’analogia con qualcos’altro, è un fatto empirico sui modelli reali, documentato in paper specifici, da tenere presente.

Anisotropia, secondo livello: implicazioni operative

Sezione intitolata “Anisotropia, secondo livello: implicazioni operative”

Il fatto che gli embedding stiano in un cono ha un secondo effetto, più sottile, che merita attenzione. Quando si applica clustering (k-means, DBSCAN) a embedding LLM grezzi, la struttura “globale” è dominata dall’asse principale del cono — la direzione lungo cui tutti gli embedding sono allineati. I primi cluster trovati spesso non corrispondono a temi semantici ma a quanta “energia” ha l’embedding lungo l’asse anisotropo. Per questo motivo, prima di clusterizzare, è prassi sottrarre la media del corpus (centratura), oppure proiettare via le prime k direzioni principali del corpus stesso (post-processing detto “all-but-the-top” di Mu, Bhat, Viswanath, ICLR 2018). Senza queste accortezze il clustering sembra “non funzionare”, ma il problema non è k-means: è la geometria di partenza.

Questa è una classe filiazione documentata da papers specifici, non un’analogia. Il punto da portare a casa: gli embedding “out of the box” di un LLM richiedono un layer di processing geometrico prima di essere usati come spazio metrico decente.

In float32, sommare molti termini di magnitudine simile fa perdere cifre significative (errore di cancellazione catastrofica nel caso peggiore). Il prodotto scalare di due vettori molto lunghi può overfloware in float16 o bf16. Il softmax exp(xi)/sumjexp(xj)exp(x_i) / sum_j exp(x_j) esplode se gli xix_i sono grandi e va sempre stabilizzato sottraendo max(x) prima dell’esponenziale.

Non sono dettagli accademici. Sono il motivo per cui il training di un modello in fp16 può divergere se manca un dettaglio di accumulazione in fp32, o per cui il retrieval su un indice mal costruito restituisce NaN per certi embedding nulli. Trattare i vettori come oggetti aritmetici reali invece che come numeri matematici idealizzati è una disciplina che fa la differenza fra “il modello converge” e “il modello non converge”. Il capitolo quantization-base (in preparazione) tornerà su questi punti in modo sistematico.

Approfondiamo un caso che ricorre. La funzione softmax(x)i=exp(xi)/sumjexp(xj)softmax(x)_i = exp(x_i) / sum_j exp(x_j) trasforma un vettore di logit in un vettore di probabilità. È usata ovunque: classificatori, attention, sampling. Ma se gli xix_i sono grandi — diciamo xi=1000x_i = 1000 — allora exp(xi)=exp(1000)exp(x_i) = exp(1000) è un numero che fp32 non rappresenta: overflow, NaN, fine del calcolo.

La soluzione, ben nota ma non sempre evidente la prima volta che ci si imbatte nel bug, è sottrarre il massimo: softmax(x)i=exp(ximax(x))/sumjexp(xjmax(x))softmax(x)_i = exp(x_i - max(x)) / sum_j exp(x_j - max(x)). Il risultato è matematicamente identico (numeratore e denominatore sono moltiplicati per lo stesso fattore exp(-max(x)), che si semplifica), ma numericamente ora il massimo argomento dell’esponenziale è 0, e exp(0) = 1 è perfettamente rappresentabile. Tutte le librerie serie lo fanno internamente. Vale la pena saperlo perché ogni tanto bisogna implementare un softmax custom — per esempio in CUDA per kernel ottimizzati — e dimenticare la sottrazione del massimo è il bug numero uno.

La cosine similarity non è definita se uno dei due vettori è zero, perché v=0||v|| = 0 e si divide per zero. Documenti vuoti, frasi tutte di stopword, errori del tokenizer: tutto può produrre embedding nulli o quasi-nulli. Codice di produzione deve trattare il caso, almeno con un controllo if norm == 0: return 0 o con un sentinel esplicito.

Analogamente, due embedding identici hanno cosine 1 esatta — utile come test, e utile come trigger di duplicate detection in pipeline di indicizzazione.

Un embedding “di dimensione 1536” non significa che lo spazio sfruttato sia genuinamente 1536-dimensionale. La dimensione effettiva — misurata per esempio dalla partecipation ratio degli autovalori della matrice di covarianza, (sumlambdai)2/sumlambdai2(sum lambda_i)^2 / sum lambda_i^2 — è tipicamente molto più bassa. Per molti modelli moderni si osservano dimensioni effettive di poche centinaia, anche su embedding nominalmente da migliaia. Significa che la maggior parte delle direzioni dello spazio non porta segnale: sono varianza residua, rumore, o ridondanza.

Implicazione pratica: si può comprimere significativamente — con PCA, con product quantization, con matryoshka embeddings — senza perdere granularità semantica utile. Allo stesso tempo, contare sulla dimensione nominale per “capacità di codifica” è ingannevole.

Vale la pena chiudere con un confine. Lo spazio vettoriale R^d è una struttura ricca, ma alcune nozioni che vorremmo non ci stanno dentro. Gerarchia: relazioni del tipo “A è un caso speciale di B” sono codificate male in spazi euclidei piatti, e meglio in spazi iperbolici (Nickel & Kiela, Poincaré Embeddings, NeurIPS 2017). Ordine ciclico: ore del giorno, giorni della settimana, hanno una struttura ciclica che embedding lineari rappresentano in modo subottimo. Composizionalità sintattica: la struttura di un albero di parsing non è naturalmente vettoriale. Sapere quando il modello mentale “tutto è R^d” si rompe è parte del mestiere di chi costruisce sistemi.

  • norme-distanze — approfondisce L1, L2, L-infinito, distanze di Mahalanobis e cosine, e quando ciascuna è la scelta giusta. Naturale capitolo successore di questo.
  • prodotto-scalare — focus sul dot product come proiezione e come misura di somiglianza, con applicazioni a kernel e a similarity search.
  • matrici-trasformazioni — il passo successivo: dai vettori statici alle trasformazioni lineari che li muovono. È la chiave per tutto quello che succede dentro un layer neurale.
  • autovalori-autovettori-intuito — direzioni privilegiate di una trasformazione, fondamento di PCA e di parte di mech interp.
  • embedding-concetto — spazi di embedding e direzioni semantiche; estensione applicativa di questo capitolo.
  • curse-dimensionalita — la patologia in alta dimensione, in dettaglio. Estende la sezione “Dove si rompe” in un trattamento sistematico.
  • ponte distribuzionale-embeddings — il ponte cognitivo verso la rappresentazione vettoriale del significato. Lì il “perché” di word2vec, qui il “come” matematico.
  • word2vec 2013 — il momento storico in cui i vettori smettono di essere matematica e diventano linguaggio.
  • convenzioni-notazione — notazione usata nel libro per vettori, matrici, norme.

Quando si lavora con vettori in un sistema reale, vale la pena tenere a portata una checklist:

  • Qual è la dimensione nominale dei vettori? Qual è la dimensione effettiva attesa?
  • I vettori sono normalizzati a norma 1? Se no, cosine ed euclidea daranno ranking diversi.
  • Il modello che ha prodotto i vettori è isotropo o anisotropo? Se anisotropo, serve centratura.
  • C’è un caso “vettore zero” possibile? Come è gestito?
  • Stiamo accumulando in fp32 o in fp16? Il risultato è stabile?
  • Vettori query e vettori indicizzati provengono dallo stesso modello?

Sembrano domande banali. Sono esattamente le domande che separano una pipeline che funziona da una che fallisce silenziosamente.

Un esempio terra-terra. In R^2 prendiamo una nuova base b1=(1,1)b_1 = (1, 1) e b2=(1,1)b_2 = (1, -1). Il vettore v = (3, 1) nella base canonica si scrive come v=a1b1+a2b2v = a_1 b_1 + a_2 b_2. Risolvendo: a1+a2=3a_1 + a_2 = 3 e a1a2=1a_1 - a_2 = 1, da cui a1=2a_1 = 2 e a2=1a_2 = 1. Quindi v ha coordinate (2, 1) nella nuova base. Il vettore non è cambiato — è ancora la stessa freccia — ma il “punto di vista” sì.

Questa è esattamente l’operazione che PCA fa quando trasforma i dati nelle direzioni principali, o che mech interp fa quando sceglie una base di “feature” interpretabili invece della base neuron-canonica.

Una convenzione che a volte confonde: in algebra lineare formale i vettori sono “colonne” (matrici n × 1) e i loro trasposti sono “righe” (matrici 1 × n). Il dot product uvu · v si scrive allora come prodotto matriciale uTvu^T v (riga per colonna) e produce uno scalare (matrice 1 × 1). Al contrario uvTu v^T (colonna per riga) produce una matrice n × n di rango 1, oggetto completamente diverso.

NumPy e PyTorch in pratica trattano i vettori come array unidimensionali e gestiscono la trasposizione implicitamente nelle funzioni. Ma quando si legge un paper o si scrive matematica formale, mantenere la distinzione fra uTvu^T v e uvTu v^T è essenziale. La differenza fra “uno scalare” e “una matrice n × n” è il tipo di confusione che fa sbagliare le derivate in backprop.

Un confronto utile: spazio vettoriale contro spazio metrico

Sezione intitolata “Un confronto utile: spazio vettoriale contro spazio metrico”

Vale la pena segnare un confine. Uno spazio metrico è un insieme con una funzione distanza che soddisfa tre proprietà (positività, simmetria, disuguaglianza triangolare). Uno spazio vettoriale è una struttura algebrica con somma e moltiplicazione per scalare, che soddisfa otto assiomi.

I due concetti sono ortogonali. Uno spazio vettoriale può non avere distanza definita (gli assiomi non parlano di distanze). Uno spazio metrico può non avere struttura vettoriale (per esempio una varietà differenziabile generica).

Quando uniamo le due cose — somma vettoriale più dot product, da cui norma e distanza derivate — otteniamo uno spazio prehilbertiano. È in questo tipo di spazio che lavoriamo praticamente sempre nei sistemi AI. Tenere a mente la distinzione aiuta a leggere i paper più formali, dove “embedding space” può significare cose leggermente diverse a seconda dell’autore.

Il capitolo ha introdotto un vocabolario minimo. In sintesi:

  • Un vettore è una lista ordinata di numeri reali. Si somma componente per componente, si moltiplica per uno scalare componente per componente.
  • Uno spazio vettoriale è qualunque insieme con due operazioni che soddisfano otto assiomi. Per noi è quasi sempre R^d.
  • Una base è un insieme di vettori indipendenti il cui span è tutto lo spazio. La sua cardinalità è la dimensione.
  • Il prodotto scalare misura allineamento. La norma L2 misura lunghezza. La cosine similarity misura solo angolo, ignora le magnitudini.
  • L’ortogonalità generalizza la perpendicolarità. La proiezione è l’ombra di un vettore su una direzione.
  • In alta dimensione l’intuizione 3D si rompe: distanze concentrano, vettori random sono quasi ortogonali, embedding LLM sono anisotropi.
  • In floating point non tutto è esatto: stabilità richiede attenzione, soprattutto nel softmax.

Questo è il prerequisito per ogni capitolo successivo della Parte IV e per gran parte della Parte IX. Il prossimo passo naturale è approfondire come si misurano lunghezze e distanze quando la L2 non è la scelta giusta — argomento di norme-distanze.

  • Strang, Gilbert. Introduction to Linear Algebra, 6a ed., Wellesley-Cambridge, 2023. Il riferimento operativo, accompagnato dalle lezioni di MIT 18.06 su OCW. Ottimo equilibrio fra geometria e calcolo. Capitoli 1, 3, 4 coprono integralmente il materiale di questo capitolo, con esercizi.
  • Axler, Sheldon. Linear Algebra Done Right, 4a ed., Springer, 2024 (open access). Per chi vuole il taglio assiomatico pulito, deliberatamente determinant-free. Disponibile gratis su linear.axler.net.
  • 3Blue1Brown. Essence of Linear Algebra (serie YouTube, 2016–2018). Quindici episodi brevi, visualizzazioni geometriche eccellenti. Da vedere come complemento, non come sostituto.
  • Goodfellow, Bengio, Courville. Deep Learning, MIT Press, 2016, capitolo 2. Sintesi mirata all’ML, con notazione coerente con il resto del libro. Disponibile gratis online su deeplearningbook.org.
  • Boyd, Vandenberghe. Introduction to Applied Linear Algebra, Cambridge, 2018. Taglio applicativo modernissimo, esempi di data science, codice in Julia/Python. Free online.