Archive

Archive for the ‘Genetic Programming’ Category

Watchmaker: A First and Second Example

September 13, 2009 6 comments

Time to look at the framework that I was sure I was going to like more than JGAP and ECJ.

I do like it; more than JGAP, but not as much as ECJ. I’ll send flowers later.

Since both of the examples, Hello World (genetic algorithm) and Simple Math Test (a genetic program), are already done in Watchmaker all I will be doing is explaining how they were done using the Watchmaker framework.

Hello World – A Genetic Algorithm

This is straight out of the Watchmaker Framework example code. Only two classes are needed:

  • StringEvaluator, the fitness function
  • StringsExample, the class that starts the evolution

The StringEvaluator class does three things:

  • hold onto the string the chromosomes are supposed to evolve into
  • check every character of the candidate string to determine how close it is to the desired string
  • return false when asked if a valid fitness value increases or decreases.

The StringsExample class creates the evolutionary environment in which the strings will evolve. The interesting stuff happens in here (reformatted to fit on this page).

This first section creates crossover and mutation objects specific to strings.

  • StringMutation mutates the evolving string using the alphabet as mutation values with a probability value of 0.02.
  • StringCrossover handles the crossover of strings based on the defined number of crossover points. The default is 1 crossover point.

The pipeline contains the list of operators to be applied to the population; in this case just the two we just discussed.

StringsExample.java
...
public static String evolveString(String target)
{
    List<EvolutionaryOperator> operators
                       = new ArrayList<EvolutionaryOperator>(2);
    operators.add(new StringMutation(ALPHABET, new Probability(0.02d)));
    operators.add(new StringCrossover());
    EvolutionaryOperator pipeline
                      = new EvolutionPipeline(operators);

The EvolutionEngine is the petri dish where everything happens. The configuration objects are as follows:
StringFactory, creates the initial chromosomes/individuals for the population
– pipeline, defined above
StringEvaluator, the fitness function also discussed above
RouletteWheelSelection, selects candidates at random where the probability is proportional to the candidates fitness score
MersenneTwisterRNG, a faster more reliable random number generator

StringsExample.java
    ...
    EvolutionEngine engine
          = new ConcurrentEvolutionEngine(
                      new StringFactory(ALPHABET, target.length()),
                      pipeline,
                      new StringEvaluator(target),
                      new RouletteWheelSelection(),
                      new MersenneTwisterRNG());

The EvolutionObserver is an interesting construct. This visitor gives you a front row seat of things as they happen. It prints out a basic string, but has the potential for coolness.

StringsExample.java
    ...
    engine.addEvolutionObserver(new EvolutionLogger());

Finally, turn on the engine for a population of 100 with a 5% elitism rate. Stop evolving when the fitness score equals 0.

StringsExample.java
    ...
    return engine.evolve(100, // 100 individuals in the population.
                           5, // 5% elitism.
                         new TargetFitness(0, false));
}

The code for the above is part of the Watchmaker Framework so download and poke it to your heart’s content.

Simple Math Test – A Genetic Program

The Watchmaker documentation states that it is a framework for genetic algorithms. That made me quite concerned as Toby Segaran’s Simple Math Test is not an example of a genetic algorithm. However, a quick look at the examples in the WF download revealed a package named org.uncommons.watchmaker.example.geneticprogramming. In that package there is a file named GeneticProgrammingExample.java and it states:

GeneticProgrammingExample.java
...
/**
 * Simple tree-based genetic programming application based on the first example
 * in Chapter 11 of Toby Segaran's Progamming Collective Intelligence.
 * @author Daniel Dyer
 */

Gotta love it. Less work for me and I don’t have to fight my way into the framework to figure out how to use WF in a genetic programming context. It seems unfair that both of the examples I chose are already done in WF, but that’s life. However, I will come up with a GP example that WF, JGAP, and ECJ have not done and see what it takes to implement them. One day. Soon.

[Perhaps Daniel Dyer will include the GP-related bits in an upcoming version of Watchmaker. Code reuse is a nice thing. I am hoping ECJ does that as well.]

The example geneticprogramming package contains the following files:
Node.java
BinaryNode.java
LeafNode.java

IfThenElse.java
IsGreater.java

Addition.java
Subtraction.java
Multiplication.java
Constant.java
Parameter.java

Simplification.java

TreeCrossover.java
TreeEvaluator.java
TreeFactory.java
TreeMutation.java

GeneticProgrammingExample.java

That’s a lot of files. Not having many of those classes in the framework shows.

In GP, the solution is an executable tree. During the evolution of the solution there will be trees that are not executable as well as trees that execute, but produce incorrect solutions.

In Watchmaker, the EvolutionEngine controls the process of creating a population, evolving them using various operators like crossover and mutation, checking their fitness and selecting the survivors for the next generation. This entails configuring it with:

In this case, the EvolutionEngine, an instance of ConcurrentEvolutionEngine, is configured with:

  • a TreeFactory which produces a tree of Nodes
  • an EvolutionPipeline which contains the crossover, mutation and simplification operators
  • a TreeEvaluator which will evaluate the fitness of the current tree/chromosome (given 2 input values does the tree produce the desired output value?)
  • a RouletteWheelSelection strategy
  • a MersenneTwisterRNG random number generator

You can read more about the RouletteWheelSelection in the Watchmaker Javadocs and at the Newcastle University Engineering Design Center, and about the MersenneTwisterRNG in the Uncommons Math Package Javadocs and at the university where it was developed. The Reader’s Digest version:

  • RouletteWheelSelection – selects candidates by giving them a higher chance of being randomly selected based on their fitness score. The higher the score the higher ther probability of their being chosen. They might be chosen more than once.
  • MersenneTwisterRNG – a random number generator that is very random, very fast and very predictable. It is used by various pieces of Watchmaker to guarantee randomness where it is needed.

Let’s look at the fitness function next. The TreeEvaluator is initialized with an array of two input values and an output value. The code, while different than that found in Collective Intelligence, heads for the same target: if the difference between the result from the evolved formula and the supplied output value is zero then we have a winner.

TreeEvaluator.java
  ...
  double actualValue = candidate.evaluate(entry.getKey());
  double diff = actualValue - entry.getValue();
  error += (diff * diff);

The Watchmaker code takes the difference between the actual and the expected values, squares it and adds it to the error value for each iteration of the input values. When error equals zero then the formula works. Same goal, different path.

The EvolutionPipeline contains three operators that are executed in the order in which they are given:

GeneticProgrammingExample.java
...
    List<EvolutionaryOperator<Node>> operators = new ArrayList<EvolutionaryOperator<Node>>(3);
    operators.add(new TreeMutation(factory, new Probability(0.4d)));
    operators.add(new TreeCrossover());
    operators.add(new Simplification());
...
    EvolutionEngine<Node> engine
        = new ConcurrentEvolutionEngine<Node>(...
                                        new EvolutionPipeline<Node>(operators),
                                        ...);

When a new population is created the EvolutionaryOperators are called in order:

  1. TreeMutation, with a mutation probability of 40%
  2. TreeCrossover, which is a single-point crossover
  3. Simplification, which asks each Node to simplify itself; for example, if the Node contains 3 + 5 then simplify it to 8, but if the Node contains x + 5 (a variable and a constant), then leave it alone.

All that is left is the TreeFactory. The TreeFactory needs to know 4 things to do its job:

  • the parameter count for the nodes that are not constants (for example, Addition gets 2 parameters)
  • the maximum depth of any given tree (too shallow doesn’t allow for a rich enough execution tree, too deep might generate a bunch of useless code)
  • the probability to use in the creation of functions
  • the probability to use in the creation of parameters

The EvolutionEngine will call TreeFactory.generateRandomCandidate(), which will call TreeFactory.makeNode(), to randomly create Nodes of functions, parameters or constants:

TreeFactory.java
  ...
private Node makeNode(Random rng, int maxDepth)
{
  if (functionProbability.nextEvent(rng) && maxDepth > 1)
  {
      // Max depth for sub-trees is one less than max depth for this node.
      int depth = maxDepth - 1;
      switch (rng.nextInt(5))
      {
        case 0: return new Addition(makeNode(rng, depth), makeNode(rng, depth));
        case 1: return new Subtraction(makeNode(rng, depth), makeNode(rng, depth));
        case 2: return new Multiplication(makeNode(rng, depth), makeNode(rng, depth));
        case 3: return new IfThenElse(makeNode(rng, depth), makeNode(rng, depth), makeNode(rng, depth));
        default: return new IsGreater(makeNode(rng, depth), makeNode(rng, depth));
      }
  }
  ...

When the TreeFactory creates a function it is only creating one of five possibilities. Adding additional baseline functions is pretty trivial once you see it presented like this. Daniel Dyer really has done a great job.

The last thing the TreeFactory does is either create a parameter with a parameter count of 1 or 0, or return a constant with a value of 0-10. All too easy.

TreeFactory.java
  ...
  else if (parameterProbability.nextEvent(rng))
  {
    return new Parameter(rng.nextInt(parameterCount));
  }
  else
  {
    return new Constant(rng.nextInt(11));
  }
}

Just for fun I have included an example of the code from two of the Nodes: Addition and IfThenElse.

Addition.java
  ...
  public Addition(Node left, Node right)
  {
      super(left, right, '+');
  }

  ...
  public double evaluate(double[] programParameters)
  {
    return left.evaluate(programParameters) + right.evaluate(programParameters);
  }
  ...

The constructor for Addition takes in two Nodes and a printable version of the operation. There is no check to see if the Nodes can evaluate to something useable by the Addition object; this is part of the evolution process. The trees that don’t work will be disposed of.

The evaluate() method calls evaluate() on the left and right Nodes and adds them together. ‘Nuff said.

The next example is the IfThenElse Node. It contains three nodes: a condition, code to execute if the condition is true, or code to execute if the condition is false.

IfThenElse.java
  ...
  public IfThenElse(Node condition, Node then, Node otherwise)
  {
    this.condition = condition;
    this.then = then;
    this.otherwise = otherwise;
  }
  ...

Again, notice no check on the actual capabilities of each Node.

IfThenElse.java
  ...
  public double evaluate(double[] programParameters)
  {
    return condition.evaluate(programParameters) > 0 // If...
           ? then.evaluate(programParameters)   // Then...
           : otherwise.evaluate(programParameters);  // Else...
  }
  ...

The evaluate() method resolves to the Java ternary operator and returns the result of either the then Node or otherwise Node. Yes, there is more code in the IfThenElse class (about 200 lines including comments), but the concepts are approachable and the code is quite clean/clear.

There. The Watchmaker framework and the Hello World and Simple Math Test examples.

I feel better now.

The Output

...
Generation 28: 493.0
Generation 29: 233.0
Generation 30: 233.0
Generation 31: 45.0
Generation 32: 5.0
Generation 33: 0.0
(((arg0 + 4.0) * arg0) + ((arg1 + -5.0) + (((6.0 + arg1) + 4.0) - arg0)))

The above simplified becomes:

(((arg0 + 4.0) * arg0) + ((arg1 + -5.0) + (((6.0 + arg1) + 4.0) - arg0)))
arg0^2 + 4*arg0 + arg1 - 5 + 6 + arg1 + 4 - arg0
arg0^2 + 3*arg0 + 2*arg1 + 5

If we make arg0 –> x and arg1 –> y then the above becomes x^2 + 3*x + 2*y + 5 which matches Toby Segaran’s formula. Go team!

And finally, in my never ending attempt to compare apples-to-apples, and failing miserably I might add, I present the same problem run by each of the three frameworks:

JGAP: 25 generations @ 1000 individuals/generation;
ECJ: 7 generations @ 1024 individuals/generation;
Watchmaker: 33 generations @ 1000 individuals/generation;

ECJ wins again.

The Bad

I was kinda disappointed when I realized that GP was not supported out of the box; the example above was an easy way to discover how to implement GP in Watchmaker, but it felt strange that such a good framework would leave it out. However, Watchmaker is at version 0.6.2 so I have high hopes for the future. Keep going!

The Good

Watchmaker is so easy to use! It uses all kinds of great design ideas, naming conventions, examples, etc. C’mon! You’re using generics! You rock!

The Code

Download the Watchmaker framework and go to town.

ECJ: A Second Tutorial

September 1, 2009 6 comments

This is the fifth of my postings on introductory examples in genetic algorithms and genetic programming. If you have been following these posts you already know that I do not go into what is GA or GP or how you would go about implementing your own GA/GP systems. If you want an introduction please read Chapter 11, Evolving Intelligence, from Toby Segaran’s Programming Collective Intelligence. It is a great introduction to GA/GP and I highly recommend it.

Time to look at the ECJ version of the GP example. Let me warn you: there are a lot of steps I will be skipping; look at the code I modified and at the code from the ECJ code drop. This framework isn’t as straightforward as JGAP or Watchmaker, but I am coming to believe it is the more powerful of the three.

Simple Math Test – A Genetic Program

Input: a series of number pairs

Output: the formula the transforms the pair of numbers to a desired output value.

As it turns out ECJ Tutorial 4 is an example of the above. In a testament to the use of highly accurate names the example is called MultiValuedRegression. Toby Segaran calls his multi-valued regression example Simple Math Test.

(Hmm. Which example would you rather try? I am pretty certain that those of us (well, me) who fit in the novice category of the Dreyfus model of skill acquisition would prefer the simpler name…unless you are into math in which case you are already insulted that we would grow code to figure out the solution instead of figuring it out ourselves. But I digress.)

I thought this post was going to be a short one. ECJ is both simple enough and complex enough that I will refer you to the full code from the ECJ download to view all the pieces involved in running this example and to the tutorial documentation to understand how this example works. However, looking at the fitness function and at the custom class I had to write will help in understanding how the ECJ pieces fit.

The following gratuitous diagram is from the ECJ documentation.

High-level Diagram of the ECJ GP Framework from the ECJ Tutorial 4 Documention

While I would have preferred a UML diagram, this will do. It is all about aggregation relationships anyway:

  • Individuals have a Species
  • Individuals contain a GPTree (solution tree)
  • A GPTree has GPTreeConstraints
  • A GPTree has Nodes which have NodeConstraints
  • Nodes may have child nodes. The child nodes in turn have NodeConstraints
  • NodeConstraints contain the child type and the Node return type

Here is my gratuitous UML diagram.

ECJ GP UML Diagram

ECJ GP UML Diagram

I admit it is not as explicit as ECJ’s diagram and it doesn’t use color (I’m not good with color. Except for purple. And maybe salmon. And bone. Or is it eggshell?).

