Reorganiza estrutura do projeto em trabalho/ e artigo/
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>
13
.gitignore
vendored
@@ -1,10 +1,13 @@
|
||||
# Arquivos grandes e dados brutos
|
||||
# Dados brutos (imagens — grandes demais para versionar)
|
||||
*.zip
|
||||
RGB/
|
||||
trabalho/RGB/
|
||||
|
||||
# Materiais fora do escopo deste repositorio
|
||||
artigo_KC390*
|
||||
artigo_promethee*
|
||||
# Cache de modelos e features (arquivos pesados, gerados pelo codigo)
|
||||
*.rds
|
||||
|
||||
# HTMLs renderizados (gerados pelo Quarto)
|
||||
*.html
|
||||
|
||||
# Configuracoes locais de assistentes
|
||||
.claude/
|
||||
.vscode/
|
||||
66
README.md
@@ -1,28 +1,50 @@
|
||||
# Analise SVM RGB CEAO 2026
|
||||
# Classificação de Imagens RGB com SVM — CEAO-802
|
||||
|
||||
Repositorio de trabalho para organizacao, processamento e documentacao das analises de classificacao de imagens RGB com SVM no contexto dos dados CEAO 2026. O projeto reune apresentacoes, artigos, referencias bibliograficas, resultados gerados e arquivos Quarto/LaTeX relacionados ao desenvolvimento metodologico e a comunicacao dos resultados.
|
||||
Repositório do trabalho de classificação supervisionada de imagens de sensoriamento remoto RGB usando Transfer Learning (MobileNetV2) como extrator de atributos e SVM como classificador. Dados de Guaratinguetá (SP), 13 classes de cobertura do solo.
|
||||
|
||||
## Estrutura atual
|
||||
## Estrutura
|
||||
|
||||
sensoriamento-rgb-svm/
|
||||
outputs/
|
||||
trabalho_svm_rgb_files/
|
||||
trabalho_svm_rgb_completo_files/
|
||||
docs/
|
||||
README.md
|
||||
|
||||
* `outputs/`: artefatos de processamento, bases de atributos, modelos, resultados e imagens extraidas.
|
||||
* `trabalho_svm_rgb.qmd`: versao principal do trabalho em Quarto.
|
||||
* `trabalho_svm_rgb_completo.qmd`: versao completa do trabalho em Quarto.
|
||||
* `artigo_svm_rgb.tex`: artigo em LaTeX relacionado ao experimento SVM/RGB.
|
||||
* `docs/`: documentacao tecnica, autoria e historico resumido de versoes.
|
||||
|
||||
## Documentacao
|
||||
|
||||
* `docs/about.md`: descricao tecnica do projeto, fluxo de trabalho, dados e limitacoes.
|
||||
* `docs/authors.md`: autoria, colaboradores e orientacoes de contribuicao.
|
||||
* `docs/changelog.md`: historico resumido de mudancas significativas.
|
||||
```
|
||||
DADOS CEAO 2026/
|
||||
├── docs/ # Documentação técnica do projeto
|
||||
│ ├── about.md # Descrição, fluxo de trabalho e limitações
|
||||
│ ├── authors.md # Autoria e colaboradores
|
||||
│ └── changelog.md # Histórico de versões
|
||||
│
|
||||
├── trabalho/ # Análise principal
|
||||
│ ├── RGB/ # Imagens brutas (gitignored — obter separadamente)
|
||||
│ ├── amostra/ # Versão parcial: 50 imagens/classe (650 total)
|
||||
│ │ ├── trabalho_svm_rgb.qmd # Código da análise (Quarto/R)
|
||||
│ │ ├── outputs_parcial/ # CSVs de resultados e cache .rds
|
||||
│ │ └── apresentacao/ # Material de apresentação
|
||||
│ │ ├── apresentacao_svm_rgb.qmd
|
||||
│ │ ├── SVM GUARA 802.pdf
|
||||
│ │ └── SVM GUARA - 802.pptx
|
||||
│ └── completo/ # Versão completa: todas as imagens disponíveis
|
||||
│ ├── trabalho_svm_rgb_completo.qmd
|
||||
│ └── outputs_completo/ # CSVs de resultados e cache .rds
|
||||
│
|
||||
└── artigo/ # Artigo científico
|
||||
├── artigo_svm_rgb.tex
|
||||
├── Referencias.bib
|
||||
├── Referencias_SVM.bib
|
||||
├── Classificação_de_Imagens_...pdf # PDF final
|
||||
└── artigos-referencia/ # Referências bibliográficas em PDF
|
||||
├── galoa-proceedings--sbsr-2023--156515.pdf
|
||||
└── datasets_2026.pdf
|
||||
```
|
||||
|
||||
## Dados brutos
|
||||
|
||||
A pasta `RGB/` e o arquivo `RGB.zip` nao sao versionados por tamanho. Eles devem permanecer localmente e ser tratados como dados brutos de entrada.
|
||||
A pasta `trabalho/RGB/` não é versionada (imagens .tiff, ~GB). Deve permanecer localmente como dado de entrada. Solicitar acesso aos dados originais aos autores.
|
||||
|
||||
## Reprodução dos resultados
|
||||
|
||||
1. Clonar o repositório
|
||||
2. Obter e posicionar as imagens em `trabalho/RGB/`
|
||||
3. Abrir `trabalho/amostra/trabalho_svm_rgb.qmd` no RStudio e renderizar
|
||||
4. Os outputs são salvos automaticamente em `outputs_parcial/`
|
||||
|
||||
## Autores
|
||||
|
||||
1T Generoso · 1T João Marcos · 1T Vitor Cesa — CEAO-802, Métodos de Análise de Dados
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
---
|
||||
title: "Classificacao de Imagens RGB com SVM"
|
||||
subtitle: "CEAO-802 — Metodos de Analise de Dados"
|
||||
author:
|
||||
- "1T Generoso"
|
||||
- "1T João Marcos"
|
||||
- "1T Vitor Cesa"
|
||||
format:
|
||||
pptx:
|
||||
fig-width: 9
|
||||
fig-height: 4.5
|
||||
execute:
|
||||
warning: false
|
||||
message: false
|
||||
echo: false
|
||||
---
|
||||
|
||||
```{r setup}
|
||||
library(tidyverse)
|
||||
library(knitr)
|
||||
library(scales)
|
||||
|
||||
resultados_holdout <- read_csv("outputs/resultados_modelos_rgb.csv", show_col_types = FALSE)
|
||||
resultados_kfold <- read_csv("outputs/resultados_kfold_rgb.csv", show_col_types = FALSE)
|
||||
importancia_df <- read_csv("outputs/importancia_variaveis_rgb.csv", show_col_types = FALSE)
|
||||
|
||||
cores_modelos <- c(
|
||||
"SVM linear" = "#2C7FB8",
|
||||
"SVM radial" = "#7FCDBB",
|
||||
"SVM radial ajustado" = "#41B6C4",
|
||||
"PCA + SVM radial" = "#1D91C0",
|
||||
"Random Forest" = "#225EA8"
|
||||
)
|
||||
```
|
||||
|
||||
# Introducao e Dataset
|
||||
|
||||
## Contexto do Problema
|
||||
|
||||
- Imagens aéreas coletadas por **drone** sobre **Guaratinguetá (SP)**
|
||||
- Objetivo: classificar cada recorte em uma das **13 classes de cobertura urbana**
|
||||
- Dataset disponibiliza 4 modalidades: **RGB dia**, **RGB noite**, **TIR dia**, **TIR noite**
|
||||
- Este trabalho utiliza somente as imagens **RGB**
|
||||
- As imagens já vinham **pré-rotuladas** pelo nome do arquivo
|
||||
|
||||
Exemplo: `C10_2023_06_M_01_RGB.tiff` → classe **C10 (Piscina)**, junho 2023, manhã
|
||||
|
||||
## As 13 Classes
|
||||
|
||||
:::: {.columns}
|
||||
|
||||
::: {.column width="50%"}
|
||||
| Classe | Descrição |
|
||||
|:------:|-----------|
|
||||
| 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 | Descrição |
|
||||
|:------:|-----------|
|
||||
| C8 | Telhado Metálico |
|
||||
| C9 | Placa Fotovoltaica |
|
||||
| C10 | Piscina |
|
||||
| C11 | Pedra Sabão |
|
||||
| C12 | Caixa D'água |
|
||||
| C13 | Veículos |
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
## Amostra Balanceada
|
||||
|
||||
```{r dataset-plot}
|
||||
tibble(
|
||||
classe = factor(paste0("C", 1:13), levels = paste0("C", 1:13)),
|
||||
n = 50
|
||||
) |>
|
||||
ggplot(aes(x = classe, y = n)) +
|
||||
geom_col(fill = "#2C7FB8", width = 0.7) +
|
||||
geom_text(aes(label = n), vjust = -0.5, size = 4, fontface = "bold") +
|
||||
scale_y_continuous(limits = c(0, 62)) +
|
||||
labs(
|
||||
title = "50 imagens por classe — amostra estratificada e balanceada",
|
||||
subtitle = "Dataset completo: 1.248 imagens TIFF | Amostra utilizada: 650 imagens",
|
||||
x = "Classe", y = "Imagens na amostra"
|
||||
) +
|
||||
theme_minimal(base_size = 14) +
|
||||
theme(panel.grid.major.x = element_blank())
|
||||
```
|
||||
|
||||
# Metodologia
|
||||
|
||||
## Pipeline de Classificação
|
||||
|
||||
```{r pipeline, fig.height=3.2}
|
||||
tibble(
|
||||
x = 1:5,
|
||||
label = c("Imagens\nRGB", "MobileNetV2\n1280 features",
|
||||
"Normalização\n+ PCA (opt.)", "SVM / RF",
|
||||
"Avaliação\nHoldout / K-Fold")
|
||||
) |>
|
||||
ggplot(aes(x = x, y = 1)) +
|
||||
geom_point(size = 20, color = "#2C7FB8") +
|
||||
geom_text(aes(label = label), size = 3.8, color = "white",
|
||||
fontface = "bold", lineheight = 1.0) +
|
||||
annotate("segment",
|
||||
x = c(1.38,2.38,3.38,4.38), xend = c(1.62,2.62,3.62,4.62),
|
||||
y = 1, yend = 1, linewidth = 1.4, color = "grey40",
|
||||
arrow = arrow(length = unit(0.3, "cm"), type = "closed")) +
|
||||
scale_x_continuous(limits = c(0.5, 5.5)) +
|
||||
scale_y_continuous(limits = c(0.6, 1.4)) +
|
||||
theme_void()
|
||||
```
|
||||
|
||||
## Transfer Learning — MobileNetV2
|
||||
|
||||
:::: {.columns}
|
||||
|
||||
::: {.column width="55%"}
|
||||
**O que é transfer learning?**
|
||||
|
||||
A rede MobileNetV2 foi treinada no **ImageNet** (1,2 milhão de imagens, 1000 categorias). Reutilizamos essa rede como **extrator de atributos**, sem retreiná-la.
|
||||
|
||||
- Imagem redimensionada para **224 × 224 px**
|
||||
- Camada de classificação removida
|
||||
- Saída: **vetor de 1280 valores** por imagem
|
||||
- Cada valor representa um padrão visual aprendido
|
||||
:::
|
||||
|
||||
::: {.column width="45%"}
|
||||
| | Tradicional | Transfer |
|
||||
|--|:-----------:|:-------:|
|
||||
| Nº features | ~60 | **1280** |
|
||||
| Origem | Manual | CNN |
|
||||
| Treinamento | Não | Pré-treinado |
|
||||
| Padrões | Cor/textura | Alto nível |
|
||||
|
||||
Não é necessário treinar a rede do zero — apenas **reutilizamos** o que ela já aprendeu.
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
## Código — Carregando o Modelo
|
||||
|
||||
```r
|
||||
library(keras3)
|
||||
|
||||
# Carregar MobileNetV2 sem a camada de classificação
|
||||
modelo_base <- application_mobilenet_v2(
|
||||
include_top = FALSE, # remove a camada final de 1000 classes
|
||||
weights = "imagenet", # pesos pré-treinados no ImageNet
|
||||
pooling = "avg", # GlobalAveragePooling → vetor 1D
|
||||
input_shape = c(224L, 224L, 3L)
|
||||
)
|
||||
# Saída: vetor de 1280 atributos por imagem
|
||||
```
|
||||
|
||||
## Código — Extraindo Features por Imagem
|
||||
|
||||
```r
|
||||
# Para cada imagem da amostra:
|
||||
img <- image_load(arquivo, target_size = c(224L, 224L))
|
||||
arr <- image_to_array(img) # array 224x224x3
|
||||
arr <- array_reshape(arr, c(1L, 224L, 224L, 3L)) # batch de 1 imagem
|
||||
arr <- (arr / 127.5) - 1.0 # normalização padrão do MobileNetV2 [-1, 1]
|
||||
|
||||
feat <- predict(modelo_base, arr, verbose = 0L)
|
||||
# feat: vetor de 1280 valores — atributos visuais de alto nível
|
||||
```
|
||||
|
||||
Processamento em **lotes de 16 imagens** para eficiência, com **cache em disco** para evitar reprocessamento.
|
||||
|
||||
# Modelos Avaliados
|
||||
|
||||
## Os 5 Modelos
|
||||
|
||||
| # | Modelo | Tipo | Parâmetros |
|
||||
|:--:|--------|:----:|-----------|
|
||||
| 1 | **SVM linear** | Linear | `cost = 1` |
|
||||
| 2 | **SVM radial** | RBF | `cost = 10`, `gamma = 0.01` |
|
||||
| 3 | **SVM radial ajustado** | RBF | `cost` e `gamma` via grid search |
|
||||
| 4 | **PCA + SVM radial** | RBF | PCA (≥ 95% variância) + RBF |
|
||||
| 5 | **Random Forest** | Ensemble | 500 árvores |
|
||||
|
||||
**SVM** encontra o hiperplano que **maximiza a margem** entre as classes. Com kernel **RBF**, fronteiras não-lineares são possíveis via *kernel trick*.
|
||||
|
||||
## Código — Treinamento dos Modelos
|
||||
|
||||
```r
|
||||
library(e1071); library(caret); library(randomForest)
|
||||
|
||||
# Pré-processamento: remover variância nula e normalizar
|
||||
preproc <- preProcess(x_treino, method = c("center", "scale"))
|
||||
x_treino_n <- predict(preproc, x_treino)
|
||||
|
||||
# Modelo 1: SVM linear
|
||||
modelo_linear <- svm(x = x_treino_n, y = y_treino,
|
||||
kernel = "linear", cost = 1, scale = FALSE)
|
||||
|
||||
# Modelo 3: Ajuste de hiperparâmetros (grid search com CV interna)
|
||||
ajuste <- tune.svm(x = x_treino_n, 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 = 5))
|
||||
|
||||
# Modelo 5: Random Forest
|
||||
modelo_rf <- randomForest(x = x_treino_n, y = y_treino,
|
||||
ntree = 500, importance = TRUE)
|
||||
```
|
||||
|
||||
# Avaliacao dos Modelos
|
||||
|
||||
## Estratégia 1 — Holdout 70/30
|
||||
|
||||
:::: {.columns}
|
||||
|
||||
::: {.column width="38%"}
|
||||
**Divisão única e aleatória**
|
||||
|
||||
- 70% treino (~455 imagens)
|
||||
- 30% teste (~195 imagens)
|
||||
- Estratificada por classe
|
||||
|
||||
**Limitação:** com datasets menores, o resultado pode variar conforme a divisão escolhida.
|
||||
:::
|
||||
|
||||
::: {.column width="62%"}
|
||||
```{r holdout-plot}
|
||||
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") +
|
||||
coord_flip(ylim = c(0, 0.53)) +
|
||||
scale_fill_manual(values = cores_modelos) +
|
||||
labs(title = "Acurácia — Holdout 70/30", x = NULL, y = "Acurácia") +
|
||||
theme_minimal(base_size = 13)
|
||||
```
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
## Estratégia 2 — Validação Cruzada K-Fold (k=5)
|
||||
|
||||
:::: {.columns}
|
||||
|
||||
::: {.column width="38%"}
|
||||
**5 partições estratificadas**
|
||||
|
||||
- Cada parte usada **uma vez** como teste
|
||||
- Acurácia final = **média das 5 rodadas**
|
||||
- Toda imagem aparece no teste
|
||||
|
||||
**Vantagem:** estimativa mais **robusta** — não depende de uma única divisão.
|
||||
:::
|
||||
|
||||
::: {.column width="62%"}
|
||||
```{r kfold-plot}
|
||||
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") +
|
||||
coord_flip(ylim = c(0, 0.53)) +
|
||||
scale_fill_manual(values = cores_modelos) +
|
||||
labs(title = "Acurácia média — K-Fold (k=5)", x = NULL, y = "Acurácia") +
|
||||
theme_minimal(base_size = 13)
|
||||
```
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
## Código — Validação Cruzada K-Fold
|
||||
|
||||
```r
|
||||
folds <- createFolds(dados$classe, k = 5, list = TRUE, returnTrain = FALSE)
|
||||
|
||||
for (fold_i in seq_len(5)) {
|
||||
idx_teste <- folds[[fold_i]]
|
||||
idx_treino <- setdiff(seq_len(nrow(dados)), idx_teste)
|
||||
|
||||
# Pré-processar apenas com dados do treino deste fold
|
||||
pp <- preProcess(x[idx_treino, ], method = c("center", "scale"))
|
||||
x_tr <- predict(pp, x[idx_treino, ])
|
||||
x_te <- predict(pp, x[idx_teste, ])
|
||||
y_tr <- dados$classe[idx_treino]
|
||||
y_te <- dados$classe[idx_teste]
|
||||
|
||||
modelo <- svm(x = x_tr, y = y_tr, kernel = "linear", cost = 1)
|
||||
pred <- predict(modelo, x_te)
|
||||
cm <- confusionMatrix(pred, y_te)
|
||||
|
||||
metricas_folds[[fold_i]] <- extrair_metricas("SVM linear", cm)
|
||||
}
|
||||
# Média das 5 rodadas
|
||||
resultados_kfold <- bind_rows(metricas_folds) |>
|
||||
group_by(modelo) |> summarise(across(everything(), mean))
|
||||
```
|
||||
|
||||
## Comparação: Holdout vs K-Fold
|
||||
|
||||
```{r comparacao-plot, fig.height=5}
|
||||
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(width = 0.72), width = 0.68) +
|
||||
geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
|
||||
position = position_dodge(width = 0.72),
|
||||
hjust = -0.1, size = 4) +
|
||||
coord_flip(ylim = c(0, 0.55)) +
|
||||
scale_fill_manual(values = c("Holdout 70/30" = "#2C7FB8",
|
||||
"K-Fold (k=5)" = "#E06C00")) +
|
||||
labs(title = "Holdout 70/30 vs K-Fold (k=5)",
|
||||
x = NULL, y = "Acurácia", fill = NULL) +
|
||||
theme_minimal(base_size = 13) +
|
||||
theme(legend.position = "top")
|
||||
```
|
||||
|
||||
## Variáveis Mais Importantes (Random Forest)
|
||||
|
||||
```{r importancia-plot, fig.height=5}
|
||||
importancia_df |>
|
||||
slice_head(n = 15) |>
|
||||
ggplot(aes(x = reorder(variavel, importancia), y = importancia)) +
|
||||
geom_col(fill = "#2C7FB8", width = 0.75) +
|
||||
geom_text(aes(label = round(importancia, 2)), hjust = -0.15, size = 4) +
|
||||
coord_flip(ylim = c(0, max(importancia_df$importancia[1:15]) * 1.18)) +
|
||||
labs(
|
||||
title = "Top 15 features mais importantes — Random Forest",
|
||||
subtitle = "feat_XXXX = neurônio da camada final do MobileNetV2",
|
||||
x = NULL, y = "Importância (Mean Decrease Gini)"
|
||||
) +
|
||||
theme_minimal(base_size = 13)
|
||||
```
|
||||
|
||||
# Conclusao
|
||||
|
||||
## Conclusão
|
||||
|
||||
```{r conclusao-calc}
|
||||
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 de **`r percent(melhor_acc_kf, accuracy = 0.1)`** (k-fold)
|
||||
- Acerto aleatório com 13 classes ≈ **7,7%** → resultado ~5× melhor
|
||||
- Holdout e k-fold convergiram → estimativa **estável e confiável**
|
||||
|
||||
**Por que SVM linear ganhou do radial?**
|
||||
|
||||
Os 1280 atributos da MobileNetV2 já tornam as classes aproximadamente **linearmente separáveis** — o kernel RBF não agrega ganho relevante neste caso.
|
||||
|
||||
**Trabalhos futuros**
|
||||
|
||||
- Outros extratores CNN: EfficientNetB0, ResNet50, VGG19
|
||||
- Incorporar canal **TIR** (térmico) ao vetor de atributos
|
||||
- Aumentar amostra por classe (atualmente 50 imagens)
|
||||
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,6 +0,0 @@
|
||||
modelo,acuracia,kappa,sensibilidade_macro,especificidade_macro,f1_macro
|
||||
SVM linear,0.40153846153846157,0.3516666666666666,0.40153846153846157,0.9501282051282052,0.40929682290313046
|
||||
PCA + SVM radial,0.3676923076923077,0.31499999999999995,0.3676923076923077,0.9473076923076923,0.37836144953604534
|
||||
SVM radial ajustado,0.36153846153846153,0.3083333333333333,0.36153846153846153,0.9467948717948718,0.3667814495681257
|
||||
Random Forest,0.3476923076923077,0.2933333333333333,0.3476923076923077,0.9456410256410256,0.35022414446407363
|
||||
SVM radial,0.15846153846153846,0.08833333333333333,0.15846153846153846,0.9298717948717948,0.22329423850231678
|
||||
|
@@ -1,6 +0,0 @@
|
||||
modelo,acuracia,kappa,sensibilidade_macro,especificidade_macro,f1_macro
|
||||
SVM linear,0.4,0.35,0.4,0.95,0.401945495575665
|
||||
Random Forest,0.37435897435897436,0.3222222222222222,0.37435897435897436,0.9478632478632478,0.3688098868848946
|
||||
PCA + SVM radial,0.358974358974359,0.3055555555555555,0.358974358974359,0.9465811965811965,0.36964765748713724
|
||||
SVM radial ajustado,0.35384615384615387,0.3,0.35384615384615387,0.9461538461538461,0.35196493343650537
|
||||
SVM radial,0.14358974358974358,0.0722222222222222,0.14358974358974358,0.9286324786324787,0.192826555606469
|
||||
|
588
trabalho/amostra/apresentacao/apresentacao_svm_rgb.qmd
Normal file
@@ -0,0 +1,588 @@
|
||||
---
|
||||
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*
|
||||
|
Can't render this file because it is too large.
|
@@ -80,7 +80,7 @@ Em R, e mais seguro usar barras `/` no caminho, mesmo no Windows.
|
||||
# 2. Caminho do dataset RGB
|
||||
# ============================================================
|
||||
|
||||
caminho_rgb <- "D:/Vitor/Documents/CEAO/802/HAB/DADOS CEAO 2026/RGB"
|
||||
caminho_rgb <- "../RGB"
|
||||
|
||||
if (!dir.exists(caminho_rgb)) {
|
||||
stop("A pasta do dataset RGB nao foi encontrada. Verifique o caminho informado em caminho_rgb.")
|
||||
@@ -223,8 +223,6 @@ A rede escolhida e a **MobileNetV2**, uma arquitetura compacta e eficiente desen
|
||||
# 6. Carregar modelo pre-treinado MobileNetV2
|
||||
# ============================================================
|
||||
|
||||
dir.create("outputs", showWarnings = FALSE)
|
||||
|
||||
modelo_base <- application_mobilenet_v2(
|
||||
include_top = FALSE,
|
||||
weights = "imagenet",
|
||||
@@ -247,7 +245,7 @@ O processamento e feito em lotes (*batch*) para maior eficiencia, e o resultado
|
||||
# 7. Extracao de features via CNN pre-treinada (com cache)
|
||||
# ============================================================
|
||||
|
||||
pasta_saida <- "outputs/parcial"
|
||||
pasta_saida <- "outputs_parcial"
|
||||
dir.create(pasta_saida, recursive = TRUE, showWarnings = FALSE)
|
||||
|
||||
arquivo_cache_features <- file.path(pasta_saida, "features_rgb_mobilenetv2.rds")
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Can't render this file because it is too large.
|
@@ -76,7 +76,7 @@ cat("Backend keras3:", backend_ativo, "\n")
|
||||
# 2. Caminho do dataset RGB
|
||||
# ============================================================
|
||||
|
||||
caminho_rgb <- "D:/Vitor/Documents/CEAO/802/HAB/DADOS CEAO 2026/RGB"
|
||||
caminho_rgb <- "../RGB"
|
||||
|
||||
if (!dir.exists(caminho_rgb)) {
|
||||
stop("A pasta do dataset RGB nao foi encontrada. Verifique o caminho informado em caminho_rgb.")
|
||||
@@ -213,8 +213,6 @@ ggplot(metadados, aes(x = classe)) +
|
||||
# 6. Carregar modelo pre-treinado MobileNetV2
|
||||
# ============================================================
|
||||
|
||||
dir.create("outputs", showWarnings = FALSE)
|
||||
|
||||
modelo_base <- application_mobilenet_v2(
|
||||
include_top = FALSE,
|
||||
weights = "imagenet",
|
||||
@@ -233,7 +231,7 @@ cat("Dimensao do vetor de features:", modelo_base$output_shape[[2]], "\n")
|
||||
# 7. Extracao de features (com cache separado do original)
|
||||
# ============================================================
|
||||
|
||||
pasta_saida <- "outputs/completo"
|
||||
pasta_saida <- "outputs_completo"
|
||||
dir.create(pasta_saida, recursive = TRUE, showWarnings = FALSE)
|
||||
|
||||
# Nomes diferentes para nao sobrescrever o cache do trabalho original
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |