Naar inhoud springen
2/30Hoofdstuk 2 van 30

Waar een verliesfunctie vandaan komt: likelihood, geen conventie

Drie lijnen op dezelfde twintig metingen en drie scoreregels met drie winnaars. Kwadratische fout is een keuze.

Op deze pagina

Het mes dat de onderdelen snijdt, slijt. Tijdens een dienst van tien uur verliest het genoeg scherpte dat de onderdelen aan het einde van de band een fractie van een millimeter breder zijn dan aan het begin, en zodra ze boven 23,5 millimeter komen, keurt de inspectie ze af. Niemand in de fabriek weet wanneer dat gebeurt. Wat ze hebben is een schuifmaat, een notitieboek en twintig metingen van afgelopen dinsdag: de uren sinds het mes is vervangen, en de breedte van het onderdeel op dat moment.

Iemand trekt een lijn door de punten. Iemand anders tekent er een die net iets anders loopt. Een derde tekent een derde. Alle drie zien er op papier redelijk uit, en ze verschillen meerdere uren in hun antwoord op de vraag wanneer het mes moet worden vervangen — in deze fabriek het verschil tussen een rustige week en een afgekeurde batch.

Welke lijn is beter?

Zo gesteld heeft die vraag geen antwoord. Geen moeilijk antwoord — helemaal geen antwoord. "Beter" is geen eigenschap van een lijn zoals haar helling dat is; het is een eigenschap van een lijn samen met een regel om lijnen te scoren, en totdat iemand die regel opschrijft, valt er niets te berekenen. Dit hoofdstuk neemt die zin serieus, en eindigt met de ontdekking dat de meest gebruikte regel in machine learning geen conventie is, maar het gevolg van een claim over de wereld — een claim die je kunt testen, en die soms onwaar is.

Eén bekentenis vóór de eerste regel code. Deze twintig metingen komen niet uit een echte fabriek: ik heb ze gegenereerd uit een lijn die ik koos, width=20.00+0.30h\text{width} = 20.00 + 0.30 \cdot h, plus willekeurige ruis met een spreiding van ongeveer een tiende millimeter. Dat doet ertoe, omdat alles hieronder gaat over de vraag of een methode een waarheid terugvindt, en de enige manier om dat te controleren is de waarheid vooraf kennen. Dus: 0,30 millimeter per uur is het antwoord achter in het boek. Je mag het niet gebruiken, alleen ertegen controleren.

Hier zijn de metingen en de drie lijnen, op drie manieren gescoord: kwadratische fout, waar iedereen naar grijpt; absolute fout, waar een statisticus misschien voor kiest; en grootste fout, waar de machinist voor zou kiezen, omdat de inspecteur niets geeft om je gemiddelde — hij keurt dat ene onderdeel af dat buiten tolerantie valt.

NumPy verschijnt hier, één hoofdstuk na de pure-Python perceptron, om één reden: aan het einde van dit hoofdstuk evalueren we vierhonderdduizend kandidaat-lijnen tegen elk twintig metingen, en een Python-loop is daarvoor het verkeerde gereedschap. Het is ook de notatie waarin elke hieronder geciteerde bron is geschreven.

loss.pyPYTHON
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}")               

De grootheid in de gemarkeerde regels is het residu: wat de lijn zei min wat de schuifmaat zei, één getal per meting. Elke scoreregel in dit hoofdstuk, en elke verliesfunctie in de achtentwintig hoofdstukken erna, is een manier om een lijst residuen samen te persen tot één enkel getal. Ze verschillen alleen in hoe ze persen.

TEXT
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.320

Lees de kolommen, niet de rijen. Kwadratische fout zegt B, absolute fout zegt A, grootste fout zegt C: drie regels, drie winnaars, op dezelfde twintig punten.

Ik heb deze drie lijnen zo gekozen dat ze het oneens zouden zijn, en dat moet ik gewoon zeggen. Het punt is hoe makkelijk dat was — een paar minuten zoeken over redelijk ogende intercepts en hellingen levert honderden van zulke drietallen op. De rangorde is een eigenschap van de regel die je kiest, geen feit over de lijnen, dus de regel is geen implementatiedetail: hij is de definitie van het probleem. Dat roept de vraag op waarvoor dit hoofdstuk bestaat: op welke gronden kies je hem?

