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神经元模型使用的是连续形式,因为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∑ωiti∑K(t−ti)+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(t−ti)=V0(exp[−(t−ti)/τ]−exp[−(t−ti)/τ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=e−x函数特性( 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,x≥00,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 m a x t_{max} tmax的定义是膜电势最大的时刻,因此突触传递效率的更新规则如下:
Δ ω i = λ ∑ t i < t max K ( t max − t i ) \Delta \omega_{i}=\lambda \sum_{t_{i}
问题就到了如何判断最大膜电势上来,可以将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(−τst−ti)−τ1exp(−τt−ti))=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