Regularyzacja

Regularyzacja jest powszechnym tematem w uczeniu maszynowym i statystyce bayesowskiej. W tym rozdziale opiszemy trzy najpopularniejsze regularne modele liniowe w literaturze dotyczącej uczenia maszynowego i przedstawimy je w kontekście zestawu danych PISA. Na końcu dokumentu znajdują się ćwiczenia, które pozwolą przetestować zdobytą wiedzę.

Regresja grzbietowa

Nie daj się zwieść innym, że regresja grzbietowa to wymyślny algorytm sztucznej inteligencji. Czy znasz regresję liniową? Jeśli tak, to regresja grzbietowa jest po prostu adaptacją regresji liniowej.

Celem regresji liniowej lub zwykłej regresji najmniejszych kwadratów (OLS) jest zminimalizowanie sumy kwadratów reszt. Innymi słowy, należy dopasować N linii regresji do danych i zachować tylko tę, która ma najniższą sumę kwadratów reszt. W prostym żargonie formuły, OLS próbuje minimalizować to:

\[\begin{equation} RSS = \sum_{k = 1}^n(actual_i - predicted_i)^2 \end{equation}\]

Dla każdej dopasowanej linii regresji należy porównać wartość przewidywaną (\(predicted_i\)) z wartością rzeczywistą (\(actual_i\)), podnieść ją do kwadratu i zsumować. Każda dopasowana linia regresji ma następnie powiązaną resztową sumę kwadratów (RSS), a model liniowy wybiera linię o najniższym RSS.

Uwaga: Naukowcy społeczni są zaznajomieni z RSS i nazywają go po prostu jego nazwą. Należy jednak pamiętać, że w żargonie uczenia maszynowego RSS należy do ogólnej rodziny zwanej funkcjami strat. Funkcje strat to wskaźniki, które oceniają dopasowanie modelu i jest ich wiele (takich jak AIC, BIC lub R2).

Regresja grzbietowa wykorzystuje poprzednią funkcję straty RSS i dodaje jeden element:

\[\begin{equation} RSS + \lambda \sum_{k = 1}^n \beta^2_j \end{equation}\]

Termin po prawej stronie nazywany jest karą za kurczenie się, ponieważ wymusza on zbliżenie każdego współczynnika \(\beta_j\) do zera poprzez podniesienie go do kwadratu. Część dotycząca kurczenia się jest jaśniejsza, gdy pomyślimy o tym określeniu jako wymuszającym, aby każdy współczynnik był tak mały, jak to możliwe, bez uszczerbku dla resztkowej sumy kwadratów (RSS). Innymi słowy, chcemy najmniejszych współczynników, które nie wpływają na dopasowanie linii (RSS).

Intuicyjnym przykładem jest myślenie o RSS i \(\sum_{k = 1}^n \beta^2_j\) jako o dwóch oddzielnych rzeczach. RSS szacuje, jak model pasuje do danych, a \(\sum_{k = 1}^n \beta^2_j\) ogranicza stopień nadmiernego dopasowania do danych. Wreszcie, \(\lambda\) pomiędzy tymi dwoma terminami (zwana lambda) może być interpretowana jako „waga”. Im wyższa lambda, tym większa waga zostanie przypisana terminowi kurczenia się równania. Jeśli \(\lambda\) wynosi 0, to pomnożenie 0 przez \(\sum_{k = 1}^n \beta^2_j\) zawsze zwróci zero, zmuszając nasze poprzednie równanie do zredukowania go do pojedynczego wyrażenia \(RSS\).

Dlaczego istnieje potrzeba „ograniczenia” tego, jak dobrze model pasuje do danych? Ponieważ my, naukowcy społeczni i naukowcy zajmujący się danymi, bardzo często nadmiernie dopasowujemy dane. Poniższy wykres przedstawia symulację z Simon Jackson, w której widzimy, że podczas testowania na zestawie treningowym, OLS i Ridge mają tendencję do nadmiernego dopasowania danych. Jednak podczas testowania na danych testowych regresja grzbietowa ma niższy błąd poza próbą, ponieważ $ R2 $ jest wyższy dla modeli z różnymi obserwacjami.

