BindsNET丨Part I. 创建网络和添加网络组件(Creating and Adding Network Components)

Posted AXYZdong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BindsNET丨Part I. 创建网络和添加网络组件(Creating and Adding Network Components)相关的知识,希望对你有一定的参考价值。

文章目录

1 创建一个网络(Creating a Network)

bindsnet.network.Network 对象是BindsNET的主要部分,它负责协调其所有组成部分的模拟:神经元、突触、学习规则(neurons, synapses, learning rules)等

from bindsnet.network import Network

network = Network()

bindsnet.network.Network 主要参数

  • dt:指定模拟时间步长,它决定模拟求解的时间粒度(以毫秒为单位)。为了计算简单,所有的模拟都是用欧拉方法进行的。如果在模拟中遇到不稳定性,请使用较小的dt来解决数值不稳定性。
  • batch_size:指定输入数据的预期小批大小。然而,由于BindsNET支持动态小批大小,因此可以安全地忽略这个参数。它用于初始化有状态的神经元和突触变量,如果事先指定,可能会提供一个小的加速。
  • learning:用于启用或禁用对网络组件的自适应参数的更新;例如,突触权重或自适应电压阈值。
  • reward_fn:参数的一个类,该类指定如何计算标量奖励信号并将其馈送到网络及其组件。通常,这个可调用类的输出将用于某些奖励调制(“reward-modulated”)或三因素(“three-factor”)学习规则。

2 添加网络组件(Adding Network Components)

BindsNET支持三种类型的网络组件:

  • layers 神经元层(nodes)
  • connections 神经元层之间的连接(bindsnet.network.topology)
  • monitors 用于记录状态变量演变的监视器(bindsnet.network.monitors)

网络中组件的名称是任意的,并且只需要在其组件组(layers, connections, 和 monitors)中是唯一的,以便唯一地处理它们。我们鼓励用户开发自己的命名约定,使用最适合他们的命名约定。

2.1 创建和添加层(Creating and adding layers)

以LIF神经元为例,创建节点层

from bindsnet.network.nodes import LIFNodes

# Create a layer of 100 LIF neurons with shape (10, 10).
layer = LIFNodes(n=100, shape=(10, 10))

bindsnet.network.nodes 参数,需要n(层中节点的数量)或shape(层的排列,从中可以计算节点的数量)中的一个。

某些节点对象的其他参数包括 thresh(为层提供电压阈值(s)的标量或张量),rest(为层提供静息电压(s)的标量或张量),traces(是否跟踪层中每个神经元的峰值痕迹),以及tc_decay(为层的神经元电压衰减的标量或张量)。

  • 要向网络添加一个层,请使用 bindsnet.network.Network 类的 add_layer 函数,并给它一个名称(字符串)来调用它。
network.add_layer(layer=layer, name="LIF population")

这样的图层被保存在字典属性 network.layer 中,可以被用户访问;例如,通过 network.layer['LIF population'] 进行访问。

其他层类型包括bindsnet.network.nodes.Input(用于用户指定的输入尖峰),bindsnet.network.nodes.McCullochPitts(McCulloch-Pitts神经元模型),bindsnet.network.nodes.AdaptiveLIFNodes(具有自适应阈值的LIF神经元)和bindsnet.network.nodes.IzhikevichNodes(Izhikevich神经元模型),任何数量的层都可以被添加到网络中。

自定义节点对象可以通过对 bindsnet.network.nodes.Nodes 进行子类化来实现,这是一个具有神经元模拟通用逻辑的抽象类。函数 forward(self, x: torch.Tensor)(计算输入数据对神经元群体的影响;例如,电压变化、尖峰发生等)、reset_state_variables(self)(将神经元状态变量重置为默认值)和_compute_decays(self) 必须被实现,因为它们被作为 bindsnet.network.nodes.Nodes 的抽象函数。

2.2 创建和添加连接(Creating and adding connections)

连接可以在不同的神经元群之间添加(投射),或者从一个神经元群返回到它自己(一个循环连接)。

from bindsnet.network.nodes import Input, LIFNodes
from bindsnet.network.topology import Connection

# Create two populations of neurons, one to act as the "source"
# population, and the other, the "target population".
source_layer = Input(n=100)
target_layer = LIFNodes(n=1000)

# Connect the two layers.
connection = Connection(
    source=source_layer, target=target_layer
)

