From 56726a5fc54354a43a88701e257c478e41142856 Mon Sep 17 00:00:00 2001 From: Kenji Marshall Date: Tue, 22 Feb 2022 10:10:50 -0500 Subject: [PATCH 1/5] Make memory workflow compatible with MSSNetwor --- conn2res/reservoir.py | 29 +++++++++++++++--------- conn2res/task.py | 2 +- conn2res/workflows.py | 44 +++++++++++++++++++++++++++++-------- examples/memory_capacity.py | 18 +++++++-------- 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/conn2res/reservoir.py b/conn2res/reservoir.py index 5638025..24f2f7f 100755 --- a/conn2res/reservoir.py +++ b/conn2res/reservoir.py @@ -8,7 +8,7 @@ import itertools as itr import numpy as np import numpy.ma as ma -from numpy.linalg import (inv, matrix_rank) +from numpy.linalg import (pinv, matrix_rank) import matplotlib.pyplot as plt @@ -348,7 +348,7 @@ def solveVi(self, Ve, Vgr=None, G=None, **kwargs): # inverse matrix A_II A_II = A[np.ix_(self._I, self._I)] # print(matrix_rank(A_II, hermitian=check_symmetric(A_II))) - A_II_inv = inv(A_II) + A_II_inv = pinv(A_II) # matrix HI H_IE = np.dot(G[np.ix_(self._I, self._E)], Ve) @@ -406,7 +406,7 @@ def simulate(self, Vext, ic=None, mode='forward'): Parameters ---------- - ext_input : (time, N_external_nodes) numpy.ndarray + Vext : (time, N_external_nodes) numpy.ndarray External input signal N_external_nodes: number of external (input) nodes ic : (N_internal_nodes,) numpy.ndarray @@ -445,7 +445,10 @@ def simulate(self, Vext, ic=None, mode='forward'): # get voltage at internal nodes Vi.append(self.iterate(Ve)) - return np.asarray(Vi) + V = np.zeros((len(Vi), self._n_nodes)) + V[:, self._I] = np.asarray(Vi) + V[:, self._E] = Vext + return V def iterate(self, Ve, tol=5e-2, iters=100): @@ -617,8 +620,10 @@ def __init__(self, vA=0.17, vB=0.22, tc=0.32e-3, NMSS=10000,\ self.NMSS = self.init_property(NMSS, noise) # constant self.Woff = self.init_property(Woff, noise) # constant self.Won = self.init_property(Won, noise) # constant - self._Ga = mask(self, np.divide(self.Woff, self.NMSS)) # constant - self._Gb = mask(self, np.divide(self.Won, self.NMSS)) # constant + self._Ga = mask(self, np.divide(self.Woff, self.NMSS, + where=self.NMSS != 0)) # constant + self._Gb = mask(self, np.divide(self.Won, self.NMSS, + where=self.NMSS != 0)) # constant self._Nb = self.init_property(Nb, noise) self._G = self._Nb * (self._Gb - self._Ga) + self.NMSS * self._Ga @@ -644,13 +649,15 @@ def dG(self, V, G=None, dt=1e-4): # set Nb values if G is not None: - Nb = mask(self, (G - self.NMSS * self._Ga)/(self._Gb - self._Ga)) + Gdiff1 = G - self.NMSS * self._Ga + Gdiff2 = self._Gb - self._Ga + Nb = mask(self, np.divide(Gdiff1, Gdiff2, where=Gdiff2 != 0)) else: Nb = self._Nb - # ration of dt to characterictic time of the device tc - alpha = dt/self.tc + # ratio of dt to characterictic time of the device tc + alpha = np.divide(dt, self.tc, where=self.tc != 0) # compute Pa exponent = -1 * (V - self.vA) / self.VT @@ -740,4 +747,6 @@ def check_square(a): def reservoir(name, **kwargs): if name == 'EchoStateNetwork': - return EchoStateNetwork(**kwargs) \ No newline at end of file + return EchoStateNetwork(**kwargs) + if name == 'MSSNetwork': + return MSSNetwork(**kwargs) diff --git a/conn2res/task.py b/conn2res/task.py index 138d617..8fd6761 100755 --- a/conn2res/task.py +++ b/conn2res/task.py @@ -9,7 +9,7 @@ import numpy as np import pandas as pd import scipy as sp -import mdp +# import mdp from sklearn import metrics from sklearn.model_selection import ParameterGrid diff --git a/conn2res/workflows.py b/conn2res/workflows.py index 7c082c8..d971efe 100644 --- a/conn2res/workflows.py +++ b/conn2res/workflows.py @@ -15,7 +15,8 @@ from . import iodata, reservoir, coding -def memory_capacity(conn, input_nodes, output_nodes, readout_modules=None, + +def memory_capacity(conn, input_nodes, output_nodes, rsn_mapping=None, readout_nodes=None, resname='EchoStateNetwork', alphas=None, input_gain=1.0, tau_max=20, plot_res=False, plot_title=None): @@ -50,23 +51,48 @@ def memory_capacity(conn, input_nodes, output_nodes, readout_modules=None, w_in = np.zeros((1, n_reservoir_nodes)) w_in[:,input_nodes] = input_gain + # if using MSSNetwork, must restructure nodes and input + if resname == 'MSSNetwork': + # select random node as ground from output nodes + gr_nodes = np.random.choice(output_nodes, 1) + output_nodes = np.setdiff1d(output_nodes, gr_nodes) + + # remove ground node from readout_nodes if necessary + if readout_nodes is not None: + readout_nodes = np.setdiff1d(readout_nodes, gr_nodes) + + # second dimension should be along the input nodes + x = np.tile(x, (1, len(input_nodes))) + + # establish readout modules + readout_modules = rsn_mapping[output_nodes] + # evaluate network performance across various dynamical regimes if alphas is None: alphas = np.linspace(0,2,11) df = [] - for alpha in alphas: + for alpha in alphas[1:]: print(f'\n----------------------- alpha = {alpha} -----------------------') - # instantiate an Echo State Network object - network = reservoir.reservoir(name=resname, - w_ih=w_in, - w_hh=alpha*conn.copy(), - activation_function='tanh' - ) + if resname == 'EchoStateNetwork': + # instantiate an Echo State Network object + network = reservoir.reservoir(name=resname, + w_ih=w_in, + w_hh=alpha * conn.copy(), + activation_function='tanh' + ) + elif resname == 'MSSNetwork': + # instantiate an MSS Network object + network = reservoir.reservoir(name=resname, + w=alpha * conn.copy(), + i_nodes=output_nodes, + e_nodes=input_nodes, + gr_nodes=gr_nodes + ) # simulate reservoir states; select only output nodes - rs = network.simulate(ext_input=x)[:,output_nodes] + rs = network.simulate(x)[:, output_nodes] # remove first tau_max points from reservoir states rs = rs[tau_max:] diff --git a/examples/memory_capacity.py b/examples/memory_capacity.py index ba12a8d..3464a0e 100644 --- a/examples/memory_capacity.py +++ b/examples/memory_capacity.py @@ -39,13 +39,13 @@ from conn2res import workflows MC = workflows.memory_capacity(conn=conn, - input_nodes=input_nodes, - output_nodes=output_nodes, - readout_modules=rsn_mapping[output_nodes], - resname='EchoStateNetwork', - alphas=np.linspace(0,4,21), - input_gain=1.0, - tau_max=16, - plot_res=True, - ) + input_nodes=input_nodes, + output_nodes=output_nodes, + rsn_mapping=rsn_mapping, + resname='MSSNetwork', + alphas=np.linspace(0, 4, 21), + input_gain=1.0, + tau_max=16, + plot_res=True, + ) From 110ab4c5131a93cd073213c9a7a71a031569382c Mon Sep 17 00:00:00 2001 From: Kenji Marshall Date: Tue, 22 Feb 2022 10:33:12 -0500 Subject: [PATCH 2/5] Update comment --- examples/memory_capacity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/memory_capacity.py b/examples/memory_capacity.py index 3464a0e..11e7c75 100644 --- a/examples/memory_capacity.py +++ b/examples/memory_capacity.py @@ -35,7 +35,8 @@ # define different sets of output nodes rsn_mapping = np.load(os.path.join(DATA_DIR, 'rsn_mapping.npy')) -# evaluate the memory capacity of an echo state network (default) +# Evaluate the memory capacity of an echo state network or +# metastable switch memristor network from conn2res import workflows MC = workflows.memory_capacity(conn=conn, From 274639340c158c6fc2f34c0103cc577da1c0abd1 Mon Sep 17 00:00:00 2001 From: Kenji Marshall Date: Tue, 1 Mar 2022 19:35:19 -0500 Subject: [PATCH 3/5] Unify MemristiveReservoir/EchoStateNetwork in OOP framework --- conn2res/reservoir.py | 262 +++++++++++------- conn2res/workflows.py | 62 +++-- examples/echo_state_network.py | 4 +- examples/echo_state_network_multiple_subjs.py | 4 +- examples/echo_state_network_multiple_tasks.py | 6 +- examples/memory_capacity.py | 1 + examples/memristive_network.py | 13 +- 7 files changed, 216 insertions(+), 136 deletions(-) diff --git a/conn2res/reservoir.py b/conn2res/reservoir.py index 24f2f7f..139acc6 100755 --- a/conn2res/reservoir.py +++ b/conn2res/reservoir.py @@ -6,6 +6,7 @@ """ import itertools as itr +from tkinter import NONE import numpy as np import numpy.ma as ma from numpy.linalg import (pinv, matrix_rank) @@ -20,41 +21,52 @@ class Reservoir: Attributes ---------- - w_ih : numpy.ndarray - input connectivity matrix (source, target) - w_hh : numpy.ndarray + w : (N, N) numpy.ndarray reservoir connectivity matrix (source, target) - _state : numpy.ndarray - reservoir activation states - input_size : int - dimension of feature space + N: number of nodes in the network. If w is directed, then rows + (columns) should correspond to source (target) nodes. hidden_size : int - dimension of the reservoir + dimension of the reservoir (N) Methods ---------- """ - def __init__(self, w_ih, w_hh): + def __init__(self, w): """ Constructor class for general Reservoir Networks Parameters ---------- - w_ih: (N_inputs, N) numpy.ndarray - input connectivity matrix (source, target) - N_inputs: number of external input nodes - N: number of nodes in the network - w_hh : (N, N) numpy.ndarray + w : (N, N) numpy.ndarray reservoir connectivity matrix (source, target) - N: number of nodes in the network. If w_hh is directed, then rows + N: number of nodes in the network. If w is directed, then rows (columns) should correspond to source (target) nodes. """ - self.w_ih = w_ih - self.w_hh = w_hh + self.w = w + self.hidden_size = w.shape[0] + self._state = None - self.input_size, self.hidden_size = w_ih.shape + + + def simulate(ext_input): + """ + Abstract method for simulating reservoir dynamics given an external + input signal 'ext_input' + + Parameters + ---------- + ext_input : (L, inputs) numpy.ndarray + external input signal + + Returns + ------- + self._state : (L, N) numpy.ndarray + activation states of the reservoir; includes all the nodes + """ + + raise NotImplementedError("'simulate' not implemented yet.") class EchoStateNetwork(Reservoir): @@ -65,66 +77,73 @@ class EchoStateNetwork(Reservoir): Attributes ---------- - w_ih : numpy.ndarray - input connectivity matrix (source, target) - w_hh : numpy.ndarray + w : (N, N) numpy.ndarray reservoir connectivity matrix (source, target) - _state : numpy.ndarray - reservoir activation states - input_size : int - dimension of feature space + N: number of nodes in the network. If w is directed, then rows + (columns) should correspond to source (target) nodes. hidden_size : int - dimension of the reservoir + dimension of the reservoir (N) + w_in : (N_inputs, N) numpy.ndarray + input connectivity matrix (source, target) + N_inputs: number of expected inputs + N: number of nodes in the network + input_size : int + dimension of feature space (N_inputs) activation_function : {'tanh', 'piecewise'} type of activation function + ic : (N,) numpy.ndarray + Initial conditions + N: number of nodes in the network Methods ------- """ - def __init__(self, activation_function='tanh', *args, **kwargs): + def __init__(self, w, w_in, activation_function='tanh', ic=None): """ Constructor class for Echo State Networks Parameters ---------- - w_ih: (N_inputs, N) numpy.ndarray - Input connectivity matrix (source, target) - N_inputs: number of external input nodes - N: number of nodes in the network - w_hh : (N, N) numpy.ndarray + w : (N, N) numpy.ndarray Reservoir connectivity matrix (source, target) - N: number of nodes in the network. If w_hh is directed, then rows + N: number of nodes in the network. If w is directed, then rows (columns) should correspond to source (target) nodes. + w_in : (N_inputs, N) numpy.ndarray + Input connectivity matrix (source, target) + N_inputs: dimension of feature space + N: number of nodes in the network nonlinearity : str {'tanh', 'relu'}, default 'tanh' Activation function + ic : (N,) numpy.ndarray + Initial conditions + N: number of nodes in the network. """ - super().__init__(*args, **kwargs) + super().__init__(w) + self.w_in = w_in + self.input_size = w_in.shape[0] self.activation_function = self.set_activation_function(activation_function) + self.ic = ic - def simulate(self, ext_input, ic=None, threshold=0.5): + def simulate(self, ext_input): """ - Simulates reservoir dynamics given an external input signal - 'ext_input' + Simulates EchoStateNetwork given an external input signal 'ext_input' Parameters ---------- - ext_input: (time, N_inputs) numpy.ndarray + ext_input : (L, N_inputs) numpy.ndarray External input signal - N_inputs: number of external input nodes - ic: (N,) numpy.ndarray - Initial conditions - N: number of nodes in the network. If w_hh is directed, then rows - (columns) should correspond to source (target) nodes. - threshold : float - Threshold for piecewise nonlinearity. Ignored for the others. - + L: length of input + N_inputs: dimension of feature space + Returns ------- - self._state : numpy.ndarray + self._state : (L, N) numpy.ndarray activation states of the reservoir; includes all the nodes + L: length of input + N: number of nodes in the network """ print('\n GENERATING RESERVOIR STATES ...') @@ -137,14 +156,14 @@ def simulate(self, ext_input, ic=None, threshold=0.5): self._state = np.zeros((len(timesteps)+1, self.hidden_size)) # set initial conditions - if ic is not None: self._state[0,:] = ic + if self.ic is not None: self._state[0,:] = self.ic # simulation of the dynamics for t in timesteps: if (t>0) and (t%100 == 0): print(f'\t ----- timestep = {t}') - synap_input = np.dot(self._state[t-1,:], self.w_hh) + np.dot(ext_input[t-1,:], self.w_ih) + synap_input = np.dot(self._state[t-1,:], self.w) + np.dot(ext_input[t-1,:], self.w_in) self._state[t,:] = self.activation_function(synap_input) return self._state @@ -190,7 +209,7 @@ def step(x, thr=0.5, vmin=0, vmax=1): return step -class MemristiveReservoir: +class MemristiveReservoir(Reservoir): """ Class that represents a general Memristive Reservoir @@ -198,22 +217,33 @@ class MemristiveReservoir: Attributes ---------- + w : (N, N) numpy.ndarray + reservoir connectivity matrix (source, target) + N: number of nodes in the network. + hidden_size : int + dimension of the reservoir (N) W : numpy.ndarray - reservoir's binary connectivity matrix + binarized and symmetric reservoir connectivity matrix I : numpy.ndarray - set of internal nodes + indices of internal nodes (voltage not in contact with input signal) E : numpy.ndarray - set of external nodes + indices of external nodes (voltage manipulated by input signal) GR : numpy.ndarray - set of grounded nodes + indices of grounded nodes (held at a constant voltage) n_internal_nodes : int number of internal nodes n_external_nodes : int number of external nodes n_grounded_nodes : int - number of gorunded nodes + number of grounded nodes + n_nodes : int + number of total nodes (sum of internal, external, and grounded nodes) G : numpy.ndarray matrix of conductances + mode : str {'forward', 'backward'} + Refers to the method used to solve the system of equations. + Use 'forward' for explicit Euler method, and 'backward' for + implicit Euler method. Methods ---------- @@ -224,7 +254,7 @@ class MemristiveReservoir: #TODO """ - def __init__(self, w, i_nodes, e_nodes, gr_nodes, *args, **kwargs): + def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward'): """ Constructor class for Memristive Networks. Memristive networks are an abstraction for physical networks of memristive elements. @@ -235,20 +265,24 @@ def __init__(self, w, i_nodes, e_nodes, gr_nodes, *args, **kwargs): reservoir's binary connectivity matrix N: total number of nodes in the network (internal + external + grounded nodes) - i_nodes : (n_internal_nodes,) numpy.ndarray + int_nodes : (n_internal_nodes,) numpy.ndarray indexes of internal nodes n_internal_nodes: number of internal nodes - e_nodes : (n_external_nodes,) numpy.ndarray + ext_nodes : (n_external_nodes,) numpy.ndarray indexes of external nodes n_external_nodes: number of external nodes gr_nodes : (n_grounded_nodes,) numpy.ndarray indexes of grounded nodes n_grounded_nodes: number of grounded nodes + mode : {'forward', 'backward'} + Refers to the method used to solve the system of equations. + Use 'forward' for explicit Euler method, and 'backward' for + implicit Euler method. """ - super().__init__(*args, **kwargs) + super().__init__(w) self._W = self.setW(w) - self._I = np.asarray(i_nodes) - self._E = np.asarray(e_nodes) + self._I = np.asarray(int_nodes) + self._E = np.asarray(ext_nodes) self._GR = np.asarray(gr_nodes) self._n_internal_nodes = len(self._I) @@ -258,6 +292,7 @@ def __init__(self, w, i_nodes, e_nodes, gr_nodes, *args, **kwargs): self._G = None + self.mode = mode def setW(self, w): """ @@ -399,56 +434,66 @@ def getV(self, Vi, Ve, Vgr=None): return mask(self, V) - def simulate(self, Vext, ic=None, mode='forward'): + def simulate(self, ext_input): """ Simulates the dynamics of a memristive reservoir given an external - voltage signal V_E + voltage signal ext_input Parameters ---------- - Vext : (time, N_external_nodes) numpy.ndarray - External input signal - N_external_nodes: number of external (input) nodes - ic : (N_internal_nodes,) numpy.ndarray - Initial conditions - N_internal_nodes: number of internal (output) nodes - mode : {'forward', 'backward'} - Refers to the method used to solve the system of equations. - Use 'forward' for explicit Euler method, and 'backward' for - implicit Euler method. + ext_input : (L, n_external_nodes) numpy.ndarray + External input signal (applied directly to external nodes so must + be of same dimensionality) + n_external_nodes: number of external nodes - #TODO + Returns + ------- + self._state : (L, N) numpy.ndarray + activation states of the reservoir; includes all the nodes + L: length of input + N: number of nodes in the network """ print('\n GENERATING RESERVOIR STATES ...') - Vi = [] - if mode == 'forward': - for t, Ve in enumerate(Vext): + # initialize reservoir states + self._state = np.zeros((len(ext_input), self.hidden_size)) + + if self.mode == 'forward': + for t, Ve in enumerate(ext_input): if (t>0) and (t%100 == 0): print(f'\t ----- timestep = {t}') + # store external voltages + self._state[t, self._E] = Ve + # get voltage at internal nodes - Vi.append(self.solveVi(Ve)) + Vi = self.solveVi(Ve) # update matrix of voltages across memristors - V = self.getV(Vi[-1], Ve) + V = self.getV(Vi, Ve) # update conductance self.updateG(V=V, update=True) + + # store state + self._state[t, self._I] = Vi - elif mode == 'backward': - for t,Ve in enumerate(Vext): + elif self.mode == 'backward': + for t,Ve in enumerate(ext_input): if (t>0) and (t%100 == 0): print(f'\t ----- timestep = {t}') + # store external voltages + self._state[t, self._E] = Ve + # get voltage at internal nodes - Vi.append(self.iterate(Ve)) + Vi = self.iterate(Ve) + + # store state + self._state[t, self._I] = Vi - V = np.zeros((len(Vi), self._n_nodes)) - V[:, self._I] = np.asarray(Vi) - V[:, self._E] = Vext - return V + return self._state def iterate(self, Ve, tol=5e-2, iters=100): @@ -522,22 +567,33 @@ class MSSNetwork(MemristiveReservoir): Attributes ---------- + w : (N, N) numpy.ndarray + reservoir connectivity matrix (source, target) + N: number of nodes in the network. + hidden_size : int + dimension of the reservoir (N) W : numpy.ndarray - reservoir's binary connectivity matrix + binarized and symmetric reservoir connectivity matrix I : numpy.ndarray - set of internal nodes + indices of internal nodes (voltage not in contact with input signal) E : numpy.ndarray - set of external nodes + indices of external nodes (voltage manipulated by input signal) GR : numpy.ndarray - set of grounded nodes + indices of grounded nodes (held at a constant voltage) n_internal_nodes : int number of internal nodes n_external_nodes : int number of external nodes n_grounded_nodes : int - number of gorunded nodes + number of grounded nodes + n_nodes : int + number of total nodes (sum of internal, external, and grounded nodes) G : numpy.ndarray matrix of conductances + mode : str {'forward', 'backward'} + Refers to the method used to solve the system of equations. + Use 'forward' for explicit Euler method, and 'backward' for + implicit Euler method. vA : numpy.ndarray of floats vB : numpy.ndarray of floats @@ -571,8 +627,9 @@ class MSSNetwork(MemristiveReservoir): b = Q/(k*Temp) VT = 1/b - def __init__(self, vA=0.17, vB=0.22, tc=0.32e-3, NMSS=10000,\ - Woff=0.91e-3, Won=0.87e-2, Nb=2000, noise=0.1, *args, **kwargs): + def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward', + vA=0.17, vB=0.22, tc=0.32e-3, NMSS=10000, Woff=0.91e-3, + Won=0.87e-2, Nb=2000, noise=0.1): """ Constructor class for Memristive Networks following the Generalized Memristive Switch Model proposed in Nugent and Molter, 2014. Default @@ -585,10 +642,23 @@ def __init__(self, vA=0.17, vB=0.22, tc=0.32e-3, NMSS=10000,\ reservoir's binary connectivity matrix N: total number of nodes in the network (internal + external + grounded nodes) - i_nodes : (n_internal_nodes,) numpy.ndarray + int_nodes : (n_internal_nodes,) numpy.ndarray + indexes of internal nodes + n_internal_nodes: number of internal nodes + ext_nodes : (n_external_nodes,) numpy.ndarray + indexes of external nodes + n_external_nodes: number of external nodes + gr_nodes : (n_grounded_nodes,) numpy.ndarray + indexes of grounded nodes + n_grounded_nodes: number of grounded nodes + mode : {'forward', 'backward'} + Refers to the method used to solve the system of equations. + Use 'forward' for explicit Euler method, and 'backward' for + implicit Euler method. + int_nodes : (n_internal_nodes,) numpy.ndarray indexes of internal nodes n_internal_nodes: number of internal nodes - e_nodes : (n_external_nodes,) numpy.ndarray + ext_nodes : (n_external_nodes,) numpy.ndarray indexes of external nodes n_external_nodes: number of external nodes gr_nodes : (n_grounded_nodes,) numpy.ndarray @@ -612,7 +682,7 @@ def __init__(self, vA=0.17, vB=0.22, tc=0.32e-3, NMSS=10000,\ #TODO """ - super().__init__(*args, **kwargs) + super().__init__(w, int_nodes, ext_nodes, gr_nodes, mode) self.vA = self.init_property(vA, noise) # constant self.vB = self.init_property(vB, noise) # constant diff --git a/conn2res/workflows.py b/conn2res/workflows.py index d971efe..65d20c5 100644 --- a/conn2res/workflows.py +++ b/conn2res/workflows.py @@ -19,7 +19,7 @@ def memory_capacity(conn, input_nodes, output_nodes, rsn_mapping=None, readout_nodes=None, resname='EchoStateNetwork', alphas=None, input_gain=1.0, tau_max=20, plot_res=False, - plot_title=None): + plot_title=None, res_kwargs=None): """ #TODO Function that measures the memory capacity of a reservoir as @@ -35,24 +35,32 @@ def memory_capacity(conn, input_nodes, output_nodes, rsn_mapping=None, data frame with task scores """ - # scale conenctivity weights between [0,1] - conn = (conn-conn.min())/(conn.max()-conn.min()) + conn = (conn - conn.min()) / (conn.max() - conn.min()) n_reservoir_nodes = len(conn) # normalize connectivity matrix by the spectral radius ew, _ = eigh(conn) - conn = conn/np.max(ew) + conn = conn / np.max(ew) - # get dataset for memory capacity task + # get dataset for memory capacity task x, y = iodata.fetch_dataset('MemoryCapacity', tau_max=tau_max) - # create input connectivity matrix - w_in = np.zeros((1, n_reservoir_nodes)) - w_in[:,input_nodes] = input_gain + # EchoStateNetwork setup + if resname == 'EchoStateNetwork': + + # create input connectivity matrix + w_in = np.zeros((1, n_reservoir_nodes)) + w_in[:, input_nodes] = input_gain + + # init parameters + if res_kwargs is None: + res_kwargs = {} + + res_kwargs['w_in'] = w_in - # if using MSSNetwork, must restructure nodes and input - if resname == 'MSSNetwork': + # MSSNetwork setup + elif resname == 'MSSNetwork': # select random node as ground from output nodes gr_nodes = np.random.choice(output_nodes, 1) output_nodes = np.setdiff1d(output_nodes, gr_nodes) @@ -61,11 +69,23 @@ def memory_capacity(conn, input_nodes, output_nodes, rsn_mapping=None, if readout_nodes is not None: readout_nodes = np.setdiff1d(readout_nodes, gr_nodes) - # second dimension should be along the input nodes + # second dimension must equal number of external nodes for MSSNetwork x = np.tile(x, (1, len(input_nodes))) + # set-up init parameters + if res_kwargs is None: + res_kwargs = {} + + res_kwargs['int_nodes'] = output_nodes + res_kwargs['ext_nodes'] = input_nodes + res_kwargs['gr_nodes'] = gr_nodes + + # establish readout modules - readout_modules = rsn_mapping[output_nodes] + if rsn_mapping is not None: + readout_modules = rsn_mapping[output_nodes] + else: + readout_modules = None # evaluate network performance across various dynamical regimes if alphas is None: alphas = np.linspace(0,2,11) @@ -75,21 +95,9 @@ def memory_capacity(conn, input_nodes, output_nodes, rsn_mapping=None, print(f'\n----------------------- alpha = {alpha} -----------------------') - if resname == 'EchoStateNetwork': - # instantiate an Echo State Network object - network = reservoir.reservoir(name=resname, - w_ih=w_in, - w_hh=alpha * conn.copy(), - activation_function='tanh' - ) - elif resname == 'MSSNetwork': - # instantiate an MSS Network object - network = reservoir.reservoir(name=resname, - w=alpha * conn.copy(), - i_nodes=output_nodes, - e_nodes=input_nodes, - gr_nodes=gr_nodes - ) + network = reservoir.reservoir(name=resname, + w=alpha * conn.copy(), + **res_kwargs) # simulate reservoir states; select only output nodes rs = network.simulate(x)[:, output_nodes] diff --git a/examples/echo_state_network.py b/examples/echo_state_network.py index 0835a98..ede114f 100755 --- a/examples/echo_state_network.py +++ b/examples/echo_state_network.py @@ -108,8 +108,8 @@ print(f'\n----------------------- alpha = {alpha} -----------------------') # instantiate an Echo State Network object - ESN = reservoir.EchoStateNetwork(w_ih=w_in, - w_hh=alpha*conn.copy(), + ESN = reservoir.EchoStateNetwork(w=alpha * conn.copy(), + w_in=w_in, activation_function='tanh', ) diff --git a/examples/echo_state_network_multiple_subjs.py b/examples/echo_state_network_multiple_subjs.py index 07c4bb2..4bbb928 100644 --- a/examples/echo_state_network_multiple_subjs.py +++ b/examples/echo_state_network_multiple_subjs.py @@ -98,8 +98,8 @@ print(f'\n----------------------- alpha = {alpha} -----------------------') # instantiate an Echo State Network object - ESN = reservoir.EchoStateNetwork(w_ih=w_in, - w_hh=alpha*w, + ESN = reservoir.EchoStateNetwork(w=alpha * w, + w_in=w_in, activation_function='tanh', ) diff --git a/examples/echo_state_network_multiple_tasks.py b/examples/echo_state_network_multiple_tasks.py index 15b1b9b..c1c85fa 100644 --- a/examples/echo_state_network_multiple_tasks.py +++ b/examples/echo_state_network_multiple_tasks.py @@ -111,9 +111,9 @@ print(f'\n----------------------- alpha = {alpha} -----------------------') # instantiate an Echo State Network object - ESN = reservoir.EchoStateNetwork(w_ih=w_in, - w_hh=alpha*conn.copy(), - activation_function='tanh', + ESN = reservoir.EchoStateNetwork(w=alpha * conn.copy(), + w_in=w_in, + activation_function='tanh', ) # simulate reservoir states; select only output nodes. diff --git a/examples/memory_capacity.py b/examples/memory_capacity.py index 11e7c75..9334e73 100644 --- a/examples/memory_capacity.py +++ b/examples/memory_capacity.py @@ -48,5 +48,6 @@ input_gain=1.0, tau_max=16, plot_res=True, + res_kwargs={'mode': 'forward'} ) diff --git a/examples/memristive_network.py b/examples/memristive_network.py index 2444c01..67f1547 100755 --- a/examples/memristive_network.py +++ b/examples/memristive_network.py @@ -48,7 +48,7 @@ from conn2res import iodata import matplotlib.pyplot as plt -task = 'GoNogo' #'PerceptualDecisionMaking' # +task = 'MemoryCapacity' x, y = iodata.fetch_dataset(task) # # visualizing input/output data @@ -105,14 +105,15 @@ # instantiate an Memristive Network object MMN = reservoir.MSSNetwork(w=alpha*conn.copy(), - i_nodes=int_nodes, - e_nodes=ext_nodes, - gr_nodes=gr_nodes + int_nodes=int_nodes, + ext_nodes=ext_nodes, + gr_nodes=gr_nodes, + mode='backward' ) # simulate reservoir states; select only readout nodes. - rs_train = MMN.simulate(Vext=x_train[:], mode='backward')[:,readout_nodes] - rs_test = MMN.simulate(Vext=x_test[:], mode='backward')[:,readout_nodes] + rs_train = MMN.simulate(ext_input=x_train[:])[:, readout_nodes] + rs_test = MMN.simulate(ext_input=x_test[:])[:, readout_nodes] # perform task df = coding.encoder(reservoir_states=(rs_train, rs_test), From 667a04c9e5677c5f0b29071838346b7707072fa9 Mon Sep 17 00:00:00 2001 From: Kenji Marshall Date: Sun, 6 Mar 2022 16:35:58 -0500 Subject: [PATCH 4/5] Remove mean from MSSNetwork states --- conn2res/reservoir.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conn2res/reservoir.py b/conn2res/reservoir.py index 139acc6..128189e 100755 --- a/conn2res/reservoir.py +++ b/conn2res/reservoir.py @@ -493,6 +493,10 @@ def simulate(self, ext_input): # store state self._state[t, self._I] = Vi + # center internal voltage measurements + self._state[:, self._I] = self._state[:, self._I] - \ + np.mean(self._state[:, self._I], axis=1, keepdims=True) + return self._state From d170af24a697ac0fa260950a6ffaab790badcaec Mon Sep 17 00:00:00 2001 From: Kenji Marshall Date: Mon, 7 Mar 2022 15:46:46 -0500 Subject: [PATCH 5/5] Add option to save conductance states over time --- conn2res/reservoir.py | 61 +++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/conn2res/reservoir.py b/conn2res/reservoir.py index 128189e..8bbd607 100755 --- a/conn2res/reservoir.py +++ b/conn2res/reservoir.py @@ -240,10 +240,16 @@ class MemristiveReservoir(Reservoir): number of total nodes (sum of internal, external, and grounded nodes) G : numpy.ndarray matrix of conductances + G_history : numpy.ndarray + history of conducatnces across simulation mode : str {'forward', 'backward'} Refers to the method used to solve the system of equations. Use 'forward' for explicit Euler method, and 'backward' for implicit Euler method. + save_conductance : bool + Indicates whether to save conductance state after each simulation + step. If True, then will be stored in self._G_history. This will + increase memory demands. Methods ---------- @@ -254,7 +260,8 @@ class MemristiveReservoir(Reservoir): #TODO """ - def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward'): + def __init__(self, w, int_nodes, ext_nodes, gr_nodes, + mode='forward', save_conductance=False): """ Constructor class for Memristive Networks. Memristive networks are an abstraction for physical networks of memristive elements. @@ -274,10 +281,15 @@ def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward'): gr_nodes : (n_grounded_nodes,) numpy.ndarray indexes of grounded nodes n_grounded_nodes: number of grounded nodes - mode : {'forward', 'backward'} + mode : {'forward', 'backward'}, optional Refers to the method used to solve the system of equations. Use 'forward' for explicit Euler method, and 'backward' for - implicit Euler method. + implicit Euler method. Default: 'forward' + save_conductance : bool, optional + Indicates whether to save conductance state after each simulation + step. If True, then will be stored in self._G_history. This will + increase memory demands. Default: False + """ super().__init__(w) self._W = self.setW(w) @@ -291,8 +303,10 @@ def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward'): self._n_nodes = len(self._W) self._G = None + self._G_history = None self.mode = mode + self.save_conductance = save_conductance def setW(self, w): """ @@ -459,6 +473,11 @@ def simulate(self, ext_input): # initialize reservoir states self._state = np.zeros((len(ext_input), self.hidden_size)) + # initialize array for storing conductance history if needed + if self.save_conductance: + self._G_history = np.zeros((len(ext_input), self.hidden_size, + self.hidden_size)) + if self.mode == 'forward': for t, Ve in enumerate(ext_input): @@ -478,6 +497,10 @@ def simulate(self, ext_input): # store state self._state[t, self._I] = Vi + + # store conductance if necessary + if self.save_conductance: + self._G_history[t] = self._G elif self.mode == 'backward': for t,Ve in enumerate(ext_input): @@ -492,6 +515,10 @@ def simulate(self, ext_input): # store state self._state[t, self._I] = Vi + + # store conductance if necessary + if self.save_conductance: + self._G_history[t] = self._G # center internal voltage measurements self._state[:, self._I] = self._state[:, self._I] - \ @@ -598,6 +625,10 @@ class MSSNetwork(MemristiveReservoir): Refers to the method used to solve the system of equations. Use 'forward' for explicit Euler method, and 'backward' for implicit Euler method. + save_conductance : bool + Indicates whether to save conductance state after each simulation + step. If True, then will be stored in self._G_history. This will + increase memory demands. vA : numpy.ndarray of floats vB : numpy.ndarray of floats @@ -632,8 +663,8 @@ class MSSNetwork(MemristiveReservoir): VT = 1/b def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward', - vA=0.17, vB=0.22, tc=0.32e-3, NMSS=10000, Woff=0.91e-3, - Won=0.87e-2, Nb=2000, noise=0.1): + save_conductance=False, vA=0.17, vB=0.22, tc=0.32e-3, + NMSS=10000, Woff=0.91e-3, Won=0.87e-2, Nb=2000, noise=0.1): """ Constructor class for Memristive Networks following the Generalized Memristive Switch Model proposed in Nugent and Molter, 2014. Default @@ -655,19 +686,14 @@ def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward', gr_nodes : (n_grounded_nodes,) numpy.ndarray indexes of grounded nodes n_grounded_nodes: number of grounded nodes - mode : {'forward', 'backward'} + mode : {'forward', 'backward'}, optional Refers to the method used to solve the system of equations. Use 'forward' for explicit Euler method, and 'backward' for - implicit Euler method. - int_nodes : (n_internal_nodes,) numpy.ndarray - indexes of internal nodes - n_internal_nodes: number of internal nodes - ext_nodes : (n_external_nodes,) numpy.ndarray - indexes of external nodes - n_external_nodes: number of external nodes - gr_nodes : (n_grounded_nodes,) numpy.ndarray - indexes of grounded nodes - n_grounded_nodes: number of grounded nodes + implicit Euler method. Default: 'forward' + save_conductance : bool, optional + Indicates whether to save conductance state after each simulation + step. If True, then will be stored in self._G_history. This will + increase memory demands. Default: False vA : float. Default: 0.17 vB : float. Default: 0.22 @@ -686,7 +712,8 @@ def __init__(self, w, int_nodes, ext_nodes, gr_nodes, mode='forward', #TODO """ - super().__init__(w, int_nodes, ext_nodes, gr_nodes, mode) + super().__init__(w, int_nodes, ext_nodes, gr_nodes, mode, + save_conductance) self.vA = self.init_property(vA, noise) # constant self.vB = self.init_property(vB, noise) # constant