Klassifikation, Cross-Entropy und wie du dich nicht selbst täuschst
Baue einen logistischen Klassifikator und erfahre, warum 98 % accuracy bedeuten können, dass ein Modell nichts findet.
Auf dieser Seite
Ein Modell, das bei jedem Teil vom Band mit dieses Teil ist in Ordnung antwortet, liegt in 98,15 % der Fälle richtig. Es ist trotzdem wertlos: Von den 74 fehlerhaften Teilen im Test-Set findet es kein einziges.
Beide Sätze beschreiben dasselbe Modell. Der Abstand zwischen ihnen ist dieses Kapitel.
Die erste Hälfte baut den Klassifikator. Sie braucht fast nichts Neues: Kapitel 2 lieferte das Rezept, um aus einer Annahme darüber, wie Daten entstehen, eine Verlustfunktion zu machen, und Kapitel 3 lieferte die Mechanik, um auf jeder Verlustfunktion bergab zu laufen, die dieses Rezept ausspuckt. Wende beides auf eine Ja/Nein-Frage an, und logistische Regression fällt heraus — plus eine neue Idee, ein logit, das in Kapitel 17 wieder abgerechnet wird.
Die zweite Hälfte ist die schwierigere. Alles ab diesem Punkt im Kurs wird nach einer Zahl beurteilt, die jemand gemessen hat. Wenn du eine echte Verbesserung nicht von einem Messartefakt unterscheiden kannst, ist jedes folgende Kapitel Dekoration. Also: die Konfusionsmatrix, Precision und Recall, die drei Splits, Leakage und die Frage, die fast niemand ehrlich beantwortet — wie viele Testbeispiele brauche ich eigentlich?
Die Rechnungen hier laufen über 20.000 Zeilen, also ist alles durchgehend vektorisiert — NumPy erledigt die Arbeit seit Kapitel 2, und ab hier lohnt es sich nicht mehr, das jedes Mal zu erwähnen.
Das Band, mit einer selteneren Frage
Link zum Abschnitt: Das Band, mit einer selteneren FrageDieselbe Fabrik wie in Kapitel 1, schwierigere Frage. Statt annehmen oder ablehnen lautet die Frage: ist dieses Teil fehlerhaft — und Fehler sind selten. Das macht die Messhälfte dieses Kapitels schwierig und die Modellierungshälfte trügerisch einfach.
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:]N = 20000 defects = 337 base rate = 0.0169
defects per split = 203 60 74Drei Splits, nicht zwei. Der Grund verdient einen eigenen Abschnitt und bekommt ihn gleich; vorerst: Trainiere auf dem ersten, tune auf dem zweiten und schau den dritten nicht an.
Die Features werden standardisiert — Mittelwert abgezogen, durch die Standardabweichung geteilt — und zwar nur mit den Trainingsstatistiken, aus dem Grund, den Kapitel 1 mit der Konvergenzschranke des Perzeptrons gezeigt hat: Nicht zentrierte Daten machen die Geometrie feindselig. Aus welchen Zeilen du diesen Mittelwert berechnen darfst, wird später in diesem Kapitel zu einer akuten Frage.
Von einem Urteil zu einer Wahrscheinlichkeit
Link zum Abschnitt: Von einem Urteil zu einer WahrscheinlichkeitDas Perzeptron gab ein Vorzeichen zurück. Ein Vorzeichen kann ablehnen nicht von ablehnen, aber nur knapp unterscheiden — und genau diesen Unterschied braucht eine Fabrik, um zu entscheiden, welche Teile ein Mensch zuerst nachprüfen sollte.
Folge also dem Rezept aus Kapitel 2 wörtlich. Schreibe auf, was du darüber behauptest, wie ein Label entsteht, nimm die Likelihood, nimm den Logarithmus, negiere ihn, und du hast einen Loss. Für ein Ja/Nein-Ergebnis ist die Behauptung eine Bernoulli-Verteilung: Es gibt eine Wahrscheinlichkeit , dass das Teil fehlerhaft ist, und
was nur eine kompakte Schreibweise ist für „ wenn , und wenn “. Nimm davon den Logarithmus und negiere ihn, und der Loss für ein Beispiel ist
Das ist binäre Cross-Entropy. Sie wurde nicht gewählt, weil sie praktisch ist; sie ist die negative Log-Likelihood der einzigen Verteilung, die ein Münzwurf haben kann. Etwas anderes stand nicht zur Verfügung.
Was noch fehlt, ist, woher kommt. Das Modell berechnet eine gewichtete Summe , also eine reelle Zahl über die gesamte Zahlengerade, während eine Wahrscheinlichkeit in liegen muss. Die Funktion, die zwischen beiden vermittelt, ist die logistische Sigmoidfunktion:
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.9820Lies die rechte Spalte wie eine Preisliste. Mit 90 % Sicherheit richtig zu liegen kostet 0,105. Sich nicht festlegen zu wollen kostet 0,693 — also , der Preis eines Achselzuckens. Mit hoher Sicherheit falsch zu liegen kostet 4,6, vierundvierzigmal mehr, und der Preis steigt unbegrenzt, je sicherer das Modell bei einem Fehler wird. Cross-Entropy zählt Fehler nicht bloß: Sie berechnet Arroganz.
Der Gradient ist Vorhersage minus Wahrheit
Link zum Abschnitt: Der Gradient ist Vorhersage minus WahrheitKapitel 3 sagte: Um irgendetwas zu trainieren, brauchst du die Ableitung des Loss nach jedem Parameter. Mach das für ein Beispiel. Mit und :
Details anzeigen
Die zwei Zeilen, die das Durcheinander verschwinden lassen. Die Sigmoidfunktion hat eine ungewöhnlich angenehme Ableitung, . Und der Loss differenziert sich zu
Multipliziere beide mit der Kettenregel, und erscheint einmal oben und einmal unten. Es kürzt sich exakt weg, und bleibt übrig. Diese Kürzung ist kein Zufall — sie passiert immer dann, wenn der Loss die negative Log-Likelihood einer Verteilung ist und die Ausgabefunktion genau die ist, die diese Verteilung natürlicherweise verwendet. Dieses Paar hat einen Namen — ein generalisiertes lineares Modell — und der saubere Gradient ist sein Fingerabdruck.1
Das Update ist also Vorhersage minus Wahrheit, mal Eingabe. Nichts sonst. Hier ist der gesamte Trainer; es ist Chapter 3's descent mit einer geänderten Zeile:
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, bDas np.where in sigmoid ist nicht kosmetisch. direkt zu berechnen läuft bei großen negativen über; der Zweig wählt jeweils die algebraisch identische Form, die den Exponenten negativ hält. Das ist die Floating-Point-Kiste aus Kapitel 2, die ihre erste Schuld eintreibt, und zwei Abschnitte weiter wird sie eine größere eintreiben.
Warum nicht quadratischer Fehler, und warum die Antwort vom Gradienten handelt
Link zum Abschnitt: Warum nicht quadratischer Fehler, und warum die Antwort vom Gradienten handeltDie Standarderklärung dafür, Cross-Entropy dem quadratischen Fehler vorzuziehen, ist das Likelihood-Argument oben: Quadratischer Fehler entsteht aus der Annahme gaußschen Rauschens, Labels sind nicht gaußsch, also lass es. Das ist korrekt und überzeugt niemanden, weil du über eine Sigmoidfunktion schreiben kannst und es trainieren wird.
Das Argument, das landet, handelt vom Gradienten. Lege quadratischen Fehler auf eine Sigmoidfunktion, und die Kettenregel ergibt
Dieses zusätzliche ist das, was sich vorher gekürzt hat. Jetzt tut es das nicht, und es geht gegen null, sobald das Modell sicher ist — auch dann, wenn das Modell mit hoher Sicherheit falsch liegt. Werte beides für ein paar Scores aus, für ein Beispiel mit echtem Label 1:
| Score | Cross-Entropy | quadratischer Fehler | Verhältnis | |
|---|---|---|---|---|
| 0.000335 | 1,491 | |||
| 0.017986 | 28.3 | |||
| 0.119203 | 4.8 | |||
| 0.500000 | 2.0 | |||
| 0.880797 | 4.8 |
Bei liegt das Modell so falsch, wie es nur liegen kann, und quadratischer Fehler antwortet mit einem Gradienten, der 1.491-mal kleiner ist als der von Cross-Entropy. Je schlimmer der Fehler, desto weniger lernt das Modell daraus. Der Gradient von Cross-Entropy sättigt sich dagegen bei : maximal falsch erzeugt ein maximal großes Signal, und kein größeres.
Lass sie gegeneinander antreten. Zweitausend balancierte Punkte, identische Startgewichte, bewusst so gewählt, dass sie mit hoher Sicherheit falsch liegen (), identische Lernrate, nur der Loss unterscheidet sich. Beide Läufe werden mit Cross-Entropy bewertet, damit die Spalten vergleichbar sind.
| Epoche | Cross-Entropy-Loss | accuracy | Loss mit quadratischem Fehler | accuracy |
|---|---|---|---|---|
| 1 | 5.4865 | 0.2300 | 5.9499 | 0.2290 |
| 10 | 1.5525 | 0.2460 | 5.9042 | 0.2290 |
| 50 | 0.4642 | 0.7780 | 5.6913 | 0.2320 |
| 100 | 0.4639 | 0.7770 | 5.3955 | 0.2410 |
| 200 | 0.4639 | 0.7770 | 4.6311 | 0.2745 |
| 500 | 0.4639 | 0.7770 | 0.5291 | 0.7660 |
| 1,000 | 0.4639 | 0.7770 | 0.4640 | 0.7765 |
Cross-Entropy ist bei Epoche 50 fertig. Quadratischer Fehler liegt bei Epoche 100 noch bei 24 % accuracy — und hatte sich seit Epoche 10 nicht von 23 % bewegt — schlechter als Raten, weil er mit hoher Sicherheit falsch gestartet ist und der Gradient, der ihn retten würde, mit 0,0007 multipliziert wurde. Er entkommt ungefähr bei Epoche 500 und landet am selben Ort. Die ehrliche Zusammenfassung lautet also: Quadratischer Fehler über einer Sigmoidfunktion ist nicht falsch; er ist genau dort langsam, wo Geschwindigkeit am wichtigsten ist. Bei einem Modell mit zwei Parametern verlierst du 450 Epochen. Bei einem Netzwerk mit hundert Schichten, in dem irgendwo immer irgendeine Einheit mit hoher Sicherheit falsch liegt, verlierst du den Trainingslauf.
Entropie, Cross-Entropy und KL auf einer Seite
Link zum Abschnitt: Entropie, Cross-Entropy und KL auf einer SeiteDrei Größen, die du in Kapitel 8 für Perplexity und in Kapitel 11 für die Strafe brauchst, die eine fine-tuned Policy nahe an ihrer Referenz hält. Sie sind einfacher als ihr Ruf.2
Entropie ist die durchschnittliche Zahl an Bits, die du ausgeben musst, um eine Ziehung aus einer Verteilung zu kommunizieren, wenn du den bestmöglichen Code dafür verwendest:
Cross-Entropy ist das, was du ausgibst, wenn du einen Code verwendest, der für gebaut wurde, die Daten aber tatsächlich aus kommen:
KL-Divergenz ist der Überschuss — die Verschwendung in Bits, die entsteht, wenn du glaubst, obwohl die Wahrheit ist:
Prüfe alle drei am Band:
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 bitsZwei Dinge sind dort sichtbar. Erstens erreicht ein Modell, das einfach die Basisrate des Trainings meldet, 1,69 %, eine Cross-Entropy von 0,1330 Bits, fast genau die Entropie der Test-Labels — wie es sein muss, da es die richtige Verteilung und keine weitere Information hat. Entropie ist der Boden, den dir Unwissen über das einzelne Beispiel erkauft. Zweitens zahlt ein Modell, das die Schultern zuckt und 0,5 sagt, exakt 1 Bit, und die Lücke zwischen beiden, 0,8671 Bits, ist genau die KL-Divergenz. ist keine Identität zum Auswendiglernen; es ist eine Rechnung, deren Addition du beobachten kannst.
Und die Verbindung zurück zum Training: Wenn das Label eine einzelne bekannte Klasse ist, ist die „wahre“ Verteilung one-hot, ihre Entropie ist null, und Cross-Entropy ist gleich der KL-Divergenz. Cross-Entropy zu minimieren und die Verteilung des Modells zur Wahrheit zu ziehen, ist derselbe Akt.
Mehr als zwei Antworten: softmax und die Verschiebung, die nichts kostet
Link zum Abschnitt: Mehr als zwei Antworten: softmax und die Verschiebung, die nichts kostetFehlerhaft ist nicht eine Sache. Beim Spritzguss kann ein Teil als short shot herauskommen (zu wenig Material), als flash (zu viel, aus der Form herausgedrückt) oder als burn. Vier Ergebnisse, also vier logits, und sie müssen zu vier Wahrscheinlichkeiten werden, die sich zu eins summieren. Das ist softmax:
Er hat eine Eigenschaft, die wie ein Unfall aussieht und in Wahrheit die gesamte Implementierung ist:
für jede Konstante , weil und sich oben und unten kürzen. Nur Unterschiede zwischen logits bedeuten etwas. Das absolute Niveau ist keine Information.
Zum Glück, denn das absolute Niveau ist es, was den Computer kaputtmacht:
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 lässt einen 64-Bit-Float überlaufen, die Summe wird unendlich, und unendlich geteilt durch unendlich ist nan — kein Fehler, kein Absturz, nur ein stilles Loch dort, wo eben noch drei Wahrscheinlichkeiten waren. Das maximale logit abzuziehen ändert mathematisch nichts und numerisch alles, weil der größte Exponent genau wird. Das ist der logsumexp-Trick aus Kapitel 2 in Arbeitskleidung, und jede ernsthafte Implementierung macht es so:
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, bDer Gradient ist wieder Vorhersage minus Wahrheit, jetzt mit one-hot. Der binäre Fall war die ganze Zeit nur ein Spezialfall.
Trainiert auf 3.000 Teilen und getestet auf 1.000, mit jeweils drei Messungen (Breite, Gewicht, Schmelztemperatur), erreicht es 94,00 % accuracy. Hier ist, was diese Zahl verbirgt:
| Wahrheit ↓ / vorhergesagt → | ok | short shot | flash | burn | recall |
|---|---|---|---|---|---|
| ok | 850 | 5 | 9 | 0 | 0.984 |
| short shot | 22 | 21 | 0 | 0 | 0.488 |
| flash | 20 | 0 | 30 | 1 | 0.588 |
| burn | 3 | 0 | 0 | 39 | 0.929 |
| precision | 0.950 | 0.808 | 0.769 | 0.975 |
Das Modell findet weniger als die Hälfte der short shots. Accuracy kann das nicht sehen, weil 86 % der Teile in Ordnung sind und diese richtig zu erkennen reicht, um den Durchschnitt zu tragen. Macro-F1 — der Mittelwert der F1-Scores pro Klasse, der eine seltene Klasse genauso gewichtet wie eine häufige — ist 0,7983, gegenüber einem Micro-F1 von 0,9400, der per Definition identisch mit accuracy ist. Wenn jemand eine einzige F1-Zahl berichtet, frag welche.
Das war der letzte Teil der Modellierung. Der Rest des Kapitels handelt von den Zahlen.
Drei Modelle, eine accuracy
Link zum Abschnitt: Drei Modelle, eine accuracyNimm das trainierte binäre Modell und erstelle zwei Varianten, indem du jedes logit mit einer Konstante multiplizierst: 0,35 für eine zögerliche Version, 4 für eine übermütige. Multiplikation mit einer positiven Zahl kann kein Vorzeichen ändern, also sagen alle drei Modelle für alle 4.000 Testteile exakt dasselbe Label voraus. Accuracy kann sie nicht unterscheiden. Cross-Entropy hat damit überhaupt kein Problem:
| Modell | accuracy | Cross-Entropy | mittlerer Loss bei richtig | mittlerer Loss bei falsch | schlimmster einzelner Loss |
|---|---|---|---|---|---|
| zögerlich (logits × 0.35) | 0.9830 | 0.1549 | 0.1369 | 1.1990 | 2.80 |
| wie trainiert | 0.9830 | 0.0564 | 0.0147 | 2.4689 | 7.82 |
| übermütig (logits × 4) | 0.9830 | 0.1563 | 0.0009 | 9.1427 | 27.63 |
Das zögerliche Modell zahlt auf jedes Teil eine kleine Steuer, auch auf die Tausenden, die es richtig bekommt. Das übermütige ist nahezu kostenlos, wenn es richtig liegt, und katastrophal, wenn es falsch liegt — ein einziges Teil in diesem Test-Set kostet es allein 27,63 nats. Die beiden landen über entgegengesetzte Wege bei fast derselben Gesamtsumme, und das trainierte Modell, dessen Wahrscheinlichkeiten auf die Daten kalibriert sind, liegt dreimal darunter.
Das ist die schärfste Art, den Unterschied zwischen einem Loss und einer Metrik zu formulieren. Der Loss ist das, was du optimierst: Er muss differenzierbar sein, und er sieht alles, was das Modell gesagt hat, inklusive seiner Sicherheit. Die Metrik ist das, woran du gemessen wirst: Sie kann eine Sprungfunktion sein, eine Geschäftsregel, eine Zählung verpasster Defekte. Sie sind nicht dasselbe Objekt und stimmen nicht immer überein — deshalb definierst du beide, bevor du anfängst, und lässt den Loss nie als Ersatz für die Metrik gelten, nur weil er gerade auf dem Bildschirm steht.
Die dumme Baseline kommt zuerst
Link zum Abschnitt: Die dumme Baseline kommt zuerstVor jedem Modell steht die Anforderung: Wie schneidet die faulste mögliche Antwort ab? Auf diesem Band: immer „in Ordnung“ sagen:
always-say-fine baseline: accuracy = 0.9815
confusion (tn, fp, fn, tp) = (3926, 0, 74, 0)98,15 %. Jetzt das trainierte logistische Modell beim Standardschwellenwert von 0,5:
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 %. Es hat die Baseline um 0,15 Prozentpunkte geschlagen, und jeder Bericht, der bei accuracy stehen bleibt, wird das einen Gewinn nennen. Die Konfusionsmatrix sagt, was tatsächlich passiert ist:
| vorhergesagt in Ordnung | vorhergesagt fehlerhaft | |
|---|---|---|
| tatsächlich in Ordnung | 3,924 | 2 |
| tatsächlich fehlerhaft | 66 | 8 |
Es fand 8 fehlerhafte Teile von 74 und ließ 66 durch. Drei Zahlen benennen die drei Arten, diese Tabelle zu lesen:
- Precision . Von den Teilen, die es markiert hat: Wie viele waren wirklich fehlerhaft? Das sind die Kosten verschwendeter Inspektionen.
- Recall . Von den fehlerhaften Teilen: Wie viele hat es erwischt? Das sind die Kosten, ein schlechtes Teil an einen Kunden zu liefern.
- F1 , ihr harmonisches Mittel, das nahe bei der kleineren der beiden Zahlen bleibt und sich deshalb nicht von einer allein schmeicheln lässt.
Was zählt, hängt von der Fabrik ab, nicht von der Mathematik: Eine Inspektion kostet ein paar Sekunden und ein ausgelieferter Defekt kostet eine Rückrufmeldung, also dominiert hier Recall, und 0,108 ist ein Fehlschlag.
Aber das Modell ist nicht das Problem. Der Schwellenwert ist es, und der Schwellenwert ist kein Teil des Modells — er ist eine Geschäftsentscheidung, die nachträglich auf eine Wahrscheinlichkeit angewendet wird. Sweep ihn:
| Schwellenwert | TP | FP | FN | accuracy | precision | recall | F1 |
|---|---|---|---|---|---|---|---|
| 0.500 | 8 | 2 | 66 | 0.9830 | 0.800 | 0.108 | 0.190 |
| 0.200 | 27 | 28 | 47 | 0.9812 | 0.491 | 0.365 | 0.419 |
| 0.100 | 42 | 118 | 32 | 0.9625 | 0.263 | 0.568 | 0.359 |
| 0.050 | 54 | 236 | 20 | 0.9360 | 0.186 | 0.730 | 0.297 |
| 0.020 | 67 | 570 | 7 | 0.8558 | 0.105 | 0.905 | 0.188 |
| 0.005 | 71 | 1,360 | 3 | 0.6593 | 0.050 | 0.959 | 0.094 |
Lies die accuracy-Spalte nach unten. Sie fällt die ganze Zeit — von 98,30 % auf 65,93 % — während das Modell von 8 gefundenen Defekten auf 71 von 74 kommt. Alles Nützliche, was dieses Modell tun kann, macht seine accuracy schlechter. Ein Team, das die Schlagzeilenzahl optimiert, würde die Version ausliefern, die nichts findet.
Details anzeigen
Class Weighting erzeugt kein Signal, es verschiebt den Betriebspunkt. Der übliche erste Reflex bei unausgeglichenen Klassen ist, die seltene Klasse im Loss zu gewichten. Wenn man das mit Gewichten von 1, 10 und 60 auf den Positiven tut:
| Gewicht auf Positiven | accuracy | precision | recall | F1 | AUC |
|---|---|---|---|---|---|
| 1 | 0.9830 | 0.800 | 0.108 | 0.190 | 0.9363 |
| 10 | 0.9605 | 0.253 | 0.581 | 0.352 | 0.9361 |
| 60 | 0.8290 | 0.091 | 0.919 | 0.166 | 0.9361 |
Precision und Recall bewegen sich weit. Die AUC — die Wahrscheinlichkeit, dass das Modell ein zufälliges fehlerhaftes Teil über ein zufälliges gutes Teil rankt, und die den Schwellenwert vollständig ignoriert — bewegt sich um 0,0002, also gar nicht. Reweighting hat dasselbe Modell entlang derselben Trade-off-Kurve verschoben. Das ist oft genau das, was du willst, und es ist nie neue Information: Wenn das Ranking schlecht ist, rettet es kein Gewichtungsschema.
Drei Splits und das Leak, das du gleich findest
Link zum Abschnitt: Drei Splits und das Leak, das du gleich findestWarum drei Splits und nicht zwei? Weil in dem Moment, in dem du eine Menge von Beispielen benutzt, um irgendetwas auszuwählen — einen Schwellenwert, eine Lernrate, welches von sechs Modellen du auslieferst —, diese Menge fürs Fitting verwendet wurde und ihr Score nicht mehr unverzerrt ist.3 Auf diesem Band gemessen: Ein Sweep des Schwellenwerts auf dem Validation-Set wählt 0,196, und das Modell erzielt anschließend F1 = 0,4122 auf dem unberührten Test-Set. Wäre der Sweep direkt auf dem Test-Set gelaufen, wäre dort 0,4186 das beste erreichbare Ergebnis gewesen — eine Zahl, die niemand berichten darf.
Die Lücke ist hier klein, 0,006, weil ein Hyperparameter einmal gegen 4.000 Validation-Beispiele gesweept wurde. Sie wächst mit jeder zusätzlichen Entscheidung und jeder Verkleinerung des Validation-Sets. Beachte außerdem: Die Richtung ist in einem einzelnen Lauf nicht garantiert. Der gewählte Schwellenwert erzielte 0,3902 auf Validation und 0,4122 auf Test, also hat Validation ihn diesmal unterschätzt. Der Bias ist über viele Entscheidungen systematisch, nicht in einer sichtbar.4
Jetzt die Übung. Das Bandprotokoll kommt mit einer dritten Spalte, station_seconds: wie lange jedes Teil an der Prüfstation verbracht hat. Sie hinzuzufügen ist eine Ein-Zeilen-Änderung im Preprocessing. Das bewirkt sie:
| Modell | accuracy | precision | recall | F1 | Cross-Entropy | AUC |
|---|---|---|---|---|---|---|
| Breite + Gewicht | 0.9830 | 0.800 | 0.108 | 0.190 | 0.0564 | 0.9363 |
| + station_seconds | 0.9920 | 0.792 | 0.770 | 0.781 | 0.0236 | 0.9970 |
Recall steigt von 10,8 % auf 77,0 %. F1 vervierfacht sich mehr als. Und beachte, was accuracy gemacht hat: 98,30 % → 99,20 %, ein Gewinn von neun Zehntelpunkten, genau die Art Zahl, die in einer Zusammenfassungsfolie zu „so oder so etwa 99 %“ gerundet wird. Accuracy hat den Fehlschlag vorher nicht gesehen und sieht jetzt den Betrug nicht.
Bevor du weiterliest: Das Modell schummelt. Finde heraus, wie.
Wie du ein Leak jagst, in der Reihenfolge, die es am schnellsten findet.
-
Vergleiche Train und Test. Overfitting zeigt sich als große Lücke. Hier: ehrliches Modell 0,9838 Train / 0,9830 Test; leaky Modell 0,9936 Train / 0,9920 Test. Beide Lücken liegen unter 0,2 Punkten. Ein Leak sieht nicht wie Overfitting aus — das leaky Feature ist zur Testzeit genauso verfügbar, also generalisiert das Modell hervorragend in eine Welt, die nicht existiert.
-
Trainiere ein Modell pro Feature, allein. Alles, was die Antwort trägt, wird sich selbst verraten:
Feature allein accuracy recall F1 AUC Breite 0.9815 0.014 0.026 0.8691 Gewicht 0.9815 0.000 0.000 0.7914 station_seconds0.9850 0.405 0.500 0.9960 Eine Spalte rankt Defekte ganz allein mit AUC 0,9960. Zwei Messungen mit Messschieber und Waage schaffen 0,87 und 0,79. Diese Asymmetrie ist der Alarm.
-
Frag, wann jede Zahl aufgeschrieben wurde. Mittlere Verweildauer: 2,23 Sekunden für Teile, die bestanden haben, 15,56 Sekunden für Teile, die durchgefallen sind. Natürlich. Ein Teil verweilt an der Station, weil ein Prüfer es vom Band genommen hat — was danach passiert, und nur deshalb, weil jemand entschieden hat, dass es fehlerhaft war. Die Spalte ist keine Messung des Teils. Sie ist eine Messung des Urteils.
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()) Die hervorgehobene Zeile ist das Leak: Die Verweildauer eines fehlerhaften Teils wird aus einer anderen Verteilung gezogen, weil ein Mensch es vom Band genommen hat. Das ist der häufigste schwere Bug im angewandten Machine Learning, und er hat einen Namen: Target Leakage — Information in den Trainingsfeatures, die in dem Moment, in dem die Vorhersage gemacht werden muss, nicht verfügbar wäre.5 Es wirft keine Exception. Es erzeugt eine bessere Zahl. Jeder Anreiz in einem Projekt zeigt darauf, es zu behalten.
Die Verteidigung ist eine Frage, gestellt an jede Spalte: Existiert dieser Wert in dem Augenblick, in dem ich diese Vorhersage brauche, schon? Auf einem Live-Band ist station_seconds unbekannt, bis das Teil geprüft wurde — also bis nach dem Vorgang, den das Modell ersetzen sollte.
Wie viele Testbeispiele brauche ich?
Link zum Abschnitt: Wie viele Testbeispiele brauche ich?Angenommen, du bewertest ein Modell auf 20 Beispielen und es liegt 17-mal richtig. Du berichtest 85 %.
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.6477Die ehrliche Lesart von 17/20 ist irgendwo zwischen 64 % und 95 %. Ein echtes 65-%-Modell erzeugt dieses Ergebnis in 4,4 % der Fälle — ein Lauf von dreiundzwanzig — und wenn du eine Handvoll prompts ausprobiert und den besten berichtet hast, hast du diesen Lauf selbst hergestellt. Siebzehn von zwanzig können ein 85-%-Modell nicht von einem 65-%-Modell unterscheiden.
Zwei Wege, ein Intervall um eine Rate zu legen, und beide gehören in dein Werkzeugset:
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)Verwende Wilson6 für eine einfache Erfolgsrate; es verhält sich bei jedem gut und braucht keine Zufälligkeit. Beachte oben, dass bei das obere Ende des Bootstraps 1,0000 ist — beim Resampling von 20 Punkten kann man leicht 20 richtige ziehen, also kann es kein Intervall darstellen, das schmaler ist als seine eigene Körnung. Verwende den Bootstrap7, wo keine Formel existiert, also in den meisten interessanten Fällen: F1, Macro-Durchschnitte, BLEU, pass@1, der Score eines rubrikbasierten Judges. Auf diesem Band trägt der F1 des getunten Modells von 0,4122 ein Bootstrap-Intervall von [0.3009, 0.5156] — und das ist die Zahl, die in den Bericht gehört, weil die Punktschätzung allein zu einem Vergleich einlädt, den sie nicht stützen kann.
Noch eine Messung, weil sie verändert, wie du zwei Modelle vergleichen solltest. Zwei Modelle, bewertet auf denselben 500 Beispielen:
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)Ihre Intervalle überlappen, und die Faustregel — überlappende Fehlerbalken bedeuten keinen signifikanten Unterschied — würde den Vergleich als ergebnislos bezeichnen. Ist er nicht. Die beiden Modelle liefen auf denselben Beispielen, also ist die richtige Größe die Differenz pro Beispiel, deren Intervall [0.0260, 0.0680] ist, komfortabel über null. Sie widersprechen sich nur bei 31 von 500 Items, und A gewinnt 27 dieser Widersprüche; die gemeinsamen Beispiele, leicht wie schwer, kürzen sich heraus, statt Rauschen hinzuzufügen. Vergleiche Modelle gepaart, und du erreichst dieselbe Schlussfolgerung mit einem Bruchteil der Daten.
Wohin es als Nächstes geht
Link zum Abschnitt: Wohin es als Nächstes gehtDu hast jetzt ein Modell, das kalibrierte Wahrscheinlichkeiten ausgibt, einen Loss, der aus einer Behauptung über die Daten abgeleitet wurde statt aus Bequemlichkeit gewählt zu sein, einen Gradienten, der buchstäblich Vorhersage minus Wahrheit ist, und — wichtiger noch — die Mechanik, um herauszufinden, ob irgendetwas davon funktioniert. Das zehnzeilige Wilson-Intervall oben wird wörtlich wiederverwendet: Es trägt die prompt-Varianten in Kapitel 15, die Retrieval-Tabellen in Kapitel 19 und das Golden Set in Kapitel 29. Der Bootstrap ist das, wonach du greifst, wenn keine Formel existiert.
Aber das Modell hat immer noch nur eine Schicht. Es zieht eine Linie, und Kapitel 1 hat mit vier XOR-Zeilen bewiesen, dass eine Linie nicht reicht. Die Lösung ist Stapeln: eine erste Schicht, die den Raum biegt, eine zweite, die im gebogenen Raum die Linie zieht.
Dort endet der saubere Gradient dieses Kapitels. Alles oben funktionierte, weil einmal von Hand aufgeschrieben werden konnte, für ein Modell mit einer Schicht zwischen Eingabe und Loss. Setze eine zweite Schicht in die Mitte, und die Frage ändert ihre Form: Was ist die Ableitung des Loss nach einem Gewicht, das die Ausgabe überhaupt nicht berührt — dessen Einfluss nur durch eine andere Schicht ankommt, vielleicht auf mehreren Pfaden zugleich?
Diese Ableitung existiert. Sie von Hand zu berechnen ist für alles Größere als ein Spielzeug hoffnungslos, und sie Parameter für Parameter zu berechnen ist auf einer anderen Skala hoffnungslos. Was gebraucht wird, ist ein Verfahren, das jede Ableitung im Netzwerk aus einem einzigen Rückwärtsdurchlauf über denselben Graphen gewinnt, den der Vorwärtsdurchlauf gerade durchlaufen hat.
Das ist Kapitel 5, und es ist der Motor, auf dem der Rest dieses Kurses läuft.
Quellen und Methode
Link zum Abschnitt: Quellen und MethodeEbenfalls lohnend neben diesem Kapitel: Bishop, Pattern Recognition and Machine Learning §1.2, §1.5, §1.6 und §4.3, das Wahrscheinlichkeit, Entscheidungstheorie, Informationstheorie und lineare Klassifikation in der Reihenfolge behandelt, der dieses Kapitel folgt; Murphy, Probabilistic Machine Learning: An Introduction, Kapitel 6 und 10; Prince, Understanding Deep Learning §5.4–5.7; sowie Saito und Rehmsmeier, The Precision-Recall Plot Is More Informative than the ROC Plot When Evaluating Binary Classifiers on Imbalanced Datasets (PLOS ONE, 2015) — warum die oben zitierte AUC nicht die einzige schwellenwertfreie Zahl sein sollte, auf die du schaust, wenn 1,7 % der Teile fehlerhaft sind.
Referenzen
Link zum Abschnitt: Referenzen-
Ma, T. und Ng, A. CS229 Lecture Notes, Stanford University, Kapitel 2 und 3. Dort hört die Kürzung, die erzeugt, auf, wie Glück auszusehen: Wähle die Exponentialfamilien-Verteilung, die zu deiner Ausgabe passt, verwende ihren kanonischen Link, und der Gradient ist immer Vorhersage minus Wahrheit. ↩
-
Olah, C. Visual Information Theory (2015),
colah.github.io/posts/2015-09-Visual-Information. Die klarste verfügbare Darstellung von Entropie, Cross-Entropy und KL-Divergenz als Kosten in Bits statt als Formeln. ↩ -
Abu-Mostafa, Y. S., Magdon-Ismail, M. und Lin, H.-T. Learning From Data (AMLBook, 2012), Vorlesungen 13 und 17 des Caltech-Kurses. Vorlesung 13 behandelt Validation; Vorlesung 17, über die drei Lernprinzipien, benennt data snooping. Zusammen sind sie die Quelle der Disziplin in diesem Kapitel: Jeder Blick auf ein Dataset ist eine Fitting-Entscheidung, ob du einen Optimierer ausgeführt hast oder nicht. ↩
-
James, G., Witten, D., Hastie, T. und Tibshirani, R. An Introduction to Statistical Learning, 2. Auflage (Springer, 2021), Kapitel 2 und 5, für die Bias-Variance-Zerlegung und für Resampling. Der Begleitband ist der Ort, an dem die Auswahlfalle ausdrücklich formuliert wird: Hastie, Tibshirani und Friedman, The Elements of Statistical Learning, 2. Auflage, §7.10.2, The Wrong and Right Way to Do Cross-validation. ↩
-
Kaufman, S., Rosset, S., Perlich, C. und Stitelman, O. Leakage in Data Mining: Formulation, Detection, and Avoidance. ACM Transactions on Knowledge Discovery from Data 6(4), 2012. Eine formale Behandlung des oben demonstrierten Fehlers, mit Fallstudien aus Wettbewerben, die ein Modell gewann, weil es ein Artefakt der Datenerstellung gelernt hatte. ↩
-
Wilson, E. B. Probable Inference, the Law of Succession, and Statistical Inference. Journal of the American Statistical Association 22(158), S. 209–212 (1927). Das Score-Intervall, das oben in
wilson()verwendet wurde, immer noch der richtige Default für einen Anteil. Das Lehrbuchintervall ist das, das du vermeiden solltest: Es liefert Unsinn nahe 0 und 1 und unterdeckt stark bei kleinem . ↩ -
Efron, B. Bootstrap Methods: Another Look at the Jackknife. The Annals of Statistics 7(1), S. 1–26 (1979). Die Idee, mit der du ein Intervall um jede Statistik legen kannst, die du berechnen kannst — auch um solche ohne Sampling-Theorie. ↩