像节点一样,每个连接对象有许多关键字参数,但 sourcetarget 都是必需的。这些必须是子类bindsnet.network.nodes.Nodes 的对象。其他参数包括 wb(连接的权重和偏置张量),wminwmax(最小和最大允许的权重值),update_rulebindsnet.learning.LearningRule;用于根据突触前后的神经元活动和/或全局神经调节信号更新连接权重),以及 norm(一个浮点值,用来规范权重)。

  • 要向网络添加连接,请使用 bindsnet.network.Network 类的 add_connection 函数,并将给定的源和目标群的名称作为 sourcetarget 参数。确保源和目标神经元也被添加到网络中。
network.add_layer(
    layer=source_layer, name="A"
)
network.add_layer(
    layer=target_layer, name="B"
)
network.add_connection(
    connection=connection, source="A", target="B"
)

连接被保存在字典属性 network.connection 中,可以被用户访问;例如,通过 network.connection['A', 'B']。这些层必须以匹配的名称(分别为A和B)添加到网络中,以便连接正常工作。对连接的方向性没有限制;层 "A "可以连接到层 “B”,而 "B "可以连接到 “A”,或者 "A "可以直接连接到自己。

自定义连接对象可以通过对 bindsnet.network.topology.AbstractConnection 的子类化来实现,这个抽象类具有计算突触输出和更新的通用逻辑。这包括 compute(用于计算下游层的输入作为尖峰和连接权重的函数)、update(用于根据突触前、突触后活动和可能的其他信号更新连接权重;例如,奖励预测误差)、normalize(用于确保突触后神经元的权重与预先指定的值相加)和 reset_state_variables(用于重新初始化有状态变量以开始新的模拟)。

2.3 指定监视器(Specifying monitors)

bindsnet.network.monitor.AbstractMonitor 对象可用于记录某些网络组件在模拟过程中的张量变量。

from bindsnet.network import Network
from bindsnet.network.nodes import Input, LIFNodes
from bindsnet.network.topology import Connection
from bindsnet.network.monitors import Monitor

network = Network()

source_layer = Input(n=100)
target_layer = LIFNodes(n=1000)

connection = Connection(
    source=source_layer, target=target_layer
)

# Create a monitor.
monitor = Monitor(
    obj=target_layer,
    state_vars=("s", "v"),  # Record spikes and voltages.
    time=500,  # Length of simulation (if known ahead of time).
)

用户必须指定一个要记录的 NodesAbstractConnection 对象,要记录的对象的属性(state_vars),以及可选的,模拟将持续多少时间步,以便通过预先分配内存来节省时间。

  • 要向网络添加一个监视器(从而实现监控),请使用 bindsnet.network.Network 类的 add_monitor 函数。
network.add_layer(
    layer=source_layer, name="A"
)
network.add_layer(
    layer=target_layer, name="B"
)
network.add_connection(
    connection=connection, source="A", target="B"
)
network.add_monitor(monitor=monitor, name="B")

给予监视器的名称并不重要。它只是被用户用来从 Network 实例控制的监视器对象中进行选择。

我们可以通过调用 network.monitor[<name>].get(<state_var>) 来获得一个监视器的内容,其中 <state_var> 是作为 state_vars 参数传入的可迭代的一个成员。这将返回一个形状为 (time, n_1, ..., n_k) 的张量,其中 (n_1, ..., n_k) 是记录的状态变量的形状。

bindsnet.network.monitor.NetworkMonitor 是用来同时记录许多网络组件的。

from bindsnet.network.monitors import NetworkMonitor

network_monitor = NetworkMonitor(
    network: Network,
    layers: Optional[Iterable[str]],
    connections: Optional[Iterable[Tuple[str, str]]],
    state_vars: Optional[Iterable[str]],
    time: Optional[int],
)

用户必须指定要记录的网络、层名的迭代器( network.layer 中的条目)、指向连接的2元组迭代器( network.connection 中的条目)、模拟过程中要记录的张量值状态变量的迭代器( state_vars ),以及可选的模拟持续的时间步骤,以便通过预先分配内存节省时间。

同样,我们可以通过调用 network.monitor[<name>].get() 来获取网络监视器的内容。注意这个函数不需要参数;它返回一个字典,将网络组件映射到一个子字典,将状态变量映射到其张量值记录。

2.4 运行模拟(Running Simulations)

在建立了一个 Network 对象后,下一步是运行一个模拟。在这里,Network.run 这个函数开始发挥作用。它需要参数 inputs(一个字典,将 AbstractInput 的层名映射到形状为 [time, batch_size, *input_shape] 的输入数据,其中 input_shape 是数据传递到的神经元群的形状)、time(模拟时间步数,通常被认为是毫秒)。和一些关键字参数,包括 camp(和 unclamp ),用于强迫神经元在任何给定的时间步长上尖峰(或不尖峰),reward 用于提供奖励调节的学习规则,以及 masks,一个将连接映射到布尔张量的字典,指定将哪些突触权重钳制为零。

