import streamlit as st import tensorflow as tf from tensorflow.keras import datasets, layers, models import matplotlib.pyplot as plt import numpy as np from PIL import Image import time # Configuração da página st.set_page_config( page_title="Classificador de Imagens CIFAR-10", page_icon="🖼️", layout="wide" ) # Título principal st.title("🖼️ Classificador de Imagens com Deep Learning") st.markdown("---") # Sidebar com informações st.sidebar.title("📊 Informações do Projeto") st.sidebar.info(""" **Problema de Negócio:** Construir um modelo de Inteligência Artificial capaz de classificar imagens considerando 10 categorias: - ✈️ Airplane - 🚗 Automobile - 🦅 Bird - 🐱 Cat - 🦌 Deer - 🐕 Dog - 🐸 Frog - 🐴 Horse - 🚢 Ship - 🚛 Truck """) st.sidebar.markdown("---") st.sidebar.markdown("### 🔧 Tecnologias Utilizadas") st.sidebar.markdown(""" - Python - TensorFlow/Keras - Streamlit - NumPy - Matplotlib - PIL """) # Classes das imagens nomes_classes = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] # Emojis para cada classe emojis_classes = { 'airplane': '✈️', 'automobile': '🚗', 'bird': '🦅', 'cat': '🐱', 'deer': '🦌', 'dog': '🐕', 'frog': '🐸', 'horse': '🐴', 'ship': '🚢', 'truck': '🚛' } # Inicializar session state no início if 'dados_carregados' not in st.session_state: st.session_state.dados_carregados = False if 'modelo_criado' not in st.session_state: st.session_state.modelo_criado = False if 'modelo_treinado' not in st.session_state: st.session_state.modelo_treinado = False # Função para carregar dados @st.cache_data def carregar_dados(): """Carrega e pré-processa o dataset CIFAR-10""" try: (imagens_treino, labels_treino), (imagens_teste, labels_teste) = datasets.cifar10.load_data() # Normaliza os valores dos pixels imagens_treino = imagens_treino / 255.0 imagens_teste = imagens_teste / 255.0 return (imagens_treino, labels_treino), (imagens_teste, labels_teste) except Exception as e: st.error(f"Erro ao carregar dados: {str(e)}") return None # Função para visualizar imagens def visualiza_imagens(images, labels, num_imagens=25): """Cria uma figura com múltiplas imagens do dataset""" fig, axes = plt.subplots(5, 5, figsize=(10, 10)) fig.suptitle('Exemplos do Dataset CIFAR-10', fontsize=16) for i, ax in enumerate(axes.flat): if i < num_imagens and i < len(images): ax.imshow(images[i]) ax.set_title(nomes_classes[labels[i][0]], fontsize=8) ax.axis('off') plt.tight_layout() plt.close('all') # Prevenir memory leak return fig # Função para criar o modelo @st.cache_resource def criar_modelo(): """Cria a arquitetura da rede neural convolucional""" try: modelo = models.Sequential([ # Primeiro bloco convolucional layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)), layers.MaxPooling2D((2, 2)), # Segundo bloco convolucional layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), # Terceiro bloco convolucional layers.Conv2D(64, (3, 3), activation='relu'), # Camadas de classificação layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(10, activation='softmax') ]) modelo.compile( optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) return modelo except Exception as e: st.error(f"Erro ao criar modelo: {str(e)}") return None # Função para treinar o modelo def treinar_modelo(modelo, imagens_treino, labels_treino, imagens_teste, labels_teste, epochs=10): """Treina o modelo com os dados fornecidos""" try: history = modelo.fit( imagens_treino, labels_treino, epochs=epochs, validation_data=(imagens_teste, labels_teste), verbose=0, batch_size=64 ) return history except Exception as e: st.error(f"Erro durante o treinamento: {str(e)}") return None # Função para processar imagem do usuário def processar_imagem_usuario(imagem_file): """Processa a imagem enviada pelo usuário""" try: imagem = Image.open(imagem_file) # Converte para RGB se necessário if imagem.mode != 'RGB': imagem = imagem.convert('RGB') # Redimensiona para 32x32 imagem_redimensionada = imagem.resize((32, 32)) # Converte para array e normaliza imagem_array = np.array(imagem_redimensionada) / 255.0 # Expande dimensão imagem_array = np.expand_dims(imagem_array, axis=0) return imagem_redimensionada, imagem_array except Exception as e: st.error(f"Erro ao processar imagem: {str(e)}") return None, None # Função para criar gráficos de treinamento def plotar_historico(history): """Cria gráficos de acurácia e perda durante o treinamento""" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) # Gráfico de acurácia ax1.plot(history.history['accuracy'], label='Treino', marker='o') ax1.plot(history.history['val_accuracy'], label='Validação', marker='s') ax1.set_title('Acurácia do Modelo') ax1.set_xlabel('Época') ax1.set_ylabel('Acurácia') ax1.legend() ax1.grid(True, alpha=0.3) # Gráfico de perda ax2.plot(history.history['loss'], label='Treino', marker='o') ax2.plot(history.history['val_loss'], label='Validação', marker='s') ax2.set_title('Perda do Modelo') ax2.set_xlabel('Época') ax2.set_ylabel('Perda') ax2.legend() ax2.grid(True, alpha=0.3) plt.tight_layout() plt.close('all') # Prevenir memory leak return fig # Abas principais tab1, tab2, tab3, tab4 = st.tabs([ "📚 Dados", "🏗️ Modelo", "📈 Treinamento", "🎯 Predição" ]) # ==================== ABA 1: DADOS ==================== with tab1: st.header("📚 Exploração dos Dados") st.markdown(""" ### Dataset CIFAR-10 O **CIFAR-10** é um dataset clássico de visão computacional que contém 60.000 imagens coloridas de 32x32 pixels em 10 classes diferentes, com 6.000 imagens por classe. - **50.000 imagens de treino** - **10.000 imagens de teste** - **10 classes balanceadas** """) if st.button("🔄 Carregar Dataset CIFAR-10", key="carregar_dados"): with st.spinner("Carregando dados..."): resultado = carregar_dados() if resultado is not None: (imagens_treino, labels_treino), (imagens_teste, labels_teste) = resultado st.session_state.dados_carregados = True st.session_state.imagens_treino = imagens_treino st.session_state.labels_treino = labels_treino st.session_state.imagens_teste = imagens_teste st.session_state.labels_teste = labels_teste st.success("✅ Dados carregados com sucesso!") # Mostrar estatísticas col1, col2, col3 = st.columns(3) with col1: st.metric("Imagens de Treino", f"{len(imagens_treino):,}") with col2: st.metric("Imagens de Teste", f"{len(imagens_teste):,}") with col3: st.metric("Classes", len(nomes_classes)) # Visualização das imagens if st.session_state.get('dados_carregados', False): st.markdown("---") st.subheader("🖼️ Exemplos de Imagens do Dataset") fig = visualiza_imagens( st.session_state.imagens_treino, st.session_state.labels_treino ) st.pyplot(fig) plt.clf() # Informações detalhadas with st.expander("📊 Informações Detalhadas dos Dados"): col1, col2 = st.columns(2) with col1: st.markdown("**Dados de Treino:**") st.write(f"- Shape: {st.session_state.imagens_treino.shape}") st.write(f"- Tipo: {st.session_state.imagens_treino.dtype}") st.write(f"- Valores min/max: {st.session_state.imagens_treino.min():.2f} / {st.session_state.imagens_treino.max():.2f}") with col2: st.markdown("**Dados de Teste:**") st.write(f"- Shape: {st.session_state.imagens_teste.shape}") st.write(f"- Tipo: {st.session_state.imagens_teste.dtype}") st.write(f"- Valores min/max: {st.session_state.imagens_teste.min():.2f} / {st.session_state.imagens_teste.max():.2f}") # ==================== ABA 2: MODELO ==================== with tab2: st.header("🏗️ Arquitetura do Modelo") st.markdown(""" ### Rede Neural Convolucional (CNN) O modelo utiliza uma arquitetura de **Rede Neural Convolucional**, ideal para processamento de imagens. A arquitetura consiste em: 1. **Camadas Convolucionais**: Extraem características das imagens 2. **Camadas de Pooling**: Reduzem a dimensionalidade 3. **Camadas Densas**: Realizam a classificação final """) if st.button("🔨 Criar Modelo", key="criar_modelo"): with st.spinner("Criando modelo..."): modelo = criar_modelo() if modelo is not None: st.session_state.modelo = modelo st.session_state.modelo_criado = True st.success("✅ Modelo criado com sucesso!") if st.session_state.get('modelo_criado', False): st.markdown("---") st.subheader("📋 Sumário da Arquitetura") # Captura o sumário do modelo from io import StringIO buffer = StringIO() st.session_state.modelo.summary(print_fn=lambda x: buffer.write(x + '\n')) summary_string = buffer.getvalue() st.code(summary_string, language='text') # Visualização da arquitetura with st.expander("🔍 Detalhes das Camadas"): st.markdown(""" **Camada 1 - Conv2D (32 filtros, 3x3):** - Primeira camada convolucional - Extrai 32 características diferentes - Ativação ReLU **Camada 2 - MaxPooling2D (2x2):** - Reduz dimensionalidade pela metade - Mantém características mais importantes **Camada 3 - Conv2D (64 filtros, 3x3):** - Segunda camada convolucional - Extrai características mais complexas **Camada 4 - MaxPooling2D (2x2):** - Segunda redução de dimensionalidade **Camada 5 - Conv2D (64 filtros, 3x3):** - Terceira camada convolucional - Refinamento de características **Camada 6 - Flatten:** - Converte matriz em vetor **Camada 7 - Dense (64 neurônios):** - Camada totalmente conectada - Ativação ReLU **Camada 8 - Dense (10 neurônios):** - Camada de saída - Ativação Softmax (probabilidades) - Uma saída para cada classe """) # Informações sobre parâmetros trainable_params = np.sum([np.prod(v.shape) for v in st.session_state.modelo.trainable_weights]) col1, col2, col3 = st.columns(3) with col1: st.metric("Parâmetros Treináveis", f"{trainable_params:,}") with col2: st.metric("Camadas", len(st.session_state.modelo.layers)) with col3: st.metric("Tamanho Input", "32x32x3") # ==================== ABA 3: TREINAMENTO ==================== with tab3: st.header("📈 Treinamento do Modelo") st.markdown(""" ### Processo de Treinamento Durante o treinamento, o modelo aprende a identificar padrões nas imagens através de múltiplas iterações (épocas) sobre os dados de treino. **Métricas monitoradas:** - **Acurácia**: Percentual de predições corretas - **Perda (Loss)**: Medida de erro do modelo """) # Verificar pré-requisitos if not st.session_state.get('dados_carregados', False): st.warning("⚠️ Por favor, carregue os dados primeiro na aba 'Dados'") elif not st.session_state.get('modelo_criado', False): st.warning("⚠️ Por favor, crie o modelo primeiro na aba 'Modelo'") else: st.markdown("---") col1, col2 = st.columns(2) with col1: epochs = st.slider( "Número de Épocas", min_value=1, max_value=15, value=5, help="Número de vezes que o modelo verá todos os dados de treino" ) with col2: st.markdown("### ⚙️ Configurações") st.write(f"- **Otimizador**: Adam") st.write(f"- **Função de Perda**: Sparse Categorical Crossentropy") st.write(f"- **Métrica**: Accuracy") if st.button("🚀 Iniciar Treinamento", key="treinar"): progress_bar = st.progress(0) status_text = st.empty() start_time = time.time() # Treinar o modelo with st.spinner("Treinando modelo..."): history = treinar_modelo( st.session_state.modelo, st.session_state.imagens_treino, st.session_state.labels_treino, st.session_state.imagens_teste, st.session_state.labels_teste, epochs=epochs ) if history is not None: st.session_state.history = history st.session_state.modelo_treinado = True end_time = time.time() training_time = end_time - start_time progress_bar.progress(100) status_text.success(f"✅ Treinamento concluído em {training_time:.2f} segundos!") # Mostrar métricas finais final_train_acc = history.history['accuracy'][-1] final_val_acc = history.history['val_accuracy'][-1] final_train_loss = history.history['loss'][-1] final_val_loss = history.history['val_loss'][-1] col1, col2, col3, col4 = st.columns(4) with col1: st.metric("Acurácia Treino", f"{final_train_acc:.2%}") with col2: st.metric("Acurácia Validação", f"{final_val_acc:.2%}") with col3: st.metric("Perda Treino", f"{final_train_loss:.4f}") with col4: st.metric("Perda Validação", f"{final_val_loss:.4f}") # Mostrar gráficos se já treinado if st.session_state.get('modelo_treinado', False): st.markdown("---") st.subheader("📊 Evolução do Treinamento") fig = plotar_historico(st.session_state.history) st.pyplot(fig) plt.clf() with st.expander("💡 Interpretação dos Gráficos"): st.markdown(""" **Gráfico de Acurácia:** - Mostra como a precisão do modelo melhora ao longo das épocas - Idealmente, ambas as curvas devem crescer juntas - Se a curva de validação estagna ou cai, pode indicar overfitting **Gráfico de Perda:** - Mostra como o erro do modelo diminui ao longo das épocas - Valores menores indicam melhor desempenho - A diferença entre treino e validação indica generalização """) # Avaliação final st.markdown("---") st.subheader("🎯 Avaliação Final") if st.button("📊 Avaliar Modelo no Conjunto de Teste"): with st.spinner("Avaliando modelo..."): try: erro_teste, acc_teste = st.session_state.modelo.evaluate( st.session_state.imagens_teste, st.session_state.labels_teste, verbose=0 ) col1, col2 = st.columns(2) with col1: st.metric( "Acurácia no Teste", f"{acc_teste:.2%}", help="Percentual de imagens classificadas corretamente" ) with col2: st.metric( "Erro no Teste", f"{erro_teste:.4f}", help="Medida de erro do modelo" ) # Interpretação do resultado if acc_teste >= 0.70: st.success(f"✅ Excelente! O modelo alcançou {acc_teste:.1%} de acurácia!") elif acc_teste >= 0.60: st.info(f"ℹ️ Bom resultado! O modelo alcançou {acc_teste:.1%} de acurácia.") else: st.warning(f"⚠️ O modelo pode melhorar. Acurácia atual: {acc_teste:.1%}") except Exception as e: st.error(f"Erro ao avaliar modelo: {str(e)}") # ==================== ABA 4: PREDIÇÃO ==================== with tab4: st.header("🎯 Fazer Predições") st.markdown(""" ### Classificação de Novas Imagens Envie uma imagem para que o modelo treinado faça a classificação! **Dicas para melhores resultados:** - Use imagens claras das categorias suportadas - Evite imagens muito diferentes do dataset de treino - Quanto mais simples a imagem, melhor """) if not st.session_state.get('modelo_treinado', False): st.warning("⚠️ Por favor, treine o modelo primeiro na aba 'Treinamento'") else: st.markdown("---") # Upload de imagem uploaded_file = st.file_uploader( "📤 Escolha uma imagem", type=['png', 'jpg', 'jpeg'], help="Formatos aceitos: PNG, JPG, JPEG" ) if uploaded_file is not None: col1, col2 = st.columns(2) with col1: st.subheader("🖼️ Imagem Original") imagem_original = Image.open(uploaded_file) st.image(imagem_original, use_column_width=True) # Informações da imagem st.markdown(f""" **Informações:** - Tamanho: {imagem_original.size[0]} x {imagem_original.size[1]} pixels - Formato: {imagem_original.format} - Modo: {imagem_original.mode} """) with col2: st.subheader("🔍 Imagem Processada (32x32)") # Processar imagem imagem_processada, imagem_array = processar_imagem_usuario(uploaded_file) if imagem_processada is not None: st.image(imagem_processada, use_column_width=True) st.info("A imagem foi redimensionada para 32x32 pixels para corresponder ao formato de entrada do modelo.") # Botão de predição st.markdown("---") if st.button("🎲 Classificar Imagem", key="classificar", use_container_width=True): if imagem_array is not None: with st.spinner("Analisando imagem..."): try: # Fazer predição previsoes = st.session_state.modelo.predict(imagem_array, verbose=0) classe_prevista = np.argmax(previsoes[0]) confianca = previsoes[0][classe_prevista] nome_classe = nomes_classes[classe_prevista] emoji_classe = emojis_classes[nome_classe] # Resultado principal st.markdown("---") st.subheader("🎉 Resultado da Classificação") # Card de resultado st.markdown(f"""

