Salta al contenuto principale

21 - Analisi matematica 1: limiti e derivate

Dalla velocità istantanea alla derivata: limiti, regole, sigmoide e ReLU, e perché la derivata numerica sbaglia con h troppo grande e con h troppo piccolo.

Cosa ne pensi di questo articolo?

21 - Analisi matematica 1: limiti e derivate
Machine Learning Engineerfedericocalo.dev

In Breve

Dalla velocità istantanea alla derivata: limiti, regole, sigmoide e ReLU, e perché la derivata numerica sbaglia con h troppo grande e con h troppo piccolo.

Corso Machine Learning Engineer · Modulo 02 · Matematica e statistica · Lezione 6

Obiettivo. Capire che cos'è una derivata (la pendenza di una curva in un punto, ottenuta come limite), saperla calcolare dalla definizione e con le regole (che dimostreremo), applicarla alle funzioni che incontri nelle reti neurali (sigmoide, tangente iperbolica, ReLU), e conoscere i limiti del calcolo numerico di una derivata, che sono i limiti dei numeri in virgola mobile visti nel modulo 01. Alla fine saprai perché «dove la pendenza è zero» è il punto di partenza di tutta l'ottimizzazione.

Falso mito da sfatare. «L'analisi è un elenco di formule da imparare a memoria.» Le formule sono poche e discendono tutte da un'idea sola: avvicinarsi indefinitamente a un valore. Se capisci l'idea, ricostruisci le formule quando servono.

Introduzione

Il machine learning impara aggiustando dei numeri (i parametri) finché un errore diventa piccolo. Per sapere in quale direzione muovere un parametro bisogna sapere come l'errore cambia quando il parametro cambia di poco: questo è esattamente ciò che la derivata misura. Senza derivate non esisterebbe l'addestramento delle reti neurali. In questa lezione costruiamo lo strumento per funzioni di una variabile; nella prossima lo estendiamo a molte variabili (il gradiente).

Il percorso va dalla velocità media a quella istantanea, introduce il limite con la definizione ε-δ raccontata come un gioco, definisce la derivata e dimostra le regole di derivazione. Applica poi tutto alle funzioni delle reti neurali (sigmoide, tanh, ReLU), spiega i due errori della derivata numerica e lo sviluppo di Taylor, e chiude con massimi e minimi. Come prerequisiti bastano i numeri in virgola mobile e il teorema binomiale della lezione di combinatoria.

Dalla velocità media a quella istantanea

L'analogia: il tachimetro

Un'auto percorre 120 km in 2 ore: la velocità media è 60 km/h. Ma il tachimetro mostra, in ogni istante, un numero diverso: la velocità istantanea. Come si definisce, se in un istante «non passa tempo» e quindi lo spazio percorso è zero? Si guarda un intervallo h sempre più piccolo attorno all'istante e si osserva a che numero tende il rapporto

(spazio percorso in h) / h.

Il numero a cui tende, quando h si avvicina a zero senza mai esserlo, è la velocità istantanea. Quel «tende a» è un limite. Tradotto in matematica: se f(x) è la posizione al tempo x, la velocità istantanea è la derivata f'(x).

Il limite

Scriviamo lim_{x→a} f(x) = L e leggiamo «quando x si avvicina ad a, f(x) si avvicina a L». Due osservazioni che sorprendono all'inizio:

  • Non importa cosa vale f in a, e nemmeno se è definita in a. Conta cosa fa f vicino ad a.
  • «Avvicinarsi» va inteso da entrambi i lati.

L'esempio classico: f(x) = (x² − 1)/(x − 1) non è definita in x = 1 (zero diviso zero). Ma per x ≠ 1 si può semplificare: x² − 1 = (x − 1)(x + 1), quindi f(x) = x + 1, e f(x) si avvicina a 2.

import math

f = lambda x: (x * x - 1) / (x - 1)
print("   x      f(x)")
for d in (0.5, 0.1, 0.01, 0.001, 1e-6):
    print(f"{1 - d:9.6f}  {f(1 - d):.6f}     {1 + d:9.6f}  {f(1 + d):.6f}")

g = lambda x: math.sin(x) / x
print("sin(x)/x per x → 0:", [round(g(x), 8) for x in (0.5, 0.1, 0.01, 0.001)])

Output ottenuto:

   x      f(x)
 0.500000  1.500000      1.500000  2.500000
 0.900000  1.900000      1.100000  2.100000
 0.990000  1.990000      1.010000  2.010000
 0.999000  1.999000      1.001000  2.001000
 0.999999  1.999999      1.000001  2.000001
sin(x)/x per x → 0: [0.95885108, 0.99833417, 0.99998333, 0.99999983]

