<- "https://raw.githubusercontent.com/sebastiansauer/Lehre/main/data/tmdb-box-office-prediction/train.csv"
d_train_path <- "https://raw.githubusercontent.com/sebastiansauer/Lehre/main/data/tmdb-box-office-prediction/test.csv" d_test_path
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
revenue
in Dollars; nicht in “Log-Dollars”. Sie müssen also rücktransformieren, falls Sierevenue
logarithmiert 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 nutzen
<- read_csv(d_train_path)
d_train_raw <- read_csv(d_test_path) d_test
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())
rec1
tidy(rec1)
Check das Rezept
prep(rec1, verbose = TRUE)
<-
d_train_baked prep(rec1) %>%
bake(new_data = NULL)
d_train_baked
%>%
d_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:
::describe_distribution(d_train_baked) datawizard
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.
<- prep(rec1)
rec1_prepped
<-
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\).
<- vfold_cv(d_train,
cv_scheme 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
<- boost_tree(mtry = tune(),
mod_boost min_n = tune(),
trees = tune()) %>%
set_mode("regression")
LM
<-
mod_lm linear_reg()
Workflow-Set
<- list(rec1 = rec1)
preproc <- list(tree1 = mod_tree,
models rf1 = mod_rf,
boost1 = mod_boost,
lm1 = mod_lm)
<- workflow_set(preproc, models) all_workflows
Fitten und tunen
Fitten/Tunen
Wenn man das Ergebnis-Objekt abgespeichert hat, dann kann man es einfach laden, spart Rechenzeit (der Tag ist kurz):
<- "tmdb_model_set.rds" result_obj_file
(Davon ausgehend, dass die Datei im Arbeitsverzeichnis liegt.)
Dann könnte man Folgendes machen:
if (file.exists(result_obj_file)) {
<- read_rds(result_obj_file)
tmdb_model_set 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:
"result"]][[1]] tmdb_model_set[[
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.
::autoplot(tmdb_model_set) tune
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_params
Finalisieren
<-
best_wf %>%
all_workflows extract_workflow("rec1_rf1")
best_wf
<-
best_wf_finalized %>%
best_wf finalize_workflow(best_model_params)
best_wf_finalized
Final Fit
<-
fit_final %>%
best_wf_finalized fit(d_train)
fit_final
$revenue <- NA
d_test
<-
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).
<- 4.79227 sol
Categories:
- ds1
- tidymodels
- statlearning
- tmdb
- random-forest
- num