Classificação Multiclasses

Author

Jessica Kubrusly

Suponha um problema de classificação em que, em vez de cada instância (observação) pertencer a uma de duas possíveis classes, ela pertence a uma entre \(k\) possíveis classes.

Este problema também pode ser resolvido com uma rede neural Perseptron. A rede que vai atender a esse tipo de problema será muito parecida com aquelas vistas até o momento, a diferença é que as redes para esse tipo de problema são definidas com mais neurônios na camada de saída.

Arquitetura do Perseptron para problemas de classificação Multiclasses

Se a variável resposta do problema de classificação puder assumir 1 entre \(k\) possíveis classes, o número de neurônios na camada de saída será igual a \(k\). Dessa forma em vez de uma saída teremos \(k\) saídas, que são previsões para cada classe.

Perceptron com 1 camada oculta e 3 neurônios na camada de saída

A figura apresenta uma rede com 3 covariáveis de entrada, 1 camada oculta com 4 neurônios e uma camada de saída com 3 neurônios. Isso indica que esta rede foi construída para um problema de classificação com 3 classes. A saída retorna um número entre 0 e 1 e este número indica a probabilidade da instância pertencer a classe correspondente.

A função de Atrivação Softmax

É esperado que, para uma instância qualquer, a soma das saídas para cada classe seja iguak a 1. Isso pode ser feito, as vezes manualmente, usando a função softmax.

Seja \(K\) o número de classes possíveis. Vamos chamar de \(v_{i,k}\) o valor que saí do somatório do \(k\)-ésimo neurônio da camada de saída ara a \(i\)-ésima observação. A função de ativação softmax é definda por:

\[ \hat{y}_{i,k} = \dfrac{e^{v_{i,k}}}{\sum_{k=1}^{K} e^{v_{i,k}}} \]

Nesse caso, a rede neural terá mais um neurônio na camada oculta com função de ativação softmax e \(\hat{y}_{i,k}\) será a saída desse neurônio.

Perceptron com 1 camada oculta e 3 neurônios na camada de saída e softmax

EC para multiclasse

O que muda no treinamento para o problema multicalsse é a função de custo, que agora deverá considerar um problema de mais de uma classe.

A expressão da entropia cruzada pode ser generalizada para o caso do problema com \(K\) classes, \(K > 2\)

\[ EC = - \dfrac{1}{N} \sum_{i=1}^N \sum_{k=1}^K y_{k,i}\ln(\hat{y}_{k,i}) \]

Essa será a função de custo para o treinamento dos problemas multiclasses, sendo eles com ou sem a função softmax.

Exemplo

Vamos usar o exemplo da base de seguros e tentar prever se um cliente é considerado de alto, médio ou baixo custo.

Primeiro, carregamos os pacotes.

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

Depois, carregamos a base de dados já tratada.

base_treino_sem_NA = readRDS("base_treino_sem_NA.RDS")
base_treino_final =  readRDS("base_treino_final_log.RDS")

O objeto base_treino_sem_NA guarda a base de treino nas unidades originais, sem a padronização das variáveis. Já o objeto base_treino_final guarda a base padroniada.

Criação da variável alvo

Os clientes de baixo custo serão aqueles com charges\(< 10.000\), os de custo médio serão aqueles com $10.000 $ charges\(\le 20.000\), e clientes de alto custo serão aqueles com charges\(> 20.000\).

hist(base_treino_sem_NA$charges)

summary(base_treino_sem_NA$charges)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1132    4843    9565   13396   17082   63770 
custobaixo = ifelse(base_treino_sem_NA$charges<5000,1,0)
customedio = ifelse(base_treino_sem_NA$charges>=5000 & base_treino_sem_NA$charges<=20000,1,0)
custoalto = ifelse(base_treino_sem_NA$charges>20000,1,0)
base_treino_final = base_treino_final |> cbind(custobaixo,customedio,custoalto)

Vamos tirar da matriz de treinamento as variáveis de região e charges para simplificar o código.