The original ECJ MultiValuedRegression example does not use constants. The fitness function used the formula x^2 * y + x*y + y, but if you recall the Toby Segaran formula was x^2 + 2y + 3x + 5. What’s different? The use of actual numbers: 2, 3 and 5, to be exact.

In order to create the parse tree of code to be executed we have to use operators for addition and multiplication, variable wrappers for x and y, and numeric wrappers for integers (in the ECJ example, the subtraction operator is also used so I left it for old-times sake). In a situation where the formula to be evolved is quite unknown you may find yourself throwing in trig functions, power notation, the division operator and any other functions you think will help your population evolve in the proper direction.

I created a new Eclipse project and called it SimpleMathTest. I added the ECJ installation to the project’s classpath and in addition I copied the following into the src folder from the ECJ code drop:

  • ec.app.tutorial4.*.java. Rename the package to hiddenclause.ec.app.tutorial4 or whatever you like, but any place in the instructions below where I reference the package name you need to substitute the proper name.
  • ec.params
  • simple.params
  • koza.params
  • tutorial4.params

The various param files refer to each other. In order to make them visible to each other I had to change the paths declared using the parent.0 property.

Within tutorial4.params I changed the value of parent.0 to:

parent.0 = koza.params

Within koza.params I changed the value of parent.0 to:

parent.0 = simple.params

Within simple.params I changed the value of parent.0 to:

parent.0 = ec.params

In order to run the example within Eclipse create a run configuration for the project. The Run Configuration tabs for SimpleMathTest should be set as:

  • Main
    • Project: SimpleMathTest
    • Main class: ec.Evolve
  • Arguments
    • Program Arguments: -file tutorial4.params -p gp.tree.c=true
    • Working Directory: ${workspace_loc:SimpleMathTest/src/hiddenclause/ec/app/tutorial4}

No other tabs needed to be changed.

The Fitness Code

I changed the fitness code from:

MultiValuedRegression.java

...
    expectedResult = currentX * currentX * currentY
                   + currentX * currentY
                   + currentY;
...

to this:

...
    expectedResult = currentX * currentX
                   + 2 * currentY
                   + 3 * currentX
                   + 5;
...

The MultiValuedRegression class has a small local API, but a rather large one if you look at its inheritance tree. The three methods I care about within MultiValuedRegression are:

  • setup() – this is where the object gets information from the properties database. It is only called once.
  • clone() – creates a deep copy of the MultiValuedRegression object.
  • evaluate() – the fitness function. Well, technically not the fitness function as the KozaFitness object looks at the fitness score generated by evaluate() and picks who goes into the next generation.

Running the modified example code in ECJ did not find the formula. From a testing perspective the failure was to be expected. Now I could implement a class to handle constants and update the configuration file.

The Custom Data Classes

I implemented two classes using ECJ naming conventions: Int and IntData. Int is a wrapper for an integer value; it has to inherit from the ERC (Ephemeral Random Constants) class which holds a constant value. IntData is a wrapper for the result of the calculation and is checked in the fitness function; its value changes with each individual checked. Int is created and populated in the GPTree as it tries and reverse engineer the formula; IntData is passed in to each Individual as a place to store the result of the code execution (in this case a calculation).

The Int class has 6 local methods. They are all quite shallow so take a look at the code for a peek into what they do (the code is located below). The method I care about is eval(): it takes an incoming GPData object, downcasts it to an object of type IntData, and stores the Int object’s value in it.

    @Override
    public void eval(final EvolutionState state, final int thread,
                     final GPData input,
                     final ADFStack stack,
                     final GPIndividual individual,
                     final Problem problem) {
        IntData rd = ((IntData) (input));
        rd.x = _val;
    }

The IntData class has one local method named copyTo(). All it does is take its current value and assign it to an incoming GPData object.

public class IntData extends GPData {
    public int x; // return value

    @Override
    public GPData copyTo(final GPData gpd)
    {
        ((IntData) gpd).x = x;
        return gpd;
    }
}

The Configuration File

The changes in here were pretty easy: Add the new wrapper as a function, add the result wrapper, and declare the use of the fitness function.

The new wrapper is defined in tutorial4.param as:

...
gp.fs.0.size = 6
...
gp.fs.0.func.2 = hiddenclause.ec.app.tutorial4.Int
gp.fs.0.func.2.nc = nc0
...

The fitness function is declared as:

eval.problem = hiddenclause.ec.app.tutorial4.MultiValuedRegression

The result wrapper is declared twice: once for external use (the value checked within MultiValuedRegression) and once for internal use.

eval.problem.data = hiddenclause.ec.app.tutorial4.IntData
eval.problem.stack.context.data = hiddenclause.ec.app.tutorial4.IntData

The Output

Once all that was done, I was able to run the example and see if I could evolve the result I was looking for. The out.stat file had this to say:

...
Final Statistics
================
Total Individuals Evaluated: 7168

Best Individual of Run:
Evaluated: true
Fitness: Raw=0.0 Adjusted=1.0 Hits=10
Tree 0:
((x - (3 - x)) + (y + y)) + ((8 + x) + (x * x))

The above simplifies to:

x - 3 + x + y + y + 8 + x + x * x

2x - 3 + 2y + 8 + x + x^2

3x + 5 + 2y + x^2

x^2 + 2y + 3x + 5

The cat was alive.

The Code

tutorial4.params

# Copyright 2006 by Sean Luke and George Mason University
# Licensed under the Academic Free License version 3.0
# See the file "LICENSE" for more information

# Modified by Carlos Valcarcel for use as an example on the Hidden Clause blog.

parent.0 = koza.params

# We have one function set, of class GPFunctionSet
gp.fs.size = 1
gp.fs.0 = ec.gp.GPFunctionSet
# We'll call the function set "f0".  It uses the default GPFuncInfo class
#gp.fs.0.name = f0
#gp.fs.0.info = ec.gp.GPFuncInfo

# The function set.
gp.fs.0.size = 6
gp.fs.0.func.0 = hiddenclause.ec.app.tutorial4.X
gp.fs.0.func.0.nc = nc0
gp.fs.0.func.1 = hiddenclause.ec.app.tutorial4.Y
gp.fs.0.func.1.nc = nc0
gp.fs.0.func.2 = hiddenclause.ec.app.tutorial4.Int
gp.fs.0.func.2.nc = nc0
gp.fs.0.func.3 = hiddenclause.ec.app.tutorial4.Add
gp.fs.0.func.3.nc = nc2
gp.fs.0.func.4 = hiddenclause.ec.app.tutorial4.Sub
gp.fs.0.func.4.nc = nc2
gp.fs.0.func.5 = hiddenclause.ec.app.tutorial4.Mul
gp.fs.0.func.5.nc = nc2

eval.problem = hiddenclause.ec.app.tutorial4.MultiValuedRegression
eval.problem.data = hiddenclause.ec.app.tutorial4.IntData
# The following should almost *always* be the same as eval.problem.data
# For those who are interested, it defines the data object used internally
# inside ADF stack contexts
eval.problem.stack.context.data = hiddenclause.ec.app.tutorial4.IntData

Int.java

/*
 * This is a version of the MultiValuedRegression code from the ECJ code drop to
 * present an implementation of Toby Segaran's SimpleMathTest example.
 *
 * This is an example only! Use it for anything else at your own risk!
 * You have been warned! Coder/user beware!
 */

