Open In Colab   Open in Kaggle

チュートリアル 2: Wilson-Cowanモデル

第2週, 第5日目: 動的ネットワーク

Neuromatch Academyによる

コンテンツ制作者: Qinglong Gu, Songtin Li, Arvind Kumar, John Murray, Julijana Gjorgjieva

コンテンツレビュアー: Maryam Vaziri-Pashkam, Ella Batty, Lorenzo Fontolan, Richard Gao, Spiros Chavlis, Michael Waskom, Siddharth Suresh

制作編集: Gagana B, Spiros Chavlis


チュートリアルの目的

チュートリアルの推定所要時間: 1時間35分

前回のチュートリアルでは、興奮性集団のみからなる神経ネットワークに慣れ親しみました。ここでは、興奮性と抑制性の両方の神経集団を含むネットワークに拡張します。2つの相互作用する興奮性および抑制性ニューロン集団のダイナミクスを研究するためのシンプルでありながら強力なモデルが、いわゆるWilson-Cowanレートモデルであり、本チュートリアルの主題です。

このチュートリアルの目的は以下の通りです:

ボーナスステップ:

ボーナスステップ(応用):


参考文献:

Wilson, H., and Cowan, J. (1972). Excitatory and inhibitory interactions in localized populations of model neurons. Biophysical Journal 12. doi: 10.1016/S0006-3495(72)86068-5.

# @title Tutorial slides
# @markdown These are the slides for the videos in all tutorials today
from IPython.display import IFrame
link_id = "nvuty"
print(f"If you want to download the slides: https://osf.io/download/{link_id}/")
IFrame(src=f"https://mfr.ca-1.osf.io/render?url=https://osf.io/{link_id}/?direct%26mode=render%26action=download%26mode=render", width=854, height=480)

セットアップ

# @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 = "W2D4_T2"
# Imports
import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize as opt  # root-finding algorithm
# @title Figure Settings
import logging
logging.getLogger('matplotlib.font_manager').disabled = True

import ipywidgets as widgets  # interactive display
%config InlineBackend.figure_format = 'retina'
plt.style.use("https://raw.githubusercontent.com/NeuromatchAcademy/course-content/main/nma.mplstyle")
# @title Plotting Functions

def plot_FI_inverse(x, a, theta):
  f, ax = plt.subplots()
  ax.plot(x, F_inv(x, a=a, theta=theta))
  ax.set(xlabel="$x$", ylabel="$F^{-1}(x)$")


def plot_FI_EI(x, FI_exc, FI_inh):
  plt.figure()
  plt.plot(x, FI_exc, 'b', label='E population')
  plt.plot(x, FI_inh, 'r', label='I population')
  plt.legend(loc='lower right')
  plt.xlabel('x (a.u.)')
  plt.ylabel('F(x)')
  plt.show()


def my_test_plot(t, rE1, rI1, rE2, rI2):

  plt.figure()
  ax1 = plt.subplot(211)
  ax1.plot(pars['range_t'], rE1, 'b', label='E population')
  ax1.plot(pars['range_t'], rI1, 'r', label='I population')
  ax1.set_ylabel('Activity')
  ax1.legend(loc='best')

  ax2 = plt.subplot(212, sharex=ax1, sharey=ax1)
  ax2.plot(pars['range_t'], rE2, 'b', label='E population')
  ax2.plot(pars['range_t'], rI2, 'r', label='I population')
  ax2.set_xlabel('t (ms)')
  ax2.set_ylabel('Activity')
  ax2.legend(loc='best')

  plt.tight_layout()
  plt.show()


def plot_nullclines(Exc_null_rE, Exc_null_rI, Inh_null_rE, Inh_null_rI):

  plt.figure()
  plt.plot(Exc_null_rE, Exc_null_rI, 'b', label='E nullcline')
  plt.plot(Inh_null_rE, Inh_null_rI, 'r', label='I nullcline')
  plt.xlabel(r'$r_E$')
  plt.ylabel(r'$r_I$')
  plt.legend(loc='best')
  plt.show()


def my_plot_nullcline(pars):
  Exc_null_rE = np.linspace(-0.01, 0.96, 100)
  Exc_null_rI = get_E_nullcline(Exc_null_rE, **pars)
  Inh_null_rI = np.linspace(-.01, 0.8, 100)
  Inh_null_rE = get_I_nullcline(Inh_null_rI, **pars)

  plt.plot(Exc_null_rE, Exc_null_rI, 'b', label='E nullcline')
  plt.plot(Inh_null_rE, Inh_null_rI, 'r', label='I nullcline')
  plt.xlabel(r'$r_E$')
  plt.ylabel(r'$r_I$')
  plt.legend(loc='best')


def my_plot_vector(pars, my_n_skip=2, myscale=5):
  EI_grid = np.linspace(0., 1., 20)
  rE, rI = np.meshgrid(EI_grid, EI_grid)
  drEdt, drIdt = EIderivs(rE, rI, **pars)

  n_skip = my_n_skip

  plt.quiver(rE[::n_skip, ::n_skip], rI[::n_skip, ::n_skip],
             drEdt[::n_skip, ::n_skip], drIdt[::n_skip, ::n_skip],
             angles='xy', scale_units='xy', scale=myscale, facecolor='c')

  plt.xlabel(r'$r_E$')
  plt.ylabel(r'$r_I$')


def my_plot_trajectory(pars, mycolor, x_init, mylabel):
  pars = pars.copy()
  pars['rE_init'], pars['rI_init'] = x_init[0], x_init[1]
  rE_tj, rI_tj = simulate_wc(**pars)

  plt.plot(rE_tj, rI_tj, color=mycolor, label=mylabel)
  plt.plot(x_init[0], x_init[1], 'o', color=mycolor, ms=8)
  plt.xlabel(r'$r_E$')
  plt.ylabel(r'$r_I$')


def my_plot_trajectories(pars, dx, n, mylabel):
  """
  Solve for I along the E_grid from dE/dt = 0.

  Expects:
  pars    : Parameter dictionary
  dx      : increment of initial values
  n       : n*n trjectories
  mylabel : label for legend

  Returns:
    figure of trajectory
  """
  pars = pars.copy()
  for ie in range(n):
    for ii in range(n):
      pars['rE_init'], pars['rI_init'] = dx * ie, dx * ii
      rE_tj, rI_tj = simulate_wc(**pars)
      if (ie == n-1) & (ii == n-1):
          plt.plot(rE_tj, rI_tj, 'gray', alpha=0.8, label=mylabel)
      else:
          plt.plot(rE_tj, rI_tj, 'gray', alpha=0.8)

  plt.xlabel(r'$r_E$')
  plt.ylabel(r'$r_I$')


def plot_complete_analysis(pars):
  plt.figure(figsize=(7.7, 6.))

  # plot example trajectories
  my_plot_trajectories(pars, 0.2, 6,
                       'Sample trajectories \nfor different init. conditions')
  my_plot_trajectory(pars, 'orange', [0.6, 0.8],
                     'Sample trajectory for \nlow activity')
  my_plot_trajectory(pars, 'm', [0.6, 0.6],
                     'Sample trajectory for \nhigh activity')

  # plot nullclines
  my_plot_nullcline(pars)

  # plot vector field
  EI_grid = np.linspace(0., 1., 20)
  rE, rI = np.meshgrid(EI_grid, EI_grid)
  drEdt, drIdt = EIderivs(rE, rI, **pars)
  n_skip = 2
  plt.quiver(rE[::n_skip, ::n_skip], rI[::n_skip, ::n_skip],
             drEdt[::n_skip, ::n_skip], drIdt[::n_skip, ::n_skip],
             angles='xy', scale_units='xy', scale=5., facecolor='c')

  plt.legend(loc=[1.02, 0.57], handlelength=1)
  plt.show()


def plot_fp(x_fp, position=(0.02, 0.1), rotation=0):
  plt.plot(x_fp[0], x_fp[1], 'ko', ms=8)
  plt.text(x_fp[0] + position[0], x_fp[1] + position[1],
           f'Fixed Point1=\n({x_fp[0]:.3f}, {x_fp[1]:.3f})',
           horizontalalignment='center', verticalalignment='bottom',
           rotation=rotation)
# @title Helper Functions

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

  # Excitatory parameters
  pars['tau_E'] = 1.     # Timescale of the E population [ms]
  pars['a_E'] = 1.2      # Gain of the E population
  pars['theta_E'] = 2.8  # Threshold of the E population

  # Inhibitory parameters
  pars['tau_I'] = 2.0    # Timescale of the I population [ms]
  pars['a_I'] = 1.0      # Gain of the I population
  pars['theta_I'] = 4.0  # Threshold of the I population

  # Connection strength
  pars['wEE'] = 9.   # E to E
  pars['wEI'] = 4.   # I to E
  pars['wIE'] = 13.  # E to I
  pars['wII'] = 11.  # I to I

  # External input
  pars['I_ext_E'] = 0.
  pars['I_ext_I'] = 0.

  # simulation parameters
  pars['T'] = 50.        # Total duration of simulation [ms]
  pars['dt'] = .1        # Simulation time step [ms]
  pars['rE_init'] = 0.2  # Initial value of E
  pars['rI_init'] = 0.2  # Initial value of I

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

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

  return pars


def F(x, a, theta):
  """
  Population activation function, F-I curve

  Args:
    x     : the population input
    a     : the gain of the function
    theta : the threshold of the function

  Returns:
    f     : the population activation response f(x) for input x
  """

  # add the expression of f = F(x)
  f = (1 + np.exp(-a * (x - theta)))**-1 - (1 + np.exp(a * theta))**-1

  return f


def dF(x, a, theta):
  """
  Derivative of the population activation function.

  Args:
    x     : the population input
    a     : the gain of the function
    theta : the threshold of the function

  Returns:
    dFdx  :  Derivative of the population activation function.
  """

  dFdx = a * np.exp(-a * (x - theta)) * (1 + np.exp(-a * (x - theta)))**-2

  return dFdx

含まれているヘルパー関数:


セクション 1: 興奮性および抑制性集団のWilson-Cowanモデル

# @title Video 1: Phase analysis of the Wilson-Cowan E-I model
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', 'GCpQmh45crM'), ('Bilibili', 'BV1CD4y1m7dK')]
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}_Phase_analysis_Video")

このビデオでは、興奮性および抑制性ニューロン集団が相互作用するネットワーク(Wilson-Cowanモデル)のモデリング方法を説明します。ネットワーク活動の時間変化の解法を示し、2次元の位相平面を紹介します。

セクション 1.1: WCモデルの数理的記述

チュートリアル開始からここまでの推定時間: 12分

関連ビデオ内容のテキスト要約はこちらをクリック

脳内で記録される豊かなダイナミクスの多くは、興奮性および抑制性ニューロンサブタイプの相互作用によって生成されます。ここでは、前回のチュートリアルと同様に、EおよびIの2つの結合した集団をモデル化します(Wilson-Cowanモデル)。興奮性または抑制性集団のダイナミクスを表す2つの結合微分方程式を記述できます:

\begin{align}
τEdrEdt\tau_E \frac{dr_E}{dt} &= -rEr_E + FE(wEErEF_E(w_{EE}r_E -wEIrIw_{EI}r_I + I^{\text{ext}}_E;a_E,θE)\theta_E)\
τIdrIdt\tau_I \frac{dr_I}{dt} &= -rIr_I + FI(wIErEF_I(w_{IE}r_E -wIIrIw_{II}r_I + I^{\text{ext}}_I;a_I,\theta_I) \qquad (1) \end{align}

rE(t)r_E(t) は時刻 tt における興奮性集団の平均活性化(または発火率)、rI(t)r_I(t) は抑制性集団の活性化(または発火率)を表します。パラメータ τE\tau_EτI\tau_I はそれぞれの集団のダイナミクスの時間スケールを制御します。結合強度は wEEw_{EE}(E→E)、wEIw_{EI}(I→E)、wIEw_{IE}(E→I)、wIIw_{II}(I→I)で与えられます。wEIw_{EI}wIEw_{IE} はそれぞれ抑制性から興奮性集団への結合、及びその逆を表します。伝達関数(またはF-I曲線)FE(x;aE,θE)F_E(x;a_E,\theta_E)FI(x;aI,θI)F_I(x;a_I,\theta_I) は興奮性および抑制性集団で異なる場合があります。

コーディング演習 1.1: EおよびI集団のF-I曲線をプロットしよう

まずはヘルパー関数 F を用いて、デフォルトパラメータでEおよびI集団のF-I曲線をプロットしましょう。

help(F)
pars = default_pars()
x = np.arange(0, 10, .1)

print(pars['a_E'], pars['theta_E'])
print(pars['a_I'], pars['theta_I'])

###################################################################
# TODO for students: compute and plot the F-I curve here          #
# Note: aE, thetaE, aI and theta_I are in the dictionary 'pairs'   #
raise NotImplementedError('student exercise: compute F-I curves of excitatory and inhibitory populations')
###################################################################

# Compute the F-I curve of the excitatory population
FI_exc = ...

# Compute the F-I curve of the inhibitory population
FI_inh = ...

# Visualize
plot_FI_EI(x, FI_exc, FI_inh)

解答を見る$

出力例:

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

セクション 1.2: Wilson-Cowanモデルのシミュレーションスキーム

チュートリアル開始からここまでの推定時間: 20分

再び、オイラー法を用いて方程式を数値積分できます。EおよびI集団のダイナミクスはステップ幅 Δt\Delta t の時間グリッド上でシミュレート可能です。興奮性および抑制性集団の活動の更新は以下のように書けます:

\begin{align}
r_E[k+1] &= r_E[k] + ΔrE\Delta r_E[k]\
r_I[k+1] &= r_I[k] + ΔrI\Delta r_I[k]
\end{align}

増分は

\begin{align}
ΔrE\Delta r_E[k] &= ΔtτE\frac{\Delta t}{\tau_E}[-r_E[k] + FE(wEErE[k]F_E(w_{EE}r_E[k] -wEIrI[k]w_{EI}r_I[k] + I^{\text{ext}}_E[k];a_E,θE)\theta_E)]\
ΔrI\Delta r_I[k] &= ΔtτI\frac{\Delta t}{\tau_I}[-r_I[k] + FI(wIErE[k]F_I(w_{IE}r_E[k] -wIIrI[k]w_{II}r_I[k] + I^{\text{ext}}_I[k];a_I,θI)\theta_I)]
\end{align}

コーディング演習 1.2: Wilson-Cowan方程式を数値積分しよう

この数値シミュレーションを実装し、似た初期値での2つのシミュレーションを可視化します。

def simulate_wc(tau_E, a_E, theta_E, tau_I, a_I, theta_I,
                wEE, wEI, wIE, wII, I_ext_E, I_ext_I,
                rE_init, rI_init, dt, range_t, **other_pars):
  """
  Simulate the Wilson-Cowan equations

  Args:
    Parameters of the Wilson-Cowan model

  Returns:
    rE, rI (arrays) : Activity of excitatory and inhibitory populations
  """
  # Initialize activity arrays
  Lt = range_t.size
  rE = np.append(rE_init, np.zeros(Lt - 1))
  rI = np.append(rI_init, np.zeros(Lt - 1))
  I_ext_E = I_ext_E * np.ones(Lt)
  I_ext_I = I_ext_I * np.ones(Lt)

  # Simulate the Wilson-Cowan equations
  for k in range(Lt - 1):

    ########################################################################
    # TODO for students: compute drE and drI and remove the error
    raise NotImplementedError("Student exercise: compute the change in E/I")
    ########################################################################

    # Calculate the derivative of the E population
    drE = ...

    # Calculate the derivative of the I population
    drI = ...

    # Update using Euler's method
    rE[k + 1] = rE[k] + drE
    rI[k + 1] = rI[k] + drI

  return rE, rI


pars = default_pars()

# Simulate first trajectory
rE1, rI1 = simulate_wc(**default_pars(rE_init=.32, rI_init=.15))

# Simulate second trajectory
rE2, rI2 = simulate_wc(**default_pars(rE_init=.33, rI_init=.15))

# Visualize
my_test_plot(pars['range_t'], rE1, rI1, rE2, rI2)

解答を見る$

出力例:

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

上の2つのプロットは、異なる初期条件での興奮性(rEr_E, 青)および抑制性(rIr_I, 赤)活動の時間発展を示しています。

インタラクティブデモ 1.2: 異なる初期値での集団軌道

このインタラクティブデモではWilson-Cowanモデルをシミュレートし、各集団の軌道をプロットします。興奮性集団の初期活動を変化させます。

異なる初期条件でEおよびI集団の軌道はどう変わるでしょうか?

# @markdown Make sure you execute this cell to enable the widget!
@widgets.interact(
    rE_init=widgets.FloatSlider(0.32, min=0.30, max=0.35, step=.01)
)


def plot_EI_diffinitial(rE_init=0.0):

  pars = default_pars(rE_init=rE_init, rI_init=.15)
  rE, rI = simulate_wc(**pars)

  plt.figure()
  plt.plot(pars['range_t'], rE, 'b', label='E population')
  plt.plot(pars['range_t'], rI, 'r', label='I population')
  plt.xlabel('t (ms)')
  plt.ylabel('Activity')
  plt.legend(loc='best')
  plt.show()

解答を見る$

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

考えてみよう! 1.2

異なる初期状態を選ぶと神経応答の定常状態が異なることが明らかです。なぜでしょうか?

次のセクションで議論しますが、まずは考えてみてください。


セクション 2: 位相平面解析

チュートリアル開始からここまでの推定時間: 45分

前回のチュートリアルで1次元システムのダイナミクスを調べるためにグラフィカル手法を使ったように、ここではWilson-Cowanモデルのような2次元システムのダイナミクスを調べるための位相平面解析というグラフィカル手法を学びます。これは前提条件の微積分の日線形システムの日で既に見たことがあります。

これまで、2つの集団の活動を時間の関数としてプロットしてきました。すなわち、Activity-t 平面、つまり (t,rE(t))(t, r_E(t)) 平面または (t,rI(t))(t, r_I(t)) 平面です。代わりに、任意の時刻 tt における2つの活動 rE(t)r_E(t)rI(t)r_I(t) を互いに対してプロットできます。この rI-rE 平面 (rI(t),rE(t))(r_I(t), r_E(t))位相平面と呼びます。位相平面の各点は、rEr_ErIr_I の両方が時間とともにどのように変化するかを示します。

# @title Video 2: Nullclines and Vector Fields
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', 'V2SBAK2Xf8Y'), ('Bilibili', 'BV15k4y1m7Kt')]
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}_Nullclines_and_Vector_Fields_Video")

