Salta ai contenuti

Prodotto scalare come proiezione e somiglianza

Una sola operazione risponde a tre domande diverse: quanto due vettori si assomigliano, quanto uno si proietta sull’altro, che angolo formano. Capirla bene significa avere in mano la chiave operativa di attention, retrieval e classificatori lineari.

Quando un sistema di retrieval recupera i documenti più “vicini” a una query, quando un transformer decide a quale token guardare, quando un classificatore lineare separa due classi, succede sempre la stessa cosa: si calcola un prodotto scalare. Tre problemi che sembrano diversi — somiglianza, attenzione, decisione — si appoggiano allo stesso meccanismo.

Il dot product è già comparso nel libro come ingrediente. In vettori e spazi è servito a definire ortogonalità; in norme e distanze è entrato nella formula della norma euclidea e della cosine similarity. Qui è il protagonista: lo guardiamo come oggetto algebrico (forma bilineare), come oggetto geometrico (lunghezza di un’ombra), come funzione di similarità, e come motore matematico di attention.

L’obiettivo non è memorizzare formule. È costruire l’intuizione che permette, davanti a una nuova architettura, di riconoscere il prodotto scalare sotto qualsiasi travestimento e di sapere quando è la metrica giusta e quando si rompe.

Tre conseguenze pratiche di questa intuizione, che orientano la lettura:

  • Capire attention senza panico. La sezione 3.2.1 di “Attention Is All You Need” è una pagina densa, ma una volta visto che QKTQ K^T è una matrice di prodotti scalari riga-per-riga, tutto il resto (softmax, scaling, multi-head) diventa decoro intorno a un’operazione che conoscevi dalle medie.
  • Scegliere la metrica giusta in retrieval. Cosine, dot product, L2 sembrano tre ricette intercambiabili. Con il quadro del prodotto interno e di Cauchy-Schwarz, sai esattamente quando coincidono e quando divergono, e perché i vector database normalizzano a monte.
  • Diagnosticare un modello che non impara. Quando l’attention non si distribuisce ma satura, o quando gli embedding sono “troppo simili a tutto”, il colpevole è quasi sempre nella geometria del prodotto scalare in alta dimensione. Sapere riconoscere questi sintomi è la differenza tra leggere log e capirli.

Il prodotto scalare ha tre nomi e una storia frammentata. La forma moderna nasce nell’Ottocento dal lavoro di Hermann Grassmann (1809-1877, matematico tedesco autodidatta che nel Die Ausdehnungslehre del 1844 sviluppa l’algebra dei vettori in dimensione arbitraria) e William Rowan Hamilton (1805-1865, matematico irlandese inventore dei quaternioni). La notazione che usiamo oggi — il puntino tra due vettori — viene dalla codifica in 3D che Josiah Willard Gibbs (1839-1903, fisico americano) e Oliver Heaviside (1850-1925, ingegnere inglese) producono tra il 1880 e il 1893, scartando i quaternioni come troppo pesanti per la fisica applicata.

La disuguaglianza che imbriglia il prodotto scalare alle norme è ancora più antica: Augustin-Louis Cauchy (1789-1857, analista francese) la dimostra nel suo Cours d’Analyse del 1821 nella forma discreta (sommadiaibialquadrato)(somma di a_i b_i al quadrato) minore o uguale a (sommaai2)(sommabi2)(somma a_i^2)(somma b_i^2). Hermann Schwarz (1843-1921, matematico tedesco) la estende agli integrali nel 1888. Da qui il nome Cauchy-Schwarz, a cui in alcuni paesi si aggiunge Bunyakovsky (versione russa del 1859).

Jørgen Pedersen Gram (1850-1916, matematico e attuario danese) introduce nel 1883 una matrice fatta di prodotti scalari a due a due — la matrice di Gram — per studiare l’indipendenza lineare via determinante. Quella matrice riapparirà nei kernel methods, nello style transfer in vision, e dentro l’attention dei transformer.

Nel 2017 Ashish Vaswani e colleghi pubblicano “Attention Is All You Need” (NeurIPS), dove la sezione 3.2.1 definisce esplicitamente la scaled dot-product attention: il blocco fondamentale dell’architettura transformer è una matrice di prodotti scalari, scalata per radicedidkradice di d_k, passata in softmax e usata per pesare i value. Il prodotto scalare diventa, in pratica, l’operazione più eseguita nel mondo dell’AI moderna.

Differenza rispetto ai capitoli vicini di questa Parte. In vettori e spazi il dot product appare come strumento per definire angoli e ortogonalità; in norme e distanze come ingrediente per L2 e cosine, una metrica fra le altre. In matrici e trasformazioni ricompare nella forma uTAvu^T A v. Qui non è ingrediente: è l’oggetto. Lo studiamo per ciò che è, non per ciò che genera.

Il decoder rapido dei nomi che useremo nel capitolo:

  • Cauchy-Schwarz: la disuguaglianza <u,v>uv|<u,v>| ≤ ||u|| ||v||, dimostrata nel 1821 (versione discreta) e 1888 (versione integrale). È il “freno” matematico che rende cosine similarity una grandezza ben definita in [-1, 1].
  • Gram-Schmidt: algoritmo per costruire basi ortonormali a partire da basi qualsiasi, via prodotti scalari iterati e sottrazioni.
  • Matrice di Gram: la tabella Gij=<vi,vj>G_ij = <v_i, v_j> di tutti i prodotti scalari a coppie di una collezione di vettori. Compare in kernel methods, style transfer, attention.
  • Mercer (1909): il teorema che caratterizza le funzioni kernel valide come quelle che producono matrici di Gram semidefinite positive.
  • Vaswani et al. (2017): il paper “Attention Is All You Need” che codifica il dot product come motore principale dei transformer.

concept graph showing dot product at center, edges to: angle, projection, similarity, attention QK^T, linear classifier, kernel; small badges: Cauchy-Schwarz, Gram-Schmidt

Prima di scrivere formule, due immagini diverse. La stessa quantità, due punti di vista che servono in momenti diversi.

Angolo 1: somma di prodotti, “quanto due liste concordano”

Sezione intitolata “Angolo 1: somma di prodotti, “quanto due liste concordano””

