Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

June 2004
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
May  Jul

Genetic Programming in Common Lisp

Saturday, June 19, 2004

I've been interested in Genetic Programming off and on over the past few years. Basically, GP uses evolutionary search (e.g. - the Darwinian principle of natural selection or survival of the fittest) to generate (and mutate) code programmatically. Although one might not think that "evolving" code is practical, in practice, GP is quite effective for certain types of problems. In particular, John Koza's books have been very useful in showing me how GP can solve a range of different types of problems in Lisp. Lee Spector also has some GP code in Lisp on his site as well as a new book on using GP for Quantum Computing.

Oh well, enough talking, let's see some code! I decided to see how Koza's GP code would do at "evolving" code to "discover" the formula for calculating the area of a circle (e.g. - PIr**2). The necessary steps I followed were:

These criteria are represented by the following Lisp code:
;=========================================================================
;;; Discovering a formula for the area of a circle
(defvar r)
(defun define-terminal-set-for-AREA-CIRCLE () (values '(r pi :floating-point-random-constant)))
(defun define-function-set-for-AREA-CIRCLE () (values '(+ - * %) '(2 2 2 2)))
(defstruct AREA-CIRCLE-fitness-case independent-variable target)
(defun define-fitness-cases-for-AREA-CIRCLE () (let (fitness-cases r this-fitness-case) (setf fitness-cases (make-array *number-of-fitness-cases*)) (format t "~%Fitness cases") (dotimes (index *number-of-fitness-cases*) (setf r (/ index *number-of-fitness-cases*)) (setf this-fitness-case (make-AREA-CIRCLE-fitness-case)) (setf (aref fitness-cases index) this-fitness-case) (setf (AREA-CIRCLE-fitness-case-independent-variable this-fitness-case) r) (setf (AREA-CIRCLE-fitness-case-target this-fitness-case) (* pi r r)) (format t "~% ~D ~D ~D" index (float r) (AREA-CIRCLE-fitness-case-target this-fitness-case))) (values fitness-cases)))
(defun AREA-CIRCLE-wrapper (result-from-program) (values result-from-program))
(defun evaluate-standardized-fitness-for-AREA-CIRCLE (program fitness-cases) (let (raw-fitness hits standardized-fitness r target-value difference value-from-program this-fitness-case) (setf raw-fitness 0.0) (setf hits 0) (dotimes (index *number-of-fitness-cases*) (setf this-fitness-case (aref fitness-cases index)) (setf r (AREA-CIRCLE-fitness-case-independent-variable this-fitness-case)) (setf target-value (AREA-CIRCLE-fitness-case-target this-fitness-case)) (setf value-from-program (AREA-CIRCLE-wrapper (eval program))) (setf difference (abs (- target-value value-from-program))) (incf raw-fitness difference) (when (< difference 0.01) (incf hits))) (setf standardized-fitness raw-fitness) (values standardized-fitness hits)))
(defun define-parameters-for-AREA-CIRCLE () (setf *number-of-fitness-cases* 10) (setf *max-depth-for-new-individuals* 6) (setf *max-depth-for-individuals-after-crossover* 17) (setf *fitness-proportionate-reproduction-fraction* 0.1) (setf *crossover-at-any-point-fraction* 0.2) (setf *crossover-at-function-point-fraction* 0.2) (setf *max-depth-for-new-subtrees-in-mutants* 4) (setf *method-of-selection* :fitness-proportionate) (setf *method-of-generation* :ramped-half-and-half) (values))
(defun define-termination-criterion-for-AREA-CIRCLE (current-generation maximum-generations best-standardized-fitness best-hits) (declare (ignore best-standardized-fitness)) (values (or (>= current-generation maximum-generations) (>= best-hits *number-of-fitness-cases*))))
(defun AREA-CIRCLE () (values 'define-function-set-for-AREA-CIRCLE 'define-terminal-set-for-AREA-CIRCLE 'define-fitness-cases-for-AREA-CIRCLE 'evaluate-standardized-fitness-for-AREA-CIRCLE 'define-parameters-for-AREA-CIRCLE 'define-termination-criterion-for-AREA-CIRCLE))
So, to test how well the GP algorithms (and my code) work:
  1. Download the GP code from Koza's first book and load it into your CL implementation.
  2. Load the above "Area of a Circle" GP code into your CL implementation.
  3. Run the following:
    (run-genetic-programming-system 'AREA-CIRCLE 1.0 31 200)
There are a lot of different options that are considered and ranked by the code, but here are the final results I got:
The best-of-run individual program for this run was found on 
generation 10 and had a standardized fitness measure of 0.0d0 and 10 hits.  
It was:
    (* (* PI R) R)
Not bad for evolution, eh?

emacs Copyright © 2004 by Bill Clementson