1 Prelúdio

O pacote ensinado pelo livro do Lantz, o neuralnet está desatualizado e algumas funções importantes não estão funcionando mais. Aproveitando isso, resolvi fazer tudo usando o nnet mas dentro do tidymodels, para aprender a fazer já algo mais organizado e seriado.

setwd('/home/heitor/Área de Trabalho/R Projects/Análise Macro/Lab 9')
library(tidyverse)
library(tidymodels)
library(nnet)
library(NeuralNetTools)

2 Importação e Divisão

dds     <- read_csv("concrete.csv")
slice_1 <- initial_split(dds)
train   <- training(slice_1)
test    <- testing(slice_1)

3 Modelo

Vamos criar a estrutura geral do nosso modelo, deixando espaços livres com tune() por serem os parâmetros a serem testados com vários números, reiteragas vezes.

nnet_1 <- mlp(
    hidden_units = integer(tune()),
    penalty      = double(tune()),
    activation   = 'linear') %>% 
    set_mode("regression") %>% 
    set_engine("nnet", verbose = 0) 
    # traduzir para pô-lo em termos de nnet:
nnet_1 %>% translate()
## Single Layer Neural Network Specification (regression)
## 
## Main Arguments:
##   hidden_units = integer(tune())
##   penalty = double(tune())
##   activation = linear
## 
## Engine-Specific Arguments:
##   verbose = 0
## 
## Computational engine: nnet 
## 
## Model fit template:
## nnet::nnet(formula = missing_arg(), data = missing_arg(), weights = missing_arg(), 
##     size = integer(tune()), decay = double(tune()), verbose = 0, 
##     trace = FALSE, linout = TRUE)

4 Tratamentos e Fórmula para Alimentar o Modelo

Defino como os dados alimentarão o modelo já descrito acima e aplico um tratamento de normalização nos dados, usando desvio da média e desvio-padrão.

recipe_1 <- recipe(strength~.,
                   data = train) %>% 
    step_normalize(all_numeric_predictors()) %>% 
    prep()

recipe_1 %>% bake(new_data=NULL)
## # A tibble: 772 × 9
##     cement   slag    ash  water superplastic coarseagg fineagg    age strength
##      <dbl>  <dbl>  <dbl>  <dbl>        <dbl>     <dbl>   <dbl>  <dbl>    <dbl>
##  1  0.488  -0.866 -0.861  0.469       -1.04      0.667  0.597  -0.599     17.4
##  2  1.39   -0.609  1.20  -0.628        0.443    -1.87  -0.288   0.157     74.4
##  3  0.444  -0.866  1.29   0.789        0.760    -2.13   0.242  -0.275     38.6
##  4 -1.11   -0.866  1.69  -0.240       -0.290     1.09   0.0870 -0.661     10.8
##  5 -0.273  -0.866  0.681 -1.62         1.33      0.202  1.45    0.835     61.0
##  6  0.0557  1.48  -0.861 -1.69         0.826     0.412  0.381  -0.599     38  
##  7  0.507  -0.662  1.68  -0.674        1.94      0.305 -1.51   -0.275     47.3
##  8  0.662  -0.866 -0.861  0.469       -1.04      1.05   0.447   0.681     40.7
##  9 -0.273  -0.866  0.681 -1.62         1.33      0.202  1.45   -0.491     42.3
## 10  0.729  -0.866  1.35   0.529        0.793    -2.13   0.0658 -0.275     40.9
## # … with 762 more rows

5 Workflow

Junto o modelo descrito e os dados tratados, formando um workflow:

nnet_1_wrkflw <- workflow() %>%
    add_model(nnet_1) %>%
    add_recipe(recipe_1)

6 Validação Cruzada

Defino a validação cruzada em grupos de cinco, ou seja, a amostra de treino será \(\frac{4}{5}\) passando por várias reamostragens.

valid_1 <- vfold_cv(train, v = 5)

7 Treinamento

Treinaremos o modelo com vários parâmetros e selecionaremos de acordo com ccc: coeficiente de concordância de correlação. Mostraremos um gráfico com os parâmetros testados.

nnet_1_trained <- nnet_1_wrkflw %>% 
    tune_grid(valid_1,
              grid    = 15,
              control = control_grid(save_pred = T),
              metrics = metric_set(ccc, mae)) 

