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.
creator: Usamos para criar as classes base do nosso problema. CriaremosFitnessMax, uma classe de fitness que busca maximizar um único objetivo, eIndividual, que será nossa representação (um indivíduo da população), baseada em uma lista com um atributo de fitness.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,)