Zum Inhalt
wannacode

Kreuzvalidierung

Kurz gesagt: Die Kreuzvalidierung teilt die Daten in mehrere Folds und trainiert und prüft das Modell viele Male; sie liefert einen mittleren Wert mit seiner Streuung statt einer einzigen Glückszahl.

Problem

„Der Baum oder die Gerade?“

Der Shop will wissen, wie viele Bestellungen jeder Kunde in den nächsten 4 Monaten aufgeben wird. Damit entscheidet er, wem er eine Rabattkarte schickt und wie viel Ware er bestellt. Die Merkmale kommen aus den Bestellungen vor dem 1. Juli 2026, wie in der Lektion über Klassifikation: Zahl der Bestellungen, Tage seit der letzten, wie lange er schon kauft und die durchschnittliche Bestellung. Die Zielgröße ist die Zahl der Bestellungen vom 1. Juli bis 31. Oktober.

Du sagst „lineare Regression“, und dein Kollege sagt „ein Baum mit 3 Ebenen“. Du lässt beide auf 6 verschiedenen Aufteilungen in Training und Test laufen (random_state von 0 bis 5) und misst den MAE in Bestellungen pro Kunde:

PythonDaten: die Tabellen des Shops

Es sind 1.279 Kunden, und sie geben in den 4 Monaten im Schnitt 2,70 Bestellungen auf. Bei den Aufteilungen 0, 1, 2 und 4 liegt der Baum weniger daneben, bei 3 und 5 die Gerade. Hättest du nur die Aufteilung 2 laufen lassen, hätte dein Kollege mit 1,25 gegen 1,31 gewonnen. Bei der Aufteilung 5 gewinnst du. Wer hat recht?

Die Idee

Eine Prüfung ist eine Stichprobe

Die Testdaten sind 256 Kunden, zufällig ausgewählt. Wie der Mittelwert aus 30 Bestellungen in der Lektion über Stichproben rauscht auch der Fehler auf 256 Kunden: Eine andere Stichprobe gibt eine andere Zahl. Der Fehler der Geraden springt von 1,31 auf 1,43 allein dadurch, welche Kunden im Test gelandet sind.

Der Abstand zwischen den beiden Modellen beträgt ein paar Hundertstel. Das Rauschen der Aufteilung liegt bei etwa zehn Hundertsteln. Mit einer Prüfung kannst du nicht sagen, welches besser ist, genauso wenig wie du aus 30 Bestellungen die durchschnittliche Bestellung bestimmen kannst.

1. Bei der Aufteilung 0 liegt der Baum um 1,31 Bestellungen daneben und die Gerade um 1,35. Was folgt daraus?

Die Idee

Kreuzvalidierung

Die Lösung ist, viele Male zu prüfen, mit jedem Kunden genau einmal im Test. Du teilst die Kunden in 5 Folds (Teilmengen). Du trainierst mit 4 und misst am fünften, dann wechselst du, welcher der Test ist. Du bekommst 5 Fehler für jedes Modell. Das heißt Kreuzvalidierung.

KFold teilt in Folds, und cross_val_score trainiert und misst das Modell an jedem. Mit shuffle=True werden die Kunden vor dem Teilen gemischt:

PythonDaten: die Tabellen des Shops

Viermal lernst du an 1.023 Kunden und prüfst an 256, einmal an 1.024 und 255. scikit-learn sucht immer die größte Zahl, deshalb kommt der Fehler mit einem Minus (neg_mean_absolute_error), und das Minus davor dreht ihn zurück.

Die Baseline (der Mittelwert, DummyRegressor) liegt um 2,70 ± 0,09 daneben. Die Gerade liegt um 1,34 ± 0,06 daneben und der Baum um 1,38 ± 0,09. Die Zahl nach dem ± ist die Streuung der fünf Fehler (std). Beide Modelle schlagen die Baseline klar, und der Abstand zwischen ihnen (0,04) ist kleiner als die Streuung. Der ehrliche Schluss lautet „die Gerade ist etwas besser, aber nicht eindeutig“. Wenn zwei Modelle so nah beieinander liegen, nimmst du das einfachere.

Genauer, für Neugierige

