Hoppa till innehållet
4/30Kapitel 4 av 30

Klassificering, cross-entropy och hur du inte lurar dig själv

Bygg en logistisk klassificerare med förlusten från kapitel 2 och descent från kapitel 3 – och se varför 98 % accuracy kan hitta ingenting.

På den här sidan

En modell som svarar den här delen är okej om varje del som kommer från bandet har rätt 98,15 % av gångerna. Den är också värdelös: av de 74 defekta delarna i testuppsättningen fångar den inte en enda.

Båda meningarna beskriver samma modell. Avståndet mellan dem är det här kapitlet.

Den första halvan bygger klassificeraren. Den behöver nästan inget nytt: kapitel 2 gav receptet för att göra ett antagande om hur data produceras till en förlustfunktion, och kapitel 3 gav maskineriet för att gå nedför på vilken förlust receptet än ger dig. Tillämpa båda på en ja/nej-fråga och logistisk regression faller ut, plus en ny idé — en logit — som kommer att debiteras igen i kapitel 17.

Den andra halvan är den svårare. Allt efter den här punkten i kursen bedöms av ett tal som någon har mätt, och om du inte kan skilja en verklig förbättring från en mätartefakt är varje följande kapitel dekoration. Alltså: förväxlingsmatrisen, precision och recall, de tre uppdelningarna, läckage och frågan nästan ingen svarar ärligt på — hur många testexempel behöver jag egentligen?

Aritmetiken här kör över 20 000 rader, så den är vektoriserad rakt igenom — NumPy har gjort jobbet sedan kapitel 2, och härifrån slutar det vara värt att påpeka.

Samma fabrik som i kapitel 1, svårare fråga. I stället för acceptera eller kassera är frågan är den här delen defekt — och defekter är sällsynta, vilket gör mäthalvan av det här kapitlet svår och modelleringshalvan bedrägligt enkel.

belt.pyPYTHON
import numpy as np

rng = np.random.default_rng(4)
N = 20_000
width  = rng.normal(22.0, 0.9, N)      # millimetres
weight = rng.normal(57.0, 3.0, N)      # grams

z_true = -5.90 + 1.90 * (width - 22.0) + 0.42 * (weight - 57.0)
y = (rng.random(N) < 1 / (1 + np.exp(-z_true))).astype(float)

perm = rng.permutation(N)
train, val, test = perm[:12_000], perm[12_000:16_000], perm[16_000:]
TEXT
N = 20000  defects = 337  base rate = 0.0169
defects per split = 203 60 74

Tre uppdelningar, inte två. Skälet förtjänar ett eget avsnitt och får ett nedan; tills vidare, träna på den första, justera på den andra och titta inte på den tredje.

Features standardiseras — medelvärdet subtraheras, dividerat med standardavvikelsen — med endast träningsstatistiken, av det skäl kapitel 1 demonstrerade med perceptronens konvergensgräns: ocentrerad data gör geometrin fientlig. Vilka rader du får beräkna medelvärdet från blir en levande fråga senare i kapitlet.

Perceptronen returnerade ett tecken. Ett tecken kan inte skilja kassera från kassera, men precis bara, och just den skillnaden är exakt vad en fabrik behöver för att avgöra vilka delar en människa ska inspektera om först.

Följ därför receptet från kapitel 2 bokstavligt. Skriv ned vad du påstår om hur en label produceras, ta likelihood, ta loggen, negera den, och du har en förlust. För ett ja/nej-utfall är påståendet en Bernoulli-fördelning: det finns en sannolikhet pp att delen är defekt, och

P(yp)=py(1p)1yP(y \mid p) = p^{\,y}\,(1-p)^{\,1-y}

vilket bara är ett kompakt sätt att skriva ”pp om y=1y = 1, och 1p1-p om y=0y = 0”. Ta loggen av det och negera den, så blir förlusten för ett exempel

L=[ylogp+(1y)log(1p)]L = -\big[\,y \log p + (1 - y)\log(1 - p)\,\big]

Det här är binär cross-entropy. Den valdes inte för att den är bekväm; den är den negativa log-likelihood för den enda fördelning ett myntkast kan ha. Något annat fanns inte.

Det som fortfarande saknas är var pp kommer ifrån. Modellen beräknar en viktad summa s=wx+bs = \mathbf{w}\cdot\mathbf{x} + b, vilket är ett reellt tal och sträcker sig över hela tallinjen, medan en sannolikhet måste ligga i (0,1)(0,1). Funktionen som flyttar mellan dem är den logistiska sigmoiden:

