Projetos
Passo a passo · Deep Learning · Tarefa 02

Classificação de Imagens Territoriais com CNN

Vamos ensinar um computador a olhar uma foto e dizer se é floresta, geleira, montanha, praia, rua ou construção. Vai entender como uma CNN "enxerga" uma imagem — passo a passo, do zero.

Visão geral

Como um computador "vê" uma imagem?

Para você, uma foto de floresta é óbvia. Para o computador, é só uma grade de números — cada pixel tem um valor de 0 a 255 para vermelho, verde e azul. Uma imagem 150×150 pixels = 67.500 números.

Uma CNN (Convolutional Neural Network) aplica filtros sobre a imagem — igual ao filtro "desfoque" ou "nitidez" do Instagram — e aprende quais combinações de pixels indicam "floresta" vs. "praia" vs. "montanha".

Vamos construir duas CNNs e comparar: uma simples (linha de base) e uma regularizada com Batch Normalization + Dropout.

Ferramentas

O que você vai precisar

  • Python 3.10+
  • TensorFlow / Keras — biblioteca principal para construir CNNs.
  • Matplotlib / Seaborn — para visualizar imagens e resultados.
  • Scikit-learn — para a matriz de confusão e relatório.

Dataset: Intel Image Classification (Kaggle) — pastas seg_train/, seg_test/ e seg_pred/ com imagens JPG de 6 categorias: buildings, forest, glacier, mountain, sea, street.

Passo 1

Importar bibliotecas e configurar ambiente

from pathlib import Path
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import layers, models
from sklearn.metrics import classification_report, confusion_matrix

# SEED fixa a aleatoriedade — garante resultados idênticos em cada execução
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
tf.random.set_seed(SEED)

print('TensorFlow:', tf.__version__)

Por que SEED? Redes neurais usam números aleatórios para inicializar os pesos. Sem fixar a semente, os resultados mudam a cada execução — impossível comparar modelos.

BASE_DIR  = Path.cwd()
TRAIN_DIR = BASE_DIR / 'seg_train' / 'seg_train'
TEST_DIR  = BASE_DIR / 'seg_test'  / 'seg_test'
PRED_DIR  = BASE_DIR / 'seg_pred'  / 'seg_pred'

for pasta in [TRAIN_DIR, TEST_DIR, PRED_DIR]:
    print(pasta, '-> existe?', pasta.exists())

O que descobrimos? As 3 pastas existem e estão no lugar certo.

Passo 2

Explorar o dataset de imagens

Antes de treinar, vamos contar quantas imagens temos por classe e visualizar amostras — para ter certeza de que os dados fazem sentido.

def contar_imagens_por_classe(diretorio):
    dados = []
    for classe_dir in sorted(diretorio.iterdir()):
        if classe_dir.is_dir():
            quantidade = len(list(classe_dir.glob('*.jpg')))
            dados.append({'classe': classe_dir.name, 'imagens': quantidade})
    return pd.DataFrame(dados)

contagem_treino = contar_imagens_por_classe(TRAIN_DIR)
display(contagem_treino)
print('Total de treino:', contagem_treino['imagens'].sum())
# Visualizar uma imagem de cada classe
classes = sorted([p.name for p in TRAIN_DIR.iterdir() if p.is_dir()])

plt.figure(figsize=(12, 7))
for i, nome_classe in enumerate(classes):
    imagens = list((TRAIN_DIR / nome_classe).glob('*.jpg'))
    img = tf.keras.utils.load_img(random.choice(imagens), target_size=(150, 150))
    plt.subplot(2, 3, i + 1)
    plt.imshow(img)
    plt.title(nome_classe)
    plt.axis('off')
plt.tight_layout()
plt.show()

O que descobrimos? ~14.000 imagens de treino em 6 categorias. As imagens têm tamanhos variados — precisamos padronizar para 150×150 pixels.

Passo 3

Carregar e pré-processar as imagens

image_dataset_from_directory lê as pastas, redimensiona, embaralha e cria batches automaticamente — economiza muitas linhas de código.

IMG_SIZE         = (150, 150)
BATCH_SIZE       = 32          # aprende 32 imagens por vez
VALIDATION_SPLIT = 0.2         # 20% do treino vira validação

