Les algorithmes génétiques démystifiés 37: Le problème du sac à dos
Pour continuer notre exploration des algorithmes génétiques, on va s’intéresser maintenant au problème du sac à dos:
En algorithmique, le problème du sac à dos, noté également KP (en anglais,
Knapsack Problem) est un problème d'optimisation combinatoire. Il modélise une
situation analogue au remplissage d'un sac à dos, ne pouvant supporter plus
d'un certain poids, avec tout ou partie d'un ensemble donné d'objets ayant
chacun un poids et une valeur. Les objets mis dans le sac à dos doivent
maximiser la valeur totale, sans dépasser le poids maximum.
Un des intéret de ce problème est que certaines solutions invalides sont plus proches de la meilleure solution que nombres de solutions valides.
De même, c’est un problème théorique qui peut être vu comme une simplification de problèmes pratiques. Par exemple: «Mon bateau peut transporter 100 containers, pour un poids de X tonnes. Je gagne plus ou moins d’argent selon les containers transportés. Quels containers je dois embarquer parmi un choix de 300 containers ?»
La liste des objets que je vais utiliser se trouve sur le projet RosettaCode.
On commence tout de suite avec la création de la population:
KnapsackItem = Struct.new(:name, :weight, :value)
module Knapsack
ITEMS = [
KnapsackItem.new('map', 9, 150),
KnapsackItem.new('compass', 13, 35),
KnapsackItem.new('water', 153, 200),
KnapsackItem.new('sandwich', 50, 160),
KnapsackItem.new('glucose', 15, 60),
KnapsackItem.new('tin', 68, 45),
KnapsackItem.new('banana', 27, 60),
KnapsackItem.new('apple', 39, 40),
KnapsackItem.new('cheese', 23, 30),
KnapsackItem.new('beer', 52, 10),
KnapsackItem.new('suntan cream', 11, 70),
KnapsackItem.new('camera', 32, 30),
KnapsackItem.new('t-shirt', 24, 15),
KnapsackItem.new('trousers', 48, 10),
KnapsackItem.new('umbrella', 73, 40),
KnapsackItem.new('waterproof trousers', 42, 70),
KnapsackItem.new('waterproof overclothes', 43, 75),
KnapsackItem.new('note-case', 22, 80),
KnapsackItem.new('sunglasses', 7, 20),
KnapsackItem.new('towel', 18, 12),
KnapsackItem.new('socks', 4, 50),
KnapsackItem.new('book', 30, 10),
]
endPour ceux qui ne connaissent pas Ruby, Struct permet de définir rapidement
une classe simpliste, une espèce de POxO (Plain Old “insérez votre langage”
Object). La classe KnapsackItem aura donc 3 accesseurs: name, weight et
value. On pourra accéder à la liste des objets avec Knapsack::ITEMS.
Maintenant la classe Individual:
class Individual
def self.random(chromosome_size)
new(nil, chromosome_size)
end
def self.from_chromosome(chromosome)
new(chromosome)
end
attr_accessor :score, :fitness
attr_reader :chromosome
def initialize(chromosome = nil, chromosome_size = nil)
if chromosome
@chromosome = chromosome
else
@chromosome = []
chromosome_size.times { @chromosome << (rand(0..1) == 1) }
end
end
private_class_method :new
def chromosome_as_list
list = []
@chromosome.each_with_index do |gene, index|
list << Knapsack::ITEMS[index].name if gene
end
list.join(', ')
end
def >(other)
return true if other.nil?
score > other.score
end
endUn chromosome est défini comme un Array de booléens:
chromosome_size.times { @chromosome << (rand(0..1) == 1) }Chaque case de l’Array nous indique si un objet est sélectionné (true) ou non (false).
J’ai aussi ajouté 2 nouvelles méthodes à cette classe. Tout d’abord
chromosome_as_list produit une chaîne de caractères avec la liste
des objets sélectionnés dans le chromosome. Puis la méthode > nous
sera utile pour comparer deux chromosomes.
Reste la classe Population, qui est identique à ce que nous avons
déjà écrit avec d’autres algorithmes génétiques:
class Population < Array
def initialize(chromosome_size, population_size)
population_size.times { self << Individual.random(chromosome_size) }
end
def best
self.sort_by{|individual| individual.score}.last
end
endLa prochaine fois on verra une première version de la méthode d’évaluation.
À demain.