R Code [zeigen / verbergen]
::p_load(tidyverse, tidymodels, magrittr, conflicted)
pacmanconflicts_prefer(magrittr::extract)
Letzte Änderung am 23. March 2024 um 21:46:15
Ein Kapitel nur über Daten? Haben wir nicht schon ein Kapitel über Daten und deren Verteilung? Hier soll es aber nicht um Daten und deren Verteilungen gehen. In diesem Kapitel beschäftigen wir uns mit der Idee der Trainings-, Test- und Validierungsdaten. Wir brauchen alle drei Arten von Daten um zum einen auf den Trainingsdaten unsere Modelle zu trainieren und dann am Ende auf den Testdaten unsere Klassifikation zu überprüfen. Dazwischen nutzen wir unsere Validierungsdaten für die Optimierung oder das Tuning des Modells. Der erste Schritt ist aber immer sich einen Traingsdaten und einen Testdatensatz zu erschaffen. Wie wir das machen, wollen wir uns hier in diesem Kapitel einmal von vielen Seiten annähern. Wir nutzen im ganzen Kapitel das R Paket {resample}
. Es gibt sicherlich noch andere Möglichkeiten sich die Daten zu teilen, aber das R Paket {resample}
hat einige Vorteile, da das Paket auch im Universium der {tidymodels}
angesiedelt ist.
Wir wollen folgende R Pakete in diesem Kapitel nutzen.
::p_load(tidyverse, tidymodels, magrittr, conflicted)
pacmanconflicts_prefer(magrittr::extract)
An der Seite des Kapitels findest du den Link Quellcode anzeigen, über den du Zugang zum gesamten R-Code dieses Kapitels erhältst.
In dieser Einführung nehmen wir die infizierten Ferkel als Beispiel um einmal die verschiedenen Verfahren zu demonstrieren. Ich füge hier noch die ID mit ein, die nichts anderes ist, als die Zeilennummer. Dann habe ich noch die ID an den Anfang gestellt. Auch brauchen wir nicht alle Spalten, da wir hier um die Zeilen und damit die Beobachtungen geht.
<- read_excel("data/infected_pigs.xlsx") |>
pig_tbl mutate(pig_id = 1:n()) |>
select(pig_id, infected, age:crp) |>
select(pig_id, infected, everything())
In Tabelle 73.1 siehst du nochmal einen Auschnitt aus den Daten. Wir haben noch die ID mit eingefügt, damit wir einzelne Beobachtungen nachvollziehen können.
pig_id | infected | age | sex | location | activity | crp |
---|---|---|---|---|---|---|
1 | 1 | 61 | male | northeast | 15.31 | 22.38 |
2 | 1 | 53 | male | northwest | 13.01 | 18.64 |
3 | 0 | 66 | female | northeast | 11.31 | 18.76 |
4 | 1 | 59 | female | north | 13.33 | 19.37 |
5 | 1 | 63 | male | northwest | 14.71 | 21.57 |
6 | 1 | 55 | male | northwest | 15.81 | 21.45 |
… | … | … | … | … | … | … |
407 | 1 | 54 | female | north | 11.82 | 21.5 |
408 | 0 | 56 | male | west | 13.91 | 20.8 |
409 | 1 | 57 | male | northwest | 12.49 | 21.95 |
410 | 1 | 61 | male | northwest | 15.26 | 23.1 |
411 | 0 | 59 | female | north | 13.13 | 20.23 |
412 | 1 | 63 | female | north | 10.01 | 19.89 |
Gehen wir jetzt mal die verschiedenen Datensätze und Begrifflichkeiten, die wir für das maschinelle Lernen später brauchen, einmal durch.
Um zu beginnen, teilen wir unseren einen Datensatz in zwei: einen Trainingssatz und einen Testsatz. Die meisten Zeilen und damit Beobachtungen des Originaldatensatzes werden im Trainingssatz sein. Wir nutzen die Trainingsdaten zum Anpassen des Modells. Wir trainieren das Modell auf den Daten des Trainingsdatensatzes. Wir messen dann das Modell auf den Testdatensatz. Warum machen wir das? Wenn wir auf dem Trainingsdatensatz auch die Modelgüte testen würden, dann könnten wir eine Überanpassung (eg. overfitting) auf die Trainingsdaten beobachten. Das Modell ist so gut an die spezifischen Trainingsdaten angepasst, dass es mit neuen Daten schwer umgehen kann.
Das R Paket {resample}
stellt die Common Resampling Patterns nochmal da. Auch findest unter Resampling for Evaluating Performance noch eine Menge mehr Ideen für das Resampling.
Zu diesem Zweck können wir das R Paket {rsample}
verwenden. Wir nutzen dann die Funktion initial_split()
um die Daten in einen Trainingsdatensatz und einen Testdatensatz aufzuteilen. Dann müssen wir noch den Trainingsdatensatz und den Testdatensatz einmal getrennt in einem Objekt abspeichern.
<- initial_split(pig_tbl, prop = 3/4)
pig_split
pig_split
<Training/Testing/Total>
<309/103/412>
Wie wir sehen, sehen wir gar nichts. Das ist auch so gewollt. Da wir im maschinellen Lernen gerne mal mit Datensätzen mit mehreren tausend Zeilen arbeiten würde es wenig helfen, wenn wir gleich alles auf der R Console ausgegeben kriegen. Die Information wie viel wir in den jeweiligen Gruppen haben, hilft schön genug.
<- training(pig_split)
train_pig_tbl <- testing(pig_split) test_pig_tbl
Nun haben wir die beiden Datensätze jeweils separat und können auf dem Trainingsdatensatz die jeweiligen Algorithmen bzw. Modelle trainieren.
Es ist schön, wenn wir Funktionen wie initial_split()
, die für uns die Arbeit machen. Wir haben dann aber auch sehr schnell das Gefühl mit einer Black Box zu arbeiten. Man weiß gar nicht, was da eigentlich passiert ist. Deshalb hier nochmal der Code, den ich dann auch immer zur Demonstration nutze. Wenn wir eine ID Spalte haben, dann können wir auch über die Funktion sample_frac()
und dem Anteil der ausgewählten Beobachtungen und der Funktion anti_join()
, die Trainings- und Testdaten erstellen.
<- pig_tbl |> sample_frac(0.75)
pig_train_tbl <- anti_join(pig_tbl,
pig_test_tbl by = 'pig_id') pig_train_tbl,
Wir können dann auch überprüfen, ob wir die gleichen Anteile von den infizierten Ferkeln in den jeweiligen Datensätzen haben. Wir berechnen dafür einfach die relativen Anteile. Ein wenig komplizierter als nötig, aber hier geht es jetzt um die Veranschaulichung.
table(pig_train_tbl$infected)/sum(table(pig_train_tbl$infected))
0 1
0.3430421 0.6569579
table(pig_test_tbl$infected)/sum(table(pig_test_tbl$infected))
0 1
0.2912621 0.7087379
Du kannst die Generierung häufiger wiederholen und du wirst sehen, dass wir es mit einem Zufallsprozess zu tun haben. Mal sind die Anteile ähnlicher mal eher nicht. Das ist dann auch der Grund warum wir unsere Modelle tunen müssen und Modelle häufig wiederholt rechnen und die Ergebnisse dann zusammenfassen.
Die finalen Modelle sollten nur einmal anhand ihres Testdatensatzes evaluieren werden. Das Überpfrüfen auf dem Testdatensatz geschieht nachdem die Optimierung und das Training der Modelle vollständig abgeschlossen ist. Was natürlich für uns nicht so schön ist, wir wollen ja auch zwischendurch mal schauen, ob wir auf dem richtigen Weg mit dem Training sind. Wir solle es auch sonst mit dem Tuning funktionieren? Deshalb ist möglich, zusätzliche Datensätze aus dem Trainingsprozess herauszuhalten, die zur mehrmaligen Evaluierung von Modellen verwendet werden können. Das machen wir dann solange bis wir bereit sind anhand des endgültigen Testsatzes zu evaluieren.
Diese zusätzlichen, aufgeteilten Datensätze werden oft als Validierungssätze bezeichnet und können in über die Funktion validation_split()
erstellt werden.
<- validation_split(pig_tbl, prop = 0.8) val_pig_lst
Warning: `validation_split()` was deprecated in rsample 1.2.0.
ℹ Please use `initial_validation_split()` instead.
val_pig_lst
# Validation Set Split (0.8/0.2)
# A tibble: 1 × 2
splits id
<list> <chr>
1 <split [329/83]> validation
In diesem Fall lassen wir den Validierungsdatensatz einmal so in der Liste stehen. Es ist faktisch wider ein Split der Daten, nur das wir jetzt auf diesem Datensatz unser Modell während des Tunings testen.
Bei der Abstimmung von Hyperparametern und der Modellanpassung ist es oft nützlich, das Modell anhand von mehr als nur einem einzigen Validierungssatz zu bewerten, um eine stabilere Schätzung der Modellleistung zu erhalten. Wir meinen hier mit Hyperparametern die Optionen, die ein Algorithmus hat um diesen Algorithmus zu optimieren. Aus diesem Grund verwenden Modellierer häufig ein Verfahren, das als Kreuzvalidierung bekannt ist und bei dem die Daten mehrfach in Analyse- und Valisierungsdaten aufgeteilt werden.
Die vielleicht häufigste Methode der Kreuzvalidierung ist die \(V\)-fache Kreuzvalidierung. Bei dieser auch als \(k\)-fold cross-validation bezeichneten Methode werden \(V\) neue Stichproben bzw. Datensätze erstellt, indem die Daten in \(V\) Gruppen (auch folds genannt) von ungefähr gleicher Größe aufgeteilt werden. Der Analysesatz jeder erneuten Stichprobe besteht aus \(V-1\) Gruppen, wobei die verbleibende Gruppe als Validierungsdatensatz verwendet wird. Insgesamt führen wir dadurch dann den Algorithmus \(V\)-mal durch. Auf diese Weise wird jede Beobachtung in Daten in genau einem Beurteilungssatz verwendet.
In R können wir dafür die Funktion vfold_cv()
nutzen. Im Folgenden einmal Split für \(V = 5\). Wir führen also eine \(5\)-fache Kreuzvalidierung durch.
vfold_cv(pig_tbl, v = 3)
# 3-fold cross-validation
# A tibble: 3 × 2
splits id
<list> <chr>
1 <split [274/138]> Fold1
2 <split [275/137]> Fold2
3 <split [275/137]> Fold3
Als ein Nachteil wird oft angesehen, dass die Kreuzvalidierung eine hohe Varianz in den Daten verursacht. Dagegen hilft dann die wiederholte Kreuzvalidierung (eng. repeated cross-validation). Wir bauen in jede Kreuzvalidierung nochmal eine oder mehr Wiederholungen ein. In unserem Fall dann drei Wiederholungen je Kreuzvalidierung \(V\).
vfold_cv(pig_tbl, v = 3, repeats = 2)
# 3-fold cross-validation repeated 2 times
# A tibble: 6 × 3
splits id id2
<list> <chr> <chr>
1 <split [274/138]> Repeat1 Fold1
2 <split [275/137]> Repeat1 Fold2
3 <split [275/137]> Repeat1 Fold3
4 <split [274/138]> Repeat2 Fold1
5 <split [275/137]> Repeat2 Fold2
6 <split [275/137]> Repeat2 Fold3
Wir sehen das der Split ungefähr immer gleich groß ist. Manchmal haben wir durch die Trennung eine Beobachtung mehr in dem Analysedatensatz mit \(n = 329\) oder \(n = 330\) Beobachtungen. Dementsprechend hat der Validierungsdatensatz einmal \(n = 82\) und einmal \(n = 83\) Beobachtungen.
Wir haben als eine Alternative zur V-fachen Kreuzvalidierung die Monte-Carlo-Kreuzvalidierung vorliegen. Während bei der V-fachen Kreuzvalidierung jede Beobachtung in den Daten einem - und zwar genau einem - Validierungsdatensatz zugewiesen wird, wird bei der Monte-Carlo-Kreuzvalidierung für jeden Validierungsdatensatz eine zufällige Teilmenge der Daten ausgewählt, d. h. jede Beobachtung kann in 0, 1 oder vielen Validierungsdatensätzen verwendet werden. Der Analysesatz besteht dann aus allen Beobachtungen, die nicht ausgewählt wurden. Da jeder Validierungsdatensatz unabhängig ausgewählt wird, können wir diesen Vorgang so oft wie gewünscht wiederholen. Das stimt natürlich nur bedingt, denn irgendwann haben wir auch bei perfekter Permutation dann Wiederholungen der Datensätze.
Die Funktion mc_cv()
liefert uns dann die Datensätze für die Monte-Carlo Kreuzvalidierung. Wir geben dabei an, wieviel der Daten in den jeweiligen Datensatz hinein permutiert werden soll.
mc_cv(pig_tbl, prop = 0.6, times = 3)
# Monte Carlo cross-validation (0.6/0.4) with 3 resamples
# A tibble: 3 × 2
splits id
<list> <chr>
1 <split [247/165]> Resample1
2 <split [247/165]> Resample2
3 <split [247/165]> Resample3
Die letzte Stichprobengenierungsmethode ist der Bootstrap. Eine Bootstrap Stichprobe ist eine Stichprobe des Datensatzes mit der gleichen Größe wie der Datensatz. Nur werden die Bootstrap Stichproben mit Ersetzung gezogen, so dass eine einzelne Beobachtung mehrfach in die Stichprobe aufgenommen werden können. Der Validierungsdatensatz besteht dann aus allen Beobachtungen, die nicht für den Analysesatz ausgewählt wurden. Im Allgemeinen führt das Bootstrap-Resampling zu pessimistischen Schätzungen der Modellgenauigkeit.
Wir können die Funktion bootstraps()
für die Generierung der Bootstrap Stichprobe nutzen.
<- pig_tbl |>
pig_boot_tbl extract(1:10, 1:5)
<- bootstraps(pig_boot_tbl, times = 3) pig_boot
Nun haben wir auch die Möglichkeit uns die einzelnen Bootstraps Stichproben mit pluck()
rauszuziehen. Hier sehen wir auch, dass einzelne Beobachtungen doppelt in der Bootstrap Stich probe vorkommen.
pluck(pig_boot, "splits", 1) |>
as_tibble()
# A tibble: 10 × 5
pig_id infected age sex location
<int> <dbl> <dbl> <chr> <chr>
1 10 1 57 male northwest
2 3 0 66 female northeast
3 9 1 58 female west
4 1 1 61 male northeast
5 1 1 61 male northeast
6 2 1 53 male northwest
7 8 0 53 male northwest
8 7 1 49 male west
9 2 1 53 male northwest
10 6 1 55 male northwest
Neben den hier vorgestellten Varianten gibt es noch weitere Möglichkeiten in dem R Paket {rsample}
sich Stichprobendatensätze zu generieren. Wir gehen jetzt hier nicht mehr im Detail auf die verschiedenen Möglichkeiten ein. Dafür dann einfach die Links auf die {rsample}
Hilfeseite nutzen.
Am Ende musst du entscheiden, welche der Resamplingmethoden für dich am besten geeignet ist. Wir müssen eben einen Trainingsdatensatz und einen Testdatensatz haben. Die Validierungsdaten dienen dann zum Tuning deiner Modelle. Nicht immer nutzen wir auch Validierungsdatensätze. In dem einfachsten Anwendungsfall nutzt du immer wieder deine Traingsdaten mit unterschiedlichen Einstellungen in deinem Algorithmus.