σ(s)=11+es\sigma(s) = \frac{1}{1 + e^{-s}}
TEXT
logit -4.0  ->  p = 0.0180        loss when y=1 and p=0.9  : 0.1054
logit -1.0  ->  p = 0.2689        loss when y=1 and p=0.5  : 0.6931
logit  0.0  ->  p = 0.5000        loss when y=1 and p=0.01 : 4.6052
logit  4.0  ->  p = 0.9820

Läs kolumnen till höger som en prislista. Att ha rätt med 90 % säkerhet kostar 0,105. Att vägra bestämma sig kostar 0,693 — vilket är log2\log 2, priset för en axelryckning. Att ha säkert fel kostar 4,6, fyrtiofyra gånger mer, och priset stiger utan gräns när modellen blir mer säker på ett misstag. Cross-entropy räknar inte bara fel: den tar betalt för arrogans.

Kapitel 3 sa: för att träna vad som helst, ta derivatan av förlusten med avseende på varje parameter. Gör det för ett exempel. Med s=wx+bs = \mathbf{w}\cdot\mathbf{x} + b och p=σ(s)p = \sigma(s):

Ls=py,Lw=(py)x,Lb=py\frac{\partial L}{\partial s} = p - y, \qquad \frac{\partial L}{\partial \mathbf{w}} = (p - y)\,\mathbf{x}, \qquad \frac{\partial L}{\partial b} = p - y
Visa detaljer

De två raderna som får röran att ta ut sig. Sigmoiden har en ovanligt trevlig derivata, σ(s)=σ(s)(1σ(s))=p(1p)\sigma'(s) = \sigma(s)\,(1 - \sigma(s)) = p(1-p). Och förlusten deriveras till

Lp=yp+1y1p=pyp(1p)\frac{\partial L}{\partial p} = -\frac{y}{p} + \frac{1-y}{1-p} = \frac{p - y}{p\,(1-p)}

Multiplicera de två med kedjeregeln och p(1p)p(1-p) dyker upp en gång i täljaren och en gång i nämnaren. Den tar ut sig exakt, och pyp - y är det som blir kvar. Den elimineringen är ingen slump — det är vad som händer när förlusten är den negativa log-likelihood för en fördelning och output-funktionen är den som fördelningen naturligt använder. Den parningen har ett namn — en generaliserad linjär modell — och den prydliga gradient är dess fingeravtryck.1

Så uppdateringen är förutsägelse minus sanning, gånger input. Inget annat. Här är hela tränaren, vilket är kapitel 3:s descent med en rad ändrad:

logistic.pyPYTHON
def sigmoid(z):
    return np.where(z >= 0, 1.0 / (1.0 + np.exp(-z)),
                    np.exp(np.minimum(z, 0)) / (1.0 + np.exp(np.minimum(z, 0))))


def fit_logistic(X, y, lr=0.5, epochs=4000):
    w, b = np.zeros(X.shape[1]), 0.0
    for _ in range(epochs):
        p = sigmoid(X @ w + b)
        g = p - y                        
        w -= lr * (X.T @ g) / len(y)     
        b -= lr * g.sum() / len(y)       
    return w, b

np.where i sigmoid är inte kosmetik. Att beräkna 1/(1+es)1/(1+e^{-s}) direkt flödar över för stora negativa ss; grenen väljer den algebraiskt identiska form som håller exponenten negativ. Det här är kapitel 2:s flyttalslåda som kräver in sin första skuld, och den kommer att kräva in en större två avsnitt härifrån.

Varför inte kvadratfel, och varför svaret handlar om gradient

Länk till avsnittet: Varför inte kvadratfel, och varför svaret handlar om gradient

Standardförklaringen till att föredra cross-entropy framför kvadratfel är likelihood-argumentet ovan: kvadratfel är vad du får om du antar Gaussiskt brus, labels är inte Gaussiska, alltså ska du inte göra det. Det är korrekt och övertygar ingen, eftersom du kan skriva L=(py)2L = (p - y)^2 över en sigmoid och den kommer att tränas.

Argumentet som landar handlar om gradient. Lägg kvadratfel ovanpå en sigmoid så ger kedjeregeln

Ls=2(py)p(1p)\frac{\partial L}{\partial s} = 2\,(p - y)\,p\,(1-p)

Den extra p(1p)p(1-p) är den som tog ut sig tidigare. Nu gör den inte det, och den går mot noll när modellen är säker — även när modellen har säkert fel. Utvärdera båda vid några poäng, för ett exempel vars sanna label är 1:

