Introdução ao aprendizado supervisionado

Guilherme Ferreira
2025-05-23

Introdução

O aprendizado supervisionado depende de dados rotulados para ajuste do modelo estatístico capaz de realizar predições ou classificações.

Muitos métodos clássicos de aprendizado estatístico, como regressão linear e regressão logística, bem como abordagens mais modernas como GAM, boosting e Support vector machines, operam no domínio do aprendizado supervisionado.1

Neste artigo, discutiremos algumas técnicas de classificação amplamente utilizadas, tais como Análise Discriminante Linear (em inglês LDA - Linear Discriminant Analysis), Análise Discriminante Quadrática (em inglês QDA - Quadratic Discriminant Analysis) e Árvores de Decisão.

Dataset

Novamente, vamos utilizar o dataset seed2 para ajustar e avaliar todos os modelos de classificação.

O conjunto de dados seeds_dataset se refere a medidas geométricas de sementes (area, perimeter, compactness, legth, width, asymmetry, length_grove) de três variedades de trigo, a saber: Kama, Rosa e Canadian.

Preparação e pré-processamento

Bibliotecas

Carregamos as seguintes bibliotecas do R:

Diretório de trabalho

Definimos o diretório onde se encontra o arquivo a ser importado:

setwd("/home/gf/Scripts/Moyses")

Importação dos dados

Carregamos o arquivo seeds_dataset2.txt, com a função read.table():

dados <- read.table('seeds_dataset2.txt', header=FALSE)

Inspeção e tratamento dos dados

Constatamos que o conjunto de dados da amostra é constituido por 210 observações, com sete variáveis contínuas, e uma, a última, categórica:

str(dados)
'data.frame':   210 obs. of  8 variables:
 $ V1: num  15.3 14.9 14.3 13.8 16.1 ...
 $ V2: num  14.8 14.6 14.1 13.9 15 ...
 $ V3: num  0.871 0.881 0.905 0.895 0.903 ...
 $ V4: num  5.76 5.55 5.29 5.32 5.66 ...
 $ V5: num  3.31 3.33 3.34 3.38 3.56 ...
 $ V6: num  2.22 1.02 2.7 2.26 1.35 ...
 $ V7: num  5.22 4.96 4.83 4.8 5.17 ...
 $ V8: int  1 1 1 1 1 1 1 1 1 1 ...

Atribuímos nomes às sete primeiras variáveis, consideradas variáveis preditoras ou variáveis independentes, conforme as medidas geométricas das sementes, adicionando o rótulo “type” à variável dependente ou variável de resposta.

names(dados) <- c('area', 'perimeter', 'compactness', 'length', 'width', 
                  'asymmetry', 'length_grove', 'type')
sementes <- dados

Verificamos a quantidade de itens da amostra por variedade:

table(dados$type)

 1  2  3 
70 70 70 

No R, as variáveis categóricas são armazendas como fator, mediante uso da função factor():

dados$type <- as.factor(dados$type)

Executamos uma função para verificar a existência de dados nulos:

sapply(dados,function(x) sum(is.na(x)))
        area    perimeter  compactness       length        width 
           0            0            0            0            0 
   asymmetry length_grove         type 
           0            0            0 

Inspecionamos novamente o dataset, agora com a função glimpse():

glimpse(dados)
Rows: 210
Columns: 8
$ area         <dbl> 15.26, 14.88, 14.29, 13.84, 16.14, 14.38, 14.69…
$ perimeter    <dbl> 14.84, 14.57, 14.09, 13.94, 14.99, 14.21, 14.49…
$ compactness  <dbl> 0.8710, 0.8811, 0.9050, 0.8955, 0.9034, 0.8951,…
$ length       <dbl> 5.763, 5.554, 5.291, 5.324, 5.658, 5.386, 5.563…
$ width        <dbl> 3.312, 3.333, 3.337, 3.379, 3.562, 3.312, 3.259…
$ asymmetry    <dbl> 2.2210, 1.0180, 2.6990, 2.2590, 1.3550, 2.4620,…
$ length_grove <dbl> 5.220, 4.956, 4.825, 4.805, 5.175, 4.956, 5.219…
$ type         <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…

Segmentação dos dados

Segmentamos o dataset em dados de treinamento e dados de validação, na proporção 70/30.

set.seed(12345)
percent_train <- 0.70
n_tr <- nrow(dados)*percent_train
tr <- sample(1:nrow(dados),floor(n_tr))

