Classificacao de Imagens RGB com SVM (Dataset Completo)

CEAO-802 - Metodos de Analise de Dados

Authors

1T Generoso

1T João Marcos

1T Vitor Cesa

1 Introducao

Este trabalho aplica tecnicas de classificacao supervisionada ao dataset RGB disponibilizado na disciplina CEAO-802 - Metodos de Analise de Dados.

O documento de datasets apresenta um problema de classificacao de cenas urbanas com imagens RGB e TIR coletadas por drone sobre Guaratingueta (SP), com 13 classes de cobertura urbana. Nesta analise sera utilizada apenas a parte RGB do dataset, armazenada localmente em D:/Vitor/Documents/CEAO/802/HAB/DADOS CEAO 2026/RGB.

Diferenca em relacao ao trabalho original: esta versao utiliza TODAS as imagens disponiveis por classe (sem limite de 50), para avaliar o impacto do tamanho do dataset no desempenho dos modelos.

A abordagem adotada segue a sugestao do material da disciplina: em vez de extrair atributos estatisticos simples (media, desvio, histogramas), serao utilizadas redes neurais convolucionais pre-treinadas como extratores de atributos (transfer learning). A rede MobileNetV2, pre-treinada no ImageNet, e carregada sem a camada de classificacao final. Cada imagem e redimensionada para 224x224 pixels e processada pela rede, que produz um vetor de 1280 atributos representando caracteristicas visuais de alto nivel aprendidas em milhoes de imagens. Esses vetores sao entao usados como entrada para os classificadores SVM e Random Forest.

Neste relatorio, serao avaliados:

  1. SVM com kernel linear;
  2. SVM com kernel radial;
  3. SVM radial com ajuste simples de hiperparametros;
  4. PCA + SVM radial;
  5. Random Forest, como modelo de comparacao.

2 Preparacao do ambiente

# ============================================================
# 1. Instalacao e carregamento dos pacotes
# ============================================================

pacotes <- c(
  "tidyverse",
  "keras3",
  "caret",
  "e1071",
  "randomForest",
  "knitr"
)

pacotes_faltando <- setdiff(pacotes, rownames(installed.packages()))

if (length(pacotes_faltando) > 0) {
  install.packages(pacotes_faltando)
}

library(tidyverse)
library(keras3)
library(caret)
library(e1071)
library(randomForest)
library(knitr)

backend_ativo <- tryCatch(config_backend(), error = function(e) NA_character_)
cat("Backend keras3:", backend_ativo, "\n")
Backend keras3: tensorflow 

3 Caminho do dataset

# ============================================================
# 2. Caminho do dataset RGB
# ============================================================

caminho_rgb <- "D:/Vitor/Documents/CEAO/802/HAB/DADOS CEAO 2026/RGB"

if (!dir.exists(caminho_rgb)) {
  stop("A pasta do dataset RGB nao foi encontrada. Verifique o caminho informado em caminho_rgb.")
}

caminho_rgb
[1] "D:/Vitor/Documents/CEAO/802/HAB/DADOS CEAO 2026/RGB"

4 Leitura dos arquivos de imagem

# ============================================================
# 3. Listar imagens
# ============================================================

extensoes_imagem <- "\\.(jpg|jpeg|png|bmp|tif|tiff)$"

arquivos <- list.files(
  path = caminho_rgb,
  pattern = extensoes_imagem,
  recursive = TRUE,
  full.names = TRUE,
  ignore.case = TRUE
)

if (length(arquivos) == 0) {
  stop("Nenhuma imagem foi encontrada na pasta RGB.")
}

length(arquivos)
[1] 1248
# ============================================================
# 4. Criar metadados
# ============================================================

obter_classe <- function(arquivo, raiz) {
  arquivo_norm <- normalizePath(arquivo, winslash = "/", mustWork = FALSE)
  raiz_norm <- normalizePath(raiz, winslash = "/", mustWork = FALSE)

  nome_sem_extensao <- tools::file_path_sans_ext(basename(arquivo))
  classe_inferida <- stringr::str_extract(nome_sem_extensao, "^C\\d+")

  if (!is.na(classe_inferida)) {
    return(classe_inferida)
  }

  caminho_relativo <- stringr::str_remove(
    arquivo_norm,
    paste0("^", stringr::fixed(raiz_norm), "/?")
  )

  partes <- strsplit(caminho_relativo, "/", fixed = TRUE)[[1]]

  if (length(partes) >= 2) {
    return(partes[1])
  }

  stringr::str_extract(nome_sem_extensao, "^[^_-]+")
}

metadados_completo <- tibble(
  arquivo = arquivos,
  classe = map_chr(arquivos, obter_classe, raiz = caminho_rgb)
) %>%
  mutate(
    classe = factor(classe, levels = paste0("C", 1:13)),
    tamanho_mb = file.info(arquivo)$size / 1024^2
  ) %>%
  arrange(classe, arquivo)

resumo_dataset <- metadados_completo %>%
  group_by(classe) %>%
  summarise(
    n_imagens = n(),
    tamanho_total_mb = sum(tamanho_mb, na.rm = TRUE),
    tamanho_medio_mb = mean(tamanho_mb, na.rm = TRUE),
    .groups = "drop"
  )

resumo_dataset %>%
  mutate(across(where(is.numeric), ~ round(.x, 2))) %>%
  kable()
classe n_imagens tamanho_total_mb tamanho_medio_mb
C1 96 3388.31 35.29
C2 96 3388.31 35.29
C3 96 3388.31 35.29
C4 96 3388.31 35.29
C5 96 3388.31 35.29
C6 96 3388.31 35.29
C7 96 3388.31 35.29
C8 96 3388.31 35.29
C9 96 3388.31 35.29
C10 96 3388.31 35.29
C11 96 3388.31 35.29
C12 96 3388.31 35.29
C13 96 3388.31 35.29

5 Amostra completa (sem limite por classe)

# ============================================================
# 5. Usar TODAS as imagens disponíveis por classe
# ============================================================

set.seed(123)

# Alteracao em relacao ao original: max_imagens_por_classe = 9999
# para usar todas as imagens disponiveis
max_imagens_por_classe <- 9999

