Var en förlustfunktion kommer ifrån: likelihood, inte konvention
Tre linjer över samma tjugo mätningar och tre regler som utser olika vinnare. Kvadratfel är ett val.
På den här sidan
Bladet som skär delarna slits ned. Under ett tiotimmarsskift tappar det så mycket skärpa att delarna kommer av bandet en bråkdel av en millimeter bredare än när de började, och när de passerar 23,5 millimeter underkänner kontrollen dem. Ingen på fabriken vet när det händer. Det de har är ett skjutmått, ett anteckningsblock och tjugo avläsningar från förra tisdagen: timmarna sedan bladet byttes och delens bredd uppmätt i det ögonblicket.
Någon drar en linje genom punkterna. Någon annan drar en lite annan. En tredje person drar en tredje. Alla tre ser rimliga ut på papperet, och de skiljer sig med flera timmar i frågan om när bladet ska bytas — på den här fabriken skillnaden mellan en lugn vecka och en kasserad batch.
Vilken linje är bättre?
Som frågan är ställd har den inget svar. Inte ett svårt svar — inget svar alls. ”Bättre” är inte en egenskap hos en linje på samma sätt som dess lutning är det; det är en egenskap hos en linje tillsammans med en regel för att poängsätta linjer, och innan någon skriver ned regeln finns inget att beräkna. Det här kapitlet tar den meningen på allvar och slutar med upptäckten att den vanligaste regeln i machine learning inte är en konvention utan följden av ett påstående om världen — ett påstående du kan testa, och ett som ibland är falskt.
En bekännelse före den första kodraden. De här tjugo avläsningarna kommer inte från en riktig fabrik: jag genererade dem från en linje jag valde, , plus slumpmässigt brus med en spridning på ungefär en tiondels millimeter. Det spelar roll, eftersom allt nedan handlar om huruvida en metod återfinner en sanning, och det enda sättet att kontrollera det är att känna sanningen i förväg. Alltså: 0,30 millimeter per timme är facit längst bak i boken. Du får inte använda det, bara kontrollera mot det.
Tre regler, tre vinnare
Länk till avsnittet: Tre regler, tre vinnareHär är avläsningarna och de tre linjerna, poängsatta på tre sätt: kvadratfel, som alla sträcker sig efter; absolutfel, som en statistiker kanske skulle välja; och största fel, som maskinisten skulle välja, eftersom inspektören inte bryr sig om ditt genomsnitt — han underkänner den enda del som ligger utanför toleransen.
NumPy kommer in här, ett kapitel efter den rena Python-perceptronen, av en enda anledning: i slutet av det här kapitlet utvärderar vi fyrahundratusen kandidatlinjer mot tjugo avläsningar vardera, och en Python-loop är fel verktyg för det. Det är också notationens språk i varje källa som citeras nedan.
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}") Storheten i de markerade raderna är residualen: vad linjen sade minus vad skjutmåttet sade, ett tal per avläsning. Varje poängregel i det här kapitlet, och varje förlustfunktion i de tjugoåtta kapitlen efter det, är något sätt att pressa ned en lista av residualer till ett enda tal. De skiljer sig bara i hur de pressar.
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.320Läs kolumnerna, inte raderna. Kvadratfel säger B, absolutfel säger A, största fel säger C: tre regler, tre vinnare, på samma tjugo punkter.
Jag valde de här tre linjerna så att de skulle vara oense, och det bör jag säga rakt ut. Poängen är hur lätt det var — några minuters sökande bland intercept och lutningar som ser vettiga ut ger hundratals sådana tripplar. Rangordningen är en egenskap hos regeln du valde, inte ett faktum om linjerna, så regeln är inte en implementationdetalj: den är definitionen av problemet. Vilket väcker frågan som det här kapitlet finns för att besvara: på vilka grunder väljer du den?
En parameter och en dal
Länk till avsnittet: En parameter och en dalFörst en mindre sak, eftersom det inte finns tre linjer utan oändligt många. Ta kvadratfel tills vidare, eftersom det är vad alla tar, och krymp problemet till ett enda tal med tricket som sparade perceptronen elvatusen epoker i kapitel 1: subtrahera medelvärdet från båda kolumnerna. När punktmolnet väl är centrerat kring origo passerar den bästa linjen under kvadratfel exakt genom origo — så interceptet är avgjort och bara lutningen återstår att välja.
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.010115Sexhundraen kandidatlutningar, en vinnare: 0,293 millimeter per timme mot en sanning på 0,300. Tjugo brusiga avläsningar och en for-loop kom inom en hundradels millimeter per timme — två och en tredjedels procent.
Det intressanta är inte vinnaren utan sökningens form. Skriv ut hela kurvan, roterad så att förlusten löper från vänster till höger:
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.7928Det där är en dal, sedd från sidan. Den har en enda botten, väggarna stiger mjukt på båda sidor, och — det här är delen som trappan i kapitel 1 inte kunde erbjuda — i varje enskild punkt på den finns en väldefinierad riktning för ”nedför”. Kom ihåg den formen. Kapitel 3 handlar helt om att gå nedför den utan att besöka alla sexhundraen punkter, och om vad som ändras när en dal har mer än en botten.
Så varför kvadrera?
Länk till avsnittet: Så varför kvadrera?Vi har en dal eftersom vi kvadrerade. Absolutfel hade gett den ett veck i botten; största fel hade gett den platta sträckor där det inte förändrar något alls att flytta linjen. Kvadrering är onekligen bekvämt — och bekvämlighet är ungefär skälet de flesta kurser ger, uppklätt på fyra sätt: det gör fel positiva (det gör absolutbelopp också); det straffar stora fel hårdare (varför ska det göra det?); det är deriverbart (det är fjärde potensen också); det är vad alla använder (det är det, och det är inget argument).
Här är den ärliga positionen. Kvadratfel valde linje B och absolutfel valde linje A. En av dem är rätt för den här fabriken och den andra är fel, och inget som sagts hittills kan säga vilken. För att välja regeln behöver du veta något om hur avläsningarna kom att skilja sig från linjen, och det är en fråga om världen, inte om matematik. För att besvara den behövs en liten bit maskineri.
En linjes likelihood
Länk till avsnittet: En linjes likelihoodHär är påståendet som förvandlar ”vilken linje är bättre” till en fråga med ett svar.
Anta att bredden på en del är linjen plus ett slumpmässigt fel, och anta att felet dras från en Gaussfördelning — klockkurvan — med medelvärde noll och standardavvikelse :
Gaussfördelningens täthet är
Gör nu något perceptronen inte kunde. För en given kandidatlutning har varje avläsning en residual, och formeln ovan gör om residualen till ett tal: hur plausibelt är ett fel av exakt den storleken, om den här lutningen är sanningen? En avläsning på linjen får ett stort tal, en avläsning en halv millimeter bort ett litet.
Avläsningarna är oberoende — skjutmåttet minns inte den förra delen — så produktregeln säger att plausibiliteten för hela anteckningsboken är produkten av de enskilda tätheterna. Den produkten är likelihood för .1 Notera riktningen, eftersom det är den riktningen Bayes regel handlar om: data är fast och känd, och det är parametern som varierar. Det här är inte ”sannolikheten för lutningen”. Det är sannolikheten som modellen tilldelar de data du faktiskt fick, läst som en funktion av lutningen.
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.190312En lutning på 0,293 gör den här anteckningsboken fyrtiosextusen gånger mer plausibel än 0,25, och hundratjugosju miljoner gånger mer plausibel än 0,35. Maximum likelihood är principen att du väljer den parameter som gör det du faktiskt observerade så föga överraskande som möjligt. Det är inte ett teorem utan ett förslag om vad ”bäst” bör betyda — ett förslag med innehåll, eftersom det tvingar dig att ange ditt antagande om bruset innan du får poängsätta något.
Produkten går sönder
Länk till avsnittet: Produkten går sönderKör samma tre kodrader på en månads skift i stället för ett, och metoden faller omkull.
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+308Tvåtusen multiplikationer och svaret är inf. Ändra en konstant — ett slarvigare skjutmått, så att tätheterna blir mindre än 1 i stället för större — och samma kod returnerar 0.0. Båda svaren är fel, åt motsatta håll, inget kastar ett undantag du kan fånga, och det andra skriver inte ens ut en varning.
Det är inget fel på matematiken. Likelihood vid de inställningarna är ett helt väldefinierat ändligt tal: dess naturliga logaritm är 1400,91, så talet självt är ungefär . Problemet är att din dator inte har det talet, och det är värt att förstå exakt vilka tal den faktiskt har, eftersom det här inte är sista gången den avgör utfallet.
Var kvadraten kommer ifrån
Länk till avsnittet: Var kvadraten kommer ifrånLösningen på den exploderande produkten är den vanliga: ta logaritmer. Logaritmen gör produkter till summor, den är strikt växande så den kan inte flytta maximumets läge, och en summa av tvåtusen måttliga tal är något float64 hanterar utan klagomål. Av konvention tar vi den negativa log-likelihood, så att bättre betyder mindre. Sätt nu in Gaussfördelningens täthet och se vad som händer.
-
Börja med produkten. Likelihood är , med som Gauss-tätheten ovan.
-
Ta minus logaritmen. Produkten blir en summa, och exponentialtermen i tätheten tar ut logaritmen direkt:
- Kasta bort allt som inte innehåller . Den första termen är en konstant. framför summan är en positiv konstant, och att skala en funktion med en positiv konstant kan inte flytta var dess minimum ligger. Det som återstår är
vilket är summan av kvadrerade residualer — saken vi började kapitlet med eftersom det var det första någon tänker på.
Det är resultatet kapitlet finns till för, och det förtjänar att sägas utan gardering: kvadratfel är inte en konvention. Det är negativ log-likelihood för en Gaussfördelning, med konstanterna borttagna. Att minimera kvadratfel är exakt samma handling som att hävda att dina fel är Gaussiska och fråga vilken parameter som gör dina data minst överraskande. Du gjorde det påståendet hela tiden; du fick bara inte veta det.
Ekvivalensen går att kontrollera, så kontrollera den: skanna samma sexhundraen lutningar med full negativ log-likelihood, konstanter och allt, och med vanligt kvadratfel.
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: TrueOlika tal på den vertikala axeln, och ett av dem är negativt, vilket en kvadratsumma aldrig är: en negativ log-likelihood kan gå under noll, eftersom en täthet kan överstiga 1. Samma botten i samma dal, till sista gridpunkten.
Visa hela härledningen
Vilka bortkastanden är exakt säkra? Samma manöver dyker upp i varje kapitel som härleder en loss, och den är inte alltid oskyldig.
Att ta bort en additiv konstant är säkert när den inte beror på parametern du optimerar, och att ta bort en positiv multiplikativ konstant är säkert eftersom för alla . Båda misslyckas i samma ögonblick som också anpassas: då är inte alls en konstant, utan termen som hindrar modellen från att hävda och oändlig plausibilitet. Det är exakt nästa avsnitt.
De misslyckas på ett annat sätt igen i kapitel 3: en multiplikativ konstant flyttar inte minimumet, men den skalar gradienten, och gradienten multipliceras med learning rate. Att dela med för att få medel-kvadratfel i stället för summa av kvadratfel är osynligt för svaret och högst synligt för träningskörningen — med summan fördubblas varje steg du tar när du fördubblar batchstorleken.
Sigma är inte gratis heller
Länk till avsnittet: Sigma är inte gratis hellerVi fixerade till 0,12 genom dekret, och ingen på fabriken känner spridningen i skjutmåttets fel. Behandla den som en andra okänd och låt maximum likelihood avgöra den också. Här kommer konstanttermen vi nyss kastade bort tillbaka, eftersom den är det enda som står mellan modellen och ett påstående om perfekt precision.
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.1006De två stämmer till fyra decimaler, och inte av en slump: deriverar man uttrycket och sätter det till noll får man exakt. Så medelkvadratfelet är inte bara som en varians. Under den här modellen är det maximum-likelihood-skattningen av brusets varians — talet du har minimerat hela tiden var en skattning av hur brusig din sensor är.
En hake, billig att formulera och dyr att återupptäcka senare: den skattningen är biased nedåt, eftersom residualerna mättes mot en anpassning som själv valdes för att göra dem små. Simulera det — tvåhundratusen anteckningsböcker med tjugo avläsningar vardera, dragna från en fördelning vars sanna varians är exakt 1, med den enda parametern i anpassningen skattad från avläsningarna själva. Att dela kvadratsumman med ger ett medel på 0,9501; att dela med ger 1,0001; och är 0,95 på pricken. Varje parameter du anpassar kostar en frihetsgrad, och det här är den minsta synliga instansen av ett mycket större problem: en modell ser alltid bättre ut på de data den anpassades till. Kapitel 4 gör det till disciplinen att hålla undan data, och kapitel 6 ger effekten dess namn.
En loss är ett påstående om bruset
Länk till avsnittet: En loss är ett påstående om brusetOm kvadratfel hävdar att bruset är Gaussiskt är nästa fråga vad som händer när påståendet är falskt. Inte lite falskt — falskt på det sätt som verkliga mätningar är falska.
På verkstadsgolvet är de flesta skjutmåttsavläsningar bra till en tiondels millimeter, och en eller två gånger per skift hamnar en spåna under käften och avläsningen hamnar flera millimeter fel. Sådana fel är heavy-tailed: små för det mesta, ibland enorma, och enorma mycket oftare än en klockkurva tillåter. Cauchyfördelningen är den vanliga rena modellen för det beteendet, och dess täthet är lika enkel som Gaussfördelningens:
Skillnaden är svansen: Gaussfördelningen faller som , brutalt snabbt, och Cauchy som , knappt alls. Konsekvensen är lättare att se än att säga:
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.10Gaussfördelningens stickprovsvarians stabiliserar sig på 0,0144, vilket är , och stannar där. Cauchys klättrar, och fortsätter klättra så länge du samplar, eftersom det inte finns något för den att konvergera till: Cauchyfördelningen har ingen varians, och inget medelvärde heller. Kvadratfel, vars hela affärsidé är att minimera ett genomsnitt av kvadrater, ombeds leverera en storhet som inte finns.
Så här är ett skift där skjutmåttet blev lurat. Samma tjugo timmar, samma blad, samma drift på 0,30 millimeter per timme — bara bruset är nu Cauchy. Anpassa det två gånger: en gång genom att minimera kvadrerade residualer, en gång genom att minimera negativ log-likelihood för bruset som faktiskt genererade data. Centreringstricket hjälper inte här — det låser interceptet bara för kvadratfel — så båda anpassningarna görs med brute force över ett grid av intercept och lutningar, eftersom vi fortfarande inte har något sätt att hitta botten av en dal annat än att besöka den.
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 två markerade raderna är hela skillnaden mellan anpassningarna. Ta logaritmen av Cauchytätheten, kasta bort konstanterna exakt som tidigare, och är det som blir kvar. Samma recept, annat påstående om bruset.
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 evaluatedMinsta kvadratmetoden rapporterar en drift på 0,108 millimeter i timmen, ungefär en tredjedel av den verkliga takten, och drar slutsatsen att bladet är bra till timme 19,6. Det sanna svaret är timme 11,7. Om fabriken agerar på den anpassningen kör den pressen i åtta extra timmar och tillverkar delar som ligger utanför toleransen, med stöd av fältets mest standardiserade loss. Cauchy-anpassningen, med samma tjugo avläsningar, samma grid och en enda rads skillnad i koden, landar på timme 11,8.
Två invändningar förtjänar svar, eftersom båda är det första en bra ingenjör säger.
Outliern är uppenbar — ta bara bort den. Det kan du göra, och det hjälper, och det räcker inte. Att ta bort den enskilt värsta avläsningen flyttar minsta-kvadrat-lutningen från 0,108 till 0,239, vilket fortfarande placerar bladbytet vid timme 13,1, en och en halv timme för sent; att ta bort den värsta, anpassa om och ta bort det som är värst nu tar dig till 0,286 — och notera att detta redan är en procedur, inte en observation: ta i stället bort de två största residualerna från den ursprungliga anpassningen och du landar på 0,223. Men du har nu gjort bedömningar du inte kan skriva ned eller försvara, och att automatisera regeln räddar den inte: ta-bort-största-residualen-och-anpassa-om, kört över tusen simulerade skift, har ett medianfel i lutning på 0,0177 mot likelihood-anpassningens 0,0100, och ligger mer än 0,05 fel i 14,7 % av skiften mot 1,3 %. Borttagning är en lapp ovanpå ett felaktigt antagande. Likelihood behöver ingen lapp, eftersom den aldrig antog att outliern var omöjlig.
Du valde ett tursamt dataset. Den invändningen är helt riktig, och därför simulerar det sista experimentet tusen oberoende skift och anpassar på båda sätten för varje.
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 shiftsMedian, inte medel, av samma skäl som allt annat i det här avsnittet: minsta-kvadrat-felen drivs av en Cauchy, så deras genomsnitt är inte något stabilt att rapportera. Minsta kvadratmetoden är kraftigt fel två skift av fem; likelihood-anpassningen är kraftigt fel ett skift av sjuttiosju, och dess värsta misslyckande över tusen skift är mindre än en femtedel av minsta kvadratmetodens värsta.
Inget av detta gör kvadratfel dåligt. Det gör det specifikt, och aritmetiken säger exakt varför. Ta en residual på 0,1 mm och en på 7 mm. Kvadrerad bidrar den dåliga avläsningen 4 900 gånger så mycket till totalen som den goda, så linjen dras kroppsligen mot den; under Cauchy-log-likelihood bidrar samma två residualer med 0,527 och 8,133, en kvot på 15,4. Den dåliga avläsningen räknas fortfarande, den får bara inte bestämma. Det här är början på robust statistik, där Hubers loss från 1964 delar skillnaden genom att bete sig kvadratiskt för små residualer och linjärt för stora,7 och där Tukey redan hade visat hur lite kontaminering som krävs för att stickprovsvariansen ska bli ett sämre verktyg än medianabsolutavvikelsen.8
En historisk not också, för bra för att utelämna. Minsta kvadratmetoden publicerades först, av Legendre 1805, som ett bekvämt algebraiskt verktyg utan annan motivering än att det fungerade.9 Fyra år senare körde Gauss argumentet baklänges: han tog för givet att det aritmetiska medelvärdet är rätt sätt att kombinera upprepade mätningar, frågade vilken felfördelning som gör medelvärdet till det mest sannolika värdet och visade att i princip bara en gör det — den som nu bär hans namn.10 Härledningen i det här kapitlet är hans, den är mer än två århundraden gammal, och den är fortfarande delen de flesta kurser utelämnar.
Vad du nu kan säga, och vad du fortfarande inte kan göra
Länk till avsnittet: Vad du nu kan säga, och vad du fortfarande inte kan göraIntjänat. En förlustfunktion är en poängregel, och rangordningen den producerar är en egenskap hos regeln, inte hos kandidaterna. Varje loss i den här kursen är negativ log-likelihood för något antagande om bruset, med konstanterna bortkastade — Gaussfördelningen ger kvadratfel här, Bernoulli ger cross-entropy i kapitel 4, och en kategorisk fördelning över ett vokabulär ger next-token-loss i kapitel 8. Receptet ändras aldrig: ange bruset, skriv likelihood, ta minus logaritmen. Och när antagandet är fel är modellen inte bara oprecis, den är fel i en riktning du kan förutsäga.
Saknas fortfarande. Vi hittade botten av dalen genom att besöka varje punkt i den. Det fungerade för en parameter och sexhundra kandidater, och överlevde två parametrar med 401 301 kandidater på en femtedels sekund. Tre parametrar med samma upplösning är 201 051 801 kandidater och ryms inte längre i en array; ett litet nätverk i kapitel 5 har tusentals parametrar, och modellerna som kapitel 10 sätter pris på har miljarder. Brute force här är inte långsamt, det är aritmetiskt omöjligt, och inget i det här kapitlet antyder ett alternativ.
Titta ändå tillbaka på dalen. När du står vid med en loss på 0,0822 är riktningen för ”nedför” inget mysterium — du kan se det på sidan, kurvan lutar nedåt åt höger. Om du kunde fråga förlustfunktionen åt vilket håll den lutar i punkten där du står, utan att utvärdera den någon annanstans, kunde du ta ett steg åt det hållet, fråga igen och upprepa tills marken är platt.
Den frågan har ett namn. Lutningen hos en funktion i en punkt är dess derivata, och för en funktion av många parametrar är samlingen av lutningar i alla riktningar på en gång gradienten. Kapitel 1 kunde inte använda någon, eftersom perceptronens fel var en trappa utan lutning att fråga om. Det här kapitlet har byggt något bättre: en loss som är slät överallt och som kom från ett uttalat antagande snarare än en preferens.
Så frågan för kapitel 3 är inte längre om en lutning finns. Den är hur man beräknar den, varför det går nedför snarare än uppför när man rör sig mot den — ett tecken nästan varje kurs ber dig ta på tro — och hur långt man ska gå innan man frågar igen, vilket visar sig vara det enda tal som avgör om en träningskörning konvergerar, oscillerar runt svaret för alltid eller rusar iväg mot oändligheten.
Källor och metod
Länk till avsnittet: Källor och metodVärt att läsa vid sidan av det här kapitlet: Prince, Understanding Deep Learning §5.1–5.2 och appendix C, som bygger varje loss i boken från maximum likelihood i den ordning som används här; Goodfellow, Bengio och Courville, Deep Learning §3.1–3.11 och §5.5, vars avsnitt om maximum likelihood också härleder KL-divergensen som kapitel 4 behöver; Murphy, Probabilistic Machine Learning: An Introduction kapitel 2 och §4.2, om vad maximum likelihood gör och inte garanterar; Deisenroth, Faisal och Ong, Mathematics for Machine Learning §6.1–6.4 för summaregeln, produktregeln och Bayes regel ordentligt genomgångna; Tom Mitchells korta CMU-anteckning Estimating Probabilities: MLE and MAP (2016); och §22.7 i Dive into Deep Learning, som når samma resultat i körbar kod.
Referenser
Länk till avsnittet: Referenser-
Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, s. 309–368 (1922). Där likelihood läggs fram som en allmän metod, tillsammans med ”parameter”, ”statistic”, sufficiency och efficiency. Själva namngivningen, och separationen från sannolikhet, är ett år äldre: Fisher, R. A., On the ”probable error” of a coefficient of correlation deduced from a small sample, Metron 1, s. 3–32 (1921), s. 24–25. ↩
-
IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. Definierar binary32 och binary16, och avrundningsreglerna som gör att summeringsexperimentet utfaller som det gör. ↩
-
Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). Formatets parametrar, och argumentet för att byta mantissabitar mot exponentbitar. ↩
-
Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. Loss scaling, och de uppmätta gradientmagnituder som gör det nödvändigt i float16. ↩
-
Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), s. 5–48 (1991). Fortfarande den bästa enskilda förklaringen till varför de två summeringsordningarna inte håller med varandra. ↩
-
Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), s. 40 (1965). Kompenserad summering på en halv sida. ↩
-
Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), s. 73–101 (1964). Den loss som är kvadratisk nära noll och linjär i svansarna, härledd snarare än ihoplagad. ↩
-
Tukey, J. W. A survey of sampling from contaminated distributions, i Contributions to Probability and Statistics (Stanford University Press, 1960), s. 448–485. ↩
-
Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805), bilaga Sur la méthode des moindres quarrés. Den första publiceringen av minsta kvadratmetoden, som ett beräkningsverktyg. ↩
-
Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809), bok II, §§175–179. Argumentet från det aritmetiska medelvärdet till den normala fellagen, och därifrån till minsta kvadratmetoden. ↩