Siła regresji grzbietowej wynika z faktu, że kompromisowo dopasowuje ona dane treningowe naprawdę dobrze w celu poprawy uogólnienia. Innymi słowy, zwiększamy bias (ponieważ zmuszamy współczynniki do bycia mniejszymi) w zamian za niższą zmienność (dzięki czemu nasze przewidywania są bardziej odporne). Innymi słowy, cała istota regresji grzbietowej polega na karaniu bardzo dużych współczynników w celu lepszej generalizacji na nowych danych.

Mając na uwadze tę intuicję, należy pamiętać o jednej ważnej rzeczy: predyktory regresji grzbietowej muszą być znormalizowane. Dlaczego tak się dzieje? Ponieważ ze względu na skalę predyktora jego współczynnik może być bardziej karany niż inne predyktory. Załóżmy, że masz dochód konkretnej osoby (mierzony w tysiącach na miesiąc) i czas spędzony z rodziną (mierzony w sekundach) i próbujesz przewidzieć szczęście. Wzrost wynagrodzenia o jedną jednostkę może być karany znacznie bardziej niż wzrost czasu spędzanego z rodziną o jedną jednostkę tylko dlatego, że wzrost wynagrodzenia o jedną jednostkę może być znacznie większy ze względu na jego metrykę.

W R można dopasować regresję grzbietową za pomocą tidymodels i tidyflow. Załadujmy pakiety, z którymi będziemy pracować i wczytajmy dane:

library(tidymodels)
library(tidyflow)

data_link <- "https://raw.githubusercontent.com/cimentadaj/ml_socsci/master/data/pisa_us_2018.csv"
pisa <- read.csv(data_link)

Skonstruujemy nasz tidyflow krok po kroku. Zaczynamy od danych, a następnie oddzielamy dane treningowe i testowe. Całe nasze modelowanie zostanie przeprowadzone na danych treningowych, a dane testowe zostaną zapisane na później (dane testowe należy całkowicie zignorować, dopóki nie uzyskasz ostatecznego dostrojonego modelu). Drugim krokiem jest określenie zmiennych w modelu i skalowanie wszystkich z nich, jak już wyjaśniłem, chcemy znormalizować wszystkie zmienne tak, aby żadna zmienna nie była bardziej karana niż inne ze względu na ich metrykę.

# Określ wszystkie zmienne i skalę
rcp <-
  # Zdefiniuj zmienną zależną (math_score) i zmienne niezależne
  ~ recipe(math_score ~ MISCED + FISCED + HISEI + REPEAT + IMMIG + DURECEC + BSMJ, data = .) %>%
  # Skaluj wszystkie predyktory (już wie, że to zmienne niezależne)
    step_scale(all_predictors())

tflow <-
  tidyflow(seed = 231141) %>%
  plug_data(pisa) %>%
  plug_split(initial_split, prop = .7) %>%
  # Dodaj przepis ze wszystkimi zmiennymi i skalą
  plug_recipe(rcp)

tflow
## ══ Tidyflow ════════════════════════════════════════════════════════════════════
## Data: 4.84K rows x 501 columns
## Split: initial_split w/ prop = ~0.7
## Recipe: available
## Resample: None
## Grid: None
## Model: None

Argument prop kontroluje część próbki, która znajdzie się w danych treningowych. Tutaj określamy go na .7, 70% danych. Trzecim krokiem jest określenie parametrów tuningu. Regresja grzbietowa ma parametr o nazwie penalty, który musi być ustawiony przez nas. penalty jest terminem „wagi” w równaniu regresji grzbietowej, który kontroluje, jaką wagę chcemy nadać „karze za kurczenie się” (jest to \(\lambda\) z równania). Jeśli ta kara jest ustawiona na 0, oznacza to, że nie przywiązujemy wagi do terminu kary i otrzymamy taki sam wynik jak w przypadku OLS. Spróbujmy tego:

############################# Regresja grzbietowa ################################
###############################################################################
regularized_reg <-
  set_engine(
    # mix określa typ regresji ujemnej: 0 oznacza regresję grzbietową
    linear_reg(penalty = 0, mixture = 0),
    "glmnet"
  )

model1 <-
  tflow %>%
  plug_model(regularized_reg) %>%
  fit()

# Pobierz współczynniki grzbietu
mod <- model1 %>% pull_tflow_fit() %>% .[["fit"]]
ridge_coef <- predict(mod, s = 0, type = "coefficients")

############################# Model liniowy ####################################
###############################################################################