Dados de treinamento:

Dados de treinamento são utilizados para ajustar o modelo.

dados_tr <- dados[tr,]

Dados de validação:

Subamostra utilizada para validar o modelo.

dados_val<-dados[-tr,]

Após a partição dos dados, assim fica a distribuição por variedade, para treinamento e validação:

table(dados_tr$type)

 1  2  3 
52 46 49 
table(dados_val$type)

 1  2  3 
18 24 21 

Análise Discriminante

Análise Discriminante Linear(em inglês LDA - Linear Discriminant Analysis) é uma metodologia utilizada para a classificação de elementos de uma amostra ou população sob o enfoque estatístico, integrante do grupo de técnicas de aprendizado supervisionado, pois a aprendizagem se dá através de treinamento com dados rotulados.

Ajuste do modelo LDA

A LDA presume que as observações dentro de cada classe são retiradas de uma distribuição multivariada gaussiana e que a covariância das variáveis preditoras são comuns através de todos os níveis da variável resposta Y.

lda.m1 <- lda(type ~ ., data = dados_tr, prior = c(1,1,1)/3)
list("Prior probabilities of groups" = lda.m1$prior,
     "SVD - Singular values" = lda.m1$svd)
$`Prior probabilities of groups`
        1         2         3 
0.3333333 0.3333333 0.3333333 

$`SVD - Singular values`
[1] 20.23893 16.11946

Ajuste do modelo QDA

A QDA também presume que as observações dentro de cada classe são retiradas de uma distribuição multivariada gaussiana, mas, diferentemente da LDA, presupõe que cada classe da variável resposta Y possui sua própria matriz de covariância.

qda.m1 <- qda(type ~ ., data = dados_tr, prior = c(1,1,1)/3)
qda.m1
Call:
qda(type ~ ., data = dados_tr, prior = c(1, 1, 1)/3)

Prior probabilities of groups:
        1         2         3 
0.3333333 0.3333333 0.3333333 

Group means:
      area perimeter compactness   length    width asymmetry
1 14.45942  14.35519   0.8801904 5.528096 3.259288  2.538215
2 18.41609  16.18196   0.8820391 6.187500 3.676761  3.760783
3 11.92204  13.28163   0.8485449 5.246224 2.857980  4.911816
  length_grove
1     5.103615
2     6.058804
3     5.134020

Ajuste do modelo LOOCV (Leave-one-out Cross-validation)

Para amostras pequenas, que torna inviável a separação em grupos de treinamento e validação, a literatura sugere o uso da abordagem LOOCV, que exclui uma observação da amostra, e aplica a função discriminante nos dados restantes.

Aplicação dos modelos para fazer predições

Para medir o desempenho conjunto, os dois modelos serão aplicados aos dados de validação.

preditos.lda <- predict(lda.m1, dados_val)
preditos.qda <- predict(qda.m1, dados_val)

Medidas de avaliação dos modelos

Tabela de confusão

Calculamos a tabela de confusão para ambos os modelos, para avaliar qual deles apresentou melhor desempenho. Recordem que a repartição amostral dos dados, na proporção 70/30, alocou 18, 24 e 21 observações para as variedades 1, 2 e 3, respectivamente, para validação do modelo.

lda.mc <- table(dados_val$type, preditos.lda$class)
lda.mc
   
     1  2  3
  1 18  0  0
  2  1 23  0
  3  3  0 18
qda.mc <- table(dados_val$type, preditos.qda$class)
qda.mc
   
     1  2  3
  1 17  0  1
  2  1 23  0
  3  3  0 18

Percentual de acurácia

ac.lda <- mean(preditos.lda$class==dados_val$type)
ac.qda <- mean(preditos.qda$class==dados_val$type)
list("Acurácia Modelo LDA" = ac.lda,
     "Acurácia Modelo QDA" = ac.qda)
$`Acurácia Modelo LDA`
[1] 0.9365079

$`Acurácia Modelo QDA`
[1] 0.9206349

Taxa de Erro Aparente

dados_val %>%
  mutate(pred.lda = (preditos.lda$class),
         pred.qda = (preditos.qda$class)) %>%
  summarise(lda.error = mean(type != pred.lda),
            qda.error = mean(type != pred.qda))
   lda.error  qda.error
1 0.06349206 0.07936508

Sensibilidade e Especificidade dos modelos

Sensibilidade mede o percentual de verdadeiros positivos. Especificidade mede o percentual de verdadeiros negativos.

Sensibilidade

sens.lda <- lda.mc[2,2]/ sum(lda.mc[2,])
sens.qda <- qda.mc[2,2]/ sum(qda.mc[2,])
list("Sensibilidade do Modelo LDA" = sens.lda,
     "Sensibilidade do Modelo QDA" = sens.qda)
$`Sensibilidade do Modelo LDA`
[1] 0.9583333

$`Sensibilidade do Modelo QDA`
[1] 0.9583333

Especificidade

esp.lda <- lda.mc[1,1]/ sum(lda.mc[1,])
esp.qda <- qda.mc[1,1]/ sum(qda.mc[1,])
list("Especificidade do Modelo LDA" = esp.lda,
     "Especificidade do Modelo QDA" = esp.qda)
$`Especificidade do Modelo LDA`
[1] 1

$`Especificidade do Modelo QDA`
[1] 0.9444444

Ao comparar os indicadores de qualidade dos modelos, concluímos que o modelo linear de Análise Discriminante apresentou desempenho ligeiramente superior ao modelo Quadrático.

Finalmente, representamos graficamente o modo como as duas funções discriminantes separam as variedades: a LD1, que responde por 61,18% da variância entre os grupos, desmembra as variedades 2 e 3 de maneira indiscutível. Contudo, a LD2, que responde por apenas 38,81% da variância entre os grupos, não aparta as variedades 1 e 2 e tampouco as 1 e 3 de forma inequívoca.

lda.data <- cbind(dados_tr, predict(lda.m1)$x)
ggplot(lda.data, aes(LD1, LD2)) +
  geom_point(aes(color = type))

Árvore de Decisão

Árvore de Decisão é um algoritmo de aprendizagem supervisionada, que pode ser utilizado para resolver problemas de classificação, adequado para variáveis dependentes categóricas.

Particionar o conjunto de dados (Treinamento e validação)

tipo1 <- dados[dados$type==1,] 
tipo2 <- dados[dados$type==2,] 
tipo3 <- dados[dados$type==3,]  
dados_train <- rbind(tipo1[1:35,],tipo2[1:35,],tipo3[1:35,])
dados_test <- rbind(tipo1[36:70,],tipo2[36:70,],tipo3[36:70,])

Ajuste de uma árvore

arvore1 <- tree(type ~ ., data = dados_train)

Resumo

Quatro variáveis foram utilizadas na construção da árvore.

summary(arvore1)

Classification tree:
tree(formula = type ~ ., data = dados_train)
Variables actually used in tree construction:
[1] "length_grove" "area"         "compactness"  "asymmetry"   
Number of terminal nodes:  6 
Residual mean deviance:  0.1691 = 16.74 / 99 
Misclassification error rate: 0.0381 = 4 / 105 

Apresentação da árvore ajustada

plot(arvore1)
text(arvore1)

Verificar se é interessante realizar a poda

cv.seed=cv.tree(arvore1)
plot(cv.seed$size ,cv.seed$dev ,type="b")

Se desejar podar

mod_poda=prune.tree(arvore1,best=3)
plot(mod_poda)
text(mod_poda, pretty =0)

Predição (considerando o modelo com poda)

pred_arv <- predict(mod_poda, dados_test, type="class")
head(pred_arv)
[1] 1 1 1 1 1 1
Levels: 1 2 3

Medidas de performance

confusionMatrix(pred_arv, dados_test$type)
Confusion Matrix and Statistics

          Reference
Prediction  1  2  3
         1 31  2  6
         2  0 33  0
         3  4  0 29

Overall Statistics
                                          
               Accuracy : 0.8857          
                 95% CI : (0.8089, 0.9395)
    No Information Rate : 0.3333          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8286          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: 1 Class: 2 Class: 3
Sensitivity            0.8857   0.9429   0.8286
Specificity            0.8857   1.0000   0.9429
Pos Pred Value         0.7949   1.0000   0.8788
Neg Pred Value         0.9394   0.9722   0.9167
Prevalence             0.3333   0.3333   0.3333
Detection Rate         0.2952   0.3143   0.2762
Detection Prevalence   0.3714   0.3143   0.3143
Balanced Accuracy      0.8857   0.9714   0.8857

Ajuste bagging

