Initial commit

This commit is contained in:
Cesa-V
2026-06-16 20:01:36 -03:00
commit e317e16cdc
91 changed files with 32046 additions and 0 deletions

367
apresentacao_svm_rgb.qmd Normal file
View File

@@ -0,0 +1,367 @@
---
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)