Menu

Tree [r18] /
 History

HTTPS access


File Date Author Commit
 src 2010-10-13 richy [r18] well- they move about randomly and do stuff. No...
 .project 2010-10-05 richy [r5] adding pyglet for graphing / drawing
 .pydevproject 2010-10-13 richy [r18] well- they move about randomly and do stuff. No...
 goals.txt 2010-09-29 richy [r3] Updated the goals and readme so that others mig...
 hello_world.py 2010-10-05 richy [r5] adding pyglet for graphing / drawing
 readme.txt 2010-10-06 richy [r8] added a python 2.5 enhancement for the if state...

Read Me

Very rough genetic algo framework in python
-------------------------------------------

richardagreen@gmail.com


Basic class hierachy..
----------------------

"Everything" extends from the Node class which can "evaluate" itself given a value (which is generally an array of inputs). 

In the code, all example trees start from a "PassThroughNode" which is then asked to "create" which generates the actual function tree.

The only Nodes that are working currently are IntegerConstantNode, SubtractFunctionNode, MultiplyFunctionNode, GetParameterNode and AddFunctionNode. 

I think Store and Get may work, but I haven't tested / worked on them much yet. "If" definitely doesn't work yet ( I am in a quandry how to divide the logical operators and whether it should be a 2, 3, 4 or 5  child function - ie "if ( a logical_op_b c ) then d else e" requires 5 children [ logical_op_b is one of =, !=, <, > etc etc ] , whereas the simple "if a > 0 then b" requires only 2.

I haven't included stuff in later version of python (ie multiprocessing) yet...

Bits that I think are "my" ideas but have probably been done by other people
----------------------------------------------------------------------------

1) "Minor Mutations" - ie if we are mutating things that are quite succesful in the population - then a minor mutation may (in the case of an integer constant node) only change the value a small value up or down. I haven't extended this to other nodes yet but thought I'd throw it in there.

2) "Graduated targets for the natural progression of generations". If I can explain with a very basic simile - you don't put a baby in a space shuttle. Genetically, the child has to learn to stand up, walk / talk, go to college etc.. The ones that don't make the grade don't get through to the next generation. Therefore, if you have a very specific challenging task, then break down the target steps into levels where only the best progress onto the next challenge. Additionally, make some algorithms just functions which are then used by parent algorithms.

3) Construction of executable code. Each Node currently can represent itself as subcode in python format - therefore the whole algorithm can display itself as executable python code (as well as XML). This can then be eval()'d instead of node.evaluate()'d hopefully improving performance. 

4) Node simplification. There may be some benefit in applying some simple math simplification / optimization to algorithm trees - ie if you have an add node of (10,5, x[1]), then just make it add(15,x[1]).Additionally, anything that is +0 should just be simplified and anything multiplied by zero will obviously be zero. This does actually reduce the potential for benificial mutations however, but it can make the algorithm more human readable.

5) If the population doesn't generate a better solution, then the population size for the next generation is doubled and the elitism factor is reduced by 75% ( effectively halving the number that make it through to the next generation). This is reset to the initial values once the generation has provided a better fitting candidate.



Current Usage
-------------

I write "current" as it is currently configured to 
1) generate a random dataset using the formula in hidden_function2 ( currently : 2 * x + 5 + y )
2) evolve a solution to the above
3) repeat 1+2 10 times and display the time taken to find the solution

Once a solution has been display you can create a python function from it directly, eg the last few lines of the last run I did looked like:

Generating...
Stats for generating = [9, 15, 0, 56, 1] (took 0.063000202179 seconds ) 
Scoring...
Scoring took 0.171000003815 seconds
0.000000e+000
Pop size now 100 . Elitism is now 0.2 
=  + ( <8>, - (  - ( P[0],<0> ) , - ( P[0],P[0] )  ) , + (  + ( <4>,P[1] ) , - ( P[0],<8> ) ,<1> )  )  
lambda x: (8 + ( ( x[0]  - 0 )  - ( x[0]  - x[0]  )  )  + ((4 + x[1] ) + ( x[0]  - 8 )  + 1))
lambda x: (8 + ( ( x[0]  - 0 )  - ( x[0]  - x[0]  )  )  + ((4 + x[1] ) + ( x[0]  - 8 )  + 1))
( 1500,2500,6500,...)
[0.21900010108947754, 0.20300006866455078, 0.28099989891052246, 0.17100000381469727]
=  + ( <8>, - (  - ( P[0],<0> ) , - ( P[0],P[0] )  ) , + (  + ( <4>,P[1] ) , - ( P[0],<8> ) ,<1> )  )  
PRE
lambda x: (8 + ( ( x[0]  - 0 )  - ( x[0]  - x[0]  )  )  + ((4 + x[1] ) + ( x[0]  - 8 )  + 1))
POST
lambda x: (8 + ( ( x[0]  - 0 )  - ( x[0]  - x[0]  )  )  + ((4 + x[1] ) + ( x[0]  - 8 )  + 1))
Detail: [1.375, 4.6089999675750732, 2.3280000686645508, 3.562999963760376, 4.9839999675750732, 2.9530000686645508, 2.3289999961853027, 2.9369997978210449, 3.3280000686645508, 1.1560001373291016] Total:29.5620000362 Avg:2.95620000362 

# Let's create a function from that.. cut/paste :

>>> fn = lambda x: (8 + ( ( x[0]  - 0 )  - ( x[0]  - x[0]  )  )  + ((4 + x[1] ) + ( x[0]  - 8 )  + 1))

# Now "fn" is a function that I can call with whatever values I would like - ie 5,4 or 5,2 etc. The inbound parameter must be addressable

>>> fn
<function <lambda> at 0x001A2E30>
>>> fn([5,4])
19
>>> fn([5,2])
17