germeval03-sent-wordvec-xgb-plain

textmining
datawrangling
germeval
prediction
tidymodels
sentiment
string
xgb
tune
Published

December 3, 2023

Aufgabe

Erstellen Sie ein prädiktives Modell für Textdaten. Nutzen Sie Sentiments und TextFeatures im Rahmen von Feature-Engineering. Nutzen Sie außerdem deutsche Word-Vektoren für das Feature-Engineering.

Als Lernalgorithmus verwenden Sie XGB.

Preppen und Backen Sie das Rezept, aber führen Sie die Pipelien mit dem gebackenen Datensatz und einem “Plain-Rezept” durch.

Daten

Verwenden Sie die GermEval-2018-Daten.

Die Daten sind unter CC-BY-4.0 lizensiert. Author: Wiegand, Michael (Spoken Language Systems, Saarland University (2010-2018), Leibniz Institute for the German Language (since 2019)),

Die Daten sind auch über das R-Paket PradaData zu beziehen.

library(tidyverse)
data("germeval_train", package = "pradadata")
data("germeval_test", package = "pradadata")

AV und UV

Die AV lautet c1. Die (einzige) UV lautet: text.

Hinweise

  • Orientieren Sie sich im Übrigen an den allgemeinen Hinweisen des Datenwerks.
  • Nutzen Sie Tidymodels.
  • Nutzen Sie das sentiws Lexikon.
  • ❗ Achten Sie darauf, die Variable c2 zu entfernen bzw. nicht zu verwenden.











Lösung

Setup

d_train <-
  germeval_train |> 
  select(id, c1, text)
library(tictoc)
library(tidymodels)
library(syuzhet)
library(beepr)
library(lobstr)  # object size
library(visdat)  # Fingerprint/footprint of dataset (CSV)
data("sentiws", package = "pradadata")

Eine Vorlage für ein Tidymodels-Pipeline findet sich hier.

Learner/Modell

mod <-
  boost_tree(mode = "classification",
             learn_rate = tune(), 
             tree_depth = tune()
             )

Rezept Workvektoren

Pfad zu den Wordvektoren:

path_wordvec <- "/Users/sebastiansaueruser/datasets/word-embeddings/wikipedia2vec/part-0.arrow"
source("https://raw.githubusercontent.com/sebastiansauer/Datenwerk2/main/funs/def_recipe_wordvec_senti.R")

rec <- def_recipe_wordvec_senti(data_train = d_train,
                                path_wordvec = path_wordvec)

Prep/Bake Wordvektoren

tic()
rec_prepped <- prep(rec)
toc()

78.021 sec elapsed

d_rec_baked <- bake(rec_prepped, new_data = NULL)
sum(is.na(d_rec_baked))

Test-Set auch baken

d_test_baked <- bake(rec_prepped, new_data = germeval_test)
dim(d_test_baked)
write_csv(d_test_baked, "data/germeval/germeval_test_recipe_wordvec_senti.csv")

Später kann man es dann analog wieder importieren:

d_test_baked <- read_csv("https://raw.githubusercontent.com/sebastiansauer/Datenwerk2/main/data/germeval/germeval_test_recipe_wordvec_senti.csv")

Gebackenen Datensatz als neue Grundlage

Den gepreppten/gebackenen Datensatz speichern wir als Datensatz ab:

write_csv(d_rec_baked, "https://raw.githubusercontent.com/sebastiansauer/Datenwerk2/main/data/germeval/germeval_train_recipe_wordvec_senti.csv")

Später können wir den Datensatz als “neuen, frischen” Datensatz für ein “Plain-Rezept”, also ein ganz einfaches Rezept nutzen. Das hat den Vorteil (hoffentlich), das die Datenvolumina viel kleiner sind.

d_train_new <-
  read_csv("https://raw.githubusercontent.com/sebastiansauer/Datenwerk2/main/data/germeval/germeval_train_recipe_wordvec_senti.csv")
vis_dat(d_train_new) +
  # remove axis labels:
  theme(axis.text.x=element_blank(),
        axis.ticks.x=element_blank() 
        )

Plain-Rezept

rec <- 
  recipe(c1 ~ ., data = d_train_new)

Neuer Workflow mit plainem Rezept

wf <-
  workflow() |> 
  add_recipe(rec) |> 
  add_model(mod)

wf
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: boost_tree()

── Preprocessor ────────────────────────────────────────────────────────────────
0 Recipe Steps

── Model ───────────────────────────────────────────────────────────────────────
Boosted Tree Model Specification (classification)

Main Arguments:
  tree_depth = tune()
  learn_rate = tune()

Computational engine: xgboost 

Parallelisierung über mehrere Kerne

library(parallel)
all_cores <- detectCores(logical = FALSE)

library(doFuture)
registerDoFuture()
cl <- makeCluster(2)
plan(cluster, workers = cl)

Achtung: Viele Kerne brauchen auch viel Speicher.

Tune/Resample/Fit

tic()
fit_wordvec_senti_xgb <-
  tune_grid(
    wf,
    grid = 50,
    resamples = vfold_cv(d_train_new, v = 5))
toc()
285.723 sec elapsed
beep()

Objekt-Größe:

lobstr::obj_size(fit_wordvec_senti_xgb)
5.11 MB

Ah! Angenehm klein.

Get best performance

autoplot(fit_wordvec_senti_xgb)

show_best(fit_wordvec_senti_xgb)
# A tibble: 5 × 8
  tree_depth learn_rate .metric .estimator  mean     n std_err .config          
       <int>      <dbl> <chr>   <chr>      <dbl> <int>   <dbl> <chr>            
1         10      0.252 roc_auc binary     0.761     5 0.0108  Preprocessor1_Mo…
2          9      0.220 roc_auc binary     0.760     5 0.00759 Preprocessor1_Mo…
3         10      0.179 roc_auc binary     0.758     5 0.0111  Preprocessor1_Mo…
4          5      0.106 roc_auc binary     0.758     5 0.0115  Preprocessor1_Mo…
5          2      0.286 roc_auc binary     0.757     5 0.00868 Preprocessor1_Mo…
best_params <- select_best(fit_wordvec_senti_xgb)

Finalisieren

best_params <- select_best(fit_wordvec_senti_xgb)
tic()
wf_finalized <- finalize_workflow(wf, best_params)
lastfit_xgb <- fit(wf_finalized, data = d_train_new)
toc()
2.853 sec elapsed

Test-Set-Güte

tic()
preds <-
  predict(lastfit_xgb, new_data = d_test_baked)
toc()
0.035 sec elapsed
d_test <-
  germeval_test |> 
  bind_cols(preds) |> 
  mutate(c1 = as.factor(c1))
my_metrics <- metric_set(accuracy, f_meas)
my_metrics(d_test,
           truth = c1,
           estimate = .pred_class)
# A tibble: 2 × 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary         0.714
2 f_meas   binary         0.484

Fazit

Verzichtet man auf ein Rezept mit viel Datenvolument (Wordvektoren blähen das Rezept mächtig auf), so wird das Fitten schlanker und schneller. Schneller auch deshalb, weil ggf. kein Swapping zwischen Speicher und Festplatte mehr nötig ist.