scieee AI-readable full text Open interactive document viewer

Rockpool Documentation

Muir, Dylan; Bauer, Felix; Weidel, Philipp

Abstract

Rockpool is a Python machine-learning package for designing, building and deploying event-driven neural network applications, particularly for Neuromorphic computing hardware. Rockpool provides a simple and comfortable API for working with spiking neural networks. Along with PyTorch and Jax support, Rockpool provides accellerated training for SNNs, and an extensible deployment pipeline.

Full text

Rockpool Release 3.0.3 Dylan Muir , Felix Bauer , Philipp W eidel Dec 17, 2025 CONTENTS 1 A bout R oc kpool 3 2 Installing Roc kpool and contributing 7 3 A simple intr oduction to spiking neurons 11 4 Introduction to R oc kpool 17 5 Hello MNIST with R oc kpool 29 6 W orking with time series data 43 7 Standard modules 57 8 The sharp points of R oc kpool 59 9 Lo w -le v el Module API 67 10 High-le v el TimedModule API 75 11 [] Lo w -le v el functional API 89 12 T raining a Roc kpool netw ork with Jax 93 13 A dvanced Jax training topics 103 14 Building R oc kpool modules with T orc h 107 15 T raining a Roc kpool netw ork with T orc h 117 16 Ho w T o: Configure and perf orm constrained optimization in R oc kpool 123 17 Gradient descent training of a rate-based recurr ent netw or k 133 18 T raining a spiking netw ork with Jax 143 19 T raining a spiking netw ork with T orc h 159 20 Easter with R oc kpool 165 21 A dv ersarial training 179 22 T raining an audio classification t ask using T orch 191 i 23 W a v eSense: T raining a Spiking N eural Ne tw ork with T emporal Con v olutions 201 24 The SynNe t ar chitectur e 215 25 Ov ervie w of the X ylo ™ f amil y 223 26 Introduction to X y lo ™ A udio 229 27 Quic k -start with X ylo ™ SNN cor e 247 28 T raining a spiking netw ork to deplo y to the X ylo ™ digital SNN 259 29 Using the analog frontend model 277 30 Quic k start with X ylo ™ A udio 3 285 31 Operation types of X yloA udio 3 287 32 Using AFESim as an audio transf orm 289 33 Using X yloSamna and X yloMonitor to deplo y a model on X yloA udio 3 HDK 293 34 Bandpass Filtering In A udio Front End (AFE) Of X ylo ™ A udio 3 301 35 Introduction to X y lo ™ IMU 307 36 The X ylo ™ IMU prepr ocessing inter face 329 37 Specifying IMU prepr ocessing parameters 335 38 Ov ervie w of Dynap-SE2 347 39 Quic k Start with Dynap-SE2 353 40 DynapSim Neur on Model 371 41 T raining a spiking netw ork to deplo y to Dynap-SE2 389 42 Computational graphs in R ockpool 403 43 Graph mapping 411 44 Import / e xport betw een toolchains with NIR 417 45 R oc kpool P arameter handling 421 46 T ypehints in R oc kpool 423 47 P er f ormance benchmarks f or LIF la y ers in R oc kpool 425 48 Full API summary f or Roc kpool 431 49 Chang e log 1647 50 UML diagrams f or Roc kpool 1663 51 Using the bac k end management sy stem 1671 52 Dynap-SE2 de v eloper notes 1675 ii 53 No tes f or de v elopers 1691 Python Module Inde x 1695 iii iv Rockpool, Release 3.0.3 R ockpool is a Python pac kage f or w orking with dynamical neural netw ork architectures, par ticular l y f or designing e v ent-dr iv en netw orks f or Neuromorphic computing hardware. R ockpool pro vides a con v enient interface f or designing, training and e v aluating recur rent netw orks, which can operate both with continuous-time dynamics and e v ent-dr iv en dynamics. R ockpool is an open-source project manag ed b y SynSense. CONTENTS 1 Rockpool, Release 3.0.3 2 CONTENTS CHAPTER ONE ABOUT R OCKPOOL R ockpool is an open source project released b y SynSense. 1.1 Citing Rockpool If y ou use R ockpool in y our academic w ork, w e w ould appreciate a citation! Y ou can cite us as DR Muir , F Bauer & P W eidel. (2019). R oc kpool Documentaton. Zenodo. https:// doi.org/ 10.5281/ zenodo.3773845 Or using the bibte x block: @misc {muir_dylan_2019_4639684, author = {Muir, Dylan R . and Bauer, Felix and Weidel, Philipp}, title = {Rockpool Documentaton}, month = sep, year = 2019 , publisher = {Zenodo}, doi = { 10.5281 / zenodo .3773845 }, url = {https: // doi . org / 10.5281 / zenodo .3773845 } } 3 Rockpool, Release 3.0.3 10 Chapter 2. Installing Rockpool and contributing CHAPTER THREE A SIMPLE INTRODUCTION T O SPIKIN G NEURONS Spiking neural netw orks are considered to be the thir d g eneration of neural netw orks, preceeded b y McCulloch-Pitts threshold neurons (“first g eneration”) which produced digital outputs and Artificial Neural N etw orks with continuous activ ations, lik e sigmoids and h yperbolic tang ets, (“second g eneration”) that are commonl y used these day s. 3.1 Ar tificial N euron (AN) Model The standard artificial neuron model used most commonl y in ANNs and DNNs is a simple equation  𝑦 = Θ( 𝑊 . 𝑥 + 𝑏 ) where Θ is typicall y a non linear activ ation function such as a sigmoid or hyperbolic tang ent function. [1]: from IPython.display import Image Image(filename = "AN-neuron.png" , width = 300 ) [1]: No te that the output depends onl y on the instant aneous in puts. The neuron does no t ha v e an internal st ate that w ould affect its output. This is where spiking neurons and spiking neural netw orks (SNNs) come into pla y . The y add an additional dependence on the cur rent state of the neuron, and include e xplicit temporal dynamics. The y also communicate with each other using pulsed e v ents called “spik es”. 3.2 Leaky Integrate and Fire (LIF) model [2]: Image(filename = "LIF-neuron-full.png" , width = 500 ) 11 Rockpool, Release 3.0.3 [2]: Diagram of a simple spiking neuron. In puts and outputs are ser ies of e v ent pulses called “spik es”. The neuron imple- ments temporal dynamics when integrating the input spik es, and when updating its internal state. 3.2.1 Internal state — “membrane potential” One of the simplest models of spiking neurons is the Leaky Integrate and Fire (LIF) neuron model, where the state of the neuron, kno wn as the membr ane pot ential 𝑉 𝑚 ( 𝑡 ) , depends on its previous s tate in addition to its in puts. The dynamics of this state can be described as f ollo ws: 𝜏 𝑚 · 𝑑𝑉 𝑚 /𝑑𝑡 = − 𝑉 𝑚 ( 𝑡 ) + 𝐼 𝑠 ( 𝑡 ) + 𝐼 𝑏 where 𝜏 𝑚 is the membrane time cons tant, which determines ho w muc h the neuron depends on its pre vious s tates and inputs and theref ore defines the neuron ’ s memory; and 𝐼 𝑏 is a cons tant bias. The linear equation abo v e descr ibes the dynamics of a sys tem with “leaky” dynamics ie o v er time, the membrane potential 𝑉 slo w ly “leaks” to 0 . This is wh y the neuron model is ref er red to as a Leaky Integrate and Fire N euron model . N ote that there is an e xplicit notion of time, which does not occur in the s tandard ar tificial neuron model abo v e. 3.2.2 Synaptic inputs Spiking input is integrated onto the membrane of the neuron via a synaptic cur rent 𝐼 𝑠 ( 𝑡 ) , which has its o wn dynamics: 𝜏 𝑠 · 𝑑𝐼 𝑠 /𝑑𝑡 + 𝐼 𝑠 ( 𝑡 ) = ∑︀ 𝑖 𝑤 𝑖 · ∑︀ 𝑗 𝛿 ( 𝑡 − 𝑡 𝑖 𝑗 ) where ∑︀ 𝑗 𝛿 ( 𝑡 − 𝑡 𝑖 𝑗 ) is a stream of spik es from presynaptic neuron 𝑖 occur r ing at times 𝑡 𝑖 𝑗 . The v alues 𝑤 𝑖 represent the cor responding w eights. The synaptic cur rents deca y to w ards zero under the synaptic time constant 𝜏 𝑠 . Let ’ s simulate the synapse and membrane dynamics with a random spike train in put, and see ho w the y e v ol v e. F irs t w e will g enerate a random (“P oisson”) spike train, b y thresholding random noise. [3]: # - Import some useful packages import numpy as np import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt (continues on ne xt page) 12 Chapter 3. A simple introduction to spiking neurons Rockpool, Release 3.0.3 (continued from pre vious pag e) % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 [4]: # - Generate a random spike train T = 100 spike_prob = 0.1 spikes = ( np . random . rand( T, ) < spike_prob ) # - Plot the spike train plt . figure() plt . stem(spikes, use_line_collection = True ) plt . xlabel( "Time (ms)" ) plt . title( "Random spikes" ); Belo w w e implement the synaptic and membrane dynamics b y iterating o v er time, and computing the synapse and membrane v alues in the simples t possible w a y . [5]: # - A simple Euler solver for the synapse and membrane. One time step is 1 ms tau_m = 10 # ms tau_s = 10 # ms V_m = 0 # Initial membrane potential I_s = 0 # Initial synaptic current I_bias = 0 # Bias current V_m_t = [] I_s_t = [] # - Loop over time and solve the synapse and membrane dynamics for t in range (T): (continues on ne xt page) 3.2. Leaky Integrate and Fire (LIF) model 13 Rockpool, Release 3.0.3 (continued from pre vious pag e) # Take account of input spikes I_s = I_s + spikes[t] # Integrate synapse dynamics dI_s = - I_s / tau_s I_s = I_s + dI_s # Integrate membrane dynamics dV_m = ( - V_m + I_s + I_bias) / tau_m V_m = V_m + dV_m # Save data V_m_t . append(V_m) I_s_t . append(I_s) [6]: plt . figure() spike_times = np . argwhere(spikes) plt . plot(spike_times, np . zeros( len (spike_times)), "." ) plt . plot(I_s_t) plt . xlabel( "Time (ms)" ) plt . ylabel( "$I_s$" ) plt . title( "Synaptic current $I_s$" ) plt . legend([ "Input spikes" , "Synaptic current" ]) plt . figure() plt . plot(spike_times, np . zeros( len (spike_times)), "." ) plt . plot(V_m_t, c = "purple" ) plt . xlabel( "Time (ms)" ) plt . ylabel( "$v_m$" ) plt . legend([ "Input spikes" , "Membrane potential" ]) plt . title( "Membrane potential $V_m$" ); 14 Chapter 3. A simple introduction to spiking neurons Rockpool, Release 3.0.3 3.2.3 Spiking neuron output The output of the LIF neuron is binar y and instantaneous. It is 1 only when 𝑉 reac hes a threshold value 𝑉 𝑡ℎ = 1 , upon which the membrane potential is immediatel y reset b y subtracting one. 𝑉 𝑚 ( 𝑡 + 𝛿 ) = 𝑉 𝑚 ( 𝑡 ) − 𝑉 𝑡ℎ , if 𝑉 𝑚 ( 𝑡 ) ≥ 𝑉 𝑡ℎ 𝑉 𝑚 ( 𝑡 + 𝛿 ) = 𝑉 𝑚 ( 𝑡 ) , other wise This instantanious output of 1 is typicall y ref er red to as a “spike” at time 𝑡 . A series of such spik es will hence f or th be ref er red to as 𝑠 ( 𝑡 ) . 𝑠 ( 𝑡 )=1 , if 𝑉 𝑚 ( 𝑡 ) ≥ 𝑉 𝑡ℎ 𝑠 ( 𝑡 )=0 , other wise W e can include spik e g eneration and reset into the LIF simulation: [7]: # - A simple Euler solver for the synapse and membrane, including spike generation. One ␣ ˓ → time step is 1 ms tau_m = 10 # ms tau_s = 10 # ms V_m = 0 # Initial membrane potential V_th = 1 # Spiking threshold potential I_s = 0 # Initial synaptic current I_bias = 0 # Bias current V_m_t = [] I_s_t = [] spikes_t = [] # - Loop over time and solve the synapse and membrane dynamics for t in range (T): # Take account of input spikes I_s = I_s + spikes[t] # Integrate synapse dynamics dI_s = - I_s / tau_s (continues on ne xt page) 3.2. Leaky Integrate and Fire (LIF) model 15 Rockpool, Release 3.0.3 (continued from pre vious pag e) I_s = I_s + dI_s # Integrate membrane dynamics dV_m = ( 1 / tau_m) * ( - V_m + I_s + I_bias) V_m = V_m + dV_m # - Spike detection if V_m > V_th: # - Reset V_m = V_m - 1 # - Save the spike time spikes_t . append(t) # Save data V_m_t . append(V_m) I_s_t . append(I_s) [8]: plt . figure() plt . plot(spikes_t, np . zeros( len (spikes_t)), "." ) plt . plot(V_m_t, c = "purple" ) plt . plot([ 0 , T], [V_th, V_th], "k:" ) plt . xlabel( "Time (ms)" ) plt . ylabel( "$v_m$" ) plt . legend([ "Output spikes" , "Membrane potential" , "Spiking threshold" ]) plt . title( "Membrane potential $V_m(t)$" ); The code abo v e solv es the dynamic eq uations of the neuron and synaps using a F orw ar d Euler sol v er . This is a v er y simple fix ed time-step sol v er , which uses the s tate at 𝑡 to to produce the state at 𝑡 + 𝑑𝑡 . The dynamic equations are con v er ted into temporal difference equations to sol v e f or ˙ 𝐼 𝑠 ( 𝑡 ) and ˙ 𝑉 𝑚 ( 𝑡 ) , then uses these to produce 𝐼 𝑠 ( 𝑡 + 𝑑𝑡 ) and 𝑉 𝑚 ( 𝑡 + 𝑑𝑡 ) . F or w ard Euler sol v ers are conv enient f or their simplicity , but the y ha v e dra w-bac ks in ter ms of numer ical accuracy . In par ticular , the time constants 𝜏 𝑠 and 𝜏 𝑚 mus t be at leas t 10 time long er than 𝑑𝑡 to produce an accurate simulation of the dynamics. 16 Chapter 3. A simple introduction to spiking neurons CHAPTER F OUR INTRODUCTION T O ROCKPOOL R ockpool is designed to let y ou design, simulate, train and test dynamical neural netw orks – in contrast to s tandard ANNs, these netw orks include e xplicit temporal dynamics and simulation of time. R oc kpool contains se v eral types of neuron simulations, including continuous-time (“rate”) models as w ell as e v ent-dr iv en spiking models. R ockpool suppor ts se v eral simulation back -ends, and la y ers with v ar ying dynamics can be combined in the same netw ork. 4.1 No use cr ying o ver spilt time R ockpool isn ’ t y our Grandma ’ s deep lear ning librar y . R ockpool is, at its hear t, a simulator of dynamical sy s tems. This is a pr imal difference to most ANN / deep learning librar ies, which treat neurons as little functional units of s tateless linear alg ebra that y ou push data through on an implicit global cloc k. Each neuron in R oc kpool is a dynamical object that e xists and e v ol v es o v er time. Y ou can influence its state b y pro viding input, but its s tate can e v ol v e e v en in the absence of input. This introduces some subtle differences in ho w in puts, states and outputs are treated in R ockpool. The aim of R ockpool is to mak e this as transparent as possible, and assist y ou in building signal processing and com- puting ar tef acts in an intuitiv e w a y . The approach is to engineer sy stems to perf or m desired tasks, with the resulting sy s tems e xisting as dynamical units that interact with the w or ld in real-time. This approach lets y ou design solutions f or real-time computing hardw are such as spiking neural netw ork inf erence engines. 4.2 Impor ting Rockpool modules [1]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Import classes to represent time series data from rockpool import TimeSeries, TSContinuous, TSEvent # - Import ` Module ` classes to use from rockpool.nn.modules import Rate, Linear [2]: # - Import numpy import numpy as np # - Import the plotting library import sys !{ sys.executable } -m pip install --quiet matplotlib rich (continues on ne xt page) 17 Rockpool, Release 3.0.3 (continued from pre vious pag e) import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 # - Rich printing from rich import print 4.3 My First Module Neurons and their s tate in R oc kpool are encapsulated in Module s, which are small Python objects that deriv e from the Module base class. These are similar in design to Modules in other mac hine lear ning librar ies: the y manag e the parameters, state and computations associated with a group of neurons. W e will first define a small population of non-spiking rate neurons using the Rate class, b y specifing the shape of the module. These will be rate-based neurons with a linear -threshold (R eLU) transf er function. R ockpool modules almos t alwa y s e xpect shape as their first parameter . This is either an integ er (f or modules that ha v e the same number of input and output c hannels), or a tuple (N_in, N_out) , f or modules that can ha v e different numbers of input and ouput c hannels. In this case, w e pro vide a single number to specify the number of neurons in the module. [3]: # - Define a feed-forward module with ` N ` neurons N = 4 mod = Rate(N) print (mod) Rate with shape ( 4 , ) Let ’ s take a look inside and see what w e ha v e. All Modules support inspection methods state() , parameters() and simulation_parameters() , which return nested dictionaries of parameters f or the module and an y sub-modules. In this case w e only ha v e a single top-lev el module, so there will be no nes ted parameters. “P arameters” are g enerall y the set of model configuration that y ou w ould modify to train a model, and that y ou w ould need to communicate to someone to giv e them y our trained model. e.g. w eights, neuron time constants, biases. “S tate” are inter nal v ariables that are maintained dur ing and between e v olutions, that influence the dynamics of a model. If y ou thro w them a w a y , the trained model still e xists. e.g. ins tantaneous synaptic cur rents and membrane potentials. “Simulation parameters” are configuration elements that the model needs to kno w about, but which aren ’ t trained to configure y our model. e.g. time-step duration, noise s td. dev . These are fix ed dur ing training. [4]: print (mod . state()) print (mod . parameters()) print (mod . simulation_parameters()) { ' x ' : array ([ 0 ., 0 ., 0 ., 0 . ])} { ' tau ' : array ([ 0.02 , 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 . ]) , ' threshold ' : ␣ ˓ → array ([ 0 ., 0 ., 0 ., 0 . ])} 18 Chapter 4. Introduction to Rockpool Rockpool, Release 3.0.3 { ' dt ' : 0.001 , ' noise_std ' : 0.0 , ' act_fn ' : < function H_ReLU at 0x172ca81f0 >} W e can ev ol v e the state of these neurons o v er time, b y pro viding some input to the module b y calling it. Lo w -le v el modules accept cloc k ed input data as ra w numpy ar ra y s with shape (T, N) , or with shape (batches, T, N) . If an ar ra y (T, N) is pro vided, it will be con v er ted to a single batch. Let ’ s create some simple input. [5]: T = 100 input_clocked = np . random . randn(T, N) * 3 # - Evolve the module with the input output_clocked, new_state, recorded_state = mod(input_clocked) # - Update the state of the module mod = mod . set_attributes(new_state) output_clocked represents the output of the neurons, whic h is a n numpy ar ra y with shape (1, T, N) . W e can visualise this using matplotlib . [6]: plt . plot(output_clocked[ 0 ]); 4.4 Getting things done in time R ockpool also offers a higher -lev el TimedModule interface, which accepts in put and retur ns output as TimeSeries objects. TimeSeries objects pro vide a con v enience encapsulation f or handling time-ser ies data, including both con- tinuous signals and streams of e v ents. [7]: # - Use the ` .timed() ` method to obtain a TimeSeries interface tmod = mod . timed() print (tmod) TimedModuleWrapper with shape ( 4 , 4 ) { Rate ' _module ' with shape ( 4 , ) } with Rate ' _module ' as module timed() con v er ts a lo w-le v el module into one which nativ el y understands time series data. T o test this out, w e need to g enerate a time series! 4.4. Getting things done in time 19 Rockpool, Release 3.0.3 (continued from pre vious pag e) ) self . w_output = Parameter( shape = self . shape[ 1 :], family = "weights" , init_func = lambda size: np . random . normal(size = size), ) # - Build submodules self . mod_recurrent = Rate((shape[ 1 ], shape[ 1 ]), has_rec = True ) # - Every module needs an ` evolve() ` method def evolve ( self , input , record: bool = False ) -> (np . array, np . array, dict ): # - Initialise output arguments new_state = {} record_dict = {} # - Pass input through the input weights and submodule x, mod_state, mod_record_dict = self . mod_recurrent( np . dot( input , self . w_input), record ) # - Maintain the submodule state and recorded state dictionaries new_state . update({ "mod_recurrent" : mod_state}) record_dict . update({ "mod_recurrent" : mod_record_dict}) # - Pass data through the output weights output = np . dot(x, self . w_output) # - Return outputs return output, new_state, record_dict N o w w e can ins tantiate a module based on our net class, and test it b y passing data through. [17]: # - Define the network size input_size = 4 rec_size = 3 output_size = 2 # - Instantiate a module net_mod = net(shape = (input_size, rec_size, output_size)) print (net_mod) net with shape ( 4 , 3 , 2 ) { Rate ' mod_recurrent ' with shape ( 3 , 3 ) } [18]: # - Define an input T = 100 input = np . random . randn(T, input_size) # - Evolve the module (continues on ne xt page) 26 Chapter 4. Introduction to Rockpool Rockpool, Release 3.0.3 (continued from pre vious pag e) output, new_state, recorded_state = net_mod( input ) net_mod = net_mod . set_attributes(new_state) plt . plot(output[ 0 ]); If w e inspect our netw ork parameters, w e can see the y f ollo w the structure of the module heirarch y , and include param- eters defined b y the sub-modules. [19]: print (net_mod . parameters()) { ' w_input ' : array ([[ 1.41978838 , -1.13184915 , -0.74752138 ] , [ -0.95091877 , 0.30172439 , 0.35265 ] , [ -0.09663117 , 0.10782144 , -1.36449476 ] , [ -0.72311345 , 0.10093025 , -0.2414413 ]]) , ' w_output ' : array ([[ -0.20141784 , 0.62143563 ] , [ 1.22627445 , -0.14979523 ] , [ -1.53157303 , 0.67460225 ]]) , ' mod_recurrent ' : { ' w_rec ' : array ([[ 0.15648791 , 0.401766 , -1.14040351 ] , [ 0.42543312 , 0.5842613 , -0.30617398 ] , [ -0.81748013 , -1.23444556 , -0.26348427 ]]) , ' tau ' : array ([ 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 . ]) } } Y ou can set the parameters of the netw ork directl y b y setting attr ibutes: [20]: # - Define weights weights_in = np . random . rand(input_size, rec_size) - 0.5 weights_rec = np . random . randn(rec_size, rec_size) / rec_size weights_out = np . random . rand(rec_size, output_size) - 0.5 # - Set the weights net_mod . w_input = weights_in net_mod . mod_recurrent . w_rec = weights_rec net_mod . w_output = weights_out (continues on ne xt page) 4.6. Building a custom nested modules 27 Rockpool, Release 3.0.3 (continued from pre vious pag e) # - Display the network print (net_mod . parameters()) { ' w_input ' : array ([[ 0.28506196 , 0.02794232 , 0.05052549 ] , [ -0.39742499 , 0.13996292 , -0.191019 ] , [ -0.0180503 , -0.03285261 , 0.01811005 ] , [ 0.05156022 , -0.23567097 , -0.29003133 ]]) , ' w_output ' : array ([[ -0.28148003 , -0.02212604 ] , [ -0.23931323 , 0.24881792 ] , [ -0.41053964 , -0.33932346 ]]) , ' mod_recurrent ' : { ' w_rec ' : array ([[ -0.91369668 , 0.15297224 , 0.4534814 ] , [ 0.05805358 , 0.35401688 , -0.29566064 ] , [ 0.2393718 , -0.02951023 , -0.00684574 ]]) , ' tau ' : array ([ 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 . ]) } } 4.7 N e xt steps R ockpool pro vides se v eral additional niceties to mak e de v eloping y our o wn Module s easier . See the documentation f or the Module class f or more inf or mation. Y ou can also lear n about R ockpool Parameter s, and about the facilities pro vided b y the rockpool.utilities pac kag e. See a simple e xample f or training SNNs: Hello MNIST with R oc kpool Lear n about the lo w -le v el API in detail: Low-lev el Module API Lear n about the high-le v el API in detail: High-lev el TimedModule API Lear n about the functional API in detail: [] Low-lev el functional API Lear n about training netw orks with Jax: T raining a R oc kpool netw ork wit h Jax Lean about the T orch API: Building Roc kpool modules with T or c h Lear n about training netw orks with T orch: T r aining a Roc kpool netw ork wit h T orc h Lear n about training netw orks f or deplo yment to hardware: Ov er view of the X ylo ™ f amily 28 Chapter 4. Introduction to Rockpool CHAPTER FIVE HELL O MNIST WITH ROCKPOOL R ockpool is a neural netw ork training library , with a f ocus on spiking neural netw orks (SNNs) and other neurons that ha v e inter nal s tate and temporal dynamics. The goal is to mak e training and deplo ying SNNs as simple as training a standard DNN . Here w e sho w the steps to train the MNIS T digit classification task — a common dataset f or g etting s tar ted with neural netw ork librar ies. [9]: # - Install required packages import sys !{ sys.executable } -m pip install --quiet rockpool tonic tqdm torch torchvision matplotlib Note: you may need to restart the kernel to use updated packages. [10]: # - Basic imports import torch import torchvision import numpy as np import matplotlib.pyplot as plt plt . rcParams[ ' figure.dpi ' ] = 300 plt . rcParams[ ' figure.figsize ' ] = [ 9.6 , 3.6 ] plt . rcParams[ ' font.size ' ] = 12 from tqdm .autonotebook import tqdm, trange from IPython.display import Image 5.1 Spiking neurons The main difference betw een a spiking neuron (SN) and a standard ar tificial neuron (AN) is in their understanding of time. S tandard ANs operate instantaneousl y , b y simply summing their w eighted inputs and appl ying a transf er function such as a R eL U . Spiking neurons, on the other hand, ha v e an inter nal state that e v olv es o v er time in response to their input. As a result, SNNs are great f or processing temporal signals — and as a consequence, w e need to consider ho w to pro vide input data to an SNN o v er time, and consider e.g. the time constants 𝜏 as additional netw ork parameters. In R ockpool, w e ha v e a simple Leaky Integ rate-and-Fire (LIF) spiking neuron, defined by the module LIF . The cell belo w sho ws y ou ho w to build a single spiking neuron, pro vide in put e v ents, simulate the neuron and e xamine its inter nal state. Y ou can read more in depth about spiking neurons in A simple intr oduction to spiking neur ons . 29 Rockpool, Release 3.0.3 [11]: # - Import the SNN modules from rockpool from rockpool.nn.modules import LIF # - Generate a spiking neuron neuron = LIF( 1 ) # - Simulate this neuron for 1 sec with poisson spiking input z(t) num_timesteps = int ( 1 / neuron . dt) input_z = 0.06 * (np . random . rand(num_timesteps) < 0.0125 ) output, _, rec_dict = neuron(input_z, record = True ) # - Display the input, internal state and output events plt . figure() plt . plot(rec_dict[ ' isyn ' ] . squeeze(), label = ' $I_s(t)$ ' ) plt . plot(rec_dict[ ' vmem ' ] . squeeze(), label = ' $V_m(t)$ ' ) b ,t ,n = np . nonzero(output) plt . scatter(t, n, marker = ' | ' ,c = ' k ' , label = ' $o(t)$ ' ) plt . plot([ 0 , num_timesteps], [neuron . threshold] * 2 , ' k: ' , label = ' $\Theta$ ' ) plt . xlabel( ' Time (dt) ' ) plt . ylabel( ' $V_m$, $I_s$ (a.u.) ' ) plt . legend(); 5.2 Data and encoding N o w w e can load the MNIS T dataset, and decide ho w to encode the data f or processing b y an SNN. W e make use of the torchvision pac kag e to obtain the MNIS T dataset and manag e the dataset and data loader classes. The data will be accessed as torch.Tensor objects. [46]: # - Number of samples per batch batch_size = 256 # - Download and access the MNIST training dataset train_data = torchvision . datasets . MNIST( (continues on ne xt page) 30 Chapter 5. Hello MNIST with Rockpool Rockpool, Release 3.0.3 (continued from pre vious pag e) root = "." , train = True , download = True , transform = torchvision . transforms . ToTensor(), ) # - Create a data loader for the training dataset train_loader = torch . utils . data . DataLoader( train_data, batch_size = batch_size, shuffle = True ) # - Create a test dataset test_loader = torch . utils . data . DataLoader( torchvision . datasets . MNIST( root = "." , train = False , transform = torchvision . transforms . ToTensor(), ), batch_size = batch_size, ) N o w w e need to decide ho w to encode the data samples f or the SNN. The MNIS T samples are 28 × 28 resolution imag es, with no temporal component. W e will encode the images b y ar ranging the 28 × 28 pix els into a 784-element v ector , and creating 784 spiking input c hannels. W e’ll use a P oisson process to g enerate an a v erag e input rate f or each channel, according to the intensity of the corresponding pix el. Pix els with v alue 0 will ha v e g enerate no input e v ents; pix els with value 1 will g enerate the highest rate of e v ents. T o do so we need to define ho w man y time-steps eac h sample will tak e, and what the duration of a single time-step will be. [13]: # - Define the temporal aspects of a data sample num_timesteps = 100 dt = 10e-3 # - Extract the number of classes and input channels num_classes = len (torchvision . datasets . MNIST . classes) input_channels = train_data[ 0 ][ 0 ] . numel() # - Define a function to encode an input into a poisson event series def encode_poisson (data: torch . Tensor, num_timesteps: int , scale: float = 0.1 ) -> torch . ˓ → Tensor: num_batches, frame_x, frame_y = data . shape data = scale * data . view((num_batches, 1 , - 1 )) . repeat(( 1 , num_timesteps, 1 )) return (torch . rand(data . shape) < (data * scale)) . float() W e also need to deter mine what the targ et output of the netw ork will look lik e. The MNIST dataset has 10 classes. W e ’ll build a netw ork with 10 output neurons, one f or each class, and train the netw ork to produce a high ev ent rate f or the targ et class and no e v ents f or the non-targ et classes. Our targ et will also be a time series of ev ents, with the same duration as the input time series, and with 10 channels. [14]: # - Define a function to encode the network target def encode_class (class_idx: torch . Tensor, num_classes: int , num_timesteps: int ) -> torch . ˓ → Tensor: num_batches = class_idx . numel() (continues on ne xt page) 5.2. Data and encoding 31 Rockpool, Release 3.0.3 (continued from pre vious pag e) target = torch . nn . functional . one_hot(class_idx, num_classes = num_classes) return target . view((num_batches, 1 , - 1 )) . repeat(( 1 , num_timesteps, 1 )) . float() [15]: # - Get one sample frame, class_idx = train_data[ 0 ] # - Encode the input and targets data = encode_poisson(frame, num_timesteps) target = encode_class(torch . tensor(class_idx), num_classes, num_timesteps) # - Plot the poisson input for this sample plt . figure() b ,t ,n = torch . nonzero(data, as_tuple = True ) plt . scatter(t * dt, n, marker = ' | ' ) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Channel ' ) plt . title( ' Input ' ) # - Plot the target event series for this sample b ,t ,n = torch . nonzero(target, as_tuple = True ) plt . figure() plt . scatter(t * dt, n, marker = ' | ' ) plt . ylim([ - 1 , num_classes + 1 ]) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Channel ' ) plt . title( ' Target ' ); 32 Chapter 5. Hello MNIST with Rockpool Rockpool, Release 3.0.3 5.3 Building a netw ork R ockpool allo w s y ou to build SNNs with a simple syntax, similar to PyT orch. W e suppor t sev eral computational bac k -ends in R ockpool, one of whic h is PyT orch, whic h w e will use here. LIFTorch is a R ockpool and torch module which pro vides a trainable simulation of LIF spiking neurons. LinearTorch is a linear w eight matr ix, comparable to the torch nn.Linear module. T o build simple f eed-f or w ard netw orks, w e use the Sequential combinator from R ockpool, whic h functions lik e the torch nn.Sequential combinator . By def ault, R oc kpool allo w s y ou to train the time constants 𝜏 and other parameters of an SNN . For simplicity , here w e ’ll define them as constant (i.e. non-trainable), using the Constant parameter decorator . The netw ork architecture w e will use is sho wn belo w ; a simple tw o-la y er SNN . [16]: Image( ' mnist-architecture.png ' ) [16]: [17]: # - Import network packages from rockpool.nn.modules import LIFTorch, LinearTorch from rockpool.nn.combinators import Sequential from rockpool.parameters import Constant # - Define a simple network num_hidden = 64 (continues on ne xt page) 5.3. Building a network 33 Rockpool, Release 3.0.3 (continued from pre vious pag e) tau_mem = Constant( 100e-3 ) tau_syn = Constant( 50e-3 ) threshold = Constant( 1. ) bias = Constant( 0. ) # - Define a two-layer feed-forward SNN snn = Sequential( LinearTorch((input_channels, num_hidden)), LIFTorch(num_hidden, tau_syn = tau_syn, tau_mem = tau_mem, threshold = threshold, ␣ ˓ → bias = bias, dt = dt), LinearTorch((num_hidden, num_classes)), LIFTorch(num_classes, tau_syn = tau_syn, tau_mem = tau_mem, threshold = threshold, ␣ ˓ → bias = bias, dt = dt) ) print (snn) TorchSequential with shape (784, 10) { LinearTorch ' 0_LinearTorch ' with shape (784, 64) LIFTorch ' 1_LIFTorch ' with shape (64, 64) LinearTorch ' 2_LinearTorch ' with shape (64, 10) LIFTorch ' 3_LIFTorch ' with shape (10, 10) } Let ’ s e xamine the un-trained output of this netw ork. W e simulate the netw ork identicall y as with the single LIF neuron abo v e, just b y passing input data to the module. W e’ll use the single data sample w e encoded abo v e. The record = True argument tells R oc kpool to record all the inter nal state of the SNN dur ing e v olution. [18]: # - Simulate the untrained network, record internal state output, _, rec_dict = snn(data, record = True ) # - Display the internal state and output plt . plot(rec_dict[ ' 1_LIFTorch ' ][ ' vmem ' ][ 0 ] . detach()); plt . plot([ 0 , num_timesteps], [threshold] * 2 , ' k: ' ) plt . ylim([ - 2.1 , 1.1 ]) plt . xlabel( ' Time (dt) ' ) plt . ylabel( ' $V_m$ (a.u.) ' ) plt . title( ' Hidden $V_m$ ' ) plt . figure() plt . plot(rec_dict[ ' 3_LIFTorch ' ][ ' vmem ' ][ 0 ] . detach()); plt . plot([ 0 , num_timesteps], [threshold] * 2 , ' k: ' ) plt . ylim([ - 2.1 , 1.1 ]) plt . xlabel( ' Time (dt) ' ) plt . ylabel( ' $V_m$ (a.u.) ' ) plt . title( ' Output $V_m$ ' ) plt . figure() t ,n = torch . nonzero(rec_dict[ ' 3_LIFTorch_output ' ][ 0 ] . detach(), as_tuple = True ) plt . scatter(t, n, marker = ' | ' , label = ' Output events ' ) plt . plot( 0 , class_idx, ' g> ' , markersize = 20 , label = ' Target class ' ) plt . ylim([ - 1 , num_classes + 1 ]) plt . xlim([ - 1 , num_timesteps + 1 ]) (continues on ne xt page) 34 Chapter 5. Hello MNIST with Rockpool Rockpool, Release 3.0.3 (continued from pre vious pag e) plt . xlabel( ' Time (dt) ' ) plt . ylabel( ' Channel ' ) plt . title( ' Output (events) ' ) plt . legend(); 5.3. Building a network 35 Rockpool, Release 3.0.3 42 Chapter 5. Hello MNIST with Rockpool CHAPTER SIX W ORKIN G WITH TIME SERIES D A T A 6.1 Concepts In R ockpool, temporal data (“time series” data) is encapsulated in a set of classes that der iv e from TimeSeries . T ime ser ies come in tw o basic fla v ours: “continuous” time ser ies, which ha v e been sampled at some set of time points but which represent v alues that can e xist at an y point in time; and “e v ent” time ser ies, which consist of discrete e v ent times. The TimeSeries subclasses pro vide methods f or e xtracting, resampling, shifting, tr imming and manipulating time ser ies data in a con v enient f ashion. Since R ockpool naturall y deals with temporal dynamics and temporal data, Time- Ser ies objects are used to pass around time ser ies data both as input and as output. TimeSeries objects ha v e an implicit shared time-base at 𝑡 0 = 0 sec. Ho w e v er , the y can easily be offset in time, concatenated, etc. Housek eeping and import statements [1]: # - Import required modules and configure # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Required imports import numpy as np from rockpool.timeseries import ( TimeSeries, TSContinuous, TSEvent, set_global_ts_plotting_backend, ) from IPython.display import Image # - Use HoloViews for plotting import sys !{ sys.executable } -m pip install --quiet colorcet holoviews import colorcet as cc import holoviews as hv hv . extension( "bokeh" ) (continues on ne xt page) 43 Rockpool, Release 3.0.3 (continued from pre vious pag e) % opts Curve [width=600] % opts Scatter [width=600] Data type cannot be displa y ed: application/ja vascript, application/vnd.holo view s_load.v0+json Data type cannot be displa y ed: application/ja vascript, application/vnd.holo view s_load.v0+json 6.2 Continuous time series represented b y TSContinuous Continuous time ser ies are represented b y tuples [ 𝑡 𝑘 , 𝑎 ( 𝑡 𝑘 )] , where 𝑎 ( 𝑡 𝑘 ) is the amplitude of a signal, sampled at the time 𝑡 𝑘 . A full time ser ies is theref ore the set of samples [ 𝑡 𝑘 , 𝑎 ( 𝑡 𝑘 )] f or 𝑘 = 1 . . . 𝐾 . Continuous time ser ies in R ockpool are represented b y the TSContinuous class. A time ser ies is constructed b y pro viding the sample times in seconds and the cor responding sample v alues. The full syntax f or constructing a TSContinuous object is giv en by def __init__ ( self , times: Optional[ArrayLike] = None , samples: Optional[ArrayLike] = None , num_channels: Optional[ int ] = None , periodic: bool = False , t_start: Optional[ float ] = None , t_stop: Optional[ float ] = None , name: str = "unnamed" , interp_kind: str = "linear" , ) [2]: # - Build a time trace vector duration = 10.0 dt = 0.01 times = np . arange( 0.0 , duration, 0.1 ) theta = times / duration * 2 * np . pi # - Create a TSContinuous object containing a sin-wave time series ts_sin = TSContinuous( times = times, samples = np . sin(theta), name = "sine wave" , ) ts_sin 44 Chapter 6. W orking with time series dat a Rockpool, Release 3.0.3 [2]: non-periodic TSContinuous object ` sine wave ` from t=0.0 to 9.9. Samples: 100. Channels: 1 TSContinuous pro vides a con v enience method from_clocked() to g enerate a TSContinuous object from a regular l y -sampled v ector of data with a fix ed time-step dt . This is the optimal w a y to w ork with cloc k ed data im- por ted from outside R ockpool, and ensures that the data will be used intuitiv el y within R ockpool. from_clocked() will retur n a TSContinuous time ser ies with e xtent from t=0 to t=N*d t , using sample- and-hold inter polation betw een samples. Each data sample is assumed to occur at the beginning of a time bin of dt duration, and the data is assumed to be v alid f or the entir ity of the dt time bin. Y ou can offset the retur ned time series accurately b y specifying the time of the initial sample with the argument t_start . [3]: # - Generate some data samples N = 100 dt = 0.01 samples = np . random . rand(N, 1 ) ts_reg = TSContinuous . from_clocked(samples, dt = dt, t_start = 5.0 ) ts_reg [3]: non-periodic TSContinuous object ` unnamed ` from t=5.0 to 6.0. Samples: 100. Channels: 1 TSContinuous objects pro vide a con v enience plotting method plot() f or visualisation. This mak es use of holo view s / bokeh or matplotlib plotting libraries, if a v ailable. If both are a vailable y ou can c hoose betw een them using the timeseries.set_global_plotting_backend func- tion. [4]: # - Set backend for Timeseries to holoviews set_global_ts_plotting_backend( "holoviews" ) # # - Alternatively, it can be set for specific Timeseries instances # ts_sin.set_plotting_backend("holoviews") # - Plot the time series ts_sin . plot() Global plotting backend has been set to holoviews. [4]: :Curve [Time] (y) TSContinuous objects can represent multiple ser ies simultaneousl y , as long as the y share a common time base: [5]: # - Create a time series containing a sin and cos trace ts_cos_sin = TSContinuous( times = times, samples = np . stack((np . sin(theta), np . cos(theta))) . T, name = "sine and cosine" , ) # - Print the representation print (ts_cos_sin) # - Plot the time series ` ts_cos_sin . plot() 6.2. Continuous time series repr esented b y TSContinuous 45 Rockpool, Release 3.0.3 non-periodic TSContinuous object ` sine and cosine ` from t=0.0 to 9.9. Samples: 100. ␣ ˓ → Channels: 2 [5]: :Overlay .Curve.I :Curve [Time] (y) .Curve.II :Curve [Time] (y) F or con v enience, TimeSeries objects can be made to be per iodic. This is particularl y useful when simulating netw orks o v er repeated tr ials. T o do so, use the periodic flag when cons tr ucting the TimeSeries object: [6]: # - Create a periodic time series object ts_sin_periodic = TSContinuous( times = times, samples = np . sin(theta), periodic = True , name = "periodic sine wave" , ) # - Print the representation print (ts_sin_periodic) # - Plot the time series plot_trace = np . arange( 0 , 100 , dt) ts_sin_periodic . plot(plot_trace) periodic TSContinuous object ` periodic sine wave ` from t=0.0 to 9.9. Samples: 100. ␣ ˓ → Channels: 1 [6]: :Curve [Time] (y) Continuous time ser ies per mit inter polation betw een sampling points, using scipy.interpolate as a bac k -end. By def ault sample-and-hold inter polation is used, but an y inter polation method suppor ted b y scipy.interpolate can be pro vided as a string when constr ucting the TSContinuous object (f or e xample, linear ). The inter polation inter f ace is simple: TSContinuous objects are callable with a list-lik e set of time points; the inter - polated v alues at those time points are returned as a numpy.ndarray . [7]: # - Interpolate the sine wave print (ts_sin([ 1 , 1.1 , 1.2 ])) [[0.58778525] [0.63742399] [0.63742399]] As a con v enience, TSContinuous objects can also be inde x ed using [] , whic h uses inter polation to build a ne w time ser ies object with the reques ted data. A second inde x can be pro vided to choose specific c hannels. Inde xing will return a ne w TSContinuous object. [8]: # - Slice a time series object ts_cos_sin[: 1 : 0.09 , 0 ] . print() non-periodic TSContinuous object ` sine and cosine ` from t=0.0 to 0.99. Samples: 12. ␣ ˓ → Channels: 1 0.0: [0.] 0.09: [0.] 0.18: [0.06279052] (continues on ne xt page) 46 Chapter 6. W orking with time series dat a Rockpool, Release 3.0.3 (continued from pre vious pag e) 0.27: [0.12533323] ... 0.72: [0.42577929] 0.8099999999999999: [0.48175367] 0.8999999999999999: [0.48175367] 0.99: [0.53582679] F or non-per iodic TSContinuous objects, the time rang e in which data can be sampled or interpolated lies between the ear liest and the lates t sample or , in other w ords, betw een the first and las t point of its times attr ibute. It is possible that the limits of the time ser ies, which are defined b y the t_start and t_stop attr ibutes, are bey ond the sampling times. In this case v alues in the cor responding inter v als are deter mined b y the fill_value attr ibute. The def ault beha vior is to e xtrapolate. [9]: # - Extrapolate between last sample and t_stop ts_cos_sin . t_stop = 12 print ( "Extrapolated fill value at t=11:" , ts_cos_sin( 11 )) # - Use constant fill value instead of extrapolation ts_cos_sin . fill_value = 42 print ( "New fill value at t=11:" , ts_cos_sin( 11 )) # - Back to extrapolation ts_cos_sin . fill_value = "extrapolate" Extrapolated fill value at t=11: [[-0.06279052 0.99802673]] New fill value at t=11: [[42. 42.]] When tr ying to sample outside of this rang e, the def ault beha vior is to raise a ValueError : [10]: # - Sampling at a point beyond the series ' time range: try : ts_cos_sin( 13 ) except ValueError as e: print ( "Caught the following exception:" ) print (e) Caught the following exception: TSContinuous ` sine and cosine ` : Some of the requested time points are beyond the first ␣ ˓ → and last time points of this series and cannot be sampled. If you think that this is due to rounding errors, try setting the ` approx_limit_times ` ␣ ˓ → attribute to ` True ` . If you want to sample at these time points anyway, you can set the ` beyond_range_ ˓ → exception ` attribute of this time series to ` False ` and will receive ` NaN ` as values. If the beyond_range_exception attr ibute is set to False , nan v alues will ins tead be retur ned and a w arning will be issued. (Y ou will not see the w arning here, because warnings are suppressed in this tutor ial) [11]: ts_cos_sin . beyond_range_exception = False print (ts_cos_sin( 13 )) [[nan nan]] Sometimes it can happen due to numer ical er rors that the pro vided sampling time is slightl y be y ond rang e, al- though the intention ma y hav e been to sample right at the beginning or end of a time ser ies. This will result in a ValueError or nan v alues being returned, depending on the value of beyond_range_exception . Ho w e v er , when 6.2. Continuous time series repr esented b y TSContinuous 47 Rockpool, Release 3.0.3 the approx_limit_times attr ibute is set to True (the def ault case), v alues that are only slightl y be y ond the defined rang e will be appro ximated b y the firs t or last time point of the series. For a time series with approx_limit_times set to True , the threshold f or this appro ximation is 1e-6 * ts.duration or 1e-9 , whiche v er is less. In either case a w arning will be raised. [12]: # - ` t ` should be 12, but is slightly larger due to numerical errors. times = np . repeat( 0.001 , 12000 ) t = np . sum(times) print ( f"We want to sample about { t - ts_cos_sin . t_stop : .1e } seconds after the series ends." ) # - Sampling at ` t ` will give same value as if sampled at t=12 print ( "Sampling in spite of rounding errors:" , ts_cos_sin(t)) print ( "Values at end of series:" , ts_cos_sin( 12 )) We want to sample about 5.3e-15 seconds after the series ends. Sampling in spite of rounding errors: [[-0.06279052 0.99802673]] Values at end of series: [[-0.06279052 0.99802673]] [13]: # - Disable time approximation in such cases ts_cos_sin . approx_limit_times = False # - Sampling at ` t ` will result in a warning and nan-values being returned. print ( "Now we get:" , ts_cos_sin(t)) Now we get: [[nan nan]] TSContinuous pro vides a larg e number of methods f or manipulating time ser ies. For e x ample, binar y operations such as addition, multiplication etc. are suppor ted betw een tw o time ser ies as w ell as betw een time ser ies and scalars. Mos t operations retur n a ne w TSContinuous object. See the api ref erence f or TSContinuous f or full detail. A ttributes ( TSContinuous ) Attribut e name Description times V ector 𝑇 of sample times samples Matr ix 𝑇 × 𝑁 of samples, cor responding to sample times in times num_channels Scalar repor ting 𝑁 : number of ser ies in this object num_traces Synon ym to num_channels t_start , t_stop Firs t and last sample times, respectiv el y duration Duration betw een t_start and t_stop plotting_backend Cur rent plotting back end f or this instance. fill_value Data to use to fill samples that f all outside t_start and t_stop 6.2.1 Examples of time series manipulation [14]: # - Perform additions, powers and subtractions of time series ts_sin . beyond_range_exception = False ( (ts_sin + 2 ) . plot() + (ts_cos_sin ** 6 ) . plot() (continues on ne xt page) 48 Chapter 6. W orking with time series dat a Rockpool, Release 3.0.3 (continued from pre vious pag e) + (ts_sin - (ts_sin ** 3 ) . delay( 2 )) . plot() ) . cols( 1 ) [14]: :Layout .Curve.Sine_wave.I :Curve [Time] (y) .Sine_and_cosine.I :Overlay .Curve.I :Curve [Time] (y) .Curve.II :Curve [Time] (y) .Curve.Sine_wave.II :Curve [Time] (y) 6.3 Ev ent-based time series represented b y TSEvent Sequences of e v ents (e.g. spike trains) are represented b y the TSEvent class, which inherits from TimeSeries . Discrete time ser ies are represented b y tuples ( 𝑡 𝑘 , 𝑐 𝑘 ) , where 𝑡 𝑘 are sample times as bef ore and 𝑐 𝑘 is a “channel” associated with each sample (e.g. the source of an e v ent). Multiple samples at identical time points are e xplictl y per mitted such that (f or e xample) multiple neurons could spik e simultaneousl y . U nless the ser ies is empty , the ar gument t_stop mus t be pro vided and has to be strictly lar ger than the time of the las t e v ent. TSEvent objects are initialised with the syntax def __init__ ( self , times: ArrayLike = None , channels: Union[ int , ArrayLike] = None , periodic: bool = False , t_start: Optional[ float ] = None , t_stop: Optional[ float ] = None , name: str = None , num_channels: int = None , ) [15]: # - Build a time trace vector times = np . sort(np . random . rand( 100 )) channels = np . random . randint( 0 , 10 , ( 100 )) ts_spikes = TSEvent( times = times, channels = channels, t_stop = 100 , ) ts_spikes [15]: non-periodic ` TSEvent ` object ` unnamed ` from t=0.001941216549281144 to 100.0. Channels: ␣ ˓ → 10. Events: 100 [16]: # - Plot the events ts_spikes . plot() 6.3. Event-based time series represented b y TSEvent 49 Rockpool, Release 3.0.3 [16]: :Scatter [Time] (Channel) If TSEvent is called, it retur ns ar ra y s of the ev ent times and channels that f all within the defined time points and cor respond to selected channels. [17]: # - Return events between t=0.5 and t=0.6 ts_spikes( 0.5 , 0.6 ) ts_spikes( 0.5 , 0.6 , channels = [ 3 , 7 , 8 ]) [17]: (array([0.51589083, 0.58430924, 0.59280105]), array([3, 8, 7])) TSEvent also suppor ts inde xing, where indices cor respond to the indices of the e v ents in the times attr ibute. A ne w TSEvent will be retur ned. F or e xample, in order to g et a new series with the first 5 e v ents of ts_spikes one can do: [18]: ts_spikes[: 5 ] [18]: non-periodic ` TSEvent ` object ` unnamed ` from t=0.001941216549281144 to 100.0. Channels: ␣ ˓ → 10. Events: 5 TSEvent pro vides se v eral methods f or combining multiple TSEvent objects and f or e xtracting data. See the API ref erence f or TSEvent f or full details. A ttributes ( TSEvent ) Attribut e name Description times V ector 𝑇 of sample times channels V ector of channels corresponding to sample times in times num_channels Scalar 𝐶 : number of channels in this object t_start , t_stop Firs t and last sample times, respectiv el y duration Duration betw een t_start and t_stop plotting_backend Cur rent plotting back end f or this instance. 6.4 Impor ting time series dat a T ime ser ies data impor ted from outside R ockpool often comes in a “cloc ked” f or mat, where the data is presented as a v ector of samples on an implicit time base. Sample times are often on a fix ed cloc k dt or sample frequency fs = 1 / dt . These representations can easil y be impor ted into R ockpool, but some care is required to mak e sure the data is handled cor rectl y . F or continuous-time data, TSContinuous pro vides the method from_clocked() . def TSContinuous . from_clocked( samples: numpy . ndarray, dt: float , t_start: float = 0.0 , periodic: bool = False , name: str = None , ) -> TSContinuous This method will accept regular l y sampled data on a specified sample cloc k dt , from multiple channels, and will retur n a cor rectl y -f or matted TSContinuous object with e xtents set cor rectly . This object will use “sample-and-hold” inter polation, as this seems to be the most common mental model that de v elopers ha v e f or data resampled within a time bin. 50 Chapter 6. W orking with time series dat a Rockpool, Release 3.0.3 [19]: # - Get a data sample T = 100 C = 3 dt = 0.1 data = np . random . rand(T, C) # - Create the time series ts = TSContinuous . from_clocked(data, dt = dt) print (ts) non-periodic TSContinuous object ` unnamed ` from t=0.0 to 10.0. Samples: 100. Channels: 3 Similar l y , impor ted ev ent data often appears in a “ras ter” f or mat, which is regular ly cloc ked. In this f or mat, a train of e v ents on 𝐶 channels o v er 𝑇 time bins is represented as a matr ix ( 𝑇 , 𝐶 ) , where the element ( 𝑡, 𝑐 ) indicates the number of e v ents occur r ing dur ing integ er time bin 𝑡 on channel 𝑐 . In the e xample belo w , se v en channels emit e v ents o v er sev en time bins. W e will use the TSEvent.from_raster() method to con v er t the set of e v ents from a numpy.ndarray into a TSEvent object. This will ensure that t_start , t_stop and num_channels attr ibutes are set appropratel y . def from_raster ( raster: np . ndarray, dt: float = 1.0 , t_start: float = 0.0 , t_stop: Optional[ float ] = None , name: Optional[ str ] = None , periodic: bool = False , num_channels: Optional[ int ] = None , spikes_at_bin_start: bool = False , ) -> TSEvent: [20]: Image( "raster_to_TSEvent.png" ) [20]: N ote that the e v ents are placed in the middle of each time bin. This beha viour can be modif ed with the spikes_at_bin_start argument to TSEvent.from_raster() . [21]: # - Generate a boolean raster T = 10 C = 20 rate = 0.1 raster = np . random . rand(T, C) <= 0.1 # - Convert to a time series using ` .from_raster() ` (continues on ne xt page) 6.4. Impor ting time series dat a 51 Rockpool, Release 3.0.3 58 Chapter 7. Standard modules CHAPTER EIGHT THE SHARP POINTS OF ROCKPOOL R ockpool aims to be an intuitiv e br idg e betw een super vised machine learning and dynamic signal processing, hiding the comple xities of the underl ying dynamical sy stems as muc h as possible. Ho w e v er , there are a f e w places where y ou need to consider that a R ockpool-based solution is actuall y a dynamical sy s tem, and not a simple stac k of stateless linear alg ebra. This notebook illustrates some of the sharp points that can jab y ou when y ou dip y our hands into R ockpool. Our goal is to mak e this list disappear o v er time. [1]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Rockpool imports from rockpool import TSContinuous # - General imports and configuration import numpy as np import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 8.1 How t o use sam pled time series data (in)correctly in Rockpool T ime ser ies data loaded from else where probably comes in a cloc ked ras ter f or mat. Y ou can easil y use this data in R ockpool, but there are a couple of tricky points to w atch out f or . 8.1.1 W rong: how to generate a time base for clock ed dat a [2]: T = 1000 dt = 1e-3 data = np . random . rand(T) t_start = 23.6 time_base = np . arange(t_start, t_start + len (data) * dt, dt) 59 Rockpool, Release 3.0.3 This approach can sometimes lead to rounding errors such that time_base is one sample too shor t, especially f or floating point numbers with < 64 bit precision. The better w a y is to g enerate integ er time s teps, then scale and shift them: [3]: time_base = np . arange(T) * dt + t_start 8.1.2 W rong: how to define a time series from clock ed dat a Sa y w e ha v e a 20-second sample, sampled on a 100ms cloc k. Let ’ s con v er t this into a continuous time ser ies: [4]: dt = 100e-3 T = round ( 20 / dt) data = np . random . rand(T) time_base = np . arange( len (data)) * dt ts = TSContinuous(time_base, data) ts [4]: non-periodic TSContinuous object ` unnamed ` from t=0.0 to 19.900000000000002. Samples: ␣ ˓ → 200. Channels: 1 Huh? The time ser ies is too shor t, it ’ s one dt off! But there are 200 samples. . . ? T ime ser ies objects don ’ t ha v e an intr isic cloc k; y ou can define samples at any point in time. So TSContinuous has no w a y to kno w that y ou e xpected a 20 second duration. By def ault, the time ser ies ends at the point in time where the last sample occurs. But f or clock ed data, y ou probabl y e xpected there to be an e xtra dt . The cor rect lo w-le v el wa y to define the time ser ies is to specify t_stop e xplicitly : [5]: ts = TSContinuous(time_base, data, t_stop = 20.0 ) ts [5]: non-periodic TSContinuous object ` unnamed ` from t=0.0 to 20.0. Samples: 200. Channels: 1 Ho w e v er , w e pro vide a con v enience method TSContinuous.from_clocked() to mak e this easier: [6]: ts = TSContinuous . from_clocked(data, dt = dt) ts [6]: non-periodic TSContinuous object ` unnamed ` from t=0.0 to 20.000000000000004. Samples: ␣ ˓ → 200. Channels: 1 TSContinuous.from_clocked() is the canonical w a y to impor t clock ed time-ser ies data into R oc kpool. If y ou use from_clocked() then e v er ything should beha v e as y ou e xpect it to. 8.2 Defining e xtents for TSEvent time series data Ev ent time ser ies are represented in R ockpool using the TSEvent class, which represents ev ents as occuring at discrete moments in time, on one of a number of channels. This is different from an “ev ent ras ter” representation, where e v ents are placed into discrete time bins, and the temporal resolution is limited to bin durations dt . [7]: from rockpool import TSEvent from matplotlib import pyplot as plt times = [ 0.2 , 0.8 , 1.2 , 1.4 , 1.8 , 2.2 , 3.3 , 3.5 , 3.6 , 4.2 , 4.8 , 5.2 , 5.8 , 6.2 , 6.5 , 6.8 ] (continues on ne xt page) 60 Chapter 8. The sharp points of Rockpool Rockpool, Release 3.0.3 (continued from pre vious pag e) channels = [ 0 , 3 , 3 , 1 , 6 , 6 , 3 , 3 , 5 , 5 , 4 , 0 , 5 , 1 , 1 , 2 ] ts = TSEvent(times, channels, t_start = 0.0 , t_stop = 7.0 ) ts . plot(); [8]: from IPython.display import Image Image( "TSEvent_to_raster.png" ) [8]: The imag e abo v e sho w s the relationship between an e v ent time ser ies and one possible raster representation of that time ser ies. N ote that a raster is inherentl y lossy — it can onl y represent ev ents do wn to a minimum temporal resolution dt . W e can con v er t a TSEvent object into a raster b y using the appropr iatel y -named raster() method: def raster ( dt: float , t_start: float = None , t_stop: float = None , num_timesteps: int = None , channels: numpy . ndarray = None , add_events: bool = False , include_t_stop: bool = False , ) -> numpy . ndarray: With an e v ent raster , the time base is e xplicitl y defined. Time proceeds from t = t_start at the beginning of the raster , and continues until t = t_start + dt * T at the end of the raster (after T time bins). If y ou con v er t a ras ter directl y to a TSEvent object using the from_raster() method, then t_start and t_stop are inf er red. But a freshl y -created TSEvent object can ’ t inf er t_start and t_stop — w e need to supply these e xplicitl y when 8.2. Defining e xtents for TSEvent time series data 61 Rockpool, Release 3.0.3 creating the TSEvent . If w e don ’ t, things can g et confusing: [9]: tsBad = TSEvent(times, channels, t_stop = 7.0 , name = "No extents " ) print (tsBad) tsGood = TSEvent(times, channels, t_start = 0.0 , t_stop = 7.0 , name = "With extents " ) print (tsGood) non-periodic ` TSEvent ` object ` No extents ` from t=0.2 to 7.0. Channels: 7. Events: 16 non-periodic ` TSEvent ` object ` With extents ` from t=0.0 to 7.0. Channels: 7. Events: 16 Imagine y ou tr ied to evolve() a la y er with the first ts — the evolve() method w ould probabl y g et the ev olution duration wrong. Things w ould also g et messy if y ou tr ied to con v er t the first ts to a raster() . The time bins w ould begin at t = 0.2 rather than t=0 . The moral of the story is, y ou should alway s set the e xtents f or a TSEvent , if y ou are creating it from a list of e v ent times. 8.3 TimeSeries and Module s share an e xplicit global time base All TimeSeries and Module objects in R ockpool are defined with a shared global time base. That means t=0 ref ers to the same point in time f or all objects. This ma y slip y ou up if y ou pla y with a la y er f or while, ev ol ving with some input time series, and e xpect that e v olution will begin from the start of the time ser ies data f or each e v olution. [10]: # - Imports from rockpool.nn.modules import Rate from rockpool import TSContinuous # - Define a Timed module with a single rate neuron tmod = Rate( 1 ) . timed() print ( "tmod:" , tmod) # - Define a time series dt = 1e-3 data = np . sin(np . arange( 0 , 10 , dt) / 4 * ( 2 * np . pi)) ts_input = TSContinuous . from_clocked(data, dt = dt) ts_input . plot(); tmod: TimedModuleWrapper with shape (1, 1) { Rate ' _module ' with shape (1,) } with Rate ' _module ' as module 62 Chapter 8. The sharp points of Rockpool Rockpool, Release 3.0.3 N o w w e e v ol v e tmod using ts_input , and look at the result. [11]: output_ts, _, _ = tmod(ts_input) output_ts . plot() [11]: [<matplotlib.lines.Line2D at 0x7fe2aefbc550>] But if w e tr y to ev ol v e mod ag ain in the same w a y , we recie v e an er ror . [12]: tmod(ts_input); --------------------------------------------------------------------------- ValueError Traceback (most recent call last) /var/folders/fj/ptcl18sd0yd8n9bm9fs_qtrc0000gn/T/ipykernel_10376/2584389186.py in ˓ → <module> ----> 1 tmod ( ts_input ); ~/SynSense Dropbox/Dylan Muir/LiveSync/Development/rockpool_GIT/rockpool/nn/modules/ ˓ → timed_module.py in __call__ (self, *args, **kwargs) 725 record_state ( dict ): If the argument `` record `` is `` True `` , ␣ ˓ → `` record_state `` must contain a dictionary of the recorded states o this and all sub - ˓ → modules during evolution . Otherwise it may be an empty dict . 726 """ (continues on ne xt page) 8.3. TimeSeries and Module s share an explicit global time base 63 Rockpool, Release 3.0.3 (continued from pre vious pag e) --> 727 return self . evolve (* args , ** kwargs ) 728 729 @ property ~/SynSense Dropbox/Dylan Muir/LiveSync/Development/rockpool_GIT/rockpool/nn/modules/ ˓ → timed_module.py in _evolve_wrapper (self, ts_input, duration, num_timesteps, kwargs_ ˓ → timeseries, record, *args, **kwargs) 289 """ 290 # - Determine number of timesteps --> 291 num_timesteps = self . _determine_timesteps ( ts_input , duration , num_ ˓ → timesteps ) 292 293 # - Call wrapped evolve ~/SynSense Dropbox/Dylan Muir/LiveSync/Development/rockpool_GIT/rockpool/nn/modules/ ˓ → timed_module.py in _determine_timesteps (self, ts_input, duration, num_timesteps) 344 duration = ts_input . t_stop - self . t --> 346 raise ValueError( 345 if duration <= 0 : 347 self . full_name 348 + "Cannot determine an appropriate evolution ␣ ˓ → duration." ValueError : TimedModuleWrapper Cannot determine an appropriate evolution duration. ' ts_ ˓ → input ' finishes before the current evolution time. This is because the inter nal time of the la y er is after the defined times of the timeser ies. There are se v eral possibilities to resol v e this issue, one of which is simpl y dela ying ts_input on creation b y using the t_start argument to from_clocked() : [13]: ts_input = TSContinuous . from_clocked(data, dt = dt, t_start = tmod . t) print ( "ts_input:" , ts_input) output, _, _ = tmod(ts_input) ts_input: non-periodic TSContinuous object ` unnamed ` from t=10.0 to 20.000000000000004. ␣ ˓ → Samples: 10000. Channels: 1 or b y using the TSContinuous.start_at() or TSContinuous.delay() methods when calling evolve() : [14]: # - Using ` .start_at() ` tmod(ts_input . start_at(tmod . t)) # - Using ` .delay() ` tmod(ts_input . delay(tmod . t - ts_input . t_start)); The final possibility is to specify that the time ser ies is per iodic, using the periodic = True flag to TSContinuous. from_clocked() . The time ser ies will then be defined o v er all time: [15]: ts_input = TSContinuous . from_clocked(data, dt = dt, periodic = True ) ts_input . plot(np . arange( - 10 , 20 , 0.1 )); 64 Chapter 8. The sharp points of Rockpool Rockpool, Release 3.0.3 and w e can ev ol v e as w e like: [16]: tmod(ts_input) tmod(ts_input); 8.4 Module s in Rockpool are dynamical syst ems, and don’ t get reset implicitly Neurons and modules in R oc kpool behav e quite differentl y from standard ANNs. The y are intr insicall y s tateful, e v en in the absence of e xplicit recur rent connectivity . Single neurons ha v e an internal state, which e v ol v es dynamically o v er time f ollo wing a set of differential equations. S tandard ANNs are stateless, meaning that each frame or sample is processed completel y independently . R ecur rent ANNs or LS TMs are often implicitl y reset at the s tar t of each trial. Neurons in R ockpool are onl y e v er reset e xplicitl y using nn.modules.Module.reset_state() . An e xplicit reset could become impor tant dur ing training, to ensure that a netw ork’ s response to a trial is not contami- nated b y the response to the previous trial. 8.4. Module s in Rockpool are dynamical systems, and don ’t get reset implicitly 65 Rockpool, Release 3.0.3 66 Chapter 8. The sharp points of Rockpool CHAPTER NINE L O W-LEVEL MODULE API The lo w -le v el API in R ockpool is designed f or minimal efficient implementation of s tateful neural netw orks. The Module base class pro vides f acilities f or configur ing, simulating and e xamining netw orks of stateful neurons. 9.1 Constructing a Module All Module subclasses accept minimall y a shape ar gument on construction. This should specify the in put, output and inter nal dimensionality of the Module completel y , so that the code can determine ho w many neurons should be g enerated, and the sizes of the s tate v ariables and parameters. Some Module subclasses allo w y ou to specify the module shape by setting concrete parameter arra y s, e.g. b y setting a v ector of length (N,) as the bias parameters f or a set of neurons. These concrete parameter v alues will be used to initialise the Module , and if the Module is reset, then the parameters will retur n to those concrete v alues. Other wise, all Module subclasses will set reasonable def ault initialisation v alues f or the parameters. [1]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Useful imports try : from rich import print except : pass # - Example of constructing a module from rockpool.nn.modules import Rate import numpy as np # - Construct a Module with 4 neurons mod = Rate( 4 ) print (mod) Rate with shape ( 4 , ) [2]: # - Construct a Module with concrete parameters mod = Rate( 4 , tau = np . ones( 4 )) print (mod) 67 Rockpool, Release 3.0.3 74 Chapter 9. Low-lev el Module API CHAPTER TEN HIGH-LEVEL TIMEDMODULE API The high-le v el API in R ockpool allo w s y ou to build netw orks of stateful neurons that accept and manipulate time-series data nativ el y . T ime-ser ies data in R ockpool is encapsulated b y TimeSeries classes: TSContinuous f or continuous signals, and TSEvent f or discrete e v ents (see W or king with time series data f or an o v er vie w). The TimedModule API allo w s y ou to specify modules that receiv e TimeSeries objects as input; process them; and retur n TimeSeries objects as output. 10.1 Constructing a TimedModule Instantiating a TimedModule w orks in a v er y similar w a y to the lo w-le v el API. Y ou must specify the simulation time- step dt , if a useful default is not pro vided. TimedModule will tr y to set a sensible dt inter nally if it can, but y ou usuall y w ant to ha v e control o v er this. [1]: # - Switch off warnings import warnings warnings . filterwarnings( ' ignore ' ) # - Useful imports import numpy as np try : from rich import print except : pass from rockpool.nn.combinators import Sequential from rockpool.nn.modules import Rate, Linear # - Construct the TimedModule Nin = 1 Nout = 5 dt = 1e-3 tmod = Sequential(Linear((Nin, Nout)), Rate(Nout)) . timed(dt = dt) print (tmod) TimedModuleWrapper with shape ( 1 , 5 ) { ModSequential ' _module ' with shape ( 1 , 5 ) { Linear ' 0_Linear ' with shape ( 1 , 5 ) Rate ' 1_Rate ' with shape ( 5 , ) (continues on ne xt page) 75 Rockpool, Release 3.0.3 (continued from pre vious pag e) } } with ModSequential ' _module ' as module 10.2 Ev olving a TimedModule P assing time ser ies data through a TimedModule is v er y con v enient. A TimedModule kno w s ho w to handle TimeSeries objects nativ el y , and handles an y required sampling, resampling, ras ter isation etc. inter nally . Let ’ s star t b y defining a sinusoid wa v ef or m to use as input. [2]: # - Import time-series handling classes from rockpool import TSContinuous # - Import and configure matplotlib for plotting import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ ' figure.figsize ' ] = [ 12 , 4 ] plt . rcParams[ ' figure.dpi ' ] = 300 # - Create a sinusoidal input signal T = 1000 omega = 10 time_base = np . arange(T) * dt ts_sin = TSContinuous . from_clocked( np . sin(time_base * 2 * np . pi * omega), dt = dt, periodic = True , name = ' Sinusoid ' , ) ts_sin . plot(); N o w w e can e v ol v e the TimedModule b y simply calling it with the in put time series. The evolve() arguments are slightl y different to that f or the lo w -le v el API: 76 Chapter 10. High-lev el TimedModule API Rockpool, Release 3.0.3 output_ts, new_state, recorded_state = evolve(ts_input: TimeSeries, duration: float, num_ ˓ → timesteps: int) Y ou can specify the e v olution duration either e xplicity in ter ms of real time ( duration in seconds), or in units of dt ( num_timesteps ); or implicitl y b y simpl y pro viding the input signal ts_input . If y ou just pro vide ts_input , then R oc kpool will e v ol v e the TimedModule f or the remaining v alid duration of time ser ies. R emember that R oc kpool [3]: # - Evolve the TimedModule output_ts, _, _ = tmod(ts_sin) plt . figure(); print (output_ts); output_ts . plot(); non-periodic TSContinuous object ` Output samples ` from t = 0.0 to 1.0 . Samples: 1000 . ␣ ˓ → Channels: 5 10.3 Inspecting a TimedModule Inspecting a TimedModule uses the common R ockpool API. All attributes can be accessed using standard Python “dot” inde xing notation, or the module can be inspected using the parameters() , state() , simulation_parameters() and modules() methods. [4]: # - Examine the parameters print ( ' Parameters: ' , tmod . parameters()) # - Examine the state print ( ' State: ' , tmod . state()) # - Examine the simulation parameters print ( ' Simulation parameters: ' , tmod . simulation_parameters()) # - ` TimedModules ` also have time print ( ' Time: ' , tmod . t) 10.3. Inspecting a TimedModule 77 Rockpool, Release 3.0.3 Parameters: { ' _module ' : { ' 0_Linear ' : { ' weight ' : array ([[ 1.1175366 , -1.50156676 , -1.88202227 , -0. ˓ → 95408104 , -0.08374108 ]])} , ' 1_Rate ' : { ' tau ' : array ([ 0.02 , 0.02 , 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 . ]) } } } State: { ' _module ' : { ' 0_Linear ' : {} , ' 1_Rate ' : { ' x ' : array ([ -0.56143606 , 0.75436789 , 0.94550386 , 0.47931808 , 0. ˓ → 04207044 ])} } } Simulation parameters: { ' dt ' : 0.001 , ' _module ' : { ' dt ' : 0.001 , ' 0_Linear ' : {} , ' 1_Rate ' : { ' dt ' : 0.001 , ' noise_std ' : 0.0 , ' act_fn ' : < function H_ReLU at ␣ ˓ → 0x7f05e46a4ee0 >} } } Time: 1.0 10.4 Building a netw ork using nested TimedModule s Y ou can inher it from the TimedModule base class to build arbitrar y netw orks, combining other TimedModule s as y ou lik e. TimedModule tak es care of man y things f or y ou, including all the handling of TimeSeries data. Y ou can k eep all data in TimeSeries objects and maniupulate the data in that f or mat, using the operator suppor t from TimeSeries subclases. Alternativ el y y ou can use con v enience methods pro vided b y TimedModule to assist with con v er ting signals to and from TimeSeries objects. T o define a netw ork as a TimedModule subclass, y ou need to minimally define an __init__() method and an evolve() method. The __init__() method specifies and initialises parameters f or y our netw ork, and initialises an y required submodules. The submodules tak e care of specifying and initialise their o wn parameters and state. In the e xample belo w , w e build a small f eed-f or ward netw ork as a TimedModule , with one la y er of linear input w eights and tw o additional la y ers of ReL U rate units with w eighting in-betw een. W e sho w ho w to maintain all signals as 78 Chapter 10. High-lev el TimedModule API Rockpool, Release 3.0.3 TimeSeries objects through e v olution. T o do so, all submodules must themsel v es be TimedModule subclasses. [5]: # - Import the base class and a TimedModule to use as a submodule from rockpool.nn.modules import TimedModule, Rate from rockpool.parameters import Parameter from rockpool import TimeSeries # - Define a new TimedModule class ffwd_tmod_net (TimedModule): # - Provide an ` __init__ ` method to specify required parameters and modules # Here you check, define and initialise whatever parameters and # state you need for your module. def __init__ ( self , shape, dt = 1. , * args, ** kwargs, ): # - Call superclass initialisation # This is always required for a ` Module ` class super () . __init__ (shape = shape, dt = dt, * args, ** kwargs) # - Specify weights attributes # We need a weights matrix for our input weights. # We specify the shape explicitly, and provide an initialisation function. # We also specify a family for the parameter, "weights". This is used to # query parameters conveniently, and is a good idea to provide. self . w_0 = Parameter( shape = self . shape[ 0 : 2 ], init_func = lambda s: np . zeros(s), family = ' weights ' , ) self . w_1 = Parameter( shape = self . shape[ 1 : 3 ], init_func = lambda s: np . zeros(s), family = ' weights ' , ) self . w_2 = Parameter( shape = self . shape[ 2 : 4 ], init_func = lambda s: np . zeros(s), family = ' weights ' , ) # - Specify and add submodules # These will be the neurons in our layer, to receive the weighted # input signals. This sub-module will be automatically configured # internally, to specify the required state and parameters self . neurons1 = Rate( self . shape[ 1 ], dt = dt) . timed() self . neurons2 = Rate( self . shape[ 2 ], dt = dt) . timed() # - The ` evolve ` method contains the internal logic of your module (continues on ne xt page) 10.4. Building a network using nested TimedModule s 79 Rockpool, Release 3.0.3 (continued from pre vious pag e) # ` evolve ` takes care of passing data in and out of the module, # and between sub-modules if present. def evolve ( self , ts_input: TimeSeries, duration: float , num_timesteps: int , record: bool = False , * args, ** kwargs): # - Pass input data through the input weights x = ts_input @ self . w_0 # - Pass the signals through the neurons and hidden weights x ,_ ,_ = self . neurons1(x) x = x @ self . w_1 x ,_ ,_ = self . neurons2(x) x = x @ self . w_2 # - Return the module output return x, {}, {} [6]: Nin = 3 Nout = 1 dt = 1 tmod = ffwd_tmod_net((Nin, 5 , 7 , Nout), dt = dt) print (tmod) ffwd_tmod_net with shape ( 3 , 5 , 7 , 1 ) { TimedModuleWrapper ' neurons1 ' with shape ( 5 , 5 ) { Rate ' _module ' with shape ( 5 , ) } TimedModuleWrapper ' neurons2 ' with shape ( 7 , 7 ) { Rate ' _module ' with shape ( 7 , ) } } [7]: # - Examine the nested parameters of the TimedModule print (tmod . parameters()) { ' w_0 ' : array ([[ 0 ., 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 ., 0 . ]]) , ' w_1 ' : array ([[ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]]) , ' w_2 ' : array ([[ 0 . ] , [ 0 . ] , [ 0 . ] , (continues on ne xt page) 80 Chapter 10. High-lev el TimedModule API Rockpool, Release 3.0.3 (continued from pre vious pag e) [ 0 . ] , [ 0 . ] , [ 0 . ] , [ 0 . ]]) , ' neurons1 ' : { ' _module ' : { ' tau ' : array ([ 0.02 , 0.02 , 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 . ]) } } , ' neurons2 ' : { ' _module ' : { ' tau ' : array ([ 0.02 , 0.02 , 0.02 , 0.02 , 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]) } } } [8]: # - Set the weights to something non-zero p = tmod . parameters() p[ ' w_0 ' ] = np . random . normal(size = p[ ' w_0 ' ] . shape) + 1 p[ ' w_1 ' ] = np . random . normal(size = p[ ' w_1 ' ] . shape) + 1 p[ ' w_2 ' ] = np . random . normal(size = p[ ' w_2 ' ] . shape) + 1 # - Assign the new parameters tmod . set_attributes(p) print ( ' Weights: ' , tmod . parameters()) Weights: { ' w_0 ' : array ([[ 1.73722151 , 1.30356212 , 0.55156482 , 2.8019148 , 1.09027286 ] , [ 1.97210989 , 1.88721147 , 1.29312512 , -0.28583698 , 1.45047169 ] , [ 0.04967296 , -0.94915265 , 1.37862991 , 1.29235999 , 0.050853 ]]) , ' w_1 ' : array ([[ 1.52490803 , 0.53006083 , 3.11873417 , -0.15332358 , 1.51820289 , 2.88070423 , -0.55447475 ] , [ 0.03845312 , 0.76734798 , 0.77595556 , 2.23176957 , -0.90711186 , 1.39216172 , 0.132243 ] , [ 0.47959018 , 0.99225231 , 0.54699617 , 0.91157126 , 0.61972611 , 3.7774115 , 2.23284232 ] , [ 0.56585036 , 2.65117266 , 0.4449058 , 0.0736041 , 0.02211706 , -0.34763718 , -0.79899013 ] , [ 1.51294292 , 0.81017138 , 1.07416865 , -1.13288234 , 0.92521921 , 1.6997927 , 1.38329005 ]]) , ' w_2 ' : array ([[ -0.01196603 ] , [ 1.74152837 ] , [ -0.35683857 ] , [ -0.29947419 ] , [ 0.38918974 ] , [ 1.76011316 ] , (continues on ne xt page) 10.4. Building a network using nested TimedModule s 81 Rockpool, Release 3.0.3 (continued from pre vious pag e) [ 0.96554928 ]]) , ' neurons1 ' : { ' _module ' : { ' tau ' : array ([ 0.02 , 0.02 , 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 . ]) } } , ' neurons2 ' : { ' _module ' : { ' tau ' : array ([ 0.02 , 0.02 , 0.02 , 0.02 , 0.02 , 0.02 , 0.02 ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' threshold ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]) } } } [9]: # - Generate some white noise input ts_input = TSContinuous . from_clocked(np . random . rand( 1000 , Nin), name = ' White noise ' , dt = dt, periodic = True , ) print ( ' Input: ' , ts_input) ts_input . plot() # - Evolve the TimedModule with the input ts_output, _, rec = tmod(ts_input) # - Display the output print ( ' Output: ' , ts_output) plt . figure() ts_output . plot(); Input: periodic TSContinuous object ` White noise ` from t = 0.0 to 1000.0 . Samples: 1000 . ␣ ˓ → Channels: 3 Output: non-periodic TSContinuous object ` Output samples '' neurons2 ''` from t = 0.0 to ␣ ˓ → 1000.0 . Samples: 1000 . Channels: 1 82 Chapter 10. High-lev el TimedModule API Rockpool, Release 3.0.3 10.5 Building a netw ork using internal Module s Sometimes the modules y ou w ant to use to build a netw ork are not a vailable as TimedModule s. In this case y ou can use the lo w -le v el Module API inter nall y , and still present a high-le v el TimedModule API inter f ace to the outside w or ld. T o do so, TimedModule pro vides a number of con v enience methods f or wrapping, un wrapping and ras ter ising time ser ies data: time_base, input_raster, num_timesteps = self._repare_input( ts_input: TimeSeries, duration: float, num_timesteps: int ) ts_output = self._gen_timeseries(output_raster, **kwargs) The e xample belo w illustrates ho w to use these methods to build a similar netw ork architecture to the one abo v e, but using lo w -le v el Module API submodules. 10.5. Building a network using internal Module s 83 Rockpool, Release 3.0.3 (continued from pre vious pag e) [ 0.05882018 0.09205481 0.15850767 ] [ 0.08602361 0.12148949 0.16591538 ] [ 0.10797263 0.11710763 0.18505278 ] [ 0.13634151 0.13502166 0.20233528 ] [ 0.15542242 0.15269144 0.20374872 ] [ 0.16027772 0.15767853 0.19997193 ]]] [4]: print ( "new_state:" , new_state) new_state: { ' x ' : DeviceArray ([ 0.16027772 , 0.15767853 , 0.19997193 ] , dtype = float32 ) , ' rng_key ' : DeviceArray ([ 2469880657 , 3700232383 ] , dtype = uint32 ) } So f ar so good. The issue with jax is that jit -compiled modules and functions cannot ha v e side-effects. For R oc kpool, e v olution almost alw ays has side-effects, in terms of updating the inter nal state v ar iables of each module. In the case of the e v olution abo v e, w e can see that the inter nal state w as not updated dur ing ev olution: [5]: print ( "mod.state:" , mod . state()) print (mod . state()[ "x" ], " != " , new_state[ "x" ]) mod.state: { ' rng_key ' : DeviceArray ([ 237268104 , 2681681569 ] , dtype = uint32 ) , ' x ' : DeviceArray ([ 0 ., 0 ., 0 . ] , dtype = float32 ) } [ 0 . 0 . 0 . ] != [ 0.16027772 0.15767853 0.19997193 ] The cor rect resolution to this is to assign new_state to the module atf er each e v olution: [6]: mod = mod . set_attributes(new_state) print (mod . state()[ "x" ], " == " , new_state[ "x" ]) [ 0.16027772 0.15767853 0.19997193 ] == [ 0.16027772 0.15767853 0.19997193 ] Y ou will ha v e noticed the functional f or m of the call to set_attributes() abo v e. This is addressed in the ne xt section. 11.2 F unctional state and attribute setting Direct attr ibute assignment w orks at the top le v el, using standard Python syntax: [7]: new_tau = mod . tau * 0.4 mod . tau = new_tau print (new_tau, " == " , mod . tau) 90 Chapter 11. [] Low-le vel functional API Rockpool, Release 3.0.3 [ 0.008 0.008 0.008 ] == [ 0.008 0.008 0.008 ] A functional f or m is also suppor ted, via the set_attributes() method. Here a cop y of the module (and submodules) is retur ned, to replace the “old” module with one with updated attr ibutes: [8]: params = mod . parameters() params[ "tau" ] = params[ "tau" ] * 3.0 # - Note the functional calling style mod = mod . set_attributes(params) # - check that the attribute was set print (params[ "tau" ], " == " , mod . tau) [ 0.024 0.024 0.024 ] == [ 0.024 0.024 0.024 ] 11.3 F unctional module reset R esetting the module s tate and parameters also must be done using a functional f or m: [9]: # - Reset the module state mod = mod . reset_state() # - Reset the module parameters mod = mod . reset_parameters() 11.4 Jax flattening JaxModule pro vides the methods tree_flatten() and tree_unflatten() , which are required to serialise and deser ialise modules f or Jax compilation and e x ecution. If y ou wr ite a JaxModule subclass, it will be automatically regis tered with Jax as a pytree . Y ou shouldn ’ t need to o v er r ide tree_flatten() or tree_unflatten() in y our modules. Flattening and unflattening requires that y our __init__() method mus t be callable with onl y a shape as in put, which should be sufficient to specify the netw ork architecture of y our module and all submodules. If that isn ’ t the case, then y ou ma y need to o v er r ide tree_flatten() and tree_unflatten() . 11.3. Functional module reset 91 Rockpool, Release 3.0.3 92 Chapter 11. [] Low-le vel functional API CHAPTER T WEL VE TRAINING A ROCKPOOL NET W ORK WITH J AX Jax is a Python pac kag e f or differentiable programming. With a con v enient numpy -lik e inter face, Jax will automagi- call y compute the gradients of y our code. This is a hug e boon f or optimisation, especiall y f or neural netw orks. In this o v er view w e sho w ho w to build and optimise a simple Jax-based netw ork in R ockpool. W e sho w ho w to wr ite a task Dataset, ho w to wr ite a loss function, and ho w to wr ite a training loop to per f orm the optimisation. W e also illustrate some adv anced topics, such as pro viding parameter bounds during optimisation. [1]: # -- Some useful imports # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Rich printing try : from rich import print except : pass # - Numpy import numpy as np # - Import and configure matplotlib for plotting import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 12.1 Jax considerations Jax is a functional programming librar y — ideally e v er ything should be wr itten as side-effect-free functions. F or this pur pose, R oc kpool pro vides the functional API (see [] Low-lev el functional API ). R ockpool tries to make using Jax -bac k ed modules as straightf or ward as possible, via the JaxModule base class. Jax pro vides a v er y conv enient nump y -compatible inter f ace, via the jax.numpy packag e. If y ou need to do an y numer ic computation inter f aced with R oc kpool/Jax, then y ou must use jax.numpy . Y ou ’ll receiv e an er ror if y ou don ’ t. 93 Rockpool, Release 3.0.3 12.2 Loss functions 12.2.1 Loss function com ponents pro vided by R ockpool R ockpool pro vides some useful training utilities under training . training.jax_loss includes se v eral components f or building y our o wn loss (or cost) functions. F unction Use mse() Mean-squared error (basic loss). Ensures that tw o signals become more similar (e.g. tar g et signal and netw ork output). l2sqr_norm() L2-squared norm, f or parameter regular isation. Keeps parameter v alues closer to zero. l0_norm_appro x() Smooth and differentiable L0 nor m appro ximation. Encourag es parameter sparsity (i.e. man y zero entr ies in a parameter v ector). bounds_cost() Pro vide a cost function component that enf orces minimum and/or maximum parameter bounds. make_bounds() Con v enience function to cons tr uct a template set of bounds f or use in an optimisation problem. softmax() Compute the softmax function. Useful f or incor poration in the readout la y ers of deep netw orks, dur ing training. logsoftmax() Compute the log softmax function. Used in readouts when training with cross-entopy loss functions. Y ou can use these components b y impor ting rockpool.training.jax_loss : [2]: # - Import the loss components for use from rockpool.training import jax_loss as l 12.2.2 W riting y our own loss function F or later use in optimising a netw ork, a loss function mus t be differentiable with respect to the netw ork parameters. A con v enient w a y to achie v e this is illutrated here, where we define a loss function that accepts the parameters, the netw ork object, the inputs f or this batc h, and the cor responding targ et signals. The loss function is then responsible f or initialising the netw ork — remember R ockpool netw orks usuall y ha v e s tate, and this needs to be tak en into account dur ing training – setting the netw ork parameters, e v ol ving the netw ork and computing the loss f or this batch. This f or m is con v enient since y ou can compute gradients o v er the entire function. By def ault, jax computes gradients o v er the first argument to a function — in this case, the set of netw ork parameters. R emember , if y ou w ant to do an y other arbitrar y calculations, y ou must use jax.numpy instead of numpy . [3]: def loss_mse (parameters, net, inputs, target): # - Handle the network state — randomise or reset net = net . reset_state() # - Assign the provided parameters to the network net = net . set_attributes(parameters) # - Evolve the network to get the ouput output, _, _ = net(inputs) # - Compute a loss value w.r.t. the target output return l . mse(output, target) 94 Chapter 12. T raining a Rockpool network with Jax Rockpool, Release 3.0.3 12.3 Defining a task dataset W e will define a simple random regression task, where random frozen input noise is mapped to randoml y chosen smooth output signals. W e implement this using a Dataset -compatible class, implementing the __len__() and __getitem__() methods. [4]: # - Define a dataset class implementing the indexing interface class MultiClassRandomSinMapping : def __init__ ( self , num_classes: int = 2 , sample_length: int = 100 , input_channels: int = 50 , target_channels: int = 2 , ): # - Record task parameters self . _num_classes = num_classes self . _sample_length = sample_length # - Draw random input signals self . _inputs = np . random . randn(num_classes, sample_length, input_channels) + 1.0 # - Draw random sinusoidal target parameters self . _target_phase = np . random . rand(num_classes, 1 , target_channels) * 2 * np . pi self . _target_omega = ( np . random . rand(num_classes, 1 , target_channels) * sample_length / 50 ) # - Generate target output signals time_base = np . atleast_2d(np . arange(sample_length) / sample_length) . T self . _targets = np . sin( 2 * np . pi * self . _target_omega * time_base + self . _target_phase ) def __len__ ( self ): # - Return the total size of this dataset return self . _num_classes def __getitem__ ( self , i): # - Return the indexed dataset sample return self . _inputs[i], self . _targets[i] [5]: # - Instantiate a dataset Nin = 2000 Nout = 2 num_classes = 3 T = 100 ds = MultiClassRandomSinMapping( num_classes = num_classes, input_channels = Nin, target_channels = Nout, sample_length = T, ) (continues on ne xt page) 12.3. Defining a task dat aset 95 Rockpool, Release 3.0.3 (continued from pre vious pag e) # Display the dataset classes plt . figure() for i, sample in enumerate (ds): plt . subplot( 2 , len (ds), i + 1 ) plt . imshow(sample[ 0 ] . T, aspect = "auto" ) plt . title( f"Input class { i } " ) plt . subplot( 2 , len (ds), i + len (ds) + 1 ) plt . plot(sample[ 1 ]) plt . xlabel( f"Target class { i } " ) 12.4 Building a Jax netw ork W e ’ll define a v er y simple netw ork to sol v e the regression task, which will in f act not be s tateful — w e ’ll use LinearJax modules to wrap w eight matr ices, and the InstantJax module to add a non-linear ity . W e ’ll build an MLP -lik e netw ork with one hidden la y er incor porating a tanh nonlinear ity . The Sequential combi- nator is used to con v enientl y stac k the modules tog ether . [6]: # - Import the Rockpool modules and Sequential combinator from rockpool.nn.modules import LinearJax, InstantJax from rockpool.nn.combinators import Sequential import jax import jax.numpy as jnp Nhidden = 8 net = Sequential( LinearJax((Nin, Nhidden)), InstantJax(Nhidden, jnp . tanh), LinearJax((Nhidden, Nout)), ) print (net) 96 Chapter 12. T raining a Rockpool network with Jax Rockpool, Release 3.0.3 JaxSequential with shape ( 2000 , 2 ) { LinearJax ' 0_LinearJax ' with shape ( 2000 , 8 ) InstantJax ' 1_InstantJax ' with shape ( 8 , ) LinearJax ' 2_LinearJax ' with shape ( 8 , 2 ) } 12.5 W riting a training loop In the e xample here w e use an implementation of the A dam optimiser pro vided b y Jax . See the Jax documentation on ho w to use their optimiser inter f ace. The jax.value_and_grad() transf or m accepts our loss function, and con v er ts it automaticall y into a function that computes the gradient of the loss with respect to the netwrork parameters (as w ell as the loss v alue itself). W e mak e use of jax.jit() to compile the optimiser and loss gradient functions, so the y are computed efficientl y on the GPU or CPU . W e k eep trac k of the loss v alue o v er tr ials, so w e can obser v e the training process. [7]: # - Useful imports from tqdm .autonotebook import tqdm from copy import deepcopy from itertools import count # -- Import an optimiser to use and initalise it import jax from jax.example_libraries.optimizers import adam, sgd # - Get the optimiser functions init_fun, update_fun, get_params = adam( 1e-4 ) # - Initialise the optimiser with the initial parameters params0 = deepcopy(net . parameters()) opt_state = init_fun(params0) # - Get a compiled value-and-gradient function loss_vgf = jax . jit(jax . value_and_grad(loss_mse)) # - Compile the optimiser update function update_fun = jax . jit(update_fun) # - Record the loss values over training iterations loss_t = [] grad_t = [] num_epochs = 1000 # - Loop over iterations i_trial = count() for _ in tqdm( range (num_epochs)): for sample in ds: # - Get an input / target sample (continues on ne xt page) 12.5. Writing a training loop 97 Rockpool, Release 3.0.3 (continued from pre vious pag e) input , target = sample[ 0 ], sample[ 1 ] # - Get parameters for this iteration params = get_params(opt_state) # - Get the loss value and gradients for this iteration loss_val, grads = loss_vgf(params, net, input , target) # - Update the optimiser opt_state = update_fun( next (i_trial), grads, opt_state) # - Keep track of the loss loss_t . append(loss_val) 100%|| 1000/1000 [00:03<00:00, 278.95it/s] W e can visualise the loss to see that w e are indeed lear ning to match the desired netw ork output. [8]: # - Plot the loss over iterations plt . plot(np . array(loss_t)) plt . yscale( "log" ) plt . xlabel( "Iteration" ) plt . ylabel( "Loss" ) plt . title( "Training loss" ); As a sanity chec k w e can e valuate the netw ork f or each class, and plot the netw ork output v ersus the targ et signals: [9]: # - Apply the trained parameters to the network params_hat = get_params(opt_state) net = net . set_attributes(params_hat) # - Evaluate classes for i_class, sample in enumerate (ds): input , target = sample # - Evaluate network net = net . reset_state() (continues on ne xt page) 98 Chapter 12. T raining a Rockpool network with Jax Rockpool, Release 3.0.3 (continued from pre vious pag e) output, _, _ = net( input , record = True ) # - Plot output and target plt . figure() plt . plot(output[ 0 ], "k-" ) plt . plot(sample[ 1 ], "--" ) plt . xlabel( "Time (steps)" ) plt . ylabel( "Value" ) plt . legend( [ "Output $y_0$" , "Output $y_1$" , "Target $\hat {y} _0$" , "Target $\hat {y} _1$" , ] ) plt . title( f"Class { i_class } " ) 12.5. Writing a training loop 99 Rockpool, Release 3.0.3 And if w e visualise the time constants, w e should see that none of them violate the lo w er bounds w e imposed: [17]: # - Get the final set of optimised parameters and apply them params = get_params(opt_state) net_stateful = net_stateful . set_attributes(params) # - Visualise the time constants plt . hist(net_stateful[ 1 ] . tau * 1e3 , 20 ) plt . xlabel( "Time constants $ \\ tau$ (ms)" ) plt . ylabel( "Count" ) plt . title( "Trained time constants" ); 106 Chapter 13. Adv anced Jax training topics CHAPTER F OURTEEN BUILDING ROCKPOOL MODULES WITH T ORCH R ockpool pro vides torc h-bac k ed modules with standard dynamics, f or simple integ ration with other torch-pro vided modules from torch.nn . Class Description RateTorc h A la y er of non-spiking fir ing-rate neurons, with trainable time constants, thresholds and biases per neu- ron; optinall y suppor ting recur rent connectivity LIFTorch A la y er of leaky integrate-and-fire spiking neurons, optionally supporting recur rent connectivity . T rain- inable with sur rogate gradient descent, with trainable time cons tants, biases, thresholds per neuron ExpSynTo rch Exponential synapses, with trainable time constants LinearTo rch Equiv alent to a standard trainable linear w eights la y er , but full y suppor ting the R ockpool APIs InstantT orch W rap an arbitrar y function as a R ockpool module 14.1 Use the Rockpool T orch-bac k ed classes The classes abo v e can be used directly to build netw ork arc hitectures in R ockpool, including mixing classes from torch.nn . Here w e build a simple f eed-f or w ard dynamical rate netw ork, including a dropout la y er from torch. [22]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Rich printing try : from rich import print except : pass # - Import and configure matplotlib for plotting import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 # - Torch imports (continues on ne xt page) 107 Rockpool, Release 3.0.3 (continued from pre vious pag e) import torch import torch.nn as nn import torch.nn.functional as F [69]: from rockpool.nn.modules import RateTorch, LinearTorch from rockpool.nn.combinators import Sequential Nin = 2 Nhidden = 5 Nout = 2 # Define a simple feed-forward network using the Torch backend net = Sequential( LinearTorch((Nin, Nhidden)), RateTorch((Nhidden,)), nn . Dropout2d( 0.25 ), LinearTorch((Nhidden, Nout)), RateTorch((Nout,)), ) net [69]: TorchSequential with shape (2, 2) { LinearTorch ' 0_LinearTorch ' with shape (2, 5) RateTorch ' 1_RateTorch ' with shape (5,) Dropout2d ' 2_Dropout2d ' with shape (None,) LinearTorch ' 3_LinearTorch ' with shape (5, 2) RateTorch ' 4_RateTorch ' with shape (2,) } [70]: # - Evolve the network on random data and plot data = torch . rand(( 1 , 100 , Nin)) out, _, _ = net(data) plt . plot(out[ 0 ] . detach()); [71]: # - Recording internal signals also works out, _, rd = net(data, record = True ) print ( list (rd . keys())) 108 Chapter 14. Building Rockpool modules with T orch Rockpool, Release 3.0.3 [ ' 0_LinearTorch ' , ' 0_LinearTorch_output ' , ' 1_RateTorch ' , ' 1_RateTorch_output ' , ' 2_Dropout2d ' , ' 2_Dropout2d_output ' , ' 3_LinearTorch ' , ' 3_LinearTorch_output ' , ' 4_RateTorch ' , ' 4_RateTorch_output ' ] 14.2 Con ver t an e xisting T orch torch.nn.module for use in Rockpool T orch modules implemented using torch.nn.Module can be con v er ted directl y to the R ockpool API using the method TorchModule.from_torch() . This method returns an object adher ing to the R oc kpool lo w-le v el API, con v er ting T orch calls and attr ibutes into R ockpool calls and regis tered attr ibutes. Here w e sho w an e xample of a simple T orch module co v er ted to a R oc kpool object. [72]: # - Torch imports import torch import torch.nn as nn import torch.nn.functional as F # - Rockpool imports from rockpool.nn.modules import TorchModule # - Implement a Torch class class TorchNet (torch . nn . Module): def __init__ ( self , * args, ** kwargs): super () . __init__ ( * args, ** kwargs) # - Build some convolutional layers self . conv1 = nn . Conv2d( 1 , 2 , 3 , 1 ) # - Add a dropout layer self . dropout1 = nn . Dropout2d( 0.25 ) # - Fully-connected layer self . fc1 = nn . Linear( 338 , 10 ) # - Register an example buffer self . register_buffer( "test_buf" , torch . zeros( 3 , 4 )) def forward ( self , x): x = self . conv1(x) x = F . relu(x) x = F . max_pool2d(x, 2 ) (continues on ne xt page) 14.2. Conv ert an existing T orch torch.nn.module for use in Rockpool 109 Rockpool, Release 3.0.3 (continued from pre vious pag e) x = self . dropout1(x) x = torch . flatten(x, 1 ) x = self . fc1(x) x = F . relu(x) output = F . log_softmax(x, dim = 1 ) return output [73]: # - Instantiate the network and test the Torch API # Equates to one random 28x28 image random_data = torch . rand(( 1 , 1 , 28 , 28 )) # - Generate torch module and test evaluation mod = TorchNet() result = mod(random_data) [74]: # - Convert object to Rockpool API, in-place TorchModule . from_torch(mod) print (mod) TorchNet ' TorchModulePatch ' with shape ( None , ) { Conv2d ' TorchModulePatch ' with shape ( None , ) Dropout2d ' TorchModulePatch ' with shape ( None , ) Linear ' TorchModulePatch ' with shape ( None , ) } [75]: # - Use the Rockpool API to evolve the module output, _, _ = mod(random_data) print (output) tensor ([[ -2.2458 , -1.9656 , -2.3617 , -2.3617 , -2.3617 , -2.3617 , -2.3617 , -2.3617 , -2.3617 , -2.3617 ]] , grad_fn = < LogSoftmaxBackward0 >) The module attr ibutes can be accessed using the R ockpool API via parameters() , state() and simulationparameters() methods. The attr ibute dictionar ies retur ned b y these methods suppor t an addi- tional method astorch() , whic h con v er ts the attr ibute dictionar y to a g enerator retur ning ra w Tensor s. Doing so is equiv alent to calling the Torch.nn.Module.parameters() method. [76]: # - Use the Rockpool API to access parameters print ( "Parameters: " , mod . parameters()) print ( "State: " , mod . state()) Parameters: { ' conv1 ' : { ' weight ' : Parameter containing: tensor ([[[[ -0.1636 , -0.3071 , 0.0886 ] , [ 0.1826 , -0.0988 , -0.2805 ] , (continues on ne xt page) 110 Chapter 14. Building Rockpool modules with T orch Rockpool, Release 3.0.3 (continued from pre vious pag e) [ 0.1841 , -0.1396 , -0.0389 ]]] , [[[ 0.2814 , -0.2359 , -0.0974 ] , [ -0.2386 , 0.3125 , 0.1958 ] , [ 0.3165 , 0.0791 , -0.0173 ]]]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ -0.1282 , 0.0541 ] , requires_grad = True ) } , ' dropout1 ' : {} , ' fc1 ' : { ' weight ' : Parameter containing: tensor ([[ 0.0261 , -0.0336 , -0.0126 , ... , -0.0055 , 0.0495 , -0.0540 ] , [ 0.0469 , 0.0163 , -0.0500 , ... , -0.0408 , -0.0364 , -0.0121 ] , [ -0.0284 , 0.0247 , 0.0290 , ... , -0.0128 , 0.0444 , 0.0534 ] , ... , [ 0.0184 , 0.0500 , 0.0326 , ... , -0.0116 , -0.0092 , 0.0071 ] , [ 0.0190 , 0.0424 , -0.0505 , ... , -0.0379 , -0.0238 , 0.0469 ] , [ 0.0119 , 0.0063 , 0.0538 , ... , -0.0211 , -0.0373 , 0.0374 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ -0.0106 , -0.0069 , -0.0463 , 0.0346 , 0.0390 , -0.0147 , -0.0386 , -0.0023 , 0.0171 , -0.0368 ] , requires_grad = True ) } } State: { ' test_buf ' : tensor ([[ 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 . ]]) , ' conv1 ' : {} , ' dropout1 ' : {} , ' fc1 ' : {} } [77]: # - Convert the parameter dictionary to torch parameters print ( "Parameters.astorch(): " , list (mod . parameters() . astorch())) Parameters.astorch () : [ Parameter containing: tensor ([[[[ -0.1636 , -0.3071 , 0.0886 ] , [ 0.1826 , -0.0988 , -0.2805 ] , [ 0.1841 , -0.1396 , -0.0389 ]]] , [[[ 0.2814 , -0.2359 , -0.0974 ] , [ -0.2386 , 0.3125 , 0.1958 ] , [ 0.3165 , 0.0791 , -0.0173 ]]]] , requires_grad = True ) , Parameter containing: (continues on ne xt page) 14.2. Conv ert an existing T orch torch.nn.module for use in Rockpool 111 Rockpool, Release 3.0.3 (continued from pre vious pag e) tensor ([ -0.1282 , 0.0541 ] , requires_grad = True ) , Parameter containing: tensor ([[ 0.0261 , -0.0336 , -0.0126 , ... , -0.0055 , 0.0495 , -0.0540 ] , [ 0.0469 , 0.0163 , -0.0500 , ... , -0.0408 , -0.0364 , -0.0121 ] , [ -0.0284 , 0.0247 , 0.0290 , ... , -0.0128 , 0.0444 , 0.0534 ] , ... , [ 0.0184 , 0.0500 , 0.0326 , ... , -0.0116 , -0.0092 , 0.0071 ] , [ 0.0190 , 0.0424 , -0.0505 , ... , -0.0379 , -0.0238 , 0.0469 ] , [ 0.0119 , 0.0063 , 0.0538 , ... , -0.0211 , -0.0373 , 0.0374 ]] , requires_grad = True ) , Parameter containing: tensor ([ -0.0106 , -0.0069 , -0.0463 , 0.0346 , 0.0390 , -0.0147 , -0.0386 , -0.0023 , 0.0171 , -0.0368 ] , requires_grad = True ) ] 14.3 W rite a nativ e Rockpool/T orch module using TorchModule Y ou can also use TorchModule directl y as a base class, in place of torch.nn.Module . Usuall y this will be a drop-in replacement, without modifying the initialisation or e v aluation code. The e xample here mimics the netw ork abo v e — onl y the inherited base class has been chang ed. [78]: # - Implement a Rockpool class using the TorchModule base class class RockpoolNet (TorchModule): def __init__ ( self , * args, ** kwargs): super () . __init__ ( * args, ** kwargs) # - Build some convolutional layers self . conv1 = nn . Conv2d( 1 , 2 , 3 , 1 ) # - Add a dropout layer self . dropout1 = nn . Dropout2d( 0.25 ) # - Fully-connected layer self . fc1 = nn . Linear( 338 , 10 ) # - Register an example buffer self . register_buffer( "test_buf" , torch . zeros( 3 , 4 )) def forward ( self , x): x = self . conv1(x) x = F . relu(x) x = F . max_pool2d(x, 2 ) x = self . dropout1(x) x = torch . flatten(x, 1 ) x = self . fc1(x) x = F . relu(x) (continues on ne xt page) 112 Chapter 14. Building Rockpool modules with T orch Rockpool, Release 3.0.3 (continued from pre vious pag e) output = F . log_softmax(x, dim = 1 ) return output [79]: # - Instantiate the Rockpool class directly rmod = RockpoolNet() print (rmod) RockpoolNet with shape ( None , ) { Conv2d ' conv1 ' with shape ( None , ) Conv2d ' conv1 ' with shape ( None , ) Dropout2d ' dropout1 ' with shape ( None , ) Dropout2d ' dropout1 ' with shape ( None , ) Linear ' fc1 ' with shape ( None , ) Linear ' fc1 ' with shape ( None , ) } [80]: # - Evaluate the module using the Rockpool API output, _, _ = rmod(random_data) print (output) tensor ([[ -2.3283 , -2.3283 , -2.3283 , -2.3283 , -2.3283 , -2.2359 , -2.2393 , -2.2600 , -2.3283 , -2.3283 ]] , grad_fn = < LogSoftmaxBackward0 >) [81]: # - Access parameters using the Rockpool API print ( "Parameters: " , rmod . parameters()) print ( "State: " , rmod . state()) Parameters: { ' conv1 ' : { ' weight ' : Parameter containing: tensor ([[[[ 0.2828 , -0.2562 , 0.0924 ] , [ 0.1476 , 0.0577 , -0.0121 ] , [ 0.0073 , 0.2832 , -0.2923 ]]] , [[[ 0.2786 , 0.0777 , -0.0933 ] , [ -0.1199 , 0.0570 , -0.2343 ] , [ 0.2991 , 0.0064 , -0.2985 ]]]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ -0.2721 , -0.2435 ] , requires_grad = True ) } , ' dropout1 ' : {} , ' fc1 ' : { ' weight ' : Parameter containing: tensor ([[ -0.0320 , 0.0249 , -0.0329 , ... , 0.0196 , 0.0241 , -0.0021 ] , [ -0.0400 , 0.0448 , -0.0266 , ... , -0.0056 , -0.0111 , 0.0318 ] , [ 0.0204 , 0.0127 , -0.0184 , ... , 0.0482 , -0.0074 , 0.0258 ] , ... , [ 0.0328 , -0.0477 , 0.0297 , ... , -0.0182 , -0.0296 , 0.0217 ] , (continues on ne xt page) 14.3. Writ e a native R ockpool/T orch module using TorchModule 113 Rockpool, Release 3.0.3 (continued from pre vious pag e) [ -0.0037 , 0.0421 , 0.0048 , ... , 0.0057 , -0.0409 , -0.0112 ] , [ -0.0214 , -0.0465 , -0.0319 , ... , 0.0304 , -0.0220 , -0.0306 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ 0.0059 , 0.0042 , -0.0443 , 0.0200 , 0.0258 , 0.0406 , 0.0433 , -0.0303 , 0.0038 , 0.0473 ] , requires_grad = True ) } } State: { ' test_buf ' : tensor ([[ 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 . ]]) , ' conv1 ' : {} , ' dropout1 ' : {} , ' fc1 ' : {} } 14.4 Con ver ting from Rockpool/torch t o pur e torch Sometimes y ou ma y want to use the R oc kpool pro vided TorchModule der iv ed classes with other software that e xpects pure torch (e.g. MLFlo w or Pytorc h Lightning). In that case y ou can use the to_torch() method to e xpose a pure torch inter f ace. Here w e sho w ho w that w orks, using the class RockpoolNet defined abo v e. 14.4.1 Rockpool API [82]: # - Instantiate the Rockpool class net = RockpoolNet() print (net) RockpoolNet with shape ( None , ) { Conv2d ' conv1 ' with shape ( None , ) Conv2d ' conv1 ' with shape ( None , ) Dropout2d ' dropout1 ' with shape ( None , ) Dropout2d ' dropout1 ' with shape ( None , ) Linear ' fc1 ' with shape ( None , ) Linear ' fc1 ' with shape ( None , ) } [83]: # - Rockpool dictionary-based parameter API print ( "Parameters:" , net . parameters()) Parameters: { ' conv1 ' : { ' weight ' : Parameter containing: (continues on ne xt page) 114 Chapter 14. Building Rockpool modules with T orch Rockpool, Release 3.0.3 (continued from pre vious pag e) tensor ([[[[ -0.2905 , 0.1654 , -0.1893 ] , [ -0.0804 , 0.1523 , -0.3320 ] , [ 0.0346 , 0.1020 , 0.0288 ]]] , [[[ -0.1374 , 0.3117 , 0.0558 ] , [ -0.3279 , 0.1651 , 0.3008 ] , [ 0.0011 , 0.1701 , -0.1425 ]]]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ -0.1574 , -0.1525 ] , requires_grad = True ) } , ' dropout1 ' : {} , ' fc1 ' : { ' weight ' : Parameter containing: tensor ([[ 0.0269 , -0.0310 , 0.0103 , ... , -0.0044 , -0.0114 , -0.0388 ] , [ 0.0409 , -0.0286 , 0.0256 , ... , 0.0166 , 0.0072 , -0.0476 ] , [ -0.0356 , 0.0108 , -0.0136 , ... , -0.0108 , 0.0268 , 0.0322 ] , ... , [ -0.0467 , -0.0277 , 0.0084 , ... , -0.0023 , 0.0034 , -0.0107 ] , [ -0.0191 , 0.0524 , -0.0005 , ... , 0.0305 , 0.0221 , -0.0304 ] , [ -0.0199 , 0.0537 , 0.0393 , ... , -0.0248 , 0.0081 , 0.0438 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ 0.0454 , 0.0186 , -0.0167 , -0.0339 , 0.0249 , 0.0274 , -0.0339 , -0.0019 , -0.0250 , -0.0271 ] , requires_grad = True ) } } [84]: # Evaluate one random 28x28 image random_data = torch . rand(( 1 , 1 , 28 , 28 )) # - Rockpool standard calling semantics print (net(random_data)) ( tensor ([[ -2.3239 , -2.3239 , -2.2561 , -2.2330 , -2.3239 , -2.3239 , -2.3239 , -2.3239 , -2.2750 , -2.3239 ]] , grad_fn = < LogSoftmaxBackward0 >) , { ' test_buf ' : tensor ([[ 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 . ] , [ 0 ., 0 ., 0 ., 0 . ]]) , ' conv1 ' : {} , ' dropout1 ' : {} , ' fc1 ' : {} } , {} ) 14.4. Conv erting from Rockpool/torch to pure torch 115 Rockpool, Release 3.0.3 122 Chapter 15. T raining a Rockpool network with T orch CHAPTER SIXTEEN HO W TO: C ONFIGURE AND PERF ORM CONS TRAINED OPTIMIZA TION IN ROCKPOOL Spiking Neural N etw orks present a more comple x optimisation problem than standard DNNs. This is due not onl y to the comple x dynamics of spiking neurons but also to the additional classes of parameters present in SNNs. DNNs usuall y optimise linear w eights and bias parameters, all of which share a common scale and which can adopt uncons trained finite values. SNNs, on the other hand, contain v ar i- ous time-constant parameters of v ar ious f or mulations, which can onl y v alidl y adopt a con- strained rang e of v alues. F or e xample, time constants in the f or m of synaptic and membrane 𝜏 ‘ 𝑠𝑚𝑢𝑠𝑡𝑏𝑒𝑝𝑜𝑠𝑖𝑡𝑖𝑣 𝑒.𝐷 𝑒𝑐𝑎𝑦 𝑓 𝑜𝑟 𝑚𝑢𝑙 𝑎𝑡𝑖𝑜𝑛𝑠𝑓 𝑜𝑟 𝑠𝑦 𝑛𝑎𝑝𝑠𝑒𝑎𝑛𝑑𝑚𝑒𝑚𝑏𝑟 𝑎𝑛𝑒𝑡𝑖𝑚𝑒𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑠𝑚𝑢𝑠𝑡𝑟 𝑎𝑛𝑔 𝑒 “(0 , 1)‘ . Fir - ing thresholds are usuall y also s tr ictl y positiv e v alues. Because of this need, R ockpool pro vides con v enient wa y s to access individual classes of parameters in a comple x netw ork via the Module.parameters() inter face and a set of tools f or easil y configur ing and imposing boundar y constraints during optimisation. This Ho w T o guide sho ws y ou ho w to use the training.torch_loss and training.jax_loss packag es and the f eatures of the utilities.tree_utils mini-librar y to set up constrained optimisation problems. [1]: # - Make sure additional required packages are installed import sys !{ sys.executable } -m pip install --quiet rich torch jax optax from rich import print import matplotlib.pyplot as plt plt . rcParams[ ' figure.figsize ' ] = [ 12 , 4 ] plt . rcParams[ ' figure.dpi ' ] = 300 import numpy as np import torch , jax , optax [ notice ] A new release of pip available: 22.2.2 -> 23.0.1 [ notice ] To update, run: pip install --upgrade pip /Users/Shared/anaconda3/envs/py38/lib/python3.8/site-packages/chex/_src/pytypes.py:37: ␣ ˓ → FutureWarning: jax.tree_structure is deprecated, and will be removed in a future ␣ ˓ → release. Use jax.tree_util.tree_structure instead. PyTreeDef = type(jax.tree_structure(None)) 123 Rockpool, Release 3.0.3 16.1 torch inter face for constrained optimization R ockpool supports both torch and jax optimisation back ends, with a common API f or setting up constrained optimi- sation. Here, w e demonstrate the torch inter f ace to set parameter constraints f or a single LIF module. [2]: # - Import the LIF module we will use from rockpool.nn.modules import LIFTorch # - Create a single LIF module net = LIFTorch( 1 ) print ( ' Module: ' , net) print ( ' Parameters: ' , net . parameters()) Module: LIFTorch with shape ( 1 , 1 ) Parameters: { ' tau_mem ' : Parameter containing: tensor ([ 0.0200 ] , requires_grad = True ) , ' tau_syn ' : Parameter containing: tensor ([[ 0.0200 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ 0 . ] , requires_grad = True ) , ' threshold ' : Parameter containing: tensor ([ 1 . ] , requires_grad = True ) } Ev en this single spiking neuron has f our parameters — tw o time constants f or synapse and membrane ( tau_syn and tau_mem ), which mus t be positiv e; a bias parameter , which can adopt an y value; and a threshold parameter which should also be positiv e. Suppose either time constant becomes neg ativ e during training. In that case, the dynamics of the module will be undefined and most lik el y unstable, leading to a breakdo wn of both netw ork dynamics and training. R ockpool pro vides a cos t function bounds_cost() , which imposes bounded parameter cons traints. W e also pro vide a helper function make_bounds() , which helps y ou build specifications f or which parameters should be cons trained and ho w . Belo w w e sho w ho w the cost function beha v es as a parameter approac hes and violates a constraint (0, 1). [3]: # - Import the ` make_bounds ` and ` bounds_cost ` helper functions from rockpool.training.torch_loss import make_bounds, bounds_cost xs = np . linspace( - 1 , 2 , 1001 ) cost = [bounds_cost({ ' x ' : torch . tensor(x)}, { ' x ' : 0. }, { ' x ' : 1. }) for x in xs] plt . figure() plt . plot(xs, cost) plt . plot([ 0 , 0 ], [ 0 , 3 ], ' r: ' ) plt . plot([ 1 , 1 ], [ 0 , 3 ], ' r: ' ) plt . xlabel( ' Parameter value ' ) plt . ylabel( ' Cost ' ); 124 Chap ter 16. How T o: Configure and per form constrained optimization in Rockpool Rockpool, Release 3.0.3 When no bounds are violated, the cost e v aluates to zero. A t the bounds, a cost of 1 is imposed, which increases f or increasing violations. N o w let ’ s see ho w to create and appl y bounds to the parameters of a LIF module. The training.torch_loss.make_bounds() function tak es the parameters of a R ockpool netw ork and g enerates lo w er - and upper -bounds configuration dictionaries. These dictionar ies mimic the structure of the netw ork parameters. [4]: # - Call ` make_bounds ` on the parameters of the module lb, ub = make_bounds(net . parameters()) print (lb, ub) { ' tau_mem ' : -inf, ' tau_syn ' : -inf, ' bias ' : -inf, ' threshold ' : -inf } { ' tau_mem ' : inf, ' tau_syn ' : inf, ' bias ' : inf, ' threshold ' : inf } By def ault, no parameters are constrained — the lo w er and upper bounds are set to neg ativ e and positiv e infinity , respectiv el y . W e set bounds b y changing the v alues to finite lo w er and upper bounds. Let ’ s use (0ms, 200ms) as the constraints f or tau_mem . [5]: lb[ ' tau_syn ' ] = 0. ub[ ' tau_syn ' ] = 200e-3 print (lb, ub) { ' tau_mem ' : -inf, ' tau_syn ' : 0.0 , ' bias ' : -inf, ' threshold ' : -inf } { ' tau_mem ' : inf, ' tau_syn ' : 0.2 , ' bias ' : inf, ' threshold ' : inf } [6]: # - Evaluate the boundary constraint cost print (bounds_cost(net . parameters(), lb, ub)) tensor ( 0 ., grad_fn = < SumBackward0 >) In this case, no bounds are violated, so the cost is zero. N o w let ’ s look at an e xample of a comple x netw ork with man y la y ers and module nes ting. W e’ll define the netw ork to use tw o different classes of leak parameters, needing different constraints on eac h class. [7]: from rockpool.nn.modules import LinearTorch from rockpool.nn.combinators import Sequential, Residual (continues on ne xt page) 16.1. torch interface for constrained optimization 125 Rockpool, Release 3.0.3 (continued from pre vious pag e) net = Sequential( LinearTorch(( 2 , 3 )), LIFTorch( 3 , leak_mode = "decays" ), Residual( LinearTorch(( 3 , 3 )), LIFTorch( 3 , leak_mode = "decays" ), ), LinearTorch(( 3 , 5 )), LIFTorch( 5 , leak_mode = "taus" ), ) print ( ' Network: ' , net) print ( ' Parameters: ' , net . parameters()) Network: TorchSequential with shape ( 2 , 5 ) { LinearTorch ' 0_LinearTorch ' with shape ( 2 , 3 ) LIFTorch ' 1_LIFTorch ' with shape ( 3 , 3 ) TorchResidual ' 2_TorchResidual ' with shape ( 3 , 3 ) { LinearTorch ' 0_LinearTorch ' with shape ( 3 , 3 ) LIFTorch ' 1_LIFTorch ' with shape ( 3 , 3 ) } LinearTorch ' 3_LinearTorch ' with shape ( 3 , 5 ) LIFTorch ' 4_LIFTorch ' with shape ( 5 , 5 ) } Parameters: { ' 0_LinearTorch ' : { ' weight ' : Parameter containing: tensor ([[ -1.7288 , 0.3565 , 0.7264 ] , [ -0.9882 , -1.2147 , 1.2812 ]] , requires_grad = True ) } , ' 1_LIFTorch ' : { ' alpha ' : Parameter containing: tensor ([ 0.5000 , 0.5000 , 0.5000 ] , requires_grad = True ) , ' beta ' : Parameter containing: tensor ([[ 0.5000 ] , [ 0.5000 ] , [ 0.5000 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ 0 ., 0 ., 0 . ] , requires_grad = True ) , ' threshold ' : Parameter containing: tensor ([ 1 ., 1 ., 1 . ] , requires_grad = True ) } , ' 2_TorchResidual ' : { ' 0_LinearTorch ' : { ' weight ' : Parameter containing: tensor ([[ 0.7141 , -1.3781 , -0.7695 ] , [ -0.8757 , -0.6188 , -0.4058 ] , [ -0.8914 , 0.4774 , 0.1480 ]] , requires_grad = True ) (continues on ne xt page) 126 Chap ter 16. How T o: Configure and per form constrained optimization in Rockpool Rockpool, Release 3.0.3 (continued from pre vious pag e) } , ' 1_LIFTorch ' : { ' alpha ' : Parameter containing: tensor ([ 0.5000 , 0.5000 , 0.5000 ] , requires_grad = True ) , ' beta ' : Parameter containing: tensor ([[ 0.5000 ] , [ 0.5000 ] , [ 0.5000 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ 0 ., 0 ., 0 . ] , requires_grad = True ) , ' threshold ' : Parameter containing: tensor ([ 1 ., 1 ., 1 . ] , requires_grad = True ) } } , ' 3_LinearTorch ' : { ' weight ' : Parameter containing: tensor ([[ 0.4936 , 1.3029 , -1.0018 , 1.1015 , -1.4031 ] , [ 0.1428 , 1.2207 , 1.1742 , 0.8693 , -0.3616 ] , [ 0.1114 , -1.3645 , -1.3504 , -0.6766 , -0.4882 ]] , requires_grad = True ) } , ' 4_LIFTorch ' : { ' tau_mem ' : Parameter containing: tensor ([ 0.0200 , 0.0200 , 0.0200 , 0.0200 , 0.0200 ] , requires_grad = True ) , ' tau_syn ' : Parameter containing: tensor ([[ 0.0200 ] , [ 0.0200 ] , [ 0.0200 ] , [ 0.0200 ] , [ 0.0200 ]] , requires_grad = True ) , ' bias ' : Parameter containing: tensor ([ 0 ., 0 ., 0 ., 0 ., 0 . ] , requires_grad = True ) , ' threshold ' : Parameter containing: tensor ([ 1 ., 1 ., 1 ., 1 ., 1 . ] , requires_grad = True ) } } This is a deepl y nes ted netw ork with a comple x set of parameters. Luc kily , R oc kpool pro vides se v eral con v enient tools that mak e it easy to build constraints e v en f or comple x netw orks. The Module.parameters() method allo w s y ou to easil y e xtract families of parameters, helping y ou identify all time constants, f or e xample. The Module.attributes_named() method allo ws y ou to specify par ticular named parame- ters. The mini-librar y tree_utils helps y ou easil y manipulate the parameter and cons traint dictionar ies to set chosen bounds. Here w e ’ll use the tree_utils.set_matching() function to set bounds f or chosen parameter sets. [8]: # - Import the tree utilities library import rockpool.utilities.tree_utils as tu # - Make template lower and upper bounds lb, ub = make_bounds(net . parameters()) (continues on ne xt page) 16.1. torch interface for constrained optimization 127 Rockpool, Release 3.0.3 (continued from pre vious pag e) # - Set lower bounds on "decays" and "taus" family parameters lb = tu . set_matching(lb, net . parameters( ' decays ' ), 0.5 ) lb = tu . set_matching(lb, net . parameters( ' taus ' ), 0. ) # - Set upper bounds on "decays" family parameters ub = tu . set_matching(ub, net . parameters( ' decays ' ), 1. ) # - Set an upper bound on a specific parameter name ub = tu . set_matching(ub, net . attributes_named( ' tau_syn ' ), 500e-3 ) print (lb, ub) { ' 0_LinearTorch ' : { ' weight ' : -inf } , ' 1_LIFTorch ' : { ' alpha ' : 0.5 , ' beta ' : 0.5 , ' bias ' : -inf, ' threshold ' : -inf } , ' 2_TorchResidual ' : { ' 0_LinearTorch ' : { ' weight ' : -inf } , ' 1_LIFTorch ' : { ' alpha ' : 0.5 , ' beta ' : 0.5 , ' bias ' : -inf, ' threshold ' : -inf } } , ' 3_LinearTorch ' : { ' weight ' : -inf } , ' 4_LIFTorch ' : { ' tau_mem ' : 0.0 , ' tau_syn ' : 0.0 , ' bias ' : -inf, ' threshold ' : -inf } } { ' 0_LinearTorch ' : { ' weight ' : inf } , ' 1_LIFTorch ' : { ' alpha ' : 1.0 , ' beta ' : 1.0 , ' bias ' : inf, ' threshold ' : inf } , ' 2_TorchResidual ' : { ' 0_LinearTorch ' : { ' weight ' : inf } , ' 1_LIFTorch ' : { ' alpha ' : 1.0 , ' beta ' : 1.0 , ' bias ' : inf, ' threshold ' : inf } } , ' 3_LinearTorch ' : { ' weight ' : inf } , ' 4_LIFTorch ' : { ' tau_mem ' : inf, ' tau_syn ' : 0.5 , ' bias ' : inf, ' threshold ' : inf } } [9]: # - Evaluate the boundary constraint cost for the full set of network parameters print (bounds_cost(net . parameters(), lb, ub)) tensor ( 0 ., grad_fn = < SumBackward0 >) Defining and e v aluating boundar y losses f or cons trained optimisation is made simple, e v en f or comple x netw orks! Imposing the constraints is as simple as including torch_loss.bounds_cost() as a f actor of the loss function dur ing training, as in the e xample belo w . [10]: from torch.optim import Adam from torch.nn import CrossEntropyLoss # - Initialise the optimiser optimizer = Adam(net . parameters() . astorch(), lr = 1e-3 ) func_loss = CrossEntropyLoss() # - Dummy dataset dataset = [(torch . tensor(np . random . rand( 1 , 1 , 2 ), dtype = torch . float), torch . tensor(np . (continues on ne xt page) 128 Chap ter 16. How T o: Configure and per form constrained optimization in Rockpool Rockpool, Release 3.0.3 (continued from pre vious pag e) ˓ → random . rand( 1 , 1 , 5 ), dtype = torch . float))] # - Optimiser loop over dataset for input , target in dataset: optimizer . zero_grad() output, _, _ = net( input ) # - Evaluate the functional and constraints losses loss = func_loss(output, target) + bounds_cost(net . parameters(), lb, ub) # - Perform the backward step loss . backward() optimizer . step() 16.2 jax inter face for constrained optimization The jax inter f ace f or constrained optimisation is identical to the torch interface. Here w e demonstrate a similar constrained optimisation problem as abo v e. [11]: # - Import the Rockpool NN modules from rockpool.nn.modules import LIFJax, LinearJax from rockpool.nn.combinators import Sequential # - Import tools from `` jax_loss `` instead of `` torch_loss `` from rockpool.training.jax_loss import make_bounds, bounds_cost # - Import the tree utility package from rockpool.utilities import tree_utils as tu [12]: # - Set up a simple network net = Sequential( LinearJax(( 2 , 3 )), LIFJax( 3 ), LinearJax(( 3 , 5 )), LIFJax( 5 ), ) print ( ' Network: ' , net) print ( ' Parameters: ' , net . parameters()) Network: JaxSequential with shape ( 2 , 5 ) { LinearJax ' 0_LinearJax ' with shape ( 2 , 3 ) LIFJax ' 1_LIFJax ' with shape ( 3 , 3 ) LinearJax ' 2_LinearJax ' with shape ( 3 , 5 ) LIFJax ' 3_LIFJax ' with shape ( 5 , 5 ) } Parameters: { ' 0_LinearJax ' : { ' weight ' : array ([[ 1.45853284 , -1.09957019 , -0.31666267 ] , [ 0.63834515 , 1.47955107 , 1.03358989 ]]) (continues on ne xt page) 16.2. jax interface for constrained optimization 129 Rockpool, Release 3.0.3 (continued from pre vious pag e) } , ' 1_LIFJax ' : { ' tau_mem ' : DeviceArray ([ 0.02 , 0.02 , 0.02 ] , dtype = float32 ) , ' tau_syn ' : DeviceArray ([[ 0.02 ] , [ 0.02 ] , [ 0.02 ]] , dtype = float32 ) , ' bias ' : DeviceArray ([ 0 ., 0 ., 0 . ] , dtype = float32 ) , ' threshold ' : DeviceArray ([ 1 ., 1 ., 1 . ] , dtype = float32 ) } , ' 2_LinearJax ' : { ' weight ' : array ([[ 0.3648217 , 0.34105733 , 1.23947428 , -0.42756732 , 0.6361447 ␣ ˓ → ] , [ -0.19837451 , 0.61290813 , 1.25214626 , 1.206278 , 0.70346237 ] , [ 0.73964399 , -1.02753273 , -0.28541291 , -1.10618743 , 0.78135608 ]]) } , ' 3_LIFJax ' : { ' tau_mem ' : DeviceArray ([ 0.02 , 0.02 , 0.02 , 0.02 , 0.02 ] , dtype = float32 ) , ' tau_syn ' : DeviceArray ([[ 0.02 ] , [ 0.02 ] , [ 0.02 ] , [ 0.02 ] , [ 0.02 ]] , dtype = float32 ) , ' bias ' : DeviceArray ([ 0 ., 0 ., 0 ., 0 ., 0 . ] , dtype = float32 ) , ' threshold ' : DeviceArray ([ 1 ., 1 ., 1 ., 1 ., 1 . ] , dtype = float32 ) } } W e ag ain use training.jax_loss.make_bounds() to build a template configuration f or constrained optimisation. W e use the tree handling librar y and Module.parameters() , to set lo w er -bounds cons traints on time constants. [13]: # - Build a template configuration lb, ub = make_bounds(net . parameters()) print (lb, ub) { ' 0_LinearJax ' : { ' weight ' : -inf } , ' 1_LIFJax ' : { ' bias ' : -inf, ' tau_mem ' : -inf, ' tau_syn ' : -inf, ' threshold ' : -inf } , ' 2_LinearJax ' : { ' weight ' : -inf } , ' 3_LIFJax ' : { ' bias ' : -inf, ' tau_mem ' : -inf, ' tau_syn ' : -inf, ' threshold ' : -inf } } { ' 0_LinearJax ' : { ' weight ' : inf } , ' 1_LIFJax ' : { ' bias ' : inf, ' tau_mem ' : inf, ' tau_syn ' : inf, ' threshold ' : inf } , ' 2_LinearJax ' : { ' weight ' : inf } , ' 3_LIFJax ' : { ' bias ' : inf, ' tau_mem ' : inf, ' tau_syn ' : inf, ' threshold ' : inf } } [14]: # - Set lower bounds for time constants lb = tu . set_matching(lb, net . parameters( ' taus ' ), 0. ) print (lb) 130 Chap ter 16. How T o: Configure and per form constrained optimization in Rockpool Rockpool, Release 3.0.3 { ' 0_LinearJax ' : { ' weight ' : -inf } , ' 1_LIFJax ' : { ' bias ' : -inf, ' tau_mem ' : 0.0 , ' tau_syn ' : 0.0 , ' threshold ' : -inf } , ' 2_LinearJax ' : { ' weight ' : -inf } , ' 3_LIFJax ' : { ' bias ' : -inf, ' tau_mem ' : 0.0 , ' tau_syn ' : 0.0 , ' threshold ' : -inf } } [15]: # - Evaluate the boundary constraint cost print (bounds_cost(net . parameters(), lb, ub)) 0.0 Theref ore, the R ockpool-pro vided inter f ace f or setting bounds is almost identical betw een torch and jax . Belo w w e sho w a v er y simple jax optimisation loop that incor porates the boundar y constraints during optimisation. [16]: # - Initialise the Adam optimiser with the initial network parameters optimizer = optax . adam( 1e-4 ) params = net . parameters() opt_state = optimizer . init(params) # - Use an MSE loss func_loss = lambda o, t: jax . numpy . mean((o - t) ** 2 ) # - Network evaluation and loss function def eval_loss (params, net, input , target): output, _, _ = net( input ) loss = func_loss(output, target) + bounds_cost(params, lb, ub) return loss # - Dummy dataset dataset = [(np . random . rand( 1 , 1 , 2 ), np . random . rand( 1 , 1 , 5 ))] # - Loop over dataset, evaluating loss and applying updates for input , target in dataset: loss_value, grads = jax . value_and_grad(eval_loss)(params, net, input , target) updates, opt_state = optimizer . update(grads, opt_state, params) params = optax . apply_updates(params, updates) 16.3 N e xt steps See T r aining a R oc kpool netw ork with Jax f or a jax training e xample that includes constraints. 16.3. N ext steps 131 Rockpool, Release 3.0.3 (continued from pre vious pag e) for _ in tqdm( range (num_epochs)): # - Get parameters for this iteration params = get_params(opt_state) # - Get the loss value and gradients for this iteration loss_val, grads = loss_vgf(params, modRNN . module, input_t, target_t) # - Update the optimiser opt_state = update_fun( next (i_trial), grads, opt_state) # - Keep track of the loss loss_t . append(loss_val) 0%| | 0/6000 [00:00<?, ?it/s] [9]: # - Plot the loss curve over training plt . plot(loss_t) plt . yscale( "log" ) plt . xlabel( "Iteration" ) plt . ylabel( "Loss" ) plt . title( "Training progress" ); Ok, the loss has decreased to a lo w v alue. Let ’ s see what the netw ork has lear ned! [10]: # - Plot the output of the trained reservoir # - Simulate with trained parameters modRNN . _module = modRNN . _module . set_attributes(get_params(opt_state)) modRNN . reset_all() ts_output, _, record_dict = modRNN(ts_input) # - Compare the output to the target ts_output . plot() ts_target . plot(ts_output . times, ls = "--" , lw = 2 ) plt . legend() plt . title( "Output vs target" ); 138 Chapter 17. Gradient descent tr aining of a rate-based recurrent network Rockpool, Release 3.0.3 If all has gone according to plan, the output of the reser v oir should closel y match the tar get signal. W e can see the effect of training b y e xamining the dis tr ibution of netw ork parameters belo w . [11]: TSContinuous . from_clocked( record_dict[ "1_RateJax" ][ "x" ][ 0 ], dt = dt, name = "Neuron state" ) . plot(skip = 20 , stagger = 1 ); [12]: # - Plot the network time constants plt . figure() plt . hist(modRNN . _module[ 1 ] . tau / 1e-3 , 21 ) plt . legend([ "Trained time constants" ]) plt . xlabel( "Time constant (ms)" ) plt . ylabel( "Count" ) plt . title( "Distribution of time constants" ); 17.3. N etwork model 139 Rockpool, Release 3.0.3 [13]: # - Plot the recurrent layer biases plt . figure() plt . stem(modRNN . _module[ 1 ] . bias) plt . title( "Distribution of trained biases" ) plt . xlabel( "Unit" ) plt . ylabel( "Bias" ); W e can e xamine something of the computational proper ties of the netw ork by finding the eig enspectr um of the Jacobian of the recur rent la y er . The Jacobian ˆ 𝐽 is giv en b y ˆ 𝐽 = ( ˆ 𝑊 𝑟 − 𝐼 ) ./ ˆ 𝑇 where 𝐼 is the identity matr ix, ./ denotes element-wise division, and 𝑇 is the matr ix composed of all time constants ˆ 𝜏 of the recur rent la y er . Belo w w e plot the eig en v alues 𝜆 of 𝐽 . In m y training result, sev eral comple x eig en values 𝜆 with real parts g reater than zero are present in the trained eig enspectr um. These cor respond to oscillator y modes, which are ob viously useful in g enerating the c hir p output. [14]: # - Plot the recurrent layer eigenspectrum J = modRNN . _module[ 1 ] . w_rec - np . identity(nResSize) J = J / modRNN . _module[ 1 ] . tau (continues on ne xt page) 140 Chapter 17. Gradient descent tr aining of a rate-based recurrent network Rockpool, Release 3.0.3 (continued from pre vious pag e) J0 = w_rec0 - np . identity(nResSize) J0 = J0 / tau0 plt . figure() eigs = np . linalg . eigvals(J) eigs0 = np . linalg . eigvals(J0) plt . plot(np . real(eigs0), np . imag(eigs0), "." ) plt . plot(np . real(eigs), np . imag(eigs), "o" ) plt . plot([ 0 , 0 ], [ - 100 , 100 ], "--" ) plt . legend( ( "Initial eigenspectrum" , "Trained eigenspectrum" , ) ) plt . xlim([ - 350 , 250 ]) plt . ylim([ - 100 , 100 ]) plt . title( "Eigenspectrum of recurrent weights" ) plt . xlabel( "Re($\lambda$)" ) plt . ylabel( "Im($\lambda$)" ); The Kernel crashed while executing code in the the current cell or a previous cell. ␣ ˓ → Please review the code in the cell(s) to identify a possible cause of the failure. ␣ ˓ → Click <a href= ' https://aka.ms/vscodeJupyterKernelCrash ' >here</a> for more info. View ␣ ˓ → Jupyter <a href= ' command:jupyter.viewOutput ' >log</a> for further details. 17.4 Summar y Gradient descent training does a good job of optimmising a dynamic recur rent netw ork f or a difficult task requir ing significant temporal memor y . jax pro vides a computationally efficient bac k -end as well as automatic differentiation of the recur rent la y er . The combination in R ockpool allo w s us to optimise not jus t the w eights and biases of a netw ork, but also to adapt the neuron dynamics to a desired task. 17.4. Summar y 141 Rockpool, Release 3.0.3 142 Chapter 17. Gradient descent tr aining of a rate-based recurrent network CHAPTER EIGHTEEN TRAINING A SPIKIN G NET W ORK WITH J AX This tutor ial demonstrates using R oc kpool and a Jax -accelerated LIF f eed-f or ward neuron la y er to per f or m g radient descent training of all netw ork parameters. The result is a trained spiking la y er whic h can g enerate a pre-defined signal from a noisy spiking input. 18.1 Requirements and housekeeping This e xample requires the R oc kpool pac kag e from SynSense, as well as jax and its dependencies. [1]: import jax [2]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) # - Rockpool imports from rockpool import TSEvent, TSContinuous from rockpool.nn.modules import LIFJax, LinearJax, ExpSynJax from rockpool.nn.modules.jax.jax_lif_ode import LIFODEJax from rockpool.nn.combinators import Sequential from rockpool.parameters import Constant # - Typing from typing import Callable, Dict, Tuple import types # - Numpy import numpy as np import copy # - Pretty printing try : from rich import print except : pass # TQDM from tqdm .autonotebook import tqdm (continues on ne xt page) 143 Rockpool, Release 3.0.3 (continued from pre vious pag e) # - Plotting imports and config import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 18.2 Signal gener ation from frozen noise task W e will use a single f eed-f or ward la y er of spiking neurons to con v er t a chosen patter n of random in put spik es o v er time, into a pre-defined temporal signal with comple x dynamics. The netw ork architecture is s trictly f eedf or w ard, but the spiking neurons nev er theless contain temporal dynamics in their synaptic and membrane signals, with e xplicit time constants. Some number of input c hannels Nin will contain independent Poisson spik es at some rate spiking_prob/dt . A single output channel should g enerate a chirp signal with increasing frequency , up to a maximum of chirp_freq_factor . Y ou can pla y with these parameters belo w . [3]: # - Define input and target Nin = 200 dt = 1e-3 chirp_freq_factor = 10 dur_input = 1000e-3 # - Generate a time base T = int (np . round(dur_input / dt)) timebase = np . linspace( 0 ,( T - 1 ) * dt, T) # - Generate a chirp signal as a target chirp = np . atleast_2d(np . sin(timebase * 2 * np . pi * (timebase * chirp_freq_factor))) . T target_ts = TSContinuous(timebase, chirp, periodic = True , name = "Target chirp" ) # - Generate a Poisson frozen random spike train spiking_prob = 0.01 input_sp_raster = np . random . rand(T, Nin) < spiking_prob input_sp_ts = TSEvent . from_raster( input_sp_raster, name = "Input spikes" , periodic = True , dt = dt ) # - Plot the input and target signals plt . figure() input_sp_ts . plot(s = 4 ) (target_ts * Nin / 2 + Nin / 2 ) . plot(color = "orange" , lw = 2 ) plt . legend() plt . title( "Input and target" ); 144 Chapt er 18. T raining a spiking network with Jax Rockpool, Release 3.0.3 18.3 LIF neuron The spiking neuron w e will use is a leaky integ rate-and-fire spiking neuron (“LIF” neuron). This neuron rece vies input spik e trains 𝑆 𝑖𝑛 ( 𝑡 ) = ∑︀ 𝑗 𝛿 ( 𝑡 − 𝑡 𝑗 ) , which are integrated via w eighted e xponential synapses. Synaptic cur rents are then integrated into a neuron state (“membrane potential”) 𝑉 𝑚𝑒𝑚 . The neuron obe ys the dynamics 𝜏 𝑚𝑒𝑚 · ˙ 𝑉 𝑚𝑒𝑚 + 𝑉 𝑚𝑒𝑚 = 𝐼 𝑠𝑦 𝑛 + 𝐼 𝑏𝑖𝑎𝑠 + 𝜎 𝜁 ( 𝑡 ) 𝜏 𝑠𝑦 𝑛 · ˙ 𝐼 𝑠𝑦 𝑛 + 𝐼 𝑠𝑦 𝑛 = 0 𝐼 𝑠𝑦 𝑛 + = 𝑊 𝑖𝑛 · 𝑆 𝑖𝑛 ( 𝑡 ) Where 𝜏 𝑚𝑒𝑚 and 𝜏 𝑠𝑦 𝑛 are membrane and synaptic time constants; 𝐼 𝑏𝑖𝑎𝑠 is a constant bias current f or each neuron; 𝜎 𝜁 ( 𝑡 ) is a white noise process with std. de v . 𝜎 . Output spik es are g enerated when 𝑉 𝑚𝑒𝑚 crosses the firing threshold 𝑉 𝑡ℎ = 0 . This process g enerates a spik e train 𝑆 ( 𝑡 ) as a ser ies of delta functions, and causes a subtractiv e reset of 𝑉 𝑚𝑒𝑚 : 𝑉 𝑚𝑒𝑚 > 𝑉 𝑡ℎ → 𝑆 ( 𝑡 ) = 𝐻 ( 𝑉 𝑚𝑒𝑚 ( 𝑡 )) , 𝑉 𝑚𝑒𝑚 = 𝑉 𝑚𝑒𝑚 − 1 The analog output signal is g enerated using a sur rog ate 𝑈 ( 𝑡 ) = tanh( 𝑥 + 1) / 2+0 . 5 The output of the netw ork 𝑜 ( 𝑡 ) is theref ore giv en b y 𝑜 ( 𝑡 ) = 𝑊 𝑜𝑢𝑡 · 𝑆 ( 𝑡 ) F or more detail, see the documentation f or the Jax module LIFJax . 18.4 Build a netw ork The netw ork architecture is a single f eedf or w ard la y er , with w eighted spiking in puts and outputs. Spiking is g enerated via a function that pro vides a sur rogate gradient in the bac kw ards pass. This per mits propagation of an error g radient through the la y er , making gradient-descent training possible. F or this regression task w e will also use an e xponential synapse la y er to per fpr m temporal smoothing of the output. R egressing to a smooth signal is much easier with a continuous output signal, than using the spik e deltas alone. 18.3. LIF neuron 145 Rockpool, Release 3.0.3 [4]: # - Network size N = 50 Nout = 1 input_scale = 1. [5]: # - Generate a network using the sequential combinator modFFwd = Sequential( LinearJax((Nin, N)), LIFJax(N, dt = dt), ExpSynJax(N), LinearJax((N, Nout)), ) print (modFFwd) JaxSequential with shape ( 200 , 1 ) { LinearJax ' 0_LinearJax ' with shape ( 200 , 50 ) LIFJax ' 1_LIFJax ' with shape ( 50 , 50 ) ExpSynJax ' 2_ExpSynJax ' with shape ( 50 , ) LinearJax ' 3_LinearJax ' with shape ( 50 , 1 ) } 18.5 Simulate initial state of netw ork If w e simulate the untrained netw ork with our random input spik es, w e don ’ t e xpect an ything sensible to come out. Let ’ s do this, and take a look at ho w the netw ork beha v es. [6]: # - Randomise the network state modFFwd . reset_state() # - Evolve with the frozen noise spiking input tsOutput, new_state, record_dict = modFFwd(input_sp_raster * input_scale, record = True ) # - Plot the analog output plt . figure() plt . plot(tsOutput[ 0 ]) [6]: [<matplotlib.lines.Line2D at 0x3186ab730>] 146 Chapt er 18. T raining a spiking network with Jax Rockpool, Release 3.0.3 W e can also e xamine the inter nal s tate of the netw ork, b y inter rogating record_dict : [7]: # - Make a function that converts `` record_dict `` def plot_record_dict (rd): Isyn_ts = TSContinuous . from_clocked( rd[ "1_LIFJax" ][ "isyn" ][ 0 ,: ,: , 0 ], dt, name = "Synaptic currents $I_ {syn} $" ) Vmem_ts = TSContinuous . from_clocked( rd[ "1_LIFJax" ][ "vmem" ][ 0 ], dt, name = "Membrane potential $V_ {mem} $" ) spikes_ts = TSEvent . from_raster( rd[ "1_LIFJax" ][ "spikes" ][ 0 ], dt, name = "LIF layer spikes" ) # - Plot the internal activity of selected neurons plt . figure() Isyn_ts . plot(stagger = 1.1 , skip = 5 ) plt . figure() Vmem_ts . plot(stagger = 1.1 , skip = 5 ) plt . figure() spikes_ts . plot(s = 4 ) plot_record_dict(record_dict) 18.5. Simulate initial state of network 147 Rockpool, Release 3.0.3 [6]: # - Scale down recurrent weights for stability net[ 2 ][ 1 ] . w_rec . data = net[ 2 ][ 1 ] . w_rec / 10. 27.3 Step 2: Extract the com putational gr aph for the ne tw ork T o obtain a g raph descr ibing the entire netw ork, whic h contains the computational flo w of inf or mation through the netw ork as w ell as all parameters, w e simply use the as_graph() method. F or more inf or mation, see Computational g r aphs in R oc kpool . [7]: print (net . as_graph()) GraphHolder "TorchSequential__11277641184" with 2 input nodes -> 2 output nodes 27.4 Step 3: Map the network t o a hardware specification W e no w need to chec k that the netw ork can be suppor ted b y the X y lo hardw are, and assign hardware resources to the v arious aspects of the netw ork architecture. F or e xample, each neuron in the netw ork mus t be assigned to a hardw are neuron. Each non-input w eight element in the netw ork must be assigned to a global hidden w eight matr ix f or X y lo. Output neurons in the final la y er mus t be assigned to output channels. The X y lo f amily includes se vral devices with differing HW blocks and suppor t. These are suppor ted b y independent subpac kag es under rockpool.devices.xylo , and named after the chip ID in y our HDK. R ockpool can detect this automaticall y f or y ou, and impor t the cor rect packag e, b y connecting to the HDK. If y ou do not ha v e a X y lo HDK, then y ou can use the rockpool.devices.xylo.syns61201 pac kag e suppor ting X y lo-A udio 2. [8]: # - Import the Xylo HDK detection function from rockpool.devices.xylo import find_xylo_hdks # - Detect a connected HDK and import the required support package connected_hdks, support_modules, chip_versions = find_xylo_hdks() found_xylo = len (connected_hdks) > 0 if found_xylo: hdk = connected_hdks[ 0 ] x = support_modules[ 0 ] else : assert False , ' This tutorial requires a connected Xylo HDK to run. ' The connected Xylo HDK contains a Xylo Audio v2 (SYNS61201). Importing ` rockpool.devices. ˓ → xylo.syns61201 ` T o conv er t the computational graph to a X ylo specfication, w e use the mapper() function. In order to retain floating- point representations f or parameters, w e can specifiy weight_dtype = float . See the documentation f or mapper() f or fur ther details. [9]: # - Call the Xylo mapper on the extracted computational graph spec = x . mapper(net . as_graph(), weight_dtype = ' float ' ) 250 Chapter 27. Quick-start with X ylo ™ SNN core Rockpool, Release 3.0.3 [10]: print (spec) { ' mapped_graph ' : GraphHolder "TorchSequential__11277641184" with 2 input nodes -> 2 ␣ ˓ → output nodes, ' weights_in ' : array ([[ -0.77903908 , 1.18929613 , 1.16114461 , 0.22493351 , 0 . ␣ ˓ → , 0 . , 0 . , 0 . ] , [ 1.32337248 , -1.16171956 , -1.33154929 , 0.3293041 , 0 . , 0 . , 0 . , 0 . ]]) , ' weights_out ' : array ([[ 0 . , 0 . ] , [ 0 . , 0 . ] , [ 0 . , 0 . ] , [ 0 . , 0 . ] , [ -0.84590757 , -1.19352996 ] , [ -0.10860074 , 0.6373018 ] , [ -0.16565597 , -0.92963493 ] , [ -0.2262736 , 0.42454898 ]]) , ' weights_rec ' : array ([[ 0 . , 0 . , 0 . , 0 . , 0. ˓ → 10326695 , -0.14892304 , 1.01041162 , -0.20021439 ] , [ 0 . , 0 . , 0 . , 0 . , 0.03674126 , -0.49414289 , -0.6105172 , 0.60044777 ] , [ 0 . , 0 . , 0 . , 0 . , 0.34814227 , -1.09550583 , 0.31232011 , -0.12054205 ] , [ 0 . , 0 . , 0 . , 0 . , -0.83039075 , 0.96797431 , -0.54736292 , 0.51089108 ] , [ 0 . , 0 . , 0 . , 0 . , -0.08836855 , 0.05024868 , 0.09914371 , 0.0179751 ] , [ 0 . , 0 . , 0 . , 0 . , -0.04342629 , 0.07340087 , 0.05501813 , 0.11716658 ] , [ 0 . , 0 . , 0 . , 0 . , 0.0911395 , 0.1181069 , 0.00574297 , 0.06705973 ] , [ 0 . , 0 . , 0 . , 0 . , -0.03105988 , -0.00630997 , 0.07385659 , -0.03389113 ]]) , ' dash_mem ' : array ([ 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 ]) , ' dash_mem_out ' : array ([ 4.32192802 , 4.32192802 ]) , ' dash_syn ' : array ([ 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 , 4.32192802 ]) , ' dash_syn_2 ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' dash_syn_out ' : array ([ 4.32192802 , 4.32192802 ]) , ' threshold ' : array ([ 1 ., 1 ., 1 ., 1 ., 10 ., 10 ., 10 ., 10 . ]) , ' threshold_out ' : array ([ 1 ., 1 . ]) , ' bias ' : array ([ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ]) , ' bias_out ' : array ([ 0 ., 0 . ]) , ' weight_shift_in ' : 0 , ' weight_shift_rec ' : 0 , ' weight_shift_out ' : 0 , ' aliases ' : [[ 4 ] , [ 5 ] , [ 6 ] , [ 7 ] , [] , [] , [] , []] , ' dt ' : 0.001 } 27.4. Step 3: Map the netw ork to a hardware specification 251 Rockpool, Release 3.0.3 27.5 Step 4: Quantize the specfication for the X ylo integer logic R ockpool pro vides a number of functions f or quantizing specifications f or X y lo, in the packag e rockpool.transform. quantize_methods . Here w e will use global_quantize() to automatically find a good shared representation of the netw ork parameters, that is compatible with the integ er logic on X y lo. [11]: from rockpool.transform import quantize_methods as q # - Quantize the specification spec . update(q . global_quantize( ** spec)) print (spec) { ' mapped_graph ' : GraphHolder "TorchSequential__11277641184" with 2 input nodes -> 2 ␣ ˓ → output nodes, ' weights_in ' : array ([[ -74 , 113 , 111 , 21 , 0 , 0 , 0 , 0 ] , [ 126 , -111 , -127 , 31 , 0 , 0 , 0 , 0 ]]) , ' weights_out ' : array ([[ 0 , 0 ] , [ 0 , 0 ] , [ 0 , 0 ] , [ 0 , 0 ] , [ -90 , -127 ] , [ -12 , 68 ] , [ -18 , -99 ] , [ -24 , 45 ]]) , ' weights_rec ' : array ([[ 0 , 0 , 0 , 0 , 10 , -14 , 96 , -19 ] , [ 0 , 0 , 0 , 0 , 4 , -47 , -58 , 57 ] , [ 0 , 0 , 0 , 0 , 33 , -104 , 30 , -11 ] , [ 0 , 0 , 0 , 0 , -79 , 92 , -52 , 49 ] , [ 0 , 0 , 0 , 0 , -8 , 5 , 9 , 2 ] , [ 0 , 0 , 0 , 0 , -4 , 7 , 5 , 11 ] , [ 0 , 0 , 0 , 0 , 9 , 11 , 1 , 6 ] , [ 0 , 0 , 0 , 0 , -3 , -1 , 7 , -3 ]]) , ' dash_mem ' : array ([ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ]) , ' dash_mem_out ' : array ([ 4 , 4 ]) , ' dash_syn ' : array ([ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ]) , ' dash_syn_2 ' : array ([ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]) , ' dash_syn_out ' : array ([ 4 , 4 ]) , ' threshold ' : array ([ 95 , 95 , 95 , 95 , 954 , 954 , 954 , 954 ]) , ' threshold_out ' : array ([ 106 , 106 ]) , ' bias ' : array ([ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]) , ' bias_out ' : array ([ 0 , 0 ]) , ' weight_shift_in ' : 0 , ' weight_shift_rec ' : 0 , ' weight_shift_out ' : 0 , ' aliases ' : [[ 4 ] , [ 5 ] , [ 6 ] , [ 7 ] , [] , [] , [] , []] , ' dt ' : 0.001 } 252 Chapter 27. Quick-start with X ylo ™ SNN core Rockpool, Release 3.0.3 27.6 Step 5: Con v er t the specification to a hardware configuration W e no w con v er t the netw ork specification to a hardw are configuration object f or the X y lo HDK. [12]: # - Use rockpool.devices.xylo.config_from_specification config, is_valid, msg = x . config_from_specification( ** spec) if not is_valid: print (msg) 27.7 Step 6: Deplo y the configuration to the X ylo HDK [13]: # - Use rockpool.devices.xylo.XyloSamna to deploy to the HDK if found_xylo: modSamna = x . XyloSamna(hdk, config, dt = dt) print (modSamna) XyloSamna with shape ( 2 , 8 , 2 ) 27.8 Step 7: Ev olv e the network on the X ylo HDK N o w w e will g enerate a random P oisson input, and e v ol v e the netw ork on the X y lo HDK using this input. Using R ockpool w e can record all inter nal states on the X ylo HDK during e v olution. N ot e that r ecor ding internal stat e slow s ev olution belo w r eal-time. [14]: # - Generate some Poisson input T = 100 f = 0.1 input_spikes = np . random . rand(T, Nin) < f TSEvent . from_raster(input_spikes, dt, name = ' Poisson input events ' ) . plot(); [15]: # - Evolve the network on the Xylo HDK if found_xylo: out, _, r_d = modSamna(input_spikes, record = True ) (continues on ne xt page) 27.6. Step 5: Con v er t the specification to a hardware configuration 253 Rockpool, Release 3.0.3 (continued from pre vious pag e) # - Show the internal state variables recorded print (r_d . keys()) dict_keys ([ ' Vmem ' , ' Isyn ' , ' Isyn2 ' , ' Spikes ' , ' Vmem_out ' , ' Isyn_out ' , ' times ' ]) [16]: # - Plot some internal state variables if found_xylo: plt . figure() plt . imshow(r_d[ ' Spikes ' ] . T, aspect = ' auto ' , origin = ' lower ' ) plt . title( ' Hidden spikes ' ) plt . ylabel( ' Channel ' ) plt . figure() TSContinuous(r_d[ ' times ' ], r_d[ ' Vmem ' ], name = ' Hidden membrane potentials ' ) . ˓ → plot(stagger = 127 ) plt . figure() TSContinuous(r_d[ ' times ' ], r_d[ ' Isyn ' ], name = ' Hidden synaptic currents ' ) . ˓ → plot(stagger = 127 ) 254 Chapter 27. Quick-start with X ylo ™ SNN core Rockpool, Release 3.0.3 27.9 Step 8: Simulate the HDK using a bit-precise simulator R ockpool comes bundled with X y loSim , a bit-precise simulator of the X y lo SNN core. The inter face to e v ol v e and record from a XyloSim object is identical to other R ockpool modules. W e create a XyloSim object using the same hardw are configuration that w e used to deplo y the netw ork to the X y lo HDK. [17]: modSim = x . XyloSim . from_config(config) print (modSim) XyloSim with shape ( 16 , 1000 , 8 ) [18]: # - Evolve the input over the network, in simulation out, _, r_d = modSim(input_spikes, record = True ) # - Show the internal state variables recorded print (r_d . keys()) dict_keys ([ ' Vmem ' , ' Isyn ' , ' Isyn2 ' , ' Spikes ' , ' Vmem_out ' , ' Isyn_out ' ]) [19]: # - Plot some internal state variables plt . figure() plt . imshow(r_d[ ' Spikes ' ] . T, aspect = ' auto ' , origin = ' lower ' ) plt . title( ' Hidden spikes ' ) plt . ylabel( ' Channel ' ) plt . figure() TSContinuous . from_clocked(r_d[ ' Vmem ' ], dt, name = ' Hidden membrane potentials ' ) . ˓ → plot(stagger = 127 ) plt . figure() TSContinuous . from_clocked(r_d[ ' Isyn ' ], dt, name = ' Hidden synaptic currents ' ) . ˓ → plot(stagger = 127 ); 27.9. Step 8: Simulate the HDK using a bit-precise simulator 255 Rockpool, Release 3.0.3 If y ou compare the traces here with those recorded from the X y lo HDK abo v e, y ou will see the y are identical. XyloSim pro vides a w a y to quickl y v er ify a netw ork configuration without requiring the X ylo HDK hardw are. 256 Chapter 27. Quick-start with X ylo ™ SNN core Rockpool, Release 3.0.3 27.10 Summar y The flo w -chart belo w summar ises the s teps in taking a R ockpool netw ork from a high-le v el definition to deplo yment on the X y lo HDK. [20]: Image(filename = ' xylo-pipeline.png ' ) [20]: 27.10. Summar y 257 Rockpool, Release 3.0.3 258 Chapter 27. Quick-start with X ylo ™ SNN core CHAPTER T WENT YEIGHT TRAINING A SPIKIN G NET W ORK T O DEPL O Y T O THE XYL O ™ DIGIT AL SNN [1]: # -- Some useful imports # - Time-series data from rockpool import TSContinuous, TSEvent # - Rich printing try : from rich import print except : pass # - Numpy import numpy as np # - Import and configure matplotlib for plotting import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ "figure.figsize" ] = [ 12 , 4 ] plt . rcParams[ "figure.dpi" ] = 300 28.1 Considerations This notebook sho w s ho w to define a spiking model using the LIFTorch class and train it on a simple task. F or simplicity , the used task is just to spik e at cer tain times giv en random but fix ed in put. In this case w e targ et the X y lo hardw are and hav e to consider the hardw are constraints. I.e. the hardware has to receiv e spiking input, has onl y 16 input c hannels and 1000 recur rentl y connected neurons. W e will use Samna to create and v alidate a model definiton f or X y lo and then use XyloSim to simulate it. No te Both Samna and X y loSim are required f or this tutorial. 259 Rockpool, Release 3.0.3 – 16 bit membrane potential – 16 bit synaptic cur rent – 8 bit w eights – 4 bit bitshift deca y f or membrane and synapses – special: possibl y 2 synapses per neuron • Simulation – Integration method: ∗ F or w ard euler , resolution betw een 1 ms and 10 ms These requirements can differ from X y lo device to de vice. See Ov er view of the X ylo ™ f amily f or more inf or mation. In order to r un our model on X y lo, w e ha v e to fulfill those requirements. The SNN in this e xample already fulfills the connectivtiy and neuron requirements. So the main difficulty is to bring the parameters to the cor rect f ormat and also to quantize the w eights and thresholds to 8 and 16 bits respectiv ely . F or that w e use the g raph representation of our netw ork and use the mapper() function to get the specs f or X y lo. [8]: from rockpool.devices.xylo.syns61201 import mapper spec = mapper(net . as_graph(), weight_dtype = "float" ) print (spec[ "weights_in" ]) [[ -0.35482565 -0.37593043 0.52281165 ... 0 . 0 . 0 . ] [ 0.34481534 0.31385767 -0.11158055 ... 0 . 0 . 0 . ] [ -0.30120176 -0.52312404 0.43335626 ... 0 . 0 . 0 . ] ... [ -0.62413222 0.59378749 0.70466232 ... 0 . 0 . 0 . ] [ -0.26751989 -0.16315432 0.23857698 ... 0 . 0 . 0 . ] [ 0.17672117 -0.08555783 0.45087662 ... 0 . 0 . 0 . ]] [9]: w_inp_float = spec[ "weights_in" ] w_rec_float = spec[ "weights_rec" ] w_out_float = spec[ "weights_out" ] aliases = spec[ "aliases" ] fig = plt . figure(figsize = ( 16 , 3 )) ax = fig . add_subplot( 141 ) ax . set_title( "w_inp float" ) im = ax . imshow(w_inp_float[:, :], aspect = "auto" , interpolation = "None" ) plt . colorbar(im) ax = fig . add_subplot( 142 ) ax . set_title( "w_rec float" ) im = ax . imshow(w_rec_float[:, :], aspect = "auto" , interpolation = "None" ) plt . colorbar(im) (continues on ne xt page) 266 Chapter 28. T raining a spiking network t o deplo y to the X ylo ™ digital SNN Rockpool, Release 3.0.3 (continued from pre vious pag e) ax = fig . add_subplot( 143 ) ax . set_title( "aliases" ) aliases_ = np . zeros_like(w_rec_float[:, :]) for i, a in enumerate (aliases): if len (a) > 0 : aliases_[i, a[ 0 ]] = 1 im = ax . imshow(aliases_, aspect = "auto" , interpolation = "None" ) ax = fig . add_subplot( 144 ) ax . set_title( "w_out float" ) im = ax . imshow(w_out_float, aspect = "auto" , interpolation = "None" ) plt . colorbar(im); As can be seen, the input w eight matr ix has a dimension of (16, 63) and contains the w eights of the firs t linear la y er of the model. The recur rent w eight matrix is nonzero in the top-r ight quar ter , reflecting that the first 63 neurons connect to the ne xt 63 neurons (filled with the w eights on the second linear la y er of the model). N ote that there are no recur rent connections as the diagonal bloc ks are all zero. In the model w e defined a skip connection skipping the hidden la y er . In T orch that ’ s easil y done b y the + operator , adding the activ ations of one la y er to another and hence skipping the la y ers in betw een (the dimensions mus t be compatible). W e g et the same beha vior in X y lo using aliases. If neuron ‘a ’ has alias ‘b’ means that the spike count of ‘b’ is increased b y the spike count of ‘a ’ . In our case, w e aliased the input neurons to the hidden neurons to add up their activ ations. The output w eight matr ix connects the second population to the output, hence there are non-zero values f or the las t 63 neurons to the tw o output neurons. The time-constants f or the e xponential deca y are less e x citing; the y are all 2 ms. But this timeconstant has to be translated to v alues f or bit-shift deca y (dash). The equation f or that is: 𝑑𝑎𝑠ℎ = [log 2 ( 𝜏 /𝑑𝑡 )] Hence, a timeconstant of 2ms with a simulation resolution of 1 ms is 1. But what is done with this bitshift of 1? Let ’ s ha v e an e xample and compare e xponential deca y to bitshift deca y . [10]: tau = 20e-3 # 20 ms dt_ = 1e-3 # 1 ms resolution dash = np . round(np . log2(tau / dt_)) . astype( int ) exp_propagator = np . exp( - dt_ / tau) simtime = 0.2 # 1 sec t_ = 0 (continues on ne xt page) 28.5. Deploying this netw ork to X yloSim 267 Rockpool, Release 3.0.3 (continued from pre vious pag e) v_tau = [ 1000 ] v_dash = [ 1000 ] while t_ < simtime: v_tau . append(v_tau[ - 1 ] * exp_propagator) if (v_dash[ - 1 ] >> dash) > 0 : v_dash . append(v_dash[ - 1 ] - (v_dash[ - 1 ] >> dash)) else : v_dash . append( int (np . clip(v_dash[ - 1 ] - 1 , 0 , np . inf))) t_ += dt_ plt . plot(np . arange( 0 , len (v_tau)) * dt_, v_tau, label = "exponential decay" ) plt . plot(np . arange( 0 , len (v_dash)) * dt_, v_dash, label = "bitshift decay" ) plt . legend() plt . xlabel( "Time (s)" ); As clear l y visible, the bitshift deca y beha v es similar to an e xponential deca y but poses limitations on time-constants and resolution. Ev en w orse, SNNs are v ery sensitiv e on the time-constants and hence e v en those ‘small’ differences can chang e the dynamics in the netw ork drasticall y . BUT bitshifts are e xtremely c heap to compute and put into hardware which is the main reason w e are using them on X y lo. N o w , let ’ s in v estig ate the quantization of w eights. W e use the f ollo wing function: [11]: from rockpool.transform import quantize_methods as q quant_spec = spec . copy() del quant_spec[ "mapped_graph" ] del quant_spec[ "dt" ] quant_spec . update(q . global_quantize( ** quant_spec)) print (quant_spec[ "weights_in" ]) [[ -43 -45 63 ... 000 ] [ 42 38 -13 ... 000 ] [ -36 -63 52 ... 000 ] ... [ -75 72 85 ... 000 ] [ -32 -20 29 ... 000 ] [ 21 -10 54 ... 000 ]] 268 Chapter 28. T raining a spiking network t o deplo y to the X ylo ™ digital SNN Rockpool, Release 3.0.3 N o w . let ’ s visualize the quantized w eight in compar ison to the floating point w eights. [12]: fig = plt . figure(figsize = ( 16 , 10 )) ax = fig . add_subplot( 321 ) ax . set_title( "w_inp float" ) ax . hist(np . ravel(w_inp_float[w_inp_float != 0 ]), bins = 2 ** 8 ) ax = fig . add_subplot( 322 ) ax . set_title( "w_inp quant" ) ax . hist(np . ravel(quant_spec[ "weights_in" ][quant_spec[ "weights_in" ] != 0 ]), bins = 2 ** 8 ) ax = fig . add_subplot( 323 ) ax . set_title( "w_rec float" ) ax . hist(np . ravel(w_rec_float[w_rec_float != 0 ]), bins = 2 ** 8 ) ax = fig . add_subplot( 324 ) ax . set_title( "w_rec quant" ) ax . hist( np . ravel(quant_spec[ "weights_rec" ][quant_spec[ "weights_rec" ] != 0 ]), bins = 2 ** 8 ) ax = fig . add_subplot( 325 ) ax . set_title( "w_out float" ) ax . hist(np . ravel(w_out_float[w_out_float != 0 ]), bins = 2 ** 8 ) ax = fig . add_subplot( 326 ) ax . set_title( "w_out quant" ) ax . hist( np . ravel(quant_spec[ "weights_out" ][quant_spec[ "weights_out" ] != 0 ]), bins = 2 ** 8 ); 28.5. Deploying this netw ork to X yloSim 269 Rockpool, Release 3.0.3 As y ou can see, the floating point w eights all belo w 0 while the quantized w eights got scaled to be between -128 and +127. In this case, w e are lucky and the dis tr ibution of w eights look v ery similar . U nluc ky cases w ould be: • W eight distribution is not centered around 0 • A f e w v er y strong w eights • N on-flat distribution In all those ‘unluc ky’ cases, the quantized w eights could not make use of the complete rang e and might the resulting netw ork beha vior might be v er y different compared with the floating point netw ork. There might be ‘tr ic ks ’ to a v oid those situations. In this case, though, e v er ything looks fine. So let ’ s go to the ne xt step and create a xy lo configuration and simulate that netw ork. [13]: from rockpool.devices.xylo.syns61201 import config_from_specification xylo_conf, is_valid, message = config_from_specification( ** quant_spec) print ( "Valid config: " , is_valid) Valid config: True [14]: from rockpool.devices.xylo.syns61201 import XyloSim from rockpool.timeseries import TSEvent [15]: sim = XyloSim . from_config(xylo_conf, dt = dt) . timed(add_events = True ) 270 Chapter 28. T raining a spiking network t o deplo y to the X ylo ™ digital SNN Rockpool, Release 3.0.3 [16]: # - Evaluate classes for i_class, [ input , target] in enumerate (ds): sim . reset_state() sim . reset_time() inp = TSEvent . from_raster( input . cpu() . numpy(), dt = dt, t_start = 0 ) output, _, rec_sim = sim(inp, record = True ) # - Plot output and target plt . figure() output . plot() plt . plot(np . arange( len (target)) * dt, target, "--" ) plt . xlabel( "Time (s)" ) plt . ylabel( "Value" ) plt . ylim([ 0 , target . max()]) plt . title( f"Class { i_class } " ) As y ou can see, the quantized model s till spik es at the same times as the floating point model (see figure after training). Let ’ s br iefly compare the inter nal dynamics. [17]: fig = plt . figure(figsize = ( 16 , 25 )) (continues on ne xt page) 28.5. Deploying this netw ork to X yloSim 271 Rockpool, Release 3.0.3 (continued from pre vious pag e) ax = fig . add_subplot( 5 , 2 , 1 ) plt . plot(rec_float[ "1_LIFBitshiftTorch" ][ "isyn" ] . squeeze( 0 ) . squeeze( - 1 ) . detach()) plt . plot( rec_float[ "2_TorchResidual" ][ "1_LIFBitshiftTorch" ][ "isyn" ] . squeeze( 0 ) . squeeze( - 1 ) . detach() ) ax . set_title( "Isyn inp / hidden float" ) ax = fig . add_subplot( 5 , 2 , 2 ) rec_sim[ "Isyn" ] . plot() ax . set_title( "Isyn recurrent layer quant" ) ax = fig . add_subplot( 5 , 2 , 3 ) plt . plot(rec_float[ "1_LIFBitshiftTorch" ][ "vmem" ] . squeeze( 0 ) . squeeze( - 1 ) . detach()) plt . plot( rec_float[ "2_TorchResidual" ][ "1_LIFBitshiftTorch" ][ "vmem" ] . squeeze( 0 ) . squeeze( - 1 ) . detach() ) ax . set_title( "Vmem inp / hidden float" ) ax = fig . add_subplot( 5 , 2 , 4 ) rec_sim[ "Vmem" ] . plot() ax . set_title( "Vmem recurrent layer quant" ) ax = fig . add_subplot( 5 , 2 , 5 ) spk_float = np . vstack( [ rec_float[ "1_LIFBitshiftTorch" ][ "spikes" ] . squeeze( 0 ) . detach() . T, rec_float[ "2_TorchResidual" ][ "1_LIFBitshiftTorch" ][ "spikes" ] . squeeze( 0 ) . detach() . T, ] ) nids, times = np . where(spk_float) plt . scatter(times, nids, s = 1 ) ax . set_title( "spikes inp / hidden float" ) ax = fig . add_subplot( 5 , 2 , 6 ) rec_sim[ "Spikes" ] . plot(s = 1 ) plt . scatter(times, nids, s = 1 ) ax . set_title( "spikes recurrent layer quant" ) ax = fig . add_subplot( 5 , 2 , 7 ) plt . plot(rec_float[ "4_LIFBitshiftTorch" ][ "isyn" ] . squeeze( 0 ) . squeeze( - 1 ) . detach()) ax . set_title( "Isyn output float" ) (continues on ne xt page) 272 Chapter 28. T raining a spiking network t o deplo y to the X ylo ™ digital SNN Rockpool, Release 3.0.3 (continued from pre vious pag e) ax = fig . add_subplot( 5 , 2 , 8 ) rec_sim[ "Isyn_out" ] . plot() ax . set_title( "Isyn output quant" ) ax = fig . add_subplot( 5 , 2 , 9 ) plt . plot(rec_float[ "4_LIFBitshiftTorch" ][ "vmem" ] . squeeze( 0 ) . squeeze( - 1 ) . detach()) ax . set_title( "Vmem output float" ) ax = fig . add_subplot( 5 , 2 , 10 ) rec_sim[ "Vmem_out" ] . plot() ax . set_title( "Vmem output quant" ); 28.5. Deploying this netw ork to X yloSim 273 Rockpool, Release 3.0.3 274 Chapter 28. T raining a spiking network t o deplo y to the X ylo ™ digital SNN Rockpool, Release 3.0.3 Although it ’ s probably hard to see as there are so man y neurons, the dynamics within the netw orks look v er y similar . 28.6 Deplo y to X ylo In the ne xt step, w e want to tak e this trained netw ork and deplo y it on the X y lo hardw are. From our X y loSim simulation, w e kno w already that the netw ork will r un on X y lo and should produce the same results. Hence, the transf er is easy and straight f or ward. Firs t step is to plug in the hardw are using the USB-C cable and find the board. [33]: from rockpool.devices.xylo import find_xylo_hdks xylo_hdk_nodes, mods, vers = find_xylo_hdks() print (xylo_hdk_nodes) The connected Xylo HDK contains a Xylo Audio v2 (SYNS61201). Importing ` rockpool.devices. ˓ → xylo.syns61201 ` [< samna.xyloA2TestBoard.XyloA2TestBoard object at 0x2d8044970 >] N o w , w e can use the same configuration f or X ylo as w e did bef ore f or X yloSim. [35]: if len (xylo_hdk_nodes) > 0 : x = mods[ 0 ] modSamna = x . XyloSamna(xylo_hdk_nodes[ 0 ], xylo_conf, dt = dt) . timed() else : modSamna = None print ( "No Xylo board found!" ) W e can also r un the netw ork on X y lo in the same wa y and see that X y lo produces the same output as X y loSim did. [36]: # - Evaluate classes if modSamna is not None : for i_class, [ input , target] in enumerate (ds): modSamna . reset_state() modSamna . reset_time() inp = TSEvent . from_raster( input . cpu() . numpy(), dt = dt, t_start = 0 ) output, _, rec_sim = modSamna(inp, record = True ) # - Plot output and target plt . figure() output . plot() plt . plot(np . arange( len (target)) * dt, target, "--" ) plt . xlabel( "Time (s)" ) plt . ylabel( "Value" ) plt . ylim([ 0 , target . max()]) plt . title( f"Class { i_class } " ) else : print ( "No Xylo board found!" ) 28.6. Deploy t o X ylo 275 Rockpool, Release 3.0.3 (continued from pre vious pag e) ax . set_xticks([]) ax . set_xlabel( "" ) ax = fig . add_subplot( 514 ) rec[ "rect" ] . plot() ax . set_xticks([]) ax . set_xlabel( "" ) ax = fig . add_subplot( 515 ) plt . imshow( filt_spikes . raster(dt = 10e-3 , add_events = True ) . T, aspect = "auto" , origin = "lower" ) ax . set_xlabel( "Time (s)" ) ax . set_ylabel( "Output channel" ) ax . set_title( "Spiking output" ); 282 Chapter 29. Using the analog frontend model Rockpool, Release 3.0.3 In this pre vious plot w e can see man y things. Let ’ s go through it piece b y piece. 29.3.1 Input The first panel is simpl y the ra w input signal. 29.3.2 LNA The lo w -noise amplifier pro vides nor malisation and pre-amplification of the input signal, and simulates the distortion and nonlinear ity present in the HW . 29.3. Input g eneration 283 Rockpool, Release 3.0.3 29.3.3 BPF The bandpass filter g et activ e in the sequence of their center frequency . Y ou can also see that their center frequency is log ar ithmicall y dis tr ibuted. The center frequency is calculated using this eq uation: 𝑓 𝑐 𝑖 = 𝑓 𝑐 𝑖 − 1 𝑓 𝑖 factor + 𝑓 𝑐 mismatc h Also, the width of the filter is scaled with its center frequency . This can be manipulated using the Q factor . 29.3.4 Rectification The rectification cor responds to an abs operation but is also subject to noise. 29.3.5 Spike con version As can be seen in the last panel, the different channels emit spik es cor responding to the center frequencies of their band-pass filters. The spik e con v ersion is done b y charging a capacitor with a current cor responding to the output of the full-w a v e recifier . If the capacitor reaches a threshold, a spike is produced. As the capacitor is rather small on the hardware, the spik e rate can g et v er y high. The solution w as to use a digital counter . The digital counter allo w s onl y e v er y n th spik e to pass and drops the rest. n can be set using the digital_counter parameter The capacitor is subject to leak, which can be set with the leakage parameter . It can be used to lo w er the impact of the noise floor . If the leak is high, small bac kground noise does not lead to threshold crossing. T r y it, if the leak is reduced, the noise g enerated b y the AFE g et ’ s visible in the spiking output. 284 Chapter 29. Using the analog frontend model CHAPTER THIRT Y QUICK ST ART WITH XYL O ™ A UDIO 3 NOTE : X y loA udio 3 requires samna==0.39.6 or higher This tutor ial will re vie w the steps and tools req uired to build and train audio applications f or deplo yment on X y lo ™ A udio 3. A typical pipeline to build and deplo y an audio application f or X y lo ™ A udio 3 contains the f ollo wing steps: • preprocessing: con v er ting audio signals to spik e trains • designing and training an SNN model with spik e-encoded audio data • deplo ying the trained model in X y lo ™ A udio 3 HDK The f ollo wing diag ram illustrates these s teps along with the required tools from R ockpool, highlighted in green: • AFESimPDM and AFESimExternal are tools in R ockpool that simulate the A udio Front End (AFE) of X y lo ™ A udio 3, used as a preprocessing step to con v er t audio signals to spik e trains. • XyloSamna and XyloMonitor are R ockpool APIs that interface the user and X ylo ™ A udio 3 HDK. These APIs are designed with user -fr iendliness in mind, making the interaction with X y loA udio 3 a seamless e xperience. Please see: Using AFESim as an audio tr ansf orm where w e elaborated on using AFESimPDM and AFESimExternal as an audio transf or m to g enerate spik e-encoded samples. In the In Depth section of the R ockpool documentation at left, y ou can also see more details about the process of designing and training a model in R ockpool. 30.1 Ov er view of training and deplo yment flow f or X ylo ™ A udio 3 [1]: from IPython.display import Image Image( "figures/tools_diagram-150.png" ) 285 Rockpool, Release 3.0.3 [1]: 286 Chapter 30. Quick st ar t with X ylo ™ Audio 3 CHAPTER THIRT Y ONE OPERA TION T YPES OF XYL O A UDIO 3 X y loA udio 3 HDK has tw o different operation modes: • A cceler at ed time • Real-time T w o different deplo yments can be per f or med depending on the operation mode. Both require mapping the model’ s configuration into the X y lo SNN core and reading the output spik es. 1. Deployment wit h liv e audio fr om micr ophone : T rained models can be tested with liv e audio pla y ed to the micro- phone. This type of deplo yment r uns in Real-time mode. 2. Deployment by bypassing t he micr ophone path : T rained models can be tes ted with pre-g enerated spike-encoded audios. This type of deplo yment r uns in A cceler at ed time mode. W e use R ockpool’ s XyloSamna and XyloMonitor APIs to manage the deplo yment pipelines in deplo yment types 1 and 2, respectiv el y .. F or more details and an e xample of each deplo yment, please see the tutor ial Using X yloSamna and X yloMonit or to deploy a model on X yloAudio 3 HDK 287 Rockpool, Release 3.0.3 288 Chapter 31. Operation types of X yloAudio 3 CHAPTER THIRT Y T W O USING AFESIM AS AN A UDIO TRANSFORM R ockpool contains a simulation of the A udio Front End (AFE) of X y loA udio 3, which is used as a pre-processing s tep to con v er t audio signals to spik e trains. The con v er ted v ersion of audio can be used in the f ollo wing scenar ios: • As a training sample to train an SNN model in R ockpool • As a test sample to tes t a model on the X y loA udio 3 SNN core f or debugging pur poses. This is done b y b ypassing the microphone and AFE in the HDK. See the r elated tutorial f or more inf or mation. In this tutor ial, w e will ref er to the AFE simulator in X y loA udio 3 as AFESim3 and will go through an e xample of ho w to configure and use AFESim3 as an audio transf or m f or a train or test pipeline. There are tw o main modes in the AFESim3 module in R ockpool: • AFESimExternal – This mode of AFESim3 is independent of the microphone type. It bypasses the microphone path and passes an e xter nal audio (14-bit QU ANTIZED signal) into the filterbank and divisiv e nor malization module • AFESimPDM – In this mode, audio samples are passed through a preprocessing chain composed of PDM microphone model, filter bank and divisiv e nor malization module Using AFESimExternal is recommended f or de v eloping applications, while AFESimPDM is more suitable f or advanced debugging tasks. 32.1 AFESimExternal As illustrated in the diagram belo w , AFESimExternal receiv es input audio as an arra y , resamples and quantizes it to 14-bit f or mat, and passes it to the filter bank (which co v ers 16 frequency bands betw een 100 Hz and 17 KHz). Depending on the mode selected f or spike_gen_mode , fix ed or adaptiv e thresholds will be applied to filter output channels to g enerate a spik e train. spike_gen_mode is b y def ault set to ' divisive_norm ' , and changing it and related parameters ( low_pass_averaging_window , rate_scale_factor , dn_EPS ) is not recommended. The Divisiv e Normalization (DN) module regulates the noise sensitivity of different frequency bands of the filter bank b y applying adaptiv e thresholds. If the a v erag e po w er of a filter in a specific time windo w is less than 𝜖 , that filter’ s threshold will be adapted to g enerate f e w er spikes. The user can deactiv ate Divisiv e Normalization onl y f or debugging pur poses b y c hoosing spike_gen_mode = ' threshold ' and passing fixed_threshold_vec . The spik e train is rasterized with a giv en dt , which should the time s tep used in y our SNN model. [1]: import warnings from IPython.display import Image (continues on ne xt page) 289 Rockpool, Release 3.0.3 (continued from pre vious pag e) warnings . filterwarnings( "ignore" ) Image( "figures/afesim_external.png" ) [1]: The f ollo wing transf or m can conv er t audio: np.ndarray samples to spike trains: [2]: import numpy as np import matplotlib.pyplot as plt plt . rcParams[ ' figure.dpi ' ] = 300 from typing import Union, Optional, Tuple from rockpool.devices.xylo.syns65302 import AFESimExternal dt_s = 0.009994 # AFESimExternal afesim_external = AFESimExternal . from_specification(spike_gen_mode = "divisive_norm" , fixed_threshold_vec = None , rate_scale_factor = 63 , low_pass_averaging_window = 84e-3 , dn_EPS = 32 , dt = dt_s, ) WARNING:root: ` dn_rate_scale_bitshift ` = (6, 0) is obtained given the target ` rate_scale_ ˓ → factor ` = 63, with diff = 0.000000e+00 WARNING:root: ` dn_low_pass_bitshift ` = 12 is obtained given the target ` low_pass_ ˓ → averaging_window ` = 0.084, with diff = 1.139200e-04 WARNING:root: ` down_sampling_factor ` = 488 is obtained given the target ` dt ` = 0.009994, ␣ ˓ → with diff = -2.400000e-07 32.2 AFESimPDM The diagram belo w illus trates the difference betw een AFESimPDM and AFESimExternal . AFESimPDM includes inter - nall y a simulation of a digital microphone model, composed of a sigma-delta modulator , and pol yphase lo wpass filter to con v er t the PDM signal to 14-bit quantized data. This module can be used when debugging the PDM modules on X y loA udio 3. [3]: Image( "figures/afesimpdm.png" ) 290 Chapter 32. Using AFESim as an audio transform Rockpool, Release 3.0.3 [3]: The f ollo wing transf or m can conv er t audio: np.ndarray samples to spike trains: [4]: from rockpool.devices.xylo.syns65302 import AFESimPDM # AFESimPDM afesim_pdm = AFESimPDM . from_specification(spike_gen_mode = "divisive_norm" , fixed_threshold_vec = None , rate_scale_factor = 63 , low_pass_averaging_window = 84e-3 , dn_EPS = 32 , dt = dt_s, ) WARNING:root: ` dn_rate_scale_bitshift ` = (6, 0) is obtained given the target ` rate_scale_ ˓ → factor ` = 63, with diff = 0.000000e+00 WARNING:root: ` dn_low_pass_bitshift ` = 12 is obtained given the target ` low_pass_ ˓ → averaging_window ` = 0.084, with diff = 1.139200e-04 WARNING:root: ` down_sampling_factor ` = 488 is obtained given the target ` dt ` = 0.009994, ␣ ˓ → with diff = -2.400000e-07 32.3 Applying AFESim3 transform W e appl y a tes t audio (a 3 second sample) to both introduced AFESim3 transf or ms to g enerate our pre-recorded data [5]: ! pip install --quiet librosa import librosa audio_path = ' audio_sample/scream_sample.wav ' test_sample, sr = librosa . load(audio_path, sr = None ) test_sample = np . expand_dims(test_sample, axis = 0 )[ 0 ] [6]: # Use AFESimExternal Transform out_external,_,_ = afesim_external((test_sample, sr)) # Use AFESimPDM Transform out_pdm,_,_ = afesim_pdm((test_sample, sr)) WARNING:2025-04-14 17:53:25,513:jax._src.xla_bridge:969: An NVIDIA GPU may be present on ␣ ˓ → this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu. WARNING:jax._src.xla_bridge:An NVIDIA GPU may be present on this machine, but a CUDA- ˓ → enabled jaxlib is not installed. Falling back to cpu. 32.3. Applying AFESim3 transform 291 Rockpool, Release 3.0.3 (continued from pre vious pag e) import simpleaudio as sa import numpy as np def get_wave_object (test_file): sample_rate, data = wavfile . read(test_file) duration = int ( len (data) / sample_rate) # in seconds n = data . ndim if data . dtype == np . int8: bytes_per_sample = 1 elif data . dtype == np . int16: bytes_per_sample = 2 elif data . dtype == np . float32: bytes_per_sample = 4 else : raise ValueError ( "recorded audio should have 1 or 2 bytes per sample!" ) wave_obj = sa . WaveObject( audio_data = data, num_channels = data . ndim, bytes_per_sample = bytes_per_sample, sample_rate = sample_rate ) return duration,wave_obj Requirement already satisfied: simpleaudio in /home/vleite/.pyenv/versions/3.11.11/envs/ ˓ → rockpool311/lib/python3.11/site-packages (1.0.4) [ notice ] A new release of pip is available: 24.0 -> 25.3 [ notice ] To update, run: python -m pip install --upgrade pip W arning : The ne xt cell will pla y an audio sample f or each class (glass-break, gun-shot, scream, back ground). If y ou w ould like to tes t the detection using the XYloAudio de v elopment board, make sure the microphone on the board is close to y our PC speak er , at a reasonable v olume. [7]: for sound in rare_sounds: print ( f ' Testing { sound } sound ' ) # Load audio sample test_audio = f ' audio_sample/ { sound } _sample.wav ' duration, wave_obj = get_wave_object(test_audio) T = int (duration / model_dt) # timesteps # Instantiate XyloMonitor, deploy network to device xylo_monitor = XyloMonitor(device = xa3, config = xylo_conf, dt = model_dt, output_mode = ˓ → ' Spike ' , dn_active = True , main_clk_rate = 12.5 ) # Evolve XyloMonitor object play_obj = wave_obj . play() out, state, rec = xylo_monitor . evolve(input_data = np . zeros([T, Nin]), record_ ˓ → power = True ) play_obj . wait_done() (continues on ne xt page) 298 Chapter 33. Using X yloSamna and X yloMonitor t o deplo y a model on X yloAudio 3 HDK Rockpool, Release 3.0.3 (continued from pre vious pag e) # Prediction prediction = rare_sounds[np . argmax(np . sum(out, axis = 0 ))] print ( f ' Detected sound: { prediction } \n ' ) Testing glass sound Detected sound: glass Testing gun sound Detected sound: gun Testing scream sound Detected sound: scream Testing background sound Detected sound: background 33.3.2 Po wer consumption W e can record the po w er consumption b y setting “record 𝑝 𝑜𝑤 𝑒𝑟 = 𝑇 𝑟 𝑢𝑒 “ 𝑤 ℎ𝑖𝑙 𝑒𝑒𝑣 𝑜𝑙 𝑣 𝑖𝑛𝑔 𝑤 𝑖𝑡ℎ : 𝑝𝑦 : 𝑐𝑙 𝑎𝑠𝑠 : ‘ .𝑠𝑦 𝑛𝑠 65302 .𝑋 𝑦 𝑙 𝑜𝑀 𝑜𝑛𝑖𝑡𝑜𝑟 ‘ .𝑇 ℎ𝑖𝑠𝑤 𝑖𝑙𝑙 𝑟 𝑒𝑐𝑜𝑟 𝑑𝑡ℎ𝑒𝑝𝑜𝑤 𝑒𝑟 𝑖𝑛𝑡ℎ𝑒𝑡ℎ𝑟 𝑒𝑒𝑐ℎ𝑎𝑛𝑛𝑒𝑙 𝑠 : “ 𝑖𝑜 “ , “ 𝑎𝑛𝑎𝑙 𝑜𝑔 “ 𝑎𝑛𝑑 “ 𝑑𝑖𝑔 𝑖𝑡𝑎𝑙 “ . N ote: w e can configure the chip to run with a slo wer cloc k to sa v e energy . N ote, ho w e v er , that w e need to use a cloc k speed that is f as t enough to finish the data processing in one step. [8]: io_power = np . mean(rec[ ' io_power ' ]) analog = np . mean(rec[ ' analog_power ' ]) digital = np . mean(rec[ ' digital_power ' ]) print ( f ' XyloAudio 3 \n io: \t { np . ceil(io_power * 1e6 ) : .0f } µ W \t AFE core: \t { np . ceil(analog * ␣ ˓ → 1e6 ) : .0f } µ W \t DFE+SNN core: \t { np . ceil(digital * 1e6 ) : .0f } µ W \n ' ) XyloAudio 3 io: 456 µ W AFE core: 13 µ W DFE+SNN core: 588 µ W [ ]: 33.3. Using X yloMonitor in Real Time mode 299 Rockpool, Release 3.0.3 300 Chapter 33. Using X yloSamna and X yloMonitor t o deplo y a model on X yloAudio 3 HDK CHAPTER THIRT YF OUR B ANDP ASS FIL TERING IN A UDIO FRONT END (AFE) OF XYL O ™ A UDIO 3 The k e y f eature e xtraction module in X ylo ™ A udio 3 is the collection of Mel-filterbanks, which compute the time- frequency transf orm of the input audio signal. In this tutor ial, w e will revie w the main f eatures of the filter bank and pro vide instructions on ho w to reconfigure it if needed. The central frequency and all filterbank parameters ha v e been designed f or efficient audio processing, co v er ing the rang e of 100 𝐻 𝑧 up to around 17 𝐾 𝐻 𝑧 audio spectr um. Main f eatures of the filterbank include: • Log ar ithmicall y spaced, matc hed to human perception mechanism, with the f ollo wing freq uency scaling 𝛼 and quality f actor 𝑄 . 𝛼 = ( 17000 100 ) 1 / (16 − 1) = 170 1 15 = 1 . 4083 and 𝑄 ≈ 6 • Designed as order -1 Butter w or th filters with digital transf er function: 𝐻 ( 𝑧 ) = 𝑏 ( 𝑧 ) 𝑎 ( 𝑧 ) = 𝑏 0 + 𝑏 1 𝑧 + 𝑏 2 𝑧 2 𝑎 0 + 𝑎 1 𝑧 + 𝑎 2 𝑧 2 . • Implemented as a cascaded of AR (auto-regressiv e filter) 𝐻 1 ( 𝑧 ) = 1 𝑎 ( 𝑧 ) f ollo w ed b y the MA (mo ving a v erag e) filter 𝐻 2 ( 𝑧 ) = 𝑏 ( 𝑧 ) , as illustrated in F igure 1. In R ockpool, the bandpass filtering is implemented as a quantized digital filterbank with hard-coded parameters (see AFESimExternal or AFESimPDM ). Def ault central frequencies in X y lo ™ A udio 3 are: [1]: import numpy as np f0 = 100 f15 = 17000 N_filters = 16 # number of filters alpha = np . power(f15 / f0, 1 / (N_filters - 1 )) freqs = [f0] for i in range ( 1 ,N_filters): freqs . append( int (alpha * freqs[ - 1 ])) print ( f ' designed filter centers are: { freqs } ' ) designed filter centers are: [100, 140, 197, 277, 390, 549, 773, 1088, 1532, 2157, 3037, ␣ ˓ → 4277, 6023, 8482, 11945, 16822] After parameter quantization, there will be a shift in the central frequency of filters. Theref ore, actual v alues f or centers of filters in AFESim and HDK are: [105 , 136 , 201 , 279 , 390 , 542 , 762 , 1070 , 1503 , 2110 , 2963 , 4161 , 5843 , 8204 , 11582 , 16611] 301 Rockpool, Release 3.0.3 Let ’ s instantiate AFESimExternal , as an e xample: [2]: from rockpool.devices.xylo.syns65302 import AFESimExternal import warnings warnings . filterwarnings( ' ignore ' ) dt_s = 0.009994 afesim_external = AFESimExternal . from_specification(spike_gen_mode = "divisive_norm" , fixed_threshold_vec = None , rate_scale_factor = 63 , low_pass_averaging_window = 84e-3 , dn_EPS = 32 , dt = dt_s,) WARNING:root: ` dn_rate_scale_bitshift ` = (6, 0) is obtained given the target ` rate_scale_ ˓ → factor ` = 63, with diff = 0.000000e+00 WARNING:root: ` dn_low_pass_bitshift ` = 12 is obtained given the target ` low_pass_ ˓ → averaging_window ` = 0.084, with diff = 1.139200e-04 WARNING:root: ` down_sampling_factor ` = 488 is obtained given the target ` dt ` = 0.009994, ␣ ˓ → with diff = -2.400000e-07 Let ’ s demonstrate the response of filters in the simulator b y passing monotone sinusoids as input (firs t and fifth filters with central frequencies 105 𝐻 𝑧 and 390 𝐻 𝑧 ). [9]: from rockpool.devices.xylo.syns65302.afe import ChipButterworth import matplotlib.pyplot as plt test_freqs = [ 105 , 390 ] #input frequencies fs = 48000 #sampling rate N = 5 #input duration (seconds) time = np . linspace( 0 ,N, int (N * fs)) EPS = 0.00001 fb = ChipButterworth() B_in = fb . bd_list[ 0 ] . B_in + 4 # quantization range for input signal plt . figure(figsize = ( 11 , 4 )) for i,f in enumerate (test_freqs): sig_in = np . sin( 2 * np . pi * f * time) sig_in = sig_in / np . max(np . abs(sig_in)) * ( 1 + EPS) * 2 ** (B_in - 1 ) # quantize the ␣ ˓ → sinal q_sig_in = sig_in . astype(np . int64) output,_, _ = afesim_external((q_sig_in,fs)) ax = plt . subplot( 1 , 2 ,i + 1 ) plt . imshow(output . T, aspect = ' auto ' ); plt . grid( True ); plt . xlabel( ' Time(sec) ' ); plt . ylabel( ' Freq(Hz) ' ) ax . set_xticks( range ( 0 , 500 , 100 )); ax . set_yticks( range (N_filters)) ax . set_xticklabels( [ int (t * np . round(dt_s, decimals = 2 )) for t in ax . get_xticks()]) ax . set_yticklabels(freqs); plt . colorbar() 302 Chapter 34. Bandpass Filtering In Audio F ront End (AFE) Of X ylo ™ Audio 3 Rockpool, Release 3.0.3 34.1 Reconfiguration of filter parameters [4]: from IPython.display import Image, display, Markdown display(Image( "figures/block_diagram.png" )) display(Markdown( "**Figure 1:** Overview of the implemented filter architecture." )) Figure 1: Ov er vie w of the implemented filter architecture. As mentioned abo v e, the design v alues of the filter parameters are suitable f or efficient audio processing. The y hav e been carefull y c hosen to ensure good co v erag e of the range betw een 100 𝐻 𝑧 and 17 𝑘 𝐻 𝑧 , as w ell as numer ical stability . Changing these parameters is not r ecommended f or audio applications. Ho w e v er , the filter parameters can be modified b y e xper t users in the simulator and also reconfigured in the rele v ant registers in hardw are, f or shifting the filter centers to desired v alues (f or special non-audio use cases). In g eneral IIR filters are sensitiv e to quantization and coefficient rounding and w e advise users to approach filter modification with caution, especially reg arding stability and numerical precision. The main parameters to modify are 𝑎 1 and 𝑎 2 in 𝐻 ( 𝑧 ) . Using scip y .signal.iir peak(), w e can compute the transf er 34.1. Reconfiguration of filter parameters 303 Rockpool, Release 3.0.3 function parameters giv en the sampling frequency , quality f actor ( 𝑄 ), and center frequency . The f ollo wing code blocks can be used to calculate the transf er function of a desired filter (i.e., the required 𝑎 1 and 𝑎 2 parameters). The e xample demons trates ho w to modify the filters to co v er a rang e betw een 300 𝐻 𝑧 and 20 𝐾 𝐻 𝑧 . [5]: import numpy as np f0 = 300 f15 = 20000 N_filters = 16 # number of filters alpha = np . power(f15 / f0, 1 / (N_filters - 1 )) new_freqs = [f0] for i in range ( 1 ,N_filters): new_freqs . append( int (alpha * new_freqs[ - 1 ])) print ( f ' modified filter centers are: { new_freqs } ' ) modified filter centers are: [300, 396, 523, 691, 914, 1209, 1599, 2115, 2798, 3702, ␣ ˓ → 4898, 6480, 8573, 11342, 15006, 19854] [6]: from scipy import signal Q = 6 #Qfactor new_params = [] for f in new_freqs: # Get digital filter coefficients (z-domain) filter_params = signal . iirpeak(f, Q, fs = fs) new_params . append(filter_params) b ,a = new_params[ 0 ] #parameters of the first filter a 1,a 2 = a[ 1 ], a[ 2 ] 𝑎 1 and 𝑎 2 parameters need to be quantized as f ollo w s: (In AFESim, the filters are quantized to match X y lo ™ A udio 3 specifications.) • ˜ 𝑎 1 = [2 𝐵 𝑏 2 𝐵 𝑎,𝑓 𝑎 1 ] ˜ 𝑎 2 = [2 𝐵 𝑏 2 𝐵 𝑎,𝑓 𝑎 2 ] where: • 𝐵 𝑏 : bits needed f or scaling b0 • 𝐵 𝑎𝑓 : bits needed f or encoding the fractional par ts of taps Without going into details, 𝐵 𝑏 and 𝐵 𝑎𝑓 are integ er v alues defining the precision of eac h parameter in the filter . The y ha v e been hardcoded in AFESim (see AFESimExternal or AFESimPDM ) f or each of the 16 filters and should not be modified. ˜ 𝑎 1 and ˜ 𝑎 2 can be calculated as f ollo w s [7]: a1_hats, a2_hats = [],[] for i, params in enumerate (new_params): b ,a = params a 1,a 2 = a[ 1 ], a[ 2 ] B_b = fb . bd_list[i] . B_b B_af = fb . bd_list[i] . B_af q_scale = ( 2 ** B_b) * ( 2 ** B_af) a1_hat, a2_hat = int (q_scale * a1), int (q_scale * a2) a1_hats . append(a1_hat) a2_hats . append(a2_hat) and replaced in ~.syns65302.ChipButterworth module f or each filter . 304 Chapter 34. Bandpass Filtering In Audio F ront End (AFE) Of X ylo ™ Audio 3 Rockpool, Release 3.0.3 Equiv alentl y in HDK, ˜ 𝑎 1 and ˜ 𝑎 2 parameters can be modified b y setting bpf_a1_values and bpf_a2_values on the chip configuration: F or e xample, in the tutor ial Using X yloSamna and X yloMonit or to deplo y a model on X y loAudio 3 HDK , a chip con- figuration is retur ned after mapping a netw ork: spec = mapper(model.as_graph(), weight_dtype= ' float ' , threshold_dtype= ' float ' , dash_ ˓ → dtype= ' float ' ) spec.update(q.channel_quantize(**spec)) xylo_conf, is_valid, msg = config_from_specification(**spec) T o set the new ˜ 𝑎 1 and ˜ 𝑎 2 parameters, modify the returned configuration as f ollo w s: xylo_conf.digital_frontend.filter_bank.bpf_a1_values = a1_hats xylo_conf.digital_frontend.filter_bank.bpf_a2_values = a2_hats 34.1. Reconfiguration of filter parameters 305 Rockpool, Release 3.0.3 306 Chapter 34. Bandpass Filtering In Audio F ront End (AFE) Of X ylo ™ Audio 3 CHAPTER THIRT YFIVE INTRODUCTION T O XYL O ™ IMU X y loIMU is a platf or m f or motion sensor y processing, combining an IMU accelerometer sensor inter f ace with a lo w- po w er SNN inf erence core. X yloIMU is designed f or sub-mW motion processing, in alwa y s-on applications. This notebook giv es y ou an o v er vie w of interfacing from R oc kpool to the v ar ious cores of X y lo-IMU . See also Quic k-s tart with X ylo ™ SNN cor e and The X ylo ™ IMU pr epr ocessing interface . [1]: # - General installation and imports import sys !{ sys.executable } -m pip install --quiet matplotlib rich "rockpool[xylo, torch]" # - Import numpy import numpy as np # - Configure matplotlib import matplotlib.pyplot as plt % matplotlib inline plt . rcParams[ ' figure.figsize ' ] = [ 12 , 4 ] plt . rcParams[ ' figure.dpi ' ] = 300 # - Display images from IPython.display import Image import warnings warnings . filterwarnings( ' ignore ' ) # - Nice printing from rich import print 35.1 Ov er view of the X ylo ™ IMU design X y loIMU receiv es direct input from a MEMSIC MC3632 IMU sensor , and is designed to enable motion classification applications in real-time at lo w po wer . The bloc k -le v el outline of the chip is sho wn belo w . A dedicated IMU inter f ace connects to the IMU sensor via a mas ter SPI bus, and con v er ts motion data into streams of e v ents. Ev ent-encoded motion data is transf er red to a digital SNN core f or inf erence. The results of inf erence, encoded as output e v ent streams, are transmitted o v er an inter r upt bus or sla v e SPI bus to an e xter nal microcontroller . A bloc k of control logic manag es configuration and communication with the cores. Configuration and readout from X y loIMU is per f or med o v er a sla v e SPI bus. 307 Rockpool, Release 3.0.3 35.4 P ART II: The IMU encoding inter face X y loIMU incor porates an efficient e v ent encoding block f or IMU motion data, whic h includes remo v al / cor rection of rotation b y detection the g ra vity-associated f orce v ector . F or a more detailed descr iption of the inter f ace, see The X y lo ™ IMU pr eprocessing int er f ace . The bloc k diagram belo w sho w s the main steps in preprocessing IMU data to e v ents. Three channels of IMU input (x, y , z) are con v er ted to 15 e v ent channels (5 per sensor c hannel), which are then sent to the X ylo SNN inf erence core. R ockpool includes a bit-accurate simulation of the IMU con v ersion inter face, in IMUIFSim . This simulator allo w s y ou to encode IMU motion data as e v ents in the same w a y as liv e IMU data on X y lo-IMU, f or use in training and testing applications. [17]: Image( ' IMU-IF-block-level.png ' ) 314 Chapter 35. Introduction to X ylo ™ IMU Rockpool, Release 3.0.3 [17]: 35.4.1 Using the IMU IF Simulation module See also The X ylo ™ IMU pr epr ocessing int er f ace and Specifying IMU pr eprocessing par amet ers [18]: from rockpool.devices.xylo.syns63300 import IMUIFSim # - Load a dummy IMU sample input_data = np . load( ' data.npy ' ) # - Create an IMUIFSim module mod = IMUIFSim() print (mod) IMUIFSim with shape ( 3 , 15 ) { ModSequential ' model ' with shape ( 3 , 15 ) { RotationRemoval ' 0_RotationRemoval ' with shape ( 3 , 3 ) { ModSequential ' sub_estimate ' with shape ( 3 , 9 ) { SubSpace ' 0_SubSpace ' with shape ( 3 , 9 ) SampleAndHold ' 1_SampleAndHold ' with shape ( 9 , 9 ) } } FilterBank ' 1_FilterBank ' with shape ( 3 , 15 ) ScaleSpikeEncoder ' 2_ScaleSpikeEncoder ' with shape ( 15 , 15 ) } } IMUIFSim is a standard R oc kpool module accepting raw IMU data, and returning encoded e v ents. [19]: # - Quantize the raw IMU data from rockpool.devices.xylo.syns63300 import Quantizer quantizer = Quantizer(shape = 3 , scale = 0.49 , num_bits = 16 ) Q_data, _, _ = quantizer(input_data) # - Test evolution and plot input and output out, _, r_d = mod(Q_data, record = True ) times = np . arange( 0 , input_data . shape[ 0 ]) * mod . dt ax = plt . subplot( 2 , 1 , 1 ) ax . plot(times, input_data) ax . set_xlim( 0 , times[ - 1 ]) ax . set_xticklabels([]) (continues on ne xt page) 35.4. P ART II: The IMU encoding inter face 315 Rockpool, Release 3.0.3 (continued from pre vious pag e) ax . set_title( ' Raw IMU Input ' ) ax . set_ylabel( ' Accel. ' ) ax = plt . subplot( 2 , 1 , 2 ) ax . imshow(out[ 0 ] . astype( int ) . T, aspect = ' auto ' , interpolation = ' none ' , origin = ' lower ' ) ax . set_xticklabels(np . array(ax . get_xticks() * mod . dt) . astype( int )) ax . set_title( ' Encoded IMU as events ' ) ax . set_xlabel( ' Time (s) ' ) ax . set_ylabel( ' Channel ' ); Using the export_config() method, y ou can obtain a hardware configuration object with whic h y ou can configure the encoding bloc k on X y lo IMU . [20]: if_config = mod . export_config() print (if_config) xyloImu::configuration:: InputInterfaceConfig ( enable = 1 , configuration_timeout = 3.000000 , ␣ ˓ → estimator_k_setting = 4 , select_iaf_output = 0 , bypass_jsvd = 0 , update_matrix_threshold = 10 , delay_threshold = 500 , bpf_ ˓ → bb_values = { 6543265 43265432 } , bpf_bwf_values = { 888888888888888 } , bpf_baf_values = { 9 ␣ ˓ → 10 11 12 13 9 10 11 12 13 9 10 11 12 13 } , bpf_a1_values = { -64458 -63288 -60684 -54529 -39246 -64458 -63288 -60684 - ˓ → 54529 -39246 -64458 -63288 -60684 -54529 -39246 } , bpf_a2_values = { 31754 30771 28888 25417 19378 31754 30771 28888 ␣ ˓ → 25417 19378 31754 30771 28888 25417 19378 } , scale_values = { 555555555555555 } , iaf_threshold_ ˓ → values = { 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 }) IMUIFSim suppor ts all optional f eatures of the IMU encoding inter face. See Specifying IMU pr epr ocessing parame t ers f or more detail on ho w to specify the parameters of the IMU IF . 316 Chapter 35. Introduction to X ylo ™ IMU Rockpool, Release 3.0.3 35.4.2 Accessing the IMU encoding interface on HW R ockpool also pro vides a module IMUIFSamna to record encoded data from the IMU interface on X yloIMU . The IMU sensor data can come either from the on-board IMU chip, or from pre-recorded data s treamed from the host PC. W arning IMUIFSamna cur rentl y uses a w ork -around to record encoded IMU data as ev ents. It is only capable of recording one e v ent per time-step per c hannel from the IMU inter face. Ho w e v er , the IMU inter face is capable of producing multiple e v ents per time-step per c hannel. As a result, data recorded using IMUIFSamna ma y differ from that trans- mitted to the SNN core on X y lo IMU . Y ou can alter nativ ely use IMUData to record ra w IMU data, and IMUIFSim to encode that data, suppor ting multiple e v ents per time-step per c hannel. This ma y result in more accurate encoding of data. [21]: Image( ' IMUIFSamna.png ' , width = 400 ) [21]: [22]: # - Import the required Rockpool module from rockpool.devices.xylo.syns63300 import IMUIFSamna # - Instantiate the module, connected to an HDK ` db ` imu_if = IMUIFSamna(db, prerecorded_imu_input = False ) # - You can also supply pre-recorded IMU data, and specify the IMU IF parameters # IMUIFSamna(db, prerecorded_imu_input = True, interface_params={}) [23]: # - Record for 1000 time steps, by providing zero data out, _, _ = imu_if(np . zeros(( 1000 , 3 ))) [24]: # times, channels = np.where(out) # plt.scatter(times, channels) plt . imshow(out . T, aspect = ' auto ' , interpolation = ' none ' ) [24]: <matplotlib.image.AxesImage at 0x386214310> 35.4. P ART II: The IMU encoding inter face 317 Rockpool, Release 3.0.3 Delete the module to release access to the IMU sensor . [25]: del imu_if [2024-06-07 13:03:24.926] [Graph] [warning] Graph is destroyed while running! Note: ␣ ˓ → Filter nodes constructed by ` sequential ` method won ' t work after corresponding graph ␣ ˓ → is destroyed and please manually stop the graph after use. 35.5 P ART III: Recording live IMU data R ockpool pro vides a module IMUData to record ra w IMU data from the MEMSIC IMU Sensor on the X y loIMU HDK. This can be used to record liv e IMU input, or to record IMU data to build a training dataset. [26]: Image( ' IMUData.png ' , width = 400 ) [26]: [27]: # - Import and instantiate and IMUData module from rockpool.devices.xylo.syns63300 import IMUData mod = IMUData(db) print (mod) IMUData with shape ( 0 , 3 ) IMUData is a standard R oc kpool module, whic h retur ns real-time samples recorded from the IMU de vice on a X y lo IMU HDK. Use a zero-element numpy ar ra y to specify ho w man y samples to record. [28]: dt = mod . dt # By default Xylo IMU operates at 200 Hz T = int ( 5. / dt) # Record for 5 seconds (continues on ne xt page) 318 Chapter 35. Introduction to X ylo ™ IMU Rockpool, Release 3.0.3 (continued from pre vious pag e) # - Record live IMU sensor data data, _, _ = mod(np . zeros(( 0 ,T , 0 ))) # - Plot the data samples times = np . arange( 0 ,T ) * mod . dt plt . plot(times, data) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Acceleration ' ); 35.6 P ART IV : Real-time streaming mode The streaming mode combines the IMU inter face and SNN inf erence core modules tog ether in real time using the class XyloIMUMonitor . In this mode y ou onl y read the output e v ents from the SNN core, with input either from the liv e IMU sensor , or streamed from a PC. [29]: Image( ' XyloIMUMonitor.png ' , width = 400 ) [29]: 35.6.1 Step 1: Build a network in rockpool and con ver t it to a hardware configura- tion [30]: # - Define the size of the network layers Nin = 15 (continues on ne xt page) 35.6. P ART IV : Real-time streaming mode 319 Rockpool, Release 3.0.3 (continued from pre vious pag e) Nhidden = 4 Nout = 2 dt = 1. / 200 [31]: # - Define the network architecture using combinators and modules net = Sequential( LinearTorch((Nin, Nhidden), has_bias = False ), LIFTorch(Nhidden, dt = dt), Residual( LinearTorch((Nhidden, Nhidden), has_bias = False ), LIFTorch(Nhidden, has_rec = True , threshold = 1. ,d t = dt), ), LinearTorch((Nhidden, Nout), has_bias = False ), LIFTorch(Nout, dt = dt), ) print (net) TorchSequential with shape ( 15 , 2 ) { LinearTorch ' 0_LinearTorch ' with shape ( 15 , 4 ) LIFTorch ' 1_LIFTorch ' with shape ( 4 , 4 ) TorchResidual ' 2_TorchResidual ' with shape ( 4 , 4 ) { LinearTorch ' 0_LinearTorch ' with shape ( 4 , 4 ) LIFTorch ' 1_LIFTorch ' with shape ( 4 , 4 ) } LinearTorch ' 3_LinearTorch ' with shape ( 4 , 2 ) LIFTorch ' 4_LIFTorch ' with shape ( 2 , 2 ) } [32]: # - Call the Xylo mapper on the extracted computational graph spec = mapper(net . as_graph(), weight_dtype = ' float ' , threshold_dtype = ' float ' , dash_dtype = ˓ → ' float ' ) # - Quantize the specification spec . update(q . global_quantize( ** spec)) # # you can also try channel-wise quantization # spec.update(q.channel_quantize(**spec)) # print(spec) # - Use rockpool.devices.xylo.config_from_specification to convert it to a hardware ␣ ˓ → configuration config, is_valid, msg = config_from_specification( ** spec) if not is_valid: print (msg) 320 Chapter 35. Introduction to X ylo ™ IMU Rockpool, Release 3.0.3 35.6.2 Step 2: Deploy the ne twork on chip and run simulation [36]: # - Find and connect to a Xylo IMU HDK xylo_hdk_nodes, _, vers = find_xylo_hdks() print (xylo_hdk_nodes, vers) if len (xylo_hdk_nodes) == 0 or vers[ 0 ] != ' syns63300 ' : assert False , ' This tutorial requires a connected Xylo IMU HDK to demonstrate. ' else : db = xylo_hdk_nodes[ 0 ] The connected Xylo HDK contains a Xylo IMU. Importing ` rockpool.devices.xylo.syns63300 ` [< samna.xyloImuBoards.XyloImuTestBoard object at 0x3508e55b0 >] [ ' syns63300 ' ] [37]: # - Use the ` XyloIMUMonitor ` module to deploy to the HDK, running in real time from rockpool.devices.xylo.syns63300 import XyloIMUMonitor output_mode = "Vmem" modMonitor = XyloIMUMonitor(device = db, config = config, dt = dt, output_mode = output_mode) [38]: # - A resultList stack to store the results from IPython.display import clear_output class ResultList ( object ): def __init__ ( self , max_len = 100 ): self . _list = [] self . max_len = max_len def reset ( self ): self . _list = [] def append ( self , num): if len ( self . _list) < self . max_len: self . _list . append(num) else : self . _list[: self . max_len - 1 ] = self . _list[ 1 :] self . _list[ self . max_len - 1 ] = num def is_full ( self ): if len ( self . _list) == self . max_len: return True else : return False def counts ( self , features = []): count = 0 for _ in self . _list: if _ in features: count += 1 return count (continues on ne xt page) 35.6. P ART IV : Real-time streaming mode 321 Rockpool, Release 3.0.3 (continued from pre vious pag e) def __len__ ( self ): return len ( self . _list) @property def list ( self ): return self . _list [39]: # - Draw a real time image for output channels, you can shake the Xylo IMU device while ␣ ˓ → running lines = [ResultList(max_len = 10 ) for _ in range (Nout)] time_base = ResultList(max_len = 10 ) tt = 0 T = 200 t_inference = 10. from time import time t_start = time() while (time() - t_start) < t_inference: # - Perform inference on the Xylo IMU HDK output, _, _ = modMonitor(input_data = np . zeros((T, 3 ))) if output is not None : output = np . max(output, axis = 0 ) for i in range (Nout): lines[i] . append(output[i]) time_base . append(tt) tt += 0.1 ax_time = time_base . list for i in range (Nout): plt . plot(ax_time, lines[i] . list, label = f"class { i } " ) plt . xlabel( ' time ' ) plt . ylabel( ' Vmem ' ) plt . legend() plt . pause( 0.1 ) clear_output(wait = True ) 322 Chapter 35. Introduction to X ylo ™ IMU Rockpool, Release 3.0.3 35.7 Par t V : Measuring power on the X ylo HDK XyloSamna pro vides an inter f ace to real-time po w er measurements on the X y lo ™ IMU HDK. Cur rent on se v eral po w er nets on the chip can be sampled async hronously , while the device is in operation. The evolve() method pro vides a po wer measurement interf ace while the de vice is in inf erence mode, and the XyloSamna module can be used also to measure idle po w er . [42]: # - Find and connect to a Xylo IMU HDK xylo_hdk_nodes, modules, versions = find_xylo_hdks() print (xylo_hdk_nodes, versions) if len (xylo_hdk_nodes) == 0 or versions[ 0 ] is not ' syns63300 ' : assert False , ' This tutorial requires a connected Xylo IMU HDK to demonstrate. ' else : db = xylo_hdk_nodes[ 0 ] x = modules[ 0 ] The connected Xylo HDK contains a Xylo IMU. Importing ` rockpool.devices.xylo.syns63300 ` [< samna.xyloImuBoards.XyloImuTestBoard object at 0x3508e55b0 >] [ ' syns63300 ' ] On instantiation, XyloSamna can specify a po w er sampling frequency . By def ault, po w er is measured at 5 Hz. Belo w w e increase this to 20 Hz. [43]: # - Set a low clock frequency for the Xylo device print ( f ' Setting Xylo main clock to { x . xylo_imu_devkit_utils . set_xylo_core_clock_freq(db, ␣ ˓ → 6.25 ) } MHz ' ) # - Use XyloSamna to deploy to the HDK modSamna = x . XyloSamna(db, config, dt = 10e-3 , power_frequency = 20. ) print (modSamna) Setting Xylo main clock to 6.25 MHz XyloSamna with shape ( 15 , 4 , 2 ) 35.7. Par t V : Measuring power on the X ylo HDK 323 Rockpool, Release 3.0.3 [11]: Ra w IMU data is obtained at 200 Hz from the IMU sensor , pro viding quantized three-c hannel (x, y , z) samples. Depending on the applicaiton use-case, an IMU sensor might ha v e an arbitrar y or changing orientation in the real w or ld. The IMU inter f ace contains a set of algorithms f or estimating and correcting f or sensor orientation , such that the gra vitity v ector is alw a ys aligned with the -Z axis. This rotation correction module can be bypassed. The IMU data is anal y sed b y a tunable bank of bandpass filters , which can separate the data frequencies with central frequencies betw een 0..20Hz. U p to fiv e filters can be used f or each (x, y , z) channel of the IMU data. T w o alter nativ e strategies are a vailable f or e v ent encoding. The first quantizes and scales the filtered data to a maximum of 15 e v ents per time-step per c hannel. The second uses a digital integrate-and-fire neur on to encode e v ents. A full simulation of the IMU encoding inter f ace is a v ailable in R ockpool, via the IMUIFSim module and the devices. xylo.syns63300.imuif pac kag e. [12]: # - Import Rockpool modules from rockpool.devices.xylo.syns63300 import Quantizer, IMUIFSim from samna.xyloImu.configuration import InputInterfaceConfig [13]: Image( ' imu-ifsim-module.png ' ) [13]: The high-le v el module IMUIFSim encapsulates the entire preprocessing toolchain, and per mits configuration of the encoding approach. Inter nally the se v eral blocks in the encoding c hain also ha v e cor responding R ockpool modules (compare the tw o figures abo v e). It is also possible to access the simulations of individual blocks, using the R oc kpool modules RotationRemoval , FilterBank , ScaleSpikeEncoder and IAFSpikeEncoder . The IMU IF simulation e xpects integ er -quantized data with 16 bits per sample per c hannel (i.e. -32’768..32’767). This 330 Chapter 36. The X ylo ™ IMU preprocessing inter face Rockpool, Release 3.0.3 can con v enientl y be simulated using the Quantizer module. [14]: ## Load and quantize an IMU sample record with open ( "data.npy" , "rb" ) as f: data = np . load(f) quantizer = Quantizer(scale = 0.49 , num_bits = 16 ) data_quantized, _, _ = quantizer(data) data_quantized . shape plt . figure() plt . plot(data_quantized[ 0 ]) plt . title( f"Quantized IMU Sample Record" ) plt . show() 36.2 Using the high-le vel simulation interface The R ockpool module IMUIFSim uses def ault configuration parameters co v er ing a reasonable set of use cases. By def ault rotation remo v al is switc hed on, and the ScaleSpikeEncoder is used. Y ou create and interact with the IMU IF simulation module identicall y to an y other R ockpool module: [15]: # - Generate an IMU IF simulation with default parameters mod_IMUIF = IMUIFSim() print (mod_IMUIF) IMUIFSim with shape ( 3 , 15 ) { ModSequential ' model ' with shape ( 3 , 15 ) { RotationRemoval ' 0_RotationRemoval ' with shape ( 3 , 3 ) { ModSequential ' sub_estimate ' with shape ( 3 , 9 ) { SubSpace ' 0_SubSpace ' with shape ( 3 , 9 ) SampleAndHold ' 1_SampleAndHold ' with shape ( 9 , 9 ) } } FilterBank ' 1_FilterBank ' with shape ( 3 , 15 ) (continues on ne xt page) 36.2. Using the high-lev el simulation interface 331 Rockpool, Release 3.0.3 (continued from pre vious pag e) ScaleSpikeEncoder ' 2_ScaleSpikeEncoder ' with shape ( 15 , 15 ) } } [16]: # - Pass an IMU sample through the simulation result, _, _ = mod_IMUIF(data_quantized) # - Display the encoded result plt . figure() plt . imshow(result[ 0 ] . astype( ' float ' ) . T, aspect = ' auto ' , origin = ' lower ' ) plt . title( f"Preprocessed IMU Sample Record" ) plt . show() Expor t the hardw are configuration, f or use in configur ing a X y lo IMU chip, using the export_config() method. [17]: config = mod_IMUIF . export_config() print (config) xyloImu::configuration:: InputInterfaceConfig ( enable = 1 , configuration_timeout = 3.000000 , ␣ ˓ → estimator_k_setting = 4 , select_iaf_output = 0 , bypass_jsvd = 0 , update_matrix_threshold = 10 , delay_threshold = 500 , bpf_ ˓ → bb_values = { 6543265 43265432 } , bpf_bwf_values = { 888888888888888 } , bpf_baf_values = { 9 ␣ ˓ → 10 11 12 13 9 10 11 12 13 9 10 11 12 13 } , bpf_a1_values = { -64458 -63288 -60684 -54529 -39246 -64458 -63288 -60684 - ˓ → 54529 -39246 -64458 -63288 -60684 -54529 -39246 } , bpf_a2_values = { 31754 30771 28888 25417 19378 31754 30771 28888 ␣ ˓ → 25417 19378 31754 30771 28888 25417 19378 } , scale_values = { 555555555555555 } , iaf_threshold_ ˓ → values = { 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 1024 }) Giv en a sa v ed hardw are configuration, y ou can also g enerate an IMU IF simulation based on that config, using the from_config() class method: 332 Chapter 36. The X ylo ™ IMU preprocessing inter face Rockpool, Release 3.0.3 [18]: mod = IMUIFSim . from_config(config) print (mod) IMUIFSim with shape ( 3 , 15 ) { ModSequential ' model ' with shape ( 3 , 15 ) { RotationRemoval ' 0_RotationRemoval ' with shape ( 3 , 3 ) { ModSequential ' sub_estimate ' with shape ( 3 , 9 ) { SubSpace ' 0_SubSpace ' with shape ( 3 , 9 ) SampleAndHold ' 1_SampleAndHold ' with shape ( 9 , 9 ) } } FilterBank ' 1_FilterBank ' with shape ( 3 , 15 ) ScaleSpikeEncoder ' 2_ScaleSpikeEncoder ' with shape ( 15 , 15 ) } } 36.3 N e xt steps See Specifying IMU pr eprocessing par amet er s f or details of ho w to configure the preprocessing and encoding modules. 36.3. N ext steps 333 Rockpool, Release 3.0.3 334 Chapter 36. The X ylo ™ IMU preprocessing inter face CHAPTER THIRT Y SEVEN SPECIFYING IMU PREPROCESSIN G P ARAMETERS X y lo ™ IMU includes a custom pre-processing and e v ent encoding inter face designed to w ork with IMU data. When designing an IMU signal processing application with X y lo ™ IMU , the parameters of the IMU inter face should be set appropr iatel y f or the giv en application. This notebook e xplains the parameters that can be modified b y the application de v eloper , and demonstrates ho w to configure them. [14]: ## - Imports and configuration import numpy as np # - Plotting and config import matplotlib.pyplot as plt plt . rcParams[ "figure.figsize" ] = [ 9.6 , 3.6 ] plt . rcParams[ "figure.dpi" ] = 1200 plt . rcParams[ "font.size" ] = 12 try : from rich import print except : pass from IPython.display import Image import warnings warnings . filterwarnings( ' ignore ' ) from rockpool.devices.xylo.syns63300 import Quantizer 37.1 Ov er view of preprocessing chain modules [15]: Image( ' imu-ifsim-module.png ' ) 335 Rockpool, Release 3.0.3 [15]: As descr ibed in The X ylo ™ IMU pr epr ocessing interface , the IMU IF simulation and configuration is encapsulated in the R ockpool modules RotationRemoval , FilterBank , ScaleSpikeEncoder and IAFSpikeEncoder . This notebook sho w s ho w to configure the various logical blocks of the preprocessing c hain f or desired beha viour . 37.2 Specifying rotation correction parameters The rotation remo val bloc k in the IMU inter face continuousl y estimates the sensor orientation in 3-D space, based on the (x, y, z) in put signal. This estimation is smoothed o v er time, with a configurable a v eraging windo w f or the estimation. It then computes a compensating transf or mation f or the (x, y, z) data which cor rects f or the sensor or ientation, such that the gra vity v ector is aligned with the -Z axis. The simulation of this bloc k is embodied b y the RotationRemoval class. Onl y a 3-channel in put and 3-channel output shape is suppor ted. The bloc k offers tw o configurable parameters: • A v eraging windo w f or estimating the sensor orientation (paramter num_avg_bitshift ) • Sample-and-hold duration deter mining ho w often to update the sensor or ientation cor rection (parameter sampling_period ) num_avg_bitshift specifies a lo w-pass filter smoothing f or estimating or ientation, with an appro ximate windo w length of 2 ** num_avg_bitshift time steps. This paramter def aults to 4 (i.e. 16 time steps). sampling_period specifies a sample-and-hold duration f or the or ientation cor rection, such that the cor rection inf or - mation is updated onl y e v er y sampling_period time steps. This parameter def aults to 10 time steps. T o tune these parameters y ou should in v estig ate the dynamics of IMU data under y our use case. Ho w often and ho w quic kly do y ou e xpect a sensor rotation? Ho w impor tant is the inf or mation ar ising from sensor rotation f or y our appli- cation? These aspects will influence ho w slo w l y y ou should cor rect f or sensor rotation, or whether y ou should do so at all. [16]: from rockpool.devices.xylo.syns63300.imuif import RotationRemoval rr = RotationRemoval( num_avg_bitshift = 6 , sampling_period = 50 , ) print (rr) 336 Chapter 37. Specifying IMU preprocessing par ameters Rockpool, Release 3.0.3 RotationRemoval with shape ( 3 , 3 ) { ModSequential ' sub_estimate ' with shape ( 3 , 9 ) { SubSpace ' 0_SubSpace ' with shape ( 3 , 9 ) SampleAndHold ' 1_SampleAndHold ' with shape ( 9 , 9 ) } } [17]: ## - Demonstrate using an IMU data sample # - Load a data sample and ensure it has the correct scale with open ( "data.npy" , "rb" ) as f: data = np . load(f) quantizer = Quantizer( 3 , scale = 0.49 , num_bits = 16 ) data_quantized, _, _ = quantizer(data) # - Apply the rotation removal correction data_corrected, _, _ = rr(data_quantized) # - Plot the IMU sample and rotation corrected sample times = np . arange( len (data)) / 200. plt . figure() plt . plot(times, data_quantized[ 0 ]) plt . plot(times[[ 0 , - 1 ]], [ 0 , 0 ], ' k: ' ) plt . yticks([]) plt . title( f"Quantized IMU Sample Record" ) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Amplitude ' ) plt . figure() plt . plot(times, data_corrected[ 0 ]) plt . plot(times[[ 0 , - 1 ]], [ 0 , 0 ], ' k: ' ) plt . yticks([]) plt . title( f"Rotation Removed IMU Sample Record" ) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Amplitude ' ); 37.2. Specifying rotation correction parameters 337 Rockpool, Release 3.0.3 37.3 Specifying filter -bank par ameters 37.3.1 Specifying a single band-pass filter The classes FilterBank and BandPassFilter are used to specify and simulate a bank of filters f or IMU data. Indi- vidual filters are specified b y pro viding a lo w -pass and high-cut frequency , as w ell as a sampling frequency . On X y lo IMU , data is designed to be sampled at 200~Hz. Here w e will sho w ho w to specify a band-pass filter with a pass-band betw een 5 and 10 Hz, at the def ault sampling fre- quency of 200 Hz. In the code belo w we specify the sampling freq uency e xplicitl y , but to use the default on X ylo ™ IMU y ou can skip this argument. [18]: from rockpool.devices.xylo.syns63300.imuif import FilterBank, BandPassFilter (continues on ne xt page) 338 Chapter 37. Specifying IMU preprocessing par ameters Rockpool, Release 3.0.3 (continued from pre vious pag e) # - Specify a band-pass from 5..10 Hz, sampling at 200 Hz low_cut = 5. # Hz high_cut = 10. # Hz Fs = 200. # Hz # - Instantiate an object for the band-pass filter bpf = BandPassFilter . from_specification(low_cut, high_cut, Fs) print (bpf) BandPassFilter ( B_b = 3 , B_wf = 8 , B_af = 12 , a1 = -59258 , a2 = 27986 , scale_out = 0.5836772581461334 ) W arning N ote that BandPassFilter is not a R oc kpool module, but simpl y a utility class simulating filters. W e can in v estig ate the transf er response of the filter b y sho wing the result of filter ing a chir p signal. T o accomplish this w e use the chirp() function from scipy.signal . W e pass the linear chirp through the single band-pass filter twice, to remo v e any filter onset transients. W e then plot the chirp response and estimate the filter po w er spectrum. [19]: ## - Generate and plot a chirp response # - Import utility functions import numpy as np import scipy.signal as sig # - Generate a 10-second chirp signal ranging 40..0..40 Hz T = 5. times = np . arange( 0 , int (T * Fs)) / Fs chirp = np . array(sig . chirp(times, 0 ,T , 40 ) * 100 , dtype = np . int64) . astype( object ) freq = np . linspace( 0 , 40 , len (times)) times2 = np . concatenate([times, times + times[ - 1 ] + 1 / Fs]) chirp2 = np . concatenate([chirp, np . flip(chirp)]) freq2 = np . concatenate([freq, np . flip(freq)]) # - Pass the signal through the filter resp = bpf(chirp2) # - Plot the chirp and the filter response plt . figure() plt . plot(times, chirp, label = ' Chirp ' ) plt . plot(times, np . flip(resp[ len (times) - 1 : - 1 ]), label = ' Response ' ) plt . legend() plt . title( ' Filter chirp response ' ) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Amplitude ' ); 37.3. Specifying filter -bank par ameters 339 Rockpool, Release 3.0.3 (continued from pre vious pag e) from rockpool.devices.xylo.syns63300 import IMUIFSim # - Instantiate the IMU IF simulation module imuif = IMUIFSim() # - Apply the module to the IMU sample data encoded, _, _ = imuif(data_quantized) # - Plot the result plt . imshow(encoded[ 0 ] . astype( float ) . T, aspect = ' auto ' , interpolation = ' none ' , origin = ˓ → ' lower ' ) xticks, _ = plt . xticks() xlim = plt . xlim() plt . xticks(xticks, xticks / Fs) plt . xlim(xlim) plt . xlabel( ' Time (s) ' ) plt . ylabel( ' Channel ' ) plt . title( ' Encoded IMU signal ' ); 346 Chapter 37. Specifying IMU preprocessing par ameters CHAPTER THIRT YEIGHT O VER VIEW OF D YNAP-SE2 This tutor ial pro vides an o v er vie w of Dynap-SE2 mix ed signal architecture. If y ou ’ re familiar with the c hip and looking f or a hands-on tutor ial, please see Dynap-SE2 Quic k S tar t tutor ial. Other wise, let ’ s deep div e into the c hip! 38.1 Introduction Dynap-SE2, (D Ynamic Neuromor phic Async hronous Processor - ScalablE 2) inher its the e v ent-dr iv en nature of the D YN AP f amil y . The mix ed-signal chip uses analog spiking neurons and analog synapses as the computing units, which directl y emulates biological beha vior . T ransis tors of the neural cores operate in the subthreshold region, which results in po w er consumption of about one-thousandth to one-millionth of the s tate-of-the-ar t digital neuromor phic chips, belo w mW . Each c hip f eatures: • 1024 A dExpIF (adaptiv e e xponential integrate-and-fire) analog ultra-lo w -po w er spiking neurons, • 64 synapses per neuron with configurable dela y , w eight, and shor t-ter m plasticity . [9]: from IPython.display import Image Image( "images/dynapse2.jpeg" ) 347 Rockpool, Release 3.0.3 [9]: In a broad perspectiv e, Dynap-SE g rants a hardw are infrastructure to f acilitate reconfigurable, g eneral-pur pose, real- time analog spiking neural netw ork applications. The no v el e v ent-routing technology of Dynap-SE mak es it possible to de v elop ultra-lo w-po w er and ultra-lo w latency solutions f or edg e computing applications. Also, the advanced, con- figurable neuron and synapse circuitr y pro vides a basis to simulate SNNs with comple x dynamical character is tics in analog hardw are. The figure belo w displa ys an abs tract arc hitecture of the mix ed signal chip. 38.2 Architecture Ov er view [10]: Image( "images/dynapse_architecture.png" ) 348 Chapter 38. Overview of Dynap-SE2 Rockpool, Release 3.0.3 [10]: The neural computation unit is the main building bloc k creating the dynamics. Each neural core pieces tog ether 256 analog neurons shar ing the same parameter set. C AM and SRAM are the digital memor y blocks holding the transmit- ting and receiving e v ent configurations. Analog computation tak es place in the synapses and the neuron soma. F our different synapses: AMP A, G AB A, NMD A, and SHUNT , integrate the incoming ev ents and inject cur rent into the membrane. While AMP A and NMD A produce e x citator y post-synaptic potentiation, G AB A and SHUNT synapses produce inhibitor y post-synaptic potentiation. In other w ords, AMP A and NMD A activ ation increase the chance that the neuron fires; GAB A and SHUNT activ ation decrease the fir ing probability . The listening e v ent setting stored in the C AM ref ers to a synapse type. Theref ore, each of the 64 connections of a neuron can specify its synaptic processing unit. Neuron soma integrates the injection cur rents and holds a temporal state. Charging and disc harging capacitors in 38.2. Architecture Overview 349 Rockpool, Release 3.0.3 configurable paths designates the temporal beha vior . A secondar y reading on the membrane capacitance, the membrane cur rent, functions as the temporal state v ar iable. U pon membrane cur rent reaching the firing threshold, the neuron ’ s reset mechanism s teps in and tr iggers the e v ent sensing units. The e v ent is packag ed in AER f or mat and is broadcasted to indicated locations. In this w a y , the neuron computes the dynamics using analog sub-threshold circuits but conv e y s the resulting outputs using a digital routing mechanism. 38.3 Parameter Handling Each neural core holds a parameter group to set the neuronal and synaptic parameters f or its 256 neurons and their pre-synaptic synapses. The neurons in the same core share the same parameter v alues, including time constants, refrac- tor y per iods, synaptic connection strengths and etc. Special digital to analog con v eters, bias g enerators denominated as BG , set these parameter cur rent v alues. In total there are 70 parameters setting different beha vioral attr ibutes of the neurons and synapses; including time constants, pulse widths, amplifier g ain ratios and synaptic w eight s trengths. [11]: Image( "images/param_conversion.png" ) [11]: F or details of the circuits please chec k: • Synapse Circuitr y – C. Bar tolozzi and G. Indiv er i, “Synaptic Dynamics in Analog VLSI, ” in Neural Computation, v ol. 19, no. 10, pp. 2581-2603, Oct. 2007, doi: 10.1162/neco.2007.19.10.2581. • Neuron Membrane Circuitry – P . Livi and G. Indiv er i, “ A cur rent-mode conductance-based silicon neuron f or address-ev ent neuromor - phic sy s tems, ” 2009 IEEE Inter national Symposium on Circuits and Sys tems, 2009, pp. 2898-2901, doi: 10.1109/ISC AS.2009.5118408. • Bias Generator Circuitr y – T . Delbr uck, R. Berner , P . Lichts teiner and C. Dualibe, “32-bit Configurable bias cur rent g enerator with sub-off-cur rent capability , ” Proceedings of 2010 IEEE Inter national Symposium on Circuits and Sy s- tems, 2010, pp. 1647-1650, doi: 10.1109/ISC AS.2010.5537475. 350 Chapter 38. Overview of Dynap-SE2 Rockpool, Release 3.0.3 38.4 Routing Each Dynap-SE2 c hip has 1024 neurons distr ibuted o v er 4 individuall y configurable neural cores, connected b y a patented hierarchical routing grid. The tag-based routing infrastructure pro vides direct communication from one c hip to 15 × 15 sur rounding chips (7 s teps w est, 7 s teps nor th, 7 steps eas t, 7 steps south), connecting up to 230k neurons. • Each neuron has 64 f an-in (neuron) and 4 f an-out (neural core) capacity . • Synapses are not uniquel y addressed, instead vir tual addresses named tags are used to specify connections. – 11-bit globall y mutliple x ed locally uniq ue/mutliple x ed tags F or details please chec k: • S. Moradi, N . Qiao, F . Stef anini and G. Indiv er i, “ A Scalable Multicore Architecture With Heterog eneous Memor y S tr uctures f or Dynamic Neuromorphic Asynchronous Processors (D YN APs), ” in IEEE T ransac- tions on Biomedical Circuits and Sy s tems, v ol. 12, no. 1, pp. 106-122, F eb. 2018, doi: 10.1109/TB- C AS.2017.2759700 [12]: Image( "images/router.png" ) [12]: The neural computation unit is the main building bloc k creating the dynamics. Each neural core pieces tog ether 256 analog neurons shar ing the same parameter set. C AM and SRAM are the digital memor y blocks holding the transmit- ting and receiving e v ent configurations. Analog computation tak es place in the synapses and the neuron soma. F our different synapses: AMP A, G AB A, NMD A, and SHUNT , integrate the incoming ev ents and inject cur rent into the membrane. While AMP A and NMD A produce e x citator y post-synaptic potentiation, G AB A and SHUNT synapses produce inhibitor y post-synaptic potentiation. In other w ords, AMP A and NMD A activ ation increase the chance that the neuron fires; GAB A and SHUNT activ ation decrease the fir ing probability . The listening e v ent setting stored in the C AM ref ers to a synapse type. Theref ore, each of the 64 connections of a neuron can specify its synaptic processing unit. Neuron soma integrates the injection cur rents and holds a temporal state. Charging and disc harging capacitors in configurable paths designates the temporal beha vior . A secondar y reading on the membrane capacitance, the membrane cur rent, functions as the temporal state v ar iable. U pon membrane cur rent reaching the firing threshold, the neuron ’ s reset mechanism s teps in and tr iggers the e v ent sensing units. The e v ent is packag ed in AER f or mat and is broadcasted to indicated locations. In this w a y , the neuron computes the dynamics using analog sub-threshold circuits but conv e y s the resulting outputs using a digital routing mechanism. 38.4. Routing 351 Rockpool, Release 3.0.3 38.5 Simulation Dynap-SE simulator uses the anal ytical transf er functions of the analog VLSI neuron&synapse implementations and sol v es them in time. It uses Forward-Euler method, which is one of the oldes t and simplest algor ithms to sol v e first- order ordinar y differential equations giv en an initial v alue. This wa y , giv en the cur rent time step s tate, it predicts the ne xt time step s tate iterativ el y . [13]: Image( "images/spiking_neuron_operation.png" ) [13]: The simulator DynapSim , pro vides an abs tract Dynap-SE machine that operates in the same parameter space as Dynap- SE f amil y processors. DynapSim does not simulate the hardware numericall y precisel y but e x ecutes a f ast and an appro ximate simulation. It uses f or ward Euler updates to predict the time-dependent dynamics and sol v es the charac- ter istic circuit transf er functions in time. In principle, the de vice and the simulator do not react e xactl y the same to the same input. Due to the nature of the analog de vice mismatc h problem, tw o ph ysical c hips w ould not react the same as w ell, it should not create a problem in application de v elopment. The netw orks to be deplo y ed to a chip should be robust ag ainst parameter v ar iations. T o be able to stress this more, DynapSim pro vides an integrated mismatch simulation f eature that can alter the parameter projection. Operating an appro ximate simulation that has the potential to g eneralize Dynap-SE processors has adv antages o v er a bulky realistic simulation. First, it runs a lot f aster than a transis tor -lev el accurate simulator . It mak es the optimization pipeline ter minate in f easible time windo ws. Second, the DynapSim methodology does not rely on e xact v alues; it f ocuses on reproducing a beha vior minimizing the dependence on v alues. The values here ref er to the e xact timing of the spik es, the v oltag e & cur rent amplitudes, and ev en the time cons tants. This w a y , abstract mac hine w ould ha v e a g eneralization capacity ins tead of o v er fitting to a specific chip la y out. Onl y a simulator with g eneralization capability could br ing neuromor phic mix ed-signal applications to our dail y liv es. 38.6 N e xt steps • Simulate a Dynap-SE2 netw ork and deplo y that netw ork to a Dynap-SE2 HDK, see Quick S tar t with Dynap-SE2 • In v es tig ate the neuron model implementation, see DynapSim N euron Model • T rain an SNN that is aimed to be deplo y ed to a Dynap-SE2 HDK, see T r aining a spiking netw ork t o deplo y to Dynap-SE2 352 Chapter 38. Overview of Dynap-SE2 CHAPTER THIRT YNINE QUICK ST ART WITH D YNAP-SE2 This notebook giv es y ou a quic k o v erview of taking a netw ork from a high-le v el Python simulator through to deplo yment on Dynap-SE2 chip. The mater ial is designed to be independent of the rest of the Dynap-SE2 tutorials. If y ou ha v e heard of R ockpool and Dynap-SE2 chip bef ore, y ou ’ re good to go! This one f ocus on hands-on e xper ience without going into too much detail. P ar ticularl y this notebook sho ws ho w to: 1. Build a Dynap-SE2 compatible netw ork with R ockpool , using DynapSim and LinearJax la y ers. 2. Extract the computational graph f or that netw ork containing all parameters needed to specify the c hip config- uration. 3. Map the computational graph to Dynap-SE2 hardware, allocating hardw are resources wisel y . 4. Quantize the netw ork parameters to make sure that the y fit. 5. Connect and Configure the chip, deplo y the netw ork. 6. Simulate on Dynap-SE2 and read the results. 7. Simulate on CPU and see ho w the netw ork affected b y all those transf or mations and quantizations affected the netw ork beha viour . 39.1 Impor ts [1]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) import numpy as np # - Rockpool imports from rockpool.nn.modules.jax import LinearJax from rockpool.nn.combinators import Sequential from rockpool.timeseries import TSEvent, TSContinuous # - Dynap-SE imports from rockpool.devices.dynapse import ( DynapSim, mapper, autoencoder_quantization, config_from_specification, (continues on ne xt page) 353 Rockpool, Release 3.0.3 (continued from pre vious pag e) find_dynapse_boards, DynapseSamna, dynapsim_net_from_config, ) # - Tutorial Utils from doc_utils import ( FrozenNoiseDataset, poisson_spike_train, visualize_device_sim, plot_Ix, plot_model_response, plot_model_response_histogram, ) # - Plotting and config import sys !{ sys.executable } -m pip install --quiet matplotlib import matplotlib.pyplot as plt plt . rcParams[ "figure.figsize" ] = [ 9.6 , 3.6 ] plt . rcParams[ "figure.dpi" ] = 1200 plt . rcParams[ "font.size" ] = 12 39.2 T utorial Utils [2]: % psource FrozenNoiseDataset class FrozenNoiseDataset : """ FrozenNoise is a synthetic dataset implementation for testing DynapSE-2 simulation ␣ ˓ → pipeline. It generates possion spike train rasters :param n_samples: number of samples included in the dataset :type n_samples: int :param n_channels: number of spiking channels (input neurons), defaults to 60 :type n_channels: int, optional :param duration: the duration of each synthetic recording, defaults to 500e-3 :type duration: float, optional :param dt: The discrete time resolution of the recording, defaults to 1e-3, defaults ␣ ˓ → to 1e-3 :type dt: float, optional :param rate: mean firing rate in Hz (applies to all channels), defaults to 50 :type rate: float, optional :param seed: random number generator seed, defaults to 2022 :type seed: Optional[float], optional """ def __init__ ( (continues on ne xt page) 354 Chapter 39. Quick Star t with Dynap-SE2 Rockpool, Release 3.0.3 (continued from pre vious pag e) self , n_samples : int , n_channels : int = 60 , duration : float = 500e-3 , dt : float = 1e-3 , rate : float = 50 , seed : Optional [ float ]= 2022 , )- > None : """__init__ parameters explained in class header""" self . n_in = n_channels self . n_out = n_samples self . dt = dt self . input_raster = poisson_spike_train ( n_channels , duration , rate , dt , batch_size = n_samples , seed = seed ) # One hot encoded target labels self . labels = np . expand_dims ( np . identity ( n_samples , dtype = float ), 1 ) def __getitem__ ( self , index : int )- > Tuple [ np . ndarray ]: """ __getitem__ [] getter implementation :param index: the sample index :type index: int :return: data, label :data: a single sample, raster :label: one hot encoded class of the sample :rtype: Tuple[np.ndarray] """ return self . input_raster [ index ], self . labels [ index ] @ property def full_batch ( self )- > Tuple [ np . ndarray ]: return self [:, :, :] def __len__ ( self )- > int : """__len__ returns the number of samples stored""" return len ( self . input_raster ) def plot_samples ( self , idx : Optional [ List [ int ] ]= None , adjust_size : bool = False )- > None : """ plot_samples visualizes the samples indicated by the idx list, stored in dataset :param idx: the index list of samples to be visualized, defaults to None :type idx: Optional[List[int]], optional :param adjust_size: adjust the size of the resulting plot accordingly or not, ␣ ˓ → defaults to False :type adjust_size: bool, optional """ (continues on ne xt page) 39.2. T utorial Utils 355 Rockpool, Release 3.0.3 39.4.2 Step 2.2 : Run N o w let ’ s r un the model and compare the responses in ter ms of fir ing rate ratio. The firing rate ratio ( FRR ) is the ratio betw een the super ior and inf er ior mean fir ing rates read from the decision neurons. FRR is calculated as f ollo ws: 𝐹 𝑅 𝑅 = 𝑟 superior 𝑟 inf erior If y ou ha v e visited the last tutorial, y ou probably realized that the FRR readings are w orse in this case, e v en though w e ha v e used the same trained w eights. Y ou ’ re right! That ’ s because w e initiated the simulator with a mismatch parameter in this case and it de viated the netw ork parameters a bit. It should be OK as long as it does not c hang e the high lev el beha viour of the netw ork. That is one neuron is firing at a clear l y higher rate than the other . W e’ll f ace this issue using different chips as w ell. [11]: plot_model_response(net, train_data, dt) It ’ s seen that neuron 0 fires at a significantly higher rate than the neuron 1 giv en the frozen noise sample 0. F or frozen noise 1, neuron 1 beats neuron 0. Let ’ s chec k the tes t samples. [12]: plot_model_response_histogram(net, test_data) Histogram: 100 iteration [00:17, 5.85 iteration/s] 362 Chapter 39. Quick Star t with Dynap-SE2 Rockpool, Release 3.0.3 This histogram sho w s us that the fir ing rates betw een neurons are much more greater than 1 onl y in the case that the training sample 1 or the training sample 2 pro vided. In all other cases, the FRR is close to 1, meaning that the netw ork does not e xpress a clear decision on the 39.5 Step 3 : Extract the gr aph and Map In this step, w e e xtract the computational g raph of the entire netw ork and tr y to allocate hardw are resources. The chip has multiple cores that w e can allocate neurons. The mapper finds the mos t suitable cores f or neurons depending on neurons ’ parameters. Each neural core has a different parameter configuration. T o obtain a g raph descr ibing the entire netw ork, whic h contains the computational flo w of inf or mation through the netw ork as w ell as all parameters, w e simply use the :p y :meth:‘ .Module.as 𝑔 𝑟 𝑎𝑝ℎ ‘ 𝑚𝑒𝑡ℎ𝑜𝑑. F or more inf or mation, see :ref:‘/adv anced/graph 𝑜 𝑣 𝑒𝑟 𝑣 𝑖𝑒𝑤 .𝑖𝑝𝑦 𝑛𝑏 ‘ . [13]: spec = mapper(net . as_graph()) # The behavior of the circuit depends on the temprature. For now, just scale the Iscale ␣ ˓ → parameter manually to make the network fire more or less. # In future, we ' ll omit this! spec[ "Iscale" ] *= 50 39.6 Step 4 : Quantize W eights The w eight matr ices stored inside the la y ers are allo w ed to g et any v alue in the simulation. Ho w ev er , the hardw are does not ha v e a free-of-choice w eight setting f eature. Only a 4-bit res tricted connection-specific weight assignment is possible. While deplo ying a netw ork to Dynap-SE2, w eight matr ices can be con v er ted to a device configuration through a quantization phase. F or Dynap-SE2, netw ork w eight configuration is a tw o-f old process. 4 base w eight parameters define the basis of the inner product space. Connection-specific digital memory cells store the 4-bit binary w eight masks, producing the e xact w eight cur rent that the synapse uses. The object of quantization is to find a base w eight cur rent v ector and a binar y bit-mask matr ix such that the y together reconstruct the desired w eight matrix with minimum deviation. A popular unsuper vised machine learning method, A utoEncoder str ucture, is used f or this. In this approach, intermediate code representation indicates the base weight cur rents, and the decoder w eight matrix giv es binar y bit-masks. 39.5. Step 3 : Extract the graph and Map 363 Rockpool, Release 3.0.3 [14]: from IPython.display import Image Image( "images/weight_quantization.png" , width = 960 ) [14]: The objectiv e of the unsuper vised A utoEncoder training is to find a hardw are configuration that reproduces the targ et w eight matr ix with minimum deviation. Mean square er ror approac h is used to calculate the matr ix reconstruction loss. The reconstruction loss simpl y computes the difference betw een absolute values of the original w eight matrix and the reconstructed v ersion. It takes the sq uares of differences of each cell and returns the mean v alue. 𝑓 𝑀 𝑆 𝐸 ( 𝑊 𝑄 , 𝑊 ) = 1 𝑁 · 𝑀 𝑁 ∑︁ 𝑖 =0 𝑀 ∑︁ 𝑗 =0 ‖ 𝑊 𝑄 [ 𝑖, 𝑗 ] − 𝑊 [ 𝑖, 𝑗 ] ‖ 2 [15]: spec . update(autoencoder_quantization( ** spec)) 39.7 Step 5 : Obtain Hardware Config W e no w con v er t the netw ork specification to a hardw are configuration object. This is fundementall y a samna object that encapsulates the hardw are configuration parameters. The input c hannel map pro vides the mapping betw een in put timeser ies channels and the des tinations which will helps us to map in fur ther stag es. Deplo ying a netw ork to the chip requires translating the neuron and synapse parameters and finding a netw ork connec- tivity configuration. The parameter translation means finding a bias g enerator setting that e xpresses the cur rent values closel y . The cur rent v alues used in the simulator are translated to coarse and fine values using bias g enerator look up tables. It requires finding the closes t possible cur rent v alue that the bias g enerator could pro vide with a vailable coarse and fine v alues. The optimized w eight matr ices ref er to a connectivity configuration but could not be applied to the c hip directl y . The quantization procedure finds the bes t possible base weight currents and w eight masks recons tr ucting the w eight matr i- ces. The w eight masks are dispatched to digital memory blocks embedded in individual neural units. The base weights are translated to coarse-fine v alues similar to other bias parameters. 364 Chapter 39. Quick Star t with Dynap-SE2 Rockpool, Release 3.0.3 The w eight values are allo w ed to be negativ e or positiv e in the simulation. Ho w ev er , the sign does not cor respond to an actual attr ibute in the hardw are. The sign is accepted to represent the synapse ’ s e x citation or inhibition beha vior and is used to deter mine the synapse type. If the value is neg ativ e, the configuration pipeline chooses the inhibitory GAB A synapse; if the v alue is positiv e, it sets up an e x citatory AMP A synapse. In this wa y , the neg ativ e and positiv e w eights produce the desired effect on the post-synaptic membrane. [16]: config = config_from_specification( ** spec) 39.8 Step 6 : Connect to De vice [17]: ## Connect to device se2_devices = find_dynapse_boards() found_se2 = len (se2_devices) > 0 if not found_se2: raise IOError ( "This tutorial requires a connected Dynap-SE2 Stack Board HDK to demonstrate." ) else : se2 = DynapseSamna(se2_devices[ 0 ], ** config) 39.9 Step 7 : Run the Simulation on Dynap-SE2 The netw ork parameters and quantized w eights are used to configure a hardw are netw ork on the Dynap-SE2 chip. The frozen noise patter ns are con v er ted to real-time AER sequences. Each e v ent sho wn in the noise patter n is con v er ted to a data pac kag e encapsulating the time and the address. In this wa y , the time ser ies can dr iv e a real-time de vice emulation. An on-board FPG A circuit con v er ts AER e v ents to digital pulses that stimulate the synaptic in put g ates of the neurons. Analog neurons process the in puts and produce spik es cor respondingl y . Whenev er a neuron fires, digital circuits on FPG A senses the e v ent and encapsulate the timestamp and the source address as an AER e v ent. These output AER e v ents are temporar il y s tored in buffers implemented inside FPG A, w aiting f or real-time reading. The output of the emulation is recorded as AER e v ent sequences and visualized in a similar w a y that the other e v ent sequences are visualized. The figures sho w us that the netw ork’ s response matches the simulated e xpectations. [18]: out, state, rec = se2(train_data[ 0 ][ 0 ], record = True ) visualize_device_sim(train_data[ 0 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> 39.8. Step 6 : Connect to De vice 365 Rockpool, Release 3.0.3 [19]: out, state, rec = se2(train_data[ 1 ][ 0 ], record = True ) visualize_device_sim(train_data[ 1 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> 39.9.1 Step 7.1 : N egativ e T ests Let ’ s f eed the tes t data to the de vice and see that there is no significant difference betw een neurons ’ output fir ing patter ns. [20]: out, state, rec = se2(test_data[ 0 ][ 0 ], record = True ) visualize_device_sim(test_data[ 0 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> 366 Chapter 39. Quick Star t with Dynap-SE2 Rockpool, Release 3.0.3 [21]: out, state, rec = se2(test_data[ 1 ][ 0 ], record = True ) visualize_device_sim(test_data[ 1 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> [22]: out, state, rec = se2(test_data[ 2 ][ 0 ], record = True ) visualize_device_sim(test_data[ 2 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> 39.9. Step 7 : Run the Simulation on Dynap-SE2 367 Rockpool, Release 3.0.3 [23]: out, state, rec = se2(test_data[ 3 ][ 0 ], record = True ) visualize_device_sim(test_data[ 3 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> [24]: out, state, rec = se2(test_data[ 4 ][ 0 ], record = True ) visualize_device_sim(test_data[ 4 ][ 0 ], out, rec, config[ "input_channel_map" ], se2 . dt); <Figure size 11520x4320 with 0 Axes> 368 Chapter 39. Quick Star t with Dynap-SE2 Rockpool, Release 3.0.3 39.10 Step 8 : Simulate the Quantized N etwork It ’ s also possible to reconstruct the simulator from a configuration object and run the simulation to obser v e the effects of the quantization and parameter translation. S teps here are no different than the simulating a rockpool netw ork. [25]: net_quantized = dynapsim_net_from_config( ** config) # Remember, we scaled the Iscale previously, now we should take this back! net_quantized[ 1 ] . Iscale /= 50 [26]: plot_model_response(net_quantized, train_data, dt) 39.10. Step 8 : Simulate the Quantized Network 369 Rockpool, Release 3.0.3 [27]: plot_model_response_histogram(net_quantized, test_data) Histogram: 100 iteration [00:17, 5.70 iteration/s] 370 Chapter 39. Quick Star t with Dynap-SE2 CHAPTER F ORT Y D YNAPSIM NEURON MODEL This tutor ial pro vides an o v er vie w of Dynap-SE2 neuron & synapse simulation and sur rog ate gradient function imple- mentation. W e will in v es tig ate: 1. A theoretical o v er view of silicon synapse and membrane circuitry . 2. Ho w to build an SNN with DynapSim . 3. A single neuron response to an e x poisson spike train s timulation 4. The sur rogate gradient implementation, whic h can be used to train a netw ork with backpropag ation. [1]: from IPython.display import Image 40.1 Section 1 : N euron Model In this chapter , a behavioral in v estig ation of silicon neuron and silicon synapse circuits presented. F or detailed anal y ses of the circuits, please ref er to: • Synapse Circuitr y – C. Bar tolozzi and G. Indiv er i, “Synaptic Dynamics in Analog VLSI, ” in Neural Computation, v ol. 19, no. 10, pp. 2581-2603, Oct. 2007, doi: 10.1162/neco.2007.19.10.2581. • Neuron Membrane Circuitry – P . Livi and G. Indiv er i, “ A cur rent-mode conductance-based silicon neuron f or address-ev ent neuromor - phic sy s tems, ” 2009 IEEE Inter national Symposium on Circuits and Sys tems, 2009, pp. 2898-2901, doi: 10.1109/ISC AS.2009.5118408. • Anal y sis of the Silicon Neuron and Synapse Circuits – E. Chicca, F . S tef anini, C. Bar tolozzi and G. Indiv er i, “Neuromorphic Electronic Circuits f or Building A utonomous Cognitiv e Sy stems, ” in Proceedings of the IEEE, v ol. 102, no. 9, pp. 1367-1388, Sept. 2014, doi: 10.1109/JPR OC.2014.2313954. Silicon neuron and synapse implementations f or m a basis f or the realization of computational neural models through adjusting some bias v oltag es and cur rents. The analy sis presented in this chapter sho w s ho w the higher -le v el h yper - parameters relate to lo w -le v el de vice v oltages and cur rents. The simulator’ s job is to translate the beha vioral dynamics of a computational neural setting into the VLSI parameters of the respectiv e circuits. 371 Rockpool, Release 3.0.3 (continued from pre vious pag e) raster = np . random . poisson ( rate * dt , ( batch_size , steps , n_channels )) # Check if raster has at least one spike if not any ( raster . flatten ()): raise ValueError ( "No spike generated at all due to low firing rate or short simulation time ␣ ˓ → duration!" ) spike_tensor = np . array ( raster , dtype = float ) return spike_tensor [25]: % psource plot_Ix def plot_Ix ( Ix_record : np . ndarray , Ithr : Optional [ Union [ float , np . ndarray ] ]= None , dt : float = 1e-3 , name : Optional [ str ]= None , idx_map : Optional [ Dict [ int , NeuronKey ] ]= None , margin : Optional [ float ]= 0.2 , ax : Optional [ matplotlib . axes . Axes ]= None , line_ratio : float = 0.3 , ylabel : str = "Current (A)", * args , ** kwargs , )- > TSContinuous : """ plot_Ix converts an ` Ix_record ` current measurements/recordings obtained from the ␣ ˓ → record dictionary to a ` TSContinuous ` object and plot :param Ix_record: Membrane or synapse currents of the neurons recorded with respect ␣ ˓ → to time (T,N) :type Ix_record: np.ndarray :param Ithr: Spike threshold or any other upper threshold for neurons. Both a single ␣ ˓ → float number for global spike threshold and an array of numbers for neuron-specific ␣ ˓ → thresholds can be provided. Plotted with dashed lines if provided, defaults to None :type Ithr: Optional[float], optional :param dt: The discrete time resolution of the recording, defaults to 1e-3 :type dt: float, optional :param name: title of the figure, name of the ` TSContinuous ` object, defaults to None :type name: str, optional :param idx_map: a dictionary of the mapping between matrix indexes of the neurons ␣ ˓ → and their global unique neuron keys, defaults to None :type idx_map: Optional[Dict[int, NeuronKey]], optional :param margin: The margin between the edges of the figure and edges of the lines, ␣ ˓ → defaults to 0.2 :type margin: Optional[float], optional :param ax: The sub-plot axis to plot the figure, defaults to None :type ax: Optional[matplotlib.axes.Axes], optional :param line_ratio: the ratio between Imem lines and the Ispkthr lines, defaults to 0. ˓ → 3 (continues on ne xt page) 378 Chapter 40. DynapSim N euron Model Rockpool, Release 3.0.3 (continued from pre vious pag e) :type line_ratio: float, optional :param ylabel: ylabel value to be printed :type ylabel: str, optional :return: Imem current in ` TSContinuous ` object format :rtype: TSContinuous """ f_margin = 1.0 + margin if margin is not None else 1.0 if ax is not None : plt . sca ( ax ) # Convert and plot Ix = TSContinuous . from_clocked ( Ix_record , dt = dt , name = name ) _lines = Ix . plot ( stagger = np . float32 ( Ix . max * f_margin ), * args , ** kwargs ) plt . ylabel ( ylabel ) if idx_map is not None : ax = plt . gca () handles , labels = ax . get_legend_handles_labels () ax . legend ( handles [::- 1 ], [f"n[{n_key}]" for n_key in idx_map . values ()][::- 1 ], bbox_to_anchor =( 1.05 , 1.05 ), ) plt . tight_layout () # Upper threshold lines if Ithr is not None : linewidth = _lines [ 0 ]. _linewidth * line_ratio Ithr = np . ones_like ( Ix_record )* Ithr Ithr = TSContinuous . from_clocked ( Ithr , dt = dt ) Ithr . plot ( stagger = np . float32 ( Ix . max * f_margin ), linestyle ="dashed", linewidth = linewidth , ) return Ix [26]: % psource split_yaxis def split_yaxis ( top_ax : matplotlib . axes . Axes , bottom_ax : matplotlib . axes . Axes , top_bottom_ratio : Tuple [ float ], )- > None : """ split_yaxis arrange ylimits such that two different plots can share the same y axis ␣ ˓ → without any intersection :param top_ax: the axis to place on top (continues on ne xt page) 40.2. Section 2 : Simulation 379 Rockpool, Release 3.0.3 (continued from pre vious pag e) :type top_ax: matplotlib.axes.Axes :param bottom_ax: the axis to place on bottom :type bottom_ax: matplotlib.axes.Axes :param top_bottom_ratio: the ratio between top and bottom axes :type top_bottom_ratio: Tuple[float] """ def arrange_ylim ( ax : matplotlib . axes . Axes , place_top : bool , factor : float )- > None : """ arrange_ylim helper function to arrange y_limits :param ax: the axis to change the limits :type ax: matplotlib.axes.Axes :param place_top: place the axis of interest to top or bottom :type place_top: bool :param factor: the factor to multiply the y-range and allocate space to the ␣ ˓ → other plot :type factor: float """ bottom , top = ax . get_ylim () if place_top : bottom = bottom - factor * ( top - bottom ) else : top = top + factor * ( top - bottom ) ax . set_ylim ( top = top , bottom = bottom ) f_top = top_bottom_ratio [ 1 ]/ top_bottom_ratio [ 0 ] f_bottom = top_bottom_ratio [ 0 ]/ top_bottom_ratio [ 1 ] arrange_ylim ( top_ax , 1 , f_top ) arrange_ylim ( bottom_ax , 0 , f_bottom ) 40.2.3 Step 1 : N etw ork Configuration Configure an SNN, no different than configur ing an y other netw ork in R ockpool. T o recall, please ref er to : Getting S tar ting with R ockpool. [27]: model = Sequential( LinearJax(shape = ( 1 , 1 ), weight = np . array([ 0.1 ]), has_bias = False ), DynapSim(( 1 , 1 ), has_rec = False ), ) model [27]: JaxSequential with shape (1, 1) { LinearJax ' 0_LinearJax ' with shape (1, 1) DynapSim ' 1_DynapSim ' with shape (1, 1) } 380 Chapter 40. DynapSim N euron Model Rockpool, Release 3.0.3 40.2.4 Step 2 : In put Spik e T rain Generate a random poisson spik e train with mean frequency 20 Hz [28]: dt = 1e-3 spike_raster = poisson_spike_train( n_channels = 1 , duration = 1.0 , rate = 20.0 , dt = dt, seed = 2022 ) . astype( bool ) spike_ts_in = TSEvent . from_raster(spike_raster[ 0 ], dt = dt, name = "Input Spike Train" ) spike_ts_in . plot() plt . tight_layout() 40.2.5 Step 3 : Run Simulate the netw ork while recording the inter mediate steps [29]: model . reset_state() out, state, record_dict = model(spike_raster, record = True ) 40.2.6 Step 4 : Analyze the r esults Get the synapse and membrane responses from the record dictionar y , plot the figure. [30]: spike_ts_out = TSEvent . from_raster(out[ 0 ], dt = dt, name = "Output Spike Train" ) spike_ts_out . plot() plt . tight_layout() 40.2. Section 2 : Simulation 381 Rockpool, Release 3.0.3 Plot the membrane potential reading and synaptic injection cur rent on the figure. [32]: # Plot the vmem and isyn on the same figure fig, ax1 = plt . subplots() ax2 = ax1 . twinx() plot_Ix( record_dict[ "1_DynapSim" ][ "vmem" ][ 0 ], ylabel = "Voltage (V)" , ax = ax1, label = "$V_ {mem} $" , ) plot_Ix(record_dict[ "1_DynapSim" ][ "isyn" ][ 0 ], ax = ax2, color = "red" , label = "$I_ {syn} $" ) split_yaxis(ax1, ax2, ( 2 , 1 )) plt . tight_layout() A t first glance, the response is similar to the response of the Leaky Integrate and Fire neuron response presented in the simple introduction tutor ial. Indeed, with small dispar ities, the y are pretty close to each other . In both cases, the synaptic cur rent instantl y increases when a spike arr iv es and leaks consis tentl y . One significant difference in DPI response is the shor t-ter m f acilitation; the jump amount depends on the synaptic state. If the cur rent v alue 𝐼 𝑠𝑦 𝑛 is sufficientl y greater than 𝐼 𝑔 𝑎𝑖𝑛 , the jump is more prominent, and else it ’ s depressed. Also, w e can see the effect of positiv e f eedback circuitry that the more the membrane capacitor is char g ed, the bigger the jumps w e obser v e. 382 Chapter 40. DynapSim N euron Model Rockpool, Release 3.0.3 F or this specific e x ecution, the la y out parameters: capacitance, ther mal v oltag e, and subthreshold slope f actor , are all k ept at their def ault v alues. The default v alues of the parameters pro vide a smooth operation region. 40.3 Section 3 : Spike Generation Logic R emember that the common f eature of e v er y spiking neuron is declared as that the y hold the temporal s tate inf or mation and produce a spik e when the state threshold cons traints are satisfied. As a computatioanl neuron model, the Dynap- SE neuron synapse simulation bloc k, stores the temporal state in 𝐼 𝑚𝑒𝑚 current and compare the 𝐼 𝑚𝑒𝑚 cur rent with spik e threshold cur rent 𝐼 𝑠𝑝𝑘 𝑡ℎ𝑟 to produce a spike. Ho w e v er , the naiv e implementation of this conditional logic mak es it troublesome to optimize a spiking neural netw ork using gradient based optimization mechanisms. Instead, a Hea viside- step function with cus tom g radient r ules is implemented to decide on spik e g eneration at eac h time step. [33]: % psource step_pwl @ custom_jvp def step_pwl ( imem : jnp . DeviceArray , Ispkthr : jnp . DeviceArray , Ireset : jnp . DeviceArray , max_spikes_per_dt : int = jnp . inf , )- > float : """ step_pwl implements heaviside step function with piece-wise linear derivative to use ␣ ˓ → as spike-generation surrogate :param imem: Input current to be compared for firing :type imem: jnp.DeviceArray :param Ispkthr: Spiking threshold current in Amperes :type Ispkthr: jnp.DeviceArray :param Ireset: Reset current after spike generation in Amperes :type Ireset: jnp.DeviceArray :return: number of spikes produced :rtype: float """ spikes = jnp . ceil ( jnp . log ( imem / Ispkthr )) n_spikes = jnp . clip ( spikes , 0.0 , max_spikes_per_dt ) return n_spikes 40.3.1 Section 3.1 Set the parameters Spiking threshold cur rent and reset cur rent [34]: # Currents in Amperes Ispkthr = 1e-6 Ireset = 5e-13 40.3.2 Section 3.2 Scan the Parameter Space Generate a log ar itmic space and record the output of the spik e g eneration function step_pwl at each time s tep 40.3. Section 3 : Spike Generation Logic 383 Rockpool, Release 3.0.3 [35]: Imem_space = np . logspace(start =- 14 , stop =- 3 , num = 10000 ) n_spikes_trace = [step_pwl(Imem, Ispkthr, Ireset) for Imem in Imem_space] [36]: # Plotting fig = plt . figure() plt . semilogx(Imem_space, n_spikes_trace, label = "# spikes" ) plt . axvline(Ispkthr, linestyle = "dashed" , color = "red" , label = "$I_ {spkthr} $" ) # Labeling plt . xlabel( "$I_ {mem} $" ) plt . ylabel( "Number of Spikes" ) plt . legend() plt . tight_layout() The x axis represents the membrane cur rent and the y axis represents the number of spikes produced. The actual circuit produces a spik e compar ing the membrane potential 𝑉 𝑚𝑒𝑚 and the spik e threshold parameter set 𝑉 𝑠𝑝𝑘𝑡ℎ𝑟 . So, although it ’ s ph y sicall y not possible, if 𝑉 𝑚𝑒𝑚 doubles the 𝑉 𝑠𝑝𝑘𝑡ℎ𝑟 then tw o spik es at the same time w ould ha v e to be produced. In the simulation, there is no har m to go be y ond the ph y sical limitations being a ware the conseq uences. Easing off the restrictions makes a broader parameter space visible. The phy sical reality could be compelled using regular ization techniq ues when necessary . In the subthreshold operation region, the relation betw een cur rent v alues and respectiv e base v oltages is e xponential. Theref ore, doubling the potential cor responds to squar ing the cur rent. R especting this, the step-function that is imple- mented requires that the current should be one order of magnitude higher than the spike threshold current in order f or multi-spik e production. A ccordingl y , the wa v ef or m pro vided in the figure resembles a linear s taircase in the log-scaling. The e xact equation producing this thresholding mec hanism is giv en as f ollo ws: num spik es = ⌈︂ ln (︂ 𝐼 𝑚𝑒𝑚 𝐼 𝑠𝑝𝑘 𝑡ℎ𝑟 )︂⌉︂ 40.4 Section 4 : Surrog ate Gradient F unction Although the functionality introduced up to this point is sufficient to build and e x ecute spiking neural netw orks using the Dynap-SE neuron model, it ’ s not enough to optimize a netw ork efficientl y . In order to run a g radient-based optimization algor ithm, this neuron model requires a surrogate function. Chec k these ref erences f or more about sur rogate gradient approach: 384 Chapter 40. DynapSim N euron Model Rockpool, Release 3.0.3 • J. Lee, T . Delbr ück, and M. Pf eiffer . T raining deep spiking neural netw orks using backpropag ation. Frontiers in Neuroscience, 10, 2016 • E. O. N eftci, H. Mostaf a, and F . Zenk e. Sur rog ate gradient lear ning in spiking neural netw orks: Br inging the po w er of gradient-based optimization to spiking neural netw orks. IEEE Signal Processing Mag azine, 36(6):51–63, 2019 Broadl y speaking, it addresses the problem that the spiking neurons deliv er discrete outputs using an indifferentiable threshold function, which mak es it impossible to bac kpropag ate the er ror . In the con v entional bac kpropag ation ap- proach, the c hain r ule is applied f or er ror credit assignment. In spiking neurons, the output is a spike train and bac k - propag ating the er ror to pre vious la y er requires taking the deriv ativ e of the threshold function. In Dynap-SE neuron implementation, taking the der iv ativ e of the output spike train with respect to a parameter that specify the membrane cur rent dynamics looks like the f ollo wing. 𝜕 𝑆 𝑜𝑢𝑡 ( 𝑡 ) 𝜕 𝑃 = 𝜕 Θ( 𝐼 𝑚𝑒𝑚 , 𝐼 𝑠𝑝𝑘 𝑡ℎ𝑟 ) 𝜕 𝐼 𝑚𝑒𝑚 · 𝜕 𝐼 𝑚𝑒𝑚 𝜕 𝑃 Here, Θ stands f or the Heaviside s tep function, and the parameter P can be an ything that chang es the membrane dynam- ics lik e a leakag e cur rent, or a gain current, and etc. In order to find ho w a small fraction of c hang e in the parameter 𝑃 affects the output spik e train, the Θ function should be differentiable. Ho we v er , the der ivativ e of the spik e generation function is almost alw a ys zero since the surface is mos tly flat. When the der iv ativ e is not zero, it ’ s infinite because of the sudden jumps. As a solution, an appro ximate continuous function that is able to substitute the e xact spik e generation function is used as a sur rogate in the bac kw ard pass. Implementation of the custom gradient r ule is giv en belo w . [37]: from rockpool.devices.dynapse.simulation.surrogate import step_pwl_jvp % psource step_pwl_jvp @ step_pwl . defjvp def step_pwl_jvp ( primals : Tuple [ jnp . DeviceArray ], tangents : Tuple [ jnp . DeviceArray ] )- > Tuple [ jnp . DeviceArray ]: """ step_pwl_jvp custom jvp function defining the custom gradient rule of the step pwl ␣ ˓ → function :param primals: the primary variables passed as the input to the ` step_pwl ` function :type primals: Tuple[jnp.DeviceArray] :param tangents: the first order gradient values of the primal variables :type tangents: Tuple[jnp.DeviceArray] :return: modified forward pass output and the gradient values :rtype: Tuple[jnp.DeviceArray] """ imem , Ispkthr , Ireset , max_spikes_per_dt = primals imem_dot , Ispkthr_dot , Ireset_dot , max_spikes_per_dt_dot = tangents primal_out = step_pwl (* primals ) tangent_out = jnp . clip ( jnp . ceil ( imem - Ireset ), 0 , 1 )* imem_dot return primal_out , tangent_out 40.4.1 Section 4.1 Gr adient Compuation N o w , re-use the logaritmic space, but this time store the gradient of the step function step_pwl computed independentl y at each point in the parameter space. [38]: # Scan the parameter space Imem_nabla = [jax . grad(step_pwl)(Imem, Ispkthr, Ireset) for Imem in Imem_space] (continues on ne xt page) 40.4. Section 4 : Surrogate Gradient F unction 385 Rockpool, Release 3.0.3 (continued from pre vious pag e) # Plot fig = plt . figure() plt . xlabel( "$I_ {mem} $" ) plt . ylabel( "$ \\ nabla I_ {mem} $" ) plt . semilogx(Imem_space, Imem_nabla) plt . tight_layout() The gradient value is eq ual to 1 pro vided that the 𝐼 𝑚𝑒𝑚 is g reater than 𝐼 𝑟 𝑒𝑠𝑒𝑡 v alue. W ith this properl y scaled gradient, bac kpropag ation or an y other g radient based method can be applied to computational Dynap-SE neuron model. N ote that, ha ving a cons tant v alue abo v e a threshold, the sur rog ate function resembles the gradient of the f amous ReLU function :). 40.4.2 Section 4.2 : Restoring Surrogate F unction Computing the integral of the g radient v alues, restores the surrogate function. Since only the cus tom g radient imple- mented, that is the onl y w a y to visualize the beha vior of the vir tual bac kw ard pass function. [39]: # Integrate diff_space = np . diff(np . concatenate(([Imem_space[ 0 ] / 2 ], Imem_space))) surrogate = np . cumsum(np . array(Imem_nabla) * diff_space) [40]: # Plot fig = plt . figure() plt . loglog(Imem_space, surrogate, label = "$I_ {surrogate} $" ) plt . axvline(Ireset, linestyle = "dashed" , color = "red" , label = "$I_ {reset} $" ) # Label plt . xlabel( "$I_ {mem} $" ) plt . ylabel( "$I_ {surrogate} $" ) plt . legend() plt . tight_layout() 386 Chapter 40. DynapSim N euron Model Rockpool, Release 3.0.3 In the sur rogate counterpar t, again the x axis represents the membrane current, but the y axis here is not the number of spik es. Instead, the w a v ef or m seen can be reg arded as a smoothed out v ersion of the staircase outlook of the actual function. Also, in order to ensure that the membrane cur rent is differentiable in the full operation rang e, the cut-off v alue is not 𝐼 𝑠𝑝𝑘𝑡ℎ𝑟 , but it ’ s 𝐼 𝑟 𝑒𝑠𝑒𝑡 . 40.5 N e xt Steps The f ollo wing tutor ial will co v er netw ork optimization, and post-optimization s tag es required to deplo y a netw ork succesfull y to the de vice. • F or gradient based Dynap-SE2 SNN optimization see T raining a spiking ne twor k to deploy t o Dynap-SE2 • F or post-optimization s teps see Quic k Start with Dynap-SE2 40.5. N ext Steps 387 Rockpool, Release 3.0.3 • Büchel, J., Zendr ik o v , D., Solinas, S. et al. Super vised training of spiking neural netw orks f or robust deplo yment on mix ed-signal neuromor phic processors. Sci R ep 11, 23376 (2021). • Zendr ik o v , D., Solinas, S., & Indiv er i, G. (2022). Brain-inspired methods f or ac hie ving robus t computation in heterog eneous mix ed-signal neuromor phic processing sys tems. bioRxiv . De vice mismatc h simulation de viates the parameters with a g aussian dis tr ibution, pinning the initial v alues as the mean v alues. In this w a y , the simulator can e xpose the reality better . [7]: Nin = train_data . n_in Nrec = train_data . n_out dt = 1e-3 [8]: net = Sequential( LinearJax(shape = (Nin, Nrec), has_bias = False ), DynapSim((Nrec, Nrec), has_rec = True , percent_mismatch = 0.05 , dt = dt), ) net WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and ␣ ˓ → rerun for more info.) [8]: JaxSequential with shape (60, 2) { LinearJax ' 0_LinearJax ' with shape (60, 2) DynapSim ' 1_DynapSim ' with shape (2, 2) } 41.4.1 Step 2.1 : The initial Results of F rozen Noise 0 [9]: plot_model_response(net, train_data, dt) 394 Chapter 41. T raining a spiking network t o deplo y to Dynap-SE2 Rockpool, Release 3.0.3 41.5 Step 3 : Response Analysis The figures abo v e sho w s that the netw ork reacts similar l y to giv en different noise patter ns. In order to put a figure on the netw ork’ s response, neurons mean fir ing rates in the giv en duration are computed b y summing up all the spik es g enerated and dividing b y the number of timesteps. 𝑟 = 1 𝑑𝑡 · 𝑁 · 𝑁 ∑︁ 𝑖 =0 𝑆 [ 𝑖 ] On top of that, to compare the neurons ’ capability to distinguish the frozen noise patterns, the ratio betw een neurons ’ mean fir ing rate is used. The firing rate ratio ( FRR ) is the ratio betw een the super ior and inf er ior mean fir ing rates read from the decision neurons. FRR is calculated as f ollo ws: 𝐹 𝑅 𝑅 = 𝑟 superior 𝑟 inf erior It ’ s seen that the outputs of the neurons are almost identical receiving the frozen noise patterns of similar discrete time ser ies of e v ents. The number is pretty close to eac h others and to 1. This sho ws that the randoml y initialized DynapSim netw ork is insensitiv e to the noise patter ns. Belo w , a collection of fir ing rate responses of the initial netw ork to the test data is giv en. [10]: plot_model_response_histogram(net, test_data) Histogram: 100 iteration [00:16, 5.95 iteration/s] 41.5. Step 3 : Response Analy sis 395 Rockpool, Release 3.0.3 In order to teach the netw ork to sense the temporal nuances hidden in these time series, a g radient-based optimization procedure will be e x ecuted ne xt. The f ollo wing sections present this procedure, star ting from introducing the objectiv e function. 41.6 Step 4 : Optimization The objectiv e of the optimization is to make one of the neurons fire at a noticeably higher rate upon receiving a specific frozen noise. F or e xample, if frozen noise 1 is dispatched to the de vice then neuron 1 should fire dominantl y , and if frozen noise 2 is dispatched, neuron 2 fir ing should dominate the output reading. In order to ac hie v e this beha vior , mean square error (MSE) loss is e xploited. 41.6.1 Step 4.1 Loss F unction 𝑓 MSE ( 𝑦 out , 𝑦 target ) = 1 𝑁 𝑁 ∑︁ 𝑖 =0 ( 𝑦 out [ 𝑖 ] − 𝑦 targ et [ 𝑖 ]) 2 The targ et train is a unif or m spike train that has an e v ent at ev ery time step. [11]: # - Loss function @jax . jit @jax . value_and_grad def loss_vgf (params, net, input , target): net = net . set_attributes(params) net = net . reset_state() output, _, _ = net( input ) return jl . mse(output, target) Ev en though, the training objectiv e is to mak e the neuron fire constantl y , the actual neuron model is not capable of producing e xactl y the same ideal spike train. The refractor y per iods and spike freq uency adaptation mechanism pre v ent the neuron from fir ing incessantl y . Theref ore, it ’ s impossible to g et zero er ror in an y case. The optimizer pushes the neurons to do their best to con v erg e to the ideal spiking regime. The e xpected training beha vior is that the er ror will start high and then g raduall y drop do wn to a le v el that is definitel y abo v e zero. 41.6.2 Step 4.2 Optimizer T o update the weight matrices such that the netw ork w ould achie v e the leas t possible mean square error , a g radient- based optimization procedure is applied. In this e xper iment, a popular g radient descent v ar iation, A daptiv e Moment Estimation, A dam , is used. This method pro vides a firs t-order gradient-based optimization of stoc hastic objectiv e functions based on adaptiv e estimates of lo w er -order moments. [12]: # - Initialise optimiser init_fun, update_fun, get_params = adam(step_size = 1e-3 ) opt_state = init_fun(net . parameters()) update_fun = jax . jit(update_fun) 41.6.3 Step 4.3 Mismatch Generator (Optional) This step is not necessary to train a DynapSim netw ork, but highly sugg ested. As e xplained abo v e, the hardware suffers from de vice mismatc h, and w e already initiated the model with a frozen mismatc h. On top of it, de viating parameter v alues during training, w ould giv e us a better , more robust parameter space. [13]: # Obtain the prototoype and the random number generator keys rng_key = jnp . array([ 2021 , 2022 ], dtype = jnp . uint32) (continues on ne xt page) 396 Chapter 41. T raining a spiking network t o deplo y to Dynap-SE2 Rockpool, Release 3.0.3 (continued from pre vious pag e) mismatch_prototype = dynamic_mismatch_prototype(net) # Get the mismatch generator function regenerate_mismatch = mismatch_generator(mismatch_prototype, percent_deviation = 0.30 , ␣ ˓ → sigma_rule = 3.0 ) regenerate_mismatch = jax . jit(regenerate_mismatch) 41.6.4 Step 4.5 Run the trainin loop Each training s tep, or epoch, includes a f or w ard and a bac kw ard pass. The f or ward pass simulates the neural dynamics in time and produces the spik e trains. The bac kw ard pass bac kpropag ates the er ror in time, assigning the credits to w eight values. Since the f or w ard computation introduces indifferentiable functions, a sur rogate function appro ximation is used in the bac kw ard pass. (see neuron model tutor ial f or more) As a result, the w eight values g et small updates in each time s tep fixing the beha vior slightl y in the appro ximate continuous space. So, this will tak e time.. Y ou can w ait or y ou can use the pre-trained w eights f or later stag es. [14]: # - Configure learning num_epochs = int ( 1e6 ) apply_mismatch = 100 loss_t = [] # - Get input and target batches batch_input, batch_target = train_data . full_batch # - Training loop t = tqdm( range (num_epochs), desc = ' Training ' , unit = ' Epoch ' , total = num_epochs) for epoch in t: # - Get parameters opt_parameters = get_params(opt_state) # - Regenerate mismatch once in a while if epoch % apply_mismatch == 0: rng_key, _ = rand . split(rng_key) new_params = regenerate_mismatch(net, rng_key = rng_key) net = net . set_attributes(new_params) # - Compute loss and gradient l ,g = loss_vgf(opt_parameters, net, batch_input, batch_target) loss_t . append(l . item()) t . set_postfix({ ' loss ' : l . item()}, refresh = False ) # - Update optimiser opt_state = update_fun(epoch, g, opt_state) Training: 100%|| 1000000/1000000 [06:41<00:00, 2491.61Epoch/s, loss=0.453] 41.6. Step 4 : Optimization 397 Rockpool, Release 3.0.3 41.7 Step 5 : Results W e ha v e trained our netw ork to recognize some giv en frozen noise patter ns. No w it ’ s time to chec k it ’ s per f or mance 41.7.1 Step 5.1. Loss Plot It ’ s seen in the figure that the loss decreased from 0.5 to a non-zero value o v er man y epochs (as e xpected). Ho w e v er , e v en this much drop creates a hug e difference in beha vior , and the netw ork obtains the ability to classify tw o similar frozen noise samples. [15]: plt . semilogy(loss_t) plt . xlabel( ' Epochs ' ) plt . ylabel( ' Loss Value ' ) plt . title( "MSE Loss" ) plt . tight_layout() 41.7.2 Section 5.2 Get the Optimized N etwork As a result of training, the netw ork must ha v e lear ned to sense the difference betw een frozen noise patter ns. In order to obser v e the beha vior of the netw ork and compare with the initial v ersion, the optimized netw ork is simulated. [16]: net_optimized = net . set_attributes(get_params(opt_state)) plot_model_response(net_optimized, train_data, dt) 398 Chapter 41. T raining a spiking network t o deplo y to Dynap-SE2 Rockpool, Release 3.0.3 41.7.3 Section 5.3 : N egative T est Optimization results sho w that this tin y recur rent spiking neural netw ork of tw o DynapSim neurons can dis tinguish one frozen noise from another . If the optimization requirements w ere satisfied, then the netw ork should respond clearl y to recognized noises and react randoml y to an ything else. In other w ords, decision neurons should fire tog ether or sta y silent upon receiving a non-recognized signal. If one of them fires and the other one s ta ys silent, then it sho w s that the netw ork can be deceiv ed and the decisions are not reliable. N o w , w e will use the test set data to sho w that our netw ork recognize onl y the the training samples. In order to test this, 1000 frozen noise patter ns with the same mean frequency and length are g enerated using the same process used in training data g eneration. T o pro v e the rate ratio is dramatically c hang ed only f or recognized signals, the FRRs betw een decision neurons are recorded. [17]: plot_model_response(net_optimized, test_data, dt, range ( 5 )) 41.7. Step 5 : Results 399 Rockpool, Release 3.0.3 400 Chapter 41. T raining a spiking network t o deplo y to Dynap-SE2 Rockpool, Release 3.0.3 It will be a long pag e if we plot 100 samples one after another , ho w ev er , w e can giv e an histogram sho wing FRR readings of all 100 test samples. [18]: plot_model_response_histogram(net_optimized, test_data) Histogram: 100 iteration [00:15, 6.27 iteration/s] 41.7.4 Section 5.4 : Sa ve the parameters for the ne xt steps It ’ s g reat that w e hav e a netw ork that can recognize the frozen noise patter ns using onl y 2 analog neurons! In the ne xt steps w e will map this netw ork to the hardware f ollo wing some steps similar to q uic k s tar t tutor ial. Let ’ s sa v e the netw ork w eights at this s tag e bef ore doing an ything else. [19]: p0, p1 = get_params(opt_state) . values() with open ( "data/w_in_optimized.npy" , "wb" ) as f: np . save(f, p0[ "weight" ]) with open ( "data/w_rec_optimized.npy" , "wb" ) as f: np . save(f, p1[ "w_rec" ]) 41.7. Step 5 : Results 401 Rockpool, Release 3.0.3 41.8 N e xt steps Please continue with the post-training tutorial to see ho w this netw ork is deplo y ed to the hardware. Quick S tar t with Dynap-SE2 402 Chapter 41. T raining a spiking network t o deplo y to Dynap-SE2 CHAPTER F ORT Y T W O COMPUT A TIONAL GRAPHS IN ROCKPOOL Graphs is R ockpool are used to con v er t the structure of an arbitrar y netw ork to deplo y on neuromor phic hardware. The graphs cannot be used f or simulation, and are g enerally more cons trained than netw orks used f or training and simulation. [1]: # - Switch off warnings import warnings warnings . filterwarnings( "ignore" ) 42.1 Graph base classes Graphs consist of modules (deriv ed from the class graph.GraphModule ), connected o v er nodes (der iv ed from the class graph.GraphNode ). graph.GraphModule s are units of computation. For e xample, a set of linear w eights; a population of spiking neurons. graph.GraphModule s contain links to sets of in put nodes and output nodes, which define the c hannels of inf or mation flo wing in and out of a module. These nodes are graph.GraphNode objects. [2]: from IPython.display import Image from pathlib import Path Image(Path( "images" , "GraphModule.png" )) [2]: The figure abo v e sho w s the conceptual contents of a graph.GraphModule . input_nodes and output_nodes are ordered lists containing ref erences to zero or more graph.GraphNode objects. graph.GraphModule subclasses define specific computational units. The y contain all additional parameters needed to define the configuration of the computational unit. For e x ample, see the graph.LIFNeuronWithSynsRealValue class, which contains the configuration parameters needed to define a standard LIF neuron with e xponential synapses. A graph.GraphNode is an object that holds connections betw een graph.GraphModule s. Eac h node can ha v e mul- tiple sources and sinks of inf or mation. 403 Rockpool, Release 3.0.3 rockpool.nn.modules.torch.torch_module Pro vide a base class f or build T orch-compatible modules F unctions to_float_tensor (x) Classes TorchModule (*args, **kw args) Base class f or modules that are compatible with both T orch and R oc kpool TorchModuleParameters A dict subclass that suppor ts con v ersion to ra w values rockpool.nn.modules.torch.updown_t orch F eedf or w ard lay er that con v erts each analogue input c hannel to one spiking up and one do wn channel Classes StepPWL (*args, **kw args) Hea viside step function with piece-wise linear derivativ e to use as spik e-generation surrogate UpDownTorch (*args, **kw args) F eedf or w ard lay er that con v erts each analogue input channel to one spiking up and one spiking do wn c hannel. rockpool.nn.network s Defines classes f or encapsulating and g enerating netw orks of la y ers Modules synnet Implements the SynNet arc hitecture f or temporal signal processing wavesense Implements the W av eSense arc hitecture f or temporal signal processing rockpool.nn.network s.synnet Implements the SynNet arc hitecture f or temporal signal processing The SynNet arc hitecture is descr ibed in Bos & Muir 2022 [ https:// ar xiv .org/ abs/ 2208.12991 ] and Bos & Muir 2024 [ https:// ar xiv .or g/ abs/ 2406.15112 ] Classes SynNet (*args, **kw ar gs) Define a SynNet architecture netw ork 48.1. Package structure summar y 495 Rockpool, Release 3.0.3 rockpool.nn.network s.wav esense Implements the W av eSense arc hitecture f or temporal signal processing The W av eSense arc hitecture is descr ibed in W eidel et al 2021 [ https:// ar xiv .org/ abs/ 2111.01456 ] See Also W aveSense: T raining a Spiking N eur al N etw or k with T emporal Conv olutions Classes WaveBlock (n_channels_res, n_c hannels_skip, ...) WaveNet ([n_classes, n_channels_in, ...]) WaveSenseBlock (*args, **kw ar gs) Implements a single W av eSenseBloc k WaveSenseNet (*args, **kw ar gs) Implement a W av eSense netw ork rockpool.parameters Classes to manag e registered Module attributes in R ockpool F unctions Constant (obj) Identify an initialisation argument as a cons tant (non- trainable) parameter Classes Parameter (data, f amily , init_func, ...) R epresent a module parameter ParameterBase (data, f amily , init_func, ...) Base class f or R oc kpool registered attributes RP_Constant () R epresent a concrete initialisation v alue as a constant pa- rameter , whic h should not be trained SimulationParameter (data, f amily , init_func, ...) R epresent a module simulation parameter State (data, f amily , init_func, ...) R epresent a module state rockpool.timeseries Classes to manag e time ser ies data F unctions full_nan (shape) Build an all-N aN ar ra y get_global_ts_plotting_backend () R etur n a string representing the cur rent plotting back end load_ts_from_file (path[, e xpected_type]) Load a timeser ies object from an npz file set_global_ts_plotting_backend (bac kend[, ...]) Set the plotting bac kend f or use b y TimeSeries classes 496 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 Classes TSContinuous ([times, samples, num_channels, ...]) R epresents a continuously -sampled time series. TSDictOnDisk ([data]) Beha v es like a dict. TSEvent ([times, channels, per iodic, ...]) R epresents a discrete time ser ies, composed of binar y e v ents (present or absent). TimeSeries ([times, per iodic, t_s tar t, ...]) Base class to represent a continuous or e v ent-based time ser ies. rockpool.training Contains pac kages f or assis ting with NN training Modules adversarial_jax Functions to implement adv ersarial training approaches using Jax ctc_loss Implementations of the CTC loss, in nump y , Jax and T orch jax_debug Utilities f or debugging Jax training loops jax_loss Jax functions useful f or training netw orks using Jax Modules. torch_loss T orch loss functions and regularizers useful f or training netw orks using T orch Modules. rockpool.training.adversarial_jax Functions to implement adv ersarial training approaches using Jax See also Adv ersarial tr aining illustrates ho w to use the functions in this module to implement adv ersarial attacks on the parameters of a netw ork dur ing training. F unctions adversarial_loss (parameters, net, inputs, ...) Implement a h ybr id task / adv ersar ial robus tness loss pga_attack (params_flattened, net, rng_ke y , ...) P er f or ms the PG A (projected gradient ascent) based at- tac k on the parameters of the netw ork giv en inputs. rockpool.training.ctc_loss Implementations of the CTC loss, in nump y , Jax and T orch F unctions ctc_loss_jax (label, log_prob, seq_length[, ...]) Jax-compatible implementation of the CTC loss ctc_loss_numpy (label, log_prob, seq_length) N ump y implementation of the CTC loss continues on ne xt page 48.1. Package structure summar y 497 Rockpool, Release 3.0.3 T able 219 – continued from pre vious page ctc_loss_torch (label, log_prob, seq_length) T orch implementation of the CTC loss rockpool.training.jax_debug Utilities f or debugging Jax training loops F unctions debug_evolution (jmod, state, parameters, input) Debug and repor t the presence of N aNs in netw ork state / output debug_optimisation (jmod, parameters, input, ...) Debug an optimisation step, repor ting the presence of N aNs in loss and g radients flatten (g ener ic_collection[, sep]) Flattens a g ener ic collection of collections into an or - dered dictionar y . rockpool.training.jax_loss Jax functions useful f or training netw orks using Jax Modules. See also See T raining a R oc kpool netw ork wit h Jax f or an introduction to training netw orks using Jax-bac ked modules in R oc kpool, including the functions in jax_loss . F unctions bounds_clip (params, lo w er_bounds, upper_bounds) bounds_cost (params, lo wer_bounds, upper_bounds) Impose a cost on parameters that violate bounds con- straints l0_norm_approx (params[, sigma]) Compute a smooth differentiable appro ximation to the L0-nor m l2sqr_norm (params) Compute the mean L2-squared-norm of the set of param- eters logsoftmax (x[, temperature]) Efficient implementation of the log softmax function make_bounds (params) Con v enience function to build a bounds template f or a problem mse (output, targ et) Compute the mean-squared error betw een output and tar - g et softmax (x[, temperature]) Implements the softmax function sse (output, targ et) Compute the sum-squared error between output and tar - g et rockpool.training.torch_loss T orch loss functions and regularizers useful f or training netw orks using T orch Modules. 498 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 F unctions bounds_cost (params, lo wer_bounds, upper_bounds) Impose a cost on parameters that violate bounds con- straints make_bounds (params) Con v enience function to build a bounds template f or a problem summed_exp_boundary_loss (data[, ...]) Compute the summed e xponential er ror of boundar y vi- olations of an input. Classes ParameterBoundaryRegularizer (*args, **kw args) Class wrapper f or the summed e xponential er ror of boundar y violations of an in put. rockpool.transform Contains pac kages f or transf or ming parameters and netw orks Modules dropout Pro vide a Dropout parameter transf ormation mismatch Analog de vice mismatch transf ormation (jax) imple- mentation param_transformer Pro vide a Module wrapper that transf orms parameters bef ore e v olution quantize Pro vide a stoc hastic q uantization parameter transf orma- tion module quantize_methods Quantisation methods f or X y lo torch_transform Defines the parameter and activ ation transf or mation-in- training pipeline f or TorchModule s rockpool.transform.dropout Pro vide a Dropout parameter transf ormation F unctions Dropout (mod, *args, **kw ar gs) Classes JaxDropout (*args, **kw ar gs) ModDropout (*args, **kw ar gs) 48.1. Package structure summar y 499 Rockpool, Release 3.0.3 rockpool.transform.mismatch Analog de vice mismatch transf ormation (jax) implementation F unctions mismatch_generator (prototype[, ...]) mismatch_g enerator returns a function which simulates the analog de vice mismatch effect. module_registery (module) module_registery traces all the nested module and registered parameters of the JaxModule base giv en and retur ns a tree, whose lea v es includes onl y the parameters.SimulationParameters and parameters.Parameters rockpool.transform.param_transformer Pro vide a Module wrapper that transf orms parameters bef ore ev olution F unctions deep_update (source, o v er r ides) U pdate a nested dictionary or similar mapping. Classes JaxParameterTransformerMixin (module[, ...]) ParameterTransformerMixin (module[, ...]) rockpool.transform.quantize Pro vide a stoc hastic q uantization parameter transf or mation module F unctions StochasticQuantize (mod, *args, **kw ar gs) Classes JaxStochasticQuantize (*args, **kw ar gs) ModStochasticQuantize (*args, **kw ar gs) 500 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 rockpool.transform.quantize_methods Quantisation methods f or X y lo Defines the post-training q uasntization methods global_quantize() and channel_quantize() . F unctions channel_quantize (w eights_in, w eights_rec, ...) Quantize a X y lo model f or deplo yment, using per - channel parameter scaling global_quantize (w eights_in, w eights_rec, ...) Quantize a X y lo model f or deplo yment, using global pa- rameter scaling validate_weights_to_2d (data) validate_weights_to_3d (data) rockpool.transform.torch_transform Defines the parameter and activ ation transf or mation-in-training pipeline f or TorchModule s See also /adv anced/QuantT orch.ip ynb Examples Construct a netw ork, and patch it to round eac h w eight parameter: >>> net = Sequential( ... ) >>> T_fn = lambda p: stochastic_rounding(p, num_levels = 2 ** num_bits) >>> T_config = make_param_T_config(net, T_fn, ' weights ' ) >>> T_net = make_param_T_network(net, T_config) T rain here. T o bur n-in and remo v e the transf ormations: >>> burned_in_net = apply_T(T_net) >>> unpatched_net = remove_T_net(burned_in_net) F unctions apply_T (T_net[, inplace]) "Bur n in" a set of parameter transf ormations, appl y- ing each transf or mation and storing the resulting trans- f ormed parameters deterministic_rounding (v alue[, input_rang e, ...]) Quantise v alues b y shifting them to the closest quantisa- tion le v el dropout (param[, dropout_prob]) Randomly set v alues of a tensor to 0. , with a defined probability int_quant (v alue[, maintain_zero, ...]) T ransf or ms a tensor to a quantized space with a rang e of integ er values defined b y n_bits continues on ne xt page 48.1. Package structure summar y 501 Rockpool, Release 3.0.3 T able 233 – continued from pre vious page make_act_T_config (net[, T_fn, ModuleClass]) Create an activity transf ormation configuration f or a net- w ork make_act_T_network (net, act_T_config[, inplace]) P atch a R oc kpool netw ork with activity transf or mers make_backward_passthrough (function) W rap a function to pass the gradient directl y through in the bac kward pass make_param_T_config (net, T_fn[, param_famil y]) Helper function to build parameter transf or mation con- figuration trees make_param_T_network (net, T_config_tree[, ...]) P atch a R oc kpool netw ork to appl y parameter transf or - mations in the f orward pass remove_T_net (T_net[, inplace]) U n-patch a transf ormed-patched netw ork stochastic_channel_rounding (v alue, out- put_rang e) P er f or m s tochastic rounding of a matrix, but with the in- put rang e defined automatically f or each column inde- pendentl y stochastic_rounding (v alue[, input_rang e, ...]) P er f or m floating-point s tochastic rounding on a tensor , with detailed control o v er quantisation le v els t_decay (deca y[, dt]) quantizes deca y factor (e xp (-dt/tau)) of LIF neurons: al- pha and beta respectiv el y f or Vmem and Isyn the quanti- zation is done based one con v er ting the deca y to bitshoft subtraction and reconstructing deca y . Classes ActWrapper (*args, **kw args) A wrapper module that applies an output activity trans- f or mation after e v olution TWrapper (*args, **kw args) A wrapper f or a R ockpool T orc hModule, implementing a parameter transf or mation in the f or w ard pass class_calc_q_decay (dt) function used to calculate bitshift equiv alent of deca y (e xp(-dt/tau)) rockpool.typehints Module to pro vide useful types f or R ockpool See Roc kpool P ar ameter handling f or more inf or mation on the a vailable types. Module A ttributes P_float A P arameter or a float P_int A P arameter or an int P_str A P arameter or a str ing P_bool A P arameter or a boolean P_Callable A P arameter or a Callable P_ndarray A P arameter or a numpy arra y Tree A Python tree-lik e object Leaf A leaf node in a tree Value The v alue in a tree leaf node Node A node in a tree P_tree A P arameter or a T ree P_tensor A P arameter or a torch tensor FloatVector A float scalar or a float v ector continues on ne xt page 502 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 T able 235 – continued from pre vious page IntVector An int scalar or an int v ector JaxRNGKey A Jax RN G ke y JaxTreeDef A Jax tree definition TreeDef A Jax-lik e tree definition Ex ceptions DRCError An Er ror class representing a Design-R ule Check viola- tion DRCWarning A W ar ning / Er ror class representing a Design-Rule Chec k warning rockpool.utilities General utilities Modules backend_management Utility functionality f or managing back ends benchmarking Utilities f or benchmarking neuron la y ers jax_tree_utils Utility functions f or w orking with trees. property_arrays proper ty_ar ra y s.py - Collection of arra y classes to be used as proper ties to control timedarray_shift Implementation of TimedArray . tree_utils T ree manipulation utilities with no e xter nal dependen- cies type_handling type_handling.p y - Con v enience functions f or chec king and con v er ting object types rockpool.utilities.backend_management Utility functionality f or managing back ends T o chec k a standard bac kend, use backend_available() . T o chec k a non-standard bac kend specification, use check_backend() . T o build a shim class that raises an er ror on instantiation, f or when a required bac kend is not a vailable, use missing_backend_shim() . F unctions backend_available (*bac kend_names) R epor t if a back end is a vailable f or use check_backend (bac kend_name[, ...]) Chec k if a back end is a v ailable, and register it in a list of a v ailable back ends check_samna_available () chec k_samna_a v ailable controls if samna pack - ag e is "installed" and "usable" The def ault backend_available() operation cannot cor rectl y identifies the samna a v ailability . continues on ne xt page 48.1. Package structure summar y 503 Rockpool, Release 3.0.3 Examples >>> data, (state0, state1, state2) = self . _auto_batch(data, ( self . state0, self . ˓ → state1, self . state2)) This will v er ify that data has the cor rect final dimension (i.e. self.size_in ). If data has onl y tw o dimensions (T, Nin) , then it will be augmented to (1, T, Nin) . The individ- ual states will be replicated out from shape (a, b, c, ...) to (n_batches, a, b, c, ...) and re- tur ned. If data has onl y a single dimension (T,) , it will be e xpanded to (1, T, self.size_in) . state0 , state1 , state2 will be replicated out along the batch dimension. >>> data, (state0,) = self . _auto_batch(data, ( self . state0,), (( 10 , - 1 , self . ˓ → size_in),)) A ttempt to replicate state0 to a specified size (10, -1, self.size_in) . P arameters • data ( np.ndarray ) – Input data tensor . Either (batches, T, Nin) or (T, Nin) • states ( Tuple ) – T uple of s tate variables. Each will be replicated out o v er batches b y prepending a batch dimension • target_shapes ( Tuple ) – A tuple of tar get size tuples, eac h cor responding to each s tate argument. The individual states will be replicated out to matc h the cor responding targ et sizes. If not pro vided (the default), then s tates will be only replicated along batc hes. R eturns (np.ndarra y , T uple[np.ndarra y]) data, states R eturn type T uple [ ndarray , T uple [ ndarr ay ]] _force_set_attributes (bool) If True , do not sanity-c heck attributes when setting. _get_attribute_family ( type_name: str , family : str | T uple | List = N one ) → dict Search f or attr ibutes of this module and submodules that match a giv en famil y This method can be used to con v enientl y get all w eights f or a netw ork; or all time constants; or an y other f amily of parameters. Parameter f amilies are defined simply b y a string: "weights" f or w eights; "taus" f or time constants, etc. These strings are arbitrar y , but if y ou f ollo w the con v entions then future dev elopers will thank y ou (that includes y ou in six month ’ s time). P arameters • type_name ( str ) – The class of parameters to search f or . Must be one of ["Parameter", "SimulationParameter", "State"] or another future subclass of ParameterBase • family ( Union [ str , Tuple [ str ]] ) – A string or list or tuple of s tr ings, that define one or more attr ibute f amilies to search f or R eturns A nested dictionary of attr ibutes that match the pro vided type_name and family R eturn type dict 510 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 _get_attribute_registry () → T uple[Dict, Dict] R etur n or initialise the attr ibute registry f or this module R eturns registered_attributes, registered_modules R eturn type (tuple) _has_registered_attribute ( name: str ) → bool Chec k if the module has a registered attr ibute P arameters name ( str ) – The name of the attr ibute to chec k R eturns True if the attr ibute name is in the attribute registr y , False other wise. R eturn type bool _in_Module_init (bool) If e xists and True , indicates that the module is in the __init__ chain. _name: str | None N ame of this module, if assigned _register_attribute ( name: str , val: P arame terBase ) R ecord an attr ibute in the attr ibute registry P arameters • name ( str ) – The name of the attr ibute to register • val ( ParameterBase ) – The ParameterBase subclass object to register . e.g. Parameter , SimulationParameter or State . _register_module ( name: str , mod: ModuleBase ) R egister a sub-module in the module regis tr y P arameters • name ( str ) – The name of the module to register • mod ( ModuleBase ) – The ModuleBase object to register _reset_attribute ( name: str ) → ModuleBase R eset an attr ibute to its initialisation v alue P arameters name ( str ) – The name of the attr ibute to reset R eturns F or compatibility with the functional API R eturn type self ( Module ) _shape The shape of this module 48.2. Base classes 511 Rockpool, Release 3.0.3 _spiking_input: bool Whether this module receiv es spiking input _spiking_output: bool Whether this module produces spiking output _submodulenames: List[str] R egistry of sub-module names _wrap_recorded_state ( r ecor ded_dict : dict , t_start : float ) → Dict[s tr , TimeSeries] Con v er t a recorded dictionar y to a TimeSeries representation This method is optional, and is pro vided to mak e the timed() conv ersion to a TimedModule w ork better . Y ou should o v er r ide this method in y our custom Module , to wrap each element of y our recorded state dictionar y as a TimeSeries P arameters • state_dict ( dict ) – A recorded state dictionary as retur ned b y evolve() • t_start ( float ) – The initial time of the recorded state, to use as the s tar ting point of the time ser ies • recorded_dict ( dict ) R eturns The mapped recorded state dictionary , wrapped as TimeSeries objects R eturn type Dict[str , TimeSeries ] as_graph () → GraphModuleBase Con v er t this module to a computational graph R eturns The computational graph cor responding to this module R eturn type Gr aphModuleBase Raises NotImplementedError – If as_graph() is not implemented f or this subclass attributes_named ( name: T uple[str] | Lis t[str] | str ) → dict Search f or attr ibutes of this or submodules b y time P arameters name ( Union [ str , Tuple [ str ] ) – The name of the attr ibute to searc h f or R eturns A nested dictionary of attr ibutes that match name R eturn type dict property class_name: str Class name of self T ype str 512 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 abstractmethod evolve ( input_data , r ecor d: bool = F alse ) → T uple[An y , Any , Any] Ev ol v e the state of this module o v er input data N OTE: THIS MODULE CLASS DOES NOT PR O VIDE DOCUMENT A TION FOR ITS EV OL VE METHOD. PLEASE UPD A TE THE DOCUMENT A TION FOR THIS MODULE. P arameters • input_data – The input data with shape (T, size_in) to e v ol v e with • record ( bool ) – If True , the module should record inter nal state during ev olution and retur n the record. If False , no recording is required. Def ault: False . R eturns (output, ne w_state, record) output (np.ndarra y): The output response of this module with shape (T, size_out) ne w_state (dict): A dictionar y containing the updated state of this and all submodules after e v olution record (dict): A dictionar y containing recorded s tate of this and all submodules, if reques ted using the record argument R eturn type tuple property full_name: str The full name of this module (class plus module name) T ype str modules () → Dict R etur n a dictionar y of all sub-modules of this module R eturns A dictionar y containing all sub-modules. Each item will be named with the sub-module name. R eturn type dict property name: str The name of this module, or an empty string if None T ype str parameters ( f amily : str | T uple | List = N one ) → Dict R etur n a nested dictionary of module and submodule Parameters Use this method to inspect the P arameters from this and all submodules. The optional ar gument family allo w s y ou to search f or Parameters in a particular famil y — f or e xample "weights" f or all w eights of this module and nested submodules. Although the family argument is an arbitrary str ing, reasonable choises are "weights" , "taus" f or time constants, "biases" f or biases. . . Examples Obtain a dictionar y of all P arameters f or this module (including submodules): >>> mod . parameters() dict{ ... } 48.2. Base classes 513 Rockpool, Release 3.0.3 Obtain a dictionar y of P arameters from a par ticular famil y: >>> mod . parameters( "weights" ) dict{ ... } P arameters family ( str ) – The famil y of P arameters to search f or . Def ault: None ; retur n all parameters. R eturns A nested dictionary of Parameters of this module and all submodules R eturn type dict reset_parameters () R eset all parameters in this module R eturns The updated module is retur ned f or compatibility with the functional API R eturn type Module reset_state () → ModuleBase R eset the state of this module R eturns The updated module is retur ned f or compatibility with the functional API R eturn type Module set_attributes ( new_attributes: dict ) → ModuleBase Set the attr ibutes and sub-module attributes from a dictionar y This method can be used with the dictionar y retur ned from module e v olution to set the ne w state of the module. It can also be used to set multiple parameters of a module and submodules. Examples Use the functional API to e v ol v e, obtain new s tates, and set those states: >>> _, new_state, _ = mod( input ) >>> mod = mod . set_attributes(new_state) Obtain a parameter dictionar y , modify it, then set the parameters bac k: >>> params = mod . parameters() >>> params[ ' w_input ' ] *= 0. >>> mod . set_attributes(params) P arameters new_attributes ( dict ) – A nested dictionary containing parameters of this module and sub-modules. R eturn type ModuleBase 514 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 property shape: tuple The shape of this module T ype tuple simulation_parameters ( f amily : str | T uple | List = N one ) → Dict R etur n a nested dictionary of module and submodule SimulationParameters Use this method to inspect the SimulationP arameters from this and all submodules. The optional argument family allo w s y ou to search f or SimulationP arameters in a par ticular f amily . Examples Obtain a dictionar y of all SimulationP arameters f or this module (including submodules): >>> mod . simulation_parameters() dict{ ... } P arameters family ( str ) – The famil y of SimulationP arameters to search f or . Def ault: None ; retur n all SimulationP arameter attr ibutes. R eturns A nested dictionary of SimulationParameters of this module and all submodules R eturn type dict property size: int (DEPREC A TED) The output size of this module T ype int property size_in: int The input size of this module T ype int property size_out: int The output size of this module T ype int property spiking_input: bool If True , this module receiv es spiking input. If False , this module e xpects continuous input. T ype bool property spiking_output If True , this module sends spiking output. If False , this module sends continuous output. T ype bool 48.2. Base classes 515 Rockpool, Release 3.0.3 state ( f amily : str | T uple | List = N one ) → Dict R etur n a nested dictionary of module and submodule States Use this method to inspect the S tates from this and all submodules. The optional argument family allo w s y ou to search f or States in a particular famil y . Examples Obtain a dictionar y of all S tates f or this module (including submodules): >>> mod . state() dict{ ... } P arameters family ( str ) – The famil y of S tates to search f or . Default: None ; retur n all S tate attr ibutes. R eturns A nested dictionary of States of this module and all submodules R eturn type dict timed ( output_num: int = 0 , dt: float = N one , add_ev ents: bool = F alse ) Con v er t this module to a TimedModule P arameters • output_num ( int ) – Specify which output of the module to tak e, if the module retur ns multiple output ser ies. Default: 0 , tak e the first (or onl y) output. • dt ( float ) – Used to pro vide a time-step f or this module, if the module does not already ha v e one. If self already defines a time-step, then self.dt will be used. Def ault: None • add_events ( bool ) – Iff True , the TimedModule will add e v ents occur r ing on a single timestep on in put and output. Def ault: False , don ’ t add time steps. R etur ns: TimedModule : A timed module that wraps this module 48.2.2 nn.modules.TimedModule class nn.modules. TimedModule ( *ar gs , **kw args ) Bases: ModuleBase The R oc kpool base class f or all TimedModule modules TimedModule pro vides functionality f or Module s to understand time ser ies data, and to con v eniently e v olv e, handle and retur n time series data from modules. The evolve() method pro vided b y TimedModule can accept TimeSeries objects nativ el y as input, or can accept cloc ked / ras ter ised input data. See also TimedModule pro vides the useful methods _prepare_input() and _gen_timeseries() to help y ou in rasterising data f or y our o wn TimedModule subclasses. F or more inf or mation on ho w to used the TimedModule API f or R ockpool, see High-lev el TimedModule API . 516 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 A ttributes ov er view class_name Class name of self full_name The full name of this module (class plus module name) input_type The TimeSeries class accepted b y this module name The name of this module, or an empty string if None output_type The TimeSeries class retur ned b y this module shape The shape of this module size (DEPREC A TED) The output size of this module size_in The input size of this module size_out The output size of this module spiking_input If True , this module receiv es spiking input. spiking_output If True , this module sends spiking output. t The cur rent e v olution time of this la y er , in seconds dt The simulation and input ras ter isation timestep f or this TimedModule Methods o verview __init__ (dt[, spiking_input, ...]) Initialise this TimedModule object as_graph () Con v er t this module to a computational graph attributes_named (name) Search f or attr ibutes of this or submodules b y time evolve ([ts_in put, duration, num_timesteps, ...]) Ev ol v e the state of this module o v er time modules () R etur n a dictionar y of all sub-modules of this module parameters ([f amily]) R etur n a nested dictionary of module and submodule P arameters reset_all () R eset the inter nal state and time of this module and all sub-modules reset_parameters () R eset all parameters in this module reset_state () R eset the state of this module reset_time () R eset the inter nal time of this module and all sub- modules to zero set_attributes (ne w_attr ibutes) Set the attr ibutes and sub-module attributes from a dictionar y simulation_parameters ([f amily]) Return a nested dictionary of module and submodule SimulationP arameters state ([f amil y]) R etur n a nested dictionary of module and submodule S tates __in_TimedModule_init: bool = False A flag indicating that this TimedModule is cur rentl y being initialised __init__ ( dt : float | SimulationP ar amet er , spiking_input : bool = F alse , spiking_output : bool = F alse , add_ev ents: bool = T r ue , *ar gs , **kwar gs ) Initialise this TimedModule object When initialised, the TimedModule will hav e a dt attr ibute assigned, as well as initialising the internal module _timestep , _parent_dt_factor and _is_child . The subclass evolve() method will be wrapped to update the inter nal times tamp clock. P arameters 48.2. Base classes 517 Rockpool, Release 3.0.3 • dt ( float ) – The duration of a single time step f or this module, in seconds • spiking_input ( bool ) – If True , this module accepts TSEvent e v ent time ser ies objects as input. If False (def ault), this module accepts TSContinuous continuous time ser ies objects as input. • spiking_output ( bool ) – If True , this module sends TSEvent e v ent time ser ies objects as output. If False (def ault), this module sends TSContinuous continuous time ser ies objects as output. • *args – A dditional positional arguments • **kwargs – A dditional ke yw ord arguments • add_events ( bool ) _abc_impl = <_abc._abc_data object> _determine_timesteps ( ts_input : TimeSeries | N one = None , dur ation: float | None = N one , num_timest eps: int | N one = None ) → int Deter mine ho w man y time steps to e v ol v e with the giv en input specification P arameters • ts_input ( Optional [ TimeSeries ] ) – TxM or Tx1 time ser ies of in put signals f or this la y er • duration ( Optional [ float ] ) – Duration of the desired ev olution, in seconds. If not pro vided, num_timesteps or the duration of ts_input will be used to determine ev olu- tion time • num_timesteps ( Optional [ int ] ) – Number of e v olution time steps, in units of dt . If not pro vided, duration or the duration of ts_input will be used to deter mine e v olution time R eturn int num_timesteps: N umber of ev olution time steps R eturn type int _evolve_wrapper ( ts_input=N one , duration=N one , num_timesteps=N one , kwar gs_timeseries=N one , recor d: bool = F alse , *ar gs , **kwar gs ) → T uple[TimeSeries, Dict, Dict] W rap a call to evolve() to update the inter nal time-steps count See evolve() f or calling syntax. P arameters record ( bool ) R eturn type T uple [ TimeSeries , Dict , Dict ] _force_set_attributes (bool) If True , do not sanity-c heck attributes when setting. _gen_time_trace ( t_start : float , num_timest eps: int ) → ndar ra y Generate a time trace starting at t_start , of length num_timesteps with time step dt P arameters • t_start ( float ) – Start time, in seconds • num_timesteps ( int ) – Number of time s teps to g enerate, in units of dt 518 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 R eturn ndarra y Generated time trace R eturn type ndarr ay _gen_timeseries ( output : ndarr ay , **kw args ) → T imeSer ies W rap a clock ed / rasterised output ar ra y into a TimeSeries object Output TimeSeries will be of the appropr iate subclass, and will be named nicel y . P arameters • output ( np.ndarray ) – The cloc k ed or raster ised output data (T, N) • **kwargs – A dditional ke yw ord arguments to TimeSeries R eturns The data in output wrapped into a TimeSeries object R eturn type TimeSeries _gen_tscontinuous ( output : ndarr ay , dt : float | N one = N one , t_start : float | None = N one , name: str | N one = N one , periodic: bool = F alse , interp_kind: str = ' pr evious ' ) → TSContinuous W rap a rasterised output ar ra y as a TSContinuous object to present as output f or this module Output TSContinuous s will be named nicel y , with cor rect s tar t times, durations, etc. Sev eral attr ibutes of the TSContinuous object can be set as arguments here. P arameters • output ( np.ndarray ) – A cloc k ed time ser ies data ar ra y (T, N) • dt ( Optional [ float ] ) – The time-step of the cloc ked ar ra y output . If not pro vided, the module dt will be used • t_start ( Optional [ float ] ) – The star t time of the output TSContinuous object, in seconds. If not pro vided, the module time bef ore ev olution will be used • name ( Optional [ str ] ) – The desired name of the TSContinuous object. If not pro vided, the object will be named nicel y according to the module name • periodic ( bool ) – Flag to indicate whether the retur ned TSContinuous should be per i- odic. Def ault: False , the TSContinuous will not be per iodic • interp_kind ( str ) – The sty le of inter polation to appl y to the retur ned TSContinuous object. Def ault: "previous" R eturns The wrapped output data as a TSContinuous object R eturn type TSContinuous _gen_tsevent ( output : ndarr ay , dt : float | N one = None , t_s tar t : float | None = N one , name: str | N one = N one , periodic: bool = F alse , num_c hannels: int | N one = None , spikes_at_bin_s tar t : bool = F alse ) → TSEv ent W rap a rasterised output ar ra y as a TSEvent object to present as output f or this module Output TSEvent s will be named nicel y , with cor rect start timesm durations, etc. Se v eral attr ibutes of the TSEvent object can be set as arguments here. P arameters 48.2. Base classes 519 Rockpool, Release 3.0.3 R eturn type tuple property full_name: str The full name of this module (class plus module name) T ype str property input_type: type The TimeSeries class accepted b y this module T ype type modules () → Dict R etur n a dictionar y of all sub-modules of this module R eturns A dictionar y containing all sub-modules. Each item will be named with the sub-module name. R eturn type dict property name: str The name of this module, or an empty string if None T ype str property output_type: type The TimeSeries class retur ned b y this module T ype type parameters ( f amily : str | T uple | List = N one ) → Dict R etur n a nested dictionary of module and submodule Parameters Use this method to inspect the P arameters from this and all submodules. The optional ar gument family allo w s y ou to search f or Parameters in a particular famil y — f or e xample "weights" f or all w eights of this module and nested submodules. Although the family argument is an arbitrary str ing, reasonable choises are "weights" , "taus" f or time constants, "biases" f or biases. . . Examples Obtain a dictionar y of all P arameters f or this module (including submodules): >>> mod . parameters() dict{ ... } Obtain a dictionar y of P arameters from a par ticular famil y: >>> mod . parameters( "weights" ) dict{ ... } P arameters family ( str ) – The famil y of P arameters to search f or . Def ault: None ; retur n all parameters. 526 Chapter 48. F ull API summar y for Rockpool Rockpool, Release 3.0.3 R eturns A nested dictionary of Parameters of this module and all submodules R eturn type dict reset_all () → N one R eset the inter nal state and time of this module and all sub-modules R eturn type N one reset_parameters () R eset all parameters in this module R eturns The updated module is retur ned f or compatibility with the functional API R eturn type Module reset_state () → ModuleBase R eset the state of this module R eturns The updated module is retur ned f or compatibility with the functional API R eturn type Module reset_time () → N one R eset the inter nal time of this module and all sub-modules to zero R eturn type N one set_attributes ( new_attributes: dict ) → ModuleBase Set the attr ibutes and sub-module attributes from a dictionar y This method can be used with the dictionar y retur ned from module e v olution to set the ne w state of the module. It can also be used to set multiple parameters of a module and submodules. Examples Use the functional API to e v ol v e, obtain new s tates, and set those states: >>> _, new_state, _ = mod( input ) >>> mod = mod . set_attributes(new_state) Obtain a parameter dictionar y , modify it, then set the parameters bac k: >>> params = mod . parameters() >>> params[ ' w_input ' ] *= 0. >>> mod . set_attributes(params) P arameters new_attributes ( dict ) – A nested dictionary containing parameters of this module and sub-modules. 48.2. Base classes 527 [Document text truncated for crawler view.]