Eerst iets kleiners, want er zijn niet drie lijnen maar oneindig veel. Neem voorlopig kwadratische fout, omdat iedereen dat doet, en verklein het probleem tot één getal met de truc die de perceptron elfduizend epochs bespaarde in Hoofdstuk 1: trek van beide kolommen het gemiddelde af. Zodra de puntenwolk rond de oorsprong gecentreerd is, loopt de beste lijn onder kwadratische fout precies door de oorsprong — dus de intercept ligt vast en alleen de helling moet nog worden gekozen.

loss.py (continued)PYTHON
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}")
TEXT
601 candidates -> theta=0.293 mse=0.010115

Zeshonderdeen kandidaat-hellingen, één winnaar: 0,293 millimeter per uur tegenover een waarheid van 0,300. Twintig rumoerige metingen en een for-loop kwamen binnen een honderdste millimeter per uur — tweeënhalve procent.

Het interessante is niet de winnaar, maar de vorm van de zoekruimte. Print de hele curve, gedraaid zodat de loss van links naar rechts loopt:

loss.py (continued)PYTHON
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}")
TEXT
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

Dat is een vallei, van opzij gezien. Ze heeft één bodem, de wanden lopen aan beide kanten soepel omhoog, en — dit is wat de trap van Hoofdstuk 1 niet kon bieden — op elk punt erop bestaat er een goed gedefinieerde richting van "omlaag". Onthoud die vorm. Hoofdstuk 3 gaat volledig over eraf lopen zonder alle zeshonderdeen punten te bezoeken, en over wat er verandert wanneer een vallei meer dan één bodem heeft.

We hebben een vallei omdat we kwadrateerden. Absolute fout zou onderaan een knik hebben gegeven; grootste fout zou vlakke stukken hebben gegeven waar het verschuiven van de lijn helemaal niets verandert. Kwadrateren is onmiskenbaar handig — en handigheid is grofweg de reden die de meeste cursussen geven, in vier vermommingen: het maakt fouten positief (absolute waarde doet dat ook); het bestraft grote fouten zwaarder (waarom zou dat moeten?); het is differentieerbaar (de vierde macht is dat ook); het is wat iedereen gebruikt (dat klopt, en dat is geen argument).

Dit is de eerlijke positie. Kwadratische fout selecteerde lijn B en absolute fout selecteerde lijn A. Eén daarvan is juist voor deze fabriek en de andere is fout, en niets van wat tot nu toe is gezegd kan je vertellen welke. Om de regel te kiezen moet je iets weten over hoe de metingen van de lijn zijn gaan verschillen, en dat is een vraag over de wereld, niet over wiskunde. Om die te beantwoorden heb je één klein stuk gereedschap nodig.

Dit is de claim die "welke lijn is beter" verandert in een vraag met een antwoord.

Neem aan dat de breedte van een onderdeel de lijn plus een willekeurige fout is, en neem aan dat die fout uit een Gaussische verdeling komt — de klokkromme — met gemiddelde nul en standaardafwijking σ\sigma:

yi=θxi+εi,εiN(0,σ2)y_i = \theta x_i + \varepsilon_i, \qquad \varepsilon_i \sim \mathcal{N}(0, \sigma^2)

De dichtheid van de Gaussische verdeling is

p(ε)=1σ2πexp ⁣(ε22σ2)p(\varepsilon) = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{\varepsilon^2}{2\sigma^2}\right)

Doe nu iets wat de perceptron niet kon. Voor een gegeven kandidaat-helling θ\theta heeft elke meting een residu, en de formule hierboven zet dat residu om in een getal: hoe plausibel is een fout van precies die grootte, als deze helling de waarheid is? Een meting op de lijn krijgt een groot getal, een meting een halve millimeter ernaast een klein getal.

