Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Workshop Prático: Da Formulação à Solução com DEAP

Objetivo: Aplicar os conceitos de formulação de problemas (representação e função de fitness) para resolver um problema clássico de otimização, o “Problema da Mochila”, utilizando a biblioteca DEAP.

Problema: Dado um conjunto de itens, cada um com um peso e um valor, determinar o subconjunto de itens a serem incluídos em uma mochila de forma que o valor total seja maximizado, sem que a soma dos pesos exceda a capacidade da mochila.


Parte 1: Configuração do Ambiente

Primeiro, vamos instalar a biblioteca deap, que fornecerá todas as ferramentas para construir nosso algoritmo genético.

# @title Instalação e importação das bibliotecas

!pip install deap -q

[notice] A new release of pip is available: 25.0.1 -> 25.2
[notice] To update, run: pip install --upgrade pip
import random
import numpy as np

from deap import base
from deap import creator
from deap import tools
from deap import algorithms

print("✅ Ambiente configurado com DEAP!")
✅ Ambiente configurado com DEAP!

Parte 2: Definição do Problema

Vamos definir os parâmetros do nosso problema: os itens disponíveis (com seus nomes, valores e pesos) e a capacidade máxima da nossa mochila.

# @title Parâmetros do Problema da Mochila

# Dicionário de itens: {nome: (valor, peso)}
ITENS = {
    "Laptop": (1500, 2),
    "Livro": (300, 1),
    "Garrafa de Água": (50, 0.5),
    "Câmera": (1200, 1.5),
    "Casaco": (400, 1),
    "Lanche": (100, 0.2),
    "Carregador Portátil": (500, 0.8),
    "Fone de Ouvido": (600, 0.3)
}

# Capacidade máxima da mochila em kg
CAPACIDADE_MAXIMA = 4.0

# Extrair listas de nomes, valores e pesos para facilitar o acesso
nomes_itens = list(ITENS.keys())
valores_itens = [item[0] for item in ITENS.values()]
pesos_itens = [item[1] for item in ITENS.values()]
n_itens = len(ITENS)

print(f"🎒 Problema da Mochila definido:")
print(f"   - {n_itens} itens disponíveis.")
print(f"   - Capacidade máxima: {CAPACIDADE_MAXIMA} kg.")
🎒 Problema da Mochila definido:
   - 8 itens disponíveis.
   - Capacidade máxima: 4.0 kg.

Parte 3: Formulação do Problema com DEAP

Aqui traduzimos os conceitos de representação e função de fitness para a estrutura do DEAP.

  1. creator: Usamos para criar as classes base do nosso problema. Criaremos FitnessMax, uma classe de fitness que busca maximizar um único objetivo, e Individual, que será nossa representação (um indivíduo da população), baseada em uma lista com um atributo de fitness.

  2. toolbox: É uma caixa de ferramentas onde registramos as funções que o algoritmo genético usará para criar a população e realizar as operações genéticas (seleção, crossover, mutação).

# @title Configuração do `creator` e `toolbox`

# 1. Usando o `creator` para definir a estrutura do fitness e do indivíduo
# Queremos maximizar o valor, então os pesos do fitness são positivos (1.0).
creator.create("FitnessMax", base.Fitness, weights=(1.0,))

# Nosso indivíduo será uma lista de 0s e 1s (representação binária).
creator.create("Individual", list, fitness=creator.FitnessMax)

# 2. Usando a `toolbox` para registrar as funções genéticas
toolbox = base.Toolbox()

# -- Geração de Indivíduos e População --

# Função `attr_bool`: gera um gene (0 ou 1). É o bloco de construção do nosso indivíduo.
toolbox.register("attr_bool", random.randint, 0, 1)

# Função `individual`: cria um indivíduo completo (cromossomo).
# Usa `tools.initRepeat` para chamar `attr_bool` n_itens vezes.
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, n=n_itens)

# Função `population`: cria a população inicial, uma lista de indivíduos.
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

print("🛠️  `creator` e `toolbox` configurados.")
print("   - Fitness: Maximizar um objetivo.")
print("   - Representação do Indivíduo: Lista binária (0s e 1s).")
🛠️  `creator` e `toolbox` configurados.
   - Fitness: Maximizar um objetivo.
   - Representação do Indivíduo: Lista binária (0s e 1s).

3.1. A Função de Fitness

Esta é a parte mais crucial. A função evalKnapsack recebe um indivíduo (uma lista de 0s e 1s) e calcula sua qualidade.

  • Ela calcula o peso total e o valor total dos itens selecionados (onde o gene é 1).

  • Implementa a restrição: Se o peso total exceder a capacidade da mochila, o fitness é zerado. Esta é a função de penalidade que discutimos na teoria.

  • Se a solução for válida, ela retorna o valor total como fitness.

# @title Implementação da Função de Fitness

def evalKnapsack(individual: list) -> tuple:
    """Função de fitness para o Problema da Mochila.

    Calcula o valor total dos itens no indivíduo. Se o peso total exceder
    a capacidade, retorna um fitness de 0 (penalidade).

    Parameters:
        individual: Uma lista de 0s e 1s representando a solução.

    Returns:
        Um tuple contendo o valor total (o fitness).
    """
    valor_total = 0.0
    peso_total = 0.0
    for i in range(len(individual)):
        if individual[i] == 1:
            valor_total += valores_itens[i]
            peso_total += pesos_itens[i]

    # Aplica a penalidade se a restrição de peso for violada
    if peso_total > CAPACIDADE_MAXIMA:
        return 0,  # Retorna uma tupla, pois o DEAP espera isso

    return valor_total,

# Testando a função com um indivíduo de exemplo
teste_ind = [1, 1, 0, 1, 0, 0, 0, 0] # Laptop, Livro, Câmera
teste_peso = pesos_itens[0] + pesos_itens[1] + pesos_itens[3]
teste_valor = valores_itens[0] + valores_itens[1] + valores_itens[3]
print(f"Indivíduo de teste: {teste_ind}")
print(f"Peso: {teste_peso:.2f} kg (inválido, > {CAPACIDADE_MAXIMA} kg)")
print(f"Fitness esperado: 0 (devido à penalidade)")
print(f"Fitness calculado: {evalKnapsack(teste_ind)}")

teste_ind_valido = [0, 1, 1, 1, 1, 1, 0, 0] # Livro, Garrafa, Câmera, Casaco, Lanche
teste_peso_valido = sum(pesos_itens[i] for i in [1,2,3,4,5])
print(f"\nIndivíduo de teste válido: {teste_ind_valido}")
print(f"Peso: {teste_peso_valido:.2f} kg (válido)")
print(f"Fitness calculado: {evalKnapsack(teste_ind_valido)}")
Indivíduo de teste: [1, 1, 0, 1, 0, 0, 0, 0]
Peso: 4.50 kg (inválido, > 4.0 kg)
Fitness esperado: 0 (devido à penalidade)
Fitness calculado: (0,)

Indivíduo de teste válido: [0, 1, 1, 1, 1, 1, 0, 0]
Peso: 4.20 kg (válido)
Fitness calculado: (0,)

3.2. Registro dos Operadores Genéticos

Agora, registramos as funções para crossover, mutação e seleção na nossa toolbox.

# @title Registro dos operadores na `toolbox`

# Função de avaliação (fitness)
toolbox.register("evaluate", evalKnapsack)

# Operador de Crossover (Recombinação)
# `cxTwoPoint` combina dois indivíduos trocando um segmento entre dois pontos.
toolbox.register("mate", tools.cxTwoPoint)

# Operador de Mutação
# `mutFlipBit` inverte o valor de um ou mais bits (genes) em um indivídu-o.
# `indpb=0.05` significa que cada gene tem 5% de chance de ser invertido.
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)

# Operador de Seleção
# `selTournament` seleciona os melhores indivíduos através de um "torneio".
# `tournsize=3` significa que 3 indivíduos são escolhidos aleatoriamente, e o melhor deles vence.
toolbox.register("select", tools.selTournament, tournsize=3)

print("✅ Operadores genéticos registrados:")
print("   - Avaliação: evalKnapsack")
print("   - Crossover: Two Point")
print("   - Mutação: Flip Bit (5% de chance por gene)")
print("   - Seleção: Torneio (tamanho 3)")
✅ Operadores genéticos registrados:
   - Avaliação: evalKnapsack
   - Crossover: Two Point
   - Mutação: Flip Bit (5% de chance por gene)
   - Seleção: Torneio (tamanho 3)

Parte 4: Execução do Algoritmo Genético

