这是国庆前导师让了解的脉冲神经仿真平台NEST的部分介绍手册的翻译和整理,记录一下留个备份,主要内容可以通过查看文档中的链接索引到官网。
如需要手册代码合辑及例程ipynb文件,请查看NEST脉冲神经网络仿真手册例程合辑
在本讲义中,我们介绍了使用PyNEST模拟神经元网络的第一步。阅读完本材料后,您将知道如何:
开始PyNEST创建神经元和刺激/记录设备查询并设置其参数将它们彼此连接或连接到设备模拟网络从记录设备中提取数据有关使用PyNEST的更多信息,请参见本底漆的其他部分:
第2部分:神经元种群第3部分:通过突触连接网络第4部分:拓扑结构化网络可以在Example Networks上找到更高级的示例,或者在子目录中查看NEST安装的源目录pynest/examples/。
图1 Python界面图。Python解释器将NEST作为模块导入,并动态加载NEST仿真器内核(pynestkernel.so)。核心功能在中定义hl_api.py。用户(mysimulation.py)的模拟脚本使用此高级API中定义的功能。这些函数以SLI(模拟语言解释器)生成代码,SLI是NEST解释器的本地语言。该解释器进而控制NEST仿真内核。
神经仿真工具(NEST:www.nest-initiative.org)1 设计用于仿真大型的点神经元异构网络。它是根据GPL许可发布的开源软件。该模拟器带有Python 2的接口。图1 说明了用户的模拟脚本(mysimulation.py)和NEST模拟器之间的交互。埃普勒等。图3 包含对该接口实现的技术上的详细描述,本文的某些部分均基于此参考。仿真内核是用C ++编写的,以获得最高的仿真性能。
您可以从Python提示或在ipython中交互使用PyNEST。当您探索PyNEST,尝试学习新功能或调试例程时,这非常有用。退出探索模式后,您会发现它节省了大量时间,可以在文本文件中编写模拟。这些可以依次从命令行或Python或ipython提示符下运行。
无论是交互式,半交互式还是纯粹执行脚本,首先要做的就是将NEST的功能导入Python解释器中。
import nest但是,应注意,在导入Nest *之前,*必须先导入某些外部软件包。这些包括scikit-learn 和SciPy。
from sklearn.svm import LinearSVC from scipy.special import erf import nest与Python的所有其他模块一样,可以提示可用的功能。
dir(nest)这样的命令之一是nest.Models()或在ipython中nest.Models?,它将返回您可以使用的所有可用模型的列表。如果要获取有关特定命令的更多信息,可以使用Python的标准帮助系统。
这将返回帮助文本(docstring),解释该特定功能的用法。NEST中也有一个帮助系统。您可以使用在浏览器中打开帮助页面,nest.helpdesk()并使用可以获取特定对象的帮助页面nest.help(object)。
NEST中的神经网络由两种基本元素类型组成:节点和连接。节点是神经元,设备或子网。设备用于刺激神经元或从中进行记录。可以在子网中安排节点,以构建分层网络,例如层,列和区域-我们将在本课程的后面部分进行介绍。现在,我们将在启动NEST时存在的默认子网中工作。root node
首先,根子网为空。使用命令创建新节点,该命令Create将所需节点类型的模型名称以及要创建的节点数和初始化参数作为参数。该函数返回到新节点的句柄列表,您可以将其分配给变量以供以后使用。这些句柄是整数,称为ids。许多PyNEST函数期望或返回ID列表(请参阅命令概述)。因此,很容易通过单个函数调用将函数应用于大型节点集。
导入NEST以及Matplotlib 4的Pylab接口后,我们将使用该接口来显示结果,我们可以开始创建节点。作为第一个示例,我们将创建一个类型为的神经元 iaf_psc_alpha。该神经元是具有α形突触后电流的整合并发射神经元。该函数返回所有已创建神经元ID的列表,在这种情况下只有一个,我们将其存储在名为的变量中neuron。
import pylab import nest neuron = nest.Create("iaf_psc_alpha")现在,我们可以使用id来访问此神经元的属性。NEST中节点的属性通常通过形式为的键-值对的Python字典进行访问。为了查看神经元具有哪些属性,您可以询问它的状态。{key: value}
nest.GetStatus(neuron)这将在Python控制台中打印出相应的字典。这些特性中的许多特性与神经元的动力学无关。要找出有趣的属性,请通过服务台查看模型的文档。如果您已经知道您感兴趣的属性,则可以指定一个键或键列表作为的可选参数GetStatus:
nest.GetStatus(neuron, "I_e") nest.GetStatus(neuron, ["V_reset", "V_th"])在第一种情况下,我们查询恒定背景电流的值 I_e; 结果以带有一个元素的元组给出。在第二种情况下,我们查询复位电位和神经元阈值的值,并将结果作为嵌套元组接收。如果GetStatus为节点列表调用if ,则外部元组的维数是节点列表的长度,而内部元组的维数是指定的键数。
要修改字典中的属性,请使用SetStatus。在下面的示例中,背景电流设置为376.0pA,该值导致神经元周期性地尖峰。
nest.SetStatus(neuron, {"I_e": 376.0})请注意,通过在字典中提供多个逗号分隔的key:value对,我们可以同时设置多个属性。另请注意,NEST是类型敏感的-如果特定属性的类型是 double,则您需要显式编写小数点:
nest.SetStatus(neuron, {"I_e": 376})将导致错误。这方便地保护我们免受整数除法错误的影响,这些错误很难被发现。
接下来,我们创建一个设备multimeter,该设备可用于记录一段时间内神经元的膜电压。我们设置其属性withtime ,以便它还将记录采样膜电压的时间点。该属性record_from需要我们要记录的变量名称的列表。万用表暴露的变量因模型而异。对于特定模型,可以通过查看神经元的属性来检查暴露变量的名称recordables。
multimeter = nest.Create("multimeter") nest.SetStatus(multimeter, {"withtime":True, "record_from":["V_m"]})现在spikedetector,我们创建一个,用于记录神经元产生的尖峰事件的另一种设备。我们使用可选的关键字参数params来设置其属性。这是使用的替代方法SetStatus。该属性withgid指示尖峰检测器是否要记录其从中接收事件的源ID(即我们的神经元的ID)。
spikedetector = nest.Create("spike_detector", params={"withgid": True, "withtime": True})关于命名的简短说明:在这里,我们称神经元neuron,万用表multimeter等。当然,您可以将创建的节点分配给您喜欢的任何变量名称,但是如果您选择反映仿真概念的名称,则脚本更易于阅读。
现在我们知道了如何创建单个节点,我们可以开始将它们连接起来以形成一个小型网络。
nest.Connect(multimeter, neuron) nest.Connect(neuron, spikedetector)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CN8kRRTw-1602122881109)(https://nest-simulator.readthedocs.io/en/nest-2.20.1/_images/vm_one_neuron.pdf.png)]
图2 具有恒定输入电流的集成并发射神经元的膜电位。
图3 神经元的尖峰。
Connect指定参数的顺序反映了事件的流程:如果神经元出现尖峰,它将向尖峰检测器发送事件。相反,万用表会在该时间点定期向神经元发送请求以询问其膜电位。这可以看作是粘在神经元中的理想电极。
现在我们已连接网络,可以开始仿真了。我们必须告知仿真内核仿真要运行多长时间。这里我们选择1000ms。
nest.Simulate(1000.0)恭喜,您刚刚在NEST中模拟了您的第一个网络!
仿真完成后,我们可以获得万用表记录的数据。
dmm = nest.GetStatus(multimeter)[0] Vms = dmm["events"]["V_m"] ts = dmm["events"]["times"]在第一行中,我们获取所有查询节点的状态字典列表。在这里,变量multimeter是只有一个节点的ID,因此返回的列表仅包含一个字典。我们通过索引来提取此列表的第一个元素(因此[0]在末尾)。当使用PyNEST时,这种类型的操作非常频繁地发生,因为大多数函数被设计为接受和返回列表,而不是单个值。这是为了使对项目组的操作(设置神经元网络模拟时的通常情况)更加方便。
该词典包含一个名为的条目events,用于保存记录的数据。它本身与所述条目的字典V_m和 times,我们分别存储Vms和ts分别在第二和第三行。如果您在想像词典的字典以及从中提取的内容时遇到困难,请首先尝试打印dmm到屏幕上,以使您更好地了解其结构,然后在下一步中提取字典events,依此类推。
现在我们准备在图中显示数据。为此,我们使用pylab。
import pylab pylab.figure(1) pylab.plot(ts, Vms)第二行打开一个数字(数字为1),第三行实际生成该图。您尚未看到它,因为我们还没有使用过pylab.show()。在此之前,我们类似地进行操作,以获取并显示来自尖峰检测器的尖峰。
dSD = nest.GetStatus(spikedetector,keys="events")[0] evs = dSD["senders"] ts = dSD["times"] pylab.figure(2) pylab.plot(ts, evs, ".") pylab.show()在这里,我们通过使用可选的关键字参数keys来更简洁地提取事件GetStatus。这将使用键events而不是整个状态字典提取字典元素。输出应如图2和图3所示。如果要将此作为脚本执行,只需将所有行粘贴到名为的文本文件中one-neuron.py。然后,您可以在命令行中通过在文件名前面加上python,或者在Python或ipython提示符下通过在文件前面加上前缀来运行它run。
可以在一个万用表上收集多个神经元的信息。这确实使信息检索变得复杂:n个神经元中每个神经元的数据将以交错方式存储和返回。幸运的是,Python为我们提供了一个方便的数组操作,可轻松拆分数据:使用一个步骤(有时称为跨步)对数组进行切片。为了解释这一点,您必须调整上一部分中创建的模型。将代码保存为新名称,在下一部分中,您还将使用此代码。创建一个额外的神经元,其背景电流具有不同的值:
neuron2 = nest.Create("iaf_psc_alpha") nest.SetStatus(neuron2 , {"I_e": 370.0})现在将这个新创建的神经元连接到万用表:
nest.Connect(multimeter, neuron2)运行模拟并绘制结果,它们看起来不正确。要解决此问题,必须分别绘制两个神经元迹线。用multimeter以下行替换从中提取事件的代码。
pylab.figure(3) 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)可以在http://docs.scipy.org/doc/numpy-1.10.0/reference/arrays.indexing.html上找到更多信息 。
神经活动的常用模型是泊松过程。现在,我们改编前面的示例,以使神经元接收2个泊松尖峰序列,一个是兴奋性的,另一个是抑制性的。因此,我们需要一个新设备,即poisson_generator。创建神经元后,我们创建这两个生成器并将它们的频率分别设置为80000Hz和15000Hz。
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})此外,恒定输入电流应设置为0:
nest.SetStatus(neuron, {"I_e": 0.0})兴奋性发生器的每个事件应产生1.2pA振幅的突触后电流,-2.0pA的抑制事件。可以在字典中定义突触权重,然后Connect使用关键字syn_spec(突触规范)将其传递给 函数。一般而言确定突触所有参数可以在突触字典来指定,例如"weight", "delay",突触模型("model")和参数特定于突触模型。
syn_dict_ex = {"weight": 1.2} syn_dict_in = {"weight": -2.0} nest.Connect(noise_ex, neuron, syn_spec=syn_dict_ex) nest.Connect(noise_in, neuron, syn_spec=syn_dict_in)图4 以Poisson噪声为输入的积分并发射神经元的膜电位。
图5 带有噪声的神经元的尖峰。
其余代码保持不变。您应该看到如图4和图5 所示的膜电位。
在引言的下一部分(第2部分:神经元种群)中,我们将研究更多一次连接多个神经元的方法。
图6 Neuron1的尖峰诱发的Neuron2的突触后电位
连接神经元没有其他魔术。为了证明这一点,我们从具有恒定输入电流的一个神经元的原始示例开始,然后添加第二个神经元。
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"]}现在我们连接neuron1到neuron2并记录膜电位,neuron2这样我们就可以观察到尖峰引起的突触后电位neuron1。
nest.Connect(neuron1, neuron2, syn_spec = {"weight":20.0}) nest.Connect(multimeter, neuron2)此处使用默认延迟1ms。如果除了重量之外还指定了延迟,则可以使用以下快捷方式:
nest.Connect(neuron1, neuron2, syn_spec={"weight":20, "delay":1.0})如果您模拟网络和以前绘制的膜电位,则应看到的突触后电位neuron2通过的尖峰引起neuron1在图6中。
这些是我们在本讲义中为示例介绍的功能。本简介的以下部分将添加更多内容。
请参阅“ 获得帮助”部分
Create(model, n=1, params=None)
在当前子网中创建n类型的实例model。可以将新节点的参数指定为 params(单个字典,或具有size的字典列表n)。如果省略,model则使用的默认值。
GetStatus(nodes, keys=None)
返回给定列表的参数字典列表 nodes。如果keys给出,则返回值列表。keys也可以是列表,在这种情况下,返回的列表包含值列表。
SetStatus(nodes, params, val=None)
将给定的参数设置nodes为params,可以是单个字典,也可以是大小与相同的字典列表nodes。如果val指定,params则必须是属性的名称,该属性在val上设置为nodes。val可以是单个值,也可以是与相同大小的列表nodes。
这是该Connect 功能文档的缩写版,请参阅NEST的联机帮助以获取完整版和“ 连接管理 ”以获取介绍和工作示例。
Connect(pre,post,conn_spec = None,syn_spec = None,model = None)连接前神经元到后神经元。pre和post中的神经元使用指定的连接性(“one_to_one”默认)和突触类型("static_synapse"默认)连接。详细信息取决于连接规则。注意:Connect不会在子网上进行迭代,它仅连接显式指定的节点。pre-突触前神经元,作为GID列表给出post-突触前神经元,作为GID列表给出conn_spec-名称或字典指定连通性规则,请参见下文syn_spec`-名称或字典指定突触,请参见下文连接性可以指定为包含连接性规则名称的字符串(默认值"one_to_one":),也可以指定为规则和特定于规则的参数(例如"indegree")的字典。另外开关允许自连接("autapses",缺省True)和一对神经元之间的多个连接("multapses",缺省True)可以包含在字典中。
突触模型及其属性可以作为描述一个突触模型的字符串(突触模型在突触中列出)插入,也可以作为字典插入,如下所述。如果未指定突触模型,"static_synapse"则将使用默认模型。在突触词典可用键是"model","weight", "delay","receptor_type"和参数特定于所选择的突触模型。所有参数都是可选的,如果未指定,将使用由当前突触模型确定的默认值。"model" 从NEST中的预定义突触类型或通过手动创建的突触中确定突触类型CopyModel()。所有其他参数可以是标量或分布。对于标量参数,除"receptor_type"必须使用整数初始化。分布式参数使用另一个字典初始化,该字典指定了分布("distribution",例如"normal")和特定于分布的参数(例如"mu"和"sigma")。
Simulate(t)
模拟网络数t毫秒。
在本讲义中,我们着眼于创建和参数化的批次 neurons,并将其连接起来。阅读完本材料后,您将知道如何:
创建具有特定参数的神经元群体在创建之前设置模型参数使用自定义参数定义模型创建后随机化参数在人群之间建立随机联系设置设备以启动,停止并将数据保存到文件重置模拟有关使用PyNEST的更多信息,请参见本底漆的其他部分:
第1部分:神经元和简单神经网络第3部分:通过突触连接网络第4部分:拓扑结构化网络可以在Example Networks上找到更高级的示例,或者在子目录中查看NEST安装的源目录pynest/examples/。
在上一个讲义中,我们介绍了函数 。它的强制参数是模型名称,该名称确定要创建的节点应为哪种类型。它的两个可选参数是,它提供要创建的节点数(默认值:1)和,它是一个字典,提供应初始化节点的参数。因此,创建一批参数相同的神经元的最基本方法是利用以下可选参数:Create(model, n=1, params=None)``n``params``Create()
ndict = {"I_e": 200.0, "tau_m": 20.0} neuronpop = nest.Create("iaf_psc_alpha", 100, params=ndict)变量neuronpop是创建的神经元的所有id的元组。
在创建时对神经元进行参数化比SetStatus()在创建后使用神经元更为有效 ,因此请尽可能尝试执行此操作。
我们还可以在创建之前设置神经元模型的参数,这使我们可以在许多情况下更简洁地定义仿真。如果要生成许多批次的神经元,则更方便的是设置模型的默认值,这样从该模型创建的所有神经元将自动具有相同的参数。可以使用查询模型的默认值GetDefaults(model),并使用进行设置,其中是包含所需参数/值对的字典。例如:SetDefaults(model, params)``params
ndict = {"I_e": 200.0, "tau_m": 20.0} nest.SetDefaults("iaf_psc_alpha", ndict) neuronpop1 = nest.Create("iaf_psc_alpha", 100) neuronpop2 = nest.Create("iaf_psc_alpha", 100) neuronpop3 = nest.Create("iaf_psc_alpha", 100)现在,这三个总体具有相同的参数化,所有参数的常规模型默认值除外,除了I_e和tau_m,它们具有在字典中指定的值ndict。
如果成批的神经元应具有相同的模型但使用不同的参数,则使用 具有自己的默认参数的神经元模型的自定义版本非常方便。此功能是有效的工具,可帮助您编写更清晰的仿真脚本,因为您可以使用模型的名称来指示其在仿真中扮演的角色。使用以下两个步骤来设置定制模型:CopyModel(existing, new, params=None)``SetDefaults()
edict = {"I_e": 200.0, "tau_m": 20.0} nest.CopyModel("iaf_psc_alpha", "exc_iaf_psc_alpha") nest.SetDefaults("exc_iaf_psc_alpha", edict)或一步:
idict = {"I_e": 300.0} nest.CopyModel("iaf_psc_alpha", "inh_iaf_psc_alpha", params=idict)无论哪种方式,新定义的模型现在都可以用于生成神经元种群,并且也将由函数返回Models()。
epop1 = nest.Create("exc_iaf_psc_alpha", 100) epop2 = nest.Create("exc_iaf_psc_alpha", 100) ipop1 = nest.Create("inh_iaf_psc_alpha", 30) ipop2 = nest.Create("inh_iaf_psc_alpha", 30)也可以使用一组不均匀的参数来创建总体。通常,您将根据实验约束创建完整的参数集,然后一次性创建所有神经元。为此,请提供与创建的神经元(或突触)数量相同长度的字典列表:
parameter_list = [{"I_e": 200.0, "tau_m": 20.0}, {"I_e": 150.0, "tau_m": 30.0}] epop3 = nest.Create("exc_iaf_psc_alpha", 2, parameter_list)在创建时或创建神经元模型时,并非总是能够设置所有参数。一个典型的例子是应该从随机分布中提取一些参数。当然,总有可能遍历整个种群并设置每个种群的状态:
Vth=-55. Vrest=-70. for neuron in epop1: nest.SetStatus([neuron], {"V_m": Vrest+(Vth-Vrest)*numpy.random.rand()})但是,SetStatus()期望有一个节点列表,并且可以为每个节点设置参数,这样效率更高,因此是首选。一种方法是给出一个字典列表,该列表的长度与要参数化的节点数相同,例如使用列表推导:
dVms = [{"V_m": Vrest+(Vth-Vrest)\*numpy.random.rand()} for x in epop1] nest.SetStatus(epop1, dVms)如果只需要对一个参数进行随机化处理,则可以通过传入参数名称和所需值列表来实现更简洁的方式。同样,该列表的大小必须与要参数化的节点数相同:
Vms = Vrest+(Vth-Vrest)\*numpy.random.rand(len(epop1)) nest.SetStatus(epop1, "V_m", Vms)请注意,我们这里的随机数比较宽松。确实,我们必须格外小心,尤其是当我们使用多个线程或在多个计算机上分布时。我们稍后会担心。
在先前的讲义中,使用突触规范连接了两个神经元。在本节中,我们将示例扩展到两个十个神经元的群体。
import pylab import nest pop1 = nest.Create("iaf_psc_alpha", 10) nest.SetStatus(pop1, {"I_e": 376.0}) pop2 = nest.Create("iaf_psc_alpha", 10) multimeter = nest.Create("multimeter", 10) nest.SetStatus(multimeter, {"withtime":True, "record_from":["V_m"]})如果未指定任何连通性模式,则将通过默认规则来连接总体all_to_all。的每个神经元pop1都连接到中的每个神经元pop2,导致102102 连接。
nest.Connect(pop1, pop2, syn_spec={"weight":20.0})另外,神经元可以与one_to_one。这意味着第一个神经元输入pop1连接到第一个神经元输入pop2,第二个连接到第二个,依此类推,总共创建十个连接。
nest.Connect(pop1, pop2, "one_to_one", syn_spec={"weight":20.0, "delay":1.0})最后,使用默认规则连接万用表
nest.Connect(multimeter, pop2)在这里,我们仅使用了非常简单的连接方案。必须在包含键rule和与规则关联的参数的键的字典中定义要求指定其他参数(例如度数或连接概率)的连接模式。请参阅连接管理 以获取有关的用法说明Connect。
在上一个讲义中,我们研究了连接模式 one_to_one和all_to_all。但是,我们经常想看看连接性比全能型更稀疏的网络。在这里,我们介绍了四种连接模式,它们在两个神经元群体之间生成随机连接。
连接规则fixed_indegree允许我们为n目标群体中的每个神经post元与从源群体中随机选择的神经元建立随机连接pre。变量weight和delay可以不指定,在这种情况下,将使用默认的权重和延迟。或者,我们可以在中设置它们syn_spec,以便每个创建的连接具有相同的权重和延迟。这是一个例子:
d = 1.0 Je = 2.0 Ke = 20 Ji = -4.0 Ki = 12 conn_dict_ex = {"rule": "fixed_indegree", "indegree": Ke} conn_dict_in = {"rule": "fixed_indegree", "indegree": Ki} syn_dict_ex = {"delay": d, "weight": Je} syn_dict_in = {"delay": d, "weight": Ji} nest.Connect(epop1, ipop1, conn_dict_ex, syn_dict_ex) nest.Connect(ipop1, epop1, conn_dict_in, syn_dict_in)现在,目标种群中的每个神经元ipop1具有Ke从源种群中选择的epop1具有权重Je和延迟的传入随机连接d,目标种群中的每个神经元 epop1具有Ki从源种群中选择的ipop1具有权重Ji和延迟的传入随机连接d。
连接规则fixed_outdegree以类似的方式工作,从源种群中的每个神经元的目标种群中随机选择n连接(关键字outdegree)。出于效率的考虑,尤其是在以分布式方式进行仿真时,如果可能的话最好使用 。post``pre``fixed_indegree
另一个可用的连接模式是fixed_total_number。在这里, n连接(关键字N)是通过从种群中随机抽取源神经元和从种群中抽取pre目标神经元来创建的post。
选择连接规则时,pairwise_bernoulli将通过遍历所有可能的源-目标对并使用概率p(关键字p)创建每个连接来生成连接。
除了规则的特定参数indegree,outdegree, N和p,所述conn_spec可包含关键字autapses 和multapses(设置为False或True)分别允许或禁止两个神经元之间的自连接和多个连接,。
请注意,所有连接的规则,这是完全合法的在角色同时具有相同的人口pre和post。
有关连接神经元的更多信息,请阅读该Connect功能的文档并查阅连接管理中的指南 。
所有设备都具有基本的计时能力;参数start (默认为0)确定设备活动的开始,参数stop(默认为:∞∞)。这些值是相对于origin(默认值:0)的值得出的。例如,以下示例创建一个poisson_generator仅在100到150ms之间有效的:
pg = nest.Create("poisson_generator") nest.SetStatus(pg, {"start": 100.0, "stop": 150.0})此功能对于设置带有在特定时间开始和停止的刺激的实验协议很有用。
到目前为止,我们已经通过提取的值直接访问了设备记录的数据events。但是,对于更大或更长时间的模拟,我们可能更愿意将数据写入文件以供以后分析。所有记录设备都可以通过参数to_memory(默认值:True), to_file(默认值:)False和to_screen(默认值:)指定存储数据的位置False。以下代码设置了,multimeter以将数据记录到命名文件中:
recdict = {"to_memory" : False, "to_file" : True, "label" : "epop_mp"} mm1 = nest.Create("multimeter", params=recdict)如果未使用label参数为文件指定名称,则NEST将使用设备名称及其ID生成自己的名称。如果模拟是多线程或分布式的,则将创建多个文件,每个进程和/或线程一个。有关如何自定义录制设备的行为和输出格式的更多信息,请阅读的文档RecordingDevice。
我们经常需要重置模拟。例如,如果您正在开发脚本,则可能需要先从ipython控制台运行它 多次,然后才能对其行为感到满意。在这种情况下,使用函数会很有用 ResetKernel()。这将消除您创建的所有节点,您创建的任何自定义模型,并将内部时钟重置为0。
重置的另一个主要用途是需要循环运行仿真时(例如,测试不同的参数设置)。在这种情况下,通常无需丢弃整个网络并创建和连接所有内容,只需重新设置网络参数即可。这里的一个好策略是在环路外创建和连接网络,然后在环路内执行参数化,仿真和数据收集步骤。在这里,ResetNetwork()在每次循环迭代中调用该函数通常会很有帮助 。它将所有节点重置为其默认配置,并擦除记录设备中的数据。
这些是我们在本讲义中为示例引入的新功能。
GetKernelStatus(keys=none)
获取仿真内核的参数。返回值:
参数字典(如果不带参数调用)如果使用单个参数名称调用单个参数值如果使用参数名称列表调用参数值列表设置仿真内核的参数。GetDefaults(model)
返回具有给定默认参数(model由字符串指定)的字典 。
SetDefaults(model, params)
将给定的默认参数设置model为params字典中指定的值。
CopyModel(existing, new, params=None)
new通过复制一个模型来创建模型existing。默认参数可以指定为params,也可以取自 existing。
ResetKernel()
重置仿真内核。这将破坏网络以及使用创建的所有自定义模型CopyModel()。内置模型的参数将重置为其默认值。调用此函数等效于重新启动NEST。
ResetNetwork()
将所有节点和连接重置为其各自模型的默认值。
在本讲义中,我们着眼于使用突触模型连接神经元。阅读完本材料后,您将知道如何:
在创建之前设置突触模型参数使用自定义参数定义突触模型在连接例程中使用突触模型连接后查询突触值在连接期间和之后设置突触值有关使用PyNEST的更多信息,请参见本底漆的其他部分:
第1部分:神经元和简单神经网络第2部分:神经元种群第4部分:拓扑结构化网络可以在Example Networks上找到更高级的示例,或者在子目录中查看NEST安装的源目录pynest/examples/。
NEST提供了各种不同的突触模型。您可以使用命令查看可用的模型,该命令Models(synapses)仅从所有可用模型的列表中选择突触模型。
突触模型可以类似于神经元模型一样进行参数化。您可以使用查找默认参数设置,GetDefaults(model) 并使用进行设置SetDefaults(model,params):
nest.SetDefaults("stdp_synapse",{"tau_plus": 15.0})从该模型生成的任何突触都将具有除以外的所有标准参数tau_plus,该参数将具有上面给出的值。
此外,我们还可以使用来创建自定义的突触模型变体 CopyModel(),正如针对神经元模型所展示的:
nest.CopyModel("stdp_synapse","layer1_stdp_synapse",{"Wmax": 90.0})现在layer1_stdp_synapse将出现在由返回的列表中 Models(),并且可以在任何可以使用内置模型名称的位置使用。
对于大多数突触,可通过GetDefaults()和访问其所有参数 SetDefaults()。突触模型依赖于尖峰时序的可塑性是一个例外,因为它们的动力学是由突触后尖峰序列和突触前尖峰序列驱动的。结果,STDP的抑制窗口的时间常数是突触后神经元的参数。可以设置如下:
nest.Create("iaf_psc_alpha", params={"tau_minus": 30.0})或通过使用本介绍的前两部分中演示的参数化神经元的其他任何方法。
可以在连接例程接受的突触规范字典中设置突触模型以及与突触类型关联的参数。
conn_dict = {"rule": "fixed_indegree", "indegree": K} syn_dict = {"model": "stdp_synapse", "alpha": 1.0} nest.Connect(epop1, epop2, conn_dict, syn_dict)如果没有给出突触模型,则使用该模型进行连接 static_synapse。
突触参数在传递给Connect-function 的突触字典中指定。如果参数设置为标量,则将使用相同的参数绘制所有连接。通过为参数分配字典,可以随机分配参数。词典必须包含用于distribution设置参数目标分布的键(例如normal)。可选地,可以设置与分布关联的参数(例如 mu)。在这里,我们显示了一个示例,其中stdp突触的参数alpha和 参数weight均匀分布。
alpha_min = 0.1 alpha_max = 2. w_min = 0.5 w_max = 5. syn_dict = {"model": "stdp_synapse", "alpha": {"distribution": "uniform", "low": alpha_min, "high": alpha_max}, "weight": {"distribution": "uniform", "low": w_min, "high": w_max}, "delay": 1.0} nest.Connect(epop1, neuron, "all_to_all", syn_dict)可用的分发和关联的参数在“ 连接管理”中进行了描述 ,最常见的是:
发行版按键normalmu, sigmalognormalmu, sigmauniformlow, highuniform_intlow, highbinomialn, pexponentiallambdagammaorder, scalepoissonlambda该函数 返回与给定规范匹配的连接标识符的列表。没有强制性的论点。如果不带任何参数调用它,它将返回网络中的所有连接。如果指定,作为一个或多个节点的列表,该函数将返回该填充中的所有传出连接:GetConnections(source=None, target=None, synapse_model=None)``source
nest.GetConnections(epop1)同样,我们可以通过指定target为一个或多个节点的列表来查找特定目标群体的传入连接:
nest.GetConnections(target=epop2)将返回网络中所有神经元和中的神经元之间的所有连接epop2。最后,可以通过指定给定的突触模型来限制搜索:
nest.GetConnections(synapse_model="stdp_synapse")将返回网络中所有类型为的连接 stdp_synapse。最后两种情况比第一种情况下更慢,作为一个完整的搜索所有连接必须是performed.The参数 source,target并且synapse_model可单独使用,如上所述,或以任何结合:
nest.GetConnections(epop1, epop2, "stdp_synapse")将返回类型epop1为的神经元与其中的神经元的所有连接。注意,所有这些查询命令将仅返回本地连接,即在分布式仿真中的特定MPI流程上表示的本地连接。epop2``stdp_synapse
一旦有了连接数组,就可以使用来从中提取数据 GetStatus()。在最简单的情况下,这将返回一个词典列表,其中包含由查找的每个连接的参数和变量GetConnections。但是,通常我们不希望来自突触的所有信息,而是其中的某些特定部分。例如,如果我们要检查是否已按预期连接网络,则可能只需要检查target每个连接的参数。我们可以使用的可选keys参数来提取这些信息 GetStatus():
conns = nest.GetConnections(epop1, synapse_model="stdp_synapse") targets = nest.GetStatus(conns, "target")targets现在,该变量是target找到的所有连接值的列表。如果我们对多个参数感兴趣,那么 keys还可以列出一个键:
conns = nest.GetConnections(epop1, synapse_model="stdp_synapse") conn_vals = nest.GetStatus(conns, ["target","weight"])变量conn_vals现在是一个列表列表,其中包含找到的每个连接的 target和weight值。
为了适应这些查询突触的方法,建议在已知所有连接的小型网络上尝试使用它们。
随着您的模拟变得越来越复杂,开发一种简洁的编码样式将非常有帮助。首先,这减少了错误的数量,但同时也帮助您调试代码,并使其他人(甚至两周后您自己)也更容易理解它。这里有一些指针,其中有些是一般编程所共有的,而有些则是针对NEST的。另一个有用建议的来源是 PEP-8,很方便,许多编辑器和IDE都可以自动对其进行检查。
模拟中通常有很多数字-我们使用它们来设置神经元模型的参数,定义连接强度,模拟时间等。有时我们想在不同的脚本中使用相同的参数,或者根据其他参数的值来计算一些参数。不建议将数字硬连接到脚本中,因为这容易出错:如果以后决定更改给定参数的值,则必须遍历所有代码并检查是否已更改每个参数的实例 。如果在不同的上下文中使用该值,例如在一个位置设置权重并在另一个位置计算平均突触输入,则特别难以捕获。
更好的方法是将变量设置为参数值,然后在每次需要该值时始终使用变量名称。如果变量的定义遍布整个脚本,那么也很难遵循代码。如果脚本中有一个参数部分,并根据功能(例如,神经元参数,突触参数,刺激参数等)对变量名称进行分组,则查找和检查它们将更加容易。同样,如果需要在仿真脚本之间共享参数,则在单独的参数文件中定义所有变量名称(每个脚本都可以导入)的错误率要小得多。因此,一个好的经验法则是,数字只能在不同的参数文件或参数部分中可见,否则它们应该由变量表示。
通常,您需要重复一段代码并进行一些小的修改。例如,您有两个multimeters,并且希望从每个变量中提取记录的变量,然后计算其最大值。诱惑是只编写一次代码,然后将其复制并粘贴到新位置并进行必要的修改:
dma = nest.GetStatus(ma, keys="events")[0] Vma = dma["Vm"] amax = max(Vma) dmb = nest.GetStatus(mb, keys="events")[0] Vmb = dmb["Vm"] bmax = max(Vmb) print(amax-bmax)这有两个问题。首先,它使代码的主要部分变得更长且更难理解。其次,它容易出错。在一定比例的时间中,您会忘记在复制粘贴后进行所有必要的修改,这将在代码中引入难以发现的错误,不仅因为它们在语义上是正确的,而且不会导致一个明显的错误,也是因为您的眼睛倾向于在它们上方漂移:
dma = nest.GetStatus(multimeter1, keys="events")[0] Vma = dma["Vm"] amax = max(Vma) dmb = nest.GetStatus(multimeter2, keys="events")[0] Vmb = dmb["Vm"] bmax = max(Vma) print(amax-bmax)避免这种情况的最好方法是定义一个函数:
def getMaxMemPot(Vdevice): dm = nest.GetStatus(Vdevice, keys="events")[0] return max(dm["Vm"])可以将此类辅助函数有用地存储在其自己的部分中,类似于参数部分。现在,我们可以以更简洁,更不易出错的方式写下功能:
amax = getMaxMemPot(multimeter1) bmax = getMaxMemPot(multimeter2) print(amax-bmax)如果您发现这会使您的代码混乱,或者,您可以编写一个lambda函数作为的参数map,并享受自在的感觉,这种感觉会遍及您的剩余时间。好的政策是,如果您发现自己要复制和粘贴多行代码,请考虑花一些额外的时间来定义一个函数。通过减少寻找错误的时间,您将很容易赢得这次机会。
在准备模拟或收集或分析数据时,通常会发生这样的情况,我们需要对总体中的每个节点(或节点的子集)执行相同的操作。由于神经元在创建时会收到ID,因此有可能明确使用您对这些ID的了解:
Nrec = 50 neuronpop = nest.Create("iaf_psc_alpha", 200) sd = nest.Create("spike_detector") nest.Connect(range(1,N_rec+1),sd,"all_to_all")但是,完全不建议这样做!。这是因为在进行仿真时,您可能会添加其他节点-这意味着您最初正确的范围边界现在不正确,并且这是一个很难捕获的错误。为了得到节点的子序列,使用 切片相关人群的:
nest.Connect(neuronpop[:Nrec],spikedetector,"all_to_all")更糟糕的是,使用有关神经元ID的知识来建立循环:
for n in range(1,len(neuronpop)+1): nest.SetStatus([n], {"V_m": -67.0})不仅像前面的示例那样容易发生此错误,而且大多数PyNEST函数仍然希望有一个列表。如果给他们一个列表,您将减少主脚本的复杂性(好的),并将循环推到更快的C ++内核中,在该内核中它将更快地运行(也很好)。因此,您应该写:
nest.SetStatus(neuronpop, {"V_m": -67.0})有关在多个神经元上进行操作的更多示例,请参见第2部分,例如根据随机分布设置状态并连接总体。
如果您确实确实需要遍历神经元,则只需遍历总体本身(或其一部分),而不要引入范围:
for n in neuronpop: my_weird_function(n)因此,我们可以得出结论:在神经元种群本身上使用切片和循环,而不是进行范围运算。如果是循环,请首先检查是否可以通过将整个总体传递给函数来完全避免它-通常可以这样做。
这些是我们在本讲义中为示例引入的新功能。
GetConnections(neuron, synapse_model="None"))
返回连接标识符数组。
参数:
source -源GID列表target -目标GID列表synapse_model -带有突触模型的字符串如果不带参数调用GetConnections,则返回网络中的所有连接。如果给出了源神经元列表,则仅返回来自这些突触前神经元的连接。如果给出了目标神经元列表,则仅返回与这些突触后神经元的连接。如果给出了突触模型,则仅返回具有此突触类型的连接。允许source,target和synapse_model参数的任何组合。每个连接ID是一个5元组,或者是一个NumPy数组,其中包含以下五个条目:source-gid,target-gid,target-thread,synapse-id,port
*注意:*仅返回执行命令的MPI进程上与目标的连接。
本讲义涵盖了NEST topology库的使用,以构建结构化网络。阅读完本材料后,您将能够:
创建具有特定空间位置的神经元种群定义人口之间的连通性概况使用配置文件连接人口可视化连接有关使用PyNEST的更多信息,请参见本底漆的其他部分:
第1部分:神经元和简单神经网络第2部分:神经元种群第3部分:通过突触连接网络可以在Example Networks上找到更高级的示例,或者在子目录中查看NEST安装的源目录pynest/examples/。
如果我们使用神经元的生物学详细模型,那么很容易理解和实现拓扑的概念,因为我们已经有了树突状树突,轴突等,这些是神经系统内连通的物理前提。但是,我们仍然可以使用点神经元网络获得一定程度的特异性。
拓扑结构和日常意义上的结构都可以看作是一组规则,用于控制对象的位置及其之间的连接。在点神经元网络中,我们可以区分三种类型的特异性:
细胞类型特异性–存在哪些类型的细胞?位置特异性–细胞在哪里?投影特异性–它们投影到哪些单元格,以及如何投影?在先前的讲义中,我们看到可以使用建立网络之间的确定性或随机选择的连接Connect()。如果我们要创建包含空间位置和空间连接配置文件的网络模型,那么该该 topology模块了。注意:《 NEST拓扑用户手册》(NTUM)1中提供了有关使用拓扑模块的完整文档 ,在以下页面中将其作为完整源。
该nest.topology模块允许我们创建具有给定空间组织的节点群体,指定神经元如何连接的连接配置文件,并提供高级连接例程。因此,我们可以通过设计连接配置文件来创建结构化网络,以提供所需的细胞类型,位置和投影特异性。
结构化网络的生成分三个步骤进行,每个步骤将在后面的部分中详细介绍:
定义层,我们在其中分配网络层内神经元的布局和类型。定义连接配置文件,我们在其中生成希望连接具有的配置文件。每个连接字典为一类连接指定属性,并包含允许我们调整配置文件的参数。这些与选择目标(mask和 kernel)与位置有关的可能性有关,并且与细胞类型的特异性有关,即层中的哪种类型的细胞可以参与连接类别(sources和 targets)。连接层,我们在层之间应用连接字典,等效于总体特定性。请注意,可以在两层之间应用多个词典,就像可以将一层与其自身相连一样。辅助,其中我们通过nest.PrintNetwork()拓扑模块中包含的可视化功能或可视化功能来可视化上述步骤的结果,并查询连接以进行进一步分析。定义层的代码遵循以下模板:
import nest.topology as topp my_layer_dict = {...} # see below for options my_layer = topp.CreateLayer(my_layer_dict)在哪里my_layer_dict定义图层的元素及其位置。
layer使用elements键指定 要填充的节点的选择。目前,我们只关心创建简单的图层,每个图层均来自同质填充。然后,此字典条目的相应值应该是神经元的模型类型,它可以是NEST集合中的现有模型,也可以是我们先前使用定义的模型CopyModel()。
接下来,我们必须决定是否应该以基于网格的方式或自由(离网)方式放置节点 ,这等效于询问``我们的网络元素是否可以规则且均匀地放置在2D网络中,或者我们需要告诉他们应该在哪里?”。
我们必须通过第m行或第n行,第n行以及范围(图层大小)来明确指定网格的大小和间距。然后从中确定网格间距i,并且对称地排列n x m个元素。注意,我们也可以指定网格的中心,否则默认偏移为原点。
以下代码片段产生grid:
layer_dict_ex = {"extent" : [2.,2.], # the size of the layer in mm "rows" : 10, # the number of rows in this layer ... "columns" : 10, # ... and the number of columns "elements" : "iaf_psc_alpha"} # the element at each (x,y) coordinate in the grid我们仅定义元素,它们的位置和范围。创建的元素数量等于位置列表的长度。此选项为我们分配神经元提供了更大的灵活性。请注意,如果位置不在默认值(范围大小= [1,1],以原点为中心)之外,我们还应该指定范围。有关更多详细信息,请参见NUTM中的2.2节。
以下代码片段产生free:
import numpy as np # grid with jitter jit = 0.03 xs = np.arange(-0.5,.501,0.1) poss = [[x,y] for y in xs for x in xs] poss = [[p[0]+np.random.uniform(-jit,jit),p[1]+np.random.uniform(-jit,jit)] for p in poss] layer_dict_ex = {"positions": poss, "extent" : [1.1,1.1], "elements" : "iaf_psc_alpha"}注意:拓扑模块确实支持3D layer,但这不在本讲义的范围内。
以下是所有可用参数的概述以及它们主要用于基于网格的图层还是自由图层:
参数网格描述可能的值要素博思网络中要包含的模型类型列出的任何模型 nest.Models()或用户定义的模型程度博思层的大小(毫米)。默认值为[1.,1。]2D清单行数上行数整型列上列数整型中央上网格或自由层的中心。允许网格彼此独立地构造(请参见NTUM中的图2.3)2D清单位置的f要创建的每个神经元的位置列表。列表或元组列表边_wr ap博思图层是否应具有周期性边界。默认值:False布尔值也可以创建复合层。这种层类型扩展了基于网格的层,并允许我们poisson_generators在每个网格位置定义许多神经元和其他元素,例如。NTUM的2.5节提供了完整的解释。这种方法的优点在于,如果我们要有一个层,其中每个元素或子网具有相同的组件组成,那么定义具有这些属性的层非常容易。对于一个简单的示例,让我们考虑一个元素网格,其中每个元素包含4个金字塔形单元,1个中间神经元,1个泊松发生器和1个噪声发生器。相应的代码是:
nest.CopyModel("iaf_psc_alpha","pyr") nest.CopyModel("iaf_psc_alpha","inh", {"V_th": -52.}) comp_layer = topp.CreateLayer({"rows":5,"columns":5, "elements": ["pyr",4,"inh","poisson_generator","noise_generator"]})为了定义我们想要的神经元群体之间的连接类型,我们指定一个连接字典。
任何连接字典的唯一两个必需参数是 connection_type和mask。所有其他条件都允许我们通过调整连接的可能性,突触类型,与连接相关的权重和/或延迟或连接的数量,以及指定可参与的小区类型的限制来调整连接配置文件连接类。
NTUM的第3章全面讨论了所有不同的可能性,建议您在那儿学习有关不同的约束,并通读那里列出的不同示例。以下是一些用于设置连接配置文件的代表性示例,下表列出了可以使用的参数。
# Circular mask, gaussian kernel. conn1 = { "connection_type":"divergent", "mask": {"circular":{"radius":0.75}}, "kernel": {"gaussian":{"p_center":1.,"sigma":0.2}}, "allow_autapses":False } # Rectangular mask, constant kernel, non-centered anchor conn2 = { "connection_type":"divergent", "mask": {"rectangular":{"lower_left":[-0.5,-0.5],"upper_right":[0.5,0.5]}, "anchor": [0.5,0.5], }, "kernel": 0.75, "allow_autapses":False } # Donut mask, linear kernel that decreases with distance # Commented out line would allow connection to target the pyr neurons (useful for composite layers) conn3 = { "connection_type": "divergent", "mask": {"doughnut":{"inner_radius":0.1,"outer_radius":0.95}}, "kernel": {"linear": {"c":1.,"a":-0.8}}, #"targets":"pyr" } # Rectangular mask, fixed number of connections, gaussian weights, linear delays conn4 = { "connection_type":"divergent", "mask": {"rectangular":{"lower_left":[-0.5,-0.5],"upper_right":[0.5,0.5]}}, "number_of_connections": 40, "weights": {"gaussian":{"p_center":J,"sigma":0.25}}, "delays" : {"linear" :{"c":0.1,"a":0.2}}, "allow_autapses":False } 参数描述可能的值连接类型确定建立连接时如何选择节点收敛,发散面具空间选择的神经元子集被视为(潜在)目标圆形,矩形,甜甜圈,网格核心确定神经元被选为目标的可能性的函数。可以是距离相关的或独立的。常数,均匀,线性,高斯,指数,高斯2D称重ts连接权重值的分布。可以是距离相关的或独立的。 注意:此值将覆盖synapse_model当前使用的任何值,因此,除非已定义,否则默认为1。恒定,均匀,线性,高斯,指数延迟连接延迟值的分布。可以是距离相关的或独立的。 注意:像权重一样,此值将覆盖synapse_model当前使用的任何值!恒定,均匀,线性,高斯,指数synap se_m odel定义要包含的突触模型的类型。nest.Models() 或当前用户定义的任何突触模型来源定义此连接的源(突触前)神经元。当前用户定义的任何神经元标签目标定义此连接的目标(突触后)神经元。当前用户定义的任何神经元标签连接数固定此神经元要发送的连接数,以确保我们具有固定的向外度分布。整型允许_mul tapse我们是要在同一源-目标对之间建立多个连接,还是要确保唯一的连接。布尔值允许_aut近视我们是否要允许神经元连接到自身布尔值连接层是最简单的步骤:定义了源层,目标层和连接字典后,我们只需使用以下函数 topp.ConnectLayers():
ex_layer = topp.CreateLayer({"rows":5,"columns":5,"elements":"iaf_psc_alpha"}) in_layer = topp.CreateLayer({"rows":4,"columns":4,"elements":"iaf_psc_alpha"}) conn_dict_ex = {"connection_type":"divergent","mask":{"circular":{"radius":0.5}}} # And now we connect E->I topp.ConnectLayers(ex_layer,in_layer,conn_dict_ex)请注意,我们可以定义多个词典,多次使用同一词典并连接到同一层:
# Extending the code from above ... we add a conndict for inhibitory neurons conn_dict_in = {"connection_type":"divergent", "mask":{"circular":{"radius":0.75}},"weights":-4.} # And finish connecting the rest of the layers: topp.ConnectLayers(ex_layer,ex_layer,conn_dict_ex) # Connect E->E topp.ConnectLayers(in_layer,in_layer,conn_dict_in) # Connect I->I topp.ConnectLayers(in_layer,ex_layer,conn_dict_in) # Connect I->E我们可以使用两种主要方法来检查网络是否正确构建:
nest.PrintNetwork(depth=1)
它以文本形式打印出网络中的所有神经元和子网络。这是检查复合层层次的一种好方法。
使用``nest.topology` < https://www.nest-simulator.org/pynest-topology/ > __中的函数创建图
可以组合三个功能:
PlotLayerPlotTargetsPlotKernel这使我们能够生成与NUTM一起使用的绘图以及此讲义。有关更多详细信息,请参见NTUM的4.2节。
除NTUM第4.1节中已列出的功能外,其他有用的功能可能是有帮助的:
功能描述nest.GetNodes(layer)返回图层元素的GID:节点或顶级子网(用于复合)nest.GetLeaves(laye r)返回结构的叶子的GID,该结构总是神经元而不是子网topp.GetPosition(gi ds)返回输入中指定的元素的位置nest.GetStatus(laye r,“拓扑”)返回图层的图层字典该MUSIC接口,由INCF一个标准,允许在运行时应用程序之间的数据的传输。它可以用于将NEST与其他模拟器,刺激生成,数据分析和可视化应用程序以及还使用MUSIC界面的自定义应用程序耦合。
要将MUSIC与NEST一起使用,我们首先需要确保在我们的系统上安装了MUSIC,并且正确配置了NEST。
请按照MUSIC网站上的说明安装MUSIC 。
在NEST的安装中,您需要向cmake添加以下配置选项。
cmake -Dwith-music=[ON </path/to/music>] make make install在本教程中,我们将向您展示如何与NEST一起使用MUSIC库。我们将介绍如何通过PyNEST和SLI语言界面使用该库。此外,我们将介绍MUSIC在C ++应用程序中的使用以及如何将此类应用程序连接到NEST仿真。
我们的目的是展示有关如何使用MUSIC的实际示例,并强调可能使粗心大意的陷阱。此外,我们仅假设您对Python,C ++和(特别是)SLI的了解很少,因此,这些示例将更加讲究简洁,而不是优雅和惯用的构造。
跳到
第1部分-连接2个NEST模拟
虽然这里的重点是MUSIC,但我们需要了解有关NEST如何工作的一些知识,以便了解MUSIC如何与其交互。
直接进入教程的第1部分-使用PyNEST连接2个仿真
NEST网络由三种类型的元素组成:神经元,设备以及它们之间的连接。
神经元是基本的构建模块,在NEST中,它们通常是尖峰神经元模型。设备是支持单元,例如生成神经元的输入或记录神经元的数据。泊松尖峰发生器,尖峰检测器记录设备以及MUSIC输入和输出代理都是设备。神经元和设备统称为节点,并使用连接进行连接。
连接是单向的,并在节点之间传送事件。每个神经元可以从任意数量的其他神经元获得多个输入连接。神经元连接通常会携带尖峰事件,但其他类型的事件(例如电压和电流)也可用于记录设备。突触不是独立的节点,而是连接的一部分。突触模型通常会修改发送到神经元的尖峰的权重或时序。所有连接都有一个突触,默认情况下为static_synapse。
图7 A:两个相连的神经元NaNa 和 NbNb,带有突触 SS 和一个受体 RR。重量峰值 WaWa 产生于 t0t0。B:尖峰穿过突触并被添加到受体的队列中。C:接收器会及时处理尖峰t0+dt0+d。
连接有延迟和重量。所有连接都在接收端实现,参数的解释最终取决于接收节点。在图7 A中,神经元 NaNa 向发送了一个峰值 NbNb 在时间 tt,与重量有关 wawa 和延迟 dd。尖峰通过突触发送,然后在接收端进行缓冲,直到 t+dt+d(图7 B)。那时,它已移交给神经元模型受体,该受体将尖峰事件转换为电流并将其应用于神经元模型(图7 C)。
在NEST中,您将MUSIC与一对称为代理的额外设备一起使用,这些设备在整个模拟之间在它们之间创建MUSIC连接。该对有效地发挥作用,就像在单个模拟中的常规连接一样。MUSIC代理之间的每个连接都称为port,并在MUSIC配置文件中按名称连接。
每个MUSIC端口可以承载多个编号的通道。通道是最小的传输单位,因为您可以区分在不同通道(而不是单个通道)中流动的数据。根据应用的不同,端口可能具有一个或多个通道,一个通道可以承载来自一个神经元模型或许多神经元的合计输出的事件。
图8 A:两个相连的神经元NaNa 和 NbNb,有延迟 dndn 和重量 wnwn。B:我们添加了带有输出代理的MUSIC连接PaPa 一端,还有一个输入代理 PbPb 在另一。
在图8A中,我们看到两个神经元之间有规律的NEST连接NaNa 和 NbNb。连接重量 wnwn 和一个延迟 dndn。在图8 B中,我们在连接中插入了一对MUSIC代理,并带有输出代理PaPa 一方面,输入代理 PbPb 在另一。
如上所述,MUSIC代理是设备,而不是常规的神经元模型。与大多数设备一样,代理忽略传入连接上的权重和延迟参数。应用于连接的任何延迟 NaNa 到输出代理 PaPa因此被默默地忽略。MUSIC使模拟间的传输延迟对于模型本身是不可见的,因此从PaPa 至 PbPb实际上是零。连接的总延迟和权重 NaNa 至 NbNb 因此是在 PbPb 至 NbNb 连接。
图9 具有两个输出和两个输入的MUSIC连接。单个输出代理将两个数据通道发送到输入事件处理程序,该事件处理程序将通道划分为两个输入代理。它们连接受体神经元模型。
当我们有多个通道时,结构看起来类似于 图9。现在我们有两个神经元Na1Na1 和 Na2Na2 我们想要连接到的 Nb1Nb1 和 Nb2Nb2分别。如上所述,NEST设备可以接受来自多个单独设备的连接,因此我们只需要一个输出代理PaPa。我们将每个输入连接到不同的通道。
节点只能输出一个连接流,因此在接收端,我们需要一个输入代理 PbPb每个输入。内部,只有一个MUSIC事件处理程序设备EvEv接受来自端口的所有输入,然后将适当的通道输入发送到每个输入代理。这些代理分别如上所述连接到受体神经元。
我们来看一个通过MUSIC连接的两个NEST模拟的示例。 从本教程的简介开始,我们将在图9中实现简单的网络。
我们需要一个发送过程,一个接收过程和一个MUSIC配置文件:
#!/usr/bin/env python import nest nest.SetKernelStatus({"overwrite_files": True}) neurons = nest.Create('iaf_psc_alpha', 2, [{'I_e': 400.0}, {'I_e': 405.0}]) music_out = nest.Create('music_event_out_proxy', 1, params = {'port_name':'p_out'}) for i, n in enumerate(neurons): nest.Connect([n], music_out, "one_to_one",{'music_channel': i}) sdetector = nest.Create("spike_detector") nest.SetStatus(sdetector, {"withgid": True, "withtime": True, "to_file": True, "label": "send", "file_extension": "spikes"}) nest.Connect(neurons, sdetector) nest.Simulate(1000.0)发送过程非常简单。我们导入NEST库并设置一个有用的内核参数。在第6行上,我们创建了两个简单的集成并发射神经元模型,一个具有400mA的电流输入,另一个具有405mA的电流,只是它们的响应方式有所不同。如果您使用ipython进行交互工作,则可以使用来检查其当前状态字典nest.GetStatus(neurons)。NEST节点的权威文档是头文件,在本例 models/iaf_psc_alpha.h中为NEST源文件。
我们music_event_out_proxy在第8行为输出创建一个单一文件,并设置端口名称。我们遍历第11-20行上的所有神经元,并将它们一个接一个地连接到代理,每个神经元都有一个不同的输出通道。如前所述,每个MUSIC端口可以具有任意数量的通道。由于代理是设备,因此它忽略此处的权重或延迟设置。
最后,我们创建一个尖峰检测器,设置参数(我们可以直接在Create调用中完成),并将神经元连接到尖峰检测器,这样我们就可以看到发送的内容。然后我们模拟一秒钟。
#!/usr/bin/env python import nest nest.SetKernelStatus({"overwrite_files": True}) music_in = nest.Create("music_event_in_proxy", 2, params = {'port_name': 'p_in'}) for i, n in enumerate(music_in): nest.SetStatus([n], {'music_channel': i}) nest.SetAcceptableLatency('p_in', 2.0) parrots = nest.Create("parrot_neuron", 2) sdetector = nest.Create("spike_detector") nest.SetStatus(sdetector, {"withgid": True, "withtime": True, "to_file": True, "label": "receive", "file_extension": "spikes"}) nest.Connect(music_in, parrots, 'one_to_one', {"weight":1.0, "delay": 2.0}) nest.Connect(parrots, sdetector) nest.Simulate(1000.0)接收过程遵循相同的逻辑,但只涉及一点点。我们music_event_in_proxy在第6-7行上创建两个(每个通道一个),并设置输入端口名称。如上所述,NEST节点可以接受许多输入,但仅发出一个数据流,因此,每个通道需要一个输入代理才能将通道彼此区分开。在第9-10行,我们为每个输入代理设置输入通道。
第 12行的SetAcceptableLatency命令设置了允许MUSIC延迟通过指定端口传输的尖峰的传递的最长时间(以毫秒为单位)。这不应超过从输入代理到目标的延迟的 最小值;这就是我们在本例中在第20行设置的2.0毫秒。
在第14行,我们创建了一组鹦鹉神经元。他们只是简单地重复输入。在第16-18行,我们创建并配置了一个尖峰检测器以保存输入。我们将输入代理与第20行的鹦鹉神经元一对一连接,然后将鹦鹉神经元与第21行的尖峰检测器相连。我们稍后将讨论其原因。最后,我们模拟一秒钟。
binary=./send.py np=2 [to] binary=./receive.py np=2 from.p_out -> to.p_in [2]MUSIC配置文件的结构很简单。我们定义一个过程from,一个to。对于每个进程,我们设置希望运行的二进制文件的名称以及它应使用的MPI进程的数量。在第9行,我们终于定义从输出端口的连接p_out在处理 from到输入端口p_in在处理 to,具有两个通道。
如果我们的程序采用了命令行选项,则可以在args命令中添加它们:
binary=./send.py args= --option -o somefile在命令行上运行模拟,如下所示:
mpirun -np 4 music python.music你应该得到的信息滚动过去的一屏,然后将剩下4个新的数据文件,名字类似send-N-0.spikes, send-N-1.spikes,receive-M-0.spikes和receive-M-1.spikes。名称和后缀当然与我们在上面send.py和 receive.py上面设置的相同。第一个数字是记录并保存数据的尖峰检测器的节点ID,最后一个数字是生成文件的每个进程的排名。
整理数据文件:
cat send-*spikes | sort -k 2 -n >send.spikes cat receive-*spikes | sort -k 2 -n >receive.spikes我们一起运行文件,并对输出进行数字排序(−n−n)在第二列(−k−k)。让我们并排看两个文件的开头:
send.spikes receive.spikes 2 26.100 4 28.100 1 27.800 3 29.800 2 54.200 4 56.200 1 57.600 3 59.600 2 82.300 4 84.300 1 87.400 3 89.400 2 110.40 4 112.40 1 117.20 3 119.20正如预期的那样,接收到的峰值比发送的峰值晚两毫秒。从输入代理到第receive.py20行中的鹦鹉神经元的连接的延迟参数说明了延迟。
同样,在这样一个简单的模型中,显而易见的是,发送方的神经元ID和接收方的神经元ID没有固定的关系。发送神经元的ID为1和2,而接收神经元的ID为3和4。如果需要将一种模拟中的事件映射到另一种模拟中的事件,则必须通过其他方式记录此信息。
MUSIC不仅可以发送尖峰事件,还可以发送连续的输入和消息。在NEST中,有接收但不发送此类输入的设备。NEST文档中有一些示例,如下所示:
#!/usr/bin/python import nest mcip = nest.Create('music_cont_in_proxy') nest.SetStatus(mcip, {'port_name' : 'contdata'}) time = 0 while time < 1000: nest.Simulate (10) data = nest.GetStatus (mcip, 'data') print data time += 10开始部分反映了我们先前的接收示例:您创建一个连续输入代理(在这种情况下为单个输入)并设置端口名称。
NEST没有通用工具可以将连续值输入直接直接应用到模型中。它的神经元仅处理尖峰事件。要使用输入,您需要在9-13行上创建一个循环,在其中进行短时间的模拟,显式读取第11行上的值,将其应用于模拟模型,然后再次模拟一个周期。
人们有时尝试使用此模式从仿真外部控制泊松发生器的速率。您可以从外部获得一个连续值的速率,然后将其应用于Poisson生成器,后者再刺激网络中的输入神经元。
问题是您需要在每个周期暂停仿真,退出到Python解释器,运行一些代码,然后调回仿真器内核并再次重新启动仿真。如果每隔几百或几千毫秒执行一次,这是可以接受的,但是由于输入可能每隔几毫秒更改一次,因此变得非常非常慢。
更好的方法是放弃使用NEST泊松发生器。 像在前面的python示例中一样,在外部过程中生成峰值事件的Poisson序列,并将峰值事件直接发送到仿真中。这是非常有效的,并且外部过程不仅限于在NEST中实现的生成器,还可以创建任何类型的尖峰输入。在下一节中,我们将研究如何执行此操作。
亨德里克·罗斯(Hendrik Rothe),汉娜·博斯(Hannah Bos),萨莎·范·阿尔巴达
这是Potjans和他的微电路模型的PyNEST实现。
本示例包含几个文件: helpers.py 助手功能用于微电路的仿真和评估。network.py 收集所有参数,并将不同的节点彼此连接。network_params.py 包含网络的参数。sim_params.py 包含模拟参数。stimulus_params.py 包含刺激的参数。example.py 使用此脚本尝试微电路。如何使用微电路模型示例:
为了在本地机器上运行微电路,我们必须首先检查变量N_scaling和K_scaling in network_params.py设置为 0.1。N_scaling调整神经元的数量,K_scaling调整要模拟的连接的数量。可以通过将这些值调整为1来运行整个网络。完成后,应在文件中将打印时间进度的选项设置为False sim_params.py。要运行,请使用 。输出将保存在目录中。python example.py``data
如果已使用这些应用程序构建了NEST(使用NEST进行并行计算),则可以使用OpenMP和MPI对代码进行并行化。(每MPI处理)的线程的数量可以通过调整被选择 local_num_threads在sim_params.py。可以通过为num_mpi_prc选择一个合理的值,然后使用以下命令运行脚本来设置MPI进程数。
mpirun -n num_mpi_prc python example.py模拟的默认版本使用泊松输入,该文件中定义了泊松输入,network_params.py以激发微电路的神经元种群。如果未提供泊松输入,则将计算直流输入,该直流输入应大致补偿泊松输入。还可以向微电路添加丘脑刺激或以恒定的直流输入来驱动它。可以在文件中定义stimulus_params.py。
Pynest微电路示例Pynest微电路网络 作者 Pynest微电路助手 作者 Pynest微电路参数微电路刺激参数微电路仿真参数本示例运行2个NEST实例和1个接收器实例。NEST实例上的神经元通过music_cont_out_proxy进行观察,并且它们的值通过MUSIC转发给接收器。
mpiexec -np 3 music test.music 音乐示例音乐示例接收器脚本