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
fina, e nemmeno se è definita ina. Conta cosa fafvicino ada. - «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.
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 numeroe ≈ 2.71828è definito in modo che(eˣ)' = eˣ: la funzione è uguale alla sua pendenza. Numericamente,(e^h − 1)/h → 1perh → 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.
- 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 ah². Piùhè piccolo, meglio. - 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 unhminuscolo 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ˣ.
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. htroppo 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)conf(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
- Implementa
derivata_centrale(f, x, h=1e-5)e usala per verificare le derivate dix³,ln(1+x²)esin(x²)in tre punti ciascuna, confrontando con le formule che ricavi con la regola della catena. - Traccia (stampa una tabella) l'errore della derivata centrale di
sininx = 1perhda10⁻¹a10⁻¹⁵e individua il minimo. - Calcola lo sviluppo di Taylor di
sin xfino al quinto ordine e mostra per qualixl'errore resta sotto10⁻⁴.
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 datanh = 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'hche 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
- M. Spivak, Calculus, 4ª ed., Publish or Perish, 2008 - limiti, derivate e Taylor; B. Taylor, Methodus incrementorum directa et inversa, 1715; A.-L. Cauchy, Cours d'analyse, 1821 (libri e opere storiche, non consultati online)
- N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2ª ed., SIAM, 2002 - errori della differenziazione numerica (libro a stampa, non consultato online)
- D. Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic (ristampa nella Numerical Computation Guide di Sun/Oracle) - la cancellazione catastrofica nella sottrazione di numeri quasi uguali, consultata il 2026-11-12
- NumPy: numpy.finfo - l'epsilon macchina del tipo
float64, consultata il 2026-11-12 - Python 3: Floating-Point Arithmetic, Issues and Limitations - perché i float sono approssimazioni binarie, consultata il 2026-11-12
Commenti
Caricamento commenti...
Accedi per commentare