metadados <- metadados_completo %>%
  group_by(classe) %>%
  group_modify(~ {
    qtd <- min(max_imagens_por_classe, nrow(.x))
    slice_sample(.x, n = qtd)
  }) %>%
  ungroup() %>%
  mutate(classe = factor(classe, levels = paste0("C", 1:13)))

cat("Total de imagens no dataset completo:", nrow(metadados_completo), "\n")
Total de imagens no dataset completo: 1248 
cat("Total de imagens na amostra:", nrow(metadados), "\n")
Total de imagens na amostra: 1248 
metadados %>%
  count(classe, name = "n_imagens_amostra") %>%
  arrange(classe) %>%
  kable()
classe n_imagens_amostra
C1 96
C2 96
C3 96
C4 96
C5 96
C6 96
C7 96
C8 96
C9 96
C10 96
C11 96
C12 96
C13 96
ggplot(metadados, aes(x = classe)) +
  geom_bar(fill = "#2C7FB8") +
  coord_flip() +
  labs(
    title = "Distribuicao de imagens por classe (dataset completo)",
    x = "Classe",
    y = "Numero de imagens"
  ) +
  theme_minimal()

6 Modelo pre-treinado (Transfer Learning)

# ============================================================
# 6. Carregar modelo pre-treinado MobileNetV2
# ============================================================

dir.create("outputs", showWarnings = FALSE)

modelo_base <- application_mobilenet_v2(
  include_top = FALSE,
  weights    = "imagenet",
  pooling    = "avg",
  input_shape = c(224L, 224L, 3L)
)

cat("Modelo carregado:", modelo_base$name, "\n")
Modelo carregado: mobilenetv2_1.00_224 
cat("Dimensao do vetor de features:", modelo_base$output_shape[[2]], "\n")
Dimensao do vetor de features: 1280 

7 Extracao de atributos por Transfer Learning

# ============================================================
# 7. Extracao de features (com cache separado do original)
# ============================================================

pasta_saida <- "outputs/completo"
dir.create(pasta_saida, recursive = TRUE, showWarnings = FALSE)

# Nomes diferentes para nao sobrescrever o cache do trabalho original
arquivo_cache_features <- file.path(pasta_saida, "features_rgb_mobilenetv2_completo.rds")
arquivo_cache_parcial  <- file.path(pasta_saida, "features_rgb_mobilenetv2_completo_temp.rds")

tamanho_imagem <- 224L
tamanho_lote   <- 16L

extrair_features_batch <- function(arquivos, modelo, tamanho = 224L, lote = 16L) {
  n <- length(arquivos)
  resultados <- matrix(NA_real_, nrow = n, ncol = as.integer(modelo$output_shape[[2]]))

  for (inicio in seq(1, n, by = lote)) {
    fim <- min(inicio + lote - 1L, n)
    bloco <- arquivos[inicio:fim]
    tam_bloco <- length(bloco)

    batch_array <- array(0, dim = c(tam_bloco, tamanho, tamanho, 3L))

    for (j in seq_len(tam_bloco)) {
      img <- image_load(bloco[j], target_size = c(tamanho, tamanho))
      batch_array[j, , , ] <- image_to_array(img)
    }

    batch_prep <- (batch_array / 127.5) - 1.0
    features   <- predict(modelo, batch_prep, verbose = 0L)
    resultados[inicio:fim, ] <- features
  }

  resultados
}

if (file.exists(arquivo_cache_features)) {
  dados <- readRDS(arquivo_cache_features)
  cat("Features carregadas do cache:", arquivo_cache_features, "\n")
} else {
  cat("Extraindo features com MobileNetV2. Isso pode levar alguns minutos...\n")

  erros_idx <- integer(0)

  if (file.exists(arquivo_cache_parcial)) {
    features_matrix <- readRDS(arquivo_cache_parcial)
    linhas_prontas  <- which(complete.cases(features_matrix))
    cat("Cache parcial encontrado:", length(linhas_prontas), "imagens ja processadas.\n")
  } else {
    n_features      <- as.integer(modelo_base$output_shape[[2]])
    features_matrix <- matrix(NA_real_, nrow = nrow(metadados), ncol = n_features)
    linhas_prontas  <- integer(0)
  }

  linhas_pendentes <- setdiff(seq_len(nrow(metadados)), linhas_prontas)

  if (length(linhas_pendentes) > 0) {
    for (inicio in seq(1, length(linhas_pendentes), by = tamanho_lote)) {
      fim   <- min(inicio + tamanho_lote - 1L, length(linhas_pendentes))
      idxs  <- linhas_pendentes[inicio:fim]
      arquivos_bloco <- metadados$arquivo[idxs]

      resultado <- tryCatch(
        extrair_features_batch(arquivos_bloco, modelo_base, tamanho_imagem, length(idxs)),
        error = function(e) {
          message("Erro no lote ", inicio, "-", fim, ": ", conditionMessage(e))
          erros_idx <<- c(erros_idx, idxs)
          NULL
        }
      )

      if (!is.null(resultado)) {
        features_matrix[idxs, ] <- resultado
      }

      cat(sprintf("  Processadas: %d / %d\r", min(fim, length(linhas_pendentes)), length(linhas_pendentes)))

      if (fim %% (tamanho_lote * 5L) == 0L) {
        saveRDS(features_matrix, arquivo_cache_parcial)
      }
    }
    cat("\n")
    saveRDS(features_matrix, arquivo_cache_parcial)
  }

  imagens_ok <- complete.cases(features_matrix)
  n_features <- ncol(features_matrix)
  nomes_feat <- paste0("feat_", stringr::str_pad(seq_len(n_features), 4, pad = "0"))
  colnames(features_matrix) <- nomes_feat

  dados <- bind_cols(
    metadados[imagens_ok, ],
    as_tibble(features_matrix[imagens_ok, ])
  )

  if (length(erros_idx) > 0) {
    erros_df <- metadados[erros_idx, ] %>% mutate(erro = "falha na extracao CNN")
    arquivo_erros <- file.path(pasta_saida, "erros_processamento_rgb_completo.csv")
    write_csv(erros_df, arquivo_erros)
    cat("Imagens com erro:", length(erros_idx), "(ver", arquivo_erros, ")\n")
  }

  saveRDS(dados, arquivo_cache_features)
  if (file.exists(arquivo_cache_parcial)) file.remove(arquivo_cache_parcial)

  cat("Features salvas em:", arquivo_cache_features, "\n")
}
Features carregadas do cache: outputs/completo/features_rgb_mobilenetv2_completo.rds 
cat("Imagens na base final:", nrow(dados), "\n")
Imagens na base final: 1248 
cat("Numero de atributos extraidos (MobileNetV2):", ncol(dados) - 3L, "\n")
Numero de atributos extraidos (MobileNetV2): 1280 

8 Separacao entre treino e teste

# ============================================================
# 8. Separacao treino/teste
# ============================================================

set.seed(123)

idx_treino <- createDataPartition(
  y = dados$classe,
  p = 0.70,
  list = FALSE
)

treino <- dados[idx_treino, ]
teste <- dados[-idx_treino, ]

cat("Imagens no treino:", nrow(treino), "\n")
Imagens no treino: 884 
cat("Imagens no teste:", nrow(teste), "\n")
Imagens no teste: 364 
treino %>%
  count(classe, name = "treino") %>%
  full_join(
    teste %>% count(classe, name = "teste"),
    by = "classe"
  ) %>%
  arrange(classe) %>%
  kable()
classe treino teste
C1 68 28
C2 68 28
C3 68 28
C4 68 28
C5 68 28
C6 68 28
C7 68 28
C8 68 28
C9 68 28
C10 68 28
C11 68 28
C12 68 28
C13 68 28

9 Pre-processamento

# ============================================================
# 9. Preparar matrizes X e vetor y
# ============================================================

colunas_nao_preditoras <- c("arquivo", "classe", "tamanho_mb")

x_treino <- treino %>%
  select(-all_of(colunas_nao_preditoras))

x_teste <- teste %>%
  select(-all_of(colunas_nao_preditoras))

y_treino <- droplevels(treino$classe)
y_teste <- factor(teste$classe, levels = levels(y_treino))

variaveis_nzv <- nearZeroVar(x_treino)

if (length(variaveis_nzv) > 0) {
  x_treino <- x_treino[, -variaveis_nzv, drop = FALSE]
  x_teste <- x_teste[, colnames(x_treino), drop = FALSE]
}

preproc <- preProcess(
  x_treino,
  method = c("center", "scale")
)

x_treino_norm <- predict(preproc, x_treino)
x_teste_norm <- predict(preproc, x_teste)

cat("Preditores usados nos modelos:", ncol(x_treino_norm), "\n")
Preditores usados nos modelos: 1262 

10 Modelo 1: SVM linear

# ============================================================
# 10. SVM linear
# ============================================================

set.seed(123)

modelo_svm_linear <- svm(
  x = x_treino_norm,
  y = y_treino,
  kernel = "linear",
  cost = 1,
  scale = FALSE
)

pred_svm_linear <- predict(modelo_svm_linear, x_teste_norm)

cm_svm_linear <- confusionMatrix(pred_svm_linear, y_teste)

cm_svm_linear
Confusion Matrix and Statistics

          Reference
Prediction C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13
       C1  12  4  1  1  1  3  1  0  8   1   1   1   1
       C2   9 14  0  0  0  5  0  0  0   0   0   0   1
       C3   0  1 10  1  0  0  0  2  4   0   0   0   5
       C4   2  1  4 23  0  0  1  0  0   0   0   0   9
       C5   0  0  5  0 21  0  0  0  0   0   0   0   2
       C6   1  7  0  0  0 11  1  7  0   2   0   4   1
       C7   0  0  2  1  0  1 11  7  0   0   0   8   2
       C8   0  0  0  0  1  4  5  7  0   1   1   5   0
       C9   2  1  3  0  0  1  0  1 13   0   0   3   1
       C10  1  0  0  0  0  0  0  0  2   1  22   0   0
       C11  1  0  1  1  0  1  0  0  1  22   3   0   0
       C12  0  0  0  0  0  0  7  3  0   0   0   5   1
       C13  0  0  2  1  5  2  2  1  0   1   1   2   5

Overall Statistics
                                          
               Accuracy : 0.3736          
                 95% CI : (0.3238, 0.4256)
    No Information Rate : 0.0769          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.3214          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: C1 Class: C2 Class: C3 Class: C4 Class: C5
Sensitivity            0.42857   0.50000   0.35714   0.82143   0.75000
Specificity            0.93155   0.95536   0.96131   0.94940   0.97917
Pos Pred Value         0.34286   0.48276   0.43478   0.57500   0.75000
Neg Pred Value         0.95137   0.95821   0.94721   0.98457   0.97917
Prevalence             0.07692   0.07692   0.07692   0.07692   0.07692
Detection Rate         0.03297   0.03846   0.02747   0.06319   0.05769
Detection Prevalence   0.09615   0.07967   0.06319   0.10989   0.07692
Balanced Accuracy      0.68006   0.72768   0.65923   0.88542   0.86458
                     Class: C6 Class: C7 Class: C8 Class: C9 Class: C10
Sensitivity            0.39286   0.39286   0.25000   0.46429   0.035714
Specificity            0.93155   0.93750   0.94940   0.96429   0.925595
Pos Pred Value         0.32353   0.34375   0.29167   0.52000   0.038462
Neg Pred Value         0.94848   0.94880   0.93824   0.95575   0.920118
Prevalence             0.07692   0.07692   0.07692   0.07692   0.076923
Detection Rate         0.03022   0.03022   0.01923   0.03571   0.002747
Detection Prevalence   0.09341   0.08791   0.06593   0.06868   0.071429
Balanced Accuracy      0.66220   0.66518   0.59970   0.71429   0.480655
                     Class: C11 Class: C12 Class: C13