Hai due liste di numeri della stessa lunghezza. Le moltiplichi posizione per posizione e sommi. Se le liste hanno valori grandi e dello stesso segno nelle stesse posizioni, la somma è grande e positiva. Se i segni si invertono, la somma è negativa. Se sono “scorrelate” — cioè i prodotti termine-a-termine sono mescolati positivi e negativi senza struttura — la somma sta vicino a zero.

Questa è la lettura algebrica. È la più utile quando devi decidere se due pattern numerici sono compatibili: due embedding di parole, due rappresentazioni interne di un modello, una query e una key. Il prodotto scalare è il punteggio di concordanza coordinata per coordinata.

Esempio veloce. u = (3, 1, -2), v = (2, 0, -1). Prodotto: 3·2 + 1·0 + (-2)·(-1) = 6 + 0 + 2 = 8. Le componenti dove entrambi i vettori sono presenti e dello stesso segno (la prima e la terza) hanno spinto in alto. La seconda non contribuisce.

Disegna un vettore v sul foglio. Disegna un sole perpendicolare a v. Adesso disegna un secondo vettore u. L’ombra di u sulla retta che porta v ha una lunghezza signed: positiva se u “punta nella stessa direzione” di v, negativa se punta nel verso opposto, zero se u è perpendicolare a v.

Quella lunghezza, moltiplicata per la lunghezza di v, è il prodotto scalare. Se v è di lunghezza 1, è direttamente la lunghezza dell’ombra. Questa è la lettura geometrica. È la più utile quando devi pensare a proiezioni: quanta della “informazione” di u sta nella direzione di v.

two vectors u and v in 2D plane, dashed perpendicular projection of u onto line of v, label the projection length as <u,v>/||v||, mark angle theta between them

Un terzo modo di guardarlo, particolarmente utile quando si lavora con embedding e attention. Pensa al prodotto scalare come a un punteggio di compatibilità tra una “domanda” e un‘“etichetta” rappresentate nello stesso spazio. La domanda è un pattern di valori positivi e negativi su d coordinate; l’etichetta è un altro pattern simile. Il prodotto scalare premia le coordinate dove i due pattern hanno segni concordi e penalizza dove sono discordi.

In retrieval questo è letterale: la query è un vettore, il documento è un vettore, il prodotto scalare risponde a “quanto la query risuona con questo documento”. In attention, ogni query chiede a ogni key “ci somigliamo?”, e il dot product è la risposta numerica. Softmax poi trasforma quei punteggi in pesi che sommano a uno.

Le tre immagini — concordanza algebrica, ombra geometrica, compatibilità operativa — sono lo stesso oggetto matematico letto a tre profondità diverse. Un teorema (lo vedremo tra poco) lo garantisce: la somma di prodotti è uguale a uvcos(theta)||u|| ||v|| cos(theta). Algebra e geometria coincidono perché siamo in uno spazio euclideo con base ortonormale. Cambia la base in modo “storto” e la somma di prodotti smette di calcolare l’angolo che vedi sul foglio: lavora in una geometria diversa.

Su uno spazio vettoriale reale V, un prodotto interno è una funzione <,><·,·> che a due vettori associa un numero reale, e che soddisfa:

  1. Bilinearità: <au+bv,w>=a<u,w>+b<v,w><a u + b v, w> = a<u,w> + b<v,w> (lineare nel primo argomento; per simmetria, anche nel secondo).
  2. Simmetria: <u,v>=<v,u><u, v> = <v, u>.
  3. Definita positiva: <v,v><v, v> è maggiore o uguale a zero, con uguaglianza solo se v è il vettore nullo.

Il dot product canonico in R^n è il caso in cui <u,v>=sommaiuivi<u, v> = somma_i u_i v_i. Si scrive anche uvu · v o uTvu^T v (riga per colonna nella notazione matriciale).

Esistono però altri prodotti interni validi sullo stesso spazio. Se A è una matrice simmetrica e definita positiva, allora <u,v>A=uTAv<u, v>_A = u^T A v è un prodotto interno. La norma indotta sqrt(uTAu)sqrt(u^T A u) è la base della distanza di Mahalanobis usata in statistica multivariata.

Verifichiamo gli assiomi rispetto al dot product canonico. Bilinearità: <au+bv,w>=sommai(aui+bvi)wi=asommaiuiwi+bsommaiviwi<a u + b v, w> = somma_i (a u_i + b v_i) w_i = a somma_i u_i w_i + b somma_i v_i w_i. Tornano a a<u,w>+b<v,w>a<u,w> + b<v,w>. Simmetria: sommaiuivi=sommaiviuisomma_i u_i v_i = somma_i v_i u_i, banale. Definita positiva: <v,v>=sommaivi2<v, v> = somma_i v_i^2, somma di quadrati, zero solo se ogni viv_i è zero. Tutti soddisfatti.

L’utilità di partire dagli assiomi e non dalla formula sommauivisomma u_i v_i è che la stessa intuizione (Cauchy-Schwarz, proiezione, ortogonalità) si trasferisce intatta a contesti molto diversi: spazi di funzioni con <f,g>=integraledif(x)g(x)dx<f, g> = integrale di f(x) g(x) dx, spazi di matrici con <A,B>=traccia(ATB)<A, B> = traccia(A^T B), spazi di kernel methods, spazi di Hilbert in fisica quantistica. Cambia la formula concreta, non cambia la struttura.

In R^n con base ortonormale vale:

<u,v>=sommaiuivi=uvcos(theta)<u, v> = somma_i u_i v_i = ||u|| ||v|| cos(theta)

dove theta è l’angolo tra u e v misurato nello spazio euclideo standard. Questa equivalenza è un teorema, non una definizione. Si dimostra applicando il teorema del coseno al triangolo formato da 0, u e v.

L’equivalenza si rompe se cambi metrica. In <u,v>A<u,v>_A, la “geometria visibile” non è più la geometria della tua intuizione: angoli e ortogonalità sono ridefiniti rispetto ad A. Una coppia di vettori può essere ortogonale in senso canonico e non ortogonale in senso A, e viceversa.

