Chapter 6: A Guide to the Software CMAC Implementation:
In this chapter, we will provide an introduction to
an object--oriented CMAC class written in C++.
We have successfully integrated pieces of this code into
path planning and control problems; however, it is still very much
research code and suitable care should be taken in its use. For example,
it is by no means optimized for speed!!
Nevertheless, we feel that having this code available will
provide an important aid to understanding these architectures.
In Chapter 1, we describe how to obtain the
tarred and compressed version of the code from our web site.
6.1 Basic Setup:
There are three files that are important for a given CMAC simulation:
-
name.cfg: This file contains
-
nameinit.txt: this is stored in
setup.cmac_file and it is the name of
the file which contains the names
of the files which configure each output CMAC
architecture
- file name of the training data:
this is stored in setup.training_file
- file name of the testing data:
this is stored in setup.testing_file
- file name of the initial weight values if
the CMAC architecture is to be initialized
to values from a previous training run:
this is stored in setup.initfile
- file name of the file that weights of the newly trained
CMAC architecture should be stored into:
this is stored in setup.wtfile
- the number of samples in the training set:
this is stored in setup.training_set_size
- the number of samples in the testing set:
this is stored in setup.testing_set_size
- the number of components in the input:
this is stored in setup.in_dimensions
- the number of components in the output:
this is stored in setup.out_dimensions
- the learning rate to be used in the
CMAC training phase:
this is stored in setup.learning_rate
- the flag that controls whether we
want to echo CMAC parameters to the screen:
this is stored in setup.echo_input
- the flag that controls whether or not we
want to initialize the weights of the CMAC architecture
using data from a file:
this is stored in setup.read_in_wts
- the flag that controls whether or not the CMAC
architecure self determines the portion of the
input space that will be focuses on:
this is stored in setup.autosize
The file also contains two more floating point numbers whose
interpretation depends on how the flag setup.autosize
is set. The two choices are
-
setup.autosize = 1: Here, we choose to self configure
the input space and the next two numbers in this file
are fudge factors that are used to adjust the width of the
window that the CMAC sensors will pay attention to when
input vectors are processed. For example, when the
class function readtr() processes the training data file,
the minimum and maximum data entry for each of the
ith input dimension
is calculated and stored in variables mi and
Mi, respectively. If we let Di = Mi - mi,
we can be certain that all the training data for the ith
component lies in the interval [mi,Mi]. But we really don't
know what is in the testing file and we shouldn't cheat and look!
In practice, the data we look at after the
CMAC architecture is trained will not be available anyway.
However, if we assume we know something about the nature of the
data --- say from physical reasoning that comes from its origin ---
we might be able to say that this range is accurate plus or minus some
fudge factor. The next two numbers will be stored as
b = setup.inflate_bottom
and t = setup.inflate_top and will be used
to either inflate or deflate this range. We let
mi'
and Mi'
denote the new values of the
range found using the formula:
mi'
= mi - b × Di and
Mi'
= Mi + t × Di. So, the
last two numbers are
-
the bottom inflation factor: this is stored in
setup.inflate_bottom.
- the top inflation factor: this is stored in
setup.inflate_top.
- setup.autosize = 0: Here, we choose to set
the input space uniformly using a common start and end value for
the width of the
window to which the CMAC sensors will pay attention when
input vectors are processed.
The next two numbers will be stored as
s = setup.common_start
and e = setup.common_end.
The range of the input focus window for input i is then set as
mi = s and Mi = e.
Here, the last two numbers are
-
the common start: this is stored in
setup.common_start.
- the common end: this is stored in
setup.common_end.
- nameinit.txt: the file which contains the names
of the files which configure each output CMAC
architecture. This file has P lines in it,
where we assume there are P output components. The file has the following appearance:
-
setname0.cfg: the file containing the information
necessary to set up the CMAC architecture for
output component 0
- setname1.cfg: the file containing the information
necessary to set up the CMAC architecture for
output component 1
- :
- setname(P-1).cfg: the file containing the information
necessary to set up the CMAC architecture for
output component (P-1)
- setnamei.cfg: 0 £ i £ P-1. This file contains
the configuration information for the
CMAC architecture for the ith output component.
It is in the form below:
-
the number of levels, stored as iomap[i].levels
- the common hash size, stored as iomap[i].Hash
- the common receptive field width base,
stored as iomap[i].width
There are a few hidden parameters which
should be set by the application program that
uses the CMAC class. These are listed below:
-
DESIRED_TOL: this determines when the CMAC weight updating
step for a given input vector should be done
- STOP_TOL: this is the desired tolerance for the
RMS error of the training set
- RUN_MAX: this is the maximum number of times the training set can
be used to update the CMAC weights
- FREQ: this is parameter such that the results of the CMAC simulation are printed out
every FREQ iterations
These parameters should be set in the application that you are
building as #define constants.
6.2 Some Sample Code:
In the code below, we will briefly discuss the example of
Chapter 8 using various instantiation strategies.
We will use a CMAC architecture to approximate the function
on the interval -1.0 £ x £ 2.0 and
-2.0 £ y £ 1.0 . Hence, there are two inputs
and one analog output.
The training set of 121 samples consists of the triples
(xi,yj,zij), where
| xi |
= |
-1.0 + .3(i-1), 1 £ i £ 11 |
| yj |
= |
-2.0 + .3(j-1), 1 £ j £ 11 |
| zij |
= |
f(xi,yj). |
The test set of 50 samples is constructed in a simpler fashion:
| xi |
= |
-1.0 + .06(i-1), 1 £ i £ 50 |
| yi |
= |
-2.0 + .06(j-1), 1 £ i £ 50 |
| zii |
= |
f(xi,yi). |
The actual graph of this function is presented in Figure 6.1.
Figure 6.1:
6.2.1 Using the Explicit Constructor:
The code below uses the standard or explicit constructor and sets the
needed tolerance and printing parameters at the top of the
file.
#include "cmac.h"
#define DESIRED_TOL (1.0e-6)
#define STOP_TOL (1.0e-5)
#define RUN_MAX (25)
#define FREQ (1)
void main(int argc,char *argv[])
{
int run,trainsize,testsize;
int do_train;
float **intr,**inte,**outtr,**outte,rms,rmstest;
do_train = 1;
// set up CMAC architecture
CMAC cmac(argv[1]);
cmac.read_training_data(&intr,&outtr);
cmac.read_testing_data(&inte,&outte);
cmac.initialize();
cmac.number();
cmac.gettrainsize(&trainsize).gettestsize(&testsize);
//compute rms on test set
rmstest = cmac.compute_rms(testsize,inte,outte);
//compute cmac rms on training
rms = cmac.compute_rms(trainsize,intr,outtr);
//train the architecture
if(do_train){
for(run=0;run<RUN_MAX;++run){
//compute rms on test set
rmstest = cmac.compute_rms(testsize,inte,outte);
//compute cmac rms on training
rms = cmac.compute_rms(trainsize,intr,outtr);
//if rms is too large, keep training
if(rms>STOP_TOL){
cmac.train(1,DESIRED_TOL,trainsize,intr,outtr);
if(run%FREQ==0){
printf("rms[%3d] = %12.6e ",run,rms);
printf("rmstest[%3d] = %12.6e\n",run,rmstest);
}
}/* rms > STOP_TOL loop */
}//run loop
printf("rms[%3d] = %12.6e ",run,rms);
printf("rmstest[%3d] = %12.6e\n",run,rmstest);
//write the weights to a file
cmac.write_wts();
}//
else{
//compute rms on test set
rmstest = cmac.compute_rms(testsize,inte,outte);
//compute cmac rms on training
rms = cmac.compute_rms(trainsize,intr,outtr);
printf("rms[%3d] = %12.6e ",run,rms);
printf("rmstest[%3d] = %12.6e\n",run,rmstest);
}
}
When we run the program, we type the name of the
executible we have built, usecmac, followed by the name of the
CMAC object configuration file func.cfg.
albert% usecmac func.cfg
The file func.cfg is then used as the startup file
that configures our CMAC object. The file func.cfg has contents
funcinit.txt
func_train.txt
func_test.txt
func.fin
func.fin
121!!! setup.training_set_size
50!!! setup.testing_set_size
2!!! setup.in_dimensions
1!!! setup.out_dimensions
.95!!! setup.learning_rate
1!!! setup.echo_input
1!!! setup.do_training
0!!! setup.read_in_wts
1!!! setup.autosize
0.0!!! setup.inflate_bottom
0.0!!! setup.inflate_top
and the meaning of this information has already been discussed.
Note that the echo input flag has been set to 1 indicating
that an echo of the instantiated CMAC object's internal
parameters can be printed according to the defaults of the
class function echo(). If the echo flag is set
to 1, this function is called at the end of the execution of
the function initialize().
When the program executes, notice that first
we print out essentially a copy of the startup file func.cfg.
Note that here the autosize flag is set on, so the last two
numbers are interpreted as inflation fudge factors.
The following lines are output from the activation of the class
constructor via the code
CMAC cmac(argv[1]);
If you look at the constructor code CMAC::CMAC(char *direc_file),
you will see that this code simply calls the class function
config(). As information from the configuration file
func.cfg is read in, we echo this information to the screen.
So, the first thing you see is this echoed information, which is
shown below:
startup_file = func.cfg
funcinit.txt
func_train.txt
func_test.txt
func.fin
func.fin
setup.training_set_size = 121 !!! setup.training_set_size
setup.testing_set_size = 50 !!! setup.testing_set_size
setup.in_dimensions = 2 !!! setup.in_dimensions
setup.out_dimensions = 1 !!! setup.out_dimensions
setup.learning_rate = 0.950000 !!! setup.learning_rate
setup.echo_input = 1 !!! setup.echo_input
setup.do_training = 1 !!! setup.do_training
setup.read_in_wts = 0 !!! setup.read_in_wts
setup.autosize = 1 !!! setup.autosize
setup.inflate_bottom = 0 !!! setup.inflate_bottom
setup.inflate_top = 0 !!! setup.inflate_top
At the end of the config() function, there is a call to the class
function cmac_files() which is used to obtain additional
configuration information as has been detailed in the
in the code listing for cmac_files(). The following function call
will print out the names of the configuration files needed:
setfunc.cfg
Next, we obtain the training and testing
data via the code
cmac.read_training_data(&intr,&outtr);
cmac.read_testing_data(&inte,&outte);
and see the following diagnostic information about
the input space for the
training and then the testing data printed to the screen.
In the call to the class function read_training_data(),
the inflationary fudge factors are used to set
input focus intervals as has been discussed previously.
Input[0]::min/max of input widths= -1.000000/ 2.000000
Input[1]::min/max of input widths= -2.000000/ 1.000000
Input[0]::min/max of input widths= -0.970000/ 1.970000
Input[1]::min/max of input widths= -1.970000/ 0.970000
The next two lines of code initialize the CMAC object with
specific parameter values and compute the number of sensors for each
input dimension.
cmac.initialize();
cmac.number();
The echo function is activated in the call to initialize()
because the echo_input flag has been set. Hence, we see
the echo
setup.startup_file = func.cfg
setup.cmac_file = funcinit.txt
setup.training_file = func_train.txt
setup.testing_file = func_test.txt
setup.initfile = func.fin
setup.wtfile = func.fin
setup.training_set_size = 121
setup.testing_set_size = 50
setup.learning_rate = 0.950000
setup.in_dimensions = 2
setup.out_dimensions = 1
input interval[ 0] = [ -1.000000, 2.000000]
input interval[ 1] = [ -2.000000, 1.000000]
setup.file[ 0] = setfunc.cfg
*************************
CMAC[ 0] parameters
*************************
number levels = 3
hash = 26
offset = 0.333333
width = 0.800000
LEVEL INPUT OFFSET FIELD WIDTH HASH_SIZE
0 0 0.000000 0.800000 26
0 1 0.000000 0.800000 26
1 0 0.266667 0.800000 26
1 1 0.266667 0.800000 26
2 0 0.533333 0.800000 26
2 1 0.533333 0.800000 26
sensors[output = 0][level = 0][input = 0] = 4
sensors[output = 0][level = 0][input = 1] = 4
sensors[output = 0][level = 1][input = 0] = 5
sensors[output = 0][level = 1][input = 1] = 5
sensors[output = 0][level = 2][input = 0] = 5
sensors[output = 0][level = 2][input = 1] = 5
flattened sensors[output = 0, levels = 3][0 input] = 12
flattened sensors[output = 0, levels = 3][1 input] = 12
flattened sensors[output = 0, levels = 3, input = 2] = 144
Finally, we see the results of the CMAC training itself.
rms[ 0] = 9.262908e-01 rmstest[ 0] = 8.694608e-01
rms[ 1] = 6.201677e-01 rmstest[ 1] = 5.492803e-01
rms[ 2] = 3.214494e-01 rmstest[ 2] = 4.723777e-01
rms[ 3] = 2.217582e-01 rmstest[ 3] = 4.646737e-01
rms[ 4] = 1.776123e-01 rmstest[ 4] = 2.943840e-01
rms[ 5] = 1.083556e-01 rmstest[ 5] = 3.293764e-01
rms[ 6] = 9.539750e-02 rmstest[ 6] = 3.438328e-01
rms[ 7] = 9.571404e-02 rmstest[ 7] = 3.172331e-01
rms[ 8] = 9.322415e-02 rmstest[ 8] = 3.204854e-01
rms[ 9] = 9.090739e-02 rmstest[ 9] = 3.261038e-01
rms[ 10] = 8.978907e-02 rmstest[ 10] = 3.299905e-01
rms[ 11] = 8.925170e-02 rmstest[ 11] = 3.320227e-01
rms[ 12] = 8.899990e-02 rmstest[ 12] = 3.328442e-01
rms[ 13] = 8.889163e-02 rmstest[ 13] = 3.331605e-01
rms[ 14] = 8.884162e-02 rmstest[ 14] = 3.333702e-01
rms[ 15] = 8.880717e-02 rmstest[ 15] = 3.335922e-01
rms[ 16] = 8.877469e-02 rmstest[ 16] = 3.338159e-01
rms[ 17] = 8.874358e-02 rmstest[ 17] = 3.340075e-01
rms[ 18] = 8.871617e-02 rmstest[ 18] = 3.341490e-01
rms[ 19] = 8.869386e-02 rmstest[ 19] = 3.342402e-01
rms[ 20] = 8.867678e-02 rmstest[ 20] = 3.342903e-01
rms[ 21] = 8.866435e-02 rmstest[ 21] = 3.343098e-01
rms[ 22] = 8.865575e-02 rmstest[ 22] = 3.343083e-01
rms[ 23] = 8.865017e-02 rmstest[ 23] = 3.342927e-01
rms[ 24] = 8.864691e-02 rmstest[ 24] = 3.342682e-01
rms[ 25] = 8.864691e-02 rmstest[ 25] = 3.342682e-01
albert%
6.2.2 Using The Default Constructor:
We can also use the following variations of object instantiation
(this will require replacing most of the occurrences of
cmac with cmac[0]):
CMAC T(argv[1]);
CMAC *cmac;
cmac = new CMAC[1];
cmac[0] = T;
or
CMAC T(argv[1]);
CMAC cmac;
cmac = T;
and there will be no difference in the output of the application.
=10000
=10000