Open In Colab   Open in Kaggle

ボーナスチュートリアル:スパイクタイミング依存可塑性(STDP)

第2週4日目:生物学的ニューロンモデル

Neuromatch Academyによる

コンテンツ作成者: Qinglong Gu, Songtin Li, John Murray, Richard Naud, Arvind Kumar

コンテンツレビュアー: Spiros Chavlis, Lorenzo Fontolan, Richard Gao, Matthew Krause

制作編集者: Gagana B, Spiros Chavlis


チュートリアルの目的

このチュートリアルでは、シナプス強度が、シナプス前ニューロンとシナプス後ニューロンのスパイクの相対的なタイミング(すなわち時間差)に応じて変化するシナプスのモデル構築に焦点を当てます。このシナプス重みの変化は**スパイクタイミング依存可塑性(STDP)**として知られています。

このチュートリアルの目標は:

これらの目標に向けて、シナプス前入力はポアソン型スパイク列としてモデル化します。シナプス後ニューロンはLIFニューロンとしてモデル化します(チュートリアル1参照)。

このチュートリアル全体を通じて、単一のシナプス後ニューロンがNN個のシナプス前ニューロンによって駆動されると仮定します。すなわち、NN個のシナプスがあり、それらの重みが入力スパイク列の統計やシナプス後ニューロンのスパイクとのタイミングにどのように依存するかを調べます。


セットアップ

# @title Install and import feedback gadget


from vibecheck import DatatopsContentReviewContainer
def content_review(notebook_section: str):
    return DatatopsContentReviewContainer(
        "",  # No text prompt
        notebook_section,
        {
            "url": "https://pmyvdlilci.execute-api.us-east-1.amazonaws.com/klab",
            "name": "neuromatch_cn",
            "user_key": "y1x3mpx5",
        },
    ).render()


feedback_prefix = "W2D3_T4_Bonus"
# Import libraries
import matplotlib.pyplot as plt
import numpy as np
import time
# @title Figure Settings
import logging
logging.getLogger('matplotlib.font_manager').disabled = True

import ipywidgets as widgets  # interactive display
%config InlineBackend.figure_format='retina'
# use NMA plot style
plt.style.use("https://raw.githubusercontent.com/NeuromatchAcademy/course-content/main/nma.mplstyle")
my_layout = widgets.Layout()
# @title Plotting functions
def my_raster_plot(range_t, spike_train, n):
  """Generates poisson trains

  Args:
    range_t     : time sequence
    spike_train : binary spike trains, with shape (N, Lt)
    n           : number of Poisson trains plot

  Returns:
    Raster_plot of the spike train
  """

  # Find the number of all the spike trains
  N = spike_train.shape[0]

  # n should be smaller than N:
  if n > N:
    print('The number n exceeds the size of spike trains')
    print('The number n is set to be the size of spike trains')
    n = N

  # Raster plot
  i = 0
  while i <= n:
    if spike_train[i, :].sum() > 0.:
      t_sp = range_t[spike_train[i, :] > 0.5]  # spike times
      plt.plot(t_sp, i * np.ones(len(t_sp)), 'k|', ms=10, markeredgewidth=2)
    i += 1
  plt.xlim([range_t[0], range_t[-1]])
  plt.ylim([-0.5, n + 0.5])
  plt.xlabel('Time (ms)')
  plt.ylabel('Neuron ID')
  plt.show()


def my_example_P(pre_spike_train_ex, pars, P):
  """Generates two plots (raster plot and LTP vs time plot)

  Args:
    pre_spike_train_ex     : spike-train
    pars : dictionary with the parameters
    P : LTP ratio

  Returns:
    my_example_P returns a rastert plot (top),
    and a LTP ratio across time (bottom)
  """
  spT = pre_spike_train_ex
  plt.figure(figsize=(7, 6))
  plt.subplot(211)
  color_set = ['red', 'blue', 'black', 'orange', 'cyan']
  for i in range(spT.shape[0]):
    t_sp = pars['range_t'][spT[i, :] > 0.5]   # spike times
    plt.plot(t_sp, i*np.ones(len(t_sp)), '|',
             color=color_set[i],
             ms=10, markeredgewidth=2)
  plt.xlabel('Time (ms)')
  plt.ylabel('Neuron ID')
  plt.xlim(0, 200)

  plt.subplot(212)
  for k in range(5):
    plt.plot(pars['range_t'], P[k, :], color=color_set[k], lw=1.5)
  plt.xlabel('Time (ms)')
  plt.ylabel('P(t)')
  plt.xlim(0, 200)

  plt.tight_layout()
  plt.show()


def mySTDP_plot(A_plus, A_minus, tau_stdp, time_diff, dW):
  plt.figure()
  plt.plot([-5 * tau_stdp, 5 * tau_stdp], [0, 0], 'k', linestyle=':')
  plt.plot([0, 0], [-A_minus, A_plus], 'k', linestyle=':')

  plt.plot(time_diff[time_diff <= 0], dW[time_diff <= 0], 'ro')
  plt.plot(time_diff[time_diff > 0], dW[time_diff > 0], 'bo')

  plt.xlabel(r't$_{\mathrm{pre}}$ - t$_{\mathrm{post}}$ (ms)')
  plt.ylabel(r'$\Delta$W', fontsize=12)
  plt.title('Biphasic STDP', fontsize=12, fontweight='bold')
  plt.show()
# @title Helper functions

def default_pars_STDP(**kwargs):
  pars = {}

  # typical neuron parameters
  pars['V_th'] = -55.     # spike threshold [mV]
  pars['V_reset'] = -75.  # reset potential [mV]
  pars['tau_m'] = 10.     # membrane time constant [ms]
  pars['V_init'] = -65.   # initial potential [mV]
  pars['V_L'] = -75.      # leak reversal potential [mV]
  pars['tref'] = 2.       # refractory time (ms)

  # STDP parameters
  pars['A_plus'] = 0.008                   # magnitude of LTP
  pars['A_minus'] = pars['A_plus'] * 1.10  # magnitude of LTD
  pars['tau_stdp'] = 20.                   # STDP time constant [ms]

  # simulation parameters
  pars['T'] = 400.  # Total duration of simulation [ms]
  pars['dt'] = .1   # Simulation time step [ms]

  # external parameters if any
  for k in kwargs:
    pars[k] = kwargs[k]

  pars['range_t'] = np.arange(0, pars['T'], pars['dt'])  # Vector of discretized time points [ms]

  return pars


def Poisson_generator(pars, rate, n, myseed=False):
  """Generates poisson trains

  Args:
    pars            : parameter dictionary
    rate            : noise amplitute [Hz]
    n               : number of Poisson trains
    myseed          : random seed. int or boolean

  Returns:
    pre_spike_train : spike train matrix, ith row represents whether
                      there is a spike in ith spike train over time
                      (1 if spike, 0 otherwise)
  """

  # Retrieve simulation parameters
  dt, range_t = pars['dt'], pars['range_t']
  Lt = range_t.size

  # set random seed
  if myseed:
    np.random.seed(seed=myseed)
  else:
    np.random.seed()

  # generate uniformly distributed random variables
  u_rand = np.random.rand(n, Lt)

  # generate Poisson train
  poisson_train = 1. * (u_rand < rate * (dt / 1000.))

  return poisson_train

セクション1:スパイクタイミング依存可塑性(STDP)

# @title Video 1: STDP
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'luHL-mO5S1w'), ('Bilibili', 'BV1AV411r75K')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_STDP_Video")

STDPのモデル

STDPの現象は一般に双相性の指数関数的減衰関数として記述されます。すなわち、重みの瞬時変化は以下のように与えられます:

\begin{eqnarray}
& ΔW\Delta W &=& A_+ e^{ (tpretpost)/t_{pre}-t_{post})/\tau_+} &iftpost>tpre\text{if} \hspace{5mm} t_{post} > t_{pre}& \
& ΔW\Delta W &=& -A_- e^{- (tpretpost)/t_{pre}-t_{post})/\tau_-} &iftpost<tpre\text{if} \hspace{5mm} t_{post} < t_{pre}&
\end{eqnarray}

ここで、ΔW\Delta Wはシナプス重みの変化を示し、A+A_+AA_-はシナプス変化の最大値(シナプス前後のスパイクのタイミング差がゼロに近いときに起こる)を決定し、τ+\tau_+τ\tau_-はシナプス強化または弱化が起こるシナプス前後のインタースパイク間隔の範囲を決定します。したがって、ΔW>0\Delta W > 0はシナプス後ニューロンがシナプス前ニューロンの後にスパイクすることを意味します。

このモデルは、シナプス後の活動電位の数ミリ秒前にシナプス前スパイクが繰り返し起こるとシナプスの長期増強(LTP)が起こり、シナプス後のスパイク後にシナプス前スパイクが繰り返し起こると同じシナプスの長期抑圧(LTD)が起こる現象を捉えています。

シナプス前後のスパイク間の遅延(Δt\Delta t)は以下のように定義されます:

Δt=tpretpost\Delta t = t_{\rm pre} - t_{\rm post}

ここで、tpret_{\rm pre}tpostt_{\rm post}はそれぞれシナプス前とシナプス後のスパイクのタイミングです。

以下のコードを完成させてSTDPパラメータを設定し、STDP関数をプロットしてください。簡単のために、**τ+=τ=τstdp\tau_{+} = \tau_{-} = \tau_{\rm stdp}**と仮定します。

コーディング演習1A:STDP変化量ΔW\Delta Wの計算

上記のように、ΔW\Delta Wの式はtpost>tpret_{post}>t_{pre}tpost<tpret_{post}<t_{pre}で異なります。コードでは、tpretpostt_{pre}-t_{post}を表すパラメータtime_diffを使用します。

コードを実装した後、STDPカーネルを可視化できます。これは、シナプス前後のスパイク間の遅延に応じてシナプス重みがどの程度変化するかを示します。

def Delta_W(pars, A_plus, A_minus, tau_stdp):
  """
  Plot STDP biphasic exponential decaying function

  Args:
    pars       : parameter dictionary
    A_plus     : (float) maximum amount of synaptic modification
                 which occurs when the timing difference between pre- and
                 post-synaptic spikes is positive
    A_minus    : (float) maximum amount of synaptic modification
                 which occurs when the timing difference between pre- and
                 post-synaptic spikes is negative
    tau_stdp   : the ranges of pre-to-postsynaptic interspike intervals
                 over which synaptic strengthening or weakening occurs

  Returns:
    dW         : instantaneous change in weights
  """
  #######################################################################
  ## TODO for students: compute dP, then remove the NotImplementedError #
  # Fill out when you finish the function
  raise NotImplementedError("Student exercise: compute dW, the change in weights!")
  #######################################################################
  # STDP change
  dW = np.zeros(len(time_diff))
  # Calculate dW for LTP
  dW[time_diff <= 0] = ...
  # Calculate dW for LTD
  dW[time_diff > 0] = ...

  return dW


pars = default_pars_STDP()
# Get parameters
A_plus, A_minus, tau_stdp = pars['A_plus'], pars['A_minus'], pars['tau_stdp']
# pre_spike time - post_spike time
time_diff = np.linspace(-5 * tau_stdp, 5 * tau_stdp, 50)

dW = Delta_W(pars, A_plus, A_minus, tau_stdp)
mySTDP_plot(A_plus, A_minus, tau_stdp, time_diff, dW)

解答を見る$

出力例:

Solution hint
# @title Submit your feedback
content_review(f"{feedback_prefix}_Compute_STDP_changes_Exercise")

シナプス前後のスパイクの追跡

ニューロンは多数のシナプス前スパイク入力を受けるため、異なるシナプスを考慮してSTDPを実装するには、シミュレーション中にシナプス前後のスパイク時刻を追跡する必要があります。

便利な方法として、各シナプス後ニューロンに対して以下の方程式を定義します:

τdMdt=M\tau_{-} \frac{dM}{dt} = -M

そしてシナプス後ニューロンがスパイクするたびに、

M(t)=M(t)AM(t) = M(t) - A_{-}

このようにしてM(t)M(t)は時定数τ\tau_{-}のスケールでシナプス後スパイクの数を追跡します。

同様に、各シナプス前ニューロンに対しては:

τ+dPdt=P\tau_{+} \frac{dP}{dt} = -P

そしてシナプス前ニューロンにスパイクがあるたびに、

P(t)=P(t)+A+P(t) = P(t) + A_{+}

変数M(t)M(t)P(t)P(t)はシナプスコンダクタンスgi(t)g_{i}(t)の方程式に似ていますが、はるかに長い時間スケールでシナプス前後のスパイク時刻を追跡するために使われます。M(t)M(t)は常に負で、P(t)P(t)は常に正です。MMはLTDを誘導し、PPはLTPを誘導するためにそれぞれAA_{-}A+A_{+}で更新されることは想像できるでしょう。

重要な注意: P(t)P(t)はシナプス前スパイク時刻に依存します。シナプス前スパイク時刻がわかれば、PPはシナプス後ニューロンのシミュレーション前に生成でき、対応するSTDP重みも計算できます。

PPの可視化

ここでは、単一のシナプス後ニューロンがNN個のシナプス前ニューロンに接続されているシナリオを考えます。

例えば、1つのシナプス後ニューロンが5つのシナプス前ニューロンからのポアソン型スパイク入力を受けています。

各シナプス前ニューロンについてPPをシミュレートできます。

コーディング演習1B:dPdPの計算

ここでも、前のチュートリアルで何度も紹介したオイラー法を使います。

LIFモデルの膜電位の動力学と同様に、時間ステップdtdtP(t)P(t)dtτ+P(t)\displaystyle{\frac{dt}{\tau_+}P(t)}だけ減少します。一方、シナプス前スパイクが到着するとP(t)P(t)は瞬時にA+A_+だけ増加します。したがって、


dP = -\displaystyle{\frac{dt}{\tau_+}P[t]} + A_+\cdot \text{sp_or_not}[t+dt]

ここで\text{sp_or_not}はバイナリ変数で、dtdt内にスパイクがあれば1、なければ0です。

def generate_P(pars, pre_spike_train_ex):
  """
  track of pre-synaptic spikes

  Args:
    pars               : parameter dictionary
    pre_spike_train_ex : binary spike train input from
                         presynaptic excitatory neuron

  Returns:
    P                  : LTP ratio
  """

  # Get parameters
  A_plus, tau_stdp = pars['A_plus'], pars['tau_stdp']
  dt, range_t = pars['dt'], pars['range_t']
  Lt = range_t.size

  # Initialize
  P = np.zeros(pre_spike_train_ex.shape)
  for it in range(Lt - 1):
    #######################################################################
    ## TODO for students: compute dP, then remove the NotImplementedError #
    # Fill out when you finish the function
    raise NotImplementedError("Student exercise: compute P, the change of presynaptic spike")
    #######################################################################
    # Calculate the delta increment dP
    dP = ...
    # Update P
    P[:, it + 1] = P[:, it] + dP

  return P


pars = default_pars_STDP(T=200., dt=1.)
pre_spike_train_ex = Poisson_generator(pars, rate=10, n=5, myseed=2020)
P = generate_P(pars, pre_spike_train_ex)
my_example_P(pre_spike_train_ex, pars, P)

解答を見る$

出力例:

Solution hint
# @title Submit your feedback
content_review(f"{feedback_prefix}_Compute_dP_Exercise")

セクション2:STDPの実装

最後に、スパイキングネットワークでSTDPを実装するために、シナプス前後のタイミングに基づいてピークシナプスコンダクタンスの値を変化させ、変数P(t)P(t)M(t)M(t)を使います。

各シナプスiiはそれぞれピークシナプスコンダクタンス(gˉi\bar g_i)を持ち、これは[0,gˉmax][0, \bar g_{max}]の範囲で変動し、シナプス前後のタイミングに応じて変化します。

ここで$M(t)$は最後のシナプス後スパイクからの時間を追跡し常に負です。したがって、*シナプス後ニューロンがシナプス前ニューロンの直前にスパイクした場合*、ピークコンダクタンスは減少します。 * シナプス後ニューロンがスパイクすると、**すべての**シナプスのピークコンダクタンスは以下のように更新されます:

\bar g_i = \bar g_i + P_i(t)\bar g_{max}, \forall i

ここで$P_i(t)$は$i$番目のシナプス前ニューロンの最後のスパイクからの時間を追跡し常に正です。 したがって、シナプス前ニューロンがシナプス後ニューロンの前にスパイクすると、そのピークコンダクタンスは増加します。

STDPを示すシナプスに接続されたLIFニューロン

次の演習では、NN個のシナプス前ニューロンを単一のシナプス後ニューロンに接続します。スパイク時刻のみ関心があるため、各シナプス前ニューロンの動力学はシミュレートしません。代わりにNN個のポアソン型スパイクを生成します。ここでは、すべての入力が興奮性であると仮定します。

シナプス後ニューロンのスパイク時刻は未知なので、その動力学はシミュレートする必要があります。シナプス後ニューロンは興奮性入力のみを受けるLIFニューロンとしてモデル化します。

τmdVdt=(VEL)gE(t)(V(t)EE)\tau_m\frac{dV}{dt} = -(V-E_L) - g_E(t) (V(t)-E_E)\,

ここで、総興奮性シナプスコンダクタンスgE(t)g_{E}(t)は以下のように与えられます:

gE(t)=i=1Ngi(t)g_E(t) = \sum_{i=1}^{N} g_i(t)

STDPをシミュレートする際は、gˉi\bar g_iがその範囲を超えないように注意することが重要です。

# @title Function for LIF neuron with STDP synapses

def run_LIF_cond_STDP(pars, pre_spike_train_ex):
  """
  conductance-based LIF dynamics

  Args:
    pars               : parameter dictionary
    pre_spike_train_ex : spike train input from presynaptic excitatory neuron

  Returns:
    rec_spikes         : spike times
    rec_v              : mebrane potential
    gE                 : postsynaptic excitatory conductance
  """

  # Retrieve parameters
  V_th, V_reset = pars['V_th'], pars['V_reset']
  tau_m = pars['tau_m']
  V_init, V_L = pars['V_init'], pars['V_L']
  gE_bar, VE, tau_syn_E = pars['gE_bar'], pars['VE'], pars['tau_syn_E']
  gE_init = pars['gE_init']
  tref = pars['tref']
  A_minus, tau_stdp = pars['A_minus'], pars['tau_stdp']
  dt, range_t = pars['dt'], pars['range_t']
  Lt = range_t.size

  P = generate_P(pars, pre_spike_train_ex)

  # Initialize
  tr = 0.
  v = np.zeros(Lt)
  v[0] = V_init
  M = np.zeros(Lt)
  gE = np.zeros(Lt)
  gE_bar_update = np.zeros(pre_spike_train_ex.shape)
  gE_bar_update[:, 0] = gE_init  # note: gE_bar is the maximum value

  # simulation
  rec_spikes = []  # recording spike times
  for it in range(Lt - 1):
      if tr > 0:
          v[it] = V_reset
          tr = tr - 1
      elif v[it] >= V_th:   # reset voltage and record spike event
          rec_spikes.append(it)
          v[it] = V_reset
          M[it] = M[it] - A_minus
          gE_bar_update[:, it] = gE_bar_update[:, it] + P[:, it] * gE_bar
          id_temp = gE_bar_update[:, it] > gE_bar
          gE_bar_update[id_temp, it] = gE_bar
          tr = tref / dt

      # update the synaptic conductance
      M[it + 1] = M[it] - dt / tau_stdp * M[it]
      gE[it + 1] = gE[it] - (dt / tau_syn_E) * gE[it] + (gE_bar_update[:, it] * pre_spike_train_ex[:, it]).sum()
      gE_bar_update[:, it + 1] = gE_bar_update[:, it] + M[it]*pre_spike_train_ex[:, it]*gE_bar
      id_temp = gE_bar_update[:, it + 1] < 0
      gE_bar_update[id_temp, it + 1] = 0.

      # calculate the increment of the membrane potential
      dv = (-(v[it] - V_L) - gE[it + 1] * (v[it] - VE)) * (dt / tau_m)

      # update membrane potential
      v[it + 1] = v[it] + dv

  rec_spikes = np.array(rec_spikes) * dt

  return v, rec_spikes, gE, P, M, gE_bar_update

print(help(run_LIF_cond_STDP))

興奮性シナプスコンダクタンスの変化

以下では、N=300N=300個のシナプス前ニューロンから入力を受けるLIFニューロンをシミュレートします。

pars = default_pars_STDP(T=200., dt=1.)  # Simulation duration 200 ms
pars['gE_bar'] = 0.024                   # max synaptic conductance
pars['gE_init'] = 0.024                  # initial synaptic conductance
pars['VE'] = 0.                          # [mV] Synapse reversal potential
pars['tau_syn_E'] = 5.                   # [ms] EPSP time constant

# generate Poisson type spike trains
pre_spike_train_ex = Poisson_generator(pars, rate=10, n=300, myseed=2020)
# simulate the LIF neuron and record the synaptic conductance
v, rec_spikes, gE, P, M, gE_bar_update = run_LIF_cond_STDP(pars,
                                                           pre_spike_train_ex)
# @title Figures of the evolution of synaptic conductance

# @markdown Run this cell to see the figures!
plt.figure(figsize=(12., 8))
plt.subplot(321)
dt, range_t = pars['dt'], pars['range_t']
if rec_spikes.size:
  sp_num = (rec_spikes / dt).astype(int) - 1
  v[sp_num] += 10   # add artificial spikes
plt.plot(pars['range_t'], v, 'k')
plt.xlabel('Time (ms)')
plt.ylabel('V (mV)')

plt.subplot(322)
# Plot the sample presynaptic spike trains
my_raster_plot(pars['range_t'], pre_spike_train_ex, 10)

plt.subplot(323)
plt.plot(pars['range_t'], M, 'k')
plt.xlabel('Time (ms)')
plt.ylabel('M')

plt.subplot(324)
for i in range(10):
  plt.plot(pars['range_t'], P[i, :])
plt.xlabel('Time (ms)')
plt.ylabel('P')

plt.subplot(325)
for i in range(10):
  plt.plot(pars['range_t'], gE_bar_update[i, :])
plt.xlabel('Time (ms)')
plt.ylabel(r'$\bar g$')

plt.subplot(326)
plt.plot(pars['range_t'], gE, 'r')
plt.xlabel('Time (ms)')
plt.ylabel(r'$g_E$')

plt.tight_layout()
plt.show()

考えてみよう!2A:シナプス強度とコンダクタンスの変化の解析

解答を見る$

# @title Submit your feedback
content_review(f"{feedback_prefix}_Analyzing_synaptic_strength_Discussion")

シナプス重みの分布

上記の例から、一部のシナプスは脱増強されることがわかりましたが、STDPを示すシナプスのシナプス重みの分布はどうなっているでしょうか?

実際、シナプス重みの分布自体も時間変動する可能性があります。そこで、シナプス重みの分布が時間とともにどのように変化するかを知りたいです。

重み分布とその時間変化のより良い推定のために、シナプス前発火率を15Hzに上げ、シナプス後ニューロンを120秒間シミュレートします。

# @title Functions for simulating a LIF neuron with STDP synapses


def example_LIF_STDP(inputrate=15., Tsim=120000.):
  """
  Simulation of a LIF model with STDP synapses

  Args:
    intputrate  :  The rate used for generate presynaptic spike trains
    Tsim        :  Total simulation time

  output:
    Interactive demo, Visualization of synaptic weights
  """

  pars = default_pars_STDP(T=Tsim,  dt=1.)
  pars['gE_bar'] = 0.024
  pars['gE_init'] = 0.014  # initial synaptic conductance
  pars['VE'] = 0.          # [mV]
  pars['tau_syn_E'] = 5.   # [ms]

  starttime = time.perf_counter()
  pre_spike_train_ex = Poisson_generator(pars, rate=inputrate, n=300,
                                         myseed=2020)  # generate Poisson trains
  v, rec_spikes, gE, P, M, gE_bar_update = run_LIF_cond_STDP(pars,
                                                             pre_spike_train_ex)  # simulate LIF neuron with STDP
  gbar_norm = gE_bar_update/pars['gE_bar']  # calculate the ratio of the synaptic conductance
  endtime = time.perf_counter()
  timecost = (endtime - starttime) / 60.

  print('Total simulation time is %.3f min' % timecost)

  my_layout.width = '620px'

  @widgets.interact(
      sample_time=widgets.FloatSlider(0.5, min=0., max=1., step=0.1,
                                      layout=my_layout)
  )


  def my_visual_STDP_distribution(sample_time=0.0):
    sample_time = int(sample_time * pars['range_t'].size) - 1
    sample_time = sample_time * (sample_time > 0)

    plt.figure(figsize=(8, 8))
    ax1 = plt.subplot(211)
    for i in range(50):
      ax1.plot(pars['range_t'][::1000] / 1000., gE_bar_update[i, ::1000], lw=1., alpha=0.7)

    ax1.axvline(1e-3 * pars['range_t'][sample_time], 0., 1., color='k', ls='--')
    ax1.set_ylim(0, 0.025)
    ax1.set_xlim(-2, 122)
    ax1.set_xlabel('Time (s)')
    ax1.set_ylabel(r'$\bar{g}$')

    bins = np.arange(-.05, 1.05, .05)
    g_dis, _ = np.histogram(gbar_norm[:, sample_time], bins)
    ax2 = plt.subplot(212)
    ax2.bar(bins[1:], g_dis, color='b', alpha=0.5, width=0.05)
    ax2.set_xlim(-0.1, 1.1)
    ax2.set_xlabel(r'$\bar{g}/g_{\mathrm{max}}$')
    ax2.set_ylabel('Number')
    ax2.set_title(('Time = %.1f s' % (1e-3 * pars['range_t'][sample_time])),
                  fontweight='bold')
    plt.show()


print(help(example_LIF_STDP))

インタラクティブデモ2:STDPを持つLIFモデルの例

# @markdown Make sure you execute this cell to enable the widget!
example_LIF_STDP(inputrate=15)

考えてみよう!2B:シナプス前発火率増加の影響

シナプス前ニューロンの発火率(例えば30Hz)を増加させ、シナプス重み分布の動態に与える影響を調べてみましょう。

解答を見る$

# @title Submit your feedback
content_review(f"{feedback_prefix}_LIF_and_STDP_Interactive_Demo_and_Discussion")

セクション3:入力相関の影響

これまで、入力集団は無相関であると仮定してきました。もしシナプス前ニューロンが相関していたらどうなると思いますか?

以下では、最初のLL個のニューロンが同一のスパイク列を持ち、残りの入力は無相関であるように入力を変更します。これは相関を導入する非常に単純化したモデルです。自分で相関スパイク列のモデルをコーディングしてみてもよいでしょう。

# @title Function for LIF neuron with STDP synapses receiving correlated inputs


def example_LIF_STDP_corrInput(inputrate=20., Tsim=120000.):
  """
  A LIF model equipped with STDP synapses, receiving correlated inputs

  Args:
    intputrate      :  The rate used for generate presynaptic spike trains
    Tsim            :  Total simulation time

  Returns:
    Interactive demo: Visualization of synaptic weights
  """

  np.random.seed(2020)
  pars = default_pars_STDP(T=Tsim, dt=1.)
  pars['gE_bar'] = 0.024
  pars['VE'] = 0.                                # [mV]
  pars['gE_init'] = 0.024 * np.random.rand(300)  # initial synaptic conductance
  pars['tau_syn_E'] = 5.                         # [ms]

  starttime = time.perf_counter()
  pre_spike_train_ex = Poisson_generator(pars, rate=inputrate, n=300, myseed=2020)
  for i_pre in range(50):
    pre_spike_train_ex[i_pre, :] = pre_spike_train_ex[0, :]  # simple way to set input correlated
  v, rec_spikes, gE, P, M, gE_bar_update = run_LIF_cond_STDP(pars, pre_spike_train_ex) # simulate LIF neuron with STDP
  gbar_norm = gE_bar_update / pars['gE_bar']  # calculate the ratio of the synaptic conductance
  endtime = time.perf_counter()
  timecost = (endtime - starttime) / 60.

  print(f'Total simulation time is {timecost:.3f} min')

  my_layout.width = '620px'

  @widgets.interact(
      sample_time=widgets.FloatSlider(0.5, min=0., max=1., step=0.05,
                                      layout=my_layout)
  )


  def my_visual_STDP_distribution(sample_time=0.0):

    sample_time = int(sample_time * pars['range_t'].size) - 1
    sample_time = sample_time*(sample_time > 0)
    figtemp = plt.figure(figsize=(8, 8))
    ax1 = plt.subplot(211)
    for i in range(50):
      ax1.plot(pars['range_t'][::1000] / 1000.,
               gE_bar_update[i, ::1000], lw=1., color='r', alpha=0.7, zorder=2)

    for i in range(50):
      ax1.plot(pars['range_t'][::1000] / 1000.,
               gE_bar_update[i + 50, ::1000], lw=1., color='b',
               alpha=0.5, zorder=1)

    ax1.axvline(1e-3 * pars['range_t'][sample_time], 0., 1.,
                color='k', ls='--', zorder=2)
    ax1.set_ylim(0, 0.025)
    ax1.set_xlim(-2, 122)
    ax1.set_xlabel('Time (s)')
    ax1.set_ylabel(r'$\bar{g}$')
    legend=ax1.legend(['Correlated input', 'Uncorrelated iput'], fontsize=18,
                      loc=[0.92, -0.6], frameon=False)
    for color, text, item in zip(['r', 'b'], legend.get_texts(), legend.legend_handles):
      text.set_color(color)
      item.set_visible(False)

    bins = np.arange(-.05, 1.05, .05)
    g_dis_cc, _ = np.histogram(gbar_norm[:50, sample_time], bins)
    g_dis_dp, _ = np.histogram(gbar_norm[50:, sample_time], bins)

    ax2 = plt.subplot(212)
    ax2.bar(bins[1:], g_dis_cc, color='r', alpha=0.5, width=0.05)
    ax2.bar(bins[1:], g_dis_dp, color='b', alpha=0.5, width=0.05)
    ax2.set_xlim(-0.1, 1.1)
    ax2.set_xlabel(r'$\bar{g}/g_{\mathrm{max}}$')
    ax2.set_ylabel('Number')
    ax2.set_title(('Time = %.1f s' % (1e-3 * pars['range_t'][sample_time])),
                  fontweight='bold')
    plt.show()


print(help(example_LIF_STDP_corrInput))

インタラクティブデモ3:相関入力を受ける可塑性シナプスを持つLIFモデル

# @markdown Make sure you execute this cell to enable the widget!
example_LIF_STDP_corrInput(inputrate=10.0)

なぜSTDPを示すシナプスで無相関ニューロンの重みが減少するのか?

上記では、相関したニューロン(同時にスパイクする)がほとんど影響を受けなかった一方で、他のニューロンの重みは減少しました。なぜでしょうか?

理由は、相関したシナプス前ニューロンはシナプス後ニューロンのスパイクを誘発する確率が高くなり、prepostpre \rightarrow postのスパイクペアリングが生じるためです。

考えてみよう!3:STDPの応用

解答を見る$

# @title Submit your feedback
content_review(f"{feedback_prefix}_LIF_plasticity_correlated_inputs_Interactive_Demo_and_Discussion")

まとめ

お疲れさまでした!この長い1日を終えましたね!このチュートリアルでは、**スパイクタイミング依存可塑性(STDP)**の概念を扱いました。

以下を達成しました:

ポアソン型スパイク列をシナプス前入力として用い、STDPを備えたシナプスを持つLIFモデルを構築しました。また、相関入力がシナプス強度に与える影響も調べました!