ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

脉冲神经网络SNN入门:从LIF神经元到类脑芯片与低功耗计算

脉冲神经网络SNN入门:从LIF神经元到类脑芯片与低功耗计算 脉冲神经网络SNN这个关键词最近在硬件圈和算法圈讨论度突然上来了。如果你已经熟悉了卷积神经网络、Transformer 这类人工神经网络大概会有个疑问深度学习都这么强了为什么还要提一个“第三代神经网络”我最初接触 SNN 时也有同样的疑惑直到动手搭了一个最小规模的脉冲网络做数字识别实验才慢慢搞明白它和传统人工神经网络的本质差别在哪里。这篇文章不是从数学公式堆起来的教程更像是我个人从零入手 SNN 的完整笔记。我会先解释 SNN 为什么被称作第三代神经网络再拆解它的核心机制——脉冲和时间然后聊聊它的优势与痛点最后带你自己用 Python 搭一个能跑的迷你 SNN 模型。适合对深度学习有基础概念、想拓展视野的开发者也适合正在调研类脑计算或低功耗硬件方案的工程师参考。1. 为什么叫“第三代神经网络”到底三代在哪里1.1 从感知机到深度学习再到脉冲计算先说一个经常被忽略的事实神经网络并不是深度学习时代才有的概念。第一代神经网络以感知机Perceptron为代表结构简单只能处理线性可分的问题连异或问题都搞不定这就是 1969 年 Minsky 那本书给 AI 浇冷水的主要原因。第二代神经网络引入了反向传播和多层结构也就是我们现在天天用的深度神经网络它靠的是连续的实数激活值比如 ReLU、Sigmoid 输出的浮点数信息以“强度”编码。第三代的说法来自神经科学家和芯片工程师的视角。他们发现人脑中的神经元并不是靠输出一个连续的浮点数来传递信息的而是靠一种叫做“动作电位”的瞬时脉冲Spike在通信。神经元兴奋时会在极短的时间内产生一次电压尖峰然后静默一段时间再产生下一次尖峰。信息的关键不在于脉冲的“高度”所有脉冲的高度几乎是固定的而在于脉冲的“时机”和“频率”。SNN 试图在计算模型里保留这种脉冲特性用离散的、稀疏的脉冲事件来表达和处理信息。这也是“第三代”这个说法的核心前两代关注的是空间上的连接模式而第三代在时间维度上引入了动态行为。1.2 脉冲神经网络和传统神经网络最割裂的三个关系我在学习过程中总结了 SNN 与 ANN人工神经网络之间三个最本质的差异把它们放在一起对比会清楚很多对比维度ANN深度神经网络SNN脉冲神经网络神经元输出连续实数激活值离散脉冲事件0/1依赖时间步信息编码方式数值强度编码脉冲频率、时序、群体编码可导性激活函数可导能反向传播脉冲发放过程不可导训练困难计算方式一次前向传播乘法密集时间步上逐帧更新事件驱动硬件友好性GPU 友好矩阵乘法强项类脑芯片友好存算一体、事件驱动你要理解 SNN核心就两句话它更接近生物神经元的行为它抛弃了“数值求导”这条路换来在专用硬件上的极低功耗和稀疏计算潜力。这不是推翻深度学习而是走了另一条围绕时间和事件的计算路线。2. SNN 的底层机制脉冲、膜电位和时间步2.1 神经元是怎么“放电”的LIF 模型SNN 的神经元模型有很多种但最入门、最常见的就是 LIFLeaky Integrate-and-Fire泄漏积分发放模型。名字很拗口实际拆开来看就好理解了。把每个神经元想象成一个装水的桶桶底有一个小洞水会慢慢漏掉这个“水”就是膜电位。当上游神经元传来脉冲时相当于往桶里加水膜电位上升。如果膜电位超过了一个阈值这个神经元就“发放”一次脉冲可以理解成桶里的水满了溢出然后膜电位被重置回静息状态。如果没到阈值膜电位会随时间慢慢泄漏回静息值。用公式表达就是τ * dV/dt - (V - V_rest) R * I_in(t)如果 V 超过阈值 V_thr就发脉冲V 重置为 V_rest。这个模型虽然简单但抓住了神经元动态的核心积累、泄漏、发放、重置。我第一次跑这个模型时用 Numpy 模拟单个 LIF 神经元对接入恒流源的响应亲眼看到膜电位的上升曲线和对脉冲输出才真正理解了“积分”和“发放”是什么意思。2.2 脉冲如何携带信息频率编码与时间编码SNN 里最常见的编码方式有两种。第一种是频率编码Rate Coding用一段时间内脉冲的数量来体现刺激的强度刺激越强脉冲越多。这种编码方式比较容易理解但信息密度低因为你需要统计一段时间内的脉冲总数实时性差。第二种是时间编码Temporal Coding用脉冲出现的相对时间来编码信息比如刺激越强第一个脉冲出现得越早。这种编码信息密度高但对时间精度要求非常高实现和训练都更困难。实际工程中高频采用的做法是把连续数据转换成脉冲序列这个环节叫编码。最简单的转换方法是泊松编码把输入值当作发放概率在每个时间步按概率生成 0 或 1。比如输入 0 到 1 范围的特征值某个时间步生成脉冲的概率就等于该特征值。我写代码时踩过一个坑概率编码生成的是随机脉冲序列同样输入每次跑出来的脉冲都不完全一致这会影响实验的可复现性后来我通过固定随机种子和增加时间步数来缓解这个问题。2.3 时间是 SNN 的隐形维度SNN 里最重要的隐藏超参数是时间步长 T。一个样本输入网络后神经元并不会在一瞬间完成计算而是会经历 T 个时间步的迭代每个时间步都会更新所有神经元的膜电位、检查是否发放脉冲。这意味着 SNN 在处理一个样本时的计算量大约是 ANN 的 T 倍因此它并不是天然地“更快”。它的优势在于可以用专用的硬件实现事件驱动计算没有输入事件时相关神经元不需要任何运算能耗极低。在 CPU 上模拟 SNN 通常会比同等规模的 ANN 更慢这一点需要提前有心理准备。3. SNN 的杀手锏和软肋得分开看3.1 杀手锏一事件驱动带来的超低功耗SNN 最受关注的应用场景是边缘计算和低功耗 AIoT。2014 年 IBM 的 TrueNorth 芯片用 54 亿个晶体管模拟了 100 万个神经元功耗只有约 70mW。2017 年 Intel 发布了 Loihi 芯片同样采用脉冲机制在稀疏事件驱动的场景下功耗远低于同制程的传统 CPU/GPU 方案。核心原因是事件驱动传统深度网络在推理时不管输入图像中是否为零每个神经元都要做一遍乘加运算SNN 在硬件实现时只有收到脉冲的神经元才需要被唤醒并更新膜电位没有事件的区域完全处于静默状态。这种处理方式和人脑高度相似人脑 860 亿个神经元总功耗才 20 瓦左右而一张高端 GPU 的功耗动辄 300 瓦以上。“snn 脉冲神经网络 cmos”这个热词反映的就是这层关系SNN 的能耗优势真正发挥离不开 CMOS 工艺下的类脑芯片实现算法模型和硬件架构是一体的。3.2 杀手锏二时间信息天然融合SNN 处理时空序列数据有天然优势比如语音、视频、脑电信号、触觉传感器阵列。因为这些数据的本质都是随时间变化的模式SNN 不需要像 ANN 那样额外拼接时间窗口或加循环结构脉冲的时间维度本身就是特征的一部分。比如在脑电信号分类中每个电极通道在某个时间点的电压幅值可以编码成脉冲序列网络可以直接在时间维度上捕捉事件的先后顺序和节奏关系。这也是 SNN 在神经形态传感器事件相机上表现出色的原因。3.3 软肋一训练方法比 ANN 困难得多脉冲发放函数是一个典型的阶跃式不可导函数——神经元发放脉冲的瞬间输出从 0 变成 1这个跳变没有斜率。你没法像 ReLU 那样写出一个导数表达式这就让标准的反向传播无法直接用在 SNN 上。目前常见的训练路线有三条直接使用代理梯度法用一个形状相似但可导的函数比如矩形函数或快速 Sigmoid在反向传播时代替真正的脉冲导数把已经训练好的 ANN 转换成 SNN替换激活函数、重定阈值和时间步使用无监督的脉冲时序依赖可塑性STDP规则但那通常在无监督场景中使用收敛速度比较慢调参难度也更高。3.4 软肋二精度目前仍然不足同样的网络结构下SNN 在静态图像分类任务上的精度通常低于 ANN。以 MNIST 为例ANN 可以轻松达到 99% 以上准确率而简单的 SNN 网络通常在 97% 到 98% 之间要逼近 99% 需要引入较深的架构和较长的时间步训练成本很高。这是由脉冲离散化带来的信息损失决定的把连续的浮点数转换为数量有限的时间步脉冲序列必然会丢失精度。因此 SNN 目前更合适的角色是低功耗场景下的补充方案而不是全面取代深度学习。4. 亲手搭一个迷你 SNN从 LIF 神经元到简单分类4.1 环境准备和工具选型在正式写代码之前先确认环境。我推荐用 Python 3.9 以上版本配合两个关键库Numpy 用于基础运算模拟SpikingJelly 或 Norse 用于构建完整的 SNN 网络。SpikingJelly 是国内开发者维护得比较活跃的框架中文文档友好封装了 LIF 神经元、编码器和训练器。如果你只想理解原理先用 Numpy 手写一个 LIF 神经元就够了如果你想实验图像分类这类完整任务直接用 SpikingJelly省时省力。安装方法以 SpikingJelly 为例pip install spikingjelly4.2 用 Numpy 模拟一个 LIF 神经元先用一个最精简的例子理解脉冲发放过程。下面这段代码模拟单个 LIF 神经元在恒定输入电流下的行为import numpy as np import matplotlib.pyplot as plt # LIF 神经元模型参数 tau 10.0 # 膜时间常数 / ms v_rest 0.0 # 静息电位 v_thr 1.0 # 发放阈值 v_reset 0.0 # 发放后的重置电位 r 1.0 # 膜电阻 dt 0.1 # 仿真步长 / ms sim_time 100.0 # 总仿真时间 / ms # 恒定输入电流大小为 1.5阈值为 1应该能周期性发放 I_input 1.5 steps int(sim_time / dt) v v_rest spikes [] v_trace [] for t in range(steps): # 欧拉法更新膜电位 dv (-(v - v_rest) r * I_input) / tau * dt v dv if v v_thr: spikes.append(t * dt) v v_reset v_trace.append(v) print(发放次数:, len(spikes)) print(发放时刻(ms):, [round(x, 1) for x in spikes[:10]])这个示例输出恒定电流下的发放脉冲时刻列表。你运行后会发现神经元每隔一段时间发放一次脉冲这就是最简单的“积分—发放”行为。把 I_input 从 1.5 改成 2.5脉冲频率会变高改成 0.5可能完全不会发放。这就是频率编码的基础输入强度映射到发放频率。4.3 用 SpikingJelly 训练一个简单 SNN 分类器接着用 SpikingJelly 搭建一个可以训练的两层 SNN完成 MNIST 手写数字分类的最小案例。import torch import torch.nn as nn from spikingjelly.activation_based import neuron, functional, layer, encoding from torch.utils.data import DataLoader from torchvision import datasets, transforms # 超参数设置 T 8 # 时间步数 batch_size 64 # 批次大小 lr 0.1 # 学习率 epochs 5 # 训练轮数 # 数据加载MNIST 归一化到 0~1 train_dataset datasets.MNIST( root./data, trainTrue, transformtransforms.ToTensor(), downloadTrue ) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) # 定义脉冲编码器泊松编码将输入像素值转为脉冲概率 encoder encoding.PoissonEncoder() class SimpleSNN(nn.Module): def __init__(self): super().__init__() self.net nn.Sequential( # 28*28784 输入128 隐藏神经元 layer.Linear(784, 128), neuron.LIFNode(), layer.Linear(128, 10), neuron.LIFNode() ) def forward(self, x): return self.net(x) model SimpleSNN() # 这里简化演示使用 Adam 加 MSE 损失 optimizer torch.optim.Adam(model.parameters(), lrlr) loss_func nn.MSELoss() for epoch in range(epochs): for img, label in train_loader: img img.flatten(start_dim1) # (B, 784) # 泊松编码生成 T 个时间步的脉冲序列 spike_seq encoder(img) # 形状 (T, B, 784) # 清零神经元状态 functional.reset_net(model) out_sum 0 # 逐时间步前向 for t in range(T): out_step model(spike_seq[t]) # 每个时间步的输出 out_sum out_step # 取平均输出作为分类结果 out out_sum / T label_onehot torch.nn.functional.one_hot( label, num_classes10 ).float() loss loss_func(out, label_onehot) optimizer.zero_grad() loss.backward() optimizer.step() print(训练完成)这段代码的关键点在几处泊松编码器PoissonEncoder会把每个样本的像素值转换成 T 个时间步的脉冲序列每个时间步单独前向计算不能用一句话概括功能。其实准确说是把像素值作为概率每个时间步随机发放脉冲。每一轮前向之后调用functional.reset_net(model)重置神经元状态这一步不能省否则上一个样本的膜电位残留会影响当前样本。最终的分类输出是 T 个时间步输出的平均结果因为单时间步的输出由于随机性波动会比较大。我刚跑完这个模型时准确率大约在 96.5% 左右虽然不如 ANN但作为最小 SNN 入门实验已经足以理解脉冲驱动的完整工作流了。4.4 参数选择背后的原理为什么 T 取 8不是随便选的。T 越小编码信息越少精度下降但计算速度更快T 越大精度提升到一定程度后会饱和因为像素值编码已经稳定再增加时间步只会增加计算量。MNIST 这类简单图像T 取 4 到 16 之间是一个常见区间。学习率取 0.1 配合 Adam 有一个好处代理梯度的数值范围通常比较小学习率太小会导致参数更新过慢。如果你用更大的网络学习率要相应下调但没有万能的最佳值。这里再提一个容易忽视的点functional.reset_net(model)必须放在每个样本前向开始前调用。我最初因为漏掉这一步模型准确率始终在 10% 左右后来查了一遍代码才发现是上一个样本的膜电位残留导致网络输出乱掉了。5. 训练 SNN 常见的坑与排查思路5.1 代理梯度法的选择陷阱如果你直接像训练 ANN 那样对脉冲发放函数求导Pytorch 会直接报梯度为 None原因是阶跃函数导数处处为 0除了毛刺点。你需要启用代理梯度。SpikingJelly 里可以通过环境变量或直接使用带代理梯度的神经元模块解决比如把neuron.LIFNode换成带surrogate_function的版本from spikingjelly.activation_based import surrogate neuron.LIFNode(surrogate_functionsurrogate.ATan())ATan 代理梯度是我最常用的因为它简单、平稳训练稳定。FastSigmoid 也可以用但需要调 alpha 参数不然梯度过大或过小都会影响收敛。5.2 网络不收敛时先检查这三处第一处是时间步编码是否正确。用泊松编码时如果输入像素值没有归一化到 0 到 1 之间比如像素值还在 0 到 255那么几乎所有时间步上的发放概率都是 1相当于网络接收的都是全 1 脉冲毫无区分度。检查方法是直接把编码后的某个样本打印出来看看像素值和脉冲密度是否正相关。第二处是膜电位重置方式的设置。LIF 有两种标准重置方式硬重置Hard Reset和软重置Soft Reset。硬重置最直观膜电位超过阈值后直接降回静息值软重置则减去阈值保留超出部分。很多框架默认硬重置但某些任务下软重置收敛更好特别是脉冲频率较高时。遇到训练波动大的情况试试切换重置方式往往有奇效。第三处是输出层的解释方式。SNN 的输出有几种常见用法取最后一个时间步的输出、取所有时间步求和/平均输出、取输出神经元的首次脉冲时间。首脉冲时间编码在生物大脑中很常见但在梯度下降训练时不好用因为可导性差。建议工程入门阶段用输出平均或累加稳定且实现简单。5.3 感受野里的向量化技巧如果你把网络扩展到卷积结构比如用脉冲卷积层layer.Conv2d需要注意输入形状是多时间步的[T, B, C, H, W]。如果你在时间步循环里逐帧送入网络GPU 并行度不高训练会很慢。更快的方式是把 T 维作为数据维度一次性送入网络即输入形状是[B, T, C, H, W]同时更新所有时间步。SpikingJelly 支持这种模式但在调用时要注意functional.reset_net仍然需要在批次开始前调用一次而不是每个时间步都重置。如果每个时间步都重置等于失去了时间维度上的积分能力SNN 就退化成了一堆独立的全连接层。5.4 ANN 转 SNN 时的权重调整ANN 转 SNN 的思路是把预训练好的 ReLU 网络替换成 IFIntegrate-and-Fire神经元网络或 LIF 网络通过调整阈值和缩放权重来逼近原网络的输出。这里面有个经典问题ReLU 的输出是连续值而 IF 神经元只输出 0 或 1需要通过发放频率来近似。工程上常用的做法是给每个神经元设置一个“发放阈值归一化”统计训练集上 ANN 某一层的最大激活值然后把该层的脉冲神经元阈值设置成该最大值。这样可以在一定程度上保证脉冲发放频率映射到激活区间的满刻度。不过这种方法对网络深度的敏感度很高层数超过 10 层时累积误差会比较明显需要额外做逐层校准。6. SNN 的硬件方向为什么芯片才是它的真正归宿6.1 从算法到 CMOS 芯片的跨越热词里出现“snn 脉冲神经网络 cmos”这不是偶然。SNN 如果只跑在 GPU 上等于抛弃了它最核心的优势GPU 本身就是为连续矩阵计算设计的处理事件驱动的稀疏脉冲并不擅长。类脑芯片的精品路线基本都是在 CMOS 工艺上实现的TrueNorth 采用 28nmLoihi 采用 14nm都是标准 CMOS 工艺。它们把神经元模型、突触权重、脉冲路由逻辑直接做进硅片里用近存计算或者存算一体的方式绕过冯·诺依曼瓶颈。为什么强调 CMOS因为这个工艺成熟、成本可控、可以直接对接现有半导体产业链。学术界也在探索新型器件比如忆阻器、相变存储、铁电器件等用电阻状态来模拟突触权重但离大规模量产还需要时间。如果你是硬件方向的研究者SNN 加 CMOS 这条路线是值得长期关注的。6.2 软件生态和硬件之间还缺什么目前 SNN 的软件生态相比深度学习有较大差距。PyTorch 到 SpikingJelly 的模拟流程相对成熟但在部署到类脑芯片时需要把模型量化为固定时间步的脉冲序列处理片上突触权重约束以及适配芯片特定的神经元参数。这些都是额外工作。好消息是像 LavaIntel 开源框架、Nengo神经工程框架、SpikingJelly 都在逐步打通“训练—仿真—部署”的闭环虽然尚不流畅但已经有越来越多团队开始在实际项目中使用。如果你想把 SNN 从论文带进产品建议先明确目标你要的是超低功耗的持续感知场景如关键词唤醒、震动监测、可穿戴健康监测还是语义理解类场景前者更适合 SNN 落地后者暂时还是深度学习的领地。7. 我对 SNN 怎么看值得花时间学但别指望短期替代深度学习个人实际体验下来SNN 的确不是一个适合所有人直接上手的技术。它的理论基础横跨神经科学、信号处理和优化理论入门门槛比 ANN 高不少。但是它的设计思路给了我一个很好的提醒大脑不是通过堆乘法来算东西的而是通过时间、事件和稀疏性来完成复杂任务。如果你时间有限我的建议是先跑通本文里的两个最小实验一个 LIF 神经元定性理解脉冲发放一个 SpikingJelly 的 MNIST 分类器走通训练流程。这个过程大概花一个周末的时间之后你对“SNN 为什么叫第三代”这个问题会有一个远比看论文更贴切的理解。另外一个延伸方向是事件相机Event Camera数据也就是 DVS 相机它的输出本身就是异步脉冲事件流。把 DVS 数据和 SNN 结合是目前少见的“数据天然匹配算法”的场景也是我体会最深的 SNN 实际应用方向。后续我会单独写一篇关于 DVS 数据和 SNN 结合的文章把脉冲编码、时间步处理和事件流的管道讲细一些。总的来说SNN 是一条值得长期观察和跟进的路线。它不会在短时间内替代深度学习但在低功耗、时间动态数据、模型可解释性这三个维度上它切中了传统神经网络结构的痛点。只要类脑芯片生态再成熟一些SNN 将来在物联网和边缘智能领域很可能找到自己的位置。
返回列表