Sensitivity            0.107143    0.17857    0.17857
Specificity            0.919643    0.96726    0.94940
Pos Pred Value         0.100000    0.31250    0.22727
Neg Pred Value         0.925150    0.93391    0.93275
Prevalence             0.076923    0.07692    0.07692
Detection Rate         0.008242    0.01374    0.01374
Detection Prevalence   0.082418    0.04396    0.06044
Balanced Accuracy      0.513393    0.57292    0.56399

11 Modelo 2: SVM radial

# ============================================================
# 11. SVM radial com parametros fixos
# ============================================================

set.seed(123)

modelo_svm_radial <- svm(
  x = x_treino_norm,
  y = y_treino,
  kernel = "radial",
  cost = 10,
  gamma = 0.01,
  scale = FALSE
)

pred_svm_radial <- predict(modelo_svm_radial, x_teste_norm)

cm_svm_radial <- confusionMatrix(pred_svm_radial, y_teste)

cm_svm_radial
Confusion Matrix and Statistics

          Reference
Prediction C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13
       C1   4  4  0  0  0  2  0  0  5   2   2   0   0
       C2   7  2  0  0  0  5  0  0  0   0   0   0   1
       C3   0  0  2  1  0  0  0  2  4   0   0   0   2
       C4   1  0  4  5  0  0  1  0  0   0   0   0   3
       C5   0  0  3  0  8  0  0  0  0   0   0   0   2
       C6   1  6  0  0  0  8  0  6  0   2   0   1   1
       C7   0  0  0  0  0  1  0  4  0   0   0   6   1
       C8   0  0  0  0  0  1  6  2  0   1   1   6   0
       C9   0  0  3  0  0  0  2  3  4   0   0   2   0
       C10  1  0  0  0  0  0  0  0  0   1  22   0   0
       C11  1  0  1  0  0  1  0  0  1  19   0   0   0
       C12  0  0  0  0  0  1  6  3  0   0   0   1   1
       C13 13 16 15 22 20  9 13  8 14   3   3  12  17

Overall Statistics
                                          
               Accuracy : 0.1484          
                 95% CI : (0.1135, 0.1891)
    No Information Rate : 0.0769          
    P-Value [Acc > NIR] : 3.007e-06       
                                          
                  Kappa : 0.0774          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: C1 Class: C2 Class: C3 Class: C4 Class: C5
Sensitivity            0.14286  0.071429  0.071429   0.17857   0.28571
Specificity            0.95536  0.961310  0.973214   0.97321   0.98512
Pos Pred Value         0.21053  0.133333  0.181818   0.35714   0.61538
Neg Pred Value         0.93043  0.925501  0.926346   0.93429   0.94302
Prevalence             0.07692  0.076923  0.076923   0.07692   0.07692
Detection Rate         0.01099  0.005495  0.005495   0.01374   0.02198
Detection Prevalence   0.05220  0.041209  0.030220   0.03846   0.03571
Balanced Accuracy      0.54911  0.516369  0.522321   0.57589   0.63542
                     Class: C6 Class: C7 Class: C8 Class: C9 Class: C10
Sensitivity            0.28571   0.00000  0.071429   0.14286   0.035714
Specificity            0.94940   0.96429  0.955357   0.97024   0.931548
Pos Pred Value         0.32000   0.00000  0.117647   0.28571   0.041667
Neg Pred Value         0.94100   0.92045  0.925072   0.93143   0.920588
Prevalence             0.07692   0.07692  0.076923   0.07692   0.076923
Detection Rate         0.02198   0.00000  0.005495   0.01099   0.002747
Detection Prevalence   0.06868   0.03297  0.046703   0.03846   0.065934
Balanced Accuracy      0.61756   0.48214  0.513393   0.55655   0.483631
                     Class: C11 Class: C12 Class: C13
Sensitivity             0.00000   0.035714    0.60714
Specificity             0.93155   0.967262    0.55952
Pos Pred Value          0.00000   0.083333    0.10303
Neg Pred Value          0.91789   0.923295    0.94472
Prevalence              0.07692   0.076923    0.07692
Detection Rate          0.00000   0.002747    0.04670
Detection Prevalence    0.06319   0.032967    0.45330
Balanced Accuracy       0.46577   0.501488    0.58333

12 Modelo 3: Ajuste de hiperparametros do SVM radial

# ============================================================
# 12. Ajuste simples de hiperparametros do SVM radial
# ============================================================

set.seed(123)

k_cv <- min(5, as.integer(min(table(y_treino))))

if (k_cv >= 2) {
  ajuste_svm_radial <- tune.svm(
    x = x_treino_norm,
    y = y_treino,
    kernel = "radial",
    cost = c(0.1, 1, 10, 100),
    gamma = c(0.001, 0.01, 0.05, 0.1),
    tunecontrol = tune.control(cross = k_cv)
  )

  modelo_svm_radial_ajustado <- ajuste_svm_radial$best.model

  pred_svm_radial_ajustado <- predict(
    modelo_svm_radial_ajustado,
    x_teste_norm
  )

  cm_svm_radial_ajustado <- confusionMatrix(
    pred_svm_radial_ajustado,
    y_teste
  )

  ajuste_svm_radial$best.parameters %>%
    kable()
} else {
  warning("Poucas amostras por classe para validacao cruzada.")
  ajuste_svm_radial <- NULL
  modelo_svm_radial_ajustado <- modelo_svm_radial
  pred_svm_radial_ajustado <- pred_svm_radial
  cm_svm_radial_ajustado <- cm_svm_radial
}
gamma cost
9 0.001 10
cm_svm_radial_ajustado
Confusion Matrix and Statistics

          Reference
Prediction C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13
       C1  12  4  0  2  0  3  0  0  8   1   1   1   1
       C2   9 14  0  0  0  5  0  0  0   0   0   0   1
       C3   0  1 10  1  0  0  0  2  4   0   0   0   3
       C4   2  1  5 20  0  0  2  0  0   0   0   0   8
       C5   0  0  5  0 18  0  0  0  0   0   0   0   1
       C6   1  7  0  0  0 10  0  8  0   1   0   4   2
       C7   0  0  1  2  0  1  9  5  0   0   0   8   2
       C8   0  0  0  0  1  4  7  8  0   1   1   5   1
       C9   1  1  3  0  0  2  0  1 15   0   0   2   0
       C10  1  0  0  0  0  0  0  0  0   1  22   0   0
       C11  2  0  1  1  0  1  0  0  1  23   3   0   0
       C12  0  0  1  1  0  0  7  3  0   0   0   5   1
       C13  0  0  2  1  9  2  3  1  0   1   1   3   8

