AI - Simple Genetic Algorithm (GA) to solve a card problem

Print Broken Article? Bookmark Discuss Send to a friend
25 votes for this article.
Popularity: 6.49. Rating: 4.64 out of 5.
<!---->

Introduction

This article describes how to solve a logic problem using a Genetic Algorithm. It assumes no prior knowledge of GAs. In fact, half of this article is dedicated to explaining the internal structure of a Genetic Algorithm.

So what is the problem domain we are trying to solve?

Well, GAs can be used to solve many problems. In fact, GAs have been used to grow new mathematical syntax trees, train multi-layer neural networks, to name but a few instances.

However, for this example, I have used a simple card splitting excercise, which is as detailed here:

  • You have 10 cards numbered 1 to 10
  • You have to divide them into two piles so that:
    • The sum of the first pile is as close as possible to 36.
    • And the product of all in the second pile is as close as possible to 360.

Now, I am not saying that this could not be done by hand, using old fashioned brain juice, it's just better suited to a GA, as it could take 100s or even 1000s of different combinations to get the correct result. Well, probably not that many for this simple problem, but it certainly could take a lot of combinations for a more difficult problem. Suffice to say, it is just good fun to do it with a GA. So, let's carry on.

So what is a Genetic Algorithm?

Well, Wikipedia says this:

A genetic algorithm is a search technique used in computing, to find true or approximate solutions to optimization and search problems, and is often abbreviated as GA. Genetic algorithms are categorized as global search heuristics. Genetic algorithms are a particular class of evolutionary algorithms that use techniques inspired by evolutionary biology such as inheritance, mutation, selection, and crossover (also called recombination).

Genetic algorithms are implemented as a computer simulation in which a population of abstract representations (called chromosomes or the genotype or the genome) of candidate solutions (called individuals, creatures, or phenotypes) to an optimization problem evolves towards better solutions. Traditionally, solutions are represented in binary as strings of 0s and 1s, but other encodings are also possible. The evolution usually starts from a population of randomly generated individuals, and happens in generations. In each generation, the fitness of every individual in the population is evaluated, multiple individuals are stochastically selected from the current population (based on their fitness), and modified (recombined and possibly mutated) to form a new population. The new population is then used in the next iteration of the algorithm.

Follow that?? If not, let's try a diagram. (Note that this is a Microbial GA, there are lots of GA types, but I just happen to like this one, and it's the one this article uses.) 

I prefer to think of a GA as a way of really quickly (well, may be quite slow, depending on the problem) trying out some evolutionary programming techniques, that mother nature has always had. 

So how does this translate into an algorithm (this article uses a Microbial GA, but there are many other varieties)?

The basic operation of the Microbial GA training is as follows:

  • Pick two genotypes at random
  • Compare Scores (Fitness) to come up with a Winner and Loser
  • Go along genotype, at each locus (Point)

That is:

  • With some probability (randomness), copy from Winner to Loser (overwrite)
  • With some probability (randomness), mutate that locus of the Loser

    So only the Loser gets changed, which gives a version of Elitism for free, this ensures that the best in the breed remains in the population.

That's it. That is the complete algorithm.

But there are some essential issues to be aware of, when playing with GAs:

  1. The genotype will be different for a different problem domain
  2. The Fitness function will be different for a different problem domain

These two items must be developed again, whenever a new problem is specified.

For example, if we wanted to find a person's favourite pizza toppings, the genotype and fitness would be different from that which is used for this article's problem domain.

These two essential elements of a GA (for this article's problem domain) are specified below. 

1. The Geneotype


        
        
  1. //the genes array, 30 members, 10 cards each   
  2. private int[,] gene = new int[30, 10];  

Well, for this article, the problem domain states that we have 10 cards. So, I created a two dimensional genes array, which is a 30*10 array. The 30 represents a population size of 30. I picked this. It could be any size, but should be big enough to allow some dominant genes to form.

2. The Fitness Function

Remembering that the problem domain description stated the following:

  • You have 10 cards numbered 1 to 10
  • You have to divide them into two piles so that:
    • The sum of the first pile is as close as possible to 36.
    • And the product of all in the second pile is as close as possible to 360.

Well, all that is being done is the following :

  • Loop through the population member's genes
  • If the current gene being looked at has a value of 0, the gene is for the sum pile (pile 0), so add to the running calculation
  • If the current gene being looked at has a value of 1, the gene is for the product pile (pile 1), so add to the running calculation
  • Calculate the overall error for this population member. If this member's geneotype has an overall error of 0.0, then the problem domain has been solved

        
        
  1. //evaluate the the nth member of the population   
  2. //@param n : the nth member of the population   
  3. //@return : the score for this member of the population.   
  4. //If score is 0.0, then we have a good GA which has solved   
  5. //the problem domain   
  6. private double evaluate(int n)   
  7. {   
  8.     //initialise field values   
  9.     int sum = 0, prod = 1;   
  10.     double scaled_sum_error, scaled_prod_error, combined_error;   
  11.     //loop though all genes for this population member   
  12.     for (int i = 0; i < LEN; i++)   
  13.     {   
  14.         //if the gene value is 0, then put it in the sum (pile 0),    
  15.         //and calculate sum   
  16.         if (gene[n,i] == 0)   
  17.         {   
  18.             sum += (1 + i);   
  19.         }   
  20.         //if the gene value is 1, then put it in the product (pile 1),    
  21.         //and calculate sum   
  22.         else  
  23.         {   
  24.             prod *= (1 + i);   
  25.         }   
  26.     }   
  27.     //work out how food this population member is, based on an overall error   
  28.     //for the problem domain   
  29.     //NOTE : The fitness function will change for every problem domain.   
  30.     scaled_sum_error = (sum - SUMTARG) / SUMTARG;   
  31.     scaled_prod_error = (prod - PRODTARG) / PRODTARG;   
  32.     combined_error = Math.Abs(scaled_sum_error) + Math.Abs(scaled_prod_error);   
  33.   
  34.     return combined_error;   
  35. }          

Using the code

The demo project attached actually contains a Visual Studio 2005 solution, with the following two classes.

Program class

Is the main entry point into the Simple_GeneticAlgorithm application. All this class does is create a new Simple_GeneticAlgorithm object and call its run() method.


        
        
  1. using System;   
  2. using System.Collections.Generic;   
  3. using System.Text;   
  4.   
  5. namespace Simple_GeneticAlgorithm   
  6. {   
  7.     class Program   
  8.     {   
  9.         //main access point   
  10.         static void Main(string[] args)   
  11.         {   
  12.             //create a new Microbial GA   
  13.             Simple_GeneticAlgorithm GA = new Simple_GeneticAlgorithm();   
  14.             GA.run();   
  15.             //read a line, to stop the Console window closing   
  16.             Console.ReadLine();   
  17.         }   
  18.     }   
  19. }           

Simple_GeneticAlgorithm class

Runs the GA to solve the problem domain.


        
        
  1. using System;   
  2. using System.Collections.Generic;   
  3. using System.Text;   
  4.   
  5. namespace Simple_GeneticAlgorithm   
  6. {   
  7.     public class Simple_GeneticAlgorithm   
  8.     {   
  9.         //population size   
  10.         private int POP = 30;   
  11.         //geneotype   
  12.         private int LEN = 10;   
  13.         //mutation rate, change it have a play   
  14.         private double MUT = 0.1;   
  15.         //recomination rate   
  16.         private double REC = 0.5;   
  17.         //how many tournaments should be played   
  18.         private double END = 1000;   
  19.         //the sum pile, end result for the SUM pile   
  20.         //card1 + card2 + card3 + card4 + card5, MUST = 36 for a good GA   
  21.         private double SUMTARG = 36;   
  22.         //the product pile, end result for the PRODUCT pile   
  23.         //card1 * card2 * card3 * card4 * card5, MUST = 360 for a good GA   
  24.         private double PRODTARG = 360;   
  25.         //the genes array, 30 members, 10 cards each   
  26.         private int[,] gene = new int[30, 10];   
  27.         //used to create randomness (Simulates selection process in nature)   
  28.         //randomly selects genes   
  29.         Random rnd = new Random();   
  30.   
  31.         //empty constructor   
  32.         public Simple_GeneticAlgorithm()   
  33.         {   
  34.         }   
  35.   
  36.         //Runs the Microbial GA to solve the problem domain   
  37.         //Where the problem domain is specified as follows   
  38.         //   
  39.         //You have 10 cards numbered 1 to 10.   
  40.         //You have to divide them into 2 piles so that:   
  41.         //   
  42.         //The sum of the first pile is as close as possible to 36   
  43.         //And the product of all in second pile is as close as poss to 360   
  44.         public void run()   
  45.         {   
  46.             //declare pop member a,b, winner and loser   
  47.             int a, b, Winner, Loser;   
  48.             //initialise the population (randomly)   
  49.             init_pop();   
  50.             //start a tournament   
  51.             for (int tournamentNo = 0; tournamentNo < END; tournamentNo++)   
  52.             {   
  53.                 //pull 2 population members at random   
  54.                 a = (int)(POP * rnd.NextDouble());   
  55.                 b = (int)(POP * rnd.NextDouble());   
  56.                 //have a fight, see who has best genes   
  57.                 if (evaluate(a) < evaluate(b))   
  58.                 {   
  59.                     Winner = a;   
  60.                     Loser = b;   
  61.                 }   
  62.                 else  
  63.                 {   
  64.                     Winner = b;   
  65.                     Loser = a;   
  66.                 }   
  67.                 //Possibly do some gene jiggling, on all genes of loser   
  68.                 //again depends on randomness (simulating the    
  69.                 //natural selection   
  70.                 //process of evolutionary selection)   
  71.                 for (int i = 0; i < LEN; i++)   
  72.                 {   
  73.                     //maybe do some recombination   
  74.                     if (rnd.NextDouble() < REC)   
  75.                         gene[Loser, i] = gene[Winner, i];   
  76.                     //maybe do some muttion   
  77.                     if (rnd.NextDouble() < MUT)   
  78.                         gene[Loser, i] = 1 - gene[Loser, i];   
  79.                     //then test to see if the new population member    
  80.                     //is a winner   
  81.                     if (evaluate(Loser) == 0.0)   
  82.                         display(tournamentNo, Loser);   
  83.                 }   
  84.             }   
  85.         }   
  86.   
  87.   
  88.         //Display the results. Only called for good GA which has solved   
  89.         //the problem domain   
  90.         //@param tournaments : the current tournament loop number   
  91.         //@param n : the nth member of the population.    
  92.         private void display(int tournaments, int n)   
  93.         {   
  94.             Console.WriteLine("\r\n==============================\r\n");   
  95.             Console.WriteLine("After " + tournaments +    
  96.                               " tournaments, Solution sum pile " +    
  97.                               "(should be 36) cards are : ");   
  98.             for (int i = 0; i < LEN; i++) {   
  99.               if (gene[n,i] == 0) {   
  100.                 Console.WriteLine(i + 1);   
  101.               }   
  102.             }   
  103.             Console.WriteLine("\r\nAnd Product pile " +    
  104.                               "(should be 360)  cards are : ");   
  105.             for (int i = 0; i < LEN; i++) {   
  106.               if (gene[n,i] == 1) {   
  107.                   Console.WriteLine(i + 1);   
  108.               }   
  109.             }   
  110.         }   
  111.   
  112.         //evaluate the the nth member of the population   
  113.         //@param n : the nth member of the population   
  114.         //@return : the score for this member of the population.   
  115.         //If score is 0.0, then we have a good GA which has solved   
  116.         //the problem domain   
  117.         private double evaluate(int n)   
  118.         {   
  119.             //initialise field values   
  120.             int sum = 0, prod = 1;   
  121.             double scaled_sum_error, scaled_prod_error, combined_error;   
  122.             //loop though all genes for this population member   
  123.             for (int i = 0; i < LEN; i++)   
  124.             {   
  125.                 //if the gene value is 0, then put it in    
  126.                 //the sum (pile 0), and calculate sum   
  127.                 if (gene[n,i] == 0)   
  128.                 {   
  129.                     sum += (1 + i);   
  130.                 }   
  131.                 //if the gene value is 1, then put it in    
  132.                 //the product (pile 1), and calculate sum   
  133.                 else  
  134.                 {   
  135.                     prod *= (1 + i);   
  136.                 }   
  137.             }   
  138.             //work out how food this population member is,    
  139.             //based on an overall error   
  140.             //for the problem domain   
  141.             //NOTE : The fitness function will change    
  142.             //       for every problem domain.   
  143.             scaled_sum_error = (sum - SUMTARG) / SUMTARG;   
  144.             scaled_prod_error = (prod - PRODTARG) / PRODTARG;   
  145.             combined_error = Math.Abs(scaled_sum_error) +    
  146.                              Math.Abs(scaled_prod_error);   
  147.   
  148.             return combined_error;   
  149.         }   
  150.   
  151.         //initialise population   
  152.         private void init_pop()   
  153.         {   
  154.             //for entire population   
  155.             for (int i = 0; i < POP; i++)   
  156.             {   
  157.                 //for all genes   
  158.                 for (int j = 0; j < LEN; j++)   
  159.                 {   
  160.                     //randomly create gene values   
  161.                     if (rnd.NextDouble() < 0.5)   
  162.                     {   
  163.                         gene[i,j] = 0;   
  164.                     }   
  165.                     else  
  166.                     {   
  167.                         gene[i,j] = 1;   
  168.                     }   
  169.                 }   
  170.             }   
  171.         }   
  172.     }   
  173. }   

The results

Taking the last good population member results found, let's test it out.

2 + 7 + 8 + 9 + 10 = 36 in Pile 0, this is all good 
1 * 3 * 4 * 5 * 6 = 360 in Pile 1, this is all good

Points of Interest

I hope this article has demonstrated how to write a simple GA to solve a problem that we as humans would probably find hard to do manually. Remember this is a simple problem. what would happen if we upped the problem domain? A GA really is the way to go.

I will very shortly publish an article on a GA training a multi layer neural network to solve some logic problems. So if your'e into this sort of stuff, watch this space.

History

  • v1.0 - 08/11/06.
<!---->
<script src="/script/togglePre.js" type="text/javascript"></script>

About Sacha Barber


I am currently doing an MSc at Sussex University in Information Technology for E-Commerce (ITEC). I currently hold a 1st class (or summa cum laude, if you like that sort of thing) BSc Hons degree from Sussex University, in Computer Science & Artificial Intelligence.

Award(s)

I was awarded the "Best IT project for 2006" award, at Sussex University, for my final year degree project, by the British Computer Society.
1st Place for C# monthly code project competition for this article, in March 2007.

Almost But Not Quite Award(s)

2nd Place for ASP .NET monthly code project competition for this article, in January 2007. The winner was Omar Al Zabir, for this article.
2nd Place for C# monthly code project competition for this article, in December 2006. The winner was Omar Al Zabir, for this article.
2nd Place for C# monthly code project competition for this article, in November 2006. The winner was Andrew Kirillov, for this article.

Interests

I am quite interested in AI / computer vision / C# / web development / imaging / compilers and reflection. I also enjoy looking at new technologies such as LINQ / WPF. I just enjoy the learning process

Final Words

I would encourage people to write articles for codeproject, as having a real project is a really good way to pick up new skills. It has certainly helped me pick up new ideas, and new coding concepts. Go for it

My Blog
sachabarber.net

Click here to view Sacha Barber's online profile.

你可能感兴趣的:(asp.net,360,Go,LINQ,WPF)