インタラクティブデモ 2: 活動-時間平面から**rIr_I - rEr_E**位相平面へ

このデモでは、Activity-time(rE, rI) 位相平面の両方でシステムのダイナミクスを可視化します。円は時刻 tt における活動を示し、線はシミュレーション全期間のシステムの進化を表します。

時間スライダーを動かして、上のプロットが下のプロットにどう対応しているか理解しましょう。下のプロットには時間の明示的な情報はありますか?どんな情報を与えてくれますか?

# @markdown Make sure you execute this cell to enable the widget!

pars = default_pars(T=10, rE_init=0.6, rI_init=0.8)
rE, rI = simulate_wc(**pars)

@widgets.interact(
    n_t=widgets.IntSlider(0, min=0, max=len(pars['range_t']) - 1, step=1)
)


def plot_activity_phase(n_t):
  plt.figure(figsize=(8, 5.5))
  plt.subplot(211)
  plt.plot(pars['range_t'], rE, 'b', label=r'$r_E$')
  plt.plot(pars['range_t'], rI, 'r', label=r'$r_I$')
  plt.plot(pars['range_t'][n_t], rE[n_t], 'bo')
  plt.plot(pars['range_t'][n_t], rI[n_t], 'ro')
  plt.axvline(pars['range_t'][n_t], 0, 1, color='k', ls='--')
  plt.xlabel('t (ms)', fontsize=14)
  plt.ylabel('Activity', fontsize=14)
  plt.legend(loc='best', fontsize=14)

  plt.subplot(212)
  plt.plot(rE, rI, 'k')
  plt.plot(rE[n_t], rI[n_t], 'ko')
  plt.xlabel(r'$r_E$', fontsize=18, color='b')
  plt.ylabel(r'$r_I$', fontsize=18, color='r')

  plt.tight_layout()
  plt.show()

解答を見る$

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

セクション 2.1: Wilson-Cowan方程式のヌルクライン

チュートリアル開始からここまでの推定時間: 1時間3分

位相平面解析の重要な概念に「ヌルクライン」があります。これは、ある集団の活動が変化しない点の集合(ただし必ずしも他方の集団は含まない)として定義されます。

言い換えれば、式(1)のEEおよびIIのヌルクラインは、それぞれ drEdt=0\displaystyle{\frac{dr_E}{dt}}=0 および drIdt=0\displaystyle\frac{dr_I}{dt}=0 となる点の集合です。すなわち:

\begin{align}
-rEr_E + FE(wEErEF_E(w_{EE}r_E -wEIrIw_{EI}r_I + I^{\text{ext}}_E;a_E,θE)\theta_E) &= 0 (2)\qquad (2)\[1mm]
-rIr_I + FI(wIErEF_I(w_{IE}r_E -wIIrIw_{II}r_I + I^{\text{ext}}_I;a_I,θI)\theta_I) &= 0 \qquad (3) \end{align}

コーディング演習 2.1: Wilson-Cowanモデルのヌルクラインを計算しよう

次の演習では、EおよびI集団のヌルクラインを計算しプロットします。

興奮性集団のヌルクライン(式2)に沿って、式2を変形して抑制性活動を計算できます:

\begin{align}
rIr_I = \frac{1}{w_{EI}}\big{[}w_{EE}r_E - F_E^{-1}(r_E; a_E,\theta_E) + I^{\text{ext}}_E \big{]}. \qquad(4) \end{align}

ここで FE1(rE;aE,θE)F_E^{-1}(r_E; a_E,\theta_E) は興奮性伝達関数の逆関数(以下で定義)です。式4は rEr_E ヌルクラインを定義します。

抑制性集団のヌルクライン(式3)に沿って、式3を変形して興奮性活動を計算できます:
\begin{align}
rEr_E = \frac{1}{w_{IE}} \big{[} w_{II}r_I + F_I^{-1}(r_I;a_I,\theta_I) - I^{\text{ext}}_I \big{]} \qquad (5) \end{align}

ここで FI1(rI;aI,θI)F_I^{-1}(r_I; a_I,\theta_I) は抑制性伝達関数の逆関数(以下で定義)です。式5は II ヌルクラインを定義します。

式4-5でヌルクラインを計算する際には、伝達関数の逆関数を計算する必要があります。

我々が用いているシグモイド型のf-I関数の逆関数は以下の通りです:

F1(x;a,θ)=1aln[1x+11+eaθ1]+θ(6)F^{-1}(x; a, \theta) = -\frac{1}{a} \ln \left[ \frac{1}{x + \displaystyle \frac{1}{1+\text{e}^{a\theta}}} - 1 \right] + \theta \qquad (6)

まずは逆伝達関数を実装しましょう:

def F_inv(x, a, theta):
  """
  Args:
    x         : the population input
    a         : the gain of the function
    theta     : the threshold of the function

  Returns:
    F_inverse : value of the inverse function
  """

  #########################################################################
  # TODO for students: compute F_inverse
  raise NotImplementedError("Student exercise: compute the inverse of F(x)")
  #########################################################################

  # Calculate Finverse (ln(x) can be calculated as np.log(x))
  F_inverse = ...

  return F_inverse


# Set parameters
pars = default_pars()
x = np.linspace(1e-6, 1, 100)

# Get inverse and visualize
plot_FI_inverse(x, a=1, theta=3)

解答を見る$

