脉冲神经网络(SNN)基础

概述

脉冲神经网络(Spiking Neural Network, SNN)是第三代神经网络,其核心特点是使用离散的**脉冲序列(Spike Train)**进行信息传递。与传统ANN相比,SNN更接近真实生物神经元的工作方式,具有时间动力学特性。1

┌─────────────────────────────────────────────────────────────┐
│                     神经网络进化历程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  第一代:阈值神经元 (McCulloch-Pitts, 1943)                  │
│  └─→ 输出:0/1  二值信号                                     │
│                                                             │
│  第二代:连续激活神经网络 (1980s-至今)                        │
│  └─→ 输出:sigmoid, ReLU 等连续值                            │
│                                                             │
│  第三代:脉冲神经网络 (1990s-至今)                            │
│  └─→ 输出:离散脉冲序列,时间动力学                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1. 脉冲神经元的生物学背景

1.1 生物神经元结构

                    轴突末梢
                       ↓
    ┌─────────────────────────────────────┐
    │           轴突 (Axon)                │
    │  ════════════════════════════════→  │
    └─────────────────────────────────────┘
                       ↑
                  轴突丘
                  (Axon Hillock)
                       │
    ┌──────────────────┼──────────────────┐
    │                  │                  │
    │    树突          │    树突          │
    │   (Dendrites)    │   (Dendrites)    │
    │       ↘           │     ↙            │
    │         ╲       ●       ╱            │
    │           ╲   细胞体    ╱             │
    │             ╲ (Soma) ╱               │
    │               ●────●                 │
    │              /│╲   │╲               │
    │             / │ ╲  │ ╲              │
    │            /  │  ╲ │  ╲             │
    │          ←输入  →输入  ←输入          │
    │                                     │
    │  突触前      突触       突触后       │
    └─────────────────────────────────────┘

1.2 动作电位

神经元通过**动作电位(Action Potential)脉冲(Spike)**进行通信:

  • 膜电位迅速升高(去极化)
  • 达到阈值后触发脉冲
  • 脉冲宽度约1-2ms,幅度约100mV
  • 脉冲后进入不应期(refracotry period)

2. 脉冲神经元模型

2.1 IF (Integrate-and-Fire) 模型

IF模型是最简单的脉冲神经元模型:

状态方程:

或离散形式:

脉冲发放规则:

重置机制:

# IF神经元实现
def IF_neuron(V, I, dt, tau, V_th, V_reset):
    """
    Integrate-and-Fire神经元
    V: 当前膜电位
    I: 输入电流
    dt: 时间步长
    tau: 时间常数
    V_th: 阈值
    V_reset: 重置电位
    """
    V = V + (dt / tau) * I  # 积分
    spike = 1 if V >= V_th else 0  # 脉冲检测
    V = V_reset if spike else V  # 重置
    return V, spike

2.2 LIF (Leaky Integrate-and-Fire) 模型

LIF模型引入”泄漏”机制,更接近真实神经元:

连续形式(微分方程):

其中:

  • :膜时间常数
  • :静息电位
  • :膜电阻
  • :输入电流

等效形式:

参数说明:

参数典型值说明
-70 mV静息电位
-55 mV发放阈值
-70 mV重置电位
10-20 ms膜时间常数
import torch
import torch.nn as nn
 
class LIF Neuron(nn.Module):
    """Leaky Integrate-and-Fire神经元"""
    def __init__(self, tau=10.0, V_th=1.0, V_reset=0.0):
        super().__init__()
        self.tau = tau
        self.V_th = V_th
        self.V_reset = V_reset
    
    def forward(self, V, I, dt=1.0):
        """
        V: 膜电位 (batch, ..., n_neurons)
        I: 输入电流
        """
        # 膜电位更新:dV/dt = -(V - V_rest)/tau + I/tau
        dV = (-(V - self.V_reset) + I) / self.tau
        V_new = V + dV * dt
        
        # 脉冲检测
        spike = (V_new >= self.V_th).float()
        
        # 重置
        V_new = torch.where(spike > 0, self.V_reset, V_new)
        
        return V_new, spike