nnet_1_trained %>% show_best(n=15)
## # A tibble: 15 × 8
##    hidden_units  penalty .metric .estimator  mean     n std_err .config         
##           <int>    <dbl> <chr>   <chr>      <dbl> <int>   <dbl> <chr>           
##  1            8 5.14e- 3 ccc     standard   0.914     5 0.0119  Preprocessor1_M…
##  2           10 1.44e- 5 ccc     standard   0.907     5 0.0103  Preprocessor1_M…
##  3            6 1.07e- 6 ccc     standard   0.907     5 0.00735 Preprocessor1_M…
##  4            9 1.18e- 8 ccc     standard   0.905     5 0.0158  Preprocessor1_M…
##  5            9 2.70e- 2 ccc     standard   0.905     5 0.00910 Preprocessor1_M…
##  6            7 4.88e-10 ccc     standard   0.903     5 0.0166  Preprocessor1_M…
##  7            5 1.93e- 1 ccc     standard   0.902     5 0.00796 Preprocessor1_M…
##  8            7 8.53e- 5 ccc     standard   0.899     5 0.00750 Preprocessor1_M…
##  9            4 8.57e- 7 ccc     standard   0.898     5 0.0114  Preprocessor1_M…
## 10            4 2.53e- 4 ccc     standard   0.898     5 0.00978 Preprocessor1_M…
## 11            6 2.18e-10 ccc     standard   0.894     5 0.0129  Preprocessor1_M…
## 12            3 4.71e- 8 ccc     standard   0.886     5 0.0128  Preprocessor1_M…
## 13            2 2.38e- 1 ccc     standard   0.878     5 0.0113  Preprocessor1_M…
## 14            2 5.39e- 4 ccc     standard   0.857     5 0.0161  Preprocessor1_M…
## 15            1 4.14e- 9 ccc     standard   0.595     5 0.151   Preprocessor1_M…
# (6.1) Auto-plot ---
ggplot2::autoplot(nnet_1_trained)

8 Testando

Selecionaremos o melhor modelo, usando o ccc.

best_tune  <- select_best(nnet_1_trained, 'ccc')
nnet_final <- nnet_1 %>%
    finalize_model(best_tune)

Aplicaremos esse modelo, nnet_final na partição feita em slice_1 e com a organização dos dados de acordo com recipe_1. Vemos que conseguimos aumentar para 92.37% a correlação entre previsto e verdadeiro.

nnet_final_wrkflw <- workflow() %>% 
    add_recipe(recipe_1) %>% 
    add_model(nnet_final) %>% 
    last_fit(slice_1) %>% 
    collect_predictions()
nnet_final_wrkflw
## # A tibble: 258 × 5
##    id               .pred  .row strength .config             
##    <chr>            <dbl> <int>    <dbl> <chr>               
##  1 train/test split  23.9     1     29.9 Preprocessor1_Model1
##  2 train/test split  27.8    18     44.9 Preprocessor1_Model1
##  3 train/test split  22.6    22     21.9 Preprocessor1_Model1
##  4 train/test split  24.6    25     31.7 Preprocessor1_Model1
##  5 train/test split  45.3    27     49.2 Preprocessor1_Model1
##  6 train/test split  32.7    30     33.8 Preprocessor1_Model1
##  7 train/test split  38.4    32     39.3 Preprocessor1_Model1
##  8 train/test split  59.8    34     44.5 Preprocessor1_Model1
##  9 train/test split  21.3    39     17.5 Preprocessor1_Model1
## 10 train/test split  39.0    43     37.4 Preprocessor1_Model1
## # … with 248 more rows
cor(nnet_final_wrkflw$.pred, nnet_final_wrkflw$strength)
## [1] 0.9282894

Por fim, faço duas ilustrações: um plot dos dados originais contra os previstos, com a estimação perfeita sendo a linha vermelha; e a representação da rede neural.

nnet_final_wrkflw %>% 
    select(.row, .pred, strength) %>% 
    ggplot() +
    aes(x = strength,
        y = .pred) +
    geom_point() +
    geom_abline(intercept = 0,
                slope     = 1,
                color     ='red',
                size      = .8)

Para fazer a representação da rede neural, precisamos traduzir o que foi feito em mlp() para colocar como um objeto nnet e fazer o gráfico.

nnet_final %>% translate()
## Single Layer Neural Network Specification (regression)
## 
## Main Arguments:
##   hidden_units = 8
##   penalty = 0.00514168063170647
##   activation = linear
## 
## Engine-Specific Arguments:
##   verbose = 0
## 
## Computational engine: nnet 
## 
## Model fit template:
## nnet::nnet(formula = missing_arg(), data = missing_arg(), weights = missing_arg(), 
##     size = 8L, decay = 0.00514168063170647, verbose = 0, trace = FALSE, 
##     linout = TRUE)
nnet3 <-nnet(strength ~ cement+slag+ash+water+superplastic+coarseagg+fineagg+age,
             size = 8, # as penalty
             data = recipe(strength~., data = train) %>% 
                step_normalize(all_numeric_predictors()) %>% 
                prep() %>% bake(new_data=NULL),
             decay = 0.0218099079606513,
             verbose = 0, trace = FALSE, 
             linout = TRUE)
nnet3 %>% plotnet()