Python

Artigo 37 — Redes Neurais com TensorFlow e Keras Já leu

13 min de leitura

Artigo 37 — Redes Neurais com TensorFlow e Keras
Redes neurais artificiais são o motor por trás do reconhecimento de imagens, tradução automática, geração de texto e dezenas de outras aplicações que pareciam

Artigo 37 — Redes Neurais com TensorFlow e Keras

Prof. Ricardo Matos Módulo 7 · Data Science e Machine Learning · Artigo 37 de 52


Introdução

Redes neurais artificiais são o motor por trás do reconhecimento de imagens, tradução automática, geração de texto e dezenas de outras aplicações que pareciam impossíveis há uma década. TensorFlow é a plataforma de deep learning mais usada no mundo, e Keras é sua API de alto nível — que permite construir e treinar redes neurais complexas com poucas linhas de código. Neste artigo construímos redes do zero, entendemos seus componentes e as aplicamos em problemas reais.


Instalação

pip install tensorflow
# ou, para Macs com Apple Silicon:
pip install tensorflow-macos tensorflow-metal
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models, callbacks

print(f"TensorFlow: {tf.__version__}")
print(f"GPU disponível: {len(tf.config.list_physical_devices('GPU')) > 0}")

O Neurônio Artificial

Um neurônio artificial recebe entradas, aplica pesos, soma e passa por uma função de ativação:

Entradas    Pesos         Soma + Bias    Ativação    Saída
  x₁  ──── w₁ ─┐
  x₂  ──── w₂ ──┤──► Σ(xᵢwᵢ) + b ──► f(z) ──► ŷ
  x₃  ──── w₃ ─┘
import numpy as np

# Simulando um neurônio manualmente
def neuronio(entradas, pesos, bias, ativacao="relu"):
    z = np.dot(entradas, pesos) + bias

    if ativacao == "relu":
        return max(0, z)
    elif ativacao == "sigmoid":
        return 1 / (1 + np.exp(-z))
    elif ativacao == "tanh":
        return np.tanh(z)
    return z


entradas = np.array([0.5, 0.3, 0.8])
pesos    = np.array([0.4, 0.7, 0.2])
bias     = 0.1

saida = neuronio(entradas, pesos, bias, "relu")
print(f"Saída do neurônio: {saida:.4f}")

Funções de Ativação

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-5, 5, 200)

funcoes = {
    "ReLU":    np.maximum(0, x),
    "Sigmoid": 1 / (1 + np.exp(-x)),
    "Tanh":    np.tanh(x),
    "LeakyReLU": np.where(x > 0, x, 0.1 * x),
}

fig, axes = plt.subplots(1, 4, figsize=(16, 4))
for ax, (nome, y) in zip(axes, funcoes.items()):
    ax.plot(x, y, color="#2E75B6", linewidth=2)
    ax.axhline(0, color="gray", linewidth=0.5)
    ax.axvline(0, color="gray", linewidth=0.5)
    ax.set_title(nome)
    ax.grid(alpha=0.3)

plt.tight_layout()
plt.savefig("funcoes_ativacao.png", dpi=120)
plt.show()

Primeira Rede Neural: Classificação Binária

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# Dataset — previsão de aprovação
np.random.seed(42)
n = 1000

X = np.column_stack([
    np.random.uniform(1, 10, n),   # horas_estudo
    np.random.randint(0, 20, n),   # faltas
    np.random.uniform(3, 10, n),   # nota_anterior
])

y = ((0.5 * X[:, 0] + 0.3 * X[:, 2] - 0.1 * X[:, 1] +
      np.random.normal(0, 0.5, n)) >= 5).astype(int)

# Divisão e normalização
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

scaler  = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)


# Construindo a rede
def construir_modelo(input_dim: int) -> keras.Model:
    modelo = keras.Sequential([
        # Camada de entrada
        layers.Input(shape=(input_dim,)),

        # Camadas ocultas
        layers.Dense(64, activation="relu"),
        layers.BatchNormalization(),
        layers.Dropout(0.3),

        layers.Dense(32, activation="relu"),
        layers.BatchNormalization(),
        layers.Dropout(0.2),

        layers.Dense(16, activation="relu"),

        # Camada de saída — 1 neurônio, sigmoid para binário
        layers.Dense(1, activation="sigmoid")
    ])

    modelo.compile(
        optimizer=keras.optimizers.Adam(learning_rate=0.001),
        loss="binary_crossentropy",
        metrics=["accuracy", keras.metrics.AUC(name="auc")]
    )

    return modelo