package hiddenclause.ec.app.tutorial4;

import ec.EvolutionState;
import ec.Problem;
import ec.gp.ADFStack;
import ec.gp.ERC;
import ec.gp.GPData;
import ec.gp.GPIndividual;
import ec.gp.GPNode;
import ec.util.Code;
import ec.util.Parameter;

public class Int extends ERC {
    private int _val;

    @Override
    public void checkConstraints(final EvolutionState state,
                                 final int tree,
                                 final GPIndividual typicalIndividual,
                                 final Parameter individualBase) {
        super.checkConstraints(state, tree, typicalIndividual, individualBase);
        if (children.length != 0)
            state.output.error("Incorrect number of children for node "
                    + toStringForError() + " at " + individualBase);
    }

    @Override
    public void eval(final EvolutionState state,
                     final int thread,
                     final GPData input,
                     final ADFStack stack,
                     final GPIndividual individual,
                     final Problem problem) {
        IntData rd = ((IntData) (input));
        rd.x = _val;
    }

    @Override
    public void resetNode(EvolutionState state, int thread) {
        _val = Math.abs(state.random[thread].nextInt() % 10);
    }

    @Override
    public String encode() {
        return Code.encode(_val);
    }

    @Override
    public boolean nodeEquals(GPNode node) {
        if (this.getClass() != node.getClass())
            return false;
        return (((Int) node)._val == _val);
    }

    @Override
    public String toString() {
        return Integer.toString(_val);
    }
}

IntData.java

/*
 * This is a version of the MultiValuedRegression code from
 * the ECJ code drop to present an implementation of Toby
 * Segaran's SimpleMathTest example.
 *
 * This is an example only! Use it for anything else at your own risk!
 * You have been warned! Coder/user beware!
 */

package hiddenclause.ec.app.tutorial4;

import ec.gp.GPData;

public class IntData extends GPData {
    public int x; // return value

    @Override
    public GPData copyTo(final GPData gpd)
    {
        ((IntData) gpd).x = x;
        return gpd;
    }
}

ECJ: A First/Simple Tutorial

August 22, 2009 7 comments

(This is going to be another long one.)

When we last left our heroes they were looking into JGAP as a cool framework for the creation of genetic algorithms. One simple example left them warm and fuzzy and the second left them temporarily bewildered. Taking a better look at the Watchmaker Framework helped clear their minds and made them realize that more thought was needed. They stopped, took a deep breath and ordered cappuccinos.

In the course of human events, research is inevitable. In my research I ran across Mehdi Khoury’s tutorial page which included a comparison of different genetic programming packages as of June 2007. The package he rated the highest? A package named ECJ developed at George Mason University’s Evolutionary Computation Laboratory.

In the course of my research in GP frameworks I decided I wanted to give ECJ a try as well. Nothing like a recommendation from a web site I’ve never heard of to influence my decision making capabilities about cool technologies.

I will be reversing the examples as the Hello World example is really about genetic algorithms while the Simple Math Test, taken from Toby Segaran’s Programming Collective Intelligence, is about genetic programming. It just makes sense to do GA before GP.

If you disagree I look forward to reading your blog posting where you do the reverse.

Hello World – A Genetic Algorithm

Input: the alphabet + a comma + an exclamation point + a space
Output: “Hello, world!”

The ECJ Properties File

The use of properties files is a very Java thing. Not to say that other programming languages don’t use something similar, but Java has made their use a virtue. In some cases, properties files are fantastic, while in other cases XML files are better. Folks who prefer verbose descriptions prefer XML and all others prefer the key=value format of properties; which you decide to use should be based on the problem at hand and not a knee-jerk reaction. Good luck figuring out if you’re having a brain-based knee-jerk reaction right now.

For this ECJ example, I rearranged the various properties so that related key/value pairs would be together. I hope this will make the explanation more coherent.

The first thing to bear in mind with ECJ is that you don’t get to write main(). The properties file tells the controller class ec.Evolve what to do and it does it with great verve and joie de vivre. I am sure you could write your own controller class to execute ECJ within your own applications, but that is left as an exercise for the reader.

The properties file is called from the command line like so:

java ec.Evolve -file [properties file]

As long as the ECJ framework is in the classpath (there is no JAR file to speak of, but you can always make your own), your custom classes are in the classpath, and the path to the properties file is accurate you should be good to go. I have run this example from within Eclipse and on the command line (using Kubuntu) and the results are the same.

verbosity    = 0

breedthreads = 1
evalthreads  = 1
seed.0       = 4357

The first batch of properties are global framework configurations:

  • log file level verbosity (0 – output everything, all the way to 5000 – output nothing). More detail here.
  • number of threads used for crossover/breeding
  • number of threads used for population evaluation
  • random number seed
state       = ec.simple.SimpleEvolutionState

pop         = ec.Population
init        = ec.simple.SimpleInitializer
finish      = ec.simple.SimpleFinisher
breed       = ec.simple.SimpleBreeder
eval        = ec.simple.SimpleEvaluator
stat        = ec.simple.SimpleStatistics
exch        = ec.simple.SimpleExchanger

generations          = 100
quit-on-run-complete = true

Next comes a number of existing classes to accomplish basic tasks. I am eternally grateful to them for the amount of work they saved me. The ones deserving of an explanation are:

  • ec.simple.SimpleEvolutionState – a subclass of EvolutionState. It contains the configuration information needed by the framework to get your genes evolving.
  • ec.Population – contains the current collection of Individuals/chromosomes that have been bred and/or evaluated.
  • ec.simple.SimpleBreeder – creates/breeds Individuals. While ECJ can handle multiple sub-populations the SimpleBreeder does not.

The generations and quit-on-run-complete are complementary; continue to evolve until 100 generations are completed or our fitness function has a perfect match.

checkpoint           = false
prefix               = ec
checkpoint-modulo    = 1

stat.file            = $out.stat

breed.elites.0       = 1

Checkpoint file configurations are defined here. The non-obvious ones are:

  • prefix – the prefix used for the checkpoint file (not used in this example, but necessary to run Evolve)
  • checkpoint-modulo – run a checkpoint every generation or after every N generations?
  • stat.file – name of the output file. The $ means write it in the folder where the Java process started. If a relative path is used then use the folder where the process started as the anchor for the relative path.
pop.subpops  = 1
pop.subpop.0 = ec.Subpopulation

pop.subpop.0.size                   = 100
pop.subpop.0.duplicate-retries      = 0
pop.subpop.0.species                = ec.vector.GeneVectorSpecies
pop.subpop.0.species.ind            = ec.vector.GeneVectorIndividual
pop.subpop.0.species.fitness        = ec.simple.SimpleFitness
#
# Hello, world! is 13 characters long
#
pop.subpop.0.species.genome-size    = 13
pop.subpop.0.species.crossover-type = two
pop.subpop.0.species.crossover-prob = 1.0
pop.subpop.0.species.mutation-prob  = 0.05

pop.subpop.0.species.pipe          = ec.vector.breed.VectorMutationPipeline
pop.subpop.0.species.pipe.source.0 = ec.vector.breed.VectorCrossoverPipeline
pop.subpop.0.species.pipe.source.0.source.0 = ec.select.TournamentSelection
pop.subpop.0.species.pipe.source.0.source.1 = ec.select.TournamentSelection