Beide Modelle werden auf denselben Folds verglichen: Deshalb ist kf ein einziges Objekt mit einem random_state. cv=5 teilt ebenfalls in 5, aber ohne Mischen: Der erste Fold sind die ersten 256 Kunden der Reihe nach. Ist die Tabelle sortiert (nach Nummer, nach dem Datum der ersten Bestellung), unterscheiden sich die Folds voneinander und der Wert wird verzerrt.

Probier es aus

Ist der Wald besser?

Dein Kollege schlägt einen Random Forest vor (RandomForestRegressor mit 50 Bäumen, max_depth=5). Prüfe ihn mit Kreuzvalidierung auf denselben Folds kf. Lege in forest_mae und line_mae die fünf Fehler (MAE, positiv) des Waldes und der Geraden ab, in gap den Unterschied im mittleren Fehler (Wald minus Gerade) und in spread die Streuung der Fehler des Waldes.

PythonDaten: die Tabellen des Shops

Die Idee

Überanpassung

Warum hat der Baum 3 Ebenen und nicht 20? Je tiefer er ist, desto feiner teilt er die Kunden. Hier ist der Fehler auf den Trainingskunden und auf den Testkunden für Tiefen von 1 bis 12:

PythonDaten: die Tabellen des Shops

Auf den Trainingskunden fällt der Fehler jedes Mal: von 1,87 auf 0,41. Auf den Testkunden fällt er bis zur Tiefe 3 (1,50) und wächst dann auf 1,91. Ein Baum ohne Begrenzung erreicht 23 Ebenen und liegt bei den Trainingskunden um 0,001 daneben, fast null, und bei den Testkunden um 2,00.

Der tiefe Baum hat nicht mehr über die Kunden gelernt. Er hat jeden Trainingskunden samt seinem Rauschen auswendig gelernt: Ein Kunde hat zufällig mehr bestellt, ein anderer weniger. Das heißt Überanpassung. Das Zeichen ist ein großer Abstand zwischen dem Trainingsfehler und dem Testfehler. Deshalb sagt der Fehler auf den Daten, aus denen das Modell gelernt hat, nichts über neue Kunden.

Probier es aus

Wähle die Tiefe

Wähle die Tiefe des Baums mit Kreuzvalidierung. Lege in cv_mae (eine Series mit der Tiefe von 1 bis 10 als Index) den mittleren MAE aus cross_val_score mit den Folds kf ab und in best_depth die Tiefe mit dem kleinsten Fehler.

PythonDaten: die Tabellen des Shops

Die Idee

Wenn die Reihenfolge die Zeit ist

Kunden kann man mischen: Ein Kunde weiß nichts von einem anderen. Tage nicht. Wenn du die Bestellungen für morgen vorhersagst, hat das Modell nur die Vergangenheit. Gemischte Folds würden es auch an Tagen nach den Testtagen trainieren, und das ist das Datenleck aus der vorigen Lektion.

TimeSeriesSplit teilt nach der Zeit: Es lernt am Anfang und prüft an den folgenden Tagen, dann lernt es an mehr und prüft an noch späteren. Ein Beispiel: die Bestellungen eines Tages aus dem Wochentag, den Bestellungen vor 7 Tagen und dem Mittelwert der vorherigen 7 Tage:

PythonDaten: die Tabellen des Shops

Die ersten 7 Tage haben kein „vor 7 Tagen“ und fallen weg, es bleiben 358. Jeder Test-Fold sind 59 Tage nach dem Trainingsteil, und der Trainingsteil wächst: erst 2 Monate, am Ende 10. Die Gerade liegt im Schnitt um 4,51 ± 0,18 Bestellungen pro Tag daneben.

Probier es aus

Schlägt es die Baseline?

Vergleiche die Gerade mit der Baseline (DummyRegressor, dem Mittelwert der Trainingstage) mit TimeSeriesSplit und 5 Folds. Lege in line_mae und base_mae die fünf Fehler (MAE, positiv) von jedem ab.

PythonDaten: die Tabellen des Shops

Die Idee

Der Test wird einmal berührt

Probierst du 10 Modelle an den Testdaten aus und nimmst das beste, war der Test an der Auswahl beteiligt. Eines von zehn hat Glück mit genau diesen Kunden, und sein Fehler sieht besser aus, als er ist.

Die Reihenfolge ist deshalb:

  1. Du legst am Anfang einen Testteil beiseite und schaust nicht hin.
  2. Du wählst das Modell und seine Einstellungen (Tiefe, Merkmale) mit Kreuzvalidierung nur auf den Trainingsdaten.
  3. Du trainierst das gewählte Modell mit dem ganzen Trainingsteil und misst es einmal am Test. Das ist die Zahl, die du dem Chef nennst.
PythonDaten: die Tabellen des Shops

Bei den 1.023 Trainingskunden liegt die Baseline um 2,74 ± 0,17 daneben, die Gerade um 1,32 ± 0,06 und der Baum um 1,36 ± 0,07. Die Gerade hat den kleineren Fehler und ist einfacher: Du wählst sie. Die 256 Testkunden sind noch unberührt.

Genauer, für Neugierige

Änderst du nach dem Test das Modell und misst noch einmal am Test, wird der Test Teil der Auswahl. Dann brauchst du einen neuen Testteil: neue Daten, zum Beispiel den nächsten Monat. Deshalb ist für ein Modell der Bestellungen die ehrlichste Prüfung die Zeit nach dem Training, wie bei TimeSeriesSplit.

Die Idee

Wann man kein Modell braucht

Ein Modell kostet Zeit: Daten, Training, darauf achten, dass es nicht schlechter wird. Manchmal lohnt es sich nicht:

  • Wenige Daten. Bei ein paar Dutzend Kunden springt der Fehler der Kreuzvalidierung so stark, dass du nicht sagen kannst, ob das Modell den Mittelwert schlägt.
  • Eine Regel ohne Modell leistet dasselbe. „Der Kunde wird im selben Tempo kaufen“ ist eine Division. Wenn das Modell sie nicht klar schlägt, ist die Regel besser: Alle verstehen sie, und nichts kann kaputtgehen.
  • Die Kosten eines Fehlers. Ist ein Fehler teuer (du lehnst einen Kredit ab, sperrst eine Karte), muss das Modell sehr sicher sein, oder ein Mensch muss prüfen.
  • Niemand handelt nach der Vorhersage. Folgt aus der Zahl keine Entscheidung (wer einen Gutschein bekommt, wie viel Ware), ist das Modell nur eine Übung.

Die Gerade hat einen Koeffizienten von 0,469 für die bisherige Zahl der Bestellungen, und die anderen Merkmale spielen kaum mit. Es ist also fast „die Hälfte der bisherigen Bestellungen“: 8 Monate davor, 4 Monate danach. Prüf es in der Übung.

Übung

Das Urteil

Die Kreuzvalidierung auf den Trainingsdaten hat die Gerade gewählt. Trainiere sie mit X_train und prüfe sie einmal am Test: Lege in model das trainierte Modell ab, in test_mae seinen Fehler (MAE) auf den Testkunden und in base_mae den Fehler der Baseline im Test.

PythonDaten: die Tabellen des Shops

Schlägt die Gerade eine Regel ohne Modell? Die Regel lautet „der Kunde wird im selben Tempo kaufen“: für 4 Monate die Hälfte seiner Bestellungen aus den 8 Monaten davor. Lege in rule die Vorhersage der Regel für jeden Testkunden ab und in rule_mae ihren Fehler im Test.

PythonDaten: die Tabellen des Shops

Test

Teste dich

1. Warum liefert die Kreuzvalidierung einen verlässlicheren Wert als eine einzelne Aufteilung?
2. Ein Baum ohne Begrenzung liegt bei den Trainingskunden um 0,001 Bestellungen daneben und bei den Testkunden um 2,00. Was bedeutet das?
3. Du sagst die Bestellungen für einen Tag voraus. Warum TimeSeriesSplit und nicht KFold(shuffle=True)?
4. Du hast 10 Modelle an den Testdaten geprüft und das beste genommen. Wie ehrlich ist sein Testfehler?
5. Die Regel „die Hälfte der bisherigen Bestellungen“ liegt um 1,41 daneben, die Gerade um 1,38. Was ist sinnvoll?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·