How to randomly mutate 5 values in a binary list?

Question:

I’m writing a genetic algorithm in which I need to select 5 numbers from the binary list genotype and flip them, so a 10 and 01. I tried putting my code in a loop with a range(1,6) however when I do this it still only changes one of the numbers. Below is my original code without the loop which randomly selects one of the binary values and mutates it. Does anyone know a better way of doing this but for 5 of the elements in the list?

genotype = [1,0,0,1,0,0,1,1,1,0]

def mutate(self):
  gene = random.choice(genotype)
  if genotype[gene] == 1:
    genotype[gene] = 0
  else:
    genotype[gene] = 1
  return genotype

Asked By: J French

||

Answers:

You can use random.sample() function to get 5 unique indices from the list, and then flip them in a loop. Like that:

import random
genotype = [1,0,0,1,0,0,1,1,1,0]

random_five = random.sample(range(len(genotype)), 5)
for i in random_five:
    genotype[i] = 0 if genotype[i] == 1 else 1

print(genotype)

Output is:

[1, 1, 0, 1, 1, 0, 0, 1, 0, 1]
Answered By: ilyankou

random.choice(genotype) will return a random element from genotype list, ie, it will be equal to either 0 or 1. So, since you are using gene as index, your function will always flip an element either at index 0, or at index 1.

You can use the random.sample(population, k) function instead.

Answered By: tado-mi

While your primary problem seems solved after the answer given by ilyankou:

for i in random.sample(range(len(genotype)), 5):
    genotype[i] ^= 1

and this alternative (more realistic) mutation model was suggested:

for i in [random.choice(range(len(genotype))) for _ in range(5)]:
    genotype[i] ^= 1

I found this observation quite challenging and somewhat inspiring

I tried putting my code in a loop with a range(1,6) however when I do this it still only changes one of the numbers.

Is this this always true? Can it or must it be?

I tried several runs of the following code (I removed the superfluous self from your original)

import random
genotype = [1,0,0,1,0,0,1,1,1,0]

def mutate():
  gene = random.choice(genotype)
  if genotype[gene] == 1:
    genotype[gene] = 0
  else:
    genotype[gene] = 1
  return genotype

print(genotype)

for _ in range(1,6):
    mutate()

print(genotype)

and observed only these outputs:

  1. [0, 0, 0, 1, 0, 0, 1, 1, 1, 0] — gene at index 0 flipped
  2. [1, 1, 0, 1, 0, 0, 1, 1, 1, 0] — gene at index 1 flipped

And indeed this has to be like this for an odd count of calls to the mutate function above:

Because gene is one of 0 and 1 and double flips on the same gene reproduce the initial value, only mutations will remain that correspond to gene indices which are chosen an odd number of times, and since you call it for range(1, 6) (an odd total), only one of 0 and 1 can be of odd quantity in the overall process.

Answered By: Wolf

Your question is more towards the software engineering problem, and not towards the EA side. But, I would like to give some suggestions towards your design decision of choosing 5 genes from the genome. You are restricting EA to explore the search space for your solutions, and your EA could definitely get stuck in local optima and might not be able to escape as mutated solutions will be changed around just 5 genes (If the limitation is from a problem itself, can’t do anything about it). As an example, EA might find a near-optimal solution by just changing 3 genes or by changing 7 genes. If it is a design choice, you might like to revisit your design decision for EA.

In general, the EA community uses mutation probability for genes. When one would like to mutate offsprings, it is based on hyperparameter mutation probability (Also, as this is hyperparameter, you also can tune this for your problem to achieve good results later).

Let’s say if your design decision of mutating 5 genes is a constraint, and also you would like to use tunable hyper-parameter mutation probability. You can have your solution like below:

import random

def mutate(genotype, m_probe):
  mutated_offspring = []
  mutated_genes_indexes = set()
  for index, gene in enumerate(genotype):
    if random.uniform(0, 1) >= m_probe and len(mutated_genes_indexes) < 5:
      mutated_offspring.append(int(not gene))
      mutated_genes_indexes.add(index)
    else:
      mutated_offspring.append(int(gene))
  print("Mutated genes indexes: ", mutated_genes_indexes)
  return mutated_offspring

# Each genes have 20% probability to get mutated! NOTE: with higher probability you might not find 5 genes mutated, 20 is chosen based on the constraint and can be tuned later with this constraint.
genotype = [1,0,0,1,0,0,1,1,1,0]
print(mutate(genotype, 0.20))

My preferred design decision would be an equal chance for all genes to get mutated without constraint. In that case, a solution might look like below:

import random

def mutate(genotype, m_probe):
  mutated_offspring = []
  mutated_genes_indexes = set()
  for index, gene in enumerate(genotype):
    if random.uniform(0, 1) >= m_probe:
      mutated_offspring.append(int(not gene))
      mutated_genes_indexes.add(index)
    else:
      mutated_offspring.append(int(gene))
  print("Mutated genes indexes: ", mutated_genes_indexes)
  return mutated_offspring

genotype = [1,0,0,1,0,0,1,1,1,0]
print(mutate(genotype, 0.50))
Answered By: Jay Patel
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.