Move arquivos de analise, apresentacao e outputs para subpastas dedicadas; remove caches orfaos e tex nao usados. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
589 lines
18 KiB
Plaintext
589 lines
18 KiB
Plaintext
---
|
||
title: "Classificação de Imagens RGB com SVM"
|
||
subtitle: "CEAO-802 — Métodos de Análise de Dados"
|
||
author: "1T Generoso · 1T João Marcos · 1T Vitor Cesa"
|
||
date: today
|
||
lang: pt-BR
|
||
format:
|
||
revealjs:
|
||
theme: simple
|
||
embed-resources: true
|
||
slide-number: c/t
|
||
progress: true
|
||
transition: slide
|
||
transition-speed: fast
|
||
code-overflow: scroll
|
||
highlight-style: github
|
||
fig-align: center
|
||
footer: "CEAO-802 — Classificação de Imagens RGB com SVM"
|
||
width: 1280
|
||
height: 720
|
||
include-in-header:
|
||
text: |
|
||
<style>
|
||
.reveal h1, .reveal h2 { color: #2C7FB8 !important; }
|
||
.reveal h3 { color: #1a5276 !important; }
|
||
.reveal .slide-number { color: #2C7FB8; }
|
||
.reveal .progress { background: #E06C00 !important; }
|
||
.destaque { color: #E06C00; font-weight: bold; }
|
||
.azul { color: #2C7FB8; font-weight: bold; }
|
||
.reveal pre { font-size: 0.72em; line-height: 1.45; }
|
||
.reveal table { font-size: 0.78em; margin: auto; }
|
||
.reveal table th {
|
||
background: #2C7FB8; color: white; padding: 4px 10px;
|
||
}
|
||
.reveal table td { padding: 3px 10px; }
|
||
.reveal table tr:nth-child(even) td { background: #EBF5FB; }
|
||
.caixa-azul {
|
||
background: #EBF5FB;
|
||
border-left: 5px solid #2C7FB8;
|
||
padding: 0.45em 1em;
|
||
border-radius: 4px;
|
||
margin-top: 0.6em;
|
||
font-size: 0.86em;
|
||
}
|
||
.caixa-laranja {
|
||
background: #FEF9E7;
|
||
border-left: 5px solid #E06C00;
|
||
padding: 0.45em 1em;
|
||
border-radius: 4px;
|
||
margin-top: 0.6em;
|
||
font-size: 0.86em;
|
||
}
|
||
.reveal .footer { color: #aaa; font-size: 0.65em; }
|
||
|
||
/* ── Redução de tamanho base (padrão simple = ~40px) ── */
|
||
:root { --r-main-font-size: 34px; }
|
||
.reveal { font-size: 34px; }
|
||
.reveal h2 { font-size: 1.35em; margin-bottom: 0.25em; }
|
||
.reveal h3 { font-size: 1.05em; margin-bottom: 0.2em; }
|
||
.reveal ul, .reveal ol { margin: 0.25em 0 0.25em 1.2em; }
|
||
.reveal li { margin: 0.15em 0; line-height: 1.3; }
|
||
.reveal p { margin: 0.3em 0; line-height: 1.35; }
|
||
.reveal blockquote { padding: 0.3em 0.8em; margin: 0.35em 0; }
|
||
</style>
|
||
execute:
|
||
echo: false
|
||
warning: false
|
||
message: false
|
||
---
|
||
|
||
```{r}
|
||
#| label: setup
|
||
#| include: false
|
||
|
||
library(tidyverse)
|
||
library(knitr)
|
||
library(scales)
|
||
library(caret)
|
||
library(e1071)
|
||
|
||
azul <- "#2C7FB8"
|
||
laranja <- "#E06C00"
|
||
|
||
# ── Carregar resultados do cache (sem rodar CNN nem modelos) ──────────────────
|
||
resultados_holdout <- read_csv("../outputs_parcial/resultados_modelos_rgb.csv",
|
||
show_col_types = FALSE) %>%
|
||
arrange(desc(acuracia))
|
||
|
||
resultados_kfold <- read_csv("../outputs_parcial/resultados_kfold_rgb.csv",
|
||
show_col_types = FALSE) %>%
|
||
arrange(desc(acuracia))
|
||
|
||
importancia_df <- read_csv("../outputs_parcial/importancia_variaveis_rgb.csv",
|
||
show_col_types = FALSE)
|
||
|
||
cores_modelos <- c(
|
||
"SVM linear" = "#2C7FB8",
|
||
"SVM radial" = "#AED6F1",
|
||
"SVM radial ajustado" = "#5DADE2",
|
||
"PCA + SVM radial" = "#1A5276",
|
||
"Random Forest" = "#E06C00"
|
||
)
|
||
```
|
||
|
||
```{r}
|
||
#| label: confmat
|
||
#| include: false
|
||
|
||
# ── Matriz de confusão do SVM linear — carregada do cache (.rds) ─────────────
|
||
# Não re-treina o modelo: só carrega o objeto salvo e prediz no mesmo split.
|
||
modelos_salvos <- tryCatch(
|
||
readRDS("../outputs_parcial/modelos_rgb.rds"),
|
||
error = function(e) NULL
|
||
)
|
||
|
||
dados_cache <- tryCatch(
|
||
read_csv("../outputs_parcial/features_rgb.csv", show_col_types = FALSE) %>%
|
||
mutate(classe = factor(classe, levels = paste0("C", 1:13))),
|
||
error = function(e) NULL
|
||
)
|
||
|
||
cm_plot <- NULL
|
||
|
||
if (!is.null(modelos_salvos) && !is.null(dados_cache)) {
|
||
set.seed(123)
|
||
idx_tr <- createDataPartition(dados_cache$classe, p = 0.70, list = FALSE)
|
||
teste <- dados_cache[-idx_tr, ]
|
||
|
||
cols_meta <- c("arquivo", "classe", "tamanho_mb")
|
||
x_te <- teste %>% select(-all_of(cols_meta))
|
||
y_te <- factor(teste$classe, levels = levels(dados_cache$classe))
|
||
|
||
# Selecionar as colunas que o preproc conhece (após nearZeroVar)
|
||
features_usadas <- names(modelos_salvos$preproc$mean)
|
||
x_te_sub <- x_te[, features_usadas, drop = FALSE]
|
||
x_te_norm <- predict(modelos_salvos$preproc, x_te_sub)
|
||
|
||
pred <- predict(modelos_salvos$modelo_svm_linear, x_te_norm)
|
||
cm_tbl <- as.data.frame(table(Predicao = pred, Real = y_te))
|
||
|
||
cm_plot <- ggplot(cm_tbl, aes(x = Real, y = Predicao, fill = Freq)) +
|
||
geom_tile(color = "white", linewidth = 0.4) +
|
||
geom_text(aes(label = ifelse(Freq > 0, Freq, "")),
|
||
size = 3, color = "black") +
|
||
scale_fill_gradient(low = "#EBF5FB", high = "#1A5276") +
|
||
labs(x = "Classe real", y = "Classe prevista", fill = "n") +
|
||
theme_minimal(base_size = 11) +
|
||
theme(axis.text.x = element_text(angle = 45, hjust = 1),
|
||
axis.text = element_text(size = 9),
|
||
panel.grid = element_blank(),
|
||
legend.position = "right")
|
||
}
|
||
```
|
||
|
||
|
||
## Roteiro {.center}
|
||
|
||
::: {.incremental}
|
||
1. Contexto e objetivo
|
||
2. Fundamentos: Transfer Learning e SVM
|
||
3. Pipeline em R — código-chave
|
||
4. Cinco modelos avaliados
|
||
5. Comparação de resultados
|
||
6. Validação cruzada K-Fold
|
||
7. Discussão e conclusão
|
||
:::
|
||
|
||
|
||
## Contexto e Objetivo
|
||
|
||
:::: {.columns}
|
||
|
||
::: {.column width="55%"}
|
||
- Imagens RGB de cenas urbanas por **drone** — Guaratinguetá (SP)
|
||
- **13 classes** de cobertura do solo (C1–C13); dataset: RGB + TIR
|
||
- Este trabalho: apenas canal **RGB**
|
||
- Amostra: **50 imagens / classe** · **650 imagens totais** (de 1.248)
|
||
- Divisão: **70% treino** (~455) / **30% teste** (~195), estratificada
|
||
:::
|
||
|
||
::: {.column width="45%"}
|
||
**Objetivo**
|
||
|
||
> Classificação supervisionada de cenas urbanas usando Transfer Learning como extrator de atributos e SVM como classificador.
|
||
|
||
**Baseline aleatório** (13 classes):
|
||
|
||
<span class="destaque" style="font-size:1.5em">7,7%</span>
|
||
|
||
Qualquer modelo acima disso aprende algo real.
|
||
:::
|
||
|
||
::::
|
||
|
||
|
||
## As 13 Classes do Dataset
|
||
|
||
:::: {.columns}
|
||
|
||
::: {.column width="50%"}
|
||
| Classe | Cobertura |
|
||
|:------:|-----------|
|
||
| C1 | Vegetação Arbórea |
|
||
| C2 | Vegetação Rasteira |
|
||
| C3 | Solo Exposto |
|
||
| C4 | Pavimento Asfáltico |
|
||
| C5 | Solo Estabilizado |
|
||
| C6 | Telhado Cerâmico |
|
||
| C7 | Telhado de Fibrocimento |
|
||
:::
|
||
|
||
::: {.column width="50%"}
|
||
| Classe | Cobertura |
|
||
|:------:|-----------|
|
||
| C8 | Telhado Metálico |
|
||
| C9 | Placa Fotovoltaica |
|
||
| C10 | Piscina |
|
||
| C11 | Pedra Sabão |
|
||
| C12 | Caixa D'água |
|
||
| C13 | Veículos |
|
||
:::
|
||
|
||
::::
|
||
|
||
::: {.caixa-azul}
|
||
Arquivo: `C10_2023_06_M_01_RGB.tiff` → classe **C10**, junho 2023, manhã. A classe é inferida do **prefixo do nome** — sem estrutura de subpastas.
|
||
:::
|
||
|
||
|
||
## Por que Transfer Learning?
|
||
|
||
:::: {.columns}
|
||
|
||
::: {.column width="48%"}
|
||
### Abordagem tradicional
|
||
|
||
Extrair atributos estatísticos manualmente:
|
||
média, desvio-padrão, histogramas de cor
|
||
|
||
❌ Representação rasa — perde padrões espaciais e texturais complexos
|
||
:::
|
||
|
||
::: {.column width="52%"}
|
||
### Abordagem adotada
|
||
|
||
**MobileNetV2** pré-treinada no ImageNet
|
||
(>1 M imagens · 1.000 classes · Google)
|
||
|
||
- Remove a camada classificadora final
|
||
- Aplica pooling global médio
|
||
- Gera vetor de **1.280 atributos** por imagem
|
||
|
||
✅ Bordas, texturas e formas de alto nível, aprendidas em escala industrial
|
||
:::
|
||
|
||
::::
|
||
|
||
::: {.caixa-azul}
|
||
A CNN faz o trabalho de **representação**. O SVM recebe os 1.280 atributos e aprende a **fronteira de decisão** — sem re-treinar a CNN.
|
||
:::
|
||
|
||
|
||
## O que é SVM?
|
||
|
||
:::: {.columns}
|
||
|
||
::: {.column width="55%"}
|
||
**Ideia central**
|
||
|
||
Encontrar o hiperplano que **maximiza a margem** entre as classes.
|
||
|
||
**Parâmetros principais**
|
||
|
||
- `cost` — tolerância a erros; maior cost = fronteira mais rígida
|
||
- `gamma` — alcance do kernel radial; maior gamma = vizinhança menor
|
||
|
||
**Kernel trick**
|
||
|
||
Kernel radial (RBF) permite fronteiras **não-lineares** projetando os dados em espaço de alta dimensão
|
||
:::
|
||
|
||
::: {.column width="45%"}
|
||
**Dois kernels testados**
|
||
|
||
| Kernel | Fronteira | Parâmetros |
|
||
|---------|-----------|------------------|
|
||
| Linear | Hiperplano | `cost` |
|
||
| Radial | Não-linear | `cost` + `gamma` |
|
||
|
||
::: {.caixa-laranja}
|
||
SVM é sensível à escala → atributos **devem ser padronizados** antes do treino.
|
||
:::
|
||
:::
|
||
|
||
::::
|
||
|
||
|
||
## Pipeline Geral
|
||
|
||
<div style="display:flex; align-items:center; justify-content:center; gap:0; margin-top:80px">
|
||
<div style="background:#2C7FB8; color:white; border-radius:50%; width:200px; height:200px; display:flex; align-items:center; justify-content:center; text-align:center; font-weight:bold; font-size:0.85em; line-height:1.4; flex-shrink:0">650 imagens<br>RGB</div>
|
||
<span style="font-size:2.2em; color:#999; margin:0 10px; flex-shrink:0">→</span>
|
||
<div style="background:#2C7FB8; color:white; border-radius:50%; width:200px; height:200px; display:flex; align-items:center; justify-content:center; text-align:center; font-weight:bold; font-size:0.85em; line-height:1.4; flex-shrink:0">MobileNetV2<br>1.280 atributos</div>
|
||
<span style="font-size:2.2em; color:#999; margin:0 10px; flex-shrink:0">→</span>
|
||
<div style="background:#2C7FB8; color:white; border-radius:50%; width:200px; height:200px; display:flex; align-items:center; justify-content:center; text-align:center; font-weight:bold; font-size:0.85em; line-height:1.4; flex-shrink:0">center + scale<br>nearZeroVar</div>
|
||
<span style="font-size:2.2em; color:#999; margin:0 10px; flex-shrink:0">→</span>
|
||
<div style="background:#2C7FB8; color:white; border-radius:50%; width:200px; height:200px; display:flex; align-items:center; justify-content:center; text-align:center; font-weight:bold; font-size:0.85em; line-height:1.4; flex-shrink:0">5 modelos<br>SVM · RF</div>
|
||
<span style="font-size:2.2em; color:#999; margin:0 10px; flex-shrink:0">→</span>
|
||
<div style="background:#2C7FB8; color:white; border-radius:50%; width:200px; height:200px; display:flex; align-items:center; justify-content:center; text-align:center; font-weight:bold; font-size:0.85em; line-height:1.4; flex-shrink:0">Holdout<br>K-Fold k=5</div>
|
||
</div>
|
||
|
||
|
||
## Código — Leitura e Amostra
|
||
|
||
```{r}
|
||
#| eval: false
|
||
#| echo: true
|
||
# Listar imagens e inferir classe do prefixo do nome
|
||
arquivos <- list.files(path = caminho_rgb,
|
||
pattern = "\\.(jpg|jpeg|png|bmp|tif|tiff)$",
|
||
recursive = TRUE, full.names = TRUE)
|
||
|
||
classe_inferida <- stringr::str_extract(nome_sem_extensao, "^C\\d+")
|
||
|
||
# Amostra estratificada e balanceada: 50 imagens/classe
|
||
set.seed(123)
|
||
metadados <- metadados_completo %>%
|
||
group_by(classe) %>%
|
||
group_modify(~ slice_sample(.x, n = min(50, nrow(.x)))) %>%
|
||
ungroup()
|
||
# → 650 imagens totais (50 × 13 classes)
|
||
```
|
||
|
||
**O que faz:** lista os TIFFs, extrai a classe `C1`–`C13` do prefixo e sorteia 50 imagens por classe com semente fixa.
|
||
|
||
|
||
## Código — Extração com MobileNetV2
|
||
|
||
```{r}
|
||
#| eval: false
|
||
#| echo: true
|
||
# Carregar MobileNetV2 sem a camada classificadora
|
||
modelo_base <- application_mobilenet_v2(
|
||
include_top = FALSE, # remove o bloco de 1.000 classes
|
||
weights = "imagenet", # pesos pré-treinados no ImageNet
|
||
pooling = "avg", # GlobalAveragePooling → vetor 1D
|
||
input_shape = c(224L, 224L, 3L)
|
||
)
|
||
|
||
# Normalização específica da MobileNetV2: pixels → [-1, 1]
|
||
batch_prep <- (batch_array / 127.5) - 1.0
|
||
|
||
# features: matriz n × 1.280 (processado em lotes de 16)
|
||
features <- predict(modelo_base, batch_prep, verbose = 0L)
|
||
```
|
||
|
||
**O que faz:** carrega MobileNetV2, normaliza pixels para [−1, 1] e extrai 1.280 atributos por imagem. Resultado salvo em cache `.rds`.
|
||
|
||
|
||
## Código — Pré-processamento e Treino
|
||
|
||
```{r}
|
||
#| eval: false
|
||
#| echo: true
|
||
# Divisão estratificada 70/30
|
||
idx_treino <- createDataPartition(dados$classe, p = 0.70, list = FALSE)
|
||
|
||
# Remover atributos de variância quase nula
|
||
variaveis_nzv <- nearZeroVar(x_treino)
|
||
|
||
# Padronizar: média 0, desvio 1 (essencial para o SVM)
|
||
preproc <- preProcess(x_treino, method = c("center", "scale"))
|
||
x_treino_norm <- predict(preproc, x_treino)
|
||
x_teste_norm <- predict(preproc, x_teste)
|
||
|
||
# M1 — SVM linear
|
||
modelo_svm_linear <- svm(x = x_treino_norm, y = y_treino,
|
||
kernel = "linear", cost = 1, scale = FALSE)
|
||
```
|
||
|
||
**O que faz:** divide 70/30, padroniza e treina. O mesmo fluxo se repete para cada um dos 5 modelos (parâmetros variam).
|
||
|
||
|
||
## Os 5 Modelos
|
||
|
||
| # | Modelo | Tipo | Parâmetros |
|
||
|:--:|--------|:----:|-----------|
|
||
| 1 | **SVM linear** | Linear | `cost = 1` |
|
||
| 2 | **SVM radial** | RBF | `cost = 10` · `gamma = 0,01` (fixos) |
|
||
| 3 | **SVM radial ajustado** | RBF | `cost` e `gamma` por grade 4×4 + CV |
|
||
| 4 | **PCA + SVM radial** | RBF | PCA (≥ 95% var., máx. 30 comp.) + RBF |
|
||
| 5 | **Random Forest** | Ensemble | 500 árvores · `importance = TRUE` |
|
||
|
||
::: {.caixa-azul}
|
||
O PCA reduz os 1.280 atributos aos componentes que explicam ≥ 95% da variância antes de treinar o SVM, eliminando redundâncias.
|
||
:::
|
||
|
||
|
||
## Comparação — Holdout 70/30
|
||
|
||
```{r}
|
||
#| fig-width: 10
|
||
#| fig-height: 4.5
|
||
|
||
ggplot(resultados_holdout,
|
||
aes(x = reorder(modelo, acuracia), y = acuracia, fill = modelo)) +
|
||
geom_col(show.legend = FALSE, width = 0.7) +
|
||
geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
|
||
hjust = -0.12, size = 4.5, fontface = "bold", color = "grey20") +
|
||
coord_flip(ylim = c(0, 0.52)) +
|
||
scale_fill_manual(values = cores_modelos) +
|
||
labs(x = NULL, y = "Acurácia") +
|
||
theme_minimal(base_size = 14) +
|
||
theme(panel.grid.major.y = element_blank(),
|
||
panel.grid.minor = element_blank())
|
||
```
|
||
|
||
::: {.caixa-azul}
|
||
Baseline aleatório (13 classes) = **7,7%** — SVM linear (~40%) é **5,2× melhor que o acaso**.
|
||
:::
|
||
|
||
|
||
## Melhor Modelo — SVM Linear
|
||
|
||
:::: {.columns}
|
||
|
||
::: {.column width="33%"}
|
||
| Métrica | Holdout | K-Fold |
|
||
|---------|:-------:|:------:|
|
||
| Acurácia | **40,0%** | **40,2%** |
|
||
| Kappa | 0,350 | 0,352 |
|
||
| F1 macro | 0,402 | 0,409 |
|
||
|
||
<br>
|
||
|
||
::: {.caixa-laranja}
|
||
Holdout e K-Fold **concordam** → resultado **estável**.
|
||
:::
|
||
:::
|
||
|
||
::: {.column width="67%"}
|
||
```{r}
|
||
#| fig-width: 6.8
|
||
#| fig-height: 5.2
|
||
|
||
if (!is.null(cm_plot)) {
|
||
print(cm_plot)
|
||
} else {
|
||
ggplot() +
|
||
annotate("text", x = 0.5, y = 0.5, size = 5, color = "grey50",
|
||
label = "Renderize com outputs/parcial/ no lugar certo") +
|
||
theme_void()
|
||
}
|
||
```
|
||
:::
|
||
|
||
::::
|
||
|
||
|
||
## Validação Cruzada K-Fold (k = 5)
|
||
|
||
```{r}
|
||
#| fig-width: 10
|
||
#| fig-height: 4.2
|
||
|
||
ggplot(resultados_kfold,
|
||
aes(x = reorder(modelo, acuracia), y = acuracia, fill = modelo)) +
|
||
geom_col(show.legend = FALSE, width = 0.7) +
|
||
geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
|
||
hjust = -0.12, size = 4.5, fontface = "bold", color = "grey20") +
|
||
coord_flip(ylim = c(0, 0.52)) +
|
||
scale_fill_manual(values = cores_modelos) +
|
||
labs(x = NULL, y = "Acurácia média (5 folds)") +
|
||
theme_minimal(base_size = 14) +
|
||
theme(panel.grid.major.y = element_blank(),
|
||
panel.grid.minor = element_blank())
|
||
```
|
||
|
||
> **Por que k-fold?** O holdout avalia uma única divisão aleatória. O k-fold usa **todas as imagens** para avaliação, em 5 rodadas independentes — estimativa mais robusta.
|
||
|
||
|
||
## Holdout × K-Fold
|
||
|
||
```{r}
|
||
#| fig-width: 10
|
||
#| fig-height: 5.0
|
||
|
||
bind_rows(
|
||
resultados_holdout %>% mutate(estrategia = "Holdout 70/30"),
|
||
resultados_kfold %>% mutate(estrategia = "K-Fold (k=5)")
|
||
) %>%
|
||
ggplot(aes(x = reorder(modelo, acuracia),
|
||
y = acuracia, fill = estrategia)) +
|
||
geom_col(position = position_dodge(0.72), width = 0.68) +
|
||
geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
|
||
position = position_dodge(0.72),
|
||
hjust = -0.1, size = 4, color = "grey20") +
|
||
coord_flip(ylim = c(0, 0.55)) +
|
||
scale_fill_manual(values = c("Holdout 70/30" = azul,
|
||
"K-Fold (k=5)" = laranja)) +
|
||
labs(x = NULL, y = "Acurácia", fill = NULL) +
|
||
theme_minimal(base_size = 14) +
|
||
theme(legend.position = "bottom",
|
||
panel.grid.major.y = element_blank(),
|
||
panel.grid.minor = element_blank())
|
||
```
|
||
|
||
|
||
## Importância das Variáveis — Random Forest
|
||
|
||
```{r}
|
||
#| fig-width: 10
|
||
#| fig-height: 5.0
|
||
|
||
top10 <- importancia_df %>% slice_head(n = 10)
|
||
|
||
ggplot(top10,
|
||
aes(x = reorder(variavel, importancia), y = importancia)) +
|
||
geom_col(fill = azul) +
|
||
geom_col(
|
||
data = top10 %>% slice_head(n = 2),
|
||
fill = laranja
|
||
) +
|
||
geom_text(aes(label = round(importancia, 2)),
|
||
hjust = -0.12, size = 4, color = "grey20") +
|
||
coord_flip(ylim = c(0, max(top10$importancia) * 1.18)) +
|
||
labs(x = "Atributo (neurônio MobileNetV2)",
|
||
y = "Importância (MeanDecreaseGini)",
|
||
subtitle = "Laranja = 2 atributos mais discriminativos") +
|
||
theme_minimal(base_size = 13) +
|
||
theme(panel.grid.major.y = element_blank(),
|
||
panel.grid.minor = element_blank())
|
||
```
|
||
|
||
::: {.fragment .caixa-azul}
|
||
`feat_0472` e `feat_0260` têm importância ~70% maior que a 3ª colocada. São índices de neurônios internos da CNN — sem interpretação visual direta.
|
||
:::
|
||
|
||
|
||
## Discussão
|
||
|
||
```{r}
|
||
#| include: false
|
||
|
||
melhor_modelo_kf <- resultados_kfold$modelo[which.max(resultados_kfold$acuracia)]
|
||
melhor_acc_kf <- max(resultados_kfold$acuracia)
|
||
```
|
||
|
||
**Resultado principal**
|
||
|
||
- Melhor modelo: **`r melhor_modelo_kf`** com acurácia k-fold de **`r percent(melhor_acc_kf, accuracy = 0.1)`** (~5,2× acima do acaso)
|
||
- Holdout e k-fold convergiram → resultado **estável e confiável**
|
||
|
||
::: {.caixa-laranja}
|
||
**Achado mais relevante —** **SVM linear** superou o SVM radial com parâmetros fixos (14%). Os atributos da MobileNetV2 formam um espaço *quase linearmente separável* — o kernel linear é suficiente.
|
||
:::
|
||
|
||
**Trabalhos futuros**
|
||
|
||
- Outros extratores CNN: EfficientNetB0 · ResNet50 · VGG19
|
||
- Incorporar o canal **TIR** ao vetor de atributos
|
||
- Aumentar a amostra por classe (50 imagens/classe) e fazer fine-tuning da CNN
|
||
|
||
|
||
## Conclusão {.center}
|
||
|
||
<br>
|
||
|
||
> Transfer learning combina o poder representacional das CNNs com a interpretabilidade dos classificadores clássicos — **sem treinar a rede do zero**.
|
||
|
||
<br>
|
||
|
||
::: {.caixa-azul}
|
||
A abordagem é simples, reprodutível e já supera em 5× o baseline aleatório com apenas 50 imagens por classe — base sólida para extensões futuras.
|
||
:::
|
||
|
||
|
||
## {.center}
|
||
|
||
<br><br>
|
||
|
||
### Obrigado — Perguntas?
|
||
|
||
<br>
|
||
|
||
**1T Generoso · 1T João Marcos · 1T Vitor Cesa**
|
||
|
||
*CEAO-802 — Métodos de Análise de Dados*
|