使用python实现脉冲神经网络,用于分类任务

基于LIF模型的神经元动态、将信息编码为时间脉冲序列、在时间维度上进行前向传播、以及将脉冲率作为信号进行网络训练。使用实现脉冲神经网络(Spiking Neural Network, SNN) 用于分类任务,由神经元模型构建开始,逐步实现了网络层、完整模型、训练逻辑以及可视化功能。

一、 漏电积分-发放(LIF)

这段代码的目标是构建并训练一个基于漏电积分-发放(LIF)模型的脉冲神经网络,用于解决一个简单的合成数据分类问题。它完整展示了SNN的关键组成部分:神经元动态、时间编码、基于脉冲率的传播以及一个简单的监督训练过程。

二、 核心组件深度解读

1. LIFNeuron 类:漏电积分-发放神经元

这是模拟生物神经元电生理特性的计算模型,是整个SNN的基础。

  • 数学模型 :其核心是差分方程 dv/dt = (-v + I) / tau_m。它模拟了:

    • 积分 :输入电流 I 对膜电位 v 的充电作用。
    • 漏电-v / tau_m 项模拟了膜电位通过离子通道的漏电,tau_m 控制漏电速度。
    • 发放与重置 :当 v 达到阈值 v_th 时,神经元"发放"一个脉冲(spike=1),随后 v 被重置为 v_reset
    • 不应期 :发放后进入短暂的refractory_period,在此期间神经元不响应输入,模拟生物神经元的不应期。
  • 状态与历史

    • v:当前膜电位(主要状态变量)。
    • spike:当前时间步是否发放脉冲(输出)。
    • refractory:不应期剩余时间步。
    • *_history 列表:用于记录整个模拟时间内电位、脉冲和输入的变化,便于分析和可视化。

2. SNNLayer 类:脉冲神经网络层

将多个LIFNeuron组织成一层,并管理其与输入的连接。

  • 结构 :包含n_neuronsLIFNeuron实例。
  • 前向传播
    1. 线性变换weighted_input = dot(inputs, weights) + bias。这与传统人工神经网络(ANN)的线性计算相同,inputs 是脉冲或编码后的模拟值。
    2. 脉冲生成 :将weighted_input作为电流I,传递给该层的每个LIF神经元。每个神经元根据自身的动力学历程独立决定是否发放脉冲。该时间步所有神经元的脉冲输出构成该层的输出向量spikes
  • 脉冲率编码get_spike_rate方法计算神经元在最近一个时间窗口内的平均发放率。在SNN中,信息通常编码在脉冲的时间序列或发放率中,而非单个时间步的模拟值。

3. SNN 类:完整的脉冲神经网络模型

这是最主要的类,集成了编码、解码、训练和评估流程。

  • 网络架构 :一个简单的双层前馈网络(输入→隐藏层→输出)。输入层是隐式的,负责将数据编码为脉冲。
  • 关键方法
    • encode_input(data)将静态的模拟数据转换为时间上的脉冲序列 。这是SNN处理静态数据的关键步骤。本代码采用一种简单的"速率编码 "加噪声的方式:将输入特征值叠加噪声后,通过固定阈值(0.5)生成二进制脉冲序列。time_steps定义了模拟的时间长度。
    • forward(input_sequence)时间驱动的前向传播。循环处理每个时间步的输入,逐层传递脉冲。它返回隐藏层和输出层在整个时间序列上的脉冲记录。
    • train(...)基于脉冲率的监督训练 。这是代码的核心逻辑之一。
      1. 前向传播:对每个样本,先编码,再进行前向传播,得到输出脉冲序列。
      2. 解码与损失计算 :计算输出层神经元在时间上的平均脉冲率 output_rate。将分类标签转换为目标脉冲率向量(如对于类别2,目标为[0, 1])。损失函数定义为均方误差(MSE) 介于输出脉冲率和目标脉冲率之间。
      3. 权重更新 :采用一种简化的梯度下降 ,近似地通过脉冲率误差来更新权重。
        • 输出层权重更新:ΔW_output ∝ hidden_rate · error
        • 隐藏层权重更新:ΔW_hidden ∝ input_rate · (W_output · error)
          这本质上是将脉冲率视为可微分的激活值,并沿用了传统ANN的反向传播思想,但这是一个非常简化的近似,并非严格基于脉冲时间的反向传播(如BPTT)。
    • visualize_activityvisualize_weights强大的诊断和教学工具,可以直观展示输入脉冲、膜电位变化、各层脉冲发放、训练曲线和权重矩阵,极大地帮助理解SNN的动态行为。