modelo = construir_modelo(X_train.shape[1])
modelo.summary()

Treinamento e Callbacks

# Callbacks — comportamentos durante o treinamento
lista_callbacks = [
    # Para o treinamento se não melhorar
    callbacks.EarlyStopping(
        monitor="val_loss",
        patience=15,
        restore_best_weights=True,
        verbose=1
    ),
    # Reduz learning rate quando estagna
    callbacks.ReduceLROnPlateau(
        monitor="val_loss",
        factor=0.5,
        patience=7,
        min_lr=1e-6,
        verbose=1
    ),
    # Salva o melhor modelo
    callbacks.ModelCheckpoint(
        filepath="melhor_modelo.keras",
        monitor="val_auc",
        save_best_only=True,
        mode="max",
        verbose=1
    ),
    # Logs para TensorBoard
    callbacks.TensorBoard(log_dir="./logs", histogram_freq=1),
]


# Treinamento
historico = modelo.fit(
    X_train, y_train,
    epochs=100,
    batch_size=32,
    validation_split=0.2,
    callbacks=lista_callbacks,
    verbose=1
)


# Avaliação no conjunto de teste
loss, acc, auc = modelo.evaluate(X_test, y_test, verbose=0)
print(f"\nResultados no Teste:")
print(f"  Loss:     {loss:.4f}")
print(f"  Acurácia: {acc:.4f}")
print(f"  AUC:      {auc:.4f}")

Visualizando o Treinamento

import matplotlib.pyplot as plt


def plotar_historico(historico):
    fig, axes = plt.subplots(1, 3, figsize=(15, 4))

    metricas = [
        ("loss",     "Loss",     "Loss de Treino vs Validação"),
        ("accuracy", "Acurácia", "Acurácia de Treino vs Validação"),
        ("auc",      "AUC",      "AUC de Treino vs Validação"),
    ]

    for ax, (metrica, ylabel, titulo) in zip(axes, metricas):
        ax.plot(historico.history[metrica],
                label="Treino", color="#2E75B6")
        ax.plot(historico.history[f"val_{metrica}"],
                label="Validação", color="#C00000", linestyle="--")
        ax.set_title(titulo)
        ax.set_xlabel("Época")
        ax.set_ylabel(ylabel)
        ax.legend()
        ax.grid(alpha=0.3)

    plt.tight_layout()
    plt.savefig("historico_treinamento.png", dpi=120)
    plt.show()


plotar_historico(historico)

Regressão com Redes Neurais

# Prevendo nota_final (valor contínuo)
y_reg = 0.4 * X[:, 0] + 0.3 * X[:, 2] - 0.1 * X[:, 1] + np.random.normal(0, 0.3, n)
y_reg = np.clip(y_reg, 0, 10)

X_tr, X_te, y_tr, y_te = train_test_split(X, y_reg, test_size=0.2, random_state=42)
X_tr = scaler.fit_transform(X_tr)
X_te = scaler.transform(X_te)


def modelo_regressao(input_dim: int) -> keras.Model:
    modelo = keras.Sequential([
        layers.Input(shape=(input_dim,)),
        layers.Dense(128, activation="relu"),
        layers.Dropout(0.2),
        layers.Dense(64,  activation="relu"),
        layers.Dropout(0.2),
        layers.Dense(32,  activation="relu"),
        layers.Dense(1)   # sem ativação — saída linear
    ])

    modelo.compile(
        optimizer="adam",
        loss="mse",
        metrics=["mae", keras.metrics.RootMeanSquaredError(name="rmse")]
    )
    return modelo


modelo_reg = modelo_regressao(3)
historico_reg = modelo_reg.fit(
    X_tr, y_tr,
    epochs=100,
    batch_size=32,
    validation_split=0.2,
    callbacks=[callbacks.EarlyStopping(patience=10, restore_best_weights=True)],
    verbose=0
)

mse, mae, rmse = modelo_reg.evaluate(X_te, y_te, verbose=0)
print(f"MSE:  {mse:.4f}")
print(f"MAE:  {mae:.4f}")
print(f"RMSE: {rmse:.4f}")