Schizzo della dimostrazione (teorema del coseno applicato al triangolo). Considera il triangolo con vertici 0, u, v. I lati hanno lunghezze u||u||, v||v||, uv||u - v||. Il teorema del coseno della trigonometria classica dice uv2=u2+v22uvcos(theta)||u - v||^2 = ||u||^2 + ||v||^2 - 2 ||u|| ||v|| cos(theta). Per altra via, espandendo con la bilinearità, uv2=<uv,uv>=u2+v22<u,v>||u - v||^2 = <u-v, u-v> = ||u||^2 + ||v||^2 - 2 <u, v>. Confrontando le due espressioni: <u,v>=uvcos(theta)<u, v> = ||u|| ||v|| cos(theta). Fine.

L’argomento è breve perché la geometria fa il lavoro pesante: il teorema del coseno è la verità geometrica primitiva, l’algebra del prodotto scalare la traduce in coordinate.

Per ogni coppia di vettori u, v in uno spazio con prodotto interno:

<u,v><=uv|<u, v>| <= ||u|| ||v||

con uguaglianza se e solo se u e v sono linearmente dipendenti (uno è multiplo dell’altro).

Dimostrazione classica via proiezione. Definisci w=u(<u,v>/<v,v>)vw = u - (<u,v>/<v,v>) v. Per costruzione, <w,v>=0<w, v> = 0 (w è ortogonale a v). Calcola <w,w><w, w> e usa la sua non-negatività: espandendo si ottiene <u,u><v,v>>=<u,v>2<u,u> <v,v> >= <u,v>^2, che è la disuguaglianza al quadrato.

Conseguenze immediate:

  • cos(theta)=<u,v>/(uv)cos(theta) = <u,v> / (||u|| ||v||) cade sempre in [-1, 1]. La definizione di angolo via dot product è ben posta.
  • Disuguaglianza triangolare per la norma L2: u+v<=u+v||u + v|| <= ||u|| + ||v||. Dimostrabile espandendo u+v2=<u+v,u+v>||u+v||^2 = <u+v, u+v> e applicando Cauchy-Schwarz al termine misto.
  • Il dot product è “frenato” dalle norme: non può crescere senza che almeno una norma cresca. È il motivo per cui la cosine similarity (dot product normalizzato) sta in [-1, 1].
  • Disuguaglianza di Hölder e altre derivate: Cauchy-Schwarz è il caso p = q = 2 di una famiglia più ampia, ed è la più usata in pratica perché si appoggia a L2 che è la norma “naturale” del prodotto scalare.

Cauchy-Schwarz è l’argine matematico del prodotto scalare. Ogni volta che ragioniamo su similarità in [-1, 1], su attention pesata, su angoli ben definiti tra vettori, stiamo implicitamente invocando questa disuguaglianza. Vale la pena ricordare la sua data: 1821 per la versione discreta, 1888 per quella continua. Tutta l’AI moderna si appoggia su un teorema vecchio di due secoli.

Dati v diverso da zero e u qualsiasi, la proiezione di u sulla retta generata da v è:

projv(u)=(<u,v>/<v,v>)v=(<u,v>/v2)vproj_v(u) = (<u, v> / <v, v>) v = (<u, v> / ||v||^2) v

Se v è unitario (v=1||v|| = 1) si semplifica in projv(u)=<u,v>vproj_v(u) = <u,v> v, e il numero <u,v><u, v> è la lunghezza signed dell’ombra di u sulla direzione di v.

È un TEOREMA che la proiezione esiste, è unica, e minimizza la distanza tra u e la retta di v. In termini operativi: tra tutti i punti della retta di v, il piede della perpendicolare è quello più vicino a u. La generalizzazione a sottospazi di dimensione qualsiasi è il “teorema del piede della perpendicolare”, e sta dietro a metodi che vanno dai minimi quadrati al PCA.

Decomposizione ortogonale. Da qui un fatto operativo importante: ogni vettore u si decompone in modo unico come u=projv(u)+(uprojv(u))u = proj_v(u) + (u - proj_v(u)), dove il primo termine è parallelo a v e il secondo è ortogonale a v. Quando estendi la proiezione a un sottospazio W, scrivi u=uW+uperpu = u_W + u_perp con uWu_W in W e uperpu_perp nel suo complemento ortogonale. È la struttura che permette di “estrarre” da un vettore la componente lungo direzioni interessanti e scartare il resto, e che sta alla base di PCA, regressione lineare via minimi quadrati, e segnale-rumore in signal processing.

Perché minimizza la distanza. Sia x un punto qualsiasi sulla retta di v, scrivibile come x = t v per qualche scalare t. La distanza al quadrato è utv2=u22t<u,v>+t2v2||u - t v||^2 = ||u||^2 - 2 t <u, v> + t^2 ||v||^2. È un parabola in t, minimo in t=<u,v>/v2t* = <u, v> / ||v||^2, che è esattamente il coefficiente che definisce projv(u)proj_v(u). Quindi tra tutti i candidati sulla retta, la proiezione ortogonale è quello che minimizza la distanza. La connessione con i minimi quadrati è diretta: regressione lineare = proiezione ortogonale di un vettore di osservazioni sul sottospazio generato dalle colonne della matrice di design.

vector u, line through v, perpendicular dropped from u meeting the line at point p; label p as proj_v(u); side annotation: distance from u to p is minimized over all points on the line

Algoritmo per costruire una base ortonormale q1,...,qnq_1, ..., q_n partendo da una base qualsiasi v1,...,vnv_1, ..., v_n. A ogni passo si sottrae la componente già coperta dai precedenti q_j, normalizzando alla fine.

q_1 = v_1 / ||v_1||
per k da 2 a n:
u_k = v_k - somma_{j<k} <v_k, q_j> q_j
q_k = u_k / ||u_k||

Cosa fa: il termine <vk,qj>qj<v_k, q_j> q_j è la proiezione di v_k su q_j. Sottraendole tutte, resta ciò che è ortogonale al sottospazio già costruito. Normalizzi e prosegui.

Risultato: una matrice Q le cui colonne sono q1,...,qnq_1, ..., q_n soddisfa QTQ=IQ^T Q = I. Le matrici così fatte si chiamano ortogonali (su R) o unitarie (su C). Sono trasformazioni che preservano lunghezze e angoli — rotazioni e/o riflessioni.

In pratica la versione “classica” di Gram-Schmidt è numericamente instabile in float; si usa la modified Gram-Schmidt o, meglio ancora, decomposizione QR via Householder reflections. Le librerie standard (numpy.linalg.qr, scipy.linalg.qr) lo fanno per te. Vedi matrici e trasformazioni per la trattazione operativa.

Perché serve un’ortonormale. In una base ortonormale, le coordinate di un vettore rispetto alla base si calcolano con un solo prodotto scalare: se q1,...,qnq_1, ..., q_n sono ortonormali, le coordinate di u sono ci=<u,qi>c_i = <u, q_i>. Senza ortonormalità avresti dovuto risolvere un sistema lineare. Il dot product diventa una “macchina di lettura” delle coordinate, e questa è la ragione per cui basi ortonormali sono onnipresenti in numerica: trasformare in tali basi (FFT, wavelet, PCA) rende i calcoli più semplici e stabili.

Data una collezione di vettori v1,...,vkv_1, ..., v_k, la matrice di Gram è Gij=<vi,vj>G_ij = <v_i, v_j>. Tre proprietà da ricordare:

  • Simmetrica.
  • Sempre semidefinita positiva: per ogni vettore di coefficienti c, cTGc=sommacivi2c^T G c = || somma c_i v_i ||^2, che non può essere negativo.
  • Definita positiva (e quindi invertibile) se e solo se i vettori sono linearmente indipendenti. det(G)=0det(G) = 0 rivela dipendenza lineare.

La matrice di Gram appare ovunque sotto travestimenti diversi. Nei kernel methods, la “kernel matrix” Kij=K(xi,xj)K_ij = K(x_i, x_j) è una matrice di Gram in uno spazio di feature implicito (lo vedremo tra poco). In style transfer per vision (Gatys et al. 2015), la perdita di stile confronta matrici di Gram di feature maps. In attention, la matrice QKTQ K^T ha la stessa struttura formale, anche se non è simmetrica perché Q e K vengono da matrici di proiezione diverse.

Lettura geometrica del determinante. det(G)det(G) è il quadrato del volume del parallelotopo generato dai vettori v1,...,vkv_1, ..., v_k. Quando i vettori “collassano” su un sottospazio (perdono indipendenza), il volume va a zero e così il determinante. Questa è una via geometrica per capire l’indipendenza lineare: misuri il volume; se è zero, sei degenerato. È anche il motivo per cui l’inversione di G diventa numericamente instabile quando i vettori sono “quasi” dipendenti: il volume tende a zero e l’inversione amplifica gli errori.

Prendiu=(1,0)ev=(1,1).Vogliamoangolo,proiezioneecosine.Prendi u = (1, 0) e v = (1, 1). Vogliamo angolo, proiezione e cosine.

Prodotto scalare: <u,v>=11+01=1<u, v> = 1·1 + 0·1 = 1.

Norme: u=sqrt(12+02)=1||u|| = sqrt(1^2 + 0^2) = 1. v=sqrt(12+12)=sqrt(2)1.414||v|| = sqrt(1^2 + 1^2) = sqrt(2) ≈ 1.414.

Cosine: <u,v>/(uv)=1/sqrt(2)0.707<u,v> / (||u|| ||v||) = 1 / sqrt(2) ≈ 0.707. L’angolo theta è arccos(0.707) = 45 gradi. Sul foglio è esattamente quello che vedi: u sta lungo l’asse x, v sale a 45 gradi.

Proiezione di v su u: proju(v)=(<v,u>/u2)u=(1/1)(1,0)=(1,0)proj_u(v) = (<v,u> / ||u||^2) u = (1/1) · (1,0) = (1, 0). L’ombra di v sull’asse x ha lunghezza 1, esattamente la prima coordinata di v. Questa coincidenza non è un caso: proiettare su un asse della base canonica equivale a leggerne la coordinata corrispondente.

Inverti. Proiezione di u su v: projv(u)=(<u,v>/v2)v=(1/2)(1,1)=(0.5,0.5)proj_v(u) = (<u,v> / ||v||^2) v = (1/2) · (1,1) = (0.5, 0.5). L’ombra di u sulla direzione di v sta esattamente a metà strada lungo v: ha lunghezza projv(u)=sqrt(0.5)||proj_v(u)|| = sqrt(0.5), che è proprio cos(45°)u=(1/sqrt(2))1cos(45°) · ||u|| = (1/sqrt(2)) · 1. Cauchy-Schwarz è rispettata: <u,v>=1|<u,v>| = 1 è minore di uv=sqrt(2)||u|| ||v|| = sqrt(2).

Esempio 2: codice, cosine similarity tra embedding di parole

