Last active
June 3, 2016 19:32
-
-
Save m-butterfield/9883166 to your computer and use it in GitHub Desktop.
The knapsack problem: http://en.wikipedia.org/wiki/Knapsack_problem
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| My Python spin on this: | |
| http://burakkanber.com/blog/machine-learning-genetic-algorithms-in-javascript-part-2/ | |
| """ | |
| import math | |
| import random | |
| import sys | |
| from copy import copy | |
| from optparse import OptionParser | |
| elements = \ | |
| {'Actinium': {'value': 317, 'weight': 149}, | |
| 'Aluminium': {'value': 343, 'weight': 195}, | |
| 'Americium': {'value': 365, 'weight': 66}, | |
| 'Antimony': {'value': 479, 'weight': 28}, | |
| 'Argon': {'value': 395, 'weight': 317}, | |
| 'Arsenic': {'value': 393, 'weight': 213}, | |
| 'Astatine': {'value': 210, 'weight': 392}, | |
| 'Barium': {'value': 417, 'weight': 307}, | |
| 'Berkelium': {'value': 458, 'weight': 289}, | |
| 'Beryllium': {'value': 387, 'weight': 405}, | |
| 'Bismuth': {'value': 497, 'weight': 33}, | |
| 'Bohrium': {'value': 479, 'weight': 236}, | |
| 'Boron': {'value': 174, 'weight': 12}, | |
| 'Bromine': {'value': 199, 'weight': 114}, | |
| 'Cadmium': {'value': 394, 'weight': 411}, | |
| 'Caesium': {'value': 416, 'weight': 361}, | |
| 'Calcium': {'value': 395, 'weight': 281}, | |
| 'Californium': {'value': 322, 'weight': 302}, | |
| 'Carbon': {'value': 483, 'weight': 298}, | |
| 'Cerium': {'value': 414, 'weight': 259}, | |
| 'Chlorine': {'value': 460, 'weight': 56}, | |
| 'Chromium': {'value': 295, 'weight': 299}, | |
| 'Cobalt': {'value': 249, 'weight': 288}, | |
| 'Copernicium': {'value': 460, 'weight': 251}, | |
| 'Copper': {'value': 314, 'weight': 91}, | |
| 'Curium': {'value': 407, 'weight': 393}, | |
| 'Darmstadtium': {'value': 344, 'weight': 308}, | |
| 'Dubnium': {'value': 187, 'weight': 168}, | |
| 'Dysprosium': {'value': 128, 'weight': 166}, | |
| 'Einsteinium': {'value': 94, 'weight': 455}, | |
| 'Erbium': {'value': 399, 'weight': 432}, | |
| 'Europium': {'value': 271, 'weight': 409}, | |
| 'Fermium': {'value': 347, 'weight': 216}, | |
| 'Fluorine': {'value': 306, 'weight': 414}, | |
| 'Francium': {'value': 253, 'weight': 433}, | |
| 'Gadolinium': {'value': 231, 'weight': 86}, | |
| 'Gallium': {'value': 254, 'weight': 470}, | |
| 'Germanium': {'value': 25, 'weight': 77}, | |
| 'Gold': {'value': 267, 'weight': 339}, | |
| 'Hafnium': {'value': 101, 'weight': 138}, | |
| 'Hassium': {'value': 353, 'weight': 201}, | |
| 'Helium': {'value': 380, 'weight': 309}, | |
| 'Holmium': {'value': 109, 'weight': 54}, | |
| 'Hydrogen': {'value': 400, 'weight': 389}, | |
| 'Indium': {'value': 329, 'weight': 322}, | |
| 'Iodine': {'value': 253, 'weight': 345}, | |
| 'Iridium': {'value': 68, 'weight': 121}, | |
| 'Iron': {'value': 360, 'weight': 422}, | |
| 'Krypton': {'value': 8, 'weight': 490}, | |
| 'Lanthanum': {'value': 453, 'weight': 291}, | |
| 'Lawrencium': {'value': 351, 'weight': 84}, | |
| 'Lead': {'value': 395, 'weight': 65}, | |
| 'Lithium': {'value': 424, 'weight': 339}, | |
| 'Lutetium': {'value': 224, 'weight': 311}, | |
| 'Magnesium': {'value': 98, 'weight': 327}, | |
| 'Manganese': {'value': 447, 'weight': 114}, | |
| 'Meitnerium': {'value': 307, 'weight': 278}, | |
| 'Mendelevium': {'value': 331, 'weight': 304}, | |
| 'Mercury': {'value': 438, 'weight': 259}, | |
| 'Molybdenum': {'value': 343, 'weight': 147}, | |
| 'Neodymium': {'value': 475, 'weight': 127}, | |
| 'Neon': {'value': 127, 'weight': 149}, | |
| 'Neptunium': {'value': 300, 'weight': 117}, | |
| 'Nickel': {'value': 482, 'weight': 458}, | |
| 'Niobium': {'value': 375, 'weight': 56}, | |
| 'Nitrogen': {'value': 303, 'weight': 409}, | |
| 'Nobelium': {'value': 236, 'weight': 49}, | |
| 'Osmium': {'value': 490, 'weight': 208}, | |
| 'Oxygen': {'value': 497, 'weight': 432}, | |
| 'Palladium': {'value': 387, 'weight': 353}, | |
| 'Phosphorus': {'value': 157, 'weight': 49}, | |
| 'Platinum': {'value': 29, 'weight': 182}, | |
| 'Plutonium': {'value': 455, 'weight': 106}, | |
| 'Polonium': {'value': 394, 'weight': 293}, | |
| 'Potassium': {'value': 221, 'weight': 383}, | |
| 'Praseodymium': {'value': 83, 'weight': 58}, | |
| 'Promethium': {'value': 480, 'weight': 11}, | |
| 'Protactinium': {'value': 50, 'weight': 457}, | |
| 'Radium': {'value': 109, 'weight': 303}, | |
| 'Radon': {'value': 203, 'weight': 116}, | |
| 'Rhenium': {'value': 141, 'weight': 480}, | |
| 'Rhodium': {'value': 428, 'weight': 418}, | |
| 'Roentgenium': {'value': 201, 'weight': 171}, | |
| 'Rubidium': {'value': 367, 'weight': 278}, | |
| 'Ruthenium': {'value': 214, 'weight': 325}, | |
| 'Rutherfordium': {'value': 233, 'weight': 345}, | |
| 'Samarium': {'value': 192, 'weight': 361}, | |
| 'Scandium': {'value': 79, 'weight': 394}, | |
| 'Seaborgium': {'value': 125, 'weight': 361}, | |
| 'Selenium': {'value': 96, 'weight': 419}, | |
| 'Silicon': {'value': 122, 'weight': 356}, | |
| 'Silver': {'value': 429, 'weight': 182}, | |
| 'Sodium': {'value': 341, 'weight': 247}, | |
| 'Strontium': {'value': 159, 'weight': 310}, | |
| 'Sulfur': {'value': 438, 'weight': 151}, | |
| 'Tantalum': {'value': 397, 'weight': 177}, | |
| 'Technetium': {'value': 105, 'weight': 123}, | |
| 'Tellurium': {'value': 305, 'weight': 443}, | |
| 'Terbium': {'value': 75, 'weight': 100}, | |
| 'Thallium': {'value': 425, 'weight': 342}, | |
| 'Thorium': {'value': 129, 'weight': 342}, | |
| 'Thulium': {'value': 395, 'weight': 361}, | |
| 'Tin': {'value': 436, 'weight': 490}, | |
| 'Titanium': {'value': 303, 'weight': 377}, | |
| 'Tungsten': {'value': 234, 'weight': 14}, | |
| 'Ununhexium': {'value': 449, 'weight': 459}, | |
| 'Ununoctium': {'value': 411, 'weight': 184}, | |
| 'Ununpentium': {'value': 497, 'weight': 145}, | |
| 'Ununquadium': {'value': 113, 'weight': 282}, | |
| 'Ununseptium': {'value': 7, 'weight': 327}, | |
| 'Ununtrium': {'value': 52, 'weight': 158}, | |
| 'Uranium': {'value': 77, 'weight': 118}, | |
| 'Vanadium': {'value': 308, 'weight': 381}, | |
| 'Xenon': {'value': 19, 'weight': 463}, | |
| 'Ytterbium': {'value': 222, 'weight': 417}, | |
| 'Yttrium': {'value': 109, 'weight': 175}, | |
| 'Zinc': {'value': 140, 'weight': 104}, | |
| 'Zirconium': {'value': 288, 'weight': 453}} | |
| class Chromosome(object): | |
| def __init__(self, members, weight=0, value=0, max_weight=1000, mutation_rate=0.6, score=0): | |
| self.members = members | |
| self.weight = weight | |
| self.value = value | |
| self.max_weight = max_weight | |
| self.mutation_rate = mutation_rate | |
| self.score = score | |
| for element in self.members: | |
| if self.members[element].get('active') is None: | |
| self.members[element]['active'] = int(round(random.random())) | |
| self.mutate() | |
| self.calc_score() | |
| def mutate(self): | |
| if self.mutation_rate < random.random(): | |
| return False | |
| element = self.members.keys()[random.randint(0, len(self.members.keys()) - 1)] | |
| self.members[element]['active'] = 0 if self.members[element]['active'] else 1 | |
| def calc_score(self): | |
| if self.score: | |
| return self.score | |
| self.value = 0 | |
| self.weight = 0 | |
| self.score = 0 | |
| for element in self.members: | |
| if self.members[element]['active']: | |
| self.value += self.members[element]['value'] | |
| self.weight += self.members[element]['weight'] | |
| self.score = self.value | |
| if self.weight > self.max_weight: | |
| self.score -= (self.weight - self.max_weight) * 10 | |
| return self.score | |
| def mate_with(self, other): | |
| child1 = {} | |
| child2 = {} | |
| i = 0 | |
| for element in self.members: | |
| if i % 2 == 0: | |
| child1[element] = copy(self.members[element]) | |
| child2[element] = copy(other.members[element]) | |
| else: | |
| child2[element] = copy(self.members[element]) | |
| child1[element] = copy(other.members[element]) | |
| i += 1 | |
| child1 = Chromosome(child1) | |
| child2 = Chromosome(child2) | |
| return [child1, child2] | |
| class Population(object): | |
| def __init__(self, size=20, elems=elements): | |
| self.size = size | |
| self.elements = elems | |
| self.elitism = 0.2 | |
| self.chromosomes = [] | |
| self.fill() | |
| def fill(self): | |
| while len(self.chromosomes) < self.size: | |
| if len(self.chromosomes) < self.size / 3: | |
| self.chromosomes.append(Chromosome(self.elements)) | |
| else: | |
| self.mate() | |
| def sort(self): | |
| self.chromosomes.sort(key=lambda x: x.calc_score(), reverse=True) | |
| def kill(self): | |
| target = math.floor(self.elitism * len(self.chromosomes)) | |
| while len(self.chromosomes) > target: | |
| self.chromosomes.pop() | |
| def mate(self): | |
| key1 = self.chromosomes[random.randint(0, len(self.chromosomes) - 1)] | |
| key2 = key1 | |
| while key2 == key1: | |
| key2 = self.chromosomes[random.randint(0, len(self.chromosomes) - 1)] | |
| children = key1.mate_with(key2) | |
| self.chromosomes += children | |
| def generation(self, reset=False): | |
| self.sort() | |
| if reset: | |
| self = Population(self.size, self.elements) | |
| else: | |
| self.kill() | |
| self.mate() | |
| self.fill() | |
| self.sort() | |
| def display(self, generation_num, no_improvement): | |
| print "Generation:\t%s" % generation_num | |
| print "Best Value:\t%s" % self.chromosomes[0].score | |
| print "Weight:\t\t%s" % self.chromosomes[0].weight | |
| print "No change in:\t%s\n" % no_improvement | |
| def main(threshold=500): | |
| p = Population() | |
| no_improvement = 0 | |
| generation_num = 0 | |
| while True: | |
| if no_improvement < threshold: | |
| last_score = p.chromosomes[0].calc_score() | |
| p.generation() | |
| if last_score >= p.chromosomes[0].calc_score(): | |
| no_improvement += 1 | |
| else: | |
| no_improvement = 0 | |
| generation_num += 1 | |
| if generation_num % 10 == 0: | |
| p.display(generation_num, no_improvement) | |
| else: | |
| if p.chromosomes[0].weight > p.chromosomes[0].max_weight: | |
| p.generation(reset=True) | |
| no_improvement = 0 | |
| else: | |
| p.display(generation_num, no_improvement) | |
| break | |
| if __name__ == '__main__': | |
| usage = "usage: %prog [options]" | |
| parser = OptionParser(usage) | |
| parser.add_option("-t", "--threshold", type="int", dest="threshold", | |
| default=500, help="Number of generations with no change") | |
| (options, args) = parser.parse_args() | |
| sys.exit(main(options.threshold)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment