脉冲神经网络

发布时间:2023-05-23

脉冲神经网络(Spiking Neural Network, SNN)是模拟生物神经系统的一种计算模型。相较于其他神经网络模型,SNN最为贴近神经元的生理结构与生物功能,以脉冲为信息传递方式并具有时间精度优势,拥有类似人脑的计算能力,因此在深度学习领域发展迅速,本文将对SNN的原理、训练、应用等多个方面进行详细阐述。

一、脉冲神经网络原理

人类神经系统由大脑、神经元细胞与神经元之间的突触构成,而SNN正是基于这种生物神经结构的模型,通常由神经元、突触和脉冲等三部分组成。神经元模拟生物神经元,突触模拟突触结构,而且通过脉冲来传输信号,也和人脑中的神经元类似。 SNN中,神经元接收来自突触的电流信号,当电流信号的总和达到一定程度时,神经元才会发放脉冲,这里的神经元是阈值型的,当神经元接收到了足够多的电流,它就会“激活”然后“发射出去”,一般被称作“脉冲 spike”。 神经元可以被看作是一个非线性的微分方程,它主要包括了电位、阈值、决策方式等属性。SNN的本质就是一种基于事件的模型,即当接收到一定数量的信号时,神经元就会发射脉冲。这种事件模拟非常适合计算中的异步、分布、不确定性等问题。

import numpy as np
import matplotlib.pyplot as plt
class Neuron:
    def __init__(self, threshold, tau, dt):
        self.threshold = threshold # 阈值
        self.tau = tau # 时间常数
        self.dt = dt # 时间步长
        self.V = np.zeros(int(tau/dt)) # 神经元电位
        self.spike = False # 初始状态,神经元未发生脉冲
    # 更新神经元电势
    def update(self, current):
        m = int(self.tau/self.dt)-1
        for i in range(m):
            self.V[i] = self.V[i+1]+(-self.V[i+1]/self.tau+self.dt*current)/self.tau*self.dt
        self.V[m] = self.V[m]+(-self.V[m]/self.tau+self.dt*current)/self.tau*self.dt
    # 判断神经元是否发生脉冲
    def fire(self):
        if self.V[-1] >= self.threshold:
            self.V = np.zeros(len(self.V))
            self.spike = True
        else:
            self.spike = False

二、脉冲神经网络训练

与其他神经网络模型不同,SNN在训练时需要处理时间信息。传统的神经网络通过反向传播算法进行优化,但SNN由于采用了类似事件的模型,因此无法使用传统反向传播算法进行训练。 目前SNN主要有两种训练方法:Spike-Timing-Dependent-Plasticity (STDP)和Rate-Coding。STDP训练方法是按照突触前后神经元的脉冲时间差异来更新突触权重,从而实现学习。而Rate-Coding则是将神经元发射脉冲密度作为输入的编码方式,然后通过权重矩阵实现神经元之间的连接与通信,从而实现学习过程。 STDP训练方法实现方案:

class Synapse:
    def __init__(self, weight, delay, dt):
        self.weight = weight # 初始权重
        self.delay = delay # 延迟时间
        self.dt = dt # 时间步长
        self.last_fire = -1.01 # 上次发射时间
        self.trace = np.zeros(int(1.5/dt))
    # 更新突触权重
    def update(self, pre_spike, now_t):
        if pre_spike and self.last_fire+self.delay < now_t:
            delta_t = now_t - (self.last_fire + self.delay)
            self.weight = self.weight + self.trace[int(delta_t/self.dt)]
            self.last_fire = now_t
        # 更新追踪值
        def update_trace(self, pre_spike, now_t):
            if not pre_spike:
                self.trace = np.maximum(0, self.trace - (now_t - self.last_fire) * self.tau * self.dt)
            else:
                self.trace[int(self.delay * self.dt)] = 1
