I've made this to pick the LQR parameters for my self balancing robots simulation. So to break down what I have done,
Randomly Create Population
I create a random population. The population is a list of lists having 4 parameters each of which is randomly generated. But I have applied bounds for them using the values I got from my manual tuning for my simulation.
def population_create(p_size,geno_size,bounds): p = [ random_param(geno_size,bounds) for i in range(p_size)] return pdef random_param(g,b): return [random.uniform(b[i][0],b[i][1]) for i in range(g)]
Try it on Robot and Evaluate
2.Then I evaluate each parameter to see how much the wheels have rotated and how much the robot is inclined after one minute.
fitness = encoder_readings + tilt
So the closer the fitness is to zero the better the robot balances.This is done for the whole population
Create next generation
3.Then I make the next generation with Mutation, Crossover and passing healthy individuals directly.
def population_reproduce(p,fitness): size_p = len(p) new_p = [] dataframe = pd.DataFrame({"Param":p,"Fitness":fitness}) dataframe = dataframe.sort_values(['Fitness']) dataframe = dataframe.reset_index(drop=True) sorted_p = dataframe['Param'].tolist() elite_part = round(ELITE_PART*size_p) new_p = new_p + sorted_p[:elite_part] for i in range(size_p-elite_part): mom = p[random.randint(0,size_p-1)] dad = p[random.randint(0,size_p-1)] child = crossover(mom,dad) child = mutate(child) new_p.append(child) return new_pdef crossover(p1,p2): crossover = [] locii = [random.randint(0,8) for _ in range(len(p1))] for i in range(len(p1)): if locii[i]>4: crossover.append(p2[i]) else: crossover.append(p1[i]) return crossoverdef mutate(c): size = len(c) for i in range(size): if random.random()< MUTATION_PROBABILITY: c[i] += random.gauss(c[i]/50,MUTATION_DEVIATION) return cThis continues for some generations.
These are the links to the full codes.
I would very much appreciate if you could take a look and let me know if this is a correct implementation of a genetic algorithm.
One observation I made is that the fitness doesn't converge to a lower value after each iteration. It randomly goes up and down. Could this be because of some inconsistency in my stimulation on Webots or is it a mistake in my code thats causing this?
- 1\$\begingroup\$Hey there, I'm under the impression this community is for reviewing code that is working, and not necessarily to check the correctness of your code :)\$\endgroup\$RGS– RGS2021-04-13 10:03:45 +00:00CommentedApr 13, 2021 at 10:03
- \$\begingroup\$I'm sorry if this isn't the place I should be posting this. This code does work. I'm not sure how much I could call this a genetic algorithm? Because most genetic algorithms I've seen tend to converge(get fitter over each iteration).Though mine works it doesn't necessarily converge like the others. So I was expecting if someone could help me improve this like giving me some ideas. Thanks @RGS\$\endgroup\$AfiJaabb– AfiJaabb2021-04-13 10:16:19 +00:00CommentedApr 13, 2021 at 10:16
1 Answer1
On the style of the code:
While I'm not saying the code is or isn't correct, there's some small tweaks you could make it to improve it's legibility:
Spacing
Write code like you would write English (in some sense :P). After commas you needs spaces, so do
def crossover(p1, p2):instead ofdef crossover(p1,p2):;random.uniform(b[i][0], b[i][1])instead ofrandom.uniform(b[i][0],b[i][1])- same thing around binary ops:
if locii[i] > 4:instead ofif locii[i]>4: - etc.
List comprehensions often do not need a leading space, so you can go with
p = [random_param(geno_size, bounds) for i in range(p_size)]instead of
p = [ random_param(geno_size,bounds) for i in range(p_size)]crossover
You have written:
def crossover(p1,p2): crossover = [] locii = [random.randint(0,8) for _ in range(len(p1))] for i in range(len(p1)): if locii[i]>4: crossover.append(p2[i]) else: crossover.append(p1[i]) return crossoverConsider this alternative definition:
def crossover(p1, p2): crossover = [] for p1_elem, p2_elem in zip(p1, p2): if random.randint(0,8) > 4: crossover.append(p2_elem) else: crossover.append(p1_elem) return crossoverNotice I don't precompute alllocii, although you could do that, of course. Also note the usage ofzip which means I can traverse bothp1 andp2, instead of having to use indices to later grab the values. (You can read more aboutzip and find other tips and trickshere.
random_param
In line with one of the things I showed above, instead of doing
def random_param(g,b): return [random.uniform(b[i][0],b[i][1]) for i in range(g)]you could iterate directly overb:
def random_param(g, b): return [random.uniform(bs[0], bs[1]) for bs in b](More on better iterating in Pythonhere.)
Then, take that and unpackbs with* instead of with indices:
def random_param(g, b): return [random.uniform(*bounds) for bounds in b]You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.