model2 <-
  tflow %>%
  plug_model(set_engine(linear_reg(), "lm")) %>%
  fit()

lm_coef <- model2 %>% pull_tflow_fit() %>% .[["fit"]] %>% coef()

############################# Porównanie modeli #################################
###############################################################################

comparison <-
  data.frame(coefs = names(lm_coef),
             `Linear coefficients` = unname(round(lm_coef, 2)),
             `Ridge coefficients` = round(as.vector(ridge_coef), 2))

knitr::kable(comparison)
coefs Linear.coefficients Ridge.coefficients
(Intercept) 334.29 336.41
MISCED 3.39 3.65
FISCED 11.59 11.28
HISEI 16.14 15.76
REPEAT -21.22 -20.67
IMMIG 4.99 4.82
DURECEC -0.28 -0.23
BSMJ 11.19 10.97

Pochodząc z nauk społecznych, może wydawać się sprzeczne z intuicją, że badacz musi określić parametry dostrajania modelu. W tradycyjnych statystykach nauk społecznych modele zwykle szacują podobne wartości wewnętrznie, a użytkownik nie musi o nich myśleć. Istnieją jednak już zaimplementowane strategie eksploracji kombinacji wielu możliwych wartości. W naszym poprzednim przykładzie musimy dodać tune() do argumentu penalty i dodać siatkę dla modelu, aby wyszukać najlepszy:

# Tutaj dodajemy walidację krzyżową i siatkę
tflow <-
  tflow %>%
  # walidacja krzyżową
  plug_resample(vfold_cv, v = 5) %>%
  # Grid
  plug_grid(grid_regular)

regularized_reg <- update(regularized_reg, penalty = tune())

res <-
  tflow %>%
  # Zaktualizuj model, aby określić, że `penalty` zostanie dostrojone
  plug_model(regularized_reg) %>%
  fit()

final_ridge <- complete_tflow(res, metric = "rmse")

final_ridge %>%
  pull_tflow_fit() %>%
  .[["fit"]] %>%
  plot(xvar = "lambda", label = TRUE)

Tutaj możemy zobaczyć, jak na nasze współczynniki wpływa zwiększenie wagi parametru penalty. Każda z tych linii to współczynniki dla zmiennych. Oś „x” zawiera wartości kar i możemy zobaczyć, jak wraz ze wzrostem kary, wielkość współczynników zmniejsza się do wartości bliskiej zeru. Przy logarytmie penalty około 8 prawie wszystkie współczynniki kurczą się bardzo blisko zera. Ten wykres jest tylko ćwiczeniem, aby zrozumieć, jak działa regresja grzbietowa. Innymi słowy, możemy automatycznie określić najlepszą lambdę:

best_tune <-
  res %>%
  pull_tflow_fit_tuning() %>%
  select_best(metric = "rmse")

best_tune
## # A tibble: 1 × 2
##        penalty .config             
##          <dbl> <chr>               
## 1 0.0000000001 Preprocessor1_Model1

Nie ma jednak potrzeby obliczania tego, ponieważ complete_tflow oblicza to za Ciebie (jak widać w powyższym fragmencie kodu, complete_tflow wyodrębnia to automatycznie i dopasowuje najlepszy model). Możemy obliczyć \(RMSE\) danych treningowych z najlepszego modelu i porównać go z przewidywaniami na danych testowych:

train_rmse_ridge <-
  final_ridge %>%
  predict_training() %>%
  rmse(math_score, .pred)

holdout_ridge <-
  final_ridge %>%
  predict_testing() %>%
  rmse(math_score, .pred)

train_rmse_ridge$type <- "training"
holdout_ridge$type <- "testing"

ridge <- as.data.frame(rbind(train_rmse_ridge, holdout_ridge))
ridge$model <- "ridge"
ridge
##   .metric .estimator .estimate     type model
## 1    rmse   standard  77.61301 training ridge
## 2    rmse   standard  75.97340  testing ridge

Błąd testowania (RMSE) jest wyższy niż błąd treningu, zgodnie z oczekiwaniami, ponieważ zestaw treningowy prawie zawsze lepiej zapamiętuje dane do treningu.

Regularyzacja Lasso