De metingen zijn onafhankelijk — de schuifmaat onthoudt het vorige onderdeel niet — dus de productregel zegt dat de plausibiliteit van het hele notitieboek het product is van de afzonderlijke dichtheden. Dat product is de likelihood van θ\theta.1 Let op de richting, want dat is waar de regel van Bayes over gaat: de data staat vast en is bekend, en de parameter varieert. Dit is niet "de kans op de helling". Het is de kans die het model toekent aan de data die je daadwerkelijk kreeg, gelezen als functie van de helling.

likelihood.pyPYTHON
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}")
TEXT
theta=0.25   likelihood = 521.952
theta=0.293   likelihood = 2.42028e+07
theta=0.35   likelihood = 0.190312

Een helling van 0,293 maakt dit notitieboek zesenveertigduizend keer plausibeler dan 0,25, en honderzevenentwintig miljoen keer plausibeler dan 0,35. Maximum likelihood is het principe dat je de parameter kiest die wat je daadwerkelijk hebt waargenomen zo weinig verrassend mogelijk maakt. Het is geen stelling maar een voorstel over wat "best" zou moeten betekenen — een voorstel met inhoud, omdat het je dwingt je aanname over de ruis te formuleren voordat je iets mag scoren.

Draai dezelfde drie regels code op een maand aan diensten in plaats van één, en de methode valt om.

likelihood.py (continued)PYTHON
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)
TEXT
RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308

Tweeduizend vermenigvuldigingen en het antwoord is inf. Verander één constante — een slordigere schuifmaat, zodat de dichtheden kleiner dan 1 uitvallen in plaats van groter — en dezelfde code geeft 0.0 terug. Beide antwoorden zijn fout, in tegengestelde richtingen, geen van beide gooit een exception die je kunt vangen, en de tweede print niet eens een waarschuwing.

Er is niets mis met de wiskunde. De likelihood bij die instellingen is een perfect goed gedefinieerd eindig getal: de natuurlijke logaritme ervan is 1400,91, dus het getal zelf is ongeveer 1060810^{608}. Het probleem is dat je computer dat getal niet heeft, en het is de moeite waard om precies te begrijpen welke getallen hij wél heeft, want dit is niet de laatste keer dat hij de uitkomst bepaalt.

De oplossing voor het exploderende product is de gebruikelijke: neem logaritmen. De logaritme zet producten om in sommen, is strikt stijgend en kan dus de plek van het maximum niet verplaatsen, en een som van tweeduizend gematigde getallen is iets wat float64 zonder klagen verwerkt. Volgens conventie nemen we de negatieve log-likelihood, zodat beter kleiner betekent. Vervang nu de Gaussische dichtheid en kijk wat er gebeurt.

  1. Begin met het product. De likelihood is L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i), met pp de Gaussische dichtheid hierboven.

  2. Neem min de log. Het product wordt een som, en de exponentiële term in de dichtheid valt rechtstreeks weg tegen de logaritme:

logL(θ)=N2log ⁣(2πσ2)+12σ2i=1N(yiθxi)2-\log \mathcal{L}(\theta) = \frac{N}{2}\log\!\left(2\pi\sigma^2\right) + \frac{1}{2\sigma^2}\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2
  1. Gooi alles weg dat θ\theta niet bevat. De eerste term is een constante. De 1/2σ21/2\sigma^2 vóór de som is een positieve constante, en het schalen van een functie met een positieve constante kan niet verplaatsen waar haar minimum ligt. Wat overblijft is
i=1N(yiθxi)2\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2

en dat is de som van gekwadrateerde residuen — het ding waarmee we het hoofdstuk begonnen omdat het het eerste is waar iedereen aan denkt.

Dat is het resultaat waarvoor dit hoofdstuk bestaat, en het verdient het zonder voorbehoud te worden uitgesproken: kwadratische fout is geen conventie. Het is de negatieve log-likelihood van een Gaussische verdeling, met de constanten verwijderd. Kwadratische fout minimaliseren is precies dezelfde handeling als beweren dat je fouten Gaussisch zijn en vragen welke parameter je data het minst verrassend maakt. Je deed die bewering al die tijd al; alleen vertelde niemand het je.

De equivalentie is controleerbaar, dus controleer haar: scan dezelfde zeshonderdeen hellingen met de volledige negatieve log-likelihood, constanten en al, en met gewone kwadratische fout.

likelihood.py (continued)PYTHON
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())
TEXT
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: True

Andere getallen op de verticale as, en één ervan is negatief, wat een som van kwadraten nooit is: een negatieve log-likelihood kan onder nul komen, omdat een dichtheid groter dan 1 kan zijn. Dezelfde bodem van dezelfde vallei, tot op het laatste roosterpunt.

Volledige afleiding tonen

Welke weggooi-acties zijn precies veilig? Dezelfde manoeuvre verschijnt in elk hoofdstuk dat een loss afleidt, en ze is niet altijd onschuldig.

Een additieve constante laten vallen is veilig wanneer die niet afhangt van de parameter die je optimaliseert, en een positieve multiplicatieve constante laten vallen is veilig omdat argminθcf(θ)=argminθf(θ)\arg\min_\theta c\,f(\theta) = \arg\min_\theta f(\theta) voor elke c>0c > 0. Beide falen zodra σ\sigma ook wordt gefit: dan is N2log(2πσ2)\frac{N}{2}\log(2\pi\sigma^2) helemaal geen constante, maar de term die voorkomt dat het model σ=0\sigma = 0 en oneindige plausibiliteit claimt. Dat is precies de volgende sectie.

Ze falen opnieuw op een andere manier in Hoofdstuk 3: een multiplicatieve constante verplaatst het minimum niet, maar schaalt wel de gradient, en de gradient wordt vermenigvuldigd met de learning rate. Delen door NN om de gemiddelde kwadratische fout te krijgen in plaats van de som is onzichtbaar voor het antwoord en heel zichtbaar voor de training run — met de som verdubbelt het verdubbelen van je batchgrootte elke stap die je zet.

We hebben σ\sigma per fiat op 0,12 gezet, en niemand in de fabriek kent de spreiding van de fout van hun schuifmaat. Behandel die als een tweede onbekende en laat maximum likelihood er ook over beslissen. Hier komt de constante term die we net weggooiden terug, omdat die het enige is tussen het model en een claim van perfecte precisie.

likelihood.py (continued)PYTHON
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))
TEXT
best sigma on the grid       : 0.1006
sqrt(mean squared residual)  : 0.1006

De twee komen tot vier decimalen overeen, en niet per ongeluk: differentieer die uitdrukking en stel haar gelijk aan nul, en je krijgt exact σ^2=1Nri2\hat{\sigma}^2 = \frac{1}{N}\sum r_i^2. Dus de gemiddelde kwadratische fout is niet alleen als een variantie. Onder dit model is hij de maximum-likelihoodschatting van de variantie van de ruis — het getal dat je al die tijd hebt geminimaliseerd was een schatting van hoe rumoerig je sensor is.

Eén nuance, goedkoop om te noemen en duur om later opnieuw te ontdekken: die schatting is naar beneden vertekend, omdat de residuen zijn gemeten tegen een fit die zelf is gekozen om ze klein te maken. Simuleer het — tweehonderdduizend notitieboeken met elk twintig metingen, getrokken uit een verdeling waarvan de ware variantie exact 1 is, waarbij de ene parameter van de fit uit de metingen zelf wordt geschat. De som van kwadraten delen door NN geeft een gemiddelde van 0,9501; delen door N1N-1 geeft 1,0001; en (N1)/N(N-1)/N is precies 0,95. Elke parameter die je fit kost één vrijheidsgraad, en dit is de kleinste zichtbare instantie van een veel groter probleem: een model ziet er altijd beter uit op de data waarop het is gefit. Hoofdstuk 4 maakt daar de discipline van het achterhouden van data van, en Hoofdstuk 6 geeft het effect zijn naam.

Als kwadratische fout stelt dat de ruis Gaussisch is, is de volgende vraag wat er gebeurt wanneer die stelling onwaar is. Niet een beetje onwaar — onwaar zoals echte metingen onwaar zijn.

Op de werkvloer zijn de meeste schuifmaatmetingen goed tot op een tiende millimeter, en één of twee keer per dienst komt er een spaantje onder de bek en zit de meting er meerdere millimeters naast. Zulke fouten zijn heavy-tailed: meestal klein, af en toe enorm, en enorm veel vaker dan een klokkromme toelaat. De Cauchy-verdeling is het standaard nette model voor dat gedrag, en haar dichtheid is even eenvoudig als die van de Gaussische verdeling:

p(ε)=1πs(1+(ε/s)2)p(\varepsilon) = \frac{1}{\pi s \left(1 + (\varepsilon/s)^2\right)}

Het verschil zit in de staart: de Gaussische verdeling valt af als eε2e^{-\varepsilon^2}, bruut snel, en de Cauchy als 1/ε21/\varepsilon^2, nauwelijks. Het gevolg is makkelijker te zien dan te zeggen:

PYTHON
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}")
TEXT
      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.10

De steekproefvariantie van de Gaussische verdeling stabiliseert op 0,0144, wat 0.1220.12^2 is, en blijft daar. Die van de Cauchy klimt, en blijft klimmen zolang je samplet, omdat er niets is waar hij naartoe kan convergeren: de Cauchy-verdeling heeft geen variantie, en ook geen gemiddelde. Kwadratische fout, waarvan het hele werk bestaat uit het minimaliseren van een gemiddelde van kwadraten, wordt gevraagd om een grootheid die niet bestaat.

Dus hier is één dienst waarin de schuifmaat werd misleid. Dezelfde twintig uur, hetzelfde mes, dezelfde drift van 0,30 millimeter per uur — alleen is de ruis nu Cauchy. Fit twee keer: één keer door gekwadrateerde residuen te minimaliseren, één keer door de negatieve log-likelihood te minimaliseren van de ruis die de data daadwerkelijk heeft gegenereerd. De centreertruc helpt hier niet — die pint de intercept alleen vast voor kwadratische fout — dus beide fits gebeuren brute force over een rooster van intercepts en hellingen, omdat we nog steeds geen manier hebben om de bodem van een vallei te vinden behalve door haar te bezoeken.

swarf.pyPYTHON
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")

De twee gemarkeerde regels zijn het hele verschil tussen de fits. Neem de log van de Cauchy-dichtheid, laat de constanten precies zoals eerder vallen, en log(1+(r/s)2)\sum \log\left(1 + (r/s)^2\right) is wat overblijft. Zelfde recept, andere claim over de ruis.

TEXT
     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 evaluated

Least squares rapporteert een drift van 0,108 millimeter per uur, ongeveer een derde van de echte snelheid, en concludeert dat het mes goed is tot uur 19,6. Het ware antwoord is uur 11,7. Als de fabriek naar die fit handelt, laat ze de pers acht extra uren onderdelen maken die buiten tolerantie vallen, op gezag van de meest standaard verliesfunctie in het vak. De Cauchy-fit, met dezelfde twintig metingen, hetzelfde rooster en één regel verschil in de code, komt uit op uur 11,8.

Twee bezwaren verdienen antwoord, omdat beide het eerste zijn wat een goede engineer zegt.

De outlier is duidelijk — verwijder hem gewoon. Dat kan, en het helpt, en het is niet genoeg. De ene slechtste meting verwijderen verschuift de least-squareshelling van 0,108 naar 0,239, wat de meswissel nog steeds op uur 13,1 zet, anderhalf uur te laat; de slechtste verwijderen, opnieuw fitten en verwijderen wat nu het slechtst is brengt je op 0,286 — en merk op dat dit al een procedure is, geen observatie: verwijder in plaats daarvan de twee grootste residuen van de oorspronkelijke fit en je komt uit op 0,223. Maar je hebt nu oordelen geveld die je niet kunt opschrijven of verdedigen, en de regel automatiseren redt het niet: drop-the-largest-residual-then-refit, gedraaid over duizend gesimuleerde diensten, heeft een mediane hellingsfout van 0,0177 tegenover 0,0100 voor de likelihood-fit, en zit in 14,7% van de diensten meer dan 0,05 fout tegenover 1,3%. Verwijderen is een pleister op een verkeerde aanname. De likelihood heeft geen pleister nodig, omdat die nooit aannam dat de outlier onmogelijk was.

Je hebt een gelukkige dataset gekozen. Dat bezwaar is precies juist, en daarom simuleert het laatste experiment duizend onafhankelijke diensten en fit het beide manieren op elk ervan opnieuw.

swarf.py (continued)PYTHON
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")
TEXT
     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 shifts

Mediaan, niet gemiddelde, om dezelfde reden als al het andere in deze sectie: de least-squaresfouten worden door een Cauchy gedreven, dus hun gemiddelde is geen stabiel ding om te rapporteren. Least squares zit in twee van de vijf diensten ernstig fout; de likelihood-fit in één van de zevenenzeventig, en zijn ergste misser over duizend diensten is minder dan een vijfde van de ergste misser van least squares.

Niets hiervan maakt kwadratische fout slecht. Het maakt haar specifiek, en de rekenkunde zegt precies waarom. Neem een residu van 0,1 mm en een van 7 mm. Gekwadrateerd draagt de slechte meting 4.900 keer zoveel bij aan het totaal als de goede, dus de lijn wordt er met haar hele gewicht naartoe getrokken; onder de Cauchy-log-likelihood dragen dezelfde twee residuen 0,527 en 8,133 bij, een verhouding van 15,4. De slechte meting telt nog steeds mee, ze mag alleen niet beslissen. Dit is het begin van robuuste statistiek, waar Hubers loss uit 1964 het verschil splitst door zich kwadratisch te gedragen voor kleine residuen en lineair voor grote,7 en waar Tukey al had laten zien hoe weinig contaminatie nodig is om de steekproefvariantie een slechter gereedschap te maken dan de gemiddelde absolute afwijking.8

Nog één historische noot, te goed om weg te laten. Least squares werd als eerste gepubliceerd, door Legendre in 1805, als een handig algebraïsch hulpmiddel zonder andere rechtvaardiging dan dat het werkte.9 Vier jaar later draaide Gauss het argument om: hij nam als gegeven dat het rekenkundig gemiddelde de juiste manier is om herhaalde metingen te combineren, vroeg welke foutverdeling het gemiddelde tot de meest waarschijnlijke waarde maakt, en liet zien dat er in essentie maar één is — degene die nu naar hem is genoemd.10 De afleiding in dit hoofdstuk is de zijne, ze is meer dan twee eeuwen oud, en ze is nog steeds het deel dat de meeste cursussen weglaten.

Wat je nu kunt zeggen, en wat je nog niet kunt doen

Link naar de sectie: Wat je nu kunt zeggen, en wat je nog niet kunt doen

Verdiend. Een verliesfunctie is een scoreregel, en de rangorde die zij produceert is een eigenschap van de regel, niet van de kandidaten. Elke loss in deze cursus is de negatieve log-likelihood van een aanname over de ruis, met de constanten weggegooid — Gaussisch geeft hier kwadratische fout, Bernoulli geeft cross-entropy in Hoofdstuk 4, en een categorische verdeling over een vocabulaire geeft de next-token loss in Hoofdstuk 8. Het recept verandert nooit: formuleer de ruis, schrijf de likelihood, neem min de log. En wanneer de aanname fout is, is het model niet alleen onnauwkeurig, maar fout in een richting die je kunt voorspellen.

Nog ontbrekend. We vonden de bodem van de vallei door elk punt erin te bezoeken. Dat werkte voor één parameter en zeshonderd kandidaten, en overleefde twee parameters met 401.301 kandidaten in een vijfde seconde. Drie parameters op dezelfde resolutie zijn 201.051.801 kandidaten en passen niet meer in één array; een klein netwerk in Hoofdstuk 5 heeft duizenden parameters, en de modellen waar Hoofdstuk 10 een prijs op zet hebben miljarden. Brute force is hier niet traag, maar rekenkundig onmogelijk, en niets in dit hoofdstuk suggereert een alternatief.

Kijk echter terug naar de vallei. Staand op θ=0.20\theta = 0.20 met een loss van 0,0822 is de richting van "omlaag" geen mysterie — je kunt het op de pagina zien, de curve helt naar rechts omlaag. Als je de verliesfunctie zou kunnen vragen welke kant ze helt op het punt waar je staat, zonder haar ergens anders te evalueren, zou je een stap die kant op kunnen zetten, opnieuw vragen, en herhalen tot de grond vlak is.

Die vraag heeft een naam. De helling van een functie in een punt is haar afgeleide, en voor een functie van veel parameters is de verzameling hellingen in alle richtingen tegelijk de gradient. Hoofdstuk 1 kon er geen gebruiken, omdat de fout van de perceptron een trap was zonder helling om naar te vragen. Dit hoofdstuk heeft iets beters gebouwd: een loss die overal glad is en die voortkwam uit een expliciete aanname in plaats van een voorkeur.

Dus de vraag voor Hoofdstuk 3 is niet langer of er een helling bestaat. Het is hoe je die berekent, waarom bewegen tegen die helling in omlaag gaat in plaats van omhoog — een teken dat bijna elke cursus je op geloof vraagt te accepteren — en hoe ver je moet stappen voordat je opnieuw vraagt, wat uiteindelijk het ene getal blijkt dat bepaalt of een training run convergeert, eindeloos rond het antwoord oscilleert, of naar oneindig wegloopt.


Ook de moeite waard om naast dit hoofdstuk te lezen: Prince, Understanding Deep Learning §5.1–5.2 en Appendix C, dat elke loss in het boek opbouwt vanuit maximum likelihood in de volgorde die hier wordt gebruikt; Goodfellow, Bengio en Courville, Deep Learning §3.1–3.11 en §5.5, waarvan de maximum-likelihoodsectie ook de KL-divergentie afleidt die Hoofdstuk 4 nodig heeft; Murphy, Probabilistic Machine Learning: An Introduction hoofdstuk 2 en §4.2, over wat maximum likelihood wel en niet garandeert; Deisenroth, Faisal en Ong, Mathematics for Machine Learning §6.1–6.4 voor de somregel, productregel en de regel van Bayes netjes uitgewerkt; Tom Mitchells korte CMU-notitie Estimating Probabilities: MLE and MAP (2016); en §22.7 van Dive into Deep Learning, dat hetzelfde resultaat bereikt in uitvoerbare code.

  1. Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, pp. 309–368 (1922). Waar likelihood als algemene methode wordt uiteengezet, samen met "parameter", "statistic", sufficiency en efficiency. De naamgeving zelf, en de scheiding van probability, is een jaar ouder: 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.

  2. IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. Definieert binary32 en binary16, en de afrondingsregels waardoor het sommatie-experiment uitkomt zoals het uitkomt.

  3. Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). De parameters van het formaat, en het pleidooi om mantissebits in te ruilen voor exponentbits.

  4. Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. Loss scaling, en de gemeten gradientgroottes die het in float16 noodzakelijk maken.

  5. Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), pp. 5–48 (1991). Nog steeds de beste afzonderlijke uitleg van waarom de twee optelvolgordes verschillen.

  6. Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), p. 40 (1965). Gecompenseerde sommatie in een halve pagina.

  7. Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), pp. 73–101 (1964). De loss die kwadratisch is rond nul en lineair in de staarten, afgeleid in plaats van aan elkaar geplakt.

  8. Tukey, J. W. A survey of sampling from contaminated distributions, in Contributions to Probability and Statistics (Stanford University Press, 1960), pp. 448–485.

  9. Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805), appendix Sur la méthode des moindres quarrés. De eerste publicatie van least squares, als rekenhulpmiddel.

  10. Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809), Book II, §§175–179. Het argument van het rekenkundig gemiddelde naar de normale foutwet, en vandaar naar least squares.

Klaar om LIA te laten kiezen?

Bouw met elk AI-model op één plek — begin vandaag nog gratis.