colnames(base_treino_final)
 [1] "age"             "sexmale"         "bmi"             "children"       
 [5] "smokeryes"       "regionnorthwest" "regionsoutheast" "regionsouthwest"
 [9] "charges"         "custobaixo"      "customedio"      "custoalto"      
base_treino_final = base_treino_final[,-c(6,7,8,9)]
colnames(base_treino_final)
[1] "age"        "sexmale"    "bmi"        "children"   "smokeryes" 
[6] "custobaixo" "customedio" "custoalto" 

Treinamento e previsão com o neuralnet

modelo_1 = neuralnet(
  custobaixo + customedio + custoalto ~ ., 
  data = base_treino_final, 
  hidden = 1,
  linear.output = FALSE,
  err.fct = "ce")
plot(modelo_1,rep="best")

modelo_2 = neuralnet(
  custobaixo + customedio + custoalto + custoalto ~ ., 
  data = base_treino_final, 
  hidden = 2,
  linear.output = FALSE,
  err.fct = "ce")
plot(modelo_2,rep="best")

modelo_3 = neuralnet(
  custobaixo + customedio + custoalto ~ ., 
  data = base_treino_final, 
  hidden = 3,
  linear.output = FALSE,
  err.fct = "ce")
plot(modelo_3,rep="best")

Agora a previsão não será mais um vetor de uma única coluna, e sim uma matriz com o número de colunas igual ao número de classes.

prev_treino_1 = modelo_1$net.result[[1]]
head(prev_treino_1)
         [,1]      [,2]       [,3]
1 0.516771665 0.1571478 0.32971149
2 0.516771665 0.1571478 0.32971149
3 0.516771665 0.1571478 0.32971149
4 0.516771665 0.1571478 0.32971149
5 0.516771665 0.1571478 0.32971149
6 0.001000017 0.9227626 0.07798269
head(rowSums(prev_treino_1))
       1        2        3        4        5        6 
1.003631 1.003631 1.003631 1.003631 1.003631 1.001745 

A função neuralnet não implementa a softmax e por isso as saidas não somam necessariamente 1. Mas isso pode ser corrigido com uma simples transformação, que não é igual a considerar o softmax no treinamento.

prev_treino_1 = prev_treino_1 / rowSums(prev_treino_1)
head(prev_treino_1)
          [,1]      [,2]       [,3]
1 0.5149020740 0.1565793 0.32851865
2 0.5149020740 0.1565793 0.32851865
3 0.5149020740 0.1565793 0.32851865
4 0.5149020740 0.1565793 0.32851865
5 0.5149020740 0.1565793 0.32851865
6 0.0009982743 0.9211549 0.07784682
head(rowSums(prev_treino_1))
1 2 3 4 5 6 
1 1 1 1 1 1 
prev_treino_2 = modelo_2$net.result[[1]]
head(prev_treino_2)
         [,1]       [,2]       [,3]
1 0.002297474 0.90570010 0.09757296
2 0.878021795 0.09659256 0.04587041
3 0.831185484 0.11828337 0.04707040
4 0.877506163 0.09684338 0.04588541
5 0.876848110 0.09716301 0.04590448
6 0.002381368 0.92760958 0.07844518
prev_treino_2 = prev_treino_2 / rowSums(prev_treino_2)
head(prev_treino_2)
         [,1]       [,2]       [,3]
1 0.002284747 0.90068281 0.09703244
2 0.860396777 0.09465360 0.04494962
3 0.834072000 0.11869414 0.04723386
4 0.860102040 0.09492263 0.04497533
5 0.859726150 0.09526573 0.04500812
6 0.002361446 0.91984961 0.07778894
prev_treino_3 = modelo_3$net.result[[1]]
head(prev_treino_3)
          [,1]       [,2]         [,3]
1 0.000000e+00 0.66122585 0.1673541996
2 8.806445e-01 0.08158596 0.0544018677
3 7.840406e-01 0.15063377 0.0436653657
4 8.717443e-01 0.08778565 0.0530420551
5 8.663019e-01 0.09159128 0.0522646450
6 3.099963e-08 0.99999992 0.0001070966
prev_treino_3 = prev_treino_3 / rowSums(prev_treino_2)
head(prev_treino_3)
          [,1]       [,2]         [,3]
