目录

  • 时间驱动与事件驱动
    • 时间驱动
    • 事件驱动
  • 基于时间驱动的脉冲神经元
    • spikingjelly:LIF神经元
    • 实验仿真

时间驱动与事件驱动

时间驱动

为了便于理解时间驱动,我们可以将SNN(spiking neuron network)看作是一种RNN,它的输入是电压增量(电流与膜电阻的乘积),隐藏状态是膜电压,输出是脉冲。这样的SNN神经元具有马尔可夫性:当前时刻的输出只与当前时刻的输入,神经元自身的状态有关。

可以用充电,放电,重置3个方程描述脉冲神经元:H(t)=f(V(t−1),X(t))H(t)=f(V(t-1),X(t))H(t)=f(V(t−1),X(t))S(t)=g(H(t)−Vthreshold)=Θ(H(t)−Vthreshold)S(t)=g(H(t)-V_{threshold})=\Theta(H(t)-V_{threshold})S(t)=g(H(t)−Vthreshold​)=Θ(H(t)−Vthreshold​)V(t)=H(t)⋅(1−S(t))+Vreset⋅S(t)V(t)=H(t)\cdot(1-S(t))+V_{reset}\cdot S(t)V(t)=H(t)⋅(1−S(t))+Vreset​⋅S(t)其中,V(t)V(t)V(t)是神经元的膜电压,X(t)X(t)X(t)是外源输入,即电压增量,H(t)H(t)H(t)是神经元的隐藏状态,可以理解为神经元还没有发脉冲前的瞬间,f(V(t−1),X(t))f(V(t-1),X(t))f(V(t−1),X(t))是神经元的状态更新方程,不同的神经元,区别在于更新方程不同。

对于LIF神经元,其动作的微分方程和近似差分方程分别为:τmdV(t)dt=−(V(t)−Vreset)+X(t)\tau_{m}\frac{dV(t)}{dt}=-(V(t)-V_{reset})+X(t)τm​dtdV(t)​=−(V(t)−Vreset​)+X(t)τm(V(t)−V(t−1))=−(V(t−1)−Vreset)+X(t)\tau_{m}(V(t)-V(t-1))=-(V(t-1)-V_{reset})+X(t)τm​(V(t)−V(t−1))=−(V(t−1)−Vreset​)+X(t)因此,对应的充电(状态更新)方程为:f(V(t−1),X(t))=V(t−1)+1τm(−(V(t−1)−Vreset)+X(t))f(V(t-1),X(t))=V(t-1)+\frac{1}{\tau_{m}}(-(V(t-1)-V_{reset})+X(t))f(V(t−1),X(t))=V(t−1)+τm​1​(−(V(t−1)−Vreset​)+X(t))放电方程中的S(t)S(t)S(t)是神经元发放的脉冲,g(x)=Θ(x)g(x)=\Theta(x)g(x)=Θ(x)是阶跃函数,也被称为脉冲函数,脉冲函数的输出仅为0或1。

重置表示电压重置过程:发放脉冲,则电压重置为VresetV_{reset}Vreset​;如果没有发放脉冲,则电压不变。

注意到,在RNN中,使用了可微分的激活函数,例如tanh。但SNN中对应的脉冲函数g(x)g(x)g(x)是不可微分的,这就导致了包含SNN层的深度学习模型不能用反向传播的方式来训练。但是我们可以用一个形状与阶跃函数相似的激活函数σ(x)\sigma(x)σ(x)去代替。这被称为梯度替代法。

在前向传播时,使用g(x)=Θ(x)g(x)=\Theta(x)g(x)=Θ(x),神经元的输出是离散的0和1,网络依然是标准的SNN,但反向传播时,使用梯度替代函数代替计算脉冲函数的梯度:g′(x)=σ′(αx)g'(x)=\sigma'(\alpha x)g′(x)=σ′(αx)其中,α\alphaα可以调整激活函数的平滑程度:σ(αx)=11+exp(−αx)\sigma(\alpha x)=\frac{1}{1+exp(-\alpha x)}σ(αx)=1+exp(−αx)1​当α\alphaα越大,σ(αx)\sigma(\alpha x)σ(αx)就越接近Θ(x)\Theta(x)Θ(x),但也越容易在靠近x=0x=0x=0时梯度爆炸,远离x=0x=0x=0时则容易梯度消失,导致网络难以训练。

下图显示了不同α\alphaα时,梯度替代函数的形状:

事件驱动

对于事件驱动的SNN,不需要通过时钟驱动SNN的计算,神经元的状态更新由事件触发。

在脉冲响应模型(Spike response model,SRM)中,使用显式的V−tV-tV−t方程来描述神经元的活动,而不是用微分方程去描述神经元的充电过程。由于V−tV-tV−t是已知的,因此给与任何输入X(t)X(t)X(t),神经元的响应V(t)V(t)V(t)都可以被直接算出(不需要提供V(t−1)V(t-1)V(t−1)的信息,因此与时钟取消了关联)。

Tempotron神经元也是一种SNN神经元,其命名来源于ANN中的感知器,感知器是最简单的ANN神经元,其对输入数据加权求和,输出二值0或1来表示数据的分类结果。Tempotron神经元可以看作是SNN中的感知器,它同样对输入数据加权求和,再输出二分类结果。

Tempotron的膜电位定义为:V(t)=∑iwi∑tiK(t−ti)+VresetV(t)=\sum_{i}w_{i}\sum_{t_{i}}K(t-t_{i})+V_{reset}V(t)=i∑​wi​ti​∑​K(t−ti​)+Vreset​其中,wiw_{i}wi​是第iii个输入的权重,也可以看作是所连接的突触的权重;tit_{i}ti​是第iii个输入的脉冲发射时刻,K(t−ti)K(t-t_{i})K(t−ti​)是由于输入脉冲引发的突触后膜电位(postsynaptic potentials,PSPs);VresetV_{reset}Vreset​是Tempotron的重置电位,或者称为静息电位。

其中,关于K(t−ti)K(t-t_{i})K(t−ti​)是一个关于tit_{i}ti​的函数(PSP Kernel),当t≥tit\geq t_{i}t≥ti​时,其函数表达为:K(t−ti)=V0(exp(−t−tiτ)−exp(−t−tiτs))K(t-t_{i})=V_{0}(exp(-\frac{t-t_{i}}{\tau})-exp(-\frac{t-t_{i}}{\tau_{s}}))K(t−ti​)=V0​(exp(−τt−ti​​)−exp(−τs​t−ti​​))当t<tit<t_{i}t<ti​时,其函数表达为:K(t−ti)=0K(t-t_{i})=0K(t−ti​)=0其中,V0V_{0}V0​是归一化系数,使得函数的最大值为1;τ\tauτ是膜电位时间常数,可以看出输入的脉冲在Tempotron上会引起瞬时的电位激增,但之后会呈指数衰减;τs\tau_{s}τs​是突触电流的时间常数,这一项的存在表示突触上传导的电流会随时间衰减。

单个的Tempotron可以作为一个二分类器,分类结果的判别,是看Tempotron的膜电位在仿真周期内是否超过阈值:

其中tmax=argmaxt{Vt}t_{max}=argmax_{t}\left\{V_{t}\right\}tmax​=argmaxt​{Vt​}。从Tempotron的输出结果也可以看出,Tempotron只能发射不超过1个脉冲。单个Tempotron只能做二分类,但多个Tempotron就可以做多分类。

关于Tempotron的训练,可以使用梯度下降法优化网络参数wiw_{i}wi​,以二分类问题为例,损失函数被定义为仅在分类错误的情况下存在。当实际类别yyy是1而实际输出y^\widehat{y}y​是0,损失为Vthreshold−VtmaxV_{threshold}-V_{t_{max}}Vthreshold​−Vtmax​​;当实际类别是0而实际输出是1,损失为Vtmax−VthresholdV_{t_{max}}-V_{threshold}Vtmax​​−Vthreshold​。可以统一写为:E=(y−y^)(Vthreshold−Vtmax)E=(y-\widehat{y})(V_{threshold}-V_{t_{max}})E=(y−y​)(Vthreshold​−Vtmax​​)直接对参数求梯度,可以得到:

因为:∂V(tmax)∂tmax=0\frac{\partial V(t_{max})}{\partial t_{max}}=0∂tmax​∂V(tmax​)​=0

基于时间驱动的脉冲神经元

spikingjelly:LIF神经元

后续内容的实验部分将基于第三方库spikingjelly进行。SpikingJelly是一个开源脉冲神经网络深度学习框架,使用PyTorch作为自动微分后端,利用C++和CUDA扩展进行性能增强,同时支持CPU和GPU。框架中包含数据集,可视化,深度学习三大模块。

安装spikingjelly:

pip install spikingjelly

在spikingjelly中,约定只能输出脉冲,即0或1的神经元,都可以称之为"脉冲神经元",使用脉冲神经元的网络,进而也可以称之为脉冲神经网络。在spikingjelly.clock_driven.neuron中定义了各种常见的脉冲神经元模型,我们以spikingjelly.clock_driven.neuron.LIFNode为例进行了解。

首先导入相关模块:

import torch
import torch.nn as nn
import numpy as np
from spikingjelly.clock_driven import neuron
from spikingjelly import visualizing
import matplotlib.pyplot as plt

新建一个LIF神经元构成的网络层:

lif=neuron.LIFNode(tau=100.0)

LIF神经元的参数有以下:

  • tau:膜电位时间常数;
  • v_threshold:神经元的阈值电压;
  • v_reset:神经元的重置电压。如果不为None,当神经元发射脉冲后,电压会被重置为v_reset;如果设置为None,则电压会被减去v_threshold;
  • surrogate_function:反向传播时,脉冲函数的替代函数。

其中 surrogate_function 参数,在前向传播时的行为与阶跃函数完全相同。

神经元的数量是在初始化或调用 reset() 函数重新初始化后,根据第一次接收输入的 shape 自动决定的。

与RNN中的神经元非常类似,脉冲神经元也是有状态的,或者说是有记忆。脉冲神经元的状态变量,一般是它的膜电位V(t)V(t)V(t)(发射脉冲前),因此,spikingjelly.clock_driven.neuron中的神经元,都有对象v,可以打印刚刚新建神经元的膜电位:

print(lif.v) # 0.0

电位是0,因为我们没有给它任何输入,我们给几个不同的输入,观察神经元的电压shape

x = torch.rand(size=[2, 3])
lif(x)
print('x.shape', x.shape, 'lif.v.shape', lif.v.shape)
# x.shape torch.Size([2, 3]) lif.v.shape torch.Size([2, 3])
lif.reset()x = torch.rand(size=[4, 5, 6])
lif(x)
print('x.shape', x.shape, 'lif.v.shape', lif.v.shape)
# x.shape torch.Size([4, 5, 6]) lif.v.shape torch.Size([4, 5, 6])

回顾前面的LIF神经元状态更新方程(充电):τmdV(t)dt=−(V(t)−Vreset)+X(t)\tau_{m}\frac{dV(t)}{dt}=-(V(t)-V_{reset})+X(t)τm​dtdV(t)​=−(V(t)−Vreset​)+X(t)其中,τm\tau_{m}τm​是膜电位时间常数,VresetV_{reset}Vreset​是重置电压,对于这样的微分方程,由于X(t)X(t)X(t)不是常数,所以不易求出解析解,我们用差分方程近似微分:τm(V(t)−V(t−1))=−(V(t−1)−Vreset)+X(t)\tau_{m}(V(t)-V(t-1))=-(V(t-1)-V_{reset})+X(t)τm​(V(t)−V(t−1))=−(V(t−1)−Vreset​)+X(t)因此可以得到V(t)V(t)V(t)(神经元未发射脉冲前的瞬时电压)的表达式:V(t)=f(V(t−1),X(t))=V(t−1)+1τm(−(V(t−1)−Vreset)+X(t))V(t)=f(V(t-1),X(t))=V(t-1)+\frac{1}{\tau_{m}}(-(V(t-1)-V_{reset})+X(t))V(t)=f(V(t−1),X(t))=V(t−1)+τm​1​(−(V(t−1)−Vreset​)+X(t))不同的神经元,充电方程不尽相同。但膜电位超过阈值电压后,释放脉冲,以及释放脉冲后,膜电位的重置都是相同的。因此它们全部继承自 spikingjelly.clock_driven.neuron.BaseNode,共享相同的放电、重置方程。

surrogate_function() 在前向传播时是阶跃函数,只要输入大于或等于0,就会返回1,否则会返回0。我们将这种元素仅为0或1的 tensor 视为脉冲。

发射脉冲消耗了神经元累积的电荷,因此膜电位会有一个瞬时的降低,即电压重置,在spikingjelly中,设置了两种重置方式,根据spikingjelly.clock_driven.neuron 中的构造函数的参数之一 v_reset选择:

  • Hard:v_reset=1.0(默认值为1.0),释放脉冲后,膜电位直接被设置成重置电压:V(t)=VresetV(t)=V_{reset}V(t)=Vreset​;
  • Soft:v_reset=None,释放脉冲后,膜电位减去阈值电压:V(t)=V(t)−VthresholdV(t)=V(t)-V_{threshold}V(t)=V(t)−Vthreshold​。

实验仿真

接下来,我们将逐步给与神经元输入,并查看它的膜电位和输出脉冲。

现在我们给与LIF神经元层持续的输入,并画出其放电后的膜电位和输出脉冲:

lif.reset()
x = torch.as_tensor([2.0])
T = 150
s_list = [] # 输出脉冲
v_list = [] # 隐藏状态:膜电位
for t in range(T):s_list.append(lif(x))v_list.append(lif.v)visualizing.plot_one_neuron_v_s(np.asarray(v_list), np.asarray(s_list), v_threshold=lif.v_threshold, v_reset=lif.v_reset,dpi=200)
plt.show()


我们给与的输入shape=[1](固定数值2.0),因此这个神经元层只有一个神经元。它的膜电位(Membrane Potentials)和输出脉冲随着时间变化情况如上图。

现在将该层神经元重置(重新初始化),并给与shape=[32]随机数组输入,查看这32个神经元的膜电位和输出脉冲:

lif.reset()
x = torch.rand(size=[32]) * 4
T = 50
s_list = [] # 32个神经元在50步仿真下输出的脉冲
v_list = [] # 32个神经元在50步仿真下的膜电位
for t in range(T):s_list.append(lif(x).unsqueeze(0)) # unsqueeze(0)增加首维度,lif(x).unsqueeze(0) shape=[1,32]v_list.append(lif.v.unsqueeze(0))s_list = torch.cat(s_list) # shape=[50,32]
v_list = torch.cat(v_list) # shape=[50,32]visualizing.plot_2d_heatmap(array=np.asarray(v_list), title='Membrane Potentials', xlabel='Simulating Step',ylabel='Neuron Index', int_x_ticks=True, x_max=T, dpi=200)
visualizing.plot_1d_spikes(spikes=np.asarray(s_list), title='Spiking', xlabel='Simulating Step',ylabel='Neuron Index', dpi=200)
plt.show()


从Spiking中可以看出,有部分神经元发射了脉冲,有的却没有发射。我们可以想像,在重复输入一个信息对象的过程中,该信息对象对应的某个特征持续被感知到,迫使该神经元膜电位上升而作出发射脉冲的行为。

从上面的仿真可以看出,脉冲神经元层可以接收连续数值张量,但只能输出脉冲,如果仅包含LIF脉冲神经元层,模型不需要训练(因为没有可学习参数)。

对比传统的神经元,传统神经元的激活是瞬时的,缺少时间尺度上提供的信息,从生物角度看,此刻兴奋的神经元在下一时刻应该惯性地也比较兴奋。这就引出了脉冲神经网络(SNN)的想法,神经元的兴奋度不应该是一瞬间更新的,而是具有慢慢衰减的表现,在持续输入信息的刺激下,最后激活的兴奋区才是预测判别结果。SNN更贴近生物角度,适合处理与脉冲序列有关的应用前景。

研究问题随笔
通过观察LIF神经元,发现神经元缺少知识,若想嵌入到深度学习模型,只能作为替代激活函数的一个工具。LIF神经元只是模拟了生物上信息的传递与神经元电位的改变,后续研究或许可以在LIF的机理上进行修改,为神经元增加知识。