score ssppcross-entropy L/s\partial L/\partial skvadratfel L/s\partial L/\partial skvot
8-80,0003350.999665-0.9996650.000670-0.0006701 491
4-40,0179860.982014-0.9820140.034690-0.03469028,3
2-20,1192030.880797-0.8807970.184956-0.1849564,8
000,5000000.500000-0.5000000.250000-0.2500002,0
+2+20,8807970.119203-0.1192030.025031-0.0250314,8

Vid s=8s = -8 har modellen så fel som det går att ha, och kvadratfel svarar med en gradient som är 1 491 gånger mindre än cross-entropy:s. Ju värre misstaget är, desto mindre lär sig modellen av det. Cross-entropy:s gradient mättas däremot vid 1-1: maximalt fel ger en maximalt stor signal, och inte större.

Kör loppet. Två tusen balanserade punkter, identiska startvikter valda för att ha säkert fel (w=[6,6]\mathbf{w} = [-6, -6]), identisk inlärningstakt, bara förlusten skiljer sig. Båda körningarna poängsätts med cross-entropy så att kolumnerna är jämförbara.

epokcross-entropy-förlustaccuracykvadratfelsförlustaccuracy
15,48650,23005,94990,2290
101,55250,24605,90420,2290
500,46420,77805,69130,2320
1000,46390,77705,39550,2410
2000,46390,77704,63110,2745
5000,46390,77700,52910,7660
1 0000,46390,77700,46400,7765

Cross-entropy är klar vid epok 50. Kvadratfel ligger fortfarande på 24 % accuracy vid epok 100 — och hade inte rört sig från 23 % vid epok 10 — värre än att gissa, eftersom den började med säkert fel och den gradient som skulle rädda den har multiplicerats med 0,0007. Den kommer loss runt epok 500 och landar på samma plats. Så den ärliga sammanfattningen är att kvadratfel över en sigmoid inte är felaktigt; det är långsamt exakt där hastighet är viktigast. På en modell med två parametrar förlorar du 450 epoker. På ett nätverk med hundra lager, där någon enhet någonstans alltid har säkert fel, förlorar du träningskörningen.

Tre storheter, som behövs ordentligt i kapitel 8 för perplexity och i kapitel 11 för straffet som håller en fine-tuned policy nära sin referens. De är enklare än sitt rykte.2

Entropi är det genomsnittliga antalet bitar du måste lägga på att kommunicera ett drag ur en fördelning, om du använder bästa möjliga kod för den:

H(p)=ipilog2piH(p) = -\sum_i p_i \log_2 p_i

Cross-entropy är vad du betalar när du använder en kod byggd för qq på data som egentligen kommer från pp:

H(p,q)=ipilog2qiH(p, q) = -\sum_i p_i \log_2 q_i

KL-divergens är överskottet — slöseriet, i bitar, som orsakas av att tro qq när sanningen är pp:

DKL(pq)=H(p,q)H(p)D_{\mathrm{KL}}(p \parallel q) = H(p,q) - H(p)

Kontrollera alla tre på bandet:

TEXT
test defect rate                                = 0.0185
entropy of that coin                            = 0.1329 bits
cross-entropy of the constant predictor on test = 0.1330 bits
KL(test coin || fair coin)                      = 0.8671 bits
H + KL                                          = 1.0000 bits
cross-entropy of the p=0.5 predictor on test    = 1.0000 bits

Två saker syns där. För det första uppnår en modell som helt enkelt rapporterar träningsbasfrekvensen, 1,69 %, en cross-entropy på 0,1330 bitar, nästan exakt entropin för test-labels — vilket den måste, eftersom den har rätt fördelning och ingen annan information. Entropi är golvet som okunskap om individen köper dig. För det andra betalar en modell som rycker på axlarna och säger 0,5 exakt 1 bit, och gapet mellan de två, 0,8671 bitar, är precis KL-divergensen. H+DKL=H(p,q)H + D_{\mathrm{KL}} = H(p,q) är inte en identitet att memorera; det är en faktura du kan se läggas ihop.

Och kopplingen tillbaka till träning: när label är en enda känd klass är den ”sanna” fördelningen one-hot, dess entropi är noll, och cross-entropy är lika med KL-divergensen. Att minimera cross-entropy och dra modellens fördelning mot sanningen är samma handling.

Mer än två svar: softmax och skiftet som inte kostar något

Länk till avsnittet: Mer än två svar: softmax och skiftet som inte kostar något

