11  Redes neurais para regressão

Autor

Robson Bruno Dutra Pereira

11.1 Definições iniciais

Seja um vetor de \(K\) variáveis regressoras \(\mathbf{x}=[x_1,x_2,\ldots,x_K]^T\), uma variável dependente ou de resposta contínua, \(y \in \mathbf{R}\) e \(N\) observações de treino disponíveis, \(\{x_1,y_1\}, \{x_2,y_2\}, \ldots, \{x_N,y_N\}\).

Uma rede neural com 1 camada oculta é ilustrada na Figura 11.1. Esta rede apresenta \(K\) variáveis preditoras ou independentes na camada de entrada, \(P\) neurônios na camada oculta, \(h_1(\mathbf{x})\), \(h_2(\mathbf{x})\), …, \(h_P(\mathbf{x})\), e entrega na camada de saída o modelo \(f(\mathbf{x})\).

Figura 11.1: Rede neural com uma camada oculta

Começando de trás para frente, o modelo final pode ser definido a partir da Equação 11.1. \[ \begin{matrix} f(\mathbf{x})=v_0 + v_1h_1(\mathbf{x}) + v_2h_2(\mathbf{x}) + \ldots + v_Ph_P(\mathbf{x}) \\ f(\mathbf{x})=v_0 + \sum_{p=1}^Pv_ph_p(\mathbf{x}) = \mathbf{v}^T\mathbf{h} \end{matrix} \tag{11.1}\]

onde \(\mathbf{v} = [v_0, v_1, ..., v_P]^T\) é um vetor de pesos do modelo final na camada oculta e \(\mathbf{h} = [1, h_1(\mathbf{x}), ... h_P(\mathbf{x})]^T\) é um vetor de funções computadas na camada oculta. O \(p\)-ésimo termo \(h_p(\mathbf{x})\), \(p=1, ..., P\), da camada oculta é calculado conforme Equação 11.2. \[ \begin{matrix} h_p=g(w_{p0} + w_{p1}x_1 + w_{p2}x_2 + \ldots + w_{pK}x_K) \\ h_p=g(w_{p0} + \sum_{k=1}^Kw_{pk}x_k) = g(\mathbf{w}_p^T\mathbf{x})\\ \end{matrix} \tag{11.2}\]

onde \(\mathbf{w}_p = [w_{p0}, w_{p1}, \ldots, w_{pK}]^T\) é um vetor de pesos do \(p\)-ésimo neurônio na camada de entrada e \(g(z)\) é uma função de ativação a qual deve ser selecionada de forma apropriada ao tipo de problema abordado. É importante observar que tano na camada oculta quanto na camada de saída não foram ilustrados na Figura 11.1 os termos de vício \(v_0\) e \(w_{0p}\), porém são importantes e considerados no modelo. Para problemas de regressão a função de unidade linear retificada (Retified Linear Unit - ReLU) pode ser usada como ativação nas camadas ocultas, sendo esta descrita matematicamente conforme Equação 11.3.