# Visualizando predições
y_pred_reg = modelo_reg.predict(X_te, verbose=0).flatten()

plt.figure(figsize=(8, 5))
plt.scatter(y_te, y_pred_reg, alpha=0.4, color="#2E75B6")
plt.plot([0, 10], [0, 10], "r--", label="Ideal")
plt.xlabel("Nota Real")
plt.ylabel("Nota Prevista")
plt.title("Real vs Previsto")
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("regressao_nn.png", dpi=120)
plt.show()

API Funcional: Redes Complexas

A API Sequencial é simples, mas a API Funcional permite arquiteturas complexas — múltiplas entradas, saídas ou conexões residuais:

from tensorflow.keras import Input, Model


def construir_rede_funcional():
    # Múltiplas entradas
    entrada_academica  = Input(shape=(3,), name="academica")   # horas, faltas, nota
    entrada_pessoal    = Input(shape=(2,), name="pessoal")     # idade, renda_familiar

    # Ramo acadêmico
    x1 = layers.Dense(32, activation="relu")(entrada_academica)
    x1 = layers.Dropout(0.2)(x1)
    x1 = layers.Dense(16, activation="relu")(x1)

    # Ramo pessoal
    x2 = layers.Dense(16, activation="relu")(entrada_pessoal)
    x2 = layers.Dense(8,  activation="relu")(x2)

    # Combinando os ramos
    combinado = layers.Concatenate()([x1, x2])
    x         = layers.Dense(32, activation="relu")(combinado)
    x         = layers.Dropout(0.3)(x)

    # Múltiplas saídas
    saida_aprovado = layers.Dense(1, activation="sigmoid", name="aprovado")(x)
    saida_nota     = layers.Dense(1, activation="linear",  name="nota")(x)

    modelo = Model(
        inputs=[entrada_academica, entrada_pessoal],
        outputs=[saida_aprovado, saida_nota]
    )

    modelo.compile(
        optimizer="adam",
        loss={
            "aprovado": "binary_crossentropy",
            "nota":     "mse"
        },
        loss_weights={"aprovado": 1.0, "nota": 0.5},
        metrics={
            "aprovado": ["accuracy"],
            "nota":     ["mae"]
        }
    )

    return modelo


modelo_funcional = construir_rede_funcional()
keras.utils.plot_model(
    modelo_funcional,
    to_file="arquitetura.png",
    show_shapes=True,
    dpi=80
)
modelo_funcional.summary()

Regularização: Evitando Overfitting

from tensorflow.keras import regularizers


def modelo_regularizado(input_dim: int) -> keras.Model:
    return keras.Sequential([
        layers.Input(shape=(input_dim,)),

        # L2 regularization — penaliza pesos grandes
        layers.Dense(128, activation="relu",
                     kernel_regularizer=regularizers.l2(0.001)),
        layers.BatchNormalization(),
        layers.Dropout(0.4),

        # L1 regularization — incentiva esparsidade
        layers.Dense(64, activation="relu",
                     kernel_regularizer=regularizers.l1(0.001)),
        layers.Dropout(0.3),

        # Combinação L1 + L2 (Elastic Net)
        layers.Dense(32, activation="relu",
                     kernel_regularizer=regularizers.l1_l2(l1=0.001, l2=0.001)),

        layers.Dense(1, activation="sigmoid")
    ])


# Comparando modelos com e sem regularização
historicos = {}

for nome, model in [
    ("Sem regularização", construir_modelo(3)),
    ("Com regularização", modelo_regularizado(3)),
]:
    h = model.fit(
        X_train, y_train,
        epochs=50,
        batch_size=32,
        validation_split=0.2,
        callbacks=[callbacks.EarlyStopping(patience=10, restore_best_weights=True)],
        verbose=0
    )
    historicos[nome] = h
    loss, acc, auc = model.evaluate(X_test, y_test, verbose=0)
    print(f"{nome:25} | Acc: {acc:.4f} | AUC: {auc:.4f}")

Transfer Learning

from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input



def modelo_transfer_learning(num_classes: int) -> keras.Model:
    """
    Usa MobileNetV2 pré-treinado no ImageNet como extrator de features.
    Útil quando o dataset próprio é pequeno.
    """
    # Base pré-treinada — congela os pesos
    base = MobileNetV2(
        weights="imagenet",
        include_top=False,        # remove a camada de classificação
        input_shape=(224, 224, 3)
    )
    base.trainable = False   # congela

    # Adiciona cabeçalho de classificação customizado
    modelo = keras.Sequential([
        base,
        layers.GlobalAveragePooling2D(),
        layers.Dense(128, activation="relu"),
        layers.Dropout(0.3),
        layers.Dense(num_classes, activation="softmax")
    ])

    modelo.compile(
        optimizer=keras.optimizers.Adam(1e-3),
        loss="categorical_crossentropy",
        metrics=["accuracy"]
    )

    return modelo


# Fine-tuning — descongela as últimas camadas da base
def fine_tuning(modelo_base: keras.Model, num_layers_descongelar: int = 20):
    base = modelo_base.layers[0]
    base.trainable = True

    # Congela tudo exceto as últimas N camadas
    for layer in base.layers[:-num_layers_descongelar]:
        layer.trainable = False

    modelo_base.compile(
        optimizer=keras.optimizers.Adam(1e-5),  # lr menor para fine-tuning
        loss="categorical_crossentropy",
        metrics=["accuracy"]
    )
    return modelo_base

Salvando e Carregando Modelos

# Formato recomendado — .keras (nativo Keras 3)
modelo.save("modelo_completo.keras")
modelo_carregado = keras.models.load_model("modelo_completo.keras")

# Apenas os pesos
modelo.save_weights("pesos.weights.h5")
modelo_novo = construir_modelo(3)
modelo_novo.load_weights("pesos.weights.h5")

# Exportando para TensorFlow SavedModel (para TensorFlow Serving)
modelo.export("saved_model/")

# Convertendo para TensorFlow Lite (mobile e edge devices)
converter = tf.lite.TFLiteConverter.from_keras_model(modelo)
tflite_model = converter.convert()
with open("modelo.tflite", "wb") as f:
    f.write(tflite_model)

print("Modelos salvos com sucesso.")

Exemplo Completo: Pipeline Completo de Deep Learning

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, callbacks
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt


class PipelineDeepLearning:
    """Pipeline completo de classificação com rede neural."""

    def __init__(self, nome: str = "modelo"):
        self.nome      = nome
        self.scaler    = StandardScaler()
        self.modelo    = None
        self.historico = None

    def preparar_dados(self, X, y, test_size=0.2, val_size=0.2):
        X_tr, X_te, y_tr, y_te = train_test_split(
            X, y, test_size=test_size,
            random_state=42, stratify=y
        )
        X_tr = self.scaler.fit_transform(X_tr)
        X_te = self.scaler.transform(X_te)
        self._X_test = X_te
        self._y_test = y_te
        return X_tr, X_te, y_tr, y_te

    def construir(self, input_dim: int, camadas: list = None):
        camadas = camadas or [64, 32, 16]

        entradas = layers.Input(shape=(input_dim,))
        x        = entradas

        for unidades in camadas:
            x = layers.Dense(unidades, activation="relu")(x)
            x = layers.BatchNormalization()(x)
            x = layers.Dropout(0.3)(x)

        saida = layers.Dense(1, activation="sigmoid")(x)
        self.modelo = keras.Model(entradas, saida, name=self.nome)
        self.modelo.compile(
            optimizer=keras.optimizers.Adam(0.001),
            loss="binary_crossentropy",
            metrics=["accuracy", keras.metrics.AUC(name="auc")]
        )
        return self

    def treinar(self, X_train, y_train, epochs=100, batch_size=32):
        self.historico = self.modelo.fit(
            X_train, y_train,
            epochs=epochs,
            batch_size=batch_size,
            validation_split=0.2,
            callbacks=[
                callbacks.EarlyStopping(patience=15, restore_best_weights=True),
                callbacks.ReduceLROnPlateau(patience=7, factor=0.5, verbose=0)
            ],
            verbose=0
        )
        epocas_treinadas = len(self.historico.history["loss"])
        print(f"Treinado por {epocas_treinadas} épocas.")
        return self

    def avaliar(self):
        y_pred_prob = self.modelo.predict(self._X_test, verbose=0).flatten()
        y_pred      = (y_pred_prob >= 0.5).astype(int)

        loss, acc, auc = self.modelo.evaluate(self._X_test, self._y_test, verbose=0)
        print(f"\n{'='*40}")
        print(f"Modelo: {self.nome}")
        print(f"  Loss:     {loss:.4f}")
        print(f"  Acurácia: {acc:.4f}")
        print(f"  AUC:      {auc:.4f}")
        print(classification_report(
            self._y_test, y_pred,
            target_names=["Reprovado", "Aprovado"]
        ))

    def salvar(self):
        self.modelo.save(f"{self.nome}.keras")
        import joblib
        joblib.dump(self.scaler, f"{self.nome}_scaler.pkl")
        print(f"Pipeline salvo: {self.nome}.keras")

    def prever(self, X_novo):
        X_proc = self.scaler.transform(X_novo)
        prob   = self.modelo.predict(X_proc, verbose=0).flatten()
        return prob


