Código Inicial - Lab 07 (Transfer Learning)
🚀 Ponto de Partida
Neste laboratório, você começará com um código básico e irá aperfeiçoá-lo. Copie os dois blocos abaixo e salve-os em sua máquina antes de abrir o Guia de Experimentos (PDF).
1. O Código Principal
Crie um arquivo chamado transfer_learning_starter.py e cole o código abaixo.
Ele contém a rede neural básica (Cenário 0) pré-configurada para salvar seu histórico de experimentos em um CSV.
\"\"\"
Laboratório 07: Transfer Learning com MobileNetV2
Arquivo Starter / Esqueleto com TODOs & Experiment Tracking (CSV)
Instruções:
1. Execute este arquivo como está. Ele possui um modelo inicial "bobo" (treinado do zero).
2. Observe a tabela de histórico e os testes de diagnóstico que vão falhar.
3. Siga o Guia de Laboratório (PDF) para substituir o modelo inicial pelas técnicas
de Transfer Learning (Cenário A, Cenário B e Cenário C).
4. Escreva sua conclusão técnica na variável CONCLUSAO_ALUNO no final do script.
\"\"\"
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import csv
import datetime
import numpy as np
import tensorflow as tf
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, GlobalAveragePooling2D, Dropout
from tensorflow.keras.callbacks import EarlyStopping
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# ==============================================================================
# IDENTIFICAÇÃO DO EXPERIMENTO ATUAL
# ==============================================================================
# Altere esta descrição a cada novo teste para enriquecer seu histórico comparativo!
DESCRICAO_EXPERIMENTO = "Cenário 0 (Rede Básica do Zero)"
print("1. Carregando dataset CIFAR-10...")
(X_train_full, y_train_full), (X_test_full, y_test_full) = tf.keras.datasets.cifar10.load_data()
# Filtrar apenas classe 0 (Avião) e classe 1 (Automóvel)
classes = [0, 1]
mask_train = np.isin(y_train_full.flatten(), classes)
mask_test = np.isin(y_test_full.flatten(), classes)
X_train = X_train_full[mask_train]
y_train = (y_train_full[mask_train].flatten() == 1).astype(int)
X_test = X_test_full[mask_test]
y_test = (y_test_full[mask_test].flatten() == 1).astype(int)
# Otimização para aula: 8.000 imagens para treino, 2.000 para teste
X_train, y_train = X_train[:8000], y_train[:8000]
X_test, y_test = X_test[:2000], y_test[:2000]
print(f" -> Treino: {X_train.shape[0]} imagens | Teste: {X_test.shape[0]} imagens")
print("2. Pré-processamento: Redimensionando para 96x96 e normalizando em [-1, 1]...")
def preprocessar(images):
images_resized = tf.image.resize(images, (96, 96)).numpy()
images_norm = images_resized / 127.5 - 1.0
return images_norm
X_train_proc = preprocessar(X_train)
X_test_proc = preprocessar(X_test)
# ==============================================================================
# SEU CÓDIGO COMEÇA AQUI: ÁREA DE IMPLEMENTAÇÃO DO MODELO
# ==============================================================================
def criar_modelo_transfer():
\"\"\"
O QUE FAZER:
Apague a CNN básica abaixo e siga o GUIA (PDF) para instanciar a
MobileNetV2 pré-treinada, construir o novo classificador e realizar os experimentos!
\"\"\"
# --- MODELO BOBO INICIAL (APAGUE DEPOIS) ---
modelo = Sequential([
Conv2D(16, (3,3), activation='relu', input_shape=(96, 96, 3)),
MaxPooling2D(2,2),
Flatten(),
Dense(16, activation='relu'),
Dense(1, activation='sigmoid')
])
base_model = modelo # Variável fictícia apenas para o código inicial rodar
# -------------------------------------------
modelo.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy']
)
return modelo, base_model
modelo, base = criar_modelo_transfer()
vigilante = EarlyStopping(
monitor='val_loss',
patience=3,
restore_best_weights=True
)
# ==============================================================================
# FIM DO SEU CÓDIGO: TREINAMENTO, EXPERIMENT TRACKING E AVALIAÇÃO
# ==============================================================================
print("\\n3. Treinando classificador...")
historico = modelo.fit(
X_train_proc, y_train,
epochs=15,
batch_size=32,
validation_split=0.2,
callbacks=[vigilante],
verbose=1
)
loss_test, acc_test = modelo.evaluate(X_test_proc, y_test, verbose=0)
def registrar_e_exibir_historico(descricao, modelo, base, loss_t, acc_t, epocas, arquivo_csv="historico_experimentos.csv"):
t_params = sum(tf.keras.backend.count_params(w) for w in modelo.trainable_weights)
c_params = sum(tf.keras.backend.count_params(w) for w in modelo.non_trainable_weights)
if acc_t >= 0.95:
status = "🏆 Ouro"
elif acc_t >= 0.80:
status = "🟢 Aprovado"
else:
status = "🔴 Reprovado"
existe = os.path.exists(arquivo_csv)
with open(arquivo_csv, mode='a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
if not existe:
writer.writerow([
"Timestamp", "Descricao", "Base_Congelada",
"Params_Treinaveis", "Params_Congelados",
"Epocas", "Loss_Teste", "Acuracia_Teste", "Status"
])
writer.writerow([
datetime.datetime.now().strftime("%H:%M:%S"),
descricao,
not base.trainable,
t_params,
c_params,
epocas,
f"{loss_t:.4f}",
f"{acc_t:.2%}",
status
])
print("\\n" + "="*88)
print("📋 PAINEL DE EXPERIMENTOS ACUMULADOS (historico_experimentos.csv)")
print("="*88)
with open(arquivo_csv, mode='r', encoding='utf-8') as f:
reader = list(csv.reader(f))
print(f"{'#':<3} | {'Descrição':<24} | {'Treináveis':<11} | {'Épocas':<6} | {'Loss':<6} | {'Acurácia':<8} | {'Status'}")
print("-" * 88)
for i, row in enumerate(reader[1:], 1):
ts, desc, cong, treina, cong_p, ep, l_val, a_val, st = row
print(f"{i:<3} | {desc:<24} | {int(treina):<11,d} | {ep:<6} | {l_val:<6} | {a_val:<8} | {st}")
print("="*88)
registrar_e_exibir_historico(DESCRICAO_EXPERIMENTO, modelo, base, loss_test, acc_test, len(historico.epoch))
# Diagnóstico visual
preds_prob = modelo.predict(X_test_proc[:10], verbose=0).flatten()
preds_classes = (preds_prob > 0.5).astype(int)
print("\\n🎯 Inspeção das 10 primeiras amostras de teste:")
print("Realidade: ", y_test[:10])
print("Previsão: ", preds_classes)
print("Prob(Auto):", np.round(preds_prob, 3))
# Gráficos
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(historico.history['accuracy'], label='Treino (Acurácia)')
ax1.plot(historico.history['val_accuracy'], label='Validação (Acurácia)')
ax1.set_title('Acurácia')
ax1.legend()
ax2.plot(historico.history['loss'], label='Treino (Loss)', color='red')
ax2.plot(historico.history['val_loss'], label='Validação (Loss)', color='darkred')
ax2.set_title('Loss')
ax2.legend()
plt.tight_layout()
plt.savefig('transfer_learning.png', dpi=150)
plt.close()
# ==============================================================================
# TODO FINAL: ESCREVA SUA CONCLUSÃO TÉCNICA DE ENGENHARIA
# ==============================================================================
CONCLUSAO_ALUNO = \"\"\"
Baseado na tabela de histórico de experimentos (CSV), responda:
1. O que aconteceu quando congelou vs descongelou a base (Cenário A vs B)?
2. Qual foi o impacto nos parâmetros ao usar GlobalAveragePooling2D vs Flatten (Cenário A vs C)?
3. Qual experimento apresentou o melhor custo/benefício de generalização? Por quê?
\"\"\"
print("\\n" + "="*88)
print("🧠 CONCLUSÃO DO ALUNO:")
print(CONCLUSAO_ALUNO.strip())
print("="*88)2. A Bateria de Testes (MLOps)
Crie uma pasta chamada tests e dentro dela crie um arquivo chamado test_transfer.py. Cole o código abaixo.
Nota: Este script faz uma importação do seu modelo (usando src.transfer_learning_starter). Se você rodar este código a partir de outra pasta, ajuste o importe conforme a sua estrutura de diretórios.
import numpy as np
# Importa o modelo treinado do arquivo principal (ajuste o caminho se necessário)
from transfer_learning_starter import (
modelo, base, historico,
X_test_proc, y_test
)
def test_base_congelada():
\"\"\"A base MobileNetV2 DEVE estar congelada.\"\"\"
status = "🟢 CONGELADA (Correto)" if not base.trainable else "🔴 DESCONGELADA (Incorreto)"
print(f"\\n🧠 [DIAGNÓSTICO BACKBONE]")
print(f" Status da base MobileNetV2: {status}")
assert base.trainable == False, (
"\\n❌ BASE DESCONGELADA:\\n"
" A variável `base.trainable` está True!\\n"
" 💡 DICA: Configure `base.trainable = False` para congelar os 2.2M pesos do Google."
)
def test_acuracia_minima():
\"\"\"Transfer Learning deve atingir meta de acurácia (>80%).\"\"\"
_, acc = modelo.evaluate(X_test_proc, y_test, verbose=0)
meta_minima = 0.80
meta_ouro = 0.95
distancia = acc - meta_minima
if acc >= meta_ouro:
selo = "🏆 NÍVEL OURO (Excelente generalização)"
elif acc >= meta_minima:
selo = "🟢 NÍVEL APROVADO (Transfer Learning funcional)"
else:
selo = "🔴 ABAIXO DA META (Necessita ajuste)"
print(f"\\n📊 [DIAGNÓSTICO ACURÁCIA]")
print(f" Acurácia Obtida: {acc:.2%}")
print(f" Meta Mínima: {meta_minima:.2%}")
print(f" Distância da Meta: {distancia:+.2%}")
print(f" Classificação: {selo}")
assert acc >= meta_minima, (
f"\\n❌ ACURÁCIA INSUFICIENTE:\\n"
f" - Acurácia atual: {acc:.2%}\\n"
f" - Meta mínima: {meta_minima:.2%}\\n"
f" - Distância: {distancia:.2%}\\n"
f" 💡 DICA: Verifique a arquitetura do seu classificador."
)
def test_poucos_parametros_treinaveis():
\"\"\"Apenas o topo deve ter parâmetros treináveis (< 1%).\"\"\"
total = modelo.count_params()
congelados = base.count_params()
treinaveis = total - congelados
razao = treinaveis / total
print(f"\\n⚙️ [DIAGNÓSTICO DE PARÂMETROS]")
print(f" Parâmetros Treináveis: {treinaveis:,} ({razao:.2%})")
print(f" Parâmetros Congelados: {congelados:,} ({1 - razao:.2%})")
print(f" Limite Máximo: < 1.00% treináveis")
assert razao < 0.01, (
f"\\n❌ EXCESSO DE PARÂMETROS TREINÁVEIS:\\n"
f" - Treináveis atuais: {treinaveis:,} ({razao:.2%})\\n"
f" - Limite máximo: < 1.00% ({int(total * 0.01):,} parâmetros)\\n"
f" 💡 DICA: Você usou `Flatten()`? Troque por `GlobalAveragePooling2D()`."
)
def test_predicoes_validas():
\"\"\"O modelo deve gerar probabilidades válidas entre 0 e 1.\"\"\"
preds = modelo.predict(X_test_proc[:10], verbose=0).flatten()
print(f"\\n🎯 [DIAGNÓSTICO DE INFERÊNCIA]")
print(f" Amostra de probabilidades: {np.round(preds[:5], 3)}")
assert np.all(preds >= 0.0) and np.all(preds <= 1.0), \\
"Predições fora do intervalo de probabilidade [0, 1] (use ativação sigmoid)."No seu terminal, para rodar as baterias de teste em conjunto com a avaliação CSV, basta digitar:
pytest tests/test_transfer.py -v -s