00001 /************************************************************************ 00002 * Verve * 00003 * Copyright (C) 2004-2006 * 00004 * Tyler Streeter tylerstreeter@gmail.com * 00005 * All rights reserved. * 00006 * Web: http://verve-agents.sourceforge.net * 00007 * * 00008 * This library is free software; you can redistribute it and/or * 00009 * modify it under the terms of EITHER: * 00010 * (1) The GNU Lesser General Public License as published by the Free * 00011 * Software Foundation; either version 2.1 of the License, or (at * 00012 * your option) any later version. The text of the GNU Lesser * 00013 * General Public License is included with this library in the * 00014 * file license-LGPL.txt. * 00015 * (2) The BSD-style license that is included with this library in * 00016 * the file license-BSD.txt. * 00017 * * 00018 * This library is distributed in the hope that it will be useful, * 00019 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files * 00021 * license-LGPL.txt and license-BSD.txt for more details. * 00022 ************************************************************************/ 00023 00024 #include "RBFNeuron.h" 00025 #include "RBFInputData.h" 00026 00027 namespace verve 00028 { 00029 RBFNeuron::RBFNeuron(unsigned int id) 00030 : Neuron(id) 00031 { 00032 //mType = NEURON_TYPE_RBF; 00033 mNumDiscreteDimensions = 0; 00034 mNumContinuousDimensions = 0; 00035 mDiscretePosition = NULL; 00036 mContinuousPosition = NULL; 00037 mRBFDenominatorInverse = 0; 00038 mActivationThresholdDistanceSquared = 0; 00039 mNewRBFThresholdDistanceSquared = 0; 00040 } 00041 00042 RBFNeuron::~RBFNeuron() 00043 { 00044 delete [] mDiscretePosition; 00045 delete [] mContinuousPosition; 00046 } 00047 00048 void RBFNeuron::init(real stdDevWidth, real newRBFThreshold, 00049 const RBFInputData& inputData) 00050 { 00051 mNumDiscreteDimensions = inputData.numDiscInputs; 00052 mNumContinuousDimensions = inputData.numContInputs; 00053 00054 if (mDiscretePosition) 00055 { 00056 delete [] mDiscretePosition; 00057 } 00058 00059 if (0 == mNumDiscreteDimensions) 00060 { 00061 mDiscretePosition = NULL; 00062 } 00063 else 00064 { 00065 mDiscretePosition = new unsigned int[mNumDiscreteDimensions]; 00066 for (unsigned int i = 0; i < mNumDiscreteDimensions; ++i) 00067 { 00068 mDiscretePosition[i] = inputData.discInputData[i]; 00069 } 00070 } 00071 00072 if (mContinuousPosition) 00073 { 00074 delete [] mContinuousPosition; 00075 } 00076 00077 if (0 == mNumContinuousDimensions) 00078 { 00079 mContinuousPosition = NULL; 00080 } 00081 else 00082 { 00083 mContinuousPosition = new real[mNumContinuousDimensions]; 00084 for (unsigned int i = 0; i < mNumContinuousDimensions; ++i) 00085 { 00086 mContinuousPosition[i] = inputData.contInputData[i]; 00087 } 00088 } 00089 00090 // This constant is the inverse of the denominator in the Gaussian 00091 // basis function. 00092 mRBFDenominatorInverse = 1 / (2 * stdDevWidth * stdDevWidth); 00093 00094 // According to the 'empirical rule', 99.7% of the data are within 00095 // three standard deviations from the mean, so we'll 00096 // let the activation threshold equal (3 * stdDevWidth), giving us a 00097 // squared distance threshold of (3 * stdDevWidth)^2 = 00098 // 9 * stdDevWidth * stdDevWidth. 00099 //mActivationThresholdDistanceSquared = 9 * stdDevWidth * stdDevWidth; 00100 // Update: The threshold distance (3 * stdDevWidth) yielded 00101 // significant artifacts (e.g. as seen by visualizing the Agent's 00102 // value function). Instead, we'll use (4 * stdDevWidth). 00103 mActivationThresholdDistanceSquared = 16 * stdDevWidth * stdDevWidth; 00104 00105 // Store the squared distance for faster computations later. 00106 mNewRBFThresholdDistanceSquared = newRBFThreshold * newRBFThreshold; 00107 } 00108 00109 RBFActivationCode RBFNeuron::updateFiringRate( 00110 const RBFInputData& inputData) 00111 { 00112 // Check the distance between this RBF and the given point along 00113 // each dimension. As soon as we can determine that the data 00114 // point is not close enough to affect this RBFNeuron's activation, 00115 // return. 00116 00117 // Compute the distance in discrete space. This distance is 00118 // defined as zero when the values match and infinite when they 00119 // don't match. 00120 for (unsigned int i = 0; i < mNumDiscreteDimensions; ++i) 00121 { 00122 // If the position doesn't match along every single discrete 00123 // dimension, quit early with no activation. 00124 if (mDiscretePosition[i] != inputData.discInputData[i]) 00125 { 00126 setFiringRate(0); 00127 return NO_ACTIVATION; 00128 } 00129 } 00130 00131 // If we've gotten to this point, the data point must match the 00132 // RBF in discrete space. For the special case when we only have 00133 // discrete inputs and no continuous ones, we can set the 00134 // activation high and quit. 00135 if (0 == mNumContinuousDimensions) 00136 { 00137 setFiringRate(1); 00138 return HIGH_ACTIVATION; 00139 } 00140 00141 // Now compute the distance in continuous space by looping over 00142 // the continuous dimensions. 00143 real distanceSquared = 0; 00144 for (unsigned int i = 0; i < mNumContinuousDimensions; ++i) 00145 { 00146 // IDEA: If we represented RBF positions using a vector of 00147 // input Connections (and the input data as pre-synaptic Neuron 00148 // firing rates), we could delete Connections over time that 00149 // never get used. This would help avoid having to compare 00150 // the data point to the RBF position in potentially every 00151 // input dimension. 00152 00153 // IDEA: Closely-related inputs might need to be grouped into 00154 // separate arrays (e.g. vision, somatosensory, etc.). Each 00155 // of these arrays should have a separate set of RBFs with the 00156 // dimensionality of the RBF array equal to that of the 00157 // number of values in the particular input array. For now, 00158 // assume all inputs use the same RBF array. 00159 00160 // The following process computes the RBFNeuron activation 00161 // level based on the Euclidean distance between the RBF and 00162 // the data point. 00163 00164 real distanceInThisDimension = inputData.contInputData[i] - 00165 mContinuousPosition[i]; 00166 00167 // Deal with circular input ranges, if necessary. 00168 if (inputData.contCircularData[i]) 00169 { 00170 // Note that the maximum input range is 2. If the position 00171 // in the ith dimensions is more than half of this range 00172 // away (i.e. 1 unit away) from the data point, we 00173 // temporarily shift the RBF position in this dimensions 00174 // to make it wrap around the ends of the range. 00175 00176 if (distanceInThisDimension > 1) 00177 { 00178 // Recompute distance with wrapped weight value. 00179 distanceInThisDimension -= 2; 00180 } 00181 else if (distanceInThisDimension < -1) 00182 { 00183 // Recompute distance with wrapped weight value. 00184 distanceInThisDimension += 2; 00185 } 00186 } 00187 00188 // Add to the squared distance. 00189 distanceSquared += 00190 (distanceInThisDimension * distanceInThisDimension); 00191 00192 // As soon as we know the distance is beyond the activation 00193 // distance threshold, we can quit early and skip the remaining 00194 // dimensions. 00195 if (distanceSquared > mActivationThresholdDistanceSquared) 00196 { 00197 setFiringRate(0); 00198 return NO_ACTIVATION; 00199 } 00200 } 00201 00202 // At this point we know that the point must be close enough to the 00203 // RBF to affect its activation level. We have computed the sum of 00204 // the squared distances along each continuous dimension. Now 00205 // update the RBFNeuron's activation using a Gaussian-shaped basis 00206 // function. 00207 setFiringRate( 00208 globals::exp(-distanceSquared * mRBFDenominatorInverse)); 00209 00214 //for (unsigned int i = 0; i < mNumContinuousDimensions; ++i) 00215 //{ 00216 // // Do we move the RBF in proportion to its distance from the 00217 // // input point? (RBFs with NO_ACTIVATION get ignored)... 00218 // real distanceInThisDimension = inputData.contInputData[i] - 00219 // mContinuousPosition[i]; 00220 // mContinuousPosition[i] += 0.01 * distanceInThisDimension; 00221 00222 // //// ...or do do we move the RBF in proportion to its activation, 00223 // //// which is more like 1/distance from the input point? 00224 // //real influence = 00225 // // globals::exp(-distanceSquared * mRBFDenominatorInverse); 00226 // //mContinuousPosition[i] += 0.01 * influence; 00227 //} 00228 00229 if (distanceSquared > mNewRBFThresholdDistanceSquared) 00230 { 00231 // The point is close enough to affect the RBFNeuron's 00232 // activation, but far enough away to consider creating a new 00233 // RBF. 00234 return LOW_ACTIVATION; 00235 } 00236 else 00237 { 00238 // The point is close enough that we won't consider creating a 00239 // new RBF. 00240 return HIGH_ACTIVATION; 00241 } 00242 } 00243 00244 unsigned int RBFNeuron::getNumDiscreteDimensions()const 00245 { 00246 return mNumDiscreteDimensions; 00247 } 00248 00249 unsigned int RBFNeuron::getNumContinuousDimensions()const 00250 { 00251 return mNumContinuousDimensions; 00252 } 00253 00254 const unsigned int* RBFNeuron::getDiscretePosition()const 00255 { 00256 return mDiscretePosition; 00257 } 00258 00259 const real* RBFNeuron::getContinuousPosition()const 00260 { 00261 return mContinuousPosition; 00262 } 00263 }