This is supposed to be an evolutionary algorithmic program, and I'm not sure if it does what it's supposed to do.
It is supposed to take a random list of numbers (containing at least one 5), and over many generations, increase the probability of 5's reproducing, thus having the 5's eventually taking over the output.
Is this an accurate representation of an evolutionary algorithm? If so, how can I make it better, and if not, how can I make it one?
import random as r
import os
import sys
import time
from subprocess import call
#call('color a', shell=True)
class Entity(object):
def __init__(self, number):
self.number = number
self.chance = 10
self.age = 0
class Fitness(object):
def __init__(self, population):
self.population = population
def StartPopulation(self, pop, entities):
for x in range(pop):
entity = Entity(r.randint(1, 10))
entities.append(entity)
def FitnessMethod(self, entities):
for x in entities:
if x.number == 5:
x.chance += 10
else:
x.chance += 0
if x.chance > 100:
x.chance = 100
else:
x.chance += 0
def Reproduce(self,entities):
x = entities[r.randint(0, (len(entities) - 1))]
if r.randint(0, 100) < x.chance:
random_children = r.randint(1, 3)
for a in range(random_children):
entity = Entity(x.number)
entity.chance = x.chance
entities.append(entity)
x.age += 1
def Aging(self, entities):
for x in entities:
if x.age >= 1:
entities.remove(x)
pop = r.randint(5, 10)
entities = []
Fitness = Fitness(pop)
Fitness.StartPopulation(pop, entities)
for x in entities:
print x.number, x.chance
print "~~~~~~~~~~~~~\n"
raw_input()
for x in range(200):
Fitness.FitnessMethod(entities)
Fitness.Reproduce(entities)
Fitness.Aging(entities)
for x in entities:
print x.number, x.age, x.chance
print "Generation_Mutation_Complete"
raw_input()
-
\$\begingroup\$ It would be preferred to post your new code as a new follow-up question. We no longer encourage extended reviews of newer code in the same question. \$\endgroup\$Jamal– Jamal2014年05月16日 15:21:24 +00:00Commented May 16, 2014 at 15:21
-
1\$\begingroup\$ Ok, editing and creating new question(feel free to edit it, I know you love that job). \$\endgroup\$FrigidDev– FrigidDev2014年05月16日 15:22:24 +00:00Commented May 16, 2014 at 15:22
-
\$\begingroup\$ I submitted a new question, here it is: (codereview.stackexchange.com/questions/50927/…) \$\endgroup\$FrigidDev– FrigidDev2014年05月16日 15:28:59 +00:00Commented May 16, 2014 at 15:28
2 Answers 2
Picking up where Josay left off:
Simulating evolution
The current algorithm has some flaws as an evolution simulation:
- Entities inherit their parents' fitness. Fitness should be determined by the entity's traits; it isn't a trait in its own right.
- Entities only age when they reproduce, and the number of offspring doesn't depend on the fitness. This means all entities reproduce eventually; fitter ones just do it faster! Furthermore, the number of offspring doesn't depend on the fitness, so all entities have the same reproductive success! In an evolution simulation, fitter organisms should have more reproductive success.
Some other oddities:
- Only one organism per step can reproduce. Why not let them all reproduce at once?
- Randomness is introduced in too many places: in particular, the fitness is random, but it's also compared to a random number. You only need one of these. It's better to keep randomness out of the fitness function entirely, so its results are more meaningful and it's easier to debug.
- The population grows without bound. It should probably be limited, perhaps with
random.sample
.
It's not necessary to model aging at all in a simple evolution simulator. You can simply have every organism die in every generation. Or you can have them die randomly, without keeping track of age.
There are two important parts of a genetic algorithm:
The fitness function: given an organism, how successful is it?
The breeding function: Given a population and a fitness function, produce the next generation. There are a lot of options here: you can choose parents randomly weighted by fitness, or let everything reproduce a few times and keep the fittest children, or let everything reproduce according to its fitness and keep random children.
Design
Fitness
does not make much sense as a class, because its instances don't represent anything. If each instance represents a population, it could be a Population
. But it's simpler to represent a population as a list of organisms, with no separate class. (Not everything needs to be a class!)
Some of the methods of Fitness
should be operations on a single Entity
, not a whole population.
Several functions modify objects unnecessarily. In particular, fitness_method
should simply return a fitness rather than modifying the Entity. (This causes a bug: the fitness of 5 increases over time!)
Minor style issues
Some names could be improved:
fitness_method
isfitness
. Method names should say what they do, not the fact that they're methods.aging
should beremoveOldOrganisms
.Entity
is vague.Organism
is more specific. Populations can be calledpopulation
, notentities
.
The loop to print a population is repeated and should be factored out.
raw_input
can print a prompt, so the separate print
before it is unnecessary. But there's no need to wait for user input at all.
It's not necessary to rename random
.
How I'd do it
Here's a version with most of the above changes:
import random
class Organism:
def __init__(self, number):
self.number = number
def fitness(self):
"How many children can this organism have?"
return 3 if self.number == 5 else 2
def reproduce(self):
"Make a child. Currently children are just copies of the parent."
return Organism(self.number)
max_population = 100
def nextGeneration(population):
children = []
for x in population:
n_children = random.randint(0, x.fitness())
children += [x.reproduce() for _ in range(n_children)]
return random.sample(children, min(len(children), max_population))
def printPopulation(population):
for x in population:
print x.number, x.fitness()
population = [Organism(random.randint(1, 10)) for _ in range(10)]
printPopulation(population)
for gen in range(10):
print "Generation", gen, "population", len(population)
population = nextGeneration(population)
printPopulation(population)
This uses fitness to determine the number of children rather than which children survive; either is reasonable.
-
\$\begingroup\$ I'm glad I wasn't able to finish my answer this morning because of the freeze, this is much better than what I would have done. \$\endgroup\$SylvainD– SylvainD2014年05月16日 14:40:27 +00:00Commented May 16, 2014 at 14:40
-
\$\begingroup\$ Thank Anonymous for that very good answer. I actually changed my code yesterday, before reading this, so I will edit my question to show you where I currently am. As of now, it is working splendidly, and with some simple changes, I can have it evolve into complete sentences. Thanks for your help, and the new code is in the question. \$\endgroup\$FrigidDev– FrigidDev2014年05月16日 15:17:14 +00:00Commented May 16, 2014 at 15:17
-
\$\begingroup\$ I'm creating a new question, it will be closely linked to this one. \$\endgroup\$FrigidDev– FrigidDev2014年05月16日 15:20:37 +00:00Commented May 16, 2014 at 15:20
The formatting of your code is a bit unusual : no line break between classes, line breaks in the middle of the functions, local variable names starting with an upper-case letter, etc. To be fair, your code does't even run on my setup because of the Fitness
variable.
A few tools exist to perform some checks on your Python code. Let's run then and see what they tell us :
pep8
fitness.py:11:1: E302 expected 2 blank lines, found 0
fitness.py:16:1: E302 expected 2 blank lines, found 0
fitness.py:30:18: W291 trailing whitespace
fitness.py:36:18: W291 trailing whitespace
fitness.py:40:5: E303 too many blank lines (2)
fitness.py:40:23: E231 missing whitespace after ','
fitness.py:42:41: W291 trailing whitespace
fitness.py:44:45: W291 trailing whitespace
fitness.py:53:35: W291 trailing whitespace
fitness.py:55:1: E302 expected 2 blank lines, found 1
fitness.py:66:5: E303 too many blank lines (2)
fitness.py:72:45:W291 trailing whitespace
pychecker
fitness.py:6: Imported module (os) not used
fitness.py:7: Imported module (sys) not used
fitness.py:8: Imported module (time) not used
fitness.py:21: Local variable (x) not used
fitness.py:44: Local variable (a) not used
fitness.py:58: Variable (Fitness) used before being set
fitness.py:59: Invalid arguments to (StartPopulation), got 2, expected
fitness.py:59: self is not first method argument
fitness.py:67: Invalid arguments to (FitnessMethod), got 1, expected 2 fitness.py:67: self is not first method argument
fitness.py:68: Invalid arguments to (Reproduce), got 1, expected 2 fitness.py:68: self is not first method argument
fitness.py:69: Invalid arguments to (Aging), got 1, expected 2
fitness.py:69: self is not first method argument
pyflakes
fitness.py:6: 'os' imported but unused
fitness.py:7: 'sys' imported but unused
fitness.py:8: 'time' imported but unused
fitness.py:9: 'call' imported but unused
fitness.py:58: local variable 'Fitness' (defined in enclosing scope on line 16) referenced before assignment
After fixing the different issues, your code looks like :
#!/usr/bin/python
"""Docstring for Python module"""
import random as r
class Entity(object):
def __init__(self, number):
self.number = number
self.chance = 10
self.age = 0
class Fitness(object):
def __init__(self, population):
self.population = population
def StartPopulation(self, pop, entities):
for _ in range(pop):
entity = Entity(r.randint(1, 10))
entities.append(entity)
def FitnessMethod(self, entities):
for x in entities:
if x.number == 5:
x.chance += 10
else:
x.chance += 0
if x.chance > 100:
x.chance = 100
else:
x.chance += 0
def Reproduce(self, entities):
x = entities[r.randint(0, (len(entities) - 1))]
if r.randint(0, 100) < x.chance:
random_children = r.randint(1, 3)
for _ in range(random_children):
entity = Entity(x.number)
entity.chance = x.chance
entities.append(entity)
x.age += 1
def Aging(self, entities):
for x in entities:
if x.age >= 1:
entities.remove(x)
def main():
pop = r.randint(5, 10)
entities = []
fitness = Fitness(pop)
fitness.StartPopulation(pop, entities)
for x in entities:
print x.number, x.chance
print "~~~~~~~~~~~~~\n"
raw_input()
for x in range(200):
fitness.FitnessMethod(entities)
fitness.Reproduce(entities)
fitness.Aging(entities)
for x in entities:
print x.number, x.age, x.chance
print "Generation_Mutation_Complete"
if __name__ == "__main__":
main()
Now, x.chance += 0
is probably not useful : let's get rid of it. Also, you can use min
if you want to be sure not to go further than 100:
def FitnessMethod(self, entities):
for x in entities:
if x.number == 5:
x.chance = min(100, x.chance+5)
Then, it seems like all method from Fitness
take entities
as a parameter. This is a good indicator that maybe it should be a member of the class. I also took this chance to move fitness_method + reproduce + aging
in a single method. Also, the StartPopulation
is a bit weird : maybe this should be in the __init__
method. Once integrated, one can easily realise that self.population
is never used.
Once re-simplified, your code becomes :
#!/usr/bin/python
"""Docstring for Python module"""
import random as r
class Entity(object):
def __init__(self, number):
self.number = number
self.chance = 10
self.age = 0
class Fitness(object):
def __init__(self, population):
self.entities = []
for _ in range(population):
self.entities.append(Entity(r.randint(1, 10)))
def fitness_method(self):
for x in self.entities:
if x.number == 5:
x.chance = min(100, x.chance+5)
def reproduce(self):
x = self.entities[r.randint(0, (len(self.entities) - 1))]
if r.randint(0, 100) < x.chance:
random_children = r.randint(1, 3)
for _ in range(random_children):
entity = Entity(x.number)
entity.chance = x.chance
self.entities.append(entity)
x.age += 1
def aging(self):
for x in self.entities:
if x.age >= 1:
self.entities.remove(x)
def run_step(self):
self.fitness_method()
self.reproduce()
self.aging()
def main():
fitness = Fitness(r.randint(5, 10))
for x in fitness.entities:
print x.number, x.chance
print "~~~~~~~~~~~~~\n"
raw_input()
for x in range(200):
fitness.run_step()
for x in fitness.entities:
print x.number, x.age, x.chance
print "Generation_Mutation_Complete"
if __name__ == "__main__":
main()
Now, a few points about list :
you shouldn't alter a container while looping over it. You'll find the explanation here and some information about the right way to do this here about what you should be doing in your
aging
function. (Btw,aging
is not a very descriptive name for what the method does, the aging process is more thex.age += 1
part).the constructor can be re-written using list comprehension
It would look like this :
def __init__(self, population):
self.entities = [Entity(r.randint(1, 10)) for _ in range(population)]
I have to go before I reach the interesting part but I am not convinced your code performs a proper evolutionnary algorithm. I suggest you have an additional look at ressources online and go back to your code.
Edit :
Anonymous' answer is probably the kind of answer you were expecting. However, I'll share with you a few other findings that I was about to post before the freeze.
You could add a default parameter
chance
in the constructor ofEntity
. This allows you to makeReproduce
more concise as it doesn't need to updatechance
after the creation. You can even take this chance to use list comprehension again.You do not need to keep track of the age of each entity : as soon as you were about to "make it older", you can remove it alltogether.
This is what you get afterwards :
class Entity(object):
def __init__(self, number, chance=10):
self.number = number
self.chance = chance
class Fitness(object):
def __init__(self, population):
self.entities = [Entity(r.randint(1, 10)) for _ in range(population)]
def fitness_method(self):
for x in self.entities:
if x.number == 5:
x.chance = min(100, x.chance+5)
def reproduce(self):
x = r.choice(self.entities)
if r.randint(0, 100) < x.chance:
# removing aging entity
self.entities.remove(x)
self.entities.extend(Entity(x.number, x.chance) for _ in range(r.randint(1, 3)))
def run_step(self):
self.fitness_method()
self.reproduce()
-
\$\begingroup\$ Thank you for pointing out some errors, but please, what's the interesting part! \$\endgroup\$FrigidDev– FrigidDev2014年05月15日 17:09:49 +00:00Commented May 15, 2014 at 17:09