Overall Statistics
                                          
               Accuracy : 0.3654          
                 95% CI : (0.3158, 0.4172)
    No Information Rate : 0.0769          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.3125          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: C1 Class: C2 Class: C3 Class: C4 Class: C5
Sensitivity            0.42857   0.50000   0.35714   0.71429   0.64286
Specificity            0.93750   0.95536   0.96726   0.94643   0.98214
Pos Pred Value         0.36364   0.48276   0.47619   0.52632   0.75000
Neg Pred Value         0.95166   0.95821   0.94752   0.97546   0.97059
Prevalence             0.07692   0.07692   0.07692   0.07692   0.07692
Detection Rate         0.03297   0.03846   0.02747   0.05495   0.04945
Detection Prevalence   0.09066   0.07967   0.05769   0.10440   0.06593
Balanced Accuracy      0.68304   0.72768   0.66220   0.83036   0.81250
                     Class: C6 Class: C7 Class: C8 Class: C9 Class: C10
Sensitivity            0.35714   0.32143   0.28571   0.53571   0.035714
Specificity            0.93155   0.94345   0.94048   0.97024   0.931548
Pos Pred Value         0.30303   0.32143   0.28571   0.60000   0.041667
Neg Pred Value         0.94562   0.94345   0.94048   0.96165   0.920588
Prevalence             0.07692   0.07692   0.07692   0.07692   0.076923
Detection Rate         0.02747   0.02473   0.02198   0.04121   0.002747
Detection Prevalence   0.09066   0.07692   0.07692   0.06868   0.065934
Balanced Accuracy      0.64435   0.63244   0.61310   0.75298   0.483631
                     Class: C11 Class: C12 Class: C13
Sensitivity            0.107143    0.17857    0.28571
Specificity            0.913690    0.96131    0.93155
Pos Pred Value         0.093750    0.27778    0.25806
Neg Pred Value         0.924699    0.93353    0.93994
Prevalence             0.076923    0.07692    0.07692
Detection Rate         0.008242    0.01374    0.02198
Detection Prevalence   0.087912    0.04945    0.08516
Balanced Accuracy      0.510417    0.56994    0.60863

13 Modelo 4: PCA + SVM radial

# ============================================================
# 13. PCA
# ============================================================

pca <- prcomp(
  x_treino_norm,
  center = FALSE,
  scale. = FALSE
)

variancia <- pca$sdev^2
variancia_exp <- variancia / sum(variancia)
variancia_acum <- cumsum(variancia_exp)

n_comp_95 <- which(variancia_acum >= 0.95)[1]
n_comp <- min(n_comp_95, 30, ncol(x_treino_norm))

cat("Componentes necessarios para 95% da variancia:", n_comp_95, "\n")
Componentes necessarios para 95% da variancia: 364 
cat("Componentes usados no modelo:", n_comp, "\n")
Componentes usados no modelo: 30 
tibble(
  componente = seq_along(variancia_acum),
  variancia_acumulada = variancia_acum
) %>%
  ggplot(aes(x = componente, y = variancia_acumulada)) +
  geom_line(color = "#2C7FB8") +
  geom_point(color = "#2C7FB8") +
  geom_hline(yintercept = 0.95, linetype = "dashed") +
  labs(
    title = "Variancia acumulada pelo PCA (dataset completo)",
    x = "Numero de componentes principais",
    y = "Variancia acumulada"
  ) +
  theme_minimal()

x_treino_pca <- as.data.frame(pca$x[, 1:n_comp, drop = FALSE])

x_teste_pca <- as.data.frame(
  predict(pca, newdata = x_teste_norm)[, 1:n_comp, drop = FALSE]
)

set.seed(123)

modelo_svm_pca <- svm(
  x = x_treino_pca,
  y = y_treino,
  kernel = "radial",
  cost = 10,
  gamma = 0.01,
  scale = FALSE
)

pred_svm_pca <- predict(modelo_svm_pca, x_teste_pca)

cm_svm_pca <- confusionMatrix(pred_svm_pca, y_teste)

cm_svm_pca
Confusion Matrix and Statistics

          Reference
Prediction C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13
       C1   9  4  0  2  0  5  1  0  6   1   1   0   2
       C2  10 10  0  0  0  5  0  0  0   0   0   0   2
       C3   0  0  9  1  0  0  0  3  4   0   0   0   2
       C4   2  1  5 15  0  0  1  0  0   0   0   1   6
       C5   0  1  5  0 14  0  0  0  0   0   0   0   1
       C6   1  6  0  0  0  9  1  7  0   1   0   1   0
       C7   1  0  0  4  0  1  9  5  0   0   0   8   3
       C8   0  0  0  0  0  1  6  5  0   1   0   5   1
       C9   1  1  3  0  0  1  1  3 13   0   0   3   0
       C10  1  0  0  0  0  0  0  0  1   3  24   0   0
       C11  1  1  1  1  1  3  0  0  1  20   1   0   0
       C12  1  0  0  1  0  1  6  4  0   0   0   7   2
       C13  1  4  5  4 13  2  3  1  3   2   2   3   9

Overall Statistics
                                          
               Accuracy : 0.3104          
                 95% CI : (0.2632, 0.3607)
    No Information Rate : 0.0769          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.253           
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: C1 Class: C2 Class: C3 Class: C4 Class: C5
Sensitivity            0.32143   0.35714   0.32143   0.53571   0.50000
Specificity            0.93452   0.94940   0.97024   0.95238   0.97917
Pos Pred Value         0.29032   0.37037   0.47368   0.48387   0.66667
Neg Pred Value         0.94294   0.94659   0.94493   0.96096   0.95918
Prevalence             0.07692   0.07692   0.07692   0.07692   0.07692
Detection Rate         0.02473   0.02747   0.02473   0.04121   0.03846
Detection Prevalence   0.08516   0.07418   0.05220   0.08516   0.05769
Balanced Accuracy      0.62798   0.65327   0.64583   0.74405   0.73958
                     Class: C6 Class: C7 Class: C8 Class: C9 Class: C10
Sensitivity            0.32143   0.32143   0.17857   0.46429   0.107143
Specificity            0.94940   0.93452   0.95833   0.96131   0.922619
Pos Pred Value         0.34615   0.29032   0.26316   0.50000   0.103448
Neg Pred Value         0.94379   0.94294   0.93333   0.95562   0.925373
Prevalence             0.07692   0.07692   0.07692   0.07692   0.076923
Detection Rate         0.02473   0.02473   0.01374   0.03571   0.008242
Detection Prevalence   0.07143   0.08516   0.05220   0.07143   0.079670
Balanced Accuracy      0.63542   0.62798   0.56845   0.71280   0.514881
                     Class: C11 Class: C12 Class: C13
Sensitivity            0.035714    0.25000    0.32143
Specificity            0.913690    0.95536    0.87202
Pos Pred Value         0.033333    0.31818    0.17308
Neg Pred Value         0.919162    0.93860    0.93910
Prevalence             0.076923    0.07692    0.07692
Detection Rate         0.002747    0.01923    0.02473
Detection Prevalence   0.082418    0.06044    0.14286
Balanced Accuracy      0.474702    0.60268    0.59673

14 Modelo 5: Random Forest

# ============================================================
# 14. Random Forest
# ============================================================

set.seed(123)

modelo_rf <- randomForest(
  x = x_treino_norm,
  y = y_treino,
  ntree = 500,
  importance = TRUE
)

pred_rf <- predict(modelo_rf, x_teste_norm)

cm_rf <- confusionMatrix(pred_rf, y_teste)

cm_rf
Confusion Matrix and Statistics

          Reference
Prediction C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13
       C1   9  4  1  3  0  3  0  0  8   2   2   0   3
       C2   9 10  0  0  0  5  0  0  0   0   0   0   1
       C3   0  1 10  1  0  0  0  2  4   0   0   0   2
       C4   5  2  5 18  0  0  2  0  0   0   0   0   6
       C5   0  0  5  0 22  0  0  0  0   0   0   0   4
       C6   1  6  0  0  0 11  0  9  0   1   0   4   2
       C7   0  0  1  3  0  1  7  4  0   0   0   7   3
       C8   0  1  0  0  0  1  8  7  1   1   0   4   1
       C9   1  3  1  0  0  0  0  0 13   0   0   2   0
       C10  1  0  2  1  0  1  0  1  0   3  24   0   0
       C11  2  1  1  0  1  2  0  0  2  20   1   0   0
       C12  0  0  0  1  1  2  8  4  0   0   0   8   1
       C13  0  0  2  1  4  2  3  1  0   1   1   3   5

Overall Statistics
                                          
               Accuracy : 0.3407          
                 95% CI : (0.2921, 0.3919)
    No Information Rate : 0.0769          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.2857          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: C1 Class: C2 Class: C3 Class: C4 Class: C5
Sensitivity            0.32143   0.35714   0.35714   0.64286   0.78571
Specificity            0.92262   0.95536   0.97024   0.94048   0.97321
Pos Pred Value         0.25714   0.40000   0.50000   0.47368   0.70968
Neg Pred Value         0.94225   0.94690   0.94767   0.96933   0.98198
Prevalence             0.07692   0.07692   0.07692   0.07692   0.07692
Detection Rate         0.02473   0.02747   0.02747   0.04945   0.06044
Detection Prevalence   0.09615   0.06868   0.05495   0.10440   0.08516
Balanced Accuracy      0.62202   0.65625   0.66369   0.79167   0.87946
                     Class: C6 Class: C7 Class: C8 Class: C9 Class: C10
Sensitivity            0.39286   0.25000   0.25000   0.46429   0.107143
Specificity            0.93155   0.94345   0.94940   0.97917   0.910714
Pos Pred Value         0.32353   0.26923   0.29167   0.65000   0.090909
Neg Pred Value         0.94848   0.93787   0.93824   0.95640   0.924471
Prevalence             0.07692   0.07692   0.07692   0.07692   0.076923
Detection Rate         0.03022   0.01923   0.01923   0.03571   0.008242
Detection Prevalence   0.09341   0.07143   0.06593   0.05495   0.090659
Balanced Accuracy      0.66220   0.59673   0.59970   0.72173   0.508929
                     Class: C11 Class: C12 Class: C13
Sensitivity            0.035714    0.28571    0.17857
Specificity            0.913690    0.94940    0.94643
Pos Pred Value         0.033333    0.32000    0.21739
Neg Pred Value         0.919162    0.94100    0.93255
Prevalence             0.076923    0.07692    0.07692
Detection Rate         0.002747    0.02198    0.01374
Detection Prevalence   0.082418    0.06868    0.06319
Balanced Accuracy      0.474702    0.61756    0.56250

15 Comparacao dos modelos

# ============================================================
# 15. Funcao de avaliacao dos modelos
# ============================================================

avaliar_modelo <- function(nome, matriz_confusao) {
  overall <- matriz_confusao$overall
  by_class <- matriz_confusao$byClass

  if (is.matrix(by_class)) {
    sensibilidade_macro <- mean(by_class[, "Sensitivity"], na.rm = TRUE)
    especificidade_macro <- mean(by_class[, "Specificity"], na.rm = TRUE)
    f1_macro <- mean(by_class[, "F1"], na.rm = TRUE)
  } else {
    sensibilidade_macro <- by_class["Sensitivity"]
    especificidade_macro <- by_class["Specificity"]
    f1_macro <- by_class["F1"]
  }

  tibble(
    modelo = nome,
    acuracia = as.numeric(overall["Accuracy"]),
    kappa = as.numeric(overall["Kappa"]),
    sensibilidade_macro = as.numeric(sensibilidade_macro),
    especificidade_macro = as.numeric(especificidade_macro),
    f1_macro = as.numeric(f1_macro)
  )
}

resultados <- bind_rows(
  avaliar_modelo("SVM linear", cm_svm_linear),
  avaliar_modelo("SVM radial", cm_svm_radial),
  avaliar_modelo("SVM radial ajustado", cm_svm_radial_ajustado),
  avaliar_modelo("PCA + SVM radial", cm_svm_pca),
  avaliar_modelo("Random Forest", cm_rf)
) %>%
  arrange(desc(acuracia))

resultados %>%
  mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
  kable(caption = "Resultados - Dataset Completo (Holdout 70/30)")
Resultados - Dataset Completo (Holdout 70/30)
modelo acuracia kappa sensibilidade_macro especificidade_macro f1_macro
SVM linear 0.3736 0.3214 0.3736 0.9478 0.3646
SVM radial ajustado 0.3654 0.3125 0.3654 0.9471 0.3630
Random Forest 0.3407 0.2857 0.3407 0.9451 0.3405
PCA + SVM radial 0.3104 0.2530 0.3104 0.9425 0.3161
SVM radial 0.1484 0.0774 0.1484 0.9290 0.1673
ggplot(resultados, aes(x = reorder(modelo, acuracia), y = acuracia)) +
  geom_col(fill = "#2C7FB8") +
  coord_flip() +
  labs(
    title = "Comparacao da acuracia dos modelos (dataset completo)",
    x = "Modelo",
    y = "Acuracia"
  ) +
  theme_minimal()

16 Importancia das variaveis

# ============================================================
# 16. Importancia das variaveis
# ============================================================

importancia <- importance(modelo_rf)

coluna_importancia <- if ("MeanDecreaseGini" %in% colnames(importancia)) {
  "MeanDecreaseGini"
} else {
  colnames(importancia)[1]
}

importancia_df <- tibble(
  variavel = rownames(importancia),
  importancia = importancia[, coluna_importancia]
) %>%
  arrange(desc(importancia))

importancia_df %>%
  slice_head(n = 20) %>%
  kable()
variavel importancia
feat_0472 2.569848
feat_1003 1.884104
feat_0321 1.852070
feat_0850 1.828746
feat_1091 1.736351
feat_0476 1.654010
feat_0689 1.572648
feat_0914 1.548601
feat_0562 1.521096
feat_0260 1.492961
feat_0971 1.416875
feat_1262 1.345804
feat_0778 1.340887
feat_0473 1.325315
feat_0065 1.320592
feat_0231 1.298018
feat_0780 1.296183
feat_0278 1.287194
feat_0190 1.277953
feat_0094 1.270837

17 Salvamento dos resultados

# ============================================================
# 17. Salvar saidas (nomes diferentes do original)
# ============================================================

dir.create(pasta_saida, recursive = TRUE, showWarnings = FALSE)

write_csv(dados, file.path(pasta_saida, "features_rgb_completo.csv"))
write_csv(resultados, file.path(pasta_saida, "resultados_modelos_rgb_completo.csv"))
write_csv(importancia_df, file.path(pasta_saida, "importancia_variaveis_rgb_completo.csv"))

saveRDS(
  list(
    preproc                    = preproc,
    modelo_svm_linear          = modelo_svm_linear,
    modelo_svm_radial          = modelo_svm_radial,
    ajuste_svm_radial          = ajuste_svm_radial,
    modelo_svm_radial_ajustado = modelo_svm_radial_ajustado,
    modelo_svm_pca             = modelo_svm_pca,
    modelo_rf                  = modelo_rf,
    pca                        = pca,
    resultados                 = resultados,
    extrator_cnn               = "MobileNetV2 (ImageNet, sem topo, pooling=avg, 1280 features)"
  ),
  file.path(pasta_saida, "modelos_rgb_completo.rds")
)

cat("Arquivos salvos em:", pasta_saida, "\n")
Arquivos salvos em: outputs/completo 

18 Validacao Cruzada K-Fold

# ============================================================
# 18. Validacao cruzada k-fold (k=5, estratificada)
# ============================================================

set.seed(123)

k_folds <- 5L

folds <- createFolds(dados$classe, k = k_folds, list = TRUE, returnTrain = FALSE)

grade_cost_kfold  <- c(1, 10, 100)
grade_gamma_kfold <- c(0.001, 0.01, 0.05, 0.1)

metricas_folds <- vector("list", k_folds)

