CARLsim  5.0.0
CARLsim: a GPU-accelerated SNN simulator
Tutorial 7: Parameter Tuning Interface (PTI)
Author
Kristofor D. Carlson

7.1 Introduction

In this tutorial we introduce the parameter tuning interface and how to tune the weight ranges of a simple SNN to achieve a particular target firing rate. We will be tuning four parameter values that represent the weight ranges. The layout of the SNN is as follows. We have 10 input excitatory neurons, 10 regular spiking (RS) excitatory neurons that receive this input, and 10 fast spiking (FS) izhikevich neurons that receive input from the excitatory RS neurons. Additionally, the inhibitory group is connected to the RS excitatory group and the RS excitatory group is connected to itself recurrently. There are therefore 4 connection weight ranges to be tuned. The goal of the tuning is make the RS excitatory group have an average firing rate of 10 Hz and make the FS inhibitory group have an average firing rate of 20 Hz.

All the required files are found in the doc/source/tutorial/3_pti directory. The user is encouraged to follow along and experiment with the source code found there.

This tutorial assumes:

  • ECJ version 22 or greater is installed.
  • CARLsim 5.0 is installed.
  • CARLsim5 and ECJ environment variables are configured correctly.

The overview of parameter tuning is as follows. ECJ handles all steps of the evolutionary algorithm (EA) except for the fitness evaluation which is done by CARLsim through the implementation of an experiment class function called run.

A more in-depth overview of CARLsim and ECJ can be found in Chapter 10: ECJ.

7.2 The ECJ Parameter File

This example has an ECJ parameter file named TuneFiringRatesECJExperiment.params. As mentioned in Chapter 10: ECJ, users must first configure this file if they want to parameter tune CARLsim programs.

We first specify that we want a maximum number of 50 generations and a population size of 10. This means 10 individuals (each a separate SNN) will be run in parallel by CARLsim:

generations = 50
pop.subpop.0.size = 10

There are four parameter values to be tuned so we set:

pop.subpop.0.species.genome-size = 4

We will allow all four weight ranges to be tuned within the same parameter range although each individual parameter can be given it's own range (see Chapter 10: ECJ). The minimum weight value for each weight is then 0.0005 while the maximum value is 0.5 as shown below:

pop.subpop.0.species.min-gene = 0.0005
pop.subpop.0.species.max-gene = 0.5

Finally we must specify the name of the carlsim binary that ECJ should execute every generation to obtain fitness values. This is done below. Notice there is a $ before the filename. This indicates that the location of the file is relative to the location of the parameter file.

eval.problem.simulationCommand = $carlsim_tuneFiringRatesECJ

Of course any of these parameters can be changed. For more information on ECJ parameter file configuration please visit the ECJ homepage.

7.3 The Experiment Class

We next discuss the CARLsim code found in the experiment class. This code is found in the main_tuneFiringRatesECJ.cpp file. Users should use this tutorial as template for writing their own code. First a specific experiment class must be inherited from the base class and default constructor must be defined.:

class TuneFiringRatesECJExperiment : public Experiment {
public:
TuneFiringRatesECJExperiment() {}

After that, the user should only be concerned with writing the run member function.

...
void run(const ParameterInstances &parameters, std::ostream &outputStream) const {
int indiNum = parameters.getNumInstances();
int poissonGroup[indiNum];
int excGroup[indiNum];
int inhGroup[indiNum];
SpikeMonitor* excMonitor[indiNum];
SpikeMonitor* inhMonitor[indiNum];
float excHz[indiNum];
float inhHz[indiNum];
float excError[indiNum];
float inhError[indiNum];
float fitness[indiNum];
...

Notice we can use the parameters object to query how many instances/individuals we have defined in the ECJ parameter file. Now that we have that value, we manually create arrays neuron groups of size indiNum. In our case we have defined 10 individuals per generation.

Next we create our CARLsim network object and assign the 4 parameter values generated by ECJ to our 10 SNN individuals. We iterate over all 10 individuals and assign them 4 distinct parameters each.

CARLsim* const network = new CARLsim("tuneFiringRatesECJ", GPU_MODE, SILENT);
for(unsigned int i = 0; i < parameters.getNumInstances(); i++) {
poissonGroup[i] = network->createSpikeGeneratorGroup("poisson", NUM_NEURONS, EXCITATORY_NEURON);
excGroup[i] = network->createGroup("exc", NUM_NEURONS, EXCITATORY_NEURON);
inhGroup[i] = network->createGroup("inh", NUM_NEURONS, INHIBITORY_NEURON);
network->setNeuronParameters(excGroup[i], REG_IZH[0], REG_IZH[1], REG_IZH[2], REG_IZH[3]);
network->setNeuronParameters(inhGroup[i], FAST_IZH[0], FAST_IZH[1], FAST_IZH[2], FAST_IZH[3]);
network->setConductances(true,COND_tAMPA,COND_tNMDA,COND_tGABAa,COND_tGABAb);
network->connect(poissonGroup[i], excGroup[i], "random", RangeWeight(parameters.getParameter(i,0)), 0.5f, RangeDelay(1));
network->connect(excGroup[i], excGroup[i], "random", RangeWeight(parameters.getParameter(i,1)), 0.5f, RangeDelay(1));
network->connect(excGroup[i], inhGroup[i], "random", RangeWeight(parameters.getParameter(i,2)), 0.5f, RangeDelay(1));
network->connect(inhGroup[i], excGroup[i], "random", RangeWeight(parameters.getParameter(i,3)), 0.5f, RangeDelay(1));
}

We then setup the SNNs, begin recording their spike data with a SpikeMonitor, and run the SNNs.

...
network->setupNetwork();
...
excMonitor[i] = network->setSpikeMonitor(excGroup[i], "/dev/null");
inhMonitor[i] = network->setSpikeMonitor(inhGroup[i], "/dev/null");
excMonitor[i]->startRecording();
inhMonitor[i]->startRecording();
...
network->runNetwork(runTime,0);

Finally we stop recording, get the average firing rate of each group and compute a fitness function which is output to ECJ using standard Linux streams.

excMonitor[i]->stopRecording();
inhMonitor[i]->stopRecording();
excHz[i] = excMonitor[i]->getPopMeanFiringRate();
inhHz[i] = inhMonitor[i]->getPopMeanFiringRate();
excError[i] = fabs(excHz[i] - EXC_TARGET_HZ);
inhError[i] = fabs(inhHz[i] - INH_TARGET_HZ);
fitness[i] = 1/(excError[i] + inhError[i]);
outputStream << fitness[i] << endl;

To compile and run the tuning framework we simply type:

make
./launchCARLsimECJ.sh

The simulation should run to completion and output the best fitness each generation.

This results in an ECJ out.stat file that we talk about in the next section.

7.4 ECJ Output Files

The output.stat file generated contains the best fitness for that generation along with the four parameter values associated with that individual. CARLsim users can then use these parameters for their tuned SNN models.

See also
Chapter 10: ECJ
GPU_MODE
@ GPU_MODE
model is run on GPU card(s)
Definition: carlsim_datastructures.h:116
CARLsim::createSpikeGeneratorGroup
int createSpikeGeneratorGroup(const std::string &grpName, int nNeur, int neurType, int preferredPartition=ANY, ComputingBackend preferredBackend=CPU_CORES)
creates a spike generator group
Definition: carlsim.cpp:1780
RangeWeight
a range struct for synaptic weight magnitudes
Definition: carlsim_datastructures.h:312
SpikeMonitor
Class SpikeMonitor.
Definition: spike_monitor.h:120
CARLsim::setConductances
void setConductances(bool isSet)
Sets default values for conduction decay and rise times or disables COBA alltogether.
Definition: carlsim.cpp:1789
SpikeMonitor::stopRecording
void stopRecording()
Ends a recording period.
Definition: spike_monitor.cpp:208
EXCITATORY_NEURON
#define EXCITATORY_NEURON
Definition: carlsim_definitions.h:77
SpikeMonitor::getPopMeanFiringRate
float getPopMeanFiringRate()
Returns the mean firing rate of the entire neuronal population.
Definition: spike_monitor.cpp:77
RangeDelay
a range struct for synaptic delays
Definition: carlsim_datastructures.h:279
CARLsim::setNeuronParameters
void setNeuronParameters(int grpId, float izh_a, float izh_a_sd, float izh_b, float izh_b_sd, float izh_c, float izh_c_sd, float izh_d, float izh_d_sd)
Sets Izhikevich params a, b, c, and d with as mean +- standard deviation.
Definition: carlsim.cpp:1816
CARLsim
CARLsim User Interface This class provides a user interface to the public sections of CARLsimCore sou...
Definition: carlsim.h:138
SpikeMonitor::startRecording
void startRecording()
Starts a new recording period.
Definition: spike_monitor.cpp:201
CARLsim::connect
short int connect(int grpId1, int grpId2, const std::string &connType, const RangeWeight &wt, float connProb, const RangeDelay &delay=RangeDelay(1), const RadiusRF &radRF=RadiusRF(-1.0), bool synWtType=SYN_FIXED, float mulSynFast=1.0f, float mulSynSlow=1.0f)
Connects a presynaptic to a postsynaptic group using fixed/plastic weights and a range of delay value...
Definition: carlsim.cpp:1740
SILENT
@ SILENT
Silent mode, no output is generated.
Definition: carlsim_datastructures.h:95
CARLsim::setSpikeMonitor
SpikeMonitor * setSpikeMonitor(int grpId, const std::string &fileName)
Sets a Spike Monitor for a groups, prints spikes to binary file.
Definition: carlsim.cpp:1973
INHIBITORY_NEURON
#define INHIBITORY_NEURON
Definition: carlsim_definitions.h:76
CARLsim::createGroup
int createGroup(const std::string &grpName, int nNeur, int neurType, int preferredPartition=ANY, ComputingBackend preferredBackend=CPU_CORES)
creates a group of Izhikevich spiking neurons
Definition: carlsim.cpp:1764