Machine Learning

Grundlagen

Was ist Machine Learning?

  • Modelle, die anhand von Daten Vorhersagen treffen
  • also z.B. auch lineare oder logistische Regression
  • meistens gemeint: komplexere Modelle bis hin zu Neural Networks

Bias-Variance Tradeoff

Problemstellung

  • Komplexere Modelle fitten besser meine Daten, aber…
  • machen oft schlechtere Vorhersagen bei neuen Daten

WARUM?

Beispieldatensatz

Code
library(ggplot2)
set.seed(123)
# Simuliere ein Polynom 3. Ordnung mit Noise
x <- seq(-3, 3, length.out = 100)
y <- 1.7 * x^3 - 2.3 * x^2 - 3.2 * x + rnorm(100, sd = 2.5)
d <- data.frame(x = x, y = y)
# ziehe eine zufällige Stichprobe von 10 Punkten
s <- sample(seq_len(nrow(d)), 10)
d$sample <- ifelse(seq_len(nrow(d)) %in% s, TRUE, FALSE)

ggplot(d, aes(x = x, y = y, color = sample)) +
    geom_point(size = 4)  +
    scale_color_manual(values = c("grey90", "blue"), labels = c("Nicht in Stichprobe", "In Stichprobe"), name = NULL) +
    theme_bw(base_size = 16)

Unterschiedliche Modellkomplexität

Code
mod1 <- lm(y ~ x, data = d[d$sample, ])
rmse1 <- sqrt(mean(resid(mod1)^2))

ggplot(aes(x = x, y = y), data = d[d$sample, ]) +
    geom_point(color = "blue", size = 4) +
    geom_smooth(method = "lm", color = "red", se = FALSE, formula = y ~ x) +
    labs(title = paste("RMSE:", round(rmse1, 2))) +
    theme_bw() +
    theme(plot.title = element_text(color = "red"))

Code
mod2 <- lm(y ~ poly(x, 2), data = d[d$sample, ])
rmse2 <- sqrt(mean(resid(mod2)^2))

ggplot(aes(x = x, y = y), data = d[d$sample, ]) +
    geom_point(color = "blue", size = 4) +
    geom_smooth(method = "lm", color = "green", se = FALSE, formula = y ~ poly(x, 2)) +
    labs(title = paste("RMSE:", round(rmse2, 2))) +
    theme_bw() +
    theme(plot.title = element_text(color = "green"))

Code
mod3 <- lm(y ~ poly(x, 3), data = d[d$sample, ])
rmse3 <- sqrt(mean(resid(mod3)^2))
ggplot(aes(x = x, y = y), data = d[d$sample, ]) +
    geom_point(color = "blue", size = 4) +
    geom_smooth(method = "lm", color = "blue", se = FALSE, formula = y ~ poly(x, 3)) +
    labs(title = paste("RMSE:", round(rmse3, 2))) +
    theme_bw() +
    theme(plot.title = element_text(color = "blue"))

Code
mod8 <- lm(y ~ poly(x, 8), data = d[d$sample, ])
rmse8 <- sqrt(mean(resid(mod8)^2))
ggplot(aes(x = x, y = y), data = d[d$sample, ]) +
    geom_point(color = "blue", size = 4) +
    geom_smooth(method = "lm", color = "black", se = FALSE, formula = y ~ poly(x, 8)) +
    labs(title = paste("RMSE:", round(rmse8, 2))) +
    theme_bw() +
    theme(plot.title = element_text(color = "black"))

Überblick Modelle

Code
ggplot(aes(x = x, y = y), data = d[d$sample, ]) +
    geom_point(data = d[!d$sample, ], color = "grey90", size = 4) +
    geom_smooth(method = "lm", color = "red", se = FALSE, formula = y ~ x, fullrange = TRUE) +
    geom_smooth(method = "lm", color = "green", se = FALSE, formula = y ~ poly(x, 2), fullrange = TRUE) +
    geom_smooth(method = "lm", color = "blue", se = FALSE, formula = y ~ poly(x, 3), fullrange = TRUE) +
    geom_smooth(method = "lm", color = "black", se = FALSE, formula = y ~ poly(x, 8), fullrange = TRUE) +
    geom_point(color = "blue", size = 4) +
    scale_y_continuous(limits = c(-60, 20)) +
    theme_bw() +
    theme(plot.title = element_text(color = "black"))

RMSE in Sample vs. Full Data