bagging <- randomForest(type ~ ., data=dados_train,  mtry = 7)
bagging

Call:
 randomForest(formula = type ~ ., data = dados_train, mtry = 7) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 7

        OOB estimate of  error rate: 5.71%
Confusion matrix:
   1  2  3 class.error
1 32  1  2  0.08571429
2  1 34  0  0.02857143
3  2  0 33  0.05714286

Avaliação do modelo (Predição)

pred_bagg <- predict(bagging, dados_test)
confusionMatrix(pred_bagg, dados_test$type)
Confusion Matrix and Statistics

          Reference
Prediction  1  2  3
         1 32  6  6
         2  0 29  0
         3  3  0 29

Overall Statistics
                                          
               Accuracy : 0.8571          
                 95% CI : (0.7753, 0.9178)
    No Information Rate : 0.3333          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7857          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: 1 Class: 2 Class: 3
Sensitivity            0.9143   0.8286   0.8286
Specificity            0.8286   1.0000   0.9571
Pos Pred Value         0.7273   1.0000   0.9063
Neg Pred Value         0.9508   0.9211   0.9178
Prevalence             0.3333   0.3333   0.3333
Detection Rate         0.3048   0.2762   0.2762
Detection Prevalence   0.4190   0.2762   0.3048
Balanced Accuracy      0.8714   0.9143   0.8929

Random Forest

rf<- randomForest(type ~area+perimeter+compactness+length
                  +width+asymmetry+length_grove,
                  data=dados_train, mtry = 2)
rf

Call:
 randomForest(formula = type ~ area + perimeter + compactness +      length + width + asymmetry + length_grove, data = dados_train,      mtry = 2) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 6.67%
Confusion matrix:
   1  2  3 class.error
1 31  2  2  0.11428571
2  2 33  0  0.05714286
3  1  0 34  0.02857143

Avaliação do modelo (Predição)

pred_rf <- predict(rf, dados_test)
confusionMatrix(pred_rf, dados_test$type)
Confusion Matrix and Statistics

          Reference
Prediction  1  2  3
         1 31  7  7
         2  0 28  0
         3  4  0 28

Overall Statistics
                                          
               Accuracy : 0.8286          
                 95% CI : (0.7427, 0.8951)
    No Information Rate : 0.3333          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7429          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: 1 Class: 2 Class: 3
Sensitivity            0.8857   0.8000   0.8000
Specificity            0.8000   1.0000   0.9429
Pos Pred Value         0.6889   1.0000   0.8750
Neg Pred Value         0.9333   0.9091   0.9041
Prevalence             0.3333   0.3333   0.3333
Detection Rate         0.2952   0.2667   0.2667
Detection Prevalence   0.4286   0.2667   0.3048
Balanced Accuracy      0.8429   0.9000   0.8714

Importância (Baseado nas amostras out-of_bag)

i_mod_rf <-importance(rf)
i_mod_rf
             MeanDecreaseGini
area                16.718417
perimeter           13.211895
compactness          7.373120
length               5.775742
width               10.845447
asymmetry            3.405758
length_grove        11.996439
varImpPlot (rf)

Apontamentos

  1. Foram calculados TEA, Acurácia, Sensibilidade e Especificidade para todos os modelos ajustados.
  2. O pior modelo de classificação foi considerado o randomForest, que apresentou Acurácia de 0,8286 e Kappa de 0,7429.
  3. Análise Linear Discriminante e Árvore de Decisão com poda podem ser considerados os melhores modelos, pois apresentam indicadores de acurácia mais robustos.
  4. As variáveis mais importantes para realizar a classificação quanto à espécie são: area, perimeter e lenght_grove.

Referências

Kuhn, M. (2008). Building Predictive Models in R Using the caret Package. Journal of Statistical Software, 28(5), 1–26. https://doi.org/10.18637/jss.v028.i05

Kuhn, M. (2019). The caret Package. Disponível em: https://topepo.github.io/caret/


  1. Veja o capítulo 4 do livro “An Introduction to Statistical Learning: with Applications in R”↩︎

  2. Dataset criado por Magorzata Charytanowicz, Jerzy Niewczas, Piotr Kulczycki, Piotr Kowalski e Szymon Lukasik, pesquisadores do Instituto de Agrofísica da Academia Polonesa de Ciências em Lublin, disponível sob a licença Creative Commons Attribution 4.0 International (CC BY 4.0)↩︎