CS 1103-01 - AY2023-T2 - Lab 7 Unit 4
CS 1103-01 - AY2023-T2 - Lab 7 Unit 4
CS 1103-01 - AY2023-T2 - Lab 7 Unit 4
Lab 7 Unit 4
The lab for this week is a continuation of last week's lab. (You probably want to save a copy of the work that you did for that lab before you
start modifying it.) The programming will be rather complicated.
Last week, you generated random expressions and tested them to see how closely they would reproduce a set of sample data. Essentially,
you were doing a random search through the space of all possible expressions and keeping track of the best one that you happened to find.
For many problems, there is a better way to search for a solution in a very large space of possible solutions. The genetic algorithm imitates
some ideas from biological evolution to search more effectively than a purely random search. In outline, the genetic algorithm works like this:
Mutation refers to making some random change to one individual. Crossover means combining parts (or traits) of two individuals to produce
a new individual. The genetic algorithm does not work well for all problems, but for some, it can give much better results than a random
search.
Two files of sample output from others’ implementations of this program are available in the code directory: For the first file, the genetic
algorithm ran for a large number of generations; every time a new best expression was found, I output the generation number, RMS error,
and the expression. For the second file, the random search algorithm was compared to the genetic programming search: Each algorithm was
allowed to run for 60 seconds, then the best RMS error and expression were printed out. That process was repeated, keeping track of the
average best fitness for both algorithms.
Genetic programming is a variant of the genetic algorithm in which the individuals that are being evolved are computer programs, and their
fitness is determined by how well they perform some specified task. Genetic programming often works with the LISP programming language,
which has a particularly simple syntax. LISP programs can be represented as a certain kind of tree, and almost any such tree is a syntactically
legal program. There are fairly easy ways to do mutation and crossover on trees. For mutation, just take a random node in the tree and make
some random changes to it. To do a crossover between individuals A and B, pick random branches in A and B, and swap the selected branch
in A with the selected branch in B.
Although we aren't working with LISP programs, our expressions can be thought of as a simple kind of program, and we can try to apply
https://my.uopeople.edu/mod/page/view.php?id=328386 1/5
12/3/22, 4:19 PM CS 1103-01 - AY2023-T2: Lab 7 Unit 4
genetic programming to them. This is an experimental lab. You cannot predict what results you will get, and your results will depend
very much on the details of your implementation. There are many ways to "tune" a genetic algorithm, and many decisions to be
made. You will just have to see what happens.
Here are some ideas. You will find that you need to be able to sort an array of expressions according to their fitness values. (The fitness here
is the RMSError.) In order to do this, it's useful to store the computed fitness value along with the expression. For this, you can use a class:
The population is then an array of individuals, and you can use the following methods to sort the array into an order of increasing fitness by
calling quickSort(population, 0, population.length-1). (This assumes that none of the individuals have fitness that is infinite or NaN.)
Here is an outline of one way to implement the algorithm, using a population size of 1000. (The population size would be a constant in your
program.) Make an array that is big enough to hold two or three times as many individuals as the population size (because you will be
discarding some after breeding). Fill the first 1000 spaces in the array with randomly created expressions, and compute their fitnesses,
making sure that the fitness is an actual number (that does not satisfy Double.isNaN(fitness) || Double.isInfinite(fitness)).
You will then go into the basic evolutionary loop: Breed the individuals in locations 0 through 999 of the array to fill the rest of the array. To
do this, choose two random individuals for that range. Do a crossover between those two individuals. Then possibly apply mutation to the
new individuals. (Too much mutation can be bad. You can experiment with the mutation probability.) Compute each new individual's fitness
and place it into the array, but only if its fitness is an actual number. (You might also find it necessary to throw away individuals that have
https://my.uopeople.edu/mod/page/view.php?id=328386 2/5
12/3/22, 4:19 PM CS 1103-01 - AY2023-T2: Lab 7 Unit 4
gotten too complex, to avoid having really huge expressions. To implement this, you can add a height() function to ExpNodes to compute the
height of the expression.) Once you've filled the array, you can sort the array by fitness, so the fittest individuals are in the first part of the
array. The next time through the loop, you will only use the first 1000 individuals for reproduction, and you will replace the other individuals
in the array, which are less fit.
The hardest part is probably implementing mutation and crossover. There are several ways to implement this. For example... For mutation,
you can select a random node in the expression and modify it. You can do this recursively: For constant nodes, you can change the constant;
for operator nodes, you can change the operator or you can mutate one of the operands (or possibly even replace one of the operands with
an entirely new random expression); and for variable nodes, you really can't do anything to mutate them. For crossover of two expressions,
you could randomly select an operator node from each expression, and swap one of the operands of one node with one of the operands of
the other node. If an expression has no operator nodes, you can't really do a crossover with it.)
The following functions could be used to test your mutate and crossover functions:
https://my.uopeople.edu/mod/page/view.php?id=328386 3/5
12/3/22, 4:19 PM CS 1103-01 - AY2023-T2: Lab 7 Unit 4
For the ".equals" tests to work properly, you have to define the equals method in all your classes. Like toString, the equals method is defined
in class Object, where it is just defined to do the same comparison as "==". Classes often need to redefine the equals method. For example,
here is the equals method from my BinOpNode class:
In the previous lab, the expression that was used to make the test data was fairly easy for a random process to guess, especially if you made
your random expressions with integers. To make it harder, you could change "double y = 2.5*x*x*x - x*x/3 + 3*x;" to "double y = 2.5345*x*x*x
- x*x/3.112009 + 3.237*x;" or something even messier.
All activities close on Wednesdays at 11:55 PM, except for Learning Journals/Portfolios which close on Thursdays at 11:55 PM always
following the clock at the top of the page.
Due dates/times displayed in activities will vary with your chosen time zone, however you are still bound to the 11:55 PM GMT-5
deadline.
◄ Code Unit 4
Jump to...
Lab 8 Unit 4 ►
Resources
UoPeople Library
Orientation Videos
LRC
Syllabus Repository
Career Resource Center
Honors Lists
Links
About Us
https://my.uopeople.edu/mod/page/view.php?id=328386 4/5
12/3/22, 4:19 PM CS 1103-01 - AY2023-T2: Lab 7 Unit 4
Policies
University Catalog
Support
Student Portal
Instructors
Instructor Portal
CTE Center for Teaching Excellence
Office 365
Tipalti
Contact us
English (en)
English (en)
العربية(ar)
https://my.uopeople.edu/mod/page/view.php?id=328386 5/5