FEATURED · 精选文章

从零构建脉冲神经网络最小闭环:LIF模型、仿真到硬件接口

发布时间 / 2026/9/7 18:43:56
来源 / 创域科博编辑部
栏目 / 资讯中心
从零构建脉冲神经网络最小闭环:LIF模型、仿真到硬件接口 神经形态计算这几年在圈子里热度一直不减但不少人一上来就被脉冲神经网络SNN和各种芯片文档劝退。我也算是从算法仿真到硬件评估都摸爬过一遍这个系列打算把“代码 仿真”这条线完整走一遍。第一期不搞玄学先做一个能在本地跑起来的SNN最小闭环有可发放的脉冲神经元、有突触输入、有可以观察的仿真输出顺手把仿真结果整理成可以往神经形态硬件部署方向去靠的格式。适合刚开始做类脑算法验证的工程师、想理解SNN仿真的算法岗朋友也适合正在做毕设或预研、想找一个能快速上手的参考框架的同学。1. 整个仿真项目该怎么切先弄清楚你在仿真“什么”1.1 仿真层级别混在一起否则后面全白干拿到“神经形态计算”这个题目第一反应容易是去折腾某一个芯片的指令集或者核间通信协议这个方向对做底层的人没问题但如果目标是先用代码验证脉冲神经网络算法就容易陷进硬件细节里出不来。我的经验是先把仿真切成分层结构然后再选这一篇要落在哪一层。业内大概可以分成三个层级层级典型工具关注的问题算法/模型层Brian2、Norse、SpikingJelly、Nengo关注神经元模型、突触规则、网络行为网络/结构层NEST、Nengo、Lava关注大规模网络动力学、拓扑、学习规则芯片/映射层Lava、厂商SDK、Verilog级平台关注核映射、资源占用、时序约束、功耗Brian2在算法/模型层非常顺手Nengo是连接算法和硬件映射的桥梁Lava则更贴近某些异步芯片架构。很多人上来就想直接跑到芯片级结果对脉冲编码、膜电位重置这些基本问题都还没建立直觉后面排查问题会非常痛苦。这个系列的第一期我选算法/模型层加一点点结构层的内容目的是把SNN的核心对象跑通至于芯片映射这个阶段只需要在接口上留好口子后面再补。1.2 为什么拿Brian2而不是PyTorch或者纯手写在做技术选型时有人会说“PyTorch都能干”这句话从工程角度看没错但放在神经形态仿真场景下很容易误导。PyTorch里的事件或者时间维度需要你手动展开而SNN天然带时间维度和事件驱动特性很多和神经形态硬件配套的仿真器已经在数学层面把离散时间步进、阈值比较以及脉冲传递这些事情封装好了我们不需要每次从零写一遍欧拉迭代。Brian2的好处有三个方面。第一个是模型方程直接写在代码里微分方程长什么样代码就长什么样这对于理解模型本身非常友好。第二个是它的单位系统很严格mV、nA、nF、ms这些单位如果配错仿真直接会报错不会让你稀里糊涂跑出垃圾数据。第三个是它研究SNN场景下的光栅图可视化非常快画raster图几行就搞定不需要手动拼matplotlib去画事件散点。但是要注意Brian2主要适合中小规模网络和教学验证如果要跑到十万、百万级神经元性能上不如NEST这类专业工具更不用说针对芯片做精简实现的Lava。所以严格来说这不是“选最好的仿真器”而是“在不同的规模阶段选最匹配的仿真器”。这一期内容全部基于Brian2来完成因为可以贴住神经元的本源行为同时又不会让代码复杂度把正题带偏。2. 先让一个神经元“活”过来LIF模型的代码实现2.1 把看到的公式翻译成可调参数SNN最常用的基础神经元是LIFLeaky Integrate-and-Fire泄漏积分发放模型。四个关键参数必须先搞清楚参数符号物理含义典型值膜时间常数tau_m膜电位衰减快慢决定神经元的“记忆长度”10 ms静息电位v_rest没有输入时膜电位回到的位置-70 mV阈值电位v_threshold超过这个电位就发放脉冲-50 mV重置电位v_reset发放后膜电位落回的位置-70 mV用一个生活化的比喻去理解神经元就像一个水桶外加的水流是输入电流桶底的漏水口是泄漏水位是膜电位。水位超过桶沿就倒出脉冲倒完水之后桶又回到初始水位。tau_m就是决定“漏得多快”的指标tau_m越大桶里的水保留得越久神经元对外部输入的积分周期越长发放率自然也会受影响。在Brian2里的写法几乎就是照着微分方程翻译的from brian2 import * start_scope() tau_m 10*ms v_rest -70*mV v_threshold -50*mV v_reset -70*mV eqs dv/dt (v_rest - v) / tau_m I_inj / C : volt I_inj : amp C : farad G NeuronGroup(1, eqs, thresholdv v_threshold, resetv v_reset, methodeuler) G.v v_rest G.C 1*nF G.I_inj 0.15*nA M StateMonitor(G, v, record0) S SpikeMonitor(G) run(200*ms)跑完之后可以用matplotlib把膜电位画出来。你会看到膜电位刚开始稳定在-70mV左右随着电流持续注入电位逐渐上升突破-50mV然后立刻重置回-70mV形成周期性放电。这里有一个特别容易被忽略的地方观察的时长要超过神经元完整充放电的周期否则你可能只看到膜电位在上升却看不到发放和重置就会误以为代码有问题。2.2 单神经元跑通后先验证膜电位轨迹是否合理很多新手跑完单神经元直接去看光栅图发现空荡荡的一片就开始怀疑模型写错了。我的建议是第一步永远先画膜电位曲线因为它能直观告诉你是“没充上去”还是“充上去之后没发放”。判断一个LIF神经元行为是否合理的几个观察点输入电流若为恒定正值膜电位应该呈现周期性“上升-越过阈值-重置-再上升”的过程每次发放后膜电位要落在v_reset附近不要出现停留在高位的情况输入电流越大发放频率越高这个直觉可以用不同电流值去对比验证如果v_reset和v_rest不是同一个值发放后膜电位要先跳到v_reset再慢慢向v_rest靠拢。如果这些现象都符合就可以认为这个神经元实现没有大的问题。这里我不建议把v_threshold设得离v_rest太近否则单个突触输入稍微大一点就会让神经元疯狂放电后面加网络时很难看清谁真正贡献了动作电位。2.3 数值仿真步长要不要调Brian2默认的时间步长是0.1ms量级部分场景下也可以显式设置例如defaultclock.dt 0.1*msdt的选取需要和tau_m匹配。如果tau_m只有1ms而dt设置成2ms仿真就会漏掉膜电位的快速变化神经元发放的时序就会错。这会直接影响后续对“时间编码”的分析所以是一个很典型但是不容易被发现的坑。dt取值不要大于tau_m的十分之一否则数值解会和理论解偏差过大。我一开始图省事把dt设成1ms去跑一个tau_m只有2ms的快速神经元出来的放电间隔明显偏大怎么调参数都不对。后来想起来去核对时间常数和步长的关系才明白这个坑所以在这里专门提一下。3. 给SNN制造“真实输入”脉冲编码与基础突触3.1 输入不能是连续电压脉冲序列才是类脑的“语言”神经形态仿真里一个容易犯的误解是给神经元的输入直接用模拟电流值比如给一个持续恒定电流。这样做当然可以跑但这就失去了SNN的脉冲驱动特性。为了和类脑芯片的输入输出方式对齐我们更常用泊松脉冲序列、固定频率脉冲串或时间编码脉冲来作为输入。最简单的脉冲输入可以由PoissonGroup产生它本质上是一个“按泊松过程随机发放”的神经元群。每个输入神经元以指定的发放率随机发放用来模拟真实生物神经元的随机发放行为或者模拟事件相机等神经形态传感器某段时间内的输出。这个做法在神经形态视觉任务里非常常见很多事件流数据最终都会转成脉冲流。input_neurons PoissonGroup(400, rates20*Hz)这里的20Hz意思是每个输入神经元平均每秒发放20个脉冲。如果你把发放率调高到200Hz下游神经元收到的输入脉冲就会更多发放率自然也会上升。可以把PoissonGroup理解成“神经形态数据源”后续如果是真实事件数据也可以替换成由事件数据构造的SpikeGeneratorGroup。3.2 一个十几行就能跑起来的前馈SNN骨架下面是一个带简单突触电流的小网络骨架主要验证脉冲输入是否能真正驱动下游神经元群产生可观察的活动。from brian2 import * start_scope() tau_m 10*ms v_rest -70*mV v_threshold -55*mV v_reset -75*mV C_m 1*nF tau_syn 2*ms eqs dv/dt (v_rest - v) / tau_m I_syn / C_m : volt dI_syn/dt -I_syn / tau_syn : amp exc NeuronGroup(400, eqs, thresholdv v_threshold, resetv v_reset, methodeuler) exc.v v_rest exc.I_syn 0*amp input_neurons PoissonGroup(400, rates20*Hz) syn Synapses(input_neurons, exc, on_preI_syn_post 0.03*nA) syn.connect(p0.1) spike_mon SpikeMonitor(exc) state_mon StateMonitor(exc, v, recordrange(20)) run(1000*ms)先解释突触部分。input_neurons作为突触前神经元exc作为突触后神经元连接概率p0.1。每次输入神经元发放一个脉冲就给突触后神经元注入0.03nA的电流增量这个电流随后按tau_syn2ms的速度衰减。为什么需要tau_syn因为真实突触电流不会瞬间消失通常是快速上升到峰值再指数衰减这个过程会直接影响下游神经元的整合窗口。如果不加突触电流衰减每次脉冲的电流贡献会一直累积很容易让神经元进入持续饱和发放的状态。跑了这段代码后用下列脚本来画光栅图和膜电位曲线import matplotlib.pyplot as plt plt.figure() plt.plot(spike_mon.t/ms, spike_mon.i, k., markersize0.5) plt.xlabel(Time (ms)) plt.ylabel(Neuron index) plt.figure() plt.plot(state_mon.t/ms, state_mon.v[0]/mV) plt.xlabel(Time (ms)) plt.ylabel(Membrane potential (mV))如果出现比较有规律的光栅图说明随机输入传入后有部分下游神经元确实被“激发”了。这会是一个很好的标志代表整条“脉冲输入→突触电流→膜电位积分→发放”的链路是通的。3.3 跑起来之后重点看哪些指标仿真跑出来只是第一步关键是从数据中读出网络行为。常用的几个直观指标总发放次数spike_mon.num_spikes看整个神经元群在仿真窗口内产生了多少脉冲发放率随时间的变化单位时间窗口内的spike数量与窗口内活跃神经元数量之比活跃神经元比例有发放的神经元索引集合占神经元总数的比例首次发放时间看网络需要多长时间才能对输入产生响应这个在时间编码场景下特别重要膜电位分布如果某个神经元的膜电位长期压在阈值附近但就是不发放很可能是输入权重太小或者阈值设置过高。这些指标不用做得多复杂先有基本的统计再去看具体神经元。很多问题在统计阶段就会暴露出来比如总发放次数为0那说明输入根本传不过去如果总发放次数巨大且神经元群全部处于高频状态那多半是权重设太大或者抑制机制缺失。这个排查思路后面单独章节会细讲。4. 从仿真结果向神经形态硬件过渡这一步最容易脱节4.1 时间驱动仿真和异步硬件之间的“时差”问题代码仿真能做到这一步算是对SNN有了基本体感。但神经形态计算最终要落到的往往是一块以事件驱动方式工作的硬件上如果不提前理解这一点很容易把“软件跑通”误当成“硬件也能跑通”。常见CPU和GPU执行神经网络时经常是按层、按时间片同步进行仿真。软件里我们取一个固定的dt某时刻更新全部神经元下一步再更新全部神经元。这本质上是“同步锁步”的。而在真实的神经形态芯片上神经元发放后是异步产生事件同一个时刻并不需要全局把所有神经元都计算一遍——有输入才更新没输入就待命。这种异步事件驱动的模式是类脑硬件低功耗的关键原因之一。用生活化的比方就是办公区里同步仿真相当于整个办公室每隔固定时间必须全员打卡汇报一次状态异步事件驱动相当于只有任务发生的人才举手报告。后者明显省掉了大量无效操作。所以Brian2这类时间步进仿真器给出的尖峰时序和硬件上真正的事件时序之间存在一个量化误差。如果dt过大事件到达前后的顺序可能错位如果dt过小仿真时间又会快速增长。第2节提到的dt与tau_m匹配问题在这个层面上会和硬件事件时序强相关。要做精细的事件时序仿真就得考虑模拟器是否支持“事件驱动”的突触更新精度或者干脆在芯片级仿真平台里做更细粒度的验证。4.2 把spike活动导出成硬件友好的数据格式仿真的最终目的如果是部署那么SpikeMonitor的记录结果需要被整理成标准事件格式而不是只停留在matplotlib图里。一个很实用的操作是把脉冲序列按时间戳和神经元编号导出后续可以作为硬件模拟器的输入或送到Nengo、Lava这一类框架去做映射初始化。如下代码可以把脉冲结果存成CSVimport csv with open(spike_output.csv, w, newline) as f: writer csv.writer(f) writer.writerow([time_ms, neuron_id]) writer.writerows(zip(spike_mon.t/ms, spike_mon.i))如果后续有“核、轴突、突触”等硬件映射概念CSV里可以扩展相应字段例如增加core_id、axon_id列。神经元索引只是软件侧的编号硬件上它们会分布在不同的计算核上这一步映射非常关键。通常我会建议在早期代码里就把“神经元ID”当作一个可扩展的元数据字段去处理不要让后面的人再去猜某个索引到底代表什么。4.3 从raster图判断网络是否真的“类脑”很多文章喜欢放一张光栅图声称这就是神经形态计算的效果但一张好看的光栅图如果看不出时间结构那很大程度上只是随机噪声的视觉化。有价值的网络行为通常具有这些特征输入刺激变化后发放时机具有稳定的相对延迟而不是完全随机群体编码下不同神经元群对输入的响应有明显差异能区分不同模式在无输入或输入较弱时网络不产生无意义的持续高频发放。如果这段代码跑出来的光栅图基本平静而偶发脉冲说明突触驱动的有效性不足如果网络在输入为0的情况下仍然持续振荡那要检查是漏电太慢还是出现某种自激发环路。神经形态仿真最后看的不是复杂度而是网络是否充分利用了脉冲的时间和稀疏特性来表达信息。这是引入硬件映射前应该有一次“行为健康检查”。5. 仿真里的坑我一个一个踩给你看5.1 常见症状与排查对照表下面是我从多次仿真调试里整理出来的对照表覆盖了不少高频问题。症状可能原因排查方法下游神经元完全不发放突触权重太小或输入频率太低阈值过高仿真时间不足调大weight降低v_threshold延长run时长观察神经元持续高频发放突触电流无衰减或启动电流过大抑制机制缺失确认tau_syn是否生效减少权重必要时加抑制性神经元光栅图看起来和随机噪声没区别输入缺少时间结构网络没有形成突触延迟改用固定频率或脉冲包络作为输入加入突触延迟膜电位一直停在阈值附近不重置重置条件写错reset没有写v_reset检查threshold和reset语句确认膜电位在超出阈值后立刻复位同一参数不同次运行结果差异大泊松输入本身是随机过程种子未固定设置seed例如seed(42)保证实验可重复仿真很慢时间步进太多网络规模略大但仍用了Python标量循环dt过小改用cpp_standalone设备或适当放大dt减少冗余StateMonitor记录神经元对于第一类“完全不发放”现象最容易排查的方法是把突触前输入神经元是否放出脉冲检查一下。看起来这是废话但很多实际场景下输入神经元本身因为参数配置错误完全不发放下游当然就没响应。先确认源头再往模型自身找原因。5.2 固定随机种子神经形态仿真实验的可复现底线不少做深度学习的人切换过来时会忽略SNN对随机性的敏感程度。SNN仿真里随机源很多泊松输入是随机过程随机突触连接也是随机的。如果不固定种子每次仿真得到的发放统计会出现明显波动。做参数网格搜索时如果某个网络结构占了便宜不是因为结构本身好而是因为随机种子幸运那最后得出的结论就很不可靠。固定种子的操作很简单seed(42)这个seed不一定只有数字形式Brian2里对整个仿真的随机状态都能做限制。我在做多组参数对比时会让每组实验共用同一个输入随机源这样对比才公平。先固定输入神经元发放模式再去对比不同网络参数效果会清晰很多。5.3 监控器别随便记录所有神经元调试时大家习惯把StateMonitor直接recordTrue记录所有神经元几百个神经元、跑一秒可能没问题一旦神经元数量到几千甚至上万内存直接暴涨。因为膜电位在每个时间步都记录1000个神经元跑1秒、dt0.1ms就是1000万行量级的数据。我建议StateMonitor只用于记录代表性神经元比如recordrange(20)就够了全量数据留给SpikeMonitor去处理因为spike记录本身是稀疏的真正发放的脉冲数量通常远小于全部时间点。如果确实需要采集大量神经元膜电位优先考虑把数据写盘而不是全部攒在内存里或者用Brian2的“cpp_standalone”目标设备编译运行性能会好很多。5.4 从测试单元到集成网络的“仿真冒烟测试”在把单神经元扩展到更大网络之前最值得做的是一个“冒烟测试”先用一个3到5个神经元的小网络跑一遍确认每个组件的接口都是通的再加大规模。这个习惯我在其他工程里也一直在用放到SNN里效果一样好。举一个实际测试方案输入层5个神经元发放率设为100Hz中间层3个神经元权重全部设为0.1nA左右输出层1个神经元。跑完看输出层是否有发放。如果没有不急着加复杂网络和STDP学习规则先回到基础参数上找问题。很多大型网络跑不出效果的根因往往就是最小闭环都没通过后面叠加的复杂度只会让问题更难定位。6. 这一期还足够干什么下一步往哪扩展到目前一个LIF神经元、一组泊松输入、一个简单突触网络和基础的spike输出格式已经全部跑通。这个最小闭环已经可以用来做不少实际验证工作比如对比不同神经元模型的响应特性验证脉冲编码方式速率编码vs时间编码对下游神经元的影响测试不同突触时间常数对网络动态的影响将仿真结果导出为后续基于Lava或Nengo的硬件映射阶段提供输入数据。后续如果要继续深入几个方向很明确。第一个方向是加入可塑性规则比如STDP让突触权重可以随脉冲时序自动调整这是SNN从“固定权重滤波”走向“可学习计算”的关键一步。第二个方向是多层网络和抑制性神经元。单纯的兴奋性网络容易进入失控发放状态抑制性神经元的加入对网络的稳定性影响非常大。第三个方向是把事件数据接入比如事件相机数据或者音频事件流让输入不再是简单的泊松脉冲而是真实任务的稀疏事件。这套代码我在做神经形态方向预研时反复使用感觉最大的价值不是某一个模型有多高级而是帮你把“脉冲—时间—稀疏”这三个概念真正长在脑子里。下一期我准备把STDP和抑制性神经元加上再引入一个简单的模式分类任务让SNN在数据上真正体现学习能力。仿真只是第一步但这一步走扎实了后面无论上真芯片还是调大规模仿真器心里都有底。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