├── .gitignore ├── README.md ├── recursion.py └── genetic.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Genetic Algorithm for Knapsack Problem 2 | === 3 | 4 | Generates high quality solution to the Knapsack problem in polynomial-time. 5 | 6 | You can find detailed write-up and understand the approach here: https://arpitbhayani.me/blogs/genetic-knapsack 7 | 8 | ### How to run 9 | 10 | ``` 11 | $ python genetic.py 12 | ``` 13 | -------------------------------------------------------------------------------- /recursion.py: -------------------------------------------------------------------------------- 1 | memo = {} 2 | 3 | def knapsack(W, w, v, n): 4 | if n == 0 or W == 0: 5 | return 0 6 | 7 | # if weight of the nth item is more than the weight 8 | # available in the knapsack the skip it 9 | if (w[n - 1] > W): 10 | return knapsack(W, w, v, n - 1) 11 | 12 | # Check if we already have an answer to the sunproblem 13 | if (W, n) in memo: 14 | return memo[(W, n)] 15 | 16 | # find value of the knapsack when the nth item is picked 17 | value_picked = v[n - 1] + knapsack(W - w[n - 1], w, v, n - 1) 18 | 19 | # find value of the knapsack when the nth item is not picked 20 | value_notpicked = knapsack(W, w, v, n - 1) 21 | 22 | # return the maxmimum of both the cases 23 | # when nth item is picked and not picked 24 | value_max = max(value_picked, value_notpicked) 25 | 26 | # store the optimal answer of the subproblem 27 | memo[(W, n)] = value_max 28 | 29 | return value_max 30 | 31 | 32 | if __name__ == '__main__': 33 | wt = [7, 2, 1, 9] 34 | val = [5, 4, 7, 2] 35 | W = 15 36 | n = len(val) 37 | print(knapsack(W, wt, val, n)) 38 | -------------------------------------------------------------------------------- /genetic.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import List 3 | 4 | 5 | class Item: 6 | def __init__(self, name, weight, value): 7 | self.name = name 8 | self.weight = weight 9 | self.value = value 10 | 11 | 12 | class Individual: 13 | def __init__(self, bits: List[int]): 14 | self.bits = bits 15 | 16 | def __str__(self): 17 | return repr(self.bits) 18 | 19 | def __hash__(self): 20 | return hash(str(self.bits)) 21 | 22 | def fitness(self) -> float: 23 | total_value = sum([ 24 | bit * item.value 25 | for item, bit in zip(items, self.bits) 26 | ]) 27 | 28 | total_weight = sum([ 29 | bit * item.weight 30 | for item, bit in zip(items, self.bits) 31 | ]) 32 | 33 | if total_weight <= MAX_KNAPSACK_WEIGHT: 34 | return total_value 35 | 36 | return 0 37 | 38 | 39 | MAX_KNAPSACK_WEIGHT = 15 40 | CROSSOVER_RATE = 0.53 41 | MUTATION_RATE = 0.013 42 | REPRODUCTION_RATE = 0.15 43 | 44 | items = [ 45 | Item("A", 7, 5), 46 | Item("B", 2, 4), 47 | Item("C", 1, 7), 48 | Item("D", 9, 2) 49 | ] 50 | 51 | 52 | def generate_initial_population(count=6) -> List[Individual]: 53 | population = set() 54 | 55 | # generate initial population having `count` individuals 56 | while len(population) != count: 57 | # pick random bits one for each item and 58 | # create an individual 59 | bits = [ 60 | random.choice([0, 1]) 61 | for _ in items 62 | ] 63 | population.add(Individual(bits)) 64 | 65 | return list(population) 66 | 67 | 68 | def selection(population: List[Individual]) -> List[Individual]: 69 | parents = [] 70 | 71 | # randomly shuffle the population 72 | random.shuffle(population) 73 | 74 | # we use the first 4 individuals 75 | # run a tournament between them and 76 | # get two fit parents for the next steps of evolution 77 | 78 | # tournament between first and second 79 | if population[0].fitness() > population[1].fitness(): 80 | parents.append(population[0]) 81 | else: 82 | parents.append(population[1]) 83 | 84 | # tournament between third and fourth 85 | if population[2].fitness() > population[3].fitness(): 86 | parents.append(population[2]) 87 | else: 88 | parents.append(population[3]) 89 | 90 | return parents 91 | 92 | 93 | def crossover(parents: List[Individual]) -> List[Individual]: 94 | N = len(items) 95 | 96 | child1 = parents[0].bits[:N//2] + parents[1].bits[N//2:] 97 | child2 = parents[0].bits[N//2:] + parents[1].bits[:N//2] 98 | 99 | return [Individual(child1), Individual(child2)] 100 | 101 | 102 | def mutate(individuals: List[Individual]) -> List[Individual]: 103 | for individual in individuals: 104 | for i in range(len(individual.bits)): 105 | if random.random() < MUTATION_RATE: 106 | # Flip the bit 107 | individual.bits[i] = ~individual.bits[i] 108 | 109 | 110 | def next_generation(population: List[Individual]) -> List[Individual]: 111 | next_gen = [] 112 | while len(next_gen) < len(population): 113 | children = [] 114 | 115 | # we run selection and get parents 116 | parents = selection(population) 117 | 118 | # reproduction 119 | if random.random() < REPRODUCTION_RATE: 120 | children = parents 121 | else: 122 | # crossover 123 | if random.random() < CROSSOVER_RATE: 124 | children = crossover(parents) 125 | 126 | # mutation 127 | if random.random() < MUTATION_RATE: 128 | mutate(children) 129 | 130 | next_gen.extend(children) 131 | 132 | return next_gen[:len(population)] 133 | 134 | 135 | def print_generation(population: List[Individual]): 136 | for individual in population: 137 | print(individual.bits, individual.fitness()) 138 | print() 139 | print("Average fitness", sum([x.fitness() for x in population])/len(population)) 140 | print("-" * 32) 141 | 142 | 143 | def average_fitness(population: List[Individual]) -> float: 144 | return sum([i.fitness() for i in population]) / len(population) 145 | 146 | 147 | def solve_knapsack() -> Individual: 148 | population = generate_initial_population() 149 | 150 | avg_fitnesses = [] 151 | 152 | for _ in range(500): 153 | avg_fitnesses.append(average_fitness(population)) 154 | population = next_generation(population) 155 | 156 | population = sorted(population, key=lambda i: i.fitness(), reverse=True) 157 | return population[0] 158 | 159 | 160 | if __name__ == '__main__': 161 | solution = solve_knapsack() 162 | print(solution, solution.fitness()) 163 | --------------------------------------------------------------------------------