Suponha um problema de classificação 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 de um neurônio de
saída.
Exemplo
Vamos usar o exemplo da base da segurado e tentar prever se um
cliente é considerado de alto, médio ou baixo custo. Os clientes de
baixo custo será aqueles com charges
\(< 6.000\), os de custo médio serão
aqueles com $6.000 $ charges
\(\le
12.000\), e clientes de alto custo serão aqueles com
charges
\(>
12.000\).
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)
base = base |>
mutate(custo = ifelse(base$charges < 6000,"baixo",
ifelse(base$charges > 12000,
"alto","medio")))
base = base |> select(-charges)
Divisão entre treino e teste
set.seed(123456789)
N = dim(base)[1]
indices_treino = caret::createDataPartition(1:N,p=0.7)[[1]]
base_treino = base[indices_treino,]
base_teste = base[-indices_treino,]
Padronização das variáveis quantitativas
base_treino_ = base_treino |> mutate(age = scale(age),
bmi = scale(bmi),
children = scale(children))
Tratamento para as variáveis categóricas
matriz_treino_ <- model.matrix( ~ ., data = base_treino_)[,-1]
Ajuste dos modelos
modelo_1 = neuralnet(
customedio + custobaixo ~ .,
data = matriz_treino_,
hidden = 1,
linear.output = FALSE,
err.fct = "ce")
plot(modelo_1,rep = "best")

modelo_2 = neuralnet(
customedio + custobaixo ~ .,
data = matriz_treino_,
hidden = 2,
linear.output = FALSE,
err.fct = "ce",
stepmax = 1e+06)
plot(modelo_2,rep = "best")

modelo_22 = neuralnet(
customedio + custobaixo ~ .,
data = matriz_treino_,
hidden = c(2,2),
linear.output = FALSE,
err.fct = "ce",
stepmax = 10e6)
plot(modelo_22,rep = "best")