select.tournament.size = 2

The next group defines the configuration of the population:

  • Only one sub-population
  • Use the ec.Subpopulation class as its container
  • Create 100 Individuals
  • Use the GeneVectorSpecies and GeneVectorIndividual as the container for my custom gene
  • Use SimpleFitness to determine who are the current winners. Our custom fitness function will configure this for every individual
  • The genome size matches the length of our string…in this case 13. One character per gene
  • The crossover-type defines one of five possible ways to breed between the selected individuals. Type two means that the genes between two points in the chromosome will be swapped out with each other.
  • The crossover probability and mutation probability defines how often a crossover and mutation will occur: a one means all the time, 0.05 means not so often.
  • The mutation and crossover pipelines contain selection objects that decide who gets to breed and who gets mutated. TounamentSelection selects a number of individuals at random (how many is defined in the property select.tournament.size) and picks a winner based on the individual’s fitness value.
# each of these is really on one line
pop.subpop.0.species.gene
                = hiddenclause.example.ecj.CharVectorGene
pop.subpop.0.species.gene.alphabet
                = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY Z,!

eval.problem = hiddenclause.example.ecj.HelloWorld

Finally, I defined my 2 implementation classes (my gene and my fitness function) and their parameters.

There was no gene/individual type I could use so I defined a subclass of VectorGene and called it (wait for it) CharVectorGene. Since that gene is responsible for creating more genes, a Prototype for you Design Patterns geeks, it will also contain the alphabet containing the allowed characters to be used in this example.

HelloWorld is the fitness function that will push the population into evolving into a greeting.

Fitness Function

ECJ has the concept of an Individual where JGAP has a Chromosome and Watchmaker has AbstractCandidateFactorys to create the chromosomes out of any type you want. In the code I do the same check as before: check that the proper character is at the proper location; give it a point for every hit.

        int fitnessValue = 0;

        GeneVectorIndividual charVectorIndividual
                              = (GeneVectorIndividual) individual;
        long length = charVectorIndividual.size();
        for (int i = 0; i < length; i++) {
            CharVectorGene charVectorGene
                      = (CharVectorGene) charVectorIndividual.genome[i];
            char actual = charVectorGene.getAllele();
            if (actual == _expected[i]) {
                fitnessValue += 1;
            }
        }

In ECJ I am responsible for configuring the Fitness object as we get closer to the perfect message so my fitness function is not the ultimate arbiter of a gene’s fitness.

    SimpleFitness fitness = (SimpleFitness) charVectorIndividual.fitness;
    fitness.setFitness(evolutionState, fitnessValue,
                    fitnessValue == charVectorIndividual.genomeLength());

CharVectorGene – A Gene for Chars

This is where the gene is both created and initialized. The setup() method is only called once for the entire run to load up any parameters, in this case the desired alphabet.

    public void setup(final EvolutionState state, final Parameter base) {
        super.setup(state, base);

        Parameter def = defaultBase();

        String alphabetStr = state.parameters.getStringWithDefault(
                     base.push(P_ALPHABET), def.push(P_ALPHABET), "");
        if (alphabetStr.length() == 0)
            state.output.fatal(
                  "CharVectorGene must have a default alphabet", 
                  base.push(P_ALPHABET));

        alphabet = alphabetStr.toCharArray();
    }

Every gene has to have a character. This method randomly assigned a character to itself.

    public void reset(EvolutionState state, int thread) {
        int idx = state.random[thread].nextInt(alphabet.length);
        allele = alphabet[idx];
    }

There are four standard Java methods that turn out to be quite important to ECJ.

    public boolean equals(Object other)
    public int hashCode()
    public Object clone()
    public String toString()

Standard Java rules apply as to how you should implement them. I probably did not do such a good job.

ECJ’s HelloWorld Output

After running HelloWorld I found that the output to stdout doesn’t do anything more than tell you how many generations have passed and that (maybe) a solution was found. For the really interesting output you want to look at the out.stat file (ECJ automatically adds a space between each letter when it collects the stringified version of each gene):

...
Generation: 45
Best Individual:
Evaluated: T
Fitness: 13.0
 H e l l o ,   w o r l d !

Best Individual of Run:
Evaluated: T
Fitness: 13.0
 H e l l o ,   w o r l d !

The most interesting thing about the above is that ECJ came up with the solution in the least number of generations (JGAP: 233, Watchmaker: 125, ECJ: 45). It would appear that ECJ, though it took me longer to figure out, is the best petri dish so far. I am sure there is fine tuning that could be done to make the various toy example behave similarly, but I think I’ll leave that to someone who cares as an exercise for the reader.

Miscellaneous Comments

Okay, maybe I should have titled this section Miscellaneous Complaints.

The above example took me about a week of on-and-off thought. There was no simple way for me to discover what I did not know about ECJ, but it did a great job of highlighting over and over again that I knew nothing. Since there was no existing class to handle strings I had to figure out, sans documentation, what I needed to do. While intellectually interesting, it was quite frustrating. As you might expect there were a lot of dead-ends in my search.

In addition, due to the flexible method of assigning classes to various categories there should be a lot of checks for class types to insure the run doesn’t crash due to a bad case of downcasting. The ECJ examples used them; I removed them from my code.

While I was happy to finally figure out how to make ECJ work I also have to admit I was exhausted by the time that happened. Hunting through the Javadocs and various README files was decidedly unsatisfying.

Without going into a lot of detail, and I won’t, the ECJ framework appears to be quite powerful. While it has self-admitted warts the most interesting design choice I found was the use of properties files to glue everything together. JGAP and Watchmaker don’t use properties files at all though they might; I just didn’t run into them. ECJ’s use of them is very Spring-like only without the XML and the strict usage of interfaces. I am a big fan of the Spring Framework so ECJ gained points on the use of properties as a pseudo-dependency-injection file, but lost points by not using interfaces properly.

My big suggestion to the ECJ team: port ECJ to Spring and use more interfaces in the implementation code (or more accurately, stop downcasting the interfaces if you know that someone could mess with the properties files). Yeah, the use of XML is ugly, but it makes extending ECJ more consistent and should make writing tests for the various framework components easier.

Or not. I know it is going to be a lot of work and who knows how much code you want to maintain backward compatibility with.

On second thought, leave it alone. How about some more documentation?

The Code

helloworld.params

verbosity    = 0

breedthreads = 1
evalthreads  = 1
seed.0       = 4357

state  = ec.simple.SimpleEvolutionState

pop    = ec.Population
init   = ec.simple.SimpleInitializer
finish = ec.simple.SimpleFinisher
breed  = ec.simple.SimpleBreeder
eval   = ec.simple.SimpleEvaluator
stat   = ec.simple.SimpleStatistics
exch   = ec.simple.SimpleExchanger

generations             = 100
quit-on-run-complete    = true
checkpoint              = false
prefix                  = ec
checkpoint-modulo       = 1

stat.file       = $out.stat

pop.subpops     = 1
pop.subpop.0    = ec.Subpopulation

pop.subpop.0.size                  = 100
pop.subpop.0.duplicate-retries     = 0
pop.subpop.0.species               = ec.vector.GeneVectorSpecies
pop.subpop.0.species.ind           = ec.vector.GeneVectorIndividual
pop.subpop.0.species.fitness       = ec.simple.SimpleFitness

# Place on one line
pop.subpop.0.species.gene          
    = hiddenclause.example.ecj.CharVectorGene
# Place on one line
pop.subpop.0.species.gene.alphabet 
    = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY Z,!
#
# Hello, world! is 13 characters long
#
pop.subpop.0.species.genome-size    = 13
pop.subpop.0.species.crossover-type = two
pop.subpop.0.species.crossover-prob = 1.0
pop.subpop.0.species.mutation-prob  = 0.05

# Place on one line
pop.subpop.0.species.pipe                   
    = ec.vector.breed.VectorMutationPipeline
# Place on one line
pop.subpop.0.species.pipe.source.0          
    = ec.vector.breed.VectorCrossoverPipeline
pop.subpop.0.species.pipe.source.0.source.0 = ec.select.TournamentSelection
pop.subpop.0.species.pipe.source.0.source.1 = ec.select.TournamentSelection

select.tournament.size = 2

eval.problem = hiddenclause.example.ecj.HelloWorld

breed.elites.0 = 1

HelloWorld.java

/**
 * HelloWorld.java
 *
 * This is an example only! Use it for anything else at your own risk!
 * You have been warned! Coder/user beware!
 */
package hiddenclause.example.ecj;

import ec.EvolutionState;
import ec.Individual;
import ec.Problem;
import ec.simple.SimpleFitness;
import ec.simple.SimpleProblemForm;
import ec.vector.GeneVectorIndividual;

public class HelloWorld extends Problem implements SimpleProblemForm {
    private char[] _expected = "Hello, world!".toCharArray();

    public void evaluate(final EvolutionState evolutionState, 
                                    final Individual individual, 
                                    final int subPopulation, 
                                    final int threadNum) {
        if (individual.evaluated)
            return;

        int fitnessValue = 0;

        GeneVectorIndividual charVectorIndividual = (GeneVectorIndividual) individual;
        long length = charVectorIndividual.size();
        for (int i = 0; i < length; i++) {
            CharVectorGene charVectorGene 
                    = (CharVectorGene) charVectorIndividual.genome[i];
            char actual = charVectorGene.getAllele();
            if (actual == _expected[i]) {
                fitnessValue += 1;
            }
        }

        SimpleFitness fitness 
                         = (SimpleFitness) charVectorIndividual.fitness;
        fitness.setFitness(evolutionState, fitnessValue, 
                fitnessValue == charVectorIndividual.genomeLength());

        charVectorIndividual.evaluated = true;
    }

    public void describe(final Individual individual, 
                         final EvolutionState state, 
                         final int subPopulation, 
                         final int threadNum,
                         final int log, final int verbosity) {
        // Do Nothing
    }
}

CharVectorGene.java

/**
 * CharVectorGene.java
 *
 * This is an example only! Use it for anything else at your own risk!
 * You have been warned! Coder/user beware!
 */
package hiddenclause.example.ecj;

import ec.EvolutionState;
import ec.util.Parameter;
import ec.vector.VectorGene;

/**
 * @author carlos
 */
public class CharVectorGene extends VectorGene {
    public final static String P_ALPHABET = "alphabet";

    private static char[]      alphabet;
    private char               allele;

    @Override
    public void setup(final EvolutionState state, final Parameter base) {
        super.setup(state, base);

        Parameter def = defaultBase();

        String alphabetStr = state.parameters.getStringWithDefault(
                          base.push(P_ALPHABET), def.push(P_ALPHABET), "");
        if (alphabetStr.length() == 0)
            state.output.fatal(
                       "CharVectorGene must have a default alphabet", 
                       base.push(P_ALPHABET));

        alphabet = alphabetStr.toCharArray();
    }

    /*
     * (non-Javadoc)
     * @see ec.vector.VectorGene#reset(ec.EvolutionState, int)
     */
    @Override
    public void reset(EvolutionState state, int thread) {
        int idx = state.random[thread].nextInt(alphabet.length);
        allele = alphabet[idx];
    }

    public char getAllele() {
        return allele;
    }

    /*
     * (non-Javadoc)
     * @see ec.vector.VectorGene#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object other) {
        if (!this.getClass().isInstance(other)) {
            return false;
        }

        CharVectorGene that = (CharVectorGene) other;

        return allele == that.allele;
    }

    /*
     * @see ec.vector.VectorGene#hashCode()
     */
    @Override
    public int hashCode() {
        int hash = this.getClass().hashCode();

        hash = (hash << 1 | hash >>> 31) ^ allele;

        return hash;
    }

    @Override
    public Object clone() {
        CharVectorGene charVectorGene = (CharVectorGene) (super.clone());

        return charVectorGene;
    }

    @Override
    public String toString() {
        return Character.toString(allele);
    }
}

Next time: the ECJ version of Toby Segaran’s Simple Math Test.

Or maybe I will do that first in Watchmaker.

JGAP: Revisiting the Second Example

August 15, 2009 Leave a comment

Something happened on the way to my next post: I thought I discovered the reason why the Hello World example under JGAP did not work, or at least why it might not have worked (I am not sure if I could characterize it as failing as GP is all about combinations, not directed choice).

There were no mutations in the population! It was kind of like expecting Vanilla ice cream to become French Vanilla without any help. How could I have been so blind?

There was only one problem: I check the source code for the DefaultConfiguration object created by JGAP and it creates a MutationOperator with a default value of 1/12 (or .08). I traced the logic through my HelloWorld object and, lo and behold, the MutationOperator is created and used. The StringGene is told to mutate and it (occasionally) does.

The Watchmaker Framework recommends a mutation value of 0.02-0.05. The JGAP value is higher which means that it should work.

The problem is…it doesn’t. What could be wrong? What appeared to be a straightforward problem has turned into a white sperm whale tasking me.

I wanted to talk about ECJ. Now I have to talk about the Watchmaker Framework and what it does differently than JGAP. I really did not want to do that.I wanted to save it for later.

Now I have to.

The example code found in the Watchmaker Framework (WF) for the Hello World Example is different than the code in the documentation so if you go to the Completing the Jigsaw section the code located there and in the rest of Chapter 2 will not reflect the code in the actual example code shipped with the project. A shame, but nothing we can’t overcome by taking on a sunny disposition and a winning smile.

If you happen to have a favorite between JGAP and WF I apologize. This is liable to get ugly.

Time to compare.

The Fitness Function

The fitness functions are logically the same; check the letters at all positions. The difference: my JGAP fitness value goes up as more letters match; WF’s fitness value goes up as more don’t match. Nothing more to talk about there.

Evolving: Breeding/Crossover and Mutating

In JGAP the evolving population is made up of Chromosomes which are themselves made up of Genes. In the case of the Hello World example I wanted to evolve a string so I created a StringGene and made it part of a Chromosome.

...
    Gene stringGene = new StringGene(conf, MESSAGE.length(),
                                     MESSAGE.length(), "!dehlorw ,");
    IChromosome sampleChromosome = new Chromosome(conf, stringGene, 1);
...

Makes sense. A StringGene is a kind of Gene and a Chromosome is a kind of IChromosome (I know, I know…what the heck is an IChromosome? It is a naming convention that has been in use for many years now and used extensively, but not exclusively, by folks who use Eclipse. For now, let’s just call an IChromosome a Chromosome. Work with me).