Code
# Funktion um RSME in sample und full data zu berechnen in Abhängigkeit von Polynomgrad
calc_rmse <- function(order) {
    mod_sample <- lm(y ~ poly(x, order), data = d[d$sample, ])
    rmse_sample <- sqrt(mean(resid(mod_sample)^2))
    mod_full_resid <- predict(mod_sample, newdata = d) - d$y
    rmse_full <- sqrt(mean(mod_full_resid^2))
   data.frame(order = order,rmse_sample = rmse_sample, rmse_full = rmse_full)
}

rmse <- lapply(1:8, calc_rmse) |> purrr::list_rbind()
rmse <- tidyr::pivot_longer(rmse, cols = c("rmse_sample", "rmse_full"), names_to = "type", values_to = "rmse")
rmse$type <- factor(rmse$type, levels = c("rmse_sample", "rmse_full"), labels = c("Sample Data (Train)", "Full Data (Test)"))

p1 <- ggplot(rmse[rmse$type == "Sample Data (Train)",], aes(x = order, y = rmse, color = type)) +
    geom_point(size = 4) +
    geom_line() +
    scale_y_log10(limits = c(0.27, 240)) +
    labs(title = "RMSE in Sample Data", x = "Complexity (Polynomial Degree)", y = "RMSE (log10 scale)", color = "Typ") +
    theme_bw(base_size = 16) +
    theme()
p2 <- ggplot(rmse, aes(x = order, y = rmse, color = type)) +
    geom_point(size = 4) +
    geom_line() +
    scale_y_log10(limits = c(0.27, 240)) +
    labs(title = "RMSE in Sample and Full Data", x = "Complexity (Polynomial Degree)", y = "RMSE (log10 scale)", color = "Typ") +
    theme_bw(base_size = 16) +
    theme() 

Bias & Variance

Bias

Wie nah liegt mein Modell an den Daten

Variance

Wie wenig ändert sich mein Modell bei anderer Stichprobe

Generelle Regeln:

  • weniger komplexe Modelle: low variance, high bias (underfitting)
  • komplexere Modelle: high variance, low bias (overfitting)

Für gute Vorhersagen brauchen wir das Optimum zwischen bias und variance

Train/Test Split

Train vs. Test Data

  • In meiner Sample ist mein Modell immer besser als in neuen Daten
  • Uns interessiert aber eigentlich nur die Performance in neuen Daten

Zwei Möglichkeiten:

  • Model in alten Daten fitten, dann neue Daten sammeln und testen
  • vorhandene Daten in Train und Testset splitten
    • Modell in Train fitten
    • Ganz am Ende in Test evaluieren

Beispieldatensatz

run_all <- read.csv("../preprocessing/running.csv")
run <- run_all[!is.na(run_all$t_3000),]
colnames(run)
 [1] "id"             "bw"             "bl"             "group"         
 [5] "bf_per"         "hr_max"         "vo2_max_rel"    "v_vo2_max"     
 [9] "re_mlss"        "v_mlss"         "v_obla"         "vo2_mlss_rel"  
[13] "vo2_obla_rel"   "vlamax_abs"     "mfo"            "v_fatmax"      
[17] "vo2_fatmax_rel" "hr_obla"        "hr_fatmax"      "la_fatmax"     
[21] "t_100"          "t_1000"         "t_2000"         "t_3000"        
[25] "vo2_max_abs"    "vo2_obla_abs"   "vo2_mlss_abs"   "vo2_obla_per"  
[29] "vo2_mlss_per"   "vo2_fatmax_per" "hr_obla_per"    "hr_fatmax_per" 
[33] "vo2_max_vlamax" "v_100"          "asr"            "cv"            
[37] "D"              "train_h"        "train_km"       "train_v"       
nrow(run)
[1] 20
summary(run$t_3000)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  531.6   567.0   599.6   606.0   636.9   689.8 

Wie gut ist mein Modell auf dem kompletten Datensatz?

mod_full <- lm(t_3000 ~ vo2_max_rel + bw + bl + mfo + hr_max + re_mlss, data = run)
broom::glance(mod_full)
# A tibble: 1 × 12
  r.squared adj.r.squared sigma statistic   p.value    df logLik   AIC   BIC
      <dbl>         <dbl> <dbl>     <dbl>     <dbl> <dbl>  <dbl> <dbl> <dbl>
1     0.883         0.829  18.8      16.4 0.0000223     6  -82.8  182.  189.
# ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
rmse_full <- sqrt(mean((mod_full$fitted.values - run$t_3000)^2))
rmse_full
[1] 15.1683

Train/Test Split

