Otimização de parâmetros do modelo de redes neurais - Uma análise através da regressão Beta

Heitor Victor

2019-11-16

Função para estudar os parâmetros do modelo

Neste projeto será feito um estudo de simulação para encontrar o melhor caminho para a Tuning dos hiperparâmetros do modelo de rede neural. Usaremos uma rede neural com 1 camada inicial e 1 camada interna. Abaixo uma função para gerar a simulação.

estuda_rede <- function(X_train,
                        X_test,
                        Y_train,
                        Y_test,
                        neurons1,
                        neurons2,# Numeric Vector
                        batch_size, # Numeric Vector
                        epochs, # Numeric Vector
                        dropout1, # Numeric Vector
                        dropout2, # Numeric Vector
                        optimizer = c("Adam","RMSprop"),
                        loss = c("categorical_hinge","categorical_crossentropy"),
                        activation1 = c("relu","elu"),
                        activation2 = c("relu","elu"),
                        last_activation = c("softmax","softplus")){
  require(progress)
  a=b=c=d=e=f=g=h=i=j=n=1
 
  combinations <- data.frame(neurons1 = NA,
                             neurons2 = NA,
                             batch_size = NA,
                             epochs = NA,
                             dropout1 = NA,
                             dropout2 = NA,
                             optimizer = NA,
                             loss = NA,
                             activation1 = NA,
                             activation2 = NA,
                             last_activation = NA)
  n <- length(neurons1)*length(neurons2)*length(batch_size)*length(epochs)*length(dropout1)*
    length(dropout2)*length(optimizer)*length(loss)*length(activation1)*
    length(activation2)*length(last_activation)
  
  cat("Total of combinations: ",n,"\nCalculating combinations \n")

  for(a in 1:length(neurons1)){
    for(b in 1:length(neurons2)){
      for(c in 1:length(batch_size)){
        for(d in 1:length(epochs)){
          for(e in 1:length(dropout1)){
            for(f in 1:length(dropout2)){
              for(g in 1:length(optimizer)){
                for(h in 1:length(loss)){
                  for(i in 1:length(activation1)){
                    for(j in 1:length(activation2)){
                      for(l in 1:length(last_activation)){
                      aux <- data.frame(neurons1 = neurons1[a],
                                        neurons2 = neurons2[b],
                                        batch_size = batch_size[c],
                                        epochs = epochs[d],
                                        dropout1 = dropout1[e],
                                        dropout2 = dropout2[f],
                                        optimizer = optimizer[g],
                                        loss = loss[h],
                                        activation1 = activation1[i],
                                        activation2 = activation2[j],
                                        last_activation = last_activation[l])
                      combinations <- rbind.data.frame(combinations,aux)
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  perda <- numeric(n)
  res <- numeric(n)
  
  combinations <- na.omit(combinations)
  
  for(k in 1:n){
    
    cat("\n Treinamento",k,"de ",n)
    
    rnn_model <- keras_model_sequential() 
    
    rnn_model %>% 
      layer_dense(units = combinations$neurons1[k],
                  activation = combinations$activation1[k],
                  input_shape = ncol(X_train)) %>% 
      layer_dropout(rate = combinations$dropout1[k]) %>% 
      layer_dense(units = combinations$neurons2[k], activation = combinations$activation2[k]) %>%
      layer_dropout(rate = combinations$dropout2[k]) %>%
      layer_dense(units = ncol(Y_train), activation = combinations$last_activation[k])
    
    if(combinations$optimizer[k]=="Adam"){
      rnn_model %>% compile(
        loss = combinations$loss[k], #categorical_accuracy
        optimizer = optimizer_adam(),
        metrics = c('accuracy')
      )
    }
    else{
      rnn_model %>% compile(
        loss = combinations$loss[k], #categorical_accuracy
        optimizer = optimizer_rmsprop(),
        metrics = c('accuracy')
      )
    }
    
    history <- rnn_model %>% fit(
      X_train, Y_train, 
      epochs = combinations$epochs[k], batch_size = combinations$batch_size[k], 
      validation_split = 0.2,verbose = 0)
    
    resu <- rnn_model %>%
      evaluate(X_test, Y_test,verbose = 0)

    perda[k] <- resu[[1]]
    res[k] <- resu[[2]]
    }
  
  combinations <- combinations %>% 
    mutate(acc = res,
           loss_value = perda)
  return(as_tibble(combinations))

}

Os parâmetros considerados para fazer as simulações serão:

  1. Neurônios da primeira cadama: 256;
  2. Neurônios da camada interna: 128;
  3. Batch Size: 128 e 100;
  4. Épocas: 10,15 e 20;
  5. Dropout da primeira camada: 30% e 40%;
  6. Dropout da camada interna: 20% e 10%;
  7. Otimizador: Adam e RMSprop;
  8. Função de perda: Hinge e Entropia cruzada;
  9. Função de ativação: Relu e Elu;
  10. Função de ativação da última camada: Softmax e Softplus;

As simulações deram um total de 768 combinações. Levou um total aproximado de 10h para gerar os dados para este estudo.

Análise estatística com os resultados da simulação

Para a análise estatística vamos utilizar a regressão beta através do pacote gamlss. Existe também o pacote betareg porém o mesmo não possui algoritmo de seleção de variáveis. Os resíduos gerados pelo gamlss seguem uma distribuição normal assintótica, e sendo assim, é interesse garantir que estes sejam normais e independentes. Além disso o gamlss possue um gráfico de resíduos chamado warm plot. Ele funciona como um gráfico de envelope. Para mais detalhes ler o livro do gamlss.

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.09869 0.96585 0.97017 0.96186 0.97269 0.97789

Note que houveram valores de acúracia que foram muito baixos. Vamos entao fazer um modelo sem esses valores. Antes de estimar um modelo, vamos fazer uma pequena análise gráfica.

Descobertas interessantes:

  1. Quanto maior o número de epócas maior a acúracia, sendo que a variância e assimetria também diminuem.
  2. A função de perda de entropia cruzada fornece melhor acúracia com uma menor variância se comparada a hinge, que varia muito mais e que possui mediana inferior.
  3. A função de ativação da primeira camada é muito mais importante do que a da camada intermédiaria. Além disso, a função relu apresenta um desempenho bastante superior se comparado com a função relu, pois a variância é bastante pequena, onde o quartil de 75% da função relu é maior que o de 25% da função elu.
  4. A função de ativação softplus para a camada de saída é superior a softmax, porém , não muito.
  5. O otimizador não influencia na acúracia. Provavelmente só irá influenciar no tempo de treinamento, porém, a escolha de um otmizador adequado é crucial, já que é graças a ele que temos como obter acúracia.
  6. O dropout para essa aplicação não mudou nada. Ele deve influenciar em aplicações de deep learning.

Modelagem estatística

## Loading required package: splines
## Loading required package: gamlss.data
## 
## Attaching package: 'gamlss.data'
## The following object is masked from 'package:datasets':
## 
##     sleep
## Loading required package: gamlss.dist
## Loading required package: MASS
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
## Loading required package: nlme
## 
## Attaching package: 'nlme'
## The following object is masked from 'package:dplyr':
## 
##     collapse
## Loading required package: parallel
##  **********   GAMLSS Version 5.1-5  **********
## For more on GAMLSS look at http://www.gamlss.org/
## Type gamlssNews() to see new features/changes/bug fixes.

## 
## Family:  c("BE", "Beta") 
## Fitting method: "nlminb" 
## 
## Call:  gamlssML(formula = resmod$acc, family = "BE", data = sys.parent()) 
## 
## Mu Coefficients:
## [1]  3.431
## Sigma Coefficients:
## [1]  -3.44
## 
##  Degrees of Freedom for the fit: 2 Residual Deg. of Freedom   760 
## Global Deviance:     -5805.1 
##             AIC:     -5801.1 
##             SBC:     -5791.83
## GAMLSS-RS iteration 1: Global Deviance = -2666.726 
## GAMLSS-RS iteration 2: Global Deviance = -3238.072 
## GAMLSS-RS iteration 3: Global Deviance = -4103.821 
## GAMLSS-RS iteration 4: Global Deviance = -5654.238 
## GAMLSS-RS iteration 5: Global Deviance = -7297.578 
## GAMLSS-RS iteration 6: Global Deviance = -7513.074 
## GAMLSS-RS iteration 7: Global Deviance = -7524.43 
## GAMLSS-RS iteration 8: Global Deviance = -7524.767 
## GAMLSS-RS iteration 9: Global Deviance = -7524.775 
## GAMLSS-RS iteration 10: Global Deviance = -7524.776
## Start:  AIC= -7468.78 
##  acc ~ (epochs + loss + activation1 + activation2 + last_activation)^2
## Warning in RS(): Algorithm RS has not yet converged

## Warning in RS(): Algorithm RS has not yet converged

## Warning in RS(): Algorithm RS has not yet converged

## Warning in RS(): Algorithm RS has not yet converged
## Start:  AIC= -7472.4 
##  ~epochs + loss + activation1 + activation2 + last_activation
## ******************************************************************
## Family:  c("BE", "Beta") 
## 
## Call:  gamlss(formula = acc ~ epochs + loss + activation1 +  
##     activation2 + last_activation + epochs:loss + epochs:activation1 +  
##     loss:activation1 + loss:last_activation + activation1:activation2 +  
##     activation1:last_activation + activation2:last_activation,  
##     sigma.formula = ~epochs + loss + activation1, family = "BE",  
##     data = resmod, trace = FALSE) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  logit
## Mu Coefficients:
##                                                       Estimate Std. Error
## (Intercept)                                           2.988149   0.009254
## epochs15                                              0.136624   0.009759
## epochs20                                              0.218030   0.009717
## losscategorical_crossentropy                          0.290726   0.009961
## activation1relu                                       0.354519   0.010710
## activation2relu                                       0.070124   0.007802
## last_activationsoftplus                               0.215630   0.009176
## epochs15:losscategorical_crossentropy                -0.008490   0.010328
## epochs20:losscategorical_crossentropy                -0.044335   0.010276
## epochs15:activation1relu                             -0.056564   0.010528
## epochs20:activation1relu                             -0.063021   0.010508
## losscategorical_crossentropy:activation1relu         -0.086112   0.008546
## losscategorical_crossentropy:last_activationsoftplus -0.216293   0.008987
## activation1relu:activation2relu                      -0.076270   0.008713
## activation1relu:last_activationsoftplus              -0.038785   0.009033
## activation2relu:last_activationsoftplus               0.021676   0.008375
##                                                      t value Pr(>|t|)    
## (Intercept)                                          322.895  < 2e-16 ***
## epochs15                                              14.000  < 2e-16 ***
## epochs20                                              22.438  < 2e-16 ***
## losscategorical_crossentropy                          29.188  < 2e-16 ***
## activation1relu                                       33.102  < 2e-16 ***
## activation2relu                                        8.988  < 2e-16 ***
## last_activationsoftplus                               23.500  < 2e-16 ***
## epochs15:losscategorical_crossentropy                 -0.822  0.41132    
## epochs20:losscategorical_crossentropy                 -4.314 1.82e-05 ***
## epochs15:activation1relu                              -5.373 1.04e-07 ***
## epochs20:activation1relu                              -5.997 3.14e-09 ***
## losscategorical_crossentropy:activation1relu         -10.077  < 2e-16 ***
## losscategorical_crossentropy:last_activationsoftplus -24.067  < 2e-16 ***
## activation1relu:activation2relu                       -8.754  < 2e-16 ***
## activation1relu:last_activationsoftplus               -4.294 1.99e-05 ***
## activation2relu:last_activationsoftplus                2.588  0.00984 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## Sigma link function:  logit
## Sigma Coefficients:
##                              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                  -4.29306    0.05846 -73.438  < 2e-16 ***
## epochs15                     -0.08767    0.06464  -1.356   0.1755    
## epochs20                     -0.13756    0.06625  -2.077   0.0382 *  
## losscategorical_crossentropy -0.12166    0.05552  -2.191   0.0288 *  
## activation1relu              -0.31394    0.05471  -5.738 1.39e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## No. of observations in the fit:  762 
## Degrees of Freedom for the fit:  21
##       Residual Deg. of Freedom:  741 
##                       at cycle:  10 
##  
## Global Deviance:     -7517.214 
##             AIC:     -7475.214 
##             SBC:     -7377.859 
## ******************************************************************

## ******************************************************************
##        Summary of the Quantile Residuals
##                            mean   =  0.0007438302 
##                        variance   =  1.00121 
##                coef. of skewness  =  -0.2444616 
##                coef. of kurtosis  =  3.191686 
## Filliben correlation coefficient  =  0.9971653 
## ******************************************************************
## Warning in wp(mod): Some points are missed out 
## increase the y limits using ylim.all

O modelo parece estar bem ajustado. Vamos agora estudar como estas variáveis influênciam no desempenho do modelo.

Interpretação de \(\mu\)

Vamos agora estudar os efeitos marginais de cada variável já que temos interação entre elas. Deste modo é mais fácil identificar onde cada uma será melhor que outra.

O efeito marginal de uma variável é a derivada parcial da variável explicativa de interesse em relação à variável resposta. Para este modelo, como a função de ligação para o parâmetro de localização é logit, o efeito marginal fica dado por:

\[ \frac{\partial X_i}{\partial Y} = \frac{\beta_ie^{-X\beta}}{1 + e^{-X\beta}} \text{ , se a a variável interessada não possui interação} \]

e

\[ \frac{\partial X_i}{\partial Y} = \frac{\beta_i + \beta_kX_ke^{-X\beta}}{1 + e^{-X\beta}} \text{ , se a a variável interessada possui interação} \]

em que \(\beta_k\) e \(X_k\) são as variáveis que fazem interação com \(X_i\).

Efeito do número de épocas

epoch15 loss_cross act1 efeito
1 1 0 11.24%
1 0 0 7.13%
1 0 1 5.81%
1 1 1 1.79%
0 0 0 0%

Note que quando temos 15 épocas, o maior efeito marginal é quando temos a função de perda sendo a entropia cruzada e função de ativação para primeira camada igual a elu.

epoch20 loss_cross act1 efeito
1 0 0 15.35%
1 1 0 13.72%
1 1 1 6.81%
1 0 1 6.66%
0 0 0 0%

Já quando temos 20 épocas, usar a entropia cruzada não é tão bom, sendo que usar a função de ativação para primeira camada sendo a elu ainda assim é melhor.

Logo, o efeito do número de épocas é melhorado quando temos função de ativação elu, sendo que, quando temos menos épocas é melhor usar a função de perda de entropia cruzada e com mais épocas, melhor utilizar a função de perda hinge.

Função de perda

loss_cross epoch15 epoch20 actrelu lact_soft efeito
1 0 0 0 0 37.4%
1 1 0 0 0 25.1%
1 0 1 0 0 18.9%
1 0 0 1 0 9.7%
1 0 1 1 0 7.7%
1 0 0 0 1 7.2%
1 1 0 0 1 5.7%
1 0 1 0 1 2.5%
1 1 0 1 0 0.47%
0 0 0 0 0 0%
1 0 0 1 1 -0.9%
1 1 0 1 1 -0.96%
1 0 1 1 1 -4.2%

Observando o efeito da função de perda de entropia cruzada, o que podemos ver é que ela funciona extremamente bem para poucas épocas. Além disso quando a função de ativação para última camada é a softmax, também existe um ganho maior em comparação quando se usa a softplus.

Note também que a função elu apresenta melhores resultados para o efeito da entropia cruzada. Um menor número de épocas apresenta o melhor resultado, contudo, este não é o maior determinante para o efeito positivo da função de perda. Observe que os efeitos negativos são quando temos a função de ativação para primeira camada sendo a relu e a da última camada sendo a softplus, e , quanto maior o número de épocas, pior o desempenho.

Logo, podemos chegar a conclusão de que a entropia cruzada funciona bem para poucas épocas e função de ativação para primeira camada sendo elu e e para última camada sendo softmax.

Função de ativação para primeira camada relu

act1relu epoch15 epoch20 loss_cross act2 lact_soft efeito
1 1 0 0 0 0 38.1%
1 0 0 0 0 0 29.32%
1 0 0 1 0 0 24.41%
1 0 0 0 0 1 23.93%
1 1 0 0 0 1 22.9%
1 0 1 0 0 1 19.71%
1 0 0 1 0 1 15.47%
1 0 1 1 0 0 14.80%
1 0 0 0 1 1 14.1%
1 1 0 0 1 1 12.8%
1 0 1 0 0 0 12.37%
1 0 0 1 1 1 12.3%
1 0 1 1 0 1 11.88%
1 1 0 1 0 1 8.86%
1 0 1 0 1 1 8.45%
1 0 1 1 1 1 7.13%
1 1 0 1 1 1 4.26%
1 0 1 1 1 0 3.09%
1 0 0 0 1 0 0.9%
1 1 0 0 1 0 0.8%
1 0 1 0 1 0 0.76%
1 0 0 1 1 0 0.7%
1 1 0 1 0 0 0.48%
1 1 0 1 1 0 0.34%
0 0 0 0 0 0 0%

Analisando o efeito marginal para primeira camada, vemos que a função relu funciona melhor com 15 épocas, ou seja, um valor intermédiario, sendo melhorada com função de perda hinge e função de ativação da segunda camada sendo elu e função de ativação para última camada sendo softmax.

Logo, fazer uma alteração na função de ativação para segunda camada é muito benefico para o treinamento. Também é importante usar a função softmax para última camada caso você utilize a relu para primeira, e função de perda também influencia mas de forma não muito acentuada.

Camada de ativação da saída

la_softplus loss_cross act1relu act2relu efeito
1 0 0 1 19.66%
1 0 0 0 15.43%
1 0 1 0 14.28%
1 0 1 1 11.71%
1 1 0 1 1.54%
0 0 0 0 0%
1 1 0 0 -0.07%
1 1 1 1 -1.20%
1 1 1 0 -2.50%

O efeito marginal da função de ativação para camada final softplus é interessante quando usamos a função de perda hinge. Note também que alternar as funções de ativação das demais camadas continua sendo interessante.

Interpretação do parâmetro de precisão \(\phi\)

O parâmetro \(\phi\) na regressão beta influência na variabilidade das estimativas. Por definição o modelo beta já é heterocedástico, contudo, ainda podemos modelar explicitamente essa variabilidade através do parâmetro \(\phi\).

A variância das estimativas é dada por:

\[ Var(y) = \frac{Var(\mu)}{1 + \phi} \]

Isto é, quão menor for a estimativa de \(\phi\) no modelo, maior será seu logit (função de ligação para \(\phi\)), e consequentemente, menor a variância. Para interpreta-lo, podemos usar a odds ratio, sendo que agora, a odds irá expressar o quão maior \(\phi\) é em relação ao \(\phi\) da referência para cada variável, o que implica que a odds ratio representa o quão maior é o \(\phi\) atual em relação ao \(\phi\) da referência para a taxa de acerto do modelo.

Parametro OddsRatio
epochs15 0.92
epochs20 0.87
losscategorical_crossentropy 0.89
activation1relu 0.73

Sendo assim, temos que quanto maior o número de épocas, menor a variância (13% em relação a refência que são 10 épocas). Utilizar a função de perda de entropia cruzada também ajuda a minimizar a variância (em 11,5% em relação a função hige). O que mais impressiona é que, utilizar a função relu na primeira camada diminui muito a variabilidade da acúracia (17% em relação a elu).

Conclusão do estudo

Para este conjunto de dados e para o tipo de rede neural treinada, podemos notar que:

  1. Aumentar o número de épocas realmente melhora a acúracia do modelo, contudo, devemos ter algumas precauções quanto a função de perda.

  2. É preferível utilizar a função de perda de entropia cruzada para poucas epócas e além disso, utilizando ativação relu e softmax, para a primeira e última camada, respectivamente.

  3. Alternar a função de ativação entre a primeira e segunda camada é altamente útil, independente da configuração utilizada.

  4. O otmizador não influência na acúracia do modelo. O otmizador irá influenciar na convergência e no seu respectivo tempo para treinamento.

  5. O dropout para esta configuração não muda nada. Esta característica provavelmente só irá mudar algo em modelos de deep learning.

  6. A função relu na primeira camada (consequentemente a elu na segunda de acordo com os outros itens), irá diminuir bastante a chance de errar, pois a variância irá reduzir consideravelmente.

Logo, a melhor combinação é utilizando de 10 a 15 épocas com relu na primeira camada, elu na segunda e softmax na última. Além disso, usar a função de perda de entropia cruzada. Utilizar esta configuração irá máximar a acúracia e diminuir em muito as chances de erro pois a variância utilizando relu na camada 1 e a função de perda de entropia cruzada reduzem bastante a variabilidade, mesmo o número de épocas não sendo o que dimuni mais as chances de erro.