Defekt är inte en sak. Vid formning kan en del komma ut som en short shot (för lite material), flash (för mycket, utpressat ur formen) eller burn. Fyra utfall, alltså fyra logits, och de måste bli fyra sannolikheter som summerar till ett. Det är softmax:

softmax(z)i=ezijezj\operatorname{softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_j e^{z_j}}

Den har en egenskap som ser ut som en olyckshändelse men i själva verket är hela implementationen:

softmax(z+c)=softmax(z)\operatorname{softmax}(\mathbf{z} + c) = \operatorname{softmax}(\mathbf{z})

för valfri konstant cc, eftersom ezi+c=ecezie^{z_i + c} = e^{c} e^{z_i} och ece^c tar ut varandra upptill och nedtill. Bara skillnader mellan logits betyder något. Den absoluta nivån är inte information.

Lyckligtvis, för den absoluta nivån är det som får datorn att gå sönder:

TEXT
logits            = [800. 801. 799.]
naive softmax     = [nan nan nan]
shifted by -max   = [0.2447 0.6652 0.09  ]
same softmax after adding 1000 to every logit: True

e800e^{800} flödar över en 64-bitars float, summan blir oändlighet, och oändlighet delat med oändlighet är nan — inte ett fel, ingen krasch, bara ett tyst hål där tre sannolikheter brukade finnas. Att subtrahera den största logit ändrar inget matematiskt och allt numeriskt, eftersom den största exponenten blir exakt e0=1e^0 = 1. Det här är kapitel 2:s logsumexp-trick i arbetskläder, och varje seriös implementation gör det:

softmax.pyPYTHON
def softmax(Z):
    Z = Z - Z.max(axis=1, keepdims=True)   
    E = np.exp(Z)
    return E / E.sum(axis=1, keepdims=True)


def fit_softmax(X, Y, lr=1.0, epochs=6000):
    W, b = np.zeros((X.shape[1], Y.shape[1])), np.zeros(Y.shape[1])
    for _ in range(epochs):
        G = (softmax(X @ W + b) - Y) / len(X)   
        W -= lr * (X.T @ G)
        b -= lr * G.sum(0)
    return W, b

Gradient är återigen förutsägelse minus sanning, nu med YY one-hot. Det binära fallet var ett specialfall hela tiden.

Tränad på 3 000 delar och testad på 1 000, med tre mätningar var (bredd, vikt, smälttemperatur), når den 94,00 % accuracy. Här är vad det talet döljer:

sanning ↓ / predikterat →okshort shotflashburnrecall
ok8505900,984
short shot2221000,488
flash2003010,588
burn300390,929
precision0,9500,8080,7690,975

Modellen hittar färre än hälften av short shots. Accuracy kan inte se det, eftersom 86 % av delarna är felfria och att få dem rätt räcker för att bära genomsnittet. Macro F1 — medelvärdet av F1-poängen per klass, som viktar en sällsynt klass lika mycket som en vanlig — är 0,7983, jämfört med micro F1 på 0,9400 som per definition är identisk med accuracy. När någon rapporterar ett enda F1-tal, fråga vilket.

Det var det sista av modelleringen. Resten av kapitlet handlar om talen.

Ta den tränade binära modellen och gör två varianter genom att multiplicera varje logit med en konstant: 0,35 för en tveksam version, 4 för en översäker. Att multiplicera med ett positivt tal kan inte ändra något tecken, så alla tre modeller predikterar exakt samma label för alla 4 000 testdelar. Accuracy kan inte skilja dem åt. Cross-entropy har inga problem alls:

modellaccuracycross-entropygenomsnittlig förlust när rättgenomsnittlig förlust när felvärsta enskilda förlust
tveksam (logits × 0,35)0,98300,15490,13691,19902,80
som tränad0,98300,05640,01472,46897,82
översäker (logits × 4)0,98300,15630,00099,142727,63

Den tveksamma modellen betalar en liten skatt på varje del, även de tusentals den får rätt. Den översäkra är nästan gratis när den har rätt och katastrofal när den har fel — en enda del i den testuppsättningen kostar den 27,63 nats på egen hand. De två landar på nästan samma total via motsatta vägar, och den tränade modellen, vars sannolikheter är kalibrerade mot datan, ligger tre gånger lägre än båda.

Det här är det skarpaste sättet att formulera skillnaden mellan en förlust och ett mått. Förlusten är det du optimerar: den måste vara deriverbar, och den ser allt modellen sade, inklusive hur säker den var. Måttet är det du bedöms på: det kan vara en stegfunktion, en affärsregel, en räkning av missade defekter. De är inte samma objekt och de är inte alltid överens — därför definierar du båda innan du börjar, och låter aldrig förlusten stå in för måttet bara för att den råkar visas på skärmen.

Före någon modell, kravet: vad får det lataste möjliga svaret för poäng? På det här bandet, säg alltid felfri:

TEXT
always-say-fine baseline: accuracy = 0.9815
confusion (tn, fp, fn, tp) = (3926, 0, 74, 0)

98,15 %. Nu den tränade logistiska modellen, vid standardtröskeln 0,5:

TEXT
logistic @0.5: accuracy=0.9830 precision=0.8000 recall=0.1081 F1=0.1905
confusion (tn, fp, fn, tp) = (3924, 2, 66, 8)

98,30 %. Den slog baslinjen med 0,15 procentenheter, och varje rapport som stannar vid accuracy kommer att kalla det en vinst. Förväxlingsmatrisen säger vad som faktiskt hände:

predikterat felfripredikterat defekt
faktiskt felfri3 9242
faktiskt defekt668

Den hittade 8 defekta delar av 74 och släppte igenom 66. Tre tal namnger de tre sätten att läsa tabellen:

  • Precision =TP/(TP+FP)=8/10=0.800= \mathrm{TP}/(\mathrm{TP}+\mathrm{FP}) = 8/10 = 0.800. Av delarna den flaggade, hur många var verkligen defekta. Det här är kostnaden för bortslösade inspektioner.
  • Recall =TP/(TP+FN)=8/74=0.108= \mathrm{TP}/(\mathrm{TP}+\mathrm{FN}) = 8/74 = 0.108. Av de defekta delarna, hur många fångade den. Det här är kostnaden för att skicka en dålig del till en kund.
  • F1 =2PR/(P+R)=0.190= 2PR/(P+R) = 0.190, deras harmoniska medelvärde, som håller sig nära det mindre av de två och därför vägrar smickras av bara det ena.

Vilket som spelar roll beror på fabriken, inte på matematiken: en inspektion kostar några sekunder och en skickad defekt kostar en återkallelse, så här dominerar recall och 0,108 är ett misslyckande.

Men modellen är inte problemet. Tröskeln är det, och tröskeln är inte en del av modellen — den är ett affärsbeslut som appliceras efteråt på en sannolikhet. Svep den:

tröskelTPFPFNaccuracyprecisionrecallF1
0,50082660,98300,8000,1080,190
0,2002728470,98120,4910,3650,419
0,10042118320,96250,2630,5680,359
0,05054236200,93600,1860,7300,297
0,0206757070,85580,1050,9050,188
0,005711 36030,65930,0500,9590,094

Läs accuracy-kolumnen nedåt. Den faller hela vägen — från 98,30 % till 65,93 % — medan modellen går från att fånga 8 defekter till att fånga 71 av 74. Varje användbar sak den här modellen kan göra gör dess accuracy sämre. Ett team som optimerar rubriktalet skulle leverera versionen som inte hittar något.

Visa detaljer

Class weighting skapar inte signal, det flyttar driftspunkten. Den vanliga första reflexen med obalanserade klasser är att vikta den sällsynta klassen i förlusten. Gör man det, med vikter på 1, 10 och 60 på positiva:

vikt på positivaaccuracyprecisionrecallF1AUC
10,98300,8000,1080,1900,9363
100,96050,2530,5810,3520,9361
600,82900,0910,9190,1660,9361

Precision och recall rör sig långt. AUC — sannolikheten att modellen rankar en slumpmässig defekt del över en slumpmässig bra del, vilket ignorerar tröskeln helt — rör sig med 0,0002, vilket är ingenting. Omviktning sköt samma modell längs samma trade-off-kurva. Det är ofta vad du vill, och det är aldrig ny information: om rankningen är dålig kommer inget viktningsschema att rädda den.

Tre uppdelningar och läckan du håller på att hitta

Länk till avsnittet: Tre uppdelningar och läckan du håller på att hitta

Varför tre uppdelningar och inte två? För att i samma ögonblick som du använder en uppsättning exempel för att välja något — en tröskel, en inlärningstakt, vilken av sex modeller som ska levereras — har den uppsättningen använts för anpassning, och dess poäng slutar vara unbiased.3 Mätt på det här bandet: att svepa tröskeln på valideringsuppsättningen väljer 0,196, och modellen får sedan F1 = 0,4122 på den orörda testuppsättningen. Om svepet hade körts direkt på testuppsättningen var det bästa möjliga där 0,4186 — ett tal ingen har rätt att rapportera.