for (fold_i in seq_len(k_folds)) {
  idx_teste  <- folds[[fold_i]]
  idx_treino <- setdiff(seq_len(nrow(dados)), idx_teste)

  fold_treino <- dados[idx_treino, ]
  fold_teste  <- dados[idx_teste,  ]

  x_fold_tr <- fold_treino %>% select(-all_of(colunas_nao_preditoras))
  x_fold_te <- fold_teste  %>% select(-all_of(colunas_nao_preditoras))
  y_fold_tr <- droplevels(fold_treino$classe)
  y_fold_te <- factor(fold_teste$classe, levels = levels(y_fold_tr))

  nzv_fold <- nearZeroVar(x_fold_tr)
  if (length(nzv_fold) > 0) {
    x_fold_tr <- x_fold_tr[, -nzv_fold, drop = FALSE]
    x_fold_te <- x_fold_te[, colnames(x_fold_tr), drop = FALSE]
  }

  pp_fold      <- preProcess(x_fold_tr, method = c("center", "scale"))
  x_fold_tr_n  <- predict(pp_fold, x_fold_tr)
  x_fold_te_n  <- predict(pp_fold, x_fold_te)

  m1 <- svm(x = x_fold_tr_n, y = y_fold_tr,
            kernel = "linear", cost = 1, scale = FALSE)

  m2 <- svm(x = x_fold_tr_n, y = y_fold_tr,
            kernel = "radial", cost = 10, gamma = 0.01, scale = FALSE)

  k_cv_fold <- min(5L, min(table(y_fold_tr)))
  ajuste_fold <- tune.svm(
    x = x_fold_tr_n,
    y = y_fold_tr,
    kernel = "radial",
    cost = grade_cost_kfold,
    gamma = grade_gamma_kfold,
    tunecontrol = tune.control(cross = k_cv_fold)
  )
  m3 <- ajuste_fold$best.model

  pca_fold    <- prcomp(x_fold_tr_n, center = FALSE, scale. = FALSE)
  var_acum_f  <- cumsum(pca_fold$sdev^2 / sum(pca_fold$sdev^2))
  n_comp_f    <- min(which(var_acum_f >= 0.95)[1], 30L, ncol(x_fold_tr_n))
  x_pca_tr    <- as.data.frame(pca_fold$x[, 1:n_comp_f, drop = FALSE])
  x_pca_te    <- as.data.frame(predict(pca_fold, x_fold_te_n)[, 1:n_comp_f, drop = FALSE])
  m4 <- svm(x = x_pca_tr, y = y_fold_tr,
            kernel = "radial", cost = 10, gamma = 0.01, scale = FALSE)

  m5 <- randomForest(x = x_fold_tr_n, y = y_fold_tr, ntree = 200L)

  metricas_folds[[fold_i]] <- bind_rows(
    avaliar_modelo("SVM linear",          confusionMatrix(predict(m1, x_fold_te_n), y_fold_te)),
    avaliar_modelo("SVM radial",          confusionMatrix(predict(m2, x_fold_te_n), y_fold_te)),
    avaliar_modelo("SVM radial ajustado", confusionMatrix(predict(m3, x_fold_te_n), y_fold_te)),
    avaliar_modelo("PCA + SVM radial",    confusionMatrix(predict(m4, x_pca_te),    y_fold_te)),
    avaliar_modelo("Random Forest",       confusionMatrix(predict(m5, x_fold_te_n), y_fold_te))
  ) %>% mutate(fold = fold_i)

  cat(sprintf(
    "Fold %d/%d concluido. SVM ajustado: cost = %s | gamma = %s\n",
    fold_i, k_folds,
    ajuste_fold$best.parameters$cost,
    ajuste_fold$best.parameters$gamma
  ))
}
Fold 1/5 concluido. SVM ajustado: cost = 10 | gamma = 0.001
Fold 2/5 concluido. SVM ajustado: cost = 10 | gamma = 0.001
Fold 3/5 concluido. SVM ajustado: cost = 10 | gamma = 0.001
Fold 4/5 concluido. SVM ajustado: cost = 10 | gamma = 0.001
Fold 5/5 concluido. SVM ajustado: cost = 10 | gamma = 0.001
resultados_kfold <- bind_rows(metricas_folds) %>%
  group_by(modelo) %>%
  summarise(across(c(acuracia, kappa, sensibilidade_macro, especificidade_macro, f1_macro), mean),
            .groups = "drop") %>%
  arrange(desc(acuracia))

write_csv(resultados_kfold, file.path(pasta_saida, "resultados_kfold_rgb_completo.csv"))

resultados_kfold %>%
  mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
  kable(caption = "Acuracia media - K-Fold (k=5) - Dataset Completo")
Acuracia media - K-Fold (k=5) - Dataset Completo
modelo acuracia kappa sensibilidade_macro especificidade_macro f1_macro
SVM linear 0.3630 0.3100 0.3634 0.9469 0.3835
SVM radial ajustado 0.3565 0.3029 0.3568 0.9464 0.3721
Random Forest 0.3285 0.2726 0.3288 0.9440 0.3406
PCA + SVM radial 0.3236 0.2674 0.3239 0.9436 0.3593
SVM radial 0.1419 0.0709 0.1433 0.9285 0.1743

19 Comparacao: Holdout vs K-Fold

# ============================================================
# 19. Comparacao direta entre as duas estrategias
# ============================================================

comparacao <- bind_rows(
  resultados      %>% mutate(estrategia = "Holdout 70/30"),
  resultados_kfold %>% mutate(estrategia = "K-Fold (k=5)")
) %>%
  select(estrategia, modelo, acuracia, kappa, f1_macro) %>%
  arrange(estrategia, desc(acuracia))

comparacao %>%
  mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
  kable(caption = "Comparacao entre Holdout 70/30 e K-Fold - Dataset Completo")
Comparacao entre Holdout 70/30 e K-Fold - Dataset Completo
estrategia modelo acuracia kappa f1_macro
Holdout 70/30 SVM linear 0.3736 0.3214 0.3646
Holdout 70/30 SVM radial ajustado 0.3654 0.3125 0.3630
Holdout 70/30 Random Forest 0.3407 0.2857 0.3405
Holdout 70/30 PCA + SVM radial 0.3104 0.2530 0.3161
Holdout 70/30 SVM radial 0.1484 0.0774 0.1673
K-Fold (k=5) SVM linear 0.3630 0.3100 0.3835
K-Fold (k=5) SVM radial ajustado 0.3565 0.3029 0.3721
K-Fold (k=5) Random Forest 0.3285 0.2726 0.3406
K-Fold (k=5) PCA + SVM radial 0.3236 0.2674 0.3593
K-Fold (k=5) SVM radial 0.1419 0.0709 0.1743
ggplot(comparacao, aes(x = reorder(modelo, acuracia), y = acuracia, fill = estrategia)) +
  geom_col(position = "dodge") +
  coord_flip() +
  scale_fill_manual(values = c("Holdout 70/30" = "#2C7FB8", "K-Fold (k=5)" = "#E06C00")) +
  labs(
    title = "Acuracia: Holdout 70/30 vs K-Fold (k=5) - Dataset Completo",
    x = "Modelo",
    y = "Acuracia",
    fill = "Estrategia"
  ) +
  theme_minimal()

20 Conclusao

melhor_holdout  <- max(resultados$acuracia)
melhor_kf       <- max(resultados_kfold$acuracia)

cat("Acuracia maxima - Holdout:", round(melhor_holdout, 4), "\n")
Acuracia maxima - Holdout: 0.3736 
cat("Acuracia maxima - K-Fold: ", round(melhor_kf,      4), "\n")
Acuracia maxima - K-Fold:  0.363 
cat("Melhor modelo (holdout):",   resultados$modelo[1], "\n")
Melhor modelo (holdout): SVM linear 
cat("Melhor modelo (k-fold):",    resultados_kfold$modelo[1], "\n")
Melhor modelo (k-fold): SVM linear