avatarThe Pragmatic Programmers

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

4530

Abstract

new_population. reinsertion/4 will use one of a number of reinsertion strategies to create a new population for you. Finally, you call evolve/4 on the new population.</p><p id="25d8">You can create reinsertion/4 like this:</p><div id="4a29"><pre>​ ​<span class="hljs-function"><span class="hljs-keyword">def</span><span class="hljs-title">reinsertion</span></span>(parents, offspring, leftover, opts \ []) ​<span class="hljs-keyword">do</span>​ ​ strategy = <span class="hljs-title class_">Keyword</span>.get(opts, ​ ​<span class="hljs-symbol">:reinsertion_strategy</span>​, ​ &<span class="hljs-title class_">Toolbox</span>.<span class="hljs-title class_">Reinsertion</span>.pure/<span class="hljs-number">3</span>) ​ apply(strategy, [parents, offspring, leftover]) ​ ​<span class="hljs-keyword">end</span></pre></div><p id="e0e2">In this function, you use apply to apply the specified reinsertion strategy. To customize a reinsertion strategy, you’d pass a function to the :reinsertion_strategy argument.</p><p id="6ca6">Next, you’ll want to edit the mutation function so that it only returns the chromosomes it’s mutated:</p><div id="c382"><pre>​ ​def​ mutation(population, opts \ []) ​<span class="hljs-keyword">do</span>​ ​ mutate_fn = Keyword.<span class="hljs-built_in">get</span>(opts, ​:mutation_type​, &Toolbox.Mutation.scramble/<span class="hljs-number">1</span>) ​ rate = Keyword.<span class="hljs-built_in">get</span>(opts, ​:mutation_rate​, <span class="hljs-number">0.05</span>) ​ n = <span class="hljs-built_in">floor</span>(<span class="hljs-built_in">length</span>(population) * rate) ​ population ​ |> Enum.take_random(n) ​ |> Enum.<span class="hljs-built_in">map</span>(& <span class="hljs-built_in">apply</span>(mutate_fn, [&<span class="hljs-number">1</span>])) ​ ​end​</pre></div><p id="6baf">Next, you need to create your reinsertion toolbox. Create a file reinsertion.ex in toolbox. Inside the file, define a module Toolbox.Reinsertion. This module will contain all of your reinsertion strategies.</p><p id="a642">You now need to define a few reinsertion strategies in your toolbox.</p><h1 id="8d5f">Pure Reinsertion and Generational Replacement</h1><p id="0805">Pure reinsertion is the type of reinsertion you used in the first few chapters of this book. Every chromosome in the old population is replaced with an offspring of the new population. With pure reinsertion, you can either treat mutants as offspring — which is fairly common — and ensure your selection rate and your mutation rate add to 1. Another option is to simply have a selection rate of 1 and mutate children.</p><p id="a9f5">Pure reinsertion is a type of generational replacement. Generational replacement refers to the process of creating an entirely new population so that there’s no overlap between populations. Technically, in a generational replacement strategy, offspring directly replace parents.</p><p id="8ef1">You’re likely to encounter two derivatives of generational replacement when working with genetic algorithms. They are μ+λ, read “mu plus lambda” and μ,λ, read “mu comma lambda.” In μ+λ replacement, a child competes with its parent for survival — the winner being the one with the larger fitness. In μ,λ replacement, more children than the required population size are created and the best children survive. You might also see μ,λ replacement referred to as fitness-based insertion.</p><p id="cd59">Given parents, offspring, and leftovers, you can implement pure reinsertion like this:</p><div id="9595"><pre>​ ​def​ pure(<span class="hljs-variable">_parents</span>, offspring, <span class="hljs-variable">_leftovers</span>), ​<span class="hljs-keyword">do</span>​: offspring</pre></div><p id="be77">Notice only offspring is returned to the next generation.</p><p id="9766">Pure reinsertion maintains none of the strengths of the old population and instead relies on the ability of selection, crossover, and mutation to form a stronger population. Pure reinsertion is fast, but you could potentially eliminate some of your stronger characteristics in a population as a result.</p><h1 id="3ef2">Elitist Reinsertion</h1><p id="63e7">Elitist reinsertion or elitist replacement is a type of reinsertion strategy in which you keep a top-portion of your old population to survive to the next generation. With this strategy, you introduce the notion of a survival rate. The survival rate dictates the percentage of parent chromoso