Gapet är litet här, 0,006, eftersom det är en hyperparameter som svepts en gång mot 4 000 valideringsexempel. Det växer med varje extra beslut och varje krympning av valideringsuppsättningen. Notera också att riktningen inte är garanterad i en enskild körning: den valda tröskeln fick 0,3902 på validering och 0,4122 på test, så validering underskattade den den här gången. Biasen är systematisk över många beslut, inte synlig i ett enda.4

Nu övningen. Bandloggen kommer med en tredje kolumn, station_seconds: hur länge varje del var vid inspektionsstationen. Att lägga till den är en rad i preprocessingen. Här är vad den gör:

modellaccuracyprecisionrecallF1cross-entropyAUC
bredd + vikt0,98300,8000,1080,1900,05640,9363
+ station_seconds0,99200,7920,7700,7810,02360,9970

Recall går från 10,8 % till 77,0 %. F1 mer än fyrdubblas. Och lägg märke till vad accuracy gjorde: 98,30 % → 99,20 %, en vinst på nio tiondelar av en punkt, vilket är den typ av tal som avrundas till ”cirka 99 % oavsett” på en sammanfattningsslide. Accuracy misslyckades med att se misslyckandet tidigare och misslyckas nu med att se fusket.

Innan du läser vidare: modellen fuskar. Ta reda på hur.

Hur du jagar en läcka, i den ordning som hittar den snabbast.

  1. Jämför train och test. Overfitting syns som ett stort gap. Här: ärlig modell 0,9838 train / 0,9830 test; läckande modell 0,9936 train / 0,9920 test. Båda gapen är under 0,2 punkter. En läcka ser inte ut som overfitting — den läckande featuren är lika tillgänglig vid testtid, så modellen generaliserar vackert till en värld som inte finns.

  2. Träna en modell per feature, ensam. Allt som bär svaret kommer att avslöja sig:

    feature ensamaccuracyrecallF1AUC
    bredd0,98150,0140,0260,8691
    vikt0,98150,0000,0000,7914
    station_seconds0,98500,4050,5000,9960

    En kolumn, på egen hand, rankar defekter med AUC 0,9960. Två mätningar tagna med ett skjutmått och en våg klarar 0,87 och 0,79. Den asymmetrin är larmet.

  3. Fråga när varje tal skrevs ned. Genomsnittlig uppehållstid: 2,23 sekunder för delar som godkändes, 15,56 sekunder för delar som underkändes. Självklart. En del stannar vid stationen för att en inspektör drog den av bandet — vilket sker efter, och bara för att, någon bestämde att den var defekt. Kolumnen är inte en mätning av delen. Den är en mätning av utslaget.

the planted leakPYTHON
station = 1.8 + rng.exponential(0.35, N)                     # a part just passing through
audited = rng.random(N) < 0.006                              # random spot checks
station[audited] += rng.uniform(6.0, 26.0, audited.sum())
station[y == 1] = 9.0 + rng.exponential(7.0, (y == 1).sum())  

Den markerade raden är läckan: en defekt dels uppehållstid dras från en annan fördelning, eftersom en människa tog den av bandet. Det här är den vanligaste allvarliga buggen i tillämpad maskininlärning, och den har ett namn: target leakage — information i tränings-features som inte skulle vara tillgänglig i det ögonblick prediktionen måste göras.5 Den kastar inget undantag. Den producerar ett bättre tal. Varje incitament i ett projekt pekar mot att behålla den.

Försvaret är en fråga, ställd till varje kolumn: vid ögonblicket då jag behöver den här prediktionen, finns det här värdet ännu? På ett liveband är station_seconds okänd tills efter att delen har inspekterats — vilket är det modellen skulle ersätta.

Anta att du poängsätter en modell på 20 exempel och den får 17 rätt. Du rapporterar 85 %.

TEXT
17 correct out of 20 -> accuracy 0.8500
  Wilson    95% CI : [0.6396, 0.9476]
  bootstrap 95% CI : [0.7000, 1.0000]
  P(a 65% model scores 17 or more out of 20) = 0.0444
  P(an 85% model scores 17 or more out of 20) = 0.6477

Den ärliga läsningen av 17/20 är någonstans mellan 64 % och 95 %. En genuin 65 %-modell producerar det här resultatet 4,4 % av gångerna — en körning av tjugotre — och om du provade en handfull prompts och rapporterade den bästa tillverkade du den körningen själv. Sjutton av tjugo kan inte skilja en 85 %-modell från en 65 %-modell.

Två sätt att sätta ett intervall på en frekvens, och båda hör hemma i din verktygslåda:

uncertainty.pyPYTHON
def wilson(k, n, z=1.959963985):
    """95% interval for k successes in n trials. Correct at small n; no simulation."""
    ph, d = k / n, 1 + z * z / n
    centre = (ph + z * z / (2 * n)) / d
    half = z * (ph * (1 - ph) / n + z * z / (4 * n * n)) ** 0.5 / d
    return centre - half, centre + half


def bootstrap_ci(correct, n_resamples=10_000, alpha=0.05, seed=0):
    """95% interval for the mean of any per-example score array. Works on F1 too."""
    rng = np.random.default_rng(seed)
    correct = np.asarray(correct, dtype=float)
    draws = correct[rng.integers(0, len(correct), size=(n_resamples, len(correct)))]
    lo, hi = np.quantile(draws.mean(axis=1), [alpha / 2, 1 - alpha / 2])
    return float(correct.mean()), float(lo), float(hi)

Använd Wilson6 för en vanlig framgångsfrekvens; den beter sig väl vid varje nn och behöver ingen slump. Notera ovan att vid n=20n = 20 är bootstrapens övre ände 1,0000 — att resampla 20 punkter kan lätt dra 20 korrekta, så den kan inte representera ett intervall smalare än sin egen granularitet. Använd bootstrap7 där ingen formel finns, vilket är de flesta intressanta fall: F1, macro-medelvärden, BLEU, pass@1, poängen från en rubric-baserad domare. På det här bandet har den justerade modellens F1 på 0,4122 ett bootstrap-intervall på [0,3009, 0,5156] — vilket är talet som bör stå i rapporten, eftersom punktskattningen ensam inbjuder till en jämförelse den inte kan stödja.

En mätning till, eftersom den ändrar hur du bör jämföra två modeller. Två modeller poängsatta på samma 500 exempel:

TEXT
model A: 0.8580  95% CI [0.8260, 0.8880]
model B: 0.8120  95% CI [0.7780, 0.8460]
the two intervals overlap: True
paired difference A-B: 0.0460  95% CI [0.0260, 0.0680]
they disagree on 31 of 500 examples (A right 27, B right 4)

Deras intervall överlappar, och folkregeln — överlappande felstaplar betyder ingen signifikant skillnad — skulle kalla jämförelsen ofullständig. Det är den inte. De två modellerna kördes på samma exempel, så den rätta storheten är skillnaden per exempel, vars intervall är [0.0260, 0.0680], bekvämt över noll. De är oense om bara 31 av 500 objekt, och A vinner 27 av de oenigheterna; de gemensamma exemplen, lätta och svåra, tar ut varandra i stället för att lägga till brus. Jämför modeller parat, så når du samma slutsats med en bråkdel av datan.

Du har nu en modell som matar ut kalibrerade sannolikheter, en förlust härledd från ett påstående om datan i stället för vald för bekvämlighet, en gradient som bokstavligen är förutsägelse minus sanning, och — viktigare — maskineriet för att ta reda på om något av det fungerar. Wilson-intervallet på tio rader ovan återanvänds ordagrant: det bär prompt-varianterna i kapitel 15, retrieval-tabellerna i kapitel 19 och golden set i kapitel 29. Bootstrap är det du tar till när ingen formel finns.

Men modellen är fortfarande ett lager. Den drar en linje, och kapitel 1 bevisade med fyra rader XOR att en linje inte räcker. Lösningen är att stapla: ett första lager som böjer rummet, ett andra som drar linjen i det böjda rummet.

Det är där den prydliga gradient från det här kapitlet tar slut. Allt ovan fungerade eftersom L/s=py\partial L/\partial s = p - y kunde skrivas ned för hand, en gång, för en modell med ett lager mellan input och förlusten. Sätt ett andra lager i mitten och frågan byter form: vad är derivatan av förlusten med avseende på en vikt som inte rör output alls — en vars påverkan bara anländer genom ett annat lager, kanske längs flera vägar samtidigt?

Den derivatan finns. Att beräkna den för hand är hopplöst för allt större än en leksak, och att beräkna den en parameter i taget är hopplöst i en annan skala. Det som behövs är en procedur som får fram varje derivata i nätverket från en enda backward pass över samma graf som forward pass just gick igenom.

Det är kapitel 5, och det är motorn som resten av den här kursen kör på.


