4
\$\begingroup\$

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

  1. 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_p
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 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 c

This continues for some generations.

These are the links to the full codes.

Genetic Algorithm Supervisor

Population Script

Robot Controller

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?

askedApr 13, 2021 at 10:01
AfiJaabb's user avatar
\$\endgroup\$
2
  • 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\$CommentedApr 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\$CommentedApr 13, 2021 at 10:16

1 Answer1

2
\$\begingroup\$

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 crossover

Consider 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 crossover

Notice 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]
answeredApr 13, 2021 at 10:17
RGS's user avatar
\$\endgroup\$

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.