1 0.000000e+00 0.66122585 0.1673541996
2 8.806445e-01 0.08158596 0.0544018677
3 7.840406e-01 0.15063377 0.0436653657
4 8.717443e-01 0.08778565 0.0530420551
5 8.663019e-01 0.09159128 0.0522646450
6 3.099963e-08 0.99999992 0.0001070966

Treinamento e previsão com o nnet

O nnet é um outro pacote que implementa redes neurais Perceptrons. Nele a função softymax é implementada, mas tem desvantagens em relação ao neuralnet. Por exemplo, só permite 1 camada oculta.

Neste pacote, diferente do neuralnet, a base de dados deve ser um data.frame e não uma matriz.

base_nnet = tibble(
  sex = base_treino_sem_NA$sex,
  smoker = base_treino_sem_NA$smoker,
  age = scale(base_treino_sem_NA$age),
  bmi = scale(base_treino_sem_NA$bmi),
  children = scale(base_treino_sem_NA$children),
  custo = as.factor(ifelse(base_treino_sem_NA$charges < 5000,"baixo",ifelse(base_treino_sem_NA$charges > 20000,"alto","medio")))
)
library(nnet)
modelo_nnet_2 = nnet(custo ~ ., data = base_nnet,size = 2)
# weights:  21
initial  value 993.326461 
iter  10 value 540.840443
iter  20 value 376.211058
iter  30 value 307.326849
iter  40 value 304.186813
iter  50 value 304.154936
iter  60 value 304.133905
iter  70 value 303.750198
iter  80 value 303.716184
iter  90 value 303.386665
iter 100 value 303.365268
final  value 303.365268 
stopped after 100 iterations
names(modelo_nnet_2)
 [1] "n"             "nunits"        "nconn"         "conn"         
 [5] "nsunits"       "decay"         "entropy"       "softmax"      
 [9] "censored"      "value"         "wts"           "convergence"  
[13] "fitted.values" "residuals"     "lev"           "call"         
[17] "terms"         "coefnames"     "contrasts"     "xlevels"      
modelo_nnet_2$softmax
[1] TRUE
head(modelo_nnet_2$fitted.values)
        alto        baixo      medio
1 0.09453284 0.0001718783 0.90529528
2 0.03764167 0.8703341593 0.09202417
3 0.04615683 0.8360622986 0.11778087
4 0.03775797 0.8698749936 0.09236704
5 0.03790625 0.8692891808 0.09280457
6 0.07637451 0.0001955817 0.92342991
head(rowSums(modelo_nnet_2$fitted.values))
1 2 3 4 5 6 
1 1 1 1 1 1 

Para a previsão, temos que tomar cuidado. Para os modelos neuralnet a ordem das colunas de previsão é igual a ordem que colocamos no argumento custobaixo + customedio + custoalto ~ ., ou seja, a primeira coluna é a previsão para baixo custo, a segunda para custo médio e a terceira para alto custo.

Já na saída nnet temos que ver a ordem que ele retonar no objeto que guardamos com o comando modelo_nnet_2$fitted.values.

prev_treino_2_nnet = modelo_nnet_2$fitted.values
head(prev_treino_2_nnet)
        alto        baixo      medio
1 0.09453284 0.0001718783 0.90529528
2 0.03764167 0.8703341593 0.09202417
3 0.04615683 0.8360622986 0.11778087
4 0.03775797 0.8698749936 0.09236704
5 0.03790625 0.8692891808 0.09280457
6 0.07637451 0.0001955817 0.92342991

para facilitar as contas de medida de qualidade, vamos reordenar as colunas.

prev_treino_2_nnet = prev_treino_2_nnet[,c("baixo","medio","alto")]
head(prev_treino_2_nnet)
         baixo      medio       alto
1 0.0001718783 0.90529528 0.09453284
2 0.8703341593 0.09202417 0.03764167
3 0.8360622986 0.11778087 0.04615683
4 0.8698749936 0.09236704 0.03775797
5 0.8692891808 0.09280457 0.03790625
6 0.0001955817 0.92342991 0.07637451