Regularyzacja Lasso jest bardzo podobna do regularyzacji grzbietowej, w której zmienia się tylko jedna rzecz: termin kary. Zamiast podnosić współczynniki do kwadratu, regularizacja lasso przyjmuje wartość bezwzględną współczynnika.

\[\begin{equation} RSS + \lambda \sum_{k = 1}^n |\beta_j| \end{equation}\]

Chociaż może to nie być oczywiste, reguralizacja lasso ma ważną różnicę: może wymusić, aby współczynnik wynosił dokładnie zero. Oznacza to, że lasso dokonuje selekcji zmiennych, które mają duże współczynniki, jednocześnie nie naruszając RSS modelu. Problem z regresją grzbietową polega na tym, że wraz ze wzrostem liczby zmiennych błąd uczenia prawie zawsze się poprawia, ale błąd testowania nie.

Na przykład, jeśli zdefiniujemy ten sam model z góry za pomocą lasso, zobaczysz, że wymusza on współczynniki dokładnie zerowe, jeśli nie dodają nic w stosunku do RSS modelu. Oznacza to, że zmienne, które nie dodają nic do modelu, zostaną wykluczone, chyba że dodadzą moc wyjaśniającą, która zrekompensuje wielkość ich współczynnika. Oto ten sam przykład lasso:

regularized_reg <- update(regularized_reg, mixture = 1)

res <-
  tflow %>%
  plug_model(regularized_reg) %>%
  fit()

final_lasso <- complete_tflow(res, metric = "rmse")

final_lasso %>%
  pull_tflow_fit() %>%
  .[["fit"]] %>%
  plot(xvar = "lambda", label = TRUE)

W przeciwieństwie do regresji grzbietowej, w której współczynniki są zmuszone być bliskie zeru, kara lasso faktycznie zmusza niektóre współczynniki do bycia zerowymi. Ta właściwość oznacza, że lasso dokonuje selekcji zmiennych o wyższych współczynnikach i eliminuje te, które nie mają silnego związku. Lasso jest zwykle lepsze w interpretacji modelu, ponieważ usuwa zbędne zmienne, podczas gdy ridge może być przydatny, jeśli chcesz zachować pewną liczbę zmiennych w modelu, mimo że są one słabymi predyktorami (na przykład jako kontrole).

Aby sprawdzić ostateczny model i jego błąd, możemy powtórzyć powyższy kod i dostosować go do lasso:

train_rmse_lasso <-
  final_lasso %>%
  predict_training() %>%
  rmse(math_score, .pred)

holdout_lasso <-
  final_lasso %>%
  predict_testing() %>%
  rmse(math_score, .pred)

train_rmse_lasso$type <- "training"
holdout_lasso$type <- "testing"

lasso <- as.data.frame(rbind(train_rmse_lasso, holdout_lasso))
lasso$model <- "lasso"
lasso
##   .metric .estimator .estimate     type model
## 1    rmse   standard  77.63741 training lasso
## 2    rmse   standard  76.02502  testing lasso

Póki co możemy sprawdzić, który model radzi sobie lepiej:

model_comparison <- rbind(ridge, lasso)
model_comparison
##   .metric .estimator .estimate     type model
## 1    rmse   standard  77.61301 training ridge
## 2    rmse   standard  75.97340  testing ridge
## 3    rmse   standard  77.63741 training lasso
## 4    rmse   standard  76.02502  testing lasso

Obecnie regresja grzbietowa ma bardzo niewielką przewagę nad lasso, ale różnica jest prawdopodobnie w granicach błędu. W zależności od celu, warto wybrać jeden z tych modeli. Na przykład, jeśli nasze modele zawierają wiele zmiennych, lasso może być bardziej interpretowalne, ponieważ zmniejsza liczbę zmiennych. Jeśli jednak istnieją powody, by sądzić, że utrzymanie wszystkich zmiennych w modelu jest ważne, wówczas przewagę zapewnia ridge.

Regularyzacja Elastic Net

Jeśli znasz ridge i lasso, to regularyzacja elastyczną siecią jest logicznym krokiem. Elastic Net (nazwa brzmi fantazyjnie, ale jest to również adaptacja OLS) łączy obie kary w jedno równanie.

Tutaj definiujemy naszą karę grzbietową:

\[ridge = \lambda \sum_{k = 1}^n \beta_j^2\]

I tutaj definiujemy naszą karę lasso:

