SNN系列|学习算法篇(1)Tempotron

Tempotron (论文传送门)

Tempotron是一个二层网络学习算法,输入脉冲序列,输出脉冲响应。对于二分类,最重要的是保证以下关系的存在:即应该发放脉冲的神经元的最大膜电势应超过阈值,否则就增加突触传递效率;反之,不该发放脉冲的其最大膜电势就不该超过阈值,否则就减小突触传递效率。
V ( t max ⁡ ⊕ ) > V t h r > V ( t max ⁡ ⊖ ) V\left(t_{\max } ^\oplus\right)>V_{\mathrm{thr}}>V\left(t_{\max } ^\ominus\right) V(tmax)>Vthr>V(tmax)

  • 神经元模型LIF

这里的LIF神经元模型使用的是连续形式,因为Tempotron是一个基于梯度下降的SNN优化算法,后续可以很方便地直接求导。
V ( t ) = ∑ i ω i ∑ t i K ( t − t i ) + V r e s t V(t)=\sum_{i} \omega_{i} \sum_{t_{i}} K\left(t-t_{i}\right)+V_{\mathrm{rest}} V(t)=iωitiK(tti)+Vrest

K ( t − t i ) = V 0 ( exp ⁡ [ − ( t − t i ) / τ ] − exp ⁡ [ − ( t − t i ) / τ s ] ) K(t-t_i) = V_{0}\left(\exp \left[-\left(t-t_{i}\right) / \tau\right]-\exp \left[-\left(t-t_{i}\right) / \tau_{\mathrm{s}}\right]\right) K(tti)=V0(exp[(tti)/τ]exp[(tti)/τs])

t i t_i ti表示脉冲发放时刻, τ \tau τ表示膜电势整合的延迟参数, τ s \tau_s τs表示突触电流的延迟参数, V 0 V_0 V0表示归一化因子,它使得PSP取决于突触效率 ω \omega ω V 0 V_0 V0的计算步骤为:设 V 0 V_0 V0为0,求最大 K K K所在时刻,然后代入 t m a x t_{max} tmax求方程的倒数即为 V 0 V_0 V0
V 0 = τ τ s τ − τ s ln ⁡ τ τ s V_0 = \frac{\tau \tau_s}{\tau-\tau_s}\ln\frac{\tau}{\tau_s} V0=ττsττslnτsτ
结合 y = e − x y = e^{-x} y=ex函数特性( x x x越大衰减越快)以及脉冲的基本形式(钟形), τ \tau τ势必是大于 τ s \tau_s τs的,原文取 τ / τ s = 4 \tau / \tau_s = 4 τ/τs=4,且比例越大,膜电势上升和下降所需时间比越小(尾巴越长)。

LIF神经元代码如下:

def compute_norm_factor(self, tau, tau_s):
    tmax = (tau * tau_s * np.log(tau/tau_s)) / (tau - tau_s)
    v_max = self.K(1, tmax, 0)
    V_0 = 1/v_max
    return V_0

def K(self, V_0, t, t_i):
		if t < t_i:
    		value = 0
    else:
        value = V_0 * (np.exp(-(t-t_i)/self.tau) - np.exp(-(t-t_i)/self.tau_s))
    return value
  
def compute_spike_contributions(self, t, spike_times):
    N_synapse = len(spike_times)
    spike_contribs = np.zeros(N_synapse)
    for neuron_pos in xrange(N_synapse):
        for spike_time in spike_times[neuron_pos]:
            spike_contribs[neuron_pos] += self.K(self.V_norm, t, spike_time)
    return spike_contribs
  
def compute_membrane_potential(self, t, spike_times):
    spike_contribs = self.compute_spike_contributions(t, spike_times)
    total_incoming = spike_contribs * self.efficacies
		# add sum and add V_rest to get membrane potential
    V = total_incoming.sum() + self.V_rest
		return V
  • 网络训练