train_ds = tf.keras.utils.image_dataset_from_directory(
    TRAIN_DIR,
    validation_split=VALIDATION_SPLIT,
    subset='training',
    seed=SEED,
    image_size=IMG_SIZE,
    batch_size=BATCH_SIZE,
    label_mode='int'   # 0=buildings, 1=forest, 2=glacier, 3=mountain, 4=sea, 5=street
)

val_ds = tf.keras.utils.image_dataset_from_directory(
    TRAIN_DIR,
    validation_split=VALIDATION_SPLIT,
    subset='validation',
    seed=SEED,
    image_size=IMG_SIZE,
    batch_size=BATCH_SIZE,
    label_mode='int'
)
AUTOTUNE = tf.data.AUTOTUNE

# .prefetch() carrega o próximo batch enquanto o atual é processado — sem tempo ocioso
train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)
val_ds   = val_ds.prefetch(buffer_size=AUTOTUNE)
test_ds  = test_ds.prefetch(buffer_size=AUTOTUNE)

O que descobrimos? Dataset carregado com eficiência. O prefetch elimina tempo ocioso entre batches, acelerando o treino.

Passo 4

Construir e treinar a CNN simples

A CNN simples é a nossa linha de base. 3 blocos Conv2D + MaxPooling detectam padrões progressivamente mais complexos: bordas → texturas → formas.

def criar_cnn_simples(num_classes):
    return models.Sequential([
        layers.Input(shape=(150, 150, 3)),
        layers.Rescaling(1./255),             # pixels 0-255 → 0-1

        # Bloco 1: detecta bordas e cores simples
        layers.Conv2D(32, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),           # reduz a imagem pela metade

        # Bloco 2: detecta texturas
        layers.Conv2D(64, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),

        # Bloco 3: detecta formas complexas
        layers.Conv2D(128, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),

        layers.Flatten(),                     # grade 2D → vetor 1D
        layers.Dense(128, activation='relu'),
        layers.Dense(num_classes, activation='softmax')  # 6 probabilidades de saída
    ])

cnn_simples = criar_cnn_simples(num_classes=6)
cnn_simples.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

historico_simples = cnn_simples.fit(train_ds, validation_data=val_ds, epochs=8)

O que descobrimos? A CNN simples aprende a classificar imagens sem ensinamento explícito. O MaxPooling é essencial: força a rede a focar em características globais, não em pixels específicos.

Passo 5

CNN regularizada: Batch Normalization + Dropout

Batch Normalization normaliza as ativações entre camadas — garante que os valores não explodam nem desapareçam. Dropout desativa neurônios aleatoriamente no treino — a rede não consegue "decorar" e aprende padrões mais gerais.

def criar_cnn_regularizada(num_classes):
    return models.Sequential([
        layers.Input(shape=(150, 150, 3)),
        layers.Rescaling(1./255),

        # Bloco 1 com regularização
        layers.Conv2D(32, (3, 3), activation='relu'),
        layers.BatchNormalization(),    # normaliza as ativações
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.20),          # desativa 20% dos neurônios aleatoriamente

        # Bloco 2
        layers.Conv2D(64, (3, 3), activation='relu'),
        layers.BatchNormalization(),
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.25),

        # Bloco 3
        layers.Conv2D(128, (3, 3), activation='relu'),
        layers.BatchNormalization(),
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.30),

        layers.Flatten(),
        layers.Dense(256, activation='relu'),
        layers.BatchNormalization(),
        layers.Dropout(0.50),          # Dropout mais forte na camada densa
        layers.Dense(num_classes, activation='softmax')
    ])

cnn_reg = criar_cnn_regularizada(num_classes=6)
cnn_reg.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
historico_reg = cnn_reg.fit(train_ds, validation_data=val_ds, epochs=8)

O que descobrimos? A CNN regularizada apresentou curvas de validação mais estáveis. As categorias mais difíceis: glacier vs. mountain — ambas têm muito branco e cinza.

Recursos

Código completo e referências

github.com/Jair-pc/puc-minas-arquiteturas_de_deep_learning

Dataset: Intel Image Classification — disponível no Kaggle.

Anterior: MLP Churn Próximo: RNNs