Com tudo configurado, podemos executar o algoritmo. O DEAP oferece algoritmos prontos, como o eaSimple, que implementa um fluxo padrão de um AG.

Para acompanhar o progresso, usamos o Statistics para coletar métricas a cada geração (média, desvio padrão, mínimo e máximo do fitness).

# @title Execução e análise dos resultados

def main():
    random.seed(42) # Para reprodutibilidade

    # Cria a população inicial
    pop = toolbox.population(n=300) # 300 indivíduos

    # HallOfFame armazena o melhor indivíduo encontrado durante a evolução
    hof = tools.HallOfFame(1)
    
    # Configura as estatísticas que queremos coletar
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", np.mean)
    stats.register("std", np.std)
    stats.register("min", np.min)
    stats.register("max", np.max)

    # Executa o algoritmo genético
    # CXPB: Probabilidade de Crossover
    # MUTPB: Probabilidade de Mutação
    # NGEN: Número de Gerações
    pop, log = algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, 
                                   stats=stats, halloffame=hof, verbose=True)

    return pop, log, hof

if __name__ == "__main__":
    pop, log, hof = main()
gen	nevals	avg    	std    	min	max 
0  	300   	963.333	1007.32	0  	3400
1  	167   	1633   	938.924	0  	3400
2  	181   	1983.33	940.357	0  	3400
3  	166   	2242.17	885.116	0  	3400
4  	202   	2193   	1041.15	0  	3400
5  	180   	2342.5 	1053.43	0  	3400
6  	160   	2562.5 	1095.93	0  	3400
7  	189   	2920.33	973.8  	0  	3400
8  	199   	3270.67	594.312	0  	3400
9  	184   	3224.67	708.231	0  	3400
10 	172   	3115.5 	898.407	0  	3400
11 	185   	3239.33	701.607	0  	3400
12 	195   	3264.33	629.678	0  	3400
13 	170   	3317.67	490.022	0  	3400
14 	188   	3193.67	761.66 	0  	3400
15 	169   	3247   	660.372	0  	3400
16 	161   	3300.67	510.555	0  	3400
17 	174   	3290.83	540.023	0  	3400
18 	189   	3269.5 	622.504	0  	3400
19 	193   	3223.67	711.716	0  	340018 	189   	3269.5 	622.504	0  	3400
19 	193   	3223.67	711.716	0  	3400

20 	174   	3279.33	557.291	0  	3400
21 	181   	3266.67	606.923	0  	3400
22 	189   	3272.33	623.593	0  	3400
23 	168   	3234.33	672.3  	0  	3400
24 	185   	3240   	703.562	0  	3400
20 	174   	3279.33	557.291	0  	3400
21 	181   	3266.67	606.923	0  	3400
22 	189   	3272.33	623.593	0  	3400
23 	168   	3234.33	672.3  	0  	3400
24 	185   	3240   	703.562	0  	3400
25 	192   	3237.67	655.907	0  	3400
26 	175   	3254.33	663.738	0  	3400
27 	195   	3273   	599.142	0  	3400
28 	164   	3284.67	592.141	0  	3400
29 	174   	3257.83	640.934	0  	3400
30 	177   	3219.33	727.204	0  	3400
31 	181   	3213.33	717    	0  	3400
32 	168   	3303.67	507.3  	0  	3400
33 	172   	3229.67	707.121	0  	3400
34 	183   	3297   	533.564	0  	3400
35 	180   	3250.67	660.227	0  	3400
36 	165   	3271.33	622.183	0  	3400
37 	199   	3294.83	536.888	0  	3400
38 	202   	3284.5 	558.153	0  	3400
39 	185   	3190   	737.631	0  	3400
40 	197   	3147   	832.801	0  	3400
25 	192   	3237.67	655.907	0  	3400
26 	175   	3254.33	663.738	0  	3400
27 	195   	3273   	599.142	0  	3400
28 	164   	3284.67	592.141	0  	3400
29 	174   	3257.83	640.934	0  	3400
30 	177   	3219.33	727.204	0  	3400
31 	181   	3213.33	717    	0  	3400
32 	168   	3303.67	507.3  	0  	3400
33 	172   	3229.67	707.121	0  	3400
34 	183   	3297   	533.564	0  	3400
35 	180   	3250.67	660.227	0  	3400
36 	165   	3271.33	622.183	0  	3400
37 	199   	3294.83	536.888	0  	3400
38 	202   	3284.5 	558.153	0  	3400
39 	185   	3190   	737.631	0  	3400
40 	197   	3147   	832.801	0  	3400