Också värt att läsa jämte det här kapitlet: Bishop, Pattern Recognition and Machine Learning §1.2, §1.5, §1.6 och §4.3, som täcker sannolikhet, beslutsteori, informationsteori och linjär klassificering i den ordning kapitlet följer; Murphy, Probabilistic Machine Learning: An Introduction, kapitel 6 och 10; Prince, Understanding Deep Learning §5.4–5.7; och Saito och Rehmsmeier, The Precision-Recall Plot Is More Informative than the ROC Plot When Evaluating Binary Classifiers on Imbalanced Datasets (PLOS ONE, 2015) — varför AUC som citeras ovan inte bör vara det enda tröskelfria talet du tittar på när 1,7 % av delarna är defekta.

  1. Ma, T. och Ng, A. CS229 Lecture Notes, Stanford University, kapitel 2 och 3. Där elimineringen som producerar pyp - y slutar se ut som tur: välj exponential-family-fördelningen som matchar din output, använd dess kanoniska link, och gradient är alltid förutsägelse minus sanning.

  2. Olah, C. Visual Information Theory (2015), colah.github.io/posts/2015-09-Visual-Information. Den tydligaste tillgängliga förklaringen av entropi, cross-entropy och KL-divergens som kostnader i bitar snarare än som formler.

  3. Abu-Mostafa, Y. S., Magdon-Ismail, M. och Lin, H.-T. Learning From Data (AMLBook, 2012), föreläsning 13 och 17 i Caltech-kursen. Föreläsning 13 är validering; föreläsning 17, om de tre inlärningsprinciperna, är där data snooping namnges. Tillsammans är de källan till disciplinen i det här kapitlet: varje titt på en datauppsättning är ett anpassningsbeslut, oavsett om du körde en optimiser eller inte.

  4. James, G., Witten, D., Hastie, T. och Tibshirani, R. An Introduction to Statistical Learning, 2:a upplagan (Springer, 2021), kapitel 2 och 5, för bias–variance-dekompositionen och för resampling. Systervolymen är där urvalsfällan sägs rakt ut: Hastie, Tibshirani och Friedman, The Elements of Statistical Learning, 2:a upplagan, §7.10.2, The Wrong and Right Way to Do Cross-validation.

  5. Kaufman, S., Rosset, S., Perlich, C. och Stitelman, O. Leakage in Data Mining: Formulation, Detection, and Avoidance. ACM Transactions on Knowledge Discovery from Data 6(4), 2012. En formell behandling av felet som demonstrerades ovan, med fallstudier från tävlingar som vanns av en modell som hade lärt sig en artefakt av hur datan sattes samman.

  6. Wilson, E. B. Probable Inference, the Law of Succession, and Statistical Inference. Journal of the American Statistical Association 22(158), s. 209–212 (1927). Score-intervallet som används i wilson() ovan, fortfarande rätt standardval för en proportion. Läroboksintervallet p^±zp^(1p^)/n\hat{p} \pm z\sqrt{\hat{p}(1-\hat{p})/n} är det du ska undvika: det ger nonsens nära 0 och 1, och undertäcker grovt vid små nn.

  7. Efron, B. Bootstrap Methods: Another Look at the Jackknife. The Annals of Statistics 7(1), s. 1–26 (1979). Idén som låter dig sätta ett intervall på vilken statistik som helst som du kan beräkna, även de som saknar samplingteori.


Skapad av

David Vicente Campos

Grundare av NeuraLIA Labs och medgrundare av MyRealFood

Jag är dataingenjör från Universitetet i León. Jag var med och grundade MyRealFood, där jag som CTO byggde appen som miljontals människor har använt för att äta bättre, och jag grundade NeuraLIA Labs, där jag bygger AI-produkter. Här skriver jag om det jag har behövt förstå längs vägen, så som jag önskar att någon hade förklarat det för mig.

Mer om författaren

Publicerad av NeuraLIA Labs.

Få nya inlägg i din inkorg

AI-nyheter, guider och produktuppdateringar — ett kort mejl när vi publicerar något som är värt din tid.

Kursindex

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jevLästid 11 min

Jevs AI-modell är byggd för beslut, inte prosa

TypeSafe AI:s Jev väcker uppmärksamhet eftersom den behandlar mjukvaruintelligens som ett sannolikhetsproblem: välj rätt gren, lägg till konfidens och undvik att betala en LLM för att skriva text när koden behöver ett beslut.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineeringLästid 11 min

Kontextteknik för AI-agenter med lång horisont

Långkörande agenter misslyckas inte bara för att fönstret är litet. De misslyckas när filer, verktygsutdata och gammal historik tränger undan uppgiften agenten skulle slutföra.

Redo att låta LIA välja åt dig?

Bygg med alla AI-modeller på ett ställe – kom igång gratis i dag.