Just like in Chapter 5, Selecting the Best, and Chapter 6, Generating New Solutions, you’ll need to slightly modify your framework to allow you to customize mutation hyperparameters. These hyperparameters are mutation strategy and mutation rate.
In this section, you’ll create a mutation toolbox and modify your framework to allow you to easily customize mutation in your algorithms.
First, you need to create a mutation toolbox, just like you created a selection and crossover toolbox. Create a new file in toolbox called mutation.ex.
Next, create the Toolbox.Mutation module, like this:
| defmodule Toolbox.Mutation do |
| alias Types.Chromosome |
| |
| # ... |
| end |
You’ll be working a lot with the Chromosome struct in this module, so you’ll want to create an alias. Now, whenever you implement a new mutation strategy, you’ll add it to your mutation toolbox.
Open up genetic.ex and navigate to the mutation/2 function. It looks like this:
| def mutation(population, opts \ []) do |
| population |
| |> Enum.map( |
| fn chromosome -> |
| if :rand.uniform() < 0.05 do |
| %Chromosome{genes: Enum.shuffle(chromosome.genes)} |
| else |
| chromosome |
| end |
| end |
| ) |
| end |
Remember, opts is a Keyword representing the options you can pass to your algorithm when you call Genetic.run/2. First, you need to extract a mutation strategy from opts, like this:
| mutate_fn = Keyword.get(opts, :mutation_type, &Toolbox.Mutation.flip/1) |
Right now, the default is called flip mutation, which is a mutation strategy you’ll implement in the next section. You can change this default to another method if you prefer.
Next, you need to apply your mutation strategy to chromosomes in the population. Change the body of Enum.map/2 to look like this:
| |> Enum.map( |
| fn chromosome -> |
| if :rand.uniform() < 0.05 do |
» | apply(mutate_fn, [chromosome]) |
| else |
| chromosome |
| end |
| ) |
Here you use apply/2 to apply your extracted mutation strategy to chromosome. The mutation strategies you’ll implement in this chapter accept a chromosome and return the mutated chromosome, so apply/2 returns a mutated version of chromosome.
You also need a way to control the mutation rate of your algorithm. Start by extracting :mutation_rate from opts:
| mutate_fn = Keyword.get(opts, :mutation_type, &Toolbox.Mutation.flip/1) |
| rate = Keyword.get(opts, :mutation_rate, 0.05) |
0.05 represents a mutation rate of 5%, which is a good default.
Next, you need to change Enum.map/2 to only mutate chromosomes according to rate. To do this, change the if-condition to look like this:
| |> Enum.map( |
| fn chromosome -> |
» | if :rand.uniform() < rate do |
| apply(mutate_fn, [chromosome]) |
| else |
| chromosome |
| end |
| ) |
Your new mutation/2 function should look like this:
| def mutation(population, opts \ []) do |
» | mutate_fn = Keyword.get(opts, :mutation_type, &Toolbox.Mutation.scramble/1) |
» | rate = Keyword.get(opts, :mutation_rate, 0.05) |
| population |
| |> Enum.map( |
| fn chromosome -> |
» | if :rand.uniform() < rate do |
» | apply(mutate_fn, [chromosome]) |
| else |
| chromosome |
| end |
| end |
| ) |
| end |