Previsão na base de treino
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]]
prev_treino_2 = modelo_2$net.result[[1]]
prev_treino_22 = modelo_22$net.result[[1]]
EC na base de treino
Para o caso do problema com mais de duas classes 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\).
#real = uma matriz com N linhas e K colunas
#previsao = uma matriz com N linhas e K colunas
EC = function(real,previsao){
N = dim(real)[1]
K = dim(real)[2]
prev_classe_real = real*previsao
prev_classe_real = apply(prev_classe_real, MARGIN = 1, FUN="sum")
ec = -sum(log(prev_classe_real)/N)
return(ec)
}
O comando $net.result[[1]]
fornece a previsão para as
duas classes com neurônios aparente. A previsão para a terceira classe,
no caso a classe de alto custo, é tirada a partir do complementar da
soma das classes já estimadas.
prev_treino_1 = prev_treino_1 |> cbind(1-apply(prev_treino_1, MARGIN = 1, FUN="sum"))
prev_treino_2 = prev_treino_2 |> cbind(1-apply(prev_treino_2, MARGIN = 1, FUN="sum"))
prev_treino_22 = prev_treino_22 |> cbind(1-apply(prev_treino_22, MARGIN = 1, FUN="sum"))
classe_real_treino = matriz_treino_[,c("customedio","custobaixo")]
classe_real_treino = classe_real_treino |> cbind(1-apply(classe_real_treino, MARGIN = 1, FUN="sum"))
EC_treino_1 = EC(classe_real_treino,prev_treino_1)
EC_treino_2 = EC(classe_real_treino,prev_treino_2)
EC_treino_22 = EC(classe_real_treino,prev_treino_22)
Entropia cruzada base de treino
0.600308 |
0.261179 |
0.254207 |
Matriz de Confusão para a base de treino
custo_real = base_treino$custo
custo_real = as.factor(custo_real)
custo_estimado = apply(X = prev_treino_1,MARGIN = 1,FUN = "which.max")
custo_estimado =
ifelse(custo_estimado==1,"medio",
ifelse(custo_estimado==2,"baixo","alto"))
custo_estimado = as.factor(custo_estimado)
CMtreino1 = confusionMatrix(data = custo_estimado,reference = custo_real)
Matriz de Confusão para o Modelo 1 na base de treino
alto |
314 |
0 |
298 |
baixo |
35 |
285 |
6 |
medio |
0 |
0 |
0 |
Medidas de qualidade da classificação para o Modelo 1 na base
de treino
Class: alto |
0.8997135 |
0.4940577 |
0.5130719 |
0.8926380 |
0.5130719 |
0.8997135 |
0.6534860 |
0.3720682 |
0.3347548 |
0.652452 |
0.6968856 |
Class: baixo |
1.0000000 |
0.9372129 |
0.8742331 |
1.0000000 |
0.8742331 |
1.0000000 |
0.9328969 |
0.3038380 |
0.3038380 |
0.347548 |
0.9686064 |
Class: medio |
0.0000000 |
1.0000000 |
NaN |
0.6759062 |
NA |
0.0000000 |
NA |
0.3240938 |
0.0000000 |
0.000000 |
0.5000000 |
custo_estimado = apply(X = prev_treino_2,MARGIN = 1,FUN = "which.max")
custo_estimado = ifelse(custo_estimado==1,"medio",ifelse(custo_estimado==2,"baixo","alto"))
custo_estimado = as.factor(custo_estimado)
CMtreino2 = confusionMatrix(data = custo_estimado,reference = custo_real)
Matriz de Confusão para o Modelo 2 na base de treino
alto |
283 |
0 |
0 |
baixo |
34 |
285 |
6 |
medio |
32 |
0 |
298 |
Medidas de qualidade da classificação para o Modelo 2 na base
de treino
Class: alto |
0.8108883 |
1.0000000 |
1.0000000 |
0.8992366 |
1.0000000 |
0.8108883 |
0.8955696 |
0.3720682 |
0.3017058 |
0.3017058 |
0.9054441 |
Class: baixo |
1.0000000 |
0.9387443 |
0.8769231 |
1.0000000 |
0.8769231 |
1.0000000 |
0.9344262 |
0.3038380 |
0.3038380 |
0.3464819 |
0.9693721 |
Class: medio |
0.9802632 |
0.9495268 |
0.9030303 |
0.9901316 |
0.9030303 |
0.9802632 |
0.9400631 |
0.3240938 |
0.3176972 |
0.3518124 |
0.9648950 |
custo_estimado = apply(
X = prev_treino_22,
MARGIN = 1,
FUN = "which.max")
custo_estimado =
ifelse(custo_estimado==1,"medio",
ifelse(custo_estimado==2,"baixo","alto"))
custo_estimado = as.factor(custo_estimado)
CMtreino22 = confusionMatrix(data = custo_estimado,reference = custo_real)
Matriz de Confusão para o Modelo 22 na base de treino
alto |
284 |
0 |
0 |
baixo |
33 |
285 |
6 |
medio |
32 |
0 |
298 |
Medidas de qualidade da classificação para o Modelo 22 na base
de treino
Class: alto |
0.8137536 |
1.0000000 |
1.0000000 |
0.9006116 |
1.0000000 |
0.8137536 |
0.8973144 |
0.3720682 |
0.3027719 |
0.3027719 |
0.9068768 |
Class: baixo |
1.0000000 |
0.9402757 |
0.8796296 |
1.0000000 |
0.8796296 |
1.0000000 |
0.9359606 |
0.3038380 |
0.3038380 |
0.3454158 |
0.9701378 |
Class: medio |
0.9802632 |
0.9495268 |
0.9030303 |
0.9901316 |
0.9030303 |
0.9802632 |
0.9400631 |
0.3240938 |
0.3176972 |
0.3518124 |
0.9648950 |
Processamento na base de teste
base_teste_ = base_teste |> mutate(age = (age - mean(base_treino$age))/sd(base_treino$age) ,
bmi = (bmi - mean(base_treino$bmi)/sd(base_treino$bmi)),
children = (children - mean(base_treino$children))/sd(base_treino$children))
matriz_teste_ <- model.matrix( ~ ., data = base_teste_)
Previsão na base de teste
prev_teste_1 = (modelo_1 |> neuralnet::compute(matriz_teste_))$net.result
prev_teste_2 = (modelo_2 |> neuralnet::compute(matriz_teste_))$net.result
prev_teste_22 = (modelo_22 |> neuralnet::compute(matriz_teste_))$net.result
prev_teste_1 = prev_teste_1 |> cbind(1-apply(prev_teste_1, MARGIN = 1, FUN="sum"))
prev_teste_2 = prev_teste_2 |> cbind(1-apply(prev_teste_2, MARGIN = 1, FUN="sum"))
prev_teste_22 = prev_teste_22 |> cbind(1-apply(prev_teste_22, MARGIN = 1, FUN="sum"))
EC na base de teste
classe_real_teste = matriz_teste_[,c("customedio","custobaixo")]
classe_real_teste = classe_real_teste |> cbind(1-apply(classe_real_teste , MARGIN = 1, FUN="sum"))
EC_teste_1 = EC(classe_real_teste,prev_teste_1)
EC_teste_2 = EC(classe_real_teste,prev_teste_2)
EC_teste_22 = EC(classe_real_teste,prev_teste_22)
Entropia cruzada base de teste
0.5923592 |
Inf |
6.972461 |
Matriz de Confusão para a base de teste
custo_real = base_teste$custo
custo_real = as.factor(custo_real)
custo_estimado = apply(X = prev_teste_1,MARGIN = 1,FUN = "which.max")
custo_estimado =
ifelse(custo_estimado==1,"medio",
ifelse(custo_estimado==2,"baixo","alto"))
custo_estimado = as.factor(custo_estimado)
CMteste1 = confusionMatrix(data = custo_estimado,reference = custo_real)
Matriz de Confusão para o Modelo 1 na base de teste
alto |
138 |
0 |
103 |
baixo |
5 |
144 |
10 |
medio |
0 |
0 |
0 |
Medidas de qualidade da classificação para o Modelo 1 na base
de teste
Class: alto |
0.965035 |
0.5992218 |
0.5726141 |
0.9685535 |
0.5726141 |
0.965035 |
0.718750 |
0.3575 |
0.345 |
0.6025 |
0.7821284 |
Class: baixo |
1.000000 |
0.9414062 |
0.9056604 |
1.0000000 |
0.9056604 |
1.000000 |
0.950495 |
0.3600 |
0.360 |
0.3975 |
0.9707031 |
Class: medio |
0.000000 |
1.0000000 |
NaN |
0.7175000 |
NA |
0.000000 |
NA |
0.2825 |
0.000 |
0.0000 |
0.5000000 |
custo_estimado = apply(X = prev_teste_2,MARGIN = 1,FUN = "which.max")
custo_estimado = ifelse(custo_estimado==1,"medio",ifelse(custo_estimado==2,"baixo","alto"))
custo_estimado = as.factor(custo_estimado)
CMteste2 = confusionMatrix(data = custo_estimado,reference = custo_real)
Matriz de Confusão para o Modelo 2 na base de teste
alto |
131 |
0 |
24 |
baixo |
8 |
144 |
29 |
medio |
4 |
0 |
60 |
Medidas de qualidade da classificação para o Modelo 2 na base
de teste
Class: alto |
0.9160839 |
0.9066148 |
0.8451613 |
0.9510204 |
0.8451613 |
0.9160839 |
0.8791946 |
0.3575 |
0.3275 |
0.3875 |
0.9113494 |
Class: baixo |
1.0000000 |
0.8554688 |
0.7955801 |
1.0000000 |
0.7955801 |
1.0000000 |
0.8861538 |
0.3600 |
0.3600 |
0.4525 |
0.9277344 |
Class: medio |
0.5309735 |
0.9860627 |
0.9375000 |
0.8422619 |
0.9375000 |
0.5309735 |
0.6779661 |
0.2825 |
0.1500 |
0.1600 |
0.7585181 |
custo_estimado = apply(
X = prev_teste_22,
MARGIN = 1,
FUN = "which.max")
custo_estimado =
ifelse(custo_estimado==1,"medio",
ifelse(custo_estimado==2,"baixo","alto"))
custo_estimado = as.factor(custo_estimado)
CMteste22 = confusionMatrix(data = custo_estimado,reference = custo_real)
Matriz de Confusão para o Modelo 22 na base de teste
alto |
138 |
64 |
64 |
baixo |
2 |
80 |
12 |
medio |
3 |
0 |
37 |
Medidas de qualidade da classificação para o Modelo 22 na base
de teste
Class: alto |
0.9650350 |
0.5019455 |
0.5187970 |
0.9626866 |
0.5187970 |
0.9650350 |
0.6748166 |
0.3575 |
0.3450 |
0.665 |
0.7334902 |
Class: baixo |
0.5555556 |
0.9453125 |
0.8510638 |
0.7908497 |
0.8510638 |
0.5555556 |
0.6722689 |
0.3600 |
0.2000 |
0.235 |
0.7504340 |
Class: medio |
0.3274336 |
0.9895470 |
0.9250000 |
0.7888889 |
0.9250000 |
0.3274336 |
0.4836601 |
0.2825 |
0.0925 |
0.100 |
0.6584903 |
Comparação entre diferentes modelos de classificação
Base de treino
Medida F1 para as três classes na base de treino
Class: alto |
0.6534860 |
0.8955696 |
0.8973144 |
Class: baixo |
0.9328969 |
0.9344262 |
0.9359606 |
Class: medio |
NA |
0.9400631 |
0.9400631 |
Tabela 1: Comparação entre modelos de classificação de acordo com a
medida F1 na base de treino
Tabela 2: Comparação entre modelos de classificação de acordo com a
medida F1 na base de treino
Base de teste
Medida F1 para as três classes na base de teste
Class: alto |
0.718750 |
0.8791946 |
0.6748166 |
Class: baixo |
0.950495 |
0.8861538 |
0.6722689 |
Class: medio |
NA |
0.6779661 |
0.4836601 |
Tabela 3: Comparação entre modelos de classificação de acordo com a
medida F1 na base de teste
Tabela 4: Comparação entre modelos de classificação de acordo com a
medida F1 na base de teste