これで式4-5(以下に再掲)を用いてヌルクラインを計算できます:

\begin{align}
rIr_I &= \frac{1}{w_{EI}}\big{[}w_{EE}r_E - F_E^{-1}(r_E; a_E,\theta_E) + I^{\text{ext}}_E \big{]} &(4)\qquad(4) \
rEr_E &= \frac{1}{w_{IE}} \big{[} w_{II}r_I + F_I^{-1}(r_I;a_I,\theta_I) - I^{\text{ext}}_I \big{]} &\qquad (5) \end{align}

def get_E_nullcline(rE, a_E, theta_E, wEE, wEI, I_ext_E, **other_pars):
  """
  Solve for rI along the rE from drE/dt = 0.

  Args:
    rE    : response of excitatory population
    a_E, theta_E, wEE, wEI, I_ext_E : Wilson-Cowan excitatory parameters
    Other parameters are ignored

  Returns:
    rI    : values of inhibitory population along the nullcline on the rE
  """
  #########################################################################
  # TODO for students: compute rI for rE nullcline and disable the error
  raise NotImplementedError("Student exercise: compute the E nullcline")
  #########################################################################

  # calculate rI for E nullclines on rI
  rI = ...

  return rI


def get_I_nullcline(rI, a_I, theta_I, wIE, wII, I_ext_I, **other_pars):
  """
  Solve for E along the rI from dI/dt = 0.

  Args:
    rI    : response of inhibitory population
    a_I, theta_I, wIE, wII, I_ext_I : Wilson-Cowan inhibitory parameters
    Other parameters are ignored

  Returns:
    rE    : values of the excitatory population along the nullcline on the rI
  """
  #########################################################################
  # TODO for students: compute rI for rE nullcline and disable the error
  raise NotImplementedError("Student exercise: compute the I nullcline")
  #########################################################################

  # calculate rE for I nullclines on rI
  rE = ...

  return rE


# Set parameters
pars = default_pars()
Exc_null_rE = np.linspace(-0.01, 0.96, 100)
Inh_null_rI = np.linspace(-.01, 0.8, 100)

# Compute nullclines
Exc_null_rI = get_E_nullcline(Exc_null_rE, **pars)
Inh_null_rE = get_I_nullcline(Inh_null_rI, **pars)

# Visualize
plot_nullclines(Exc_null_rE, Exc_null_rI, Inh_null_rE, Inh_null_rI)

解答を見る$

出力例:

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

位相平面上の青線に沿っては drE(t)dt=0\displaystyle{\frac{dr_E(t)}{dt}} = 0 であるため、この線はヌルクラインと呼ばれます。

つまり、青のヌルクラインは rE,rIr_E, r_I で張られた位相平面を2つの領域に分けます。ヌルクラインの片側では drE(t)dt>0\displaystyle{\frac{dr_E(t)}{dt}} > 0、もう片側では drE(t)dt<0\displaystyle{\frac{dr_E(t)}{dt}} < 0 となります。

同様に、赤線に沿っては drI(t)dt=0\displaystyle{\frac{dr_I(t)}{dt}} = 0 であり、赤のヌルクラインは位相平面を2つの領域に分けます。片側では drI(t)dt>0\displaystyle{\frac{dr_I(t)}{dt}} > 0、もう片側では drI(t)dt<0\displaystyle{\frac{dr_I(t)}{dt}} < 0 となります。

セクション 2.2: ベクトル場

チュートリアル開始からここまでの推定時間: 1時間20分

位相平面とヌルクラインはWilson-Cowanモデルの挙動理解にどう役立つでしょうか?

関連ビデオ内容のテキスト要約はこちらをクリック

EEおよびII集団の活動 rE(t)r_E(t)rI(t)r_I(t) は時刻 tt における位相平面上の点 (rE(t),rI(t))(r_E(t),r_I(t)) に対応します。したがって、システムの時間依存軌道は位相平面上の連続曲線として表され、その軌道の接ベクトルはベクトル \bigg{(}\displaystyle{\frac{dr_E(t)}{dt},\frac{dr_I(t)}{dt}}\bigg{)} で定義されます。このベクトルは活動がどの方向にどの速さで変化しているかを示します。実際、位相平面上の任意の点 (E,I)(E,I) で接ベクトル \bigg{(}\displaystyle{\frac{dr_E}{dt},\frac{dr_I}{dt}}\bigg{)} を計算でき、システムがその点を通過するときの挙動を示します。

