d_train_path <- "https://raw.githubusercontent.com/sebastiansauer/Lehre/main/data/tmdb-box-office-prediction/train.csv"
d_test_path <- "https://raw.githubusercontent.com/sebastiansauer/Lehre/main/data/tmdb-box-office-prediction/test.csv"tmdb03
Aufgabe
Wir bearbeiten hier die Fallstudie TMDB Box Office Prediction - Can you predict a movie’s worldwide box office revenue?, ein Kaggle-Prognosewettbewerb.
Ziel ist es, genaue Vorhersagen zu machen, in diesem Fall für Filme.
Die Daten können Sie von der Kaggle-Projektseite beziehen oder so:
Aufgabe
Reichen Sie bei Kaggle eine Submission für die Fallstudie ein! Berichten Sie den Score!
Hinweise:
- Sie müssen sich bei Kaggle ein Konto anlegen (kostenlos und anonym möglich); alternativ können Sie sich mit einem Google-Konto anmelden.
- Verwenden Sie mehrere, und zwar folgende Algorithmen: Random Forest, Boosting, lineare Regression. Tipp: Ein Workflow-Set ist hilfreich.
- Logarithmieren Sie
budget. - Betreiben Sie Feature Engineering, zumindest etwas. Insbesondere sollten Sie den Monat und das Jahr aus dem Datum extrahieren und als Features (Prädiktoren) nutzen.
- Verwenden Sie
tidymodels. - Die Zielgröße ist
revenuein Dollars; nicht in “Log-Dollars”. Sie müssen also rücktransformieren, falls Sierevenuelogarithmiert haben.
Lösung
Vorbereitung
library(tidyverse)
library(tidymodels)
library(tictoc) # Rechenzeit messen
#library(Metrics)
library(lubridate) # Datumsangaben
library(VIM) # fehlende Werte
library(visdat) # Datensatz visualisieren
library(lubridate) # Datum/Uhrzeit verarbeiten
library(doParallel) # mehrere CPUs nutzend_train_raw <- read_csv(d_train_path)
d_test <- read_csv(d_test_path)Mal einen Blick werfen:
glimpse(d_train_raw)
glimpse(d_test)Train-Set verschlanken
d_train <-
d_train_raw %>%
select(popularity, runtime, revenue, budget, release_date) Datensatz kennenlernen
library(visdat)
vis_dat(d_train)Fehlende Werte prüfen
Welche Spalten haben viele fehlende Werte?
vis_miss(d_train)Mit {VIM} kann man einen Datensatz gut auf fehlende Werte hin untersuchen:
aggr(d_train)Rezept definieren
rec1 <-
recipe(revenue ~ ., data = d_train) %>%
#update_role(all_predictors(), new_role = "id") %>%
#update_role(popularity, runtime, revenue, budget, original_language) %>%
#update_role(revenue, new_role = "outcome") %>%
step_mutate(budget = if_else(budget < 10, 10, budget)) %>%
step_log(budget) %>%
step_mutate(release_date = mdy(release_date)) %>%
step_date(release_date, features = c("year"), keep_original_cols = FALSE) %>%
step_impute_bag(all_predictors()) %>%
step_dummy(all_nominal())
rec1tidy(rec1)Check das Rezept
prep(rec1, verbose = TRUE)d_train_baked <-
prep(rec1) %>%
bake(new_data = NULL)
d_train_bakedd_train_baked %>%
map_df(~ sum(is.na(.)))Keine fehlenden Werte mehr in den Prädiktoren.
Nach fehlenden Werten könnte man z.B. auch so suchen:
datawizard::describe_distribution(d_train_baked)So bekommt man gleich noch ein paar Infos über die Verteilung der Variablen. Praktische Sache.
Check Test-Sample
Das Test-Sample backen wir auch mal. Das hat nur den Zwecke, zu prüfen, ob unser Rezept auch richtig funktioniert. Das Preppen und Backen des Test-Samples wir automatisch von predict() bzw. last_fit() erledigt.
Wichtig: Wir preppen den Datensatz mit dem Train-Sample, auch wenn wir das Test-Sample backen wollen.
rec1_prepped <- prep(rec1)
d_test_baked <-
bake(rec1_prepped, new_data = d_test)
d_test_baked %>%
head()Kreuzvalidierung
Nur aus Zeitgründen ist hier \(v=5\) eingestellt; besser wäre z.B. \(v=10\) und \(r=3\).
cv_scheme <- vfold_cv(d_train,
v = 5,
repeats = 1)Modelle
Baum
mod_tree <-
decision_tree(cost_complexity = tune(),
tree_depth = tune(),
mode = "regression")Random Forest
mod_rf <-
rand_forest(mtry = tune(),
min_n = tune(),
trees = 1000,
mode = "regression") XGBoost
mod_boost <- boost_tree(mtry = tune(),
min_n = tune(),
trees = tune()) %>%
set_mode("regression")LM
mod_lm <-
linear_reg()Workflow-Set
preproc <- list(rec1 = rec1)
models <- list(tree1 = mod_tree,
rf1 = mod_rf,
boost1 = mod_boost,
lm1 = mod_lm)
all_workflows <- workflow_set(preproc, models)Fitten und tunen
Fitten/Tunen
Wenn man das Ergebnis-Objekt abgespeichert hat, dann kann man es einfach laden, spart Rechenzeit (der Tag ist kurz):
result_obj_file <- "tmdb_model_set.rds"(Davon ausgehend, dass die Datei im Arbeitsverzeichnis liegt.)
Dann könnte man Folgendes machen:
if (file.exists(result_obj_file)) {
tmdb_model_set <- read_rds(result_obj_file)
} else {
<computer_workflow_set_and_be_happy>
}Achtung Gefährlich! Zwischenspeichern auf der Festplatte birgt die Gefahr, dass man vergisst, das Objekt auf der Festplatte zu aktualisieren und Sie noch in einem Jahr und nach 100 Updates Ihres Rezepts immer noch das uralte Objekt von der Festplatte laden …
Um Rechenzeit zu sparen, kann man das Ergebnisobjekt abspeichern, dann muss man beim nächsten Mal nicht wieder von Neuem berechnen:
#write_rds(tmdb_model_set, "objects/tmdb_model_set.rds")Hier berechnen wir aber lieber das Modell neu:
tic()
tmdb_model_set <-
all_workflows %>%
workflow_map(
resamples = cv_scheme,
#grid = 10,
metrics = metric_set(rmse),
seed = 42, # reproducibility
control = control_grid(verbose = FALSE))
toc()Ohne Parallelisierung dauerte die Berechnung bei mir knapp 4 Minuten (225 Sec). Ich habe hier auf Parallelisierung verzichtet, da Tidymodels einen Fehler aufwarf mit der Begründung, dass das Paket lubridate in den parallel laufenden Instanzen nicht verfügbar sei (und der parameter pckgs = 'lubridate keine Linderung brachte).
Check:
tmdb_model_set[["result"]][[1]]Finalisieren
Welcher Algorithmus schneidet am besten ab?
Genauer gesagt, welches Modell, denn es ist ja nicht nur ein Algorithmus, sondern ein Algorithmus plus ein Rezept plus die Parameterinstatiierung plus ein spezifischer Datensatz.
tune::autoplot(tmdb_model_set)R-Quadrat ist nicht so entscheidend; rmse ist wichtiger.
Die Ergebnislage ist nicht ganz klar, aber einiges spricht für das Random-Forest-Modell.
tmdb_model_set %>%
collect_metrics() %>%
arrange(mean) %>%
slice_head(n = 10)best_model_params <-
extract_workflow_set_result(tmdb_model_set, "rec1_rf1") %>%
select_best()
best_model_paramsFinalisieren
best_wf <-
all_workflows %>%
extract_workflow("rec1_rf1")
best_wfbest_wf_finalized <-
best_wf %>%
finalize_workflow(best_model_params)
best_wf_finalizedFinal Fit
fit_final <-
best_wf_finalized %>%
fit(d_train)
fit_finald_test$revenue <- NA
final_preds <-
fit_final %>%
predict(new_data = d_test) %>%
bind_cols(d_test)Submission
submission_df <-
final_preds %>%
select(id, revenue = .pred)Abspeichern und einreichen:
#write_csv(submission_df, file = "submission.csv")Kaggle Score
Diese Submission erzielte einen Score von 4.79227 (RMSLE).
sol <- 4.79227Categories:
- ds1
- tidymodels
- statlearning
- tmdb
- random-forest
- num