\[ g(z) = max(0,z) = \bigg\{ \begin{matrix} 0,z<0 \\ z,z\geq0 \end{matrix} \tag{11.3}\]

A função de ativação ReLu é plotada a seguir. Uma vez que no processo de otimização dos parâmetros da rede, o gradiente da função perda em relação a tais parâmetros é computado iterativamente, a função de ativação ReLU apresenta a vantagem de ter derivada nula para valores negativos de \(z\), desativando alguns neurônios quando \(z \leq 0\), tornando os cálculos computacionais mais fáceis.

É importante esclarecer que a função ReLU deve ser utilizada em camadas ocultas, conforme a descrição matemática aqui sugere. Ademais em problemas de regressão não se usa uma função de ativação na camada de saída, ainda de acordo com a formulação aqui exposta. Oportunamente, quando o tema classificação for abordado, outras funções de ativação serão expostas, além de adaptações necessárias.

Dadas tais considerações, pode-se escrever o modelo explícito para uma rede neural com uma camada oculta conforme segue. \[ \begin{aligned} f(\mathbf{x})=v_0 + v_1.g(w_{10} + w_{11}x_1 + w_{12}x_2 + \ldots + w_{1K}x_K)\\ + v_2.g(w_{20} + w_{21}x_1 + w_{22}x_2 + \ldots + w_{2K}x_K)\\ + \ldots + v_P.g(w_{P0} + w_{P1}x_1 + w_{P2}x_2 + \ldots + w_{PK}x_K) \end{aligned} \]

Logo, \[ \begin{matrix} f(\mathbf{x})=v_0 + v_1.g(w_{10} + \sum_{k=1}^Kw_{1k}x_k) + v_2.g(w_{20} + \sum_{k=1}^Kw_{2k}x_k) + \ldots \\ + v_P.g(w_{P0} + \sum_{k=1}^Kw_{Pk}x_k) \end{matrix} \]

De forma, mais compacta, temos o modelo já exposto anteriormente. \[ \begin{matrix} f(\mathbf{x})=v_0 + \sum_{p=1}^Pv_p.g(w_{p0} + \sum_{k=1}^Kw_{pk}x_k)\\ f(\mathbf{x})=v_0 + \sum_{p=1}^Pv_ph_p(\mathbf{x}) = \mathbf{v}^T\mathbf{h}\\ \end{matrix} \]

O aprendizado profundo consiste simplesmente em uma rede neural com duas ou mais camadas ocultas.

11.2 Descida do gradiente e propagação para trás

Os parâmetros a serem estimados do modelo de rede neural consistem em \(\mathbf{v} = [v_0, v_1,v_2,\ldots,v_p]^T\) e \(\mathbf{w}_p=[w_{p0},w_{p1},w_{p2},\ldots,w_{pK}]^T\). Tomando as \(N\) observações de treino \((\mathbf{x}_i,y_i)\), \(i=1,...,N\), estima-se o modelo minimizando a função perda quadrática da Equação 11.4. \[ \min_{\{w_p\}_1^N,v} \frac{1}{2} \sum_{i=1}^{N}(y_i-f(\mathbf{x}_i))^2 \tag{11.4}\]

onde

\[ f(\mathbf{x})=v_0+\sum_{p=1}^Pv_p.g(w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik}). \]

Devido a não convexidade da função perda, múltiplas soluções podem estar presentes. A otimização é realizada de forma lenta e iterativa com o algoritmo de descida do gradiente. Seja \(\mathbf{\theta}\) o vetor de todos os parâmetros da rede, \(\mathbf{\theta} = [\mathbf{v}, \mathbf{w}_0, \mathbf{w}_1, ..., \mathbf{w}_P]^T\). A fução perda pode ser reescrita conforme Equação 11.5. \[ L(\theta)=\frac{1}{2} \sum_{i=1}^{N}(y_i-f_\theta(\mathbf{x}_i))^2 \tag{11.5}\]

O algoritmo de descida do gradiente pode ser descrito a partir dos seguintes passos:

  1. Defina um valor inicial \(\theta_0\) para \(\theta\).
  2. Itere até \(L(\theta)\) parar de decrescer ou então até um critério de parada:
  1. Encontre um vetor \(\delta\) de forma que \(\theta_{t+1}=\theta_t+\delta\) reduza \(L(\theta)\).
  2. Faça \(t \leftarrow t+1\).

O valor de \(\delta\) deve ser escolhido na direção de máximo crescimento da função perda. O algoritmo de propagação para trás (backpropagation) considera o gradiente da função perda em relação aos parâmetros da rede, isto é, \[ \nabla L(\theta_t)=\frac{\partial L}{\partial \theta} \Bigg|_{\theta=\theta_t}. \]

