Ako zrýchliť XGBoost v R: Benchmark trénovania modelu na CPU vs. GPU
Ako rýchlo dokážete vytrénovať model na veľkých datasetoch? Toto je otázka, ktorú si kladie mnoho analytikov a data scientistov, keď začínajú pracovať s pokročilými algoritmami strojového učenia, ako je XGBoost. V tomto článku si ukážeme, ako vytvoriť klasifikačný model pomocou XGBoost v R a porovnáme si dĺžku výpočtu pri použití CPU a GPU.
Štandardná verzia knižnice XGBoost na CRAN však nepodporuje parameter device = “cuda”. Preto je potrebné nainštalovať špeciálnu verziu XGBoostu – buď skompilovať z repozitára na GitHube, alebo využiť poskytovaný inštalačný balík (napríklad pre Windows takto):
Inštalácia xgboost
options(timeout = 1200)
<- "https://s3-us-west-2.amazonaws.com/xgboost-nightly-builds/release_2.0.0/xgboost_r_gpu_win64_82d846bbeb83c652a0b1dff0e3519e67569c4a3d.tar.gz"
xgboost_url install.packages(xgboost_url, repos = NULL, type = "source")
CPU vs GPU
Na veľkých datasetoch môže trénovanie modelov trvať hodiny, ak nie dni, najmä na bežných procesoroch (CPU). Moderné grafické karty (GPU) od spoločnosti Nvidia však umožňujú paralelné spracovanie dát, čo dramaticky zrýchľuje niektoré výpočtové operácie.
CPU je ako pracovník, ktorý rieši úlohy sekvenčne, zatiaľ čo GPU je tím tisícov pracovníkov, ktorí dokážu riešiť mnohé úlohy naraz.
Ešte predtým, než prejdeme k modelovaniu, môžeme si zistiť, aký hardvér vlastne používame.
AMD Ryzen 9 7900X je vysoko výkonný desktopový procesor s 12 fyzickými jadrami (24 vláknami), ktorý je všeobecne považovaný za high-end/výkonný segment pre multivláknové operácie (aktuálne je dostupná už novšia generácia AMD procesorov). K nemu je 67.8 GB RAM, čo dáva dostatočnú rezervu na spracovanie stredne veľkých až väčších datasetov. Grafická karta Nvidia RTX 4060Ti s 8 GB VRAM sa radí do strednej až vyššej strednej triedy, no pri veľmi rozsiahlych datasetoch môže byť 8 GB VRAM limitujúce. Napriek tomu dokáže pri bežných projektoch a primerane veľkých dátach výrazne zrýchliť trénovanie XGBoost modelov v porovnaní s CPU. Viac info nájdete v staršom blogu.
1. Načítanie knižníc a príprava prostredia
Na začiatku si načítame potrebné balíky. Je dobré nastaviť aj niektoré globálne možnosti, napríklad options(scipen = 999)
, aby sme obmedzili vedecký zápis čísel.
Knižnice
options(scipen = 999)
library(data.table)
library(readr)
library(xgboost)
library(tidymodels)
library(dplyr)
library(stringr)
library(doParallel)
library(tictoc)
data.table nám umožní rýchle nahratie .csv. tidymodels je framework pre tvorbu a ladenie modelov. xgboost je samotná implementácia gradient boosting algoritmov. tictoc nám zmeria čas operácie (funkcie tic() a toc())
2. Načítanie, čistenie a rozdelenie dát
Začneme s dvoma datasetmi: train.csv a test.csv. Oba súbory sú dostupné na Kaggle.
Dátový stĺpec id odstránime (neobsahuje relevantné informácie pre model). Premennú class transformujeme tak, aby hodnota “e” mala label 0, a “p” label 1. Prázdne stringy (textové premenné) nahradíme za “missing”, pretože chceme explicitne ošetriť chýbajúce hodnoty.
Príprava dát
## data prep ----
<- data.table::fread("data/train.csv") |>
train select(-id) |>
mutate(class = factor(class, levels = c("p", "e"), labels = c(1, 0))) |>
mutate(across(where(is.character), ~ if_else(. == "", "missing", .)))
dim(train)
[1] 3116945 21
Finálny trénovací dataset má 21 stĺpcov vrátane triedy, ktorú budeme predikovať a 3 116 945 záznamov.
Nasleduje klasické rozdelenie pomocou initial_split z rsample: prop = 0.7
znamená, že 70 % záznamov ide do trénovacej množiny a 30 % do testovacej, strata = class
zachováva približný pomer tried v oboch množinách.
Pri ladení modelu použijeme 10-násobnú cross validáciu.
Rozdelenie dát
set.seed(123)
<- initial_split(train, prop = 0.7, strata = class)
split
<- training(split)
train_set <- testing(split)
test_set
set.seed(123)
<- vfold_cv(train_set, strata = class) folds
3. Pre-Processing
V recipe()
si definujeme jednotlivé kroky v tzv. recipe pipeline.
step_other()
s threshold = 0.005 zlúči veľmi vzácne kategórie (s podielom < 0.5 %) do kategórie “other”. step_string2factor()
prevedie stringové atribúty na faktory. step_dummy()
vytvorí dummy premenné z faktorov.
Nastavenie pre-processingu
<- recipe(class ~ ., data = train_set) |>
recipe step_other(all_nominal_predictors(), threshold = 0.005) |>
step_string2factor(all_nominal_predictors()) |>
step_dummy(all_nominal_predictors())
4. Definícia XGBoost modelov (CPU a GPU)
4.1. Základné XGBoost parametre
- trees = 1000: Počet stromov.
- tree_depth = tune(): Hĺbka stromu, ktorú budeme ladiť.
- min_n = tune(): Minimálny počet záznamov v koncovom uzle stromu (v XGBoost často min_child_weight).
- loss_reduction = tune(): (v XGBoost označované ako gamma) – ovplyvňuje, o koľko sa musí znížiť chyba, aby došlo k deleniu uzla.
- sample_size = tune(): (v XGBoost subsample) – podiel vzoriek použitých pre každý strom.
- mtry = tune(): (v XGBoost colsample_bynode) – podiel stĺpcov vyberaných pri každom delení.
- learn_rate = tune(): (v XGBoost eta) – reguluje rýchlosť učenia.
Rozdiel medzi CPU a GPU modelmi: Zásadné je device = “cuda” a tree_method = “hist”. Týmito parametrami vravíme xgboostu, že chceme použiť GPU. Nastavujeme nthread = 12
, aby sme využili všetky dostupné jadrá CPU.
4.2. Model pre CPU
Definovanie CPU modelu
<- boost_tree(
xgb_cpu trees = 1000,
tree_depth = tune(), min_n = tune(),
loss_reduction = tune(), ## first three: model complexity
sample_size = tune(), mtry = tune(), ## randomness
learn_rate = tune() ## step size
|>
) set_engine("xgboost", nthread = 12) |>
set_mode("classification")
4.3. Model pre GPU
Definovanie GPU modelu
<- boost_tree(
xgb_gpu trees = 1000,
tree_depth = tune(), min_n = tune(),
loss_reduction = tune(), ## first three: model complexity
sample_size = tune(), mtry = tune(), ## randomness
learn_rate = tune() ## step size
|>
) set_engine("xgboost", device = "cuda", tree_method = "hist", nthread = 12) |>
set_mode("classification")
5. Workflows
V tidymodels je workflow elegantný spôsob, ako zlúčiť recept a model:
Vytvorenie workflows
<- workflow() |>
xgb_cpu_wf add_recipe(recipe) |>
add_model(xgb_cpu)
<- workflow() |>
xgb_gpu_wf add_recipe(recipe) |>
add_model(xgb_gpu)
Aby sme mohli ladiť hyperparametre, vytvoríme si ešte bayes_param objekt. Použijeme finalize(mtry(), train_set), aby sme odhadli maximálnu hodnotu mtry na základe počtu stĺpcov.
Extrakcia hyperparametrov
<- xgb_gpu_wf %>%
bayes_param extract_parameter_set_dials() %>%
update(mtry = finalize(mtry(), train_set))
6. Bayesovské ladenie a meranie času
Ladenie bude prebiehať v 6 iteráciách (parametrizovaných v iter = 6). Ide len o ukážku, v reálnom projekte by sme spravidla volili viac iterácií (10, 20, či viac), aby sme dôkladnejšie preskúmali priestor parametrov.
6.1. CPU – tune_bayes
Tuning CPU modelu
tic()
set.seed(234)
<- tune_bayes(
cpu_xgb_res
xgb_cpu_wf,param_info = bayes_param,
resamples = folds,
iter = 6
)toc()
# 24278.11 sec elapsed
6.2. GPU – tune_bayes
Tuning GPU modelu
tic()
set.seed(345)
<- tune_bayes(
gpu_xgb_res
xgb_gpu_wf,param_info = bayes_param,
resamples = folds,
iter = 6
)toc()
# 7345.68 sec elapsed
Porovnanie času výpočtu
- CPU: ~ 6 hodín 45 minút
- GPU: ~ 2 hodiny
GPU model je teda približne 3x rýchlejší, čo predstavuje významnú úsporu času pri práci na veľkých datasetoch. V praxi ešte záleží na type GPU, verzii CUDA, atď.
7. Výber najlepších parametrov a finálny model
Po skončení hyperparameter tuningu si môžeme pozrieť metriky cez collect_metrics(), a vybrať najlepšie nastavenia napr. na základe metriky roc_auc.
Výsledné CPU metriky
<- collect_metrics(cpu_xgb_res)
metrics_cpu <- select_best(cpu_xgb_res, metric = "roc_auc")
best_cpu
<- metrics_cpu |>
cpu_roc_auc filter(.metric == "roc_auc") |>
slice_max(mean) |>
pull(mean)
Pre CPU model je výsledná hodnota 0.9971878.
Výsledné GPU metriky
<- collect_metrics(gpu_xgb_res)
metrics_gpu <- select_best(gpu_xgb_res, metric = "roc_auc")
best_gpu
<- metrics_gpu |>
gpu_roc_auc filter(.metric == "roc_auc") |>
slice_max(mean) |>
pull(mean)
Pre GPU je to 0.9972345.
GPU model nielen že trénuje rýchlejšie, ale dosahuje aj lepšiu presnosť. Je to však veľmi individuálne, závisí od datasetu i parametrov.
Zhrnutie
Čo sme videli?
- XGBoost sa dá používať na CPU aj GPU pomocou jednoduchých parametrov (device = “cuda”, tree_method = “hist”).
- Čas výpočtu môže klesnúť až na zlomok, ak máme vhodnú GPU a väčší dataset.
- Pri ladení parametrov sme použili Bayesovskú optimalizáciu, ktorá je síce efektívnejšia vo vyhľadávaní správnych hyperparametrov, ale je aj výpočtovo náročnejšia než jednoduchšie metódy.
Praktické tipy a nuansy
- Pamäť GPU môže byť limitujúca. Veľké datasety nemusia vojsť do GPU pamäte.
- Overheads: Pri relatívne malých datasetoch môže byť efekt GPU nižší alebo dokonca negatívny pre “overheads” spojené s kopírovaním dát medzi CPU a GPU.
- Data pre-processing: Prechod na GPU je nápomocný hlavne pri trénovaní modelu, ale pre-processing zostáva často na CPU. Ak máte rozsiahlu prípravu dát, toto stále môže byť “bottleneck”. Aj z tohto dôvodu ponechávame nthreads na dostatočne vysokej hodnote.
- Viac iterácií v Bayesovskej optimalizácii obvykle pomôže presnejšie nájsť najlepšie hyperparametre.
Záver
Modelovanie s XGBoost algoritmom na GPU môže drasticky skrátiť čas trénovania oproti CPU, najmä pri veľkých datasetoch. Ak je to aj Váš prípad a navyše pravidelne pracujete so zložitými modelmi, GPU je dlhodobou investíciou, ktorá šetrí čas a zvyšuje efektivitu práce. Pre menšie projekty však bude postačujúci aj výkon moderného CPU.
Leave a Reply