Skip to content

Commit

Permalink
Tutorials (#35)
Browse files Browse the repository at this point in the history
* Updated older tutorials.
* Requires numpy<2.0 to avert failed check from nixio (`np.unicode_` was removed from Numpy 2.0).
  • Loading branch information
rm875 authored Jul 14, 2024
1 parent 950cd4e commit a8b7300
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 62 deletions.
2 changes: 1 addition & 1 deletion sapicore/pipeline/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class SimpleSimulator(Pipeline):
Note
----
For a more complex demonstration involving data processing, selection, sampling, and cross-validation,
see `tutorials/basic_training.py`. If using this script with data (be it Data or Tensor),
see `tutorials/basic_experiment.py`. If using this script with data (be it Data or Tensor),
it should be curated and coerced to the shape buffer X labels ahead of time.
"""
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
packages=find_packages(),
install_requires=[
"h5py",
"numpy",
"numpy<2.0",
"scipy",
"pandas",
"torch",
Expand Down
2 changes: 1 addition & 1 deletion tutorials/EPL/EPL.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ model:
# Simulation settings.
simulation:
# simulation duration in steps (by default, 1 ms).
steps: 2000
steps: 1000

# optional constant current to be added to the data.
current: 1000.0
Expand Down
4 changes: 2 additions & 2 deletions tutorials/EPL/synapses/GC--MC.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ model:
fixed:
weight_max: 0.0
weight_min: -100.0
alpha_plus: 0.1
alpha_minus: 0.1
alpha_plus: 1.0
alpha_minus: 1.0

random:
weights:
Expand Down
4 changes: 2 additions & 2 deletions tutorials/EPL/synapses/MC--GC.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ model:
fixed:
weight_max: 150.0
weight_min: 0.0
alpha_plus: 0.1
alpha_minus: 0.1
alpha_plus: 1.0
alpha_minus: 1.0

random:
weights:
Expand Down
25 changes: 9 additions & 16 deletions tutorials/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
# Tutorials

Scripts in this directory are intended to facilitate network development and testing by new users.
Basic tutorials will introduce and drill into the following features:


* Components: neurons (spiking, analog), synapses (STDP).
* Configuration: dictionary/YAML API, parameter sweeps.
* Simulation: pipelines, network API.
* Input: current injection, data loading & synthesis.
* Visualization: tensorboard.


Advanced tutorials may include:

* Numeric Approximation (Euler vs. RK4).
* Neuromorphic Normalization (Analog).
* Oscillators and Pyramidal-Interneuron Gamma (PING).
* Exploring Spike-Timing-Dependent Plasticity (STDP).
* Simulation: pipeline, network, and model API.
* Input: data loading and synthesis.
* Visualization: matplotlib and tensorboard (legacy).

* Feedforward Networks (LIF/IZ).
* Recurrent Networks (LIF/IZ).
Covered topics include:

* Olfactory Bulb Model.
* Numeric approximation (Euler vs. RK4).
* Oscillators and pyramidal-interneuron gamma (PING).
* Spike-timing-dependent plasticity (STDP).
* Setting up recurrent networks of LIF/IZ neurons.
* Designing cross validated simulation experiments.
12 changes: 10 additions & 2 deletions tutorials/basic_training.py → tutorials/basic_experiment.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
""" Train a model on a real-world dataset with cross validation. """
""" Train a model on a real-world dataset with cross validation.
Note
----
This tutorial is obsolete and will be superseded in the flagship model repository.
For now, users are encouraged to run `sapicore/pipeline/simple.py -config ../../tutorials/EPL/EPL.yaml`.
"""
import os
import logging
from argparse import ArgumentParser
Expand Down Expand Up @@ -128,7 +135,8 @@ def run(self):
action="store",
dest="config",
metavar="FILE",
required=True,
required=False,
default=os.path.join(os.getcwd(), "EPL", "EPL.yaml"),
help="Path to a model-simulation configuration YAML.",
)
parser.add_argument(
Expand Down
89 changes: 52 additions & 37 deletions tutorials/basic_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,27 @@
import matplotlib.pyplot as plt


# experiment with the following parameters:
INPUT = 5.0

A = 0.1
B = 0.27
C = -65.0

DELAY = 5.0
WEIGHT_MIN = -5.0
WEIGHT_MAX = 20.0
WEIGHT_MULTIPLIER = 5.0

NUM_UNITS = 2
STEPS = 250
LEARNING = False
# EXPERIMENT WITH THE FOLLOWING PARAMETERS.
INPUT = 2.0

# IZ neuron parameters.
NEURON_PARAMS = {"num_units": 2, "a": 0.1, "b": 0.27, "c": -65.0}

# STDP synapse parameters.
WEIGHT_MULTIPLIER = 1.0
STDP_PARAMS = {
"delay_ms": 5.0,
"weight_min": -1.0,
"weight_max": 1.0,
"alpha_plus": 1.0,
"alpha_minus": 1.0,
"tau_plus": 1.0,
"tau_minus": 1.0,
}

# Simulation parameters.
STEPS = 500
LEARNING = True

INTEGRATOR = RungeKutta(order=4)

Expand All @@ -36,34 +42,31 @@ class BasicSimulation(Pipeline):

_config_props_ = ("num_units", "steps")

def __init__(self, num_units: int, steps: int, learning: bool = True, **kwargs):
def __init__(self, steps: int, learning: bool = True, **kwargs):
super().__init__(**kwargs)

self.steps = steps
self.num_units = num_units
self.learning = learning

# fixes RNG seed across scientific stack libraries for consistency.
fix_random_seed(9846)
fix_random_seed(314)

def run(self):
# initialize two default IZ ensembles (with RK4 numeric approximation).
l1 = IZEnsemble(identifier="L1", num_units=self.num_units, a=A, b=B, c=C, integrator=INTEGRATOR)
l2 = IZEnsemble(identifier="L2", num_units=self.num_units, a=A, b=B, c=C, integrator=INTEGRATOR)
l1 = IZEnsemble(identifier="L1", integrator=INTEGRATOR, **NEURON_PARAMS)
l2 = IZEnsemble(identifier="L2", integrator=INTEGRATOR, **NEURON_PARAMS)

# initialize a default excitatory STDP synapse with random weights.
syn = STDPSynapse(
src_ensemble=l1, dst_ensemble=l2, delay_ms=DELAY, weight_max=WEIGHT_MAX, weight_min=WEIGHT_MIN
)
syn = STDPSynapse(src_ensemble=l1, dst_ensemble=l2, **STDP_PARAMS)

# connect the layers all-to-all. see `engine.synapse.Synapse.connect` for additional settings.
# you may also edit the `syn.connections` attribute directly (a matrix of shape source X destination).
syn.connect("all")

# multiply the random weights.
# scale the random weights to match cell parameterization.
syn.weights = syn.weights * WEIGHT_MULTIPLIER

# toggle learning on/off based on given setting.
# toggle learning on/off.
syn.set_learning(self.learning)

# initialize a network object and add our components.
Expand All @@ -72,17 +75,20 @@ def run(self):
network.add_ensembles(l1, l2)
network.add_synapses(syn)

# conjure an input data tensor.
# conjure an example input data tensor.
data = torch.ones_like(l1.voltage) * INPUT

# initialize tensors to store rolling simulation data.
l1_voltage = torch.zeros((self.steps, self.num_units))
l2_voltage = torch.zeros((self.steps, self.num_units))
l1_voltage = torch.zeros((self.steps, l1.num_units))
l2_voltage = torch.zeros((self.steps, l2.num_units))

l1_spiked = torch.zeros((self.steps, self.num_units))
l2_spiked = torch.zeros((self.steps, self.num_units))
l1_spiked = torch.zeros((self.steps, l1.num_units))
l2_spiked = torch.zeros((self.steps, l2.num_units))

l1_l2_weights = torch.zeros((self.steps, l2.num_units, l1.num_units))

# run the simulation for `steps`.
print(str(network))
for i in range(self.steps):
network(data)

Expand All @@ -92,16 +98,18 @@ def run(self):
l1_spiked[i, :] = l1.spiked
l2_spiked[i, :] = l2.spiked

l1_l2_weights[i, :, :] = syn.weights

# alternatively, accumulate data into a nested dictionary using a list comprehension.
# output = [network(data) for _ in range(self.steps)]

# plot all voltages.
plt.figure()
plt.plot(l1_voltage)
plt.plot(l1_voltage.numpy())
plt.title("Layer I Voltages")

plt.figure()
plt.plot(l2_voltage)
plt.plot(l2_voltage.numpy())
plt.title("Layer II Voltages")

# plot spikes.
Expand All @@ -111,16 +119,23 @@ def run(self):

# plot weights.
plt.figure()
plt.grid(False)

plt.imshow(syn.weights, cmap=plt.cm.viridis, aspect="auto")
plt.clim(torch.min(syn.weights), torch.max(syn.weights))
plt.title("Final Synaptic Weights L1 (x) --> L2 (y)")
plt.imshow(syn.weights, cmap=plt.cm.viridis, aspect=1)

plt.title("Synaptic Weights L1 (x) to L2 (y)")
plt.clim(torch.min(syn.weights), torch.max(syn.weights))
plt.colorbar()
plt.show()

plt.figure()
plt.title("Evolution of Synaptic Weights L1 (x) --> L2 (y)")

for i in range(l1_l2_weights.shape[1]):
for j in range(l1_l2_weights.shape[2]):
plt.plot(l1_l2_weights[:, i, j].numpy())

plt.show()


if __name__ == "__main__":
BasicSimulation(num_units=NUM_UNITS, steps=STEPS, learning=LEARNING).run()
BasicSimulation(steps=STEPS, learning=LEARNING).run()

0 comments on commit a8b7300

Please sign in to comment.