# Usando o pipeline
np.random.seed(42)
n  = 2000
X  = np.column_stack([
    np.random.uniform(1, 10, n),
    np.random.randint(0, 20, n),
    np.random.uniform(3, 10, n),
    np.random.uniform(0, 5,  n),
])
y = ((0.5*X[:,0] + 0.3*X[:,2] - 0.1*X[:,1] + 0.2*X[:,3]
      + np.random.normal(0, 0.5, n)) >= 5).astype(int)

pipeline = PipelineDeepLearning("aprovacao_nn")
X_tr, X_te, y_tr, y_te = pipeline.preparar_dados(X, y)
pipeline.construir(input_dim=4, camadas=[128, 64, 32])
pipeline.treinar(X_tr, y_tr, epochs=150)
pipeline.avaliar()
pipeline.salvar()

# Prevendo novos alunos
novos = np.array([
    [8.0, 2,  9.0, 4.0],   # perfil forte
    [2.0, 18, 4.0, 0.5],   # perfil fraco
])
probs = pipeline.prever(novos)
for i, p in enumerate(probs, 1):
    print(f"Aluno {i}: {p:.1%} de probabilidade de aprovação")

Resumo

  • Redes neurais são empilhamentos de camadas densas com funções de ativação não-lineares
  • Sequential é adequada para arquiteturas lineares; a API Funcional permite múltiplas entradas e saídas
  • BatchNormalization estabiliza o treinamento; Dropout previne overfitting
  • EarlyStopping e ReduceLROnPlateau automatizam o controle do treinamento
  • Regressão usa ativação linear na saída e loss MSE; classificação usa sigmoid/softmax e cross-entropy
  • Transfer Learning reaproveita modelos pré-treinados — essencial quando os dados são escassos
  • Salve sempre o pipeline completo incluindo o scaler — necessário para predições em produção
  • TensorBoard visualiza métricas, grafos e distribuições de pesos em tempo real

Referências e Leituras Complementares

  • TensorFlow — documentação oficial — https://www.tensorflow.org/api_docs/python/tf
  • Keras — guia do desenvolvedor — https://keras.io/guides/
  • TensorFlow Playground — visualização interativa — https://playground.tensorflow.org/
  • Transfer Learning com Keras — https://keras.io/guides/transfer_learning/
  • GÉRON, Aurélien. Hands-On Machine Learning with Scikit-Learn, Keras and TensorFlow. 3. ed. O'Reilly Media, 2022. Cap. 10–16 — redes neurais do básico ao avançado.
  • CHOLLET, François. Deep Learning with Python. 2. ed. Manning, 2021. — escrito pelo criador do Keras, referência definitiva sobre deep learning prático.
  • GOODFELLOW, Ian; BENGIO, Yoshua; COURVILLE, Aaron. Deep Learning. MIT Press, 2016. — fundamentos teóricos do deep learning, disponível em https://www.deeplearningbook.org/

Prof. Ricardo Matos — Dominando o Python em 1 Ano · Artigo 37 de 52 Próximo: Artigo 38 — Processamento de Linguagem Natural (NLP)


you asked

Sim


claude response

Dominando o Python em 1 Ano

Comentários

Mais em Python

Decoradores e Metaprogramação
Decoradores e Metaprogramação

Decoradores são um dos recursos mais elegantes do Python. Eles permite...

Automação de Planilhas e PDFs
Automação de Planilhas e PDFs

Planilhas e PDFs são dois dos formatos mais onipresentes no ambiente corporat...

Variáveis e Tipos de Dados
Variáveis e Tipos de Dados

Todo programa manipula informação. Para isso, a linguagem preci...