set.seed(123)
index <- sample(1:20, size = 20)
index
 [1] 15 19 14  3 10  2  6 11  5  4 18  9 20 17 12  7 16  1  8 13
train <- run[index[1:10],]
test <- run[index[11:20],]

nrow(train)
[1] 10
nrow(test)
[1] 10

Modell in Train fitten, in Test evaluieren

mod_train <- lm(t_3000 ~ vo2_max_rel + bw + bl + mfo + hr_max + re_mlss, data = train)
rmse_train <- sqrt(mean((mod_train$fitted.values - train$t_3000)^2))
pred_test <- predict(mod_train, newdata = test)
rmse_test <- sqrt(mean((pred_test - test$t_3000)^2))
rmse_train
[1] 16.72595
rmse_test
[1] 31.5736

Wie finde ich im Trainset das Modell, das am besten im Testset performt?

Beispiel

Code
ggplot(d, aes(x = x, y = y, color = sample)) +
    geom_point(size = 4)  +
    scale_color_manual(values = c("grey90", "blue"), labels = c("Nicht in Stichprobe", "In Stichprobe"), name = NULL) +
    theme_bw(base_size = 16)

dtrain <- d[d$sample,]
dtrain
            x           y sample
16 -2.0909091 -14.4373002   TRUE
20 -1.8484848 -13.8630259   TRUE
30 -1.2424242   0.2996652   TRUE
48 -0.1515152  -0.7405038   TRUE
54  0.2121212   2.6554540   TRUE
67  1.0000000  -2.6794756   TRUE
75  1.4848485  -5.9771326   TRUE
88  2.2727273   1.8918606   TRUE
89  2.3333333   0.7925784   TRUE
94  2.6363636   5.1584091   TRUE

Hyperparameter

  • Parameter, die ich nicht direkt aus den Daten fitte, sondern festlege
  • z.B. Polynomgrad, Anzahl Prädiktoren, …
  • Hyperparameter beeinflussen Bias-Variance Tradeoff
  • Wie finde ich den besten Wert für meinen Hyperparameter ohne Testset zu benutzen?

Cross-Validation

  • Ich mache verschiedene Splits, fitte jeweils neu, evaluiere jeweils neu, dann mitteln
  • Beispiel: 5-fold CV: Ich teile meinen Train-Datensatz in 5 Teile (4 Train, 1 Validation), mache das 5 mal, sodass jedes Teil einmal Validation ist

Cross-Validation in R

set.seed(123)
mod2 <- lm(y ~ poly(x, 2), data = dtrain)
res <- cv::cv(mod2, k = 5)
as.data.frame(res)
  fold      mse adjusted.mse full.mse coef.Intercept coef.poly(x, 2)1
1    0 35.06370       32.774   16.719       -2.68995           14.484
2    1  0.29632           NA       NA       -2.71838           14.391
3    2 25.57694           NA       NA       -2.66467           12.455
4    3 39.83809           NA       NA       -3.50123           16.968
5    4 27.50860           NA       NA       -3.49876           11.531
6    5 82.09853           NA       NA       -0.91005           17.267
  coef.poly(x, 2)2
1          -5.7190
2          -5.7533
3          -2.2789
4          -4.6987
5          -8.4606
6         -10.4657

Cross-Validation für Hyperparamter-Tuning

Code
# Funktion um RMSE für gegebenen Polynomgrad zu berechnen
calc_cv_rmse <- function(order, data) {
    set.seed(123)
    lm(y ~ poly(x, degree = order), data = data)
    mod <- lm(data$y ~ poly(data$x, degree = order))
    res <- cv::cv(mod, k = 5)
    data.frame(order = order, rmse_cv = as.data.frame(res)$mse[1])
}
# Funktion auf Polynomgrade 1 bis 6 anwenden und Ergebnisse in Tabelle anzeigen
lapply(1:6, calc_cv_rmse, data = dtrain) |> purrr::list_rbind() |> knitr::kable(col.names = c("Polynomgrad", "CV RMSE"))
Polynomgrad CV RMSE
1 25.03841
2 35.06370
3 12.86487
4 20.97448
5 228.74941
6 550.08656

Workflow

  1. Train/Test Split, Testset beiseitelegen
  2. In Trainset: bestes Modell auswählen (z.B. durch CV)
  3. Bestes Modell auf ganzen Trainset fitten
  4. Bestes Modell auf Testset evaluieren

Wörterbuch: Statistik <-> Machine Learning

Statistik ML
Prädiktor Feature
Outcome Target
Parameter Weight
Fitting/Estimation Learning