第三十二课.脉冲神经网络SNN相关推荐

  1. NeHe OpenGL第三十二课:拾取游戏

    NeHe OpenGL第三十二课:拾取游戏 拾取, Alpha混合, Alpha测试, 排序: 这又是一个小游戏,交给的东西会很多,慢慢体会吧   欢迎来到32课. 这课大概是在我所写作已来最大的一课 ...

  2. 深度学习入门(三十二)卷积神经网络——BN批量归一化

    深度学习入门(三十二)卷积神经网络--BN批量归一化 前言 批量归一化batch normalization 课件 批量归一化 批量归一化层 批量归一化在做什么? 总结 教材 1 训练深层网络 2 批 ...

  3. 第三十四课.模糊神经网络

    目录 模糊理论 Fuzzy Set Fuzzy set operations Fuzzy Min Max Classifier 应用:基于模糊神经网络的水轮机调速器PID控制 模糊理论 在本篇内容中, ...

  4. python第三十二课——队列

    队列:满足特点 --> 先进先出,类似于我们生活中的买票.安检 [注意] 对于队列而言:python中有为其封装特定的函数,在collections模块中的deque函数就可以获取一个队列对象; ...

  5. HLS第三十二课(codingstyle )

    HLS中,C是用来描述硬件的,不是软件编程的,这是基本概念. 下面记录一些常用的C描述技巧. ++++++++++++++++++++++++++++++ 移位寄存器的描述. for(i = N - ...

  6. 【问链财经-区块链基础知识系列】 第三十二课 从区块链溯源来看农产品链的设计

    在区块链的应用场景中,农产品溯源和食品溯源一直是翻来覆去讲的故事,但实际上溯源是一个伪命题,农产品和食品的加密上链实现溯源看起来很丰满,然而现实却很骨干.我们经常忘记了初心:溯源的目的是为了什么? 农 ...

  7. 新版标准日本语初级_第三十二课

    语法   1. 动(基本形/ない形) つもりです:表示说话之前已经形成的意志.打算,和动词意志形 + と思っています意思相同. 今度の日曜日に遊園地へ行くつもりです(这个星期天打算去游乐园) ボーナス ...

  8. 潭州课堂25班:Ph201805201 django 项目 第三十二课 后台站点管理(课堂笔记)

    一.后台站点模版抽取 1.获取静态站点模版 可以使用git clone到本地 git clone https://github.com/almasaeed2010/AdminLTE.git 也可以在g ...

  9. 实践数据湖iceberg 第三十四课 基于数据湖icerberg的流批一体架构-流架构测试

    系列文章目录 实践数据湖iceberg 第一课 入门 实践数据湖iceberg 第二课 iceberg基于hadoop的底层数据格式 实践数据湖iceberg 第三课 在sqlclient中,以sql ...

最新文章

  1. 导师讨厌什么样的学生 ?
  2. Jquery源码分析之匿名函数的自执行
  3. Android环境下通过C框架层控制WIFI【转】
  4. mysql常见监控项
  5. stax 和jaxb 关系_XML解组基准:JAXB,STAx,Woodstox
  6. 华为怎么改输入法皮肤_微信和QQ个性键盘皮肤
  7. struts单例模式 java_Java单例设计模式详细介绍
  8. 如何调用windows 的调色板 以及如何打开文件
  9. 如何修复 AirPods 无法连接到 Mac?
  10. iOS开发UIScrollView常见属性和方法
  11. md4c语言_docsify-demo/c-4操作系统.md at master · lvITStudy/docsify-demo · GitHub
  12. 如何判断自己的Windows系统是否为盗版系统?
  13. MATLAB时域频域波形显示GUI
  14. html5校园生活,校园日常生活日记(精选12篇)
  15. 北京市高等教育英语听力计算机考试,北京市2018年高考英语听力机考问答
  16. native聊天界面 react_ReactNative 聊天 App 实战|RN 仿微信界面群聊|朋友圈
  17. 罗克韦尔AB PLC RSLogix数字量IO模块基本介绍
  18. 会议室预订小程序,共享会议室小程序,微信小程序会议室预约系统毕设作品
  19. DICTATOR第一周二分查找作业
  20. 深度解析NRF24L01

热门文章

  1. 从零到一编码实现Redis分布式锁
  2. vivo 互联网业务就近路由技术实战
  3. 高并发之存储篇:关注下索引原理和优化吧!躲得过实践,躲不过面试官!
  4. Redis 日志篇:无畏宕机实现高可用的杀手锏
  5. 数据库锁机制为什么很重要?
  6. Spring Cloud 万字总结!
  7. 快被系统性能逼疯了?你需要这份性能优化策略
  8. 技术转管理的成功率不到70%!你必须先迈过这9道坎...10页PPT详解
  9. 特朗普推文的文本分析
  10. 实现大数据可视化的10个技巧