果蝇大脑被上传驱动虚拟身体-初探类脑计算

文章目录

  • 写在前面的话
  • 脉冲神经网络(SNN)入门探索
    • 中文讲解
      • [1. 粒子群优化(PSO)算法](#1. 粒子群优化(PSO)算法)
      • [2. LIF 神经元模型与脉冲神经网络(SNN)](#2. LIF 神经元模型与脉冲神经网络(SNN))
      • [3. 程序讲解](#3. 程序讲解)
        • [3.1 常量定义](#3.1 常量定义)
        • [3.2 手写数字样本](#3.2 手写数字样本)
        • [3.3 LIF 神经元类 `LIFNeuron`](#3.3 LIF 神经元类 LIFNeuron)
        • [3.4 SNN 类](#3.4 SNN 类)
        • [3.5 PSO 粒子类 `Particle`](#3.5 PSO 粒子类 Particle)
        • [3.6 PSO 群类 `Swarm`](#3.6 PSO 群类 Swarm)
        • [3.7 适应度函数 `fitness_function`](#3.7 适应度函数 fitness_function)
        • [3.8 主函数 `main`](#3.8 主函数 main)
      • [4. 程序特点](#4. 程序特点)
    • [English Explanation](#English Explanation)
      • [1. Particle Swarm Optimization (PSO)](#1. Particle Swarm Optimization (PSO))
      • [2. LIF Neuron Model and Spiking Neural Network (SNN)](#2. LIF Neuron Model and Spiking Neural Network (SNN))
      • [3. Program Explanation](#3. Program Explanation)
        • [3.1 Constants](#3.1 Constants)
        • [3.2 Handwritten Digit Samples](#3.2 Handwritten Digit Samples)
        • [3.3 LIF Neuron Class `LIFNeuron`](#3.3 LIF Neuron Class LIFNeuron)
        • [3.4 SNN Class](#3.4 SNN Class)
        • [3.5 PSO Particle Struct `Particle`](#3.5 PSO Particle Struct Particle)
        • [3.6 PSO Swarm Class `Swarm`](#3.6 PSO Swarm Class Swarm)
        • [3.7 Fitness Function `fitness_function`](#3.7 Fitness Function fitness_function)
        • [3.8 Main Function `main`](#3.8 Main Function main)
      • [4. Program Features](#4. Program Features)
    • 中文解释
      • [1. 微分方程的含义](#1. 微分方程的含义)
      • [2. 为什么需要离散化?](#2. 为什么需要离散化?)
      • [3. 欧拉法(Euler Method)](#3. 欧拉法(Euler Method))
      • [4. 将LIF微分方程离散化](#4. 将LIF微分方程离散化)
      • [5. 欧拉法的精度与局限性](#5. 欧拉法的精度与局限性)
    • [English Explanation](#English Explanation)
      • [1. Meaning of the Differential Equation](#1. Meaning of the Differential Equation)
      • [2. Why Discretization?](#2. Why Discretization?)
      • [3. Euler Method](#3. Euler Method)
      • [4. Discretizing the LIF Equation](#4. Discretizing the LIF Equation)
      • [5. Accuracy and Limitations of Euler Method](#5. Accuracy and Limitations of Euler Method)
  • [c++ 代码实现](#c++ 代码实现)
  • 更多关于SNN的研究

写在前面的话

As is well known, large language models represent second-generation AI technology. With the help of RAG and fine-tuning, they have been widely applied across various industries. However, a recent news story has quickly gone viral: the entire brain of a fruit fly has been fully uploaded and used to drive a virtual body! Silicon Valley company Eon Systems, based on the LIF model, scanned the entire brain of an adult fruit fly using electron microscopy and reconstructed it with AI, creating a complete "digital brain." This signals that third-generation AI technology is on the verge of a comprehensive explosion, and brain-like computing is not far off. In the future, it is very likely that the human brain could be digitized.


众所周知,大模型是第二代AI技术,借助RAG和微调,它在各行各业得到了广泛的应用,但最近一个新闻迅速火爆全网。

果蝇大脑被完整上传并驱动虚拟身体!硅谷公司 Eon Systems以LIF模型为基础,将成年果蝇的整个大脑通过电子显微镜扫描与AI重建,做成了一套完整的"数字大脑",这意味着第三代AI技术即将进入全面爆发阶段,类脑计算离我们已经不远了,将来人类大脑也可以实现数字化。

脉冲神经网络(SNN)入门探索

下面对脉冲神经网络详细讲解,包括粒子群优化(PSO)算法、LIF 神经元模型和脉冲神经网络(SNN)的基本概念,以及程序的具体实现分析。讲解分为中文和英文两部分。

中文讲解

1. 粒子群优化(PSO)算法

粒子群优化是一种受鸟群觅食行为启发的群体智能优化算法。它通过一群粒子在搜索空间中移动,寻找最优解。

  • 粒子 :每个粒子代表问题的一个候选解,包含位置向量(对应优化参数)和速度向量(决定移动方向和步长)。每个粒子还记录自身历史上找到的最佳位置(个体最优,pbest)和该位置的适应度。
  • 群体 :整个粒子群共享一个全局最佳位置(全局最优,gbest),即所有粒子中适应度最高的位置。
  • 更新规则 :在每次迭代中,每个粒子根据自身当前速度、个体最优和全局最优调整速度,然后更新位置。速度更新公式为:
    v i t + 1 = w ⋅ v i t + c 1 r 1 ( p b e s t i − x i t ) + c 2 r 2 ( g b e s t − x i t ) v_{i}^{t+1} = w \cdot v_{i}^{t} + c_1 r_1 (pbest_i - x_i^t) + c_2 r_2 (gbest - x_i^t) vit+1=w⋅vit+c1r1(pbesti−xit)+c2r2(gbest−xit)
    其中 w w w是惯性权重,控制对先前速度的继承; c 1 , c 2 c_1, c_2 c1,c2是学习因子; r 1 , r 2 r_1, r_2 r1,r2是 [0,1] 之间的随机数。位置更新: x i t + 1 = x i t + v i t + 1 x_i^{t+1} = x_i^t + v_i^{t+1} xit+1=xit+vit+1。
  • 边界处理:当粒子飞出搜索范围时,通常将其拉回边界并重置速度(吸收壁策略)。
  • 适应度函数 :衡量粒子位置优劣的函数,本例中为fitness_function,根据网络对数字样本的分类表现打分。

2. LIF 神经元模型与脉冲神经网络(SNN)

LIF(Leaky Integrate-and-Fire)模型 是一种简化的生物神经元模型,描述了膜电位随时间的变化:

  • 积分 :神经元接收输入电流 I ext I_{\text{ext}} Iext,膜电位 v v v逐渐上升。
  • 漏电 :膜电位会自然衰减向静息电位 V rest V_{\text{rest}} Vrest,由时间常数 τ m \tau_m τm控制。
  • 发放 :当膜电位超过阈值 V thresh V_{\text{thresh}} Vthresh时,神经元发放一个脉冲(动作电位),然后膜电位重置为 V reset V_{\text{reset}} Vreset,并进入一段绝对不应期(refractory),期间不响应输入。
  • 微分方程: τ m d v d t = − ( v − V rest ) + R m I ext \tau_m \frac{dv}{dt} = -(v - V_{\text{rest}}) + R_m I_{\text{ext}} τmdtdv=−(v−Vrest)+RmIext。离散化后,用欧拉法更新。

脉冲神经网络(SNN) 以脉冲(离散事件)作为信息载体,更接近生物神经系统。与传统的ANN不同,SNN引入了时间维度,神经元仅在发放脉冲时传递信息。本例中的SNN为三层全连接结构:

  • 输入层:64个神经元,对应8×8图像的像素值,直接作为电流输入。
  • 隐藏层1:20个LIF神经元。
  • 隐藏层2:20个LIF神经元。
  • 输出层:10个LIF神经元,对应数字0-9。
  • 网络中的权重和偏置是可训练参数,将输入像素值加权后作为下一层神经元的输入电流。

3. 程序讲解

程序整体结构分为以下几个部分:

3.1 常量定义
  • 仿真参数(SIM_TIME, DT, NUM_STEPS 等)和 LIF 神经元参数(V_REST, V_THRESH 等)由 constexpr 定义。
  • 网络结构:N_INPUT, N_HIDDEN1, N_HIDDEN2, N_OUTPUT
  • PSO 参数:粒子数、迭代次数、惯性权重、学习因子、搜索范围等。
3.2 手写数字样本
  • SAMPLES 是一个 vector<vector<double>>,包含10个手工定义的8×8数字图案(每个数字一个样本)。LABELS 是对应的标签。
3.3 LIF 神经元类 LIFNeuron
  • 成员:v(膜电位),refractory(剩余不应期步数)。
  • 方法:step(double I_ext) 根据输入电流更新神经元状态,返回是否发放脉冲。实现了欧拉法离散化和阈值检查。
3.4 SNN 类
  • 包含三个神经元层(hidden1, hidden2, output)和权重矩阵(w_in_h1, w_h1_h2, w_h2_out)以及偏置(bias_h1, bias_h2, bias_out)。
  • 构造函数:从参数向量 params 中解析所有权重和偏置。
  • simulate 方法:对一个输入样本(像素向量)进行仿真,返回输出层每个神经元的脉冲计数。仿真过程:
    1. 复制当前网络状态(net_copy)以独立仿真。
    2. 对每个时间步:
      • 计算隐藏层1输入电流(像素值乘以权重 + 偏置)。
      • 更新隐藏层1,记录发放状态。
      • 根据隐藏层1的发放,通过权重计算隐藏层2的输入电流。
      • 更新隐藏层2,记录发放。
      • 根据隐藏层2的发放,通过权重计算输出层输入电流。
      • 更新输出层,若发放则相应神经元的脉冲计数加1。
3.5 PSO 粒子类 Particle
  • 存储位置、速度、个体最优位置和最优适应度。
  • 构造函数随机初始化位置和速度,个体最优初始化为当前位置。
3.6 PSO 群类 Swarm
  • 包含粒子数组、全局最优位置和最优适应度。
  • update 方法:执行一次 PSO 迭代。
    1. 评估所有粒子的适应度(调用 fitness_function),更新个体最优和全局最优。
    2. 对每个粒子,按 PSO 公式更新速度和位置,并进行边界处理(吸收壁)。
3.7 适应度函数 fitness_function
  • 对每个样本:
    1. 用当前参数初始化 SNN。
    2. 仿真样本,得到输出层脉冲计数。
    3. 找出脉冲数最多的输出神经元索引。
    4. 若该索引等于标签,则适应度增加该脉冲数;否则减去该脉冲数。
  • 对所有样本求和得到总适应度。最大化该适应度使网络正确分类并产生高脉冲。
3.8 主函数 main
  • 计算参数维度 dim
  • 输出基本信息。
  • 初始化 Swarm 对象。
  • 进入 PSO 主循环,每次迭代调用 swarm.update 并打印当前最优适应度。
  • 迭代结束后输出最优适应度、部分最优参数和总耗时。

4. 程序特点

  • 使用纯 C++17 标准,vector 自动管理内存,避免手动 new/delete
  • 采用 mt19937 生成高质量随机数。
  • 仿真和优化逻辑清晰,易于扩展。

English Explanation

1. Particle Swarm Optimization (PSO)

Particle Swarm Optimization is a population-based stochastic optimization technique inspired by the social behavior of bird flocking.

  • Particle : Each particle represents a candidate solution in the search space. It has a position vector (the parameters to optimize) and a velocity vector (direction and step size). Each particle also remembers its own best position (pbest) and the corresponding fitness value.
  • Swarm : The entire group shares a global best position (gbest) -- the best position found by any particle.
  • Update Rules : At each iteration, every particle updates its velocity and position based on its current velocity, its personal best, and the global best:
    v i t + 1 = w ⋅ v i t + c 1 r 1 ( p b e s t i − x i t ) + c 2 r 2 ( g b e s t − x i t ) v_{i}^{t+1} = w \cdot v_{i}^{t} + c_1 r_1 (pbest_i - x_i^t) + c_2 r_2 (gbest - x_i^t) vit+1=w⋅vit+c1r1(pbesti−xit)+c2r2(gbest−xit)
    where w w wis the inertia weight, c 1 , c 2 c_1, c_2 c1,c2are acceleration coefficients, and r 1 , r 2 r_1, r_2 r1,r2are random numbers in [0,1]. Position update: x i t + 1 = x i t + v i t + 1 x_i^{t+1} = x_i^t + v_i^{t+1} xit+1=xit+vit+1.
  • Boundary Handling: If a particle flies outside the search range, it is pulled back to the boundary and its velocity is set to zero (absorbing walls).
  • Fitness Function : A function that evaluates how good a particle's position is. In this code, fitness_function measures the classification performance of the SNN on handwritten digits.

2. LIF Neuron Model and Spiking Neural Network (SNN)

LIF (Leaky Integrate-and-Fire) model is a simplified neuron model describing the dynamics of the membrane potential:

  • Integration : The neuron receives an input current I ext I_{\text{ext}} Iext, causing the membrane potential v v vto rise.
  • Leak : The potential naturally decays toward the resting potential V rest V_{\text{rest}} Vrest, governed by the time constant τ m \tau_m τm.
  • Spike : When v v vexceeds a threshold V thresh V_{\text{thresh}} Vthresh, the neuron fires a spike (action potential). The potential is then reset to V reset V_{\text{reset}} Vreset, and the neuron enters an absolute refractory period (refractory) during which it ignores inputs.
  • Differential equation: τ m d v d t = − ( v − V rest ) + R m I ext \tau_m \frac{dv}{dt} = -(v - V_{\text{rest}}) + R_m I_{\text{ext}} τmdtdv=−(v−Vrest)+RmIext. Discretized using Euler's method.

Spiking Neural Network (SNN) uses spikes (discrete events) as information carriers, mimicking biological neural systems. Unlike traditional ANNs, SNNs incorporate time and communicate via spikes. The network in this code is a three-layer fully-connected SNN:

  • Input layer: 64 neurons (8×8 pixels), each pixel value directly used as input current.
  • Hidden layer 1: 20 LIF neurons.
  • Hidden layer 2: 20 LIF neurons.
  • Output layer: 10 LIF neurons (digits 0--9).
  • The weights and biases are trainable parameters that scale the input currents to subsequent layers.

3. Program Explanation

The program is structured as follows:

3.1 Constants
  • Simulation parameters (SIM_TIME, DT, NUM_STEPS, etc.) and LIF parameters (V_REST, V_THRESH, etc.) are defined as constexpr.
  • Network dimensions: N_INPUT, N_HIDDEN1, N_HIDDEN2, N_OUTPUT.
  • PSO parameters: number of particles, iterations, inertia weight, learning factors, search range, etc.
3.2 Handwritten Digit Samples
  • SAMPLES is a vector<vector<double>> containing 10 handcrafted 8×8 patterns (one per digit). LABELS holds the corresponding labels.
3.3 LIF Neuron Class LIFNeuron
  • Members: v (membrane potential), refractory (remaining refractory steps).
  • Method: step(double I_ext) updates the neuron given an input current, returns true if a spike occurs. It implements the Euler discretization and threshold check.
3.4 SNN Class
  • Contains three neuron layers (hidden1, hidden2, output) and weight matrices (w_in_h1, w_h1_h2, w_h2_out) and biases (bias_h1, bias_h2, bias_out).
  • Constructor: Extracts weights and biases from the flat params vector.
  • simulate method: Runs the network on a single input sample and returns spike counts for each output neuron. Steps:
    1. Make a copy of the network (net_copy) to preserve state for independent simulation.
    2. For each time step:
      • Compute input current to hidden layer 1 (pixel values × weights + bias).
      • Update hidden layer 1, record spikes.
      • Compute input to hidden layer 2 based on spikes from hidden layer 1 and weights.
      • Update hidden layer 2, record spikes.
      • Compute input to output layer based on spikes from hidden layer 2 and weights.
      • Update output layer; if a spike occurs, increment the corresponding neuron's count.
3.5 PSO Particle Struct Particle
  • Stores position, velocity, personal best position, and personal best fitness.
  • Constructor randomly initializes position and velocity; personal best initially set to current position.
3.6 PSO Swarm Class Swarm
  • Contains an array of particles, global best position, and global best fitness.
  • update method performs one PSO iteration:
    1. Evaluate fitness of all particles (calling fitness_function), update personal bests and global best.
    2. For each particle, update velocity and position using the PSO formula, then apply boundary clipping (absorbing walls).
3.7 Fitness Function fitness_function
  • For each sample:
    1. Construct an SNN with the current parameters.
    2. Simulate the sample to get output spike counts.
    3. Find the output neuron with the highest spike count.
    4. If that index matches the label, add its spike count to total fitness; otherwise subtract it.
  • Sum over all samples to obtain the final fitness. Maximizing this fitness encourages correct classification with high spike rates.
3.8 Main Function main
  • Computes the total dimension dim of the parameter vector.
  • Prints basic info.
  • Creates a Swarm object.
  • Enters the main PSO loop, printing the best fitness after each iteration.
  • After finishing, outputs the best fitness, the first 10 parameters, and the total elapsed time.

4. Program Features

  • Written in pure C++17, using std::vector for automatic memory management.
  • Uses std::mt19937 for high-quality random number generation.
  • Clear separation of simulation and optimization logic, easy to modify or extend.

中文解释

1. 微分方程的含义

LIF(漏积分放电)神经元模型的膜电位变化可以用以下微分方程描述:

τ m d v d t = − ( v − V rest ) + R m I ext \tau_m \frac{dv}{dt} = -(v - V_{\text{rest}}) + R_m I_{\text{ext}} τmdtdv=−(v−Vrest)+RmIext

其中:

  • v v v是神经元的膜电位(单位:mV),即细胞膜内外的电压差。
  • t 时间(单位:ms)。
  • d v d t \frac{dv}{dt} dtdv表示膜电位随时间的变化率(斜率)。
  • τ m \tau_m τm是膜时间常数 (单位:ms),它决定了膜电位对输入电流的响应速度。 τ m = R m C m \tau_m = R_m C_m τm=RmCm,其中 R m R_m Rm是膜电阻, C m C_m Cm是膜电容。
  • V rest V_{\text{rest}} Vrest是静息电位,即没有外部输入时膜电位的稳定值。
  • R m R_m Rm是膜电阻(单位:MΩ),它反映了离子通道对电流的阻碍作用。
  • I ext I_{\text{ext}} Iext是外部注入电流(单位:nA),来自突触输入或其他刺激。

这个方程描述了膜电位的动力学行为:

  • 漏电项 -(v - V_{\\text{rest}}) :当膜电位 v v v高于静息电位时,该项为负,使电位下降;反之亦然。这模拟了离子通过膜通道的被动扩散,使电位趋向静息电位。
  • 驱动项 R_m I_{\\text{ext}} :外部电流通过膜电阻产生电压,使电位升高。

因此,方程表示膜电位的变化由"漏电"和"输入驱动"共同决定。当输入电流为零时,电位指数衰减到静息电位;当有恒定电流时,电位会上升到一个新的稳态值。

2. 为什么需要离散化?

计算机仿真无法处理连续的时间,必须将时间离散化为一系列离散的时刻(时间步长 Δ t \Delta t Δt)。因此,需要将连续的微分方程转化为离散的差分方程,以便在每个时间步更新膜电位。

3. 欧拉法(Euler Method)

欧拉法是最简单的数值积分方法,它用当前时刻的斜率来近似下一时刻的值。具体来说,对于微分方程 d v d t = f ( v , t ) \frac{dv}{dt} = f(v, t) dtdv=f(v,t),欧拉法给出:

v ( t + Δ t ) ≈ v ( t ) + Δ t ⋅ f ( v ( t ) , t ) v(t + \Delta t) \approx v(t) + \Delta t \cdot f(v(t), t) v(t+Δt)≈v(t)+Δt⋅f(v(t),t)

即将当前时刻的变化率乘以时间步长,加到当前值上,得到下一时刻的近似值。

4. 将LIF微分方程离散化

在我们的方程中, d v d t = 1 τ m [ − ( v − V rest ) + R m I ext ] \frac{dv}{dt} = \frac{1}{\tau_m} \left[ -(v - V_{\text{rest}}) + R_m I_{\text{ext}} \right] dtdv=τm1[−(v−Vrest)+RmIext]。应用欧拉法:

v ( t + Δ t ) = v ( t ) + Δ t ⋅ 1 τ m [ − ( v ( t ) − V rest ) + R m I ext ( t ) ] v(t + \Delta t) = v(t) + \Delta t \cdot \frac{1}{\tau_m} \left[ -(v(t) - V_{\text{rest}}) + R_m I_{\text{ext}}(t) \right] v(t+Δt)=v(t)+Δt⋅τm1[−(v(t)−Vrest)+RmIext(t)]

通常我们记 v t v_t vt为当前时刻的膜电位, v t + 1 v_{t+1} vt+1为下一时刻的膜电位, Δ t \Delta t Δt为固定时间步长(程序中用 DT 表示)。于是更新公式为:

v t + 1 = v t + Δ t τ m [ − ( v t − V rest ) + R m I ext ] v_{t+1} = v_t + \frac{\Delta t}{\tau_m} \left[ -(v_t - V_{\text{rest}}) + R_m I_{\text{ext}} \right] vt+1=vt+τmΔt[−(vt−Vrest)+RmIext]

这就是程序中 step_neuron 函数里所用的公式:

cpp 复制代码
double dv = (DT / TAU_M) * (-(v - V_REST) + R_M * I_ext);
v += dv;
  • 首先计算变化量 dv,然后加到当前膜电位上。
  • 之后检查是否超过阈值,若超过则发放脉冲,并重置电位和不应期。

5. 欧拉法的精度与局限性

欧拉法是一阶精度,误差与步长 Δ t \Delta t Δt成正比。为了保证仿真稳定,通常要求步长远小于系统的最小时间常数(此处为 τ m \tau_m τm)。本程序中 Δ t = 0.5 \Delta t = 0.5 Δt=0.5ms, τ m = 10 \tau_m = 10 τm=10ms,满足稳定性条件。


English Explanation

1. Meaning of the Differential Equation

The membrane potential dynamics of a LIF (Leaky Integrate-and-Fire) neuron are described by the following differential equation:

τ m d v d t = − ( v − V rest ) + R m I ext \tau_m \frac{dv}{dt} = -(v - V_{\text{rest}}) + R_m I_{\text{ext}} τmdtdv=−(v−Vrest)+RmIext

Where:

  • v v vis the membrane potential (in mV), the voltage difference across the cell membrane.
  • t t tis time (in ms).
  • d v d t \frac{dv}{dt} dtdvis the rate of change of the membrane potential over time.
  • τ m \tau_m τmis the membrane time constant (in ms), which determines how quickly the potential responds to input. τ m = R m C m \tau_m = R_m C_m τm=RmCm, with R m R_m Rmbeing the membrane resistance and C m C_m Cmthe membrane capacitance.
  • V rest V_{\text{rest}} Vrestis the resting potential, the stable value when no external current is applied.
  • R m R_m Rmis the membrane resistance (in MΩ), representing the opposition to ionic current.
  • I ext I_{\text{ext}} Iextis the external input current (in nA), originating from synaptic inputs or other stimuli.

This equation captures two key processes:

  • Leakage term − ( v − V rest ) -(v - V_{\text{rest}}) −(v−Vrest): When v v vis above V rest V_{\text{rest}} Vrest, this term is negative, driving the potential downward; when below, it is positive. This models passive diffusion of ions through membrane channels, always pushing the potential back toward rest.
  • Driving term R m I ext R_m I_{\text{ext}} RmIext: External current flowing through the membrane resistance generates a voltage, increasing the potential.

Thus, the rate of change is a balance between leakage and input drive. With zero input, the potential decays exponentially to rest; with constant current, it rises to a new steady state.

2. Why Discretization?

Computer simulations cannot handle continuous time; we must discretize time into a series of steps (with step size Δ t \Delta t Δt). Therefore, the continuous differential equation must be converted into a discrete difference equation to update the membrane potential at each time step.

3. Euler Method

The Euler method is the simplest numerical integration technique. It approximates the value at the next time step using the slope (derivative) at the current time:

v ( t + Δ t ) ≈ v ( t ) + Δ t ⋅ f ( v ( t ) , t ) v(t + \Delta t) \approx v(t) + \Delta t \cdot f(v(t), t) v(t+Δt)≈v(t)+Δt⋅f(v(t),t)

where d v d t = f ( v , t ) \frac{dv}{dt} = f(v, t) dtdv=f(v,t). Essentially, we add the current slope multiplied by the step size to the current value.

4. Discretizing the LIF Equation

For our LIF model, d v d t = 1 τ m [ − ( v − V rest ) + R m I ext ] \frac{dv}{dt} = \frac{1}{\tau_m} \left[ -(v - V_{\text{rest}}) + R_m I_{\text{ext}} \right] dtdv=τm1[−(v−Vrest)+RmIext]. Applying the Euler method:

v ( t + Δ t ) = v ( t ) + Δ t ⋅ 1 τ m [ − ( v ( t ) − V rest ) + R m I ext ( t ) ] v(t + \Delta t) = v(t) + \Delta t \cdot \frac{1}{\tau_m} \left[ -(v(t) - V_{\text{rest}}) + R_m I_{\text{ext}}(t) \right] v(t+Δt)=v(t)+Δt⋅τm1[−(v(t)−Vrest)+RmIext(t)]

Denoting v t v_t vtas the current potential and v t + 1 v_{t+1} vt+1as the next one, with fixed step Δ t \Delta t Δt(represented by DT in code), we get:

v t + 1 = v t + Δ t τ m [ − ( v t − V rest ) + R m I ext ] v_{t+1} = v_t + \frac{\Delta t}{\tau_m} \left[ -(v_t - V_{\text{rest}}) + R_m I_{\text{ext}} \right] vt+1=vt+τmΔt[−(vt−Vrest)+RmIext]

This is exactly the update implemented in the step_neuron function:

cpp 复制代码
double dv = (DT / TAU_M) * (-(v - V_REST) + R_M * I_ext);
v += dv;
  • First compute the change dv, then add it to the current potential.
  • After updating, the code checks whether the threshold is exceeded; if so, it emits a spike, resets the potential, and enters a refractory period.

5. Accuracy and Limitations of Euler Method

The Euler method is first-order accurate, meaning the error is proportional to the step size Δ t \Delta t Δt. To ensure stability, the step size should be much smaller than the smallest time constant of the system (here τ m = 10 \tau_m = 10 τm=10ms). Our program uses Δ t = 0.5 \Delta t = 0.5 Δt=0.5ms, which satisfies this condition and yields sufficiently accurate results for this neuron model.

c++ 代码实现

在vs2022中调试通过,具体参数和功能有待于各位朋友去优化和扩充。

cpp 复制代码
/**
 * 文件: exalnn.cpp
 * 描述: 三层 LIF 脉冲神经网络 + PSO 手写数字识别 (纯 C++ 版)
 * 编译: Visual Studio 2022 创建空项目,添加此文件,编译运行 (建议 Release 模式)
 */

#include <iostream>
#include <vector>
#include <random>
#include <cmath>
#include <chrono>

using namespace std;

// ==================== 常量定义 (C++ 风格) ====================
constexpr double SIM_TIME = 500.0;          // 仿真时间 (ms)
constexpr double DT = 0.5;                   // 时间步长 (ms)
constexpr int NUM_STEPS = static_cast<int>(SIM_TIME / DT);
constexpr double T_REFRACT = 2.0;             // 不应期 (ms)
constexpr int REFRACT_STEPS = static_cast<int>(T_REFRACT / DT);

// LIF 神经元参数
constexpr double V_REST = -70.0;              // 静息电位 (mV)
constexpr double V_RESET = -75.0;              // 重置电位
constexpr double V_THRESH = -55.0;             // 发放阈值
constexpr double TAU_M = 10.0;                 // 膜时间常数 (ms)
constexpr double R_M = 10.0;                   // 膜电阻 (MOhm)

// 网络结构
constexpr int N_INPUT = 64;                    // 输入层 (8x8像素)
constexpr int N_HIDDEN1 = 20;                  // 第一隐藏层
constexpr int N_HIDDEN2 = 20;                  // 第二隐藏层
constexpr int N_OUTPUT = 10;                    // 输出层 (数字0-9)

// PSO 参数
constexpr int N_PARTICLES = 300;                 // 粒子数
constexpr int MAX_ITER = 200;                    // 迭代次数
constexpr double W = 0.7;                        // 惯性权重
constexpr double C1 = 1.5;                       // 个体学习因子
constexpr double C2 = 1.5;                       // 社会学习因子
constexpr double W_MAX = 5.0;                    // 权重搜索范围 [-W_MAX, W_MAX]
constexpr double INIT_VEL_SCALE = 0.1;           // 初始速度比例

// ==================== 手写数字样本数据 (手工定义) ====================
constexpr int N_SAMPLES = 10;
const vector<vector<double>> SAMPLES = {
    // 数字 0
    {0,1,1,1,1,1,1,0,
     1,0,0,0,0,0,0,1,
     1,0,0,0,0,0,0,1,
     1,0,0,0,0,0,0,1,
     1,0,0,0,0,0,0,1,
     1,0,0,0,0,0,0,1,
     1,0,0,0,0,0,0,1,
     0,1,1,1,1,1,1,0},
     // 数字 1
     {0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0,
      0,0,1,0,0,0,0,0},
      // 数字 2
      {1,1,1,1,1,1,1,1,
       0,0,0,0,0,0,0,1,
       0,0,0,0,0,0,1,0,
       0,0,0,0,0,1,0,0,
       0,0,0,0,1,0,0,0,
       0,0,0,1,0,0,0,0,
       0,0,1,0,0,0,0,0,
       1,1,1,1,1,1,1,1},
       // 数字 3
       {1,1,1,1,1,1,1,0,
        0,0,0,0,0,0,0,1,
        0,0,0,0,0,0,0,1,
        1,1,1,1,1,1,1,0,
        0,0,0,0,0,0,0,1,
        0,0,0,0,0,0,0,1,
        1,1,1,1,1,1,1,0,
        0,0,0,0,0,0,0,0},
        // 数字 4
        {0,0,1,0,0,1,0,0,
         0,0,1,0,0,1,0,0,
         0,0,1,0,0,1,0,0,
         0,0,1,1,1,1,1,0,
         0,0,0,0,0,1,0,0,
         0,0,0,0,0,1,0,0,
         0,0,0,0,0,1,0,0,
         0,0,0,0,0,0,0,0},
         // 数字 5
         {1,1,1,1,1,1,1,1,
          1,0,0,0,0,0,0,0,
          1,0,0,0,0,0,0,0,
          1,1,1,1,1,1,1,0,
          0,0,0,0,0,0,0,1,
          0,0,0,0,0,0,0,1,
          1,1,1,1,1,1,1,0,
          0,0,0,0,0,0,0,0},
          // 数字 6
          {0,1,1,1,1,1,1,0,
           1,0,0,0,0,0,0,1,
           1,0,0,0,0,0,0,0,
           1,0,0,0,0,0,0,0,
           1,1,1,1,1,1,1,0,
           1,0,0,0,0,0,0,1,
           1,0,0,0,0,0,0,1,
           0,1,1,1,1,1,1,0},
           // 数字 7
           {1,1,1,1,1,1,1,1,
            0,0,0,0,0,0,0,1,
            0,0,0,0,0,0,1,0,
            0,0,0,0,0,1,0,0,
            0,0,0,0,1,0,0,0,
            0,0,0,1,0,0,0,0,
            0,0,1,0,0,0,0,0,
            0,1,0,0,0,0,0,0},
            // 数字 8
            {0,1,1,1,1,1,1,0,
             1,0,0,0,0,0,0,1,
             1,0,0,0,0,0,0,1,
             0,1,1,1,1,1,1,0,
             1,0,0,0,0,0,0,1,
             1,0,0,0,0,0,0,1,
             1,0,0,0,0,0,0,1,
             0,1,1,1,1,1,1,0},
             // 数字 9
             {0,1,1,1,1,1,1,0,
              1,0,0,0,0,0,0,1,
              1,0,0,0,0,0,0,1,
              1,0,0,0,0,0,0,1,
              0,1,1,1,1,1,1,0,
              0,0,0,0,0,0,0,1,
              1,0,0,0,0,0,0,1,
              0,1,1,1,1,1,1,0}
};
const vector<int> LABELS = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// ==================== LIF 神经元类 ====================
struct LIFNeuron {
    double v;           // 膜电位
    int refractory;     // 剩余不应期步数

    LIFNeuron() : v(V_REST), refractory(0) {}

    // 单步更新,返回是否发放脉冲
    bool step(double I_ext) {
        if (refractory > 0) {
            refractory--;
            return false;
        }
        double dv = (DT / TAU_M) * (-(v - V_REST) + R_M * I_ext);
        v += dv;
        if (v >= V_THRESH) {
            v = V_RESET;
            refractory = REFRACT_STEPS;
            return true;
        }
        return false;
    }
};

// ==================== 三层 SNN 类 ====================
class SNN {
public:
    vector<LIFNeuron> hidden1;
    vector<LIFNeuron> hidden2;
    vector<LIFNeuron> output;

    // 权重 (一维数组,按行主序存储)
    vector<double> w_in_h1;      // [N_HIDDEN1 * N_INPUT]
    vector<double> w_h1_h2;      // [N_HIDDEN2 * N_HIDDEN1]
    vector<double> w_h2_out;     // [N_OUTPUT * N_HIDDEN2]
    vector<double> bias_h1;      // [N_HIDDEN1]
    vector<double> bias_h2;      // [N_HIDDEN2]
    vector<double> bias_out;     // [N_OUTPUT]

    // 构造函数:从参数向量初始化网络
    SNN(const vector<double>& params) {
        hidden1.resize(N_HIDDEN1);
        hidden2.resize(N_HIDDEN2);
        output.resize(N_OUTPUT);

        w_in_h1.resize(N_HIDDEN1 * N_INPUT);
        w_h1_h2.resize(N_HIDDEN2 * N_HIDDEN1);
        w_h2_out.resize(N_OUTPUT * N_HIDDEN2);
        bias_h1.resize(N_HIDDEN1);
        bias_h2.resize(N_HIDDEN2);
        bias_out.resize(N_OUTPUT);

        // 解析 params
        size_t pos = 0;
        for (int i = 0; i < N_HIDDEN1 * N_INPUT; ++i) w_in_h1[i] = params[pos++];
        for (int i = 0; i < N_HIDDEN2 * N_HIDDEN1; ++i) w_h1_h2[i] = params[pos++];
        for (int i = 0; i < N_OUTPUT * N_HIDDEN2; ++i) w_h2_out[i] = params[pos++];
        for (int i = 0; i < N_HIDDEN1; ++i) bias_h1[i] = params[pos++];
        for (int i = 0; i < N_HIDDEN2; ++i) bias_h2[i] = params[pos++];
        for (int i = 0; i < N_OUTPUT; ++i) bias_out[i] = params[pos++];
    }

    // 仿真单个样本,返回输出层各神经元脉冲计数
    vector<int> simulate(const vector<double>& input) const {
        vector<int> spike_counts(N_OUTPUT, 0);
        // 每次仿真需要复制神经元状态,因此将网络拷贝一份(非 const 操作)
        SNN net_copy = *this;   // 利用默认拷贝构造函数(浅拷贝成员 vector 会复制数据)

        for (int t = 0; t < NUM_STEPS; ++t) {
            // 计算隐藏层1输入
            vector<double> I_h1(N_HIDDEN1);
            for (int i = 0; i < N_HIDDEN1; ++i) {
                I_h1[i] = bias_h1[i];
                for (int j = 0; j < N_INPUT; ++j) {
                    I_h1[i] += w_in_h1[i * N_INPUT + j] * input[j];
                }
            }

            // 更新隐藏层1,记录发放
            vector<bool> spike_h1(N_HIDDEN1);
            for (int i = 0; i < N_HIDDEN1; ++i) {
                spike_h1[i] = net_copy.hidden1[i].step(I_h1[i]);
            }

            // 计算隐藏层2输入
            vector<double> I_h2(N_HIDDEN2, 0.0);
            for (int i = 0; i < N_HIDDEN2; ++i) {
                I_h2[i] = bias_h2[i];
                for (int j = 0; j < N_HIDDEN1; ++j) {
                    if (spike_h1[j]) {
                        I_h2[i] += w_h1_h2[i * N_HIDDEN1 + j];
                    }
                }
            }

            // 更新隐藏层2,记录发放
            vector<bool> spike_h2(N_HIDDEN2);
            for (int i = 0; i < N_HIDDEN2; ++i) {
                spike_h2[i] = net_copy.hidden2[i].step(I_h2[i]);
            }

            // 计算输出层输入
            vector<double> I_out(N_OUTPUT, 0.0);
            for (int i = 0; i < N_OUTPUT; ++i) {
                I_out[i] = bias_out[i];
                for (int j = 0; j < N_HIDDEN2; ++j) {
                    if (spike_h2[j]) {
                        I_out[i] += w_h2_out[i * N_HIDDEN2 + j];
                    }
                }
            }

            // 更新输出层,累计脉冲
            for (int i = 0; i < N_OUTPUT; ++i) {
                if (net_copy.output[i].step(I_out[i])) {
                    spike_counts[i]++;
                }
            }
        }
        return spike_counts;
    }
};

// ==================== PSO 粒子类 ====================
struct Particle {
    vector<double> position;
    vector<double> velocity;
    vector<double> best_pos;
    double best_fitness;

    Particle(int dim) : position(dim), velocity(dim), best_pos(dim), best_fitness(-1e30) {
        static mt19937 rng(static_cast<unsigned>(chrono::steady_clock::now().time_since_epoch().count()));
        uniform_real_distribution<double> dist(-W_MAX, W_MAX);
        uniform_real_distribution<double> vel_dist(-W_MAX * INIT_VEL_SCALE, W_MAX * INIT_VEL_SCALE);

        for (int i = 0; i < dim; ++i) {
            position[i] = dist(rng);
            velocity[i] = vel_dist(rng);
            best_pos[i] = position[i];
        }
    }
};

// ==================== PSO 群类 ====================
class Swarm {
public:
    vector<Particle> particles;
    vector<double> global_best_pos;
    double global_best_fitness;
    int dim;

    Swarm(int dim_) : dim(dim_), global_best_pos(dim_), global_best_fitness(-1e30) {
        particles.reserve(N_PARTICLES);
        for (int i = 0; i < N_PARTICLES; ++i) {
            particles.emplace_back(dim);
        }
    }

    // 更新粒子群 (使用给定的适应度函数)
    void update(double (*fitness_func)(const vector<double>&)) {
        static mt19937 rng(static_cast<unsigned>(chrono::steady_clock::now().time_since_epoch().count()));
        uniform_real_distribution<double> dist01(0.0, 1.0);

        // 评估适应度
        for (auto& p : particles) {
            double fitness = fitness_func(p.position);
            if (fitness > p.best_fitness) {
                p.best_fitness = fitness;
                p.best_pos = p.position;
            }
            if (fitness > global_best_fitness) {
                global_best_fitness = fitness;
                global_best_pos = p.position;
            }
        }

        // 更新速度和位置
        for (auto& p : particles) {
            for (int j = 0; j < dim; ++j) {
                double r1 = dist01(rng);
                double r2 = dist01(rng);
                double vel = W * p.velocity[j]
                    + C1 * r1 * (p.best_pos[j] - p.position[j])
                    + C2 * r2 * (global_best_pos[j] - p.position[j]);
                p.velocity[j] = vel;
                p.position[j] += vel;

                // 边界处理 (吸收壁)
                if (p.position[j] > W_MAX) {
                    p.position[j] = W_MAX;
                    p.velocity[j] = 0.0;
                }
                else if (p.position[j] < -W_MAX) {
                    p.position[j] = -W_MAX;
                    p.velocity[j] = 0.0;
                }
            }
        }
    }
};

// ==================== 适应度函数 (需要全局样本) ====================
double fitness_function(const vector<double>& params) {
    double total_fitness = 0.0;
    for (int s = 0; s < N_SAMPLES; ++s) {
        SNN net(params);
        auto spike_counts = net.simulate(SAMPLES[s]);

        // 找出脉冲最多的输出神经元
        int max_spike = -1;
        int max_idx = -1;
        for (int i = 0; i < N_OUTPUT; ++i) {
            if (spike_counts[i] > max_spike) {
                max_spike = spike_counts[i];
                max_idx = i;
            }
        }
        if (max_idx == LABELS[s]) {
            total_fitness += max_spike;
        }
        else {
            total_fitness -= max_spike;
        }
    }
    return total_fitness;
}

// ==================== 主函数 ====================
int main() {
    // 计算参数维度
    int dim = N_HIDDEN1 * N_INPUT + N_HIDDEN2 * N_HIDDEN1 + N_OUTPUT * N_HIDDEN2
        + N_HIDDEN1 + N_HIDDEN2 + N_OUTPUT;

    cout << "三层 LIF-SNN 手写数字识别 (PSO 优化)" << endl;
    cout << "网络结构: " << N_INPUT << " -> " << N_HIDDEN1 << " -> "
        << N_HIDDEN2 << " -> " << N_OUTPUT << endl;
    cout << "优化参数维度: " << dim << endl;
    cout << "粒子数: " << N_PARTICLES << ", 迭代次数: " << MAX_ITER << endl;

    // 初始化粒子群
    Swarm swarm(dim);

    // 开始计时 (可选)
    auto start_time = chrono::steady_clock::now();

    // PSO 主循环
    for (int iter = 1; iter <= MAX_ITER; ++iter) {
        swarm.update(fitness_function);
        cout << "迭代 " << iter << ", 最优适应度: " << fixed << swarm.global_best_fitness << endl;
    }

    auto end_time = chrono::steady_clock::now();
    auto elapsed_ms = chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count();

    cout << "\n优化完成!最优适应度: " << swarm.global_best_fitness << endl;
    cout << "最优参数前10个值: ";
    for (int i = 0; i < min(10, dim); ++i) {
        cout << swarm.global_best_pos[i] << " ";
    }
    cout << "\n总耗时: " << elapsed_ms << " ms" << endl;

    return 0;
}

更多关于SNN的研究

我的开源仓库:https://gitee.com/waterruby/exlann

相关推荐
十铭忘1 小时前
EgoPoseFormer v2:解决 AR/VR 场景中的第一视角人体动捕问题
人工智能·计算机视觉·ar·vr
东离与糖宝1 小时前
Gradle 9.4爆改Java构建:编译速度提升300%,微服务多模块一键优化
java·人工智能
星爷AG I1 小时前
14-5 运动控制的生态学理论(AGI基础理论)
人工智能·机器学习·agi
yukai080082 小时前
【203篇系列】046 基于openclaw的agent swarm
人工智能
吴佳浩 Alben2 小时前
OpenClaw macOS 完整安装与本地模型配置教程(实战版)
人工智能·macos
AdMergeX2 小时前
出海行业热点 | App开发商起诉苹果抄袭;欧盟要求Google开放Android AI权限;Google搜索推AI对话模式;中国小游戏冲上美国游戏总榜;
android·人工智能·游戏
八月瓜科技2 小时前
擎策·知海全球专利数据库 技术赋能检索 让科技创新少走弯路
大数据·数据库·人工智能·科技·深度学习·娱乐
Fuliy962 小时前
第三阶段:进化与群体智能 (Evolutionary & Swarm Intelligence)
人工智能·笔记·python·学习·算法
淘矿人2 小时前
【claude】05_Claude Skill 实战案例精选(上):开发类技能+weelinking中转服务
大数据·人工智能·python·elasticsearch·搜索引擎·cloudera