Chapter 7: The Structure of the CMAC Class:
7.1 The Makefile: MakeLibrary:
# -------------------------- #
# paths for this simulation #
# -------------------------- #
INCLUDE_PATH_UTILITY = /home/peterson/programs/utility
INCLUDE_PATH_CMAC = /home/peterson/programs/cmacclass
INCLUDE_PATH_CONSTANTS = /home/peterson/Xheaders
INCLUDES = -I$(INCLUDE_PATH_UTILITY) -I$(INCLUDE_PATH_CMAC) \
-I$(INCLUDE_PATH_CONSTANTS)
LIBRARY_PATH_UTILITY = /home/peterson/programs/utility
LIBRARY_PATH_CMAC = /home/peterson/programs/cmacclass
SIMLIBS = -lutility
# ----------- #
# Definitions #
# ----------- #
CC = CC
LIBS = -ll -lm -lgen
COMPILE_FLAGS_LINK = -g $(INCLUDES)
COMPILE_FLAGS_SOURCES = -c -g $(INCLUDES)
LIBRARY_FLAGS_SOURCES = -L$(LIBRARY_PATH_UTILITY)
RANLIB = touch
# ------------- #
# Define target #
# ------------- #
TARGET = libcmac
LIB_FILES_OBJ = echo.o \
gettrainsize.o \
gettestsize.o \
getcmac.o \
cmacinit.o \
getrms.o \
virtmem.o \
func.o \
cmacfile.o \
config.o \
constructor.o \
default_constructor.o \
copy_constructor.o \
equal.o \
cmacfree.o \
readtr.o \
readte.o \
writew.o \
readw.o \
train.o \
number.o \
cmaceval.o \
getlevels.o \
getinputsize.o \
getoutputsize.o \
getworkingaddress.o \
getnumberintervals.o \
getminx.o \
getmaxx.o \
getwidth.o \
loadinputsize.o \
loadoutputsize.o \
loadtrainsize.o \
loadtestsize.o
SOURCES = $(LIB_FILES_OBJ:.o=.c)
$(TARGET).a: $(LIB_FILES_OBJ)
rm -f $(TARGET).a
ar cr $(TARGET).a $(LIB_FILES_OBJ)
$(RANLIB) $(TARGET).a
# --------------------------------------------- #
# Dependencies of source files on header files #
# --------------------------------------------- #
$(LIB_FILES_OBJ):
$(CC) $(COMPILE_FLAGS_SOURCES) $(LIBRARY_FLAGS_SOURCES) \
$(SIMLIBS) $(LIBS) $*.c
echo.c: cmac.h
@touch $@
gettrainsize.c: cmac.h
@touch $@
gettestsize.c: cmac.h
@touch $@
cmacinit.c: cmac.h
@touch $@
getrms.c: cmac.h
@touch $@
virtmem.c: cmac.h
@touch $@
func.c: cmac.h
@touch $@
cmacfile.c: cmac.h
@touch $@
getcmac.c: cmac.h
@touch $@
config.c: cmac.h
@touch $@
cmacfree.c: cmac.h
@touch $@
readtr.c: cmac.h
@touch $@
readte.c: cmac.h
@touch $@
writew.c: cmac.h
@touch $@
readw.c: cmac.h
@touch $@
train.c: cmac.h
@touch $@
number.c: cmac.h
@touch $@
cmaceval.c: cmac.h
@touch $@
constructor.c: cmac.h
@touch $@
default_constructor.c: cmac.h
@touch $@
copy_constructor.c: cmac.h
@touch $@
equal.c: cmac.h
@touch $@
getlevels.c: cmac.h
@touch $@
getinputsize.c: cmac.h
@touch $@
getoutputsize.c: cmac.h
@touch $@
getworkingaddress.c: cmac.h
@touch $@
getnumberintervals.c: cmac.h
@touch $@
getminx.c: cmac.h
@touch $@
getmaxx.c: cmac.h
@touch $@
getwidth.c: cmac.h
@touch $@
loadinputsize.c: cmac.h
@touch $@
loadoutputsize.c: cmac.h
@touch $@
loadtrainsize.c: cmac.h
@touch $@
loadtestsize.c: cmac.h
@touch $@
7.2 The CMAC Class:
7.2.1 The CMAC Class header:
The header file for the C++ class is given in this section.
A few auxiliary functions, primarily for allocating and deallocating
two--dimensional arrays of various data types, are defined outside of the class. Their prototype information is in the file utility.h
and indeed that source is used in an archived library form with
title utility.a.
The CMAC class is designed to use a hashing function to calculate
the working memory addresses from the virtual addresses. The code allows
for a separate hash function to be used on each level for each output
architecture. However, in practice, a single hash function is used to
initialize all the individual hash functions in the CMAC architecture.
The prototype for this function is given using a standard
typedef mechanism below. Note that, at the present time, the hash function
utilizes four arguments and returns type int.
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "utility.h"
#include "myconstants.h"
typedef float *pfloat;
typedef int (* fhash)(int size,int *subinterval,
int *num_interval,int hash_size);
typedef char file_word[MY_MESSAGE_SIZE];
The parameters required to complete the configuration of the CMAC
architecture are stored in the CMAC configuration file.
typedef struct{
char startup_file[MY_MESSAGE_SIZE];
char cmac_file[MY_MESSAGE_SIZE];
char training_file[MY_MESSAGE_SIZE];
char testing_file[MY_MESSAGE_SIZE];
char initfile[MY_MESSAGE_SIZE];
char wtfile[MY_MESSAGE_SIZE];
int training_set_size;
int testing_set_size;
int in_dimensions;
int out_dimensions;
float learning_rate;
float *min_x;
float *max_x;
file_word *file;
int autosize;
int echo_input;
int do_training;
int read_in_wts;
float inflate_bottom;
float inflate_top;
float common_start;
float common_end;
} CMACINPUT;
The meaning of the given structure elements is explained below
(see also the explanations in Chapter 6).
The value of MY_MESSAGE_SIZE is set in cmac.h to be 256.
-
char startup_file[MY_MESSAGE_SIZE]:
-
This is the startup file for the CMAC object instantiation, typically
named name.cfg.
- char cmac_file[MY_MESSAGE_SIZE]:
-
This is the file typically called nameinit.txt.
It is the name of
the file which contains the names
of the files which configure each output CMAC
architecture (see also the documentation for the
class function cmac_files()).
- char training_file[MY_MESSAGE_SIZE]:
-
This is the file that stores the training data.
- char testing_file[MY_MESSAGE_SIZE]:
-
This is the file that stores the testing data.
- char initfile[MY_MESSAGE_SIZE]:
-
This is the file that stores the parameters
to initialize a CMAC object for use.
- char wtfile[MY_MESSAGE_SIZE]:
-
This is the file in which the CMAC objects parameters
are stored, typically after they have been altered by
the CMAC training process.
- int training_set_size:
-
This is the number of data items in the training set.
- int testing_set_size:
-
This is the number of data items in the testing set.
- int in_dimensions:
-
This is the dimension of the input space.
- int out_dimensions:
-
This is the dimension of the output space.
- float learning_rate:
-
This is the learning rate used in the CMAC
training algorithm.
- float *min_x:
-
The CMAC architecture focuses on a portion of the ith input
which will be denoted by [ai,bi]. The exact values of
ai and bi are determined both by the training data and
whether we autosize the data or use a common start and end value.
This procedure has been detailed in Chapter 6.
Once these values are set, we store ai in min_x[i].
- float *max_x:
-
The CMAC architecture focuses on a portion of the ith input
which will be denoted by [ai,bi]. The exact values of
ai and bi are determined both by the training data and
whether we autosize the data or use a common start and end value.
This procedure has been detailed in Chapter 6.
Once these values are set, we store bi in max_x[i].
- file_word *file:
-
This vector stores the names of the files that determine the
configuration of the IOMAP structures for each output dimension.
See the discussion for the class function cmac_files() for
more information.
- int autosize:
-
This variable determines how we determine the focus
interval [ai,bi] for the ith input dimension.
Setting autosize to 1 allows for a stretching or shrinking
of the actual values obtained via the training data. Setting
autosize to 0 will force a common focus interval [a,b] to
be used.
- int echo_input:
-
Setting this variable to 1 allows for an automatic echoing of
CMAC object parameters to the screen.
- int do_training:
-
Setting this flag variable to 1 allows us to train the CMAC
architecture on the given training data.
- int read_in_wts:
-
Setting this flag variable to 1 allows us to read in
CMAC object parameters from the external file setup.initfile
in order to initialize the CMAC object.
- float inflate_bottom:
-
This is a fudge factor to expand or shrink the ai value of the
ith inputs focus interval.
- float inflate_top:
-
This is a fudge factor to expand or shrink the bi value of the
ith inputs focus interval.
- float common_start:
-
If we decide to use a constant focus interval [a,b] for the
input intervals, we set a to common_start.
- float common_end:
-
If we decide to use a constant focus interval [a,b] for the
input intervals, we set b to common_end.
The basic computational unit of the CMAC architecture is the
IOMAP structure. You need one IOMAP structure for each
output dimension in the CMAC architecture. Hence, the IOMAP
structure contains the information about number of levels,
sensor characteristics such as offset and receptive field width, etc.
typedef struct{
int levels;
int Hash;
float offset_size;
float width;
float **offset;
float **receptive_field_width;
int *hash_size;
fhash *hash;
int **num_interval;
int *subinterval;
int *working_address;
float **working_memory;
float output;
} IOMAP;
The individual field elements have the following meaning:
-
int levels:
-
This is the number of levels L of offsetted sensors we
use in architecture.
- int Hash:
-
This is the hash size H we use in the construction of the
working memory.
- float offset_size:
-
Each level of offsetted sensors is offset from the
previous level by the constant value O = W/L.
- float width:
-
This is the width W of sensor fields for this architecture.
- float **offset:
-
We need a potentially different offset for each level i
and each input j. Hence, we need a two--dimensional array
of size L × N, where N is the dimension of the input space.
Thus, iomap.offset[i][j] is the offset value for the
i level and j input.
- float **receptive_field_width:
-
We need a potentially different field width for each level i
and each input j. Hence, we need a two--dimensional array
of size L × N.
Thus, iomap.receptive_field_width[i][j] is the field width value for the
i level and j input.
- int *hash_size:
-
We may have a different hash size for each level. Hence,
iomap.hash_size is size L and iomap.hash_size[i]
is the hash size for the ith level.
- fhash *hash:
-
We can have a different hash function for each level. Thus
iomap.hash[i] is the hash function for the ith level.
- int **num_interval:
-
We compute the number of possible sensors for each level i
and input j. Thus, iomap.num_interval is size
L × N and iomap.num_interval[i][j] gives the
number of sensors for level i and input j.
- int *subinterval:
-
This auxiliary vector stores the active sensor list for
a given data vector x as the array
[a0,...,aN-1], where aj is the index of the active sensor
for input j.
- int *working_address:
-
Once the working address for data x for level i is computed via hashing,
we store this address in the vector iomap.working_address.
Hence, iomap.working_address has size L and iomap.working_address[i]
is the hashed address of data x for level i.
- float **working_memory:
-
This array stores the values that are used to compute
the output of the iomap architecture for a given data x.
Hence, iomap.working_memory has L rows, where
each row stands for a level. Row i corresponds then to
level i and has size iomap.hash_size[i]. Thus,
iomap.working_memory[i][j] gives the value of the
CMAC weight for level i and memory position j.
- float output:
-
This is the scalar ouput of the CMAC IOMAP architecture.
The CMAC class functions appear below. Note that
the CMAC is instantiated using as private elements
a CMACINPUT structure and a pointer to the IOMAP structure.
class CMAC{
public:
CMAC(char *direc_file);
CMAC(void);
CMAC(const CMAC&);
CMAC& CMAC::operator=(const CMAC&);
~CMAC(void);
CMAC& config(char* direc_file);
CMAC& cmac_files(void);
CMAC& getcmac(void);
CMAC& initialize(void);
CMAC& virtual_memory(int k,float *x);
CMAC& read_training_data(float ***in,float ***out);
CMAC& read_testing_data(float ***in,float ***out);
CMAC& write_wts(void);
CMAC& read_wts(void);
CMAC& echo(void);
CMAC& getinputsize(int *input_size);
CMAC& getoutputsize(int *output_size);
CMAC& gettrainsize(int *trainsize);
CMAC& gettestsize(int *testsize);
CMAC& getlevels(int *levels,int which_output);
CMAC& getnumberintervals(int which_output,int which_level,
int which_input,int *number);
CMAC& getminx(int which_input,float *min);
CMAC& getmaxx(int which_input,float *max);
CMAC& getwidth(int which_output,int which_level,
int which_input,float *width);
CMAC& getworkingaddress(int which_output,int *address);
CMAC& loadinputsize(int input_size);
CMAC& loadoutputsize(int output_size);
CMAC& loadtrainsize(int trainsize);
CMAC& loadtestsize(int testsize);
CMAC& number(void);
float compute_rms(int sample_size,float **in,float **out);
float *cmaceval(int do_address,float *x);
CMAC& train(int do_address,float DESIRED_TOL,int sample_size,
float **in,float **out);
CMAC& matrix(float **intr,float **outtr);
private:
CMACINPUT setup;
IOMAP *iomap;
};
The last item listed is the prototype for the
common hash function used in the instantiation of the
architecture. It would be nice to include this function as
a class function, but we haven't yet been able to figure out how to do that.
int hhash(int size,int *subinterval,int *num_interval,
int hash_size);
7.3 Construction of the CMAC Object:
7.3.1 Explicit Constructor: constructor.c:
#include "cmac.h"
This function is the explicit constructor for the CMAC class.
To use this function, you would type a source line like
CMAC T(application.cfg}
and all of the information stored in the file application.cfg
would be used in the instantiation of the CMAC object T.
The function simply calls the configuration function
CMAC& CMAC::config().
CMAC::CMAC(char *direc_file)
{
config(direc_file);
}
7.3.2 Default Constructor: default_constructor.c:
#include "cmac.h"
This function is the default constructor for the CMAC class.
To use this function, you may type a source line like
CMAC T[4];
and at compile time the default constructor is called.
In the case above, an example of the code use is as follows:
CMAC default(application.cfg);
CMAC T[4];
for(i=0;i<4;++i){
T[i] = default;
}
Let's explain this code line by line:
-
The default constructor is used to initialize the CMAC
object default using the information in the file
application.cfg. The file name
application.cfg is stored as part of the
CMAC object creation process in default.setup.startup_file.
- The overloaded class equal is then used to
build the CMAC object T[i] by first
copying the startup file default.setup.startup_file
into T[i].setup.startup_file. Then the
startup file, which is simply
application.cfg, is used in the
class function config() to construct the new CMAC object T[i]
that will share the values of default.
The overloaded = is
automatically invoked by the syntax of this line.
Another possibility is to use code of the form
CMAC *T;
An example of such code use is as follows:
CMAC default(application.cfg);
CMAC *T;
T = new CMAC[4];
for(i=0;i<4;++i){
T[i] = default;
}
Let's explain this code line by line as well:
-
The default constructor is used to initialize the CMAC
object default using the information in the file
application.cfg. The file name
application.cfg is stored as part of the
CMAC object creation process in default.setup.startup_file.
- The global new operator is then used to allocate
storage for four CMAC objects.
- The overloaded class = is then used to
build the CMAC object T[i] by first
copying the startup file default.setup.startup_file
into T[i].setup.startup_file. Then the
startup file, which is simply
application.cfg, is used in the
class function config() to construct the new CMAC object T[i]
that will share the values of default.
The overloaded = is
automatically invoked by the syntax of this line.
There are some subtle points here. When
we are training the CMAC architecture,
critical parameters are periodically written into a
file whose name is stored in setup.wtfile. Also, if the
CMAC architecture parameters are to be initialized using values stored
in a file, the name of such an initialization file is stored in
setup.initfile. The names of these files are simply copied
when the class = is invoked. Hence,
since it is not likely that different CMAC architectures
should share commom weight files and/or initialization files,
we are forced to provide a class function to set the names of these
files as desired.
CMAC::CMAC(void)
{
iomap = NULL;
}
7.3.3 Copy Constructor: copy_constructor.c:
#include "cmac.h"
#include <string.h>
This function is the class copy constructor. The design decision
here is that, if we use the source code
CMAC T(application.cfg);
CMAC S = T;
then the following sequence of events takes place:
-
The standard constructor is used to build the CMAC
object T using the information in the file
application.cfg. The file name
application.cfg is stored as part of the
CMAC object creation process in the T.setup.startup_file.
- The copy constructor is used to
build the CMAC object S by first
copying the startup file T.setup.startup_file
into S.setup.startup_file. Then the
startup file, which is simply
application.cfg, is used in the
class function config() to construct the new CMAC object S
that will share the values of T.
The copy constructor is
automatically invoked by the syntax of this line.
Again, there are some subtle points here. When
we are training the CMAC architecture,
critical parameters are periodically written into a
file whose name is stored in setup.wtfile. Also, if the
CMAC architecture parameters are to be initialized using values stored
in a file, the name of such an initialization file is stored in
setup.initfile. The names of these files are simply copied
when the copy constructor is invoked. Hence,
since it is not likely that different CMAC architectures
should share commom weight files and/or initialization files,
we are forced to provide a class function to set the names of these
files as desired.
CMAC::CMAC(const CMAC& b)
{
strcpy(setup.startup_file,b.setup.startup_file);
config(setup.startup_file);
}
7.3.4 Configuration Function: config.c:
#include "cmac.h"
#include <string.h>
This function is the core of the constructors for the CMAC class.
When this function is called, the appropriate allocations and
initializations are performed.
CMAC& CMAC::config(char *direc_file)
{
FILE *fd2;
char message[MESSAGE_SIZE];
//store the name of the startup file in the IOMAP structure
strcpy(setup.startup_file,direc_file);
printf("startup_file = %s\n",setup.startup_file);
if((fd2=fopen(direc_file,"r")) == NULL){
printf("\nCan't open file %s\n",direc_file);
exit(0);
}
// read in name of file containing configuration
// information for all the IOMAP structures that will
// comprise the full CMAC
get_my_text(setup.cmac_file,fd2);
// read in name of training file
get_my_text(setup.training_file,fd2);
// read in name of testing file
get_my_text(setup.testing_file,fd2);
// read in name of the file that initializes the
// CMAC tunable parameters
get_my_text(setup.initfile,fd2);
// read in name of file to which the CMAC tunable
// paramters will be written to during the training process
get_my_text(setup.wtfile,fd2);
// training set size
fscanf(fd2,"%d",&(setup.training_set_size));
printf("setup.training_set_size = %d ",setup.training_set_size);
get_my_text(message,fd2);
// testing set size
fscanf(fd2,"%d",&(setup.testing_set_size));
printf("setup.testing_set_size = %d ",setup.testing_set_size);
get_my_text(message,fd2);
// number of input dimensions
fscanf(fd2,"%d",&(setup.in_dimensions));
printf("setup.in_dimensions = %d ",setup.in_dimensions);
get_my_text(message,fd2);
// number of output dimensions
fscanf(fd2,"%d",&(setup.out_dimensions));
printf("setup.out_dimensions = %d ",setup.out_dimensions);
get_my_text(message,fd2);
// learning rate
fscanf(fd2,"%f",&(setup.learning_rate));
printf("setup.learning_rate = %f ",setup.learning_rate);
get_my_text(message,fd2);
// echo input
fscanf(fd2,"%d",&(setup.echo_input));
printf("setup.echo_input = %d ",setup.echo_input);
get_my_text(message,fd2);
// do_training
fscanf(fd2,"%d",&(setup.do_training));
printf("setup.do_training = %d ",setup.do_training);
get_my_text(message,fd2);
// read_in_weights
fscanf(fd2,"%d",&(setup.read_in_wts));
printf("setup.read_in_wts = %d ",setup.read_in_wts);
get_my_text(message,fd2);
// autosize
fscanf(fd2,"%d",&(setup.autosize));
printf("setup.autosize = %d ",setup.autosize);
get_my_text(message,fd2);
if(setup.autosize==0){
fscanf(fd2,"%f",&setup.common_start);
printf("setup.common_start = %d ",setup.common_start);
get_my_text(message,fd2);
fscanf(fd2,"%f",&setup.common_end);
printf("setup.common_end = %d ",setup.common_end);
get_my_text(message,fd2);
}
else{
// inflate_bottom
fscanf(fd2,"%f",&(setup.inflate_bottom));
printf("setup.inflate_bottom = %f ",setup.inflate_bottom);
get_my_text(message,fd2);
// inflate_top
fscanf(fd2,"%f",&(setup.inflate_top));
printf("setup.inflate_top = %f ",setup.inflate_top);
get_my_text(message,fd2);
}
// allocate space for vector containing
// minimum input dimensions
setup.min_x = new float[setup.in_dimensions];
// allocate space for vector containing
// maximum input dimensions
setup.max_x = new float[setup.in_dimensions];
// allocate memory for setup.out_dimensions files
// to contain each output components CMAC configuration information
setup.file = new file_word[setup.out_dimensions];
// allocate space for setup.out_dimensions IOMAP structures
iomap = new IOMAP[setup.out_dimensions];
cmac_files();
fclose(fd2);
return *this;
}
7.3.5 Get Configuration Files: cmacfile.c:
#include "cmac.h"
For each output dimension n in the CMAC architecture, there is
a separate IOMAP structure. The individual field elements of this
structure are set using information stored in a configuration file
whose name is stored in the private CMAC class element
setup.file[n] of structure CMACINPUT.
The main configuration file for the CMAC architecture is usually called
name.cfg, where name is chosen to be pertinent to the application
at hand. The first item inside this file is the name of the
file which itself contains the names of the files used to
initialize the IOMAP structures. This file is typically called
nameinit.txt.
The name nameinit.txt has already been stored in the
private CMAC class element setup.cmac_file.
Inside this file setup.cmac_file is a list of files
with names of the form setname0.cfg,
setname1.cfg and so forth. The total number of files
is the same as the output dimension of the CMAC architecture.
Hence, the file containing the information to initialize
the nth IOMAP structure is called setnamen.cfg.
The cmac_file class function's purpose is to open
the main configuration file setup.cmac_file
and extract the individual IOMAP initialization file names.
These file names are then stored in the field elements
setup.file[].
CMAC& CMAC::cmac_files(void)
{
int i;
FILE *fd2;
if((fd2=fopen(setup.cmac_file,"r")) == NULL){
printf("\nCan't open file %s\n",setup.cmac_file);
exit(0);
}
// get configuration file for IOMAP for ith output dimension
for(i=0;i<setup.out_dimensions;++i)
get_my_text(setup.file[i],fd2);
fclose(fd2);
return *this;
}
7.3.6 Internal Allocation: getcmac.c:
#include "cmac.h"
This function handles the primary allocation of memory for the
CMAC object. The individual allocation tasks are carefully documented
in the code below.
CMAC& CMAC::getcmac(void)
{
FILE *fd2;
char message[MESSAGE_SIZE];
int i,j;
// The IOMAP for the ith output dimension is configured using
// the file name stored in setup.file[i]. The entries in
// setup.file[] are read in using the class function cmac_file()
for(j=0;j<setup.out_dimensions;++j){
if((fd2=fopen(setup.file[j],"r")) == NULL){
printf("\nCan't open file %s\n",setup.file[j]);
exit(0);
}
// Number of Levels
fscanf(fd2,"%d",&(iomap[j].levels));
get_my_text(message,fd2);
// Hash size
fscanf(fd2,"%d",&(iomap[j].Hash));
get_my_text(message,fd2);
// offset
iomap[j].offset_size = 1.0/(float)iomap[j].levels;
// width
fscanf(fd2,"%f",&(iomap[j].width));
get_my_text(message,fd2);
//done with the file, so close it
fclose(fd2);
// allocate space for iomap[j].offset, an array
// containing offsets for each level in iomap[j].
// This is a 2D matrix having iomap[j].levels rows and
// setup.in_dimensions columns.
get_2dmatrix_float(&(iomap[j].offset),iomap[j].levels,
setup.in_dimensions);
// allocate space for iomap[j].receptive_field_width, an array
// containing receptive field widths for each level in iomap[j].
// This is a 2D matrix having iomap[j].levels rows and
// setup.in_dimensions columns.
get_2dmatrix_float(&(iomap[j].receptive_field_width),iomap[j].levels,
setup.in_dimensions);
// allocate space for vector containing hash_size for
// each level of iomap[j]
iomap[j].hash_size = new int[iomap[j].levels];
for(i=0;i<iomap[j].levels;++i)
iomap[j].hash_size[i] = iomap[j].Hash;
// allocate space for vector containing hash functions
// for each level of iomap[j]
iomap[j].hash = new fhash[iomap[j].levels];
// allocate space for iomap[j].working_memory, a 2D array
// storing working memory for each level in iomap[j].
// This is a 2D matrix having iomap[j].levels rows and
// each row is length iomap[j].hash_size[i]
iomap[j].working_memory = new pfloat[iomap[j].levels];
for(i=0;i<iomap[j].levels;++i)
iomap[j].working_memory[i] = new float[iomap[j].hash_size[i]];
// allocate space for a a vector to temporarily store
// the computed address for iomap[j]
iomap[j].working_address = new int[iomap[j].levels];
// allocate space for iomap[j].num_interval, a 2D array
// storing the number of intervals for each level and
// each input component in iomap[j].
// This is a 2D matrix having iomap[j].levels rows and
// setup.in_dimensions columns
get_2dmatrix_int(&(iomap[j].num_interval),iomap[j].levels,
setup.in_dimensions);
// allocate space for iomap[j].subinterval, a vector
// storing the number of intervals per input component
iomap[j].subinterval = new int[setup.in_dimensions];
}//output dimension loop
return *this;
}
7.3.7 Initialization: cmacinit.c:
#include "cmac.h"
This function sets the initial values of many of the important
CMAC object parameters. For each output dimension i, there is
a corresponding IOMAP structure, iomap[i]. The architecture
specified by iomap[i] consists of Li = iomap[i].levels.
In principle, there can be a distinct hash function for each level; in
this code, we are more pragmatic and a common hash function is used
to initialize all the level hash functions. The common hash function
is denoted by hhash().
Each IOMAP structure iomap[i] is essentially a CMAC architecture
to model a scalar valued input/output mapping from an input space
of dimension N = setup.in_dimensions.
Each of these architectures consists of Li levels,
and on each of these levels the portion of the input space
on which the CMAC architecture focuses is divided up into
hypercubes that function as binary sensors. Each of the
hypercubes on level j is constructed from sides of length
wjk = iomap[i].receptive_field_width[j][k].
Each of the receptive field widths can be set in several ways.
Currently the code sets this width using a common value
stored in the IOMAP structure, W = iomap[i].width. This is
certainly not the best solution for all applications. This
approach subdivides the input space into hypercubes of
fixed side length. So, the user must think about the side
length before the application is run. Another way is
to set the widths to be a fraction of each input variables
focus. If the focus of input variable k is the interval
(a,b) = (setup.max_x[k]-setup.min_x[k]), an alternate
approach is to set the width using the common value iomap[i].width
and then scale the result using
wjk *= (b-a). This second approach can be activated
by setting SCALED to 1 in the code and recompiling.
The hypercubes for each level j and input k are staggered using an
offset strategy.
We begin by setting all the offsets to a common value: Ojk = iomap[i].offset[j][k] = O = iomap[i].offset_size.
Currently, the numerical value of the common offset parameter
is set to be the reciprocal of the the number of levels L.
We then stagger the offsets for the kth input as follows:
| O0k |
= |
0.0 |
| O1k |
= |
|
| O2k |
= |
|
| O3k |
= |
|
| |
· · · |
|
| OL-1,k |
= |
|
Hence, an additional offset term would give an offset value of
W and we begin repeating the same offset strategy.
#define SCALED (0)
CMAC& CMAC::initialize(void)
{
int i,j,k;
getcmac();
for(i=0;i<setup.out_dimensions;++i){
for(j=0;j<iomap[i].levels;++j){
//Initialize the hash function
//for each level of iomap[i]
iomap[i].hash[j] = hhash;
//Initialize receptive field widths
for(k=0;k<setup.in_dimensions;++k){
// field width for level j, input k
iomap[i].receptive_field_width[j][k] = iomap[i].width;
if(SCALED)
iomap[i].receptive_field_width[j][k] *=
(setup.max_x[k]-setup.min_x[k]);
// offset for level j and input k
iomap[i].offset[j][k] = iomap[i].offset_size;
iomap[i].offset[j][k] *= (float)(j)*
(iomap[i].receptive_field_width[j][k]);
}//input loop
}// level loop
}//output loop
If the option to read in existing weights from
an already trained CMAC architecture is set, then
the read_wts() function is called; otherwise,
all of the CMAC weights are initialized to 0.0.
// load weights
if(setup.read_in_wts==0){
for(i=0;i<setup.out_dimensions;++i){
for(j=0;j<iomap[i].levels;++j){
for(k=0;k<iomap[i].hash_size[j];++k){
iomap[i].working_memory[j][k] = 0.0;
}
}
}
}
else{
read_wts();
}
if(setup.echo_input)
echo();
return *this;
}
7.4 Destruction of the CMAC Object:
7.4.1 Destructor: cmacfree.c:
#include "cmac.h"
This function is the default destructor for the CMAC object.
Note that the private elements of a CMAC object contain only one pointer,
IOMAP *iomap. This pointer is explicitly freed in the destructor
CMAC::~CMAC(void)
{
delete iomap;
}
7.5 Evaluation Functions for the CMAC Object:
7.5.1 Evaluation: cmaceval.c:
#include "cmac.h"
This function evaluates the output of the CMAC architecture for a given
input x. Note that if the working_memory structure holds
current addressing information, we can pass in the value of
0 for the argument do_address and the address calculation function
virtmem() will not be called. If address information is required,
setting the argument do_address to 1 enables the address
calculation call to virtmem(). In addition, there is the ability
to enable diagnostic prints for debugging purposes by setting the
#defined variable diagnostic.
#define diagnostic (0)
float * CMAC::cmaceval(int do_address,float *x)
{
int i,p,base;
static float *value;
value = new float[setup.out_dimensions];
if(diagnostic==1){
for(i=0;i<setup.in_dimensions;++i)
printf("x[%d] = %12.6f\n",i,x[i]);
}
for(i=0;i<setup.out_dimensions;++i){
if(do_address==1)
virtual_memory(i,x);
iomap[i].output = 0.0;
for(p=0;p<iomap[i].levels;++p){
base = iomap[i].working_address[p];
iomap[i].output += iomap[i].working_memory[p][base];
}// level loop
value[i]=iomap[i].output;
}//output dimension loop
if(diagnostic==1){
for(i=0;i<setup.out_dimensions;++i)
printf("iomap[%d].output = %5.1f\n",i,iomap[i].output);
}
return(value);
}
7.5.2 Hashed Memory Computations: virtmem.c:
#include "cmac.h"
This function computes the hashed address used to find CMAC
weight values in the
working memory array.
In our class implementation, let N be the dimension of the
input space and
ai and bi denote the initial
and final value, respectively, of the focus interval for input i.
Then define the auxiliary variables:
-
offset = O :
-
the offset for output component k, level j and input i,
given by iomap[k].offset[j][i]
- width = W:
- the field width for output component k,
level j and input i,
given by
iomap[k].receptive_field_width[j][i]
- init = I:
- the ai value for the focus interval for input i,
given by setup.min_x[i]
- final = F:
- the bi value for the focus interval for input i,
given by setup.max_x[i]
For a given output component k, level j and input i
we interpret the first sensor as covering the
interval (-¥, I + O) or (-¥, I + W)
depending on whether the offset is nonzero. Hence,
if the ith component of x, xi,
satisfies xi < I, it is covered by the first sensor field.
Similarly, the last sensor is interpreted as covering out to
+¥, so if xi > F, it is covered by the last sensor.
We will let OFFSET_ZERO denote the value under which we will
treat the offset as zero.
We then have two cases:
-
O > OFFSET_ZERO:
-
The subintervals are then organized as follows:
[I, I+O],
[I+O, I+O+W],
[I+O+W, I+O+2W],
...,
[I+O+(N-1)W, I+O+NW]
giving N+1 sensors.
- O £ OFFSET_ZERO:
-
The subintervals are then organized as follows:
[I, I+W],
[I+W, I+2W],
[I+2W, I+3W],
...,
[I+(M-1)W, I+MW]
giving M sensors.
So for a given component xi, first we need to find which
sensor is active on each level. Fixing the level,
the determined active sensor will be stored in
ai =
iomap[k].subinterval[i]. The active sensor can be determined by
using ceil() and floor() calculations according to the
algorithm below:
| if(O > OFFSET_ZERO) { |
| if( xi < I+O) |
| ai = 0 |
| else |
| ai = ceil(xi-I-O/W) |
| } |
| else { |
| ai = floor(xi-I/W) |
| } |
Once the full active sensor vector
[a0,a1,...,aN-1] has been calculated,
we then call the hash function for level j, iomap[k].hash[j],
to compute the working address for data x.
A stripped version of the code without
a substantial amount of diagnostic checking code is listed below. We enter this function to find the virtual memory address
for output component k that corresponds to data x.
#define OFFSET_ZERO (1.0e-6)
CMAC& CMAC::virtual_memory(int k,float *x)
{
int i,j;
float offset,width,init,final;
for(j=0;j<iomap[k].levels;++j){ // level loop
for(i=0;i<setup.in_dimensions;++i){ //input loop
offset = iomap[k].offset[j][i];
width = iomap[k].receptive_field_width[j][i];
init = setup.min_x[i];
final = setup.max_x[i];
if(offset>OFFSET_ZERO){
if(x[i]<(init+offset))
iomap[k].subinterval[i] = 0;
else
iomap[k].subinterval[i] = (int)(ceil( (x[i]-init-offset)/width));
}
else{
iomap[k].subinterval[i] = (int)(floor( (x[i]-init)/width));
}
if(iomap[k].subinterval[i]<0){
printf("BIG ERROR--NEGATIVE SUBINTERVAL CALCULATION\n");
exit(1);
}
}// level loop
iomap[k].working_address[j]
= iomap[k].hash[j](setup.in_dimensions,iomap[k].subinterval,
iomap[k].num_interval[j],iomap[k].hash_size[j]);
}// j loop: level loop for kth iomap
return *this;
}
The full code is listed below; this code has built-in
diagnostic and check code which check to see if the active sensor calculations
are correct. For speed, these diagnostics are typically turned off.
If the value of the #defined variable check is set to 1, the built
in checking will be activated. This will require a recompile of the
CMAC class.
#define OFFSET_ZERO (1.0e-6)
#define diagnostic (0)
#define check (0)
CMAC& CMAC::virtual_memory(int k,float *x)
{
int i,j;
float offset,width,init,final;
float down,up;
// we enter this routine to find the virtual memory address
// for output component k that corresponds to input vector x.
for(j=0;j<iomap[k].levels;++j){ // level loop
for(i=0;i<setup.in_dimensions;++i){ //input loop
offset = iomap[k].offset[j][i];
width = iomap[k].receptive_field_width[j][i];
init = setup.min_x[i];
final = setup.max_x[i];
if(offset>OFFSET_ZERO){
if(x[i]<(init+offset))
iomap[k].subinterval[i] = 0;
else
iomap[k].subinterval[i] = (int)(ceil( (x[i]-init-offset)/width));
}
else{
iomap[k].subinterval[i] = (int)(floor( (x[i]-init)/width));
}
if(check==1){
/* check subinterval calculation:
should have
1. O>OFFSET_ZERO ==> s=0 I<=x<I+O
s>0 I+O+(s-1)W<=x<I+O+sW
2. O<=OFFSET_ZERO ==> I+sW<=x<I+(s+1)W */
if(offset>OFFSET_ZERO){
if(iomap[k].subinterval[i]==0){
if(!(x[i]<init+offset+1.0e-6)){
printf("x[%d] = %12.8e\n",i,x[i]);
printf("offset = %12.8e\n",offset);
printf("init = %12.8e\n",init);
printf("final = %12.8e\n",final);
printf("width = %12.8e\n",width);
printf("num_interval[level=%3d][input=%3d] = %3d\n",
j,i,iomap[k].num_interval[j][i]);
printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
printf("[%12.8e,%12.8e,%12.8e]\n",init,x[i],init+offset);
exit(1);
}
}
else{
down = offset+(iomap[k].subinterval[i]-1)*width;
up = down+width;
if(!( (init+down-1.0e-6)<=x[i]
&&x[i]<(init+up+1.0e-6))){
printf("x[%d] = %12.8e\n",i,x[i]);
printf("offset = %12.8e\n",offset);
printf("init = %12.8e\n",init);
printf("final = %12.8e\n",final);
printf("width = %12.8e\n",width);
printf("num_interval[level=%3d][input=%3d] = %3d\n",
j,i,iomap[k].num_interval[j][i]);
printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
printf("[%12.8e,%12.8e,%12.8e]\n",
init+offset+(iomap[k].subinterval[i]-1)*width,x[i],
init+offset+iomap[k].subinterval[i]*width);
exit(1);
}
}
}// offset < OFFSET_ZERO
else{
down = offset+iomap[k].subinterval[i]*width;
up = down + width;
if(iomap[k].subinterval[i]==0){
if(!(x[i]<init+up+1.0e-6)){
printf("x[%d] = %12.8e\n",i,x[i]);
printf("offset = %12.8e\n",offset);
printf("init = %12.8e\n",init);
printf("final = %12.8e\n",final);
printf("width = %12.8e\n",width);
printf("num_interval[level=%3d][input=%3d] = %3d\n",
j,i,iomap[k].num_interval[j][i]);
printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
printf("[%12.8e,%12.8e,%12.8e]\n",
init+offset+iomap[k].subinterval[i]*width,x[i],
init+offset+(iomap[k].subinterval[i]+1)*width);
exit(1);
}
}
else{
if(!( init+down-1.0e-6<=x[i]
&&x[i]<init+up+1.0e-6)){
printf("x[%d] = %12.8e\n",i,x[i]);
printf("offset = %12.8e\n",offset);
printf("init = %12.8e\n",init);
printf("final = %12.8e\n",final);
printf("width = %12.8e\n",width);
printf("num_interval[level=%3d][input=%3d] = %3d\n",
j,i,iomap[k].num_interval[j][i]);
printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
printf("[%12.8e,%12.8e,%12.8e]\n",
init+offset+iomap[k].subinterval[i]*width,x[i],
init+offset+(iomap[k].subinterval[i]+1)*width);
exit(1);
}
}
}
}// check subinterval calculations?
if(diagnostic==1)
printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
if(iomap[k].subinterval[i]<0){
printf("BIG ERROR--NEGATIVE SUBINTERVAL CALCULATION\n");
exit(1);
}
}/* input loop */
iomap[k].working_address[j]
= iomap[k].hash[j](setup.in_dimensions,iomap[k].subinterval,
iomap[k].num_interval[j],iomap[k].hash_size[j]);
}// j loop: level loop for kth iomap
return *this;
}
7.5.3 CMAC Training Algorithm: train.c:
#include "cmac.h"
#define diagnostic (0)
CMAC& CMAC::train(int do_address,float DESIRED_TOL,
int sample_size,float **in,float **out)
{
int i,j,k,base;
for(k=0;k<sample_size;++k){
//evaluate address and compute cmac output
cmaceval(1,in[k]);
for(i=0;i<setup.out_dimensions;++i){
for(j=0;j<iomap[i].levels;++j){
base = iomap[i].working_address[j];
if(fabs(out[k][i]-iomap[i].output)>DESIRED_TOL){
iomap[i].working_memory[j][base]
+= setup.learning_rate*(out[k][i]-iomap[i].output)
/(float)iomap[i].levels;
}//weight update step
}//levels loop
}// output component loop
}// sample loop
return *this;
}
7.6 Utility Functions for the CMAC Object:
7.6.1 Size Calculations: number.c:
#include "cmac.h"
#define OFFSET_ZERO (1.0e-6)
#define diagnostic (0)
#define check (0)
CMAC& CMAC::number(void)
{
int i,j,k,p;
float offset,width,init,final,temp;
/*----------------------------------------------------------------
Case 0: no degeneracies: offset > OFFSET_ZERO
I----------I+O--------I+O+W------I+O+2W-----/ /----I+O+NW-----F
subintervals:
0 1 2 N N+1
Case 1: offset <= OFFSET_ZERO
I=I+O--------I+W--------I+2W----------------/ /---I+MW--------F
0 1 2 M-1 M */
for(k=0;k<setup.out_dimensions;++k){
for(j=0;j<iomap[k].levels;++j){
for(i=0;i<setup.in_dimensions;++i){
offset = iomap[k].offset[j][i];
width = iomap[k].receptive_field_width[j][i];
init = setup.min_x[i];
final = setup.max_x[i];
temp = (final-init-offset)/width;
if(temp<0.0){
printf("iomap[%d].num_interval calculation negative\n",k);
printf("final = %12.6e init = %12.6e offset = %12.6e\n",
final,init,offset);
}
iomap[k].num_interval[j][i] = (int)temp;
if((temp-(int)temp)>0)
iomap[k].num_interval[j][i] += 1;
if(offset>OFFSET_ZERO)
iomap[k].num_interval[j][i] += 1;
p = iomap[k].num_interval[j][i];
if(check==1){
if(offset>OFFSET_ZERO){
if(!( init+offset+(p-2)*width-OFFSET_ZERO<=final
&&final<=init+offset+(p-1)*width+OFFSET_ZERO)){
printf("init = %12.8e\n",init);
printf("final = %12.8e\n",final);
printf("offset = %12.8e\n",offset);
printf("width = %12.8e\n",width);
printf("(F-I-O)/W = %12.8e\n",temp);
printf("(int)temp = %3d\n",(int)temp);
printf("ceil(temp) = %12.6e\n",ceil(temp));
printf("floor(temp) = %12.6e\n",floor(temp));
printf("iomap[%d].num_interval[level=%3d][input=%3d] = %3d\n",
k,j,i,p);
printf("[%12.8e,%12.8e,%12.8e]\n",
init+offset+(p-2)*width,final,init+offset+(p-1)*width);
exit(1);
}
}
else{
if(!( init+offset+(p-1)*width-OFFSET_ZERO<=final
&&final<=init+offset+(p)*width+OFFSET_ZERO)){
printf("init = %12.8e\n",init);
printf("offset = %12.8e\n",offset);
printf("width = %12.8e\n",width);
printf("iomap[%d].num_interval[level=%3d][input=%3d] = %3d\n",
k,j,i,p);
printf("[%12.8e,%12.8e,%12.8e]\n",
init+offset+(p-1)*width,final,init+offset+(p)*width);
exit(1);
}
}
}// check iomap[k].number calculation for level j, input
// component i
if(diagnostic==1)
printf("iomap[%d].num_interval[level = %3d][input = %3d] = %3d\n",
k,j,i,p);
}// input loop
}// level loop
}// output component loop
return *this;
}
7.6.2 Echo CMAC Class Parameters: echo.c:
#include "cmac.h"
This function will echo to the screen the critical
parameters of the CMAC architecture. Since there can be
many such parameters for each choice of
output dimension, this code defaults to printing the
required echo for output component 0. This choice can
be changed by altering the # defined variable
WHICH_OUTPUT_COMPONENT to which ever index is needed.
This, of course, implies a recompile of the class.
#define WHICH_OUTPUT_COMPONENT (0)
CMAC& CMAC::echo(void)
{
int total,i,j,k,m,n;
float temp;
printf("setup.startup_file = %s\n",setup.startup_file);
printf("setup.cmac_file = %s\n",setup.cmac_file);
printf("setup.training_file = %s\n",setup.training_file);
printf("setup.testing_file = %s\n",setup.testing_file);
printf("setup.initfile = %s\n",setup.initfile);
printf("setup.wtfile = %s\n",setup.wtfile);
printf("setup.training_set_size = %3d\n",setup.training_set_size);
printf("setup.testing_set_size = %3d\n",setup.testing_set_size);
printf("setup.learning_rate = %12.6f\n",setup.learning_rate);
printf("setup.in_dimensions = %3d\n",setup.in_dimensions);
printf("setup.out_dimensions = %3d\n",setup.out_dimensions);
for(i=0;i<setup.in_dimensions;++i){
printf("input interval[%3d] = [%12.6f,%12.6f]\n",i,
setup.min_x[i],setup.max_x[i]);
}
printf("\n");
for(i=0;i<setup.out_dimensions;++i){
printf("setup.file[%3d] = %s\n",i,setup.file[i]);
printf("\n*************************\n");
printf("CMAC[%3d] parameters\n",WHICH_OUTPUT_COMPONENT);
printf("*************************\n\n");
printf("number levels = %3d\n",iomap[WHICH_OUTPUT_COMPONENT].levels);
printf("hash = %3d\n",iomap[WHICH_OUTPUT_COMPONENT].Hash);
printf("offset = %12.6f\n",iomap[WHICH_OUTPUT_COMPONENT].offset_size);
printf("width = %12.6f\n",iomap[WHICH_OUTPUT_COMPONENT].width);
printf("LEVEL INPUT OFFSET FIELD WIDTH HASH_SIZE\n\n");
for(j=0;j<iomap[WHICH_OUTPUT_COMPONENT].levels;++j){
for(k=0;k<setup.in_dimensions;++k){
printf(" %3d %3d %12.6f %12.6f %3d\n",
j,k,iomap[WHICH_OUTPUT_COMPONENT].offset[j][k],
iomap[WHICH_OUTPUT_COMPONENT].receptive_field_width[j][k],
iomap[WHICH_OUTPUT_COMPONENT].hash_size[j]);
}
}
}
for(i=0;i<setup.out_dimensions;++i){
for(j=0;j<iomap[i].levels;++j){
for(k=0;k<setup.in_dimensions;++k){
printf("sensors[output = %d][level = %d][input = %d] = %d\n",
i,j,k,iomap[i].num_interval[j][k]);
}
}
}
This function also computes the total number of
sensor cells that the CMAC architecture would have
if the level structure would be collapsed or ``flattened''
from L levels to one level. Essentially, if there
are N output components, then for output component
i, there are Li levels in the architecture with
a fixed receptive field width Wi
and an interval [ai,bi] where the CMAC sensors are active.
Thus, the number of sensors on a given level is given by
Si = (bi-ai)/Wi and the total number of
sensors for the output component i is
Ti = Si × Li. This number must be
converted to the nearest integer -- call this integer Mi.
The total number of flattened sensors for the full CMAC
architecture is then å0N-1 Mi.
total = 0;
for(i=0;i<setup.out_dimensions;++i){
n = 1;
for(k=0;k<setup.in_dimensions;++k){
temp = (setup.max_x[k]-setup.min_x[k])*iomap[i].levels/iomap[i].width;
m = (int)temp;
if((temp - m)>0.0) m +=1;
printf("flattened sensors[output = %d, levels = %d][%d input] = %d\n",
i,iomap[i].levels,k,m);
n *= m;
}
printf("flattened sensors[output = %d, levels = %d, input = %d] = %d\n",
i,iomap[i].levels,k,n);
total +=n;
}
printf("Total flattened sensors for %d outputs = %d\n",
setup.out_dimensions,total);
return *this;
}
7.6.3 Hash Algorithm: func.c:
#include "cmac.h"
This function is the generic hash function for the CMAC
architecture.
The subinterval array coming in represents
the coarse encoding the CMAC architecture gives to an input x.
In other words, letting N = denote the size of subinterval
and ai the active sensor or subinterval in which the ith
input component resides, the array subinterval
stores the active sensor list
[a0,a1,...,aN-1]. The argument size stores the value of N.
Let
the number of sensors or subintervals for input component i
be denoted by Mi.
The full list of the number of sensors per input
component has been placed in the array num_interval
= [M0,M1,...,MN-1].
Then, the list
[a0,a1,...,aN-1] represents the location of an element
in an N-dimensional space where
the ith input component can take on at most Mi values.
Alternately, we can visualize an N-dimensional array A where
the ith row has Mi elements. The list
[a0,a1,...,aN-1] then represents the array
element
in an array with unequal row sizes. Now this ragged
array A can be converted into an equivalent contiguous
block of memory space by linearizing the elements
array address in the standard way given below:
define the elements Ti by
where we see
| T-1 |
= |
1 |
| T0 |
= |
M0 |
| T1 |
= |
M0 × M1 |
| T2 |
= |
M0 × M1 × M2 |
| |
· · · |
|
| TN-2 |
= |
M0 × M1 × M2 × ··· × MN-2 |
Then, T1 is the number of elements for input component 1,
T2 is the number of possible elements for the input
1 and 2 combined and so forth.
The equivalent linear address L is given by
| L |
= |
|
| |
= |
a0 + a1*M0 + a2*M0*M1 + |
| |
|
... + aN-1*M0*M1*...*MN-2. |
Now the linear address L is usually too large to store in
our finite memory computer architectures; hence this large
linear address will be converted into a
smaller working address W by computing the remainder
of the integer division of L by a fixed integer H, the hash size.
The argument hash_size stores the value of H.
This method will result in overflow as often L is too large to store as
int or long int.
We now prove some basic facts about arithmetic operations modulo H.
Proposition 1
Let H be a positive integer greater than two and
A and B be arbitrary positive integers. Then
| (A+B)%H |
= |
(A%H + B%H) % H |
| (AB)%H |
= |
[ (A%H) (B%H) ] % H. |
Proof:
We know that there are integers s0, s1, t0 and t1 such that
| A |
= |
s0 H + t0, 0 £ t0 < H |
| B |
= |
s1 H + t1, 0 £ t1 < H. |
Thus,
| (A+B)%H |
= |
[ (s0 + s1) H + t0 + t1] % H |
| |
= |
(t0 + t1) % H |
| |
= |
[(A%H) + (B%H)] % H |
| (AB)%H |
= |
[ (s0 s1) H2 + (s0 t1 + s1 t0) H + t0 t1] % H |
| |
= |
[t0 t1] % H |
| |
= |
[ (A%H) (B%H) ] % H |
Q.E.D.
Proposition 2
Let H be a positive integer greater than two and
Then, V % H can be computed via the iterative scheme
| V-1 |
= |
0 |
| Vi |
= |
(Vi-1 % H) MN-i-1 + aN-i-1,
0 £ i £ N-2 |
| VN-1 |
= |
(VN-2 % H) M0 + a0 |
| V % H |
= |
VN-1 % H. |
Proof:
We know that there are integers si, ti, ci,
di, ai and bi such that for all
appropriate indices i
| Mi |
= |
ci H + di, 0 £ di < H |
| Ti |
= |
si H + ti, 0 £ ti < H |
| ai |
= |
ai H + bi, 0 £ bi < H. |
Thus, using Proposition 1 repeatedly,
| V0 |
= |
aN-1 |
| V1 |
= |
(V0 % H) MN-2 + aN-2 |
| |
= |
(aN-1 % H) MN-2 + aN-2 |
| |
= |
(bN-1)
(cN-2 H + dN-2)
+(aN-2 H + bN-2) |
| V1 % H |
= |
(bN-1 dN-2 + bN-2) % H |
| |
= |
(aN-1 % H MN-2 % H + aN-2 % H) % H |
| |
= |
(aN-1 MN-2 + aN-2) % H |
| V2 |
= |
(V1 % H) MN-3 + aN-3 |
| V2 % H |
= |
((V1 % H) MN-3 % H + aN-3 % H) % H |
| |
= |
( (aN-1 MN-2 + aN-2) MN-3 + aN-3) % H |
| |
= |
( aN-1 MN-2 MN-3
+ aN-2 MN-3 + aN-3) % H |
| V3 |
= |
(V2 % H) MN-3 + aN-3 |
| V3 % H |
= |
( aN-1 MN-2 MN-3 MN-4
+ aN-2 MN-3 MN-4
+ aN-3 MN-4
+ aN-4) % H. |
Continuing in this way, we see
| VN-2 % H |
= |
| ( aN-1 |
|
Mi
+ aN-2 |
|
Mi
+ ··· +
+ a2 |
|
Mi
+ a1) % H |
|
| VN-1 |
= |
(VN-2 % H) M0 + a0 |
| VN-1 % H |
= |
|
| |
= |
V % H |
Q.E.D.
The code to implement the iterative scheme of Proposition 2
is given in the function hhash.
int hhash(int size,int *subinterval,int *num_interval,
int hash_size)
{
int i,work_address,accumulator;
unsigned long virtual_address;
virtual_address = 0;
accumulator = 1;
for(i=0;i<size;++i){
virtual_address = virtual_address*(unsigned long)accumulator
+(unsigned long)subinterval[size-1-i];
virtual_address = virtual_address%(unsigned long)hash_size;
if(i<size-1)
accumulator = num_interval[size-i-2];
}
work_address = virtual_address%(unsigned long)hash_size;
return(work_address);
}
7.6.4 Compute RMS Error: getrms.c:
#include "cmac.h"
#define diagnostic (0)
float CMAC::compute_rms(int sample_size,float **in,float **out)
{
int i,k,base;
float rms, big;
rms = 0.0;
big = -1.0;
for(k=0;k<sample_size;++k){
cmaceval(1,in[k]);
if(diagnostic==1){
for(i=0;i<setup.in_dimensions;++i)
printf("in[%3d][%3d] = %12.6f\n",k,i,in[k][i]);
}
for(i=0;i<setup.out_dimensions;++i){
rms += (out[k][i] - iomap[i].output)
*(out[k][i] - iomap[i].output);
if(fabs(out[k][i]-iomap[i].output)>big)
big = fabs(out[k][i]-iomap[i].output);
}//output dimensions loop
}// sample loop
rms=sqrt(rms/(float)sample_size/(float)setup.out_dimensions);
return(rms);
}
7.6.5 getworkingaddress.c:
#include "cmac.h"
CMAC& CMAC::getworkingaddress(int which_output,int *address)
{
int i;
for(i=0;i<iomap[which_output].levels;++i)
address[i] = iomap[which_output].working_address[i];
return *this;
}
7.6.6 getlevels.c:
#include "cmac.h"
CMAC& CMAC::getlevels(int *levels,int which_output)
{
*levels = iomap[which_output].levels;
return *this;
}
7.6.7 getnumberintervals.c:
#include "cmac.h"
CMAC& CMAC::getnumberintervals(int which_output,int which_level,
int which_input,int *number)
{
*number = iomap[which_output].num_interval[which_level][which_input];
return *this;
}
7.6.8 getinputsize.c:
#include "cmac.h"
CMAC& CMAC::getinputsize(int *input_size)
{
*input_size = setup.in_dimensions;
return *this;
}
7.6.9 getoutputsize.c:
#include "cmac.h"
CMAC& CMAC::getoutputsize(int *output_size)
{
*output_size = setup.out_dimensions;
return *this;
}
7.6.10 getminx.c:
#include "cmac.h"
CMAC& CMAC::getminx(int which_input,float *min)
{
*min = setup.min_x[which_input];
return *this;
}
7.6.11 getmaxx.c:
#include "cmac.h"
CMAC& CMAC::getmaxx(int which_input,float *max)
{
*max = setup.max_x[which_input];
return *this;
}
7.6.12 getwidth.c:
#include "cmac.h"
CMAC& CMAC::getwidth(int which_output,int which_level,
int which_input,float *width)
{
*width =
iomap[which_output].receptive_field_width[which_level][which_input];
return *this;
}
7.6.13 loadinputsize.c:
#include "cmac.h"
CMAC& CMAC::loadinputsize(int input_size)
{
setup.in_dimensions = input_size;
return *this;
}
7.6.14 loadoutputsize.c:
#include "cmac.h"
CMAC& CMAC::loadoutputsize(int output_size)
{
setup.out_dimensions = output_size;
return *this;
}
7.6.15 loadtrainsize.c:
#include "cmac.h"
CMAC& CMAC::loadtrainsize(int trainsize)
{
setup.training_set_size = trainsize;
return *this;
}
7.6.16 loadtestsize.c:
#include "cmac.h"
CMAC& CMAC::loadtestsize(int testsize)
{
setup.testing_set_size = testsize;
return *this;
}
7.6.17 gettestsize.c:
#include "cmac.h"
CMAC& CMAC::gettestsize(int *testsize)
{
*testsize = setup.testing_set_size;
return *this;
}
7.6.18 gettrainsize.c:
#include "cmac.h"
CMAC& CMAC::gettrainsize(int *trainsize)
{
*trainsize = setup.training_set_size;
return *this;
}
7.6.19 Get Test Data: readte.c:
#include "cmac.h"
#define INFINITY (1.0e9)
#define NEGATIVE_INFINITY (-1.0e9)
CMAC& CMAC::read_testing_data(float ***in,float ***out)
/*---------------------------------------------------------------
read and store testing data in vectors in and out
---------------------------------------------------------------*/
{
int i,j,insize,outsize,setsize;
float *min,*max;
FILE *fd2;
insize = setup.in_dimensions;
outsize = setup.out_dimensions;
setsize = setup.testing_set_size;
if((fd2=fopen(setup.testing_file,"r")) == NULL){
printf("\nCan't open input file %s\n",setup.testing_file);
exit(0);
}
get_2dmatrix_float(in,setsize+1,insize);
get_2dmatrix_float(out,setsize+1,outsize);
for(j=0;j<setsize;++j){
// read and store ith component of jth input vector
for(i=0;i<insize;++i)
fscanf(fd2,"%f",&(*in)[j][i]);
// read and store ith component of jth desired output vector
for(i=0;i<outsize;++i)
fscanf(fd2,"%f",&(*out)[j][i]);
}
fclose(fd2);
min = new float[setup.in_dimensions];
max = new float[setup.in_dimensions];
// find each input coordinate's interval
for(i=0;i<setup.in_dimensions;++i){
max[i] = NEGATIVE_INFINITY;
min[i] = INFINITY;
for(j=0;j<setup.testing_set_size;++j){
if(max[i]<(*in)[j][i])
max[i] = (*in)[j][i];
if(min[i]>(*in)[j][i])
min[i] = (*in)[j][i];
}
printf("Input[%d]::min/max of input widths= %12.6f/%12.6f\n",
i,min[i],max[i]);
if(min[i]<setup.min_x[i] || max[i]>setup.max_x[i]){
printf("\nError!\n");
printf("[minTr,minTe,maxTe,maxTr][%d]=[%10.6f,%10.6f,%10.6f,%10.6f]\n",
i,setup.min_x[i],min[i],max[i],setup.max_x[i]);
}
}
delete min;
delete max;
return *this;
}
7.6.20 Get Training Data: readtr.c:
#include "cmac.h"
#define INFINITY (1.0e9)
#define NEGATIVE_INFINITY (-1.0e9)
CMAC& CMAC::read_training_data(float ***in,float ***out)
/*---------------------------------------------------------------
read and store training data in vectors in and out
---------------------------------------------------------------*/
{
int i,j,insize,outsize,setsize;
float *min,*max,temp;
FILE *fd2;
insize = setup.in_dimensions;
outsize = setup.out_dimensions;
setsize = setup.training_set_size;
if((fd2=fopen(setup.training_file,"r")) == NULL){
printf("\nCan't open input file %s\n",setup.training_file);
exit(0);
}
get_2dmatrix_float(in,setsize+1,insize);
get_2dmatrix_float(out,setsize+1,outsize);
for(j=0;j<setsize;++j){
// read and store ith component of jth input vector
for(i=0;i<insize;++i)
fscanf(fd2,"%f",&(*in)[j][i]);
// read and store ith component of jth desired output vector
for(i=0;i<outsize;++i)
fscanf(fd2,"%f",&(*out)[j][i]);
}
fclose(fd2);
min = new float[setup.in_dimensions];
max = new float[setup.in_dimensions];
// find each input coordinate's interval
for(i=0;i<insize;++i){
max[i] = NEGATIVE_INFINITY;
min[i] = INFINITY;
for(j=0;j<setsize;++j){
if(max[i]<(*in)[j][i])
max[i] = (*in)[j][i];
if(min[i]>(*in)[j][i])
min[i] = (*in)[j][i];
}
printf("Input[%d]::min/max of input widths= %12.6f/%12.6f\n",
i,min[i],max[i]);
}
for(i=0;i<insize;++i){
if(setup.autosize==1){
setup.min_x[i] = min[i]-setup.inflate_bottom*(max[i]-min[i]);
setup.max_x[i] = max[i]+setup.inflate_top*(max[i]-min[i]);
}
else{
setup.min_x[i] = setup.common_start;
setup.max_x[i] = setup.common_end;
}
}
delete min;
delete max;
return *this;
}
7.6.21 Get Stored Parameters: readw.c:
#include "cmac.h"
#define write_to_console (0)
/*-------------------------------------------------------------
read in weights from file and output to console
(if desired)
------------------------------------------------------------ */
CMAC& CMAC::read_wts(void)
{
FILE *fd2;
int i,j,k;
if((fd2=fopen(setup.initfile,"r")) == NULL){
printf("\nCan't open input file %s\n",setup.initfile);
exit(0);
}
for(i=0;i<setup.out_dimensions;++i){
if(write_to_console==1)
printf("\n iomap[%d] weights \n",i);
for(j=0;j<iomap[i].levels;++j){
if(write_to_console==1)
printf("\nLevel %3d\n",j);
for(k=0;k<iomap[i].hash_size[j];++k){
fscanf(fd2,"%f\n",&(iomap[i].working_memory[j][k]));
if(write_to_console==1)
printf("working_memory[level =%d][element = %d] = %12.6f\n",
j,k,iomap[i].working_memory[j][k]);
}//element loop
}//level loop
}//output component loop
fclose(fd2);
return *this;
}
7.6.22 Save CMAC Parameters: writew.c:
#include "cmac.h"
#define write_to_console (0)
/*-------------------------------------------------------------
print out weights both to console(if desired)
and to file weights.dat
------------------------------------------------------------ */
CMAC& CMAC::write_wts(void)
{
FILE *fd2;
int i,j,k;
if((fd2=fopen(setup.wtfile,"w")) == NULL){
printf("\nCan't open file %s\n",setup.wtfile);
exit(0);
}
for(i=0;i<setup.out_dimensions;++i){
if(write_to_console==1)
printf("\niomap[%d] weights\n",i);
for(j=0;j<iomap[i].levels;++j){
if(write_to_console==1)
printf("\nlevel %3d\n",j);
for(k=0;k<iomap[i].hash_size[j];++k){
if(write_to_console==1)
printf("working_memory[level = %d][element =%d] = %12.6f\n",
j,k,iomap[i].working_memory[j][k]);
fprintf(fd2,"%14.10f ",iomap[i].working_memory[j][k]);
if((k+1)%4==0){
if(write_to_console==1)
printf("\n");
fprintf(fd2,"\n");
}
}
if(write_to_console==1)
printf("\n");
fprintf(fd2,"\n");
}
}
fclose(fd2);
return *this;
}