Spaces:
Sleeping
Sleeping
Crie o arquivo app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import tensorflow as tf
|
| 3 |
+
from tensorflow.keras import datasets, layers, models
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
import numpy as np
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import time
|
| 8 |
+
|
| 9 |
+
# Configuração da página
|
| 10 |
+
st.set_page_config(
|
| 11 |
+
page_title="Classificador de Imagens CIFAR-10",
|
| 12 |
+
page_icon="🖼️",
|
| 13 |
+
layout="wide"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Título principal
|
| 17 |
+
st.title("🖼️ Classificador de Imagens com Deep Learning")
|
| 18 |
+
st.markdown("---")
|
| 19 |
+
|
| 20 |
+
# Sidebar com informações
|
| 21 |
+
st.sidebar.title("📊 Informações do Projeto")
|
| 22 |
+
st.sidebar.info("""
|
| 23 |
+
**Problema de Negócio:**
|
| 24 |
+
|
| 25 |
+
Construir um modelo de Inteligência Artificial capaz de classificar imagens
|
| 26 |
+
considerando 10 categorias:
|
| 27 |
+
- ✈️ Airplane
|
| 28 |
+
- 🚗 Automobile
|
| 29 |
+
- 🐦 Bird
|
| 30 |
+
- 🐱 Cat
|
| 31 |
+
- 🦌 Deer
|
| 32 |
+
- 🐕 Dog
|
| 33 |
+
- 🐸 Frog
|
| 34 |
+
- 🐴 Horse
|
| 35 |
+
- 🚢 Ship
|
| 36 |
+
- 🚛 Truck
|
| 37 |
+
""")
|
| 38 |
+
|
| 39 |
+
st.sidebar.markdown("---")
|
| 40 |
+
st.sidebar.markdown("### 🔧 Tecnologias Utilizadas")
|
| 41 |
+
st.sidebar.markdown("""
|
| 42 |
+
- Python
|
| 43 |
+
- TensorFlow/Keras
|
| 44 |
+
- Streamlit
|
| 45 |
+
- NumPy
|
| 46 |
+
- Matplotlib
|
| 47 |
+
- PIL
|
| 48 |
+
""")
|
| 49 |
+
|
| 50 |
+
# Classes das imagens
|
| 51 |
+
nomes_classes = ['airplane', 'automobile', 'bird', 'cat', 'deer',
|
| 52 |
+
'dog', 'frog', 'horse', 'ship', 'truck']
|
| 53 |
+
|
| 54 |
+
# Emojis para cada classe
|
| 55 |
+
emojis_classes = {
|
| 56 |
+
'airplane': '✈️',
|
| 57 |
+
'automobile': '🚗',
|
| 58 |
+
'bird': '🐦',
|
| 59 |
+
'cat': '🐱',
|
| 60 |
+
'deer': '🦌',
|
| 61 |
+
'dog': '🐕',
|
| 62 |
+
'frog': '🐸',
|
| 63 |
+
'horse': '🐴',
|
| 64 |
+
'ship': '🚢',
|
| 65 |
+
'truck': '🚛'
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
# Função para carregar dados
|
| 69 |
+
@st.cache_data
|
| 70 |
+
def carregar_dados():
|
| 71 |
+
"""Carrega e pré-processa o dataset CIFAR-10"""
|
| 72 |
+
(imagens_treino, labels_treino), (imagens_teste, labels_teste) = datasets.cifar10.load_data()
|
| 73 |
+
|
| 74 |
+
# Normaliza os valores dos pixels
|
| 75 |
+
imagens_treino = imagens_treino / 255.0
|
| 76 |
+
imagens_teste = imagens_teste / 255.0
|
| 77 |
+
|
| 78 |
+
return (imagens_treino, labels_treino), (imagens_teste, labels_teste)
|
| 79 |
+
|
| 80 |
+
# Função para visualizar imagens
|
| 81 |
+
def visualiza_imagens(images, labels, num_imagens=25):
|
| 82 |
+
"""Cria uma figura com múltiplas imagens do dataset"""
|
| 83 |
+
fig, axes = plt.subplots(5, 5, figsize=(10, 10))
|
| 84 |
+
fig.suptitle('Exemplos do Dataset CIFAR-10', fontsize=16)
|
| 85 |
+
|
| 86 |
+
for i, ax in enumerate(axes.flat):
|
| 87 |
+
if i < num_imagens:
|
| 88 |
+
ax.imshow(images[i])
|
| 89 |
+
ax.set_title(nomes_classes[labels[i][0]], fontsize=8)
|
| 90 |
+
ax.axis('off')
|
| 91 |
+
|
| 92 |
+
plt.tight_layout()
|
| 93 |
+
return fig
|
| 94 |
+
|
| 95 |
+
# Função para criar o modelo
|
| 96 |
+
@st.cache_resource
|
| 97 |
+
def criar_modelo():
|
| 98 |
+
"""Cria a arquitetura da rede neural convolucional"""
|
| 99 |
+
modelo = models.Sequential([
|
| 100 |
+
# Primeiro bloco convolucional
|
| 101 |
+
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
|
| 102 |
+
layers.MaxPooling2D((2, 2)),
|
| 103 |
+
|
| 104 |
+
# Segundo bloco convolucional
|
| 105 |
+
layers.Conv2D(128, (3, 3), activation='relu'),
|
| 106 |
+
layers.MaxPooling2D((2, 2)),
|
| 107 |
+
|
| 108 |
+
# Terceiro bloco convolucional
|
| 109 |
+
layers.Conv2D(64, (3, 3), activation='relu'),
|
| 110 |
+
layers.MaxPooling2D((2, 2)),
|
| 111 |
+
|
| 112 |
+
# Camadas de classificação
|
| 113 |
+
layers.Flatten(),
|
| 114 |
+
layers.Dense(64, activation='relu'),
|
| 115 |
+
layers.Dense(10, activation='softmax')
|
| 116 |
+
])
|
| 117 |
+
|
| 118 |
+
modelo.compile(
|
| 119 |
+
optimizer='adam',
|
| 120 |
+
loss='sparse_categorical_crossentropy',
|
| 121 |
+
metrics=['accuracy']
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
return modelo
|
| 125 |
+
|
| 126 |
+
# Função para treinar o modelo
|
| 127 |
+
def treinar_modelo(modelo, imagens_treino, labels_treino, imagens_teste, labels_teste, epochs=10):
|
| 128 |
+
"""Treina o modelo com os dados fornecidos"""
|
| 129 |
+
history = modelo.fit(
|
| 130 |
+
imagens_treino,
|
| 131 |
+
labels_treino,
|
| 132 |
+
epochs=epochs,
|
| 133 |
+
validation_data=(imagens_teste, labels_teste),
|
| 134 |
+
verbose=0
|
| 135 |
+
)
|
| 136 |
+
return history
|
| 137 |
+
|
| 138 |
+
# Função para processar imagem do usuário
|
| 139 |
+
def processar_imagem_usuario(imagem_file):
|
| 140 |
+
"""Processa a imagem enviada pelo usuário"""
|
| 141 |
+
imagem = Image.open(imagem_file)
|
| 142 |
+
|
| 143 |
+
# Redimensiona para 32x32
|
| 144 |
+
imagem_redimensionada = imagem.resize((32, 32))
|
| 145 |
+
|
| 146 |
+
# Converte para array e normaliza
|
| 147 |
+
imagem_array = np.array(imagem_redimensionada) / 255.0
|
| 148 |
+
|
| 149 |
+
# Expande dimensão
|
| 150 |
+
imagem_array = np.expand_dims(imagem_array, axis=0)
|
| 151 |
+
|
| 152 |
+
return imagem_redimensionada, imagem_array
|
| 153 |
+
|
| 154 |
+
# Função para criar gráficos de treinamento
|
| 155 |
+
def plotar_historico(history):
|
| 156 |
+
"""Cria gráficos de acurácia e perda durante o treinamento"""
|
| 157 |
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
|
| 158 |
+
|
| 159 |
+
# Gráfico de acurácia
|
| 160 |
+
ax1.plot(history.history['accuracy'], label='Treino', marker='o')
|
| 161 |
+
ax1.plot(history.history['val_accuracy'], label='Validação', marker='s')
|
| 162 |
+
ax1.set_title('Acurácia do Modelo')
|
| 163 |
+
ax1.set_xlabel('Época')
|
| 164 |
+
ax1.set_ylabel('Acurácia')
|
| 165 |
+
ax1.legend()
|
| 166 |
+
ax1.grid(True)
|
| 167 |
+
|
| 168 |
+
# Gráfico de perda
|
| 169 |
+
ax2.plot(history.history['loss'], label='Treino', marker='o')
|
| 170 |
+
ax2.plot(history.history['val_loss'], label='Validação', marker='s')
|
| 171 |
+
ax2.set_title('Perda do Modelo')
|
| 172 |
+
ax2.set_xlabel('Época')
|
| 173 |
+
ax2.set_ylabel('Perda')
|
| 174 |
+
ax2.legend()
|
| 175 |
+
ax2.grid(True)
|
| 176 |
+
|
| 177 |
+
plt.tight_layout()
|
| 178 |
+
return fig
|
| 179 |
+
|
| 180 |
+
# Abas principais
|
| 181 |
+
tab1, tab2, tab3, tab4 = st.tabs([
|
| 182 |
+
"📚 Dados",
|
| 183 |
+
"🏗️ Modelo",
|
| 184 |
+
"📈 Treinamento",
|
| 185 |
+
"🎯 Predição"
|
| 186 |
+
])
|
| 187 |
+
|
| 188 |
+
# ==================== ABA 1: DADOS ====================
|
| 189 |
+
with tab1:
|
| 190 |
+
st.header("📚 Exploração dos Dados")
|
| 191 |
+
|
| 192 |
+
st.markdown("""
|
| 193 |
+
### Dataset CIFAR-10
|
| 194 |
+
|
| 195 |
+
O **CIFAR-10** é um dataset clássico de visão computacional que contém 60.000 imagens
|
| 196 |
+
coloridas de 32x32 pixels em 10 classes diferentes, com 6.000 imagens por classe.
|
| 197 |
+
|
| 198 |
+
- **50.000 imagens de treino**
|
| 199 |
+
- **10.000 imagens de teste**
|
| 200 |
+
- **10 classes balanceadas**
|
| 201 |
+
""")
|
| 202 |
+
|
| 203 |
+
if st.button("🔄 Carregar Dataset CIFAR-10", key="carregar_dados"):
|
| 204 |
+
with st.spinner("Carregando dados..."):
|
| 205 |
+
(imagens_treino, labels_treino), (imagens_teste, labels_teste) = carregar_dados()
|
| 206 |
+
st.session_state.dados_carregados = True
|
| 207 |
+
st.session_state.imagens_treino = imagens_treino
|
| 208 |
+
st.session_state.labels_treino = labels_treino
|
| 209 |
+
st.session_state.imagens_teste = imagens_teste
|
| 210 |
+
st.session_state.labels_teste = labels_teste
|
| 211 |
+
|
| 212 |
+
st.success("✅ Dados carregados com sucesso!")
|
| 213 |
+
|
| 214 |
+
# Mostrar estatísticas
|
| 215 |
+
col1, col2, col3 = st.columns(3)
|
| 216 |
+
|
| 217 |
+
with col1:
|
| 218 |
+
st.metric("Imagens de Treino", f"{len(imagens_treino):,}")
|
| 219 |
+
with col2:
|
| 220 |
+
st.metric("Imagens de Teste", f"{len(imagens_teste):,}")
|
| 221 |
+
with col3:
|
| 222 |
+
st.metric("Classes", len(nomes_classes))
|
| 223 |
+
|
| 224 |
+
# Visualização das imagens
|
| 225 |
+
if st.session_state.get('dados_carregados', False):
|
| 226 |
+
st.markdown("---")
|
| 227 |
+
st.subheader("🖼️ Exemplos de Imagens do Dataset")
|
| 228 |
+
|
| 229 |
+
fig = visualiza_imagens(
|
| 230 |
+
st.session_state.imagens_treino,
|
| 231 |
+
st.session_state.labels_treino
|
| 232 |
+
)
|
| 233 |
+
st.pyplot(fig)
|
| 234 |
+
|
| 235 |
+
# Informações detalhadas
|
| 236 |
+
with st.expander("📊 Informações Detalhadas dos Dados"):
|
| 237 |
+
col1, col2 = st.columns(2)
|
| 238 |
+
|
| 239 |
+
with col1:
|
| 240 |
+
st.markdown("**Dados de Treino:**")
|
| 241 |
+
st.write(f"- Shape: {st.session_state.imagens_treino.shape}")
|
| 242 |
+
st.write(f"- Tipo: {st.session_state.imagens_treino.dtype}")
|
| 243 |
+
st.write(f"- Valores min/max: {st.session_state.imagens_treino.min():.2f} / {st.session_state.imagens_treino.max():.2f}")
|
| 244 |
+
|
| 245 |
+
with col2:
|
| 246 |
+
st.markdown("**Dados de Teste:**")
|
| 247 |
+
st.write(f"- Shape: {st.session_state.imagens_teste.shape}")
|
| 248 |
+
st.write(f"- Tipo: {st.session_state.imagens_teste.dtype}")
|
| 249 |
+
st.write(f"- Valores min/max: {st.session_state.imagens_teste.min():.2f} / {st.session_state.imagens_teste.max():.2f}")
|
| 250 |
+
|
| 251 |
+
# ==================== ABA 2: MODELO ====================
|
| 252 |
+
with tab2:
|
| 253 |
+
st.header("🏗️ Arquitetura do Modelo")
|
| 254 |
+
|
| 255 |
+
st.markdown("""
|
| 256 |
+
### Rede Neural Convolucional (CNN)
|
| 257 |
+
|
| 258 |
+
O modelo utiliza uma arquitetura de **Rede Neural Convolucional**,
|
| 259 |
+
ideal para processamento de imagens. A arquitetura consiste em:
|
| 260 |
+
|
| 261 |
+
1. **Camadas Convolucionais**: Extraem características das imagens
|
| 262 |
+
2. **Camadas de Pooling**: Reduzem a dimensionalidade
|
| 263 |
+
3. **Camadas Densas**: Realizam a classificação final
|
| 264 |
+
""")
|
| 265 |
+
|
| 266 |
+
if st.button("🔨 Criar Modelo", key="criar_modelo"):
|
| 267 |
+
with st.spinner("Criando modelo..."):
|
| 268 |
+
modelo = criar_modelo()
|
| 269 |
+
st.session_state.modelo = modelo
|
| 270 |
+
st.session_state.modelo_criado = True
|
| 271 |
+
|
| 272 |
+
st.success("✅ Modelo criado com sucesso!")
|
| 273 |
+
|
| 274 |
+
if st.session_state.get('modelo_criado', False):
|
| 275 |
+
st.markdown("---")
|
| 276 |
+
st.subheader("📋 Sumário da Arquitetura")
|
| 277 |
+
|
| 278 |
+
# Captura o sumário do modelo
|
| 279 |
+
from io import StringIO
|
| 280 |
+
import sys
|
| 281 |
+
|
| 282 |
+
buffer = StringIO()
|
| 283 |
+
st.session_state.modelo.summary(print_fn=lambda x: buffer.write(x + '\n'))
|
| 284 |
+
summary_string = buffer.getvalue()
|
| 285 |
+
|
| 286 |
+
st.code(summary_string, language='text')
|
| 287 |
+
|
| 288 |
+
# Visualização da arquitetura
|
| 289 |
+
with st.expander("🔍 Detalhes das Camadas"):
|
| 290 |
+
st.markdown("""
|
| 291 |
+
**Camada 1 - Conv2D (32 filtros, 3x3):**
|
| 292 |
+
- Primeira camada convolucional
|
| 293 |
+
- Extrai 32 características diferentes
|
| 294 |
+
- Ativação ReLU
|
| 295 |
+
|
| 296 |
+
**Camada 2 - MaxPooling2D (2x2):**
|
| 297 |
+
- Reduz dimensionalidade pela metade
|
| 298 |
+
- Mantém características mais importantes
|
| 299 |
+
|
| 300 |
+
**Camada 3 - Conv2D (128 filtros, 3x3):**
|
| 301 |
+
- Segunda camada convolucional
|
| 302 |
+
- Extrai características mais complexas
|
| 303 |
+
|
| 304 |
+
**Camada 4 - MaxPooling2D (2x2):**
|
| 305 |
+
- Segunda redução de dimensionalidade
|
| 306 |
+
|
| 307 |
+
**Camada 5 - Conv2D (64 filtros, 3x3):**
|
| 308 |
+
- Terceira camada convolucional
|
| 309 |
+
- Refinamento de características
|
| 310 |
+
|
| 311 |
+
**Camada 6 - MaxPooling2D (2x2):**
|
| 312 |
+
- Terceira redução de dimensionalidade
|
| 313 |
+
|
| 314 |
+
**Camada 7 - Flatten:**
|
| 315 |
+
- Converte matriz em vetor
|
| 316 |
+
|
| 317 |
+
**Camada 8 - Dense (64 neurônios):**
|
| 318 |
+
- Camada totalmente conectada
|
| 319 |
+
- Ativação ReLU
|
| 320 |
+
|
| 321 |
+
**Camada 9 - Dense (10 neurônios):**
|
| 322 |
+
- Camada de saída
|
| 323 |
+
- Ativação Softmax (probabilidades)
|
| 324 |
+
- Uma saída para cada classe
|
| 325 |
+
""")
|
| 326 |
+
|
| 327 |
+
# Informações sobre parâmetros
|
| 328 |
+
trainable_params = np.sum([np.prod(v.shape) for v in st.session_state.modelo.trainable_weights])
|
| 329 |
+
|
| 330 |
+
col1, col2, col3 = st.columns(3)
|
| 331 |
+
with col1:
|
| 332 |
+
st.metric("Parâmetros Treináveis", f"{trainable_params:,}")
|
| 333 |
+
with col2:
|
| 334 |
+
st.metric("Camadas", len(st.session_state.modelo.layers))
|
| 335 |
+
with col3:
|
| 336 |
+
st.metric("Tamanho Input", "32x32x3")
|
| 337 |
+
|
| 338 |
+
# ==================== ABA 3: TREINAMENTO ====================
|
| 339 |
+
with tab3:
|
| 340 |
+
st.header("📈 Treinamento do Modelo")
|
| 341 |
+
|
| 342 |
+
st.markdown("""
|
| 343 |
+
### Processo de Treinamento
|
| 344 |
+
|
| 345 |
+
Durante o treinamento, o modelo aprende a identificar padrões nas imagens
|
| 346 |
+
através de múltiplas iterações (épocas) sobre os dados de treino.
|
| 347 |
+
|
| 348 |
+
**Métricas monitoradas:**
|
| 349 |
+
- **Acurácia**: Percentual de predições corretas
|
| 350 |
+
- **Perda (Loss)**: Medida de erro do modelo
|
| 351 |
+
""")
|
| 352 |
+
|
| 353 |
+
# Verificar pré-requisitos
|
| 354 |
+
if not st.session_state.get('dados_carregados', False):
|
| 355 |
+
st.warning("⚠️ Por favor, carregue os dados primeiro na aba 'Dados'")
|
| 356 |
+
elif not st.session_state.get('modelo_criado', False):
|
| 357 |
+
st.warning("⚠️ Por favor, crie o modelo primeiro na aba 'Modelo'")
|
| 358 |
+
else:
|
| 359 |
+
st.markdown("---")
|
| 360 |
+
|
| 361 |
+
col1, col2 = st.columns(2)
|
| 362 |
+
|
| 363 |
+
with col1:
|
| 364 |
+
epochs = st.slider(
|
| 365 |
+
"Número de Épocas",
|
| 366 |
+
min_value=1,
|
| 367 |
+
max_value=20,
|
| 368 |
+
value=10,
|
| 369 |
+
help="Número de vezes que o modelo verá todos os dados de treino"
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
with col2:
|
| 373 |
+
st.markdown("### ⚙️ Configurações")
|
| 374 |
+
st.write(f"- **Otimizador**: Adam")
|
| 375 |
+
st.write(f"- **Função de Perda**: Sparse Categorical Crossentropy")
|
| 376 |
+
st.write(f"- **Métrica**: Accuracy")
|
| 377 |
+
|
| 378 |
+
if st.button("🚀 Iniciar Treinamento", key="treinar"):
|
| 379 |
+
|
| 380 |
+
progress_bar = st.progress(0)
|
| 381 |
+
status_text = st.empty()
|
| 382 |
+
|
| 383 |
+
# Container para métricas em tempo real
|
| 384 |
+
metrics_container = st.empty()
|
| 385 |
+
|
| 386 |
+
start_time = time.time()
|
| 387 |
+
|
| 388 |
+
# Treinar o modelo
|
| 389 |
+
with st.spinner("Treinando modelo..."):
|
| 390 |
+
history = treinar_modelo(
|
| 391 |
+
st.session_state.modelo,
|
| 392 |
+
st.session_state.imagens_treino,
|
| 393 |
+
st.session_state.labels_treino,
|
| 394 |
+
st.session_state.imagens_teste,
|
| 395 |
+
st.session_state.labels_teste,
|
| 396 |
+
epochs=epochs
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
st.session_state.history = history
|
| 400 |
+
st.session_state.modelo_treinado = True
|
| 401 |
+
|
| 402 |
+
end_time = time.time()
|
| 403 |
+
training_time = end_time - start_time
|
| 404 |
+
|
| 405 |
+
progress_bar.progress(100)
|
| 406 |
+
status_text.success(f"✅ Treinamento concluído em {training_time:.2f} segundos!")
|
| 407 |
+
|
| 408 |
+
# Mostrar métricas finais
|
| 409 |
+
final_train_acc = history.history['accuracy'][-1]
|
| 410 |
+
final_val_acc = history.history['val_accuracy'][-1]
|
| 411 |
+
final_train_loss = history.history['loss'][-1]
|
| 412 |
+
final_val_loss = history.history['val_loss'][-1]
|
| 413 |
+
|
| 414 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 415 |
+
|
| 416 |
+
with col1:
|
| 417 |
+
st.metric("Acurácia Treino", f"{final_train_acc:.2%}")
|
| 418 |
+
with col2:
|
| 419 |
+
st.metric("Acurácia Validação", f"{final_val_acc:.2%}")
|
| 420 |
+
with col3:
|
| 421 |
+
st.metric("Perda Treino", f"{final_train_loss:.4f}")
|
| 422 |
+
with col4:
|
| 423 |
+
st.metric("Perda Validação", f"{final_val_loss:.4f}")
|
| 424 |
+
|
| 425 |
+
# Mostrar gráficos se já treinado
|
| 426 |
+
if st.session_state.get('modelo_treinado', False):
|
| 427 |
+
st.markdown("---")
|
| 428 |
+
st.subheader("📊 Evolução do Treinamento")
|
| 429 |
+
|
| 430 |
+
fig = plotar_historico(st.session_state.history)
|
| 431 |
+
st.pyplot(fig)
|
| 432 |
+
|
| 433 |
+
with st.expander("💡 Interpretação dos Gráficos"):
|
| 434 |
+
st.markdown("""
|
| 435 |
+
**Gráfico de Acurácia:**
|
| 436 |
+
- Mostra como a precisão do modelo melhora ao longo das épocas
|
| 437 |
+
- Idealmente, ambas as curvas devem crescer juntas
|
| 438 |
+
- Se a curva de validação estagna ou cai, pode indicar overfitting
|
| 439 |
+
|
| 440 |
+
**Gráfico de Perda:**
|
| 441 |
+
- Mostra como o erro do modelo diminui ao longo das épocas
|
| 442 |
+
- Valores menores indicam melhor desempenho
|
| 443 |
+
- A diferença entre treino e validação indica generalização
|
| 444 |
+
""")
|
| 445 |
+
|
| 446 |
+
# Avaliação final
|
| 447 |
+
st.markdown("---")
|
| 448 |
+
st.subheader("🎯 Avaliação Final")
|
| 449 |
+
|
| 450 |
+
if st.button("📊 Avaliar Modelo no Conjunto de Teste"):
|
| 451 |
+
with st.spinner("Avaliando modelo..."):
|
| 452 |
+
erro_teste, acc_teste = st.session_state.modelo.evaluate(
|
| 453 |
+
st.session_state.imagens_teste,
|
| 454 |
+
st.session_state.labels_teste,
|
| 455 |
+
verbose=0
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
col1, col2 = st.columns(2)
|
| 459 |
+
|
| 460 |
+
with col1:
|
| 461 |
+
st.metric(
|
| 462 |
+
"Acurácia no Teste",
|
| 463 |
+
f"{acc_teste:.2%}",
|
| 464 |
+
help="Percentual de imagens classificadas corretamente"
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
with col2:
|
| 468 |
+
st.metric(
|
| 469 |
+
"Erro no Teste",
|
| 470 |
+
f"{erro_teste:.4f}",
|
| 471 |
+
help="Medida de erro do modelo"
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
# Interpretação do resultado
|
| 475 |
+
if acc_teste >= 0.70:
|
| 476 |
+
st.success(f"✅ Excelente! O modelo alcançou {acc_teste:.1%} de acurácia!")
|
| 477 |
+
elif acc_teste >= 0.60:
|
| 478 |
+
st.info(f"ℹ️ Bom resultado! O modelo alcançou {acc_teste:.1%} de acurácia.")
|
| 479 |
+
else:
|
| 480 |
+
st.warning(f"⚠️ O modelo pode melhorar. Acurácia atual: {acc_teste:.1%}")
|
| 481 |
+
|
| 482 |
+
# ==================== ABA 4: PREDIÇÃO ====================
|
| 483 |
+
with tab4:
|
| 484 |
+
st.header("🎯 Fazer Predições")
|
| 485 |
+
|
| 486 |
+
st.markdown("""
|
| 487 |
+
### Classificação de Novas Imagens
|
| 488 |
+
|
| 489 |
+
Envie uma imagem para que o modelo treinado faça a classificação!
|
| 490 |
+
|
| 491 |
+
**Dicas para melhores resultados:**
|
| 492 |
+
- Use imagens claras das categorias suportadas
|
| 493 |
+
- Evite imagens muito diferentes do dataset de treino
|
| 494 |
+
- Quanto mais simples a imagem, melhor
|
| 495 |
+
""")
|
| 496 |
+
|
| 497 |
+
if not st.session_state.get('modelo_treinado', False):
|
| 498 |
+
st.warning("⚠️ Por favor, treine o modelo primeiro na aba 'Treinamento'")
|
| 499 |
+
else:
|
| 500 |
+
st.markdown("---")
|
| 501 |
+
|
| 502 |
+
# Upload de imagem
|
| 503 |
+
uploaded_file = st.file_uploader(
|
| 504 |
+
"📤 Escolha uma imagem",
|
| 505 |
+
type=['png', 'jpg', 'jpeg'],
|
| 506 |
+
help="Formatos aceitos: PNG, JPG, JPEG"
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
if uploaded_file is not None:
|
| 510 |
+
col1, col2 = st.columns(2)
|
| 511 |
+
|
| 512 |
+
with col1:
|
| 513 |
+
st.subheader("🖼️ Imagem Original")
|
| 514 |
+
imagem_original = Image.open(uploaded_file)
|
| 515 |
+
st.image(imagem_original, use_container_width=True)
|
| 516 |
+
|
| 517 |
+
# Informações da imagem
|
| 518 |
+
st.markdown(f"""
|
| 519 |
+
**Informações:**
|
| 520 |
+
- Tamanho: {imagem_original.size[0]} x {imagem_original.size[1]} pixels
|
| 521 |
+
- Formato: {imagem_original.format}
|
| 522 |
+
- Modo: {imagem_original.mode}
|
| 523 |
+
""")
|
| 524 |
+
|
| 525 |
+
with col2:
|
| 526 |
+
st.subheader("🔍 Imagem Processada (32x32)")
|
| 527 |
+
|
| 528 |
+
# Processar imagem
|
| 529 |
+
imagem_processada, imagem_array = processar_imagem_usuario(uploaded_file)
|
| 530 |
+
st.image(imagem_processada, use_container_width=True)
|
| 531 |
+
|
| 532 |
+
st.info("A imagem foi redimensionada para 32x32 pixels para corresponder ao formato de entrada do modelo.")
|
| 533 |
+
|
| 534 |
+
# Botão de predição
|
| 535 |
+
st.markdown("---")
|
| 536 |
+
|
| 537 |
+
if st.button("🎲 Classificar Imagem", key="classificar", use_container_width=True):
|
| 538 |
+
with st.spinner("Analisando imagem..."):
|
| 539 |
+
# Fazer predição
|
| 540 |
+
previsoes = st.session_state.modelo.predict(imagem_array, verbose=0)
|
| 541 |
+
classe_prevista = np.argmax(previsoes[0])
|
| 542 |
+
confianca = previsoes[0][classe_prevista]
|
| 543 |
+
nome_classe = nomes_classes[classe_prevista]
|
| 544 |
+
emoji_classe = emojis_classes[nome_classe]
|
| 545 |
+
|
| 546 |
+
# Resultado principal
|
| 547 |
+
st.markdown("---")
|
| 548 |
+
st.subheader("🎉 Resultado da Classificação")
|
| 549 |
+
|
| 550 |
+
# Card de resultado
|
| 551 |
+
st.markdown(f"""
|
| 552 |
+
<div style="
|
| 553 |
+
padding: 30px;
|
| 554 |
+
border-radius: 10px;
|
| 555 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 556 |
+
color: white;
|
| 557 |
+
text-align: center;
|
| 558 |
+
margin: 20px 0;
|
| 559 |
+
">
|
| 560 |
+
<h1 style="font-size: 4em; margin: 0;">{emoji_classe}</h1>
|
| 561 |
+
<h2 style="margin: 10px 0;">{nome_classe.upper()}</h2>
|
| 562 |
+
<p style="font-size: 1.5em; margin: 0;">Confiança: {confianca:.1%}</p>
|
| 563 |
+
</div>
|
| 564 |
+
""", unsafe_allow_html=True)
|
| 565 |
+
|
| 566 |
+
# Barra de progresso da confiança
|
| 567 |
+
st.progress(float(confianca))
|
| 568 |
+
|
| 569 |
+
# Mostrar todas as probabilidades
|
| 570 |
+
st.markdown("---")
|
| 571 |
+
st.subheader("📊 Probabilidades para Todas as Classes")
|
| 572 |
+
|
| 573 |
+
# Criar DataFrame com resultados
|
| 574 |
+
import pandas as pd
|
| 575 |
+
|
| 576 |
+
resultados = pd.DataFrame({
|
| 577 |
+
'Classe': [f"{emojis_classes[nome]} {nome}" for nome in nomes_classes],
|
| 578 |
+
'Probabilidade': previsoes[0],
|
| 579 |
+
'Confiança (%)': previsoes[0] * 100
|
| 580 |
+
})
|
| 581 |
+
resultados = resultados.sort_values('Probabilidade', ascending=False)
|
| 582 |
+
|
| 583 |
+
# Gráfico de barras
|
| 584 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
| 585 |
+
colors = ['#667eea' if i == classe_prevista else '#cccccc'
|
| 586 |
+
for i in range(len(nomes_classes))]
|
| 587 |
+
|
| 588 |
+
bars = ax.barh(
|
| 589 |
+
resultados['Classe'],
|
| 590 |
+
resultados['Probabilidade'],
|
| 591 |
+
color=[colors[nomes_classes.index(nome.split()[-1])] for nome in resultados['Classe']]
|
| 592 |
+
)
|
| 593 |
+
|
| 594 |
+
ax.set_xlabel('Probabilidade', fontsize=12)
|
| 595 |
+
ax.set_title('Distribuição de Probabilidades', fontsize=14, fontweight='bold')
|
| 596 |
+
ax.set_xlim(0, 1)
|
| 597 |
+
|
| 598 |
+
# Adicionar valores nas barras
|
| 599 |
+
for bar in bars:
|
| 600 |
+
width = bar.get_width()
|
| 601 |
+
ax.text(width, bar.get_y() + bar.get_height()/2,
|
| 602 |
+
f'{width:.1%}',
|
| 603 |
+
ha='left', va='center', fontsize=9,
|
| 604 |
+
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
|
| 605 |
+
|
| 606 |
+
plt.tight_layout()
|
| 607 |
+
st.pyplot(fig)
|
| 608 |
+
|
| 609 |
+
# Tabela detalhada
|
| 610 |
+
with st.expander("📋 Ver Tabela Detalhada"):
|
| 611 |
+
st.dataframe(
|
| 612 |
+
resultados.style.format({
|
| 613 |
+
'Probabilidade': '{:.4f}',
|
| 614 |
+
'Confiança (%)': '{:.2f}%'
|
| 615 |
+
}).background_gradient(subset=['Probabilidade'], cmap='Blues'),
|
| 616 |
+
use_container_width=True
|
| 617 |
+
)
|
| 618 |
+
|
| 619 |
+
# Interpretação
|
| 620 |
+
with st.expander("💡 Como Interpretar o Resultado"):
|
| 621 |
+
st.markdown(f"""
|
| 622 |
+
### Análise da Predição:
|
| 623 |
+
|
| 624 |
+
- **Classe Prevista**: {emoji_classe} **{nome_classe.upper()}**
|
| 625 |
+
- **Confiança**: {confianca:.1%}
|
| 626 |
+
|
| 627 |
+
#### O que significa a confiança?
|
| 628 |
+
|
| 629 |
+
A confiança indica o quão "certo" o modelo está sobre sua predição:
|
| 630 |
+
|
| 631 |
+
- **> 90%**: 🟢 Muito confiante - O modelo está muito seguro da classificação
|
| 632 |
+
- **70-90%**: 🟡 Confiante - Boa certeza, mas com alguma margem de dúvida
|
| 633 |
+
- **50-70%**: 🟠 Moderado - O modelo tem dúvidas significativas
|
| 634 |
+
- **< 50%**: 🔴 Baixa confiança - O modelo está muito incerto
|
| 635 |
+
|
| 636 |
+
#### Por que o modelo pode errar?
|
| 637 |
+
|
| 638 |
+
- Imagem muito diferente das do dataset de treino
|
| 639 |
+
- Objeto muito pequeno ou distante
|
| 640 |
+
- Ângulo ou iluminação incomum
|
| 641 |
+
- Múltiplos objetos na imagem
|
| 642 |
+
- Qualidade da imagem (blur, pixelização)
|
| 643 |
+
""")
|
| 644 |
+
|
| 645 |
+
# Footer
|
| 646 |
+
st.markdown("---")
|
| 647 |
+
st.markdown("""
|
| 648 |
+
<div style="text-align: center; color: #666; padding: 20px;">
|
| 649 |
+
<p><strong>🎓 Projeto Educacional - Deep Learning com TensorFlow</strong></p>
|
| 650 |
+
<p>Desenvolvido para fins de aprendizado e demonstração</p>
|
| 651 |
+
<p style="font-size: 0.8em;">Dataset: CIFAR-10 | Framework: TensorFlow/Keras | Interface: Streamlit</p>
|
| 652 |
+
</div>
|
| 653 |
+
""", unsafe_allow_html=True)
|
| 654 |
+
|
| 655 |
+
# Inicializar session state
|
| 656 |
+
if 'dados_carregados' not in st.session_state:
|
| 657 |
+
st.session_state.dados_carregados = False
|
| 658 |
+
if 'modelo_criado' not in st.session_state:
|
| 659 |
+
st.session_state.modelo_criado = False
|
| 660 |
+
if 'modelo_treinado' not in st.session_state:
|
| 661 |
+
st.session_state.modelo_treinado = False
|