2.3 Hodgkin-Huxley (HH) 模型

HH模型是最精确的生物物理模型,描述了乌贼巨型轴突的电生理特性(获1963年诺贝尔奖)。

电路类比:

        Cm
    ───┬───→ V ───┬───→ V
        │         │
       ╱╲        ╱╲
      ╱  ╲      ╱  ╲
     ╱    ╲    ╱    ╲
    →I     G_K→    G_Na→
    →      →      →
    │      │      │
    └──────┴──────┘
       G_L

动力学方程:

门控变量(非线性):


2.4 Izhikevich模型

Izhikevich模型在HH模型的精度和LIF模型的效率之间取得平衡:

方程:

重置:

参数范围含义
0.01-0.02恢复变量时间尺度
0.1-0.2恢复变量对V的敏感性
-65 mV脉冲后重置值
2-8恢复变量更新量
def izhikevich_step(v, u, I, a=0.02, b=0.2, c=-65, d=2):
    """Izhikevich神经元单步更新"""
    dv = 0.04*v*v + 5*v + 140 - u + I
    v_new = v + dv
    
    du = a * (b * v - u)
    u_new = u + du
    
    # 脉冲检测
    if v_new >= 30:
        v_new = c
        u_new = u_new + d
        spike = 1
    else:
        spike = 0
    
    return v_new, u_new, spike

2.5 模型对比

模型微分方程参数数量计算复杂度精度
IF线性2-3
LIF线性3-4⭐⭐
Izhikevich二次4⭐⭐⭐
HH非线性8+⭐⭐⭐⭐⭐最高

3. 信息编码方式

SNN利用脉冲的时间结构来编码信息,主要有以下几种编码方式:

3.1 Rate Coding (频率编码)

最直观的编码方式:脉冲发放频率代表信息强度。

其中 为时间窗口。

时间窗口 T
├── 频率低: ○    ○      ○   → 信息值小
├── 频率中: ○  ○    ○  ○   → 信息值中
└── 频率高: ○○○○○○○○○○○○○○○ → 信息值大

优点:直观、理论成熟
缺点:需要较长平均时间,丢失精确时间信息

3.2 Temporal Coding (时间编码)

利用脉冲的精确时间编码信息。

Latency Coding:

输入强度越大,脉冲发放越早。

输入x₁=0.9 (强): ●→ (早期发放)
输入x₂=0.5 (中):   ●→ (中期发放)
输入x₃=0.1 (弱):      ●→ (晚期发放)

Phase Coding:

3.3 Population Coding (群体编码)

多个神经元协同编码一个变量:

其中 为第 个神经元的权重, 为其发放率。

                    偏好方向
                   ↗
                  ↗  ↗
                 ↗ ●●● ↗    ← 群体响应
                ↗ ●●●●●● ↗
               ↗ ●●●●●●●● ↗
              ←───────────→
                 刺激方向

3.4 编码方式对比

编码方式信息容量抗噪声能力计算效率
Rate Coding
Temporal Coding
Population Coding
混合编码

4. 膜电位动力学深入分析

4.1 时间常数的影响

  • :膜电位变化慢,响应迟缓但平滑
  • :膜电位变化快,响应迅速但易波动
# 不同时间常数的效果
def simulate_lif_different_tau():
    """
    模拟不同时间常数下LIF神经元的响应
    """
    results = {}
    for tau in [5, 10, 20]:  # ms
        V_trace = []
        V = V_reset
        for t in range(100):
            I = 1.5 if 10 <= t <= 50 else 0  # 脉冲输入
            dV = (-(V - V_reset) + I) / tau
            V = V + dV
            if V >= V_th:
                V = V_reset
                spike = 1
            else:
                spike = 0
            V_trace.append(V)
        results[tau] = V_trace
    return results

4.2 共模抑制与选择性

神经元对特定频率或强度的输入具有选择性:

4.3 不应期机制

脉冲发放后,神经元进入一段不应期(Refractory Period)

class LIFWithRefractory(nn.Module):
    """带不应期的LIF神经元"""
    def __init__(self, tau=10.0, V_th=1.0, V_reset=0.0, 
                 tau_ref=2.0, dt=1.0):
        super().__init__()
        self.tau = tau
        self.V_th = V_th
        self.V_reset = V_reset
        self.tau_ref = tau_ref  # 不应期时间
        self.dt = dt
    
    def forward(self, V, I, refractory_remaining):
        # 不应期内不响应
        active_mask = (refractory_remaining <= 0).float()
        
        # 膜电位更新(仅在非不应期)
        dV = (-(V - self.V_reset) + I) / self.tau
        V_new = V + dV * self.dt
        
        # 脉冲检测
        spike = ((V_new >= self.V_th) & (refractory_remaining <= 0)).float()
        
        # 重置与不应期更新
        V_new = torch.where(spike > 0, self.V_reset, V_new)
        refractory_new = torch.where(
            spike > 0, 
            self.tau_ref, 
            torch.clamp(refractory_remaining - self.dt, min=0)
        )
        
        return V_new, spike, refractory_new

5. 网络层面的动力学

5.1 突触模型

电流型突触:

电导型突触(更精确):

5.2 网络连接模式

    全连接                  局部连接              前馈
    ┌───┬───┐              ┌───┬───┐            ┌───┐
    │ ● │ ● │              │ ● │   │            │ ● │
    ├───┼───┤              ├───┼───┤            ├───┤
    │ ● │ ● │              │ ● │ ● │   →        │ ● │  →
    ├───┼───┤              ├───┼───┤            ├───┤
    │ ● │ ● │              │   │ ● │            │ ● │
    └───┴───┘              └───┴───┘            └───┘

5.3 动态行为

SNN网络可以表现出丰富的动态行为:

  1. 同步振荡:神经元群同步发放
  2. 行波:激活模式在网络中传播
  3. 全局吸引子:网络收敛到稳定状态

6. 与ANN的对比

特性ANNSNN
信息载体连续值离散脉冲
时间动力学
计算基元MAC操作事件驱动
能效低(持续计算)高(稀疏激活)
生物可信度
训练难度成熟仍在发展中

7. 代码实践:完整LIF神经元

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
 
class LeakyIntegrateFire(nn.Module):
    """
    完整的LIF神经元实现
    支持批处理和多种参数配置
    """
    def __init__(
        self, 
        tau_mem=10.0,      # 膜时间常数 (ms)
        tau_syn=5.0,       # 突触时间常数 (ms)
        V_th=1.0,          # 发放阈值
        V_reset=0.0,        # 重置电位
        V_rest=0.0,        # 静息电位
        dt=1.0,            # 仿真时间步长
        surrogate_grad='arctan',  # 代理梯度类型
        alpha=1.0          # 代理梯度参数
    ):
        super().__init__()
        self.tau_mem = tau_mem
        self.tau_syn = tau_syn
        self.V_th = V_th
        self.V_reset = V_reset
        self.V_rest = V_rest
        self.dt = dt
        self.surrogate_grad = surrogate_grad
        self.alpha = alpha
        
        # 衰减因子
        self.beta_mem = torch.exp(torch.tensor(-dt / tau_mem))
        self.beta_syn = torch.exp(torch.tensor(-dt / tau_syn))
    
    def membrane_dynamics(self, V, I_syn):
        """
        膜电位动力学: dV/dt = -(V - V_rest)/tau_mem + I_syn/C
        """
        dV = (-(V - self.V_rest) + I_syn) / self.tau_mem
        return V + dV * self.dt
    
    def surrogate_gradient(self, V):
        """
        代理梯度函数
        """
        if self.surrogate_grad == 'arctan':
            # arctan代理梯度(常用)
            return (self.alpha / torch.pi) / (1 + (self.alpha * (V - self.V_th))**2)
        elif self.surrogate_grad == 'sigmoid':
            # Sigmoid代理梯度
            x = V - self.V_th
            return torch.sigmoid(x / self.alpha) * (1 - torch.sigmoid(x / self.alpha))
        elif self.surrogate_grad == 'fast_sigmoid':
            # 快速Sigmoid
            return self.alpha / (self.alpha + torch.abs(V - self.V_th))**2
        else:
            raise ValueError(f"Unknown surrogate gradient: {self.surrogate_grad}")
    
    def forward(self, V, I_ext):
        """
        前向传播
        V: 当前膜电位
        I_ext: 外部输入电流
        """
        # 突触电流更新
        I_syn = I_syn * self.beta_syn + I_ext * (1 - self.beta_syn)
        
        # 膜电位更新
        V_new = self.membrane_dynamics(V, I_syn)
        
        # 脉冲检测(可微版本用于反向传播)
        if self.training:
            # 训练时使用代理梯度
            spike_prob = torch.sigmoid((V_new - self.V_th) * self.alpha)
            spike = self.surrogate_gradient(V_new) * (V_new - self.V_th)
        else:
            # 推理时使用硬阈值
            spike = (V_new >= self.V_th).float()
        
        # 膜电位重置
        V_new = torch.where(
            V_new >= self.V_th,
            self.V_reset,
            V_new
        )
        
        return V_new, spike, I_syn
    
    def reset(self, batch_size, device):
        """重置神经元状态"""
        return {
            'V': torch.full((batch_size,), self.V_reset, device=device),
            'I_syn': torch.zeros((batch_size,), device=device),
            'spike_count': torch.zeros((batch_size,), device=device)
        }
 
def simulate_neuron_response():
    """模拟神经元对不同输入的响应"""
    lif = LeakyIntegrateFire(tau_mem=10.0, V_th=1.0, alpha=2.0)
    
    # 模拟参数
    T = 100  # 总时间步
    batch_size = 1
    device = 'cpu'
    
    state = lif.reset(batch_size, device)
    V_history = []
    spike_history = []
    
    for t in range(T):
        # 输入电流:不同阶段不同强度
        if 10 <= t <= 40:
            I_ext = 1.5  # 强输入
        elif 50 <= t <= 70:
            I_ext = 0.8  # 中等输入
        else:
            I_ext = 0.0  # 无输入
        
        V, spike, I_syn = lif(
            state['V'], 
            torch.tensor([I_ext], device=device)
        )
        state['V'] = V
        state['spike_count'] += spike
        
        V_history.append(V.item())
        spike_history.append(spike.item())
    
    # 绘图
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
    
    t_axis = range(T)
    ax1.plot(t_axis, V_history, 'b-', label='Membrane Potential')
    ax1.axhline(y=lif.V_th, color='r', linestyle='--', label='Threshold')
    ax1.set_ylabel('Membrane Potential (V)')
    ax1.legend()
    ax1.grid(True)
    
    ax2.eventplot([i for i, s in enumerate(spike_history) if s > 0], 
                   lineoffsets=0.5, linelengths=0.8, color='red')
    ax2.set_ylabel('Spikes')
    ax2.set_xlabel('Time (ms)')
    ax2.set_ylim(0, 1)
    
    plt.tight_layout()
    plt.savefig('lif_response.png')
    plt.show()
    
    return V_history, spike_history
 
if __name__ == '__main__':
    simulate_neuron_response()

8. 总结

本章介绍了脉冲神经网络的基础知识:

  1. 神经元模型:从简单的IF模型到复杂的HH模型
  2. 编码方式:Rate、Temporal、Population等多种编码
  3. 动力学特性:时间常数、不应期、突触整合
  4. 实现代码:完整的LIF神经元PyTorch实现

参考

Footnotes

  1. Gerstner, W., & Kistler, W. M. (2002). Spiking Neuron Models: Single Neurons, Populations, Plasticity. Cambridge University Press.