ELAI S.r.l.

Il controllo del gradiente fallisce: è davvero un errore del modello?

Dal seno alla ReLU: deriviamo le differenze finite, misuriamo l’effetto del passo e spieghiamo perché un controllo può confrontare quantità diverse.

Il controllo del gradiente fallisce: è davvero un errore del modello?

Quando anche il controllore ha bisogno di essere controllato

Durante lo sviluppo di un modello AI confrontiamo spesso una derivata calcolata dal programma con una stima ottenuta cambiando leggermente un parametro. Se i numeri non coincidono, la tentazione è cercare subito un errore nella retropropagazione. Ma anche il controllo numerico ha ipotesi e limiti. Un passo troppo piccolo può peggiorare la stima; un punto angoloso può non avere una derivata da confrontare. La domanda è come distinguere un segnale utile da un falso allarme.

Useremo due funzioni scalari: il seno, regolare e con derivata nota, e la ReLU, una funzione a tratti comune nelle reti neurali. Non addestriamo una rete e non eseguiamo una libreria di differenziazione automatica. Il nostro esperimento rende visibili le ipotesi di un gradient check, cioè un controllo delle derivate. Le quantità sono adimensionali; il valore 0,7 del seno è un angolo in radianti. Ogni risultato numerico riportato proviene dal codice archiviato.

Una pendenza locale, non soltanto una differenza

La derivata misura quanto cambia l’uscita per un piccolo cambiamento dell’ingresso, nel limite in cui quel cambiamento tende a zero. Su un grafico è la pendenza della tangente. Per una funzione scalare f e un punto x, il rapporto in avanti usa f(x+h) e f(x); il rapporto centrale usa due punti ai lati di x. h è il passo, che nel calcolo deve essere diverso da zero. Il rapporto centrale non è la definizione della derivata in un punto non regolare: è una particolare stima che coincide con essa soltanto sotto le condizioni appropriate.

D_forward(h) = [f(x+h) - f(x)] / h D_central(h) = [f(x+h) - f(x-h)] / (2h)

Per capire l’accuratezza senza affidarci soltanto al grafico, sviluppiamo f intorno a x. Se esistono abbastanza derivate regolari, f(x+h)=f(x)+hf′(x)+h²f″(x)/2+h³f‴(x)/6+… . Nel rapporto in avanti resta un errore principale proporzionale a h. Sottraendo lo sviluppo con −h, i termini pari si cancellano e nel rapporto centrale l’errore principale è h²f‴(x)/6. Questa è la ragione del miglior ordine teorico del metodo centrale, non una promessa di precisione per qualunque programma.

Perché ridurre h non basta

La derivazione precedente tratta operazioni esatte. Nel computer valutiamo f con arrotondamenti e sottraiamo valori quasi uguali. Un’incertezza assoluta dell’ordine della precisione macchina nella differenza viene divisa per h. In un modello d’errore locale semplificato, il metodo centrale combina quindi un termine C₁h² con un termine C₂u/h, dove u descrive la precisione relativa e le costanti dipendono dalla funzione e dalla sua scala. Ridurre h diminuisce il primo ma può aumentare il secondo.

Il bilanciamento degli ordini suggerisce una scala h proporzionale a u^(1/3) per questo modello centrale, dopo aver considerato le scale del problema. Non è un valore universale da copiare in ogni rete: rumore, arrotondamenti, grandezze dei parametri e termini quasi nulli cambiano il quadro. Il punto pratico è controllare una sequenza di passi e cercare una zona di accordo, anziché trasformare “più piccolo” in sinonimo di “più affidabile”.

Il seno: un controllo regolare eseguito

Eseguiamo f(x)=sin(x) in x=0,7 con passi da 10⁻¹ a 10⁻¹⁶. Il riferimento è math.cos(0.7), circa 0,7648421872844885: è la valutazione in doppia precisione della derivata analitica, non un oracolo a precisione arbitraria. Nella scansione il migliore errore centrale rispetto a questo riferimento compare a h=10⁻⁵, circa 1,70×10⁻¹¹. A h=10⁻¹⁶ la stima diventa invece circa 1,1102230246251565, con scarto circa 0,34538. Qui x+h e x−h sono ancora diversi da x: non serve che i punti coincidano esattamente per avere un controllo pessimo.

Il passo complesso: potente, ma con un contratto

Per una funzione che ammette l’estensione analitica appropriata ai numeri complessi, possiamo usare un passo immaginario ih. Lo sviluppo contiene f(x)+ihf′(x)−h²f″(x)/2−ih³f‴(x)/6+… . Prendendo la parte immaginaria e dividendo per h otteniamo f′(x) con errore teorico di ordine h², senza sottrarre due valori reali quasi uguali. Il simbolo i soddisfa i²=−1; Im indica la parte immaginaria. Non è una tecnica valida solo perché il programma accetta il tipo complex.

D_complex(h) = Im[f(x + i*h)] / h