\[lasso = \lambda \sum_{k = 1}^n |\beta_j|\]

Regularyzacja elastycznej sieci polega na dodaniu tych dwóch kar w porównaniu do RSS:

\[RSS + lasso + ridge\]

Myślę, że najlepszym wyjaśnieniem dla elastycznej regularyzacji sieci jest:

Chociaż modele lasso dokonują selekcji cech, gdy dwie silnie skorelowane cechy są popychane w kierunku zera, jedna z nich może zostać całkowicie zepchnięta do zera, podczas gdy druga pozostaje w modelu. Co więcej, proces wchodzenia i wychodzenia jednej cechy z modelu nie jest zbyt systematyczny. Z kolei kara regresji grzbietowej jest nieco bardziej skuteczna w systematycznym radzeniu sobie ze skorelowanymi cechami. W związku z tym zaletą kary elastycznej siatki jest to, że umożliwia ona skuteczną regularyzację za pomocą kary regresji grzbietowej z charakterystyką selekcji cech kary lasso.

Zasadniczo masz teraz dwa parametry strojenia. W siatce wartości, zamiast określania mixture o wartości 0 (ridge) lub 1 (lasso), tidyflow przesunie się przez kilka wartości mixture od 0 do 1 i porówna je z kilkoma wartościami lambda. Jest to formalnie nazywane grid search.

Możemy powtórzyć ten sam kod co powyżej:

regularized_reg <- update(regularized_reg, mixture = tune())

res <-
  tflow %>%
  plug_model(regularized_reg) %>%
  fit()

final_elnet <- complete_tflow(res, metric = "rmse")

train_rmse_elnet <-
  final_elnet %>%
  predict_training() %>%
  rmse(math_score, .pred)

holdout_elnet <-
  final_elnet %>%
  predict_testing() %>%
  rmse(math_score, .pred)

train_rmse_elnet$type <- "training"
holdout_elnet$type <- "testing"

elnet <- as.data.frame(rbind(train_rmse_elnet, holdout_elnet))
elnet$model <- "elnet"
elnet
##   .metric .estimator .estimate     type model
## 1    rmse   standard  77.61860 training elnet
## 2    rmse   standard  75.98468  testing elnet

RMSE elastycznej siatki jest nieco niższy niż grzbietu i lasso, ale również prawdopodobnie mieści się w marginesie błędu. Porównajmy to wizualnie:

model_comparison <- rbind(model_comparison, elnet)

model_comparison %>%
  ggplot(aes(model, .estimate, color = type, group = type)) +
  geom_point(position = "dodge") +
  geom_line() +
  scale_y_continuous(name = "RMSE") +
  scale_x_discrete(name = "Models") +
  theme_minimal()

Przykłady

Fragile Families Challenge to badanie, którego celem było przewidzenie szeregu wskaźników dzieci w wieku 15 lat wyłącznie na podstawie danych w wieku od 0 do 9 lat. W ramach tego wyzwania główni badacze chcieli sprawdzić, czy umiejętności takie jak zdolności poznawcze i pozapoznawcze zostały prawidłowo przewidziane. Mając to na uwadze, byli zainteresowani śledzeniem dzieci, które przekroczyły „przewidywania”: tych dzieci, które przekroczyły przewidywania modelu, na przykład biorąc pod uwagę ich warunki początkowe.

Korzystając z podobnie skonstruowanego proxy niekognitywnego, stworzyłem indeks niekognitywny przy użyciu PISA 2018 dla Stanów Zjednoczonych, który jest średnią z pytań:

  • ST182Q03HA - Czerpię satysfakcję z pracy tak ciężko, jak tylko potrafię.
  • ST182Q04HA - Po rozpoczęciu zadania wytrwale pracuję, aż do jego ukończenia.
  • ST182Q05HA - Część przyjemności czerpię z robienia rzeczy, gdy poprawiam swoje poprzednie wyniki.
  • ST182Q06HA - Jeśli nie jestem w czymś dobry, wolę dalej walczyć, aby to opanować, niż przejść do czegoś, co mogę […].

