/***************************************************************************** * * * FOX Controller Library, Version 1.3.3. * * * * Copyright (C) 1998,1999,2000 Russell Smith (rl.smith@auckland.ac.nz) * * * * The FOX Controller Library is free software; you can redistribute it * * and/or modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either version * * 2 of the License, or (at your option) any later version. * * * * The FOX Controller Library is distributed in the hope that it will be * * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with the Fox Controller Library; see the file COPYING.LIB. * * If not, write to the Free Software Foundation, Inc., 59 Temple Place - * * Suite 330, Boston, MA 02111-1307, USA. * * * *****************************************************************************/ /* FOXN: CMAC with vector eligibility ---------------------------------- We want to avoid having two or more weight indexes point to the same weight for any input, so we dont have to do tricky things with the eligibility computations to take the multiple references into account. In the "normal" cmac the weight index is hash function of the virtual address (made up of the input receptive field numbers) and the AU number. The weight index was between 0..wsize-1. We could instead compute the weight index as a hash function of the virtual address alone, hashed into the range 0..wsize-1, PLUS wsize times the AU number, so the weight index is between 0..(wsize*C)-1. The weight table would be divided into `C' wsize-blocks. This has the advantage that the weight indexes would always be distinct. BUT this scheme is not quite perfect because the basis functions for each AU will look the same, so a hash collision (i.e. a basis function localized in more than one useful area) will not "spread itself around" but will instead be concentrated in the AU basis functions. The solution is to make the AU number a parameter of the hash function also. However, as C increased the total number of weights would increase, but the weight requirement actually decreases. Thus we have the user specify the desired number of weights per output, wsize is computed so that wsize*C is close to this number. The "slow" method of updating eligibilities is to perform the filter calculations for each weight. For accurate comparisons with the fast method we need to get the effect of "retiring" weights, so we use the aux[] index value to store the time at which the weight was last accessed. The "inactive weight" queue is split into two parts, `buffer1' and `buffer2'. Buffer 1 stores C weight indexes at each buffer position. Each index is the index of an inactive weight, or -1 if none. Buffer 2 stores C eligibility vectors at each buffer position, which correspond to the non -1 weight indexes. Each vector has size `ny', so C*ny numbers are stored at each buffer position. The vector is ignored if the corresponding weight index is -1. NOTE that the eligibility vectors stored in buffer2 should be unique for each output (the eligibility filter driving force from matrix B is different for each output), but we wont worry about that yet as we are currently only supporting one output. A weight index can only be in the inactive queue once. The eligibility vector of an inactive weight says its eligibility the last time it was active. If a weight is _not_ in the inactive queue then it's eligibility is assumed to be 0. We store some auxiliary information about each weight index (NOT for each weight, i.e. for more outputs there is still the same amount of auxiliary information). The auxiliary information is an integer `i' : if the weight is in the inactive queue, this is its position, otherwise the weight's stored value is correct and `i' is -1. See cmac.cc for additional comments. */ #include #include "foxstuff.h" #include "fox-n.h" //*************************************************************************** // see the .cc file for a description of what this does char * GetCMACOverlayDisplacementVector (int n, int p); //*************************************************************************** // Here is a really hashy hash function from "Numerical Recipes in C", // 2nd Ed, page 300. It does a pseudo-DES hashing of the given 64 bit word // (lword,irword). Both 32-bit arguments are returned hashed on all bits. // This does a much better job of nonlinear pseudo-random bit mixing than // some other hash functions (like mod-prime or linear congruential) but // at the expense of speed. It's still pretty quick, though. #define PDES_ITERS 4 static void pdes_hash (unsigned long &lword, unsigned long &irword) { unsigned long i,ia,ib,iswap,itmph=0,itmpl=0; static unsigned long c1 [PDES_ITERS] = { 0xbaa96887L,0x1e17d32cL,0x03bcdc3cL,0x0f33d1b2L}; static unsigned long c2 [PDES_ITERS] = { 0x4b0f3b58L,0xe874f0c3L,0x6955c5a6L,0x55a7ca46L}; // Perform PDES_ITERS iterations of DES logic, using a simpler // (non-cryptographic) nonlinear function instead of DES's. for (i=0; i> 16; ib = itmpl*itmpl + ~(itmph*itmph); irword = lword^(((ia=(ib>>16)|((ib&0xffff)<<16))^c2[i])+itmpl*itmph); lword = iswap; } } // Test pdes_hash to make sure the integer arithmetic is being done ok. // If anything is wrong an error message is printed and the program is halted. static void test_pdes_hash() { unsigned long a,b; if (sizeof (a) != 4) ZFatalError ("Internal error: sizeof(unsigned long) must be 4."); a=99; b=99; pdes_hash (a,b); if ((a != 0xd7f376f0) || (b != 0x59ba89eb)) ZFatalError ("Internal error: pdes_hash integer arithmetic incorrect."); } // Automatic pdes_hash tester static struct automatic_pdes_hash_tester { automatic_pdes_hash_tester() { test_pdes_hash(); }; } test_pdes_hash_automatically; //*************************************************************************** // Check that we can set a `cftype' to zero by setting all its bits to zero, // because we want to use memset() to zero out some large arrays. static void check_zero_cftype() { cftype a; a = 3.14159265358979323846; memset (&a,0,sizeof(a)); if (a != 0.0) ZFatalError ("Internal error: floating point format not supported."); } // Automatic check_zero_cftype caller static struct automatic_check_zero_cftype { automatic_check_zero_cftype() { check_zero_cftype(); }; } call_check_zero_cftype_automatically; //*************************************************************************** // FoxN FoxN::FoxN (int num_inputs, CmacInputInfo *input_info, int num_weights, int iC, int _ny, cftype *_matA, cftype *_matB, cftype *_matC, int _history) { int i; unsigned long a; valid = 0; // set all pointers to NULL so that the destructor can safely destruct a // partially constructed object weights = NULL; windex = NULL; buffer1 = NULL; buffer2 = NULL; atoi = NULL; aux = NULL; trace = NULL; #ifdef SLOW_METHOD slow_eli = NULL; #endif nin = num_inputs; C = iC; checkthat (nin > 0 && nin <= CMAC_MAXIN,"bad number of inputs (%d)",nin); checkthat (C > 0 && C <= CMAC_MAXC,"bad number of association units (%d)",C); // setup input info array for (i=0; i= 2,"resolution for input %d too small",i); iinfo[i].min = input_info[i].min; iinfo[i].max = input_info[i].max; iinfo[i].q = cftype(iinfo[i].res) / (iinfo[i].max - iinfo[i].min); checkthat (iinfo[i].q > 0,"bad max / min for input %d",i); iinfo[i].parts = (iinfo[i].res-2)/C + 2; } // Check that the receptive field numbers for all inputs can be packed // into a 32-bit word - multiply them together and check for overflow. a = iinfo[0].parts; for (i=1; i= C,"number of weights must be > C"); wsize = num_weights / C; // Setup weights. checkmemptr (weights = new cftype [wsize * C]); memset (weights, 0, wsize*C * sizeof(cftype)); // setup other arrays checkmemptr (windex = new int [C]); odv = GetCMACOverlayDisplacementVector (nin, C); if (!odv) return; checkmemptr (aux = new int [wsize*C]); output = 0.0; old_output = 0.0; // setup eligibility stuff ny = _ny; checkthat (ny >= 1 && ny <= MAX_NY,"ny must be in the range 1..%d",MAX_NY); memcpy (matA,_matA,ny*ny * sizeof(cftype)); memcpy (matB,_matB,ny*1 * sizeof(cftype)); memcpy (matC,_matC,ny * sizeof(cftype)); elimode = 1; history = _history; checkthat (history > 0,"history must be > 0"); bufsize1 = history * C; bufsize2 = history * C * ny; checkmemptr (buffer1 = new int [bufsize1]); checkmemptr (buffer2 = new cftype [bufsize2]); checkmemptr (atoi = new GetAtoi (ny,matA,history + 2)); if (!atoi->Valid()) return; checkmemptr (trace = new TraceN (ny,matA,matC,history + 2,atoi)); if (!trace->Valid()) return; // set all buffer indexes to -1, else Reset() will try to correct the weights memset (buffer1,~0,sizeof(int) * bufsize1); #ifdef SLOW_METHOD checkmemptr (slow_eli = new cftype [wsize * C * ny]); #endif Reset(); valid = 1; } FoxN::~FoxN() { delete[] weights; delete[] windex; delete[] buffer1; delete[] buffer2; delete atoi; delete[] aux; delete trace; #ifdef SLOW_METHOD delete[] slow_eli; #endif } void FoxN::SetEligibilityMode (int _elimode) { myassert (elimode !=2,"can't change eligibility mode, Train() call pending"); int newelimode = (_elimode != 0); if (elimode == newelimode) return; if (elimode == 1 && newelimode == 0) Reset(); elimode = newelimode; } void FoxN::Map (cftype *input) { MapGate (input,1); } void FoxN::MapGate (cftype *input, int gate) { int i,j; Quantize (input); GetAssociations(); #ifdef FAST_METHOD // If in mapping mode, all weights will be correct. if (elimode) { myassert (elimode == 1,"called Map() but call to Train() pending"); elimode = 2; // advance buffer head and retire old weights time++; head += C; if (head >= bufsize1) head = 0; for (i=0; i= 0) && ((time - aux [i]) >= history) ) { eptr = slow_eli + i*ny; for (j=0; jNext (k); #endif #ifdef SLOW_METHOD cftype k = error / (cftype (C)); cftype dot,*eli = slow_eli; for (int i=0; i < (wsize*C); i++) { dot = 0.0; for (int j=0; j limit) { cftype winc = (limit - output)/cftype(C); for (int i=0; i= bufsize1) head = 0; for (int i=0; iReset(); // clear out aux array: set indexes to -1 memset (aux,~0,sizeof(int) * wsize*C); } void FoxN::ResetAll() { Reset(); memset (weights, 0, wsize*C * sizeof(cftype)); } int FoxN::InRange (cftype *input) { int i; for (i=0; i iinfo[i].max) return 0; else if (input[i] < iinfo[i].min) return 0; } return 1; } void FoxN::Quantize (cftype *input) { for (int i=0; i= iinfo[i].max) qin[i] = iinfo[i].res-1; else { if (input[i] <= iinfo[i].min) qin[i] = 0; # ifdef USING_HPFLOAT else { cftype result = (input[i] - iinfo[i].min) * iinfo[i].q; qin[i] = (int) result.Double(); } # else else qin[i] = int ( (input[i] - iinfo[i].min) * iinfo[i].q ); # endif } } } int FoxN::Association (int a) { int i,r; unsigned long hh,h; // (hh,h) = 64 bit hash key int oo; // AU receptive field ("overlay") offset int addr; // returned address myassert (a>=0 && a= hd) : (bpos > hd)) t -= history; myassert (t >= 0 && t <= time && t >= (time-history), "bad time in GetCorrectWeight"); cftype *eli = buffer2 + aux[i] * ny; // eligibility vector for this weight // Find increment to weight (for all outputs), and change in eligiblity. if ((time-t)==1) { // If weight was active last timestep (as most weights usually are) then // we can do it the easy way. cftype dot = 0.0; for (int k=0; kGetDelta (t); dot = 0.0; for (int k=0; kPreMulByAtoPlus (neweli,eli,time-t); } aux[i] = -1; return neweli; }