Parte 5: Análise da Melhor Solução

O HallOfFame (hof) guardou a melhor solução encontrada. Vamos inspecioná-la para ver quais itens o algoritmo escolheu.

# @title Inspeção da melhor solução encontrada

best_solution = hof[0]
print("🏆 Melhor Solução Encontrada:")
print(f"   - Representação: {best_solution}")
print(f"   - Fitness (Valor Total): {best_solution.fitness.values[0]:.2f} R$")

print("\n🎒 Itens na Mochila:")
valor_final = 0
peso_final = 0
for i in range(len(best_solution)):
    if best_solution[i] == 1:
        nome = nomes_itens[i]
        valor = valores_itens[i]
        peso = pesos_itens[i]
        print(f"   - {nome} (Valor: {valor}, Peso: {peso} kg)")
        valor_final += valor
        peso_final += peso

print("\n📊 Resumo Final:")
print(f"   - Valor Total: {valor_final:.2f} R$")
print(f"   - Peso Total: {peso_final:.2f} kg (Capacidade: {CAPACIDADE_MAXIMA} kg)")
🏆 Melhor Solução Encontrada:
   - Representação: [1, 0, 0, 1, 0, 1, 0, 1]
   - Fitness (Valor Total): 3400.00 R$

🎒 Itens na Mochila:
   - Laptop (Valor: 1500, Peso: 2 kg)
   - Câmera (Valor: 1200, Peso: 1.5 kg)
   - Lanche (Valor: 100, Peso: 0.2 kg)
   - Fone de Ouvido (Valor: 600, Peso: 0.3 kg)

📊 Resumo Final:
   - Valor Total: 3400.00 R$
   - Peso Total: 4.00 kg (Capacidade: 4.0 kg)
# @title Implementação da Função de Fitness

def evalKnapsack(individual: list) -> tuple:
    """Função de fitness para o Problema da Mochila.

    Calcula o valor total dos itens no indivíduo. Se o peso total exceder
    a capacidade, retorna um fitness de 0 (penalidade).

    Parameters:
        individual: Uma lista de 0s e 1s representando a solução.

    Returns:
        Um tuple contendo o valor total (o fitness).
    """
    valor_total = 0.0
    peso_total = 0.0
    for i in range(len(individual)):
        if individual[i] == 1:
            valor_total += valores_itens[i]
            peso_total += pesos_itens[i]

    # Aplica a penalidade se a restrição de peso for violada
    if peso_total > CAPACIDADE_MAXIMA:
        return 0,  # Retorna uma tupla, pois o DEAP espera isso

    return valor_total,

# Testando a função com um indivíduo de exemplo
teste_ind = [1, 1, 0, 1, 0, 0, 0, 0] # Laptop, Livro, Câmera
teste_peso = pesos_itens[0] + pesos_itens[1] + pesos_itens[3]
teste_valor = valores_itens[0] + valores_itens[1] + valores_itens[3]
print(f"Indivíduo de teste: {teste_ind}")
print(f"Peso: {teste_peso:.2f} kg (inválido, > {CAPACIDADE_MAXIMA} kg)")
print(f"Fitness esperado: 0 (devido à penalidade)")
print(f"Fitness calculado: {evalKnapsack(teste_ind)}")

teste_ind_valido = [0, 1, 1, 1, 1, 1, 0, 0] # Livro, Garrafa, Câmera, Casaco, Lanche
teste_peso_valido = sum(pesos_itens[i] for i in [1,2,3,4,5])
print(f"\nIndivíduo de teste válido: {teste_ind_valido}")
print(f"Peso: {teste_peso_valido:.2f} kg (válido)")
print(f"Fitness calculado: {evalKnapsack(teste_ind_valido)}")
Indivíduo de teste: [1, 1, 0, 1, 0, 0, 0, 0]
Peso: 4.50 kg (inválido, > 4.0 kg)
Fitness esperado: 0 (devido à penalidade)
Fitness calculado: (0,)

Indivíduo de teste válido: [0, 1, 1, 1, 1, 1, 0, 0]
Peso: 4.20 kg (válido)
Fitness calculado: (0,)