Projeto de Machine Learning desenvolvido como parte do Tech Challenge - Fase 2 do curso FIAP AI para DEVs (8IADT). O objetivo é aprimorar o modelo Random Forest construído na Fase 1 por meio de um Algoritmo Genético (AG) implementado do zero, que automatiza a busca pela combinação ideal de hiperparâmetros.
- Rodrigo de Araújo Rosa
- Elias Maximiano da Silva
- Danilo Pereira
- Fábia Gomes de Jesus
Na Fase 1, o melhor modelo obtido foi um Random Forest com acurácia de 75,32% no conjunto de teste e CV accuracy de 78,67% (5-fold), cujos hiperparâmetros foram encontrados via GridSearchCV. Na Fase 2, o desafio é superar esse resultado utilizando um Algoritmo Genético como estratégia de busca por hiperparâmetros.
Implementar do zero um Algoritmo Genético capaz de otimizar os hiperparâmetros do RandomForestClassifier, superando a acurácia de validação cruzada (CV 5-fold = 0,7867) obtida pelo GridSearch na Fase 1.
- Arquivo:
data/processed/diabetes_treated.csv - Contexto: Dados clínicos tratados na Fase 1 para detecção de diabetes (Pima Indians Dataset).
- Variável Target:
Outcome(0 = Não Diabético, 1 = Diabético) - Divisão: 80% treino / 20% teste, com estratificação pelo target (
random_state=42).
Um Algoritmo Genético é uma metaheurística inspirada na teoria evolutiva de Darwin. A ideia central é manter uma população de soluções candidatas (indivíduos), que evoluem geração a geração por meio de três operadores:
- Seleção — os indivíduos mais aptos têm maior chance de se reproduzir.
- Crossover (Cruzamento) — combina material genético de dois pais para gerar filhos.
- Mutação — introduz variações aleatórias para manter diversidade genética.
Ao longo das gerações, a população tende a convergir para regiões de alta aptidão no espaço de busca — neste projeto, regiões com boa acurácia de validação cruzada.
Cada indivíduo é uma lista de 5 genes inteiros, onde cada posição representa um hiperparâmetro do RandomForestClassifier:
| Gene | Hiperparâmetro | Tipo | Intervalo / Opções | Descrição |
|---|---|---|---|---|
| [0] | n_estimators |
inteiro | 20 – 60 | Número de árvores na floresta |
| [1] | max_depth |
categórico | 5, 10, 15, 20, 25 | Profundidade máxima de cada árvore |
| [2] | min_samples_leaf |
inteiro | 1 – 10 | Mínimo de amostras por folha |
| [3] | min_samples_split |
inteiro | 2 – 20 | Mínimo de amostras para dividir um nó interno |
| [4] | max_features |
binário | 0 = sqrt, 1 = log2 |
Critério de seleção de variáveis por divisão |
[n_estimators, max_depth, min_samples_leaf, min_samples_split, max_features]
A aptidão de cada indivíduo é calculada treinando um RandomForestClassifier com seus genes e aplicando validação cruzada estratificada de 5 folds:
Combinar média e desvio padrão penaliza soluções instáveis — aquelas que acertam muito em alguns folds mas erram em outros. O objetivo é encontrar modelos acurados e consistentes.
A execução encerra quando o melhor indivíduo supera a meta de acurácia:
target_cv = 0.7867 × (1 + target_improvement)
O parâmetro target_improvement (padrão 0.0) permite exigir uma melhoria percentual adicional sobre a referência da Fase 1. Se o dashboard do Streamlit estiver sendo utilizado, o usuário também pode definir o número máximo de gerações, se a evolução chegar até a geração máxima definada, sem que a meta tenha sido batida, a execução se encerra. No caso da execução estar sendo realizada via linha de comando (CLI), o usuário também pode interromper manualmente a qualquer momento com Ctrl+C quando executado por linha de comando.
A população inicial é gerada aleatoriamente, mas o primeiro indivíduo é substituído pela melhor solução conhecida do GridSearch da Fase 1 (n_estimators=30, max_depth=15, min_samples_leaf=1, min_samples_split=5, max_features='log2'). Essa estratégia garante que o AG parta de um ponto já competitivo, reduzindo o tempo de convergência.
Para cada vaga no offspring (descendência):
- Sorteia
tournsize=3candidatos aleatoriamente da população. - O campeão do torneio (maior fitness) é selecionado.
Vantagem sobre a seleção por roleta: não exige fitness positivo ou normalizado, e o parâmetro
tournsizecontrola diretamente a pressão seletiva.
O crossover é realizado em pares consecutivos do offspring. Uma melhoria importante foi implementada para evitar crossovers nulos: antes de aplicar o operador, o algoritmo identifica as posições onde os dois pais diferem e ancora um dos pontos de corte nessa região, garantindo que o segmento trocado contenha ao menos um gene distinto.
Pai 1: [53, 15, 8, 5, 1] Pai 2: [36, 13, 1, 5, 1]
Genes divergentes: posições 0, 1, 2 → ancora em pivot = 1
cx1=0, cx2=3
Filho 1: [36, 13, 1, 5, 1] Filho 2: [53, 15, 8, 5, 1]
Para cada indivíduo, com probabilidade mut_pb=0.6, a mutação é aplicada. Para cada gene i, com probabilidade mut_indpb=0.5, o gene é substituído por um inteiro sorteado uniformemente dentro dos limites [GENE_LOW[i], GENE_HIGH[i]].
Com
mut_indpb=0.5e 5 genes, em média 2–3 genes são alterados a cada mutação.
Cada indivíduo carrega um atributo fitness. Quando o crossover ou a mutação modifica um indivíduo, seu fitness é invalidado (fitness = None). No passo de avaliação, apenas os indivíduos com fitness is None são reavaliados. Isso economiza chamadas ao modelo e reduz significativamente o tempo total de execução — especialmente importante pois o CV representa ~99,9% do tempo computacional.
O melhor indivíduo global (best_ind) é mantido fora da população corrente. Mesmo que gerações futuras percam diversidade, o melhor resultado já visto é sempre preservado.
Geração N:
1. Seleção por Torneio → k=n_pop indivíduos escolhidos da população atual
2. Clonagem → offspring é uma cópia profunda dos selecionados
3. Crossover (pares) → combina pares consecutivos; invalida fitness dos filhos alterados
4. Mutação → candidato a mutação por indivíduo; invalida fitness dos mutados
5. Avaliação Lazy → reavalia somente indivíduos com fitness=None (CV 5-fold)
6. Atualiza best_ind → se o melhor da geração supera o best_ind global
7. Verifica critério → se best_fitness > target_cv → encerra; senão → próxima geração
Critério de parada: cv_acc > target_cv, onde target_cv = PHASE1_CV_ACCURACY × (1 + target_improvement).
flowchart TD
A([Início]) --> B[Gerar população inicial<br/>n_pop indivíduos aleatórios]
B --> C[Injetar semente GridSearch<br/>melhor resultado Fase 1]
C --> D[Avaliar fitness de todos<br/>CV 5-fold: mean − 0.1·std]
D --> E{fitness_melhor<br/>> target_cv?}
E -- Sim --> Z([Retornar melhor indivíduo])
E -- Não --> F[Seleção por Torneio<br/>tournsize=3, k=n_pop]
F --> G[Crossover de dois pontos<br/>pares não-idênticos]
G --> H[Mutação uniforme inteira<br/>mut_pb · mut_indpb por gene]
G --> J[Atualizar melhor indivíduo<br/>Hall of Fame]
H --> J[Atualizar melhor indivíduo<br/>Hall of Fame]
J --> E
Esta seção documenta os principais desafios enfrentados durante o desenvolvimento e as soluções implementadas para resolvê-los. Cada decisão de projeto descreve o problema que a motivou e a abordagem adotada, cobrindo desde a estrutura interna dos indivíduos até as estratégias evolutivas escolhidas.
src/
├── main.py ← CLI: ponto de entrada via linha de comando
├── app.py ← Dashboard Streamlit (interface visual)
└── engine/
├── ga_rf_optimizer.py ← AG implementado do zero (implementação principal)
├── ga_deap.py ← AG alternativo usando o framework DEAP
└── ga_logger.py ← Logging, profiling e geração de relatórios
graph LR
main.py --> ga_rf_optimizer.py
main.py --> ga_deap.py
app.py --> ga_rf_optimizer.py
ga_rf_optimizer.py --> ga_logger.py
ga_rf_optimizer.py --> sklearn["scikit-learn<br/>(RandomForest + CV)"]
O quê: A classe Individual estende a lista nativa do Python em vez de encapsular os genes como atributo.
Por quê: Os operadores genéticos (crossover, mutação) precisam indexar e fatiar os genes diretamente com ind[i] e ind[cx1:cx2]. Herdar de list evita camadas de indireção (ind.genes[i]) sem perda semântica — o indivíduo é o seu cromossomo.
O quê: O primeiro indivíduo da população inicial é substituído pelo melhor resultado do GridSearch da Fase 1: [30, 15, 1, 5, 1] (CV acc = 78,67%).
Por quê: Reduz o tempo de convergência garantindo que o AG parta de um ponto já sabidamente competitivo. Os demais indivíduos continuam aleatórios para preservar diversidade genética e não viesar a busca.
O quê: A cada vaga do offspring, sorteia-se tournsize=3 candidatos da população e escolhe-se o de maior fitness.
Por quê: A seleção por roleta exige fitness positivo e normalizado (proporção de área). A seleção por torneio não impõe nenhuma dessas restrições e o parâmetro tournsize controla diretamente a pressão seletiva — valores maiores aumentam a pressão, valores menores preservam mais diversidade.
O quê: Antes de sortear os pontos de corte, o algoritmo identifica os índices onde os dois pais diferem e ancora um dos pontos de corte nessa região (pivot = random.choice(diff)).
Por quê: O crossover de dois pontos clássico pode gerar filhos idênticos aos pais se os pontos de corte caírem em regiões onde os genes já são iguais. Ancorar o corte em um gene divergente elimina esses crossovers nulos, aumentando a eficiência evolutiva.
O quê: O fitness combina média e desvio padrão do CV 5-fold:
Por quê: Um modelo que acerta 85% em dois folds e 70% nos outros três tem média similar a um modelo consistente de ~78% — mas é muito menos confiável em produção. O coeficiente 0,1 foi calibrado como penalidade leve: desfavorece instabilidade sem descartar indivíduos com boa acurácia média.
O quê: Todo indivíduo carrega um atributo fitness. Após crossover ou mutação, o fitness do indivíduo modificado é invalidado (fitness = None). No passo de avaliação, apenas os indivíduos com fitness is None são reavaliados.
Por quê: O CV 5-fold representa ~99,9% do custo computacional total de uma execução. Revaliar indivíduos que não foram modificados seria desperdício puro. A avaliação lazy elimina esse custo mantendo a correção do algoritmo.
O quê: O melhor indivíduo já visto é mantido em uma variável best_ind externa à população corrente, atualizada a cada geração via cópia profunda.
Por quê: Garante que o resultado da execução nunca regride — mesmo que uma geração ruim de mutações degrade a população inteira, o melhor histórico é preservado. Isso torna o elitismo independente do tamanho da população (sem precisar de um "slot reservado" no offspring).
O quê: O RandomForestClassifier usa random_state=42 para avaliação de CV. O módulo random do AG não tem seed global fixada.
Por quê: A reprodutibilidade da avaliação é importante — dois indivíduos com os mesmos genes sempre terão o mesmo fitness. Já o AG em si é estocástico por natureza: fixar a seed do AG tornaria todas as execuções idênticas, eliminando o benefício de rodar o algoritmo múltiplas vezes para explorar o espaço de busca.
O quê: A implementação principal (ga_rf_optimizer.py) foi escrita do zero. O DEAP (ga_deap.py) existe como alternativa.
Por quê: O enunciado do Tech Challenge exige implementação do zero, demonstrando compreensão dos operadores genéticos. O DEAP é mantido como referência comparativa e alternativa de uso — ele reduz significativamente o boilerplate mas abstrai os mecanismos internos que o projeto precisa evidenciar.
O dataset pré-processado da Fase 1 (diabetes_treated.csv) é carregado e dividido em treino (80%) e teste (20%) com estratificação.
O AG otimiza os 5 hiperparâmetros do RandomForestClassifier usando CV 5-fold como métrica de aptidão. A cada geração, o progresso é exibido no console ou no dashboard Streamlit.
Ao encerrar, o melhor indivíduo encontrado é decodificado e um RandomForestClassifier final é treinado com esses hiperparâmetros e avaliado no conjunto de teste.
O modelo otimizado pelo AG é comparado diretamente com o modelo da Fase 1 (salvo em models/model_diabetes_rf_original.pkl), exibindo acurácia, delta de melhoria e relatório de classificação completo.
Se a meta de CV for atingida, o modelo otimizado é serializado em models/model_diabetes_rf_optimized_{timestamp}.pkl e o relatório comparativo é salvo em logs/summary_ga_rf_optimizer_{timestamp}.txt.
A aplicação Streamlit (src/app.py) oferece uma interface visual completa para acompanhar e controlar o AG em tempo real:
- Configuração via sidebar: tamanho da população, número máximo de gerações, melhoria-alvo e probabilidades de mutação.
- Gráfico de evolução ao vivo: fitness do melhor indivíduo e fitness médio da população por geração, com linha de referência da meta.
- Visualização do cromossomo: heatmap com os 5 genes do melhor indivíduo atual.
- Exploração do espaço de hiperparâmetros: scatter plots acumulados de todos os indivíduos já avaliados, coloridos por fitness.
- Tabela da população: todos os indivíduos da geração atual, ordenados por fitness.
- Avaliação no conjunto de teste: acurácia e relatório de classificação do melhor indivíduo ao final.
- Exportação do modelo: download do modelo treinado no formato
.pklcom validação de integridade da serialização. - Imagem resumo: figura salva automaticamente em
images/quando a meta é atingida, contendo evolução do fitness, hiperparâmetros e comparação com o modelo original.
streamlit run src/app.py| Painel de Evolução |
|---|
![]() |
| Painel de Exploração |
|---|
![]() |
| Painel de Comparação |
|---|
![]() |
Cada execução do AG gera automaticamente um arquivo de log detalhado em logs/{algoritmo}_{timestamp}.log, registrando:
- Parâmetros de configuração da execução.
- Evento de seleção: indivíduos escolhidos por torneio a cada geração.
- Crossovers: pares originais, filhos gerados e se houve troca efetiva.
- Mutações: gene(s) alterados por indivíduo.
- Estatísticas por geração: melhor fitness, fitness médio, situação da meta.
- Profiling: tempo acumulado por fase (seleção, crossover, mutação, avaliação).
- Resumo final comparativo salvo em
logs/summary_ga_rf_optimizer_{timestamp}.txt.
O nível de detalhe é controlável via variável de ambiente GA_LOG_LEVEL (valores: INFO ou DEBUG).
A pasta src/demo/ contém scripts independentes para visualizar o funcionamento de cada operador genético isoladamente, sem depender do dataset:
| Script | Descrição |
|---|---|
demo_selection.py |
Demonstra a seleção por torneio sobre uma população artificial |
demo_crossover.py |
Ilustra o crossover de dois pontos com marcadores de genes trocados |
demo_mutate.py |
Exibe a mutação gene a gene com destaques de alterações |
demo_ga_operators.py |
Simula uma geração completa (seleção + crossover + mutação) |
python src/demo/demo_selection.py
python src/demo/demo_crossover.py
python src/demo/demo_mutate.py
python src/demo/demo_ga_operators.pyOs testes em src/test/test_ga_rf_optimizer.py cobrem:
- Criação e comportamento da classe
Individual(inicialização, cópia) - Geração de indivíduos aleatórios dentro dos limites do espaço de busca
- Criação da população com semente (
create_seeded_pop) - Operador de crossover: troca efetiva, pais idênticos, imutabilidade dos pais
- Operador de mutação: limites respeitados, probabilidade
mut_indpb - Operador de seleção: tamanho correto, preferência pelos mais aptos
pytest src/test/test_ga_rf_optimizer.py -v -sO módulo src/engine/ga_deap.py implementa o mesmo algoritmo genético usando o framework DEAP (Distributed Evolutionary Algorithms in Python), e pode ser utilizado como alternativa à implementação manual via argumento de linha de comando.
O DEAP abstrai grande parte da infraestrutura de um AG, oferecendo vantagens como:
- Operadores prontos e testados:
tools.cxTwoPoint,tools.mutUniformInt,tools.selTournament, entre outros, evitando a necessidade de implementação manual. - Sistema de registro (
Toolbox): centraliza geradores de indivíduos, operadores e funções de avaliação em um único objeto configurável. HallOfFame: rastreia automaticamente os melhores indivíduos de toda a execução sem código adicional.- Tipos extensíveis (
creator): permite criar tipos customizados deFitnesseIndividualvia herança dinâmica, com suporte nativo a problemas mono e multiobjetivo. - Suporte a paralelismo: integração direta com
multiprocessingeSCOOPpara avaliações paralelas. - Menor boilerplate: o mesmo AG requer significativamente menos código, tornando o framework adequado especialmente para prototipagem rápida e problemas de otimização evolutiva mais complexos.
Documentação: DEAP documentation
Para executar a versão DEAP:
python src/main.py ga_deap --n_pop 10├── data/
│ ├── processed/
│ │ └── diabetes_treated.csv # Dataset pré-processado (Fase 1)
│ └── raw/
│ └── diabetes.csv # Dataset original
├── images/
│ └── resultado_ga_rf_optimizer_*.png # Figuras resumo geradas pelo AG
├── logs/
│ ├── ga_rf_optimizer_*.log # Log detalhado por execução
│ └── summary_ga_rf_optimizer_*.txt # Relatório comparativo por execução
├── models/
│ ├── model_diabetes_rf_original.pkl # Modelo original da Fase 1
│ └── model_diabetes_rf_optimized_*.pkl # Modelos otimizados pelo AG
├── src/
│ ├── app.py # Dashboard Streamlit (visualização ao vivo)
│ ├── main.py # CLI: python main.py ga_rf_optimizer [opções]
│ ├── demo/
│ │ ├── demo_crossover.py # Demo do operador de crossover
│ │ ├── demo_ga_operators.py # Demo de uma geração completa do AG
│ │ ├── demo_mutate.py # Demo do operador de mutação
│ │ └── demo_selection.py # Demo da seleção por torneio
│ ├── engine/
│ │ ├── __init__.py
│ │ ├── ga_rf_optimizer.py # Implementação manual do AG (principal)
│ │ ├── ga_deap.py # Implementação alternativa com DEAP
│ │ └── ga_logger.py # Módulo de logging e profiling
│ └── test/
│ └── test_ga_rf_optimizer.py # Testes unitários do AG
├── README.md # Documentação do projeto
├── requirements.txt # Dependências do projeto
└── LICENSE
- Python 3.10 ou superior
- pip (gerenciador de pacotes Python)
-
Clone o repositório e acesse a pasta do projeto.
-
Crie e ative um ambiente virtual
# Windows
python -m venv .venv
.venv\Scripts\activate
# Linux/Mac
python3 -m venv .venv
source .venv/bin/activate- Se necessário, atualize o pip (opcional)
python -m pip install --upgrade pip- Instale as dependências
pip install -r requirements.txt- Se estiver usando VSCode (selecione o interpretador Python)
Pressione Ctrl+Shift+P
Digite "Python: Select Interpreter"
Selecione o .venv do projeto (ex: python.exe)- Execute o Dashboard Streamlit (modo recomendado)
streamlit run src/app.py- Ou execute via CLI para rodar o AG no terminal:
# Execução padrão (população 10, meta: superar CV 0.7867)
python src/main.py ga_rf_optimizer
# Com parâmetros customizados
python src/main.py ga_rf_optimizer --n_pop 20 --target_improvement 0.01 --mut_pb 0.5 --mut_indpb 0.4| Parâmetro | Padrão | Descrição |
|---|---|---|
algorithm |
— | Implementação a usar: ga_rf_optimizer ou ga_deap |
--n_pop |
10 | Tamanho da população |
--target_improvement |
0.0 | Melhoria percentual desejada sobre a meta base (ex: 0.01 = +1% acima de 0.7867) |
--mut_pb |
0.6 | Probabilidade de um indivíduo sofrer mutação |
--mut_indpb |
0.5 | Probabilidade de mutar cada gene individualmente |
- Python 3.10+
- Scikit-learn —
RandomForestClassifier,cross_val_score,train_test_split - Pandas / NumPy — Manipulação de dados
- Streamlit — Dashboard interativo com atualização em tempo real
- Altair — Gráficos declarativos para visualização da evolução e exploração do espaço
- Matplotlib — Geração de figuras resumo salvas em
images/ - DEAP — Framework evolucional (utilizado na implementação alternativa)
- Python-dotenv — Configuração do nível de log via variável de ambiente
- pytest — Testes unitários
O nível de detalhamento do log pode ser controlado criando um arquivo .env na raiz do projeto:
# .env
GA_LOG_LEVEL=DEBUG # INFO (padrão) ou DEBUG (logs mais detalhados)Cada linha do log é prefixada com uma tag de seção entre colchetes. A tabela abaixo descreve o conteúdo de cada uma:
| Seção | Nível | Descrição |
|---|---|---|
[RUN_START] |
INFO |
Registra os parâmetros da execução: algoritmo, tamanho da população, max_gen, probabilidades de mutação e meta de CV. |
[GEN_START] |
INFO |
Marca o início de cada geração (ex.: ── geração=1 ──). A geração 0 corresponde à população inicial. |
[GEN_STATS] |
INFO |
Estatísticas ao fim de cada geração: melhor fitness, fitness médio, meta de CV e genes do melhor indivíduo. |
[SELECTION] |
INFO |
Resume a seleção por torneio: método, tournsize e quantidade de indivíduos selecionados. |
[CROSSOVER] |
INFO |
Para pares que realizaram troca (CX=SIM): registra os índices do par, os genes trocados e os cromossomos antes/depois. Pares com pais idênticos (CX=NAO) são omitidos no nível INFO. |
[MUTATION] |
INFO |
Para indivíduos mutados (MUT=SIM): registra o índice do indivíduo, os genes alterados e o cromossomo antes/depois. Indivíduos não mutados (MUT=NAO) são omitidos no nível INFO. |
[RUN_END] |
INFO |
Registra o encerramento da execução: geração final, melhor fitness, tempo total, se a meta foi atingida e os genes do melhor indivíduo. |
[PROFILING] |
INFO |
Bloco emitido ao final com o tempo acumulado por fase (seleção, crossover, mutação, avaliação): total, chamadas, média e percentual. |
| Log INFO - Estrutura |
|---|
![]() |
Com GA_LOG_LEVEL=DEBUG, duas seções adicionais são emitidas:
| Seção | Nível | Descrição |
|---|---|---|
[INDIVIDUAL] |
DEBUG |
Lista todos os indivíduos da população ao fim de cada geração, ordenados por fitness (rank), com seus genes decodificados. |
[SELECTED] |
DEBUG |
Lista cada indivíduo escolhido pela seleção por torneio, com seu slot na nova geração, fitness e genes. |
Com
DEBUGtambém passam a aparecer as entradas[CROSSOVER] CX=NAOe[MUTATION] MUT=NAO, registrando os pares e indivíduos que não foram modificados em cada geração.
| Log DEBUG - Estrutura |
|---|
![]() |
- Confirme que o arquivo está em
data/processed/diabetes_treated.csv - O arquivo é gerado pelo pré-processamento da Fase 1; baixando o repositório completo ele já está incluído
- O arquivo
models/model_diabetes_rf_original.pkldeve estar presente para a comparação funcionar - Sem ele, o CLI exibe apenas os resultados do modelo otimizado, sem delta de melhoria
- A avaliação via CV 5-fold representa ~99,9% do tempo computacional
- Reduza
--n_poppara populações menores (ex:--n_pop 5) para execuções mais rápidas em hardware limitado - O AG usa
n_jobs=-1no RandomForest, aproveitando todos os núcleos disponíveis
- Certifique-se de que o ambiente virtual está ativado antes de executar
streamlit run src/app.py - Em caso de erro de importação, verifique se o terminal está na raiz do projeto
Todos os processos aleatórios usam random_state=42 onde aplicável:
- Divisão treino/teste do dataset
- Inicialização do
RandomForestClassifierpara avaliação do CV - O AG em si usa o módulo
randomdo Python (sem seed global fixa), pois a natureza estocástica é intencional — execuções distintas exploram regiões diferentes do espaço de busca
Uso educacional. Ajuste conforme necessário para seu contexto.
Desenvolvido como parte do Tech Chalenge FIAP - AI para DEVs (Fase 2) - Turma 8IADT - 2025/2026




