Da dove nasce una funzione di loss: verosimiglianza, non convenzione
Tre linee sugli stessi venti dati e tre regole di punteggio incoronano tre vincitori diversi. L’errore quadratico è una scelta.
In questa pagina
La lama che taglia i pezzi si consuma. Durante un turno di dieci ore perde abbastanza filo perché i pezzi escano dal nastro una frazione di millimetro più larghi rispetto all’inizio, e quando superano i 23,5 millimetri l’ispezione li scarta. Nessuno in fabbrica sa quando succeda. Quello che hanno è un calibro, un quaderno e venti letture del martedì precedente: le ore trascorse dal cambio lama e la larghezza del pezzo misurata in quel momento.
Qualcuno traccia una linea tra i punti. Qualcun altro ne traccia una leggermente diversa. Una terza persona ne traccia una terza. Tutte e tre sembrano ragionevoli sulla carta, e divergono di diverse ore su quando cambiare la lama — in questa fabbrica, la differenza tra una settimana tranquilla e un lotto da buttare.
Quale linea è migliore?
Così formulata, la domanda non ha risposta. Non una risposta difficile — proprio nessuna risposta. "Migliore" non è una proprietà di una linea come lo è la sua pendenza; è una proprietà di una linea insieme a una regola per assegnare un punteggio alle linee, e finché qualcuno non scrive la regola non c’è nulla da calcolare. Questo capitolo prende sul serio quella frase, e si chiude con la scoperta che la regola più comune nel machine learning non è una convenzione ma la conseguenza di un’affermazione sul mondo — una che puoi testare, e una che a volte è falsa.
Una confessione prima della prima riga di codice. Queste venti letture non arrivano da una fabbrica reale: le ho generate da una linea che ho scelto, , più rumore casuale con una dispersione di circa un decimo di millimetro. È importante, perché tutto quello che segue riguarda se un metodo recupera una verità, e l’unico modo per verificarlo è conoscere la verità in anticipo. Quindi: 0,30 millimetri all’ora è la risposta in fondo al libro. Non puoi usarla, solo confrontarti con essa.
Tre regole, tre vincitori
Link alla sezione: Tre regole, tre vincitoriEcco le letture e le tre linee, valutate in tre modi: errore quadratico, quello a cui arrivano tutti; errore assoluto, quello che potrebbe scegliere uno statistico; ed errore peggiore, quello che sceglierebbe il macchinista, perché all’ispettore non importa la tua media — scarta il singolo pezzo fuori tolleranza.
NumPy arriva qui, un capitolo dopo il perceptron in Python puro, per una ragione: entro la fine di questo capitolo valutiamo quattrocentomila linee candidate contro venti letture ciascuna, e un loop Python è lo strumento sbagliato. È anche la notazione in cui è scritta ogni fonte citata sotto.
import numpy as np
# Hours since the blade was changed, and the width of the part measured then.
SHIFT = np.array([
(0.5, 20.17), (1.0, 20.28), (1.5, 20.53), (2.0, 20.61), (2.5, 20.69),
(3.0, 20.94), (3.5, 21.21), (4.0, 21.31), (4.5, 21.27), (5.0, 21.35),
(5.5, 21.58), (6.0, 21.80), (6.5, 21.67), (7.0, 22.07), (7.5, 22.10),
(8.0, 22.31), (8.5, 22.48), (9.0, 22.66), (9.5, 22.90), (10.0, 23.13),
])
h, y = SHIFT[:, 0], SHIFT[:, 1]
LINES = {"A": (20.10, 0.26), "B": (20.20, 0.28), "C": (20.30, 0.26)}
for name, (a, b) in LINES.items():
r = y - (a + b * h)
print(f"{name} mean square {np.mean(r**2):.5f}"
f" mean absolute {np.mean(np.abs(r)):.5f}"
f" worst {np.max(np.abs(r)):.3f}") La quantità nelle righe evidenziate è il residuo: ciò che ha detto la linea meno ciò che ha detto il calibro, un numero per lettura. Ogni regola di punteggio in questo capitolo, e ogni loss function nei ventotto capitoli successivi, è un modo per schiacciare una lista di residui in un singolo numero. Differiscono solo per come la schiacciano.
A mean square 0.02699 mean absolute 0.12600 worst 0.430
B mean square 0.02524 mean absolute 0.13700 worst 0.350
C mean square 0.03179 mean absolute 0.15000 worst 0.320Leggi le colonne, non le righe. L’errore quadratico dice B, l’errore assoluto dice A, l’errore peggiore dice C: tre regole, tre vincitori, sugli stessi venti punti.
Ho scelto queste tre linee perché non fossero d’accordo, e va detto chiaramente. Il punto è quanto sia stato facile — pochi minuti di ricerca tra intercette e pendenze dall’aspetto sensato producono centinaia di terne simili. La classifica è una proprietà della regola che hai scelto, non un fatto sulle linee, quindi la regola non è un dettaglio implementativo: è la definizione del problema. Il che solleva la domanda a cui questo capitolo esiste per rispondere: su quali basi la scegli?
Un parametro, e una valle
Link alla sezione: Un parametro, e una vallePrima una questione più piccola, perché non ci sono tre linee ma infinite. Prendi per ora l’errore quadratico, dato che è quello che prendono tutti, e riduci il problema a un solo numero usando il trucco che ha risparmiato al perceptron undicimila epoche nel Capitolo 1: sottrai la media da entrambe le colonne. Una volta che la nuvola di punti è centrata sull’origine, la migliore linea secondo l’errore quadratico passa esattamente per l’origine — quindi l’intercetta è fissata e resta da scegliere solo la pendenza.
u, v = h - h.mean(), y - y.mean() # 5.25 hours, 21.553 mm
def mse(theta):
return np.mean((v - theta * u) ** 2)
grid = np.arange(0.0, 0.6001, 0.001)
curve = np.array([mse(t) for t in grid])
print(grid.size, "candidates ->", f"theta={grid[curve.argmin()]:.3f}", f"mse={curve.min():.6f}")601 candidates -> theta=0.293 mse=0.010115Seicento uno pendenze candidate, un vincitore: 0,293 millimetri all’ora contro una verità di 0,300. Venti letture rumorose e un for-loop sono arrivati entro un centesimo di millimetro all’ora — due virgola tre per cento.
La parte interessante non è il vincitore ma la forma della ricerca. Stampa l’intera curva, ruotata in modo che la loss scorra da sinistra a destra:
ts = np.arange(0.0, 0.6001, 0.04)
ls = np.array([mse(t) for t in ts])
for t, l in zip(ts, ls):
col = round(l / ls.max() * 50)
print(f"theta={t:.2f} |{' ' * col}*{' ' * (50 - col)}| mse={l:7.4f}")theta=0.00 | * | mse= 0.7244
theta=0.04 | * | mse= 0.5428
theta=0.08 | * | mse= 0.3878
theta=0.12 | * | mse= 0.2593
theta=0.16 | * | mse= 0.1575
theta=0.20 | * | mse= 0.0822
theta=0.24 | * | mse= 0.0336
theta=0.28 | * | mse= 0.0116
theta=0.32 | * | mse= 0.0161
theta=0.36 | * | mse= 0.0473
theta=0.40 | * | mse= 0.1050
theta=0.44 | * | mse= 0.1894
theta=0.48 | * | mse= 0.3004
theta=0.52 | * | mse= 0.4379
theta=0.56 | * | mse= 0.6021
theta=0.60 | *| mse= 0.7928È una valle, vista di lato. Ha un solo fondo, le pareti salgono dolcemente da entrambi i lati e — questa è la parte che la scala del Capitolo 1 non poteva offrire — in ogni singolo punto esiste una direzione ben definita di "discesa". Ricorda questa forma. Il Capitolo 3 parla interamente di come scenderla senza visitare tutti i seicento uno punti, e di cosa cambia quando una valle ha più di un fondo.
Allora perché il quadrato?
Link alla sezione: Allora perché il quadrato?Abbiamo una valle perché abbiamo elevato al quadrato. L’errore assoluto avrebbe prodotto uno spigolo sul fondo; l’errore peggiore avrebbe prodotto tratti piatti dove spostare la linea non cambia nulla. Elevare al quadrato è innegabilmente comodo — e la comodità è più o meno la ragione che danno la maggior parte dei corsi, rivestita in quattro modi: rende positivi gli errori (anche il valore assoluto); penalizza di più gli errori grandi (perché dovrebbe?); è differenziabile (lo è anche la quarta potenza); è ciò che usano tutti (vero, e non è un argomento).
La posizione onesta è questa. L’errore quadratico ha scelto la linea B e l’errore assoluto ha scelto la linea A. Una delle due è giusta per questa fabbrica e l’altra è sbagliata, e nulla di quanto detto finora può dirti quale. Per scegliere la regola devi sapere qualcosa su come le letture sono arrivate a differire dalla linea, e questa è una domanda sul mondo, non sulla matematica. Per rispondere serve un piccolo meccanismo.
La likelihood di una linea
Link alla sezione: La likelihood di una lineaEcco l’affermazione che trasforma "quale linea è migliore" in una domanda con risposta.
Supponi che la larghezza di un pezzo sia la linea più un errore casuale, e supponi che quell’errore sia estratto da una Gaussiana — la curva a campana — con media zero e deviazione standard :
La densità della Gaussiana è
Ora fai qualcosa che il perceptron non poteva fare. Per una pendenza candidata data , ogni lettura ha un residuo, e la formula sopra trasforma quel residuo in un numero: quanto è plausibile un errore esattamente di quella grandezza, se questa pendenza è la verità? Una lettura sulla linea ottiene un numero grande, una lettura distante mezzo millimetro uno piccolo.
Le letture sono indipendenti — il calibro non ricorda il pezzo precedente — quindi la regola del prodotto dice che la plausibilità dell’intero quaderno è il prodotto delle singole densità. Quel prodotto è la likelihood di .1 Nota la direzione, perché è la direzione di cui parla la regola di Bayes: i dati sono fissi e noti, ed è il parametro a variare. Questa non è "la probabilità della pendenza". È la probabilità che il modello assegna ai dati che hai effettivamente ottenuto, letta come funzione della pendenza.
SIGMA = 0.12
def gaussian(r, sigma):
return np.exp(-r ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))
def likelihood(theta):
return np.prod(gaussian(v - theta * u, SIGMA))
for t in (0.25, 0.293, 0.35):
print(f"theta={t} likelihood = {likelihood(t):.6g}")theta=0.25 likelihood = 521.952
theta=0.293 likelihood = 2.42028e+07
theta=0.35 likelihood = 0.190312Una pendenza di 0,293 rende questo quaderno quarantaseimila volte più plausibile di 0,25, e centoventisette milioni di volte più plausibile di 0,35. La massima likelihood è il principio secondo cui scegli il parametro che rende ciò che hai effettivamente osservato il meno sorprendente possibile. Non è un teorema ma una proposta su cosa dovrebbe significare "migliore" — una proposta con contenuto, perché ti costringe a dichiarare la tua assunzione sul rumore prima di potere assegnare un punteggio a qualcosa.
Il prodotto si rompe
Link alla sezione: Il prodotto si rompeEsegui le stesse tre righe di codice su un mese di turni invece che su uno, e il metodo cede.
rng = np.random.default_rng(7)
u_big = rng.uniform(-5.25, 5.25, 2000) # 2000 readings, not 20
v_big = 0.30 * u_big + 0.12 * rng.standard_normal(2000)
print("2000 readings, sigma = 0.12 mm :", np.prod(gaussian(v_big - 0.30 * u_big, 0.12)))
noisy = 0.30 * u_big + 2.0 * rng.standard_normal(2000)
print("2000 readings, sigma = 2.00 mm :", np.prod(gaussian(noisy - 0.30 * u_big, 2.0)))
print("largest float64 :", np.finfo(np.float64).max)RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308Duemila moltiplicazioni e la risposta è inf. Cambia una sola costante — un calibro più impreciso, così le densità risultano minori di 1 invece che maggiori — e lo stesso codice restituisce 0.0. Entrambe le risposte sono sbagliate, in direzioni opposte, nessuna solleva un’eccezione che puoi intercettare, e la seconda non stampa nemmeno un warning.
Non c’è nulla che non va nella matematica. La likelihood con quelle impostazioni è un numero finito perfettamente definito: il suo logaritmo naturale è 1400,91, quindi il numero stesso è circa . Il problema è che il tuo computer non possiede quel numero, e vale la pena capire esattamente quali numeri possiede, perché non sarà l’ultima volta che deciderà il risultato.
Da dove viene il quadrato
Link alla sezione: Da dove viene il quadratoLa soluzione per il prodotto che esplode è quella solita: prendere i logaritmi. Il logaritmo trasforma i prodotti in somme, è strettamente crescente quindi non può spostare la posizione del massimo, e una somma di duemila numeri moderati è qualcosa che float64 gestisce senza lamentarsi. Per convenzione prendiamo la log-likelihood negativa, così migliore significa più piccolo. Ora sostituisci la densità Gaussiana e guarda cosa succede.
-
Parti dal prodotto. La likelihood è , con la densità Gaussiana sopra.
-
Prendi meno il log. Il prodotto diventa una somma, e l’esponenziale nella densità si cancella direttamente con il logaritmo:
- Butta via tutto ciò che non contiene . Il primo termine è una costante. Il davanti alla somma è una costante positiva, e moltiplicare una funzione per una costante positiva non può spostare il punto in cui ha il minimo. Ciò che resta è
che è la somma dei residui al quadrato — la cosa da cui abbiamo iniziato il capitolo perché era la prima cosa a cui tutti pensano.
Questo è il risultato per cui il capitolo esiste, e merita di essere affermato senza esitazioni: l’errore quadratico non è una convenzione. È la log-likelihood negativa di una Gaussiana, con le costanti rimosse. Minimizzare l’errore quadratico è esattamente lo stesso atto che affermare che i tuoi errori sono Gaussiani e chiedere quale parametro renda i tuoi dati meno sorprendenti. Stavi facendo quell’affermazione da sempre; semplicemente non te lo dicevano.
L’equivalenza è verificabile, quindi verificala: scansiona le stesse seicento uno pendenze con la log-likelihood negativa completa, costanti incluse, e con il semplice errore quadratico.
N = v.size
def nll(theta):
r = v - theta * u
return N * np.log(SIGMA * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * SIGMA ** 2)
nlls = np.array([nll(t) for t in grid])
mses = np.array([mse(t) for t in grid])
print(f"argmin of the negative log-likelihood : theta={grid[nlls.argmin()]:.3f} nll={nlls.min():.6f}")
print(f"argmin of the mean squared error : theta={grid[mses.argmin()]:.3f} mse={mses.min():.6f}")
print("same index:", nlls.argmin() == mses.argmin())argmin of the negative log-likelihood : theta=0.293 nll=-17.001977
argmin of the mean squared error : theta=0.293 mse=0.010115
same index: TrueNumeri diversi sull’asse verticale, e uno dei due è negativo, cosa che una somma di quadrati non è mai: una log-likelihood negativa può scendere sotto zero, perché una densità può superare 1. Stesso fondo della stessa valle, fino all’ultimo punto della griglia.
Mostra la derivazione completa
Quali scarti sono sicuri, esattamente? La stessa manovra appare in ogni capitolo che deriva una loss, e non è sempre innocente.
Eliminare una costante additiva è sicuro ogni volta che non dipende dal parametro che stai ottimizzando, ed eliminare una costante moltiplicativa positiva è sicuro perché per ogni . Entrambi falliscono nel momento in cui anche viene stimato: allora non è affatto una costante, è il termine che impedisce al modello di dichiarare e plausibilità infinita. È esattamente la prossima sezione.
Falliscono in modo diverso ancora nel Capitolo 3: una costante moltiplicativa non sposta il minimo, ma scala il gradient, e il gradient viene moltiplicato per il learning rate. Dividere per per ottenere l’errore quadratico medio invece della somma è invisibile per la risposta e molto visibile per il training run — con la somma, raddoppiare la dimensione del batch raddoppia ogni passo che fai.
Anche sigma non è gratis
Link alla sezione: Anche sigma non è gratisAbbiamo fissato a 0,12 per decreto, e nessuno in fabbrica conosce la dispersione dell’errore del calibro. Trattala come una seconda incognita e lascia che la massima likelihood decida anche quella. Qui il termine costante che abbiamo appena scartato ritorna, perché è l’unica cosa che sta tra il modello e una dichiarazione di precisione perfetta.
r = v - 0.293 * u
sigmas = np.arange(0.01, 1.0001, 0.0001)
nll_sigma = N * np.log(sigmas * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * sigmas ** 2)
print("best sigma on the grid :", round(float(sigmas[nll_sigma.argmin()]), 4))
print("sqrt(mean squared residual) :", round(float(np.sqrt(np.mean(r ** 2))), 4))best sigma on the grid : 0.1006
sqrt(mean squared residual) : 0.1006I due concordano a quattro decimali, e non per caso: derivare quell’espressione e porla uguale a zero dà esattamente . Quindi l’errore quadratico medio non è semplicemente simile a una varianza. In questo modello è la stima di massima likelihood della varianza del rumore — il numero che hai minimizzato fin dall’inizio era una stima di quanto sia rumoroso il tuo sensore.
Una piega, economica da enunciare e costosa da riscoprire più avanti: quella stima è distorta verso il basso, perché i residui sono stati misurati rispetto a un fit scelto proprio per renderli piccoli. Simulalo — duecentomila quaderni da venti letture ciascuno, estratti da una distribuzione la cui varianza vera è esattamente 1, con l’unico parametro del fit stimato dalle letture stesse. Dividere la somma dei quadrati per dà una media di 0,9501; dividere per dà 1,0001; e è 0,95 esatto. Ogni parametro che stimi costa un grado di libertà, e questa è la più piccola istanza visibile di un problema molto più grande: un modello sembra sempre migliore sui dati su cui è stato fittato. Il Capitolo 4 lo trasforma nella disciplina di tenere da parte dei dati, e il Capitolo 6 dà un nome all’effetto.
Una loss è un’affermazione sul rumore
Link alla sezione: Una loss è un’affermazione sul rumoreSe l’errore quadratico afferma che il rumore è Gaussiano, la domanda successiva è che cosa succede quando l’affermazione è falsa. Non leggermente falsa — falsa nel modo in cui le misure reali sono false.
In officina, la maggior parte delle letture del calibro è buona entro un decimo di millimetro, e una o due volte per turno una scheggia di truciolo finisce sotto la ganascia e la lettura sbaglia di diversi millimetri. Errori del genere hanno code pesanti: piccoli quasi sempre, occasionalmente enormi, ed enormi molto più spesso di quanto permetta una curva a campana. La distribuzione di Cauchy è il modello pulito standard di questo comportamento, e la sua densità è semplice quanto quella della Gaussiana:
La differenza è la coda: la Gaussiana cala come , brutalmente in fretta, e la Cauchy come , quasi per niente. La conseguenza è più facile da vedere che da dire:
rng = np.random.default_rng(3)
g = 0.12 * rng.standard_normal(10 ** 6) # Gaussian noise
c = 0.12 * rng.standard_cauchy(10 ** 6) # Cauchy noise, same scale
for k in (10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5, 10 ** 6):
print(f"{k:>9,} samples gaussian var {g[:k].var():.4f} cauchy var {c[:k].var():10.2f}") 100 samples gaussian var 0.0164 cauchy var 0.26
1,000 samples gaussian var 0.0146 cauchy var 59.88
10,000 samples gaussian var 0.0145 cauchy var 358.17
100,000 samples gaussian var 0.0144 cauchy var 3097.98
1,000,000 samples gaussian var 0.0144 cauchy var 32886.10La varianza campionaria della Gaussiana si stabilizza su 0,0144, cioè , e lì resta. Quella della Cauchy cresce, e continua a crescere finché campioni, perché non c’è nulla a cui possa convergere: la distribuzione di Cauchy non ha varianza, e nemmeno media. All’errore quadratico, il cui intero mestiere è minimizzare una media di quadrati, viene chiesta una quantità che non esiste.
Ecco quindi un turno in cui il calibro è stato ingannato. Stesse venti ore, stessa lama, stessa deriva di 0,30 millimetri all’ora — solo che il rumore ora è Cauchy. Fittalo due volte: una minimizzando i residui quadratici, una minimizzando la log-likelihood negativa del rumore che ha effettivamente generato i dati. Il trucco del centraggio qui non aiuta — fissa l’intercetta solo per l’errore quadratico — quindi entrambi i fit procedono per forza bruta su una griglia di intercette e pendenze, dato che non abbiamo ancora un modo per trovare il fondo di una valle se non visitandolo.
SWARF = np.array([
(0.5, 20.08), (1.0, 21.95), (1.5, 20.86), (2.0, 27.51), (2.5, 20.64),
(3.0, 20.75), (3.5, 21.01), (4.0, 21.03), (4.5, 21.37), (5.0, 20.60),
(5.5, 22.03), (6.0, 21.95), (6.5, 21.98), (7.0, 22.01), (7.5, 21.73),
(8.0, 22.97), (8.5, 22.60), (9.0, 22.66), (9.5, 22.44), (10.0, 22.78),
])
hs, ys = SWARF[:, 0], SWARF[:, 1]
A = np.arange(18.0, 22.001, 0.005) # 801 intercepts
B = np.arange(-0.20, 0.8001, 0.002) # 501 slopes
R = ys - (A[:, None, None] + B[None, :, None] * hs) # every line against every point
SCALE = 0.12
square = np.sum(R ** 2, axis=2) # least squares
cauchy = np.sum(np.log(1 + (R / SCALE) ** 2), axis=2) # Cauchy likelihood
for name, surface in (("least squares", square), ("Cauchy likelihood", cauchy)):
i, j = np.unravel_index(surface.argmin(), surface.shape)
print(f"{name:>18}: width = {A[i]:.3f} + {B[j]:.4f} * hours"
f" -> 23.5 mm at hour {(23.5 - A[i]) / B[j]:.2f}")
print(f"{'the truth':>18}: width = 20.000 + 0.3000 * hours"
f" -> 23.5 mm at hour {(23.5 - 20.0) / 0.30:.2f}")
print(f"{A.size * B.size:,} candidate lines evaluated")Le due righe evidenziate sono l’intera differenza tra i fit. Prendi il log della densità di Cauchy, elimina le costanti esattamente come prima, e è ciò che sopravvive. Stessa ricetta, diversa affermazione sul rumore.
least squares: width = 21.380 + 0.1080 * hours -> 23.5 mm at hour 19.63
Cauchy likelihood: width = 19.935 + 0.3020 * hours -> 23.5 mm at hour 11.80
the truth: width = 20.000 + 0.3000 * hours -> 23.5 mm at hour 11.67
401,301 candidate lines evaluatedI minimi quadrati riportano una deriva di 0,108 millimetri all’ora, circa un terzo del tasso reale, e concludono che la lama va bene fino all’ora 19,6. La risposta vera è l’ora 11,7. Agendo su quel fit, la fabbrica fa funzionare la pressa per otto ore in più producendo pezzi fuori tolleranza, con l’autorità della loss function più standard del settore. Il fit Cauchy, usando le stesse venti letture, la stessa griglia e una sola riga di differenza nel codice, arriva all’ora 11,8.
Due obiezioni meritano risposta, perché entrambe sono la prima cosa che dice un bravo ingegnere.
L’outlier è ovvio — basta cancellarlo. Puoi farlo, aiuta, e non basta. Cancellare la singola lettura peggiore sposta la pendenza dei minimi quadrati da 0,108 a 0,239, il che mette comunque il cambio lama all’ora 13,1, un’ora e mezza in ritardo; cancellare il peggiore, rifittare e cancellare qualunque sia il peggiore ora ti porta a 0,286 — e nota che questa è già una procedura, non un’osservazione: cancella invece i due residui più grandi del fit originale e arrivi a 0,223. Ma ora hai preso decisioni di giudizio che non puoi scrivere né difendere, e automatizzare la regola non la salva: elimina-il-residuo-più-grande-poi-rifitta, eseguita su mille turni simulati, ha un errore mediano di pendenza di 0,0177 contro lo 0,0100 del fit di likelihood, ed è fuori di più di 0,05 nel 14,7% dei turni contro l’1,3%. La cancellazione è una toppa sopra un’assunzione sbagliata. La likelihood non ha bisogno di toppe, perché non ha mai assunto che l’outlier fosse impossibile.
Hai scelto un dataset fortunato. Questa obiezione è esattamente giusta, ed è per questo che l’ultimo esperimento simula mille turni indipendenti e rifitta in entrambi i modi su ciascuno.
A = np.arange(18.0, 22.001, 0.02) # a coarser grid: a thousand fits to do
B = np.arange(-0.20, 0.8001, 0.005)
lines = A[:, None, None] + B[None, :, None] * hs
rng = np.random.default_rng(2026)
err_sq, err_ca = [], []
for _ in range(1000): # 1000 independent shifts
ys = 20.00 + 0.30 * hs + SCALE * rng.standard_cauchy(hs.size)
R = ys - lines
_, j = np.unravel_index(np.sum(R ** 2, axis=2).argmin(), (A.size, B.size))
_, q = np.unravel_index(np.sum(np.log1p((R / SCALE) ** 2), axis=2).argmin(), (A.size, B.size))
err_sq.append(abs(B[j] - 0.30))
err_ca.append(abs(B[q] - 0.30))
err_sq, err_ca = np.array(err_sq), np.array(err_ca)
for name, e in (("least squares", err_sq), ("Cauchy likelihood", err_ca)):
print(f"{name:>18}: median slope error {np.median(e):.4f} mm/h"
f" off by more than 0.05 in {100 * np.mean(e > 0.05):4.1f}% of shifts"
f" worst {e.max():.3f}")
print(f"the likelihood fit is the closer of the two in {100 * np.mean(err_ca < err_sq):.1f}% of shifts") least squares: median slope error 0.0350 mm/h off by more than 0.05 in 40.4% of shifts worst 0.500
Cauchy likelihood: median slope error 0.0100 mm/h off by more than 0.05 in 1.3% of shifts worst 0.090
the likelihood fit is the closer of the two in 75.6% of shiftsMediana, non media, per la stessa ragione di tutto il resto in questa sezione: gli errori dei minimi quadrati sono guidati da una Cauchy, quindi la loro media non è una cosa stabile da riportare. I minimi quadrati sbagliano di grosso in due turni su cinque; il fit di likelihood sbaglia di grosso in un turno su settantasette, e il suo peggior fallimento su mille turni è meno di un quinto del peggiore dei minimi quadrati.
Niente di tutto questo rende cattivo l’errore quadratico. Lo rende specifico, e l’aritmetica dice esattamente perché. Prendi un residuo di 0,1 mm e uno di 7 mm. Elevato al quadrato, il dato cattivo contribuisce al totale 4.900 volte quanto quello buono, quindi la linea viene trascinata di peso verso di lui; sotto la log-likelihood di Cauchy gli stessi due residui contribuiscono 0,527 e 8,133, un rapporto di 15,4. Il dato cattivo conta ancora, semplicemente non decide. Questo è l’inizio della statistica robusta, dove la loss di Huber del 1964 divide la differenza comportandosi in modo quadratico per residui piccoli e lineare per residui grandi,7 e dove Tukey aveva già mostrato quanta poca contaminazione serva per rendere la varianza campionaria uno strumento peggiore della deviazione assoluta media.8
Una nota storica, troppo bella per lasciarla fuori. I minimi quadrati furono pubblicati per primi da Legendre nel 1805, come dispositivo algebrico comodo senza altra giustificazione se non che funzionava.9 Quattro anni dopo Gauss fece il ragionamento al contrario: prese per buono che la media aritmetica fosse il modo giusto per combinare misure ripetute, chiese quale distribuzione degli errori rendesse la media il valore più probabile, e mostrò che essenzialmente una sola lo fa — quella che ora porta il suo nome.10 La derivazione in questo capitolo è la sua, ha più di due secoli, ed è ancora la parte che la maggior parte dei corsi lascia fuori.
Cosa puoi dire ora, e cosa ancora non sai fare
Link alla sezione: Cosa puoi dire ora, e cosa ancora non sai fareConquistato. Una loss function è una regola di punteggio, e la classifica che produce è una proprietà della regola, non dei candidati. Ogni loss in questo corso è la log-likelihood negativa di qualche assunzione sul rumore, con le costanti buttate via — la Gaussiana dà qui l’errore quadratico, la Bernoulli dà la cross-entropy nel Capitolo 4, e una distribuzione categoriale su un vocabolario dà la loss del next-token nel Capitolo 8. La ricetta non cambia mai: dichiara il rumore, scrivi la likelihood, prendi meno il log. E quando l’assunzione è sbagliata il modello non è semplicemente impreciso, è sbagliato in una direzione che puoi prevedere.
Ancora mancante. Abbiamo trovato il fondo della valle visitando ogni suo punto. Ha funzionato per un parametro e seicento candidati, ed è sopravvissuto a due parametri con 401.301 candidati in un quinto di secondo. Tre parametri alla stessa risoluzione sono 201.051.801 candidati e non entrano più in un array; una piccola rete nel Capitolo 5 ha migliaia di parametri, e i modelli a cui il Capitolo 10 assegna un prezzo ne hanno miliardi. Qui la forza bruta non è lenta, è aritmeticamente impossibile, e nulla in questo capitolo suggerisce un’alternativa.
Guarda però di nuovo la valle. Stando in con una loss di 0,0822, la direzione di "discesa" non è un mistero — la vedi sulla pagina, la curva scende verso destra. Se potessi chiedere alla loss function da che parte pende nel punto in cui ti trovi, senza valutarla da nessun’altra parte, potresti fare un passo in quella direzione, chiedere di nuovo e ripetere finché il terreno non diventa piatto.
Questa domanda ha un nome. La pendenza di una funzione in un punto è la sua derivata, e per una funzione di molti parametri la raccolta delle pendenze in ogni direzione contemporaneamente è il gradient. Il Capitolo 1 non poteva usarne uno, perché l’errore del perceptron era una scala senza pendenza da interrogare. Questo capitolo ha costruito qualcosa di migliore: una loss liscia ovunque e nata da un’assunzione dichiarata invece che da una preferenza.
Quindi la domanda per il Capitolo 3 non è più se una pendenza esista. È come calcolarla, perché muoversi contro di essa porta in discesa invece che in salita — un segno che quasi ogni corso ti chiede di prendere per fede — e quanto lungo deve essere il passo prima di chiedere di nuovo, che si rivela essere l’unico numero che decide se un training run converge, oscilla intorno alla risposta per sempre o scappa all’infinito.
Fonti e metodo
Link alla sezione: Fonti e metodoVale la pena leggere insieme a questo capitolo anche: Prince, Understanding Deep Learning §5.1–5.2 e Appendice C, che costruisce ogni loss del libro dalla massima likelihood nell’ordine usato qui; Goodfellow, Bengio e Courville, Deep Learning §3.1–3.11 e §5.5, la cui sezione sulla massima likelihood deriva anche la divergenza KL necessaria al Capitolo 4; Murphy, Probabilistic Machine Learning: An Introduction capitolo 2 e §4.2, su cosa la massima likelihood garantisce e non garantisce; Deisenroth, Faisal e Ong, Mathematics for Machine Learning §6.1–6.4 per la regola della somma, la regola del prodotto e la regola di Bayes fatte per bene; la breve nota CMU di Tom Mitchell Estimating Probabilities: MLE and MAP (2016); e §22.7 di Dive into Deep Learning, che arriva allo stesso risultato in codice eseguibile.
Riferimenti
Link alla sezione: Riferimenti-
Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, pp. 309–368 (1922). Dove la likelihood viene presentata come metodo generale, insieme a "parameter", "statistic", sufficienza ed efficienza. Il nome stesso, e la separazione dalla probabilità, è di un anno prima: Fisher, R. A., On the "probable error" of a coefficient of correlation deduced from a small sample, Metron 1, pp. 3–32 (1921), pp. 24–25. ↩
-
IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. Definisce binary32 e binary16, e le regole di arrotondamento che fanno uscire l’esperimento di somma come esce. ↩
-
Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). I parametri del formato e il caso a favore dello scambio di bit di mantissa con bit di esponente. ↩
-
Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. Loss scaling, e le magnitudini misurate dei gradient che lo rendono necessario in float16. ↩
-
Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), pp. 5–48 (1991). Ancora la migliore spiegazione singola del perché i due ordini di somma non concordino. ↩
-
Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), p. 40 (1965). Somma compensata in mezza pagina. ↩
-
Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), pp. 73–101 (1964). La loss che è quadratica vicino a zero e lineare nelle code, derivata invece che rattoppata. ↩
-
Tukey, J. W. A survey of sampling from contaminated distributions, in Contributions to Probability and Statistics (Stanford University Press, 1960), pp. 448–485. ↩
-
Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805), appendice Sur la méthode des moindres quarrés. La prima pubblicazione dei minimi quadrati, come dispositivo computazionale. ↩
-
Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809), Book II, §§175–179. L’argomento dalla media aritmetica alla legge normale degli errori, e da lì ai minimi quadrati. ↩