In WF the evolving population is whatever type you want it to be. In this case, we want to evolve a String so it becomes the chromosome. No special wrappers here. Nothing wrong with using a StringGene; from an OO perspective it is a perfectly reasonable abstraction. It is just not used in WF. In addition, the population of Strings is created by a factory: the StringFactory which is a type of CandidateFactory. In JGAP I passed a selection of characters into the StringGene constructor, while in WF the selection of characters is passed into the StringFactory constructor (the following is example code shipped with WF).

...
    private static final char[] ALPHABET = new char[27];
    static
    {
        for (char c = 'A'; c <= 'Z'; c++)
        {
            ALPHABET[c - 'A'] = c;
        }
        ALPHABET[26] = ' ';
    }
...
    EvolutionEngine engine = new ConcurrentEvolutionEngine(
                                 new StringFactory(ALPHABET, target.length()),
                                 ...)
...

(Yes, it is different than the WF documentation. I also would not have used a for() to create a static array of 26 letters when actually listing the letters would have worked as well and been more obvious, but that is a different story and it is example code anyway. Jeez, just relax…)

After combing through the JGAP code, and taking a walk with my mom, I realized what was wrong: Watchmaker handles the string as a complete chromosome. I implemented the JGAP code for the string to be one gene!

Of course it didn’t work. The JGAP crossover was doing nothing.

I changed the JGAP example code to:

...
   private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
...
   Gene stringGene = new StringGene(conf, 1, 1, ALPHABET);
   IChromosome sampleChromosome = new Chromosome(conf, stringGene, MESSAGE.length());
...

The above configures one StringGene to hold one character and create as many StringGene objects in the Chromosome as the length of the MESSAGE. I changed the list of characters to reflect the data used in the WF example.

In addition, I changed the fitness function to check each individual allele against each individual character of the target string:

...
    protected double evaluate(IChromosome aSubject) {
        int fitnessValue = 0;

        int length = aSubject.size();
        for (int i = 0; i < length; i++) {
            String actual = (String) aSubject.getGene(i).getAllele();
            if (actual.toCharArray()[0] == _expected[i]) {
                fitnessValue += 1;
            }
        }

        return fitnessValue;
    }
...

I also changed the loop that evolves the population to continue until a perfect match is found.

Running the code to use the message “HELLO WORLD” and “ABCDEFGHIJKLMNOPQRSTUVWXYZ ” as the alphabet string I got:

Stopped at generation 123
Total evolution time: 0:0
The best solution has a fitness value of 11
Looking for 'HELLO WORLD':
	 'HELLO WORLD'

Much more reasonable than failure after 1 million tries with a population of 800K.

If I change the message to mixed case and a more complete alphabet:

...
    private static final String MESSAGE = "Hello, world!";
    private static final String ALPHABET =
                           "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,!";
...
        Gene stringGene = new StringGene(conf, 1, 1, ALPHABET);
...

The results become:

Stopped at generation 233
Total evolution time: 0:0
The best solution has a fitness value of 13
Looking for 'Hello, world!':
	 'Hello, world!'

I slightly modified the WF StringsExample code to accommodate my compulsion desire to avoid using command line args in examples. With an input of “HELLO WORLD” and alphabet of “ABCDEFGHIJKLMNOPQRSTUVWXYZ ” the output was:

...
Generation 46: HELLO WORLD
Evolution result: HELLO WORLD

If I changed the inputs to mixed case:

...
Generation 125: Hello, world!
Evolution result: Hello, world!

WF took fewer generations to find the solution than JGAP. Interesting. Perhaps WF is a better petri dish.

I suspect there is some fine tuning that could be done with JGAP, but works better in this particular WF example.

JGAP is at version 3.4.3. WF is at version 0.6.1. If you would like to see the WF example code please download the package and have a look.

So there you have it.

The reason why it didn’t work with JGAP was operator error.

The cat was dead and I didn’t notice even after opening the box.

I hate when that happens.

The Code

HelloWorld2.java

package hiddenclause.example;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.StringGene;

public class HelloWorld2 {

//    private static final String MESSAGE = "HELLO WORLD";
//    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";

//    private static final String ALPHABET = "dehlorw ,!";
//    private static final String MESSAGE = "hello, world!";

    private static final String MESSAGE = "Hello, world!";
    private static final String ALPHABET =
                                  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,!";

    public static void main(String[] args) throws Exception {
        long startTime = 0;
        long endTime = 0;
        HelloWorld2 hello = new HelloWorld2();

        Genotype population = hello.create(10);

        startTime = System.currentTimeMillis();
        IChromosome solution = null;
        int popIdx = 0;
        do {
            population.evolve();
            popIdx++;
            solution = population.getFittestChromosome();
        } while (solution.getFitnessValue() != MESSAGE.length());
        endTime = System.currentTimeMillis();

        outputSolution(population, startTime, endTime, popIdx);
    }

    private Genotype create(int popSize) throws InvalidConfigurationException {
        Configuration conf = new DefaultConfiguration();
        conf.setPreservFittestIndividual(true);

        FitnessFunction myFunc = new HelloWorldFitnessFunction2(MESSAGE);
        conf.setFitnessFunction(myFunc);

        Gene stringGene = new StringGene(conf, 1, 1, ALPHABET);
        IChromosome sampleChromosome = new Chromosome(conf, stringGene, MESSAGE.length());
        conf.setSampleChromosome(sampleChromosome);
        conf.setPopulationSize(popSize);

        Genotype population;
        population = Genotype.randomInitialGenotype(conf);

        return population;
    }

    private static void outputSolution(Genotype population, long startTime,
            long endTime, int evolutionIdx) {
        System.out.println("Stopped at generation " + evolutionIdx);

        long totalSeconds = (endTime - startTime) / 1000;
        long actualSeconds = totalSeconds % 60;
        long actualMinutes = totalSeconds / 60;
        System.out.println("Total evolution time: " + actualMinutes + ":"
                + actualSeconds);

        IChromosome solution = population.getFittestChromosome();
        int fitnessValue = (int) solution.getFitnessValue();
        System.out.println("The best solution has a fitness value of " + fitnessValue);

        System.out.println("Looking for '" + MESSAGE + "':");
        String message = getMessage(solution);
        System.out.println("\t '" + message + "'");
    }

    private static String getMessage(IChromosome solution) {
        String result = ""; 

        int length = solution.size();
        for (int i = 0; i < length; i++) {
            result += (String) solution.getGene(i).getAllele();
        }

        return result;
    }
}

HelloWorldFitnessFunction2.java

package hiddenclause.example;

import org.jgap.FitnessFunction;
import org.jgap.IChromosome;

public class HelloWorldFitnessFunction2 extends FitnessFunction {

    private char[] _expected;

    public HelloWorldFitnessFunction2(String desiredMessage) {
        _expected = desiredMessage.toCharArray();
    }

    @Override
    protected double evaluate(IChromosome aSubject) {
        int fitnessValue = 0;

        int length = aSubject.size();
        for (int i = 0; i < length; i++) {
            String actual = (String) aSubject.getGene(i).getAllele();
            if (actual.toCharArray()[0] == _expected[i]) {
                fitnessValue += 1;
            }
        }

        return fitnessValue;
    }

}

JGAP: A Second Example and Observations

August 11, 2009 2 comments

In the first chapter of the documentation for the Watchmaker Framework Daniel W. Dyer presents an interesting genetic algorithm example: what if we wanted to evolve the string “HELLO WORLD”?

