Neurons and simple neural networkspynest–nest simulator interface
The Neural Simulation tool (nest:www.nest-initiative.org) is designed for large heterogeneous networks that simulate point neurons. It is open source software released under the GPL license. The simulator has a Python interface [4]. Figure 1 illustrates the interaction between the user's mock script (mysimulation.py) and the Nest emulator. [2] contains a technical detailed description of the implementation of the interface, part of this article is based on this reference. The emulation kernel is written using C + + for maximum performance simulation.
You can use pynest interactively from the Python prompt or from within Ipython. This is very useful when you are exploring Pynest, trying to learn a new feature or debugging a routine. Once you exit Explore mode, you'll find that it can save a lot of time writing simulations in a text file. These can be run from the command line or from a Python or Ipython prompt, in turn.
Whether it's interactive, semi-interactive, or purely executed scripts, you first need to import the nest functionality into the Python interpreter.
Import Nest
As with all the other modules in Python, you can provide the available functions.
Dir (Nest)
One such command is nest. Models? , it returns a list of all available models that you can use. If you want to get more information about a particular command, you can use Ipython's standard help system.
Nest. Models?
This will return the Help text (docstring), explaining the use of this particular function. There is also a help system in the nest. You can use Nest.helpdesk () to open the help page in a browser, and you can use Nest.help (object) to get help pages for a specific object.
Creating nodes
The neural network in Nest consists of two basic element types: nodes and connections. A node is a neuron, device, or sub-network. Devices are used to stimulate neurons or to record from them. Nodes can be arranged in a sub-network to build layered networks, example layers, columns, and areas-we'll discuss them in a later course. Now we will work in the default subnet that appears when we start Nest, which is called the root node.
First, the root network is empty. Creates a new node using the Create command, which takes the parameter as the model name for the desired node type, and optionally the number of nodes to create and the initialization parameters. This function returns a list of the handles for the new node that you can assign to variables for later use. These handles are integers, called IDs. Many pynest functions expect or return a list of IDs (see section 8th). Therefore, it is easy to apply a function to a large node set using a single function call.
After importing the nest and the Pylab interface to Matplotlib [[3]] (# # #), we'll use it to display the results, and we can start creating the nodes. As a first example, we will create a neuron of type iaf_psc_alpha. The neuron is an integrated flame neuron with an alpha-shaped synaptic current. This function returns a list of all the created neurons ' IDs, in this case there is only one, and we store them in a variable named neuron.
import pylab
import nest
neuron = nest.Create("iaf_psc_alpha")
We can now use the ID to access the properties of this neuron. The properties of a node in nest are usually accessed through a Python dictionary of key-value pairs in the form of {Key:value}. To see the properties of a neuron, you can ask its state.
Nest. GetStatus (Neuron)
This will print out the appropriate dictionary in the Python console. Many of these properties are unrelated to the dynamics of neurons. To learn about interesting properties, see the documentation for your model from the Help desk. If you already know the property you are interested in, you can specify a key or a list of keys as an optional parameter for GetStatus:
nest.GetStatus(neuron, "I_e")
nest.GetStatus(neuron, ["V_reset", "V_th"])
In the first case, we query the value of the constant background current i_e; The result is given in the form of a tuple that contains an element. In the second case, we query the values of the reset potential and neuron thresholds and receive the results as nested tuples. If GetStatus is called for the node list, the dimension of the outer tuple is the length of the node list, and the dimension of the inner tuple is the number of the specified key.
To modify the properties in the dictionary, we use SetStatus. In the following example, the background current is set to 376.0pA, which is the value that causes the neuron to periodically spike.
Nest. SetStatus (Neuron, {"I_e": 376.0})
Note that we can set multiple properties at the same time by giving multiple comma-separated key: value pairs in the dictionary. Also note that nest is type sensitive-if a particular property is of type double, then you do need to explicitly write the decimal point:
Nest. SetStatus (Neuron, {"I_e": 376})
Will cause an error. This is handy to protect us from integer division errors, which are difficult to find.
Next we create a multimeter, which is a device that we can use to record the neuron's membrane voltage changes over time. We set the time of its properties so that it will also record the time point at which the membrane voltage is sampled. The Record_from property requires a list of the names of the variables we want to record. The variables exposed to the multimeter vary depending on the model. For a particular model, you can check the name of the exposed variable by looking at the properties of the neuron to be logged.
multimeter = nest.Create("multimeter")
nest.SetStatus(multimeter, {"withtime":True, "record_from":["V_m"]})
We now create a spike detector, another device that records the spike events generated by neurons. We use the Optional keyword parameter params to set its properties. This is an alternate way to use SetStatus. The Withgid property indicates whether the spike detector records the source ID (that is, the ID of our neurons) from which it receives events.
spikedetector = nest.Create("spike_detector",
params={"withgid": True, "withtime": True})
A short description of the naming: here we call neuron neurons, multimeter multimeter and so on. Of course, you can assign the node you create to any variable name you like, but it's easier to read if you choose to reflect the name of the concept in the simulation.
Connecting nodes with default connections
Now that we know how to create a single node, we can start connecting them to form a small network.
nest.Connect(multimeter, neuron)
nest.Connect(neuron, spikedetector)
The order of the specified connection parameters reflects the flow of the event: if the neuron spikes, it sends an event to the spike detector. Instead, the multimeter periodically sends a request to the neuron to ask for its membrane potential at that point in time. This can be thought of as the perfect electrode stuck in a neuron.
Now that we are connected to the network, we can start the simulation. We must inform the simulation kernel when the simulation runs. Here we choose 1000ms.
Nest. Simulate (1000.0)
Congratulations, you just simulated your first network in nest!
Extracting and plotting data from devices
After the simulation is complete, we can obtain the data of the multimeter record.
dmm = nest.GetStatus(multimeter)[0]
Vms = dmm["events"]["V_m"]
ts = dmm["events"]["times"]
In the first row, we get a list of the state dictionaries for all the query nodes. Here, the variable multimeter is just the ID of a node, so the returned list contains only one dictionary. We index it to extract the first element of the list (hence the last [0]). This type of operation occurs very frequently when using pynest, because most functions are designed to receive and return lists instead of individual values. This is intended to make it easier for the project group (typically setting up neural network simulations) to operate.
The dictionary contains an entry named events that holds the data for the record. It is itself a dictionary in which entries v_m and time are stored in VMS and TS, respectively, in the second and third rows. If you can't imagine the dictionary and what you extracted from it, first try printing the DMM to the screen to better understand its structure, and then extract the dictionary events in the next step.
Now we're going to show the data in a diagram. To do this, we use Pylab.
import pylab
pylab.figure(1)
pylab.plot(ts, Vms)
The second line opens a number (number 1), the third row clears the window, and the fourth line actually produces the diagram. You haven't seen it since we haven't used Pylab.show (). Before we do this, we have similarly obtained and displayed spikes from the spike detector.
dSD = nest.GetStatus(spikedetector,keys="events")[0]
evs = dSD["senders"]
ts = dSD["times"]
pylab.figure(2)
pylab.plot(ts, evs, ".")
pylab.show()
Here, we extract the event more succinctly by using the Optional keyword parameter key of GetStatus. This uses key events rather than the entire state dictionary to extract the dictionary elements. The output should be shown in 2. If you want to execute it as a script, simply paste all the lines into a text file named one-neuron.py. You can then run it from the command line by adding a file name before Python, or by adding a run prefix to the Python or Ipython prompt.
Information on multiple neurons can be collected on a single multimeter. This really complicates the retrieval of information: the data for each neuron in n neurons is stored and returned in an interleaved fashion. Fortunately, Python provides us with a handy array operation to easily split the data: Use a single step (sometimes called a stride) to slice the array. To explain this, you have to adjust the model created in the previous section. Save your code with a new name, and you'll use this code in the next section. Create an extra neuron with the background current given a different value:
neuron2 = nest.Create("iaf_neuron")
nest.SetStatus(neuron2 , {"I_e": 370.0})
Now connect the newly created neuron to the multimeter:
Nest. Connect (Multimeter, Neuron2)
Run the simulation and draw the results, they don't look right. To solve this problem, you must draw two neuron trajectories separately. Used to replace the code that extracts events from the multimeter.
pylab.figure(2)
Vms1 = dmm["events"]["V_m"][::2] # start at index 0: till the end: each second entry
ts1 = dmm["events"]["times"][::2]
pylab.plot(ts1, Vms1)
Vms2 = dmm["events"]["V_m"][1::2] # start at index 1: till the end: each second entry
ts2 = dmm["events"]["times"][1::2]
pylab.plot(ts2, Vms2)
Connecting nodes with specific connections
A commonly used neural activity model is the Poisson process. We now modify the previous example so that neurons receive 2 of the Poisson peak trains, one of which is excitatory and the other is suppressed. Therefore, we need a new device poisson_generator. After creating the neuron, we create the two generators and set their rate to 80000Hz and 15000Hz, respectively.
noise_ex = nest.Create("poisson_generator")
noise_in = nest.Create("poisson_generator")
nest.SetStatus(noise_ex, {"rate": 80000.0})
nest.SetStatus(noise_in, {"rate": 15000.0})
In addition, the constant input current should be set to 0:
Nest. SetStatus (Neuron, {"I_e": 0.0})
Each event of the excitation generator should produce a 1.2pA amplitude of synaptic current, about -2.0pa of the suppression event. The synaptic weights can be defined in the dictionary, which is passed to the Connect function using the keyword SYN_SPEC (synaptic specification). In general, you can specify all the parameters of the synapse in the synaptic dictionary, such as "weight", "delay", the synaptic model ("model"), and the parameters specific to the synaptic model.
syn_dict_ex = {"weight": 1.2}
syn_dict_in = {"weight": -2.0}
nest.Connect([noise[0]], neuron, syn_spec=syn_dict_ex)
nest.Connect([noise[1]], neuron, syn_spec=syn_dict_in)
The rest of the code remains the same as before. You should see the membrane potential shown in 3.
In the next part of the introduction (part 2nd: Neuron population), we will see more ways to connect many neurons at once.
Connected neurons
No additional magic is attached to the neuron. To prove this, we start with a primitive example of a neuron with constant input current and add a second neuron.
import pylab
import nest
neuron1 = nest.Create("iaf_psc_alpha")
nest.SetStatus(neuron1, {"I_e": 376.0})
neuron2 = nest.Create("iaf_psc_alpha")
multimeter = nest.Create("multimeter")
nest.SetStatus(multimeter, {"withtime":True, "record_from":["V_m"]})
We now connect neuron 1 to neuron 2 and record the membrane potential from neuron 2 so that we can observe the synaptic potential caused by the spike in neuron 1.
nest.Connect(neuron1, neuron2, syn_spec = {"weight":20.0})
nest.Connect(multimeter, neuron2)
The default delay of 1ms is used here. If you specify a delay beyond the weight, you can use the following shortcut:
Nest. Connect (Neuron1, Neuron2, syn_spec={"weight": +, "delay": 1.0})
If you simulate the network as before and draw the membrane potential, you should be able to see the synaptic potential of neuron 2 caused by the spike in Neuron 1, as shown in 4.
Command overview
These are the features we present in this handout as examples; The following sections of this introduction will add more content.
Getting Information about NEST
Models(mtype="all", sel=None)
:
Returns a list of all available models (nodes and synapses). Use Mtype = "Node" To view only the node model, mtype = "synapse" To view only the synaptic model. The SEL can be a string that filters the list of results and returns only the model that contains it.
helpdesk(browser="firefox")
:
Opens the Nest Document page in a given browser.
help(obj=None,pager="less")
:
Opens the help page for the given object.
Nodes
Create(model, n=1, params=None)
:
Creates an instance of the N type model in the current sub-network. The parameters of the new node can be given as parameters (a single dictionary or a list of dictionaries of size n). If omitted, the default value of the model is used.
GetStatus(nodes, keys=None)
:
Returns a list of parameter dictionaries for the given node list. If the key is given, a list of values is returned. The key can also be a list, in which case the returned list contains a list of values.
SetStatus(nodes, params, val=None)
:
Sets the parameter of the given node to a parameter, which can be a single dictionary or a list of dictionaries that are the same size as the node. If given Val, the params must be the name of the property, which is set to Val on the node. Val can be a single value, or it can be a list of the same size as the node.
Connections
This is an abbreviated version of the Connect function document, see the full version of Nest and the online Help for connection management for an introduction and a working example.
Connection (Pre,post,conn_spec = None,syn_spec = None,model = none):
Connect the pre neuron to the post neuron. The nerves in the pre and post are connected using the specified connection (default is "One_to_one") and the synaptic type (default is "Static_synapse"). The details depend on the connection rules. Note: Connect does not traverse the subnet, it only connects the nodes that are explicitly specified. Pre-synaptic neurons, given in the form of GID lists-synaptic neurons, given as GID list Conn_spec-Specifies the name or dictionary of the connection rule, see below Syn_spec-Specify the name or dictionary of the synapse, see below
Connection
A connection can be specified as a string containing the connection rule name (default: "One_to_one") or a dictionary of rules and rule-specific parameters (such as "Indegree"), which must be given a connection rule. In addition, the dictionary can also contain switches that allow self-joins ("Autapses", Default: TRUE) and a pair of neurons ("multapses", Default: TRUE) between multiple connections.
Synaptic
You can insert a synaptic model and its attributes into a string that describes a synaptic model (the synaptic model is listed in synapsedict) or as a dictionary insert, as described below. If no synaptic model is specified, the default model "Static_synapse" is used. The available keys in the synaptic dictionary are "model", "weight", "delay", "receptor type" and the parameters specific to the selected synaptic model. All parameters are optional and, if not specified, the default values determined by the current synaptic model are used. The model determines the type of synaptic, taken from a predefined synaptic type in nest or a synapse created manually by Copymodel (). All other parameters can be scalar or distributed. In the case of scalar parameters, all keys will be doubled except for "receptor_type" that must be initialized with integers. Distributed parameters are initialized with a specified distribution ("distribution", such as "normal") and another dictionary that distributes specific parameters such as "Mu" and "sigma".
Analog control
Analog (T):
Analog network t milliseconds.
Pynest--part1:neurons and simple neural networks