神经元 SpikingFlow.neuron¶
本教程作者: fangwei123456
本节教程主要关注 SpikingFlow.neuron
,包括如何使用已有神经元、如何定义新的神经元。
LIF神经元仿真¶
我们使用一个LIF神经元进行仿真,代码如下:
import SpikingFlow
import SpikingFlow.neuron as neuron
# 导入绘图模块
from matplotlib import pyplot
import torch
# 新建一个LIF神经元
lif_node = neuron.LIFNode([1], r=9.0, v_threshold=1.0, tau=20.0)
# 新建一个空list,保存仿真过程中神经元的电压值
v_list = []
# 新建一个空list,保存神经元的输出脉冲
spike_list = []
T = 200
# 运行200次
for t in range(T):
# 前150次,输入电流都是0.1
if t < 150:
spike_list.append(lif_node(0.1).float().item())
# 后50次,不输入,也就是输入0
else:
spike_list.append(lif_node(0).float().item())
# 记录每一次输入后,神经元的电压
v_list.append(lif_node.v.item())
# 画出电压的变化
pyplot.subplot(2, 1, 1)
pyplot.plot(v_list, label='v')
pyplot.xlabel('t')
pyplot.ylabel('voltage')
pyplot.legend()
# 画出脉冲
pyplot.subplot(2, 1, 2)
pyplot.bar(torch.arange(0, T).tolist(), spike_list, label='spike')
pyplot.xlabel('t')
pyplot.ylabel('spike')
pyplot.legend()
pyplot.show()
print('t', 'v', 'spike')
for t in range(T):
print(t, v_list[t], spike_list[t])
运行后得到的电压和脉冲如下:
你会发现,LIF神经元在有恒定输入电流时,电压会不断增大,但增速越来越慢。
如果输入电流不是足够大,最终在每个dt内,LIF神经元的电压衰减值会恰好等于输入电流造成的电压增加值,电压不再增大,导致无法充电到过阈值、发放脉冲。
当停止输入后,LIF神经元的电压会指数衰减,从图中500个dt后的曲线可以看出。
我们修改代码,给予更大的电流输入:
...
for t in range(T):
# 前150次,输入电流都是0.12
if t < 150:
spike_list.append(lif_node(0.12).float().item())
...
运行后得到的电压和脉冲如下(需要说明的是,脉冲是以pyplot柱状图的形式画出,当柱状图的横轴,也就是时间太长时,而图像的宽度又不够大,一些“落单”的脉冲在图像上会无法画出,因为宽度小于一个像素点):
可以发现,LIF神经元已经开始发放脉冲了:
定义新的神经元¶
在SNN中,不同的神经元模型,区别往往体现在描述神经元的微分方程。上文所使用的LIF神经元,描述其动态特性的微分方程为:
其中 \(\tau_{m}\) 是细胞膜的时间常数, \(V(t)\) 是膜电位, \(V_{reset}\) 是静息电压, \(R_{m}\) 是膜电阻, \(I(t)\) 是输入电流
SpikingFlow是时间驱动(time-driven)的框架,即将微分方程视为差分方程,通过逐步仿真来进行计算。例如LIF神经元,代码位于 SpikingFlow.neuron.LIFNode
,参考它的实现:
def forward(self, i):
'''
:param i: 当前时刻的输入电流,可以是一个float,也可以是tensor
:return: out_spike: shape与self.shape相同,输出脉冲
'''
out_spike = self.next_out_spike
# 将上一个dt内过阈值的神经元重置
if isinstance(self.v_reset, torch.Tensor):
self.v[out_spike] = self.v_reset[out_spike]
else:
self.v[out_spike] = self.v_reset
v_decay = -(self.v - self.v_reset)
self.v += (self.r * i + v_decay) / self.tau
self.next_out_spike = (self.v >= self.v_threshold)
self.v[self.next_out_spike] = self.v_threshold
self.v[self.v < self.v_reset] = self.v_reset
return out_spike
从代码中可以发现,t-dt时刻电压没有达到阈值,t时刻电压达到了阈值,则到t+dt时刻才会放出脉冲。这是为了方便查看波形图,如果不这样设计,若t-dt时刻电压为0.1,v_threshold=1.0,v_reset=0.0, t时刻增加了0.9,直接在t时刻发放脉冲,则从波形图上看,电压从0.1直接跳变到了0.0,不利于进行数据分析。
此外,“脉冲”被定义为“torch.bool”类型的变量。SNN中的神经元,输出的应该是脉冲而不是电压之类的其他值。
如果想自行实现其他类型的神经元,只需要继承 SpikingFlow.neuron.BaseNode
,并实现 __init__()
, forward()
, reset()
函数即可。