diff --git a/gui/genetic_algorithm_example.py b/gui/genetic_algorithm_example.py
new file mode 100644
index 000000000..418da02e9
--- /dev/null
+++ b/gui/genetic_algorithm_example.py
@@ -0,0 +1,172 @@
+# author: ad71
+# A simple program that implements the solution to the phrase generation problem using
+# genetic algorithms as given in the search.ipynb notebook.
+#
+# Type on the home screen to change the target phrase
+# Click on the slider to change genetic algorithm parameters
+# Click 'GO' to run the algorithm with the specified variables
+# Displays best individual of the current generation
+# Displays a progress bar that indicates the amount of completion of the algorithm
+# Displays the first few individuals of the current generation
+
+import sys
+import time
+import random
+import os.path
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+
+from tkinter import *
+from tkinter import ttk
+
+import search
+from utils import argmax
+
+LARGE_FONT = ('Verdana', 12)
+EXTRA_LARGE_FONT = ('Consolas', 36, 'bold')
+
+canvas_width = 800
+canvas_height = 600
+
+black = '#000000'
+white = '#ffffff'
+p_blue = '#042533'
+lp_blue = '#0c394c'
+
+# genetic algorithm variables
+# feel free to play around with these
+target = 'Genetic Algorithm' # the phrase to be generated
+max_population = 100 # number of samples in each population
+mutation_rate = 0.1 # probability of mutation
+f_thres = len(target) # fitness threshold
+ngen = 1200 # max number of generations to run the genetic algorithm
+
+generation = 0 # counter to keep track of generation number
+
+u_case = [chr(x) for x in range(65, 91)] # list containing all uppercase characters
+l_case = [chr(x) for x in range(97, 123)] # list containing all lowercase characters
+punctuations1 = [chr(x) for x in range(33, 48)] # lists containing punctuation symbols
+punctuations2 = [chr(x) for x in range(58, 65)]
+punctuations3 = [chr(x) for x in range(91, 97)]
+numerals = [chr(x) for x in range(48, 58)] # list containing numbers
+
+# extend the gene pool with the required lists and append the space character
+gene_pool = []
+gene_pool.extend(u_case)
+gene_pool.extend(l_case)
+gene_pool.append(' ')
+
+# callbacks to update global variables from the slider values
+def update_max_population(slider_value):
+ global max_population
+ max_population = slider_value
+
+def update_mutation_rate(slider_value):
+ global mutation_rate
+ mutation_rate = slider_value
+
+def update_f_thres(slider_value):
+ global f_thres
+ f_thres = slider_value
+
+def update_ngen(slider_value):
+ global ngen
+ ngen = slider_value
+
+# fitness function
+def fitness_fn(_list):
+ fitness = 0
+ # create string from list of characters
+ phrase = ''.join(_list)
+ # add 1 to fitness value for every matching character
+ for i in range(len(phrase)):
+ if target[i] == phrase[i]:
+ fitness += 1
+ return fitness
+
+# function to bring a new frame on top
+def raise_frame(frame, init=False, update_target=False, target_entry=None, f_thres_slider=None):
+ frame.tkraise()
+ global target
+ if update_target and target_entry is not None:
+ target = target_entry.get()
+ f_thres_slider.config(to=len(target))
+ if init:
+ population = search.init_population(max_population, gene_pool, len(target))
+ genetic_algorithm_stepwise(population)
+
+# defining root and child frames
+root = Tk()
+f1 = Frame(root)
+f2 = Frame(root)
+
+# pack frames on top of one another
+for frame in (f1, f2):
+ frame.grid(row=0, column=0, sticky='news')
+
+# Home Screen (f1) widgets
+target_entry = Entry(f1, font=('Consolas 46 bold'), exportselection=0, foreground=p_blue, justify=CENTER)
+target_entry.insert(0, target)
+target_entry.pack(expand=YES, side=TOP, fill=X, padx=50)
+target_entry.focus_force()
+
+max_population_slider = Scale(f1, from_=3, to=1000, orient=HORIZONTAL, label='Max population', command=lambda value: update_max_population(int(value)))
+max_population_slider.set(max_population)
+max_population_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
+
+mutation_rate_slider = Scale(f1, from_=0, to=1, orient=HORIZONTAL, label='Mutation rate', resolution=0.0001, command=lambda value: update_mutation_rate(float(value)))
+mutation_rate_slider.set(mutation_rate)
+mutation_rate_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
+
+f_thres_slider = Scale(f1, from_=0, to=len(target), orient=HORIZONTAL, label='Fitness threshold', command=lambda value: update_f_thres(int(value)))
+f_thres_slider.set(f_thres)
+f_thres_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
+
+ngen_slider = Scale(f1, from_=1, to=5000, orient=HORIZONTAL, label='Max number of generations', command=lambda value: update_ngen(int(value)))
+ngen_slider.set(ngen)
+ngen_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
+
+button = ttk.Button(f1, text='RUN', command=lambda: raise_frame(f2, init=True, update_target=True, target_entry=target_entry, f_thres_slider=f_thres_slider)).pack(side=BOTTOM, pady=50)
+
+# f2 widgets
+canvas = Canvas(f2, width=canvas_width, height=canvas_height)
+canvas.pack(expand=YES, fill=BOTH, padx=20, pady=15)
+button = ttk.Button(f2, text='EXIT', command=lambda: raise_frame(f1)).pack(side=BOTTOM, pady=15)
+
+# function to run the genetic algorithm and update text on the canvas
+def genetic_algorithm_stepwise(population):
+ root.title('Genetic Algorithm')
+ for generation in range(ngen):
+ # generating new population after selecting, recombining and mutating the existing population
+ population = [search.mutate(search.recombine(*search.select(2, population, fitness_fn)), gene_pool, mutation_rate) for i in range(len(population))]
+ # genome with the highest fitness in the current generation
+ current_best = ''.join(argmax(population, key=fitness_fn))
+ # collecting first few examples from the current population
+ members = [''.join(x) for x in population][:48]
+
+ # clear the canvas
+ canvas.delete('all')
+ # displays current best on top of the screen
+ canvas.create_text(canvas_width / 2, 40, fill=p_blue, font='Consolas 46 bold', text=current_best)
+
+ # displaying a part of the population on the screen
+ for i in range(len(members) // 3):
+ canvas.create_text((canvas_width * .175), (canvas_height * .25 + (25 * i)), fill=lp_blue, font='Consolas 16', text=members[3 * i])
+ canvas.create_text((canvas_width * .500), (canvas_height * .25 + (25 * i)), fill=lp_blue, font='Consolas 16', text=members[3 * i + 1])
+ canvas.create_text((canvas_width * .825), (canvas_height * .25 + (25 * i)), fill=lp_blue, font='Consolas 16', text=members[3 * i + 2])
+
+ # displays current generation number
+ canvas.create_text((canvas_width * .5), (canvas_height * 0.95), fill=p_blue, font='Consolas 18 bold', text=f'Generation {generation}')
+
+ # displays blue bar that indicates current maximum fitness compared to maximum possible fitness
+ scaling_factor = fitness_fn(current_best) / len(target)
+ canvas.create_rectangle(canvas_width * 0.1, 90, canvas_width * 0.9, 100, outline=p_blue)
+ canvas.create_rectangle(canvas_width * 0.1, 90, canvas_width * 0.1 + scaling_factor * canvas_width * 0.8, 100, fill=lp_blue)
+ canvas.update()
+
+ # checks for completion
+ fittest_individual = search.fitness_threshold(fitness_fn, f_thres, population)
+ if fittest_individual:
+ break
+
+raise_frame(f1)
+root.mainloop()
\ No newline at end of file
diff --git a/search.ipynb b/search.ipynb
index d537bd6c0..96ac09aa7 100644
--- a/search.ipynb
+++ b/search.ipynb
@@ -15,11 +15,13 @@
"cell_type": "code",
"execution_count": 1,
"metadata": {
+ "collapsed": true,
"scrolled": true
},
"outputs": [],
"source": [
"from search import *\n",
+ "from notebook import psource\n",
"\n",
"# Needed to hide warnings in the matplotlib sections\n",
"import warnings\n",
@@ -1286,7 +1288,9 @@
{
"cell_type": "code",
"execution_count": 2,
- "metadata": {},
+ "metadata": {
+ "collapsed": true
+ },
"outputs": [],
"source": [
"# heuristics for 8 Puzzle Problem\n",
@@ -1501,12 +1505,123 @@
{
"cell_type": "code",
"execution_count": 2,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "def genetic_algorithm(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ngen=1000, pmut=0.1):\n",
+ " """[Figure 4.8]"""\n",
+ " for i in range(ngen):\n",
+ " population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, pmut)\n",
+ " for i in range(len(population))]\n",
+ "\n",
+ " fittest_individual = fitness_threshold(fitness_fn, f_thres, population)\n",
+ " if fittest_individual:\n",
+ " return fittest_individual\n",
+ "\n",
+ "\n",
+ " return argmax(population, key=fitness_fn)\n",
+ "
\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
- "%psource genetic_algorithm"
+ "psource(genetic_algorithm)"
]
},
{
@@ -1536,65 +1651,904 @@
"source": [
"For each generation, the algorithm updates the population. First it calculates the fitnesses of the individuals, then it selects the most fit ones and finally crosses them over to produce offsprings. There is a chance that the offspring will be mutated, given by `pmut`. If at the end of the generation an individual meets the fitness threshold, the algorithm halts and returns that individual.\n",
"\n",
- "The function of mating is accomplished by the method `reproduce`:"
+ "The function of mating is accomplished by the method `recombine`:"
]
},
{
"cell_type": "code",
"execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "def recombine(x, y):\n",
+ " n = len(x)\n",
+ " c = random.randrange(0, n)\n",
+ " return x[:c] + y[c:]\n",
+ "
\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "psource(recombine)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The method picks at random a point and merges the parents (`x` and `y`) around it.\n",
+ "\n",
+ "The mutation is done in the method `mutate`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "def mutate(x, gene_pool, pmut):\n",
+ " if random.uniform(0, 1) >= pmut:\n",
+ " return x\n",
+ "\n",
+ " n = len(x)\n",
+ " g = len(gene_pool)\n",
+ " c = random.randrange(0, n)\n",
+ " r = random.randrange(0, g)\n",
+ "\n",
+ " new_gene = gene_pool[r]\n",
+ " return x[:c] + [new_gene] + x[c+1:]\n",
+ "
\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "psource(mutate)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We pick a gene in `x` to mutate and a gene from the gene pool to replace it with.\n",
+ "\n",
+ "To help initializing the population we have the helper function `init_population`\":"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "def init_population(pop_number, gene_pool, state_length):\n",
+ " """Initializes population for genetic algorithm\n",
+ " pop_number : Number of individuals in population\n",
+ " gene_pool : List of possible values for individuals\n",
+ " state_length: The length of each individual"""\n",
+ " g = len(gene_pool)\n",
+ " population = []\n",
+ " for i in range(pop_number):\n",
+ " new_individual = [gene_pool[random.randrange(0, g)] for j in range(state_length)]\n",
+ " population.append(new_individual)\n",
+ "\n",
+ " return population\n",
+ "
\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "psource(init_population)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The function takes as input the number of individuals in the population, the gene pool and the length of each individual/state. It creates individuals with random genes and returns the population when done."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Explanation\n",
+ "\n",
+ "Before we solve problems using the genetic algorithm, we will explain how to intuitively understand the algorithm using a trivial exmaple.\n",
+ "\n",
+ "#### Generating Phrases\n",
+ "\n",
+ "In this problem, we use a genetic algorithm to generate a particular target phrase from a population of random strings. This is a classic example that helps build intuition about how to use this algorithm in other problems as well. Before we break the problem down, let us try to brute force the solution. Let us say that we want to generate the phrase \"genetic algorithm\". The phrase is 17 characters long. We can use any character from the 26 lowercase characters and the space character. To generate a random phrase of length 17, each space can be filled in 27 ways. So the total number of possible phrases is\n",
+ "\n",
+ "$$ 27^{17} = 2153693963075557766310747 $$\n",
+ "\n",
+ "which is a massive number. If we wanted to generate the phrase \"Genetic Algorithm\", we would also have to include all the 26 uppercase characters into consideration thereby increasing the sample space from 27 characters to 53 characters and the total number of possible phrases then would be\n",
+ "\n",
+ "$$ 53^{17} = 205442259656281392806087233013 $$\n",
+ "\n",
+ "If we wanted to include punctuations and numerals into the sample space, we would have further complicated an already impossible problem. Hence, brute forcing is not an option. Now we'll apply the genetic algorithm and see how it significantly reduces the search space. We essentially want to *evolve* our population of random strings so that they better approximate the target phrase as the number of generations increase. Genetic algorithms work on the principle of Darwinian Natural Selection according to which, there are three key concepts that need to be in place for evolution to happen. They are:\n",
+ "\n",
+ "1. Heredity : There must be a process in place by which children receive the properties of their parents.
\n",
+ "For this particular problem, two strings from the population will be chosen as parents and will be split at a random index and recombined as described in the `recombine` function to create a child. This child string will then be added to the new generation.\n",
+ "\n",
+ "
\n",
+ "2. Variation : There must be a variety of traits present in the population or a means with which to introduce variation.
If there is no variation in the sample space, we might never reach the global optimum. To ensure that there is enough variation, we can initialize a large population, but this gets computationally expensive as the population gets larger. Hence, we often use another method called mutation. In this method, we randomly change one or more characters of some strings in the population based on a predefined probability value called the mutation rate or mutation probability as described in the `mutate` function. The mutation rate is usually kept quite low. A mutation rate of zero fails to introduce variation in the population and a high mutation rate (say 50%) is as good as a coin flip and the population fails to benefit from the previous recombinations. An optimum balance has to be maintained between population size and mutation rate so as to reduce the computational cost as well as have sufficient variation in the population.\n",
+ "\n",
+ "
\n",
+ "3. Selection : There must be some mechanism by which some members of the population have the opportunity to be parents and pass down their genetic information and some do not. This is typically referred to as \"survival of the fittest\".
\n",
+ "There has to be some way of determining which phrases in our population have a better chance of eventually evolving into the target phrase. This is done by introducing a fitness function that calculates how close the generated phrase is to the target phrase. The function will simply return a scalar value corresponding to the number of matching characters between the generated phrase and the target phrase."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Before solving the problem, we first need to define our target phrase."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
- "%psource reproduce"
+ "target = 'Genetic Algorithm'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": true
+ },
+ "source": [
+ "We then need to define our gene pool, i.e the elements which an individual from the population might comprise of. Here, the gene pool contains all uppercase and lowercase letters of the English alphabet and the space character."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# The ASCII values of uppercase characters ranges from 65 to 91\n",
+ "u_case = [chr(x) for x in range(65, 91)]\n",
+ "# The ASCII values of lowercase characters ranges from 97 to 123\n",
+ "l_case = [chr(x) for x in range(97, 123)]\n",
+ "\n",
+ "gene_pool = []\n",
+ "gene_pool.extend(u_case) # adds the uppercase list to the gene pool\n",
+ "gene_pool.extend(l_case) # adds the lowercase list to the gene pool\n",
+ "gene_pool.append(' ') # adds the space character to the gene pool"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "The method picks at random a point and merges the parents (`x` and `y`) around it.\n",
+ "We now need to define the maximum size of each population. Larger populations have more variation but are computationally more expensive to run algorithms on."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "max_population = 100"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As our population is not very large, we can afford to keep a relatively large mutation rate."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "mutation_rate = 0.07 # 7%"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Great! Now, we need to define the most important metric for the genetic algorithm, i.e the fitness function. This will simply return the number of matching characters between the generated sample and the target phrase."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def fitness_fn(sample):\n",
+ " # initialize fitness to 0\n",
+ " fitness = 0\n",
+ " for i in range(len(sample)):\n",
+ " # increment fitness by 1 for every matching character\n",
+ " if sample[i] == target[i]:\n",
+ " fitness += 1\n",
+ " return fitness"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Before we run our genetic algorithm, we need to initialize a random population. We will use the `init_population` function to do this. We need to pass in the maximum population size, the gene pool and the length of each individual, which in this case will be the same as the length of the target phrase."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "population = init_population(max_population, gene_pool, len(target))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We will now define how the individuals in the population should change as the number of generations increases. First, the `select` function will be run on the population to select *two* individuals with high fitness values. These will be the parents which will then be recombined using the `recombine` function to generate the child."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "parents = select(2, population, fitness_fn) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# The recombine function takes two parents as arguments, so we need to unpack the previous variable\n",
+ "child = recombine(*parents)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we need to apply a mutation according to the mutation rate. We call the `mutate` function on the child with the gene pool and mutation rate as the additional arguments."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "child = mutate(child, gene_pool, mutation_rate)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The above lines can be condensed into\n",
"\n",
- "The mutation is done in the method `mutate`:"
+ "`child = mutate(recombine(*select(2, population, fitness_fn)), gene_pool, mutation_rate)`\n",
+ "\n",
+ "And, we need to do this `for` every individual in the current population to generate the new population."
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 42,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
- "%psource mutate"
+ "population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, mutation_rate) for i in range(len(population))]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We pick a gene in `x` to mutate and a gene from the gene pool to replace it with.\n",
+ "The individual with the highest fitness can then be found using the `max` function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "current_best = max(population, key=fitness_fn)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's print this out"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "['j', 'F', 'm', 'F', 'N', 'i', 'c', 'v', 'm', 'j', 'V', 'o', 'd', 'r', 't', 'V', 'H']\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(current_best)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We see that this is a list of characters. This can be converted to a string using the join function"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "jFmFNicvmjVodrtVH\n"
+ ]
+ }
+ ],
+ "source": [
+ "current_best_string = ''.join(current_best)\n",
+ "print(current_best_string)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We now need to define the conditions to terminate the algorithm. This can happen in two ways\n",
+ "1. Termination after a predefined number of generations\n",
+ "2. Termination when the fitness of the best individual of the current generation reaches a predefined threshold value.\n",
"\n",
- "To help initializing the population we have the helper function `init_population`\":"
+ "We define these variables below"
]
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 46,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "ngen = 1200 # maximum number of generations\n",
+ "# we set the threshold fitness equal to the length of the target phrase\n",
+ "# i.e the algorithm only terminates whne it has got all the characters correct \n",
+ "# or it has completed 'ngen' number of generations\n",
+ "f_thres = len(target)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": true
+ },
+ "source": [
+ "To generate `ngen` number of generations, we run a `for` loop `ngen` number of times. After each generation, we calculate the fitness of the best individual of the generation and compare it to the value of `f_thres` using the `fitness_threshold` function. After every generation, we print out the best individual of the generation and the corresponding fitness value. Lets now write a function to do this."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
- "%psource init_population"
+ "def genetic_algorithm_stepwise(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ngen=1200, pmut=0.1):\n",
+ " for generation in range(ngen):\n",
+ " population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, pmut) for i in range(len(population))]\n",
+ " # stores the individual genome with the highest fitness in the current population\n",
+ " current_best = ''.join(max(population, key=fitness_fn))\n",
+ " print(f'Current best: {current_best}\\t\\tGeneration: {str(generation)}\\t\\tFitness: {fitness_fn(current_best)}\\r', end='')\n",
+ " \n",
+ " # compare the fitness of the current best individual to f_thres\n",
+ " fittest_individual = fitness_threshold(fitness_fn, f_thres, population)\n",
+ " \n",
+ " # if fitness is greater than or equal to f_thres, we terminate the algorithm\n",
+ " if fittest_individual:\n",
+ " return fittest_individual, generation\n",
+ " return max(population, key=fitness_fn) , generation "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "The function takes as input the number of individuals in the population, the gene pool and the length of each individual/state. It creates individuals with random genes and returns the population when done."
+ "The function defined above is essentially the same as the one defined in `search.py` with the added functionality of printing out the data of each generation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "def genetic_algorithm(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ngen=1000, pmut=0.1):\n",
+ " """[Figure 4.8]"""\n",
+ " for i in range(ngen):\n",
+ " population = [mutate(recombine(*select(2, population, fitness_fn)), gene_pool, pmut)\n",
+ " for i in range(len(population))]\n",
+ "\n",
+ " fittest_individual = fitness_threshold(fitness_fn, f_thres, population)\n",
+ " if fittest_individual:\n",
+ " return fittest_individual\n",
+ "\n",
+ "\n",
+ " return argmax(population, key=fitness_fn)\n",
+ "
\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "psource(genetic_algorithm)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We have defined all the required functions and variables. Let's now create a new population and test the function we wrote above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Current best: Genetic Algorithm\t\tGeneration: 472\t\tFitness: 17\r"
+ ]
+ }
+ ],
+ "source": [
+ "population = init_population(max_population, gene_pool, len(target))\n",
+ "solution, generations = genetic_algorithm_stepwise(population, fitness_fn, gene_pool, f_thres, ngen, mutation_rate)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The genetic algorithm was able to converge!\n",
+ "We implore you to rerun the above cell and play around with `target, max_population, f_thres, ngen` etc parameters to get a better intuition of how the algorithm works. To summarize, if we can define the problem states in simple array format and if we can create a fitness function to gauge how good or bad our approximate solutions are, there is a high chance that we can get a satisfactory solution using a genetic algorithm. \n",
+ "- There is also a better GUI version of this program `genetic_algorithm_example.py` in the GUI folder for you to play around with."
]
},
{
@@ -1878,420 +2832,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.5.4rc1"
- },
- "widgets": {
- "state": {
- "013d8df0a2ab4899b09f83aa70ce5d50": {
- "views": []
- },
- "01ee7dc2239c4b0095710436453b362d": {
- "views": []
- },
- "04d594ae6a704fc4b16895e6a7b85270": {
- "views": []
- },
- "052ea3e7259346a4b022ec4fef1fda28": {
- "views": [
- {
- "cell_index": 32
- }
- ]
- },
- "0ade4328785545c2b66d77e599a3e9da": {
- "views": [
- {
- "cell_index": 29
- }
- ]
- },
- "0b94d8de6b4e47f89b0382b60b775cbd": {
- "views": []
- },
- "0c63dcc0d11a451ead31a4c0c34d7b43": {
- "views": []
- },
- "0d91be53b6474cdeac3239fdffeab908": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "0fe9c3b9b1264d4abd22aef40a9c1ab9": {
- "views": []
- },
- "10fd06131b05455d9f0a98072d7cebc6": {
- "views": []
- },
- "1193eaa60bb64cb790236d95bf11f358": {
- "views": [
- {
- "cell_index": 38
- }
- ]
- },
- "11b596cbf81a47aabccae723684ac3a5": {
- "views": []
- },
- "127ae5faa86f41f986c39afb320f2298": {
- "views": []
- },
- "16a9167ec7b4479e864b2a32e40825a1": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "170e2e101180413f953a192a41ecbfcc": {
- "views": []
- },
- "181efcbccf89478792f0e38a25500e51": {
- "views": []
- },
- "1894a28092604d69b0d7d465a3b165b1": {
- "views": []
- },
- "1a56cc2ab5ae49ea8bf2a3f6ca2b1c36": {
- "views": []
- },
- "1cfd8f392548467696d8cd4fc534a6b4": {
- "views": []
- },
- "1e395e67fdec406f8698aa5922764510": {
- "views": []
- },
- "23509c6536404e96985220736d286183": {
- "views": []
- },
- "23bffaca1206421fb9ea589126e35438": {
- "views": []
- },
- "25330d0b799e4f02af5e510bc70494cf": {
- "views": []
- },
- "2ab8bf4795ac4240b70e1a94e14d1dd6": {
- "views": [
- {
- "cell_index": 30
- }
- ]
- },
- "2bd48f1234e4422aaedecc5815064181": {
- "views": []
- },
- "2d3a082066304c8ebf2d5003012596b4": {
- "views": []
- },
- "2dc962f16fd143c1851aaed0909f3963": {
- "views": [
- {
- "cell_index": 35
- }
- ]
- },
- "2f659054242a453da5ea0884de996008": {
- "views": []
- },
- "30a214881db545729c1b883878227e95": {
- "views": []
- },
- "3275b81616424947be98bf8fd3cd7b82": {
- "views": []
- },
- "330b52bc309d4b6a9b188fd9df621180": {
- "views": []
- },
- "3320648123f44125bcfda3b7c68febcf": {
- "views": []
- },
- "338e3b1562e747f197ab3ceae91e371f": {
- "views": []
- },
- "34658e2de2894f01b16cf89905760f14": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "352f5fd9f698460ea372c6af57c5b478": {
- "views": []
- },
- "35dc16b828a74356b56cd01ff9ddfc09": {
- "views": []
- },
- "3805ce2994364bd1b259373d8798cc7a": {
- "views": []
- },
- "3d1f1f899cfe49aaba203288c61686ac": {
- "views": []
- },
- "3d7e943e19794e29b7058eb6bbe23c66": {
- "views": []
- },
- "3f6652b3f85740949b7711fbcaa509ba": {
- "views": []
- },
- "43e48664a76342c991caeeb2d5b17a49": {
- "views": [
- {
- "cell_index": 35
- }
- ]
- },
- "4662dec8595f45fb9ae061b2bdf44427": {
- "views": []
- },
- "47ae3d2269d94a95a567be21064eb98a": {
- "views": []
- },
- "49c49d665ba44746a1e1e9dc598bc411": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "4a1c43b035f644699fd905d5155ad61f": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "4eb88b6f6b4241f7b755f69b9e851872": {
- "views": []
- },
- "4fbb3861e50f41c688e9883da40334d4": {
- "views": []
- },
- "52d76de4ee8f4487b335a4a11726fbce": {
- "views": []
- },
- "53eccc8fc0ad461cb8277596b666f32a": {
- "views": [
- {
- "cell_index": 29
- }
- ]
- },
- "54d3a6067b594ad08907ce059d9f4a41": {
- "views": []
- },
- "612530d3edf8443786b3093ab612f88b": {
- "views": []
- },
- "613a133b6d1f45e0ac9c5c270bc408e0": {
- "views": []
- },
- "636caa7780614389a7f52ad89ea1c6e8": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "63aa621196294629b884c896b6a034d8": {
- "views": []
- },
- "66d1d894cc7942c6a91f0630fc4321f9": {
- "views": []
- },
- "6775928a174b43ecbe12608772f1cb05": {
- "views": []
- },
- "6bce621c90d543bca50afbe0c489a191": {
- "views": []
- },
- "6ebbb8c7ec174c15a6ee79a3c5b36312": {
- "views": []
- },
- "743219b9d37e4f47a5f777bb41ad0a96": {
- "views": [
- {
- "cell_index": 29
- }
- ]
- },
- "774f464794cc409ca6d1106bcaac0cf1": {
- "views": []
- },
- "7ba3da40fb26490697fc64b3248c5952": {
- "views": []
- },
- "7e79fea4654f4bedb5969db265736c25": {
- "views": []
- },
- "85c82ed0844f4ae08a14fd750e55fc15": {
- "views": []
- },
- "86e8f92c1d584cdeb13b36af1b6ad695": {
- "views": [
- {
- "cell_index": 35
- }
- ]
- },
- "88485e72d2ec447ba7e238b0a6de2839": {
- "views": []
- },
- "892d7b895d3840f99504101062ba0f65": {
- "views": []
- },
- "89be4167713e488696a20b9b5ddac9bd": {
- "views": []
- },
- "8a24a07d166b45498b7d8b3f97c131eb": {
- "views": []
- },
- "8e7c7f3284ee45b38d95fe9070d5772f": {
- "views": []
- },
- "98985eefab414365991ed6844898677f": {
- "views": []
- },
- "98df98e5af87474d8b139cb5bcbc9792": {
- "views": []
- },
- "99f11243d387409bbad286dd5ecb1725": {
- "views": []
- },
- "9ab2d641b0be4cf8950be5ba72e5039f": {
- "views": []
- },
- "9b1ffbd1e7404cb4881380a99c7d11bc": {
- "views": []
- },
- "9c07ec6555cb4d0ba8b59007085d5692": {
- "views": []
- },
- "9cc80f47249b4609b98223ce71594a3d": {
- "views": []
- },
- "9d79bfd34d3640a3b7156a370d2aabae": {
- "views": []
- },
- "a015f138cbbe4a0cad4d72184762ed75": {
- "views": []
- },
- "a27d2f1eb3834c38baf1181b0de93176": {
- "views": []
- },
- "a29b90d050f3442a89895fc7615ccfee": {
- "views": [
- {
- "cell_index": 29
- }
- ]
- },
- "a725622cfc5b43b4ae14c74bc2ad7ad0": {
- "views": []
- },
- "ac2e05d7d7e945bf99862a2d9d1fa685": {
- "views": []
- },
- "b0bb2ca65caa47579a4d3adddd94504b": {
- "views": []
- },
- "b8995c40625d465489e1b7ec8014b678": {
- "views": []
- },
- "ba83da1373fe45d19b3c96a875f2f4fb": {
- "views": []
- },
- "baa0040d35c64604858c529418c22797": {
- "views": []
- },
- "badc9fd7b56346d6b6aea68bfa6d2699": {
- "views": [
- {
- "cell_index": 38
- }
- ]
- },
- "bdb41c7654e54c83a91452abc59141bd": {
- "views": []
- },
- "c2399056ef4a4aa7aa4e23a0f381d64a": {
- "views": [
- {
- "cell_index": 38
- }
- ]
- },
- "c73b47b242b4485fb1462abcd92dc7c9": {
- "views": []
- },
- "ce3f28a8aeee4be28362d068426a71f6": {
- "views": [
- {
- "cell_index": 32
- }
- ]
- },
- "d3067a6bb84544bba5f1abd241a72e55": {
- "views": []
- },
- "db13a2b94de34ce9bea721aaf971c049": {
- "views": []
- },
- "db468d80cb6e43b6b88455670b036618": {
- "views": []
- },
- "e2cb458522b4438ea3f9873b6e411acb": {
- "views": []
- },
- "e77dca31f1d94d4dadd3f95d2cdbf10e": {
- "views": []
- },
- "e7bffb1fed664dea90f749ea79dcc4f1": {
- "views": [
- {
- "cell_index": 39
- }
- ]
- },
- "e80abb145fce4e888072b969ba8f455a": {
- "views": []
- },
- "e839d0cf348c4c1b832fc1fc3b0bd3c9": {
- "views": []
- },
- "e948c6baadde46f69f105649555b84eb": {
- "views": []
- },
- "eb16e9da25bf4bef91a34b1d0565c774": {
- "views": []
- },
- "ec82b64048834eafa3e53733bb54a713": {
- "views": []
- },
- "edbb3a621c87445e9df4773cc60ec8d2": {
- "views": []
- },
- "ef6c99705936425a975e49b9e18ac267": {
- "views": []
- },
- "f1b494f025dd48d1ae58ae8e3e2ebf46": {
- "views": []
- },
- "f435b108c59c42989bf209a625a3a5b5": {
- "views": [
- {
- "cell_index": 32
- }
- ]
- },
- "f71ed7e15a314c28973943046c4529d6": {
- "views": []
- },
- "f81f726f001c4fb999851df532ed39f2": {
- "views": []
- }
- },
- "version": "1.1.1"
+ "version": "3.6.1"
}
},
"nbformat": 4,