Como \(\nabla L(\theta_t)\), que consiste no vetor de derivadas parciais de \(L\) avaliadas em \(\theta_t\), o algoritmo de descida do gradiente busca mover o vetor \(\theta\) na direção contrária do gradiente, considerando uma taxa de aprendizagem \(\rho\) pequena para facilitar a convergência, isto é, \[ \theta_{t+1} \leftarrow \theta_t - \rho \nabla L(\theta_t) \]

A propagação para trás consiste simplesmente na aplicação da regra da cadeia de diferenciação. Como \[ L(\theta)=\sum_{i=1}^NL_i(\theta)=\frac{1}{2} \sum_{i=1}^{N}(y_i-f_\theta(\mathbf{x}_i))^2 \] é uma soma, o gradiente também será uma soma em \(N\). Assim, a derivada pode ser computada termo a termo, para cada observação \(i\): \[ L_i=\frac{1}{2} (y_i-v_0-\sum_{p=1}^Pv_p.g(w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik}))^2. \]

Considerando o termo \(v_p\), a derivada parcial da função perda pela regra da cadeia fica conforme segue:

\[ \frac{\partial L_i}{\partial v_p}=\frac{\partial L_i}{\partial f_\theta}\frac{\partial f_\theta}{\partial v_p} \]

\[ \frac{\partial L_i}{\partial v_p}=-(y_i-f_\theta(\mathbf{x}_i)).g(w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik}). \]

Já derivando em relação ao termo \(w_{pk}\), tem-se: \[ \frac{\partial L_i}{\partial w_{pk}}=\frac{\partial L_i}{\partial f_\theta}\frac{\partial f_\theta}{\partial g(w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik})}\frac{\partial g(w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik})}{\partial (w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik})} \frac{\partial (w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik})}{\partial w_{pk}} \]

