PyNest——Part1:neurons and simple neural networks

2023-05-18,,

neurons and simple neural networks

pynest – nest模拟器的界面

神经模拟工具(NEST:www.nest-initiative.org)专为仿真点神经元的大型异构网络而设计。 它是根据GPL许可证发布的开源软件。 该模拟器带有Python的接口[4]。 图1说明了用户的模拟脚本(mysimulation.py)和NEST模拟器之间的交互。 [2]包含该接口实现的技术详细描述,本文的部分内容均基于此参考。 仿真内核使用C ++编写,以获得最高性能的仿真。

您可以从Python提示符或ipython内部以交互方式使用PyNEST。 当你在探索PyNEST时,这是非常有用的,试图学习一个新的功能或调试一个例程。 一旦退出探索模式,您会发现它可以节省大量时间在文本文件中编写模拟。 这些可以依次从命令行或Python或ipython提示符运行。

无论是交互式,半交互式还是纯粹执行的脚本,首先需要将NEST的功能导入Python解释器。

import nest

和Python的其他所有模块一样,可以提供可用的函数。

dir(nest)

一个这样的命令是nest.Models?,它将返回您可以使用的所有可用模型的列表。 如果你想获得更多关于特定命令的信息,你可以使用IPython的标准帮助系统。

nest.Models?

这将返回帮助文本(docstring),解释这个特定函数的用法。 NEST内还有一个帮助系统。 您可以使用nest.helpdesk()在浏览器中打开帮助页面,并且可以使用nest.help(object)获取特定对象的帮助页面。

creating nodes

NEST中的神经网络由两种基本元素类型组成:节点和连接。 节点是神经元,设备或子网络。 设备用于刺激神经元或从它们进行记录。 节点可以安排在子网络中以构建分层网络,例如图层,列和区域 - 我们将在后面的课程中讨论。 现在我们将在我们启动NEST时出现的默认子网络中工作,这就是所谓的根节点。

首先,根子网络是空的。 使用Create命令创建新节点,该命令将参数作为所需节点类型的型号名称,以及可选的要创建的节点数量和初始化参数。 该函数返回新节点的句柄列表,您可以将其分配给变量供以后使用。 这些句柄是整数,称为ids。 许多PyNEST函数期望或返回一个id列表(参见第8节)。 因此,使用单个函数调用将函数应用于大型节点集很容易。

在导入NEST以及Pylab接口到Matplotlib [[3]](#3)后,我们将使用它来显示结果,我们可以开始创建节点。 作为第一个例子,我们将创建一个类型为iaf_psc_alpha的神经元。 该神经元是具有α形突触后电流的整合火焰神经元。 该函数返回所有创建的神经元的id的列表,在本例中只有一个,我们将其存储在名为neuron的变量中。

    import pylab
    import nest
    neuron = nest.Create("iaf_psc_alpha")

我们现在可以使用id来访问这个神经元的属性。 NEST中节点的属性通常通过形式为{key:value}的键值对的Python字典来访问。 为了查看神经元的属性,你可以问它的状态。

nest.GetStatus(neuron)

这将在Python控制台中打印出相应的字典。 这些属性中的许多与神经元的动力学无关。 要了解哪些有趣的属性,请通过帮助台查看模型的文档。 如果您已经知道您感兴趣的属性,您可以指定一个键或一个键列表作为GetStatus的可选参数:

    nest.GetStatus(neuron, "I_e")
    nest.GetStatus(neuron, ["V_reset", "V_th"])

在第一种情况下,我们查询常数背景电流I_e的值; 结果以包含一个元素的元组的形式给出。 在第二种情况下,我们查询重置电位和神经元阈值的值,并将结果作为嵌套元组接收。 如果为节点列表调用GetStatus,则外部元组的维度是节点列表的长度,内部元组的维度是指定的键的数量。

要修改字典中的属性,我们使用SetStatus。 在以下示例中,背景电流设置为376.0pA,这是导致神经元周期性尖峰的值。

nest.SetStatus(neuron, {"I_e": 376.0})

请注意,我们可以通过在字典中给出多个以逗号分隔的键:值对来同时设置多个属性。 另外请注意,NEST是类型敏感的 - 如果某个特定属性是double类型的,那么您确实需要明确写入小数点:

nest.SetStatus(neuron, {"I_e": 376})

将导致错误。 这很方便地保护我们避免发生整数除法错误,这很难被发现。

接下来我们创建一个万用表,这是一种我们可以用来记录神经元膜电压随时间变化的器件。 我们设置其属性的时间,以便它也将记录膜电压采样的时间点。 record_from属性需要一个我们想要记录的变量的名字列表。 暴露于万用表的变量因型号而异。 对于特定的模型,您可以通过查看神经元的属性可记录来检查暴露变量的名称。

    multimeter = nest.Create("multimeter")
    nest.SetStatus(multimeter, {"withtime":True, "record_from":["V_m"]})

我们现在创建一个尖峰探测器,另一个记录神经元产生的尖峰事件的设备。 我们使用可选的关键字参数params来设置它的属性。 这是使用SetStatus的替代方法。 属性withgid指示尖峰检测器是否记录它从其接收事件的源ID(即,我们的神经元的ID)。

    spikedetector = nest.Create("spike_detector",
    params={"withgid": True, "withtime": True})

关于命名的简短说明:这里我们称之为神经元神经元,万用表万用表等。 当然,您可以将创建的节点分配给您喜欢的任何变量名称,但如果选择反映仿真中的概念的名称,则该脚本更易于阅读。

connecting nodes with default connections

现在我们知道如何创建单个节点,我们可以开始连接它们以形成一个小型网络。

    nest.Connect(multimeter, neuron)
    nest.Connect(neuron, spikedetector)

指定连接参数的顺序反映了事件的流程:如果神经元尖峰,它会向尖峰检测器发送一个事件。 相反,万用表会定期向神经元发送请求,以便在该时间点询问其膜电位。 这可以被认为是粘在神经元中的完美电极。

现在我们连接了网络,我们可以开始模拟。 我们必须通知仿真内核模拟运行的时间。 这里我们选择1000ms。

nest.Simulate(1000.0)

恭喜,你刚刚在NEST模拟你的第一个网络!

extracting and plotting data from devices

模拟完成后,我们可以获取万用表记录的数据。

    dmm = nest.GetStatus(multimeter)[0]
    Vms = dmm["events"]["V_m"]
    ts = dmm["events"]["times"]

在第一行中,我们获取所有查询节点的状态字典列表。 这里,变量万用表只是一个节点的ID,所以返回的列表只包含一个字典。 我们通过索引它来提取这个列表的第一个元素(因此最后的[0])。 这种类型的操作在使用PyNEST时非常频繁地发生,因为大多数函数都被设计为接收和返回列表,而不是单独的值。 这是为了使项目组(通常情况下设置神经网络模拟)的操作更方便。

该词典包含一个名为events的条目,用于保存记录的数据。 它本身就是一个字典,其中的条目V_m和时间分别存储在Vms和ts中,分别存储在第二行和第三行中。 如果您无法想象字典词典以及您从哪里提取的内容,请首先尝试将dmm打印到屏幕上,以便更好地了解其结构,然后在下一步中提取字典事件等。

现在我们准备在一个图中显示数据。 为此,我们使用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()

在这里,我们通过使用GetStatus的可选关键字参数键更简洁地提取事件。 这会使用关键事件而不是整个状态字典来提取字典元素。 输出结果应该如图2所示。如果要将其作为脚本执行,只需将所有行粘贴到名为one-neuron.py的文本文件中即可。 然后,您可以通过在python前加上文件名,或者在Python或ipython提示符前加上run前缀来从命令行运行它。

可以在单个万用表上收集多个神经元的信息。 这确实使检索信息复杂化:n个神经元中的每一个神经元的数据将以交错方式存储和返回。 幸运的是,Python为我们提供了一个方便的数组操作来轻松分割数据:使用一个步骤(有时称为步幅)进行数组切片。 为了解释这个,你必须调整前一部分创建的模型。 以新名称保存您的代码,在下一节中您还将使用此代码。 创建一个额外的神经元,背景电流给定不同的值:

    neuron2 = nest.Create("iaf_neuron")
    nest.SetStatus(neuron2 , {"I_e": 370.0})

现在将这个新创建的神经元连接到万用表上:

nest.Connect(multimeter, neuron2)

运行模拟并绘制结果,它们看起来不正确。 要解决这个问题,你必须单独绘制两条神经元轨迹。 用以下行替换从万用表中提取事件的代码。

    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

一种常用的神经活动模型是泊松过程。 我们现在修改前面的例子,以便神经元接收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的抑制事件。 突触权重可以在字典中定义,该字典使用关键字syn_spec(突触规范)传递给Connect函数。 一般来说,可以在突触词典中指定确定突触的所有参数,例如“重量”,“延迟”,突触模型(“模型”)和特定于突触模型的参数。

    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)

剩下的代码仍然像以前一样。 你应该看到如图3所示的膜电位。

在引言的下一部分(第2部分:神经元群体)中,我们将看到更多的方法来一次连接许多神经元。

two connected neurons

连接神经元没有额外的魔法。 为了证明这一点,我们从一个具有恒定输入电流的神经元的原始示例开始,并添加第二个神经元。

    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"]})

我们现在将神经元1连接到神经元2,并记录来自神经元2的膜电位,以便我们可以观察由神经元1的尖峰引起的突触后电位。

    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})

如果您像以前一样模拟网络并绘制膜电位,您应该可以看到由神经元1的尖峰引起的神经元2的突触后电位,如图4所示。

command overview

这些是我们在这份讲义中为例子介绍的功能; 本介绍的以下部分将增加更多内容。

Getting information about NEST

Models(mtype="all", sel=None):
返回所有可用模型(节点和突触)的列表。 使用mtype =“节点”仅查看节点模型,mtype =“突触”仅查看突触模型。 sel可以是一个字符串,用于过滤结果列表并只返回包含它的模型。

helpdesk(browser="firefox"):
在给定的浏览器中打开NEST文档页面。

help(obj=None,pager="less"):
打开给定对象的帮助页面。

Nodes

Create(model, n=1, params=None):
在当前的子网络中创建n个类型模型的实例。 新节点的参数可以以参数形式给出(单个字典或大小为n的字典列表)。 如果省略,则使用模型的默认值。

GetStatus(nodes, keys=None):
返回给定节点列表的参数字典列表。 如果给出键,则会返回值列表。 键也可以是一个列表,在这种情况下返回的列表包含值列表。

SetStatus(nodes, params, val=None):
将给定节点的参数设置为参数,该参数可以是单个字典,也可以是与节点大小相同的字典列表。 如果给定val,则params必须是属性的名称,该属性在节点上设置为val。 val可以是单个值,也可以是与节点大小相同的列表。

Connections

这是Connect函数文档的缩写版本,请参阅NEST的完整版和连接管理的在线帮助以获取简介和工作示例。

连接(pre,post,conn_spec = None,syn_spec = None,model =无):

将pre神经元连接到post神经元。pre和post中的神经使用指定的连接(默认为“one_to_one”)和突触类型(默认为“static_synapse”)进行连接。 细节取决于连接规则。 注意:Connect不会遍历子网,它只会连接显式指定的节点。 突触前神经元,以GID列表的形式给出 - 突触后神经元,以GID列表形式给出conn_spec - 指定连接规则的名称或字典,见下文syn_spec - 指定突触的名称或字典,参见下文

连接

连接可以指定为包含连接规则名称(默认值:“one_to_one”)的字符串,也可以指定规则和规则特定参数(例如“indegree”)的字典,必须给出连接规则。 此外,字典中还可以包含允许自连接(“autapses”,默认值:True)和一对神经元(“multapses”,默认值:True)之间的多个连接的开关。

突触

可以将突触模型及其属性插入为描述一个突触模型的字符串(突触模型在synapsedict中列出)或作为字典插入,如下所述。如果没有指定突触模型,将使用默认模型“static_synapse”。突触字典中的可用键是“模型”,“体重”,“延迟”,“受体类型”和特定于所选突触模型的参数。所有参数都是可选的,如果未指定,将使用当前突触模型确定的默认值。 “模型”确定突触类型,取自NEST中的预定义突触类型或通过CopyModel()手动创建的突触。所有其他参数可以是标量或分布。在标量参数的情况下,除了必须用整数初始化的“receptor_type”之外,所有的键都会加倍。分布式参数用指定分布(“分布”,如“正常”)和分布特定参数(如“mu”和“sigma”)的另一个字典进行初始化。

模拟控制

模拟(T):

模拟网络t毫秒。

PyNest——Part1:neurons and simple neural networks的相关教程结束。

《PyNest——Part1:neurons and simple neural networks.doc》

下载本文的Word格式文档,以方便收藏与打印。