{emoji_classe}

{nome_classe.upper()}

Confiança: {confianca:.1%}

""", unsafe_allow_html=True) # Barra de progresso da confiança st.progress(float(confianca)) # Mostrar todas as probabilidades st.markdown("---") st.subheader("📊 Probabilidades para Todas as Classes") # Criar DataFrame com resultados import pandas as pd resultados = pd.DataFrame({ 'Classe': [f"{emojis_classes[nome]} {nome}" for nome in nomes_classes], 'Probabilidade': previsoes[0], 'Confiança (%)': previsoes[0] * 100 }) resultados = resultados.sort_values('Probabilidade', ascending=False) # Gráfico de barras fig, ax = plt.subplots(figsize=(10, 6)) colors = ['#667eea' if i == classe_prevista else '#cccccc' for i in range(len(nomes_classes))] bars = ax.barh( resultados['Classe'], resultados['Probabilidade'], color=[colors[nomes_classes.index(nome.split()[-1])] for nome in resultados['Classe']] ) ax.set_xlabel('Probabilidade', fontsize=12) ax.set_title('Distribuição de Probabilidades', fontsize=14, fontweight='bold') ax.set_xlim(0, 1) # Adicionar valores nas barras for bar in bars: width = bar.get_width() ax.text(width, bar.get_y() + bar.get_height()/2, f'{width:.1%}', ha='left', va='center', fontsize=9, bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) plt.tight_layout() st.pyplot(fig) plt.clf() # Tabela detalhada with st.expander("📋 Ver Tabela Detalhada"): st.dataframe( resultados.style.format({ 'Probabilidade': '{:.4f}', 'Confiança (%)': '{:.2f}%' }).background_gradient(subset=['Probabilidade'], cmap='Blues'), use_container_width=True ) # Interpretação with st.expander("💡 Como Interpretar o Resultado"): st.markdown(f""" ### Análise da Predição: - **Classe Prevista**: {emoji_classe} **{nome_classe.upper()}** - **Confiança**: {confianca:.1%} #### O que significa a confiança? A confiança indica o quão "certo" o modelo está sobre sua predição: - **> 90%**: 🟢 Muito confiante - O modelo está muito seguro da classificação - **70-90%**: 🟡 Confiante - Boa certeza, mas com alguma margem de dúvida - **50-70%**: 🟠 Moderado - O modelo tem dúvidas significativas - **< 50%**: 🔴 Baixa confiança - O modelo está muito incerto #### Por que o modelo pode errar? - Imagem muito diferente das do dataset de treino - Objeto muito pequeno ou distante - Ângulo ou iluminação incomum - Múltiplos objetos na imagem - Qualidade da imagem (blur, pixelização) """) except Exception as e: st.error(f"Erro ao fazer predição: {str(e)}") # Footer st.markdown("---") st.markdown("""

🎓 Projeto Educacional - Deep Learning com TensorFlow

Desenvolvido para fins de aprendizado e demonstração

Dataset: CIFAR-10 | Framework: TensorFlow/Keras | Interface: Streamlit

""", unsafe_allow_html=True)