[jrgp | -- Documentation ] |
Here is a typical java program using 'gap':
import gap.*; public class SymbolicRegressionMain extends Object { public static void main (String args[]) { try{ Class[] functionSet = {Add.class,Cos.class,Div.class,Exp.class,Mul.class, Rlog.class,Sin.class,Sub.class}; Class[] terminalSet = {}; int[] argumentsType = {GPFunction.NO_TYPE}; GPParams params = new GPParams( functionSet, terminalSet, argumentType ); params.makeDefinitive(); GPPopulation pop = new GPPopulation( 5000, params, new RegrEvaluator() ); pop.setLogBook( new LogScreen() ); pop.setOverSelection( -1.0f ); for( int i=0; i<20; i++) pop.nextGeneration(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }
First of all, you have to define the parameters of your GP-problems. You
have to create a gap.GPParams
object specifying the function set and the type of the arguments (if any).
Then you can define the ADFs using the functions setADFs
,
setADFFunctionSets
(to define a function set different from the
main one for an ADF), setADFLinks
(to specify how the ADF can
use each other). Please refer to the java
documentation for more details. You may also want to specify the type of
the root (see the setRootCompatibility
method) and set standard
parameters such as the maximal depth of new programs or the max depth of the
ADFs.
When you have finished with these settings, call the
GPParams.makeDefinitive
method to have your program setting up
all the GPParams internal tables. After this point, you can only modify the
numerical parameters of your parameters object, while the structure (e.g. the
number of ADFs or the content of the function set) can only be modified in a
GP-population (gap.GPPopulation
) . See Modifying the structure
of a parameters object.
You can now use your parameters to create a GP-program or a new random GP-population. Otherwise You can write a program as a LISP-like string and parse it constructing a GP-program.
The
GPPopulation
constructor takes as arguments the size of the
created population, a GPParams
instance and an evaluator
implementing the gap.Evaluator interface.
(A possible start-point for implementing this is given by gap.DefaultEvaluator class, check the
shipped examples for the concrete details). Note that a GPPopulation contains
additional parameters (e.g. the probability of mutation or a overselection
flag). If you want your GPPopulation to print some information during the
run, you can set a log book (see the GPPopulation.setLogBook
method). Standard log books are provided: LogFile
prints to a
file and LogScreen
to the standard output.
We found very comfortable to work on GP-problems with jython. In this way you merge the flexibility of an interactive session as in 'gool' and the power of low-level interaction with the objects.
Here's a possible jython session:
>>> import java >>> import gap >>> from symbolic_regression import * >>> no_type=gap.GPFunction.NO_TYPE >>> fs=[Add,Cos,Div,Exp,Mul,Rlog,Sin,Sub] >>> ts=[] >>> arg_type=[no_type] >>> params=gap.GPParams( fs, ts, arg_type ) >>> # create a new population >>> pop=gap.GPPopulation(5000, params, RegrEvaluator()) >>> pop.setOverSelection(-1.0) >>> # 10 generations >>> for i in range(10): ... pop.nextGeneration() ... >>> pop.getBestIndividual() (* jython output omitted *) >>> pop.getBestFitness() (* jython output omitted *) >>> pop.save('test.pop') >>> # remove the Sin function from the function set >>> pop.removeFunction(Sin, gap.GPPopulation.REPLACE_BRANCH) >>> # add an ADF >>> pop.addADF(no_type, [no_type, no_type], None, None, None)
See the gool how-to.
You can make your GP-programs independent from the gap library by compiling them into self-standing java classes. This is really useful in particular if you evolved a program that is interesting because of its effects (e.g. if your program controls an external agent as in the 'ant' example).
Just call the the GPProgram.compile
method specifying the class name (package name included) and the file in which
to save the class. 'gool' has a 'compile...' option for population program
nodes too.
You may want to modify the structure of your parameters during a run, for example if you find out that a function is not useful or if you want to add an ADF. Although you can`t modify the parameters directly after you made them definitive, you can do this by calling the corresponding methods of your GPPopulation.
If you remove something from the function set some branches of your program
trees may be invalid. You can choose one out of three ways of replacing them:
you can replace the entire program with a new random one
(GPPopulation.REPLACE_PROGRAM
), or only the tree
(GPPopulation.REPLACE_TREE
) or the branch in which the removed
function is called (GPPopulation.REPLACE_BRANCH
). See
removeADF, removeFunction, removeTerminal, removeArg, addADF,
addFunction, addTerminal, addArg
in the java documentation for further
details.
There are a few important methods you may want to overload in your GPPopulation to change the standard behaviour:
newIndividual
: overload this method if you want to
implement special ways to create new individuals. This method is called at
the creation of a new population and when a program is created to replace
an invalid one (see Modifying the
structure of a parameters object );nextGeneration
: overload this method if you need to create
new generation in a non-standard way or if you want to implement your own
genetic operations;fitnessDependentChoice
: this method is called when 'gap'
has to choose a random individual in the population.The standard random numbers generator in 'gap' is an implementation of the Park-Miller one and is defined in the TheRandomGenerator class, which contains static methods to obtain random numbers or to set the seed.
If you want 'gap' to use another generator, you have to create an
implementation of the RandomGenerator
interface, and
then call the TheRandomGenerator.setRandomGenerator(RandomGenerator
rg)
method.
Q: You claim that gap is strong-typed, but actually only float values can be returned.
A: The structure of the program tree is strong-typed, as you can define arbitrary types and then require that the different branches of a function node belong to a subset of this types by specifying a compatibility table.
In contrast, the computed value type is always 'float'. This is enough for most application. However, if you need to return something else, you can play with the free Object argument of the GPFunction exec method.