class SNN:
    def __init__(self, input_num, hidden_num, output_num, tau, dt, epsilon, sim_time):
        self.input_num = input_num # 输入层神经元数量
        self.hidden_num = hidden_num # 隐藏层神经元数量
        self.output_num = output_num # 输出层神经元数量
        self.tau = tau # 时间常数
        self.dt = dt # 时间步长
        self.epsilon = epsilon # 更新速率
        self.sim_time = sim_time # 模拟时长
        self.W1 = np.random.randn(hidden_num, input_num)*0.1 # 输入层到隐藏层突触权重
        self.W2 = np.random.randn(output_num, hidden_num)*0.1 # 隐藏层到输出层突触权重
        self.x = np.zeros(input_num) # 输入层电位
        self.a1 = [Neuron(1, dt) for i in range(hidden_num)] # 隐藏层神经元数组
        self.a2 = [Neuron(1, dt) for i in range(output_num)] # 输出层神经元数组
        self.s1 = [] # 输入层到隐藏层突触数组
        self.s2 = [] # 隐藏层到输出层突触数组
        for i in range(hidden_num):
            self.s1.append([Synapse(np.random.randn()*0.01, 0, dt) for j in range(input_num)])
        for i in range(output_num):
            self.s2.append([Synapse(np.random.randn()*0.01, 0, dt) for j in range(hidden_num)])
    # 网络前向传播
    def forward(self, x):
        for i in range(self.hidden_num):
            current = np.sum(self.W1[i,:]*self.x)
            self.a1[i].update(current)
            self.a1[i].fire()
            if self.a1[i].spike:
                for j in range(self.input_num):
                    self.s1[i][j].update_trace(True, now_t)
        for i in range(self.output_num):
            current = np.sum(self.W2[i,:]*self.x)
            self.a2[i].update(current)
            self.a2[i].fire()
            if self.a2[i].spike:
                self.s2[i][j].update_trace(True, now_t)
    # 网络后向传播
    def backward(self, label):
        target = np.zeros(self.output_num)
        target[label] = 1
        for i in range(self.output_num):
            self.a2[i].delta = -self.epsilon*(self.a2[i].V - target[i])
            for j in range(self.hidden_num):
                delta_w2 = self.epsilon * self.a2[i].delta * self.a1[j].V
                self.s2[i][j].weight += delta_w2
        for i in range(self.hidden_num):
            self.a1[i].delta = 0
            for j in range(self.output_num):
                self.a1[i].delta += self.s2[j][i].weight * self.a2[j].delta
            for j in range(self.input_num):
                delta_w1 = self.epsilon * self.a1[i].delta * self.x[j]
                self.s1[i][j].weight += delta_w1
    # 训练网络
    def train(self, X, y):
        for i in range(len(X)):
            x_i = X[i]
            label = y[i]
            self.forward(x_i)
            self.backward(label)
    # 网络预测
    def predict(self, X):
        res = []
        for i in range(len(X)):
            x_i = X[i]
            self.forward(x_i)
            output = np.array([a.spike for a in self.a2])
            res.append(np.argmax(output))
        return np.array(res)

三、脉冲神经网络应用

脉冲神经网络在各种领域都有着广泛应用,如图像识别、语音识别、人脑计算模拟、智能控制等。下面以语音识别为例,阐述SNN在该领域中的应用: 语音信号本身就是时间序列数据,脉冲神经网络能够更好的处理自然界中的时间序列数据,因此在语音识别领域有着广泛的应用。Yan和Zhang在2006年提出了一种基于SNN的语音识别模型,将时间尺度嵌入到深度学习网络中,利用SNN在时间轴上的渐进式挑选机制进行模型学习,从而实现对语音的识别。 脉冲神经网络的应用不仅仅局限于语音领域,其强大的事件表达模型,在处理时间序列数据的领域具有广阔发展前景。

四、总结

本文主要介绍了脉冲神经网络的原理、训练与应用等多个方面。脉冲神经网络作为一种基于时间事件的模型,与生物神经元的结构与功能相似,因此在多种领域有着广泛的应用。由于其时间耦合性的特点,脉冲神经网络训练具有一定的挑战性,但是研究人员针对不同的应用场景,提出了多种有效的训练方法。未来,脉冲神经网络在各个领域都将发挥重要作用,迎接更为广阔的发展空间。