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)
SNN系列|学习算法篇(1)Tempotron_第1张图片

  • 神经元模型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,且比例越大,膜电势上升和下降所需时间比越小(尾巴越长)。

SNN系列|学习算法篇(1)Tempotron_第2张图片

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}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}Δω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 ) = 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 τ )    ⟹    t m a x = τ τ s τ − τ s ( ln ⁡ τ τ s + ln ⁡ ∑ ω i exp ⁡ ( t i τ ) ∑ ω i exp ⁡ ( t i τ s ) ) 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 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) 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)τstmaxlnτs1lnωiexp(τsti)=τtmaxlnτ1lnωiexp(τti)tmax=ττsττs(lnτsτ+lnωiexp(τsti)ωiexp(τti))
∑ ω i exp ⁡ ( t i τ ) ∑ ω i exp ⁡ ( t i τ s ) \frac{\sum\omega_i\exp(\frac{t_i}{\tau})}{\sum\omega_i\exp(\frac{t_i}{\tau_s})} ωiexp(τsti)ωiexp(τti)是不能小于等于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

你可能感兴趣的:(Spiking,Neuron,Network,人工智能,机器学习)