001 package com.croftsoft.apps.cyborg;
002
003 import java.io.*;
004 import java.util.*;
005
006 import org.apache.commons.math.MathException;
007 import org.apache.commons.math.distribution.DistributionFactory;
008 import org.apache.commons.math.distribution.NormalDistribution;
009 import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
010
011 /*********************************************************************
012 * Surrogate directional activity.
013 *
014 * Translated from SurrogateDirectionalActivityV2.xls (2004 October)
015 * by Lawrence J. Cauller, Ph.D., which was donated to the
016 * Public Domain by Dr. Cauller on 2004-12-01.
017 *
018 * @version
019 * 2004-12-23
020 * @since
021 * 2004-10-19
022 * @author
023 * <a href="http://www.CroftSoft.com/">David Wallace Croft</a>
024 *********************************************************************/
025
026 public final class Activity
027 //////////////////////////////////////////////////////////////////////
028 //////////////////////////////////////////////////////////////////////
029 {
030
031 private static final int DIRECTIONS = 8;
032
033 private static final int NEURONS = 24;
034
035 private static final int TRIALS = 100;
036
037 //
038
039 private static final double RATE_SPONTANEOUS = 10.0;
040
041 private static final double RATE_MIN = 5.0;
042
043 private static final double RATE_MAX = 50.0;
044
045 private static final double RATE_DEVIATION = 10.0;
046
047 //
048
049 /** Random number generator. */
050 private static final Random random = new Random ( );
051
052 /** Used to create normal distributions for adding noise. */
053 private static final DistributionFactory distributionFactory
054 = DistributionFactory.newInstance ( );
055
056 //
057
058 private static final String OUTPUT_FILENAME = "out.txt";
059
060 //////////////////////////////////////////////////////////////////////
061 //////////////////////////////////////////////////////////////////////
062
063 public static void main ( String [ ] args )
064 throws Exception
065 //////////////////////////////////////////////////////////////////////
066 {
067 // Write the output to a file using PrintStream printf().
068
069 PrintStream printStream = new PrintStream (
070 new FileOutputStream ( OUTPUT_FILENAME ) );
071
072 // Assume we know the preferred stimulus direction for each of the
073 // neurons that we are recording from.
074
075 double [ ] preferredDirections = new double [ NEURONS ];
076
077 for ( int unit = 0; unit < NEURONS; unit++ )
078 {
079 // Assume that the neurons we chose have evenly distributed
080 // preferred stimulus directions
081
082 preferredDirections [ unit ]
083 = ( 2.0 * Math.PI * ( unit ) ) / NEURONS;
084 }
085
086 printTemplateActivations ( printStream, preferredDirections );
087
088 runTrials ( printStream, preferredDirections );
089 }
090
091 /*********************************************************************
092 * Prints the template activations with one standard dev of noise.
093 *********************************************************************/
094 public static void printTemplateActivations (
095 PrintStream printStream,
096 double [ ] preferredDirections )
097 throws Exception
098 //////////////////////////////////////////////////////////////////////
099 {
100 double [ ] templateActivations = new double [ NEURONS ];
101
102 double [ ] addDeviations = new double [ NEURONS ];
103
104 double [ ] subDeviations = new double [ NEURONS ];
105
106 printStream.println ( "unit preferred template addDev subDev" );
107
108 for ( int unit = 0; unit < NEURONS; unit++ )
109 {
110 templateActivations [ unit ] = computeNoiselessActivation (
111 0.0, preferredDirections [ unit ] );
112
113 addDeviations [ unit ]
114 = templateActivations [ unit ] + RATE_DEVIATION;
115
116 subDeviations [ unit ]
117 = templateActivations [ unit ] - RATE_DEVIATION;
118
119 subDeviations [ unit ] = Math.ceil ( subDeviations [ unit ] );
120
121 if ( subDeviations [ unit ] <= 0.0 )
122 {
123 subDeviations [ unit ] = 0.0;
124 }
125
126 printStream.printf (
127 "%1$02d %2$03.0f %3$06.3f %4$06.3f %5$06.3f%n",
128 new Object [ ] {
129 new Integer ( unit ),
130 new Double ( Math.toDegrees (
131 preferredDirections [ unit ] ) ),
132 new Double ( templateActivations [ unit ] ),
133 new Double ( addDeviations [ unit ] ),
134 new Double ( subDeviations [ unit ] ) } );
135 }
136 }
137
138 /*********************************************************************
139 * Runs the trials, gathers statistics, and prints the results.
140 *********************************************************************/
141 public static void runTrials (
142 PrintStream printStream,
143 double [ ] preferredDirections )
144 throws Exception
145 //////////////////////////////////////////////////////////////////////
146 {
147 // initialize statistics objects
148
149 DescriptiveStatistics [ ] descriptiveStatisticsArray
150 = new DescriptiveStatistics [ DIRECTIONS ];
151
152 DescriptiveStatistics [ ] [ ]
153 averagePopulationVectorsDescriptiveStatistics
154 = new DescriptiveStatistics [ DIRECTIONS ] [ NEURONS ];
155
156 for ( int i = 0; i < DIRECTIONS; i++ )
157 {
158 descriptiveStatisticsArray [ i ]
159 = DescriptiveStatistics.newInstance ( );
160
161 for ( int j = 0; j < NEURONS; j++ )
162 {
163 averagePopulationVectorsDescriptiveStatistics [ i ] [ j ]
164 = DescriptiveStatistics.newInstance ( );
165 }
166 }
167
168 DescriptiveStatistics grandDescriptiveStatistics
169 = DescriptiveStatistics.newInstance ( );
170
171 // run the trial data
172
173 printStream.println ( "\n" + "trial actual predicted error" );
174
175 for ( int trial = 0; trial < TRIALS; trial++ )
176 {
177 int directionIndex = ( trial * DIRECTIONS ) / TRIALS;
178
179 double actualDirection
180 = directionIndex * 2.0 * Math.PI / DIRECTIONS;
181
182 double [ ] noisyActivations = new double [ NEURONS ];
183
184 for ( int unit = 0; unit < NEURONS; unit++ )
185 {
186 // record the activity from a single neuron for the given trial
187
188 noisyActivations [ unit ] = generateNoisyActivation (
189 preferredDirections [ unit ],
190 actualDirection );
191
192 // record the statistical data for this direction and this unit
193
194 averagePopulationVectorsDescriptiveStatistics
195 [ directionIndex ] [ unit ].addValue (
196 noisyActivations [ unit ] );
197 }
198
199 // Knowing only the preferred directions for each neuron and their
200 // current activations, predict the direction for this trial.
201
202 double predictedDirection = predictDirection (
203 preferredDirections,
204 noisyActivations );
205
206 // Compute the error between predicted and actual direction
207
208 double error = Math.abs ( Math.min (
209 predictedDirection - actualDirection,
210 actualDirection + 2.0 * Math.PI - predictedDirection ) );
211
212 // How variable is the error for the trials in a given direction?
213
214 descriptiveStatisticsArray [ directionIndex ].addValue ( error );
215
216 // How variable is the error from trial to trial?
217
218 grandDescriptiveStatistics.addValue ( error );
219
220 // trial actual predicted error
221
222 printStream.printf (
223 "%1$02d %2$03.0f %3$07.3f %4$07.3f%n",
224 new Object [ ] {
225 new Integer ( trial ),
226 new Double ( Math.toDegrees ( actualDirection ) ),
227 new Double ( Math.toDegrees ( predictedDirection ) ),
228 new Double ( Math.toDegrees ( error ) ) } );
229 }
230
231 printStream.println ( "\n" +
232 "direction / mean error / error std / mean-error / mean+error" );
233
234 for ( int i = 0; i < DIRECTIONS; i++ )
235 {
236 double direction = i * 2.0 * Math.PI / DIRECTIONS;
237
238 double meanError = descriptiveStatisticsArray [ i ].getMean ( );
239
240 double errorStd
241 = descriptiveStatisticsArray [ i ].getStandardDeviation ( );
242
243 // the mean error and standard deviation in error for a direction
244
245 printStream.printf (
246 "%1$03.0f %2$06.3f %3$06.3f %4$06.3f %5$06.3f%n",
247 new Double ( Math.toDegrees ( direction ) ),
248 new Double ( Math.toDegrees ( meanError ) ),
249 new Double ( Math.toDegrees ( errorStd ) ),
250 new Double ( Math.toDegrees ( meanError - errorStd ) ),
251 new Double ( Math.toDegrees ( meanError + errorStd ) ) );
252 }
253
254 // the mean error and standard deviation in error for all trials
255
256 printStream.println ( "\n" + "grand mean error: "
257 + Math.toDegrees ( grandDescriptiveStatistics.getMean ( ) ) );
258
259 printStream.println ( "\n" + "grand deviation: "
260 + Math.toDegrees (
261 grandDescriptiveStatistics.getStandardDeviation ( ) ) );
262
263 // print the mean activity from each neuron for each direction
264
265 printStream.println ( "\n" + "direction preferred average" );
266
267 for ( int i = 0; i < DIRECTIONS; i++ )
268 {
269 for ( int j = 0; j < NEURONS; j++ )
270 {
271 printStream.printf (
272 "%1$03.0f %2$03.0f %3$03.0f%n",
273 new Double [ ] {
274 new Double (
275 Math.toDegrees ( i * 2.0 * Math.PI / DIRECTIONS ) ),
276 new Double (
277 Math.toDegrees ( j * 2.0 * Math.PI / NEURONS ) ),
278 new Double (
279 Math.ceil (
280 averagePopulationVectorsDescriptiveStatistics
281 [ i ] [ j ]
282 .getMean ( ) ) ) } );
283 }
284 }
285 }
286
287 /*********************************************************************
288 * Generates activation for a neuron without random noise.
289 *********************************************************************/
290 public static double computeNoiselessActivation (
291 double preferredDirection,
292 double actualDirection )
293 //////////////////////////////////////////////////////////////////////
294 {
295 double angleFromPreferred
296 = preferredDirection - actualDirection;
297
298 if ( angleFromPreferred < 0.0 )
299 {
300 angleFromPreferred += 2.0 * Math.PI;
301 }
302
303 double computedActivation;
304
305 if ( ( angleFromPreferred <= Math.PI / 2 )
306 || ( angleFromPreferred >= 3 * Math.PI / 2 ) )
307 {
308 computedActivation
309 = RATE_SPONTANEOUS
310 + Math.cos ( angleFromPreferred )
311 * ( RATE_MAX - RATE_SPONTANEOUS );
312 }
313 else
314 {
315 computedActivation
316 = RATE_SPONTANEOUS
317 + Math.cos ( angleFromPreferred )
318 * ( RATE_SPONTANEOUS - RATE_MIN );
319 }
320
321 return computedActivation;
322 }
323
324 /*********************************************************************
325 * Generates activation for a neuron with random noise.
326 *********************************************************************/
327 public static double generateNoisyActivation (
328 double preferredDirection,
329 double actualDirection )
330 throws Exception
331 //////////////////////////////////////////////////////////////////////
332 {
333 double noiselessActivation = computeNoiselessActivation (
334 preferredDirection,
335 actualDirection );
336
337 double probability = random.nextDouble ( );
338
339 double noisyActivation = Math.ceil (
340 normInv (
341 probability,
342 noiselessActivation,
343 RATE_DEVIATION ) );
344
345 if ( noisyActivation <= 0.0 )
346 {
347 noisyActivation = 0.0;
348 }
349
350 return noisyActivation;
351 }
352
353 public static double normInv (
354 double probability,
355 double mean,
356 double standardDeviation )
357 throws MathException
358 //////////////////////////////////////////////////////////////////////
359 {
360 NormalDistribution normalDistribution
361 = distributionFactory.createNormalDistribution (
362 mean, standardDeviation );
363
364 return
365 normalDistribution.inverseCumulativeProbability ( probability );
366 }
367
368 /*********************************************************************
369 * Predicts the direction for this trial.
370 * Knowing only the preferred directions for each neuron and their
371 * current activations, predict the direction for this trial.
372 * This method is the heart of the algorithm.
373 *********************************************************************/
374 public static double predictDirection (
375 double [ ] preferredDirections,
376 double [ ] noisyActivations )
377 //////////////////////////////////////////////////////////////////////
378 {
379 double sumCosComponents = 0.0;
380
381 double sumSinComponents = 0.0;
382
383 for ( int unit = 0; unit < NEURONS; unit++ )
384 {
385 // weight the activation of each neuron by their cartesian
386 // components along their preferred directions
387
388 double cosComponent
389 = Math.cos ( preferredDirections [ unit ] )
390 * noisyActivations [ unit ];
391
392 double sinComponent
393 = Math.sin ( preferredDirections [ unit ] )
394 * noisyActivations [ unit ];
395
396 // sum the weighted contributions from each neuron for this trial
397
398 sumCosComponents += cosComponent;
399
400 sumSinComponents += sinComponent;
401 }
402
403 // Here is the predicted direction.
404
405 double predictedDirection = Math.atan2 (
406 sumSinComponents,
407 sumCosComponents );
408
409 // return a non-negative angle for the answer
410
411 if ( predictedDirection < 0.0 )
412 {
413 predictedDirection += 2.0 * Math.PI;
414 }
415
416 // Note the arctan adjustment is not necessary since
417 // the atan2(y,x) function was used.
418
419 return predictedDirection;
420 }
421
422 //////////////////////////////////////////////////////////////////////
423 //////////////////////////////////////////////////////////////////////
424 }