EC na base de treino

Para o caso do problema com \(K\) classes, \(K > 2\), a expressão da entropia cruzada precisa ser generalizada.

\[ EC = - \dfrac{1}{N} \sum_{i=1}^N \sum_{k=1}^K y_{k,i}\ln(\hat{y}_{k,i}) \]

sendo, \(N\) o número de instâncias (observações), \(K\) o número de classes, \(y_{k,i}\) a variável indicadora da classe \(k\), isto é, \(y_{k,i} = 1\) se a isntância \(i\) pertence a classe \(k\) e 0 caso contrário, e \(\hat{y}_{k,i}\) o valor de saída para a classe \(k\).

clip = function(x,eps=0.00001){
  return(pmax(pmin(x,1-eps),eps))
}

#real = uma matriz com N linhas e K colunas
#real(i,j)=1 se a observacao i é da classe j 
#previsao = uma matriz com N linhas e K colunas
#previsao eh uma matriz de numeros reais entre 0 e 1 de forma que a soma das linhas tem que ser 1. 
EC = function(real,previsao){
  N = dim(real)[1]
  K = dim(real)[2] 
  ec = -sum(rowSums(real*log(clip(previsao))))/N
  return(ec)
}
classe_real_treino = matrix(
  c(custobaixo,customedio,custoalto),
  ncol = 3,byrow = F)
head(classe_real_treino)
     [,1] [,2] [,3]
[1,]    0    1    0
[2,]    1    0    0
[3,]    1    0    0
[4,]    1    0    0
[5,]    1    0    0
[6,]    0    1    0
(EC_treino_1 = EC(classe_real_treino,prev_treino_1))
[1] 0.6466046
(EC_treino_2 = EC(classe_real_treino,prev_treino_2))
[1] 0.3240459
(EC_treino_3 = EC(classe_real_treino,prev_treino_3))
[1] 0.3100071
(EC_treino_2_nnet = EC(classe_real_treino,prev_treino_2_nnet))
[1] 0.3241082

Matriz de Confusão para a base de treino

Primeiro definimos a variável custo que deve ser do tipo factor e conter a classe para cada observação da base de teste.

custo = as.factor(ifelse(base_treino_sem_NA$charges < 5000,"baixo",ifelse(base_treino_sem_NA$charges > 20000,"alto","medio")))

Vamos usar como a classe prevista aquela de maior valor estimado:

coluna = apply(prev_treino_1,MARGIN = 1,FUN = "which.max")
prev_custo_1 = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_1 = factor(prev_custo_1)
(CM_1 = confusionMatrix(
  factor(prev_custo_1),
  factor(custo)
))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto     0     1     0
     baixo  157   242    74
     medio   36     2   424

Overall Statistics
                                          
               Accuracy : 0.7115          
                 95% CI : (0.6813, 0.7404)
    No Information Rate : 0.5321          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5231          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity             0.000000       0.9878       0.8514
Specificity             0.998654       0.6657       0.9132
Pos Pred Value          0.000000       0.5116       0.9177
Neg Pred Value          0.793583       0.9935       0.8439
Prevalence              0.206197       0.2618       0.5321
Detection Rate          0.000000       0.2585       0.4530
Detection Prevalence    0.001068       0.5053       0.4936
Balanced Accuracy       0.499327       0.8267       0.8823
coluna = apply(prev_treino_2,MARGIN = 1,FUN = "which.max")
prev_custo_2 = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_2 = factor(prev_custo_2)
(CM_2 = confusionMatrix(
  factor(prev_custo_2),
  factor(custo)
))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto   137     0     4
     baixo   13   238    28
     medio   43     7   466

Overall Statistics
                                          
               Accuracy : 0.8985          
                 95% CI : (0.8774, 0.9171)
    No Information Rate : 0.5321          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8302          
                                          
 Mcnemar's Test P-Value : 1.602e-12       

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity               0.7098       0.9714       0.9357
Specificity               0.9946       0.9407       0.8858
Pos Pred Value            0.9716       0.8530       0.9031
Neg Pred Value            0.9296       0.9893       0.9238
Prevalence                0.2062       0.2618       0.5321
Detection Rate            0.1464       0.2543       0.4979
Detection Prevalence      0.1506       0.2981       0.5513
Balanced Accuracy         0.8522       0.9560       0.9108
coluna = apply(prev_treino_3,MARGIN = 1,FUN = "which.max")
prev_custo_3 = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_3 = factor(prev_custo_3)
(CM_3 = confusionMatrix(
  factor(prev_custo_3),
  factor(custo)
))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto   138     0     4
     baixo   13   238    28
     medio   42     7   466

Overall Statistics
                                          
               Accuracy : 0.8996          
                 95% CI : (0.8785, 0.9181)
    No Information Rate : 0.5321          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.832           
                                          
 Mcnemar's Test P-Value : 2.581e-12       

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity               0.7150       0.9714       0.9357
Specificity               0.9946       0.9407       0.8881
Pos Pred Value            0.9718       0.8530       0.9049
Neg Pred Value            0.9307       0.9893       0.9240
Prevalence                0.2062       0.2618       0.5321
Detection Rate            0.1474       0.2543       0.4979
Detection Prevalence      0.1517       0.2981       0.5502
Balanced Accuracy         0.8548       0.9560       0.9119
coluna = apply(prev_treino_2_nnet,MARGIN = 1,FUN = "which.max")
prev_custo_2_nnet = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_2_nnet = factor(prev_custo_2_nnet)
(CM_2_nnet = confusionMatrix(
  factor(prev_custo_2_nnet),
  factor(custo)
))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto   136     0     4
     baixo   13   238    28
     medio   44     7   466

Overall Statistics
                                          
               Accuracy : 0.8974          
                 95% CI : (0.8762, 0.9161)
    No Information Rate : 0.5321          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8283          
                                          
 Mcnemar's Test P-Value : 9.933e-13       

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity               0.7047       0.9714       0.9357
Specificity               0.9946       0.9407       0.8836
Pos Pred Value            0.9714       0.8530       0.9014
Neg Pred Value            0.9284       0.9893       0.9236
Prevalence                0.2062       0.2618       0.5321
Detection Rate            0.1453       0.2543       0.4979
Detection Prevalence      0.1496       0.2981       0.5524
Balanced Accuracy         0.8496       0.9560       0.9097

Leitura da base de teste

Vamos carregar a base de teste, que já está sem NA e com as variáveis independentes quantitativas padronizadas e as qualitativas transformadas em indicadoras.

base_teste = readRDS("base_teste_final.RDS")
head(base_teste)
         age sexmale        bmi    children smokeryes regionnorthwest
1 -0.4405096       1 -1.2931960 -0.92254085         0               1
2  0.4752807       0  0.4404965 -0.08337833         0               0
3 -1.1449637       1  0.5955356 -0.92254085         0               0
4 -1.1449637       1 -1.1090870 -0.92254085         0               0
5  1.1797348       1  1.5483800 -0.92254085         0               0
6 -0.1587280       1 -0.4340210  0.75578419         0               1
  regionsoutheast regionsouthwest
1               0               0
2               1               0
3               0               1
4               0               0
5               0               1
6               0               0

Previsão na base de teste

prev_teste_1 = predict(modelo_1,newdata = base_teste)
prev_teste_2 = predict(modelo_2,newdata = base_teste)
prev_teste_3 = predict(modelo_3,newdata = base_teste)

EC na base de teste

base_teste_bruta = readRDS("base_teste_bruta.RDS")
base_teste_bruta = base_teste_bruta |> select(-married)
base_teste_bruta = na.omit(base_teste_bruta)
custobaixo_teste = ifelse(base_teste_bruta$charges<5000,1,0)
customedio_teste = ifelse(base_teste_bruta$charges>=5000 & base_teste_bruta$charges<=20000,1,0)
custoalto_teste = ifelse(base_teste_bruta$charges>20000,1,0)
classe_real_teste = matrix(
  c(custobaixo_teste,customedio_teste,custoalto_teste),
  ncol = 3,byrow = F)