Options

mes that survive to the next generation. With a population of 100 and a survival rate of 20% or 0.2, you’d keep the top 20% of your parents.</p><p id="a531">One thing to consider with elitist reinsertion is how survival rate affects your population size. Later in this chapter, you’ll see how your population grows based on different survival rates.</p><p id="f1d1">Given parents, offspring, leftovers, and a survival_rate, this is how you would implement elitist reinsertion:</p><div id="6c5e"><pre>​ ​def​ elitist(parents, offspring, leftovers, survival_rate) ​<span class="hljs-keyword">do</span>​ ​ <span class="hljs-keyword">old</span> = parents ++ leftovers ​ n = floor(length(<span class="hljs-keyword">old</span>) * survival_rate) ​ survivors = ​ <span class="hljs-keyword">old</span> ​ |> <span class="hljs-keyword">Enum</span>.sort_by(& &<span class="hljs-number">1</span>.fitness, &>=/<span class="hljs-number">2</span>) ​ |> <span class="hljs-keyword">Enum</span>.take(n) ​ offspring ++ survivors ​ ​<span class="hljs-keyword">end</span></pre></div><p id="ab5c">In this function, you combine parents and leftovers to represent your old population. You ensure the old population is sorted by each chromosome’s fitness in descending order. You then select the first n where n is calculated from the population size and the survival rate. Next, you combine offspring with the top n chromosomes from your old population.</p><p id="9332">Elitist reinsertion is probably the most common reinsertion strategy. It’s reasonably fast with small populations, and it works well because it preserves the strengths of your old population. The purest form of elitist reinsertion maintains only the strongest individual from the previous population. You can do this by specifying a selection rate that selects only one chromosome from the old population to move on to the next generation.</p><h1 id="15b1">Uniform Reinsertion</h1><p id="fe98">Uniform reinsertion or random replacement is a reinsertion strategy that selects random chromosomes from the old population to survive to the next generation. The purpose of uniform reinsertion is to maintain as much genetic diversity as possible in the new population. Uniform reinsertion isn’t very common, but it’s worth trying to see what happens.</p><p id="57bb">Just as with elitist reinsertion, you need to consider how your survival rate will impact your population size when using uniform reinsertion. With uniform reinsertion, you select n random chromosomes from the old population to survive to the next generation.</p><p id="0991">Given parents, offspring, leftover, and a survival_rate, you can implement uniform reinsertion like this:</p><div id="57f6"><pre>​ ​def​ uniform(parents, offspring, leftover, survival_rate) ​<span class="hljs-keyword">do</span>​ ​ <span class="hljs-built_in">old</span> = parents ++ leftover ​ n = floor(length(<span class="hljs-built_in">old</span>) * survival_rate) ​ survivors = ​ <span class="hljs-built_in">old</span> ​ |> Enum.take_random(n) ​ offspring ++ survivors ​ ​<span class="hljs-keyword">end</span></pre></div><p id="8275">This implementation of uniform reinsertion is very similar to elitist reinsertion with a few key differences. First, there’s no need to sort the old population based on fitness because you don’t care about the fitness when selecting survivors. Second, you use take_random/2 rather than take/2 to sample random chromosomes from the old population.</p><p id="4abe">You’ll likely never want to use uniform reinsertion, but it can be good in maintaining the genetic diversity of your population.</p><p id="2cf5"><i>👈 <a href="https://readmedium.com/creating-a-class-schedule-e1da63c04e7d">Creating a Class Schedule</a> | <a href="https://readmedium.com/table-of-contents-879fc8614df">TOC</a> | <a href="https://readmedium.com/experimenting-with-reinsertion-663dd09d9c94">Experimenting with Reinsertion</a> 👉</i></p><p id="cb34"><i>Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats <a href="https://pragprog.com/titles/smgaelixir">directly from the Pragmatic Programmers</a>. If you notice a code error or formatting mistake, please let us know <a href="https://readmedium.com/how-to-report-errata-4e164674347a">here</a> so that we can fix it.</i></p><figure id="3111"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*U2E2a23SuM4520w9CUXSWw.jpeg"><figcaption></figcaption></figure></article></body>

Understanding Reinsertion

Genetic Algorithms in Elixir — by Sean Moriarity (65 / 101)

👈 Creating a Class Schedule | TOC | Experimenting with Reinsertion 👉

Reinsertion is the process of taking chromosomes produced from selection, crossover, and mutation and inserting them back into a population to move on to the next generation. Look at evolve/4 in genetic.ex. It looks like this:

​ ​defevolve(population, problem, generation, opts \\ []) ​do​
​   population = evaluate(population, &problem.fitness_function/1, opts)
​   best = hd(population)
​   IO.write(​"​​\rCurrent best: ​​#{​best.fitness​}​​\tGeneration: ​​#{​generation​}​​"​)
​   ​if​ problem.terminate?(population, generation) ​do​
​     best
​   ​else​
​     {parents, leftover} = select(population, opts)
​     children = crossover(parents, opts)
​     children ++ leftover
​     |> mutation(opts)
​     |> evolve(problem, generation+1, opts)
​   ​end​
​ ​end

If you recall from Chapter 5, Selecting the Best, you combined children and leftover and mutated the result to form a new population. This was a naive approach, but it ensured your population size remained fixed and worked well enough at the time.

Your goal with reinsertion is to utilize all of the chromosomes at your disposal to create a new population that has a good amount of genetic diversity and has a better fitness than your previous one. Your previous reinsertion strategy was to replace your parents with their children. This approach works well enough, but you’ll want to be able to experiment with different reinsertion strategies and take smarter approaches to creating new populations.

Now, you will see how you can implement a few common reinsertion strategies and integrate them in your framework. First, edit evolve/4 so that it matches this:

​ ​defevolve(population, problem, generation, opts \\ []) ​do​
​   population = evaluate(population, &problem.fitness_function/1, opts)
​   best = hd(population)
​   fit_str =
​     best.fitness
​     |> ​:erlang​.float_to_binary(​decimals:4)
​   IO.write(​"​​\rCurrent best: ​​#{​fit_str​}​​\tGeneration: ​​#{​generation​}​​"​)
​   ​if​ problem.terminate?(population, generation) ​do​
​     best
​   ​else​
​     {parents, leftover} = select(population, opts)
​     children = crossover(parents, opts)
​     mutants = mutation(population, opts)
​     offspring = children ++ mutants
​     new_population = reinsertion(parents, offspring, leftover, opts)
​     evolve(new_population, problem, generation+1, opts)
​   ​end​
​ ​end

Here, you’ve broken your population down into four parts: parents, leftover, mutants, and children. mutants and children are combined to form offspring — this is the most traditional approach; however, you could just as easily choose to mutate your children or mutate your new combined population.

Next, you call reinsertion/4 to create a new_population. reinsertion/4 will use one of a number of reinsertion strategies to create a new population for you. Finally, you call evolve/4 on the new population.

You can create reinsertion/4 like this:

​ ​defreinsertion(parents, offspring, leftover, opts \\ []) ​do​
​   strategy = Keyword.get(opts,
​                          ​:reinsertion_strategy​,
​                          &Toolbox.Reinsertion.pure/3)
​   apply(strategy, [parents, offspring, leftover])
​ ​end

In this function, you use apply to apply the specified reinsertion strategy. To customize a reinsertion strategy, you’d pass a function to the :reinsertion_strategy argument.

Next, you’ll want to edit the mutation function so that it only returns the chromosomes it’s mutated:

​ ​def​ mutation(population, opts \\ []) ​do​
​   mutate_fn = Keyword.get(opts, ​:mutation_type​, &Toolbox.Mutation.scramble/1)
​   rate = Keyword.get(opts, ​:mutation_rate​, 0.05)
​   n = floor(length(population) * rate)
​   population
​   |> Enum.take_random(n)
​   |> Enum.map(& apply(mutate_fn, [&1]))
​ ​end​

Next, you need to create your reinsertion toolbox. Create a file reinsertion.ex in toolbox. Inside the file, define a module Toolbox.Reinsertion. This module will contain all of your reinsertion strategies.

You now need to define a few reinsertion strategies in your toolbox.

Pure Reinsertion and Generational Replacement

