Medidas de desempenho

Nessa aula veremos com mais detalhes como usar as medidas de desempenho. Suponha que a base de dados está dividida em treino e teste e que a base de treino foi usada para ajustar o modelo.

Regressão

Para os problemas de regressão a variável resposta \(Y\) é quantitativa contínua. Suponha que um modelo de regressão \(k\) realizou a previsão \(\hat{y}_i^k\) para a i-ésima observação da variável \(Y\). Suponha que \(y_i\) seja o real valor observado para a i-ésima observação da variável \(Y\). Para comparar os diferentes modelos vamos seguir os seguintes passos.

Primeiro calculamos a previsão da variável resposta \(Y\) para cada unidade amostral com cada um dos modelos ajustados. A tabela abaixo representa os valores calculados.

i Valor observado Previsão para o Modelo 1 Previsão para o Modelo 2 Previsão para o Modelo 3
1 \(y_1\) \(\hat y_1^1\) \(\hat y_1^2\) \(\hat y_1^3\)
2 \(y_2\) \(\hat y_2^1\) \(\hat y_2^2\) \(\hat y_2^3\)
3 \(y_3\) \(\hat y_3^1\) \(\hat y_3^2\) \(\hat y_3^3\)
N \(y_N\) \(\hat y_N^1\) \(\hat y_N^2\) \(\hat y_N^3\)

Uma vez conhecidas as previsões para a variável resposta considerando todos os modelos ajusatados, podemos calcular o SSE (soma dos erros ao quadrado) ou MSE (média dos erros ao quadrado) para cada um deles.

Soma dos erros ao quadrado: \[ SSE^k = \sum_{i=1}^N (\hat{y}_i ^k - y_i)^2 \]

Erro médio quadrático: \[ MSE^k = \dfrac{1}{N} \sum_{i=1}^N (\hat{y}_i^k - y_i)^2 \]

Vamos supor que a medida escolhida para comparação doi o MSE. Veja que a conta acima pode ser feita considerando tanto a base de treino quanto a base de teste. Em geral vamos medir o MSE (ou SSE) para as duas bases. Nesse caso podemos criar a seguinte tabela, que vai nos ajudar a decidir qual dos modelos é aquele mais adequado.

Modelo MSE na base de treino MSE na base de teste
1 \(MSE^1_{treino}\) \(MSE^1_{teste}\)
2 \(MSE^2_{treino}\) \(MSE^2_{teste}\)
3 \(MSE^3_{treino}\) \(MSE^3_{teste}\)

O modelo com menor MSE na base de teste será aquele que parece ter melhor desempenho. Porpém observar o valor do MSE na base de treino pode nos trazer informações importantes. Por exemplo, se um modelo apresenta MSE na base de treino bem menor que o MSE na base de teste, principalmente quando comparado com os outros modelos, percebemos que para este modelo em questão está ocorrendo o sobreajuste (overfiting).

Outras informações que podemos extrair da comparação entre os valores do MSE é sober a importancia de uma covariável ou a inclusão de uma nova camada na rede. Se a diferença entre dois modelos é algo pontual, onde um indica a simplificação do outro, a comparação dos valores do MSE tanto na base de treino quanto na base de teste indica o quanto esse complicador pontual acrescentado no modelo mais complexo contribuiu na previsão da variável resposta.

Exemplo

Vamos usar o exemplo da aula prática. Considere a base de dados composta pelas variáveis: age, sex, bmi, children, smoker, region, charges. Vamos ajustar diferentes modelos de redes neurais perceptron camada única para prever o custo de um cliente. A difereça entre os diferentes modelos é apenas as covariáveis usadas como entrada.

Carregar pacotes necessários

library(caret)
library(tidyverse)
library(neuralnet)

Leitura da base de dados

base = read.csv2("insurance.csv",sep = ",",dec=".")
base = tibble::as_tibble(base)

Divisão entre treino e teste

set.seed(123456789)
N = dim(base)[1]
indices_treino = createDataPartition(1:N,p=0.7)[[1]]
base_treino = base[indices_treino,]
base_teste  = base[-indices_treino,]