三、 工作流程 (main函数)

  1. 数据准备 :使用make_blobs生成合成二维数据集,标准化后分割为训练集、验证集和测试集。
  2. 模型初始化 :创建SNN实例,定义各层尺寸。
  3. 模型训练 :调用train方法,在训练集上迭代优化权重,并在验证集上监控性能。
  4. 模型评估:在测试集上评估最终准确率。
  5. 结果可视化
    • 随机选取一个测试样本,详细可视化其在前向传播过程中的所有活动:输入脉冲、隐藏神经元膜电位变化、各层脉冲发放图、训练损失和准确率曲线。
    • 可视化训练后的权重矩阵,观察网络学到了什么。
  6. 统计与预测:输出训练时间、准确率等统计信息,并展示几个测试样本的预测结果。

四、 完整代码

复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import time

class LIFNeuron:
    """漏电积分-发放神经元"""
    def __init__(self, tau_m=20.0, v_th=1.0, v_reset=0.0, dt=1.0):
        """
        初始化LIF神经元
        
        参数:
        tau_m: 膜时间常数 (ms)
        v_th: 阈值电位
        v_reset: 重置电位
        dt: 时间步长 (ms)
        """
        self.tau_m = tau_m
        self.v_th = v_th
        self.v_reset = v_reset
        self.dt = dt
        
        # 状态变量
        self.v = v_reset
        self.spike = 0
        self.refractory = 0
        self.refractory_period = 5
        
        # 记录历史
        self.v_history = []
        self.spike_history = []
        self.input_history = []
    
    def update(self, I):
        """
        更新神经元状态
        
        参数:
        I: 输入电流
        
        返回:
        spike: 当前时间步是否发放脉冲
        """
        # 记录输入
        self.input_history.append(I)
        
        # 检查不应期
        if self.refractory > 0:
            self.v = self.v_reset
            self.refractory -= 1
            self.spike = 0
        else:
            # 计算膜电位变化
            dv = (-self.v + I) / self.tau_m
            self.v += dv * self.dt
            
            # 检查是否发放脉冲
            if self.v >= self.v_th:
                self.spike = 1
                self.v = self.v_reset
                self.refractory = self.refractory_period
            else:
                self.spike = 0
        
        # 记录历史
        self.v_history.append(self.v)
        self.spike_history.append(self.spike)
        
        return self.spike
    
    def reset(self):
        """重置神经元状态"""
        self.v = self.v_reset
        self.spike = 0
        self.refractory = 0
        self.v_history = []
        self.spike_history = []
        self.input_history = []


class SNNLayer:
    """脉冲神经网络层"""
    def __init__(self, n_neurons, n_inputs, tau_m=20.0, v_th=1.0, name=""):
        """
        初始化SNN层
        
        参数:
        n_neurons: 神经元数量
        n_inputs: 输入维度
        tau_m: 膜时间常数
        v_th: 阈值电位
        name: 层名称
        """
        self.name = name
        self.n_neurons = n_neurons
        self.n_inputs = n_inputs
        
        # 初始化神经元
        self.neurons = [LIFNeuron(tau_m=tau_m, v_th=v_th) for _ in range(n_neurons)]
        
        # 初始化权重
        self.weights = np.random.randn(n_inputs, n_neurons) * 0.1
        self.bias = np.zeros(n_neurons)
        
        # 记录脉冲历史
        self.spike_train = []
    
    def forward(self, inputs):
        """
        前向传播
        
        参数:
        inputs: 输入向量 (n_inputs,)
        
        返回:
        spikes: 输出脉冲 (n_neurons,)
        """
        # 计算加权输入
        weighted_input = np.dot(inputs, self.weights) + self.bias
        
        # 更新每个神经元
        spikes = np.zeros(self.n_neurons)
        for i, neuron in enumerate(self.neurons):
            spikes[i] = neuron.update(weighted_input[i])
        
        # 记录脉冲
        self.spike_train.append(spikes.copy())
        
        return spikes
    
    def reset(self):
        """重置层状态"""
        for neuron in self.neurons:
            neuron.reset()
        self.spike_train = []
    
    def get_spike_rate(self, window_size=10):
        """
        计算脉冲率
        
        参数:
        window_size: 时间窗口大小
        
        返回:
        spike_rate: 平均脉冲率
        """
        if len(self.spike_train) == 0:
            return np.zeros(self.n_neurons)
        
        # 计算最近时间窗口的平均脉冲率
        recent_spikes = np.array(self.spike_train[-window_size:])
        return np.mean(recent_spikes, axis=0)


class SNN:
    """脉冲神经网络模型"""
    def __init__(self, input_size, hidden_size, output_size, time_steps=50):
        """
        初始化SNN模型
        
        参数:
        input_size: 输入维度
        hidden_size: 隐藏层神经元数量
        output_size: 输出层神经元数量
        time_steps: 时间步数
        """
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.time_steps = time_steps
        
        # 创建网络层
        self.hidden_layer = SNNLayer(hidden_size, input_size, tau_m=15.0, v_th=0.8, name="hidden")
        self.output_layer = SNNLayer(output_size, hidden_size, tau_m=20.0, v_th=1.0, name="output")
        
        # 训练历史
        self.loss_history = []
        self.accuracy_history = []
        self.train_time = 0
    
    def encode_input(self, data):
        """
        编码输入数据为脉冲序列
        
        参数:
        data: 输入数据 (batch_size, input_size)
        
        返回:
        spike_sequence: 脉冲序列 (time_steps, batch_size, input_size)
        """
        batch_size = data.shape[0] if len(data.shape) > 1 else 1
        
        if len(data.shape) == 1:
            data = data.reshape(1, -1)
            batch_size = 1
        
        # 将输入数据转换为脉冲序列
        spike_sequence = np.zeros((self.time_steps, batch_size, self.input_size))
        
        for t in range(self.time_steps):
            # 添加噪声模拟脉冲不确定性
            noise = np.random.normal(0, 0.1, (batch_size, self.input_size))
            encoded = data + noise
            
            # 通过阈值函数转换为脉冲
            spikes = (encoded > 0.5).astype(float)
            spike_sequence[t] = spikes
        
        return spike_sequence
    
    def forward(self, input_sequence):
        """
        前向传播
        
        参数:
        input_sequence: 输入脉冲序列 (time_steps, input_size)
        
        返回:
        output_spikes: 输出脉冲序列 (time_steps, output_size)
        hidden_spikes: 隐藏层脉冲序列 (time_steps, hidden_size)
        """
        # 重置网络状态
        self.hidden_layer.reset()
        self.output_layer.reset()
        
        time_steps = len(input_sequence)
        hidden_spikes_seq = []
        output_spikes_seq = []
        
        # 时间步循环
        for t in range(time_steps):
            # 获取当前时间步输入
            current_input = input_sequence[t]
            
            # 隐藏层前向传播
            hidden_spikes = self.hidden_layer.forward(current_input)
            hidden_spikes_seq.append(hidden_spikes)
            
            # 输出层前向传播
            output_spikes = self.output_layer.forward(hidden_spikes)
            output_spikes_seq.append(output_spikes)
        
        return (np.array(hidden_spikes_seq), 
                np.array(output_spikes_seq))
    
    def train(self, X_train, y_train, X_val, y_val, epochs=50, lr=0.01):
        """
        训练SNN
        
        参数:
        X_train: 训练数据
        y_train: 训练标签
        X_val: 验证数据
        y_val: 验证标签
        epochs: 训练轮数
        lr: 学习率
        """
        start_time = time.time()
        
        for epoch in range(epochs):
            epoch_loss = 0
            correct = 0
            total = 0
            
            # 打乱训练数据
            indices = np.random.permutation(len(X_train))
            
            for idx in indices:
                # 获取单个样本
                sample = X_train[idx]
                label = y_train[idx]
                
                # 编码输入
                input_seq = self.encode_input(sample)
                input_seq = input_seq.reshape(self.time_steps, -1)
                
                # 前向传播
                hidden_spikes, output_spikes = self.forward(input_seq)
                
                # 计算输出脉冲率
                output_rate = np.mean(output_spikes, axis=0)
                
                # 创建目标向量
                target = np.zeros(self.output_size)
                if self.output_size > 1:
                    target[label] = 1.0
                else:
                    target[0] = label
                
                # 计算损失
                loss = np.mean((output_rate - target) ** 2)
                epoch_loss += loss
                
                # 预测
                predicted = np.argmax(output_rate) if self.output_size > 1 else (output_rate[0] > 0.5)
                if predicted == label:
                    correct += 1
                total += 1
                
                # 简单梯度下降更新权重
                error = output_rate - target
                
                # 计算隐藏层脉冲率
                hidden_rate = np.mean(hidden_spikes, axis=0)
                
                # 更新输出层权重
                dw_output = np.outer(hidden_rate, error)
                self.output_layer.weights -= lr * dw_output
                
                # 更新隐藏层权重
                input_rate = np.mean(input_seq, axis=0)
                error_hidden = np.dot(self.output_layer.weights, error)
                dw_hidden = np.outer(input_rate, error_hidden)
                self.hidden_layer.weights -= lr * dw_hidden
            
            # 计算验证集准确率
            val_acc = self.evaluate(X_val, y_val)
            train_acc = correct / total
            
            # 记录历史
            epoch_loss /= len(X_train)
            self.loss_history.append(epoch_loss)
            self.accuracy_history.append((train_acc, val_acc))
            
            print(f"Epoch {epoch+1:3d}/{epochs} | "
                  f"Loss: {epoch_loss:.4f} | "
                  f"Train Acc: {train_acc:.2%} | "
                  f"Val Acc: {val_acc:.2%}")
        
        self.train_time = time.time() - start_time
        print(f"\n训练完成!总时间: {self.train_time:.2f}秒")
    
    def evaluate(self, X, y):
        """评估模型性能"""
        correct = 0
        
        for i in range(len(X)):
            # 编码输入
            input_seq = self.encode_input(X[i])
            input_seq = input_seq.reshape(self.time_steps, -1)
            
            # 前向传播
            _, output_spikes = self.forward(input_seq)
            
            # 计算输出脉冲率
            output_rate = np.mean(output_spikes, axis=0)
            
            # 预测
            if self.output_size > 1:
                predicted = np.argmax(output_rate)
            else:
                predicted = 1 if output_rate[0] > 0.5 else 0
            
            if predicted == y[i]:
                correct += 1
        
        return correct / len(X)
    
    def predict(self, X):
        """预测"""
        predictions = []
        
        for i in range(len(X)):
            # 编码输入
            input_seq = self.encode_input(X[i])
            input_seq = input_seq.reshape(self.time_steps, -1)
            
            # 前向传播
            _, output_spikes = self.forward(input_seq)
            
            # 计算输出脉冲率
            output_rate = np.mean(output_spikes, axis=0)
            
            # 预测
            if self.output_size > 1:
                predicted = np.argmax(output_rate)
            else:
                predicted = 1 if output_rate[0] > 0.5 else 0
            
            predictions.append(predicted)
        
        return np.array(predictions)
    
    def visualize_activity(self, sample, label=None, timesteps=30):
        """可视化网络活动"""
        # 编码输入
        input_seq = self.encode_input(sample)
        input_seq = input_seq.reshape(self.time_steps, -1)
        
        # 前向传播
        hidden_spikes, output_spikes = self.forward(input_seq)
        
        # 创建图形
        fig = plt.figure(figsize=(15, 10))
        
        # 1. 输入脉冲
        ax1 = plt.subplot(3, 2, 1)
        ax1.imshow(input_seq[:timesteps].T, aspect='auto', cmap='binary')
        ax1.set_title('输入脉冲序列')
        ax1.set_xlabel('时间步')
        ax1.set_ylabel('输入神经元')
        ax1.set_yticks(range(self.input_size))
        
        # 2. 隐藏层膜电位
        ax2 = plt.subplot(3, 2, 2)
        for i in range(min(5, self.hidden_size)):
            v_history = self.hidden_layer.neurons[i].v_history[:timesteps]
            ax2.plot(v_history, label=f'神经元 {i+1}')
        ax2.axhline(y=self.hidden_layer.neurons[0].v_th, color='r', 
                    linestyle='--', label='阈值')
        ax2.set_title('隐藏层膜电位')
        ax2.set_xlabel('时间步')
        ax2.set_ylabel('膜电位')
        ax2.legend(loc='upper right')
        ax2.grid(True, alpha=0.3)
        
        # 3. 隐藏层脉冲
        ax3 = plt.subplot(3, 2, 3)
        ax3.imshow(hidden_spikes[:timesteps].T, aspect='auto', cmap='binary')
        ax3.set_title('隐藏层脉冲')
        ax3.set_xlabel('时间步')
        ax3.set_ylabel('隐藏神经元')
        ax3.set_yticks(range(min(20, self.hidden_size)))
        
        # 4. 输出层脉冲
        ax4 = plt.subplot(3, 2, 4)
        ax4.imshow(output_spikes[:timesteps].T, aspect='auto', cmap='binary')
        ax4.set_title('输出层脉冲')
        ax4.set_xlabel('时间步')
        ax4.set_ylabel('输出神经元')
        ax4.set_yticks(range(self.output_size))
        
        # 5. 训练损失
        ax5 = plt.subplot(3, 2, 5)
        ax5.plot(self.loss_history, 'b-', linewidth=2)
        ax5.set_title('训练损失')
        ax5.set_xlabel('轮次')
        ax5.set_ylabel('损失')
        ax5.grid(True, alpha=0.3)
        
        # 6. 准确率
        ax6 = plt.subplot(3, 2, 6)
        if self.accuracy_history:
            train_acc = [acc[0] for acc in self.accuracy_history]
            val_acc = [acc[1] for acc in self.accuracy_history]
            ax6.plot(train_acc, 'b-', linewidth=2, label='训练集')
            ax6.plot(val_acc, 'r--', linewidth=2, label='验证集')
            ax6.set_title('准确率曲线')
            ax6.set_xlabel('轮次')
            ax6.set_ylabel('准确率')
            ax6.legend()
            ax6.grid(True, alpha=0.3)
        
        title = 'SNN网络活动'
        if label is not None:
            prediction = self.predict(sample.reshape(1, -1))[0]
            title += f' (标签: {label}, 预测: {prediction})'
        
        plt.suptitle(title, fontsize=14, fontweight='bold')
        plt.tight_layout()
        plt.show()
    
    def visualize_weights(self):
        """可视化权重矩阵"""
        fig, axes = plt.subplots(1, 2, figsize=(12, 5))
        
        # 输入-隐藏层权重
        im1 = axes[0].imshow(self.hidden_layer.weights.T, cmap='RdBu_r', 
                             aspect='auto', vmin=-1, vmax=1)
        axes[0].set_title('输入 → 隐藏层权重')
        axes[0].set_xlabel('输入特征')
        axes[0].set_ylabel('隐藏神经元')
        plt.colorbar(im1, ax=axes[0])
        
        # 隐藏-输出层权重
        im2 = axes[1].imshow(self.output_layer.weights.T, cmap='RdBu_r', 
                             aspect='auto', vmin=-1, vmax=1)
        axes[1].set_title('隐藏层 → 输出层权重')
        axes[1].set_xlabel('隐藏神经元')
        axes[1].set_ylabel('输出神经元')
        plt.colorbar(im2, ax=axes[1])
        
        plt.tight_layout()
        plt.show()


def generate_synthetic_data(n_samples=200, n_features=5, random_state=42):
    """生成合成数据"""
    np.random.seed(random_state)
    
    # 生成二分类数据
    X, y = make_blobs(n_samples=n_samples, n_features=n_features, 
                      centers=2, cluster_std=1.5, random_state=random_state)
    
    # 标准化
    scaler = StandardScaler()
    X = scaler.fit_transform(X)
    
    return X, y


def main():
    """主函数"""
    print("=" * 60)
    print("脉冲神经网络(SNN)分类器")
    print("=" * 60)
    
    # 1. 生成数据
    print("\n1. 生成数据...")
    X, y = generate_synthetic_data(n_samples=200, n_features=5)
    
    # 分割数据集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.125, random_state=42)
    
    print(f"   训练集: {len(X_train)} 样本")
    print(f"   验证集: {len(X_val)} 样本")
    print(f"   测试集: {len(X_test)} 样本")
    
    # 2. 创建SNN模型
    print("\n2. 创建SNN模型...")
    input_size = X_train.shape[1]
    hidden_size = 20
    output_size = 2  # 二分类
    
    snn = SNN(input_size=input_size, 
              hidden_size=hidden_size, 
              output_size=output_size,
              time_steps=50)
    
    print(f"   输入层: {input_size} 神经元")
    print(f"   隐藏层: {hidden_size} 神经元")
    print(f"   输出层: {output_size} 神经元")
    
    # 3. 训练模型
    print("\n3. 训练模型...")
    snn.train(X_train, y_train, X_val, y_val, epochs=30, lr=0.01)
    
    # 4. 测试模型
    print("\n4. 测试模型...")
    test_acc = snn.evaluate(X_test, y_test)
    print(f"   测试集准确率: {test_acc:.2%}")
    
    # 5. 可视化
    print("\n5. 可视化结果...")
    
    # 随机选择一个样本可视化
    sample_idx = np.random.randint(len(X_test))
    sample = X_test[sample_idx]
    label = y_test[sample_idx]
    
    snn.visualize_activity(sample, label, timesteps=30)
    snn.visualize_weights()
    
    # 6. 显示统计信息
    print("\n" + "=" * 60)
    print("模型统计信息")
    print("=" * 60)
    print(f"训练时间: {snn.train_time:.2f}秒")
    print(f"训练轮次: {len(snn.loss_history)}")
    print(f"最终训练损失: {snn.loss_history[-1]:.4f}")
    
    if snn.accuracy_history:
        train_acc, val_acc = snn.accuracy_history[-1]
        print(f"最终训练准确率: {train_acc:.2%}")
        print(f"最终验证准确率: {val_acc:.2%}")
    
    print(f"测试集准确率: {test_acc:.2%}")
    
    # 7. 预测示例
    print("\n" + "=" * 60)
    print("预测示例")
    print("=" * 60)
    
    n_examples = 5
    indices = np.random.choice(len(X_test), n_examples, replace=False)
    
    for i, idx in enumerate(indices):
        sample = X_test[idx]
        true_label = y_test[idx]
        prediction = snn.predict(sample.reshape(1, -1))[0]
        correct = "✓" if prediction == true_label else "✗"
        print(f"样本 {i+1}: 真实标签={true_label}, 预测={prediction} {correct}")


if __name__ == "__main__":
    main()
相关推荐
qyzm2 小时前
AtCoder Beginner Contest 449
数据结构·python·算法·贪心算法
no_work2 小时前
python-深度学习快速入门实战-数据集和源码
开发语言·人工智能·python·深度学习·神经网络·cnn
MoRanzhi12032 小时前
一维概率分布可视化实践:基于 Python 的理论曲线与样本图对照
python·概率论·matplotlib·seaborn·scipy·统计学·概率分布可视化
jay神2 小时前
基于深度学习的人脸检测与识别系统
人工智能·python·深度学习·可视化·计算机毕业设计
KIHU快狐2 小时前
KIHU快狐|RK3399系统户外触摸一体机强悍算力支持超清播放
大数据·人工智能·python
郝学胜-神的一滴2 小时前
人工智能发展漫谈:从专家系统到AIGC,再探深度学习核心与Pytorch入门
人工智能·pytorch·python·深度学习·算法·cnn·aigc
老杨_QQ1222090173 小时前
对量化交易未来的思考
人工智能·python·金融
nananaij3 小时前
【LeetCode-03 判断根结点是否等于子结点之和 python解法】
python·算法·leetcode
老师好,我是刘同学3 小时前
归并排序原理与Python实现详解
python·排序算法