Cosa fa il codice. Calcola f per valori di x che si avvicinano a 1 da sinistra (1 − d) e da destra (1 + d): tutti i valori si avvicinano a 2 da entrambi i lati, anche se f(1) non esiste. La seconda riga mostra un limite meno ovvio: sin(x)/x tende a 1 per x → 0 (con x in radianti), un limite che non si ottiene per semplificazione algebrica ma da un argomento geometrico. Calcolare valori vicini suggerisce il limite; non lo dimostra: per questo serve la definizione.

La definizione precisa: un gioco a due giocatori

Come si dice con rigore «f(x) si avvicina quanto si vuole a L»? Come un gioco. Tu (lo sfidante) scegli una tolleranza ε > 0 (quanto vuoi che f(x) sia vicina a L). Io (chi afferma il limite) devo rispondere con una distanza δ > 0 tale che tutti gli x entro δ da a (escluso a stesso) producano un valore di f entro ε da L. Se, qualunque ε tu scelga, riesco sempre a trovare un δ, il limite è L. È la logica dei quantificatori della lezione 1:

lim_{x→a} f(x) = L ⇔ ∀ε > 0 ∃δ > 0 : 0 < |x − a| < δ ⇒ |f(x) − L| < ε.

Esempio svolto: lim_{x→1} 3x = 3. Dato ε, devo trovare δ. Vale |3x − 3| = 3|x − 1|. Voglio che sia minore di ε: basta |x − 1| < ε/3. Quindi scelgo δ = ε/3 e la risposta vale per qualunque ε. ∎

import numpy as np

def gioco(eps, delta, a=1.0, L=3.0, funzione=lambda x: 3 * x, punti=100_001):
    """Verifica numericamente la mia risposta δ alla sfida ε: controlla molti x entro δ da a."""
    xs = np.linspace(a - delta, a + delta, punti)
    xs = xs[xs != a]
    return bool(np.all(np.abs(funzione(xs) - L) < eps))

for eps in (1.0, 0.1, 1e-3, 1e-6):
    print(f"sfida ε = {eps:<7}: risposta δ = ε/3 = {eps / 3:.2e} -> regge: {gioco(eps, eps / 3 * 0.999)}")
print("risposta sbagliata δ = ε:", gioco(0.1, 0.1))

Output ottenuto:

sfida ε = 1.0    : risposta δ = ε/3 = 3.33e-01 -> regge: True
sfida ε = 0.1    : risposta δ = ε/3 = 3.33e-02 -> regge: True
sfida ε = 0.001  : risposta δ = ε/3 = 3.33e-04 -> regge: True
sfida ε = 1e-06  : risposta δ = ε/3 = 3.33e-07 -> regge: True
risposta sbagliata δ = ε: False

Cosa fa il codice. gioco controlla che, per molti x entro δ da a, la distanza di f(x) da L sia minore di ε. La risposta corretta δ = ε/3 (ridotta di un millesimo per evitare il confine esatto) regge per ogni sfida; la risposta sbagliata δ = ε fallisce (qui |3x − 3| può arrivare a 3ε). È un controllo, non una dimostrazione: la dimostrazione è l'argomento algebrico di sopra.

Continuità. Una funzione è continua in a se lim_{x→a} f(x) = f(a): il limite esiste, f(a) esiste, e coincidono. Intuitivamente, il grafico si disegna senza staccare la penna.

La derivata

Definizione

La derivata di f nel punto x è

f'(x) = lim_{h→0} (f(x + h) − f(x)) / h

se il limite esiste. Il rapporto (f(x+h) − f(x))/h è il rapporto incrementale: la pendenza della retta secante che unisce due punti del grafico a distanza h. Quando h si avvicina a zero la secante ruota e si assesta sulla tangente: la derivata è la pendenza della tangente.

Dalla secante alla tangenteUna parabola con una retta secante tra x e x+h e la tangente in x.xx + hsecante: pendenza (f(x+h) − f(x))/htangente: pendenza f′(x), il limite per h → 0
Figura: la derivata è il limite delle pendenze delle secanti.

Altri nomi e notazioni equivalenti: f'(x), df/dx, «velocità di variazione», «sensibilità»: quanto cambia l'uscita se muovi un po' l'ingresso. Una derivata positiva significa funzione crescente in quel punto, negativa decrescente, zero «piatta».

Calcolo dalla definizione

La parabola. Per f(x) = x²: (f(x+h) − f(x))/h = ((x+h)² − x²)/h = (2xh + h²)/h = 2x + h. Per h → 0: f'(x) = 2x.

La potenza xⁿ. Per il teorema binomiale (lezione 2): (x + h)ⁿ = xⁿ + n xⁿ⁻¹ h + C(n,2) xⁿ⁻² h² + …. Sottraendo xⁿ e dividendo per h: n xⁿ⁻¹ + C(n,2) xⁿ⁻² h + …. Tutti i termini oltre il primo contengono h e spariscono per h → 0: (xⁿ)' = n xⁿ⁻¹.

def derivata_incr(f, x, h):
    return (f(x + h) - f(x)) / h

x = 3.0
print("(x^2)' in x=3, esatto 6:  ", [round(derivata_incr(lambda t: t ** 2, x, h), 6) for h in (1, 0.1, 0.01, 0.001)])
print("(x^5)' in x=3, esatto 405:", [round(derivata_incr(lambda t: t ** 5, x, h), 3) for h in (1, 0.1, 0.01, 0.001)])

Output ottenuto:

(x^2)' in x=3, esatto 6:   [7.0, 6.1, 6.01, 6.001]
(x^5)' in x=3, esatto 405: [781.0, 432.915, 407.709, 405.27]

Cosa fa il codice. Calcola il rapporto incrementale per h sempre più piccolo e mostra che si avvicina ai valori esatti 2·3 = 6 e 5·3⁴ = 405. Per la parabola, il rapporto è esattamente 2x + h, quindi si vede la convergenza: 7, 6.1, 6.01, 6.001.

Le regole di derivazione

Calcolare ogni volta un limite sarebbe faticoso. Bastano poche regole, ciascuna dimostrata con la definizione.

Linearità: (a·f + b·g)' = a·f' + b·g'. Il rapporto incrementale di una somma è la somma dei rapporti, e le costanti escono dal limite.

Regola del prodotto: (f·g)' = f'·g + f·g'. Dimostrazione. f(x+h)g(x+h) − f(x)g(x) si riscrive aggiungendo e togliendo f(x+h)g(x):

= f(x+h)[g(x+h) − g(x)] + g(x)[f(x+h) − f(x)].

Dividendo per h e facendo h → 0: il primo pezzo diventa f(x)·g'(x) (perché f(x+h) → f(x) per la continuità di f, conseguenza della derivabilità), il secondo g(x)·f'(x). ∎

Regola della catena: (f(g(x)))' = f'(g(x)) · g'(x). È la regola più importante per il machine learning: una rete neurale è una composizione di funzioni, e la sua derivata è un prodotto di derivate. Dimostrazione (per g non costante nei dintorni): con u = g(x) e k = g(x+h) − g(x),

(f(g(x+h)) − f(g(x)))/h = [(f(u + k) − f(u))/k] · [(g(x+h) − g(x))/h].

Per h → 0 anche k → 0 (g continua), il primo fattore tende a f'(u) = f'(g(x)) e il secondo a g'(x). ∎ (Il caso in cui k può annullarsi si tratta con un argomento un po' più fine, ma il risultato non cambia.)

L'analogia della catena: gli ingranaggi. Se una ruota gira 3 volte più veloce di un'altra, e questa 2 volte più veloce di una terza, la prima gira 3 · 2 = 6 volte più veloce della terza. I rapporti di variazione si moltiplicano.

Quoziente. (f/g)' = (f'g − fg')/g² si ricava dalla regola del prodotto applicata a f · (1/g).

Le derivate «di base»

  • Costante: (c)' = 0. Retta: (ax + b)' = a.
  • Esponenziale eˣ: il numero e ≈ 2.71828 è definito in modo che (eˣ)' = eˣ: la funzione è uguale alla sua pendenza. Numericamente, (e^h − 1)/h → 1 per h → 0, che è la derivata in zero.
  • Logaritmo naturale: da e^{ln x} = x, derivando entrambi i lati con la regola della catena: e^{ln x} · (ln x)' = 1, cioè x · (ln x)' = 1, quindi (ln x)' = 1/x.
  • Seno e coseno: (sin x)' = cos x, (cos x)' = −sin x.

Queste quattro righe, combinate con linearità, prodotto, quoziente e catena, bastano per derivare quasi qualunque funzione che incontrerai. Il logaritmo è l'unico caso che non si impara a memoria: si ricava dalla regola della catena, ed è un buon modello del ragionamento da ripetere quando serve una derivata nuova.

h = 1e-6
for nome, f, df in [("exp", math.exp, math.exp), ("ln", math.log, lambda x: 1 / x), ("sin", math.sin, math.cos)]:
    x = 1.7
    num = (f(x + h) - f(x - h)) / (2 * h)
    print(f"{nome:>3}: derivata numerica {num:.8f} | formula {df(x):.8f}")

print("(e^h - 1)/h per h = 1e-3, 1e-6 :", [round((math.exp(hh) - 1) / hh, 7) for hh in (1e-3, 1e-6)])

Output ottenuto:

exp: derivata numerica 5.47394739 | formula 5.47394739
 ln: derivata numerica 0.58823529 | formula 0.58823529
sin: derivata numerica -0.12884449 | formula -0.12884449
(e^h - 1)/h per h = 1e-3, 1e-6 : [1.0005002, 1.0000005]

Cosa fa il codice. Confronta, per tre funzioni, la derivata numerica (con la differenza centrale (f(x+h) − f(x−h))/(2h), che spieghiamo meglio più avanti) con la formula: coincidono fino all'ottava cifra. L'ultima riga verifica che il rapporto (e^h − 1)/h tende a 1.

Le funzioni del machine learning

Una funzione di attivazione è la funzione non lineare applicata all'uscita di ciascun neurone; la sua derivata deve esistere (quasi ovunque) perché l'addestramento funzioni.

Sigmoide: σ(x) = 1/(1 + e⁻ˣ). Schiaccia i numeri reali in (0, 1) e si legge come una probabilità. Derivata, con la regola della catena: σ(x) = (1 + e⁻ˣ)⁻¹, quindi

σ'(x) = −(1 + e⁻ˣ)⁻² · (−e⁻ˣ) = e⁻ˣ/(1 + e⁻ˣ)² = σ(x) · (1 − σ(x)),

perché 1 − σ = e⁻ˣ/(1 + e⁻ˣ). È una derivata che si esprime con la funzione stessa: comodissima, perché il valore σ(x) è già calcolato nel passaggio in avanti. La sua forma ha un difetto noto: la derivata massima vale 1/4 (in x = 0) e per |x| grande si avvicina a zero, il che rallenta l'apprendimento («gradiente che svanisce»).

Tangente iperbolica: tanh(x) = (eˣ − e⁻ˣ)/(eˣ + e⁻ˣ), con valori in (−1, 1). Derivata 1 − tanh²(x).

ReLU: max(0, x). Derivata 0 per x < 0, 1 per x > 0, e non esiste in x = 0: le due pendenze (0 a sinistra, 1 a destra) non coincidono, il grafico ha uno spigolo. In pratica si sceglie una convenzione (di solito 0) per quel punto isolato, che quasi mai viene colpito.

sigma = lambda x: 1 / (1 + np.exp(-x))
x = np.linspace(-6, 6, 7)

dsig_formula = sigma(x) * (1 - sigma(x))
h = 1e-5
dsig_numerica = (sigma(x + h) - sigma(x - h)) / (2 * h)
print("x            :", x)
print("σ'(x) formula:", np.round(dsig_formula, 5))
print("σ'(x) numerica:", np.round(dsig_numerica, 5))
print("massimo di σ' :", dsig_formula.max(), "(= 1/4 in x = 0)")

tanh_deriv = 1 - np.tanh(x) ** 2
print("tanh'(x) formula vs numerica coincidono:", np.allclose(tanh_deriv, (np.tanh(x + h) - np.tanh(x - h)) / (2 * h)))

relu = lambda x: np.maximum(0.0, x)
print("pendenza ReLU a sinistra e a destra di 0:", (relu(0) - relu(-1e-8)) / 1e-8, (relu(1e-8) - relu(0)) / 1e-8)

Output ottenuto:

x            : [-6. -4. -2.  0.  2.  4.  6.]
σ'(x) formula: [0.00247 0.01766 0.10499 0.25    0.10499 0.01766 0.00247]
σ'(x) numerica: [0.00247 0.01766 0.10499 0.25    0.10499 0.01766 0.00247]
massimo di σ' : 0.25 (= 1/4 in x = 0)
tanh'(x) formula vs numerica coincidono: True
pendenza ReLU a sinistra e a destra di 0: 0.0 1.0

Cosa fa il codice. Calcola la derivata della sigmoide in sette punti in due modi, con la formula σ(1−σ) e per differenze finite, e le due righe coincidono; mostra che il massimo è 0.25 e che ai bordi (x = ±6) la derivata è ~0.002, quasi zero. Verifica poi la derivata di tanh. L'ultima riga misura le due pendenze della ReLU attorno a zero: 0 e 1, diverse, quindi la derivata in 0 non esiste.

La derivata numerica e i suoi due errori

Il computer non conosce i limiti: calcola il rapporto incrementale con un h piccolo ma finito. Questo introduce due errori in competizione.

  1. Errore di troncamento. Il rapporto non è la derivata, ne è un'approssimazione. Per capirne la grandezza serve lo sviluppo di Taylor (prossima sezione): per la differenza in avanti l'errore è proporzionale a h, per quella centrale a h². Più h è piccolo, meglio.
  2. Errore di arrotondamento. Sottrarre due numeri quasi uguali (f(x+h) − f(x)) è la cancellazione catastrofica della lezione sui float: il risultato perde cifre significative; poi si divide per un h minuscolo che amplifica l'errore. L'errore cresce come ε_macchina / h. Più h è piccolo, peggio.

Il totale è una curva a V: esiste un h ottimale intermedio. I due errori vanno in direzioni opposte, quindi nessuna delle due scelte estreme è corretta: con h grande vince il troncamento, con h minuscolo vince l'arrotondamento. La curva del listato seguente mostra dove si incontrano per la funzione eˣ.

Errore della derivata numerica in funzione del passoDue curve a V in scala logaritmica: l'errore cala con h fino a un minimo poi risale per colpa dell'arrotondamento.10^−110^−410^−710^−1010^−1310^−1610^-1610^-1210^-810^-410^0differenza in avantidifferenza centralepasso h (scala logaritmica)errore assolutoderivata di e^x in x = 1: l'errore scende, poi risale
Figura: calcolata con il codice della lezione.
x0 = 1.0
esatto = math.exp(x0)                       # (e^x)' = e^x
print("   h       errore in avanti    errore centrale")
for k in range(1, 17):
    h = 10.0 ** (-k)
    avanti = (math.exp(x0 + h) - math.exp(x0)) / h
    centr = (math.exp(x0 + h) - math.exp(x0 - h)) / (2 * h)
    print(f"1e-{k:02d}   {abs(avanti - esatto):15.3e}   {abs(centr - esatto):15.3e}")

eps = np.finfo(float).eps
print("ε macchina:", eps, "| h ottimale in avanti ~ √ε =", f"{eps ** 0.5:.1e}", "| centrale ~ ε^(1/3) =", f"{eps ** (1 / 3):.1e}")

Output ottenuto:

   h       errore in avanti    errore centrale
1e-01         1.406e-01         4.533e-03
1e-02         1.364e-02         4.530e-05
1e-03         1.360e-03         4.530e-07
1e-04         1.359e-04         4.531e-09
1e-05         1.359e-05         5.859e-11
1e-06         1.359e-06         1.635e-10
1e-07         1.399e-07         5.859e-11
1e-08         6.603e-09         6.603e-09
1e-09         2.154e-07         6.603e-09
1e-10         1.548e-06         6.727e-07
1e-11         3.263e-05         1.043e-05
1e-12         4.323e-04         2.103e-04
1e-13         4.559e-04         4.559e-04
1e-14         9.338e-03         9.338e-03
1e-15         3.903e-01         1.683e-01
1e-16         2.718e+00         2.718e+00
ε macchina: 2.220446049250313e-16 | h ottimale in avanti ~ √ε = 1.5e-08 | centrale ~ ε^(1/3) = 6.1e-06

Cosa fa il codice. Calcola l'errore delle due formule per h da 10⁻¹ a 10⁻¹⁶ sulla funzione eˣ in x = 1, dove la risposta esatta è nota. La differenza in avanti raggiunge il minimo attorno a h ≈ 10⁻⁸ (circa √ε, con ε ≈ 2.2·10⁻¹⁶ l'epsilon macchina) e poi peggiora; la centrale ha il minimo più in basso (circa 6·10⁻¹¹) e a h più grande, attorno a 10⁻⁵, anche se nell'intorno del minimo i valori non scendono in modo regolare (5.9·10⁻¹¹ a 10⁻⁵ e 10⁻⁷, 1.6·10⁻¹⁰ a 10⁻⁶) perché l'arrotondamento è casuale (la teoria dà ε^{1/3} ≈ 6·10⁻⁶). A h = 10⁻¹⁶ il rapporto è 0 o spazzatura: x + h coincide con x per l'arrotondamento. Lezione pratica: più piccolo non è meglio.

Lo sviluppo di Taylor

L'analogia: l'ingrandimento

Zooma su una curva liscia in un punto: più ingrandisci, più la curva somiglia alla sua tangente (una retta). La retta è la migliore approssimazione di ordine 1. Se vuoi qualcosa di meglio aggiungi la curvatura: una parabola. E così via. Lo sviluppo di Taylor (1715) fa questo sistematicamente:

f(x + h) ≈ f(x) + f'(x)·h + f''(x)·h²/2 + f'''(x)·h³/6 + …

con f'' la derivata della derivata (la derivata seconda, la curvatura). Il termine di ordine n è f⁽ⁿ⁾(x) hⁿ/n!: compare il fattoriale della lezione di combinatoria.