在本指南前几部分的基础上,我们提出了一个简单的端到端例子,模拟一个两层的、输入输出的尖峰神经网络。

import torch
import matplotlib.pyplot as plt
from bindsnet.network import Network
from bindsnet.network.nodes import Input, LIFNodes
from bindsnet.network.topology import Connection
from bindsnet.network.monitors import Monitor
from bindsnet.analysis.plotting import plot_spikes, plot_voltages

# Simulation time.
time = 500

# Create the network.
network = Network()

# Create and add input, output layers.
source_layer = Input(n=100)
target_layer = LIFNodes(n=1000)

network.add_layer(
    layer=source_layer, name="A"
)
network.add_layer(
    layer=target_layer, name="B"
)

# Create connection between input and output layers.
forward_connection = Connection(
    source=source_layer,
    target=target_layer,
    w=0.05 + 0.1 * torch.randn(source_layer.n, target_layer.n),  # Normal(0.05, 0.01) weights.
)

network.add_connection(
    connection=forward_connection, source="A", target="B"
)

# Create recurrent connection in output layer.
recurrent_connection = Connection(
    source=target_layer,
    target=target_layer,
    w=0.025 * (torch.eye(target_layer.n) - 1), # Small, inhibitory "competitive" weights.
)

network.add_connection(
    connection=recurrent_connection, source="B", target="B"
)

# Create and add input and output layer monitors.
source_monitor = Monitor(
    obj=source_layer,
    state_vars=("s",),  # Record spikes and voltages.
    time=time,  # Length of simulation (if known ahead of time).
)
target_monitor = Monitor(
    obj=target_layer,
    state_vars=("s", "v"),  # Record spikes and voltages.
    time=time,  # Length of simulation (if known ahead of time).
)

network.add_monitor(monitor=source_monitor, name="A")
network.add_monitor(monitor=target_monitor, name="B")

# Create input spike data, where each spike is distributed according to Bernoulli(0.1).
input_data = torch.bernoulli(0.1 * torch.ones(time, source_layer.n)).byte()
inputs = "A": input_data

# Simulate network on input data.
network.run(inputs=inputs, time=time)

# Retrieve and plot simulation spike, voltage data from monitors.
spikes = 
    "A": source_monitor.get("s"), "B": target_monitor.get("s")

voltages = "B": target_monitor.get("v")

plt.ioff()
plot_spikes(spikes)
plot_voltages(voltages, plot_type="line")
plt.show()



注意,在电压图中,没有电压超过-52mV,这是 LIFNodes 对象的默认阈值。在达到这个点后,神经元的电压被重置为-64mV,这在图中也可以看到。

2.5 仿真说明(Simulation Notes)

所有网络组件的模拟是同步的(时钟驱动 clock-driven);也就是说,所有组件在每个时间步长都会被更新。其他框架使用的是事件驱动模拟,其中尖峰可以在任意时间发生,而不是在 dt 的规则倍数上。我们选择了时钟驱动的仿真,因为它易于实现,并考虑到计算效率。

在一个模拟步骤中,每个层的输入被计算为前一个模拟时间步骤中连接到该层的所有输出的总和(由突触权重加权)(由 bindsnet.network.Network 类的 _get_inputs 方法实现)。这个模型允许我们将网络组件解耦,并在选定的 dt 的时间粒度上分别进行模拟,只在模拟步骤之间进行交互。

这与深度神经网络(DNN)的计算有严格的区别,在深度神经网络中,层的排序是假定的,层的激活在一个时间步骤中从最浅的层到最深的层依次计算,但不包括递归层,其计算仍然是按时间排序的。

—— END ——


如果以上内容有任何错误或者不准确的地方,欢迎在下面 👇 留言。或者你有更好的想法,欢迎一起交流学习~~~

更多精彩内容请前往 AXYZdong的博客

以上是关于BindsNET丨Part I. 创建网络和添加网络组件(Creating and Adding Network Components)的主要内容,如果未能解决你的问题,请参考以下文章

BindsNET丨环境配置及安装

ACM-ICPC 2018 沈阳赛区网络预赛 I. Lattice's basics in digital electronics 阅读题加模拟题

大咖说丨山石网科贾彬:NDR技术 新型网络病毒的有效“疫苗”

声网传输层协议 AUT 的总结与展望丨Dev for Dev 专栏

用声网 Android UIKit 为实时视频通话应用添加自定义背景丨声网 SDK 教程

干货丨RPA内网验证码识别技巧