Padronização das variáveis quantitativas

scale = scale(base_treino$age)
age_ = scale[,1] 
media_age = attr(scale,"scaled:center")
dp_age = attr(scale,"scaled:scale")

scale = scale(base_treino$bmi)
bmi_ = scale[,1] 
media_bmi = attr(scale,"scaled:center")
dp_bmi = attr(scale,"scaled:scale")

scale = scale(base_treino$children)
children_ = scale[,1] 
media_children = attr(scale,"scaled:center")
dp_children = attr(scale,"scaled:scale")

scale = scale(base_treino$charges)
charges_ = scale[,1] 
media_charges = attr(scale,"scaled:center")
dp_charges = attr(scale,"scaled:scale")

#completar como ficam as variaveis quantitativas depos da base modificada
base_treino_ = base_treino |> mutate(age = age_,
                                     bmi = bmi_,
                                     children = children_,
                                     charges = charges_)

Tratamento para as variáveis categóricas

matriz_treino_ <- model.matrix( ~ age + sex + bmi + children + smoker + region + charges, data = base_treino_)

Ajuste dos modelos

#modelo completo
modelo_1 = neuralnet(charges ~ age + sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem idade
modelo_2 = neuralnet(charges ~ sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem sexo
modelo_3 = neuralnet(charges ~ age + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem bmi
modelo_4 = neuralnet(charges ~ age + sexmale + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem children
modelo_5 = neuralnet(charges ~ age + sexmale + bmi + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem smoker
modelo_6 = neuralnet(charges ~ age + sexmale + bmi + children + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem region
modelo_7 = neuralnet(charges ~ age + sexmale + bmi + children + smokeryes , data = matriz_treino_, hidden = 0,linear.output = TRUE)

Previsão na base de treino

prev_treino_1_ = modelo_1$net.result[[1]][,1]
prev_treino_1 = prev_treino_1_*dp_charges + media_charges

prev_treino_2_ = modelo_2$net.result[[1]][,1]
prev_treino_2 = prev_treino_2_*dp_charges + media_charges

prev_treino_3_ = modelo_3$net.result[[1]][,1]
prev_treino_3 = prev_treino_3_*dp_charges + media_charges

prev_treino_4_ = modelo_4$net.result[[1]][,1]
prev_treino_4 = prev_treino_4_*dp_charges + media_charges

prev_treino_5_ = modelo_5$net.result[[1]][,1]
prev_treino_5 = prev_treino_5_*dp_charges + media_charges

prev_treino_6_ = modelo_6$net.result[[1]][,1]
prev_treino_6 = prev_treino_6_*dp_charges + media_charges

prev_treino_7_ = modelo_7$net.result[[1]][,1]
prev_treino_7 = prev_treino_7_*dp_charges + media_charges

MSE na base de treino

charges_treino = base_treino$charges
n = length(charges_treino)

MSE_treino_1 = (1/n)*sum((prev_treino_1 - charges_treino)^2)
MSE_treino_2 = (1/n)*sum((prev_treino_2 - charges_treino)^2)
MSE_treino_3 = (1/n)*sum((prev_treino_3 - charges_treino)^2)
MSE_treino_4 = (1/n)*sum((prev_treino_4 - charges_treino)^2)
MSE_treino_5 = (1/n)*sum((prev_treino_5 - charges_treino)^2)
MSE_treino_6 = (1/n)*sum((prev_treino_6 - charges_treino)^2)
MSE_treino_7 = (1/n)*sum((prev_treino_7 - charges_treino)^2)

Processamento na base de teste

base_teste_ = base_teste |> mutate(age = (age - media_age)/dp_age ,
                                   bmi = (bmi - media_bmi)/dp_bmi,
                                   children = (children - media_children)/dp_children,
                                   charges = (charges - media_charges)/dp_charges)

matriz_teste_ <- model.matrix( ~ age + sex + bmi + children + smoker + region + charges, data = base_teste_)

Previsão na base de teste

prev_teste_1_ = (modelo_1 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_1 = prev_teste_1_*dp_charges + media_charges

prev_teste_2_ = (modelo_2 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_2 = prev_teste_2_*dp_charges + media_charges

prev_teste_3_ = (modelo_3 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_3 = prev_teste_3_*dp_charges + media_charges

prev_teste_4_ = (modelo_4 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_4 = prev_teste_4_*dp_charges + media_charges

prev_teste_5_ = (modelo_5 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_5 = prev_teste_5_*dp_charges + media_charges

prev_teste_6_ = (modelo_6 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_6 = prev_teste_6_*dp_charges + media_charges

prev_teste_7_ = (modelo_7 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_7 = prev_teste_7_*dp_charges + media_charges

MSE na base de teste

charges_teste = base_teste$charges
n = length(charges_teste)

MSE_teste_1 = (1/n)*sum((prev_teste_1 - charges_teste)^2)
MSE_teste_2 = (1/n)*sum((prev_teste_2 - charges_teste)^2)
MSE_teste_3 = (1/n)*sum((prev_teste_3 - charges_teste)^2)
MSE_teste_4 = (1/n)*sum((prev_teste_4 - charges_teste)^2)
MSE_teste_5 = (1/n)*sum((prev_teste_5 - charges_teste)^2)
MSE_teste_6 = (1/n)*sum((prev_teste_6 - charges_teste)^2)
MSE_teste_7 = (1/n)*sum((prev_teste_7 - charges_teste)^2)

Comparação entre MSE de diferentes modelos

Modelo MSE na base de treino MSE na base de teste Observações
1 37921827 33551260 completo
2 50125221 47900202 sem idade
3 37922627 33535116 sem sexo
4 41369965 38334393 sem bmi
5 38229334 33939101 sem nº filhos
6 127356957 130684270 sem smoker
7 38077180 33724508 sem região
Tabela 1: Comparação do MSE na base de treino e teste.

Tabela 2: Comparação do MSE na base de treino e teste.

Quais interpretações podemos tirar da Tabela 1 e da Figura 1 ?

Qual entre os modelos apresentados acima você prefere?

Você tentaria ajustar algum outro modelo após a observação da Tabela 1 e da Figura 1 ?

  • A variável smoker traz muita informação para o modelo.

  • A variável sex parece trazer pouca informação para o modelo.

  • A variável age parece que também contribui para o melhor ajuste do modelo.

  • Podemos ajustar outros modelos, todos contendo smoker e age e não contendo sex. A diferença entre os modelos será nas demais variáveis.

#modelo sem sex e bmi
modelo_8 = neuralnet(charges ~ age + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem sex e children
modelo_9 = neuralnet(charges ~ age + bmi + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = TRUE)

#modelo sem sex e regiao
modelo_10 = neuralnet(charges ~ age + bmi + children + smokeryes, data = matriz_treino_, hidden = 0,linear.output = TRUE)
prev_treino_8_ = modelo_8$net.result[[1]][,1]
prev_treino_8 = prev_treino_8_*dp_charges + media_charges

prev_treino_9_ = modelo_9$net.result[[1]][,1]
prev_treino_9 = prev_treino_9_*dp_charges + media_charges

prev_treino_10_ = modelo_10$net.result[[1]][,1]
prev_treino_10 = prev_treino_10_*dp_charges + media_charges
n = length(charges_treino)
MSE_treino_8 = (1/n)*sum((prev_treino_8 - charges_treino)^2)
MSE_treino_9 = (1/n)*sum((prev_treino_9 - charges_treino)^2)
MSE_treino_10 = (1/n)*sum((prev_treino_10 - charges_treino)^2)
prev_teste_8_ = (modelo_8 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_8 = prev_teste_8_*dp_charges + media_charges

prev_teste_9_ = (modelo_9 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_9 = prev_teste_9_*dp_charges + media_charges

prev_teste_10_ = (modelo_10 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_10 = prev_teste_10_*dp_charges + media_charges
n = length(charges_teste)
MSE_teste_8 = (1/n)*sum((prev_teste_8 - charges_teste)^2)
MSE_teste_9 = (1/n)*sum((prev_teste_9 - charges_teste)^2)
MSE_teste_10 = (1/n)*sum((prev_teste_10 - charges_teste)^2)

Modelo MSE na base de treino MSE na base de teste Observações
1 37921827 33551260 completo
3 37922627 33535116 sem sex
8 41382393 38286177 sem sex e bmi
9 38230533 33920085 sem sex e nº filhos
10 38078022 33707882 sem sex e região
Tabela 3: Comparação do MSE na base de treino e teste.

Tabela 4: Comparação do MSE na base de treino e teste.

Entre todas as comparações parece que o Modelo 3 tem uma bom configuração.

Validação Cruzada

A validação cruzada (k-fold cross-validation) é um outro processo de análise do ajuste do modelo. Neste processo a base de dados é dividido aleatoriamente em \(k\) pedaços de (aproximadamente) mesmo tamanho. Uma vez a base dividida, considera-se um dos \(k\) pedaços a base de teste e os outros \(k-1\) pedaços a base de treino. O modelo é ajustado na base de treino e é calculado o erro na base de teste. Esse processo é feito \(k\) vezes, sendo que em cada uma das vezes uma das partes é utilizada como base de teste e as outras \(k-1\) como base de treino. Ao final do processo, em vez de o valor do MSE na base de treino, teremos \(k\) valores do MSE na base de treino para cada modelo.

  1. Divide o dataset em \(k\) partes;

  2. Faz \(i=1\);

  3. Considere a parte \(i\) como base de teste e o restante da base de dados como base de treino.

  4. Ajuste cada modelo para a base de treino.

  5. Calcule o MSE na base de teste para cada modelo ajustado na base de treino.

  6. Faz \(i = i + 1\) e se \(i \le k\), volte para o passo 3.

Divisão da base em \(k\) partes

Vamos realizar a validação cruzada considerando \(k=10\). Neste caso chamamos este processo de 10-fold cross-validation.

K = 10
N = dim(base)[1]
folds = createFolds(1:N, k = K, list = TRUE, returnTrain = FALSE)
MSE_treino = matrix(NA,ncol=10,nrow = K)
MSE_teste  = matrix(NA,ncol=10,nrow = K)

for(k in 1:K){
  
  base_treino = base[-folds[[k]],]
  
  scale = scale(base_treino$age)
  age_ = scale[,1] 
  media_age = attr(scale,"scaled:center")
  dp_age = attr(scale,"scaled:scale")
  
  scale = scale(base_treino$bmi)
  bmi_ = scale[,1] 
  media_bmi = attr(scale,"scaled:center")
  dp_bmi = attr(scale,"scaled:scale")
  
  scale = scale(base_treino$children)
  children_ = scale[,1] 
  media_children = attr(scale,"scaled:center")
  dp_children = attr(scale,"scaled:scale")
  
  scale = scale(base_treino$charges)
  charges_ = scale[,1] 
  media_charges = attr(scale,"scaled:center")
  dp_charges = attr(scale,"scaled:scale")

  base_treino_ = base_treino |> mutate(age = age_,
                                       bmi = bmi_,
                                       children = children_,
                                       charges = charges_)

  matriz_treino_ = model.matrix( ~ age + sex + bmi + children + smoker + region + charges, 
                                 data = base_treino_)
  
  modelos = list()
  
  #modelo completo
  modelos[[1]] = neuralnet(charges ~ age + sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem idade
  modelos[[2]] = neuralnet(charges ~ sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem sexo
  modelos[[3]] = neuralnet(charges ~ age + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem bmi
  modelos[[4]] = neuralnet(charges ~ age + sexmale + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem children
  modelos[[5]] = neuralnet(charges ~ age + sexmale + bmi + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem smoker
  modelos[[6]] = neuralnet(charges ~ age + sexmale + bmi + children + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem region
  modelos[[7]] = neuralnet(charges ~ age + sexmale + bmi + children + smokeryes , 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)
  
  #modelo sem sex e bmi
  modelos[[8]] = neuralnet(charges ~ age + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)

  #modelo sem sex e children
  modelos[[9]] = neuralnet(charges ~ age + bmi + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest, 
                       data = matriz_treino_, 
                       hidden = 0,
                       linear.output = TRUE)

  #modelo sem sex e regiao
  modelos[[10]] = neuralnet(charges ~ age + bmi + children + smokeryes, 
                        data = matriz_treino_, 
                        hidden = 0,linear.output = TRUE)


  
  base_teste = base[folds[[k]],]
  base_teste_ = base_teste |> mutate(age = (age - media_age)/dp_age,
                                      bmi = (bmi - media_bmi)/dp_bmi,
                                      children = (children - media_children)/dp_children,
                                      charges = (charges - media_charges)/dp_charges)
   
  matriz_teste_ = model.matrix( ~ age + sex + bmi + children + smoker + region + charges,
                                 data = base_teste_)
   
  charges_treino = base_treino$charges
  charges_teste  = base_teste$charges
  n = length(charges_treino)
   
  for(i in 1:10){
    prev_treino_ = modelos[[i]]$net.result[[1]][,1]
    prev_treino  = prev_treino_*dp_charges + media_charges
    
    MSE_treino[k,i] = (1/n)*sum((prev_treino - charges_treino)^2)
    
    prev_teste_ = (modelos[[i]] |> neuralnet::compute(matriz_teste_))$net.result[,1]
    prev_teste = prev_teste_*dp_charges + media_charges
    
    MSE_teste[k,i] = (1/(N-n))*sum((prev_teste - charges_teste)^2)
  }
}

media_mse_treino = apply(MSE_treino,2,mean)
media_mse_teste = apply(MSE_teste,2,mean)

max_mse_treino = apply(MSE_treino,2,max)
max_mse_teste = apply(MSE_teste,2,max)

min_mse_treino = apply(MSE_treino,2,min)
min_mse_teste = apply(MSE_teste,2,min)
Modelo Min. 1st Qu. Median Mean 3rd Qu. Max. obs
1 35052383 35859299 36362589 36462926 37149119 37828746 completo
2 48167648 48583527 48874624 49255779 49886897 50825434 sem idade
3 35080895 35859759 36367902 36471857 37154393 37847439 sem sexo
4 39353102 39561328 40023264 40320867 41079033 41624153 sem bmi
5 35387781 36222173 36684736 36792364 37493957 38218945 sem nº filhos
6 124297319 125784996 128227520 127927198 130082360 130994366 sem smoker
7 35293910 36047431 36540830 36648811 37361346 37945907 sem região
8 39354352 39568702 40042337 40327416 41080815 41624343 sem sex e bmi
9 35411030 36222400 36690789 36800334 37500644 38235650 sem sex e nº filhos
10 35323610 36047679 36546258 36657980 37370452 37965074 sem sex e região
Tabela 5: Comparação entre estatísticas do MSE na base de treino.

Modelo Min. 1st Qu. Median Mean 3rd Qu. Max. obs
1 25043589 30909894 38383064 37261278 42514234 50069096 completo
2 35665740 44503005 53762260 50166572 56504750 59835798 sem idade
3 24870982 30902541 38259841 37175013 42534000 49746972 sem sexo
4 29451005 34159790 44207911 41229772 47876783 49924468 sem bmi
5 24783705 31010821 38749435 37541574 42523241 50277693 sem nº filhos
6 101478352 110121862 126610967 129699208 149584506 162707852 sem smoker
7 25575475 30574566 38321676 37214522 42440986 49562596 sem região
8 29438603 34110740 43897482 41117489 47783057 49918550 sem sex e bmi
9 24613161 30975390 38598393 37450594 42535417 50004567 sem sex e nº filhos
10 25392315 30522216 38193623 37121399 42456642 49213230 sem sex e região
Tabela 6: Comparação entre estatísticas do MSE na base de teste.

Tabela 7: Comparação entre média do MSE para a validação cruzada.

Tabela 8: Comparação entre valor máximo do MSE para a validação cruzada.

Tabela 9: Comparação entre valor mínimo do MSE para a validação cruzada.

A partir da validação cruzada o modelo mais interessante é aquele que não considera sex e nem region.