Nel nostro seno eseguito con cmath.sin, a h=10⁻¹⁶ lo scarto dal riferimento è circa 1,11×10⁻¹⁶. È un buon risultato su questa funzione, non una prova di assenza universale di arrotondamento. Il controesempio è abs: per x=0,7 la derivata reale di |x| è 1, ma abs(x+ih) restituisce un modulo reale e perde la parte immaginaria. La stessa ricetta produrrebbe zero, come verifica il codice. Tagli, confronti, conversioni a reale e operazioni non analitiche devono quindi essere esaminati prima di usare il metodo.

La ReLU: quando 0,5 non è una derivata

ReLU significa Rectified Linear Unit: r(x)=max(0, x). Per x negativo l’uscita è zero e la pendenza è zero; per x positivo l’uscita coincide con x e la pendenza è uno. In zero i limiti delle pendenze dai due lati non coincidono: la derivata ordinaria non esiste. Eppure il rapporto centrale vale (h−0)/(2h)=1/2 per ogni h positivo. Ridurre il passo non lo farà convergere a zero o a uno: stiamo mediando due rami diversi.

hSinistraDestraCentrale
1e-1010.5
1e-3010.5
1e-6010.5

In ottimizzazione non liscia si possono usare convenzioni di sottogradiente. Per questa funzione convessa, in zero l’insieme dei sottogradienti è [0,1]. Scegliere zero è una possibile convenzione di un algoritmo, non dichiarare che la derivata ordinaria esista e valga zero. Se una procedura con quella convenzione viene confrontata con la differenza centrale, la discrepanza 0 contro 0,5 non dimostra da sola un bug. Qui non eseguiamo autograd e non attribuiamo tale risultato a una particolare versione di un framework.

A sinistra scarto da math.cos(0.7) per i passi eseguiti; asse orizzontale decrescente e verticale simmetrico logaritmico, lineare entro ±10⁻¹⁷ per rappresentare lo zero. A destra le pendenze della ReLU in zero, dove la derivata ordinaria non esiste.
A sinistra scarto da math.cos(0.7) per i passi eseguiti; asse orizzontale decrescente e verticale simmetrico logaritmico, lineare entro ±10⁻¹⁷ per rappresentare lo zero. A destra le pendenze della ReLU in zero, dove la derivata ordinaria non esiste.

Dalla prova scalare a una funzione con molti parametri

Per una loss scalare L(theta) con d parametri, la differenza centrale coordinata richiede due valutazioni per parametro, quindi circa 2d valutazioni della loss, oltre al gradiente da verificare. Un controllo direzionale confronta invece [L(theta+hv)−L(theta−hv)]/(2h) con il prodotto g·v, dove g è il gradiente proposto e v una direzione scelta. È più economico per direzione, ma un errore ortogonale a v può sfuggire. Non abbiamo eseguito qui questo controllo multidimensionale: lo presentiamo come estensione matematica, senza risultati di una rete inventata.

Anche la funzione valutata deve restare la stessa: casualità, stato aggiornato fra chiamate o dati diversi possono trasformare una differenza di funzioni in un presunto errore di derivata. Un accordo numerico a pochi punti è evidenza locale, non dimostra la correttezza globale del modello. Nel vicino di un cambio di ramo occorre osservare se la perturbazione attraversa quel cambio; altrove una discrepanza stabile su una gamma di passi resta un indizio serio da investigare. La conclusione non è ignorare i test, ma interpretare il loro oggetto.

Una risposta utile per lo sviluppo AI

Un gradient check fallito non identifica da solo il colpevole. Sul seno abbiamo visto un errore che cresce quando il passo diventa troppo piccolo; sulla ReLU abbiamo ottenuto 0,5 in un punto senza derivata. Sono due motivi diversi per non trattare la stima numerica come verità assoluta. Prima di correggere il modello bisogna rendere espliciti funzione, regolarità, passo, precisione e convenzione del confronto. Il controllo diventa così uno strumento scientifico con un campo di validità, anziché un verdetto opaco.

Fonti e riproducibilità

La documentazione SciPy 1.18.0 di least_squares, parametro jac, è stata letta per le condizioni del metodo complex-step; non abbiamo eseguito SciPy né i suoi esempi di ottimizzazione. Il codice proprio usa math e cmath di Python 3.14.0, senza casualità, e salva tutti i sedici passi, le differenze laterali e il controesempio abs. Le formule sono derivate qui, non risultati sperimentali di un paper o di un prodotto EL-AI. Le figure sono calcolate con Matplotlib; nessuna prestazione hardware o revisione umana è dichiarata.

SciPy 1.18.0 — least_squares, jac.

Python 3.14 — math.

Python 3.14 — cmath.

import math
x=0.7
reference=math.cos(x)
for h in [1e-3,1e-5,1e-16]:
    estimate=(math.sin(x+h)-math.sin(x-h))/(2*h)
    print(h,estimate,abs(estimate-reference))
relu=lambda z:max(0.0,z)
h=1e-6
assert (relu(h)-relu(-h))/(2*h)==0.5

Codice, dati e istruzioni · JSON. Calcoli didattici eseguiti con Python 3.14.0; figure con Matplotlib 3.11.2. Analisi con assistenza AI, senza dichiarare peer review o revisione umana. Copertina originale ImageGen, illustrativa: non documenta persone, sedi o installazioni EL-AI. Fonti consultate il 21 ottobre 2026.