part 3: connecting networks with synapses
parameterising synapse models
NEST提供了各种不同的突触模型。 您可以使用命令nest.Models(mtype ='synapses')查看可用模型,该命令仅从所有可用模型列表中选取突触模型。
Synapse模型可以类似于神经元模型进行参数化。 您可以使用GetDefaults(模型)发现默认参数设置,并使用SetDefaults(模型,参数)设置它们:
nest.SetDefaults("stdp_synapse",{"tau_plus": 15.0})
这个模型生成的任何突触都将具有除tau_plus之外的所有标准参数,tau_plus将具有上面给出的值。
此外,我们还可以使用CopyModel()创建自定义的突触模型变体,这与神经元模型所证明的完全相同:
nest.CopyModel("stdp_synapse","layer1_stdp_synapse",{"Wmax": 90.0})
现在,layer1_stdp_synapse将出现在Models()所返回的列表中,并且可以在任何可以使用内置模型名称的地方使用。
STDP synapses
对于大多数突触来说,它们的所有参数都可以通过GetDefaults()和SetDefaults()来访问。 实施穗定时依赖可塑性的突触模型是一个例外,因为它们的动力学是由突触后尖峰火车以及突触前突触驱动的。 结果,STDP的压抑窗口的时间常数是突触后神经元的参数。 它可以设置如下:
nest.Create("iaf_psc_alpha", params={"tau_minus": 30.0})
或者通过使用本导言前两部分中介绍的任何其他参数化神经元的方法。
connecting with synapse models
突触模型以及与突触类型相关的参数可以在由连接例程接受的突触规范字典中设置。
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进行连接。
distributing synapse parameters
突触参数在传递给连接功能的突触字典中指定。 如果参数设置为标量,则所有连接都将使用相同的参数绘制。 参数可以通过给参数分配一个字典来随机分配。 字典必须包含设置参数目标分布的密钥分配(例如正常)。 或者,可以设置与分配相关的参数(例如mu)。 在这里我们展示了一个例子,其中参数alpha和stdp突触的重量是均匀分布的。
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)
连接管理中描述了可用的分配和相关参数,最常见的分配是:
querying the synapses
函数GetConnections(source = None,target = None,synapse_model = None)返回符合给定规范的连接标识符列表。 没有强制性的论据。 如果它没有任何参数被调用,它将返回网络中的所有连接。 如果指定了源,则作为一个或多个节点的列表,该函数将返回该群体的所有传出连接:
nest.GetConnections(epop1)
同样,通过将目标指定为一个或多个节点的列表,我们可以找到特定目标人群的传入连接:
nest.GetConnections(target=epop2)
将返回网络中所有神经元和pop2中神经元之间的所有连接。 最后,可以通过指定给定的突触模型来限制搜索:
nest.GetConnections(synapse_model="stdp_synapse")
将返回网络中类型为stdp_synapse的所有连接。 最后两种情况比第一种情况慢,因为必须执行对所有连接的完整搜索。参数源,目标和synapse_model可以单独使用,如上所述,或者可以结合使用:
nest.GetConnections(epop1, epop2, "stdp_synapse")
将返回epop1中的神经元对stdp_synapse类型的epop2中的神经元的所有连接。 请注意,所有这些查询命令将仅返回本地连接,即在分布式仿真中返回该特定MPI进程上的那些连接。
一旦我们有了连接数组,我们就可以使用GetStatus()从数据中提取数据。 在最简单的情况下,这将返回一个字典列表,其中包含GetConnections发现的每个连接的参数和变量。 但是,通常我们不希望所有的信息都来自突触,但它的某些特定部分。 例如,如果我们想要检查我们是否按照预期连接了网络,那么我们可能只想检查每个连接的参数目标。 我们可以通过使用GetStatus()的可选键参数来提取这些信息:
conns = nest.GetConnections(epop1, synapse_model="stdp_synapse")
targets = nest.GetStatus(conns, "target")
变量目标现在是所有找到的连接的目标值列表。 如果我们对多个参数感兴趣,则键也可以是键的列表:
conns = nest.GetConnections(epop1, synapse_model="stdp_synapse")
conn_vals = nest.GetStatus(conns, ["target","weight"])
变量conn_vals现在是一个列表列表,包含找到的每个连接的目标和权重值。
要习惯这些查询突触的方法,建议在已知所有连接的小型网络上尝试它们。
coding style
随着您的模拟变得越来越复杂,开发干净的编码风格非常有帮助。 这首先减少了错误的数量,同时也帮助您调试代码并使其他人更容易理解(甚至在两周后)。 这里有一些指针,其中一些对于编程通常是常见的,其中一些更具有NEST特定性。 另一个有用建议的来源是PEP-8,它很方便地可以被许多编辑和IDE自动检查。
Numbers and variables
模拟通常有很多数字 - 我们用它们来设置神经元模型的参数,定义连接的强度,模拟的长度等等。有时候我们想在不同的脚本中使用相同的参数,或者根据其他参数的值计算一些参数。不建议在数字硬连接到你的脚本,这是容易出错:如果您以后决定改变给定参数的值,你必须要经过所有的代码,并检查是否已经改变了它的每一个实例。如果在不同情况下使用该值,这一点特别难以理解,例如在一个地方设置权重并计算另一个地方的平均突触输入。
更好的方法是将变量设置为参数值,然后在每次需要值时始终使用变量名称。如果变量的定义散布在整个脚本中,那么也很难遵循代码。如果脚本中有参数部分,并根据功能对变量名进行分组(例如神经元参数,突触参数,刺激参数等),那么查找和检查它们会更容易。同样,如果您需要在仿真脚本之间共享参数,那么在单独的参数文件中定义所有变量名称(单个脚本可以导入)的错误率要低得多。因此,一个好的经验法则是数字只能在不同的参数文件或参数部分中可见,否则应该用变量表示。
Repetitive code, copy-and-paste, functions
通常您需要稍微修改一段代码。 例如,您有两个万用表,您希望从每个万用表中提取记录的变量,然后计算其最大值。 诱惑是编写一次代码,然后将其复制并粘贴到新的位置并进行必要的修改
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的一个参数,并且享受你的日常生活中会充满自满的感觉。 一个很好的策略是,如果您发现自己要复制并粘贴多行代码,请考虑定义一个函数所需的几秒钟时间。 通过花费更少的时间寻找错误,您将很容易赢回这次。
Subsequences and loops
在准备模拟或收集或分析数据时,通常会发生我们需要在群体中的每个节点(或节点的子集)上执行相同的操作。 由于神经元在创建时接收到ids,所以可以明确地使用这些ids的知识:
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)
因此我们可以得出结论:不是范围操作,而是使用切片和循环遍布神经元群体本身。 在循环的情况下,首先检查是否可以通过将整个群体传递给函数来完全避免它 - 通常可以。
command overview
这些是我们为这个讲义中的例子介绍的新功能。
Querying Synapses
返回一个连接标识符数组。
参数:
源 - 源GID列表
目标 - 目标GID列表 synapse_model - 带突触模型的字符串如果不带参数调用GetConnections,则返回网络中的所有连接。 如果给出源神经元列表,则仅返回来自这些突触前神经元的连接。 如果给出目标神经元列表,则只返回这些突触后神经元的连接。 如果给出突触模型,则仅返回具有此突触类型的连接。 源,目标和synapse_model参数的任何组合都是允许的。 每个连接ID都是一个5元组,或者如果可用的话,是一个NumPy数组,其中包含以下五个条目:source-gid,target-gid,target-thread,synapse-id,port
注意:仅返回执行该命令的MPI进程上的目标连接。