位相平面上の接ベクトルのマップはベクトル場と呼ばれます。軌道の挙動は i) 初期条件 (rE(0),rI(0))(r_E(0),r_I(0)) と ii) ベクトル場 \bigg{(}\displaystyle{\frac{dr_E(t)}{dt},\frac{dr_I(t)}{dt}}\bigg{)} によって決まります。

一般に、位相平面上の特定の点でのベクトル場の値は矢印で表されます。矢印の向きと大きさはそれぞれベクトルの方向とノルムを反映します。

コーディング演習 2.2: ベクトル場 \displaystyle{\Big{(}\frac{dr_E}{dt}, \frac{dr_I}{dt} \Big{)}} を計算・プロットしよう

以下の式に注意してください:

\begin{align}
drEdt\frac{dr_E}{dt} &= [-rEr_E + FE(wEErEF_E(w_{EE}r_E -wEIrIw_{EI}r_I + I^{\text{ext}}_E;a_E,θE)\theta_E)]1τE\frac{1}{\tau_E}\
drIdt\frac{dr_I}{dt} &= [-rIr_I + FI(wIErEF_I(w_{IE}r_E -wIIrIw_{II}r_I + I^{\text{ext}}_I;a_I,θI)\theta_I)]\frac{1}{\tau_I} \end{align}

def EIderivs(rE, rI,
             tau_E, a_E, theta_E, wEE, wEI, I_ext_E,
             tau_I, a_I, theta_I, wIE, wII, I_ext_I,
             **other_pars):
  """Time derivatives for E/I variables (dE/dt, dI/dt)."""
  ######################################################################
  # TODO for students: compute drEdt and drIdt and disable the error
  raise NotImplementedError("Student exercise: compute the vector field")
  ######################################################################

  # Compute the derivative of rE
  drEdt = ...

  # Compute the derivative of rI
  drIdt = ...

  return drEdt, drIdt


# Create vector field using EIderivs
plot_complete_analysis(default_pars())

解答を見る$

出力例:

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

最後の位相平面プロットからわかること:

  • 軌道はベクトル場の方向に沿って進むように見える
  • 異なる軌道は初期条件に応じて最終的に2つの点のいずれかに収束する
  • 軌道が収束する2点は2つのヌルクラインの交点である

考えてみよう! 2.2: ベクトル場の解析

交点は合計3つあり、システムには3つの固定点が存在します。

  1. 3つの固定点のうち中央のものは軌道の最終状態にならないのはなぜでしょう?
  2. 矢印が固定点に近づくにつれて小さくなるのはなぜでしょう?

解答を見る$

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

まとめ

チュートリアルの推定所要時間: 1時間35分

おめでとうございます!Neuromatch Academyの第2週第4日目を修了しました!ここでは、興奮性および抑制性ニューロン集団からなるレートベースモデルのシミュレーション方法を学びました。

前回の動的神経ネットワークのチュートリアルでは、以下を学びました:

  • Wilson-Cowanモデルを用いてEおよびI集団からなる2次元システムを実装・シミュレートする
  • 両集団の周波数-電流(F-I)曲線をプロットする
  • 位相平面解析ベクトル場、およびヌルクラインを用いてシステムの挙動を調べる

まだ時間がありますか?早く終わりましたか?さらに楽しい内容があります!

ボーナスチュートリアルでは、動的システムのより高度な概念を扱います:

  • 固定点の見つけ方と、ダイナミクスの線形化およびヤコビ行列の調査による安定性の検証
  • Wilson-Cowanモデルが振動を示す条件の特定

さらに必要なら、Wilson-Cowanモデルの2つの応用があります:

  • 抑制安定化ネットワークの可視化
  • 作業記憶のシミュレーション