Disciplina de Aprendizagem de Máquina · 2025.2

Lista 1 — Árvores de Decisão, RIPPER e k-Nearest Neighbors

O repositório fabio-linhares/ml consolida a resolução completa da primeira lista de exercícios da disciplina, combinando cálculos manuais, implementações próprias em Python e uma aplicação interativa em Streamlit. O material cobre do preparo dos dados à avaliação crítica dos modelos, documentando cada etapa de forma reprodutível.

Visão Geral do Projeto

A Lista 1 integra o Mestrado em Informática da UFAL e explora diferentes estratégias de modelagem supervisionada. O repositório entrega:

  • Modelagem manual e interpretável das árvores ID3, C4.5 e CART a partir da base expandida clientes.csv, com registros fotográficos dos cálculos em sodeusnacausa/.
  • Implementações próprias com arquitetura modular: uma classe core (OptimizedDecisionTree) atende aos três algoritmos, wrappers leves expõem as interfaces e a tabela de memoização reduz recomputações.
  • Aplicação web interativa (app.py) publicada em tutu.zerocopia.com.br, permitindo subir datasets, treinar modelos, visualizar métricas e comparar regras.
  • Análises adicionais com RIPPER, kNN, avaliação de overfitting/underfitting e geração de gráficos e matrizes de confusão automatizadas em evaluate_models.py.
ID3 C4.5 CART RIPPER k-NN Streamlit

Como executar e reproduzir os experimentos

Quickstart

  1. Clone o repositório e crie um ambiente isolado: python -m venv venv ou conda create --name lista1_ml python=3.12.
  2. Ative o ambiente (source venv/bin/activate ou conda activate lista1_ml).
  3. Instale as dependências: pip install -r requirements.txt.
  4. Inicie a aplicação: streamlit run app.py e acesse http://localhost:8501.
  5. Para a Questão 3, execute download_heart_dataset.py caso heart.csv não esteja presente.
O ambiente foi pensado para ser multiplataforma. A aplicação expõe dois fluxos principais: árvores de decisão (Questões 1 & 2) e análise de doenças cardíacas com RIPPER (Questão 3), reaproveitando os mesmos artefatos descritos no relatório.

Estrutura essencial do repositório

app.py # Interface Streamlit interativa class_id3.py / class_c45.py / class_cart.py optimized_tree.py # Núcleo com memoização e suportes de split clientes.csv # Base expandida (30 x 6) usada na Questão 1 heart.csv # Dataset Heart Disease (Questão 3) heart_disease_ripper.py # Comparação Árvore vs. RIPPER knn_classifier.py # Implementação didática do k-NN evaluate_models.py # Métricas, matrizes e visualizações sodeusnacausa/ # Scans dos cálculos manuais resultados_img/ # Árvores e matrizes geradas automaticamente

Os experimentos são versionados para garantir rastreabilidade entre o estudo manual, o código otimizado e as visualizações que sustentam a análise crítica.

Questão 1 · Construção manual das árvores

O primeiro exercício amplia a base fornecida pelo "gerente" para 30 instâncias e seis atributos, equilibrando as classes Baixo, Moderado e Alto risco. A partir daí, as árvores ID3, C4.5 e CART são construídas manualmente.

  • Dataset: clientes.csv documenta as 30 entradas adicionadas com coerência à distribuição original. A classe alvo possui 12 exemplos de Baixo, 8 de Moderado e 10 de Alto risco.
  • Cálculos fundamentais: entropia, ganho de informação, ganho normalizado e índice Gini são detalhados passo a passo, com evidências em sodeusnacausa/.
  • Árvores finais: o ID3 gera regras específicas (maior tendência ao overfitting), o C4.5 introduz cortes contínuos (ex.: idade) e o CART consolida uma árvore binária com quatro regras legíveis.
  • Base de regras: as regras extraídas (R1–R8) evidenciam como renda e histórico de crédito direcionam o risco. Destaque para a regra "SE renda > $35k ENTÃO risco = Baixo" válida para 13 registros.
Recomendação: a base de regras do CART foi escolhida como mais equilibrada entre simplicidade e poder explicativo, ideal para ambientes financeiros que exigem interpretabilidade.

Questão 2 · Implementação com bibliotecas e código próprio

A solução programática refatora o exercício manual em uma arquitetura modulada, capaz de reproduzir e comparar os algoritmos dentro da aplicação.

  • Motor unificado: OptimizedDecisionTree centraliza métricas, cortes contínuos e suporte a dados categóricos. Memoização (classe AdvancedMemoizationTable) elimina recomputações redundantes.
  • Wrappers enxutos: ID3DecisionTree, C45DecisionTree e CARTDecisionTree apenas configuram o motor para cada variação, simplificando a comparação.
  • Precisão vs. código manual: divergências entre árvores manuais e automatizadas são explicadas por empates quebrados automaticamente, busca exaustiva de limiares e controle de profundidade via hiperparâmetros.
Algoritmo Tempo de treino médio Taxa de acerto do cache Acurácia no teste (70/30)
ID3 ≈ 0,275 s 42,7% 77,78% (melhor equilíbrio de classes)
C4.5 ≈ 0,223 s 63,0% 77,78% (precisão menor para classe Moderado)
CART ≈ 0,299 s 67,4% 77,78% (regras binárias concisas)

As matrizes de confusão e as árvores renderizadas (em resultados_img/) acompanham o relatório e são exibidas na aplicação, reforçando a análise comparativa das classes.

Questão 3 · Dataset Heart Disease e RIPPER

A terceira questão migra para o dataset Heart Disease UCI, avaliando regras induzidas diretamente.

  • Preparação resiliente: download_heart_dataset.py busca o dataset no OpenML e gera um conjunto sintético compatível se necessário, garantindo que heart.csv esteja disponível.
  • Pipeline de avaliação: heart_disease_ripper.py treina uma árvore de decisão (DecisionTreeClassifier) limitada a cinco níveis e um modelo RIPPER via biblioteca wittgenstein, exibindo métricas e o conjunto de regras.
  • Interface Streamlit: em app.py, a aba "Análise de Doenças Cardíacas" carrega o dataset, compara acurácia e expõe o ruleset textual do RIPPER, ressaltando sua interpretabilidade face à árvore.
Na prática, o RIPPER oferece regras mais compactas e acessíveis para tomada de decisão médica, enquanto a árvore fornece a hierarquia completa das divisões — ambos com desempenho semelhante no conjunto de teste estratificado.

Questões 4 & 5 · Overfitting, Underfitting e o papel do C4.5

Esta parte do estudo reinterpreta os conceitos de sobreajuste e subajuste dentro do contexto das árvores de decisão.

  • Overfitting: árvores profundas (ex.: ID3 sem poda) aderem ao ruído, apresentando acurácia alta em treino e queda no teste.
  • Underfitting: estruturas excessivamente simplificadas falham em capturar o padrão — baixa performance em ambos os conjuntos.
  • C4.5 como antídoto: poda pessimista, tratamento criterioso de atributos contínuos e limites mínimos por nó balanceiam variância e viés.
  • Memoização e hiperparâmetros: controles como max_depth e cache de métricas permitem experimentar rapidamente diferentes combinações e observar o trade-off.

A documentação compara as matrizes de confusão resultantes e destaca as classes mais afetadas pela poda agressiva, justificando a escolha cuidadosa dos hiperparâmetros dentro da aplicação.

k-NN na prática · Exploração interativa

A aplicação Streamlit inclui uma seção dedicada para investigar o comportamento do k-NN no dataset cardíaco.

  • Explorador interativo: usuários entram com novos pontos e visualizam vizinhos mais próximos, retornados por KNNClassifier.
  • Análise de sensibilidade: o painel "🔬 Overfitting/Underfitting com k" gera um gráfico da acurácia em função de diferentes valores de k, reforçando empiricamente os conceitos das Questões 4 & 5.
  • Insights principais: valores baixos de k (ex.: 1) sofrem com variância alta; valores elevados amortecem demais as fronteiras, levando ao underfitting; o equilíbrio ocorre na região intermediária.

Questão 6 · Fundamentação do k-Nearest Neighbors

Além da experimentação, a lista descreve com detalhes o algoritmo k-NN.

  • Exemplo numérico resolvido: demonstração passo a passo das distâncias euclidianas para um ponto candidato considerando k = 1, 3 e 7.
  • Escolha do hiperparâmetro: recomendações que vão da regra empírica k ≈ √N à validação cruzada sistemática.
  • Limitações da distância euclidiana: para dados mistos, a distância de Gower é sugerida por normalizar atributos numéricos e lidar com categorias.
  • Perfis lazy vs. eager: comparação entre k-NN (treino mínimo, inferência custosa) e modelos como árvores ou SVM (treino custoso, inferência rápida).

Tudo isso está implementado em knn_classifier.py, garantindo que os exemplos teóricos possam ser reproduzidos diretamente em código.

Notas finais e referências

  • Documentação visual: matrizes de confusão e árvores exportadas estão em resultados_img/; os cálculos manuais permanecem registrados em sodeusnacausa/.
  • Pré-processamento: a codificação simples (LabelEncoder) favorece a didática. Em cenários produtivos, recomenda-se avaliar One-Hot Encoding e normalização.
  • Dependências: requirements.txt reúne bibliotecas como Streamlit, scikit-learn, pandas, seaborn, graphviz e wittgenstein, refletindo o ecossistema usado nas análises.
  • Próximos passos: ampliar os testes com validação cruzada e incluir métricas adicionais (F1 macro, MCC) podem aprofundar o diagnóstico das classes minoritárias.
Destinos oficiais: a aplicação está disponível em tutu.zerocopia.com.br e o código completo permanece versionado em github.com/fabio-linhares/ml.