Package: sapicore
A project of the Computational Physiology Laboratory at Cornell University.Please see the onlinedocsfor complete documentation.A PyTorch-based framework for neuromorphic modeling.Sapicore is a framework that provides high level abstractions to help write
neuromorphic models using pytorch. Sapicore itself does not contain any concrete
models, instead each model may have its own repo that implements the Sapicore
components used by the model.Following this methodology will allow Sapicore to be used by multiple models
independently, without each polluting the other with different implementation
details or requirements.Sapicore models common to a lab can be placed in a package outside the
framework and re-used by other projects that want to use these common models.Sapicore supports model user-configuration using thetree-configpackage.
Similarly, Sapicore supports annotating properties and buffers for logging
to e.g.tensorboardxfor live display or usingnixioHDF5 based files for later analysis or
debugging.InstallationSapicore has minimal requirements. It requiresPython 3.7+Pytorch 1.5+ (seePyTorch installation).Scientific stack (see the list insetup.py).Tensorboard and tensorboardx (optional).The easiest way is to install them with conda as follows:conda install -c conda-forge numpy tqdm pandas ruamel.yaml tensorboard tensorboardxor using pip, simply (pip automatically installs the remaining dependencies):python -m pip install tensorboard tensorboardxOnce the dependencies are installed, to install Sapicore in the current
conda/pip environment:User installYou can install the latest stable Sapicore with:pip install sapicoreTo install the latest Sapicore from github, do:pip install https://github.com/cplab/sapicore/archive/refs/heads/main.zipDevelopment installTo install Sapicore for development and editing Sapicore itself:Clone sapicore from github:git clone https://github.com/cplab/sapicore.gitcdinto sapicore:cd sapicoreInstall it as an editable install:pip install -e .Example modelFollowing is brief runnable example. A similar but complete example with configuration and logging
can be found under sapicore/examples.importtorchfromsapicore.modelimportSapicoreModelfromsapicore.neuron.analogimportAnalogNeuronfromsapicore.synapseimportSapicoreSynapsefromsapicore.pipelineimportPipelineBasefromsapicore.learningimportSapicoreLearningclassSimpleNeuron(AnalogNeuron):"""Represents a neuron or matrix of neurons."""activation:torch.Tensordefforward(self,data:torch.tensor)->torch.tensor:self.activation=torch.clip(data,-2,2)returnself.activationclassSimpleSynapse(SapicoreSynapse):"""Represents a synapse connecting a neuron or matrix of neurons."""weight:torch.Tensordef__init__(self,**kwargs):super().__init__(**kwargs)# register weight as part of the state so it is saved with the modelself.register_buffer("weight",torch.zeros(0))self.weight=torch.normal(0,1,size=(5,))defforward(self,data:torch.tensor)->torch.tensor:returndata*self.weightclassSimpleLearning(SapicoreLearning):"""Learns the synapse weight based on pre-post activation."""defapply_learning(self,pre_neuron:SimpleNeuron,synapse:SimpleSynapse,post_neuron:SimpleNeuron,**kwargs):synapse.weight*=torch.abs(pre_neuron.activation)*\torch.abs(post_neuron.activation)classMyModel(SapicoreModel):"""Network model that contains neurons/synapses."""neuron_1:SimpleNeuronsynapse:SimpleSynapseneuron_2:SimpleNeuronlearning:SimpleLearningdef__init__(self,**kwargs):super().__init__(**kwargs)self.neuron_1=SimpleNeuron()self.synapse=SimpleSynapse()self.neuron_2=SimpleNeuron()self.add_neuron(None,'entry_neuron',self.neuron_1)self.add_synapse(self.neuron_1,'synapse',self.synapse)self.add_neuron(self.synapse,'exit_neuron',self.neuron_2)self.learning=SimpleLearning()self.add_learning_rule('learning',self.learning)definitialize_learning_state(self)->None:self.learning.initialize_state()defforward(self,data:torch.tensor)->torch.tensor:data=self.neuron_1(data)data=self.synapse(data)data=self.neuron_2(data)returndatadefapply_learning(self,**kwargs)->None:self.learning.apply_learning(self.neuron_1,self.synapse,self.neuron_2)classSimplePipeline(PipelineBase):"""Training pipeline."""defrun(self)->None:use_cuda=torch.cuda.is_available()cuda_device=torch.device("cuda:0"ifuse_cudaelse"cpu")model=MyModel()model.initialize_state()model.initialize_learning_state()model.to(cuda_device)print('Pre-learning weight: ',model.synapse.weight.cpu().numpy())# these models don't use gradientswithtorch.no_grad():foriinrange(3):# fake datadata=torch.normal(0,1,size=(5,))# pass it through the modelmodel.forward(data)# apply model learningmodel.apply_learning()print('Post-learning weight: ',model.synapse.weight.cpu().numpy())if__name__=='__main__':# create and run the modelpipeline=SimplePipeline()pipeline.run()When run, this print:Pre-learning weight:  [-0.95982265 -0.2735969   0.6473335  -0.37592512  0.05847792]
Post-learning weight:  [-6.0495706e-09 -8.3768668e-08  3.3906079e-05 -3.3586942e-09
  1.3144294e-32]AuthorsA project of the Computational Physiology Laboratory at Cornell University.Neuromorphic algorithms by Ayon Borthakur and Thomas Cleland.Framework architecture by Matthew Einhorn.