Previous Next Contents

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:

  1. name.cfg: This file contains
    1. 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
    2. file name of the training data: this is stored in setup.training_file
    3. file name of the testing data: this is stored in setup.testing_file
    4. 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
    5. file name of the file that weights of the newly trained CMAC architecture should be stored into: this is stored in setup.wtfile
    6. the number of samples in the training set: this is stored in setup.training_set_size
    7. the number of samples in the testing set: this is stored in setup.testing_set_size
    8. the number of components in the input: this is stored in setup.in_dimensions
    9. the number of components in the output: this is stored in setup.out_dimensions
    10. the learning rate to be used in the CMAC training phase: this is stored in setup.learning_rate
    11. the flag that controls whether we want to echo CMAC parameters to the screen: this is stored in setup.echo_input
    12. 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
    13. 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
    1. 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
      1. the bottom inflation factor: this is stored in setup.inflate_bottom.
      2. the top inflation factor: this is stored in setup.inflate_top.
    2. 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
      1. the common start: this is stored in setup.common_start.
      2. the common end: this is stored in setup.common_end.
  2. 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:
    1. setname0.cfg: the file containing the information necessary to set up the CMAC architecture for output component 0
    2. setname1.cfg: the file containing the information necessary to set up the CMAC architecture for output component 1
    3. :
    4. setname(P-1).cfg: the file containing the information necessary to set up the CMAC architecture for output component (P-1)
  3. 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:
    1. the number of levels, stored as iomap[i].levels
    2. the common hash size, stored as iomap[i].Hash
    3. 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:

  1. DESIRED_TOL: this determines when the CMAC weight updating step for a given input vector should be done
  2. STOP_TOL: this is the desired tolerance for the RMS error of the training set
  3. RUN_MAX: this is the maximum number of times the training set can be used to update the CMAC weights
  4. 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

f(x,y) =
e
.1 (x2 + y2)
 
sin(2x + y)

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


Previous Next Contents