avatarThe Pragmatic Programmers

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

5109

Abstract

s-string"> larger </span>| +--------------------+--------------+--------------+</pre></div><p id="bb0f">Because you’re monitoring eight traits, your chromosome will consist of eight binary genes. Implement the genotype like this:</p><div id="1f8f"><pre>​ ​def​ genotype ​<span class="hljs-keyword">do</span>​ ​ genes = for _ <- <span class="hljs-number">1.</span><span class="hljs-number">.8</span>, ​<span class="hljs-keyword">do</span>​: <span class="hljs-keyword">Enum</span>.random(<span class="hljs-number">0.</span><span class="hljs-number">.1</span>) ​ %Chromosome{​genes:​ genes, ​<span class="hljs-built_in">size</span>:​ <span class="hljs-number">8</span>} ​ ​<span class="hljs-keyword">end</span></pre></div><p id="fea1">As you’ve seen before, this is a basic binary genotype of size 8. The initial population will contain tigers of varying combinations of traits.</p><p id="f16f">The next thing you need to do is determine how to evaluate each tiger based on its traits in both a tropical and tundra environment.</p><h1 id="8cc3">Evaluating Fitness in Different Environments</h1><p id="8b3b">Remember, your goal is to determine how tigers evolve in each environment. Because the importance of each trait differs between environments, you need to evaluate chromosomes differently depending on the environment.</p><p id="d2a9">The easiest way to do this is to assign weights or scores to each trait, indicating whether or not a trait is positive or negative to survival. The magnitude of a weight or score indicates the relative importance of that trait in a given environment.</p><p id="997c">The table shows the scores you’ll assign to each trait in both environments.</p><div id="f0a9"><pre>+--------------------+------------+----------+ |<span class="hljs-string"> Trait </span>|<span class="hljs-string"> Tropical </span>|<span class="hljs-string"> Tundra </span>| +--------------------+------------+----------+ |<span class="hljs-string"> Size </span>|<span class="hljs-string">  0.0 </span>|<span class="hljs-string"> 1.0 </span>| |<span class="hljs-string"> Swimming Ability </span>|<span class="hljs-string">  3.0 </span>|<span class="hljs-string"> 3.0 </span>| |<span class="hljs-string"> Fur Color </span>|<span class="hljs-string">  2.0 </span>|<span class="hljs-string"> -2.0 </span>| |<span class="hljs-string"> Fat Stores </span>|<span class="hljs-string"> -1.0 </span>|<span class="hljs-string"> 1.0 </span>| |<span class="hljs-string"> Activity Period </span>|<span class="hljs-string">  0.5 </span>|<span class="hljs-string"> 0.5 </span>| |<span class="hljs-string"> Hunting Ground </span>|<span class="hljs-string">  1.0 </span>|<span class="hljs-string"> 2.0 </span>| |<span class="hljs-string"> Fur Thickness </span>|<span class="hljs-string"> -1.0 </span>|<span class="hljs-string"> 1.0 </span>| |<span class="hljs-string"> Tail Length </span>|<span class="hljs-string">  0.0 </span>|<span class="hljs-string"> 0.0 </span>| +--------------------+------------+----------+</pre></div><h1 id="349a">Determining Scores</h1><h2 id="e567">INFORMATION</h2><p id="018c">The scores chosen for each trait in this example are arbitrary. In a practical simulation, you’d want to determine scores with research and data, and hopefully be able to provide a justification for each one. These scores were chosen based on intuition. They don’t mean anything nor are they scientifically correct. You can always adjust them and see how it affects your evolutions.</p><p id="0e2b">Notice that some scores are negative, indicating that they have a negative impact on survival; some scores are zero, indicating they have no impact on survival; and some are positive, indicating they have a positive impact on survival.</p><p id="b2aa">Now, to translate these scores into a fitness function, add the following code to tiger_simulation.exs:</p><div id="29e2"><pre>​ ​def​ fitness_function(chromosome) ​<span class="hljs-built_in">do</span>​ ​ tropic_scores = [<span class="hljs-number">0.0</span>, <span class="hljs-number">3.0</span>, <span class="hljs-number">2.0</span>, <span class="hljs-number">1.0</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">1.0</span>, <span class="hljs-number">-1.0</span>, <span class="hljs-number">0.0</span>] ​ tundra_scores = [<span class="hljs-number">1.0</span>, <span class="hljs-number">3.0</span>, <span class="hljs-number">-2.0</span>, <span class="hljs-number">-1.0</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">2.0</span>, <span class="hljs-number">1.0</span>, <span class="hljs-number">0.0</span>] ​ traits = chromosome.genes ​ ​ traits ​ |<span class="hljs-type">> Enum</span>.zip(tropic_scores) ​ |<span class="hljs-type">> Enum</span>.map(​fn​ {t, s} -> t*s ​<span class="hljs-keyword">end</span>​) ​ |<span class="hljs-type">> Enum</span>.<span class="hljs-built_in">sum</span>() ​ ​<span class="hljs-keyword"

Options

end</span></pre></div><p id="5adf">The fitness function pairs traits with their corresponding score, multiplies them together, and returns the sum to represent a tiger’s ability to survive in the given environment. For simplicity, you can just change tropic_scores with tundra_scores in Enum.map/2 when running trials on different environments. In practice, you’d want a way to change this dynamically and run experiments side by side.</p><h1 id="bbd5">Finishing and Running the Simulation</h1><p id="64b7">All that’s left for you to do is define some termination criteria. You’ll want to stop the evolution after 1000 generations. Implement your termination criteria like this:</p><div id="b430"><pre>​ ​def​ <span class="hljs-keyword">terminate</span>?(_population, generation), ​<span class="hljs-keyword">do</span>​: generation == <span class="hljs-number">1000</span></pre></div><p id="e7b1">Next, add the following below the TigerSimulation module:</p><div id="16c6"><pre>​ tiger = Genetic.<span class="hljs-title function_ invoke__">run</span>(TigerSimulation, ​ ​<span class="hljs-attr">population_size</span>:​ <span class="hljs-number">20</span>, ​ ​<span class="hljs-attr">selection_rate</span>:​ <span class="hljs-number">0.9</span>, ​ ​<span class="hljs-attr">mutation_rate</span>:​ <span class="hljs-number">0.1</span>) ​ ​ IO.<span class="hljs-title function_ invoke__">write</span>(​<span class="hljs-string">"​​\n"</span>​) ​ IO.<span class="hljs-title function_ invoke__">inspect</span>(tiger)</pre></div><p id="e839">You pass your TigerSimulation into Genetic.run/2 as well as specify a population size of 20, selection rate of 0.9 and a mutation rate of 0.9.</p><p id="f8bc">Remember, Genetic.run/2 returns the best chromosome in the population after the termination criteria has been met. That means that tiger will be the current best chromosome in the population after 1000 generations.</p><p id="816d">Now, run your genetic algorithm in a tropic environment (with tropic_scores):</p><div id="bed7"><pre>​ ​ ​​mix​​ ​​run​​ ​​scripts/tiger_simulation.exs​ ​ <span class="hljs-keyword">Current</span> best: <span class="hljs-number">7.5000</span> Generation: <span class="hljs-number">1000</span> ​ <span class="hljs-meta">%Type</span>s.Chromosome{ ​ age: <span class="hljs-number">1</span>, ​ fitness: <span class="hljs-number">7.5</span>, ​ genes: [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>], ​ size: <span class="hljs-number">8</span> ​ }</pre></div><p id="5a4a">And again in a tundra environment (with tundra_scores):</p><div id="fcea"><pre>​ ​ ​​mix​​ ​​run​​ ​​scripts/tiger_simulation.exs​ ​ <span class="hljs-keyword">Current</span> best: <span class="hljs-number">7.5000</span> Generation: <span class="hljs-number">1000</span><span class="hljs-meta">%Type</span>s.Chromosome{ ​ age: <span class="hljs-number">1</span>, ​ fitness: <span class="hljs-number">7.5</span>, ​ genes: [<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>], ​ size: <span class="hljs-number">8</span> ​ }</pre></div><p id="f39b">You’ve successfully analyzed and produced the fittest tiger in each environment. You can see that in tropical environments, the best tiger is smaller, a strong swimmer, and has dark fur and a generally smaller hunting territory. The tundra tiger is larger, a strong swimmer, and has lighter fur and larger fat stores.</p><p id="cfd2">You might be thinking that achieving this result isn’t that impressive. You could have derived them yourself intuitively or through a simple brute-force search. However, the most important aspect of this experiment isn’t the final result but what happens before that. You need a way to peek inside.</p><p id="14a8"><i>👈 <a href="https://readmedium.com/chapter-9-tracking-genetic-algorithms-79f497444859">Chapter 9 Tracking Genetic Algorithms</a> | <a href="https://readmedium.com/table-of-contents-879fc8614df">TOC</a> | <a href="https://readmedium.com/logging-statistics-using-ets-b0bd5e4c14d5">Logging Statistics Using ETS</a> 👉</i></p><p id="22e7"><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="dd89"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*OZt5jtL90ZqYviPotaxYkQ.jpeg"><figcaption></figcaption></figure></article></body>

Using Genetic Algorithms to Simulate Evolution

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

👈 Chapter 9 Tracking Genetic Algorithms | TOC | Logging Statistics Using ETS 👉

One of the more interesting applications of genetic algorithms that you have yet to discover is their ability to model real evolutionary processes. Genetic algorithms are inspired by evolution, and while the internal processes that guide genetic algorithms such as selection, crossover, and mutation are only loosely based on science, they can still be used to offer valuable insights into the evolutionary process.

Say you’ve been tasked by a biologist to write a simulation of how tigers evolve under different environmental conditions. Obviously, the traits required to survive in a desert versus an arctic tundra differ drastically. Your goal is to write a simulation that models the basic evolution of the tiger in two different environments, tropical and tundra, over the course of 1000 generations. Additionally, your simulation needs to keep track of valuable statistics such as average fitness, average age, genealogy, and the most fit chromosome from every generation.

Using a genetic algorithm and a bit of knowledge about tigers, you can accomplish this task in no time.

Start by creating a new file in scripts named tiger_simulation.exs. Next, create a shell for a Problem in tiger_simulation.exs, like so:

​ ​defmoduleTigerSimulationdo​
​   @behaviour Problemalias Types.Chromosome
​ 
​   @impl true
​   ​defgenotype, ​do​: ​# ...​
​ 
​   @impl true
​   ​deffitness_function(c), ​do​: ​# ...​
​ 
​   @impl true
​   ​defterminate?(population, generation), ​do​: ​# ...​
​ ​end

By now, all of this code should be familiar. Now you need to figure out how to fit your simulation into the Problem behaviour.

Representing Tigers as Chromosomes

The first thing you need to do is determine how to represent tigers as chromosomes.

While there’s no right or wrong answer, the easiest way is to use a binary genotype that represents various traits present in a single tiger. Each of these traits contributes in one way or another to the tiger’s ability to survive in different environments.

You’ll monitor eight traits as shown in the table.

+--------------------+--------------+--------------+
|                    |  0           |  1           |
+--------------------+--------------+--------------+
|  Size              |  smaller     |  larger      |
|  Swimming Ability  |  low         |  high        |
|  Fat Stores        |  less        |  more        |
|  Activity Period   |  diurnal     |  nocturnal   |
|  Hunting Range     |  smaller     |  larger      |
|  Fur Thickness     |  less thick  |  more thick  |
|  Tail Length       |  smaller     |  larger      |
+--------------------+--------------+--------------+

Because you’re monitoring eight traits, your chromosome will consist of eight binary genes. Implement the genotype like this:

​ ​def​ genotype ​do​
​   genes = for _ <- 1..8, ​do​: Enum.random(0..1)
​   %Chromosome{​genes:​ genes, ​size:​ 8}
​ ​end

As you’ve seen before, this is a basic binary genotype of size 8. The initial population will contain tigers of varying combinations of traits.

The next thing you need to do is determine how to evaluate each tiger based on its traits in both a tropical and tundra environment.

Evaluating Fitness in Different Environments

Remember, your goal is to determine how tigers evolve in each environment. Because the importance of each trait differs between environments, you need to evaluate chromosomes differently depending on the environment.

The easiest way to do this is to assign weights or scores to each trait, indicating whether or not a trait is positive or negative to survival. The magnitude of a weight or score indicates the relative importance of that trait in a given environment.

The table shows the scores you’ll assign to each trait in both environments.

+--------------------+------------+----------+
|  Trait             |  Tropical  |  Tundra  |
+--------------------+------------+----------+
|  Size              |    0.0     |   1.0    |
|  Swimming Ability  |    3.0     |   3.0    |
|  Fur Color         |    2.0     |  -2.0    |
|  Fat Stores        |  -1.0      |   1.0    |
|  Activity Period   |    0.5     |   0.5    |
|  Hunting Ground    |    1.0     |   2.0    |
|  Fur Thickness     |  -1.0      |   1.0    |
|  Tail Length       |    0.0     |   0.0    |
+--------------------+------------+----------+

Determining Scores

INFORMATION

The scores chosen for each trait in this example are arbitrary. In a practical simulation, you’d want to determine scores with research and data, and hopefully be able to provide a justification for each one. These scores were chosen based on intuition. They don’t mean anything nor are they scientifically correct. You can always adjust them and see how it affects your evolutions.

Notice that some scores are negative, indicating that they have a negative impact on survival; some scores are zero, indicating they have no impact on survival; and some are positive, indicating they have a positive impact on survival.

Now, to translate these scores into a fitness function, add the following code to tiger_simulation.exs:

​ ​def​ fitness_function(chromosome) ​do​
​   tropic_scores = [0.0, 3.0, 2.0, 1.0, 0.5, 1.0, -1.0, 0.0]
​   tundra_scores = [1.0, 3.0, -2.0, -1.0, 0.5, 2.0, 1.0, 0.0]
​   traits = chromosome.genes
​ 
​   traits
​   |> Enum.zip(tropic_scores)
​   |> Enum.map(​fn​ {t, s} -> t*s ​end​)
​   |> Enum.sum()
​ ​end

The fitness function pairs traits with their corresponding score, multiplies them together, and returns the sum to represent a tiger’s ability to survive in the given environment. For simplicity, you can just change tropic_scores with tundra_scores in Enum.map/2 when running trials on different environments. In practice, you’d want a way to change this dynamically and run experiments side by side.

Finishing and Running the Simulation

All that’s left for you to do is define some termination criteria. You’ll want to stop the evolution after 1000 generations. Implement your termination criteria like this:

​ ​def​ terminate?(_population, generation), ​do​: generation == 1000

Next, add the following below the TigerSimulation module:

​ tiger = Genetic.run(TigerSimulation,
​                     ​population_size:​ 20,
​                     ​selection_rate:​ 0.9,
​                     ​mutation_rate:​ 0.1)
​ 
​ IO.write(​"​​\n"​)
​ IO.inspect(tiger)

You pass your TigerSimulation into Genetic.run/2 as well as specify a population size of 20, selection rate of 0.9 and a mutation rate of 0.9.

Remember, Genetic.run/2 returns the best chromosome in the population after the termination criteria has been met. That means that tiger will be the current best chromosome in the population after 1000 generations.

Now, run your genetic algorithm in a tropic environment (with tropic_scores):

​ ​$ ​​mix​​ ​​run​​ ​​scripts/tiger_simulation.exs​
​ Current best: 7.5000  Generation: 1000%Types.Chromosome{
​   age: 1,
​   fitness: 7.5,
​   genes: [0, 1, 1, 1, 1, 1, 0, 1],
​   size: 8
​ }

And again in a tundra environment (with tundra_scores):

​ ​$ ​​mix​​ ​​run​​ ​​scripts/tiger_simulation.exs​
​ Current best: 7.5000  Generation: 1000%Types.Chromosome{
​   age: 1,
​   fitness: 7.5,
​   genes: [1, 1, 0, 0, 1, 1, 1, 0],
​   size: 8
​ }

You’ve successfully analyzed and produced the fittest tiger in each environment. You can see that in tropical environments, the best tiger is smaller, a strong swimmer, and has dark fur and a generally smaller hunting territory. The tundra tiger is larger, a strong swimmer, and has lighter fur and larger fat stores.

You might be thinking that achieving this result isn’t that impressive. You could have derived them yourself intuitively or through a simple brute-force search. However, the most important aspect of this experiment isn’t the final result but what happens before that. You need a way to peek inside.

👈 Chapter 9 Tracking Genetic Algorithms | TOC | Logging Statistics Using ETS 👉

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