head(classe_real_teste)
     [,1] [,2] [,3]
[1,]    0    0    1
[2,]    0    1    0
[3,]    1    0    0
[4,]    1    0    0
[5,]    0    1    0
[6,]    0    1    0
(EC_teste_1 = EC(classe_real_teste,prev_teste_1))
[1] 0.6131556
(EC_teste_2 = EC(classe_real_teste,prev_teste_2))
[1] 0.2743206
(EC_teste_3 = EC(classe_real_teste,prev_teste_3))
[1] 0.2948038

Matriz de Confusão para a base de teste

Primeiro definimos a variável custo_teste que deve ser do tipo factor e conter a classe para cada observação da base de teste.

custo_teste = as.factor(ifelse(base_teste_bruta$charges < 5000,"baixo",ifelse(base_teste_bruta$charges > 20000,"alto","medio")))

Vamos usar como a classe prevista aquela de maior valor estimado:

coluna = apply(prev_teste_1,MARGIN = 1,FUN = "which.max")
prev_custo_1_teste = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_1_teste = factor(prev_custo_1_teste)

Os levels de prev_custo_1_teste e custo_teste devem ser os mesmos.

levels(prev_custo_1_teste)
[1] "alto"  "baixo" "medio"
levels(custo_teste)
[1] "alto"  "baixo" "medio"

Pelo que percebemos, o modelo_1 não realiza previsão do tipo altocusto, mas precisamos inlcuir essa classe para que a função de confusão seja construída.

levels(prev_custo_1_teste) = c(levels(prev_custo_1_teste),"alto")
levels(prev_custo_1_teste)
[1] "alto"  "baixo" "medio"
(CM_1_teste = confusionMatrix(prev_custo_1_teste,custo_teste))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto     1     0     0
     baixo   67   113    29
     medio   10     0   177

Overall Statistics
                                          
               Accuracy : 0.733           
                 95% CI : (0.6866, 0.7759)
    No Information Rate : 0.5189          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5589          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity             0.012821       1.0000       0.8592
Specificity             1.000000       0.6620       0.9476
Pos Pred Value          1.000000       0.5407       0.9465
Neg Pred Value          0.805556       1.0000       0.8619
Prevalence              0.196474       0.2846       0.5189
Detection Rate          0.002519       0.2846       0.4458
Detection Prevalence    0.002519       0.5264       0.4710
Balanced Accuracy       0.506410       0.8310       0.9034

Repetimos o mesmo processo para os demais modelos.

coluna = apply(prev_teste_2,MARGIN = 1,FUN = "which.max")
prev_custo_2_teste = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_2_teste = factor(prev_custo_2_teste)

Os levels de prev_custo_2_teste e custo_teste devem ser os mesmos.

levels(prev_custo_2_teste)
[1] "alto"  "baixo" "medio"
levels(custo_teste)
[1] "alto"  "baixo" "medio"
(CM_2_teste = confusionMatrix(prev_custo_2_teste,custo_teste))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto    64     0     6
     baixo    2   110     7
     medio   12     3   193

Overall Statistics
                                          
               Accuracy : 0.9244          
                 95% CI : (0.8939, 0.9484)
    No Information Rate : 0.5189          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.8757          
                                          
 Mcnemar's Test P-Value : 0.1328          

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity               0.8205       0.9735       0.9369
Specificity               0.9812       0.9683       0.9215
Pos Pred Value            0.9143       0.9244       0.9279
Neg Pred Value            0.9572       0.9892       0.9312
Prevalence                0.1965       0.2846       0.5189
Detection Rate            0.1612       0.2771       0.4861
Detection Prevalence      0.1763       0.2997       0.5239
Balanced Accuracy         0.9009       0.9709       0.9292
coluna = apply(prev_teste_3,MARGIN = 1,FUN = "which.max")
prev_custo_3_teste = ifelse(coluna==1,"baixo",ifelse(coluna==2,"medio","alto"))
prev_custo_3_teste = factor(prev_custo_3_teste)

Os levels de prev_custo_23_teste e custo_teste devem ser os mesmos.

levels(prev_custo_3_teste)
[1] "alto"  "baixo" "medio"
levels(custo_teste)
[1] "alto"  "baixo" "medio"
(CM_3_teste = confusionMatrix(prev_custo_3_teste,custo_teste))
Confusion Matrix and Statistics

          Reference
Prediction alto baixo medio
     alto    63     0     5
     baixo    2   109     7
     medio   13     4   194

Overall Statistics
                                         
               Accuracy : 0.9219         
                 95% CI : (0.891, 0.9463)
    No Information Rate : 0.5189         
    P-Value [Acc > NIR] : < 2e-16        
                                         
                  Kappa : 0.8711         
                                         
 Mcnemar's Test P-Value : 0.09478        

Statistics by Class:

                     Class: alto Class: baixo Class: medio
Sensitivity               0.8077       0.9646       0.9417
Specificity               0.9843       0.9683       0.9110
Pos Pred Value            0.9265       0.9237       0.9194
Neg Pred Value            0.9544       0.9857       0.9355
Prevalence                0.1965       0.2846       0.5189
Detection Rate            0.1587       0.2746       0.4887
Detection Prevalence      0.1713       0.2972       0.5315
Balanced Accuracy         0.8960       0.9665       0.9264

Comparação de resultados

Vamos comparar os resultados de todos os modelos treinados considerando os valores de R\(^2\) tanto na base de treino quanto na base de teste.

Precisamos escolher uma ou mais medidas de comparação. Vamos começar pela acurácia.

Acuracia = matrix(
  c(CM_1$overall["Accuracy"],CM_2$overall["Accuracy"],CM_3$overall["Accuracy"],CM_1_teste$overall["Accuracy"],CM_2_teste$overall["Accuracy"],CM_3_teste$overall["Accuracy"]),
  nrow = 2, byrow = T)
barplot(Acuracia,
        beside = T,
        names.arg = c("Modelo 1","Modelo 2","Modelo 3"),
        ylim = c(0,1),
        col=c("tomato","blue4"),  
        args.legend = list(x = "bottomright"),
        legend.text = c("treino","teste"),main="Acurácia")

Mas podemos usar outra medida, como por exemplo, a sensibilidade da classe alto custo, que indica a taxa de acerto para a classe altocusto, ou seja, a proporção de clientes de alto custo que foram corretamente classificados como alto custo.

S_alto = 
  matrix(
  c(CM_1$byClass["Class: alto","Sensitivity"],CM_2$byClass["Class: alto","Sensitivity"],CM_3$byClass["Class: alto","Sensitivity"],CM_1_teste$byClass["Class: alto","Sensitivity"],CM_2_teste$byClass["Class: alto","Sensitivity"],CM_3_teste$byClass["Class: alto","Sensitivity"]),
  nrow = 2, byrow = T)
barplot(S_alto,
        beside = T,
        names.arg = c("Modelo 1","Modelo 2","Modelo 3"),
        ylim = c(0,1),
        col=c("tomato","blue4"),  
        args.legend = list(x = "bottomright"),
        legend.text = c("treino","teste"),main="Sensibilidade para Alto Custo")

Vamos fazer mais um exemplo de comparação, considerando a sensibilidade da classe baixo custo.

S_baixo = 
  matrix(
  c(CM_1$byClass["Class: baixo","Sensitivity"],CM_2$byClass["Class: baixo","Sensitivity"],CM_3$byClass["Class: baixo","Sensitivity"],CM_1_teste$byClass["Class: baixo","Sensitivity"],CM_2_teste$byClass["Class: baixo","Sensitivity"],CM_3_teste$byClass["Class: baixo","Sensitivity"]),
  nrow = 2, byrow = T)
barplot(S_baixo,
        beside = T,
        names.arg = c("Modelo 1","Modelo 2","Modelo 3"),
        ylim = c(0,1),
        col=c("tomato","blue4"),  
        args.legend = list(x = "bottomright"),
        legend.text = c("treino","teste"),main="Sensibilidade para Baixo Custo")