The fitness function would be straightforward: check every letter of a possible solution and every matching letter at a matching position would raise that chromosome’s fitness value.

I highly recommend you read the chapter referenced above before continuing. It is quite well written and will make the pain I describe below clearer.

My first thought: implementing this in JGAP shouldn’t be that hard.

Implementing it: easy. Getting a good solution: not so much. This is not a problem with JGAP so much as it is a problem with monkeys and Shakespeare. Before I go into that let me present the code.

My target was a little bit more ambitious: instead of hello world in all upper case characters I wanted to evolve the string “hello, world!” all in lower case and containing a space, a comma and an exclamation point.

The fitness function was easy: check every letter and its position and if it matches the target string increment the fitness value by 1. The higher the fitness value the better the match.

Here is my fitness function:

package hiddenclause.example;

import org.jgap.FitnessFunction;
import org.jgap.IChromosome;

public class HelloWorldFitnessFunction extends FitnessFunction {

    private char[] _expected;

    public HelloWorldFitnessFunction(String desiredMessage) {
        _expected = desiredMessage.toCharArray();
    }

    @Override
    protected double evaluate(IChromosome aSubject) {
        int fitnessValue = 0;

        String msg = (String) aSubject.getGene(0).getAllele();
        char [] actual = msg.toCharArray();
        for (int i = 0; i < actual.length; i++) {
            if (actual[i] == _expected[i]) {
                fitnessValue += 1;
            }
        }

        return fitnessValue;
    }

}

Next step: implement the chromosome that will contain the letters that will evolve into the target string.

In order to accomplish the next step I decided to use the JGAP genetic algorithm* class StringGene. I configured it to randomly generate a string of exactly the length desired and use an alphabet of just the letters and punctuation from the target string. Why not add the entire alphabet and punctuation to add to the diversity? Monkeys, remember? More on that later.

The chromosome creation code is:

package hiddenclause.example;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.StringGene;

public class HelloWorld {

    private static final String MESSAGE = "hello, world!";
    private static final int MAX_EVOLUTION = 1000000;

    public static void main(String[] args) throws Exception {
        long startTime = 0;
        long endTime = 0;
        HelloWorld hello = new HelloWorld();

        Genotype population = hello.create(1000);

        startTime = System.currentTimeMillis();
        int i = 0;
        for (i = 0; i < MAX_EVOLUTION; i++) {
            population.evolve();
            IChromosome solution = population.getFittestChromosome();
            if (solution.getFitnessValue() == MESSAGE.length()) {
                break;
            }
        }
        endTime = System.currentTimeMillis();

        outputSolution(population, startTime, endTime, i);
    }

    private Genotype create(int popSize) throws InvalidConfigurationException {
        Configuration conf = new DefaultConfiguration();
        conf.setPreservFittestIndividual(true);

        FitnessFunction myFunc = new HelloWorldFitnessFunction(MESSAGE);
        conf.setFitnessFunction(myFunc);

        Gene stringGene = new StringGene(conf, MESSAGE.length(), MESSAGE.length(),
                "!dehlorw ,");
        IChromosome sampleChromosome = new Chromosome(conf, stringGene, 1);
        conf.setSampleChromosome(sampleChromosome);
        conf.setPopulationSize(popSize);

        Genotype population;
        population = Genotype.randomInitialGenotype(conf);

        return population;
    }

    private static void outputSolution(Genotype population, long startTime,
            long endTime, int evolutionIdx) {
        System.out.println("Stopped at generation " + evolutionIdx);

        long totalSeconds = (endTime - startTime) / 1000;
        long actualSeconds = totalSeconds % 60;
        long actualMinutes = totalSeconds / 60;
        System.out.println("Total evolution time: " + actualMinutes + ":"
                + actualSeconds);

        IChromosome solution = population.getFittestChromosome();
        int fitnessValue = (int) solution.getFitnessValue();
        System.out.println("The best solution has a fitness value of " + fitnessValue);

        System.out.println("Looking for '" + MESSAGE + "':");
        String message = (String) solution.getGene(0).getAllele();
        System.out.println("\t '" + message + "'");
    }
}

Notice that the HelloWorld class will handle any kind of incoming string; I just happened to use a hard coded constant. One of its assumptions (euphemism for bug) is that the incoming string and the solution string are the same length. If the two strings are not the same length the loop might try to access part of an array that doesn’t exist and there are very few exceptions that cause as severe a level of mental anguish as the ArrayIndexOutOfBoundsException (however, NullPointerException is right up there).

The above gave the following output after 1 million generations:
Break: Executed evolution 1000000 times.
Total evolution time: 76:45
The best solution has a fitness value of 10
Looking for 'hello, world!':
'!ello,  or!d!'

Not pretty.

What happened?

While the use of crossover should make the evolution progress faster there are certain statistical issues that must be taken into account. For a chromosome to get a high value it has to have 2 things in its favor:

  1. it has to have one or more letters in the new string
  2. the letter(s) have to be in the proper position(s)

This is where the monkeys come in.

The more I thought of it the more I thought about the scenario of an infinite number of monkeys trying to write Shakespeare. To begin their arduous task one or more of the monkeys has to hit the proper first letter. Assuming a keyboard with just the alphabet, and no punctuation, they have a 1 in 52 chance of hitting the right first letter. Shouldn’t be too hard with an infinite number of primates.

The next letter becomes a little harder. Of the subset of monkeys that hit the proper first letter the chances of any of them hitting that second letter has a statistical probability of 1 in 2704. The third: 1 in 140608. The fourth: 1 in 7,311,616. The fifth: 1 in 380,204,032.

And that doesn’t include punctuation and white space.

That mean that if the letter h never makes it to the first character the system will never (never, as in…well, never) evolve the target string. Never. In order to up the statistical odds of my chromosome succeeding I created an alphabet of just the letters that comprise the target string: “!dehlorw ,”.

And yet, with that small set of characters, an ever changing population of 1000 chromosomes at 1 million generations was unable to generate the string. Can it succeed? Of course. The proper population (random generation of strings) in the petri dish that is JGAP would generate the target string. But it is random and random does not mean regular.

So, adding more letters and punctuation adds more noise to the mix and just makes the combinations even worse (though possibly better as more randomly mixed letters could elevate the use of letters from the target string).

When I changed the StringGene’s alphabet to include a few more letters, “!abcdefghlmnorstwxyz ,”, this is what the output was:

Stopped at generation 1000000
Total evolution time: 77:20
The best solution has a fitness value of 7
Looking for 'hello, world!':
'zblxo, ,orrdx'

Defintely not an improvement.

Is there a way of configuring this differently to encourage a solution? Perhaps a better fitness function? Custom gene? Better randomizer? I will gladly take any suggestions and try them out. Maybe.

Exercise for the reader: up the population value to create more opportunities for the desired letters to end up in the desired positions. Bake overnight.

When I increased the population number to 800K and ran it again my Kubuntu box locked up after 2 days because I continued to do things like run Eclipse, read PDFs and back-up my work. I guess 800 trillion iterations of the fitness function was exhausting.

No joy in Mudville.

Update 8/15/09: the problem solved.

* To create a genetic program using JGAP you would create a GPGenotype using a class that inherits from GPProblem and a fitness function that inherits from GPFitnessFunction.