Skala indeksu wynosi od 1 do 4, gdzie w 4 student zdecydowanie się zgadza, a 1 oznacza, że całkowicie się nie zgadza. Innymi słowy, wskaźnik ten pokazuje, że im wyższa wartość, tym wyższe umiejętności pozapoznawcze. Możesz sprawdzić pełną książkę kodów PISA [tutaj] (https://docs.google.com/spreadsheets/d/12--3vD737rcu6olviKutRLEiyKNZ2bynXcJ4CpwtNsQ/edit?usp=sharing).

W tej serii ćwiczeń będziesz musiał(a) wypróbować różne modele, które przewidują ten wskaźnik umiejętności pozapoznawczych, przeprowadzić wyszukiwanie siatki dla trzech modeli i porównać przewidywania trzech modeli.

Najpierw zapoznaj się z danymi za pomocą:

library(tidymodels)
library(tidyflow)

data_link <- "https://raw.githubusercontent.com/cimentadaj/ml_socsci/master/data/pisa_us_2018.csv"
pisa <- read.csv(data_link)

1. Utwórz tidyflow z podziałem {-#ex1}.

  • Rozpocznij z danymi pisa
  • Aby podłączyć podział, użyj initial_split.

Pamiętaj, aby ustawić seed na 2341, aby każdy mógł porównać swoje wyniki.

> Odpowiedź
tflow <-
  pisa %>%
  tidyflow(seed = 2341) %>%
  plug_split(initial_split)

tflow

2. Uruchom regresję grzbietową z niepoznawalnością (noncogn) jako zmienną zależną {-#ex2}.

  • Podłącz formułę (podpowiedź, spójrz na ?plug_formula) i użyj tylu zmiennych, ile chcesz (możesz ponownie użyć poprzednich zmiennych z przykładów lub wybrać je wszystkie). Formuła typu noncogn ~ . spowoduje regresję noncogn na wszystkich zmiennych.

  • Podłącz regresję grzbietową z penalty ustawionym na 0.001 (wskazówka: pamiętaj, aby ustawić mixture na wartość odpowiadającą regresji grzbietowej).

  • Dopasowanie modelu grzbietowego (za pomocą fit)

  • Prognozuj na danych treningowych za pomocą predict_training i zbadaj \(R^2\) (rsq) i \(RMSE\) (rmse).

> Odpowiedź
ridge_mod <- set_engine(linear_reg(penalty = 0.001, mixture = 0), "glmnet")

tflow <-
  tflow %>%
  plug_formula(noncogn ~ .) %>% 
  plug_model(ridge_mod)

m1 <- fit(tflow)

m1_rsq <- predict_training(m1) %>% rsq(noncogn, .pred)
m1_rmse <- predict_training(m1) %>% rmse(noncogn, .pred)

3. Dodanie przepisu na skalowanie wszystkich predyktorów i ponowne uruchomienie poprzedniego modelu {-#ex3}.

  • Usunąć formułę z tidyflow za pomocą drop_formula.

  • Dodaj recepturę z tą samą formułą, ale zawierającą step_scale dla wszystkich predyktorów.

  • Ponownie uruchom model i wyodrębnij \(R^2\) i \(RMSE\).

Jak zmieniły się \(R^2\) i \(RMSE\)? Czy zmiana miała jakiś wpływ?

> Odpowiedź
rcp <-
  ~ recipe(noncogn ~ ., data = .) %>%
    step_scale(all_predictors())

tflow <-
  tflow %>% 
  drop_formula() %>%
  plug_recipe(rcp)

m2 <- fit(tflow)

m2_rsq <- predict_training(m2) %>% rsq(noncogn, .pred)
m2_rmse <- predict_training(m2) %>% rmse(noncogn, .pred)

4. Dostosuj poprzedni model, aby przeszukać siatkę wartości penalty {-#ex4}.

  • Dodaj próbkę walidacji krzyżowej (vfold_cv)
  • Dodaj siatkę strojenia (grid_regular) i określ levels = 10. Spowoduje to utworzenie siatki strojenia składającej się z 10 wartości
  • Zaktualizuj parametr penalty, aby był tuned
  • Uruchom wyszukiwanie siatki (fit)
  • Wyodrębnij siatkę strojenia (pull_tflow_fit_tuning) i wizualizuj autoplot.

Czy istnieje wzorzec poprawy/zmniejszenia metryk dopasowania w odniesieniu do penalty?

> Odpowiedź
ridge_mod <- update(ridge_mod, penalty = tune())

tflow <-
  tflow %>%
  replace_model(ridge_mod) %>%
  plug_resample(vfold_cv) %>%
  plug_grid(grid_regular, levels = 10)

m3 <- fit(tflow)

m3 %>%
  pull_tflow_fit_tuning() %>%
  autoplot()

5. Uruchom regresję lasso z taką samą specyfikacją jak powyżej {-#ex5}.

  • Zaktualizuj model, aby miał mieszankę 1 (to określa, że chcemy lasso).
  • Uruchom wyszukiwanie siatki (fit)
  • Wyodrębnienie siatki strojenia (pull_tflow_fit_tuning) i wizualizacja autoplot.

Który model działa lepiej? Ridge czy Lasso? Czy możesz skomentować wzór kary między ridge i lasso?

> Odpowiedź
lasso_mod <- update(ridge_mod, mixture = 1)

m4 <-
  tflow %>%
  replace_model(lasso_mod) %>%
  fit()

m4 %>%
  pull_tflow_fit_tuning() %>%
  autoplot()

6. Przeprowadzenie elastycznej regresji netto dla umiejętności innych niż poznawcze {-#ex6}.

  • Zaktualizuj model, aby miał tuned mixture
  • Zastąp model w tidyflow modelem elastycznej siatki.
  • Uruchom wyszukiwanie siatki (fit)
  • Wyodrębnij siatkę strojenia (pull_tflow_fit_tuning) i wizualizuj autoplot.
> Odpowiedź
elnet_mod <- update(lasso_mod, mixture = tune())

m5 <-
  tflow %>%
  replace_model(elnet_mod) %>%
  fit()

m5 %>%
  pull_tflow_fit_tuning() %>%
  autoplot()

# Dodatkowy wykres z błędem standardowym
library(tidyr)
m5 %>%
  pull_tflow_fit_tuning() %>%
  collect_metrics() %>%
  pivot_longer(penalty:mixture) %>%
  mutate(low = mean - (std_err * 2),
         high = mean + (std_err * 2)) %>% 
  ggplot(aes(value, mean)) +
  geom_point() +
  geom_errorbar(aes(ymin = low, ymax = high)) +
  facet_grid(.metric ~ name)

7. Porównaj trzy modele {-#ex7}.

  • Sfinalizuj trzy modele (model grzbietowy, model lasso i model sieci elastycznej) za pomocą complete_tflow. Pamiętaj, aby ustawić metric!
  • Użyj trzech sfinalizowanych modeli (tych, które zostały wyprodukowane przez complete_tflow) do predict_training i predict_testing na każdym z nich
  • Oblicz rmse trzech modeli zarówno na treningu, jak i na testach.
  • Zwizualizuj trzy modele i ich błędy dla treningu/testu
  • Dodaj komentarz na temat tego, który model jest lepszy w dopasowaniu poza próbą!
  • Czy lepiej jest zachować najdokładniejszy model, czy model uwzględniający istotne czynniki zakłócające (nawet jeśli ich związek jest nieco słabszy)?
> Odpowiedź
# Ponieważ będziemy powtarzać ten sam proces wiele razy
# napiszmy funkcję do przewidywania na podstawie treningu/testu
# i połączmy je. Ta funkcja zaakceptuje pojedynczy
# model i utworzy ramkę danych z błędem RMSE dla
# treningu i testów. W ten sposób możemy ponownie wykorzystać kod
# bez konieczności kopiowania wszystkiego wiele razy
calculate_err <- function(final_model, type_model = NULL) {
  final_model <- complete_tflow(final_model, metric = "rmse")
  err_train <-
    final_model %>%
    predict_training() %>%
    rmse(noncogn, .pred)
  
  err_test <-
    final_model %>%
    predict_testing() %>%
    rmse(noncogn, .pred)

  err_train$type <- "train"
  err_test$type <- "test"
  res <- as.data.frame(rbind(err_train, err_test))
  res$model <- type_model
  res
}

final_res <-
  rbind(
    calculate_err(m3, "ridge"),
    calculate_err(m4, "lasso"),
    calculate_err(m5, "elnet")
  )

final_res %>%
  ggplot(aes(model, .estimate, color = type)) +
  geom_point() +
  theme_minimal()

## BONUS
## Dopasuj regresję liniową i porównaj cztery modele
## Jaki model jest najlepszy, biorąc pod uwagę zarówno dokładność, jak i prostotę?