├── README.md ├── SingleFileSimulations ├── ISI │ ├── PPD.py │ ├── gamma_process.py │ ├── inhomogeneous_poisson_process.py │ ├── poisson_process.py │ └── poisson_process_fast.py ├── Neurons │ ├── HH_FI_curve.py │ ├── HH_multiple.py │ ├── HH_single.py │ ├── HH_single_anodal_break.py │ ├── Izhikevich_single.py │ ├── LIF_FI_curve_analytical.py │ ├── LIF_FI_curve_numerical.py │ ├── LIF_single.py │ ├── event_based_simulation_LIF.py │ └── leakyRNN_chainer.py ├── STDP │ ├── stdp.py │ ├── stdp2.py │ └── stdp3.py └── Synapses │ ├── exponential_synapse.py │ └── kinetic_synapse.py ├── TrainingSNN ├── Izhikevich_FORCE_sinewave.py ├── LIF_FORCE_sinewave.py ├── LIF_SuperSpike.py ├── LIF_WTA_STDP_MNIST.py ├── LIF_WTA_STDP_MNIST_evaluation.py ├── LIF_WTA_STDP_MNIST_visualize_weights.py ├── LIF_random_network.py ├── Models │ ├── Connections.py │ ├── Neurons.py │ ├── Synapses.py │ └── __init__.py └── example_using_delay_connection.py ├── notebook ├── Hodgkin-Huxley-FIcurve.ipynb ├── Hodgkin-Huxley.ipynb ├── Hodgkin-Huxley_AP.ipynb ├── Izhikevich.ipynb ├── LIF.ipynb └── images │ └── parallel_conductance_model.JPG └── pdf ├── ゼロから作る Spiking Neural Networks第1版_正誤表.pdf └── ゼロから作るSpiking_Neural_Networks_2版_サンプル.pdf /README.md: -------------------------------------------------------------------------------- 1 | # ゼロから作るSpiking Neural Networks 2 | ## BOOTH 3 | - pdf版 : 4 | - 書籍+pdf版 : 5 | 6 | ## pdf 7 | - [サンプルpdf](https://github.com/takyamamoto/SNN-from-scratch-with-Python/blob/master/pdf/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8B%20Spiking%20Neural%20Networks%E7%AC%AC1%E7%89%88_%E6%AD%A3%E8%AA%A4%E8%A1%A8.pdf) 8 | - [第一版正誤表](https://github.com/takyamamoto/SNN-from-scratch-with-Python/blob/master/pdf/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8BSpiking_Neural_Networks_2%E7%89%88_%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB.pdf) 9 | 10 | ## Requirements 11 | 本書で使用したPythonとライブラリ(のバージョン)一覧です 12 | - Python >= 3.5 13 | - numpy == 1.16.4 14 | - matplotlib == 3.1.0 15 | - tqdm == 4.32.2 16 | - scipy == 1.3.0 17 | - chainer == 6.0.0rc1 18 | 19 | ## Usage 20 | ### SingleFileSimulations 21 | 1ファイル完結のシミュレーションのコードです 22 | 23 | ### TrainingSNN 24 | `./TrainingSNN/Models/`下のファイル内に書かれたモデルを用いてネットワークの構築と学習をシミュレーションします。 25 | -------------------------------------------------------------------------------- /SingleFileSimulations/ISI/PPD.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | np.random.seed(seed=0) 6 | 7 | dt = 1e-3; T = 1; nt = round(T/dt) # シミュレーション時間 8 | n_neurons = 10 # ニューロンの数 9 | 10 | tref = 5e-3 # 不応期 (s) 11 | fr = 30 # ポアソンスパイクの発火率(Hz) 12 | spikes = np.zeros((nt, n_neurons)) #スパイク記録変数 13 | tlast = np.zeros(n_neurons) # 発火時刻の記録変数 14 | for i in range(nt): 15 | s = np.where(np.random.rand(n_neurons) < fr*dt, 1, 0) 16 | spikes[i] = ((dt*i) > (tlast + tref))*s 17 | tlast = tlast*(1-s) + dt*i*s # 発火時刻の更新 18 | 19 | print("Num. of spikes:", np.sum(spikes)) 20 | print("Firing rate:", np.sum(spikes)/(n_neurons*T)) 21 | # 描画 22 | t = np.arange(nt)*dt 23 | plt.figure(figsize=(5, 4)) 24 | for i in range(n_neurons): 25 | plt.plot(t, spikes[:, i]*(i+1), 'ko', markersize=2, 26 | rasterized=True) 27 | plt.xlabel('Time (s)') 28 | plt.ylabel('Neuron index') 29 | plt.xlim(0, T) 30 | plt.ylim(0.5, n_neurons+0.5) 31 | plt.tight_layout() 32 | plt.savefig("PPD.pdf", dpi=300) 33 | plt.show() 34 | -------------------------------------------------------------------------------- /SingleFileSimulations/ISI/gamma_process.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import scipy.special as sps 6 | 7 | np.random.seed(seed=0) 8 | 9 | dt = 1e-3; T = 1; nt = round(T/dt) # シミュレーション時間 10 | n_neurons = 10 # ニューロンの数 11 | 12 | fr = 30 # ガンマスパイクの発火率(Hz) 13 | k = 12 # k=1のときはポアソン過程に一致 14 | theta = 1/(k*(fr*dt)) # fr = 1/(k*theta) 15 | isi = np.random.gamma(shape=k, scale=theta, 16 | size=(int(nt*1.5/fr), n_neurons)) 17 | spike_time = np.cumsum(isi, axis=0) # ISIを累積 18 | spike_time[spike_time > nt - 1] = -1 # ntを超える場合を0に 19 | spike_time = spike_time.astype(np.int32) # float to int 20 | spikes = np.zeros((nt, n_neurons)) # スパイク記録変数 21 | for i in range(n_neurons): 22 | spikes[spike_time[:, i], i] = 1 23 | spikes[0] = 0 # (spike_time=0)の発火を削除 24 | print("Num. of spikes:", np.sum(spikes)) 25 | print("Firing rate:", np.sum(spikes)/(n_neurons*T)) 26 | 27 | # 描画 28 | plt.figure(figsize=(5, 5)) 29 | t = np.arange(nt)*dt 30 | plt.subplot(2,1,1) 31 | count, bins, ignored = plt.hist(isi.flatten(), 32 | 50, density=True, 33 | color="gray", alpha=0.5) 34 | y = bins**(k-1)*(np.exp(-bins/theta) / (sps.gamma(k)*theta**k)) 35 | plt.plot(bins, y, linewidth=2, color="k") 36 | plt.title('$k=$'+str(k)) 37 | plt.xlabel('ISI (ms)') 38 | plt.ylabel('Probability density') 39 | 40 | plt.subplot(2,1,2) 41 | for i in range(n_neurons): 42 | plt.plot(t, spikes[:, i]*(i+1), 'ko', markersize=2, 43 | rasterized=True) 44 | plt.xlabel('Time (s)') 45 | plt.ylabel('Neuron index') 46 | plt.xlim(0, T) 47 | plt.ylim(0.5, n_neurons+0.5) 48 | plt.tight_layout() 49 | plt.savefig("gamma_process2.pdf", dpi=300) 50 | plt.show() 51 | 52 | -------------------------------------------------------------------------------- /SingleFileSimulations/ISI/inhomogeneous_poisson_process.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | np.random.seed(seed=0) 6 | 7 | dt = 1e-3; T = 1; nt = round(T/dt) # シミュレーション時間 8 | n_neuron = 10 # ニューロンの数 9 | t = np.arange(nt)*dt 10 | 11 | # ポアソンスパイクの発火率(Hz) 12 | fr = np.expand_dims(30*np.sin(10*t)**2, 1) 13 | 14 | # スパイク記録変数 15 | spikes = np.where(np.random.rand(nt, n_neuron) < fr*dt, 1, 0) 16 | 17 | print("Num. of spikes:", np.sum(spikes)) 18 | # 描画 19 | plt.figure(figsize=(5, 4)) 20 | plt.subplot(2,1,1) 21 | plt.plot(t, fr[:, 0], color="k") 22 | plt.ylabel('Firing rate (Hz)') 23 | plt.xlim(0, T) 24 | 25 | plt.subplot(2,1,2) 26 | for i in range(n_neuron): 27 | plt.plot(t, spikes[:, i]*(i+1), 'ko', markersize=2, 28 | rasterized=True) 29 | plt.xlabel('Time (s)') 30 | plt.ylabel('Neuron index') 31 | plt.xlim(0, T) 32 | plt.ylim(0.5, n_neuron+0.5) 33 | plt.tight_layout() 34 | plt.savefig("inhomogenous_poisson_process.pdf", dpi=300) 35 | plt.show() 36 | 37 | -------------------------------------------------------------------------------- /SingleFileSimulations/ISI/poisson_process.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | np.random.seed(seed=0) 7 | 8 | dt = 1e-3; T = 1; nt = round(T/dt) # シミュレーション時間 9 | n_neurons = 10 # ニューロンの数 10 | 11 | fr = 30 # ポアソンスパイクの発火率(Hz) 12 | isi = np.random.exponential(1/(fr*dt), 13 | size=(round(nt*1.5/fr), n_neurons)) 14 | spike_time = np.cumsum(isi, axis=0) # ISIを累積 15 | spike_time[spike_time > nt - 1] = 0 # ntを超える場合を0に 16 | spike_time = spike_time.astype(np.int32) # float to int 17 | spikes = np.zeros((nt, n_neurons)) # スパイク記録変数 18 | for i in range(n_neurons): 19 | spikes[spike_time[:, i], i] = 1 20 | spikes[0] = 0 # (spike_time=0)の発火を削除 21 | print("Num. of spikes:", np.sum(spikes)) 22 | print("Firing rate:", np.sum(spikes)/(n_neurons*T)) 23 | # 描画 24 | t = np.arange(nt)*dt 25 | plt.figure(figsize=(5, 4)) 26 | for i in range(n_neurons): 27 | plt.plot(t, spikes[:, i]*(i+1), 'ko', markersize=2, 28 | rasterized=True) 29 | plt.xlabel('Time (s)') 30 | plt.ylabel('Neuron index') 31 | plt.xlim(0, T) 32 | plt.ylim(0.5, n_neurons+0.5) 33 | plt.tight_layout() 34 | plt.savefig("poisson_process.pdf", dpi=300) 35 | plt.show() 36 | -------------------------------------------------------------------------------- /SingleFileSimulations/ISI/poisson_process_fast.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | np.random.seed(seed=0) 6 | 7 | dt = 1e-3; T = 1; nt = round(T/dt) # シミュレーション時間 8 | n_neurons = 10 # ニューロンの数 9 | 10 | fr = 30 # ポアソンスパイクの発火率(Hz) 11 | 12 | # スパイク記録変数 13 | spikes = np.where(np.random.rand(nt, n_neurons) < fr*dt, 1, 0) 14 | 15 | print("Num. of spikes:", np.sum(spikes)) 16 | print("Firing rate:", np.sum(spikes)/(n_neurons*T)) 17 | # 描画 18 | t = np.arange(nt)*dt 19 | plt.figure(figsize=(5, 4)) 20 | for i in range(n_neurons): 21 | plt.plot(t, spikes[:, i]*(i+1), 'ko', markersize=2, 22 | rasterized=True) 23 | plt.xlabel('Time (s)') 24 | plt.ylabel('Neuron index') 25 | plt.xlim(0, T) 26 | plt.ylim(0.5, n_neurons+0.5) 27 | plt.tight_layout() 28 | plt.savefig("poisson_process_fast.pdf", dpi=300) 29 | 30 | -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/HH_FI_curve.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | class HodgkinHuxleyModel: 8 | def __init__(self, dt=1e-3, solver="RK4"): 9 | self.C_m = 1.0 # 膜容量 (uF/cm^2) 10 | self.g_Na = 120.0 # Na+の最大コンダクタンス (mS/cm^2) 11 | self.g_K = 36.0 # K+の最大コンダクタンス (mS/cm^2) 12 | self.g_L = 0.3 # 漏れイオンの最大コンダクタンス (mS/cm^2) 13 | self.E_Na = 50.0 # Na+の平衡電位 (mV) 14 | self.E_K = -77.0 # K+の平衡電位 (mV) 15 | self.E_L = -54.387 #漏れイオンの平衡電位 (mV) 16 | 17 | self.solver = solver 18 | self.dt = dt 19 | 20 | # V, m, h, n 21 | self.states = np.array([-65, 0.05, 0.6, 0.32]) 22 | self.I_inj = None 23 | 24 | def Solvers(self, func, x, dt): 25 | # 4th order Runge-Kutta法 26 | if self.solver == "RK4": 27 | k1 = dt*func(x) 28 | k2 = dt*func(x + 0.5*k1) 29 | k3 = dt*func(x + 0.5*k2) 30 | k4 = dt*func(x + k3) 31 | return x + (k1 + 2*k2 + 2*k3 + k4) / 6 32 | 33 | # 陽的Euler法 34 | elif self.solver == "Euler": 35 | return x + dt*func(x) 36 | else: 37 | return None 38 | 39 | # イオンチャネルのゲートについての6つの関数 40 | def alpha_m(self, V): 41 | return 0.1*(V+40.0)/(1.0 - np.exp(-(V+40.0) / 10.0)) 42 | 43 | def beta_m(self, V): 44 | return 4.0*np.exp(-(V+65.0) / 18.0) 45 | 46 | def alpha_h(self, V): 47 | return 0.07*np.exp(-(V+65.0) / 20.0) 48 | 49 | def beta_h(self, V): 50 | return 1.0/(1.0 + np.exp(-(V+35.0) / 10.0)) 51 | 52 | def alpha_n(self, V): 53 | return 0.01*(V+55.0)/(1.0 - np.exp(-(V+55.0) / 10.0)) 54 | 55 | def beta_n(self, V): 56 | return 0.125*np.exp(-(V+65) / 80.0) 57 | 58 | # Na+電流 (uA/cm^2) 59 | def I_Na(self, V, m, h): 60 | return self.g_Na * m**3 * h * (V - self.E_Na) 61 | 62 | # K+電流 (uA/cm^2) 63 | def I_K(self, V, n): 64 | return self.g_K * n**4 * (V - self.E_K) 65 | 66 | # 漏れ電流 (uA/cm^2) 67 | def I_L(self, V): 68 | return self.g_L * (V - self.E_L) 69 | 70 | # 微分方程式 71 | def dALLdt(self, states): 72 | V, m, h, n = states 73 | 74 | dVdt = (self.I_inj - self.I_Na(V, m, h) \ 75 | - self.I_K(V, n) - self.I_L(V)) / self.C_m 76 | dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m 77 | dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h 78 | dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n 79 | return np.array([dVdt, dmdt, dhdt, dndt]) 80 | 81 | def __call__(self, I): 82 | self.I_inj = I 83 | states = self.Solvers(self.dALLdt, self.states, self.dt) 84 | self.states = states 85 | return states 86 | 87 | ########## 88 | ## Main ## 89 | ########## 90 | dt = 0.01; T = 500 # (ms) 91 | nt = round(T/dt) # ステップ数 92 | 93 | N = 50 94 | I_inj = 30*np.random.rand(N) # 刺激電流 (uA/cm^2) 95 | firing_rate = np.zeros(N) 96 | 97 | for j in tqdm(range(N)): 98 | HH_neuron = HodgkinHuxleyModel(dt=dt, solver="Euler") 99 | V_arr = np.zeros((nt, 4)) # 記録用配列 100 | 101 | # シミュレーション 102 | for i in tqdm(range(nt)): 103 | X = HH_neuron(I_inj[j]) 104 | V_arr[i] = X[0] 105 | 106 | spike = np.bitwise_and(V_arr[:-1]<0, V_arr[1:]>0) 107 | rate = np.sum(spike) / T*1e3 108 | 109 | firing_rate[j] = rate 110 | 111 | plt.figure(figsize=(5, 4)) 112 | plt.scatter(I_inj, firing_rate) 113 | #plt.plot(I_inj, firing_rate) 114 | plt.xlabel('Input current (uA)') 115 | plt.ylabel('Firing rate (Hz)') 116 | plt.tight_layout() 117 | #plt.savefig('HH_FI.pdf') 118 | plt.show() 119 | -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/HH_multiple.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | class HodgkinHuxleyModel: 8 | def __init__(self, N, dt=1e-3, solver="RK4"): 9 | self.C_m = 1.0 # 膜容量 (uF/cm^2) 10 | self.g_Na = 120.0 # Na+の最大コンダクタンス (mS/cm^2) 11 | self.g_K = 36.0 # K+の最大コンダクタンス (mS/cm^2) 12 | self.g_L = 0.3 # 漏れイオンの最大コンダクタンス (mS/cm^2) 13 | self.E_Na = 50.0 # Na+の平衡電位 (mV) 14 | self.E_K = -77.0 # K+の平衡電位 (mV) 15 | self.E_L = -54.387 #漏れイオンの平衡電位 (mV) 16 | 17 | self.solver = solver 18 | self.dt = dt 19 | 20 | # V, m, h, n 21 | self.states = np.zeros((N, 4)) 22 | self.states[:, 0] = -65*np.ones(N) 23 | self.states[:, 1] = 0.05*np.ones(N) 24 | self.states[:, 2] = 0.6*np.ones(N) 25 | self.states[:, 3] = 0.32*np.ones(N) 26 | 27 | self.N = N 28 | self.I_inj = None 29 | 30 | def Solvers(self, func, x, dt): 31 | # 4th order Runge-Kutta法 32 | if self.solver == "RK4": 33 | k1 = dt*func(x) 34 | k2 = dt*func(x + 0.5*k1) 35 | k3 = dt*func(x + 0.5*k2) 36 | k4 = dt*func(x + k3) 37 | return x + (k1 + 2*k2 + 2*k3 + k4) / 6 38 | 39 | # 陽的Euler法 40 | elif self.solver == "Euler": 41 | return x + dt*func(x) 42 | else: 43 | return None 44 | 45 | # イオンチャネルのゲートについての6つの関数 46 | def alpha_m(self, V): 47 | return 0.1*(V+40.0)/(1.0 - np.exp(-(V+40.0) / 10.0)) 48 | 49 | def beta_m(self, V): 50 | return 4.0*np.exp(-(V+65.0) / 18.0) 51 | 52 | def alpha_h(self, V): 53 | return 0.07*np.exp(-(V+65.0) / 20.0) 54 | 55 | def beta_h(self, V): 56 | return 1.0/(1.0 + np.exp(-(V+35.0) / 10.0)) 57 | 58 | def alpha_n(self, V): 59 | return 0.01*(V+55.0)/(1.0 - np.exp(-(V+55.0) / 10.0)) 60 | 61 | def beta_n(self, V): 62 | return 0.125*np.exp(-(V+65) / 80.0) 63 | 64 | # Na+電流 (uA/cm^2) 65 | def I_Na(self, V, m, h): 66 | return self.g_Na * m**3 * h * (V - self.E_Na) 67 | 68 | # K+電流 (uA/cm^2) 69 | def I_K(self, V, n): 70 | return self.g_K * n**4 * (V - self.E_K) 71 | 72 | # 漏れ電流 (uA/cm^2) 73 | def I_L(self, V): 74 | return self.g_L * (V - self.E_L) 75 | 76 | # 微分方程式 77 | def dALLdt(self, states): 78 | V = states[:, 0] 79 | m = states[:, 1] 80 | h = states[:, 2] 81 | n = states[:, 3] 82 | 83 | dVdt = (self.I_inj - self.I_Na(V, m, h) \ 84 | - self.I_K(V, n) - self.I_L(V)) / self.C_m 85 | dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m 86 | dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h 87 | dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n 88 | 89 | derivatives = np.zeros([self.N, 4]) 90 | derivatives[:, 0] = dVdt 91 | derivatives[:, 1] = dmdt 92 | derivatives[:, 2] = dhdt 93 | derivatives[:, 3] = dndt 94 | return derivatives 95 | 96 | def __call__(self, I): 97 | self.I_inj = I 98 | states = self.Solvers(self.dALLdt, self.states, self.dt) 99 | self.states = states 100 | return states 101 | 102 | ########## 103 | ## Main ## 104 | ########## 105 | dt = 0.01; T = 450 # (ms) 106 | nt = round(T/dt) # ステップ数 107 | time = np.arange(0.0, T, dt) 108 | 109 | I_inj = 10*(time>100) - 10*(time>200) + 35*(time>300) - 35*(time>400) # 印加電流 (uA/cm^2) 110 | 111 | N = 2 #ニューロンの数 112 | HH_neuron = HodgkinHuxleyModel(N=N, dt=dt, solver="Euler") 113 | 114 | X_arr = np.zeros((nt, 2, 4)) # 記録用配列 115 | 116 | # シミュレーション 117 | for i in tqdm(range(nt)): 118 | X = HH_neuron(I_inj[i]) 119 | X_arr[i] = X 120 | 121 | # 描画 122 | plt.figure() 123 | plt.subplot(2,1,1) 124 | plt.plot(time, X_arr[:, 0, 0]) 125 | plt.ylabel('V (mV)') 126 | 127 | plt.subplot(2,1,2) 128 | plt.plot(time, I_inj) 129 | plt.xlabel('t (ms)') 130 | plt.ylabel('$I_{inj}$ ($\\mu{A}/cm^2$)') 131 | plt.ylim(-1, 40) 132 | plt.tight_layout() 133 | plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/HH_single.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | class HodgkinHuxleyModel: 8 | def __init__(self, dt=1e-3, solver="RK4"): 9 | self.C_m = 1.0 # 膜容量 (uF/cm^2) 10 | self.g_Na = 120.0 # Na+の最大コンダクタンス (mS/cm^2) 11 | self.g_K = 36.0 # K+の最大コンダクタンス (mS/cm^2) 12 | self.g_L = 0.3 # 漏れイオンの最大コンダクタンス (mS/cm^2) 13 | self.E_Na = 50.0 # Na+の平衡電位 (mV) 14 | self.E_K = -77.0 # K+の平衡電位 (mV) 15 | self.E_L = -54.387 #漏れイオンの平衡電位 (mV) 16 | 17 | self.solver = solver 18 | self.dt = dt 19 | 20 | # V, m, h, n 21 | self.states = np.array([-65, 0.05, 0.6, 0.32]) 22 | self.I_m = None 23 | 24 | def Solvers(self, func, x, dt): 25 | # 4th order Runge-Kutta法 26 | if self.solver == "RK4": 27 | k1 = dt*func(x) 28 | k2 = dt*func(x + 0.5*k1) 29 | k3 = dt*func(x + 0.5*k2) 30 | k4 = dt*func(x + k3) 31 | return x + (k1 + 2*k2 + 2*k3 + k4) / 6 32 | 33 | # 陽的Euler法 34 | elif self.solver == "Euler": 35 | return x + dt*func(x) 36 | else: 37 | return None 38 | 39 | # イオンチャネルのゲートについての6つの関数 40 | def alpha_m(self, V): 41 | return 0.1*(V+40.0)/(1.0 - np.exp(-(V+40.0) / 10.0)) 42 | 43 | def beta_m(self, V): 44 | return 4.0*np.exp(-(V+65.0) / 18.0) 45 | 46 | def alpha_h(self, V): 47 | return 0.07*np.exp(-(V+65.0) / 20.0) 48 | 49 | def beta_h(self, V): 50 | return 1.0/(1.0 + np.exp(-(V+35.0) / 10.0)) 51 | 52 | def alpha_n(self, V): 53 | return 0.01*(V+55.0)/(1.0 - np.exp(-(V+55.0) / 10.0)) 54 | 55 | def beta_n(self, V): 56 | return 0.125*np.exp(-(V+65) / 80.0) 57 | 58 | # Na+電流 (uA/cm^2) 59 | def I_Na(self, V, m, h): 60 | return self.g_Na * m**3 * h * (V - self.E_Na) 61 | 62 | # K+電流 (uA/cm^2) 63 | def I_K(self, V, n): 64 | return self.g_K * n**4 * (V - self.E_K) 65 | 66 | # 漏れ電流 (uA/cm^2) 67 | def I_L(self, V): 68 | return self.g_L * (V - self.E_L) 69 | 70 | # 微分方程式 71 | def dALLdt(self, states): 72 | V, m, h, n = states 73 | 74 | dVdt = (self.I_m - self.I_Na(V, m, h) \ 75 | - self.I_K(V, n) - self.I_L(V)) / self.C_m 76 | dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m 77 | dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h 78 | dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n 79 | return np.array([dVdt, dmdt, dhdt, dndt]) 80 | 81 | def __call__(self, I): 82 | self.I_m = I 83 | states = self.Solvers(self.dALLdt, self.states, self.dt) 84 | self.states = states 85 | return states 86 | 87 | ########## 88 | ## Main ## 89 | ########## 90 | dt = 0.01; T = 400 # (ms) 91 | nt = round(T/dt) # ステップ数 92 | time = np.arange(0.0, T, dt) 93 | 94 | # 刺激電流 (uA/cm^2) 95 | I_inj = 10*(time>100) - 10*(time>200) + 35*(time>250) - 35*(time>350) 96 | HH_neuron = HodgkinHuxleyModel(dt=dt, solver="Euler") 97 | 98 | X_arr = np.zeros((nt, 4)) # 記録用配列 99 | 100 | # シミュレーション 101 | for i in tqdm(range(nt)): 102 | X = HH_neuron(I_inj[i]) 103 | X_arr[i] = X 104 | 105 | spike = np.bitwise_and(X_arr[:-1, 0] < 0, X_arr[1:, 0] > 0) 106 | print("Num. of spikes :", np.sum(spike)) 107 | 108 | # 描画 109 | plt.figure(figsize=(5, 5)) 110 | plt.subplot(3,1,1) 111 | plt.plot(time, X_arr[:,0], color="k") 112 | plt.ylabel('V (mV)') 113 | plt.xlim(0, T) 114 | 115 | plt.subplot(3,1,2) 116 | plt.plot(time, I_inj, color="k") 117 | plt.ylabel('$I_{inj}$ ($\\mu{A}/cm^2$)') 118 | plt.xlim(0, T) 119 | plt.tight_layout() 120 | 121 | plt.subplot(3,1,3) 122 | plt.plot(time, X_arr[:,1], 'k', label='m') 123 | plt.plot(time, X_arr[:,2], 'gray', label='h') 124 | plt.plot(time, X_arr[:,3], 'k', linestyle="dashed", label='n') 125 | plt.xlabel('t (ms)') 126 | plt.ylabel('Gating Value') 127 | plt.legend(loc="upper left") 128 | plt.tight_layout() 129 | plt.savefig("HH_model.pdf") 130 | plt.show() 131 | -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/HH_single_anodal_break.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | class HodgkinHuxleyModel: 8 | def __init__(self, dt=1e-3, solver="RK4"): 9 | self.C_m = 1.0 # 膜容量 (uF/cm^2) 10 | self.g_Na = 120.0 # Na+の最大コンダクタンス (mS/cm^2) 11 | self.g_K = 36.0 # K+の最大コンダクタンス (mS/cm^2) 12 | self.g_L = 0.3 # 漏れイオンの最大コンダクタンス (mS/cm^2) 13 | self.E_Na = 50.0 # Na+の平衡電位 (mV) 14 | self.E_K = -77.0 # K+の平衡電位 (mV) 15 | self.E_L = -54.387 #漏れイオンの平衡電位 (mV) 16 | 17 | self.solver = solver 18 | self.dt = dt 19 | 20 | # V, m, h, n 21 | self.states = np.array([-65, 0.05, 0.6, 0.32]) 22 | self.I_m = None 23 | 24 | def Solvers(self, func, x, dt): 25 | # 4th order Runge-Kutta法 26 | if self.solver == "RK4": 27 | k1 = dt*func(x) 28 | k2 = dt*func(x + 0.5*k1) 29 | k3 = dt*func(x + 0.5*k2) 30 | k4 = dt*func(x + k3) 31 | return x + (k1 + 2*k2 + 2*k3 + k4) / 6 32 | 33 | # 陽的Euler法 34 | elif self.solver == "Euler": 35 | return x + dt*func(x) 36 | else: 37 | return None 38 | 39 | # イオンチャネルのゲートについての6つの関数 40 | def alpha_m(self, V): 41 | return 0.1*(V+40.0)/(1.0 - np.exp(-(V+40.0) / 10.0)) 42 | 43 | def beta_m(self, V): 44 | return 4.0*np.exp(-(V+65.0) / 18.0) 45 | 46 | def alpha_h(self, V): 47 | return 0.07*np.exp(-(V+65.0) / 20.0) 48 | 49 | def beta_h(self, V): 50 | return 1.0/(1.0 + np.exp(-(V+35.0) / 10.0)) 51 | 52 | def alpha_n(self, V): 53 | return 0.01*(V+55.0)/(1.0 - np.exp(-(V+55.0) / 10.0)) 54 | 55 | def beta_n(self, V): 56 | return 0.125*np.exp(-(V+65) / 80.0) 57 | 58 | # Na+電流 (uA/cm^2) 59 | def I_Na(self, V, m, h): 60 | return self.g_Na * m**3 * h * (V - self.E_Na) 61 | 62 | # K+電流 (uA/cm^2) 63 | def I_K(self, V, n): 64 | return self.g_K * n**4 * (V - self.E_K) 65 | 66 | # 漏れ電流 (uA/cm^2) 67 | def I_L(self, V): 68 | return self.g_L * (V - self.E_L) 69 | 70 | # 微分方程式 71 | def dALLdt(self, states): 72 | V, m, h, n = states 73 | 74 | dVdt = (self.I_m - self.I_Na(V, m, h) \ 75 | - self.I_K(V, n) - self.I_L(V)) / self.C_m 76 | dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m 77 | dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h 78 | dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n 79 | return np.array([dVdt, dmdt, dhdt, dndt]) 80 | 81 | def __call__(self, I): 82 | self.I_m = I 83 | states = self.Solvers(self.dALLdt, self.states, self.dt) 84 | self.states = states 85 | return states 86 | 87 | ########## 88 | ## Main ## 89 | ########## 90 | dt = 0.01; T = 250 # (ms) 91 | nt = round(T/dt) # ステップ数 92 | time = np.arange(0.0, T, dt) 93 | 94 | # 刺激電流 (uA/cm^2) 95 | I_inj = -10*(time>50) + 10*(time>100) - 20*(time>150) + 20*(time>200) 96 | HH_neuron = HodgkinHuxleyModel(dt=dt, solver="Euler") 97 | 98 | X_arr = np.zeros((nt, 4)) # 記録用配列 99 | 100 | # シミュレーション 101 | for i in tqdm(range(nt)): 102 | X = HH_neuron(I_inj[i]) 103 | X_arr[i] = X 104 | 105 | spike = np.bitwise_and(X_arr[:-1, 0] < 0, X_arr[1:, 0] > 0) 106 | print("Num. of spikes :", np.sum(spike)) 107 | 108 | # 描画 109 | plt.figure(figsize=(5, 5)) 110 | plt.subplot(3,1,1) 111 | plt.plot(time, X_arr[:,0], color="k") 112 | plt.ylabel('V (mV)') 113 | plt.xlim(0, T) 114 | 115 | plt.subplot(3,1,2) 116 | plt.plot(time, I_inj, color="k") 117 | plt.ylabel('$I_{inj}$ ($\\mu{A}/cm^2$)') 118 | plt.xlim(0, T) 119 | plt.ylim(-25, 10) 120 | plt.tight_layout() 121 | 122 | plt.subplot(3,1,3) 123 | plt.plot(time, X_arr[:,1], 'k', label='m') 124 | plt.plot(time, X_arr[:,2], 'gray', label='h') 125 | plt.plot(time, X_arr[:,3], 'k', linestyle="dashed", label='n') 126 | plt.xlabel('t (ms)') 127 | plt.ylabel('Gating Value') 128 | plt.legend(loc="upper left") 129 | plt.tight_layout() 130 | plt.savefig("HH_model_anodal_break.pdf") 131 | plt.show() 132 | -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/Izhikevich_single.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from tqdm import tqdm 5 | 6 | dt = 0.5; T = 400 # ms 7 | nt = round(T/dt) # シミュレーションのステップ数 8 | 9 | # Regular spiking (RS) neurons 10 | C = 100 # 膜容量(pF) 11 | a = 0.03 # 回復時定数の逆数 (1/ms) 12 | b = -2 # uのvに対する共鳴度合い(pA/mV) 13 | k = 0.7 # ゲイン (pA/mV) 14 | d = 100 # 発火で活性化される正味の外向き電流(pA) 15 | vrest = -60 # 静止膜電位 (mV) 16 | vreset = -50 # リセット電位 (mV) 17 | vthr = -40 # 閾値電位 (mV) 18 | vpeak = 35 # ピーク電位 (mV) 19 | t = np.arange(nt)*dt 20 | I = 100*(t>50) - 100*(t>350) # 入力電流(pA) 21 | 22 | """ 23 | # Intrinsically Bursting (IB) neurons 24 | C = 150; a = 0.01; b = 5; k =1.2; d = 130 25 | vrest = -75; vreset = -56; vthr = -45; vpeak = 50; 26 | I = 600*(t>50) - 600*(t>350) 27 | 28 | # Chattering (CH) or fast rhythmic bursting (FRB) neurons 29 | C = 50; a = 0.03; b = 1; k =1.5; d = 150 30 | vrest = -60; vreset = -40; vthr = -40; vpeak = 35; 31 | I = 600*(t>50) - 600*(t>350) 32 | """ 33 | 34 | # 初期化(膜電位, 膜電位(t-1), 回復電流) 35 | v = vrest; v_ = v; u = 0 36 | v_arr = np.zeros(nt) # 膜電位を記録する配列 37 | u_arr = np.zeros(nt) # 回復変数を記録する配列 38 | 39 | # シミュレーション 40 | for i in tqdm(range(nt)): 41 | dv = (k*(v - vrest)*(v - vthr) - u + I[i]) / C 42 | v = v + dt*dv # 膜電位の更新 43 | u = u + dt*(a*(b*(v_-vrest)-u)) # 膜電位の更新 44 | 45 | s = 1*(v>=vpeak) #発火時は1, その他は0の出力 46 | 47 | u = u + d*s # 発火時に回復変数を上昇 48 | v = v*(1-s) + vreset*s # 発火時に膜電位をリセット 49 | v_ = v # v(t-1) <- v(t) 50 | 51 | v_arr[i] = v # 膜電位の値を保存 52 | u_arr[i] = u # 回復変数の値を保存 53 | 54 | # 描画 55 | plt.figure(figsize=(5, 5)) 56 | plt.subplot(2,1,1) 57 | plt.plot(t, v_arr, color="k") 58 | #plt.title("Regular spiking (RS) neurons") 59 | plt.ylabel('Membrane potential (mV)') 60 | plt.xlim(0, T) 61 | plt.tight_layout() 62 | 63 | plt.subplot(2,1,2) 64 | plt.plot(t, u_arr, color="k") 65 | plt.xlabel('Time (ms)') 66 | plt.ylabel('Recovery current (pA)') 67 | plt.xlim(0, T) 68 | plt.tight_layout() 69 | plt.savefig('Izhikevich_regular.pdf') 70 | plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/LIF_FI_curve_analytical.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | tc_m = 1e-2 # 膜時定数 (s) 7 | R = 1 #膜抵抗 8 | vthr = 1 # 閾値電位 (mV) 9 | tref = 5e-3 # 不応期 (s) 10 | I_max = 3 11 | I = np.arange(0, I_max, 0.01) #入力電流 12 | 13 | rate = 1 / (tref + tc_m*np.log(R*I / (R*I - vthr))) 14 | rate[np.isnan(rate)] = 0 # nan to 0 15 | 16 | # 描画 17 | plt.figure(figsize=(4, 3)) 18 | plt.plot(I, rate, color="k") 19 | plt.xlabel('Input current (nA)') 20 | plt.ylabel('Firing rate (Hz)') 21 | plt.xlim(0, I_max) 22 | plt.tight_layout() 23 | plt.savefig('LIF_FI_analytical.pdf') 24 | #plt.show() 25 | -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/LIF_FI_curve_numerical.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | dt = 5e-5 # 時間ステップ (s) 8 | T = 1 # シミュレーション時間 (s) 9 | nt = round(T/dt) #Time steps 10 | 11 | tref = 5e-3 # 不応期 (s) 12 | tc_m = 1e-2 # 膜時定数 (s) 13 | vrest = 0 # 静止膜電位 (mV) 14 | vreset = 0 # リセット電位 (mV) 15 | vthr = 1 # 閾値電位 (mV) 16 | 17 | I_max = 3 # (nA) 18 | N = 100 19 | 20 | # 入力電流 21 | I = np.linspace(0, I_max, N) # pA 22 | spikes = np.zeros((N, nt)) 23 | 24 | for i in tqdm(range(N)): 25 | # 初期化 26 | v = vreset 27 | tlast = 0 28 | 29 | # シミュレーション 30 | for t in range(nt): 31 | # 更新 32 | dv = (vrest - v + I[i]) / tc_m 33 | update = 1 if ((dt*t) > (tlast + tref)) else 0 34 | v = v + update*dv*dt 35 | 36 | # 発火の確認 37 | s = 1 if (v>=vthr) else 0 #発火時は1, その他は0の出力 38 | tlast = tlast*(1-s) + dt*t*s 39 | 40 | # 保存 41 | spikes[i, t] = s 42 | 43 | # リセット 44 | v = v*(1-s) + vreset*s 45 | 46 | # 描画 47 | rate = np.sum(spikes, axis=1) / T 48 | plt.figure(figsize=(4, 3)) 49 | plt.plot(I, rate, color="k") 50 | plt.xlabel('Input current (nA)') 51 | plt.ylabel('Firing rate (Hz)') 52 | plt.xlim(0, I_max) 53 | plt.tight_layout() 54 | plt.savefig('LIF_FI_numerical.pdf') 55 | #plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/LIF_single.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | np.random.seed(seed=0) 8 | 9 | dt = 5e-5; T = 0.4 # (s) 10 | nt = round(T/dt) # シミュレーションのステップ数 11 | 12 | tref = 2e-3 # 不応期 (s) 13 | tc_m = 1e-2 # 膜時定数 (s) 14 | vrest = -60 # 静止膜電位 (mV) 15 | vreset = -65 # リセット電位 (mV) 16 | vthr = -40 # 閾値電位 (mV) 17 | vpeak = 30 # ピーク電位 (mV) 18 | 19 | t = np.arange(nt)*dt*1e3 # 時間(ms) 20 | I = 21*(t>50) - 21*(t>350) # 入力電流(に比例する値)(mV) 21 | 22 | # 初期化 23 | v = vreset # 膜電位の初期値 24 | tlast = 0 # 最後のスパイクの時間を記録する変数 25 | v_arr = np.zeros(nt) # 膜電位を記録する配列 26 | 27 | # シミュレーション 28 | for i in tqdm(range(nt)): 29 | dv = (vrest - v + I[i]) / tc_m # 膜電位の変化量 30 | v = v + ((dt*i) > (tlast + tref))*dv*dt # 更新 31 | 32 | s = 1*(v>=vthr) # 発火の確認 33 | tlast = tlast*(1-s) + dt*i*s # 発火時刻の更新 34 | v = v*(1-s) + vpeak*s # 発火している場合ピーク電位に更新 35 | v_arr[i] = v # 膜電位の値を保存 36 | v = v*(1-s) + vreset*s # 発火している場合に膜電位をリセット 37 | 38 | # 描画 39 | 40 | plt.figure(figsize=(5, 3)) 41 | plt.plot(t, v_arr, color="k") 42 | plt.ylabel('Membrane potential (mV)') 43 | plt.xlim(0, t.max()) 44 | plt.tight_layout() 45 | plt.savefig('LIF_single.pdf') 46 | plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/event_based_simulation_LIF.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Oct 13 00:17:20 2019 4 | 5 | @author: user 6 | """ 7 | # http://www.scholarpedia.org/article/Chaos_in_neurons 8 | # Poincare sections of chaotic networks 9 | 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | from tqdm import tqdm 13 | 14 | def h(phase): 15 | return np.sign(phase)*np.exp(np.abs(phase) - 1) 16 | 17 | def inv_h(x): 18 | return np.sign(x)*np.log(1+np.abs(x)) 19 | 20 | # number of spikes in calculation 21 | nspikes = int(1e5) 22 | 23 | # define adjacency matrix 24 | topo = np.array([[0,0,0], 25 | [1,0,1], 26 | [0,1,0]]) 27 | 28 | # define effective coupling strength 29 | c = 1 30 | 31 | #initial network state 32 | phi = np.random.rand(3) 33 | 34 | # initialize phase history 35 | phi_arr = np.zeros((nspikes, 3)) 36 | 37 | for t in tqdm(range(0, nspikes)): 38 | # find next spiking neuron j 39 | phi_max = np.max(phi) 40 | j = np.argmax(phi) 41 | 42 | # calculate next spike time 43 | dt = np.pi*0.5 - phi_max 44 | 45 | # evolve phases till next spike time 46 | phi += dt 47 | 48 | # get postsynaptic neurons 49 | post_idx = np.where(topo[:, j] > 0)[0] 50 | 51 | # update postsynaptic neurons (leaky integrate and fire neuron) 52 | phi[post_idx] = inv_h(h(phi[post_idx]) - c) 53 | # reset spiking neuron 54 | phi[j] = -np.pi*0.5 55 | phi_arr[t] = phi 56 | 57 | idx = np.where(phi_arr[:, 0] == -np.pi*0.5) 58 | plt.figure(figsize=(5, 4)) 59 | plt.scatter(phi_arr[idx, 1], phi_arr[idx, 2], s=1, 60 | alpha=1, color="k") 61 | plt.axis('off') 62 | plt.tight_layout() 63 | plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/Neurons/leakyRNN_chainer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import chainer 4 | import chainer.functions as F 5 | import chainer.links as L 6 | from chainer import cuda 7 | from chainer import Variable 8 | xp = cuda.cupy 9 | # import numpy as xp 10 | 11 | class leakyRNN(chainer.Chain): 12 | def __init__(self, inp=32, mid=128, alpha=0.2, sigma_rec=0.1): 13 | super(leakyRNN, self).__init__() 14 | """ 15 | Leaky RNN unit. 16 | 17 | Usage: 18 | >>> network = leakyRNN() 19 | >>> network.reset_state() 20 | >>> x = xp.ones((1, 32)).astype(xp.float32) 21 | >>> y = network(x) 22 | >>> y.shape 23 | (1, 128) 24 | """ 25 | 26 | with self.init_scope(): 27 | self.Wx = L.Linear(inp, mid) # feed-forward 28 | self.Wr = L.Linear(mid, mid, nobias=True) # recurrent 29 | 30 | self.inp = inp # 入力ユニット数 31 | self.mid = mid # 出力ユニット数 32 | self.alpha = alpha # tau / dt 33 | self.sigma_rec = sigma_rec # standard deviation of input noise 34 | 35 | def reset_state(self, r=None): 36 | self.r = r 37 | 38 | def initialize_state(self, shape): 39 | self.r = Variable(xp.zeros((shape[0], self.mid), 40 | dtype=self.xp.float32)) 41 | 42 | def forward(self, x): 43 | if self.r is None: 44 | self.initialize_state(x.shape) 45 | 46 | z = self.Wr(self.r) + self.Wx(x) 47 | if self.sigma_rec is not None: 48 | z += xp.random.normal(0, self.sigma_rec, 49 | (x.shape[0], self.mid)) # Add noise 50 | r = (1 - self.alpha)*self.r + self.alpha*F.relu(z) 51 | 52 | self.r = r 53 | return r -------------------------------------------------------------------------------- /SingleFileSimulations/STDP/stdp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Aug 11 13:52:03 2019 4 | 5 | @author: user 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | 11 | tau_p = tau_m = 20 #ms 12 | A_p = 0.01; A_m = 1.05*A_p; 13 | dt = np.arange(-50, 50, 1) #ms 14 | dw = A_p*np.exp(-dt/tau_p)*(dt>0) - A_m*np.exp(dt/tau_p)*(dt<0) 15 | 16 | plt.figure(figsize=(5, 4)) 17 | plt.plot(dt, dw, color="k") 18 | plt.hlines(0, -50, 50) 19 | plt.xlabel("$\Delta t$ (ms)") 20 | plt.ylabel("$\Delta w$") 21 | plt.xlim(-50, 50) 22 | plt.tight_layout() 23 | plt.savefig('stdp.pdf') 24 | #plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/STDP/stdp2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Aug 11 13:52:03 2019 4 | 5 | @author: user 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | 11 | np.random.seed(seed=0) 12 | 13 | #定数 14 | dt = 1e-3 #sec 15 | T = 0.5 #sec 16 | nt = round(T/dt) 17 | 18 | tau_p = tau_m = 2e-2 #ms 19 | A_p = 0.01; A_m = 1.05*A_p 20 | 21 | #pre/postsynaptic spikes 22 | spike_pre = np.zeros(nt); spike_pre[[50, 200, 225, 300, 425]] = 1 23 | spike_post = np.zeros(nt); spike_post[[100, 150, 250, 350, 400]] = 1 24 | 25 | #記録用配列 26 | x_pre_arr = np.zeros(nt) 27 | x_post_arr = np.zeros(nt) 28 | w_arr = np.zeros(nt) 29 | 30 | #初期化 31 | x_pre = x_post = 0 # pre/post synaptic trace 32 | w = 0 # synaptic weight 33 | 34 | #Online STDP 35 | for t in range(nt): 36 | x_pre = x_pre*(1-dt/tau_p) + spike_pre[t] 37 | x_post = x_post*(1-dt/tau_m) + spike_post[t] 38 | dw = A_p*x_pre*spike_post[t] - A_m*x_post*spike_pre[t] 39 | w += dw #重みの更新 40 | 41 | x_pre_arr[t] = x_pre 42 | x_post_arr[t] = x_post 43 | w_arr[t] = w 44 | 45 | # 描画 46 | time = np.arange(nt)*dt*1e3 47 | plt.figure(figsize=(6, 6)) 48 | def hide_ticks(): #上と右の軸を表示しないための関数 49 | plt.gca().spines['right'].set_visible(False) 50 | plt.gca().spines['top'].set_visible(False) 51 | plt.gca().yaxis.set_ticks_position('left') 52 | plt.gca().xaxis.set_ticks_position('bottom') 53 | 54 | plt.subplot(5,1,1) 55 | plt.plot(time, x_pre_arr, color="k") 56 | plt.ylabel("$x_{pre}$"); hide_ticks(); plt.xticks([]) 57 | plt.subplot(5,1,2) 58 | plt.plot(time, spike_pre, color="k") 59 | plt.ylabel("pre- spikes"); hide_ticks(); plt.xticks([]) 60 | plt.subplot(5,1,3) 61 | plt.plot(time, spike_post, color="k") 62 | plt.ylabel("post- spikes"); hide_ticks(); plt.xticks([]) 63 | plt.subplot(5,1,4) 64 | plt.plot(time, x_post_arr, color="k") 65 | plt.ylabel("$x_{post}$"); hide_ticks(); plt.xticks([]) 66 | plt.subplot(5,1,5) 67 | plt.plot(time, w_arr, color="k") 68 | plt.xlabel("$t$ (ms)"); plt.ylabel("$w$"); hide_ticks() 69 | plt.tight_layout() 70 | plt.savefig("online_stdp.pdf") 71 | -------------------------------------------------------------------------------- /SingleFileSimulations/STDP/stdp3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Aug 11 13:52:03 2019 4 | 5 | @author: user 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | 11 | np.random.seed(seed=0) 12 | 13 | dt = 1e-3; T = 5e-2 #sec 14 | nt = round(T/dt) 15 | 16 | N_pre = nt; N_post = 2 17 | 18 | tau_p = tau_m = 2e-2 #ms 19 | A_p = 0.01; A_m = 1.05*A_p 20 | 21 | # pre/postsynaptic spikes 22 | spike_pre = np.eye(N_pre) #単位行列でdtごとに発火するニューロンをN個作成 23 | spike_post = np.zeros((N_post, nt)) 24 | spike_post[0, -1] = spike_post[1, 0] = 1 25 | 26 | # 初期化 27 | x_pre = np.zeros(N_pre) 28 | x_post = np.zeros(N_post) 29 | W = np.zeros((N_post, N_pre)) 30 | 31 | for t in range(nt): 32 | # 1次元配列 -> 縦ベクトル or 横ベクトル 33 | spike_pre_ = np.expand_dims(spike_pre[:, t], 0) # (1, N) 34 | spike_post_ = np.expand_dims(spike_post[:, t], 1) # (2, 1) 35 | x_pre_ = np.expand_dims(x_pre, 0) # (1, N) 36 | x_post_ = np.expand_dims(x_post, 1) # (2, 1) 37 | 38 | # Online STDP 39 | dW = A_p*np.matmul(spike_post_, x_pre_) 40 | dW -= A_m*np.matmul(x_post_, spike_pre_) 41 | W += dW 42 | 43 | # Update 44 | x_pre = x_pre*(1-dt/tau_p) + spike_pre[:, t] 45 | x_post = x_post*(1-dt/tau_m) + spike_post[:, t] 46 | 47 | 48 | # 結果 49 | delta_w = np.zeros(nt*2-1) # スパイク時間差 = 0msが重複 50 | delta_w[:nt] = W[0, :]; delta_w[nt:] = W[1, 1:] 51 | 52 | # 描画 53 | time = np.arange(-T, T-dt, dt)*1e3 54 | plt.figure(figsize=(5, 4)) 55 | plt.plot(time, delta_w[::-1], color="k") 56 | plt.hlines(0, -50, 50) 57 | plt.xlabel("$\Delta t$ (ms)") 58 | plt.ylabel("$\Delta w$") 59 | plt.xlim(-50, 50) 60 | plt.tight_layout() 61 | plt.savefig('online_stdp2.pdf') 62 | #plt.show() 63 | -------------------------------------------------------------------------------- /SingleFileSimulations/Synapses/exponential_synapse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | dt = 5e-5 # 時間ステップ (sec) 7 | td = 2e-2 # synaptic decay time (sec) 8 | tr = 2e-3 # synaptic rise time (sec) 9 | T = 0.1 # シミュレーション時間 (sec) 10 | nt = round(T/dt) # シミュレーションの総ステップ 11 | 12 | # 単一指数関数型シナプス 13 | r = 0 # 初期値 14 | single_r = [] #記録用配列 15 | for t in range(nt): 16 | spike = 1 if t == 0 else 0 17 | single_r.append(r) 18 | r = r*(1-dt/td) + spike/td 19 | #r = r*np.exp(-dt/td) + spike/td 20 | 21 | # 二重指数関数型シナプス 22 | r = 0; hr = 0 # 初期値 23 | double_r = [] #記録用配列 24 | for t in range(nt): 25 | spike = 1 if t == 0 else 0 26 | double_r.append(r) 27 | r = r*(1-dt/tr) + hr*dt 28 | hr = hr*(1-dt/td) + spike/(tr*td) 29 | #r = r*np.exp(-dt/tr) + hr*dt 30 | #hr = hr*np.exp(-dt/td) + spike/(tr*td) 31 | 32 | # 描画 33 | time = np.arange(nt)*dt 34 | plt.figure(figsize=(4, 3)) 35 | plt.plot(time, np.array(single_r), linestyle="dashed", 36 | color="k", label="single exponential") 37 | plt.plot(time, np.array(double_r), color="k", label="double exponential") 38 | plt.xlabel('Time (s)'); plt.ylabel('Post-synaptic current (pA)') 39 | plt.legend() 40 | plt.tight_layout() 41 | plt.savefig('exp_synapse.pdf') 42 | plt.show() -------------------------------------------------------------------------------- /SingleFileSimulations/Synapses/kinetic_synapse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | dt = 1e-4 # 時間ステップ (sec) 7 | alpha = 1/5e-4 # synaptic decay time (sec) 8 | beta = 1/5e-3 # synaptic rise time (sec) 9 | T = 0.05 # シミュレーション時間 (sec) 10 | nt = round(T/dt) # シミュレーションの総ステップ 11 | 12 | r = 0 # 初期値 13 | single_r = [] #記録用配列 14 | for t in range(nt): 15 | spike = 1 if t == 0 else 0 16 | single_r.append(r) 17 | r += (alpha*spike*(1-r) - beta*r)*dt 18 | 19 | # 描画 20 | time = np.arange(nt)*dt 21 | plt.figure(figsize=(4, 3)) 22 | plt.plot(time, np.array(single_r), color="k") 23 | plt.xlabel('Time (s)'); plt.ylabel('Post-synaptic current (pA)') 24 | plt.tight_layout() 25 | plt.savefig('kinetic_synapse.pdf') 26 | plt.show() -------------------------------------------------------------------------------- /TrainingSNN/Izhikevich_FORCE_sinewave.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | import math 7 | 8 | from Models.Neurons import IzhikevichNeuron 9 | from Models.Synapses import DoubleExponentialSynapse 10 | 11 | np.random.seed(seed=0) 12 | 13 | ################# 14 | ## モデルの定義 ### 15 | ################# 16 | N = 2000 # ニューロンの数 17 | dt = 0.04 # タイムステップ (ms) 18 | 19 | C = 250 # 膜容量(pF) 20 | a = 0.01 # 回復時定数の逆数 (1/ms) 21 | b = -2 # uのvに対する共鳴度合い(pA/mV) 22 | k = 2.5 # ゲイン (pA/mV) 23 | d = 200 # 発火で活性化される正味の外向き電流(pA) 24 | vrest = -60 # 静止膜電位 (mV) 25 | vreset = -65 # リセット電位 (mV) 26 | vthr = -20 # 閾値電位 (mV) 27 | vpeak = 30 # ピーク電位 (mV) 28 | td = 20; tr = 2 # シナプスの時定数 29 | P = np.eye(N)*2 # 相関行列の逆行列の初期化 30 | 31 | # 教師信号(正弦波)の生成 32 | T = 15000 # シミュレーション時間 (ms) 33 | tmin = round(5000/dt) # 重み更新の開始ステップ 34 | tcrit = round(10000/dt) # 重み更新の終了ステップ 35 | step = 50 # 重み更新のステップ間隔 36 | nt = round(T/dt) # シミュレーションステップ数 37 | Q = 5e3; G = 5e3 38 | 39 | zx = np.sin(2*math.pi*np.arange(nt)*dt*5*1e-3) # 教師信号 40 | 41 | # ニューロンとシナプスの定義 42 | neurons = IzhikevichNeuron(N=N, dt=dt, C=C, a=a, b=b, 43 | k=k, d=d, vrest=vrest, vreset=vreset, 44 | vthr=vthr, vpeak=vpeak) 45 | neurons.v = vrest + np.random.rand(N)*(vpeak-vrest) # 膜電位の初期化 46 | 47 | synapses_out = DoubleExponentialSynapse(N, dt=dt, td=td, tr=tr) 48 | synapses_rec = DoubleExponentialSynapse(N, dt=dt, td=td, tr=tr) 49 | 50 | # 再帰重みの初期値 51 | p = 0.1 # ネットワークのスパース性 52 | OMEGA = G*(np.random.randn(N,N))*(np.random.rand(N,N)0)[0] 56 | OMEGA[i,QS] = OMEGA[i,QS] - np.sum(OMEGA[i,QS], axis=0)/len(QS) 57 | 58 | 59 | # 変数の初期値 60 | k = 1 # 出力ニューロンの数 61 | E = (2*np.random.rand(N,k) - 1)*Q 62 | PSC = np.zeros(N) # シナプス後電流 63 | JD = np.zeros(N) # 再帰入力の重み和 64 | z = np.zeros(k) # 出力の初期化 65 | Phi = np.zeros(N) # 学習される重みの初期値 66 | 67 | # 記録用変数 68 | REC_v = np.zeros((nt,10)) # 膜電位の記録変数 69 | current = np.zeros(nt) # 出力の電流の記録変数 70 | tspike = np.zeros((5*nt,2)) # スパイク時刻の記録変数 71 | ns = 0 # スパイク数の記録変数 72 | 73 | BIAS = 1000 # 入力電流のバイアス 74 | 75 | ################# 76 | ## シミュレーション ### 77 | ################# 78 | for t in tqdm(range(nt)): 79 | I = PSC + np.dot(E, z) + BIAS # シナプス電流 80 | s = neurons(I) # 中間ニューロンのスパイク 81 | 82 | index = np.where(s)[0] # 発火したニューロンのindex 83 | 84 | len_idx = len(index) 85 | if len_idx > 0: 86 | JD = np.sum(OMEGA[:, index], axis=1) 87 | tspike[ns:ns+len_idx,:] = np.vstack((index, 0*index+dt*t)).T 88 | ns = ns + len_idx # スパイク数の記録 89 | 90 | PSC = synapses_rec(JD*(len_idx>0)) # 再帰的入力電流 91 | #PSC = OMEGA @ r # 遅い 92 | 93 | r = synapses_out(s) # 出力電流(神経伝達物質の放出量) 94 | r = np.expand_dims(r,1) # (N,) -> (N, 1) 95 | 96 | z = Phi.T @ r # デコードされた出力 97 | err = z - zx[t] # 誤差 98 | 99 | # FORCE法(RLS)による重み更新 100 | if t % step == 1: 101 | if t > tmin: 102 | if t < tcrit: 103 | cd = (P @ r) 104 | Phi = Phi - (cd @ err.T) 105 | P = P - (cd @ cd.T) / (1.0 + r.T @ cd) 106 | 107 | current[t] = z 108 | REC_v[t] = neurons.v_[:10] 109 | 110 | ################# 111 | #### 結果表示 #### 112 | ################# 113 | TotNumSpikes = ns 114 | M = tspike[tspike[:,1]>dt*tcrit,:] 115 | AverageRate = len(M)/(N*(T-dt*tcrit))*1e3 116 | print("\n") 117 | print("Total number of spikes : ", TotNumSpikes) 118 | print("Average firing rate(Hz): ", AverageRate) 119 | 120 | def hide_ticks(): #上と右の軸を表示しないための関数 121 | plt.gca().spines['right'].set_visible(False) 122 | plt.gca().spines['top'].set_visible(False) 123 | plt.gca().yaxis.set_ticks_position('left') 124 | plt.gca().xaxis.set_ticks_position('bottom') 125 | 126 | step_range = 20000 127 | plt.figure(figsize=(10, 5)) 128 | plt.subplot(1,2,1) 129 | for j in range(5): 130 | plt.plot(np.arange(step_range)*dt*1e-3, 131 | REC_v[:step_range, j]/(50-vreset)+j, 132 | color="k") 133 | hide_ticks() 134 | plt.title('Pre-Learning') 135 | plt.xlabel('Time (s)') 136 | plt.ylabel('Neuron Index') 137 | plt.tight_layout() 138 | 139 | plt.subplot(1,2,2) 140 | for j in range(5): 141 | plt.plot(np.arange(nt-step_range, nt)*dt*1e-3, 142 | REC_v[nt-step_range:, j]/(50-vreset)+j, 143 | color="k") 144 | hide_ticks() 145 | plt.title('Post Learning') 146 | plt.xlabel('Time (s)') 147 | plt.tight_layout() 148 | plt.savefig("Iz_FORCE_prepost.pdf") 149 | plt.show() 150 | 151 | plt.figure(figsize=(10,5)) 152 | plt.subplot(1,2,1) 153 | plt.plot(np.arange(nt)*dt*1e-3, zx, 154 | label="Target", color="k") 155 | plt.plot(np.arange(nt)*dt*1e-3, current, 156 | label="Decoded output", 157 | linestyle="dashed", color="k") 158 | plt.xlim(4.5,5.5) 159 | plt.ylim(-1.1,1.4) 160 | hide_ticks() 161 | plt.title('Pre/peri Learning') 162 | plt.xlabel('Time (s)') 163 | plt.ylabel('current') 164 | plt.tight_layout() 165 | 166 | plt.subplot(1,2,2) 167 | plt.title('Post Learning') 168 | plt.plot(np.arange(nt)*dt*1e-3, zx, 169 | label="Target", color="k") 170 | plt.plot(np.arange(nt)*dt*1e-3, current, 171 | label="Decoded output", 172 | linestyle="dashed", color="k") 173 | plt.xlim(14,15) 174 | plt.ylim(-1.1,1.4) 175 | hide_ticks() 176 | plt.xlabel('Time (s)') 177 | plt.legend(loc='upper right') 178 | plt.tight_layout() 179 | plt.savefig("Iz_FORCE_decoded.pdf") 180 | plt.show() 181 | 182 | """ 183 | Z = np.linalg.eig(OMEGA + np.expand_dims(E,1) @ np.expand_dims(Phi,1).T) 184 | Z2 = np.linalg.eig(OMEGA) 185 | plt.figure(figsize=(6, 5)) 186 | plt.title('Weight eigenvalues') 187 | plt.scatter(Z2[0].real, Z2[0].imag, c='r', s=5, label='Pre-Learning') 188 | plt.scatter(Z[0].real, Z[0].imag, c='k', s=5, label='Post-Learning') 189 | plt.legend() 190 | plt.xlabel('Real') 191 | plt.ylabel('Imaginary') 192 | #plt.savefig("LIF_weight_eigenvalues.png") 193 | plt.show() 194 | """ -------------------------------------------------------------------------------- /TrainingSNN/LIF_FORCE_sinewave.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | from Models.Neurons import CurrentBasedLIF 8 | from Models.Synapses import DoubleExponentialSynapse 9 | 10 | np.random.seed(seed=0) 11 | 12 | ################# 13 | ## モデルの定義 ### 14 | ################# 15 | N = 2000 # ニューロンの数 16 | dt = 5e-5 # タイムステップ(s) 17 | tref = 2e-3 # 不応期(s) 18 | tc_m = 1e-2 # 膜時定数(s) 19 | vreset = -65 # リセット電位(mV) 20 | vrest = 0 # 静止膜電位(mV) 21 | vthr = -40 # 閾値電位(mV) 22 | vpeak = 30 # ピーク電位(mV) 23 | BIAS = -40 # 入力電流のバイアス(pA) 24 | td = 2e-2; tr = 2e-3 # シナプスの時定数(s) 25 | alpha = dt*0.1 26 | P = np.eye(N)*alpha 27 | Q = 10; G = 0.04 28 | 29 | # 教師信号(正弦波)の生成 30 | T = 15 # シミュレーション時間 (s) 31 | tmin = round(5/dt) # 重み更新の開始ステップ 32 | tcrit = round(10/dt) # 重み更新の終了ステップ 33 | step = 50 # 重み更新のステップ間隔 34 | nt = round(T/dt) # シミュレーションステップ数 35 | zx = np.sin(2*np.pi*np.arange(nt)*dt*5) # 教師信号 36 | 37 | # ニューロンとシナプスの定義 38 | neurons = CurrentBasedLIF(N=N, dt=dt, tref=tref, tc_m=tc_m, 39 | vrest=vrest, vreset=vreset, vthr=vthr, vpeak=vpeak) 40 | neurons.v = vreset + np.random.rand(N)*(vpeak-vreset) # 膜電位の初期化 41 | 42 | synapses_out = DoubleExponentialSynapse(N, dt=dt, td=td, tr=tr) 43 | synapses_rec = DoubleExponentialSynapse(N, dt=dt, td=td, tr=tr) 44 | 45 | # 再帰重みの初期値 46 | p = 0.1 # ネットワークのスパース性 47 | OMEGA = G*(np.random.randn(N,N))*(np.random.rand(N,N)0)[0] 50 | OMEGA[i,QS] = OMEGA[i,QS] - np.sum(OMEGA[i,QS], axis=0)/len(QS) 51 | 52 | 53 | # 変数の初期値 54 | k = 1 # 出力ニューロンの数 55 | E = (2*np.random.rand(N, k) - 1)*Q 56 | PSC = np.zeros(N).astype(np.float32) # シナプス後電流 57 | JD = np.zeros(N).astype(np.float32) # 再帰入力の重み和 58 | z = np.zeros(k).astype(np.float32) # 出力の初期化 59 | Phi = np.zeros(N).astype(np.float32) # 学習される重みの初期値 60 | 61 | # 記録用変数 62 | REC_v = np.zeros((nt,10)).astype(np.float32) # 膜電位の記録変数 63 | current = np.zeros(nt).astype(np.float32) # 出力の電流の記録変数 64 | tspike = np.zeros((4*nt,2)).astype(np.float32) # スパイク時刻の記録変数 65 | ns = 0 # スパイク数の記録変数 66 | 67 | ################# 68 | ## シミュレーション ### 69 | ################# 70 | for t in tqdm(range(nt)): 71 | I = PSC + np.dot(E, z) + BIAS # シナプス電流 72 | s = neurons(I) # 中間ニューロンのスパイク 73 | 74 | index = np.where(s)[0] # 発火したニューロンのindex 75 | 76 | len_idx = len(index) # 発火したニューロンの数 77 | if len_idx > 0: 78 | JD = np.sum(OMEGA[:, index], axis=1) 79 | tspike[ns:ns+len_idx,:] = np.vstack((index, 0*index+dt*t)).T 80 | ns = ns + len_idx # スパイク数の記録 81 | 82 | PSC = synapses_rec(JD*(len_idx>0)) # 再帰的入力電流 83 | #PSC = OMEGA @ r # 遅い 84 | 85 | r = synapses_out(s) # 出力電流(神経伝達物質の放出量) 86 | r = np.expand_dims(r,1) # (N,) -> (N, 1) 87 | 88 | z = Phi.T @ r # デコードされた出力 89 | err = z - zx[t] # 誤差 90 | 91 | # FORCE法(RLS)による重み更新 92 | if t % step == 1: 93 | if t > tmin: 94 | if t < tcrit: 95 | cd = (P @ r) 96 | Phi = Phi - (cd @ err.T) 97 | P = P - (cd @ cd.T) / (1.0 + r.T @ cd) 98 | 99 | current[t] = z # デコード結果の記録 100 | REC_v[t] = neurons.v_[:10] # 膜電位の記録 101 | 102 | ################# 103 | #### 結果表示 #### 104 | ################# 105 | TotNumSpikes = ns 106 | M = tspike[tspike[:,1]>dt*tcrit,:] 107 | AverageRate = len(M)/(N*(T-dt*tcrit)) 108 | print("\n") 109 | print("Total number of spikes : ", TotNumSpikes) 110 | print("Average firing rate(Hz): ", AverageRate) 111 | 112 | def hide_ticks(): #上と右の軸を表示しないための関数 113 | plt.gca().spines['right'].set_visible(False) 114 | plt.gca().spines['top'].set_visible(False) 115 | plt.gca().yaxis.set_ticks_position('left') 116 | plt.gca().xaxis.set_ticks_position('bottom') 117 | 118 | step_range = 20000 119 | plt.figure(figsize=(10, 5)) 120 | plt.subplot(1,2,1) 121 | for j in range(5): 122 | plt.plot(np.arange(step_range)*dt, 123 | REC_v[:step_range, j]/(50-vreset)+j, 124 | color="k") 125 | hide_ticks() 126 | plt.title('Pre-Learning') 127 | plt.xlabel('Time (s)') 128 | plt.ylabel('Neuron Index') 129 | plt.tight_layout() 130 | 131 | plt.subplot(1,2,2) 132 | for j in range(5): 133 | plt.plot(np.arange(nt-step_range, nt)*dt, 134 | REC_v[nt-step_range:, j]/(50-vreset)+j, 135 | color="k") 136 | hide_ticks() 137 | plt.title('Post Learning') 138 | plt.xlabel('Time (s)') 139 | plt.tight_layout() 140 | plt.savefig("LIF_FORCE_prepost.pdf") 141 | plt.show() 142 | 143 | plt.figure(figsize=(10,5)) 144 | plt.subplot(1,2,1) 145 | plt.plot(np.arange(nt)*dt, zx, 146 | label="Target", color="k") 147 | plt.plot(np.arange(nt)*dt, current, 148 | label="Decoded output", 149 | linestyle="dashed", color="k") 150 | plt.xlim(4.5,5.5) 151 | plt.ylim(-1.1,1.4) 152 | hide_ticks() 153 | plt.title('Pre/peri Learning') 154 | plt.xlabel('Time (s)') 155 | plt.ylabel('current') 156 | plt.tight_layout() 157 | 158 | plt.subplot(1,2,2) 159 | plt.title('Post Learning') 160 | plt.plot(np.arange(nt)*dt, zx, 161 | label="Target", color="k") 162 | plt.plot(np.arange(nt)*dt, current, 163 | label="Decoded output", 164 | linestyle="dashed", color="k") 165 | plt.xlim(14,15) 166 | plt.ylim(-1.1,1.4) 167 | hide_ticks() 168 | plt.xlabel('Time (s)') 169 | plt.legend(loc='upper right') 170 | plt.tight_layout() 171 | plt.savefig("LIF_FORCE_decoded.pdf") 172 | plt.show() 173 | 174 | """ 175 | Z = np.linalg.eig(OMEGA + np.expand_dims(E,1) @ np.expand_dims(Phi,1).T) 176 | Z2 = np.linalg.eig(OMEGA) 177 | plt.figure(figsize=(6, 5)) 178 | plt.title('Weight eigenvalues') 179 | plt.scatter(Z2[0].real, Z2[0].imag, c='r', s=5, label='Pre-Learning') 180 | plt.scatter(Z[0].real, Z[0].imag, c='k', s=5, label='Post-Learning') 181 | plt.legend() 182 | plt.xlabel('Real') 183 | plt.ylabel('Imaginary') 184 | #plt.savefig("LIF_weight_eigenvalues.png") 185 | plt.show() 186 | """ -------------------------------------------------------------------------------- /TrainingSNN/LIF_SuperSpike.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | from Models.Neurons import CurrentBasedLIF 8 | from Models.Synapses import DoubleExponentialSynapse 9 | from Models.Connections import FullConnection, DelayConnection 10 | 11 | np.random.seed(seed=0) 12 | 13 | class ErrorSignal: 14 | def __init__(self, N, dt=1e-4, td=1e-2, tr=5e-3): 15 | self.dt = dt 16 | self.td = td 17 | self.tr = tr 18 | self.N = N 19 | self.r = np.zeros(N) 20 | self.hr = np.zeros(N) 21 | self.b = (td/tr)**(td/(tr-td)) # 規格化定数 22 | 23 | def initialize_states(self): 24 | self.r = np.zeros(self.N) 25 | self.hr = np.zeros(self.N) 26 | 27 | def __call__(self, output_spike, target_spike): 28 | r = self.r*(1-self.dt/self.tr) + self.hr/self.td*self.dt 29 | hr = self.hr*(1-self.dt/self.td) + (target_spike - output_spike)/self.b 30 | 31 | self.r = r 32 | self.hr = hr 33 | 34 | return r 35 | 36 | class EligibilityTrace: 37 | def __init__(self, N_in, N_out, dt=1e-4, td=1e-2, tr=5e-3): 38 | self.dt = dt 39 | self.td = td 40 | self.tr = tr 41 | self.N_in = N_in 42 | self.N_out = N_out 43 | self.r = np.zeros((N_out, N_in)) 44 | self.hr = np.zeros((N_out, N_in)) 45 | 46 | def initialize_states(self): 47 | self.r = np.zeros((self.N_out, self.N_in)) 48 | self.hr = np.zeros((self.N_out, self.N_in)) 49 | 50 | def surrogate_derivative_fastsigmoid(self, u, beta=1, vthr=-50): 51 | return 1 / (1 + np.abs(beta*(u-vthr)))**2 52 | 53 | def __call__(self, pre_current, post_voltage): 54 | # (N_out, 1) x (1, N_in) -> (N_out, N_in) 55 | pre_ = np.expand_dims(pre_current, axis=0) 56 | post_ = np.expand_dims( 57 | self.surrogate_derivative_fastsigmoid(post_voltage), 58 | axis=1) 59 | 60 | r = self.r*(1-self.dt/self.tr) + self.hr*self.dt 61 | hr = self.hr*(1-self.dt/self.td) + (post_ @ pre_)/(self.tr*self.td) 62 | 63 | self.r = r 64 | self.hr = hr 65 | 66 | return r 67 | 68 | ################# 69 | ## モデルの定義 ### 70 | ################# 71 | 72 | dt = 1e-4; T = 0.5; nt = round(T/dt) 73 | 74 | t_weight_update = 0.5 #重みの更新時間 75 | nt_b = round(t_weight_update/dt) #重みの更新ステップ 76 | 77 | num_iter = 200 # 学習のイテレーション数 78 | 79 | N_in = 50 # 入力ユニット数 80 | N_mid = 4 # 中間ユニット数 81 | N_out = 1 # 出力ユニット数 82 | 83 | # 入力(x)と教師信号(y)の定義 84 | fr_in = 10 # 入力のPoisson発火率 (Hz) 85 | x = np.where(np.random.rand(nt, N_in) < fr_in * dt, 1, 0) 86 | y = np.zeros((nt, N_out)) 87 | y[int(nt/10)::int(nt/5), :] = 1 # T/5に1回発火 88 | 89 | # モデルの定義 90 | neurons_1 = CurrentBasedLIF(N_mid, dt=dt) 91 | neurons_2 = CurrentBasedLIF(N_out, dt=dt) 92 | delay_conn1 = DelayConnection(N_in, delay=8e-4) 93 | delay_conn2 = DelayConnection(N_mid, delay=8e-4) 94 | synapses_1 = DoubleExponentialSynapse(N_in, dt=dt, td=1e-2, tr=5e-3) 95 | synapses_2 = DoubleExponentialSynapse(N_mid, dt=dt, td=1e-2, tr=5e-3) 96 | es = ErrorSignal(N_out) 97 | et1 = EligibilityTrace(N_in, N_mid) 98 | et2 = EligibilityTrace(N_mid, N_out) 99 | 100 | connect_1 = FullConnection(N_in, N_mid, 101 | initW=0.1*np.random.rand(N_mid, N_in)) 102 | connect_2 = FullConnection(N_mid, N_out, 103 | initW=0.1*np.random.rand(N_out, N_mid)) 104 | #B = np.random.rand(N_mid, N_out) 105 | 106 | r0 = 1e-3 107 | gamma = 0.7 108 | 109 | # 記録用配列 110 | current_arr = np.zeros((N_mid, nt)) 111 | voltage_arr = np.zeros((N_out, nt)) 112 | error_arr = np.zeros((N_out, nt)) 113 | lambda_arr = np.zeros((N_out, N_mid, nt)) 114 | dw_arr = np.zeros((N_out, N_mid, nt)) 115 | cost_arr = np.zeros(num_iter) 116 | 117 | ################# 118 | ## シミュレーション ### 119 | ################# 120 | for i in tqdm(range(num_iter)): 121 | if i%15 == 0: 122 | r0 /= 2 # 重み減衰 123 | 124 | # 状態の初期化 125 | neurons_1.initialize_states() 126 | neurons_2.initialize_states() 127 | synapses_1.initialize_states() 128 | synapses_2.initialize_states() 129 | delay_conn1.initialize_states() 130 | delay_conn2.initialize_states() 131 | es.initialize_states() 132 | et1.initialize_states() 133 | et2.initialize_states() 134 | 135 | m1 = np.zeros((N_mid, N_in)) 136 | m2 = np.zeros((N_out, N_mid)) 137 | v1 = np.zeros((N_mid, N_in)) 138 | v2 = np.zeros((N_out, N_mid)) 139 | cost = 0 140 | count = 0 141 | 142 | # one iter. 143 | for t in range(nt): 144 | # Feed-forward 145 | c1 = synapses_1(delay_conn1(x[t])) # input current 146 | h1 = connect_1(c1) 147 | s1 = neurons_1(h1) # spike of mid neurons 148 | 149 | c2 = synapses_2(delay_conn2(s1)) 150 | h2 = connect_2(c2) 151 | s2 = neurons_2(h2) 152 | 153 | # Backward(誤差の伝搬) 154 | e2 = np.expand_dims(es(s2, y[t]), axis=1) / N_out 155 | e1 = connect_2.backward(e2) / N_mid 156 | #e1 = B @ e2 / N_mid 157 | 158 | # コストの計算 159 | cost += np.sum(e2**2) 160 | 161 | lambda2 = et2(c2, neurons_2.v_) 162 | lambda1 = et1(c1, neurons_1.v_) 163 | 164 | g2 = e2 * lambda2 165 | g1 = e1 * lambda1 166 | 167 | v1 = np.maximum(gamma*v1, g1**2) 168 | v2 = np.maximum(gamma*v2, g2**2) 169 | 170 | m1 += g1 171 | m2 += g2 172 | 173 | count += 1 174 | if count == nt_b: 175 | # 重みの更新 176 | lr1 = r0/np.sqrt(v1+1e-8) 177 | lr2 = r0/np.sqrt(v2+1e-8) 178 | dW1 = np.clip(lr1*m1*dt, -1e-3, 1e-3) 179 | dW2 = np.clip(lr2*m2*dt, -1e-3, 1e-3) 180 | connect_1.W = np.clip(connect_1.W+dW1, -0.1, 0.1) 181 | connect_2.W = np.clip(connect_2.W+dW2, -0.1, 0.1) 182 | 183 | # リセット 184 | m1 = np.zeros((N_mid, N_in)) 185 | m2 = np.zeros((N_out, N_mid)) 186 | v1 = np.zeros((N_mid, N_in)) 187 | v2 = np.zeros((N_out, N_mid)) 188 | count = 0 189 | 190 | # 保存 191 | if i == num_iter-1: 192 | current_arr[:, t] = c2 193 | voltage_arr[:, t] = neurons_2.v_ 194 | error_arr[:, t] = e2 195 | lambda_arr[:, :, t] = lambda2 196 | 197 | cost_arr[i] = cost 198 | print("\n cost:", cost) 199 | 200 | ################# 201 | #### 結果表示 #### 202 | ################# 203 | def hide_ticks(): #上と右の軸を表示しないための関数 204 | plt.gca().spines['right'].set_visible(False) 205 | plt.gca().spines['top'].set_visible(False) 206 | plt.gca().yaxis.set_ticks_position('left') 207 | plt.gca().xaxis.set_ticks_position('bottom') 208 | 209 | t = np.arange(nt)*dt*1e3 210 | plt.figure(figsize=(8, 10)) 211 | plt.subplot(6,1,1) 212 | plt.plot(t, voltage_arr[0], color="k") 213 | plt.ylabel('Membrane\n potential (mV)') 214 | hide_ticks() 215 | plt.tight_layout() 216 | 217 | plt.subplot(6,1,2) 218 | plt.plot(t, et1.surrogate_derivative_fastsigmoid(u=voltage_arr[0]), 219 | color="k") 220 | plt.ylabel('Surrogate derivative') 221 | hide_ticks() 222 | plt.tight_layout() 223 | 224 | plt.subplot(6,1,3) 225 | plt.plot(t, error_arr[0], color="k") 226 | plt.ylabel('Error') 227 | hide_ticks() 228 | plt.tight_layout() 229 | 230 | plt.subplot(6,1,4) 231 | plt.plot(t, lambda_arr[0, 0], color="k") 232 | plt.ylabel('$\lambda$') 233 | hide_ticks() 234 | plt.tight_layout() 235 | 236 | 237 | plt.subplot(6,1,5) 238 | plt.plot(t, current_arr[0], color="k") 239 | plt.ylabel('Input current (pA)') 240 | hide_ticks() 241 | plt.tight_layout() 242 | 243 | plt.subplot(6,1,6) 244 | for i in range(N_in): 245 | plt.plot(t, x[:, i]*(i+1), 'ko', markersize=2, 246 | rasterized=True) 247 | hide_ticks() 248 | plt.xlabel('Time (ms)') 249 | plt.ylabel('Neuron index') 250 | plt.xlim(0, t.max()) 251 | plt.ylim(0.5, N_in+0.5) 252 | plt.tight_layout() 253 | plt.savefig("super_spike.svg") 254 | plt.show() 255 | 256 | plt.figure(figsize=(4, 3)) 257 | plt.plot(cost_arr, color="k") 258 | plt.title('Cost') 259 | plt.xlabel('Iter') 260 | plt.ylabel('Cost') 261 | hide_ticks() 262 | plt.tight_layout() 263 | plt.savefig("super_spike_cost.svg") 264 | plt.show() -------------------------------------------------------------------------------- /TrainingSNN/LIF_WTA_STDP_MNIST.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | import chainer 8 | import os 9 | 10 | from Models.Neurons import ConductanceBasedLIF, DiehlAndCook2015LIF 11 | from Models.Synapses import SingleExponentialSynapse 12 | from Models.Connections import FullConnection, DelayConnection 13 | 14 | np.random.seed(seed=0) 15 | 16 | ################# 17 | #### Utils #### 18 | ################# 19 | # 画像をポアソンスパイク列に変換 20 | def online_load_and_encoding_dataset(dataset, i, dt, n_time, max_fr=32, 21 | norm=140): 22 | fr_tmp = max_fr*norm/np.sum(dataset[i][0]) 23 | fr = fr_tmp*np.repeat(np.expand_dims(dataset[i][0], 24 | axis=0), n_time, axis=0) 25 | input_spikes = np.where(np.random.rand(n_time, 784) < fr*dt, 1, 0) 26 | input_spikes = input_spikes.astype(np.uint8) 27 | 28 | return input_spikes 29 | 30 | # ラベルの割り当て 31 | def assign_labels(spikes, labels, n_labels, rates=None, alpha=1.0): 32 | """ 33 | Assign labels to the neurons based on highest average spiking activity. 34 | 35 | Args: 36 | spikes (n_samples, n_neurons) : A single layer's spiking activity. 37 | labels (n_samples,) : Data labels corresponding to input samples. 38 | n_labels (int) : The number of target labels in the data. 39 | rates (n_neurons, n_labels) : If passed, these represent spike rates 40 | from a previous ``assign_labels()`` call. 41 | alpha (float): Rate of decay of label assignments. 42 | return: Class assignments, per-class spike proportions, and per-class firing rates. 43 | """ 44 | n_neurons = spikes.shape[1] 45 | 46 | if rates is None: 47 | rates = np.zeros((n_neurons, n_labels)).astype(np.float32) 48 | 49 | # 時間の軸でスパイク数の和を取る 50 | for i in range(n_labels): 51 | # サンプル内の同じラベルの数を求める 52 | n_labeled = np.sum(labels == i).astype(np.int16) 53 | 54 | if n_labeled > 0: 55 | # label == iのサンプルのインデックスを取得 56 | indices = np.where(labels == i)[0] 57 | 58 | # label == iに対する各ニューロンごとの平均発火率を計算(前回の発火率との移動平均) 59 | rates[:, i] = alpha*rates[:, i] + (np.sum(spikes[indices], axis=0)/n_labeled) 60 | 61 | sum_rate = np.sum(rates, axis=1) 62 | sum_rate[sum_rate==0] = 1 63 | # クラスごとの発火頻度の割合を計算する 64 | proportions = rates / np.expand_dims(sum_rate, 1) # (n_neurons, n_labels) 65 | proportions[proportions != proportions] = 0 # Set NaNs to 0 66 | 67 | # 最も発火率が高いラベルを各ニューロンに割り当てる 68 | assignments = np.argmax(proportions, axis=1).astype(np.uint8) # (n_neurons,) 69 | 70 | return assignments, proportions, rates 71 | 72 | # assign_labelsで割り当てたラベルからサンプルのラベルの予測をする 73 | def prediction(spikes, assignments, n_labels): 74 | """ 75 | Classify data with the label with highest average spiking activity over all neurons. 76 | 77 | Args: 78 | spikes (n_samples, n_neurons) : A layer's spiking activity. 79 | assignments (n_neurons,) : Neuron label assignments. 80 | n_labels (int): The number of target labels in the data. 81 | return: Predictions (n_samples,) 82 | """ 83 | 84 | n_samples = spikes.shape[0] 85 | 86 | # 各サンプルについて各ラベルの発火率を見る 87 | rates = np.zeros((n_samples, n_labels)).astype(np.float32) 88 | 89 | for i in range(n_labels): 90 | # 各ラベルが振り分けられたニューロンの数 91 | n_assigns = np.sum(assignments == i).astype(np.uint8) 92 | 93 | if n_assigns > 0: 94 | # 各ラベルのニューロンのインデックスを取得 95 | indices = np.where(assignments == i)[0] 96 | 97 | # 各ラベルのニューロンのレイヤー全体における平均発火数を求める 98 | rates[:, i] = np.sum(spikes[:, indices], axis=1) / n_assigns 99 | 100 | # レイヤーの平均発火率が最も高いラベルを出力 101 | return np.argmax(rates, axis=1).astype(np.uint8) # (n_samples, ) 102 | 103 | 104 | ################# 105 | #### Model #### 106 | ################# 107 | class DiehlAndCook2015Network: 108 | def __init__(self, n_in=784, n_neurons=100, wexc=2.25, winh=0.875, 109 | dt=1e-3, wmin=0.0, wmax=5e-2, lr=(1e-2, 1e-4), 110 | update_nt=100): 111 | """ 112 | Network of Diehl and Cooks (2015) 113 | https://www.frontiersin.org/articles/10.3389/fncom.2015.00099/full 114 | 115 | Args: 116 | n_in: Number of input neurons. Matches the 1D size of the input data. 117 | n_neurons: Number of excitatory, inhibitory neurons. 118 | wexc: Strength of synapse weights from excitatory to inhibitory layer. 119 | winh: Strength of synapse weights from inhibitory to excitatory layer. 120 | dt: Simulation time step. 121 | lr: Single or pair of learning rates for pre- and post-synaptic events, respectively. 122 | wmin: Minimum allowed weight on input to excitatory synapses. 123 | wmax: Maximum allowed weight on input to excitatory synapses. 124 | update_nt: Number of time steps of weight updates. 125 | """ 126 | 127 | self.dt = dt 128 | self.lr_p, self.lr_m = lr 129 | self.wmax = wmax 130 | self.wmin = wmin 131 | 132 | # Neurons 133 | self.exc_neurons = DiehlAndCook2015LIF(n_neurons, dt=dt, tref=5e-3, 134 | tc_m=1e-1, 135 | vrest=-65, vreset=-65, 136 | init_vthr=-52, 137 | vpeak=20, theta_plus=0.05, 138 | theta_max=35, 139 | tc_theta=1e4, 140 | e_exc=0, e_inh=-100) 141 | 142 | self.inh_neurons = ConductanceBasedLIF(n_neurons, dt=dt, tref=2e-3, 143 | tc_m=1e-2, 144 | vrest=-60, vreset=-45, 145 | vthr=-40, vpeak=20, 146 | e_exc=0, e_inh=-85) 147 | # Synapses 148 | self.input_synapse = SingleExponentialSynapse(n_in, dt=dt, td=1e-3) 149 | self.exc_synapse = SingleExponentialSynapse(n_neurons, dt=dt, td=1e-3) 150 | self.inh_synapse = SingleExponentialSynapse(n_neurons, dt=dt, td=2e-3) 151 | 152 | self.input_synaptictrace = SingleExponentialSynapse(n_in, dt=dt, 153 | td=2e-2) 154 | self.exc_synaptictrace = SingleExponentialSynapse(n_neurons, dt=dt, 155 | td=2e-2) 156 | 157 | # Connections 158 | initW = 1e-3*np.random.rand(n_neurons, n_in) 159 | self.input_conn = FullConnection(n_in, n_neurons, 160 | initW=initW) 161 | self.exc2inh_W = wexc*np.eye(n_neurons) 162 | self.inh2exc_W = (winh/(n_neurons-1))*(np.ones((n_neurons, n_neurons)) - np.eye(n_neurons)) 163 | 164 | self.delay_input = DelayConnection(N=n_neurons, delay=5e-3, dt=dt) 165 | self.delay_exc2inh = DelayConnection(N=n_neurons, delay=2e-3, dt=dt) 166 | 167 | self.norm = 0.1 168 | self.g_inh = np.zeros(n_neurons) 169 | self.tcount = 0 170 | self.update_nt = update_nt 171 | self.n_neurons = n_neurons 172 | self.n_in = n_in 173 | self.s_in_ = np.zeros((self.update_nt, n_in)) 174 | self.s_exc_ = np.zeros((n_neurons, self.update_nt)) 175 | self.x_in_ = np.zeros((self.update_nt, n_in)) 176 | self.x_exc_ = np.zeros((n_neurons, self.update_nt)) 177 | 178 | # スパイクトレースのリセット 179 | def reset_trace(self): 180 | self.s_in_ = np.zeros((self.update_nt, self.n_in)) 181 | self.s_exc_ = np.zeros((self.n_neurons, self.update_nt)) 182 | self.x_in_ = np.zeros((self.update_nt, self.n_in)) 183 | self.x_exc_ = np.zeros((self.n_neurons, self.update_nt)) 184 | self.tcount = 0 185 | 186 | # 状態の初期化 187 | def initialize_states(self): 188 | self.exc_neurons.initialize_states() 189 | self.inh_neurons.initialize_states() 190 | self.delay_input.initialize_states() 191 | self.delay_exc2inh.initialize_states() 192 | self.input_synapse.initialize_states() 193 | self.exc_synapse.initialize_states() 194 | self.inh_synapse.initialize_states() 195 | 196 | def __call__(self, s_in, stdp=True): 197 | # 入力層 198 | c_in = self.input_synapse(s_in) 199 | x_in = self.input_synaptictrace(s_in) 200 | g_in = self.input_conn(c_in) 201 | 202 | # 興奮性ニューロン層 203 | s_exc = self.exc_neurons(self.delay_input(g_in), self.g_inh) 204 | c_exc = self.exc_synapse(s_exc) 205 | g_exc = np.dot(self.exc2inh_W, c_exc) 206 | x_exc = self.exc_synaptictrace(s_exc) 207 | 208 | # 抑制性ニューロン層 209 | s_inh = self.inh_neurons(self.delay_exc2inh(g_exc), 0) 210 | c_inh = self.inh_synapse(s_inh) 211 | self.g_inh = np.dot(self.inh2exc_W, c_inh) 212 | 213 | if stdp: 214 | # スパイク列とスパイクトレースを記録 215 | self.s_in_[self.tcount] = s_in 216 | self.s_exc_[:, self.tcount] = s_exc 217 | self.x_in_[self.tcount] = x_in 218 | self.x_exc_[:, self.tcount] = x_exc 219 | self.tcount += 1 220 | 221 | # Online STDP 222 | if self.tcount == self.update_nt: 223 | W = np.copy(self.input_conn.W) 224 | 225 | # postに投射される重みが均一になるようにする 226 | W_abs_sum = np.expand_dims(np.sum(np.abs(W), axis=1), 1) 227 | W_abs_sum[W_abs_sum == 0] = 1.0 228 | W *= self.norm / W_abs_sum 229 | 230 | # STDP則 231 | dW = self.lr_p*(self.wmax - W)*np.dot(self.s_exc_, self.x_in_) 232 | dW -= self.lr_m*W*np.dot(self.x_exc_, self.s_in_) 233 | clipped_dW = np.clip(dW / self.update_nt, -1e-3, 1e-3) 234 | self.input_conn.W = np.clip(W + clipped_dW, 235 | self.wmin, self.wmax) 236 | self.reset_trace() # スパイク列とスパイクトレースをリセット 237 | 238 | return s_exc 239 | 240 | if __name__ == '__main__': 241 | # 350ms画像入力、150ms入力なしでリセットさせる(膜電位の閾値以外) 242 | dt = 1e-3 # タイムステップ(sec) 243 | t_inj = 0.350 # 刺激入力時間(sec) 244 | t_blank = 0.150 # ブランク時間(sec) 245 | nt_inj = round(t_inj/dt) 246 | nt_blank = round(t_blank/dt) 247 | 248 | n_neurons = 100 #興奮性/抑制性ニューロンの数 249 | n_labels = 10 #ラベル数 250 | n_epoch = 30 #エポック数 251 | 252 | n_train = 10000 # 訓練データの数 253 | update_nt = nt_inj # STDP則による重みの更新間隔 254 | 255 | train, _ = chainer.datasets.get_mnist() # ChainerによるMNISTデータの読み込み 256 | labels = np.array([train[i][1] for i in range(n_train)]) # ラベルの配列 257 | 258 | # ネットワークの定義 259 | network = DiehlAndCook2015Network(n_in=784, n_neurons=n_neurons, 260 | wexc=2.25, winh=0.85, 261 | dt=dt, wmin=0.0, wmax=5e-2, 262 | lr=(1e-2, 1e-4), 263 | update_nt=update_nt) 264 | 265 | network.initialize_states() # ネットワークの初期化 266 | spikes = np.zeros((n_train, n_neurons)).astype(np.uint8) #スパイクを記録する変数 267 | accuracy_all = np.zeros(n_epoch) # 訓練精度を記録する変数 268 | blank_input = np.zeros(784) # ブランク入力 269 | init_max_fr = 32 # 初期のポアソンスパイクの最大発火率 270 | 271 | results_save_dir = "./LIF_WTA_STDP_MNIST_results/" # 結果を保存するディレクトリ 272 | os.makedirs(results_save_dir, exist_ok=True) # ディレクトリが無ければ作成 273 | 274 | ################# 275 | ## Simulation ## 276 | ################# 277 | for epoch in range(n_epoch): 278 | for i in tqdm(range(n_train)): 279 | max_fr = init_max_fr 280 | while(True): 281 | # 入力スパイクをオンラインで生成 282 | input_spikes = online_load_and_encoding_dataset(train, i, dt, 283 | nt_inj, max_fr) 284 | spike_list = [] # サンプルごとにスパイクを記録するリスト 285 | # 画像刺激の入力 286 | for t in range(nt_inj): 287 | s_exc = network(input_spikes[t], stdp=True) 288 | spike_list.append(s_exc) 289 | 290 | spikes[i] = np.sum(np.array(spike_list), axis=0) # スパイク数を記録 291 | 292 | # ブランク刺激の入力 293 | for _ in range(nt_blank): 294 | _ = network(blank_input, stdp=False) 295 | 296 | num_spikes_exc = np.sum(np.array(spike_list)) # スパイク数を計算 297 | if num_spikes_exc >= 5: # スパイク数が5より大きければ次のサンプルへ 298 | break 299 | else: # スパイク数が5より小さければ入力発火率を上げて再度刺激 300 | max_fr += 16 301 | 302 | # ニューロンを各ラベルに割り当てる 303 | if epoch == 0: 304 | assignments, proportions, rates = assign_labels(spikes, labels, 305 | n_labels) 306 | else: 307 | assignments, proportions, rates = assign_labels(spikes, labels, 308 | n_labels, rates) 309 | print("Assignments:\n", assignments) 310 | 311 | # スパイク数の確認(正常に発火しているか確認) 312 | sum_nspikes = np.sum(spikes, axis=1) 313 | mean_nspikes = np.mean(sum_nspikes).astype(np.float16) 314 | print("Ave. spikes:", mean_nspikes) 315 | print("Min. spikes:", sum_nspikes.min()) 316 | print("Max. spikes:", sum_nspikes.max()) 317 | 318 | # 入力サンプルのラベルを予測する 319 | predicted_labels = prediction(spikes, assignments, n_labels) 320 | 321 | # 訓練精度を計算 322 | accuracy = np.mean(np.where(labels==predicted_labels, 1, 0)).astype(np.float16) 323 | print("epoch :", epoch, " accuracy :", accuracy) 324 | accuracy_all[epoch] = accuracy 325 | 326 | # 学習率の減衰 327 | network.lr_p *= 0.75 328 | network.lr_m *= 0.75 329 | 330 | # 重みの保存(エポック毎) 331 | np.save(results_save_dir+"weight_epoch"+str(epoch)+".npy", 332 | network.input_conn.W) 333 | 334 | ################# 335 | ###  Results ### 336 | ################# 337 | plt.figure(figsize=(5,4)) 338 | plt.plot(np.arange(1, n_epoch+1), accuracy_all*100, 339 | color="k") 340 | plt.xlabel("Epoch") 341 | plt.ylabel("Train accuracy (%)") 342 | plt.savefig(results_save_dir+"accuracy.svg") 343 | #plt.show() 344 | 345 | # パラメータの保存 346 | np.save(results_save_dir+"assignments.npy", assignments) 347 | np.save(results_save_dir+"weight.npy", network.input_conn.W) 348 | np.save(results_save_dir+"exc_neurons_theta.npy", 349 | network.exc_neurons.theta) 350 | -------------------------------------------------------------------------------- /TrainingSNN/LIF_WTA_STDP_MNIST_evaluation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from tqdm import tqdm 5 | 6 | import chainer 7 | from LIF_WTA_STDP_MNIST import online_load_and_encoding_dataset, prediction 8 | from LIF_WTA_STDP_MNIST import DiehlAndCook2015Network 9 | 10 | ################# 11 | #### Main #### 12 | ################# 13 | # 350ms画像入力、150ms入力なしでリセットさせる(膜電位の閾値以外) 14 | dt = 1e-3 # タイムステップ(sec) 15 | t_inj = 0.350 # 刺激入力時間(sec) 16 | t_blank = 0.150 # ブランク時間(sec) 17 | nt_inj = round(t_inj/dt) 18 | nt_blank = round(t_blank/dt) 19 | 20 | n_neurons = 100 #興奮性/抑制性ニューロンの数 21 | n_labels = 10 #ラベル数 22 | 23 | n_train = 1000 # 訓練データの数 24 | update_nt = nt_inj # STDP則による重みの更新間隔 25 | 26 | _, test = chainer.datasets.get_mnist() # ChainerによるMNISTデータの読み込み 27 | labels = np.array([test[i][1] for i in range(n_train)]) # ラベルの配列 28 | 29 | # ネットワークの定義 30 | results_save_dir = "./LIF_WTA_STDP_MNIST_results/" # 結果が保存されているディレクトリ 31 | 32 | network = DiehlAndCook2015Network(n_in=784, n_neurons=n_neurons, 33 | wexc=2.25, winh=0.85, dt=dt) 34 | network.initialize_states() # ネットワークの初期化 35 | network.input_conn.W = np.load(results_save_dir+"weight.npy") 36 | network.exc_neurons.theta = np.load(results_save_dir+"exc_neurons_theta.npy") 37 | network.exc_neurons.theta_plus = 0 38 | 39 | spikes = np.zeros((n_train, n_neurons)).astype(np.uint8) #スパイクを記録する変数 40 | blank_input = np.zeros(784) # ブランク入力 41 | init_max_fr = 32 # 初期のポアソンスパイクの最大発火率 42 | 43 | ################# 44 | ## Simulation ## 45 | ################# 46 | for i in tqdm(range(n_train)): 47 | max_fr = init_max_fr 48 | while(True): 49 | # 入力スパイクをオンラインで生成 50 | input_spikes = online_load_and_encoding_dataset(test, i, dt, 51 | nt_inj, max_fr) 52 | spike_list = [] # サンプルごとにスパイクを記録するリスト 53 | # 画像刺激の入力 54 | for t in range(nt_inj): 55 | s_exc = network(input_spikes[t], stdp=False) 56 | spike_list.append(s_exc) 57 | 58 | spikes[i] = np.sum(np.array(spike_list), axis=0) # スパイク数を記録 59 | 60 | # ブランク刺激の入力 61 | for _ in range(nt_blank): 62 | _ = network(blank_input, stdp=False) 63 | 64 | num_spikes_exc = np.sum(np.array(spike_list)) # スパイク数を計算 65 | if num_spikes_exc >= 5: # スパイク数が5より大きければ次のサンプルへ 66 | break 67 | else: # スパイク数が5より小さければ入力発火率を上げて再度刺激 68 | max_fr += 16 69 | 70 | 71 | # 入力サンプルのラベルを予測する 72 | assignments = np.load(results_save_dir+"assignments.npy") 73 | predicted_labels = prediction(spikes, assignments, n_labels) 74 | 75 | # 訓練精度を計算 76 | accuracy = np.mean(np.where(labels==predicted_labels, 1, 0)).astype(np.float16) 77 | print("Test accuracy :", accuracy) 78 | -------------------------------------------------------------------------------- /TrainingSNN/LIF_WTA_STDP_MNIST_visualize_weights.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | epoch = 29 8 | n_neurons = 100 9 | 10 | results_save_dir = "./LIF_WTA_STDP_MNIST_results/" # 結果が保存されているディレクトリ 11 | 12 | input_conn_W = np.load(results_save_dir+"weight_epoch"+str(epoch)+".npy") 13 | reshaped_W = np.reshape(input_conn_W, (n_neurons, 28, 28)) 14 | 15 | # 描画 16 | fig = plt.figure(figsize=(6,6)) 17 | fig.subplots_adjust(left=0, right=1, bottom=0, top=1, 18 | hspace=0.05, wspace=0.05) 19 | row = col = np.sqrt(n_neurons) 20 | for i in tqdm(range(n_neurons)): 21 | ax = fig.add_subplot(row, col, i+1, xticks=[], yticks=[]) 22 | ax.imshow(reshaped_W[i], cmap="gray") 23 | plt.savefig("weights_"+str(epoch)+".png") 24 | plt.show() -------------------------------------------------------------------------------- /TrainingSNN/LIF_random_network.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from tqdm import tqdm 4 | 5 | from Models.Neurons import CurrentBasedLIF 6 | from Models.Synapses import DoubleExponentialSynapse #, SingleExponentialSynapse 7 | 8 | np.random.seed(seed=0) 9 | 10 | dt = 1e-4; T = 1; nt = round(T/dt) # シミュレーション時間 11 | num_in = 10; num_out = 1 # 入力 / 出力ニューロンの数 12 | 13 | # 入力のポアソンスパイク 14 | fr_in = 30 # 入力のポアソンスパイクの発火率(Hz) 15 | x = np.where(np.random.rand(nt, num_in) < fr_in * dt, 1, 0) 16 | W = 0.2*np.random.randn(num_out, num_in) # ランダムな結合重み 17 | 18 | # モデル 19 | neurons = CurrentBasedLIF(N=num_out, dt=dt, tref=5e-3, 20 | tc_m=1e-2, vrest=-65, vreset=-60, 21 | vthr=-40, vpeak=30) 22 | synapses = DoubleExponentialSynapse(N=num_out, dt=dt, td=1e-2, tr=1e-2) 23 | #synapses = SingleExponentialSynapse(N=num_out, dt=dt, td=1e-2) 24 | 25 | # 記録用配列 26 | current = np.zeros((num_out, nt)) 27 | voltage = np.zeros((num_out, nt)) 28 | 29 | # シミュレーション 30 | neurons.initialize_states() # 状態の初期化 31 | for t in tqdm(range(nt)): 32 | # 更新 33 | I = synapses(np.dot(W, x[t])) 34 | s = neurons(I) 35 | 36 | # 記録 37 | current[:, t] = I 38 | voltage[:, t] = neurons.v_ 39 | 40 | # 結果表示 41 | t = np.arange(nt)*dt 42 | plt.figure(figsize=(7, 6)) 43 | plt.subplot(3,1,1) 44 | plt.plot(t, voltage[0], color="k") 45 | plt.xlim(0, T) 46 | plt.ylabel('Membrane potential (mV)') 47 | plt.tight_layout() 48 | 49 | plt.subplot(3,1,2) 50 | plt.plot(t, current[0], color="k") 51 | plt.xlim(0, T) 52 | plt.ylabel('Synaptic current (pA)') 53 | plt.tight_layout() 54 | 55 | plt.subplot(3,1,3) 56 | for i in range(num_in): 57 | plt.plot(t, x[:, i]*(i+1), 'ko', markersize=2, 58 | rasterized=True) 59 | plt.xlabel('Time (s)') 60 | plt.ylabel('Neuron index') 61 | plt.xlim(0, T) 62 | plt.ylim(0.5, num_in+0.5) 63 | plt.tight_layout() 64 | plt.savefig('LIF_random_network.pdf') 65 | plt.show() -------------------------------------------------------------------------------- /TrainingSNN/Models/Connections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | class FullConnection: 6 | def __init__(self, N_in, N_out, initW=None): 7 | """ 8 | FullConnection 9 | """ 10 | if initW is not None: 11 | self.W = initW 12 | else: 13 | self.W = 0.1*np.random.rand(N_out, N_in) 14 | 15 | def backward(self, x): 16 | return np.dot(self.W.T, x) #self.W.T @ x 17 | 18 | def __call__(self, x): 19 | return np.dot(self.W, x) #self.W @ x 20 | 21 | 22 | class DelayConnection: 23 | def __init__(self, N, delay, dt=1e-4): 24 | """ 25 | Args: 26 | delay (float): Delay time 27 | """ 28 | self.N = N 29 | self.nt_delay = round(delay/dt) # 遅延のステップ数 30 | self.state = np.zeros((N, self.nt_delay)) 31 | 32 | def initialize_states(self): 33 | self.state = np.zeros((self.N, self.nt_delay)) 34 | 35 | def __call__(self, x): 36 | out = self.state[:, -1] # 出力 37 | 38 | self.state[:, 1:] = self.state[:, :-1] # 配列をずらす 39 | self.state[:, 0] = x # 入力 40 | 41 | return out -------------------------------------------------------------------------------- /TrainingSNN/Models/Neurons.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | class CurrentBasedLIF: 6 | def __init__(self, N, dt=1e-4, tref=5e-3, tc_m=1e-2, 7 | vrest=-60, vreset=-60, vthr=-50, vpeak=20): 8 | """ 9 | Current-based Leaky integrate-and-fire model. 10 | 11 | Args: 12 | N (int) : Number of neurons. 13 | dt (float) : Simulation time step in seconds. 14 | tc_m (float) : Membrane time constant in seconds. 15 | tref (float) : Refractory time constant in seconds. 16 | vreset (float): Reset membrane potential (mV). 17 | vrest (float) : Resting membrane potential (mV). 18 | vthr (float) : Threshold membrane potential (mV). 19 | vpeak (float) : Peak membrane potential (mV). 20 | """ 21 | self.N = N 22 | self.dt = dt 23 | self.tref = tref 24 | self.tc_m = tc_m 25 | self.vrest = vrest 26 | self.vreset = vreset 27 | self.vthr = vthr 28 | self.vpeak = vpeak 29 | 30 | self.v = self.vreset*np.ones(N) 31 | self.v_ = None 32 | self.tlast = 0 33 | self.tcount = 0 34 | 35 | def initialize_states(self, random_state=False): 36 | if random_state: 37 | self.v = self.vreset + np.random.rand(self.N)*(self.vthr-self.vreset) 38 | else: 39 | self.v = self.vreset*np.ones(self.N) 40 | self.tlast = 0 41 | self.tcount = 0 42 | 43 | def __call__(self, I): 44 | dv = (self.vrest - self.v + I) / self.tc_m 45 | v = self.v + ((self.dt*self.tcount) > (self.tlast + self.tref))*dv*self.dt 46 | 47 | s = 1*(v>=self.vthr) #発火時は1, その他は0の出力 48 | 49 | self.tlast = self.tlast*(1-s) + self.dt*self.tcount*s #最後の発火時の更新 50 | v = v*(1-s) + self.vpeak*s #閾値を超えると膜電位をvpeakにする 51 | self.v_ = v #発火時の電位も含めて記録するための変数 52 | self.v = v*(1-s) + self.vreset*s #発火時に膜電位をリセット 53 | self.tcount += 1 54 | 55 | return s 56 | 57 | class ConductanceBasedLIF: 58 | def __init__(self, N, dt=1e-4, tref=5e-3, tc_m=1e-2, 59 | vrest=-60, vreset=-60, vthr=-50, vpeak=20, 60 | e_exc=0, e_inh=-100): 61 | """ 62 | Conductance-based Leaky integrate-and-fire model. 63 | 64 | Args: 65 | N (int) : Number of neurons. 66 | dt (float) : Simulation time step in seconds. 67 | tc_m (float) : Membrane time constant in seconds. 68 | tref (float) : Refractory time constant in seconds. 69 | vreset (float): Reset membrane potential (mV). 70 | vrest (float) : Resting membrane potential (mV). 71 | vthr (float) : Threshold membrane potential (mV). 72 | vpeak (float) : Peak membrane potential (mV). 73 | e_exc (float) : equilibrium potential of excitatory synapses (mV). 74 | e_inh (float) : equilibrium potential of inhibitory synapses (mV). 75 | """ 76 | self.N = N 77 | self.dt = dt 78 | self.tref = tref 79 | self.tc_m = tc_m 80 | self.vrest = vrest 81 | self.vreset = vreset 82 | self.vthr = vthr 83 | self.vpeak = vpeak 84 | 85 | self.e_exc = e_exc # 興奮性シナプスの平衡電位 86 | self.e_inh = e_inh # 抑制性シナプスの平衡電位 87 | 88 | self.v = self.vreset*np.ones(N) 89 | self.v_ = None 90 | self.tlast = 0 91 | self.tcount = 0 92 | 93 | def initialize_states(self, random_state=False): 94 | if random_state: 95 | self.v = self.vreset + np.random.rand(self.N)*(self.vthr-self.vreset) 96 | else: 97 | self.v = self.vreset*np.ones(self.N) 98 | self.tlast = 0 99 | self.tcount = 0 100 | 101 | def __call__(self, g_exc, g_inh): 102 | I_synExc = g_exc*(self.e_exc - self.v) 103 | I_synInh = g_inh*(self.e_inh - self.v) 104 | dv = (self.vrest - self.v + I_synExc + I_synInh) / self.tc_m #Voltage equation with refractory period 105 | v = self.v + ((self.dt*self.tcount) > (self.tlast + self.tref))*dv*self.dt 106 | 107 | s = 1*(v>=self.vthr) #発火時は1, その他は0の出力 108 | self.tlast = self.tlast*(1-s) + self.dt*self.tcount*s #最後の発火時の更新 109 | v = v*(1-s) + self.vpeak*s #閾値を超えると膜電位をvpeakにする 110 | self.v_ = v #発火時の電位も含めて記録するための変数 111 | self.v = v*(1-s) + self.vreset*s #発火時に膜電位をリセット 112 | self.tcount += 1 113 | 114 | return s 115 | 116 | class DiehlAndCook2015LIF: 117 | def __init__(self, N, dt=1e-3, tref=5e-3, tc_m=1e-1, 118 | vrest=-65, vreset=-65, init_vthr=-52, vpeak=20, 119 | theta_plus=0.05, theta_max=35, tc_theta=1e4, e_exc=0, e_inh=-100): 120 | """ 121 | Leaky integrate-and-fire model of Diehl and Cooks (2015) 122 | https://www.frontiersin.org/articles/10.3389/fncom.2015.00099/full 123 | 124 | Args: 125 | N (int) : Number of neurons. 126 | dt (float) : Simulation time step in seconds. 127 | tc_m (float) : Membrane time constant in seconds. 128 | tref (float) : Refractory time constant in seconds. 129 | vreset (float): Reset membrane potential (mV). 130 | vrest (float) : Resting membrane potential (mV). 131 | vthr (float) : Threshold membrane potential (mV). 132 | vpeak (float) : Peak membrane potential (mV). 133 | e_exc (float) : equilibrium potential of excitatory synapses (mV). 134 | e_inh (float) : equilibrium potential of inhibitory synapses (mV). 135 | """ 136 | self.N = N 137 | self.dt = dt 138 | self.tref = tref 139 | self.tc_m = tc_m 140 | self.vreset = vreset 141 | self.vrest = vrest 142 | self.init_vthr = init_vthr 143 | self.theta = np.zeros(N) 144 | self.theta_plus = theta_plus 145 | self.theta_max = theta_max 146 | self.tc_theta = tc_theta 147 | self.vpeak = vpeak 148 | 149 | self.e_exc = e_exc # 興奮性シナプスの平衡電位 150 | self.e_inh = e_inh # 抑制性シナプスの平衡電位 151 | 152 | self.v = self.vreset*np.ones(N) 153 | self.vthr = self.init_vthr 154 | self.v_ = None 155 | self.tlast = 0 156 | self.tcount = 0 157 | 158 | def initialize_states(self, random_state=False): 159 | if random_state: 160 | self.v = self.vreset + np.random.rand(self.N)*(self.vthr-self.vreset) 161 | else: 162 | self.v = self.vreset*np.ones(self.N) 163 | self.vthr = self.init_vthr 164 | self.theta = np.zeros(self.N) 165 | self.tlast = 0 166 | self.tcount = 0 167 | 168 | def __call__(self, g_exc, g_inh): 169 | I_synExc = g_exc*(self.e_exc - self.v) 170 | I_synInh = g_inh*(self.e_inh - self.v) 171 | dv = (self.vrest - self.v + I_synExc + I_synInh) / self.tc_m #Voltage equation with refractory period 172 | v = self.v + ((self.dt*self.tcount) > (self.tlast + self.tref))*dv*self.dt 173 | 174 | s = 1*(v>=self.vthr) #発火時は1, その他は0の出力 175 | theta = (1-self.dt/self.tc_theta)*self.theta + self.theta_plus*s 176 | self.theta = np.clip(theta, 0, self.theta_max) 177 | self.vthr = self.theta + self.init_vthr 178 | self.tlast = self.tlast*(1-s) + self.dt*self.tcount*s #最後の発火時の更新 179 | v = v*(1-s) + self.vpeak*s #閾値を超えると膜電位をvpeakにする 180 | self.v_ = v #発火時の電位も含めて記録するための変数 181 | self.v = v*(1-s) + self.vreset*s #発火時に膜電位をリセット 182 | self.tcount += 1 183 | 184 | return s 185 | 186 | class IzhikevichNeuron: 187 | def __init__(self, N, dt=0.5, C=250, a=0.01, b=-2, 188 | k=2.5, d=200, vrest=-60, vreset=-65, vthr=-20, vpeak=30): 189 | """ 190 | Izhikevich model. 191 | 192 | Args: 193 | N (int) : Number of neurons. 194 | dt (float) : Simulation time step in milliseconds. 195 | C (float) : Membrane capacitance (pF). 196 | a (float) : Adaptation reciprocal time constant (ms^-1). 197 | b (float) : Resonance parameter (pA mV^-1). 198 | d (float) : Adaptation jump current (pA). 199 | k (float) : Gain on v (pA mV^-1). 200 | vreset (float): Reset membrane potential (mV). 201 | vrest (float) : Resting membrane potential (mV). 202 | vthr (float) : Threshold membrane potential (mV). 203 | vpeak (float) : Peak membrane potential (mV). 204 | """ 205 | self.N = N 206 | self.dt = dt 207 | self.C = C 208 | self.a = a 209 | self.b = b 210 | self.d = d 211 | self.k = k 212 | self.vrest = vrest 213 | self.vreset = vreset 214 | self.vthr = vthr 215 | self.vpeak = vpeak 216 | 217 | self.u = np.zeros(N) 218 | self.v = self.vrest*np.ones(N) 219 | self.v_ = self.v 220 | 221 | def initialize_states(self, random_state=False): 222 | if random_state: 223 | self.v = self.vreset + np.random.rand(self.N)*(self.vthr-self.vreset) 224 | else: 225 | self.v = self.vrest*np.ones(self.N) 226 | self.u = np.zeros(self.N) 227 | 228 | def __call__(self, I): 229 | dv = (self.k*(self.v-self.vrest)*(self.v-self.vthr)-self.u+I) / self.C 230 | v = self.v + self.dt*dv 231 | u = self.u + self.dt*(self.a*(self.b*(self.v_-self.vrest)-self.u)) 232 | 233 | s = 1*(v>=self.vpeak) #発火時は1, その他は0の出力 234 | 235 | self.u = u + self.d*s 236 | self.v = v*(1-s) + self.vreset*s 237 | self.v_ = self.v 238 | 239 | return s -------------------------------------------------------------------------------- /TrainingSNN/Models/Synapses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | class SingleExponentialSynapse: 6 | def __init__(self, N, dt=1e-4, td=5e-3): 7 | """ 8 | Args: 9 | td (float):Synaptic decay time 10 | """ 11 | self.N = N 12 | self.dt = dt 13 | self.td = td 14 | self.r = np.zeros(N) 15 | 16 | def initialize_states(self): 17 | self.r = np.zeros(self.N) 18 | 19 | def __call__(self, spike): 20 | r = self.r*(1-self.dt/self.td) + spike/self.td 21 | self.r = r 22 | return r 23 | 24 | 25 | class DoubleExponentialSynapse: 26 | def __init__(self, N, dt=1e-4, td=1e-2, tr=5e-3): 27 | """ 28 | Args: 29 | td (float):Synaptic decay time 30 | tr (float):Synaptic rise time 31 | """ 32 | self.N = N 33 | self.dt = dt 34 | self.td = td 35 | self.tr = tr 36 | self.r = np.zeros(N) 37 | self.hr = np.zeros(N) 38 | 39 | def initialize_states(self): 40 | self.r = np.zeros(self.N) 41 | self.hr = np.zeros(self.N) 42 | 43 | def __call__(self, spike): 44 | r = self.r*(1-self.dt/self.tr) + self.hr*self.dt 45 | hr = self.hr*(1-self.dt/self.td) + spike/(self.tr*self.td) 46 | 47 | self.r = r 48 | self.hr = hr 49 | 50 | return r 51 | 52 | -------------------------------------------------------------------------------- /TrainingSNN/Models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /TrainingSNN/example_using_delay_connection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from tqdm import tqdm 6 | 7 | from Models.Neurons import CurrentBasedLIF 8 | from Models.Connections import DelayConnection 9 | 10 | dt = 1e-4; T = 5e-2 11 | nt = round(T/dt) 12 | 13 | neuron1 = CurrentBasedLIF(N=1, dt=dt, tc_m=1e-2, tref=0, 14 | vrest=0, vreset=0, vthr=1, vpeak=1) 15 | neuron2 = CurrentBasedLIF(N=1, dt=dt, tc_m=1e-1, tref=0, 16 | vrest=0, vreset=0, vthr=1, vpeak=1) 17 | delay_connect = DelayConnection(N=1, delay=2e-3, dt=dt) 18 | 19 | I = 2 # 入力電流 20 | v_arr1 = np.zeros(nt) 21 | v_arr2 = np.zeros(nt) 22 | 23 | # シミュレーション 24 | for t in tqdm(range(nt)): 25 | # 更新 26 | s1 = neuron1(I) 27 | d1 = delay_connect(s1) 28 | s2 = neuron2(0.02/dt*d1) 29 | 30 | # 保存 31 | v_arr1[t] = neuron1.v_ 32 | v_arr2[t] = neuron2.v_ 33 | 34 | time = np.arange(nt)*dt*1e3 35 | plt.figure(figsize=(5, 4)) 36 | plt.plot(time, v_arr1, label="Neuron1", color="k", 37 | linestyle="dashed") 38 | plt.plot(time, v_arr2, color="k", label="Neuron2") 39 | plt.xlabel("Time (ms)"); plt.ylabel("v"); 40 | plt.legend(loc="upper left"); plt.tight_layout(); 41 | plt.savefig('delay.pdf') 42 | plt.show() 43 | 44 | -------------------------------------------------------------------------------- /notebook/Hodgkin-Huxley-FIcurve.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Hodgkin-HuxleyモデルのFI curveをplotする\n", 8 | "ライブラリをimportします" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 28, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import numpy as np\n", 18 | "from tqdm.notebook import tqdm\n", 19 | "import matplotlib.pyplot as plt\n", 20 | "import seaborn as sns\n", 21 | "sns.set_style('whitegrid')" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "次にモデルを定義します。" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 4, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "class HodgkinHuxleyModel:\n", 38 | " def __init__(self, dt=1e-3, solver=\"RK4\"):\n", 39 | " self.C_m = 1.0 # 膜容量 (uF/cm^2)\n", 40 | " self.g_Na = 120.0 # Na+の最大コンダクタンス (mS/cm^2)\n", 41 | " self.g_K = 36.0 # K+の最大コンダクタンス (mS/cm^2)\n", 42 | " self.g_L = 0.3 # 漏れイオンの最大コンダクタンス (mS/cm^2)\n", 43 | " self.E_Na = 50.0 # Na+の平衡電位 (mV)\n", 44 | " self.E_K = -77.0 # K+の平衡電位 (mV)\n", 45 | " self.E_L = -54.387 #漏れイオンの平衡電位 (mV)\n", 46 | " \n", 47 | " self.solver = solver\n", 48 | " self.dt = dt\n", 49 | " \n", 50 | " # V, m, h, n\n", 51 | " self.states = np.array([-65, 0.05, 0.6, 0.32])\n", 52 | " self.I_inj = None\n", 53 | " \n", 54 | " def Solvers(self, func, x, dt):\n", 55 | " # 4th order Runge-Kutta法\n", 56 | " if self.solver == \"RK4\":\n", 57 | " k1 = dt*func(x)\n", 58 | " k2 = dt*func(x + 0.5*k1)\n", 59 | " k3 = dt*func(x + 0.5*k2)\n", 60 | " k4 = dt*func(x + k3)\n", 61 | " return x + (k1 + 2*k2 + 2*k3 + k4) / 6\n", 62 | " \n", 63 | " # 陽的Euler法\n", 64 | " elif self.solver == \"Euler\": \n", 65 | " return x + dt*func(x)\n", 66 | " else:\n", 67 | " return None\n", 68 | " \n", 69 | " # イオンチャネルのゲートについての6つの関数\n", 70 | " def alpha_m(self, V):\n", 71 | " return 0.1*(V+40.0)/(1.0 - np.exp(-(V+40.0) / 10.0))\n", 72 | "\n", 73 | " def beta_m(self, V):\n", 74 | " return 4.0*np.exp(-(V+65.0) / 18.0)\n", 75 | "\n", 76 | " def alpha_h(self, V):\n", 77 | " return 0.07*np.exp(-(V+65.0) / 20.0)\n", 78 | "\n", 79 | " def beta_h(self, V):\n", 80 | " return 1.0/(1.0 + np.exp(-(V+35.0) / 10.0))\n", 81 | "\n", 82 | " def alpha_n(self, V):\n", 83 | " return 0.01*(V+55.0)/(1.0 - np.exp(-(V+55.0) / 10.0))\n", 84 | "\n", 85 | " def beta_n(self, V):\n", 86 | " return 0.125*np.exp(-(V+65) / 80.0)\n", 87 | "\n", 88 | " # Na+電流 (uA/cm^2)\n", 89 | " def I_Na(self, V, m, h):\n", 90 | " return self.g_Na * m**3 * h * (V - self.E_Na)\n", 91 | " \n", 92 | " # K+電流 (uA/cm^2)\n", 93 | " def I_K(self, V, n):\n", 94 | " return self.g_K * n**4 * (V - self.E_K)\n", 95 | "\n", 96 | " # 漏れ電流 (uA/cm^2)\n", 97 | " def I_L(self, V):\n", 98 | " return self.g_L * (V - self.E_L)\n", 99 | " \n", 100 | " # 微分方程式\n", 101 | " def dALLdt(self, states):\n", 102 | " V, m, h, n = states\n", 103 | " \n", 104 | " dVdt = (self.I_inj - self.I_Na(V, m, h) \\\n", 105 | " - self.I_K(V, n) - self.I_L(V)) / self.C_m\n", 106 | " dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m\n", 107 | " dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h\n", 108 | " dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n\n", 109 | " return np.array([dVdt, dmdt, dhdt, dndt])\n", 110 | " \n", 111 | " def __call__(self, I):\n", 112 | " self.I_inj = I\n", 113 | " states = self.Solvers(self.dALLdt, self.states, self.dt)\n", 114 | " self.states = states\n", 115 | " return states" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "シミュレーションのための定数と入力電流の大きさを定義します。入力電流は0~30uA/cm$^2$の間から50回サンプリングします。" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 8, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "dt = 0.01; T = 500 # (ms)\n", 132 | "nt = round(T/dt) # ステップ数\n", 133 | "\n", 134 | "N = 50\n", 135 | "I_inj = np.sort(30*np.random.rand(N)) # 刺激電流 (uA/cm^2)\n", 136 | "firing_rate = np.zeros(N) # 発火率の記録用配列" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "入力電流を確認しておきます。" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 9, 149 | "metadata": {}, 150 | "outputs": [ 151 | { 152 | "data": { 153 | "text/plain": [ 154 | "array([ 0.25898393, 0.58540839, 0.63372405, 0.922206 , 0.92693746,\n", 155 | " 4.11140144, 4.37448833, 5.85445395, 6.29928263, 6.33229761,\n", 156 | " 6.37900989, 7.65039074, 8.6641491 , 8.98230984, 10.46369835,\n", 157 | " 10.92631924, 10.99678457, 11.3542475 , 11.71461959, 11.78827167,\n", 158 | " 12.23116329, 12.57366304, 14.37297872, 15.08918803, 15.34638366,\n", 159 | " 15.52960925, 15.63810896, 16.2993015 , 16.5329323 , 17.38720276,\n", 160 | " 18.25247975, 18.9745069 , 19.76695294, 20.10668766, 20.56184969,\n", 161 | " 21.16658323, 21.54429203, 21.74503282, 21.97272258, 22.21142224,\n", 162 | " 22.34060333, 22.82560621, 22.88899313, 24.24171949, 25.13223366,\n", 163 | " 27.01472273, 27.26022086, 27.95931478, 28.16317976, 29.78231273])" 164 | ] 165 | }, 166 | "execution_count": 9, 167 | "metadata": {}, 168 | "output_type": "execute_result" 169 | } 170 | ], 171 | "source": [ 172 | "I_inj" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "シミュレーションを実行します。" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 12, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "name": "stderr", 189 | "output_type": "stream", 190 | "text": [ 191 | "C:\\Users\\user\\miniconda3\\lib\\site-packages\\ipykernel_launcher.py:1: TqdmDeprecationWarning: This function will be removed in tqdm==5.0.0\n", 192 | "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`\n", 193 | " \"\"\"Entry point for launching an IPython kernel.\n" 194 | ] 195 | }, 196 | { 197 | "data": { 198 | "application/vnd.jupyter.widget-view+json": { 199 | "model_id": "8d5029384f904f748cbc3b51f746c0bb", 200 | "version_major": 2, 201 | "version_minor": 0 202 | }, 203 | "text/plain": [ 204 | "HBox(children=(FloatProgress(value=0.0, max=50.0), HTML(value='')))" 205 | ] 206 | }, 207 | "metadata": {}, 208 | "output_type": "display_data" 209 | }, 210 | { 211 | "name": "stdout", 212 | "output_type": "stream", 213 | "text": [ 214 | "\n" 215 | ] 216 | } 217 | ], 218 | "source": [ 219 | "for j in tqdm_notebook(range(N)):\n", 220 | " HH_neuron = HodgkinHuxleyModel(dt=dt, solver=\"Euler\")\n", 221 | " V_arr = np.zeros((nt, 4)) # 記録用配列\n", 222 | " \n", 223 | " # シミュレーション\n", 224 | " for i in range(nt):\n", 225 | " X = HH_neuron(I_inj[j])\n", 226 | " V_arr[i] = X[0]\n", 227 | " \n", 228 | " spike = np.bitwise_and(V_arr[:-1]<0, V_arr[1:]>0)\n", 229 | " rate = np.sum(spike) / T*1e3\n", 230 | " \n", 231 | " firing_rate[j] = rate" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "metadata": {}, 237 | "source": [ 238 | "結果をplotします。" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 34, 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "data": { 248 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaQAAAFcCAYAAACHjcpBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nOzdeXhUZZr38e+pJZU9JCEhEGLYCRIgNIigQBodAhKibK0Igq2DYLfAaNs4Cmlp+1VEhx4uUWx7RtzQURDZQVBQsVkULBSIRPYAgZB9qyS1n/ePSEkkgUpIUpXU/bkurqaqTlXdT8rOj+ecu55HUVVVRQghhPAwjacLEEIIIUACSQghhJeQQBJCCOEVJJCEEEJ4BQkkIYQQXkHn6QKag9PppKKiAr1ej6Ioni5HCCFaNVVVsdlsBAUFodG4P+/xiUCqqKjg+PHjni5DCCF8So8ePQgJCXH7eJ8IJL1eD1T/cPz8/DxcTdPKyMggMTHR02U0OV8ZJ/jOWGWcrYfVauX48eOu373u8olAunyazs/PD4PB4OFqmp4vjBF8Z5zgO2OVcbYu9b1EIk0NQgghvIIEkhBCCK8ggSSEEMIrSCAJIYTwChJIQgghvIIEkhBCCK/gFYH00ksv8fTTTwOwd+9e0tLSSElJYenSpa5jMjMzmTBhAqNGjWLBggXY7XZPlSuEEKIJeDyQ9u3bx7p16wAwm83Mnz+f119/na1bt5KRkcGuXbsAmDdvHs8++yzbt29HVVVWr17tybKFEEJc4cjJAha/u5/H//sr/vHJoQa9hkcDqaSkhKVLl/Loo48CcPjwYeLj44mLi0On05GWlsa2bdu4cOECZrOZpKQkACZMmMC2bds8WboQQoifHTlZwIpNGRSXmwkPNVBWaW3Q63g0kJ599lmeeOIJQkNDAcjLyyMqKsr1eHR0NLm5uVfdHxUVRW5ubrPXK4QQ4hdWm4NDx/NZvuYHLuSZOHqmGFOVjQBDwxYB8tjSQR9//DHt27dnyJAhrF27FqhelfvKpSZUVUVRlDrvr6+MjIwbL7wFMBqNni6hWfjKOMF3xirj9G6qqpJfZudUjplTOWay8qzYHSoAAX4KbUN0OCyVlJdZgIh6v77HAmnr1q3k5+dzzz33UFpaSmVlJRcuXECr1bqOyc/PJzo6mpiYGPLz8133FxQUEB0dXe/3TExMbPVrSBmNRgYMGODpMpqcr4wTfGesMk7vVFZh5dDxfL4/nsf3x/IoKDUDEBsVzF23xdK/RxSffZNFeZWNQP/qxVQDGriGtccC6e2333b9fe3atezfv5/nnnuOlJQUzp49S8eOHdm8eTMTJ04kNjYWg8Hg+iA3bNjA8OHDPVW6EEK0WEdOFrBlz2kuFVbib9CioFBlsRMTGUjq7V3o1TmCn7KK+P54PgeP5XEquwRVhaAAPUndo5jcM5r+PaKIjgh0vaa/n44Vm6rPQAUYdFRZHED9VvoGL1vt22AwsHjxYubMmYPFYiE5OZnRo0cDsGTJEtLT0zGZTPTu3Zvp06d7uFohhGhZLjcf+Ptp0WrhVHYpqqrSoW0Qpy6U8vzb32J3OLHanGg0Cj1vCuf+lAR+0zOKbnHhaDW1Xyrp060t/56WWB10RZV0ahdY63HX4xWBNGHCBCZMmADAkCFD2Lhx41XHJCQksGbNmuYuTQghWo0te07jp9dQUm7lYoEJVFCBMznlAOh1GiJD/Xn47t707RZFUID7s5w+3drSp1tbACwWS4Ou2XtFIAkhhGh6WTllFJWZqbI40GgUtFoFhepQSugUjkGvpbjcwpA+HTxSnwSSEEK0cpVmGys/zeRCfgU6rYZucWEUlpix2R2gKOi1Gvz9dFSabcRENOx0W2OQQBJCiBbuWo0KXWPbsHVfFoWlVQxObE9uUQUGvZaocH/OXTIBKrFtg6g02zBbHaTe3sVj45BAEkKIFqy2RgWA2KggjpwqZM/hHKLDA3l59jASOkX8El5FlXTtGFYdXlY7bYMNpN7exXUdyBMkkIQQogVbv+skVpsDU6WNwtIq1OrvqXL6QhkoEB0eQNfYUBI6VX9R9crmA28jgSSEEC2EqqrkFlVy9EwRR88UcvRMIedzTQAoCijgWsVGo1Ho1TkCfz8teSVVHqzafRJIQgjhpRxOlXOXyjh6upAffw6hwp9XSgjy19GrcyQaRUHRQGRoAKcvlNZoVAgweL5RoT4kkIQQohll5Zr5/N39XCqsdK2OcPkUmtXm4MT5Eo6eKeTH04X8lFVEhbl677fIMH96d47k5i6R3Nw5gviYUDQaxXUNyWy1e2WjQn1IIAkhRDM5crKA7QdLiQgPITzUQGFpFctWf0/Pm8LJK67ixPkS7A4nAHHtQhiaFEvvLpHc3DmS6PCAWheV/vUqCd7WqFAfEkhCCNFMtuw5jQLkF1dRXmmjylI9+8ktrKRHfDhpw7rQu3MECZ0iCAt2fyFob25UqA8JJCGEaAbnc8s5dKIAU5UNjWIjONCPiNAgggL02OxOlsyVBaMlkIQQogmdzy3no8+P8a8fLqAoCqEBGrrERaLXVe+PWmm2EdUmwMNVegcJJCGEz7typYNfNxo09PltQgyuIDLotUz4bTd6xkfw9obvsdkd6LTVqym0pKaDpiaBJITwaVeudBAeaqDEZGHFpgz+PS3RrVD69fNziypZ9M5+KqpsGPy0TBzRnXHJXV3XhC6eP82JfD2XiiqJiah/+LVmEkhCCJ+2Zc9p/P20+PvpKCw1o6rV7ddvbcrgrts6X/f5n+49Q5XZjsPhJKegkqIyMxoFOkQF8dLsYVc1J3Rq58/EMS1nx9jmJIEkhPBplworCQ81UFRm5szFshqPvbr6h3q9lkZRiIkMpF1EIKYqW7065YQEkhDCx8VEBlJisvy87Tb07dYWs9VGaKCBP05Kuu7zX1/zA2WVFvwNenRaBa1G06JWR/AmGk8XIIQQnpR6exfMVkf1NR+9BofTicMJE0Z0Jyo84Lp/JozojsMJDocTjaK0uNURvInMkIQQrcLlTrfTF0qx2h0Y9DrCQw2uvYH8DVoqKm3kl1SBAp3bhzFlVAJ9urVlxG868vaWozidKpcKKxif3M3tRoNfr5QgjQoNJ4EkhGjxXOu5WWwUlVsAKDVZyCuuRKMotAn2IyvHjMMJBp0GRatw/Fwx//2hkdv6tMd4LA/VCW3bBBAdHsCXB7PpEtumXqEkAXTjJJCEEC3elj2ncTqdXMiv/NUjKqCSV2J23WOxO6F6xR4KSsxs/NcZ12OBBh2B/nrXa0rINC8JJCFEi3c+18TFAhOKAv5+WhSl+jSdqkKgv45Ks921X5AKBBiqf/XZ7U6cqkqnDqFoFIWQQD/4+fFLRb8ON9HUJJCEEC1aRVX1dSGnCiGBepxOFa1Wg8WqoCjVG9XpdRqcThVVVdFpNOh1GhxOFYOfFj+9BoNe65oZAVRZ7NIl5wEeD6RXXnmF7du3oygKkyZN4qGHHuKZZ57BaDQSEFC9vtPs2bMZOXIkmZmZLFiwgIqKCgYOHMhzzz2HTufxIQghbtCRkwWs/lchK3d95Vp6B6hzOZ/LDQw5BRUUlZkxW+3ERAbhp1PILzHjsDvRagAU7HYn7cIDyC2qxK6CTqtgdzhxOFQiwvwZM6QTXx7MBqpnRrKcj+d49Lf5/v37+eabb9i4cSN2u50xY8aQnJxMRkYG77//PtHR0TWOnzdvHs8//zxJSUnMnz+f1atXM2XKFA9VL4RoDJcbEmxmO20jgykqM7Ns9UEURaFNsIGwYD+Kysz878Yj/H7MzQC8s/Uo/not5ZU2SkxWosIDSL2tE5lZRVjtTqw2B4Zgwy9ddlY7PeLDq7vsSqu38+7WsY2ry65LbBvpkvMCHg2kQYMG8d5776HT6cjNzcXhcODv78/FixeZP38+ubm5jBw5ktmzZ5OTk4PZbCYpqfqLahMmTGDZsmUSSEK0cFv2nMag13CpwMG5gvwaj+UU1LyOs/B/v7nq+TGRgUSG+ZOZVcTTDw5qUA3SJecdPH6+S6/Xs2zZMt566y1Gjx6N3W5n8ODBLFy4kJCQEGbNmsWaNWvo3r07UVFRrudFRUWRm5vrwcqFEI3hUmEllRYbFWYn0eEB+Om15BZVoqoqMZFBruNUVXWtphBgqG5c0Os0RIb5V7+ONCG0eB4PJIC5c+fyyCOP8Oijj7Jv3z6WL1/uemzatGmsX7+erl271ti+V1XVWrfzvZaMjIxGq9mbGY1GT5fQLHxlnNC6x2qzVJFTYCU0QEubABWwo9eoqECgzu46zmJzEhOmBaDCbMPw835CZaVWLDYnQf7aFvNzail1NjePBtKpU6ewWq306tWLgIAAUlJS2Lp1K23atGHUqFFAdfDodDpiYmLIz/9lOl9QUHDVNabrSUxMxGBo3YsdGo1GBgxo/SsJ+8o4oeWN1Z29hS4fcz7XxIUiGzqthrAgDaFhoVRZ7ISFaqpnQP4GV6OBQ+PggbGJAKzYlIHeT3vVYy3htFtL+zwbwmKxNGgC4NG17LKzs0lPT8dqtWK1Wtm5cye33HILixYtorS0FJvNxqpVqxg5ciSxsbEYDAbXvyw2bNjA8OGy5a8Q3uRyg0KJyVJjb6EjJwuuOqao3ExhaRWqqhIe6odGo1BcbqFNsIE5v+vP7ElJtAk2uO67vD/R5aV6antMtGwenSElJydz+PBhxo0bh1arJSUlhdmzZxMeHs7999+P3W4nJSWFsWPHArBkyRLS09MxmUz07t2b6dOne7J8IcSvbNlzGlA5n2vCYq2+3qOqKn9b8Q3hIdXXeorLzdXfCQJsdiddO4bh76dFtVXx0uO/rfF6dYWMNCG0Th6/hjRnzhzmzJlT476pU6cyderUq45NSEhgzZo1zVWaEKIezBY7P54upNRkRatVCA0y/Lw0gorV7qRnfDgA32Xm4uevAUUhNFBPRKg/qqpyodh+7TcQrZ7HA0kI0fJ9m5HDP9cfocRkpU2Igc7tQ9H93HRQabbRJtjAk1Orr5ssfnc/JSbLVSsjhAfLryNfJ/8FCCHcakSo7bjb+8ay6/tsvv3xEvExIcwa14cd353Daneg1Sq1rnqQensXVmyqvuB95coIw3oGXfV+wrdIIAnh4y43Gfj7aWs0Ivy6UeDK48JC/DiZXcLeIznotBoeGnszdw/vik6rIb596DVXPahr/yBr6VlPDF94EQkkIXzc5ZUSLFYHF/MrUKne/XTJB9/R46Zw13HHzxVjszvRajWYf57VhATq6XFTGyaM6O46zp2Gg9qOMRolkHydBJIQPkxVVU5fKKW0wkql2Y5ep0FXvSopFpuNS4W/rH5gqrSh1WqwOxxotRq6xQW7Wq+FaAwSSEL4qB9PF7Ly00xyCivR6zR07hBKZJg/iqK4GhGuXBuutmaESrNNtmkQjUYCSYhW6HLzwekLpVjtDgx6HZ07hJJ6excCDDpWbsvk4E95hIcYSBvWhR9PF7g2ras022rdfqGuZgTZpkE0FgkkIVqZy80HdruD0gorKlBptsNFlRfe+ZaKKjshgXoeGnszY27vjL+f7pfuuWtsv1BXM4J8QVU0FgkkIVqZLXtO46fXkJNfgc3hQKMoOJwqFwsq0SgQFx3Mf80dTlDAL6fe3F35QFZIEE1JAkmIVubspXIKS81UWS6vfKAC1Tul9u4SianKViOMhPAWEkhCtBJ2h5M1X5zgQp4JrVYhKECHAmi1GhxOFb1Wg93hlCYE4bUkkITwYkdOFrD6X4Ws3PUV/gYtCgqFpVWYqqzY7CparULn9mHcMTCOrXvPcDK7lH7d21JqsqCqKvklZhx2J6gqbcP8pQlBeDUJJCG8lKs5weLAEACnsktxOJzVX1x1Vp+G81MVfjpbRMbpQoL89Tz94C3c3reDq0nBanditVV32cVGBUsTgvBqEkhCeCnXCgpVKhfyKgAVh1PFqYJGqb4yZLVVb+NQfX0onNv7dgCk+UC0TBJIQnipc5fKKTZZMFXarnrs5wkSAAGG6m29C8tkxQTRskkgCeFlLDYHH+84Tna+CUVRiA7TYbYp2BxOLFaHa4YEoFEUtFoNCkizgmjxJJCE8CLfH8vjH58cJqewgqTuURSbzGC30CYsgHOXTGg1Chp+uYak0ynY7U4iwvylWUG0eBJIQnjIlXsLRYQasNicHD5ZQPu2Qfy/WUNI6hHNkZMFvL/5IBYndO0YVt1lV1aFqbJml92UUQlyzUi0eBJIQnjA5Q46g776u0HGY/k4nSp3DIjjsd/1w09ffV2oT7e23DsskgEDBni4YiGangSSEB6wefcpLFY7F/LNmC3V+wq1iwjEYrO7wkgIXyOBJEQzcjhV/vV9Ngcy87DZnfgbtHSJDSMi1ADApaLK67yCEK2XBJIQzcDhcLLr+2xW7zjOhfwKAv11tIsIJCYyEEWpbpmTvYWEr5NAEqIJ2R1OvjKeZ/WOE+QUVtC5QyjPPHgLQQF63t78I1UWu+wtJMTPJJCEaAI2u5MvvjvPxzuPk1tUSZfYMBY8NIhBN8eg+flLRLK3kBA1eTyQXnnlFbZv346iKEyaNImHHnqIvXv38uKLL2KxWLjrrrt44oknAMjMzGTBggVUVFQwcOBAnnvuOXQ6jw9BCBeb3cnOA+f4eOdx8oqr6BbXhpnj+3BLr3auU3OXyfI+QtTk0d/m+/fv55tvvmHjxo3Y7XbGjBnDkCFDmD9/PitXrqR9+/bMmjWLXbt2kZyczLx583j++edJSkpi/vz5rF69milTpnhyCEIAYLM7+Hz/OT7eeYKCkip63hTOHyb2Y0BC9FVBJISonUcDadCgQbz33nvodDpyc3NxOByUlZURHx9PXFwcAGlpaWzbto1u3bphNptJSkoCYMKECSxbtkwCSXiU1ebgs2/PsuaLExSWmunVKYI59ybRv0eUBJEQ9VTvQMrMzCQjI4Pi4mJKS0vx9/cnJiaGhIQEEhMT6/1/Qr1ez7Jly3jrrbcYPXo0eXl5REVFuR6Pjo4mNzf3qvujoqLIzc2t13tlZGTU6/iWymg0erqEZtEY48zKNfPl4TIuldhQgHbhekb0CQVg//EKik12woN1xLXVc77A5rr9m66BFJTZ2Z1ZjqnKyU1Rfoy5oy1d2hlQTec5ePD8Ddd2JflMWxdfGWd9uRVIWVlZvP3222zbto2ysjIAVPWX5YYvh1BYWBhjxozhwQcfJD4+3u0i5s6dyyOPPMKjjz5KVlZWjVBTVRVFUXA6nbXeXx+JiYkYDIZ6PaelMRqNPvGt/sYY55GTBWz/7HuKyxzodVpUILfEwcbvyvH30xEeEkBsjI784kq+yjDRvm0QMdHBXMw38eHXRTicKoldI7k/pSd9urZtshmRfKatiy+M02KxNGgCcM1Ays3NZdGiRXz++edoNBqSkpJITEykW7duhIeHExgYSFlZGcXFxZw4cYKDBw+yatUqVq1axejRo3nyySfp0KFDna9/6tQprFYrvXr1IiAggJSUFLZt24ZW+8s31fPz84mOjiYmJob8/HzX/QUFBURHR9d7wEJctmXPaSqqqmdGVRYHKoCqYrY60CgWSsqrt3OoXmFb5Xyuiew8E3aHSpC/js4dQnnxj0M9OQQhWpU6A+mjjz7iv/7rv7jpppt47rnnuOuuuwgODr7uCxYUFLBu3To++eQTxo4dy5///Oc6r/NkZ2ezbNkyPvzwQwB27tzJ5MmTefnllzl79iwdO3Zk8+bNTJw4kdjYWAwGg+tfFxs2bGD48OENHLYQcKmwEqu9eksHFNBqFFAUnA4VFNDrNACYrQ60GgWnCmFBBqIjAggO0FNcLvsPCdGY6gykFStW8Pzzz3PXXXfV6wXbtm3LI488wowZM9iwYcM1Gw+Sk5M5fPgw48aNQ6vVkpKSQmpqKhEREcyZMweLxUJycjKjR48GYMmSJaSnp2MymejduzfTp0+vV21CXCkqPIAzOaWoQHCAHq1GweFUMZvtBPjr6HFTOAAnz5dgtjnw12vp2jEMkFUVhGgKdQbS1q1b0ev1DX5hRVEYN24cqamp1zxuzpw5zJkzp8Z9Q4YMYePGjVcdm5CQwJo1axpck/AtR04W8MH2TLIuloECnduHMTgxhsysInIKKigsNeN0/jwTUlXsDhWHQyU0xA9/Px2VZhsBBh2hQXoqC22EhQegqqqsqiBEE6kzkBoSRhcvXuTChQvccsstN/Q6QtyoIycLePXj7ykus6DTKqjAsbNFHDtbRGx0MHaHk9IKKyGBeiJD/ckvrQKgW8c2TBmVAOBaRaFjdAgjB8WTmVUkqyoI0YTcbvvu1asX/fv355VXXqnRfn2ltWvXsnz5cjIzMxutQCEa4sqGBYvNCYDdoaICF/MrsNqdhIcaaB8ZSHiIP6/Ou+Oq1/h14NyT3AyFC+HDNO4eqKoqBw8eZOLEifzwww9NWZMQN+xSYSUWq4MqqwOb3Ynd4eTyFxVsdidhwX507hBGoL9etnwQwku4HUhQfW3HbDYzbdo0Vq1a1VQ1CXHDdFoFi82JRoGQID2hQX5oNQoaBdqEGOhxUzhajUKVxS7NCUJ4iXqt1DBgwAD++te/8uijj/LXv/6VjIwMnn32WblOJDzuyMmC6ms+hZVoNHDyfCl6nQaNAqpTxY6KRgEUhbBgP2lOEMIL1WuGBBAfH8/HH3/M8OHD+fjjj5k2bRp5eXkAsnaX8IgjJwtYsSmDEpMFu8PJifOl+Bu0TEnpQbe4Nq5TdT3jI3gw9WZio4IpLrfQJtjAv6clSnOCEF6iQYurBgcH88Ybb/D3v/+dN998kwkTJvDKK68QEBDQ2PUJcV1b9pzGoNdQarKSnWciLNiPDm2DOJldyuLHhl11vDQnCOGdGrzat6Io/PnPfyYhIYH09HQeeuihGu3eQjSXsznlFJvMVFTZCQ8x0CU2DEVBmhWEaGHqfcru18aOHcv7779PeHg4e/bsaYyahHCLqdLKG2sPk51vwmx10LlDKF07hqGRZgUhWiS3A2n27NnceuuttT6WmJjI2rVrGTJkCO3bt2+04oSojdOp8v2pCh59aSef7j3DkD7tiWsXQqB/9YS/0myTZgUhWiC3T9nNnj37mo9HRkby9ttv33BBQlzLqewS3lh7mJ/OFtOrUwSPzuxLl9iwX7rsZCUFIVqsOgPp4sWLDX7Ra205IURDmKpsfPBpJlv3niEkyI9xg8N5aOJQNJrqzs4+3dpKAAnRwtUZSHfccUeD2rgVReHo0aM3VJQQlzmdKl98d553tvxIeYWVMbd1ZupdvTh29LArjIQQrUOdgVRXx9yFCxe4ePGidNSJJnf6QilvrD1MZlYRCfHh/G3mbXSJDfN0WUKIJlJnIK1cubLW+1977TWWL19e5+NC3Khfn577j/v6c8fAOJkRCdHKNfh7SEI01JXL/MREBtKrU4RrjyKtRuFiYSVVZht33daZB0YnEBzo5+mShRDNQAJJNKvLy/z46TUEBejIyinlwNFLRIYFUF5pw1Rlw+Cn5Q8T+zF6SCdPlyuEaEYSSKJZbdlzGlSVk+dLsdmdrvtzCivRaRU6tQ8l0F/LD8fzJJCE8DESSKJZnb5QSm5xFVqNwk0xIT+fpgOnE3p3jUSn1aCqqiz7I4QPkkASzWbH/nNcKqzEz09LQnw4fnot5RVWzDYHQQYtOm31wiGy7I8QvkkCSTQ5VVX56LNj/N9nx+jWsQ0OZ/UOrnqdhtAgPZWFNsLCA2SPIiF8XJ2BNH369Frvv3DhwjUfVxSFd999txFKE62B3eFk+ceH2HHgHHfeEsfs3yWReabItcxPx+gQRg6KJzOrSJb9EcLH1RlI+/fvv+YT63pcNukTl1WabSx+9wDfH8/n/pSe3J/SE0VRal3mR/YoEkLUGUg7d+5slgJee+01Pv30UwCSk5N56qmneOaZZzAaja4N/2bPns3IkSPJzMxkwYIFVFRUMHDgQJ577jl0Ojnr6I0KS6t47s1vOHupnLn3JjHy1nhPlySE8HJ1/jaPjY1t8jffu3cvu3fvZt26dSiKwowZM/j888/JyMjg/fffJzo6usbx8+bN4/nnnycpKYn58+ezevVqpkyZ0uR1ivo5e6mMv/7vN1RUWVn474P5TUL09Z8khPB5dQbSjz/+SO/evW/4DQ4fPkzfvn1rfSwqKoqnn34aP7/qb+J37dqVixcvcvHiRebPn09ubi4jR45k9uzZ5OTkYDabSUpKAmDChAksW7ZMAskLXLnygr9By6nsUgL9dbz4x6F07djG0+UJIVqIOjfoe+CBB/jP//xPzp8/36AXPnXqFHPnzuXBBx+s85ju3bu7AiYrK4tPP/2UYcOGMXjwYBYtWsTq1av57rvvWLNmDXl5eURFRbmeGxUVRW5uboNqE43n8soLJSYLTlXl6OkiHE6VGXf3kTASQtRLnTOk9evXk56ezl133cXw4cO5++67ue222wgNDa3zxS5dusTu3btZu3Yt33//Pf3792fdunXXLeLEiRPMmjWLp556ii5durB8+XLXY9OmTWP9+vV07dq1RsOEqqr1bqDIyMio1/EtldFobLb3Wv2vQuwWB3nlTvJL7QT4aWgbomXzrh8JdF5q0vduznF6mq+MVcbp2+oMpPj4eFauXMm6det48803efzxx9FqtXTs2JGuXbsSHh5OQEAA5eXlFBcXc/LkSXJyclBVlW7duvHSSy+RlpZ23dAwGo3MnTuX+fPnk5qayrFjx8jKymLUqFFAdfDodDpiYmLIz893Pa+goOCqa0zXk5iYiMFgqNdzWhqj0ciAAQOa7f1W7vqKwCAN5/KLaRPsR9eObVAUKC63NGkdzT1OT/KVsco4Ww+LxdKgCcB1W9TGjx/P+PHj2bVrF5s3b+bAgQN88cUXVx3Xvn177r33Xu68806GDx/u1pvn5OTw2GOPsXTpUoYMGQJUB9CiRYsYPHgwgYGBrFq1ivHjxxMbG4vBYHB9mBs2bHD7fUTTiWrjz8Fj+eh1GjrHhqHRKFSabbLSghCi3tzumU5OTiY5ufrLIkVFRRQVFVFeXk5YWBjR0dEEBwfX+81XrFiBxWJh8eLFrvsmT57MzJkzuf/++7Hb7aSkpDB27KVotK4AACAASURBVFgAlixZQnp6OiaTid69e9f55VzRfGwOFavdSaf2IWh/DiNZaUEI0RAN+hJPREQEERERN/zm6enppKen1/rY1KlTr7ovISGBNWvW3PD7isax59BFjD/l8dvfdMRmd8hKC0KIGyLfKhUNkl9cxasf/0CPm9rwH5P7uxZGFUKIhpLfIqLeHE6V//7QiNPp5MmpAySMhBCNQn6TiHpb++UJMk4VMnNcXzq0rf+1QyGEqI0EkqiX4+eK+WDbTwzt14E7b4nzdDlCiFZEAkm4rcpiZ8kHRsJD/XlsUj9Z2V0I0agaHEh5eXmcOHECALvd3mgFCe/1P+uOcKmwgien/IbgQD9PlyOEaGXqFUhms5klS5Zw2223kZyczD333APAW2+9xfTp0zl9+nSTFCk8b/ehC+w4cI5Jd3Qnsau0dAshGp/bgVRRUcGUKVN48803MRgMxMXFoaoqUB1U+/fvZ+rUqWRnZzdZscIz8oureO3jQ/S4qQ1TRiV4uhwhRCvldiD94x//4OjRo6Snp/PFF1+Qlpbmemzu3LksXryY0tJSXn/99SYpVHiGtHgLIZqL279dLm8N8cADD6AoylUXtMeNG8dvf/tbvv3220YvUniOtHgLIZqL24GUl5dHr169rnlM586da6zILVo2afEWQjQntwMpIiKCU6dOXfOYEydONMoad8LzpMVbCNHc3A6kESNG8OWXX7Jr165aH9++fTtff/21bAnRSvzPuiPkSou3EKIZub246uzZs/nyyy/5wx/+wPDhwykpKQHg1VdfJSMjg6+//prIyEgee+yxJitWNI/LLd73/lsPafEWQjQbtwOpbdu2fPTRRyxcuJBdu3a5Wr4vbzd+yy238Le//Y127do1TaWiyRw5WcA/1x0iO68Cp1NFBaLCA7g/paenSxNC+JB6bT/Rvn17/ud//of8/HyOHj1KWVkZgYGB9OzZk44dOzZVjaIJHTlZwJIPDlBcbgUV1J/vLy6tYsvu09yT3M2j9QkhfIfb15Bee+01Dhw4AEBUVBTJycmkpaVx5513usLoyy+/5C9/+UvTVCqaxJY9pymrsNUII0UBpwrrdp30aG1CCN9Sr0Dav3//NY/ZtWsXGzZsuOGiRPO5VFiJw6nCz010GqX6jwqUV9o8WpsQwrfUecrugw8+uGq78A8//JAdO3bUerzNZuP06dNy6q6FiYkM5OylMhyO6vlRdXe3goJKSKDeo7UJIXxLnYF0zz33sHz5coqKigBQFIWCggIKCgpqfyGdjvbt27NgwYKmqVQ0idTbu5CZVUhRmRUApxNARadVGC/Xj4QQzajOQAoODmbv3r2u2wkJCcyePZvZs2c3S2GiefTp1pY/T72Fxe/tp6zChqJAcKAf997ZXRoahBDNyu0uu/fee4/Y2NimrEV4SJ9ubfntb+LYeeAcH72Q6ulyhBA+yu2mhkGDBl0zkOx2OwUFBXzyySeNUphoXhabA4Of1tNlCCF8mNszJIfDwd///nc2b95MUVERDoejzmMnTpzYKMWJ5mOxOjDo6/W1NCGEaFRuz5DefPNN3nrrLUpKSoiPj0er1dK2bVs6d+6MwWBAVVUiIiJ45pln6lXAa6+9RmpqKqmpqbz88ssA7N27l7S0NFJSUli6dKnr2MzMTCZMmMCoUaNYsGCBbJ3eiGSGJITwNLcDadOmTYSFhfH555+zZcsWbrnlFm699Va2bt3Kt99+y+9+9zuKioro1s39C+F79+5l9+7drFu3jvXr1/Pjjz+yefNm5s+fz+uvv87WrVvJyMhwLeg6b948nn32WbZv346qqqxevbr+Ixa1stgcGPQSSEIIz3E7kLKzs0lJSXGtVdenTx++++47AAwGA8899xydOnXinXfecfvNo6KiePrpp/Hz80Ov19O1a1eysrKIj48nLi4OnU5HWloa27Zt48KFC5jNZpKSkgCYMGEC27Ztq8dQxbVYrDJDEkJ4Vr0uGly511GnTp3Izc2lrKyM0NBQNBoNQ4cOZefOnW6/Xvfu3V1/z8rK4tNPP+WBBx4gKirKdX90dDS5ubnk5eXVuD8qKorc3Nz6lE9GRka9jm+pjEZjvZ9TXFJGkL+2Qc/1lJZU643ylbHKOH2b24HUoUMHzpw547p90003AXD8+HEGDhxY/WI6neuLtPVx4sQJZs2axVNPPYVWqyUrK8v1mKqqKIqC0+mssUnc5fvrIzExEYPBUO/6WhKj0ciAAQPq/Tztzi+IbhvSoOd6QkPH2RL5ylhlnK2HxWJp0ATA7VN2ycnJfPHFF3zyySc4HA5uvvlm/P39+eCDDwAoKytjx44d9d5+wmg08vvf/54nn3yS8ePHExMTU2Mb9Pz8fKKjo6+6v6CggOjo6Hq9l6ibNDUIITzN7UCaOXMmMTExpKen88knnxAYGMi9997Lp59+ytChQ7nzzjvJzs7mnnvucfvNc3JyeOyxx1iyZAmpqdVfyOzXrx9nzpzh7NmzOBwONm/ezPDhw4mNjcVgMLimuhs2bJDdaRuR1SpNDUIIz3L7lF14eDjr16/n//7v/+jbty8ATz75JFarlS1btmAwGPjd737HzJkz3X7zFStWYLFYWLx4seu+yZMns3jxYubMmYPFYiE5OZnRo0cDsGTJEtLT0zGZTPTu3Zvp06e7/V7i2iw2u8yQhBAe5XYgbd++naSkpBqB4+fnx8KFC1m4cGGD3jw9PZ309PRaH9u4ceNV9yUkJFy1Arm4caqqYrE5ZYYkhPAot0/Z3UjwCO9md6g4nSp+EkhCCA9yO5AsFgtdunRpylqEh1hs1ctAySk7IYQnuR1IEydOZOPGjRw/frwp6xEeYLFWL8Ekp+yEEJ7k9jWk0NBQAMaNG0d8fDyxsbH4+/tfdZyiKLz66quNV6FocjJDEkJ4A7cD6fXXX3f9/cyZMzW+JHul+n5ZVXiexfpzIMkMSQjhQW4HUn2WBBIti1VmSEIIL+B2IMlusa2X65SdzJCEEB7kdlODaL0un7KTtm8hhCdJIAlpahBCeAUJJCFNDUIIryCBJGSGJITwChJIQmZIQgivIIEkfmn7lkASQniQ223f7mz1oNVq8ff3p3379gwePJiUlJQbKk40D4vNgU6roNXKv0+EEJ7jdiDl5ORQVlZGaWlp9RN1OiIiIqioqKCiogKoXqVBVVUAPvzwQ4YNG8Y//vEPtFr5l7c3s8jmfEIIL+D2P4nfeOMNAAYMGMCHH37I4cOH+frrrzEajWzevJnk5GTCw8PZtGkTO3bs4L777uNf//oX77zzTlPVLhqJbF8uhPAGbgfSSy+9RGRkJO+88w79+/dHo/nlqd26dePVV18lPDycpUuX0rFjR/7617/Sr1+/WjfaE96leobk9mRZCCGahNuBdODAAUaMGIFer6/1cT8/P26//Xb27dvnuq9///6cP3/+xqsUTUpmSEIIb+B2IAUGBpKdnX3NYy5dulQjsJxOZ50BJryHxSbXkIQQnud2IN16663s2LGDzz//vNbHd+3axc6dO7nlllsAsNlsfP3113Tu3LlxKhVNxmKVGZIQwvPcvnDwxBNPsG/fPubOncvAgQPp06cPUVFRmEwmDh8+zN69ewkKCuJPf/oTdrudu+++m6ysLJ5//vmmrF80AovNQZtgg6fLEEL4OLcDKS4ujlWrVrFo0SK+/vprDhw44HpMURRuu+020tPT6dy5M+fOnSM3N5eHH36YiRMnNknhovFI27cQwhvUq7Xqpptu4o033qC4uJgff/yR4uJigoODufnmm2nXrp3ruLi4OA4ePNjoxYqmIU0NQghv0KBe3/DwcIYOHVrn4/XZxtxkMjF58mTeeOMNOnbsyDPPPIPRaCQgIACA2bNnM3LkSDIzM1mwYAEVFRUMHDiQ5557Dp1OWpUbg1VmSEIIL1Cv3+jnz59n/fr1ZGVlYbVaXasyXElRFF599VW3Xu/QoUOkp6eTlZXlui8jI4P333+f6OjoGsfOmzeP559/nqSkJObPn8/q1auZMmVKfcoXdbDY7DJDEkJ4nNuBtH//fmbMmIHNZqs1iC6rz+xo9erVLFy4kKeeegqAqqoqLl68yPz588nNzWXkyJHMnj2bnJwczGYzSUlJAEyYMIFly5ZJIDUCVVWx2JwyQxJCeJzbgbRs2TLsdjuPP/44ycnJBAcH1yt8avPCCy/UuF1QUMDgwYNZuHAhISEhzJo1izVr1tC9e3eioqJcx0VFRZGbm1vv98vIyLihelsKo9Ho9rF2h4rTqZKffwmjsbIJq2p89RlnS+crY5Vx+ja3AykjI4MxY8Ywa9asJismLi6O5cuXu25PmzaN9evX07Vr1xrhp6pqg8IwMTERg6F1tzcbjUYGDBjg9vGmKhusukCXTjcxYEDXJqyscdV3nC2Zr4xVxtl6WCyWBk0A3P5irMFgqDFLaQrHjh1j+/btrtuqqqLT6YiJiSE/P991f0FBwVXXmETDWKx2QPZCEkJ4ntuBNHToUHbv3o3D4WiyYlRVZdGiRZSWlmKz2Vi1ahUjR44kNjYWg8HgmuZu2LCB4cOHN1kdvkS2LxdCeAu3A+mpp56isrKSxx9/HKPRSFFRESaTqdY/DZWQkMDMmTO5//77SU1NpVevXowdOxaAJUuW8OKLLzJ69GgqKyvd2jBQXJ9sXy6E8BZuX0OaMmUKlZWVfP755+zYsaPO4xRF4ejRo/Uq4osvvnD9ferUqUydOvWqYxISElizZk29Xldcn1VmSEIIL+F2IHXo0KEp6xAe4jplJzMkIYSHuR1IK1eubMo6hIdcPmXnJ4EkhPAwt68hidZJmhqEEN6izhnSiy++yLBhw1xr1r344otuvaCiKDz99NONU51octLUIITwFnUG0rvvvktISIgrkN599123XlACqWWRGZIQwlvUGUjvvfcesbGxNW6L1kdmSEIIb1FnIA0aNKjG7czMTJKSkujXr1+TFyWaj1W67IQQXsLtpobXXnvN7dN2ouWw2BzotAparfS3CCE8q16/hdq2bdtUdQgPke3LhRDewu1AmjFjBuvWrWPXrl3X3A9JtCyyfbkQwlu4/cXYrKwsDAYDjz76KP7+/sTExODv73/VcYqisHbt2kYtUjSd6hmSbAUvhPA8t38TrVu3zvX3qqoqzpw5U+txN7ppn2heMkMSQngLtwPpp59+aso6hIdYbHINSQjhHaS1ysdZrDJDEkJ4h2t+MTYpKYm+ffu6brtL9ipqOSw2B22CW/e27kKIlqHOQFq0aBGzZ892BdKiRYtQFOW6HXaKokggtSDS9i2E8BbXXFy1V69eNW6L1keaGoQQ3qLOQBo/fvw1b4vWwSozJCGEl6izqWHnzp11tnaL1sNis8sMSQjhFeoMpMcee4wtW7Zcdf/Fixc5cOBAkxYlmoeqqlhsTpkhCSG8Qr3bvteuXStNC62E3aHidKqyfbkQwivI95B8mGzOJ4TwJhJIPsxitQOyF5IQwjt4PJBMJhNjx44lOzsbgL1795KWlkZKSgpLly51HZeZmcmECRMYNWoUCxYswG63e6rkVkNmSEIIb+LRQDp06BD3338/WVlZAJjNZubPn8/rr7/O1q1bycjIYNeuXQDMmzePZ599lu3bt6OqKqtXr/Zg5a2DbF8uhPAmHg2k1atXs3DhQqKjowE4fPgw8fHxxMXFodPpSEtLY9u2bVy4cAGz2UxSUhIAEyZMYNu2bZ4svVWwygxJCOFFrrna9/79+3nttddq3Pftt98CsHz58lqXEVIUhccee8ytN3/hhRdq3M7LyyMqKsp1Ozo6mtzc3Kvuj4qKIjc31633EHVznbKTGZIQwgtcN5D2799f62OvvvpqrffXJ5B+zel01thPSVVVFEWp8/76ysjIaFBdLY3RaHTruOMXqgA4feoElpKzTVlSk3B3nK2Br4xVxunbrrmWXXOLiYkhPz/fdTs/P5/o6Oir7i8oKHCd5quPxMREDIbWvbK10WhkwIABbh1bpbsAFNKvb2/iY0KbtrBGVp9xtnS+MlYZZ+thsVgaNAFwey275tCvXz/OnDnD2bNn6dixI5s3b2bixInExsZiMBhcH+SGDRsYPnx4s9fX2khTgxDCm7i9Y2xzMBgMLF68mDlz5mCxWEhOTmb06NEALFmyhPT0dEwmE71795bVIhqBtH0LIbyJVwTSF1984fr7kCFD2Lhx41XHJCQksGbNmuYsq9WTGZIQwpt4/IuxwnOs0mUnhPAiEkg+zGJzoNMqaLXyn4EQwvPkN5EPk+3LhRDeRALJh8n25UIIbyKB5MOqZ0he0dcihBASSL5MZkhCCG8igeTDLDa5hiSE8B4SSD7MYpUZkhDCe0gg+TCLzYGfzJCEEF5CAsmHSdu3EMKbSCD5MGlqEEJ4EwkkH2aVGZIQwotIIPkwi80uMyQhhNeQQPJRqqpisTllhiSE8BoSSD7K7lBxOlXpshNCeA0JJB8lm/MJIbyNBJKPsljtgOyFJITwHhJIPkpmSEIIbyOB5KNk+3IhhLeRQPJRVpkhCSG8jASSj3KdspMZkhDCS0gg+ajLp+yk7VsI4S0kkHyUNDUIIbyNBJKPkqYGIYS30Xm6gLpMmzaNoqIidLrqEv/2t79RUVHBiy++iMVi4a677uKJJ57wcJUtl8yQhBDexisDSVVVsrKy+PLLL12BZDabGT16NCtXrqR9+/bMmjWLXbt2kZyc7OFqWyaZIQkhvI1XBtLp06cBePjhhykpKeHee++lR48exMfHExcXB0BaWhrbtm2TQGogq3TZCSG8jFcGUllZGUOGDOEvf/kLNpuN6dOnM2PGDKKiolzHREdHk5ubW6/XzcjIaOxSvZLRaLzuMVnnStFo4Icfvm+GipqGO+NsLXxlrDJO3+aVgdS/f3/69+/vuj1p0iSWLVvGgAEDXPepqoqiKPV63cTERAwGQ6PV6Y2MRmONn1NdDp4/QoBflVvHeiN3x9ka+MpYZZyth8ViadAEwCu77L777jv27dvnuq2qKrGxseTn57vuy8/PJzo62hPltQqyfbkQwtt4ZSCVl5fz8ssvY7FYMJlMrFu3jj/96U+cOXOGs2fP4nA42Lx5M8OHD/d0qS2WxerAoPfKCbIQwkd55W+kESNGcOjQIcaNG4fT6WTKlCn079+fxYsXM2fOHCwWC8nJyYwePdrTpbZYMkMSQngbrwwkgMcff5zHH3+8xn1Dhgxh48aNHqqodbHYHNJhJ4TwKl55yk40PYtVZkhCCO8igeSjLDaHLKwqhPAqEkg+qrqpQQJJCOE9JJB8lDQ1CCG8jQSSj7LKDEkI4WUkkHyUxWaXGZIQwqtIIPkgVVWx2JwyQxJCeBUJJB9kd6g4nap02QkhvIoEkg+SzfmEEN5IAskHWax2QPZCEkJ4FwkkHyQzJCGEN5JA8kGyfbkQwhtJIPkgq8yQhBBeSALJB7lO2ckMSQjhRSSQfNDlU3bS9i2E8CYSSD5ImhqEEN5IAskHSVODEMIbSSD5IJkhCSG8kdduYS6qHTlZwJY9p7lUWIm/QYuCQpXFTkxkIKm3d6FPt7b1fr1P954BYPnHP3DP8G71fg0hhGgKMkPyYkdOFrBiUwYlJgtaLZzKLuVkdgk6rUKJycKKTRkcOVlQ79erNFev1FBeaa33awghRFORGZKXcjicrPniOHa7A5PdSV5RJU6nCgqcu1ROZJsArHYH/1x3mN8OiHM978KFMs6Unqj1Nb8ynqfKaqfKYkcBggL8UBQbW/acllmSEMLjJJA8yGpzkFtUSU5hBTkFV/wprCCvqBKHU63zudl5Jtff391ytOaDP/zqdi0CDDrX/14qqmzYAIQQohFJIDUxs8VeM3AKf/nfgpIq1CsyJ8hfR/u2QXTr2Iah/Trw/bE87A4nYcEGzuaUYbM7QFHQazV07diGSrONsGADf35goOs1vj/4Pf1/07/WWpa8/x2lJguB/noUpfq+KoudmIjApvwRCCGEW1pcIG3atIl//OMf2O12HnzwQaZOner2c4+dLWbrvnNcKqwkJjKQsGA/vv3xEuWVNkIC9XTrGMbJ7FLX7fHJ3bgnudt1X9dUaa09dAoqKC631Dg2LNiP9pFB9O4SSYfIINq3rf4TExlEaJAfyuWkAPr3iGbFpgzsDifREQGcu2QCVDpGBWK22rHandw9rGuN9m29TqmznfvuYV1ZsSkDjdVOgEFHpdmG2eog9fYubv8MhRCiqbSoQMrNzWXp0qWsXbsWPz8/Jk+ezK233kq3btcPDYBVO47hUDWEhxo4cb6Y/GIzfnoN/n5aSkwWvv0xD51OIdCgo9LiYOWnmQDcPbwrpSbrz2FjIqeg8oq/V1BeaavxPpFh/sREBjGwV7sagdM+MoigAL3b4+3TrS3/npZY3WVXVEnXjmHVXXZWO22DDfXusvv168VENKxTTwghmkKLCqS9e/cyePBg2rRpA8CoUaPYtm0bs2fPduv5Bj8tDqeGUpOVwlILKtW7p2odKo7qr+Zgt6vYtE6cThWbQ+XtzUf5YPsxqix21+toFGgbHkiHyCCG9ot1BU6HtkG0iwzE36/xfqx9urVt1MBo7NcTQojG0qICKS8vj6ioKNft6OhoDh8+7PbzbeZKzuZbKTY5XPc5nKqrDfqyKkv14wqgAn3j/QkP0RIRrCMiREebIB067eVTa3agFKylFFyEgosNHFwjMhqNni6hWfjKOMF3xirj9G0tKpCcTmeNayyqqta4fT16/0C6xIVQabZz/FwxDoeKVqPgb9BSUfVLKIUEVl/0tzlUAg1a0mfd2ajjaEpGo5EBAwZ4uowm5yvjBN8Zq4yz9bBYLGRkZNT7eS3qi7ExMTHk5+e7bufn5xMdHe328y1WB2aLnSB/HZGhBhRAp1XQahS0P/cB6HSKK4ycDifj3WhqEEIIceNaVCDddttt7Nu3j6KiIqqqqvjss88YPny428+/79960ibYQHG5he5x4dx1WzzBgXrMVgdtgg3c2juasCA/zFYHgQYt0+7q5VaXnRBCiBvXok7ZtWvXjieeeILp06djs9mYNGkSffv2dfv5PePD6dsjpsZ9f5jY2FUKIYRoiBYVSABpaWmkpaV5ugwhhBCNrEWdshNCCNF6SSAJIYTwChJIQgghvIIEkhBCCK8ggSSEEMIrSCAJIYTwCi2u7bsh1J83HbJarR6upHlYLJbrH9QK+Mo4wXfGKuNsHS7/rlXVujcZrY2i1vcZLVB5eTnHjx/3dBlCCOFTevToQUhIiNvH+0QgOZ1OKioq0Ov19VqMVQghRP2pqorNZiMoKAiNxv0rQz4RSEIIIbyfNDUIIYTwChJIQgghvIIEkhBCCK8ggSSEEMIrSCAJIYTwChJIQgghvIIEkhBCCK/gE4G0adMmxowZQ0pKCh988IGny2ky06ZNIzU1lXvuuYd77rmHQ4cOebqkRmMymRg7dizZ2dkA7N27l7S0NFJSUli6dKmHq2tcvx7rM888Q0pKiutz/fzzzz1c4Y177bXXSE1NJTU1lZdffhlonZ9pbeNsjZ/nK6+8wpgxY0hNTeXtt98GGvh5qq3cpUuX1BEjRqjFxcVqRUWFmpaWpp44ccLTZTU6p9OpDh06VLXZbJ4updH98MMP6tixY9XevXur58+fV6uqqtTk5GT13Llzqs1mUx9++GH1q6++8nSZjeLXY1VVVR07dqyam5vr4coaz549e9T77rtPtVgsqtVqVadPn65u2rSp1X2mtY3zs88+a3Wf57fffqtOnjxZtdlsalVVlTpixAg1MzOzQZ9nq58h7d27l8GDB9OmTRsCAwMZNWoU27Zt83RZje706dMAPPzww9x99928//77Hq6o8axevZqFCxcSHR0NwOHDh4mPjycuLg6dTkdaWlqr+Ux/PdaqqiouXrzI/PnzSUtLY9myZTidTg9XeWOioqJ4+umn8fPzQ6/X07VrV7KyslrdZ1rbOC9evNjqPs9Bgwbx3nvvodPpKCwsxOFwUFZW1qDPs9UHUl5eHlFRUa7b0dHR5ObmerCiplFWVsaQIUNYvnw577zzDh999BF79uzxdFmN4oUXXmDgwIGu2635M/31WAsKChg8eDCLFi1i9erVfPfdd6xZs8aDFd647t27k5SUBEBWVhaffvopiqK0us+0tnEOGzas1X2eAHq9nmXLlpGamsqQIUMa/P/RVh9ITqezxoKqqqq2ygVW+/fvz8svv0xISAgRERFMmjSJXbt2ebqsJuErnylAXFwcy5cvJzo6moCAAKZNm9ZqPtcTJ07w8MMP89RTTxEXF9dqP9Mrx9mlS5dW+3nOnTuXffv2kZOTQ1ZWVoM+z1YfSDExMeTn57tu5+fnu06HtCbfffcd+/btc91WVRWdrnVud+UrnynAsWPH2L59u+t2a/lcjUYjv//973nyyScZP358q/1Mfz3O1vh5njp1iszMTAACAgJISUnh22+/bdDn2eoD6bbbbmPfvn0UFRVRVVXFZ599xvDhwz1dVqMrLy/n5ZdfxmKxYDKZWLduHSNHjvR0WU2iX79+nDlzhrNnz+JwONi8eXOr/Eyh+hfWokWLKC0txWazsWrVqhb/uebk5PDYY4+xZMkSUlNTgdb5mdY2ztb4eWZnZ5Oeno7VasVqtbJz504mT57coM+zZUezG9q1a8cTTzzB9OnTsdlsTJo0ib59+3q6rEY3YsQIDh06xLhx43A6nUyZMoX+/ft7uqwmYTAYWLx4MXPmzMFisZCcnMzo0aM9XVaTSEhIYObMmdx///3Y7XZSUlIYO3asp8u6IStWrMBisbB48WLXfZMnT251n2ld42xtn2dycjKHDx9m3LhxaLVaUlJSSE1NJSIiot6fp+yHJIQQwiu0+lN2QgghWgYJJCGEEF5BAkkIIYRXkEASQgjhFSSQhBBCeAUJJCGEEF5BAkkIIYRXaPVfjBVCiMv+oFDJ3AAADOZJREFU+c9/8tlnn3HmzBn8/PxISkriT3/6Ez169PB0aQKZIQkhfMj+/fuZMmUKH330Ee+++y5arZaHHnqIkpIST5cmkEASdcjOzqZnz55MmzbN06Vc5fDhw+zevdvTZXiNhvw80tPTefTRRxu1jhkzZtCzZ09mzpx53WOtViv9+/dv9n27VqxYwcSJE+nRowc9e/bk5ZdfpqioiIMHD9Y4bunSpdx3330tfq+ilkYCSbQoX331Fffddx8nT570dCleoSE/j2+++YZ169Yxb968RqsjPz+fvXv3EhAQwO7du7l06dI1jzcajVRWVjJ06NBGq6EhKioqcDqdhIaG1rj/kUce4fz5861qo8uWQAJJtChFRUXyr9Yr1PfnYbfbefbZZxk7dixdu3ZttDo2bdqEw+FgxowZOBwOPvnkk2sev3v3bjp27EinTp0arYaGeOGFF+jVq9dVCxEHBwczc+ZMli5dWmMbBdG0JJCE8CHbt2/n7NmzjX4qdv369YSFhTFjxgxCQkL45JNPuNa6zbt37+b2229v1Brq68UXX8RoNPLqq6+i1WqvenzSpEmoqsrKlSs9UJ1vkkASbnv66afp2bMnpaWlLFy4kNtvv50+ffowYcKEGpuOXXlsYWEh8+bNY+DAgQwaNIg//vGPnDhxosax06ZNo2fPnpSVldW4//J1rD/+8Y+u13zmmWeA6l8mPXv2JDs7+7p1FxUVsWjRIu644w769u3LqFGjWLp0KRUVFfWu4fKxd9xxB7t27eKOO+6gX79+/Md//Eed919mMplYsmQJ//Zv/0ZiYiLDhg1j4cKFFBYWNujn3JCfx9tvv02XLl1ITEy86rH33nuPnj171rqldnl5OQkJCUyfPv2qx3766SeOHTvGkCFD8P//7d19TFX1H8DxNw9xRZDwoTAhJ07QfGCKumCBsCBQGyVFOUY6iZWpzR7WEp1ztpItXTlnbi5XgaXlvcMJQ0mEahNIrt5uijKuD9Tudl1Ek9A6wgU7vz/YOT8O5yKcqxfZ+L42x/icc8/53u8Xz+d8H869Y8aQnp6Oy+Wivr7eYxna2tpwOBwkJydr4kNpp8LCQmbPnk17eztbt24lISGBBQsWUFBQgNPpxO12s2vXLpKSkoiPj2fVqlU0NzfrylBUVMTx48cpKSnh8ccf91jO0NBQUlNT+e6775AkyeM+wv0lEpJgWH5+PqdPn2bZsmVkZWVx5coV3nrrLc6dO6fb97XXXuPMmTO8+OKLJCYm8tNPP5Gbm+vxIjGY9PR00tLSAEhKSuLNN9/Ujf3319bWRk5ODiUlJURFRZGXl8fkyZPZv38/GzZsoKenx3A5ANrb23n77beJj48nOzubRYsW3TV+69YtcnNzOXDgAFFRUaxevZoFCxZgNpt56aWX+PPPP3XnGKyejdaH0+mksbFxwHmbpqYmAI/J6tKlS8iyzJw5c3Tbjh07BsDy5cs1Py0Wi8fz1NXVERAQQEJCghoz0k6yLLN69WrsdjvZ2dnEx8dTW1vL2rVr2bhxI5WVlSxdupTk5GSsViuvv/46t2/fVl//0UcfUVFRQUlJyaDDlklJSXR0dIhFNMNEPIckGBYQEEBFRQVjx44FIDExkffeew+z2axegBV///03ZWVlTJgwAegdMtq4cSM7duwwPBSSnp7OzZs3qampITk5mTVr1gz6ml27duFyudi8ebNm/23btnHkyBF++OEHMjIyDJUDQJIk8vPzKSwsVGNVVVUe4wCffvoply9fZtu2beTl5anxmpoa1q9fz44dO9izZ4/mNYPVs9H6aGhoADwnHOhNOiaTiRkzZnjcBugSkvJtoCEhIaSmpgLw1FNPMXHiRKqrq7lx44ba9orTp08TFxfHuHHj1JiRdvrvv/8IDg7mm2++ISgoCOj94ju73Y7b7aa8vJzQ0FAANm/ezNGjR7FaraSkpPDBBx9QVlbGvn37CAsLU+eHxo4dS0hIiO59K3VltVq9+jsRjBE9JMGwvLw89SIJvd8YCfD777/r9l23bp3mgpSZmcnChQuxWq2DrsS6V263m1OnTjFt2jTdxXrt2rW88cYbPPLII14fPzMzc0jxnp4ejh07RkxMjCYZAaSlpREfH8+pU6f4559/NNuM1PNQKD0gTwmnq6uLlpYWZs6cSWCg/j51oIRUV1dHW1sbzzzzDCaTCehNpEuXLqW7u5vy8nLN/rIsU19fr+mledNOubm5ajIC1EUJK1euVJMRoH47tMvlAuDw4cP8+++/rFmzhqSkJPXfl19+qXvPANOnT8ff35+LFy963C7cX6KHJBgWHR2t+V2503W73bp9Fy9erIvFxcVhs9lwOBxMnjzZN4Wkd4hKkiTmz5+v2xYZGck777xzT8ePjIwcUvy3335DkiTu3LnD3r17dft3dXVx584dHA4HCxcuVONG6nkolLmq8ePH67Y5HA56eno8DskBXLx4kZCQEN2quLKyMgCeffZZTTwrK4tDhw5hsVg0SebSpUvcuHFDM3/kTTtNnTpV87uSuKOiojRxJUkqdeZwODy+v4EEBQURGhpKe3u7odcJ3hEJSTCs750pgJ+fH4DHVVURERG62KRJk4DeeRVf6ujoANDcMd9PY8aMGVJcWSjR0tLCZ599NuDxlPIqjNTzUCg9ME/lHqgHBL3t5HQ6WbRokVoG5XjV1dVA71yhJ1evXsVut6s9mNraWsLDwzXDht60U9+eY1/96+x+CA4O1rWN4BsiIQk+1dnZSXBwsCamJKL+d+r9L7SdnZ33dG5lTqDvKq2+JEnSXdjudxn6luP5559n586d93w8bz388MNAbyLpP69ztwUNv/zyC7IsM3v2bE38+++/p7Ozk3nz5um2QW/P0Gq1YrFYNAkpMTERf///zxZ4007D6datW2rdCb4lEpLgU42NjSxZskQTs9vtBAYGqnfjyl2tJEma//hOp1N3vL536IOJjo7moYce4sKFC7ptra2tLFmyhJdffpkPP/zQUBmMio6OJigoSF2p1v89FBcXI0kSubm5HofT7sZIfSjzMO3t7bohL6WH5KlHe/z4cUDfe1KG6woLC3WLWQCuX79OWloalZWVbNmyBYBff/2V7du3a/Yz0k7DraurC0mSmDlz5rCfezQSixoEn9q7d69msv7kyZM0NDSQlpZGeHg48P+5kh9//FHdr6uriy+++EJ3PGXCvbu7e9Bzm0wmMjMzuXbtmm4J8v79+4HelWtGy2CUyWRi+fLlXL16la+++kqzraGhgZ07d1JaWurVXbiR+oiJiQHQPQfW3d2txmw2m2bbiRMn1IUJfS/KLpeLs2fPEhkZqZn36mvKlCkkJCQgSRInTpzgzJkzdHd365adG2mn4Xb58mUAZs2a9UDOP9qIHpLgU06nkxUrVpCamkprayvV1dVERERolkXn5ORw+PBhioqKOH/+POPHj6empoZx48bphmqUO/hvv/2Wjo4OVq1a5fGuXvH+++9js9nYunUrJ0+eJCYmhsbGRs6ePUt6err6zIyRMnhj06ZN2O12Pv74Y2pqaoiLi6O1tZWqqioCAwMpKirSDGMNlZH6SElJwc/PD5vNRk5Ojhq/cuUKbrebSZMm8e6775KRkUF4eDjNzc00NTUxceJE/vrrL/bt20dBQQHz58+nrKwMWZbJysq6ay/thRdeoL6+HovFwpw5c5gxY4bHhSxDbafhpnzo6oP+VInRQvSQBJ/as2cPc+fOpbS0FJvNxooVK7BYLEyZMkXdZ9asWXz++efMnTuXyspKysvLSUxMpLi4WPeRLosXLyYvL4+Ojg4OHTrEtWvX7nr+iIgILBYLK1euxOFwcPDgQa5fv866devYvXu3V2XwxoQJEzCbzbz66qu0trby9ddfc+7cOZ5++mnMZjNPPvmkV8c1Uh+PPvoo8+bN4+eff9Z8/p0yXLd+/XpeeeUVamtrKS0tJSAggIMHD1JQUEBwcDAul4vHHnsMQO01Pffcc3ctX0ZGBmFhYVy4cIEjR44MeGEfajsNt7q6OsLCwnTDzoKPyILgA5s2bZJjY2PlpqamB10UoY+Kigo5NjZWrq2tVWPbt2+XY2Nj5fPnzz/Ako08f/zxh/zEE0/Iu3fvftBFGTVED0kQRpFly5Yxbdo0zGazGmtqaiIgIEB8a2o/R48exWQyefz8PsE3REIShFHE39+fLVu2UFVVRXNzs/pA7vTp0wd8rmo0unnzJsXFxWzYsEG3RF7wHZGQBGGUSUlJITs7m08++YSWlhZu374tVpH1c+DAAaZOnUp+fv6DLsqo4ifLXj72LQiCIAj3keghCYIgCCOCSEiCIAjCiCASkiAIgjAiiIQkCIIgjAgiIQmCIAgjgkhIgiAIwoggEpIgCIIwIoiEJAiCIIwIIiEJgiAII8L/AFARLSwv+4gnAAAAAElFTkSuQmCC\n", 249 | "text/plain": [ 250 | "
" 251 | ] 252 | }, 253 | "metadata": {}, 254 | "output_type": "display_data" 255 | } 256 | ], 257 | "source": [ 258 | "plt.figure(figsize=(6, 5))\n", 259 | "plt.plot(I_inj, firing_rate)\n", 260 | "plt.scatter(I_inj, firing_rate, alpha=0.7)\n", 261 | "plt.xlabel('Input current ($\\mu$A/cm$^2$)', size=20)\n", 262 | "plt.ylabel('Firing rate (Hz)', size=20) \n", 263 | "plt.xlim(0, 30)\n", 264 | "plt.tight_layout()\n", 265 | "plt.savefig(\"HH_FIcurve.png\", dpi=300)\n", 266 | "plt.show()" 267 | ] 268 | } 269 | ], 270 | "metadata": { 271 | "kernelspec": { 272 | "display_name": "Python 3", 273 | "language": "python", 274 | "name": "python3" 275 | }, 276 | "language_info": { 277 | "codemirror_mode": { 278 | "name": "ipython", 279 | "version": 3 280 | }, 281 | "file_extension": ".py", 282 | "mimetype": "text/x-python", 283 | "name": "python", 284 | "nbconvert_exporter": "python", 285 | "pygments_lexer": "ipython3", 286 | "version": "3.7.6" 287 | } 288 | }, 289 | "nbformat": 4, 290 | "nbformat_minor": 4 291 | } 292 | -------------------------------------------------------------------------------- /notebook/Hodgkin-Huxley_AP.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "from tqdm import tqdm" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 3, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "class HodgkinHuxleyModel:\n", 21 | " def __init__(self, dt=1e-3, solver=\"RK4\"):\n", 22 | " self.C_m = 1.0 # 膜容量 (uF/cm^2)\n", 23 | " self.g_Na = 120.0 # Na+の最大コンダクタンス (mS/cm^2)\n", 24 | " self.g_K = 36.0 # K+の最大コンダクタンス (mS/cm^2)\n", 25 | " self.g_L = 0.3 # 漏れイオンの最大コンダクタンス (mS/cm^2)\n", 26 | " self.E_Na = 50.0 # Na+の平衡電位 (mV)\n", 27 | " self.E_K = -77.0 # K+の平衡電位 (mV)\n", 28 | " self.E_L = -54.387 #漏れイオンの平衡電位 (mV)\n", 29 | " \n", 30 | " self.solver = solver\n", 31 | " self.dt = dt\n", 32 | " \n", 33 | " # V, m, h, n\n", 34 | " self.states = np.array([-65, 0.05, 0.6, 0.32])\n", 35 | " self.I_m = None\n", 36 | " \n", 37 | " def Solvers(self, func, x, dt):\n", 38 | " # 4th order Runge-Kutta法\n", 39 | " if self.solver == \"RK4\":\n", 40 | " k1 = dt*func(x)\n", 41 | " k2 = dt*func(x + 0.5*k1)\n", 42 | " k3 = dt*func(x + 0.5*k2)\n", 43 | " k4 = dt*func(x + k3)\n", 44 | " return x + (k1 + 2*k2 + 2*k3 + k4) / 6\n", 45 | " \n", 46 | " # 陽的Euler法\n", 47 | " elif self.solver == \"Euler\": \n", 48 | " return x + dt*func(x)\n", 49 | " else:\n", 50 | " return None\n", 51 | " \n", 52 | " # イオンチャネルのゲートについての6つの関数\n", 53 | " def alpha_m(self, V):\n", 54 | " return 0.1*(V+40.0)/(1.0 - np.exp(-(V+40.0) / 10.0))\n", 55 | "\n", 56 | " def beta_m(self, V):\n", 57 | " return 4.0*np.exp(-(V+65.0) / 18.0)\n", 58 | "\n", 59 | " def alpha_h(self, V):\n", 60 | " return 0.07*np.exp(-(V+65.0) / 20.0)\n", 61 | "\n", 62 | " def beta_h(self, V):\n", 63 | " return 1.0/(1.0 + np.exp(-(V+35.0) / 10.0))\n", 64 | "\n", 65 | " def alpha_n(self, V):\n", 66 | " return 0.01*(V+55.0)/(1.0 - np.exp(-(V+55.0) / 10.0))\n", 67 | "\n", 68 | " def beta_n(self, V):\n", 69 | " return 0.125*np.exp(-(V+65) / 80.0)\n", 70 | "\n", 71 | " # Na+電流 (uA/cm^2)\n", 72 | " def I_Na(self, V, m, h):\n", 73 | " return self.g_Na * m**3 * h * (V - self.E_Na)\n", 74 | " \n", 75 | " # K+電流 (uA/cm^2)\n", 76 | " def I_K(self, V, n):\n", 77 | " return self.g_K * n**4 * (V - self.E_K)\n", 78 | "\n", 79 | " # 漏れ電流 (uA/cm^2)\n", 80 | " def I_L(self, V):\n", 81 | " return self.g_L * (V - self.E_L)\n", 82 | " \n", 83 | " # 微分方程式\n", 84 | " def dALLdt(self, states):\n", 85 | " V, m, h, n = states\n", 86 | " \n", 87 | " dVdt = (self.I_m - self.I_Na(V, m, h) \\\n", 88 | " - self.I_K(V, n) - self.I_L(V)) / self.C_m\n", 89 | " dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m\n", 90 | " dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h\n", 91 | " dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n\n", 92 | " return np.array([dVdt, dmdt, dhdt, dndt])\n", 93 | " \n", 94 | " def __call__(self, I):\n", 95 | " self.I_m = I\n", 96 | " states = self.Solvers(self.dALLdt, self.states, self.dt)\n", 97 | " self.states = states\n", 98 | " return states" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 64, 104 | "metadata": {}, 105 | "outputs": [ 106 | { 107 | "name": "stderr", 108 | "output_type": "stream", 109 | "text": [ 110 | "100%|█████████████████████████████████████████████████████████████████████████| 40000/40000 [00:01<00:00, 24051.70it/s]" 111 | ] 112 | }, 113 | { 114 | "name": "stdout", 115 | "output_type": "stream", 116 | "text": [ 117 | "Num. of spikes : 0\n" 118 | ] 119 | }, 120 | { 121 | "name": "stderr", 122 | "output_type": "stream", 123 | "text": [ 124 | "\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "dt = 0.01\n", 130 | "T = 400 # (ms)\n", 131 | "td = 20 # synaptic decay time (ms)\n", 132 | "tr = 2 # synaptic rise time (ms)\n", 133 | "nt = round(T/dt) # ステップ数\n", 134 | "time = np.arange(0.0, T, dt) \n", 135 | "\n", 136 | "# 刺激電流 (uA/cm^2)\n", 137 | "spike = np.zeros(nt)\n", 138 | "spike_times = [10000]\n", 139 | "spike[spike_times] = 1\n", 140 | "\n", 141 | "HH_neuron = HodgkinHuxleyModel(dt=dt, solver=\"Euler\")\n", 142 | "r = 0; hr = 0 # 初期値\n", 143 | "X_arr = np.zeros((nt, 4)) # 記録用配列\n", 144 | "\n", 145 | "# シミュレーション\n", 146 | "r_list = []\n", 147 | "for i in tqdm(range(nt)):\n", 148 | " r = r*(1-dt/tr) + hr*dt \n", 149 | " hr = hr*(1-dt/td) + spike[i]/(tr*td)\n", 150 | " r_list.append(r)\n", 151 | " X = HH_neuron(r*50)\n", 152 | " X_arr[i] = X\n", 153 | "\n", 154 | "spike = np.bitwise_and(X_arr[:-1, 0] < 0, X_arr[1:, 0] > 0)\n", 155 | "print(\"Num. of spikes :\", np.sum(spike))" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 65, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "data": { 165 | "image/png": "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\n", 166 | "text/plain": [ 167 | "
" 168 | ] 169 | }, 170 | "metadata": { 171 | "needs_background": "light" 172 | }, 173 | "output_type": "display_data" 174 | } 175 | ], 176 | "source": [ 177 | "plt.figure(figsize=(4, 4))\n", 178 | "plt.subplot(2,1,1)\n", 179 | "plt.plot(time[5000:], X_arr[5000:,0])\n", 180 | "plt.ylabel('V (mV)')\n", 181 | "\n", 182 | "plt.subplot(2,1,2)\n", 183 | "plt.plot(time[5000:], np.array(r_list)[5000:])\n", 184 | "plt.tight_layout()\n", 185 | "plt.show()" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 18, 191 | "metadata": {}, 192 | "outputs": [ 193 | { 194 | "data": { 195 | "text/plain": [ 196 | "40000" 197 | ] 198 | }, 199 | "execution_count": 18, 200 | "metadata": {}, 201 | "output_type": "execute_result" 202 | } 203 | ], 204 | "source": [ 205 | "nt" 206 | ] 207 | } 208 | ], 209 | "metadata": { 210 | "kernelspec": { 211 | "display_name": "Python 3", 212 | "language": "python", 213 | "name": "python3" 214 | }, 215 | "language_info": { 216 | "codemirror_mode": { 217 | "name": "ipython", 218 | "version": 3 219 | }, 220 | "file_extension": ".py", 221 | "mimetype": "text/x-python", 222 | "name": "python", 223 | "nbconvert_exporter": "python", 224 | "pygments_lexer": "ipython3", 225 | "version": "3.7.6" 226 | } 227 | }, 228 | "nbformat": 4, 229 | "nbformat_minor": 4 230 | } 231 | -------------------------------------------------------------------------------- /notebook/Izhikevich.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "from tqdm import tqdm" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 68, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "dt = 0.5; T = 200 # ms\n", 21 | "nt = round(T/dt) # シミュレーションのステップ数\n", 22 | "\n", 23 | "# Regular spiking (RS) neurons\n", 24 | "C = 100 # 膜容量(pF)\n", 25 | "a = 0.03 # 回復時定数の逆数 (1/ms)\n", 26 | "b = -2 # uのvに対する共鳴度合い(pA/mV)\n", 27 | "k = 0.65 # ゲイン (pA/mV)\n", 28 | "d = 100 # 発火で活性化される正味の外向き電流(pA)\n", 29 | "vrest = -60 # 静止膜電位 (mV) \n", 30 | "vreset = -50 # リセット電位 (mV) \n", 31 | "vthr = -40 # 閾値電位 (mV)\n", 32 | "vpeak = 35 # ピーク電位 (mV)\n", 33 | "t = np.arange(nt)*dt\n", 34 | "\n", 35 | "spike = np.zeros(nt)\n", 36 | "spike_times = [100, 150, 200]\n", 37 | "spike[spike_times] = 1\n", 38 | "\n", 39 | "td = 20 # synaptic decay time (ms)\n", 40 | "tr = 2 # synaptic rise time (ms)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 69, 46 | "metadata": {}, 47 | "outputs": [ 48 | { 49 | "name": "stderr", 50 | "output_type": "stream", 51 | "text": [ 52 | "100%|█████████████████████████████████████████████████████████████████████████████| 400/400 [00:00<00:00, 66854.82it/s]\n" 53 | ] 54 | } 55 | ], 56 | "source": [ 57 | "# 初期化(膜電位, 膜電位(t-1), 回復電流)\n", 58 | "v = vrest; v_ = v; u = 0\n", 59 | "v_arr = np.zeros(nt) # 膜電位を記録する配列\n", 60 | "u_arr = np.zeros(nt) # 回復変数を記録する配列\n", 61 | "\n", 62 | "r = 0; hr = 0 # 初期値\n", 63 | "X_arr = np.zeros((nt, 4)) # 記録用配列\n", 64 | "\n", 65 | "# シミュレーション\n", 66 | "r_list = []\n", 67 | "# シミュレーション\n", 68 | "for i in tqdm(range(nt)):\n", 69 | " r = r*(1-dt/tr) + hr*dt \n", 70 | " hr = hr*(1-dt/td) + spike[i]/(tr*td)\n", 71 | " r_list.append(r)\n", 72 | "\n", 73 | " dv = (k*(v - vrest)*(v - vthr) - u + r*2e3) / C\n", 74 | " v = v + dt*dv # 膜電位の更新\n", 75 | " u = u + dt*(a*(b*(v_-vrest)-u)) # 膜電位の更新\n", 76 | " \n", 77 | " s = 1*(v>=vpeak) #発火時は1, その他は0の出力\n", 78 | " \n", 79 | " u = u + d*s # 発火時に回復変数を上昇\n", 80 | " v = v*(1-s) + vreset*s # 発火時に膜電位をリセット\n", 81 | " v_ = v # v(t-1) <- v(t)\n", 82 | "\n", 83 | " v_arr[i] = v # 膜電位の値を保存\n", 84 | " u_arr[i] = u # 回復変数の値を保存" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 70, 90 | "metadata": {}, 91 | "outputs": [ 92 | { 93 | "data": { 94 | "image/png": "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\n", 95 | "text/plain": [ 96 | "
" 97 | ] 98 | }, 99 | "metadata": { 100 | "needs_background": "light" 101 | }, 102 | "output_type": "display_data" 103 | } 104 | ], 105 | "source": [ 106 | "# 描画\n", 107 | "plt.figure(figsize=(5, 6))\n", 108 | "plt.subplot(2,1,1)\n", 109 | "plt.plot(t, v_arr, color=\"k\")\n", 110 | "#plt.title(\"Regular spiking (RS) neurons\")\n", 111 | "plt.ylabel('Membrane potential (mV)') \n", 112 | "plt.xlim(0, T)\n", 113 | "plt.tight_layout()\n", 114 | "\n", 115 | "plt.subplot(2,1,2)\n", 116 | "plt.plot(t, np.array(r_list), color=\"k\")\n", 117 | "plt.xlabel('Time (ms)')\n", 118 | "plt.ylabel('Recovery current (pA)')\n", 119 | "plt.xlim(0, T) \n", 120 | "\n", 121 | "plt.tight_layout()\n", 122 | "plt.show()" 123 | ] 124 | } 125 | ], 126 | "metadata": { 127 | "kernelspec": { 128 | "display_name": "Python 3", 129 | "language": "python", 130 | "name": "python3" 131 | }, 132 | "language_info": { 133 | "codemirror_mode": { 134 | "name": "ipython", 135 | "version": 3 136 | }, 137 | "file_extension": ".py", 138 | "mimetype": "text/x-python", 139 | "name": "python", 140 | "nbconvert_exporter": "python", 141 | "pygments_lexer": "ipython3", 142 | "version": "3.7.6" 143 | } 144 | }, 145 | "nbformat": 4, 146 | "nbformat_minor": 4 147 | } 148 | -------------------------------------------------------------------------------- /notebook/LIF.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "from tqdm import tqdm\n", 12 | " \n", 13 | "np.random.seed(seed=0)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "dt = 5e-5; T = 0.4 # (s)\n", 23 | "nt = round(T/dt) # シミュレーションのステップ数\n", 24 | " \n", 25 | "tref = 2e-3 # 不応期 (s)\n", 26 | "tc_m = 1e-2 # 膜時定数 (s)\n", 27 | "vrest = -60 # 静止膜電位 (mV) \n", 28 | "vreset = -65 # リセット電位 (mV) \n", 29 | "vthr = -40 # 閾値電位 (mV)\n", 30 | "vpeak = 30 # ピーク電位 (mV)\n", 31 | "\n", 32 | "t = np.arange(nt)*dt*1e3 # 時間(ms)\n", 33 | "I = 21*(t>50) - 21*(t>350) # 入力電流(に比例する値)(mV)\n", 34 | "\n", 35 | "# 初期化\n", 36 | "v = vreset # 膜電位の初期値\n", 37 | "tlast = 0 # 最後のスパイクの時間を記録する変数 \n", 38 | "v_arr = np.zeros(nt) # 膜電位を記録する配列" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 3, 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "name": "stderr", 48 | "output_type": "stream", 49 | "text": [ 50 | "100%|███████████████████████████████████████████████████████████████████████████| 8000/8000 [00:00<00:00, 58552.78it/s]\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "# シミュレーション\n", 56 | "for i in tqdm(range(nt)):\n", 57 | " dv = (vrest - v + I[i]) / tc_m # 膜電位の変化量\n", 58 | " v = v + ((dt*i) > (tlast + tref))*dv*dt # 更新\n", 59 | " \n", 60 | " s = 1*(v>=vthr) # 発火の確認\n", 61 | " tlast = tlast*(1-s) + dt*i*s # 発火時刻の更新\n", 62 | " v = v*(1-s) + vpeak*s # 発火している場合ピーク電位に更新\n", 63 | " v_arr[i] = v # 膜電位の値を保存\n", 64 | " v = v*(1-s) + vreset*s # 発火している場合に膜電位をリセット" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 8, 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "data": { 74 | "image/png": "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1nhx4sQJHTVqlDZt2lQBLVOmjN599936zjvv6KZNm7Rx48Z67733Rl3Pjh079NFHH9VzzjlHAb3wwgv1ySef1BkzZugnn3yigC5fvjyqOk6dOqVTpkzRa6+9VgEtWbKktm/fXl9//XVdu3at3nrrrdqqVauot2X//v06YMAArVy5sgJau3ZtfeSRR3TSpEm6aNEiBXT8+PFR17Nw4UK99dZbFVAR0TZt2uhLL72kK1eu1B49emjt2rWjriM1NVVfeuklrVmzpgJavXp1feihh3Ts2LH67bffKqDDhg2Lup6VK1dqly5dtESJEgpoixYtdPDgwbp06VIdMGCAOqkhOmlpafr2229rgwYNFNCKFSvq/fffr++//75+//33WqFCBX3iiSeirmfTpk3ao0cPLVWqlAI6fPjwqMv0GpCkOeXJnBbqmYn0XWAF8HecuYGfAp7K7315lHc5kOIm3u1ABk4/8HnAAGBAtnVnAa3yKzNeEvDcuXO1SZMmCujll1+uI0eO1J9//vmMdZo0aRJVAj569Kj2799fS5UqpSVLltSHHnpIly1bpqdOnTq9zvTp06NOwF9//bVec801CmjDhg319ddf15SUlDPWadu2bVQJ+OTJk/rKK69ouXLlVES0W7duOnfuXM3MzDy9zpo1a6JOwJs3b9a2bdsqoOedd54+//zzunPnzjPWefjhh6NKwKdOndIRI0ZotWrVFNB27drplClT9MSJE6fX+fnnn6NOwHv27NF7771XAa1cubIOGDBAv/322zPWGTRoUNQJeNKkSVqnTh0FtFWrVvrxxx/rsWPHzlinYsWKUSXggwcP6p///GctUaKEli1bVh977DGtW7eu/uY3v4kqdj9Ek4AH5fTI732RPs5qATcFVgGJQANgG87oi7hOwMePH9dHHnnkdLKaPHnyGQkxu2gS8MqVK7VRo0YKaM+ePXX79u05rhdNAs7IyNBnnnlGS5QooTVr1tT3339fT548meO60STgLVu26FVXXaWAduzYUdevX5/jetEk4FOnTulbb72lZcqU0YoVK+prr732qySSJZoE/MMPP+gtt9yigN5www365Zdf5rhetAl4/PjxWqlSJU1MTNRBgwbpoUOHclwvmgScmpp6OsFfccUVOmfOnFw/y9Ek4Pnz5+t5552nJUqU0Mcff1z37dunqqp9+/bVMmXK6JEjRwpVrl8KnYD1l0RZEagQ6foFKPd0AnafDwS2ApuAOyIpI8wJ+IcfftDmzZsroP369dPjx4/nuX5hE/BHH32kZcqU0dq1a+v8+fPzXDcrAS9btqxAdaSmpmqHDh0U0O7du+uBAwfyXL+wCXj27NlauXJlrVKlin722We5HuCqvyTgTz/9tEB1pKen6+9//3sF9I477tDdu3fnuf7DDz+stWrVKlAdqqpJSUlaq1YtLVu2rL777rtntN7PVtgEnJmZqf3791dAW7ZsqZs2bcpz/awEnNd+zcmWLVu0SZMmmpCQoM8///wZrfecFDYBv/7665qQkKBNmjTRpKSkM16bN2+eAjpp0qQCl+unaFrAzYE12boMVgFX5/e+WD7CmoB37NihF198sZ5zzjk6efLkiN5TmAT87rvvKqA33XTT6ZZCXgqTgA8dOqStWrXShIQE/de//hXRwVuYBDx16lQtXbq0XnHFFbpt27Z81y9MAk5LS9NOnTopoIMGDcozKWYpTAL+4osvtGLFilqvXj395ptv8l2/MAk4MzNTe/furYD+6U9/0vT09HzfU5gEvHHjRr3gggu0WrVq+X7BZylMAn722WcV0C5dumhqauqvXj9x4oRWrlxZe/ToUaBy/RZNAl4N3JDteWtgdX7vi+UjjAn4wIED2qRJE61YsaIuWbIk4vcVNAF/9NFHp1tx+bWus8yYMaNACTgtLU1bt26tJUuW1AkTJkQcW0ET8Lx587RUqVJ6zTXX5Nu6zrJ27doCJeDMzEzt2rWrAvrmm29GHFuvXr0KlIBXr16tFSpU0AsvvFB37NgR0XsKk4D79u2rgA4YMCDihDp48OACJeCdO3fq+eefr+eee66uWbMm4tgKmoCHDh2qgPbo0UMzMjJyXa9Xr15avnz5ItUNkVsCjuRKuMOq+p+sJ6q6BDgcwftMLk6ePEnXrl3ZunUr06ZN4/rrry/Q+53/z/x9+eWXPPzww9x0001MmjSJMmXKFCbcfGPp3bs3S5YsYcyYMdx9990Ffn8kNm/eTLdu3bjooouYPXs2VapUKUy4+Ro0aBATJkxg6NChPProo77UkZKSQseOHalQoQILFiygTp06BXp/pPts+PDhvPHGG/Tt25fnn3/el0uZjx49SqdOnThy5Ajz5s3jsssuK9D7I92Wzz//nL/85S9069aNkSNHkpCQkOu6PXr04MiRI0yePLlAsQQh1wQsIs1EpBnwlYi8KyJtROQmEXkbWBizCOPQc889x4IFC3jvvfe48cYbfanj0KFDdOvWjVq1ajFhwgQSExN9qWfEiBGMHj2awYMHc9999/lSx4kTJ7jnnnsoUaIE06ZNo3Llyr7UM3PmTJ577jl69erFU0895UsdqkrPnj3Zt28fU6ZMoXbt2r7Us2rVKvr06cMdd9zBsGHDfJtH4oknniA5OZlx48YVOPlGaufOnTz44INcddVVjBo1Ks/kC9C6dWvq16/P6NFF/65peV2IMeys54Oy/W3z4RXSsmXLeO655+jRowc9e/Ys8PsjPZD69u3L3r17Wb58OdWqVStwPZHYvn07Tz75JLfccgt///vfC/z+SLflf//3f1m1ahVTp06lYcOGBa4nEgcPHqRXr140bdqUN99807eE9d577zFz5kzeeustmjdvXqD3RhpTeno63bt3p1q1anz44Yf5JqzCmj59Ov/3f/9H//796dChQ4HfH8n2qCq9evUiIyOD8ePHc8455+T7nhIlStCjRw/+8Y9/sG3bNt8+M57IqV8ibI+w9AGfPHlSr7jiCq1bt+6vxvdG6pJLLtF77rknz3VmzpypgD7zzDOFqiPSPuB27dpphQoV9Pvvvy9UPbfffru2bNkyz3VWrVqlJUqU0IceeqhQdUTaB/yHP/xBS5Ys+auz6pGKpA94z549Wr58eW3btm2BRxioOqNMAB06dGie62WdqPr8888LXIdqZH3AR44c0Vq1aunll1+uaWlphaqnUqVK2rdv3zzXGTlypAL6zjvvFKjsXbt2acmSJfMtP1bIpQ84krsiVwZ68Mtt6bMS9+Pefx3Et+HDh7N69WrGjx9PxYoVfakjIyODfv360ahRIwYOHOhLHeD8XJ81axavvfaab5eCqyr9+vWjUqVKDB061Jc6wPm5PmLECJ544gmuvvpq3+r5+9//zokTJ/jXv/7lWwt7z549vPzyy3Tr1o0777zTlzoAhg4dyu7du/n000996946cuQIAwcOpGXLlvTu3btA761Vqxb3338/I0aM4Nlnn6VSpUq+xBitSE7CTcdJvmuAldkepgAOHz7MM888Q5s2beja1Yu5jHI2YsQI1q1bx5AhQyhdurQvdWQl+QsvvJD/+q//8qUOgBkzZjB37lwGDRpE1apVfavn6aefpkqVKoXqRonUqlWrGDlyJI899hiNGjXyrZ6//e1vZGRk8PLLL/tWx549exgyZAj33nsv1113nW/1vPLKK+zdu5dXX321UF9YTz75JEeOHOGtt97yITpvRDIbWhlV9eeMRDHyzjvv8NNPP/Hiiy/61vo5efIkL7zwAq1ataJLly6+1AEwYcIE1q9fz6effupbkldVnn32WRo0aMAjjzziSx0AX3zxBXPnzmXYsGG+jawA58RrxYoVff1V8t133zFq1Cgef/xxX/s9hw4dSnp6Oi+++KJvdfz888+8/vrrdO3alVatWhWqjGbNmtGxY0eGDBnCn//8Z1+/xAsrkhbwhyLyRxE5X0SqZj18jyyOHD9+nGHDhnHbbbfRsmVL3+oZN24cO3bsYODAgb4leVXl5ZdfpnHjxr625BctWsRXX33FX//6V9+SPMDLL79M1apV+dOf/uRbHZs3b2bChAk8+uijvib5YcOGkZCQwNNPP+1bHQcOHGD48OE88MADvib5d955h9TUVP7nf/4nqnJeeOEFUlNTeeGFFzyKzFuRJOATwCs4c/JmdT8k+RlUvBkzZgz79u3ztfWjqgwZMoTLLrusUGekIzV37ly++eYb/vKXv1CiREQ3VCmUIUOGcO655xZqpEik1q9fz9SpU3nssccoVy7qewzkaujQoZQuXZrHH/fvtMn+/fsZOXIk3bt393Qa0bO9/fbbHD16lL/+9a++1ZGens7rr79O27ZtadasWVRlXXbZZfz+97/njTfe8G3q0GhEcgQ9BVyoqvVVtYH7KMLjOoqe4cOHc9lll3HTTTf5VseSJUtYu3YtTz31lK/3Dnv77bepUaMGDz74oG91bNu2jRkzZvDoo49StmxZ3+p55513KF26NH369PGtjtTUVMaMGUP37t2pWbOmb/WMGjWK48eP069fP9/qyMzM5N133+X222/n8ssv962eyZMn88MPP3i2La+88gpVqlQ5PZytKIkkAa8DjvkdSLz6+uuvSUpKonfv3r4mxhEjRlChQgXuvfde3+rYt28fn3/+OT179vTtzDfA+++/j4jw8MMP+1ZHWloaY8aMoUuXLlSvXj3/NxTSuHHjOHbsGH/84x99q0NVGTFiBNddd52nE5yfbfbs2ezatcvXbQHns1y3bl3atm3rSXnVqlXjzTffJCkpyddfoYURyUm4TJz7wS0ATt8uwYahRea9996jbNmydO/e3bc6UlNTGT9+PL/73e98/Sk9evRoMjIy6NWrl291ZGZm8sEHH9CuXTvfrhIDp5WVdfGFn0aMGEHTpk255pprfKtj2bJlbNy4kREjRvhWBzjbUr16dTp16uRbHd9//z1z587lmWee8bSL695772XBggUMGTKE5s2bc88993hWdjQiScCT3YcpoJMnTzJ+/Hjuuusu3y6fBfjss884duyY58lEz7pOf9SoUVx33XU0adLE03qymz9/Prt27eK1117ztNyctqVevXrceuutvtWxceNGvvrqq0IPo4rUBx98QLly5Tz/9aOqp+M+ePAgU6dOpU+fPr6eFP3www8BeOihhzwv+/XXX2fVqlU8+OCDVK5c2bMWdjTy/YpR1VHAp8ByVR2V9fA/tPBbsGABP/30ky8HRnafffYZDRo0oEWLFp7Wk93GjRtZt24dDzzwgKfl5rQt5cuX5ze/+Y2n9WR38OBB5s6dy3333efricTPPvsMwPM5MrLvs4yMDCZNmkSnTp0oX768p/VkN3XqVE6ePBmT///rrruOevXqeVoPQGJiIp9//jmXXHIJnTt3ZurUqZ7XUVD5fvpEpCOQDMx0n18pIsFHHgLjx4+nfPnytG/f3rc6Dh06xNy5c+natatnraycypk4cSKAr+OLMzMzmTx5MnfeeadnM7fltC2ff/45GRkZng6jy6meCRMmcN1113HBBRd4Vs/ZlixZwo8//hiTbalTp06B568oiK1bt7Jq1SpfhzdWrVqVOXPmcNlll3HXXXfxwgsvkJmZ6Vt9+Ynk638wzq3hDwGoajLO7YJMHjIyMpg4cSKdOnXydBrIsw+Of//736ent/TTxIkTadmypadDnM7elqVLl5KSklLgKS0LauLEidSuXdvXZLJt2zaSk5M93ZbcvhjLli3r65f84cOHmT17NnfffbenXSlnl5X1Je/3/3+NGjVYuHAh9957LwMHDuTGG29k7dq1vtaZm0gScIaq/nzWMpsNLR9fffUVBw4coHPnzr7WM2nSJC644AJfux927NjBypUrfT8wJk2aRGJioq/jmI8dO8bMmTPp0qWLr90PWXPR+rnPVJVJkybRrl07X0++zpw5k/T09Jj8/1999dW+dD+c7ZxzzmHs2LGMGTOGDRs2cMUVV9CjR4+YJ+JIPoFrReS3QIKI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\n", 75 | "text/plain": [ 76 | "
" 77 | ] 78 | }, 79 | "metadata": { 80 | "needs_background": "light" 81 | }, 82 | "output_type": "display_data" 83 | } 84 | ], 85 | "source": [ 86 | "# 描画\n", 87 | "\n", 88 | "plt.figure(figsize=(5, 5))\n", 89 | "plt.subplot(2,1,1)\n", 90 | "plt.plot(t, v_arr, color=\"k\")\n", 91 | "plt.ylabel('Membrane potential (mV)') \n", 92 | "plt.xlim(0, t.max())\n", 93 | "\n", 94 | "plt.subplot(2,1,2)\n", 95 | "plt.plot(t, I, color=\"k\")\n", 96 | "plt.ylabel('Membrane potential (mV)') \n", 97 | "plt.xlim(0, t.max())\n", 98 | "plt.tight_layout()\n", 99 | "plt.show()" 100 | ] 101 | } 102 | ], 103 | "metadata": { 104 | "kernelspec": { 105 | "display_name": "Python 3", 106 | "language": "python", 107 | "name": "python3" 108 | }, 109 | "language_info": { 110 | "codemirror_mode": { 111 | "name": "ipython", 112 | "version": 3 113 | }, 114 | "file_extension": ".py", 115 | "mimetype": "text/x-python", 116 | "name": "python", 117 | "nbconvert_exporter": "python", 118 | "pygments_lexer": "ipython3", 119 | "version": "3.7.6" 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 4 124 | } 125 | -------------------------------------------------------------------------------- /notebook/images/parallel_conductance_model.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takyamamoto/SNN-from-scratch-with-Python/8e5873acfb804564319ec92edfb297fbd4feea5f/notebook/images/parallel_conductance_model.JPG -------------------------------------------------------------------------------- /pdf/ゼロから作る Spiking Neural Networks第1版_正誤表.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takyamamoto/SNN-from-scratch-with-Python/8e5873acfb804564319ec92edfb297fbd4feea5f/pdf/ゼロから作る Spiking Neural Networks第1版_正誤表.pdf -------------------------------------------------------------------------------- /pdf/ゼロから作るSpiking_Neural_Networks_2版_サンプル.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takyamamoto/SNN-from-scratch-with-Python/8e5873acfb804564319ec92edfb297fbd4feea5f/pdf/ゼロから作るSpiking_Neural_Networks_2版_サンプル.pdf --------------------------------------------------------------------------------