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>
This commit is contained in:
Cesa-V
2026-06-17 10:35:57 -03:00
parent 77b7ba11b7
commit 953758497d
92 changed files with 644 additions and 10336 deletions

13
.gitignore vendored
View File

@@ -1,10 +1,13 @@
# Arquivos grandes e dados brutos # Dados brutos (imagens — grandes demais para versionar)
*.zip *.zip
RGB/ trabalho/RGB/
# Materiais fora do escopo deste repositorio # Cache de modelos e features (arquivos pesados, gerados pelo codigo)
artigo_KC390* *.rds
artigo_promethee*
# HTMLs renderizados (gerados pelo Quarto)
*.html
# Configuracoes locais de assistentes # Configuracoes locais de assistentes
.claude/ .claude/
.vscode/

View File

@@ -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/ DADOS CEAO 2026/
trabalho_svm_rgb_files/ ├── docs/ # Documentação técnica do projeto
trabalho_svm_rgb_completo_files/ │ ├── about.md # Descrição, fluxo de trabalho e limitações
docs/ │ ├── authors.md # Autoria e colaboradores
README.md └── changelog.md # Histórico de versões
* `outputs/`: artefatos de processamento, bases de atributos, modelos, resultados e imagens extraidas. ├── trabalho/ # Análise principal
* `trabalho_svm_rgb.qmd`: versao principal do trabalho em Quarto. │ ├── RGB/ # Imagens brutas (gitignored — obter separadamente)
* `trabalho_svm_rgb_completo.qmd`: versao completa do trabalho em Quarto. │ ├── amostra/ # Versão parcial: 50 imagens/classe (650 total)
* `artigo_svm_rgb.tex`: artigo em LaTeX relacionado ao experimento SVM/RGB. │ │ ├── trabalho_svm_rgb.qmd # Código da análise (Quarto/R)
* `docs/`: documentacao tecnica, autoria e historico resumido de versoes. │ │ ├── outputs_parcial/ # CSVs de resultados e cache .rds
│ │ └── apresentacao/ # Material de apresentação
## Documentacao │ │ ├── apresentacao_svm_rgb.qmd
│ │ ├── SVM GUARA 802.pdf
* `docs/about.md`: descricao tecnica do projeto, fluxo de trabalho, dados e limitacoes. │ │ └── SVM GUARA - 802.pptx
* `docs/authors.md`: autoria, colaboradores e orientacoes de contribuicao. │ └── completo/ # Versão completa: todas as imagens disponíveis
* `docs/changelog.md`: historico resumido de mudancas significativas. │ ├── 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 ## 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

File diff suppressed because one or more lines are too long

View File

@@ -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)

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

View File

@@ -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 modelo acuracia kappa sensibilidade_macro especificidade_macro f1_macro
2 SVM linear 0.40153846153846157 0.3516666666666666 0.40153846153846157 0.9501282051282052 0.40929682290313046
3 PCA + SVM radial 0.3676923076923077 0.31499999999999995 0.3676923076923077 0.9473076923076923 0.37836144953604534
4 SVM radial ajustado 0.36153846153846153 0.3083333333333333 0.36153846153846153 0.9467948717948718 0.3667814495681257
5 Random Forest 0.3476923076923077 0.2933333333333333 0.3476923076923077 0.9456410256410256 0.35022414446407363
6 SVM radial 0.15846153846153846 0.08833333333333333 0.15846153846153846 0.9298717948717948 0.22329423850231678

View File

@@ -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
1 modelo acuracia kappa sensibilidade_macro especificidade_macro f1_macro
2 SVM linear 0.4 0.35 0.4 0.95 0.401945495575665
3 Random Forest 0.37435897435897436 0.3222222222222222 0.37435897435897436 0.9478632478632478 0.3688098868848946
4 PCA + SVM radial 0.358974358974359 0.3055555555555555 0.358974358974359 0.9465811965811965 0.36964765748713724
5 SVM radial ajustado 0.35384615384615387 0.3 0.35384615384615387 0.9461538461538461 0.35196493343650537
6 SVM radial 0.14358974358974358 0.0722222222222222 0.14358974358974358 0.9286324786324787 0.192826555606469

View 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 (C1C13); 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*

View File

Can't render this file because it is too large.

View File

@@ -80,7 +80,7 @@ Em R, e mais seguro usar barras `/` no caminho, mesmo no Windows.
# 2. Caminho do dataset RGB # 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)) { if (!dir.exists(caminho_rgb)) {
stop("A pasta do dataset RGB nao foi encontrada. Verifique o caminho informado em 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 # 6. Carregar modelo pre-treinado MobileNetV2
# ============================================================ # ============================================================
dir.create("outputs", showWarnings = FALSE)
modelo_base <- application_mobilenet_v2( modelo_base <- application_mobilenet_v2(
include_top = FALSE, include_top = FALSE,
weights = "imagenet", 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) # 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) dir.create(pasta_saida, recursive = TRUE, showWarnings = FALSE)
arquivo_cache_features <- file.path(pasta_saida, "features_rgb_mobilenetv2.rds") arquivo_cache_features <- file.path(pasta_saida, "features_rgb_mobilenetv2.rds")

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -76,7 +76,7 @@ cat("Backend keras3:", backend_ativo, "\n")
# 2. Caminho do dataset RGB # 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)) { if (!dir.exists(caminho_rgb)) {
stop("A pasta do dataset RGB nao foi encontrada. Verifique o caminho informado em 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 # 6. Carregar modelo pre-treinado MobileNetV2
# ============================================================ # ============================================================
dir.create("outputs", showWarnings = FALSE)
modelo_base <- application_mobilenet_v2( modelo_base <- application_mobilenet_v2(
include_top = FALSE, include_top = FALSE,
weights = "imagenet", 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) # 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) dir.create(pasta_saida, recursive = TRUE, showWarnings = FALSE)
# Nomes diferentes para nao sobrescrever o cache do trabalho original # Nomes diferentes para nao sobrescrever o cache do trabalho original

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff