Nessa aula veremos com mais detalhes como usar as medidas de desempenho para os métodos de classificação. Suponha a base de dados dividida em treino e teste e que a base de treino foi usada para ajustar os modelos.
Para os problemas de regressão a variável resposta \(Y\) é qualitativa. Vamos supor que ela será binária, isto é, admite apenas duas classe. O problema com mais de duas classes será discutido mais para frente.
Suponha que um modelo de classificação \(k\) realizou a previsão \(\hat{y}_i^k\) para a i-ésima observação da variável \(Y\). Como se trata de um modelo de classificação binário, as classes serão 0 ou 1 e a previsão \(\hat{y}_i^k\) é um valor no intervalo \((0,1)\).
Seja \(y_i\) a classe real observada para a i-ésima observação da variável \(Y\). Veja que temos duas opções, \(y_i-0\) ou \(y_i=1\). Para comparar os diferentes modelos de classificação vamos seguir os seguintes passos.
Primeiro calcula-se a previsão da variável resposta \(Y\) para cada unidade amostral, para cada um dos modelos ajustados. A tabela abaixo representa os valores calculados.
i | Classe real | 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 a EC (entropia cruzada) e usar essa medida como comparação de qualidade do ajuste.
\[ EC = - \dfrac{1}{N} \sum_{i=1}^N \left( y_i\ln(\hat{y}_i) + (1-y_i)\ln(1-\hat{y}_i) \right) \]
Veja que quando \(\hat{y}_i\) está próximo da classe real a parcela \(i\) do somatório é bem pequena e quando \(\hat{y}_i\) está próxima da classificação errada, a parcela \(i\) do somatório é bem grande.
Veja que a conta acima pode ser feita considerando tanto a base de treino quanto a base de teste. Em geral vamos medir a EC para as duas bases.
Uma vez calculada as EC podemos criar a seguinte tabela, que vai ajudar a decidir qual dos modelos é mais adequado.
Modelo | MSE na base de treino | MSE na base de teste |
---|---|---|
1 | \(EC^1_{treino}\) | \(EC^1_{teste}\) |
2 | \(EC^2_{treino}\) | \(EC^2_{teste}\) |
3 | \(EC^3_{treino}\) | \(EC^3_{teste}\) |
O modelo com menor EC na base de teste será aquele que parece ter melhor desempenho. Porém observar o valor do EC na base de treino pode trazer informações importantes. Por exemplo, se um modelo apresenta EC na base de treino bem menor que a EC na base de teste, principalmente quando comparado com os outros modelos, percebemos que para este modelo em questão está ocorrendo o sobreajuste (overfiting).
Assim como no caso da regressão, a comparação da EC pode indicar a importância (ou não) de uma covariável.
A entropia cruzada é a medida adequada para comparar o desempenho de modelos de classificação.
A validação cruzada (k-fold cross-validation) também pode ser adotada para comparar modelos de classificação. O processo é semelhante aquele apresentado para os modelos de regressão, porém em vez de compararmos as estatísticas para o MSE serão comparadas as estatísticas para a EC.
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 (EC) 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 um valor da EC teremos \(k\) valores para cada modelo.
Divide o dataset em \(k\) partes;
Faz \(i=1\);
Considere a parte \(i\) como base de teste e o restante da base de dados como base de treino.
Ajuste cada modelo para a base de treino.
Calcule o EC na base de teste para cada modelo ajustado na base de treino.
Faz \(i = i + 1\) e se \(i \le k\), volte para o passo 3.
Os modelos de classificação retornam como previsão para a \(i\)-ésima observação um valor \(\hat{y}_i^k \in (0,1)\). Mas o objetivo final é prever a classe da observação \(i\).
Uma vez escolhido o modelo de classificação que melhor se ajusta aos dados, podemos realizar agora a previsão da classe para a observação \(i\). Suponha o seguinte critério para a escolha da classe prevista:
\[ \hat{c}_i = \left\{ \begin{array}{ll} 0 & \hbox{, se } \hat{y}_i < q\\ 1 & \hbox{, se } \hat{y}_i \ge q\\ \end{array} \right. \] Para entender melhor a capacidade de previsão do modelo de classificação queremos encontrar as medidas de Acurácia, Precisão, Sensibilidade, entre outras. Estas medidas serão definidas a partir da matriz de confusão, que é formada pela contagem de classes reais e classes previstas.
Real 0 | Real 1 | |
Prev 0 | VN | FN |
Prev 1 | FP | VP |
VN = verdadeiro negativo = número de observações iguais a 0 que foram previstas como 0.
FN = falso negativo = número de observações iguais a 1 que foram previstas como 0.
FP = falso positivo = número de observações iguais a 0 que foram previstas como 1.
VP = verdadeiro positivo = número de observações iguais a 1 que foram previstas como 1.
Quanto maior o número de observações na diagonal principal da matriz de confusão melhor. A partir desta tabela podemos calcular algumas medidas de desempenho para os modelos de classificação.
A acurácia é a taxa de acerto do classificador. Ela é a proporção de predições corretas dentre todas as predições.
\[ Acurácia = \dfrac{V P + V N}{V P + V N + FP + FN} \]
A sensibilidade é a taxa de acerto dos casos positivos. Ela é a proporção de casos positivos que foram corretamente classificados como positivos.
\[ Sensibilidade = \dfrac{VP}{VP + FN} \]
A especificidade é a taxa de acerto dos casos negativos. Ela é a proporção dos casos negativos que foram corretamente classificados como negativos.
\[ Especificidade = \dfrac{VN}{V N + FP} \]
A precisão é a taxa de acerto dentras previsões positivas. Ela é a proporção dos acertos entre os casos classificados como positivos.
\[ Precisão = \dfrac{VP}{V P + FP} \]
É uma combinação da Precisão e do Recall que na prática é a média harmônica entre a Precisão e o Recall.
\[ F1-score = 2 \dfrac{Precisão \times Recall}{Precisão + Recall} \]
Mas qual valor escolher para \(q\)? A escolha do valor de corte \(q\) pode ser feita a partir da curva ROC. A curva ROC é uma curva parametrizada pelo valor \(q\) definida por:
\[ ROC(q) = (1-Especificidade(q)\ , \ Sensibilidade(q)) \ , \quad q \in (0,1) \]
A Figura 1 mostra como em geral é a curva ROC.
Vamos escolher o valor de \(q\) que gerou o ponto mais acima e à esquerda. O valor da área embaixo da curva ROC (AUC) também é uma medida de qualidade do ajuste bastante usada.
Vamos usar o exemplo da aula prática e ajustar diferentes modelos de redes neurais perceptron camada única para prever se um cliente é considerado de alto custo ou não. A difereça entre os diferentes modelos será apenas as covariáveis usadas como entrada.
library(caret)
library(tidyverse)
library(neuralnet)
base = read.csv2("insurance.csv",sep = ",",dec=".")
base = tibble::as_tibble(base)
base = base |>
mutate(altocusto = ifelse(base$charges > 15000,"sim","nao"))
base = base |> select(-charges)
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,]
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")
#completar como ficam as variaveis quantitativas depos da base modificada
base_treino_ = base_treino |> mutate(age = age_,
bmi = bmi_,
children = children_)
matriz_treino_ <- model.matrix( ~ age + sex + bmi + children + smoker + region + altocusto, data = base_treino_)
#colnames(matriz_treino_)
#modelo completo
modelo_1 = neuralnet(altocustosim ~ age + sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem idade
modelo_2 = neuralnet(altocustosim ~ sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem sexo
modelo_3 = neuralnet(altocustosim ~ age + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem bmi
modelo_4 = neuralnet(altocustosim ~ age + sexmale + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem children
modelo_5 = neuralnet(altocustosim ~ age + sexmale + bmi + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem smoker
modelo_6 = neuralnet(altocustosim ~ age + sexmale + bmi + children + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem region
modelo_7 = neuralnet(altocustosim ~ age + sexmale + bmi + children + smokeryes, data = matriz_treino_, hidden = 0,linear.output = FALSE)
prev_treino_1 = modelo_1$net.result[[1]][,1]
prev_treino_2 = modelo_2$net.result[[1]][,1]
prev_treino_3 = modelo_3$net.result[[1]][,1]
prev_treino_4 = modelo_4$net.result[[1]][,1]
prev_treino_5 = modelo_5$net.result[[1]][,1]
prev_treino_6 = modelo_6$net.result[[1]][,1]
prev_treino_7 = modelo_7$net.result[[1]][,1]
EC = function(real,previsao){
n = length(real)
ec = -sum(ifelse(real==1,log(previsao),log(1-previsao)))/n
return(ec)
}
classe_real_treino = matriz_treino_[,"altocustosim"]
EC_treino_1 = EC(classe_real_treino,prev_treino_1)
EC_treino_2 = EC(classe_real_treino,prev_treino_2)
EC_treino_3 = EC(classe_real_treino,prev_treino_3)
EC_treino_4 = EC(classe_real_treino,prev_treino_4)
EC_treino_5 = EC(classe_real_treino,prev_treino_5)
EC_treino_6 = EC(classe_real_treino,prev_treino_6)
EC_treino_7 = EC(classe_real_treino,prev_treino_7)
base_teste_ = base_teste |> mutate(age = (age - media_age)/dp_age ,
bmi = (bmi - media_bmi)/dp_bmi,
children = (children - media_children)/dp_children)
matriz_teste_ <- model.matrix( ~ age + sex + bmi + children + smoker + region + altocusto, data = base_teste_)
prev_teste_1 = (modelo_1 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_2 = (modelo_2 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_3 = (modelo_3 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_4 = (modelo_4 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_5 = (modelo_5 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_6 = (modelo_6 |> neuralnet::compute(matriz_teste_))$net.result[,1]
prev_teste_7 = (modelo_7 |> neuralnet::compute(matriz_teste_))$net.result[,1]
classe_real_teste = matriz_teste_[,"altocustosim"]
EC_teste_1 = EC(classe_real_teste,prev_teste_1)
EC_teste_2 = EC(classe_real_teste,prev_teste_2)
EC_teste_3 = EC(classe_real_teste,prev_teste_3)
EC_teste_4 = EC(classe_real_teste,prev_teste_4)
EC_teste_5 = EC(classe_real_teste,prev_teste_5)
EC_teste_6 = EC(classe_real_teste,prev_teste_6)
EC_teste_7 = EC(classe_real_teste,prev_teste_7)
Modelo | treino | teste | Observações |
---|---|---|---|
1 | 0.2844 | 0.1633 | completo |
2 | 0.2812 | 0.1797 | sem idade |
3 | 0.284 | 0.1635 | sem sexo |
4 | 0.2805 | 0.1692 | sem bmi |
5 | 0.2803 | 0.176 | sem nº filhos |
6 | 0.5824 | 0.5542 | sem smoker |
7 | 0.2913 | 0.1635 | sem região |
Quais interpretações podemos tirar da Tabela 1 e da Figura 2 ?
A variável smoker
traz muita informação para o
modelo.
A variável retirada da covariável sex
até melhorou a
EC na base de teste.
As outras variáveis parecem não contribuir muito.
K = 10
N = dim(base)[1]
folds = createFolds(1:N, k = K, list = TRUE, returnTrain = FALSE)
EC_treino = matrix(NA,ncol=7,nrow = K)
EC_teste = matrix(NA,ncol=7,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")
base_treino_ = base_treino |> mutate(age = age_,
bmi = bmi_,
children = children_)
matriz_treino_ = model.matrix( ~ age + sex + bmi + children + smoker + region + altocusto, data = base_treino_)
modelos = list()
#modelo completo
modelos[[1]] = neuralnet(altocustosim ~ age + sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem idade
modelos[[2]] = neuralnet(altocustosim ~ sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem sexo
modelos[[3]] = neuralnet(altocustosim ~ age + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem bmi
modelos[[4]] = neuralnet(altocustosim ~ age + sexmale + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem children
modelos[[5]] = neuralnet(altocustosim ~ age + sexmale + bmi + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem smoker
modelos[[6]] = neuralnet(altocustosim ~ age + sexmale + bmi + children + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
#modelo sem region
modelos[[7]] = neuralnet(altocustosim ~ age + sexmale + bmi + children + smokeryes, data = matriz_treino_, hidden = 0,linear.output = FALSE)
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)
matriz_teste_ = model.matrix( ~ age + sex + bmi + children + smoker + region + altocusto,data = base_teste_)
altocusto_treino = matriz_treino_[,"altocustosim"]
altocusto_teste = matriz_teste_[,"altocustosim"]
for(i in 1:7){
prev_treino = modelos[[i]]$net.result[[1]][,1]
EC_treino[k,i] = EC(altocusto_treino,prev_treino)
prev_teste = (modelos[[i]] |> neuralnet::compute(matriz_teste_))$net.result[,1]
EC_teste[k,i] = EC(altocusto_teste,prev_teste)
}
}
Modelo | Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. | obs |
---|---|---|---|---|---|---|---|
1 | 0.234 | 0.283 | 0.387 | 0.39 | 0.475 | 0.563 | completo |
2 | 0.235 | 0.249 | 0.252 | 0.25 | 0.253 | 0.257 | sem idade |
3 | 0.235 | 0.282 | 0.388 | 0.39 | 0.498 | 0.544 | sem sexo |
4 | 0.234 | 0.252 | 0.301 | 0.302 | 0.327 | 0.412 | sem bmi |
5 | 0.234 | 0.249 | 0.251 | 0.249 | 0.252 | 0.257 | sem children |
6 | 0.566 | 0.571 | 0.572 | 0.573 | 0.574 | 0.579 | sem smoker |
7 | 0.238 | 0.269 | 0.341 | 0.359 | 0.438 | 0.515 | sem região |
Modelo | Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. | obs |
---|---|---|---|---|---|---|---|
1 | 0.204 | 0.331 | 0.384 | 0.416 | 0.476 | 0.735 | completo |
2 | 0.193 | 0.227 | 0.244 | 0.258 | 0.267 | 0.405 | sem idade |
3 | 0.203 | 0.318 | 0.388 | 0.415 | 0.517 | 0.672 | sem sexo |
4 | 0.21 | 0.25 | 0.291 | 0.324 | 0.376 | 0.494 | sem bmi |
5 | 0.198 | 0.236 | 0.237 | 0.259 | 0.27 | 0.38 | sem children |
6 | 0.52 | 0.565 | 0.579 | 0.578 | 0.594 | 0.642 | sem smoker |
7 | 0.2 | 0.283 | 0.342 | 0.377 | 0.446 | 0.668 | sem região |
Vamos seguir com os modelos 2 e 5, sem o primeiro sem a variável
age
e o segundo sem a variável children
.
modelo_2 = neuralnet(altocustosim ~ sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
prev_treino_2 = modelo_2$net.result[[1]][,1]
prev_teste_2 = (modelo_2 |> neuralnet::compute(matriz_teste_))$net.result[,1]
modelo_5 = neuralnet(altocustosim ~ age + sexmale + bmi + smokeryes + regionnorthwest + regionsoutheast + regionsouthwest, data = matriz_treino_, hidden = 0,linear.output = FALSE)
prev_treino_5 = modelo_5$net.result[[1]][,1]
prev_teste_5 = (modelo_5 |> neuralnet::compute(matriz_teste_))$net.result[,1]
library(pROC)
classe_treino = matriz_treino_[,"altocustosim"]
roc2 = roc(response = classe_treino, predictor = prev_treino_2)
plot.roc(roc2,print.auc = TRUE)
q2 = coords(roc2, "best", ret = "threshold")[1,1]
roc5 = roc(response = classe_treino, predictor = prev_treino_5)
plot.roc(roc5,legacy.axes=TRUE,print.auc = TRUE)
q5 = coords(roc5, "best", ret = "threshold")[1,1]
Vamos computar a classificação e as medidas de qualidade da classificação para o Modelo 2.
classe_treino_pred_mod_2 = ifelse(prev_treino_2 < q2,0,1)
tabela_treino_2 = table(prev2=classe_treino_pred_mod_2,real=classe_treino)
CM_treino_2 = confusionMatrix(tabela_treino_2,positive = "1")
classe_teste = matriz_teste_[,"altocustosim"]
classe_teste_pred_mod_2 = ifelse(prev_teste_2 < q2,0,1)
tabela_teste_2 = table(prev2=classe_teste_pred_mod_2,real=classe_teste)
CM_teste_2 = confusionMatrix(tabela_teste_2,positive = "1")