\[ \frac{\partial L_i}{\partial w_{pk}}=-(y_i-f_\theta(\mathbf{x}_i))v_pg'(w_{p0}+\sum_{k=1}^Kw_{pk}x_{ik})x_{ik}. \]

Pode-se observar que em ambos resultados das derivadas os resíduos \(y_i-f_\theta(\mathbf{x}_i)\) estão presentes. Ou seja, na diferenciação uma fração do resíduos é atribuída aos parâmetros a partir da regra da cadeia.

Dada a possibilidade de overfitting no processo de otimização dos parâmetros da rede, diversas estratégias são usadas. Por exemplo a regularização ou penalização rígida ou LASSO pode ser aplicada, sendo a função perda modificada conforme Equação 11.6 para o primeiro caso. \[ L(\theta)=\frac{1}{2} \sum_{i=1}^{N}(y_i-f_\theta(\mathbf{x}))^2 +\lambda\sum_p\theta_p^2 \tag{11.6}\]

Outra estratégia utilizada é o dropout, que consiste na remoção aleatória de uma proporção dos neurônios de uma ou mais camadas. Este processo tem, de certa forma, similaridade com a estratégia de regularização via LASSO e também com a estratégia de seleção de variáveis para o particionamento binário recursivo no algoritmo de floresta aleatória.

O processo de treinamento da rede neural envolve a definição da arquitetura da rede, isto é, o número de camadas ocultas e o número de neurônios em cada uma, além da otimização dos hiperparâmetros de encolhimento e dropout. Todos estes podem ser considerados hiperpâmetros a serem otimizados e, para tal, deve-se utilizar de validação cruzada e grid search.

Existem diversos outros tipos de redes neurais, como as redes neurais convolucionais, com grande potencial para classificação de imagens e as redes neurais recorrentes, para problemas de séries temporais, reconhecimento de fala, entre outros. O leitor é convidado a ler a bibliografia citada para mais informações.

11.3 Validação cruzada e grid search para treinar múltiplos modelos de regressão passo a passo via tidymodels

Previsão do preço de computadores.

library(Ecdat) # para dados
library(tidymodels) 
library(modelsummary)
library(finetune) # para grid search
library(dplyr)
library(baguette) # bag_tree

Leitura de dados.

data(Computers)
dados2 <- na.omit(Computers)

Entendendo os dados.

dados2 |> glimpse()
Rows: 6,259
Columns: 10
$ price   <dbl> 1499, 1795, 1595, 1849, 3295, 3695, 1720, 1995, 2225, 2575, 21…
$ speed   <dbl> 25, 33, 25, 25, 33, 66, 25, 50, 50, 50, 33, 66, 50, 25, 50, 50…
$ hd      <dbl> 80, 85, 170, 170, 340, 340, 170, 85, 210, 210, 170, 210, 130, …
$ ram     <dbl> 4, 2, 4, 8, 16, 16, 4, 2, 8, 4, 8, 8, 4, 8, 8, 4, 2, 4, 4, 8, …
$ screen  <dbl> 14, 14, 15, 14, 14, 14, 14, 14, 14, 15, 15, 14, 14, 14, 14, 14…
$ cd      <fct> no, no, no, no, no, no, yes, no, no, no, no, no, no, no, no, n…
$ multi   <fct> no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no…
$ premium <fct> yes, yes, yes, no, yes, yes, yes, yes, yes, yes, yes, yes, yes…
$ ads     <dbl> 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94…
$ trend   <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…

Estatísticas descritivas.

datasummary_skim(dados2)
Unique Missing Pct. Mean SD Min Median Max Histogram
price 808 0 2219.6 580.8 949.0 2144.0 5399.0
speed 6 0 52.0 21.2 25.0 50.0 100.0
hd 59 0 416.6 258.5 80.0 340.0 2100.0
ram 6 0 8.3 5.6 2.0 8.0 32.0
screen 3 0 14.6 0.9 14.0 14.0 17.0
ads 34 0 221.3 74.8 39.0 246.0 339.0
trend 35 0 15.9 7.9 1.0 16.0 35.0
N %
cd no 3351 53.5
yes 2908 46.5
multi no 5386 86.1
yes 873 13.9
premium no 612 9.8
yes 5647 90.2

Criando coluna com combinações das variáveis categóricas que tem níveis desbalanceados.

dados2 <- dados2 |>
  mutate(multi_premium = paste(multi, premium,  
                               sep = '_'))

Separando dados de treino e teste.

set.seed(16)
dados_split2 <- initial_split(dados2, 
                              prop = 0.25,
                              strata = multi_premium)
  
dados_train2 <- training(dados_split2)
dados_test2  <- testing(dados_split2)

set.seed(17)
dados_folds2 <- 
  vfold_cv(v = 10, dados_train2, repeats = 2)

Definindo a receita. A receita cntém o modelo, além dos preprocessamentos realizados nas variáveis preditoras.

normalized_rec2 <- 
  recipe(price ~ speed + hd + ram + screen + cd + multi + premium + ads + trend, 
         data = dados_train2) |> 
  step_normalize(all_numeric_predictors()) |>
  step_dummy(all_nominal_predictors()) 

Definindo os métodos de regressão a serem testados. Deve-se definir os hiperparâmetros a serem testados e o pacote (engine), uma vez que geralmente há várias opções de pacotes em R para um mesmo método.

linear_reg_spec <- 
  linear_reg(penalty = tune(), mixture = tune()) |> 
  set_engine("glmnet")

tree_spec <- decision_tree(tree_depth = tune(), min_n = tune(), cost_complexity = tune()) |> 
  set_engine("rpart") |> 
  set_mode("regression")

bag_cart_spec <- 
   bag_tree(tree_depth = tune(), min_n = tune(), cost_complexity = tune()) |> 
   set_engine("rpart") |> 
   set_mode("regression")

rforest_spec <- rand_forest(mtry = tune(), min_n = tune(), trees = tune()) |> 
  set_engine("ranger") |> 
  set_mode("regression")

xgb_spec <- # evolution of GBM
  boost_tree(tree_depth = tune(), learn_rate = tune(), loss_reduction = tune(), 
             min_n = tune(), sample_size = tune(), trees = tune()) |> 
  set_engine("xgboost") |> 
  set_mode("regression")

svm_r_spec <- 
  svm_rbf(cost = tune(), rbf_sigma = tune()) |> 
  set_engine("kernlab") |> 
  set_mode("regression")

svm_p_spec <- 
  svm_poly(cost = tune(), degree = tune()) |> 
  set_engine("kernlab") |> 
  set_mode("regression")

mars_spec <- # method similar to GAM
   mars(prod_degree = tune()) %>%  
   set_engine("earth") %>% 
   set_mode("regression")

nnet_spec <- 
  mlp(hidden_units = tune(), penalty = tune(), epochs = tune()) |> 
  set_engine("nnet", MaxNWts = 2600) |> 
  set_mode("regression")

nnet_param <- 
  nnet_spec |> 
  extract_parameter_set_dials() |> 
  update(hidden_units = hidden_units(c(1, 27)))

Definindo o workflow. O workflow contém a receita e os modelos.

normalized2 <- 
  workflow_set(
    preproc = list(normalized = normalized_rec2), 
    models = list(linear_reg = linear_reg_spec,
                  tree = tree_spec,
                  bagging = bag_cart_spec,
                  rforest = rforest_spec,
                  XGB = xgb_spec,
                  SVM_radial = svm_r_spec, 
                  SVM_poly = svm_p_spec,
                  MARS = mars_spec,
                  neural_network = nnet_spec)
  )
normalized2
# A workflow set/tibble: 9 × 4
  wflow_id                  info             option    result    
  <chr>                     <list>           <list>    <list>    
1 normalized_linear_reg     <tibble [1 × 4]> <opts[0]> <list [0]>
2 normalized_tree           <tibble [1 × 4]> <opts[0]> <list [0]>
3 normalized_bagging        <tibble [1 × 4]> <opts[0]> <list [0]>
4 normalized_rforest        <tibble [1 × 4]> <opts[0]> <list [0]>
5 normalized_XGB            <tibble [1 × 4]> <opts[0]> <list [0]>
6 normalized_SVM_radial     <tibble [1 × 4]> <opts[0]> <list [0]>
7 normalized_SVM_poly       <tibble [1 × 4]> <opts[0]> <list [0]>
8 normalized_MARS           <tibble [1 × 4]> <opts[0]> <list [0]>
9 normalized_neural_network <tibble [1 × 4]> <opts[0]> <list [0]>
all_workflows2 <- 
  bind_rows(normalized2) |> 
  # Make the workflow ID's a little more simple: 
  mutate(wflow_id = gsub("(simple_)|(normalized_)", "", wflow_id))
all_workflows2
# A workflow set/tibble: 9 × 4
  wflow_id       info             option    result    
  <chr>          <list>           <list>    <list>    
1 linear_reg     <tibble [1 × 4]> <opts[0]> <list [0]>
2 tree           <tibble [1 × 4]> <opts[0]> <list [0]>
3 bagging        <tibble [1 × 4]> <opts[0]> <list [0]>
4 rforest        <tibble [1 × 4]> <opts[0]> <list [0]>
5 XGB            <tibble [1 × 4]> <opts[0]> <list [0]>
6 SVM_radial     <tibble [1 × 4]> <opts[0]> <list [0]>
7 SVM_poly       <tibble [1 × 4]> <opts[0]> <list [0]>
8 MARS           <tibble [1 × 4]> <opts[0]> <list [0]>
9 neural_network <tibble [1 × 4]> <opts[0]> <list [0]>

Realizando grid search e validação cruzada.

race_ctrl <-
  control_race(
    save_pred = TRUE,
    parallel_over = "everything",
    save_workflow = TRUE
  )

race_results2 <-
  all_workflows2 |>
  workflow_map(
    "tune_race_anova",
    seed = 1503,
    resamples = dados_folds2,
    grid = 25,
    control = race_ctrl
  )
Registered S3 method overwritten by 'butcher':
  method                 from    
  as.character.dev_topic generics
i Creating pre-processing data to finalize 1 unknown parameter: "mtry"

Anexando pacote: 'plotrix'
O seguinte objeto é mascarado por 'package:scales':

    rescale
→ A | warning: A correlation computation is required, but `estimate` is constant and has 0
               standard deviation, resulting in a divide by 0 error. `NA` will be returned.
There were issues with some computations   A: x1
There were issues with some computations   A: x2
There were issues with some computations   A: x14
There were issues with some computations   A: x19
There were issues with some computations   A: x20

Extraindo métricas para avaliar os resultados da validação cruzada.

collect_metrics(race_results2) |> 
  filter(.metric == "rmse") |>
  arrange(mean)
# A tibble: 35 × 9
   wflow_id       .config   preproc model .metric .estimator  mean     n std_err
   <chr>          <chr>     <chr>   <chr> <chr>   <chr>      <dbl> <int>   <dbl>
 1 XGB            pre0_mod… recipe  boos… rmse    standard    173.    20    3.22
 2 rforest        pre0_mod… recipe  rand… rmse    standard    197.    20    3.95
 3 SVM_poly       pre0_mod… recipe  svm_… rmse    standard    213.    20    3.60
 4 bagging        pre0_mod… recipe  bag_… rmse    standard    217.    20    4.77
 5 SVM_radial     pre0_mod… recipe  svm_… rmse    standard    219.    20    4.58
 6 MARS           pre0_mod… recipe  mars  rmse    standard    230.    20    3.47
 7 tree           pre0_mod… recipe  deci… rmse    standard    247.    20    5.51
 8 neural_network pre0_mod… recipe  mlp   rmse    standard    254.    20    8.69
 9 neural_network pre0_mod… recipe  mlp   rmse    standard    261.    20    5.63
10 neural_network pre0_mod… recipe  mlp   rmse    standard    268.    20    8.79
# ℹ 25 more rows
collect_metrics(race_results2) |> 
  filter(.metric == "rsq") |>
  arrange(desc(mean))
# A tibble: 35 × 9
   wflow_id       .config   preproc model .metric .estimator  mean     n std_err
   <chr>          <chr>     <chr>   <chr> <chr>   <chr>      <dbl> <int>   <dbl>
 1 XGB            pre0_mod… recipe  boos… rsq     standard   0.912    20 0.00318
 2 rforest        pre0_mod… recipe  rand… rsq     standard   0.887    20 0.00341
 3 SVM_poly       pre0_mod… recipe  svm_… rsq     standard   0.866    20 0.00511
 4 bagging        pre0_mod… recipe  bag_… rsq     standard   0.862    20 0.00491
 5 SVM_radial     pre0_mod… recipe  svm_… rsq     standard   0.859    20 0.00515
 6 MARS           pre0_mod… recipe  mars  rsq     standard   0.844    20 0.00454
 7 tree           pre0_mod… recipe  deci… rsq     standard   0.819    20 0.00802
 8 neural_network pre0_mod… recipe  mlp   rsq     standard   0.806    20 0.0147 
 9 neural_network pre0_mod… recipe  mlp   rsq     standard   0.797    20 0.00968
10 neural_network pre0_mod… recipe  mlp   rsq     standard   0.784    20 0.0152 
# ℹ 25 more rows

Visualizando desempenho dos métodos.

IC_rmse2 <- collect_metrics(race_results2) |> 
  filter(.metric == "rmse") |> 
  group_by(wflow_id) |>
  filter(mean == min(mean)) |>
  group_by(wflow_id) |> 
  arrange(mean) |> 
  ungroup()

IC_r22 <- collect_metrics(race_results2) |> 
  filter(.metric == "rsq") |> 
  group_by(wflow_id) |>
  filter(mean == max(mean)) |>
  group_by(wflow_id) |> 
  arrange(desc(mean)) |> 
  ungroup() 

IC2 <- bind_rows(IC_rmse2, IC_r22)

ggplot(IC2, aes(x = factor(wflow_id, levels = unique(wflow_id)), y = mean)) +
  facet_wrap(~.metric, scales = "free") +
  geom_point(stat="identity", aes(color = wflow_id), pch = 1) +
  geom_errorbar(stat="identity", aes(color = wflow_id, 
                                     ymin=mean-1.96*std_err,
                                     ymax=mean+1.96*std_err), width=.2) + 
  labs(y = "", x = "method") + theme_bw() +
  theme(legend.position = "none",
        axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

Obtendo níveis ótimos dos hiperparâmetros do melhor modelo.

best_rmse2 <- 
  race_results2 |> 
  extract_workflow_set_result("XGB") |> 
  select_best(metric = "rmse")
best_rmse2
# A tibble: 1 × 7
  trees min_n tree_depth learn_rate loss_reduction sample_size .config         
  <int> <int>      <int>      <dbl>          <dbl>       <dbl> <chr>           
1  1416     9          5     0.0226           3.48       0.962 pre0_mod18_post0
XGB_test_results <- 
  race_results2 |> 
  extract_workflow("XGB") |> 
  finalize_workflow(best_rmse2) |> 
  last_fit(split = dados_split2)

collect_metrics(XGB_test_results)
# A tibble: 2 × 4
  .metric .estimator .estimate .config        
  <chr>   <chr>          <dbl> <chr>          
1 rmse    standard     170.    pre0_mod0_post0
2 rsq     standard       0.915 pre0_mod0_post0

Plotando previstos versus observados para os dados de teste dos dois melhores métodos.

best_rmse2_2 <- 
  race_results2 |> 
  extract_workflow_set_result("rforest") |> 
  select_best(metric = "rmse")
best_rmse2_2
# A tibble: 1 × 4
   mtry trees min_n .config         
  <int> <int> <int> <chr>           
1     6  1167     2 pre0_mod17_post0
rf_test_results <- 
  race_results2 |> 
  extract_workflow("rforest") |> 
  finalize_workflow(best_rmse2_2) |> 
  last_fit(split = dados_split2)

collect_metrics(rf_test_results)
# A tibble: 2 × 4
  .metric .estimator .estimate .config        
  <chr>   <chr>          <dbl> <chr>          
1 rmse    standard     196.    pre0_mod0_post0
2 rsq     standard       0.888 pre0_mod0_post0
test_results2 <- rbind(XGB_test_results |>
                        collect_predictions(),
                       rf_test_results |>
                        collect_predictions())

test_results2$method <- c(rep("XGB", nrow(XGB_test_results |>
                        collect_predictions())),
                         rep("rforest", nrow(rf_test_results |>
                        collect_predictions())))

test_results2 |>
  ggplot(aes(x = price, y = .pred)) + 
  geom_abline(color = "gray50", lty = 2) + 
  geom_point(alpha = 0.2) + 
  facet_grid(col = vars(method)) +
  coord_obs_pred() + 
  labs(x = "observed", y = "predicted") + 
  theme_bw()

Modelo final.

XGB_final <- race_results2 |> 
  extract_workflow("XGB") |> 
  finalize_workflow(best_rmse2)
XGB_final
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: boost_tree()

── Preprocessor ────────────────────────────────────────────────────────────────
2 Recipe Steps

• step_normalize()
• step_dummy()

── Model ───────────────────────────────────────────────────────────────────────
Boosted Tree Model Specification (regression)

Main Arguments:
  trees = 1416
  min_n = 9
  tree_depth = 5
  learn_rate = 0.0226030302714192
  loss_reduction = 3.48070058842842
  sample_size = 0.9625

Computational engine: xgboost 

Referências

Bishop, Christopher M., and Hugh Bishop. “Deep learning: foundations and concepts.” (2024).

Gareth, J., Daniela, W., Trevor, H., & Robert, T. (2013). An introduction to statistical learning: with applications in R. Spinger.