Pure reinsertion is the type of reinsertion you used in the first few chapters of this book. Every chromosome in the old population is replaced with an offspring of the new population. With pure reinsertion, you can either treat mutants as offspring — which is fairly common — and ensure your selection rate and your mutation rate add to 1. Another option is to simply have a selection rate of 1 and mutate children.

Pure reinsertion is a type of generational replacement. Generational replacement refers to the process of creating an entirely new population so that there’s no overlap between populations. Technically, in a generational replacement strategy, offspring directly replace parents.

You’re likely to encounter two derivatives of generational replacement when working with genetic algorithms. They are μ+λ, read “mu plus lambda” and μ,λ, read “mu comma lambda.” In μ+λ replacement, a child competes with its parent for survival — the winner being the one with the larger fitness. In μ,λ replacement, more children than the required population size are created and the best children survive. You might also see μ,λ replacement referred to as fitness-based insertion.

Given parents, offspring, and leftovers, you can implement pure reinsertion like this:

​ ​def​ pure(_parents, offspring, _leftovers), ​do​: offspring

Notice only offspring is returned to the next generation.

Pure reinsertion maintains none of the strengths of the old population and instead relies on the ability of selection, crossover, and mutation to form a stronger population. Pure reinsertion is fast, but you could potentially eliminate some of your stronger characteristics in a population as a result.

Elitist Reinsertion

Elitist reinsertion or elitist replacement is a type of reinsertion strategy in which you keep a top-portion of your old population to survive to the next generation. With this strategy, you introduce the notion of a survival rate. The survival rate dictates the percentage of parent chromosomes that survive to the next generation. With a population of 100 and a survival rate of 20% or 0.2, you’d keep the top 20% of your parents.

One thing to consider with elitist reinsertion is how survival rate affects your population size. Later in this chapter, you’ll see how your population grows based on different survival rates.

Given parents, offspring, leftovers, and a survival_rate, this is how you would implement elitist reinsertion:

​ ​def​ elitist(parents, offspring, leftovers, survival_rate) ​do​
​   old = parents ++ leftovers
​   n = floor(length(old) * survival_rate)
​   survivors =
​     old
​     |> Enum.sort_by(& &1.fitness, &>=/2)
​     |> Enum.take(n)
​   offspring ++ survivors
​ ​end

In this function, you combine parents and leftovers to represent your old population. You ensure the old population is sorted by each chromosome’s fitness in descending order. You then select the first n where n is calculated from the population size and the survival rate. Next, you combine offspring with the top n chromosomes from your old population.

Elitist reinsertion is probably the most common reinsertion strategy. It’s reasonably fast with small populations, and it works well because it preserves the strengths of your old population. The purest form of elitist reinsertion maintains only the strongest individual from the previous population. You can do this by specifying a selection rate that selects only one chromosome from the old population to move on to the next generation.

Uniform Reinsertion

Uniform reinsertion or random replacement is a reinsertion strategy that selects random chromosomes from the old population to survive to the next generation. The purpose of uniform reinsertion is to maintain as much genetic diversity as possible in the new population. Uniform reinsertion isn’t very common, but it’s worth trying to see what happens.

Just as with elitist reinsertion, you need to consider how your survival rate will impact your population size when using uniform reinsertion. With uniform reinsertion, you select n random chromosomes from the old population to survive to the next generation.

Given parents, offspring, leftover, and a survival_rate, you can implement uniform reinsertion like this:

​ ​def​ uniform(parents, offspring, leftover, survival_rate) ​do​
​   old = parents ++ leftover
​   n = floor(length(old) * survival_rate)
​   survivors =
​     old
​     |> Enum.take_random(n)
​   offspring ++ survivors
​ ​end

This implementation of uniform reinsertion is very similar to elitist reinsertion with a few key differences. First, there’s no need to sort the old population based on fitness because you don’t care about the fitness when selecting survivors. Second, you use take_random/2 rather than take/2 to sample random chromosomes from the old population.

You’ll likely never want to use uniform reinsertion, but it can be good in maintaining the genetic diversity of your population.

👈 Creating a Class Schedule | TOC | Experimenting with Reinsertion 👉

Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats directly from the Pragmatic Programmers. If you notice a code error or formatting mistake, please let us know here so that we can fix it.

Smgaelixir
Recommended from ReadMedium