Spaces:
Sleeping
Sleeping
| 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 | |
| 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 | |
| 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""" | |
| <div style=" | |
| padding: 30px; | |
| border-radius: 10px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| text-align: center; | |
| margin: 20px 0; | |
| "> | |
| <h1 style="font-size: 4em; margin: 0;">{emoji_classe}</h1> | |
| <h2 style="margin: 10px 0;">{nome_classe.upper()}</h2> | |
| <p style="font-size: 1.5em; margin: 0;">Confiança: {confianca:.1%}</p> | |
| </div> | |
| """, 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(""" | |
| <div style="text-align: center; color: #666; padding: 20px;"> | |
| <p><strong>🎓 Projeto Educacional - Deep Learning com TensorFlow</strong></p> | |
| <p>Desenvolvido para fins de aprendizado e demonstração</p> | |
| <p style="font-size: 0.8em;">Dataset: CIFAR-10 | Framework: TensorFlow/Keras | Interface: Streamlit</p> | |
| </div> | |
| """, unsafe_allow_html=True) |