Tempotron只在错误发生时更新突触传递效率,其更新规则是梯度下降,损失函数如下:
E ± = ± ( V thr  − V ( t max  ) ) Θ ( ± ( V thr  − V ( t max  ) ) ) Θ ( x ) = { 1 , x ≥ 0 0 , x < 0 E_{\pm}=\pm\left(V_{\text {thr }}-V\left(t_{\text {max }}\right)\right) \Theta\left(\pm\left(V_{\text {thr }}-V\left(t_{\text {max }}\right)\right)\right)\\ \Theta(x)=\begin{cases} 1, x\geq0 \\ 0,x<0\end{cases} E±=±(Vthr V(tmax ))Θ(±(Vthr V(tmax )))Θ(x)={1,x00,x<0
损失函数对传递效率(权重)求偏导,
− d E ± d ω i = ± ∑ t i < t max ⁡ K ( Δ t i ) ± ∂ V ( t max ⁡ ) ∂ t max ⁡ d t max ⁡ d ω i -\frac{\mathrm{d} E_{\pm}}{\mathrm{d} \omega_{i}}=\pm \sum_{t_{i}<t_{\max }} K\left(\Delta t_{i}\right) \pm \frac{\partial V\left(t_{\max }\right)}{\partial t_{\max }} \frac{d t_{\max }}{d \omega_{i}} dωidE±=±ti<tmaxK(Δti)±tmaxV(tmax)dωidtmax
因为 t m a x t_{max} tmax的定义是膜电势最大的时刻,因此突触传递效率的更新规则如下:
Δ ω i = λ ∑ t i < t max  K ( t max ⁡ − t i ) \Delta \omega_{i}=\lambda \sum_{t_{i}<t_{\text {max }}} K\left(t_{\max }-t_{i}\right) Δωi=λti<tmax K(tmaxti)鉴于Tempotron应用于二层网络,其输出希望对 ⊕ \oplus 模式发放脉冲,而对 ⊖ \ominus 模式不发放脉冲,因此对应于最大膜电势要大于脉冲发放阈值,而对另一种模式小于脉冲发放阈值。注意:在T时间内,输出神经元只负责计算膜电势,只在最后判断响应对错时才将其与阈值进行比较。也就是说,Tempotron算法的输出层最多只发放一个脉冲(发不发无所谓,我们关心的是最大膜电势),因此它也无法扩展到多层神经网络中使用。

问题就到了如何判断最大膜电势上来,可以将LIF神经元膜电势对时间进行求导,一定在导数为0的那一些时刻序列里,这也就是为什么LIF神经元模型使用连续绝对形式,而不是离散增量形式的原因,求导可得最大膜电势时刻为
t m a x = τ τ s τ − τ s ( ln ⁡ τ τ s + ln ⁡ ∑ ω i exp ⁡ ( t i τ ) ∑ ω i exp ⁡ ( t i τ s ) ) t_{max} = \frac{\tau \tau_s}{\tau -\tau_s}\left( \ln \frac{\tau}{\tau_s} + \ln \frac{\sum\omega_i\exp(\frac{t_i}{\tau})}{\sum\omega_i\exp(\frac{t_i}{\tau_s})}\right) tmax=ττsττs(lnτsτ+lnωiexp(τsti)ωiexp(τti))
使用连续LIF形式的另一个好处就是不用在每个仿真时刻都计算膜电势,而只需要计算这些疑似最大膜电势时刻的膜电势即可,能有效加快运行。

参数更新如下:

def adapt_weights(self, spike_times, target, learning_rate):
		tmax = self.compute_tmax(spike_times)
    vmax = self.compute_membrane_potential(tmax, spike_times)
    
    if (vmax >= self.threshold) == target:
        return
      
		dw = self.dw(learning_rate, tmax, spike_times)
    if target is True:
        self.efficacies += dw
    else:
        self.efficacies -= dw

def dw(self, learning_rate, tmax, spike_times):
    spike_contribs = self.compute_spike_contributions(tmax, spike_times)
		update = learning_rate * spike_contribs
		return update

def compute_tmax(self, spike_times):
		spikes_chron = [(time, synapse) for synapse in xrange(len(spike_times)) for time in spike_times[synapse]]
    spikes_chron.sort()
      
    spikes = [(s[0], self.efficacies[s[1]]) for s in spikes_chron]
    times = np.array([spike[0] for spike in spikes])
    weights = np.array([spike[1] for spike in spikes])

    sum_tau = (weights*np.exp(times/self.tau)).cumsum()
    sum_tau_s = (weights*np.exp(times/self.tau_s)).cumsum()

    div = sum_tau_s/sum_tau
    boundary_cases = div < 0
    div[boundary_cases] = 10

    tmax_list = self.tau*self.tau_s*(self.log_tts + np.log(div))/(self.tau - self.tau_s)
    tmax_list[boundary_cases] = times[boundary_cases]

    vmax_list = np.array([self.compute_membrane_potential(t, spike_times) for t in tmax_list])

    tmax = tmax_list[vmax_list.argmax()]
    return tmax

如果觉得求导比较麻烦或者绕,可以使用离散LIF模型,计算仿真时刻内所有的膜电势,然后就可以很轻易地找到最大膜电势并使用梯度下降,但存储量稍微大一些。

知乎Jay Wang的启发,想到了Tempotron的容量其实不大,不能使用大规模的训练集进行训练,除非有非常高效的编码方法,因为作者提到,Tempotron的类别信息不是嵌入在脉冲数和单个神经元的脉冲时序中,而是在同步性之中。当样本数量增多后,同类输入的产生的输出也会有很大差异。

以上求解过程均为解析过程,同样也可以使用迭代的思想求解,其他实验细节参考原论文或以后补充。

  • 附:最大模电势时刻推导

  • 这里将一个神经元的多个脉冲理解成多个相同权重的神经元分别发放一个脉冲,从而使得膜电势计算公式有如下变化
    V ( t ) = ∑ ω i ∑ t i V 0 ( exp ⁡ ( − t − t i τ s ) − exp ⁡ ( − t − t i τ ) )    ⟹    V ( t ) = V 0 ∑ ω i ( exp ⁡ ( − t − t i τ s ) − exp ⁡ ( − t − t i τ ) ) V(t) = \sum \omega_i \sum \limits _{t_i}V_0\left(\exp(-\frac{t-t_i}{\tau_s})-\exp(-\frac{t-t_i}{\tau})\right)\\ \implies V(t) = V_0 \sum \omega_i \left(\exp(-\frac{t-t_i}{\tau_s})-\exp(-\frac{t-t_i}{\tau})\right) V(t)=ωitiV0(exp(τstti)exp(τtti))V(t)=V0ωi(exp(τstti)exp(τtti))最大膜电势时刻其导数为0
    V ′ ( t ) = V 0 ∑ ω i ( 1 τ s exp ⁡ ( − t − t i τ s ) − 1 τ exp ⁡ ( − t − t i τ ) ) = 0    ⟹    1 τ s ∑ ω i exp ⁡ ( t i τ s ) exp ⁡ ( − t m a x τ s ) = 1 τ ∑ ω i exp ⁡ ( t i τ ) exp ⁡ ( − t m a x τ )    ⟹    exp ⁡ ( − t m a x τ s ) 1 τ s ∑ ω i exp ⁡ ( t i τ s ) = exp ⁡ ( − t m a x τ ) 1 τ ∑ ω i exp ⁡ ( t i τ )    ⟹    − t m a x τ s + ln ⁡ 1 τ s + ln ⁡ ∑ ω i exp ⁡ ( t i τ s ) = − t m a x τ + ln ⁡ 1 τ + ln ⁡ ∑ ω i exp ⁡ ( t i τ )    ⟹    τ − τ s τ τ s t m a x = ln ⁡ τ τ s + ln ⁡ ∑ ω i exp ⁡ ( t i τ s ) ∑ ω i exp ⁡ ( t i τ )    ⟹    t m a x = τ τ s τ − τ s ( ln ⁡ τ τ s + ln ⁡ ∑ ω i exp ⁡ ( t i τ s ) ∑ ω i exp ⁡ ( t i τ ) ) V^{\prime}(t) = V_0 \sum \omega_i \left( \frac{1}{\tau_s}\exp(-\frac{t-t_i}{\tau_s})-\frac{1}{\tau}\exp(-\frac{t-t_i}{\tau})\right) = 0\\ \implies \frac{1}{\tau_s}\sum \omega_i\exp(\frac{t_i}{\tau_s})\exp(-\frac{t_{max}}{\tau_s})=\frac{1}{\tau}\sum \omega_i\exp(\frac{t_i}{\tau})\exp(-\frac{t_{max}}{\tau})\\ \implies \exp(-\frac{t_{max}}{\tau_s}) \frac{1}{\tau_s}\sum \omega_i\exp(\frac{t_i}{\tau_s})=\exp(-\frac{t_{max}}{\tau})\frac{1}{\tau}\sum \omega_i\exp(\frac{t_i}{\tau})\\ \implies -\frac{t_{max}}{\tau_s} +\ln\frac{1}{\tau_s}+\ln\sum \omega_i\exp(\frac{t_i}{\tau_s})=-\frac{t_{max}}{\tau}+\ln\frac{1}{\tau}+\ln\sum \omega_i\exp(\frac{t_i}{\tau})\\ \implies \frac{\tau-\tau_s}{\tau \tau_s}t_{max} = \ln \frac{\tau}{\tau_s}+ \ln \frac{\sum\omega_i\exp(\frac{t_i}{\tau_s})}{\sum\omega_i\exp(\frac{t_i}{\tau})} \\ \implies t_{max} = \frac{\tau \tau_s}{\tau -\tau_s}\left( \ln \frac{\tau}{\tau_s} + \ln \frac{\sum\omega_i\exp(\frac{t_i}{\tau_s})}{\sum\omega_i\exp(\frac{t_i}{\tau})}\right) V(t)=V0ωi(τs1exp(τstti)τ1exp(τtti))=0τs1ωiexp(τsti)exp(τstmax)=τ1ωiexp(τti)exp(τtmax)exp(τstmax)τs1ωiexp(τsti)=exp(τtmax)τ1ωiexp(τti)τstmax+lnτs1+lnωiexp(τsti)=τtmax+lnτ1+lnωiexp(τti)ττsττstmax=lnτsτ+lnωiexp(τti)ωiexp(τsti)tmax=ττsττs(lnτsτ+lnωiexp(τti)ωiexp(τsti))
    ∑ ω i exp ⁡ ( t i τ s ) ∑ ω i exp ⁡ ( t i τ ) \frac{\sum\omega_i\exp(\frac{t_i}{\tau_s})}{\sum\omega_i\exp(\frac{t_i}{\tau})} ωiexp(τti)ωiexp(τsti)是不能小于等于0的,不然 t m a x t_{max} tmax便不会存在,当出现小于0的情况时,手动设置其比值为10。

参考

[1] The tempotron: a neuron that learns spike timing–based decisions

[2] Python代码:dieuwkehupkes/Tempotron

[3] Matlab代码:laurence-lin/tempotron
[4] SNN-Tempotron

  • 16
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
好的,以下是一个简单的Spike Response Model (SRM)的Matlab实现代码: ``` % SRM model parameters tau_m = 10; % Membrane time constant (ms) theta = -50; % Membrane threshold (mV) tau_s = 2; % Synaptic time constant (ms) w = 1; % Synaptic weight % Simulation parameters dt = 0.1; % Time step (ms) T = 1000; % Simulation time (ms) t = 0:dt:T; % Time vector % Input spike train spikes = zeros(size(t)); spikes(1:100:end) = 1; % Membrane potential and spike train v = zeros(size(t)); spike_train = zeros(size(t)); % Simulation loop for i = 2:length(t) dvdt = (-v(i-1) + w*spikes(i-1))/tau_m; v(i) = v(i-1) + dt*dvdt; if v(i) > theta spike_train(i) = 1; v(i) = 0; end end % Plot results figure; subplot(2,1,1); plot(t, spikes); title('Input spike train'); xlabel('Time (ms)'); ylabel('Spike train'); subplot(2,1,2); plot(t, spike_train); title('Output spike train'); xlabel('Time (ms)'); ylabel('Spike train'); ``` 在上面的代码中,我们首先设置了SRM模型的参数,包括膜时间常数tau_m、膜阈值theta、突触时间常数tau_s和突触权重w。然后,我们设置了模拟的参数,包括时间步长dt、模拟时间T和时间向量t。接下来,我们创建了一个输入脉冲列spikes。我们使用一个简单的方法,在每100个时间步长之间设置一个脉冲,以模拟一个周期为100ms的输入信号。然后,我们使用模拟循环来计算SRM模型的膜电位v和输出脉冲列spike_train。如果膜电位v超过了阈值theta,我们就会发射一个脉冲,并将膜电位v重置为0。最后,我们绘制了输入脉冲列和输出脉冲列。 希望这个代码能够帮助你更好地理解SRM模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值