Sezione intitolata “Esempio 2: codice, cosine similarity tra embedding di parole”
import numpy as np
# embedding di parole 4-dim (esempio illustrativo, in pratica sono 256-1024)
re = np.array([ 0.8, 0.1, 0.6, -0.2])
regina = np.array([ 0.7, 0.2, 0.65, -0.15])
banana = np.array([-0.1, 0.9, -0.3, 0.5])
def cosine(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(cosine(re, regina)) # ~0.998 — molto alto
print(cosine(re, banana)) # ~-0.5 — basso, quasi opposto

Il dot product crudo np.dot(re, regina) è 1.16 e np.dot(re, banana) è -0.5. Il risultato è già leggibile, ma le norme dei due vettori sono diverse, quindi non è una similarità “pulita”. La cosine normalizza e restituisce un numero in [-1, 1] che dipende solo dall’angolo.

Truccooperativo.Seprenormalizzigliembeddinganorma1:Trucco operativo. Se pre-normalizzi gli embedding a norma 1:
re_n = re / np.linalg.norm(re)
regina_n = regina / np.linalg.norm(regina)
np.dot(re_n, regina_n) # uguale a cosine(re, regina)

A questo punto dot product e cosine coincidono. È il motivo per cui database vettoriali come FAISS, Milvus e pgvector normalizzano a monte e poi usano inner_product come metrica: è più veloce di una cosine vera (evita due divisioni per riga di query). Vedi norme e distanze per il quadro completo delle metriche.

Esempio 3: scenario reale, attention come prodotto scalare riga-per-riga

Sezione intitolata “Esempio 3: scenario reale, attention come prodotto scalare riga-per-riga”

Costruiamo un attention head in piccolo. Due query, tre key, dimensione dk=4d_k = 4.

import numpy as np
# n_q=2 query, n_k=3 key, d_k=4
Q = np.array([[1, 0, 1, 0],
[0, 1, 1, 0]], dtype=float)
K = np.array([[1, 0, 0, 0],
[0, 1, 1, 0],
[1, 1, 0, 1]], dtype=float)
V = np.array([[10, 0],
[ 0, 10],
[ 5, 5]], dtype=float)
d_k = Q.shape[1]
scores = Q @ K.T # matrice 2x3: prodotti scalari riga x riga
scores_scaled = scores / np.sqrt(d_k)
weights = np.exp(scores_scaled) / np.exp(scores_scaled).sum(axis=1, keepdims=True)
out = weights @ V
print(scores)
# [[1 1 1]
# [0 2 1]]
print(out)

Riga per riga: (QKT)[0,0]=q0k0=11+00+10+00=1(Q K^T)[0, 0] = q_0 · k_0 = 1·1 + 0·0 + 1·0 + 0·0 = 1. La matrice degli score non è altro che una griglia di prodotti scalari tra ogni query e ogni key. EQUIVALENZA esplicita: “la matrice di compatibilità di attention è una matrice di prodotti scalari riga-per-riga” non è analogia, è definizione operativa.

Lo scaling per sqrt(dk)sqrt(d_k) riporta gli score a varianza 1 prima della softmax. Senza lo scaling, per d_k grande (in pratica 64-128 per testa) gli score tenderebbero a magnitudini ~sqrt(dk)sqrt(d_k), la softmax saturerebbe (un solo peso vicino a 1, gli altri vicini a 0) e il gradiente sparirebbe, impedendo al modello di imparare. Vaswani e colleghi mostrano l’ablazione nel paper. Vedi attention-intuizione (in preparazione) e qkv-da-zero (in preparazione) per la costruzione dettagliata.

Calcoliamo a mano la prima riga della matrice di output. Per la prima query q0=(1,0,1,0)q_0 = (1, 0, 1, 0):

  • Score con k_0 = (1,0,0,0): 1·1 + 0·0 + 1·0 + 0·0 = 1.
  • Score con k_1 = (0,1,1,0): 1·0 + 0·1 + 1·1 + 0·0 = 1.
  • Score con k_2 = (1,1,0,1): 1·1 + 0·1 + 1·0 + 0·1 = 1.

Tutti gli score della prima query sono 1 (per costruzione dell’esempio). Diviso per sqrt(4)=2sqrt(4) = 2, gli score scalati sono 0.5. Softmax di tre 0.5 identici è la distribuzione uniforme (1/3, 1/3, 1/3). L’output è quindi la media dei tre value: ((10+0+5)/3, (0+10+5)/3) = (5, 5).

Per la seconda query q1=(0,1,1,0)q_1 = (0, 1, 1, 0) gli score grezzi sono (0, 2, 1), scalati (0, 1, 0.5). La softmax assegna pesi più alti al secondo elemento. Il risultato è una combinazione pesata che enfatizza v1=(0,10)v_1 = (0, 10).

Lezione: la matrice degli score “decide” la distribuzione di attenzione, e questa matrice è interamente costruita dal prodotto scalare. Niente magia, solo somme di prodotti.

Stesso esempio del precedente, ma con d_k = 64 e Q, K riempite con valori iid normali. Senza scaling:

import numpy as np
np.random.seed(0)
d_k = 64
n_q, n_k = 1, 100
Q = np.random.randn(n_q, d_k)
K = np.random.randn(n_k, d_k)
scores = Q @ K.T # n_q x n_k
print("std degli score senza scaling:", scores.std()) # ~ sqrt(64) = 8
print("std degli score con scaling:", (scores / np.sqrt(d_k)).std()) # ~ 1
w_no_scale = np.exp(scores) / np.exp(scores).sum(axis=1, keepdims=True)
w_scale = np.exp(scores/np.sqrt(d_k)) / np.exp(scores/np.sqrt(d_k)).sum(axis=1, keepdims=True)
print("max(weight) senza scaling:", w_no_scale.max()) # spesso > 0.99
print("max(weight) con scaling:", w_scale.max()) # ~0.05-0.1

Senza scaling, un solo elemento (quello con score più alto) si prende quasi tutto il peso: la softmax si trasforma in argmax soft. Con lo scaling, il peso si distribuisce. La differenza è di prim’ordine per il training: nel primo caso il gradiente passa solo attraverso un percorso e tutti gli altri sono “morti”.

Prendi v_1 = (1, 1, 0) e v_2 = (1, 0, 1). Vogliamo una base ortonormale.

Passo 1: v1=sqrt(2)||v_1|| = sqrt(2). Quindi q1=(1/sqrt(2),1/sqrt(2),0)q_1 = (1/sqrt(2), 1/sqrt(2), 0).

Passo 2: calcola <v2,q1>=1(1/sqrt(2))+0(1/sqrt(2))+10=1/sqrt(2)<v_2, q_1> = 1·(1/sqrt(2)) + 0·(1/sqrt(2)) + 1·0 = 1/sqrt(2). Sottrai la proiezione: u2=v2(1/sqrt(2))q1=(1,0,1)(1/2,1/2,0)=(1/2,1/2,1)u_2 = v_2 - (1/sqrt(2)) q_1 = (1, 0, 1) - (1/2, 1/2, 0) = (1/2, -1/2, 1).

Verifica ortogonalità: <u2,q1>=(1/2)(1/sqrt(2))+(1/2)(1/sqrt(2))+10=0<u_2, q_1> = (1/2)·(1/sqrt(2)) + (-1/2)·(1/sqrt(2)) + 1·0 = 0. Funziona.

Normalizza: u2=sqrt(1/4+1/4+1)=sqrt(3/2)||u_2|| = sqrt(1/4 + 1/4 + 1) = sqrt(3/2). q2=(1/sqrt(6),1/sqrt(6),2/sqrt(6))q_2 = (1/sqrt(6), -1/sqrt(6), 2/sqrt(6)) (scalando per 1/sqrt(3/2)=sqrt(2/3)1/sqrt(3/2) = sqrt(2/3)).

Verifica finale: <q1,q2>=0<q_1, q_2> = 0, q1=q2=1||q_1|| = ||q_2|| = 1. Hai due vettori ortonormali che generano lo stesso piano di v_1 e v_2. Il “lavoro” che hai fatto è interamente prodotti scalari e divisioni per norme.

Retrieval e RAG. Pipeline standard: encoder genera embedding di documenti, embedding normalizzati a norma 1, similarità query-documento calcolata via dot product (equivalente a cosine sui normalizzati), top-k restituiti al modello generativo. Database come FAISS, Milvus, Weaviate, pgvector espongono inner_product, cosine, l2 come metriche scegliibili. Cross-link a rag-base (in preparazione) e embeddings-retrieval (in preparazione).

Approfondimento: con N documenti in base, calcolare il dot product di una query contro tutti è O(N · d). Per N grande (milioni di documenti), si usano strutture approssimate (HNSW, IVF, ScaNN) che riducono il costo a circa O(log N · d) accettando un piccolo tasso di errore nel top-k. Il prodotto scalare resta l’operazione fondamentale; cambia solo la struttura di indicizzazione che evita di calcolarli tutti. Cross-link a ann-intro, ann-hnsw, ann-ivf-pq (in preparazione).

Attention nei transformer. Il blocco fondamentale dei transformer è una matrice di prodotti scalari QKTQ K^T, scalata e passata in softmax. Multi-head attention ne esegue più in parallelo su sottospazi distinti. FlashAttention (Dao et al. 2022) è una versione ottimizzata che fonde i calcoli per minimizzare il movimento di memoria GPU, ma l’operazione matematica è identica. Cross-link a transformer-2017 (già scritto) e flash-attention (in preparazione).

Conta operativa: per una sequenza di lunghezza n e dimensione d_k = 64, la matrice QKTQ K^T è n × n, con ogni elemento un dot product di vettori in R^64. Per n = 4096, sono 16 milioni di prodotti scalari per testa, per layer. Un transformer da 32 layer con 32 teste ne calcola circa 16 miliardi a ogni forward pass. È la ragione per cui ottimizzare il prodotto scalare in hardware (tensor cores, FlashAttention, sparse attention) ha effetti di prim’ordine sulle prestazioni dei modelli moderni.

Classificatori lineari. Un classificatore binario calcola score(x)=wTx+bscore(x) = w^T x + b. Il dot product wTxw^T x misura quanto x sta nella direzione di w. La frontiera di decisione wTx+b=0w^T x + b = 0 è l’iperpiano perpendicolare a w. Ogni neurone di una rete neurale è un dot product seguito da non-linearità: una rete è una pila di prodotti scalari intervallati da ReLU/GELU/altro.

Geometria: w è il vettore “normale” all’iperpiano. La direzione di w punta verso la classe positiva; la direzione opposta verso la negativa. La distanza signed di un punto x dall’iperpiano è (wTx+b)/w(w^T x + b) / ||w||. Massimizzare il margine (SVM) significa scegliere w tale che la distanza minima dei punti al confine sia massima. Tutto ruota attorno al prodotto scalare.

Un’osservazione che aiuta: il “logit” di un classificatore softmax multi-classe sulle k classi è zi=wiTx+biz_i = w_i^T x + b_i, k prodotti scalari paralleli. La classe scelta è quella con prodotto scalare massimo (più una correzione di bias). Anche qui, decisione = chi risuona di più con x.

Kernel methods (cenno). Un kernel K(x, y) è un prodotto interno in uno spazio di feature implicito: K(x,y)=<phi(x),phi(y)>K(x, y) = <phi(x), phi(y)>. Il “kernel trick” permette di calcolare quel prodotto senza mai costruire phi. EQUIVALENZA: un kernel valido (nel senso del teorema di Mercer, 1909) è esattamente un prodotto interno in qualche spazio. Esempi: lineare xTyx^T y, polinomiale (xTy+c)p(x^T y + c)^p, RBF gaussiano exp(gammaxy2)exp(-gamma ||x-y||^2) (corrisponde a uno spazio di feature di dimensione infinita). Cross-link a svm (in preparazione).

Perché conta concettualmente: il deep learning ha sostituito il kernel trick con feature apprese. Invece di scegliere a mano un kernel che separi le classi, lascia che la rete impari un encoder phi: x → R^d, e poi calcoli prodotti scalari nello spazio appreso. Il dot product resta l’arbitro finale di similarità; cambia il modo in cui produciamo le feature.

Matrice di Gram in style transfer. Gatys, Ecker, Bethge nel 2015 mostrano che lo “stile” di un’immagine può essere catturato dalla matrice di Gram delle feature di una CNN: Gij=<featurei,featurej>G_ij = <feature_i, feature_j>. Trasferire lo stile significa minimizzare la distanza tra matrici di Gram. Il prodotto scalare cattura le co-occorrenze di feature, che è una buona definizione operativa di “texture”.

Word2vec e analogie semantiche. Mikolov e colleghi nel 2013 mostrano che gli embedding di parole imparati da word2vec supportano analogie tipo “re - uomo + donna ≈ regina” via aritmetica vettoriale. Il punteggio di “vicinanza semantica” tra due parole è vwvcv_w · v_c — un dot product. La struttura geometrica che emerge dal training distribuzionale rende il prodotto scalare un misuratore naturale di compatibilità semantica. È il ponte che la Parte III aveva preparato in ponte semantica distribuzionale e embeddings: Firth ha detto “una parola è la sua compagnia”, word2vec rende quella compagnia un vettore, il prodotto scalare misura la compatibilità.

Whitening e normalizzazione. Quando gli embedding sono anisotropici (concentrati in un cono), una pre-trasformazione lineare via Σ1/2Σ^{-1/2} (radice inversa della covarianza) li riporta a una distribuzione approssimativamente isotropica. Dopo la trasformazione, il dot product canonico recupera il suo significato di “angolo”. È un esempio operativo di passaggio da prodotto interno canonico a prodotto interno generalizzato: stai implicitamente lavorando con <u,v>A<u, v>_A dove A è la pseudo-inversa della covarianza.

1. Cosine ignora la norma. Per due embedding di documenti, la magnitudine può codificare densità o specificità di contenuto. Cosine appiattisce questa informazione a zero. Risultato pratico: un documento generico e un documento specialistico nello stesso topic vengono trattati come ugualmente simili. Soluzione: usare dot product non normalizzato (sensibile alla magnitudine), o pesi misti, o normalizzazione “a metà” (radice della norma).

Lo stesso problema in retrieval testuale: una query molto breve e una molto lunga produrranno embedding di norme diverse. Cosine ignora questa differenza, dot product no. La scelta dipende dal task: se l’asse “lunghezza/specificità” è informativo, conservalo; se è rumore, normalizza.

2. Anisotropia degli embedding LLM. Ethayarajh nel 2019 e Gao e colleghi nel 2019 documentano un fenomeno spiacevole: gli embedding contestuali prodotti da BERT, GPT e simili tendono a occupare un cono ristretto dello spazio. La cosine media tra due embedding “casuali” non sta vicino a zero come ci si aspetterebbe da vettori in alta dimensione, ma a 0.3-0.6. Risultato: la cosine smette di discriminare bene perché tutti sembrano simili. Rimedi noti: BERT-flow (Li et al. 2020) impara una trasformazione invertibile a una distribuzione gaussiana isotropica; SimCSE (Gao et al. 2021) fa contrastive learning con dropout come augmentation; whitening sottrae la media e decorrela via radice inversa della covarianza.

3. Concentrazione in alta dimensione. Per vettori random iid in R^d, la cosine si concentra attorno a 0 con deviazione standard dell’ordine di 1/sqrt(d)1/sqrt(d). In d = 1024, le differenze utili stanno in una banda strettissima, e piccole variazioni numeriche possono ribaltare il ranking. È un caso particolare della maledizione della dimensionalità; vedi curse-dimensionalita. In retrieval, è il motivo per cui la qualità dell’encoder conta più della metrica: la metrica da sola non può creare contrasto se l’encoder colloca tutto vicino.

Una conseguenza pratica: in alta dimensione, il primo vicino e il centesimo vicino possono avere cosine quasi identiche. Differenze “vere” tra documenti si distinguono solo dopo che l’encoder ha imparato a piazzare similari vicini e dissimili lontani in modo deciso. Senza un buon training contrastivo, la geometria collassa e il prodotto scalare diventa rumore.

4. Non-canonical inner products quando servono. Il dot product canonico sommauivisomma u_i v_i assume base ortonormale e feature alla stessa scala. Se le feature hanno scale o correlazioni diverse, il prodotto è dominato dalle componenti a scala maggiore. Soluzione: standardizzare le feature, o usare prodotto interno con metrica <u,v>A=uTAv<u, v>_A = u^T A v con A scelta in base al problema (Mahalanobis quando A è l’inversa della covarianza).

5. Numerica float. Il calcolo sommauivisomma u_i v_i perde precisione per somme cancellanti su vettori lunghi. In float32 con d = 4096, l’errore relativo è dell’ordine di 1e-5; per loss computate su batch grandi, l’errore si propaga. FlashAttention e altre implementazioni efficienti di attention tengono accumulatori in float32 anche quando input e output sono in float16 o bfloat16, proprio per non perdere precisione nel softmax che segue.

6. Attention senza scaling. Senza la divisione per sqrt(dk)sqrt(d_k), gli score di attention crescono come sqrt(dk)sqrt(d_k) (assumendo Q, K iid a media 0 e varianza 1). Per d_k = 64, gli score hanno modulo medio circa 8, la softmax satura su un elemento e il gradiente sparisce. Vaswani e colleghi mostrano l’ablazione: senza lo scaling il training fatica a convergere. È l’errore tipico di chi reimplementa attention da zero la prima volta.

Conto rapido: se q, k hanno componenti iid con media 0 e varianza 1, allora qk=sommaiqikiq · k = somma_i q_i k_i. Per indipendenza, varianza della somma = somma delle varianze = dk1=dkd_k · 1 = d_k. Deviazione standard sqrt(dk)sqrt(d_k). Dividere per sqrt(dk)sqrt(d_k) riporta la deviazione a 1. La softmax di valori con deviazione 1 distribuisce attenzione in modo morbido; la softmax di valori con deviazione 8 diventa quasi un argmax hard.

7. Spazi complessi e simmetria coniugata. Su vettori complessi, il prodotto interno non è simmetrico ma coniugato simmetrico: <u,v>=coniugatodi<v,u><u, v> = coniugato di <v, u>. La forma u^H v (con u^H trasposto coniugato) è lineare nel secondo argomento e antilineare nel primo. Confondere le due forme rompe Cauchy-Schwarz e le formule di proiezione. Conta in signal processing e in alcune forme di attention complessa, raramente in deep learning standard.

8. Cancellazione catastrofica. Quando calcoli <u,v><u, v> come differenza implicita tra due numeri molto vicini (per esempio in formule di proiezione iterate), l’errore relativo cresce drammaticamente. Un caso noto è la formula uv2=u2+v22<u,v>||u - v||^2 = ||u||^2 + ||v||^2 - 2<u,v>: se u e v sono quasi uguali, i tre termini si cancellano e i pochi bit informativi rimanenti sono sommersi dal rumore numerico. Soluzione: usa direttamente somma(uivi)2somma (u_i - v_i)^2, che evita la cancellazione.

9. Anisotropia indotta da pre-training. Anche con encoder ben addestrati, i prodotti scalari fra embedding di token frequenti possono avere magnitudine sistematicamente più alta di quelli fra token rari. Per attention significa che i token frequenti attirano peso più del dovuto. Tecniche come temperature scaling sulla softmax mitigano, ma il problema è la metrica che eredita le statistiche del corpus.

10. Distribuzioni non gaussiane. L’argomento dello scaling 1/sqrt(dk)1/sqrt(d_k) assume Q, K iid gaussiane a media 0 e varianza 1. In realtà gli embedding contestuali in un transformer addestrato sono tutt’altro che gaussiani: hanno code pesanti, struttura sparsa, anisotropia. Il fattore sqrt(dk)sqrt(d_k) resta una buona euristica ma non è ottimale; alcune varianti recenti (per esempio QK-norm) normalizzano ulteriormente Q e K prima del prodotto scalare per stabilizzare il training a profondità maggiori.

11. Bias-variance del prodotto scalare empirico. Quando stimi una similarità tra due distribuzioni di vettori (per esempio embedding di due cluster), il prodotto scalare medio sui campioni è uno stimatore con varianza che decresce come 1/n. Per cluster piccoli, la stima è rumorosa, e decisioni basate su differenze fini di prodotto scalare medio possono essere illusorie. È un caso del bias-varianza che vedremo in bias-varianza.

  • Vettori e spazi vettoriali: la grammatica preliminare. Definizione di vettore, base, dimensione, ortogonalità. Senza quel linguaggio, il prodotto scalare resta una somma di numeri.
  • Norme e distanze: il prodotto scalare come ingrediente di L2 e cosine. Qui il dot product è protagonista; là, era una metrica fra le altre.
  • Matrici come trasformazioni: le matrici ortogonali (QTQ=IQ^T Q = I) come trasformazioni che preservano il prodotto scalare. La forma uTAvu^T A v come prodotto interno generalizzato.
  • Ponte semantica distribuzionale e embeddings: il prodotto scalare come misura operativa di similarità semantica nei modelli distribuzionali. Da Firth a word2vec.
  • attention-intuizione (in preparazione): il “perché” concettuale di attention. Qui costruiamo il “cosa” matematico.
  • qkv-da-zero (in preparazione): costruzione passo-passo di Q, K, V a partire dal prodotto scalare.
  • embedding-concetto: direzioni semantiche e analogie via dot product.
  • curse-dimensionalita: perché in alta dimensione la cosine si concentra.
  • svm (in preparazione): kernel methods e il prodotto interno in spazi di feature impliciti.
  • flash-attention (in preparazione): ottimizzazione hardware-aware del prodotto scalare di attention.

Per il lettore che incontra alcuni di questi termini per la prima volta, una micro-glossa.

  • Forma bilineare: una funzione di due argomenti che è lineare in ciascuno separatamente (tieni fisso uno, l’altro è lineare). Il dot product è bilineare.
  • Definita positiva: una matrice A tale che xTAx>0x^T A x > 0 per ogni x diverso da zero. Garantisce che <u,v>A<u,v>_A sia un prodotto interno valido.
  • Spazio di Hilbert: spazio vettoriale con prodotto interno completo (in senso di limiti di successioni). Il quadro generale dove vivono kernel methods e meccanica quantistica.
  • Ortonormale: un insieme di vettori a due a due ortogonali, ciascuno di norma 1. La base canonica di R^n lo è.
  • Coniugato simmetrico: nel caso complesso, <u,v>=coniugatodi<v,u><u, v> = coniugato di <v, u>. Sostituisce la simmetria standard.
  • Mahalanobis: metrica della forma sqrt((uv)TΣ1(uv))sqrt((u-v)^T Σ^{-1} (u-v)), dove Σ è la covarianza. Tiene conto di scale e correlazioni delle feature.
  • Whitening: trasformazione che riporta una distribuzione di vettori a covarianza identità. Operativamente: moltiplica per Σ1/2Σ^{-1/2}.
  • Teorema di Mercer (1909): una funzione K(x, y) simmetrica continua è un kernel valido se e solo se la matrice [K(xi,xj)][K(x_i, x_j)] è semidefinita positiva per ogni scelta finita di punti. Equivalentemente: K è un prodotto interno in qualche spazio.
  • Decomposizione QR: fattorizzazione di una matrice A come A = Q R con Q ortogonale e R triangolare superiore. Output canonico di Gram-Schmidt stabilizzato.
  • Logit: nel contesto di classificazione, il punteggio pre-softmax wTx+bw^T x + b. Tradotto: prodotto scalare con un vettore di pesi più un bias.
  • Mahalanobis: distanza che usa l’inversa della covarianza come metrica, sqrt((uv)TΣ1(uv))sqrt((u-v)^T Σ^{-1} (u-v)). Tiene conto di scale e correlazioni delle feature, normalizzando il prodotto scalare al contesto statistico dei dati.
  • Householder reflection: trasformazione lineare che riflette un vettore rispetto a un iperpiano. Strumento numericamente stabile per calcolare decomposizione QR.
  • Sesquilinearità: generalizzazione della bilinearità ai numeri complessi, in cui la funzione è lineare in un argomento e antilineare nell’altro (cioè coniuga gli scalari). Necessaria per definire prodotti interni complessi che restino definiti positivi.
  • Matrice ortogonale: matrice quadrata Q tale che QTQ=IQ^T Q = I. Le sue colonne (e righe) formano una base ortonormale; preserva lunghezze e angoli sotto la trasformazione x → Q x.
  • Strang, G. (2016). Introduction to Linear Algebra, 5th ed. Wellesley-Cambridge Press. Capitoli 1.2 e 4. Trattazione operativa, geometrica, accessibile. Il riferimento se ti senti arrugginito sull’algebra lineare.
  • Axler, S. (2015). Linear Algebra Done Right, 3rd ed. Springer. Capitolo 6 “Inner Product Spaces”. Trattazione assiomatica pulita; Cauchy-Schwarz dimostrata come corollario della proiezione.
  • Vaswani, A., e altri (2017). “Attention Is All You Need”. NeurIPS 2017, arXiv:1706.03762. Sezione 3.2.1 per la formula di scaled dot-product attention e la motivazione dello scaling.
  • Shawe-Taylor, J., Cristianini, N. (2004). Kernel Methods for Pattern Analysis. Cambridge University Press. Per l’angolo “il prodotto interno è l’oggetto centrale di una grossa fetta di ML pre-2012”.
  • Cauchy, A.-L. (1821). Cours d’Analyse, Nota II. La fonte primaria della disuguaglianza, leggibile in francese ottocentesco con un po’ di pazienza. Disponibile in scansione su Gallica BNF.
  • Mikolov, T., e altri (2013). “Distributed Representations of Words and Phrases and their Compositionality”. NeurIPS 2013. Per vedere il prodotto scalare al lavoro come misuratore di compatibilità semantica negli embedding di parole, prima ancora che diventasse il motore di attention.
  • Gatys, L., Ecker, A., Bethge, M. (2015). “A Neural Algorithm of Artistic Style”. arXiv:1508.06576. La matrice di Gram applicata alla vision: stile come correlazioni tra feature.
  • Ethayarajh, K. (2019). “How Contextual are Contextualized Word Representations?”. EMNLP 2019. Il paper di riferimento sull’anisotropia degli embedding contestuali e sulle sue conseguenze per la cosine similarity.
  • Dao, T., e altri (2022). “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness”. arXiv:2205.14135. Per come si ottimizza il prodotto scalare di attention sull’hardware reale, senza alterarne la matematica.