Da qui si legge subito l'errore della differenza in avanti: (f(x+h) − f(x))/h = f'(x) + f''(x)·h/2 + …, quindi lo scarto dalla derivata vale circa f''·h/2: proporzionale a h. Con la centrale i termini pari si cancellano e resta f'''·h²/6: proporzionale a h².

def taylor_exp(h, ordine):
    """Approssimazione di e^h attorno a 0 con i primi (ordine+1) termini."""
    return sum(h ** k / math.factorial(k) for k in range(ordine + 1))

h = 1.0
print("e^1 =", round(math.exp(h), 8))
for ordine in (1, 2, 3, 5, 8):
    ap = taylor_exp(h, ordine)
    print(f"ordine {ordine}: {ap:.8f}  errore {abs(ap - math.exp(h)):.2e}")

print("sigmoide vicino a 0: σ(x) ≈ 1/2 + x/4  ->", [(x, round(float(sigma(x)), 5), round(0.5 + x / 4, 5)) for x in (0.1, 0.5, 1.0)])

Output ottenuto:

e^1 = 2.71828183
ordine 1: 2.00000000  errore 7.18e-01
ordine 2: 2.50000000  errore 2.18e-01
ordine 3: 2.66666667  errore 5.16e-02
ordine 5: 2.71666667  errore 1.62e-03
ordine 8: 2.71827877  errore 3.06e-06
sigmoide vicino a 0: σ(x) ≈ 1/2 + x/4  -> [(0.1, 0.52498, 0.525), (0.5, 0.62246, 0.625), (1.0, 0.73106, 0.75)]

Cosa fa il codice. taylor_exp somma i primi termini dello sviluppo di eˣ (tutte le derivate di eˣ sono eˣ, che in 0 vale 1, quindi i coefficienti sono 1/k!). L'errore scende rapidamente con l'ordine: con 9 termini (ordine 8) l'errore è già sotto 10⁻⁵. L'ultima riga linearizza la sigmoide in zero, usando σ(0) = 1/2 e σ'(0) = 1/4: la retta 1/2 + x/4 è una buona approssimazione vicino a 0 e peggiora allontanandosi. Questa linearizzazione (approssimare localmente con una retta) è alla base di ogni metodo di ottimizzazione di cui parliamo nella lezione successiva.

Massimi e minimi

Se una funzione liscia ha un massimo o un minimo locale in x*, lì la tangente è orizzontale: f'(x*) = 0 (teorema di Fermat). Perché: se f'(x*) > 0, spostandosi un poco a destra la funzione aumenta, quindi x* non è un massimo; se f'(x*) < 0 spostandosi a sinistra aumenta; resta solo f'(x*) = 0.

La condizione è necessaria, non sufficiente: f(x) = x³ ha derivata zero in 0 ma non ha né massimo né minimo. Per distinguere si usa la derivata seconda: f'' > 0 (curva a coppa verso l'alto) indica un minimo, f'' < 0 un massimo, f'' = 0 richiede più attenzione.

Esempio importante: la media minimizza l'errore quadratico. Dati y₁, …, yₙ, qual è il numero c che li riassume minimizzando S(c) = Σ (yᵢ − c)²? Derivando rispetto a c: S'(c) = −2 Σ (yᵢ − c) = 0 ⇒ Σ yᵢ = n·c ⇒ c = ȳ. E S''(c) = 2n > 0: è un minimo. La media è il riassunto che minimizza l'errore quadratico. (Con l'errore assoluto Σ|yᵢ − c| il minimo è la mediana: perciò media e mediana si comportano diversamente con gli outlier, lezione di statistica.)

y = np.array([2.0, 3.0, 4.0, 9.0, 12.0])

S = lambda c: ((y - c) ** 2).sum()
A = lambda c: np.abs(y - c).sum()
cs = np.linspace(0, 15, 15001)
print("minimo di Σ(y-c)²  in c =", round(float(cs[np.argmin([S(c) for c in cs])]), 3), "| media =", y.mean())
print("minimo di Σ|y-c|   in c =", round(float(cs[np.argmin([A(c) for c in cs])]), 3), "| mediana =", np.median(y))

# ricerca del punto a derivata zero con il metodo di bisezione sulla derivata di S
dS = lambda c: -2 * (y - c).sum()
lo, hi = 0.0, 15.0
for _ in range(60):
    medio = (lo + hi) / 2
    lo, hi = (medio, hi) if dS(medio) < 0 else (lo, medio)
print("zero della derivata trovato per bisezione:", round((lo + hi) / 2, 6))

Output ottenuto:

minimo di Σ(y-c)²  in c = 6.0 | media = 6.0
minimo di Σ|y-c|   in c = 4.0 | mediana = 4.0
zero della derivata trovato per bisezione: 6.0

Cosa fa il codice. Cerca il minimo di due funzioni di errore su una griglia di valori di c: per l'errore quadratico cade sulla media (6), per quello assoluto sulla mediana (4). Poi trova il minimo del primo con un metodo diverso: la bisezione sulla derivata S'(c), che è negativa a sinistra del minimo e positiva a destra; dimezzando ripetutamente l'intervallo in cui cambia segno si converge allo zero di S'. È un assaggio di ciò che fa un ottimizzatore: cercare dove la pendenza si annulla.

Errori tipici (sintomo, causa, rimedio)

  • Derivata del prodotto come prodotto delle derivate. (fg)' ≠ f'g'. Rimedio: la regola del prodotto, con i due termini.
  • Dimenticare la derivata interna nella regola della catena. Rimedio: scrivere f'(g(x)) · g'(x) per esteso.
  • h troppo piccolo nella derivata numerica. Rimedio: h ≈ √ε (in avanti) o ε^{1/3} (centrale), e differenza centrale.
  • Derivata zero = minimo. Rimedio: verificare la derivata seconda o confrontare i valori.
  • Derivare dove non si può. Il valore assoluto, la ReLU, un gradino: spigoli. Rimedio: convenzione dichiarata o sotto-gradiente.
  • Confondere f'(x) con f(x): la funzione e la sua pendenza sono oggetti diversi.
  • Trascurare le unità. La derivata ha unità unità di f / unità di x (km/h, euro per cliente).

I primi due errori sono di algebra e li smaschera il controllo numerico del mini-progetto; il terzo e il quarto sono di giudizio sul calcolo; gli ultimi tre riguardano il significato di ciò che si calcola. Un buon criterio per difendersi da tutti è scrivere l'unità di misura della derivata prima di usarla: se non riesci a dirla, probabilmente stai derivando la cosa sbagliata.

Come lavorare nella pratica

Verifica ogni derivata analitica con la derivata numerica (differenza centrale, h ≈ 10⁻⁵): è il gradient check, un test che userai nelle reti neurali. Esprimi la derivata con valori già calcolati (σ' = σ(1−σ)), disegna la funzione e la sua derivata per vedere dove stanno le pendenze quasi nulle e gli spigoli, e ricorda che per il computer i limiti non esistono: esiste solo un h finito.

Il lessico essenziale. Il limite è il valore a cui f(x) si avvicina quando x si avvicina a un punto; una funzione è continua se il limite coincide con il suo valore. La derivata è la pendenza della tangente, cioè il limite del rapporto incrementale (la pendenza della secante). La regola della catena dice che la derivata di una composizione è il prodotto delle derivate, la derivata seconda misura la curvatura e linearizzare significa sostituire localmente la funzione con la sua tangente. L'epsilon macchina è la più piccola spaziatura relativa fra due float vicini a 1.

Mini-progetto

  1. Implementa derivata_centrale(f, x, h=1e-5) e usala per verificare le derivate di x³, ln(1+x²) e sin(x²) in tre punti ciascuna, confrontando con le formule che ricavi con la regola della catena.
  2. Traccia (stampa una tabella) l'errore della derivata centrale di sin in x = 1 per h da 10⁻¹ a 10⁻¹⁵ e individua il minimo.
  3. Calcola lo sviluppo di Taylor di sin x fino al quinto ordine e mostra per quali x l'errore resta sotto 10⁻⁴.

Il primo punto è sviluppato nella sfida svolta qui sotto. Il secondo ti fa riprodurre a mano la curva a V con la funzione seno, e il terzo applica Taylor a una funzione che hai già derivato: ti aspetti che l'errore cresca allontanandosi da zero, e la domanda è quanto in fretta.

Una sfida svolta: il verificatore di derivate

Il mini-progetto proponeva di costruire un piccolo strumento che controlli le derivate scritte a mano. È il gradient check in versione a una variabile, ed è un'abitudine che ti salverà molte volte. Le tre funzioni di prova richiedono la regola della catena: x³ (la derivata è 3x²), ln(1 + x²) (derivata 2x/(1 + x²): la derivata del logaritmo 1/u per la derivata interna 2x) e sin(x²) (derivata cos(x²)·2x).

import math

def derivata_centrale(f, x, h=1e-5):
    return (f(x + h) - f(x - h)) / (2 * h)

casi = [
    ("x^3",         lambda x: x ** 3,               lambda x: 3 * x ** 2),
    ("ln(1 + x^2)", lambda x: math.log(1 + x * x),  lambda x: 2 * x / (1 + x * x)),
    ("sin(x^2)",    lambda x: math.sin(x * x),      lambda x: 2 * x * math.cos(x * x)),
]
print(f"{'funzione':<12} {'x':>5} {'numerica':>14} {'analitica':>14} {'errore relativo':>16}")
for nome, f, df in casi:
    for x in (0.5, 1.0, 2.0):
        num, ana = derivata_centrale(f, x), df(x)
        print(f"{nome:<12} {x:>5} {num:>14.8f} {ana:>14.8f} {abs(num - ana) / max(abs(ana), 1e-12):>16.2e}")

# errore al variare del passo, per sin(x) in x = 1: la V della lezione
print()
x0, esatto = 1.0, math.cos(1.0)
for k in (2, 4, 5, 6, 8, 10, 12, 14):
    h = 10.0 ** (-k)
    print(f"h = 1e-{k:02d}: errore = {abs(derivata_centrale(math.sin, x0, h) - esatto):.2e}")

Output ottenuto:

funzione         x       numerica      analitica  errore relativo
x^3            0.5     0.75000000     0.75000000         1.32e-10
x^3            1.0     3.00000000     3.00000000         3.25e-11
x^3            2.0    12.00000000    12.00000000         1.77e-11
ln(1 + x^2)    0.5     0.80000000     0.80000000         5.10e-11
ln(1 + x^2)    1.0     1.00000000     1.00000000         1.57e-11
ln(1 + x^2)    2.0     0.80000000     0.80000000         1.00e-12
sin(x^2)       0.5     0.96891242     0.96891242         4.34e-11
sin(x^2)       1.0     1.08060461     1.08060461         2.21e-10
sin(x^2)       2.0    -2.61457448    -2.61457448         3.77e-10

h = 1e-02: errore = 9.00e-06
h = 1e-04: errore = 9.00e-10
h = 1e-05: errore = 1.11e-11
h = 1e-06: errore = 2.77e-11
h = 1e-08: errore = 2.58e-09
h = 1e-10: errore = 5.85e-08
h = 1e-12: errore = 1.23e-05
h = 1e-14: errore = 3.71e-03

Come leggere il codice. derivata_centrale implementa la differenza centrale (f(x+h) − f(x−h))/(2h) con h = 10⁻⁵, il valore che la lezione indica come buon compromesso. La lista casi associa a ciascuna funzione la sua derivata calcolata a mano; il ciclo confronta le due in tre punti e stampa l'errore relativo (la differenza divisa per il valore, con un minimo al denominatore per evitare di dividere per zero). Un errore relativo dell'ordine di 10⁻¹⁰ o 10⁻¹¹ dice che la formula a mano è corretta; un errore dell'ordine di 10⁻¹ o più avrebbe segnalato un errore di calcolo (un fattore dimenticato, un segno sbagliato): è così che si smaschera, per esempio, la derivata interna dimenticata nella regola della catena. La seconda parte ripete la misura della curva a V sulla funzione seno: l'errore scende fino a h ≈ 10⁻⁵-10⁻⁶ e poi risale per l'arrotondamento. Tienilo a mente quando scegli h: più piccolo non è meglio.

Challenge

  • Easy. Calcola dalla definizione la derivata di f(x) = 3x² + 2x.
  • Medium. Dimostra (1/g)' = −g'/g² con la definizione di limite e ricava la regola del quoziente.
  • Hard. Ricava tanh'(x) = 1 − tanh²(x) partendo da tanh = 2σ(2x) − 1, usando σ' = σ(1 − σ) e la regola della catena.
  • Expert. Dimostra con lo sviluppo di Taylor che l'errore di troncamento della differenza centrale è f'''(x)·h²/6 + …, e ricava l'h che minimizza la somma di errore di troncamento e di arrotondamento (suggerimento: l'errore di arrotondamento è circa ε·|f|/h).
  • Enterprise. Prendi una funzione di costo del tuo dominio (costo totale in funzione di una soglia, di un prezzo, di una capacità) e trova numericamente dove la derivata si annulla; confrontala con l'ottimo che ricavi a mano e discuti se è un minimo.

Le prime due sfide consolidano la definizione e il quoziente, la terza collega sigmoide e tangente iperbolica, la quarta unisce Taylor e arrotondamento in un solo argomento. L'ultima è la più vicina al lavoro reale: cercare lo zero della derivata di una funzione di costo è esattamente ciò che fa un ottimizzatore.

Che cosa sai fare adesso

Sai calcolare una derivata dalla definizione e con le regole, derivare sigmoide, tanh e ReLU e dire dove ReLU non è derivabile, scegliere un h ragionevole per la derivata numerica sapendo perché la curva è a V, e riconoscere in un massimo o in un minimo il punto in cui la pendenza si annulla. Nella lezione successiva, gradiente, integrali e discesa del gradiente, la stessa idea passa da una a molte variabili; il calcolo che dà la derivata di una rete intera è sviluppato in Backpropagation, e la discesa del gradiente in La discesa del gradiente.

Nella stessa serie trovi anche: 20 - Statistica: stimare, confrontare, decidere (la lezione precedente), 16 - Insiemi e logica: il linguaggio della matematica, 23 - Algebra lineare 1: vettori, matrici, sistemi (che richiamano questa lezione).

Fonti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti