From e051de1f6e39f29467f5d84ebcca5b1ab3cfb9de Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sat, 11 Dec 2021 20:26:29 +0100 Subject: [PATCH 01/30] detach executor calls and their results at class Async --- sorn/sorn.py | 261 +++++++++++++++++++++++++++++---------------------- 1 file changed, 151 insertions(+), 110 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 7bacc94..f1f6a2c 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -170,7 +170,8 @@ def stdp(self, wee: np.array, x: np.array, cutoff_weights: list): if wee_t[j][i] != 0.0: # Check connectivity # Get the change in weight - delta_wee_t = self.eta_stdp * (xt[i] * xt_1[j] - xt_1[i] * xt[j]) + delta_wee_t = self.eta_stdp * \ + (xt[i] * xt_1[j] - xt_1[i] * xt[j]) # Update the weight between jth neuron to i ""Different from notation in article @@ -260,7 +261,8 @@ def istdp(self, wei: np.array, x: np.array, y: np.array, cutoff_weights: list): # Get the change in weight delta_wei_t = ( - -self.eta_inhib * yt_1[j] * (1 - xt[i] * (1 + 1 / self.mu_ip)) + -self.eta_inhib * yt_1[j] * + (1 - xt[i] * (1 + 1 / self.mu_ip)) ) # Update the weight between jth neuron to i ""Different from notation in article @@ -334,7 +336,6 @@ def initialize_plasticity(): ) Wee_init = Initializer.zero_sum_incoming_check(WEE_init) - # Wei_init = initializer.zero_sum_incoming_check(WEI_init.T) # For SORN 1 Wei_init = Initializer.zero_sum_incoming_check(WEI_init) Wie_init = Initializer.zero_sum_incoming_check(WIE_init) @@ -374,53 +375,91 @@ def initialize_plasticity(): class Async: - def __init__(self, max_workers=4): + """Execute STDP, iSTDP, IP and SP asynchrously followed by SS + + Args: + X(array): Excitatory network activity + Y(array): Inhibitory network activity + Wee(array): Synaptic strengths from excitatory to excitatory + Wei(array): Synaptic strengths from inhibitory to excitatory + Te(array): Excitatory threshold + freeze(array): List of synaptic plasticity mechanisms which will + be turned off during simulation. Defaults to None. + max_workers (int, optional): Defaults to min(32, os.cpu_count() + 4). + This default value preserves at least 5 workers for I/O bound tasks. + It utilizes at most 32 CPU cores for CPU bound tasks which release the GIL. + And it avoids using very large resources implicitly on many-core machines + + Returns: + params(dict): Dictionary networks parameters Wee, Wei, Te + """ + + def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4)): + super().__init__() + self.X = X + self.Y = Y + self.freeze = freeze self.max_workers = max_workers + self.params = {'Wee': Wee, 'Wei': Wei, 'Te': Te} self.plasticity = Plasticity() + self.execute() + self.update_params() - def step(self, X, Y, Wee, Wei, Te, freeze): + def execute(self): with concurrent.futures.ThreadPoolExecutor( max_workers=self.max_workers ) as executor: - # STDP - if "stdp" not in freeze: - stdp = executor.submit( - self.plasticity.stdp, Wee, X, cutoff_weights=(0.0, 1.0) + if "stdp" not in self.freeze: + self.stdp = executor.submit( + self.plasticity.stdp, self.params['Wee'], self.X, cutoff_weights=( + 0.0, 1.0) ) - Wee = stdp.result() - - # Intrinsic plasticity - if "ip" not in freeze: - ip = executor.submit(self.plasticity.ip, Te, X) - Te = ip.result() - # Structural plasticity - if "sp" not in freeze: - sp = executor.submit(self.plasticity.structural_plasticity, Wee) - Wee = sp.result() - # iSTDP - if "istdp" not in freeze: - istdp = executor.submit( - self.plasticity.istdp, Wei, X, Y, cutoff_weights=(0.0, 1.0) + + if "ip" not in self.freeze: + self.ip = executor.submit(self.plasticity.ip, + self.params['Te'], self.X) + + if "sp" not in self.freeze: + self.sp = executor.submit( + self.plasticity.structural_plasticity, self.params['Wee']) + + if "istdp" not in self.freeze: + self.istdp = executor.submit( + self.plasticity.istdp, self.params['Wei'], self.X, self.Y, cutoff_weights=( + 0.0, 1.0) ) - Wei = istdp.result() - # Synaptic scaling Wee - if "ss" not in freeze: - Wee = self.plasticity.ss(Wee) - Wei = self.plasticity.ss(Wei) - return Wee, Wei, Te + def update_params(self): + + self.params['Wee'] = self.stdp.result( + ) if "stdp" not in self.freeze else self.params['Wee'] + self.params['Wei'] = self.istdp.result( + ) if "istdp" not in self.freeze else self.params['Wei'] + self.params['Te'] = self.ip.result( + ) if "ip" not in self.freeze else self.params['Te'] + self.params['Wee'] = self.sp.result( + ) if "sp" not in self.freeze else self.params['Wee'] + + if "ss" not in self.freeze: + self.params['Wee'] = self.plasticity.ss(self.params['Wee']) + self.params['Wei'] = self.plasticity.ss(self.params['Wei']) + + def __new__(cls, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4)): + async_instance = super(Async, cls).__new__(cls) + async_instance.__init__(X, Y, Wee, Wei, Te, freeze, max_workers) + return async_instance.params class MatrixCollection(Sorn): - """Collect all matrices initialized and updated during simulation (plasiticity and training phases) + """Collect all matrices initialized and updated during simulation(plasiticity and training phases) Args: - phase (str): Training or Plasticity phase + phase(str): Training or Plasticity phase - matrices (dict): Network activity, threshold and connection matrices + matrices(dict): Network activity, threshold and connection matrices Returns: MatrixCollection instance""" @@ -500,15 +539,15 @@ def weight_matrix(self, wee: np.array, wei: np.array, wie: np.array, i: int): """Update weight matrices Args: - wee (array): Excitatory-Excitatory weight matrix + wee(array): Excitatory-Excitatory weight matrix - wei (array): Inhibitory-Excitatory weight matrix + wei(array): Inhibitory-Excitatory weight matrix - wie (array): Excitatory-Inhibitory weight matrix + wie(array): Excitatory-Inhibitory weight matrix - i (int): Time step + i(int): Time step Returns: - tuple (array): Weight Matrices Wee, Wei, Wie""" + tuple(array): Weight Matrices Wee, Wei, Wie""" self.Wee[i + 1] = wee self.Wei[i + 1] = wei @@ -520,14 +559,14 @@ def threshold_matrix(self, te: np.array, ti: np.array, i: int): """Update threshold matrices Args: - te (array): Excitatory threshold + te(array): Excitatory threshold - ti (array): Inhibitory threshold + ti(array): Inhibitory threshold - i (int): Time step + i(int): Time step Returns: - tuple (array): Threshold Matrices Te and Ti""" + tuple(array): Threshold Matrices Te and Ti""" self.Te[i + 1] = te self.Ti[i + 1] = ti @@ -539,14 +578,14 @@ def network_activity_t( """Network state at current time step Args: - excitatory_net (array): Excitatory network activity + excitatory_net(array): Excitatory network activity - inhibitory_net (array): Inhibitory network activity + inhibitory_net(array): Inhibitory network activity - i (int): Time step + i(int): Time step Returns: - tuple (array): Updated Excitatory and Inhibitory states + tuple(array): Updated Excitatory and Inhibitory states """ self.X[i + 1] = excitatory_net @@ -558,14 +597,14 @@ def network_activity_t_1(self, x: np.array, y: np.array, i: int): """Network activity at previous time step Args: - x (array): Excitatory network activity + x(array): Excitatory network activity - y (array): Inhibitory network activity + y(array): Inhibitory network activity - i (int): Time step + i(int): Time step Returns: - tuple (array): Previous Excitatory and Inhibitory states + tuple(array): Previous Excitatory and Inhibitory states """ x_1, y_1 = [0] * self.time_steps, [0] * self.time_steps x_1[i] = x @@ -579,10 +618,10 @@ class NetworkState(Plasticity): """The evolution of network states Args: - v_t (array): External input/stimuli + v_t(array): External input/stimuli Returns: - instance (object): NetworkState instance""" + instance(object): NetworkState instance""" def __init__(self, v_t: np.array): super().__init__() @@ -600,12 +639,12 @@ def incoming_drive(self, weights: np.array, activity_vector: np.array): """Excitatory Post synaptic potential towards neurons in the reservoir in the absence of external input Args: - weights (array): Synaptic strengths + weights(array): Synaptic strengths - activity_vector (list): Acitivity of inhibitory or Excitatory neurons + activity_vector(list): Acitivity of inhibitory or Excitatory neurons Returns: - incoming (array): Excitatory Post synaptic potential towards neurons + incoming(array): Excitatory Post synaptic potential towards neurons """ incoming = weights * activity_vector incoming = np.array(incoming.sum(axis=0)) @@ -623,20 +662,20 @@ def excitatory_network_state( """Activity of Excitatory neurons in the network Args: - wee (array): Excitatory-Excitatory weight matrix + wee(array): Excitatory-Excitatory weight matrix - wei (array): Inhibitory-Excitatory weight matrix + wei(array): Inhibitory-Excitatory weight matrix - te (array): Excitatory threshold + te(array): Excitatory threshold - x (array): Excitatory network activity + x(array): Excitatory network activity - y (array): Inhibitory network activity + y(array): Inhibitory network activity - white_noise_e (array): Gaussian noise + white_noise_e(array): Gaussian noise Returns: - x (array): Current Excitatory network activity + x(array): Current Excitatory network activity """ xt = x[:, 1] xt = xt.reshape(self.ne, 1) @@ -668,18 +707,18 @@ def inhibitory_network_state( """Activity of Excitatory neurons in the network Args: - wee (array): Excitatory-Excitatory weight matrix + wee(array): Excitatory-Excitatory weight matrix - wie (array): Excitatory-Inhibitory weight matrix + wie(array): Excitatory-Inhibitory weight matrix - ti (array): Inhibitory threshold + ti(array): Inhibitory threshold - y (array): Inhibitory network activity + y(array): Inhibitory network activity - white_noise_i (array): Gaussian noise + white_noise_i(array): Gaussian noise Returns: - y (array): Current Inhibitory network activity""" + y(array): Current Inhibitory network activity""" wie = np.asarray(wie) yt = y[:, 1] @@ -708,20 +747,20 @@ def recurrent_drive( Args: - wee (array): Excitatory-Excitatory weight matrix + wee(array): Excitatory-Excitatory weight matrix - wei (array): Inhibitory-Excitatory weight matrix + wei(array): Inhibitory-Excitatory weight matrix - te (array): Excitatory threshold + te(array): Excitatory threshold - x (array): Excitatory network activity + x(array): Excitatory network activity - y (array): Inhibitory network activity + y(array): Inhibitory network activity - white_noise_e (array): Gaussian noise + white_noise_e(array): Gaussian noise Returns: - xt (array): Recurrent network state + xt(array): Recurrent network state """ xt = x[:, 1] xt = xt.reshape(self.ne, 1) @@ -749,26 +788,26 @@ class Simulator_(Sorn): """Simulate SORN using external input/noise using the fresh or pretrained matrices Args: - inputs (np.array, optional): External stimuli. Defaults to None. + inputs(np.array, optional): External stimuli. Defaults to None. - phase (str, optional): Plasticity phase. Defaults to "plasticity". + phase(str, optional): Plasticity phase. Defaults to "plasticity". - matrices (dict, optional): Network states, connections and threshold matrices. Defaults to None. + matrices(dict, optional): Network states, connections and threshold matrices. Defaults to None. - time_steps (int, optional): Total number of time steps to simulate the network. Defaults to 1. + time_steps(int, optional): Total number of time steps to simulate the network. Defaults to 1. - noise (bool, optional): If True, noise will be added. Defaults to True. + noise(bool, optional): If True, noise will be added. Defaults to True. Returns: - plastic_matrices (dict): Network states, connections and threshold matrices + plastic_matrices(dict): Network states, connections and threshold matrices - X_all (array): Excitatory network activity collected during entire simulation steps + X_all(array): Excitatory network activity collected during entire simulation steps - Y_all (array): Inhibitory network activity collected during entire simulation steps + Y_all(array): Inhibitory network activity collected during entire simulation steps - R_all (array): Recurrent network activity collected during entire simulation steps + R_all(array): Recurrent network activity collected during entire simulation steps - frac_pos_active_conn (list): Number of positive connection strengths in the network at each time step during simulation""" + frac_pos_active_conn(list): Number of positive connection strengths in the network at each time step during simulation""" def __init__(self): super().__init__() @@ -788,30 +827,30 @@ def simulate_sorn( """Simulation/Plasticity phase Args: - inputs (np.array, optional): External stimuli. Defaults to None. + inputs(np.array, optional): External stimuli. Defaults to None. - phase (str, optional): Plasticity phase. Defaults to "plasticity" + phase(str, optional): Plasticity phase. Defaults to "plasticity" - matrices (dict, optional): Network states, connections and threshold matrices. Defaults to None. + matrices(dict, optional): Network states, connections and threshold matrices. Defaults to None. - time_steps (int, optional): Total number of time steps to simulate the network. Defaults to 1. + time_steps(int, optional): Total number of time steps to simulate the network. Defaults to 1. - noise (bool, optional): If True, noise will be added. Defaults to True. + noise(bool, optional): If True, noise will be added. Defaults to True. - freeze (list, optional): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. + freeze(list, optional): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. - max_workers (int, optional): Maximum workers for multhreading the plasticity steps + max_workers(int, optional): Maximum workers for multhreading the plasticity steps Returns: - plastic_matrices (dict): Network states, connections and threshold matrices + plastic_matrices(dict): Network states, connections and threshold matrices - X_all (array): Excitatory network activity collected during entire simulation steps + X_all(array): Excitatory network activity collected during entire simulation steps - Y_all (array): Inhibitory network activity collected during entire simulation steps + Y_all(array): Inhibitory network activity collected during entire simulation steps - R_all (array): Recurrent network activity collected during entire simulation steps + R_all(array): Recurrent network activity collected during entire simulation steps - frac_pos_active_conn (list): Number of positive connection strengths in the network at each time step during simulation""" + frac_pos_active_conn(list): Number of positive connection strengths in the network at each time step during simulation""" assert ( phase == "plasticity" or "training" @@ -849,7 +888,8 @@ def simulate_sorn( Sorn.ni = int(0.2 * Sorn.ne) # Initialize/Get the weight, threshold matrices and activity vectors - matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) + matrix_collection = MatrixCollection( + phase=self.phase, matrices=self.matrices) # Collect the network activity at all time steps @@ -957,30 +997,30 @@ def train_sorn( """Train the network with the fresh or pretrained network matrices and external stimuli Args: - inputs (np.array, optional): External stimuli. Defaults to None. + inputs(np.array, optional): External stimuli. Defaults to None. - phase (str, optional): Training phase. Defaults to "training". + phase(str, optional): Training phase. Defaults to "training". - matrices (dict, optional): Network states, connections and threshold matrices. Defaults to None. + matrices(dict, optional): Network states, connections and threshold matrices. Defaults to None. - time_steps (int, optional): Total number of time steps to simulate the network. Defaults to 1. + time_steps(int, optional): Total number of time steps to simulate the network. Defaults to 1. - noise (bool, optional): If True, noise will be added. Defaults to True. + noise(bool, optional): If True, noise will be added. Defaults to True. - freeze (list, optional): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. + freeze(list, optional): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. - max_workers (int, optional): Maximum workers for multhreading the plasticity steps + max_workers(int, optional): Maximum workers for multhreading the plasticity steps Returns: - plastic_matrices (dict): Network states, connections and threshold matrices + plastic_matrices(dict): Network states, connections and threshold matrices - X_all (array): Excitatory network activity collected during entire simulation steps + X_all(array): Excitatory network activity collected during entire simulation steps - Y_all (array): Inhibitory network activity collected during entire simulation steps + Y_all(array): Inhibitory network activity collected during entire simulation steps - R_all (array): Recurrent network activity collected during entire simulation steps + R_all(array): Recurrent network activity collected during entire simulation steps - frac_pos_active_conn (list): Number of positive connection strengths in the network at each time step during simulation""" + frac_pos_active_conn(list): Number of positive connection strengths in the network at each time step during simulation""" assert ( phase == "plasticity" or "training" @@ -1024,7 +1064,8 @@ def train_sorn( frac_pos_active_conn = [] - matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) + matrix_collection = MatrixCollection( + phase=self.phase, matrices=self.matrices) for i in range(self.time_steps): From 3fb677d544e423e4f35bd18de63fab58ec365c6d Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 12 Dec 2021 10:29:26 +0100 Subject: [PATCH 02/30] async return param dict->list --- sorn/sorn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index f1f6a2c..e717299 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -450,7 +450,7 @@ def update_params(self): def __new__(cls, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4)): async_instance = super(Async, cls).__new__(cls) async_instance.__init__(X, Y, Wee, Wei, Te, freeze, max_workers) - return async_instance.params + return async_instance.params.values() class MatrixCollection(Sorn): From 86b2d299e31d7307311ce2eb3397dedfe325d09a Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 12 Dec 2021 11:31:33 +0100 Subject: [PATCH 03/30] added streaming log handlers --- .gitignore | 5 ++++- Dockerfile | 2 +- setup.py | 10 +++++++--- sorn/__init__.py | 2 +- sorn/sorn.py | 16 ++++++++++++---- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 86d3ad3..86fe54b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,10 @@ __pycache__/ build/ dist/ sorn.egg-info/ - + +# Logs +*.logs + ### VisualStudioCode ### .vscode/* !.vscode/tasks.json diff --git a/Dockerfile b/Dockerfile index 2d0465d..78f2881 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# set base image (host OS) +# set base image FROM python:3.8 # set the working directory in the scontainer diff --git a/setup.py b/setup.py index 17ca386..09750cd 100644 --- a/setup.py +++ b/setup.py @@ -5,12 +5,15 @@ # Used for the long_description. It's nice, because now 1) we have a top level # README file and 2) it's easier to type in the README file than to put a raw # string in below ... + + def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() + setup( name="sorn", - version="0.6.2", + version="0.7.1", author="Saranraj Nambusubramaniyan", author_email="saran_nns@hotmail.com", description="Self-Organizing Recurrent Neural Networks", @@ -31,9 +34,10 @@ def read(fname): "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], include_package_data=True, - install_requires=["numpy", "configparser", "scipy", "seaborn", "pandas", "networkx"], + install_requires=["numpy", "configparser", + "scipy", "seaborn", "pandas", "networkx"], zip_safe=False, ) - diff --git a/sorn/__init__.py b/sorn/__init__.py index 98fd132..f26f649 100644 --- a/sorn/__init__.py +++ b/sorn/__init__.py @@ -3,6 +3,6 @@ from .utils import * __author__ = "Saranraj Nambusubramaniyan" -__version__ = "0.6.2" +__version__ = "0.7.1" logging.basicConfig(level=logging.INFO) diff --git a/sorn/sorn.py b/sorn/sorn.py index e717299..3bce320 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -5,12 +5,20 @@ import os import random import concurrent.futures +import logging try: from sorn.utils import Initializer except: from utils import Initializer +logging.basicConfig(level=logging.INFO, + format='%(asctime)s:%(levelname)s:%(message)s', + handlers=[ + logging.FileHandler("sorn.log"), + logging.StreamHandler() + ]) + class Sorn(object): @@ -343,9 +351,10 @@ def initialize_plasticity(): v = np.count_nonzero(Wei_init) b = np.count_nonzero(Wie_init) - print("Network Initialized") - print("Number of connections in Wee %s , Wei %s, Wie %s" % (c, v, b)) - print( + logging.info("Network Initialized") + logging.info( + "Number of connections in Wee %s , Wei %s, Wie %s" % (c, v, b)) + logging.info( "Shapes Wee %s Wei %s Wie %s" % (Wee_init.shape, Wei_init.shape, Wie_init.shape) ) @@ -1049,7 +1058,6 @@ def train_sorn( if key in kwargs_: setattr(Sorn, key, value) Sorn.ni = int(0.2 * Sorn.ne) - # assert Sorn.nu == len(inputs[:,0]),"Size mismatch: Input != Nu " self.phase = phase self.matrices = matrices From 1bc60f08aeef510a5b71208ee2908f37962f9203 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 12 Dec 2021 14:10:04 +0100 Subject: [PATCH 04/30] Async __new__ method call update --- sorn/sorn.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 3bce320..80db31b 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -392,7 +392,7 @@ class Async: Wee(array): Synaptic strengths from excitatory to excitatory Wei(array): Synaptic strengths from inhibitory to excitatory Te(array): Excitatory threshold - freeze(array): List of synaptic plasticity mechanisms which will + freeze(array): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. max_workers (int, optional): Defaults to min(32, os.cpu_count() + 4). This default value preserves at least 5 workers for I/O bound tasks. @@ -403,14 +403,17 @@ class Async: params(dict): Dictionary networks parameters Wee, Wei, Te """ - def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4)): + def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers): super().__init__() self.X = X self.Y = Y self.freeze = freeze - self.max_workers = max_workers self.params = {'Wee': Wee, 'Wei': Wei, 'Te': Te} + if max_workers == None: + self.max_workers = min(32, os.cpu_count() + 4) + else: + self.max_workers = max_workers self.plasticity = Plasticity() self.execute() self.update_params() @@ -960,9 +963,8 @@ def simulate_sorn( y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T - Wee[i], Wei[i], Te[i] = Async(max_workers=max_workers).step( - x_buffer, y_buffer, Wee[i], Wei[i], Te[i], self.freeze - ) + Wee[i], Wei[i], Te[i] = Async(x_buffer, y_buffer, Wee[i], Wei[i], Te[i], + self.freeze, max_workers=max_workers) # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) From 17f41ef8d9e1dc7bc21e9b7a0d39840c58d7199e Mon Sep 17 00:00:00 2001 From: saran_nns Date: Mon, 13 Dec 2021 09:22:48 +0100 Subject: [PATCH 05/30] move async results inside context manager --- README.md | 2 +- setup.py | 2 - sorn/sorn.py | 103 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 13d24e0..c100d64 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ pip install git+https://github.com/Saran-nns/sorn ``` ## Dependencies -SORN supports Python 3.5+ ONLY. For older Python versions please use the official Python client. +SORN supports Python 3.7+ ONLY. For older Python versions please use the official Python client. To install all optional dependencies, ```python diff --git a/setup.py b/setup.py index 09750cd..2f01aaa 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,6 @@ def read(fname): "Topic :: Scientific/Engineering :: Artificial Intelligence", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/sorn/sorn.py b/sorn/sorn.py index 80db31b..4956020 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -12,12 +12,11 @@ except: from utils import Initializer -logging.basicConfig(level=logging.INFO, - format='%(asctime)s:%(levelname)s:%(message)s', - handlers=[ - logging.FileHandler("sorn.log"), - logging.StreamHandler() - ]) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s:%(levelname)s:%(message)s", + handlers=[logging.FileHandler("sorn.log"), logging.StreamHandler()], +) class Sorn(object): @@ -178,8 +177,7 @@ def stdp(self, wee: np.array, x: np.array, cutoff_weights: list): if wee_t[j][i] != 0.0: # Check connectivity # Get the change in weight - delta_wee_t = self.eta_stdp * \ - (xt[i] * xt_1[j] - xt_1[i] * xt[j]) + delta_wee_t = self.eta_stdp * (xt[i] * xt_1[j] - xt_1[i] * xt[j]) # Update the weight between jth neuron to i ""Different from notation in article @@ -269,8 +267,7 @@ def istdp(self, wei: np.array, x: np.array, y: np.array, cutoff_weights: list): # Get the change in weight delta_wei_t = ( - -self.eta_inhib * yt_1[j] * - (1 - xt[i] * (1 + 1 / self.mu_ip)) + -self.eta_inhib * yt_1[j] * (1 - xt[i] * (1 + 1 / self.mu_ip)) ) # Update the weight between jth neuron to i ""Different from notation in article @@ -352,8 +349,7 @@ def initialize_plasticity(): b = np.count_nonzero(Wie_init) logging.info("Network Initialized") - logging.info( - "Number of connections in Wee %s , Wei %s, Wie %s" % (c, v, b)) + logging.info("Number of connections in Wee %s , Wei %s, Wie %s" % (c, v, b)) logging.info( "Shapes Wee %s Wei %s Wie %s" % (Wee_init.shape, Wei_init.shape, Wie_init.shape) @@ -401,7 +397,7 @@ class Async: Returns: params(dict): Dictionary networks parameters Wee, Wei, Te - """ + """ def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers): @@ -409,14 +405,13 @@ def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers): self.X = X self.Y = Y self.freeze = freeze - self.params = {'Wee': Wee, 'Wei': Wei, 'Te': Te} + self.params = {"Wee": Wee, "Wei": Wei, "Te": Te} if max_workers == None: self.max_workers = min(32, os.cpu_count() + 4) else: self.max_workers = max_workers self.plasticity = Plasticity() self.execute() - self.update_params() def execute(self): @@ -426,40 +421,51 @@ def execute(self): if "stdp" not in self.freeze: self.stdp = executor.submit( - self.plasticity.stdp, self.params['Wee'], self.X, cutoff_weights=( - 0.0, 1.0) + self.plasticity.stdp, + self.params["Wee"], + self.X, + cutoff_weights=(0.0, 1.0), ) if "ip" not in self.freeze: - self.ip = executor.submit(self.plasticity.ip, - self.params['Te'], self.X) + self.ip = executor.submit(self.plasticity.ip, self.params["Te"], self.X) if "sp" not in self.freeze: self.sp = executor.submit( - self.plasticity.structural_plasticity, self.params['Wee']) + self.plasticity.structural_plasticity, self.params["Wee"] + ) if "istdp" not in self.freeze: self.istdp = executor.submit( - self.plasticity.istdp, self.params['Wei'], self.X, self.Y, cutoff_weights=( - 0.0, 1.0) + self.plasticity.istdp, + self.params["Wei"], + self.X, + self.Y, + cutoff_weights=(0.0, 1.0), ) - def update_params(self): - - self.params['Wee'] = self.stdp.result( - ) if "stdp" not in self.freeze else self.params['Wee'] - self.params['Wei'] = self.istdp.result( - ) if "istdp" not in self.freeze else self.params['Wei'] - self.params['Te'] = self.ip.result( - ) if "ip" not in self.freeze else self.params['Te'] - self.params['Wee'] = self.sp.result( - ) if "sp" not in self.freeze else self.params['Wee'] + self.params["Wee"] = ( + self.stdp.result() if "stdp" not in self.freeze else self.params["Wee"] + ) + self.params["Wei"] = ( + self.istdp.result() + if "istdp" not in self.freeze + else self.params["Wei"] + ) + self.params["Te"] = ( + self.ip.result() if "ip" not in self.freeze else self.params["Te"] + ) + self.params["Wee"] = ( + self.sp.result() if "sp" not in self.freeze else self.params["Wee"] + ) if "ss" not in self.freeze: - self.params['Wee'] = self.plasticity.ss(self.params['Wee']) - self.params['Wei'] = self.plasticity.ss(self.params['Wei']) + self.params["Wee"] = self.plasticity.ss(self.params["Wee"]) + self.params["Wei"] = self.plasticity.ss(self.params["Wei"]) - def __new__(cls, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4)): + def __new__( + cls, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4) + ): async_instance = super(Async, cls).__new__(cls) async_instance.__init__(X, Y, Wee, Wei, Te, freeze, max_workers) return async_instance.params.values() @@ -900,8 +906,7 @@ def simulate_sorn( Sorn.ni = int(0.2 * Sorn.ne) # Initialize/Get the weight, threshold matrices and activity vectors - matrix_collection = MatrixCollection( - phase=self.phase, matrices=self.matrices) + matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) # Collect the network activity at all time steps @@ -963,8 +968,15 @@ def simulate_sorn( y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T - Wee[i], Wei[i], Te[i] = Async(x_buffer, y_buffer, Wee[i], Wei[i], Te[i], - self.freeze, max_workers=max_workers) + Wee[i], Wei[i], Te[i] = Async( + x_buffer, + y_buffer, + Wee[i], + Wei[i], + Te[i], + self.freeze, + max_workers=max_workers, + ) # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) @@ -1074,8 +1086,7 @@ def train_sorn( frac_pos_active_conn = [] - matrix_collection = MatrixCollection( - phase=self.phase, matrices=self.matrices) + matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) for i in range(self.time_steps): @@ -1127,8 +1138,14 @@ def train_sorn( y_buffer[:, 1] = inhibitory_state_yt_buffer.T if self.phase == "plasticity": - Wee[i], Wei[i], Te[i] = Async(max_workers=self.max_workers).step( - x_buffer, y_buffer, Wee[i], Wei[i], Te[i], self.freeze + Wee[i], Wei[i], Te[i] = Async( + x_buffer, + y_buffer, + Wee[i], + Wei[i], + Te[i], + self.freeze, + max_workers=max_workers, ) else: From 74759407025af934fcada3f11fe5b633504e9d62 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Mon, 13 Dec 2021 16:25:19 +0100 Subject: [PATCH 06/30] replace thread executor with multiprocessing --- sorn/sorn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 4956020..3204743 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -415,7 +415,7 @@ def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers): def execute(self): - with concurrent.futures.ThreadPoolExecutor( + with concurrent.futures.ProcessPoolExecutor( max_workers=self.max_workers ) as executor: From 570c38949bbe165eb53ec68b3892a59df399f396 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Wed, 15 Dec 2021 10:39:50 +0100 Subject: [PATCH 07/30] added sp into process pool --- setup.py | 10 +++- sorn/sorn.py | 138 ++++++++++++++------------------------------------- 2 files changed, 45 insertions(+), 103 deletions(-) diff --git a/setup.py b/setup.py index 2f01aaa..ac36e1f 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,13 @@ def read(fname): "Programming Language :: Python :: 3.9", ], include_package_data=True, - install_requires=["numpy", "configparser", - "scipy", "seaborn", "pandas", "networkx"], + install_requires=[ + "numpy", + "configparser", + "scipy", + "seaborn", + "pandas", + "networkx", + ], zip_safe=False, ) diff --git a/sorn/sorn.py b/sorn/sorn.py index 3204743..bad0ebd 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -379,98 +379,6 @@ def initialize_plasticity(): return wee, wei, wie, te, ti, x, y -class Async: - """Execute STDP, iSTDP, IP and SP asynchrously followed by SS - - Args: - X(array): Excitatory network activity - Y(array): Inhibitory network activity - Wee(array): Synaptic strengths from excitatory to excitatory - Wei(array): Synaptic strengths from inhibitory to excitatory - Te(array): Excitatory threshold - freeze(array): List of synaptic plasticity mechanisms which will - be turned off during simulation. Defaults to None. - max_workers (int, optional): Defaults to min(32, os.cpu_count() + 4). - This default value preserves at least 5 workers for I/O bound tasks. - It utilizes at most 32 CPU cores for CPU bound tasks which release the GIL. - And it avoids using very large resources implicitly on many-core machines - - Returns: - params(dict): Dictionary networks parameters Wee, Wei, Te - """ - - def __init__(self, X, Y, Wee, Wei, Te, freeze, max_workers): - - super().__init__() - self.X = X - self.Y = Y - self.freeze = freeze - self.params = {"Wee": Wee, "Wei": Wei, "Te": Te} - if max_workers == None: - self.max_workers = min(32, os.cpu_count() + 4) - else: - self.max_workers = max_workers - self.plasticity = Plasticity() - self.execute() - - def execute(self): - - with concurrent.futures.ProcessPoolExecutor( - max_workers=self.max_workers - ) as executor: - - if "stdp" not in self.freeze: - self.stdp = executor.submit( - self.plasticity.stdp, - self.params["Wee"], - self.X, - cutoff_weights=(0.0, 1.0), - ) - - if "ip" not in self.freeze: - self.ip = executor.submit(self.plasticity.ip, self.params["Te"], self.X) - - if "sp" not in self.freeze: - self.sp = executor.submit( - self.plasticity.structural_plasticity, self.params["Wee"] - ) - - if "istdp" not in self.freeze: - self.istdp = executor.submit( - self.plasticity.istdp, - self.params["Wei"], - self.X, - self.Y, - cutoff_weights=(0.0, 1.0), - ) - - self.params["Wee"] = ( - self.stdp.result() if "stdp" not in self.freeze else self.params["Wee"] - ) - self.params["Wei"] = ( - self.istdp.result() - if "istdp" not in self.freeze - else self.params["Wei"] - ) - self.params["Te"] = ( - self.ip.result() if "ip" not in self.freeze else self.params["Te"] - ) - self.params["Wee"] = ( - self.sp.result() if "sp" not in self.freeze else self.params["Wee"] - ) - - if "ss" not in self.freeze: - self.params["Wee"] = self.plasticity.ss(self.params["Wee"]) - self.params["Wei"] = self.plasticity.ss(self.params["Wei"]) - - def __new__( - cls, X, Y, Wee, Wei, Te, freeze, max_workers=min(32, os.cpu_count() + 4) - ): - async_instance = super(Async, cls).__new__(cls) - async_instance.__init__(X, Y, Wee, Wei, Te, freeze, max_workers) - return async_instance.params.values() - - class MatrixCollection(Sorn): """Collect all matrices initialized and updated during simulation(plasiticity and training phases) @@ -905,6 +813,7 @@ def simulate_sorn( # assert Sorn.nu == len(inputs[:,0]),"Size mismatch: Input != Nu " Sorn.ni = int(0.2 * Sorn.ne) + self.plasticity = Plasticity() # Initialize/Get the weight, threshold matrices and activity vectors matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) @@ -968,15 +877,42 @@ def simulate_sorn( y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T - Wee[i], Wei[i], Te[i] = Async( - x_buffer, - y_buffer, - Wee[i], - Wei[i], - Te[i], - self.freeze, - max_workers=max_workers, - ) + + with concurrent.futures.ProcessPoolExecutor( + max_workers=min(32, os.cpu_count() + 4) + ) as executor: + + if "stdp" not in self.freeze: + stdp = executor.submit( + self.plasticity.stdp, + Wee[i], + self.X, + cutoff_weights=(0.0, 1.0), + ) + + if "ip" not in self.freeze: + ip = executor.submit(self.plasticity.ip, Te[i], self.X) + + if "istdp" not in self.freeze: + istdp = executor.submit( + self.plasticity.istdp, + Wei[i], + self.X, + self.Y, + cutoff_weights=(0.0, 1.0), + ) + if "sp" not in self.freeze: + sp = executor.submit(self.plasticity.structural_plasticity,Wee[i]) + + Wee[i] = stdp.result() if "stdp" not in self.freeze else Wee[i] + Wei[i] = istdp.result() if "istdp" not in self.freeze else Wei[i] + Te[i] = ip.result() if "ip" not in self.freeze else Wee[i] + Wee[i] = sp.result() if "sp" not in self.freeze else Wee[i] + + if "ss" not in self.freeze: + Wee[i] = self.plasticity.ss(Wee[i]) + Wei[i] = self.plasticity.ss(Wei[i]) + # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) From 74f7dc37528ac254d3f36cba1b73ec3a98e984dd Mon Sep 17 00:00:00 2001 From: saran_nns Date: Wed, 15 Dec 2021 10:43:41 +0100 Subject: [PATCH 08/30] remove x and y vars from simulator instance --- sorn/sorn.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index bad0ebd..4814130 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -877,7 +877,6 @@ def simulate_sorn( y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T - with concurrent.futures.ProcessPoolExecutor( max_workers=min(32, os.cpu_count() + 4) ) as executor: @@ -886,23 +885,23 @@ def simulate_sorn( stdp = executor.submit( self.plasticity.stdp, Wee[i], - self.X, + X, cutoff_weights=(0.0, 1.0), ) if "ip" not in self.freeze: - ip = executor.submit(self.plasticity.ip, Te[i], self.X) + ip = executor.submit(self.plasticity.ip, Te[i], X) if "istdp" not in self.freeze: istdp = executor.submit( self.plasticity.istdp, Wei[i], - self.X, - self.Y, + X, + Y, cutoff_weights=(0.0, 1.0), ) if "sp" not in self.freeze: - sp = executor.submit(self.plasticity.structural_plasticity,Wee[i]) + sp = executor.submit(self.plasticity.structural_plasticity, Wee[i]) Wee[i] = stdp.result() if "stdp" not in self.freeze else Wee[i] Wei[i] = istdp.result() if "istdp" not in self.freeze else Wei[i] From a22e6eac7d316dc108be183f23cb5306c33252ec Mon Sep 17 00:00:00 2001 From: saran_nns Date: Wed, 15 Dec 2021 10:55:10 +0100 Subject: [PATCH 09/30] typo at ip --- sorn/sorn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 4814130..9dd67c4 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -891,7 +891,6 @@ def simulate_sorn( if "ip" not in self.freeze: ip = executor.submit(self.plasticity.ip, Te[i], X) - if "istdp" not in self.freeze: istdp = executor.submit( self.plasticity.istdp, @@ -900,12 +899,13 @@ def simulate_sorn( Y, cutoff_weights=(0.0, 1.0), ) + if "sp" not in self.freeze: sp = executor.submit(self.plasticity.structural_plasticity, Wee[i]) Wee[i] = stdp.result() if "stdp" not in self.freeze else Wee[i] Wei[i] = istdp.result() if "istdp" not in self.freeze else Wei[i] - Te[i] = ip.result() if "ip" not in self.freeze else Wee[i] + Te[i] = ip.result() if "ip" not in self.freeze else Te[i] Wee[i] = sp.result() if "sp" not in self.freeze else Wee[i] if "ss" not in self.freeze: From 382858d594504bf2a99f76b278f6ec12be7af7e4 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Wed, 15 Dec 2021 12:45:20 +0100 Subject: [PATCH 10/30] max_workers fixed, gitignore update --- .gitignore | 6 +++--- sorn/sorn.py | 24 ++++++++++++------------ sorn/test_multi.py | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 sorn/test_multi.py diff --git a/.gitignore b/.gitignore index 86fe54b..a7a43f5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,12 +16,12 @@ dist/ sorn.egg-info/ # Logs -*.logs +sorn/*.log ### VisualStudioCode ### .vscode/* -!.vscode/tasks.json -!.vscode/launch.json +.vscode/tasks.json +.vscode/launch.json *.code-workspace ### VisualStudioCode Patch ### diff --git a/sorn/sorn.py b/sorn/sorn.py index 9dd67c4..54c60a7 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -747,7 +747,6 @@ def simulate_sorn( time_steps: int = None, noise: bool = True, freeze: list = None, - max_workers: int = 4, **kwargs ): """Simulation/Plasticity phase @@ -765,8 +764,6 @@ def simulate_sorn( freeze(list, optional): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. - max_workers(int, optional): Maximum workers for multhreading the plasticity steps - Returns: plastic_matrices(dict): Network states, connections and threshold matrices @@ -787,7 +784,6 @@ def simulate_sorn( self.phase = phase self.matrices = matrices self.freeze = [] if freeze == None else freeze - self.max_workers = max_workers kwargs_ = [ "ne", "nu", @@ -885,28 +881,32 @@ def simulate_sorn( stdp = executor.submit( self.plasticity.stdp, Wee[i], - X, + x_buffer, cutoff_weights=(0.0, 1.0), ) if "ip" not in self.freeze: - ip = executor.submit(self.plasticity.ip, Te[i], X) + ip = executor.submit(self.plasticity.ip, Te[i], x_buffer) if "istdp" not in self.freeze: istdp = executor.submit( self.plasticity.istdp, Wei[i], - X, - Y, + x_buffer, + y_buffer, cutoff_weights=(0.0, 1.0), ) if "sp" not in self.freeze: sp = executor.submit(self.plasticity.structural_plasticity, Wee[i]) - Wee[i] = stdp.result() if "stdp" not in self.freeze else Wee[i] - Wei[i] = istdp.result() if "istdp" not in self.freeze else Wei[i] - Te[i] = ip.result() if "ip" not in self.freeze else Te[i] - Wee[i] = sp.result() if "sp" not in self.freeze else Wee[i] + if "stdp" not in self.freeze: + Wee[i] = stdp.result() + if "istdp" not in self.freeze: + Wei[i] = istdp.result() + if "ip" not in self.freeze: + Te[i] = ip.result() + if "sp" not in self.freeze: + Wee[i] = sp.result() if "ss" not in self.freeze: Wee[i] = self.plasticity.ss(Wee[i]) diff --git a/sorn/test_multi.py b/sorn/test_multi.py new file mode 100644 index 0000000..2b4965e --- /dev/null +++ b/sorn/test_multi.py @@ -0,0 +1,23 @@ +import sorn +from sorn import Simulator +import numpy as np +import timeit + +# Sample input +num_features = 10 +time_steps = 10 +inputs = np.random.rand(num_features, time_steps) + +start_time = timeit.default_timer() + +# Simulate the network with default hyperparameters under gaussian white noise +Simulator.simulate_sorn( + inputs=inputs, + phase="plasticity", + matrices=None, + noise=True, + time_steps=time_steps, + ne=200, +) + +print(timeit.default_timer() - start_time) From 54749de4e58d450fcb100046baa86cc5bf82992d Mon Sep 17 00:00:00 2001 From: saran_nns Date: Wed, 15 Dec 2021 13:44:03 +0100 Subject: [PATCH 11/30] set stdp last in pool --- sorn/sorn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 54c60a7..af19c01 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -899,14 +899,14 @@ def simulate_sorn( if "sp" not in self.freeze: sp = executor.submit(self.plasticity.structural_plasticity, Wee[i]) - if "stdp" not in self.freeze: - Wee[i] = stdp.result() if "istdp" not in self.freeze: Wei[i] = istdp.result() if "ip" not in self.freeze: Te[i] = ip.result() if "sp" not in self.freeze: Wee[i] = sp.result() + if "stdp" not in self.freeze: + Wee[i] = stdp.result() if "ss" not in self.freeze: Wee[i] = self.plasticity.ss(Wee[i]) From 809f55ed19f7c88a0b5f3eb2782cb4f9913ec08c Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 19 Dec 2021 23:16:24 +0100 Subject: [PATCH 12/30] parallel exec stdp, istdp and ip --- sorn/sorn.py | 268 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 103 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index af19c01..e7183fa 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -4,8 +4,8 @@ import numpy as np import os import random -import concurrent.futures import logging +from multiprocessing import Process, Manager try: from sorn.utils import Initializer @@ -147,7 +147,14 @@ def __init__(self): self.te_min = Sorn.te_min # Excitatory minimum Threshold self.te_max = Sorn.te_max # Excitatory maximum Threshold - def stdp(self, wee: np.array, x: np.array, cutoff_weights: list): + def stdp( + self, + wee: np.array, + x: np.array, + cutoff_weights: list, + freeze: bool = False, + **kwargs + ): """Apply STDP rule : Regulates synaptic strength between the pre(Xj) and post(Xi) synaptic neurons Args: @@ -157,41 +164,48 @@ def stdp(self, wee: np.array, x: np.array, cutoff_weights: list): cutoff_weights (list): Maximum and minimum weight ranges + freeze (bool): If True, skip the structural plasticity step. Default False + Returns: wee (array): Weight matrix """ + if freeze: + kwargs["wee"] = wee + return kwargs + else: - x = np.asarray(x) - xt_1 = x[:, 0] - xt = x[:, 1] - wee_t = wee.copy() - - # STDP applies only on the neurons which are connected. + x = np.asarray(x) + xt_1 = x[:, 0] + xt = x[:, 1] + wee_t = wee.copy() - for i in range(len(wee_t[0])): # Each neuron i, Post-synaptic neuron + # STDP applies only on the neurons which are connected. - for j in range( - len(wee_t[0:]) - ): # Incoming connection from jth pre-synaptic neuron to ith neuron + for i in range(len(wee_t[0])): # Each neuron i, Post-synaptic neuron - if wee_t[j][i] != 0.0: # Check connectivity + for j in range( + len(wee_t[0:]) + ): # Incoming connection from jth pre-synaptic neuron to ith neuron - # Get the change in weight - delta_wee_t = self.eta_stdp * (xt[i] * xt_1[j] - xt_1[i] * xt[j]) + if wee_t[j][i] != 0.0: # Check connectivity - # Update the weight between jth neuron to i ""Different from notation in article + # Get the change in weight + delta_wee_t = self.eta_stdp * ( + xt[i] * xt_1[j] - xt_1[i] * xt[j] + ) - wee_t[j][i] = wee[j][i] + delta_wee_t + # Update the weight between jth neuron to i ""Different from notation in article - # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight - wee_t = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) + wee_t[j][i] = wee[j][i] + delta_wee_t - # Check and set all weights < upper cutoff weight - wee_t = Initializer.set_max_cutoff_weight(wee_t, cutoff_weights[1]) + # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight + wee_t = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) - return wee_t + # Check and set all weights < upper cutoff weight + kwargs["wee"] = Initializer.set_max_cutoff_weight(wee_t, cutoff_weights[1]) + return kwargs - def ip(self, te: np.array, x: np.array): + def ip(self, te: np.array, x: np.array, freeze: bool = False, **kwargs): """Intrinsic Plasiticity mechanism Args: @@ -199,39 +213,57 @@ def ip(self, te: np.array, x: np.array): x (array): Excitatory network activity + freeze (bool): If True, skip the structural plasticity step. Default False + Returns: te (array): Threshold vector of excitatory units """ + if freeze: + kwargs["te"] = te + return kwargs + else: + # IP rule: Active unit increases its threshold and inactive decreases its threshold. + xt = x[:, 1] - # IP rule: Active unit increases its threshold and inactive decreases its threshold. - xt = x[:, 1] - - te_update = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) + kwargs["te"] = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) - # Check whether all te are in range [0.0,1.0] and update acordingly + # Check whether all te are in range [0.0,1.0] and update acordingly - # Update te < 0.0 ---> 0.0 - # te_update = prune_small_weights(te_update,self.te_min) + # Update te < 0.0 ---> 0.0 + # te = prune_small_weights(te_update,self.te_min) - # Set all te > 1.0 --> 1.0 - # te_update = set_max_cutoff_weight(te_update,self.te_max) + # Set all te > 1.0 --> 1.0 + # te = set_max_cutoff_weight(te_update,self.te_max) - return te_update + return kwargs @staticmethod - def ss(wee: np.array): + def ss(wee: np.array, freeze: bool = False): """Synaptic Scaling or Synaptic Normalization Args: wee (array): Weight matrix + freeze (bool): If True, skip the structural plasticity step. Default False + Returns: wee (array): Scaled Weight matrix """ - wee = wee / np.sum(wee, axis=0) - return wee + if freeze: + return wee + else: + wee = wee / np.sum(wee, axis=0) + return wee - def istdp(self, wei: np.array, x: np.array, y: np.array, cutoff_weights: list): + def istdp( + self, + wei: np.array, + x: np.array, + y: np.array, + cutoff_weights: list, + freeze: bool = False, + **kwargs + ): """Apply iSTDP rule, which regulates synaptic strength between the pre inhibitory(Xj) and post Excitatory(Xi) synaptic neurons Args: @@ -243,72 +275,84 @@ def istdp(self, wei: np.array, x: np.array, y: np.array, cutoff_weights: list): cutoff_weights (list): Maximum and minimum weight ranges + freeze (bool): If True, skip the structural plasticity step. Default False + Returns: wei (array): Synaptic strengths from inhibitory to excitatory""" - # Excitatory network activity - xt = np.asarray(x)[:, 1] + if freeze: + kwargs["wei"] = wei + return kwargs + else: + # Excitatory network activity + xt = np.asarray(x)[:, 1] - # Inhibitory network activity - yt_1 = np.asarray(y)[:, 0] + # Inhibitory network activity + yt_1 = np.asarray(y)[:, 0] - # iSTDP applies only on the neurons which are connected. - wei_t = wei.copy() + # iSTDP applies only on the neurons which are connected. + wei_t = wei.copy() - for i in range( - len(wei_t[0]) - ): # Each neuron i, Post-synaptic neuron: means for each column; + for i in range( + len(wei_t[0]) + ): # Each neuron i, Post-synaptic neuron: means for each column; - for j in range( - len(wei_t[0:]) - ): # Incoming connection from j, pre-synaptic neuron to ith neuron + for j in range( + len(wei_t[0:]) + ): # Incoming connection from j, pre-synaptic neuron to ith neuron - if wei_t[j][i] != 0.0: # Check connectivity + if wei_t[j][i] != 0.0: # Check connectivity - # Get the change in weight - delta_wei_t = ( - -self.eta_inhib * yt_1[j] * (1 - xt[i] * (1 + 1 / self.mu_ip)) - ) + # Get the change in weight + delta_wei_t = ( + -self.eta_inhib + * yt_1[j] + * (1 - xt[i] * (1 + 1 / self.mu_ip)) + ) - # Update the weight between jth neuron to i ""Different from notation in article + # Update the weight between jth neuron to i ""Different from notation in article - wei_t[j][i] = wei[j][i] + delta_wei_t + wei_t[j][i] = wei[j][i] + delta_wei_t - # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight - wei_t = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) + # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight + wei_t = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) - # Check and set all weights < upper cutoff weight - wei_t = Initializer.set_max_cutoff_weight(wei_t, cutoff_weights[1]) + # Check and set all weights < upper cutoff weight + kwargs["wei"] = Initializer.set_max_cutoff_weight(wei_t, cutoff_weights[1]) - return wei_t + return kwargs @staticmethod - def structural_plasticity(wee: np.array): + def structural_plasticity(wee: np.array, freeze: bool = False): """Add new connection value to the smallest weight between excitatory units randomly Args: wee (array): Weight matrix + freeze (bool): If True, skip the structural plasticity step. Default False Returns: wee (array): Weight matrix""" - p_c = np.random.randint(0, 10, 1) + if freeze: + return wee + else: + p_c = np.random.randint(0, 10, 1) - if p_c == 0: # p_c= 0.1 + if p_c == 0: # p_c= 0.1 - # Do structural plasticity - # Choose the smallest weights randomly from the weight matrix wee - indexes = Initializer.get_unconnected_indexes(wee) + # Do structural plasticity + # Choose the smallest weights randomly from the weight matrix wee + indexes = Initializer.get_unconnected_indexes(wee) - # Choose any idx randomly such that i!=j - while True: - idx_rand = random.choice(indexes) - if idx_rand[0] != idx_rand[1]: - break + # Choose any idx randomly such that i!=j + while True: + idx_rand = random.choice(indexes) + if idx_rand[0] != idx_rand[1]: + break - wee[idx_rand[0]][idx_rand[1]] = 0.001 + wee[idx_rand[0]][idx_rand[1]] = 0.001 - return wee + return wee @staticmethod def initialize_plasticity(): @@ -872,45 +916,63 @@ def simulate_sorn( y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T + # Plasticity phase + plasticity = Plasticity() - with concurrent.futures.ProcessPoolExecutor( - max_workers=min(32, os.cpu_count() + 4) - ) as executor: + Wee[i] = plasticity.structural_plasticity( + Wee[i], freeze="sp" in self.freeze + ) - if "stdp" not in self.freeze: - stdp = executor.submit( - self.plasticity.stdp, + manager = Manager() + result_dict = manager.dict() + jobs = [] + for i in range(3): + stdp = Process( + target=plasticity.stdp, + args=( Wee[i], x_buffer, - cutoff_weights=(0.0, 1.0), - ) - - if "ip" not in self.freeze: - ip = executor.submit(self.plasticity.ip, Te[i], x_buffer) - if "istdp" not in self.freeze: - istdp = executor.submit( - self.plasticity.istdp, + [0.0, 1.0], + "stdp" in self.freeze, + result_dict, + ), + ) + stdp.start() + jobs.append(stdp) + + ip = Process( + target=plasticity.ip, + args=(Te[i], x_buffer, "ip" in self.freeze, result_dict), + ) + jobs.append(ip) + ip.start() + + istdp = Process( + target=plasticity.istdp, + args=( Wei[i], x_buffer, y_buffer, - cutoff_weights=(0.0, 1.0), - ) - - if "sp" not in self.freeze: - sp = executor.submit(self.plasticity.structural_plasticity, Wee[i]) + [0.0, 1.0], + "istdp" in self.freeze, + result_dict, + ), + ) + istdp.start() + jobs.append(istdp) - if "istdp" not in self.freeze: - Wei[i] = istdp.result() - if "ip" not in self.freeze: - Te[i] = ip.result() - if "sp" not in self.freeze: - Wee[i] = sp.result() - if "stdp" not in self.freeze: - Wee[i] = stdp.result() + for proc in jobs: + proc.join() + Wee[i], Te[i], Wei[i] = ( + result_dict["wee"], + result_dict["te"], + result_dict["wei"], + ) if "ss" not in self.freeze: - Wee[i] = self.plasticity.ss(Wee[i]) - Wei[i] = self.plasticity.ss(Wei[i]) + + Wee[i] = plasticity.ss(Wee[i], freeze="ss" in self.freeze) + Wei[i] = plasticity.ss(Wei[i], freeze="ss" in self.freeze) # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) From f86d7e0bb3799be60dd4b572d061694cb6f91cc8 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 19 Dec 2021 23:27:09 +0100 Subject: [PATCH 13/30] process manager object to hold results --- sorn/sorn.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index e7183fa..764b989 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -153,7 +153,7 @@ def stdp( x: np.array, cutoff_weights: list, freeze: bool = False, - **kwargs + result: dict = None, ): """Apply STDP rule : Regulates synaptic strength between the pre(Xj) and post(Xi) synaptic neurons @@ -170,8 +170,8 @@ def stdp( wee (array): Weight matrix """ if freeze: - kwargs["wee"] = wee - return kwargs + result["wee"] = wee + else: x = np.asarray(x) @@ -202,10 +202,9 @@ def stdp( wee_t = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) # Check and set all weights < upper cutoff weight - kwargs["wee"] = Initializer.set_max_cutoff_weight(wee_t, cutoff_weights[1]) - return kwargs + result["wee"] = Initializer.set_max_cutoff_weight(wee_t, cutoff_weights[1]) - def ip(self, te: np.array, x: np.array, freeze: bool = False, **kwargs): + def ip(self, te: np.array, x: np.array, freeze: bool = False, result: dict = None): """Intrinsic Plasiticity mechanism Args: @@ -219,13 +218,12 @@ def ip(self, te: np.array, x: np.array, freeze: bool = False, **kwargs): te (array): Threshold vector of excitatory units """ if freeze: - kwargs["te"] = te - return kwargs + result["te"] = te else: # IP rule: Active unit increases its threshold and inactive decreases its threshold. xt = x[:, 1] - kwargs["te"] = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) + result["te"] = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) # Check whether all te are in range [0.0,1.0] and update acordingly @@ -235,8 +233,6 @@ def ip(self, te: np.array, x: np.array, freeze: bool = False, **kwargs): # Set all te > 1.0 --> 1.0 # te = set_max_cutoff_weight(te_update,self.te_max) - return kwargs - @staticmethod def ss(wee: np.array, freeze: bool = False): """Synaptic Scaling or Synaptic Normalization @@ -262,7 +258,7 @@ def istdp( y: np.array, cutoff_weights: list, freeze: bool = False, - **kwargs + result: dict = None, ): """Apply iSTDP rule, which regulates synaptic strength between the pre inhibitory(Xj) and post Excitatory(Xi) synaptic neurons @@ -281,8 +277,7 @@ def istdp( wei (array): Synaptic strengths from inhibitory to excitatory""" if freeze: - kwargs["wei"] = wei - return kwargs + result["wei"] = wei else: # Excitatory network activity xt = np.asarray(x)[:, 1] @@ -318,9 +313,7 @@ def istdp( wei_t = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) # Check and set all weights < upper cutoff weight - kwargs["wei"] = Initializer.set_max_cutoff_weight(wei_t, cutoff_weights[1]) - - return kwargs + result["wei"] = Initializer.set_max_cutoff_weight(wei_t, cutoff_weights[1]) @staticmethod def structural_plasticity(wee: np.array, freeze: bool = False): From b7d4cd7719946911bce9993f0297be1cf25fd293 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 19 Dec 2021 23:30:59 +0100 Subject: [PATCH 14/30] Update sorn.py --- sorn/sorn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 764b989..b797e86 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -956,7 +956,7 @@ def simulate_sorn( for proc in jobs: proc.join() - + print(result_dict) Wee[i], Te[i], Wei[i] = ( result_dict["wee"], result_dict["te"], From b46f312b11e145808b5db2d23cd06acca920f890 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 19 Dec 2021 23:43:28 +0100 Subject: [PATCH 15/30] Update sorn.py --- sorn/sorn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index b797e86..5381131 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -883,7 +883,7 @@ def simulate_sorn( ) Te, Ti = matrix_collection.Te, matrix_collection.Ti X, Y = matrix_collection.X, matrix_collection.Y - + print("$$$", Wee) # Fraction of active connections between E-E network frac_pos_active_conn.append((Wee[i] > 0.0).sum()) From 3d0b1c2a0f997a580dc8214d8cfe3bdb35cd8b45 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 19 Dec 2021 23:46:19 +0100 Subject: [PATCH 16/30] Update sorn.py --- sorn/sorn.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 5381131..a53dddc 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -912,10 +912,6 @@ def simulate_sorn( # Plasticity phase plasticity = Plasticity() - Wee[i] = plasticity.structural_plasticity( - Wee[i], freeze="sp" in self.freeze - ) - manager = Manager() result_dict = manager.dict() jobs = [] @@ -956,12 +952,16 @@ def simulate_sorn( for proc in jobs: proc.join() - print(result_dict) Wee[i], Te[i], Wei[i] = ( result_dict["wee"], result_dict["te"], result_dict["wei"], ) + + Wee[i] = plasticity.structural_plasticity( + Wee[i], freeze="sp" in self.freeze + ) + if "ss" not in self.freeze: Wee[i] = plasticity.ss(Wee[i], freeze="ss" in self.freeze) From 981518229bd3a7ac66d36a04f82dcab8ca5fd4aa Mon Sep 17 00:00:00 2001 From: saran_nns Date: Sun, 19 Dec 2021 23:48:45 +0100 Subject: [PATCH 17/30] Update sorn.py --- sorn/sorn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index a53dddc..c566345 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -883,7 +883,7 @@ def simulate_sorn( ) Te, Ti = matrix_collection.Te, matrix_collection.Ti X, Y = matrix_collection.X, matrix_collection.Y - print("$$$", Wee) + print("$$$", Wee[i]) # Fraction of active connections between E-E network frac_pos_active_conn.append((Wee[i] > 0.0).sum()) From bb8b3a99739f1b7923f1692d2f76e11996a5fd44 Mon Sep 17 00:00:00 2001 From: Saranraj Nambusubramaniyan Date: Wed, 22 Dec 2021 19:01:32 +0100 Subject: [PATCH 18/30] concurrent (stdp,istdp,ip) update #41 --- .gitignore | 3 +- CODE_OF_CONDUCT.md | 80 +++++++++++++++ setup.py | 2 +- sorn/__init__.py | 2 +- sorn/sorn.py | 242 +++++++++++++++++++++------------------------ 5 files changed, 199 insertions(+), 130 deletions(-) create mode 100644 CODE_OF_CONDUCT.md diff --git a/.gitignore b/.gitignore index a7a43f5..b726bc8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,8 @@ dist/ sorn.egg-info/ # Logs -sorn/*.log +sorn/*.log +*.log ### VisualStudioCode ### .vscode/* diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..faf7492 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,80 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic +address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when there is a +reasonable belief that an individual's behavior may have a negative impact on +the project or its community. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team head at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/setup.py b/setup.py index ac36e1f..b622a09 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(fname): setup( name="sorn", - version="0.7.1", + version="0.7.2", author="Saranraj Nambusubramaniyan", author_email="saran_nns@hotmail.com", description="Self-Organizing Recurrent Neural Networks", diff --git a/sorn/__init__.py b/sorn/__init__.py index f26f649..d7aa167 100644 --- a/sorn/__init__.py +++ b/sorn/__init__.py @@ -3,6 +3,6 @@ from .utils import * __author__ = "Saranraj Nambusubramaniyan" -__version__ = "0.7.1" +__version__ = "0.7.2" logging.basicConfig(level=logging.INFO) diff --git a/sorn/sorn.py b/sorn/sorn.py index c566345..9eb6c24 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -5,7 +5,7 @@ import os import random import logging -from multiprocessing import Process, Manager +from multiprocessing import Pool try: from sorn.utils import Initializer @@ -149,11 +149,7 @@ def __init__(self): def stdp( self, - wee: np.array, - x: np.array, - cutoff_weights: list, - freeze: bool = False, - result: dict = None, + params, ): """Apply STDP rule : Regulates synaptic strength between the pre(Xj) and post(Xi) synaptic neurons @@ -164,16 +160,15 @@ def stdp( cutoff_weights (list): Maximum and minimum weight ranges - freeze (bool): If True, skip the structural plasticity step. Default False - Returns: wee (array): Weight matrix """ + wee, x, cutoff_weights, freeze = params[0], params[1], params[2], params[3] + if freeze: - result["wee"] = wee + return wee else: - x = np.asarray(x) xt_1 = x[:, 0] xt = x[:, 1] @@ -199,12 +194,13 @@ def stdp( wee_t[j][i] = wee[j][i] + delta_wee_t # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight - wee_t = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) + wee = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) # Check and set all weights < upper cutoff weight - result["wee"] = Initializer.set_max_cutoff_weight(wee_t, cutoff_weights[1]) + wee = Initializer.set_max_cutoff_weight(wee, cutoff_weights[1]) + return wee - def ip(self, te: np.array, x: np.array, freeze: bool = False, result: dict = None): + def ip(self, params): """Intrinsic Plasiticity mechanism Args: @@ -212,54 +208,33 @@ def ip(self, te: np.array, x: np.array, freeze: bool = False, result: dict = Non x (array): Excitatory network activity - freeze (bool): If True, skip the structural plasticity step. Default False - Returns: te (array): Threshold vector of excitatory units """ + + te, x, freeze = (params[0], params[1], params[2]) if freeze: - result["te"] = te + return te else: # IP rule: Active unit increases its threshold and inactive decreases its threshold. xt = x[:, 1] - - result["te"] = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) - - # Check whether all te are in range [0.0,1.0] and update acordingly - - # Update te < 0.0 ---> 0.0 - # te = prune_small_weights(te_update,self.te_min) - - # Set all te > 1.0 --> 1.0 - # te = set_max_cutoff_weight(te_update,self.te_max) + te = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) + return te @staticmethod - def ss(wee: np.array, freeze: bool = False): + def ss(param): """Synaptic Scaling or Synaptic Normalization Args: - wee (array): Weight matrix - - freeze (bool): If True, skip the structural plasticity step. Default False + param (array): Weight matrix Returns: - wee (array): Scaled Weight matrix + param (array): Scaled Weight matrix """ - if freeze: - return wee - else: - wee = wee / np.sum(wee, axis=0) - return wee + param = param / np.sum(param, axis=0) + return param - def istdp( - self, - wei: np.array, - x: np.array, - y: np.array, - cutoff_weights: list, - freeze: bool = False, - result: dict = None, - ): + def istdp(self, params): """Apply iSTDP rule, which regulates synaptic strength between the pre inhibitory(Xj) and post Excitatory(Xi) synaptic neurons Args: @@ -276,8 +251,16 @@ def istdp( Returns: wei (array): Synaptic strengths from inhibitory to excitatory""" + wei, x, y, cutoff_weights, freeze = ( + params[0], + params[1], + params[2], + params[3], + params[4], + ) + if freeze: - result["wei"] = wei + return wei else: # Excitatory network activity xt = np.asarray(x)[:, 1] @@ -310,42 +293,39 @@ def istdp( wei_t[j][i] = wei[j][i] + delta_wei_t # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight - wei_t = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) + wei = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) # Check and set all weights < upper cutoff weight - result["wei"] = Initializer.set_max_cutoff_weight(wei_t, cutoff_weights[1]) + wei = Initializer.set_max_cutoff_weight(wei, cutoff_weights[1]) + return wei @staticmethod - def structural_plasticity(wee: np.array, freeze: bool = False): + def structural_plasticity(param): """Add new connection value to the smallest weight between excitatory units randomly Args: - wee (array): Weight matrix - freeze (bool): If True, skip the structural plasticity step. Default False + param (array): Weight matrix Returns: - wee (array): Weight matrix""" + param (array): Weight matrix""" - if freeze: - return wee - else: - p_c = np.random.randint(0, 10, 1) + p_c = np.random.randint(0, 10, 1) - if p_c == 0: # p_c= 0.1 + if p_c == 0: # p_c= 0.1 - # Do structural plasticity - # Choose the smallest weights randomly from the weight matrix wee - indexes = Initializer.get_unconnected_indexes(wee) + # Do structural plasticity + # Choose the smallest weights randomly from the weight matrix wee + indexes = Initializer.get_unconnected_indexes(param) - # Choose any idx randomly such that i!=j - while True: - idx_rand = random.choice(indexes) - if idx_rand[0] != idx_rand[1]: - break + # Choose any idx randomly such that i!=j + while True: + idx_rand = random.choice(indexes) + if idx_rand[0] != idx_rand[1]: + break - wee[idx_rand[0]][idx_rand[1]] = 0.001 + param[idx_rand[0]][idx_rand[1]] = 0.001 - return wee + return param @staticmethod def initialize_plasticity(): @@ -855,7 +835,8 @@ def simulate_sorn( X_all = [0] * self.time_steps Y_all = [0] * self.time_steps R_all = [0] * self.time_steps - + Te_all = [0] * self.time_steps + Wee_all = [0] * self.time_steps frac_pos_active_conn = [] # To get the last activation status of Exc and Inh neurons @@ -883,7 +864,7 @@ def simulate_sorn( ) Te, Ti = matrix_collection.Te, matrix_collection.Ti X, Y = matrix_collection.X, matrix_collection.Y - print("$$$", Wee[i]) + # Fraction of active connections between E-E network frac_pos_active_conn.append((Wee[i] > 0.0).sum()) @@ -909,63 +890,32 @@ def simulate_sorn( y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T + # Plasticity phase plasticity = Plasticity() + pool = Pool(processes=4) - manager = Manager() - result_dict = manager.dict() - jobs = [] - for i in range(3): - stdp = Process( - target=plasticity.stdp, - args=( - Wee[i], - x_buffer, - [0.0, 1.0], - "stdp" in self.freeze, - result_dict, - ), - ) - stdp.start() - jobs.append(stdp) + stdp = pool.apply_async( + plasticity.stdp, [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)] + ) - ip = Process( - target=plasticity.ip, - args=(Te[i], x_buffer, "ip" in self.freeze, result_dict), - ) - jobs.append(ip) - ip.start() - - istdp = Process( - target=plasticity.istdp, - args=( - Wei[i], - x_buffer, - y_buffer, - [0.0, 1.0], - "istdp" in self.freeze, - result_dict, - ), - ) - istdp.start() - jobs.append(istdp) - - for proc in jobs: - proc.join() - Wee[i], Te[i], Wei[i] = ( - result_dict["wee"], - result_dict["te"], - result_dict["wei"], + ip = pool.apply_async( + plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] ) - Wee[i] = plasticity.structural_plasticity( - Wee[i], freeze="sp" in self.freeze + istdp = pool.apply_async( + plasticity.istdp, + [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], ) + Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + + if "sp" not in self.freeze: + Wee[i] = plasticity.structural_plasticity(Wee[i]) if "ss" not in self.freeze: - Wee[i] = plasticity.ss(Wee[i], freeze="ss" in self.freeze) - Wei[i] = plasticity.ss(Wei[i], freeze="ss" in self.freeze) + Wee[i] = plasticity.ss(Wee[i]) + Wei[i] = plasticity.ss(Wei[i]) # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) @@ -975,6 +925,8 @@ def simulate_sorn( X_all[i] = x_buffer[:, 1] Y_all[i] = y_buffer[:, 1] R_all[i] = r + Te_all[i] = Te[i] + Wee_all[i] = Wee[i] plastic_matrices = { "Wee": matrix_collection.Wee[-1], @@ -986,7 +938,15 @@ def simulate_sorn( "Y": Y[-1], } - return plastic_matrices, X_all, Y_all, R_all, frac_pos_active_conn + return ( + plastic_matrices, + X_all, + Y_all, + R_all, + Te_all, + Wee_all, + frac_pos_active_conn, + ) class Trainer_(Sorn): @@ -1073,6 +1033,8 @@ def train_sorn( X_all = [0] * self.time_steps Y_all = [0] * self.time_steps R_all = [0] * self.time_steps + Te_all = [0] * self.time_steps + Wee_all = [0] * self.time_steps frac_pos_active_conn = [] @@ -1128,16 +1090,33 @@ def train_sorn( y_buffer[:, 1] = inhibitory_state_yt_buffer.T if self.phase == "plasticity": - Wee[i], Wei[i], Te[i] = Async( - x_buffer, - y_buffer, - Wee[i], - Wei[i], - Te[i], - self.freeze, - max_workers=max_workers, + # Plasticity phase + plasticity = Plasticity() + pool = Pool(processes=4) + + stdp = pool.apply_async( + plasticity.stdp, + [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)], ) + ip = pool.apply_async( + plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] + ) + + istdp = pool.apply_async( + plasticity.istdp, + [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], + ) + Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + + if "sp" not in self.freeze: + Wee[i] = plasticity.structural_plasticity(Wee[i]) + + if "ss" not in self.freeze: + + Wee[i] = plasticity.ss(Wee[i]) + Wei[i] = plasticity.ss(Wei[i]) + else: # Wee[i], Wei[i], Te[i] remain same pass @@ -1150,7 +1129,8 @@ def train_sorn( X_all[i] = x_buffer[:, 1] Y_all[i] = y_buffer[:, 1] R_all[i] = r - + Te_all[i] = Te[i] + Wee_all[i] = Wee[i] plastic_matrices = { "Wee": matrix_collection.Wee[-1], "Wei": matrix_collection.Wei[-1], @@ -1161,7 +1141,15 @@ def train_sorn( "Y": Y[-1], } - return plastic_matrices, X_all, Y_all, R_all, frac_pos_active_conn + return ( + plastic_matrices, + X_all, + Y_all, + R_all, + Te_all, + Wee_all, + frac_pos_active_conn, + ) Trainer = Trainer_() From daf325d4db57c4b2fad693be94e12f2ab2139ac8 Mon Sep 17 00:00:00 2001 From: Saranraj Nambusubramaniyan Date: Thu, 23 Dec 2021 10:56:50 +0100 Subject: [PATCH 19/30] light weight update Track E, R and C --- sorn/sorn.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 9eb6c24..0037be9 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -833,10 +833,7 @@ def simulate_sorn( # Collect the network activity at all time steps X_all = [0] * self.time_steps - Y_all = [0] * self.time_steps R_all = [0] * self.time_steps - Te_all = [0] * self.time_steps - Wee_all = [0] * self.time_steps frac_pos_active_conn = [] # To get the last activation status of Exc and Inh neurons @@ -923,10 +920,7 @@ def simulate_sorn( matrix_collection.network_activity_t(x_buffer, y_buffer, i) X_all[i] = x_buffer[:, 1] - Y_all[i] = y_buffer[:, 1] R_all[i] = r - Te_all[i] = Te[i] - Wee_all[i] = Wee[i] plastic_matrices = { "Wee": matrix_collection.Wee[-1], @@ -941,10 +935,7 @@ def simulate_sorn( return ( plastic_matrices, X_all, - Y_all, R_all, - Te_all, - Wee_all, frac_pos_active_conn, ) @@ -1031,10 +1022,7 @@ def train_sorn( self.freeze = [] if freeze == None else freeze self.max_workers = max_workers X_all = [0] * self.time_steps - Y_all = [0] * self.time_steps R_all = [0] * self.time_steps - Te_all = [0] * self.time_steps - Wee_all = [0] * self.time_steps frac_pos_active_conn = [] @@ -1127,10 +1115,8 @@ def train_sorn( matrix_collection.network_activity_t(x_buffer, y_buffer, i) X_all[i] = x_buffer[:, 1] - Y_all[i] = y_buffer[:, 1] R_all[i] = r - Te_all[i] = Te[i] - Wee_all[i] = Wee[i] + plastic_matrices = { "Wee": matrix_collection.Wee[-1], "Wei": matrix_collection.Wei[-1], @@ -1144,10 +1130,7 @@ def train_sorn( return ( plastic_matrices, X_all, - Y_all, R_all, - Te_all, - Wee_all, frac_pos_active_conn, ) From af67af5d5ed1935de5829bae87c48cdd588bef4b Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 10:42:03 +0100 Subject: [PATCH 20/30] clean childtasks at iter --- sorn/sorn.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sorn/sorn.py b/sorn/sorn.py index 0037be9..533ec10 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -906,6 +906,9 @@ def simulate_sorn( ) Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + pool.close() + pool.join() + if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) @@ -1097,6 +1100,9 @@ def train_sorn( ) Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + pool.close() + pool.join() + if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) From fdd89ed64a13391ab5500bf585377b3ca851da43 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 13:00:32 +0100 Subject: [PATCH 21/30] free pool initiate new at each iter --- sorn/sorn.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 533ec10..98e591e 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -890,7 +890,7 @@ def simulate_sorn( # Plasticity phase plasticity = Plasticity() - pool = Pool(processes=4) + pool = Pool(processes=3, maxtasksperchild=1000) stdp = pool.apply_async( plasticity.stdp, [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)] @@ -906,9 +906,6 @@ def simulate_sorn( ) Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() - pool.close() - pool.join() - if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) @@ -916,7 +913,7 @@ def simulate_sorn( Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) - + pool.close() # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) @@ -1083,7 +1080,7 @@ def train_sorn( if self.phase == "plasticity": # Plasticity phase plasticity = Plasticity() - pool = Pool(processes=4) + pool = Pool(processes=3, maxtasksperchild=10000) stdp = pool.apply_async( plasticity.stdp, @@ -1100,8 +1097,7 @@ def train_sorn( ) Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() - pool.close() - pool.join() + # pool.join() if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) @@ -1110,6 +1106,7 @@ def train_sorn( Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) + pool.close() else: # Wee[i], Wei[i], Te[i] remain same From 4139b9196a0e70fd9ef49b0144d2ef64224aa99b Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 20:04:29 +0100 Subject: [PATCH 22/30] ray parallelize stdp and istdp tremoved async. ip,sp,ss are I/O task and are freed from pool --- sorn/sorn.py | 127 +++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 66 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 98e591e..04a74b5 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -2,10 +2,9 @@ from __future__ import division import numpy as np -import os import random import logging -from multiprocessing import Pool +import ray try: from sorn.utils import Initializer @@ -147,9 +146,13 @@ def __init__(self): self.te_min = Sorn.te_min # Excitatory minimum Threshold self.te_max = Sorn.te_max # Excitatory maximum Threshold + @ray.remote def stdp( self, - params, + wee: np.array, + x: np.array, + cutoff_weights: list, + freeze: bool = False, ): """Apply STDP rule : Regulates synaptic strength between the pre(Xj) and post(Xi) synaptic neurons @@ -160,10 +163,11 @@ def stdp( cutoff_weights (list): Maximum and minimum weight ranges + freeze (bool): If True, skip the structural plasticity step. Default False + Returns: wee (array): Weight matrix """ - wee, x, cutoff_weights, freeze = params[0], params[1], params[2], params[3] if freeze: return wee @@ -200,7 +204,7 @@ def stdp( wee = Initializer.set_max_cutoff_weight(wee, cutoff_weights[1]) return wee - def ip(self, params): + def ip(self, te: np.array, x: np.array, freeze: bool = False): """Intrinsic Plasiticity mechanism Args: @@ -208,11 +212,12 @@ def ip(self, params): x (array): Excitatory network activity + freeze (bool): If True, skip the structural plasticity step. Default False + Returns: te (array): Threshold vector of excitatory units """ - te, x, freeze = (params[0], params[1], params[2]) if freeze: return te else: @@ -222,19 +227,32 @@ def ip(self, params): return te @staticmethod - def ss(param): + def ss(wee: np.array, freeze: bool = False): """Synaptic Scaling or Synaptic Normalization Args: - param (array): Weight matrix + wee (array): Weight matrix + + freeze (bool): If True, skip the structural plasticity step. Default False Returns: - param (array): Scaled Weight matrix + wee (array): Scaled Weight matrix """ - param = param / np.sum(param, axis=0) - return param + if freeze: + return wee + else: + wee = wee / np.sum(wee, axis=0) + return wee - def istdp(self, params): + @ray.remote + def istdp( + self, + wei: np.array, + x: np.array, + y: np.array, + cutoff_weights: list, + freeze: bool = False, + ): """Apply iSTDP rule, which regulates synaptic strength between the pre inhibitory(Xj) and post Excitatory(Xi) synaptic neurons Args: @@ -251,14 +269,6 @@ def istdp(self, params): Returns: wei (array): Synaptic strengths from inhibitory to excitatory""" - wei, x, y, cutoff_weights, freeze = ( - params[0], - params[1], - params[2], - params[3], - params[4], - ) - if freeze: return wei else: @@ -300,32 +310,35 @@ def istdp(self, params): return wei @staticmethod - def structural_plasticity(param): + def structural_plasticity(wee: np.array, freeze: bool = False): """Add new connection value to the smallest weight between excitatory units randomly Args: - param (array): Weight matrix + wee (array): Weight matrix Returns: - param (array): Weight matrix""" + wee (array): Weight matrix""" + if freeze: + return wee + else: - p_c = np.random.randint(0, 10, 1) + p_c = np.random.randint(0, 10, 1) - if p_c == 0: # p_c= 0.1 + if p_c == 0: # p_c= 0.1 - # Do structural plasticity - # Choose the smallest weights randomly from the weight matrix wee - indexes = Initializer.get_unconnected_indexes(param) + # Do structural plasticity + # Choose the smallest weights randomly from the weight matrix wee + indexes = Initializer.get_unconnected_indexes(wee) - # Choose any idx randomly such that i!=j - while True: - idx_rand = random.choice(indexes) - if idx_rand[0] != idx_rand[1]: - break + # Choose any idx randomly such that i!=j + while True: + idx_rand = random.choice(indexes) + if idx_rand[0] != idx_rand[1]: + break - param[idx_rand[0]][idx_rand[1]] = 0.001 + wee[idx_rand[0]][idx_rand[1]] = 0.001 - return param + return wee @staticmethod def initialize_plasticity(): @@ -890,21 +903,16 @@ def simulate_sorn( # Plasticity phase plasticity = Plasticity() - pool = Pool(processes=3, maxtasksperchild=1000) - stdp = pool.apply_async( - plasticity.stdp, [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)] + stdp_id = plasticity.stdp.remote( + Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze ) - - ip = pool.apply_async( - plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] + istdp_id = plasticity.istdp.remote( + Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze ) + Te[i] = plasticity.ip(Te[i], x_buffer, "ip" in self.freeze) - istdp = pool.apply_async( - plasticity.istdp, - [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], - ) - Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + Wee[i], Wei[i] = ray.get([stdp_id, istdp_id]) if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) @@ -913,7 +921,6 @@ def simulate_sorn( Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) - pool.close() # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) @@ -1080,34 +1087,22 @@ def train_sorn( if self.phase == "plasticity": # Plasticity phase plasticity = Plasticity() - pool = Pool(processes=3, maxtasksperchild=10000) - - stdp = pool.apply_async( - plasticity.stdp, - [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)], - ) - ip = pool.apply_async( - plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] + stdp_id = plasticity.stdp.remote( + Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze ) - istdp = pool.apply_async( - plasticity.istdp, - [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], + istdp_id = plasticity.istdp.remote( + Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze ) - Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() - - # pool.join() - - if "sp" not in self.freeze: - Wee[i] = plasticity.structural_plasticity(Wee[i]) + Te[i] = plasticity.ip(Te[i], x_buffer, "ip" in self.freeze) + Wee[i], Wei[i] = ray.get([stdp_id, istdp_id]) + Wee[i] = plasticity.structural_plasticity(Wee[i], "sp" in self.freeze) if "ss" not in self.freeze: Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) - pool.close() - else: # Wee[i], Wei[i], Te[i] remain same pass From ac6d8904d01e6a780ccac8db232eed06d322ec08 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 20:05:03 +0100 Subject: [PATCH 23/30] include ray pkg --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9b0be14..434fc20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ scipy seaborn six wincertstore +ray From 0890045268b5cea400dd213d61a5741568df1ccd Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 20:06:19 +0100 Subject: [PATCH 24/30] Delete test_multi.py --- sorn/test_multi.py | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 sorn/test_multi.py diff --git a/sorn/test_multi.py b/sorn/test_multi.py deleted file mode 100644 index 2b4965e..0000000 --- a/sorn/test_multi.py +++ /dev/null @@ -1,23 +0,0 @@ -import sorn -from sorn import Simulator -import numpy as np -import timeit - -# Sample input -num_features = 10 -time_steps = 10 -inputs = np.random.rand(num_features, time_steps) - -start_time = timeit.default_timer() - -# Simulate the network with default hyperparameters under gaussian white noise -Simulator.simulate_sorn( - inputs=inputs, - phase="plasticity", - matrices=None, - noise=True, - time_steps=time_steps, - ne=200, -) - -print(timeit.default_timer() - start_time) From 81e95d42c2224a5bcd1d20258b3f99c69a2c025a Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 20:08:54 +0100 Subject: [PATCH 25/30] Update setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b622a09..91fde9f 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ def read(fname): "seaborn", "pandas", "networkx", + "ray", ], zip_safe=False, ) From 7c7189e02274bd36d3f800a9a9dc2c83147e2538 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 20:43:29 +0100 Subject: [PATCH 26/30] ray init() update --- sorn/sorn.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 04a74b5..6938405 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -11,6 +11,8 @@ except: from utils import Initializer +ray.init() + logging.basicConfig( level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s", @@ -893,10 +895,8 @@ def simulate_sorn( ) # Update X and Y - x_buffer[:, 0] = X[i][:, 1] # xt -->(becomes) xt_1 - x_buffer[ - :, 1 - ] = excitatory_state_xt_buffer.T # New_activation; x_buffer --> xt + x_buffer[:, 0] = X[i][:, 1] # xt --> xt_1 + x_buffer[:, 1] = excitatory_state_xt_buffer.T # x_buffer --> xt y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T @@ -929,7 +929,7 @@ def simulate_sorn( X_all[i] = x_buffer[:, 1] R_all[i] = r - plastic_matrices = { + state_dict = { "Wee": matrix_collection.Wee[-1], "Wei": matrix_collection.Wei[-1], "Wie": matrix_collection.Wie[-1], @@ -940,7 +940,7 @@ def simulate_sorn( } return ( - plastic_matrices, + state_dict, X_all, R_all, frac_pos_active_conn, @@ -1115,7 +1115,7 @@ def train_sorn( X_all[i] = x_buffer[:, 1] R_all[i] = r - plastic_matrices = { + state_dict = { "Wee": matrix_collection.Wee[-1], "Wei": matrix_collection.Wei[-1], "Wie": matrix_collection.Wie[-1], @@ -1126,7 +1126,7 @@ def train_sorn( } return ( - plastic_matrices, + state_dict, X_all, R_all, frac_pos_active_conn, From 71ce256ee29d5043c3af1d02607cb08bc2932405 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Fri, 24 Dec 2021 21:26:48 +0100 Subject: [PATCH 27/30] ray doesnt support class methods --- sorn/sorn.py | 143 ++++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/sorn/sorn.py b/sorn/sorn.py index 6938405..6f12bf8 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -2,17 +2,16 @@ from __future__ import division import numpy as np +import os import random import logging -import ray +from multiprocessing import Pool try: from sorn.utils import Initializer except: from utils import Initializer -ray.init() - logging.basicConfig( level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s", @@ -148,13 +147,9 @@ def __init__(self): self.te_min = Sorn.te_min # Excitatory minimum Threshold self.te_max = Sorn.te_max # Excitatory maximum Threshold - @ray.remote def stdp( self, - wee: np.array, - x: np.array, - cutoff_weights: list, - freeze: bool = False, + params, ): """Apply STDP rule : Regulates synaptic strength between the pre(Xj) and post(Xi) synaptic neurons @@ -165,11 +160,10 @@ def stdp( cutoff_weights (list): Maximum and minimum weight ranges - freeze (bool): If True, skip the structural plasticity step. Default False - Returns: wee (array): Weight matrix """ + wee, x, cutoff_weights, freeze = params[0], params[1], params[2], params[3] if freeze: return wee @@ -206,7 +200,7 @@ def stdp( wee = Initializer.set_max_cutoff_weight(wee, cutoff_weights[1]) return wee - def ip(self, te: np.array, x: np.array, freeze: bool = False): + def ip(self, params): """Intrinsic Plasiticity mechanism Args: @@ -214,12 +208,11 @@ def ip(self, te: np.array, x: np.array, freeze: bool = False): x (array): Excitatory network activity - freeze (bool): If True, skip the structural plasticity step. Default False - Returns: te (array): Threshold vector of excitatory units """ + te, x, freeze = (params[0], params[1], params[2]) if freeze: return te else: @@ -229,32 +222,19 @@ def ip(self, te: np.array, x: np.array, freeze: bool = False): return te @staticmethod - def ss(wee: np.array, freeze: bool = False): + def ss(param): """Synaptic Scaling or Synaptic Normalization Args: - wee (array): Weight matrix - - freeze (bool): If True, skip the structural plasticity step. Default False + param (array): Weight matrix Returns: - wee (array): Scaled Weight matrix + param (array): Scaled Weight matrix """ - if freeze: - return wee - else: - wee = wee / np.sum(wee, axis=0) - return wee + param = param / np.sum(param, axis=0) + return param - @ray.remote - def istdp( - self, - wei: np.array, - x: np.array, - y: np.array, - cutoff_weights: list, - freeze: bool = False, - ): + def istdp(self, params): """Apply iSTDP rule, which regulates synaptic strength between the pre inhibitory(Xj) and post Excitatory(Xi) synaptic neurons Args: @@ -271,6 +251,14 @@ def istdp( Returns: wei (array): Synaptic strengths from inhibitory to excitatory""" + wei, x, y, cutoff_weights, freeze = ( + params[0], + params[1], + params[2], + params[3], + params[4], + ) + if freeze: return wei else: @@ -312,35 +300,32 @@ def istdp( return wei @staticmethod - def structural_plasticity(wee: np.array, freeze: bool = False): + def structural_plasticity(param): """Add new connection value to the smallest weight between excitatory units randomly Args: - wee (array): Weight matrix + param (array): Weight matrix Returns: - wee (array): Weight matrix""" - if freeze: - return wee - else: + param (array): Weight matrix""" - p_c = np.random.randint(0, 10, 1) + p_c = np.random.randint(0, 10, 1) - if p_c == 0: # p_c= 0.1 + if p_c == 0: # p_c= 0.1 - # Do structural plasticity - # Choose the smallest weights randomly from the weight matrix wee - indexes = Initializer.get_unconnected_indexes(wee) + # Do structural plasticity + # Choose the smallest weights randomly from the weight matrix wee + indexes = Initializer.get_unconnected_indexes(param) - # Choose any idx randomly such that i!=j - while True: - idx_rand = random.choice(indexes) - if idx_rand[0] != idx_rand[1]: - break + # Choose any idx randomly such that i!=j + while True: + idx_rand = random.choice(indexes) + if idx_rand[0] != idx_rand[1]: + break - wee[idx_rand[0]][idx_rand[1]] = 0.001 + param[idx_rand[0]][idx_rand[1]] = 0.001 - return wee + return param @staticmethod def initialize_plasticity(): @@ -895,24 +880,31 @@ def simulate_sorn( ) # Update X and Y - x_buffer[:, 0] = X[i][:, 1] # xt --> xt_1 - x_buffer[:, 1] = excitatory_state_xt_buffer.T # x_buffer --> xt + x_buffer[:, 0] = X[i][:, 1] # xt -->(becomes) xt_1 + x_buffer[ + :, 1 + ] = excitatory_state_xt_buffer.T # New_activation; x_buffer --> xt y_buffer[:, 0] = Y[i][:, 1] y_buffer[:, 1] = inhibitory_state_yt_buffer.T # Plasticity phase plasticity = Plasticity() + pool = Pool() - stdp_id = plasticity.stdp.remote( - Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze + stdp = pool.apply_async( + plasticity.stdp, [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)] ) - istdp_id = plasticity.istdp.remote( - Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze + + ip = pool.apply_async( + plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] ) - Te[i] = plasticity.ip(Te[i], x_buffer, "ip" in self.freeze) - Wee[i], Wei[i] = ray.get([stdp_id, istdp_id]) + istdp = pool.apply_async( + plasticity.istdp, + [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], + ) + Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) @@ -921,6 +913,7 @@ def simulate_sorn( Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) + pool.close() # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) @@ -929,7 +922,7 @@ def simulate_sorn( X_all[i] = x_buffer[:, 1] R_all[i] = r - state_dict = { + plastic_matrices = { "Wee": matrix_collection.Wee[-1], "Wei": matrix_collection.Wei[-1], "Wie": matrix_collection.Wie[-1], @@ -940,7 +933,7 @@ def simulate_sorn( } return ( - state_dict, + plastic_matrices, X_all, R_all, frac_pos_active_conn, @@ -1087,22 +1080,34 @@ def train_sorn( if self.phase == "plasticity": # Plasticity phase plasticity = Plasticity() + pool = Pool() + + stdp = pool.apply_async( + plasticity.stdp, + [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)], + ) - stdp_id = plasticity.stdp.remote( - Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze + ip = pool.apply_async( + plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] ) - istdp_id = plasticity.istdp.remote( - Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze + istdp = pool.apply_async( + plasticity.istdp, + [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], ) - Te[i] = plasticity.ip(Te[i], x_buffer, "ip" in self.freeze) - Wee[i], Wei[i] = ray.get([stdp_id, istdp_id]) - Wee[i] = plasticity.structural_plasticity(Wee[i], "sp" in self.freeze) + Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + + # pool.join() + + if "sp" not in self.freeze: + Wee[i] = plasticity.structural_plasticity(Wee[i]) if "ss" not in self.freeze: Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) + pool.close() + else: # Wee[i], Wei[i], Te[i] remain same pass @@ -1115,7 +1120,7 @@ def train_sorn( X_all[i] = x_buffer[:, 1] R_all[i] = r - state_dict = { + plastic_matrices = { "Wee": matrix_collection.Wee[-1], "Wei": matrix_collection.Wei[-1], "Wie": matrix_collection.Wie[-1], @@ -1126,7 +1131,7 @@ def train_sorn( } return ( - state_dict, + plastic_matrices, X_all, R_all, frac_pos_active_conn, From 968bdc5a8fc40f28857731356d0719d53ad78eeb Mon Sep 17 00:00:00 2001 From: saran_nns Date: Mon, 27 Dec 2021 21:19:17 +0100 Subject: [PATCH 28/30] added callbacks, removed async ops --- CODE_OF_CONDUCT.md | 80 -------- README.md | 39 +++- requirements.txt | 1 - setup.py | 3 +- sorn/__init__.py | 2 +- sorn/callbacks.py | 204 +++++++++++++++++++ sorn/sorn.py | 482 ++++++++++++++++++++++++--------------------- sorn/test_sorn.py | 227 --------------------- sorn/utils.py | 80 ++++---- test_sorn.py | 70 +++++-- 10 files changed, 594 insertions(+), 594 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md create mode 100644 sorn/callbacks.py delete mode 100644 sorn/test_sorn.py diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index faf7492..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,80 +0,0 @@ -# Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic -address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a -professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies within all project spaces, and it also applies when -an individual is representing the project or its community in public spaces. -Examples of representing a project or community include using an official -project e-mail address, posting via an official social media account, or acting -as an appointed representative at an online or offline event. Representation of -a project may be further defined and clarified by project maintainers. - -This Code of Conduct also applies outside the project spaces when there is a -reasonable belief that an individual's behavior may have a negative impact on -the project or its community. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team head at . All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/README.md b/README.md index c100d64..e524c27 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,12 @@ time_steps = 200 inputs = np.random.rand(num_features,time_steps) # Simulate the network with default hyperparameters under gaussian white noise -state_dict, E, I, R, C = Simulator.simulate_sorn(inputs = inputs, phase='plasticity', +state_dict, sim_dict = Simulator.simulate_sorn(inputs = inputs, phase='plasticity', matrices=None, noise = True, - time_steps=time_steps) + time_steps=time_steps, + callbacks = ["ExcitatoryActivation", + "WEE", + "EEConnectionCounts"]) ``` ``` @@ -71,22 +74,40 @@ from sorn import Trainer inputs = np.random.rand(num_features,1) # SORN network is frozen during training phase -state_dict, E, I, R, C = Trainer.train_sorn(inputs = inputs, phase='training', +state_dict, sim_dict = Trainer.train_sorn(inputs = inputs, phase='training', matrices=state_dict, noise= False, time_steps=1, ne = 100, nu=num_features, - lambda_ee = 10, eta_stdp=0.001 ) + lambda_ee = 10, eta_stdp=0.001, + callbacks = ["InhibitoryActivation", + "WEI", + "EIConnectionCounts"] ) ``` ### Network Output Descriptions - `state_dict` - Dictionary of connection weights (`Wee`,`Wei`,`Wie`) , Excitatory network activity (`X`), Inhibitory network activities(`Y`), Threshold values (`Te`,`Ti`) + `state_dict` - Dictionary of connection weights (`Wee`, `Wei`, `Wie`) , Excitatory network activity (`X`), Inhibitory network activities(`Y`), Threshold values (`Te`, `Ti`) - `E` - Excitatory network activity of entire simulation period + `sim_dict` - Dictionary of network states and parameters collected during the simulation/training: Provided, all available options of the argument `callbacks`, then the `sim_dict` should contain the following; - `I` - Inhibitory network activity of entire simulation period + "ExcitatoryActivation" - Excitatory network activity of entire simulation period - `R` - Recurrent network activity of entire simulation period + "InhibitoryActivation" - Inhibitory network activity of entire simulation period + + "RecurrentActivation" - Recurrent network activity of entire simulation period + + "EEConnectionCounts" - Number of active connections in the Excitatory pool at each time step + + "EIConnectionCounts" - Number of active connections from Inhibitory to Excitatory pool at each time step + + "TE" - Threshold values of excitatory neurons at each time step + + "TI" - Threshold values of inhibitory neurons at each time step + + "WEE" - Synaptic efficacies between excitatory neurons + + "WEI" - Connection weights from inhibitory to excitatory neurons + + "WIE" - Connection weights from excitatory to inhibitory neurons - `C` - Number of active connections in the Excitatory pool at each time step ### Documentation For detailed documentation about development, analysis, plotting methods and a sample experiment with OpenAI Gym, please visit [SORN Documentation](https://self-organizing-recurrent-neural-networks.readthedocs.io/en/latest/) diff --git a/requirements.txt b/requirements.txt index 434fc20..9b0be14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,4 @@ scipy seaborn six wincertstore -ray diff --git a/setup.py b/setup.py index 91fde9f..013dc9c 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(fname): setup( name="sorn", - version="0.7.2", + version="0.7.3", author="Saranraj Nambusubramaniyan", author_email="saran_nns@hotmail.com", description="Self-Organizing Recurrent Neural Networks", @@ -42,7 +42,6 @@ def read(fname): "seaborn", "pandas", "networkx", - "ray", ], zip_safe=False, ) diff --git a/sorn/__init__.py b/sorn/__init__.py index d7aa167..cb6eed7 100644 --- a/sorn/__init__.py +++ b/sorn/__init__.py @@ -3,6 +3,6 @@ from .utils import * __author__ = "Saranraj Nambusubramaniyan" -__version__ = "0.7.2" +__version__ = "0.7.3" logging.basicConfig(level=logging.INFO) diff --git a/sorn/callbacks.py b/sorn/callbacks.py new file mode 100644 index 0000000..aab1f53 --- /dev/null +++ b/sorn/callbacks.py @@ -0,0 +1,204 @@ +class Callbacks: + def __init__(self, timesteps, avail_callbacks, *argv): + self.timesteps = timesteps + self.argv = argv + if argv: + self.dispatcher = Validate().callbacks(argv, avail_callbacks) + + self.callbacks = [] + for class_name in list(self.dispatcher.keys()): + instance = self.create(class_name) + self.dispatcher[class_name] = instance + + def create(self, class_name) -> object: + instance = globals()[class_name](self.timesteps) + return instance + + def update(self, func, value, timestep): + try: + func[timestep] = value + except: + return f"Invalid callback instance {func}" + + def step(self, state, time_step) -> None: + + for callback in list(self.dispatcher.keys()): + self.update(self.dispatcher[callback], state[callback], time_step) + + def get(self) -> dict: + + if self.argv: + for name, callback in self.dispatcher.items(): + self.dispatcher[name] = callback.values + return self.dispatcher + + else: + return {} + + +class Validate: + def __init__(self): + pass + + def callbacks(self, req_callbacks, avail_callbacks): + + self.argv = req_callbacks + self.avail_callbacks = list(avail_callbacks.keys()) + + if self.argv: + return self.assert_callbacks() + + else: + return None + + def assert_callbacks(self) -> dict: + + if not set(*self.argv) == set(self.avail_callbacks): + assert set(*self.argv).issubset( + set(self.avail_callbacks) + ), f"{list(set(*self.argv)-set(self.avail_callbacks))} not available" + + return dict.fromkeys(set(*self.argv) & set(self.avail_callbacks)) + + else: + return dict.fromkeys(self.avail_callbacks) + + +class ExcitatoryActivation: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Excitatory network state at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class InhibitoryActivation: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Inhibitory network state at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class RecurrentActivation: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Recurrent network state at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class WEE: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Excitatory to Excitatory Connection strength at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class WEI: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Inhibitory to Excitatory Connection strength at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class TE: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Excitatory neurons firing threshold values at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class TI: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Inhibitory neurons firing threshold values at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class EEConnectionCounts: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Number active connections in the Excitatory pool at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class EIConnectionCounts: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Number active connections from Inhibitory to Excitatory pool at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) + + +class IEConnectionCounts: + def __init__(self, timesteps=0): + self.values = [0] * timesteps + + def __setitem__(self, index, value): + self.values[index] = value + + def __getitem__(self, index): + return f"Number active connections from Excitatory to Inhibitory pool at time_step {index}: {self.values[index]}" + + def __str__(self): + return str(self.values) diff --git a/sorn/sorn.py b/sorn/sorn.py index 6f12bf8..b05168f 100644 --- a/sorn/sorn.py +++ b/sorn/sorn.py @@ -7,6 +7,8 @@ import logging from multiprocessing import Pool +from sorn.callbacks import * + try: from sorn.utils import Initializer except: @@ -143,171 +145,138 @@ def __init__(self): self.mu_ip = Sorn.mu_ip # Mean target firing rate # Number of inhibitory units in the network self.ni = int(0.2 * Sorn.ne) - self.time_steps = Sorn.time_steps # Total time steps of simulation + self.timesteps = Sorn.timesteps # Total time steps of simulation self.te_min = Sorn.te_min # Excitatory minimum Threshold self.te_max = Sorn.te_max # Excitatory maximum Threshold - def stdp( - self, - params, - ): + def stdp(self, wee: np.array, x: np.array, cutoff_weights: list): """Apply STDP rule : Regulates synaptic strength between the pre(Xj) and post(Xi) synaptic neurons - Args: wee (array): Weight matrix - x (array): Excitatory network activity - cutoff_weights (list): Maximum and minimum weight ranges - Returns: wee (array): Weight matrix """ - wee, x, cutoff_weights, freeze = params[0], params[1], params[2], params[3] - if freeze: - return wee + x = np.asarray(x) + xt_1 = x[:, 0] + xt = x[:, 1] + wee_t = wee.copy() - else: - x = np.asarray(x) - xt_1 = x[:, 0] - xt = x[:, 1] - wee_t = wee.copy() + # STDP applies only on the neurons which are connected. - # STDP applies only on the neurons which are connected. + for i in range(len(wee_t[0])): # Each neuron i, Post-synaptic neuron - for i in range(len(wee_t[0])): # Each neuron i, Post-synaptic neuron + for j in range( + len(wee_t[0:]) + ): # Incoming connection from jth pre-synaptic neuron to ith neuron - for j in range( - len(wee_t[0:]) - ): # Incoming connection from jth pre-synaptic neuron to ith neuron + if wee_t[j][i] != 0.0: # Check connectivity - if wee_t[j][i] != 0.0: # Check connectivity + # Get the change in weight + delta_wee_t = self.eta_stdp * (xt[i] * xt_1[j] - xt_1[i] * xt[j]) - # Get the change in weight - delta_wee_t = self.eta_stdp * ( - xt[i] * xt_1[j] - xt_1[i] * xt[j] - ) + # Update the weight between jth neuron to i ""Different from notation in article - # Update the weight between jth neuron to i ""Different from notation in article + wee_t[j][i] = wee[j][i] + delta_wee_t - wee_t[j][i] = wee[j][i] + delta_wee_t + # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight + wee_t = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) - # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight - wee = Initializer.prune_small_weights(wee_t, cutoff_weights[0]) + # Check and set all weights < upper cutoff weight + wee_t = Initializer.set_max_cutoff_weight(wee_t, cutoff_weights[1]) - # Check and set all weights < upper cutoff weight - wee = Initializer.set_max_cutoff_weight(wee, cutoff_weights[1]) - return wee + return wee_t - def ip(self, params): + def ip(self, te: np.array, x: np.array): """Intrinsic Plasiticity mechanism - Args: te (array): Threshold vector of excitatory units - x (array): Excitatory network activity - Returns: te (array): Threshold vector of excitatory units """ - te, x, freeze = (params[0], params[1], params[2]) - if freeze: - return te - else: - # IP rule: Active unit increases its threshold and inactive decreases its threshold. - xt = x[:, 1] - te = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) - return te + # IP rule: Active unit increases its threshold and inactive decreases its threshold. + xt = x[:, 1] + + te_update = te + self.eta_ip * (xt.reshape(self.ne, 1) - self.h_ip) + + # Check whether all te are in range [0.0,1.0] and update acordingly + + # Update te < 0.0 ---> 0.0 + # te_update = prune_small_weights(te_update,self.te_min) + + # Set all te > 1.0 --> 1.0 + # te_update = set_max_cutoff_weight(te_update,self.te_max) + + return te_update @staticmethod - def ss(param): + def ss(wee: np.array): """Synaptic Scaling or Synaptic Normalization - Args: - param (array): Weight matrix - + wee (array): Weight matrix Returns: - param (array): Scaled Weight matrix + wee (array): Scaled Weight matrix """ - param = param / np.sum(param, axis=0) - return param + wee = wee / np.sum(wee, axis=0) + return wee - def istdp(self, params): + def istdp(self, wei: np.array, x: np.array, y: np.array, cutoff_weights: list): """Apply iSTDP rule, which regulates synaptic strength between the pre inhibitory(Xj) and post Excitatory(Xi) synaptic neurons - Args: wei (array): Synaptic strengths from inhibitory to excitatory - x (array): Excitatory network activity - y (array): Inhibitory network activity - cutoff_weights (list): Maximum and minimum weight ranges - - freeze (bool): If True, skip the structural plasticity step. Default False - Returns: wei (array): Synaptic strengths from inhibitory to excitatory""" - wei, x, y, cutoff_weights, freeze = ( - params[0], - params[1], - params[2], - params[3], - params[4], - ) + # Excitatory network activity + xt = np.asarray(x)[:, 1] - if freeze: - return wei - else: - # Excitatory network activity - xt = np.asarray(x)[:, 1] + # Inhibitory network activity + yt_1 = np.asarray(y)[:, 0] - # Inhibitory network activity - yt_1 = np.asarray(y)[:, 0] + # iSTDP applies only on the neurons which are connected. + wei_t = wei.copy() - # iSTDP applies only on the neurons which are connected. - wei_t = wei.copy() + for i in range( + len(wei_t[0]) + ): # Each neuron i, Post-synaptic neuron: means for each column; - for i in range( - len(wei_t[0]) - ): # Each neuron i, Post-synaptic neuron: means for each column; + for j in range( + len(wei_t[0:]) + ): # Incoming connection from j, pre-synaptic neuron to ith neuron - for j in range( - len(wei_t[0:]) - ): # Incoming connection from j, pre-synaptic neuron to ith neuron + if wei_t[j][i] != 0.0: # Check connectivity - if wei_t[j][i] != 0.0: # Check connectivity + # Get the change in weight + delta_wei_t = ( + -self.eta_inhib * yt_1[j] * (1 - xt[i] * (1 + 1 / self.mu_ip)) + ) - # Get the change in weight - delta_wei_t = ( - -self.eta_inhib - * yt_1[j] - * (1 - xt[i] * (1 + 1 / self.mu_ip)) - ) + # Update the weight between jth neuron to i ""Different from notation in article - # Update the weight between jth neuron to i ""Different from notation in article + wei_t[j][i] = wei[j][i] + delta_wei_t - wei_t[j][i] = wei[j][i] + delta_wei_t + # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight + wei_t = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) - # Prune the smallest weights induced by plasticity mechanisms; Apply lower cutoff weight - wei = Initializer.prune_small_weights(wei_t, cutoff_weights[0]) + # Check and set all weights < upper cutoff weight + wei_t = Initializer.set_max_cutoff_weight(wei_t, cutoff_weights[1]) - # Check and set all weights < upper cutoff weight - wei = Initializer.set_max_cutoff_weight(wei, cutoff_weights[1]) - return wei + return wei_t @staticmethod - def structural_plasticity(param): + def structural_plasticity(wee: np.array): """Add new connection value to the smallest weight between excitatory units randomly - Args: - param (array): Weight matrix - + wee (array): Weight matrix Returns: - param (array): Weight matrix""" + wee (array): Weight matrix""" p_c = np.random.randint(0, 10, 1) @@ -315,7 +284,7 @@ def structural_plasticity(param): # Do structural plasticity # Choose the smallest weights randomly from the weight matrix wee - indexes = Initializer.get_unconnected_indexes(param) + indexes = Initializer.get_unconnected_indexes(wee) # Choose any idx randomly such that i!=j while True: @@ -323,9 +292,9 @@ def structural_plasticity(param): if idx_rand[0] != idx_rand[1]: break - param[idx_rand[0]][idx_rand[1]] = 0.001 + wee[idx_rand[0]][idx_rand[1]] = 0.001 - return param + return wee @staticmethod def initialize_plasticity(): @@ -414,15 +383,15 @@ def __init__(self, phase: str, matrices: dict = None): self.matrices = matrices if self.phase == "plasticity" and self.matrices == None: - self.time_steps = Sorn.time_steps + 1 # Total training steps + self.timesteps = Sorn.timesteps + 1 # Total training steps self.Wee, self.Wei, self.Wie, self.Te, self.Ti, self.X, self.Y = ( - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, ) wee, wei, wie, te, ti, x, y = Plasticity.initialize_plasticity() @@ -437,15 +406,15 @@ def __init__(self, phase: str, matrices: dict = None): elif self.phase == "plasticity" and self.matrices != None: - self.time_steps = Sorn.time_steps + 1 # Total training steps + self.timesteps = Sorn.timesteps + 1 # Total training steps self.Wee, self.Wei, self.Wie, self.Te, self.Ti, self.X, self.Y = ( - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, ) # Assign matrices from plasticity phase to the new master matrices for training phase self.Wee[0] = matrices["Wee"] @@ -458,16 +427,16 @@ def __init__(self, phase: str, matrices: dict = None): elif self.phase == "training": - # NOTE:time_steps here is diferent for plasticity and training phase - self.time_steps = Sorn.time_steps + 1 # Total training steps + # NOTE:timesteps here is diferent for plasticity and training phase + self.timesteps = Sorn.timesteps + 1 # Total training steps self.Wee, self.Wei, self.Wie, self.Te, self.Ti, self.X, self.Y = ( - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, - [0] * self.time_steps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, + [0] * self.timesteps, ) # Assign matrices from plasticity phase to new respective matrices for training phase self.Wee[0] = matrices["Wee"] @@ -549,7 +518,7 @@ def network_activity_t_1(self, x: np.array, y: np.array, i: int): Returns: tuple(array): Previous Excitatory and Inhibitory states """ - x_1, y_1 = [0] * self.time_steps, [0] * self.time_steps + x_1, y_1 = [0] * self.timesteps, [0] * self.timesteps x_1[i] = x y_1[i] = y @@ -737,7 +706,7 @@ class Simulator_(Sorn): matrices(dict, optional): Network states, connections and threshold matrices. Defaults to None. - time_steps(int, optional): Total number of time steps to simulate the network. Defaults to 1. + timesteps(int, optional): Total number of time steps to simulate the network. Defaults to 1. noise(bool, optional): If True, noise will be added. Defaults to True. @@ -754,17 +723,38 @@ class Simulator_(Sorn): def __init__(self): super().__init__() - pass + + self.avail_callbacks = { + "ExcitatoryActivation": ExcitatoryActivation, + "InhibitoryActivation": InhibitoryActivation, + "RecurrentActivation": RecurrentActivation, + "WEE": WEE, + "WEI": WEI, + "TE": TE, + "TI": TI, + "EIConnectionCounts": EIConnectionCounts, + "EEConnectionCounts": EEConnectionCounts, + } + + def update_callback_state(self, *args) -> None: + if self.callbacks: + (indices,) = np.array(self.callback_mask).nonzero() + keys = [list(self.avail_callbacks.keys())[i] for i in indices] + + values = [args[idx] for idx in indices] + for key, val in zip(keys, values): + self.callback_state[key] = val def simulate_sorn( self, inputs: np.array = None, phase: str = "plasticity", matrices: dict = None, - time_steps: int = None, + timesteps: int = None, noise: bool = True, freeze: list = None, - **kwargs + callbacks: list = [], + **kwargs, ): """Simulation/Plasticity phase @@ -775,32 +765,31 @@ def simulate_sorn( matrices(dict, optional): Network states, connections and threshold matrices. Defaults to None. - time_steps(int, optional): Total number of time steps to simulate the network. Defaults to 1. + timesteps(int, optional): Total number of time steps to simulate the network. Defaults to 1. noise(bool, optional): If True, noise will be added. Defaults to True. freeze(list, optional): List of synaptic plasticity mechanisms which will be turned off during simulation. Defaults to None. + callbacks(list, optional): Requested values from ["ExcitatoryActivation", "InhibitoryActivation", + "RecurrentActivation", "WEE", "WEI", "TE", "EEConnectionCounts"] collected and returned from the simulate sorn object. + Returns: plastic_matrices(dict): Network states, connections and threshold matrices - X_all(array): Excitatory network activity collected during entire simulation steps - - Y_all(array): Inhibitory network activity collected during entire simulation steps - - R_all(array): Recurrent network activity collected during entire simulation steps - - frac_pos_active_conn(list): Number of positive connection strengths in the network at each time step during simulation""" + callback_values(dict): Requexted network parameters and activations""" assert ( phase == "plasticity" or "training" ), "Phase can be either 'plasticity' or 'training'" - self.time_steps = time_steps - Sorn.time_steps = time_steps + self.timesteps = timesteps + Sorn.timesteps = timesteps self.phase = phase self.matrices = matrices self.freeze = [] if freeze == None else freeze + self.callbacks = callbacks + kwargs_ = [ "ne", "nu", @@ -823,21 +812,27 @@ def simulate_sorn( for key, value in kwargs.items(): if key in kwargs_: setattr(Sorn, key, value) - # assert Sorn.nu == len(inputs[:,0]),"Size mismatch: Input != Nu " Sorn.ni = int(0.2 * Sorn.ne) - self.plasticity = Plasticity() + plasticity = Plasticity() # Initialize/Get the weight, threshold matrices and activity vectors matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) - # Collect the network activity at all time steps - - X_all = [0] * self.time_steps - R_all = [0] * self.time_steps - frac_pos_active_conn = [] + if self.callbacks: + assert isinstance(self.callbacks, list), "Callbacks must be a list" + assert all(isinstance(callback, str) for callback in self.callbacks) + self.callback_mask = np.isin( + list(self.avail_callbacks.keys()), self.callbacks + ).astype(int) + self.callback_state = dict.fromkeys(self.callbacks, None) + + # Requested values to be collected and returned + self.dispatcher = Callbacks( + self.timesteps, self.avail_callbacks, self.callbacks + ) # To get the last activation status of Exc and Inh neurons - for i in range(self.time_steps): + for i in range(self.timesteps): if noise: white_noise_e = Initializer.white_gaussian_noise( @@ -862,8 +857,9 @@ def simulate_sorn( Te, Ti = matrix_collection.Te, matrix_collection.Ti X, Y = matrix_collection.X, matrix_collection.Y - # Fraction of active connections between E-E network - frac_pos_active_conn.append((Wee[i] > 0.0).sum()) + # Fraction of active connections between E-E and E-I networks + ei_conn = (Wei[i] > 0.0).sum() + ee_conn = (Wee[i] > 0.0).sum() # Recurrent drive r = network_state.recurrent_drive( @@ -890,37 +886,48 @@ def simulate_sorn( # Plasticity phase plasticity = Plasticity() - pool = Pool() - stdp = pool.apply_async( - plasticity.stdp, [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)] - ) + # STDP + if "stdp" not in self.freeze: + Wee[i] = plasticity.stdp(Wee[i], x_buffer, cutoff_weights=(0.0, 1.0)) - ip = pool.apply_async( - plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] - ) - - istdp = pool.apply_async( - plasticity.istdp, - [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], - ) - Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() + # Intrinsic plasticity + if "ip" not in self.freeze: + Te[i] = plasticity.ip(Te[i], x_buffer) + # Structural plasticity if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) - if "ss" not in self.freeze: + # iSTDP + if "istdp" not in self.freeze: + Wei[i] = plasticity.istdp( + Wei[i], x_buffer, y_buffer, cutoff_weights=(0.0, 1.0) + ) + # Synaptic scaling Wee + if "ss" not in self.freeze: Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) - pool.close() + # Assign the matrices to the matrix collections matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) matrix_collection.network_activity_t(x_buffer, y_buffer, i) + if self.callbacks: + self.update_callback_state( + x_buffer[:, 1], + y_buffer[:, 1], + r, + Wee[i], + Wei[i], + Te[i], + Ti[i], + ei_conn, + ee_conn, + ) - X_all[i] = x_buffer[:, 1] - R_all[i] = r + self.dispatcher.step(self.callback_state, time_step=i) plastic_matrices = { "Wee": matrix_collection.Wee[-1], @@ -931,13 +938,10 @@ def simulate_sorn( "X": X[-1], "Y": Y[-1], } - - return ( - plastic_matrices, - X_all, - R_all, - frac_pos_active_conn, - ) + if self.callbacks: + return plastic_matrices, self.dispatcher.get() + else: + return plastic_matrices, {} class Trainer_(Sorn): @@ -945,18 +949,37 @@ class Trainer_(Sorn): def __init__(self): super().__init__() - pass + self.avail_callbacks = { + "ExcitatoryActivation": ExcitatoryActivation, + "InhibitoryActivation": InhibitoryActivation, + "RecurrentActivation": RecurrentActivation, + "WEE": WEE, + "WEI": WEI, + "TE": TE, + "TI": TI, + "EIConnectionCounts": EIConnectionCounts, + "EEConnectionCounts": EEConnectionCounts, + } + + def update_callback_state(self, *args) -> None: + if self.callbacks: + (indices,) = np.array(self.callback_mask).nonzero() + keys = [list(self.avail_callbacks.keys())[i] for i in indices] + + values = [args[idx] for idx in indices] + for key, val in zip(keys, values): + self.callback_state[key] = val def train_sorn( self, inputs: np.array = None, phase: str = "training", matrices: dict = None, - time_steps: int = None, + timesteps: int = None, noise: bool = True, freeze: list = None, - max_workers: int = 4, - **kwargs + callbacks: list = [], + **kwargs, ): """Train the network with the fresh or pretrained network matrices and external stimuli @@ -967,7 +990,7 @@ def train_sorn( matrices(dict, optional): Network states, connections and threshold matrices. Defaults to None. - time_steps(int, optional): Total number of time steps to simulate the network. Defaults to 1. + timesteps(int, optional): Total number of time steps to simulate the network. Defaults to 1. noise(bool, optional): If True, noise will be added. Defaults to True. @@ -1016,19 +1039,27 @@ def train_sorn( self.phase = phase self.matrices = matrices - self.time_steps = time_steps - Sorn.time_steps = time_steps + self.timesteps = timesteps + Sorn.timesteps = timesteps self.inputs = np.asarray(inputs) self.freeze = [] if freeze == None else freeze - self.max_workers = max_workers - X_all = [0] * self.time_steps - R_all = [0] * self.time_steps - - frac_pos_active_conn = [] - + self.callbacks = callbacks matrix_collection = MatrixCollection(phase=self.phase, matrices=self.matrices) - for i in range(self.time_steps): + if self.callbacks: + assert isinstance(self.callbacks, list), "Callbacks must be a list" + assert all(isinstance(callback, str) for callback in self.callbacks) + self.callback_mask = np.isin( + list(self.avail_callbacks.keys()), self.callbacks + ).astype(int) + self.callback_state = dict.fromkeys(self.callbacks, None) + + # Requested values to be collected and returned + self.dispatcher = Callbacks( + self.timesteps, self.avail_callbacks, self.callbacks + ) + + for i in range(self.timesteps): if noise: white_noise_e = Initializer.white_gaussian_noise( @@ -1054,8 +1085,9 @@ def train_sorn( Te, Ti = matrix_collection.Te, matrix_collection.Ti X, Y = matrix_collection.X, matrix_collection.Y - # Fraction of active connections between E-E network - frac_pos_active_conn.append((Wee[i] > 0.0).sum()) + # Fraction of active connections between E-E and E-I networks + ei_conn = (Wei[i] > 0.0).sum() + ee_conn = (Wee[i] > 0.0).sum() # Recurrent drive at t+1 used to predict the next external stimuli r = network_state.recurrent_drive( @@ -1080,33 +1112,30 @@ def train_sorn( if self.phase == "plasticity": # Plasticity phase plasticity = Plasticity() - pool = Pool() + # STDP + if "stdp" not in self.freeze: + Wee[i] = plasticity.stdp( + Wee[i], x_buffer, cutoff_weights=(0.0, 1.0) + ) - stdp = pool.apply_async( - plasticity.stdp, - [(Wee[i], x_buffer, (0.0, 1.0), "stdp" in self.freeze)], - ) - - ip = pool.apply_async( - plasticity.ip, [(Te[i], x_buffer, "ip" in self.freeze)] - ) - - istdp = pool.apply_async( - plasticity.istdp, - [(Wei[i], x_buffer, y_buffer, (0.0, 1.0), "istdp" in self.freeze)], - ) - Wee[i], Te[i], Wei[i] = stdp.get(), ip.get(), istdp.get() - - # pool.join() + # Intrinsic plasticity + if "ip" not in self.freeze: + Te[i] = plasticity.ip(Te[i], x_buffer) + # Structural plasticity if "sp" not in self.freeze: Wee[i] = plasticity.structural_plasticity(Wee[i]) - if "ss" not in self.freeze: + # iSTDP + if "istdp" not in self.freeze: + Wei[i] = plasticity.istdp( + Wei[i], x_buffer, y_buffer, cutoff_weights=(0.0, 1.0) + ) + # Synaptic scaling Wee + if "ss" not in self.freeze: Wee[i] = plasticity.ss(Wee[i]) Wei[i] = plasticity.ss(Wei[i]) - pool.close() else: # Wee[i], Wei[i], Te[i] remain same @@ -1116,9 +1145,20 @@ def train_sorn( matrix_collection.weight_matrix(Wee[i], Wei[i], Wie[i], i) matrix_collection.threshold_matrix(Te[i], Ti[i], i) matrix_collection.network_activity_t(x_buffer, y_buffer, i) + if self.callbacks: + self.update_callback_state( + x_buffer[:, 1], + y_buffer[:, 1], + r, + Wee[i], + Wei[i], + Te[i], + Ti[i], + ei_conn, + ee_conn, + ) - X_all[i] = x_buffer[:, 1] - R_all[i] = r + self.dispatcher.step(self.callback_state, time_step=i) plastic_matrices = { "Wee": matrix_collection.Wee[-1], @@ -1130,12 +1170,10 @@ def train_sorn( "Y": Y[-1], } - return ( - plastic_matrices, - X_all, - R_all, - frac_pos_active_conn, - ) + if self.callbacks: + return plastic_matrices, self.dispatcher.get() + else: + return plastic_matrices, {} Trainer = Trainer_() diff --git a/sorn/test_sorn.py b/sorn/test_sorn.py deleted file mode 100644 index 01a0c38..0000000 --- a/sorn/test_sorn.py +++ /dev/null @@ -1,227 +0,0 @@ -import unittest -import pickle -import numpy as np -from sorn.sorn import RunSorn, Generator -from sorn.utils import Plotter, Statistics, Initializer -from sorn import Simulator, Trainer - -num_features = 10 -inputs = np.random.rand(num_features, 1) - -# Get the pickled matrices: -with open("sample_matrices.pkl", "rb") as f: - ( - matrices_dict, - Exc_activity, - Inh_activity, - Rec_activity, - num_active_connections, - ) = pickle.load(f) - - -class TestSorn(unittest.TestCase): - def test_runsorn(self): - - self.assertRaises( - Exception, Generator().get_initial_matrices("./sorn/")) - - matrices_dict = Generator().get_initial_matrices("./sorn") - - self.assertRaises( - Exception, RunSorn(phase="Plasticity", - matrices=None).run_sorn([0.0]) - ) - - self.assertRaises( - Exception, RunSorn( - phase="Training", matrices=matrices_dict).run_sorn([0.0]) - ) - - self.assertRaises( - Exception, - Simulator.simulate_sorn( - inputs=[0.0], - phase="plasticity", - matrices=None, - noise=True, - time_steps=2, - ne=20, - nu=1, - ), - ) - - self.assertRaises( - Exception, - Simulator.simulate_sorn( - inputs=[0.0], - phase="plasticity", - matrices=matrices_dict, - noise=True, - time_steps=2, - ne=20, - nu=10, - ), - ) - - self.assertRaises( - Exception, - Trainer.train_sorn( - inputs=inputs, - phase="Training", - matrices=matrices_dict, - nu=num_features, - time_steps=1, - ), - ) - - def test_plotter(self): - - self.assertRaises( - Exception, - Plotter.hist_outgoing_conn( - weights=matrices_dict["Wee"], bin_size=5, histtype="bar", savefig=False - ), - ) - - self.assertRaises( - Exception, - Plotter.hist_outgoing_conn( - weights=matrices_dict["Wee"], bin_size=5, histtype="bar", savefig=False - ), - ) - - self.assertRaises( - Exception, - Plotter.hist_incoming_conn( - weights=matrices_dict["Wee"], bin_size=5, histtype="bar", savefig=False - ), - ) - - self.assertRaises( - Exception, - Plotter.network_connection_dynamics( - connection_counts=num_active_connections, - initial_steps=10, - final_steps=10, - savefig=False, - ), - ) - - self.assertRaises( - Exception, - Plotter.hist_firing_rate_network( - spike_train=np.asarray(Exc_activity), bin_size=5, savefig=False - ), - ) - - self.assertRaises( - Exception, - Plotter.scatter_plot(spike_train=np.asarray( - Exc_activity), savefig=False), - ) - - self.assertRaises( - Exception, - Plotter.raster_plot(spike_train=np.asarray( - Exc_activity), savefig=False), - ) - - self.assertRaises( - Exception, - Plotter.isi_exponential_fit( - spike_train=np.asarray(Exc_activity), - neuron=10, - bin_size=5, - savefig=False, - ), - ) - - self.assertRaises( - Exception, - Plotter.weight_distribution( - weights=matrices_dict["Wee"], bin_size=5, savefig=False - ), - ) - - self.assertRaises( - Exception, - Plotter.linear_lognormal_fit( - weights=matrices_dict["Wee"], num_points=10, savefig=False - ), - ) - - self.assertRaises( - Exception, - Plotter.hamming_distance( - hamming_dist=[0, 0, 0, 1, 1, 1, 1, 1, 1], savefig=False - ), - ) - - def test_statistics(self): - - self.assertRaises( - Exception, - Statistics.firing_rate_neuron( - spike_train=np.asarray(Exc_activity), neuron=10, bin_size=5 - ), - ) - - self.assertRaises( - Exception, - Statistics.firing_rate_network( - spike_train=np.asarray(Exc_activity)), - ) - - self.assertRaises( - Exception, - Statistics.scale_dependent_smoothness_measure( - firing_rates=[1, 1, 5, 6, 3, 7] - ), - ) - - self.assertRaises( - Exception, Statistics.autocorr( - firing_rates=[1, 1, 5, 6, 3, 7], t=2) - ) - - self.assertRaises( - Exception, Statistics.avg_corr_coeff( - spike_train=np.asarray(Exc_activity)) - ) - - self.assertRaises( - Exception, Statistics.spike_times( - spike_train=np.asarray(Exc_activity)) - ) - - self.assertRaises( - Exception, - Statistics.hamming_distance( - actual_spike_train=np.asarray(Exc_activity), - perturbed_spike_train=np.asarray(Exc_activity), - ), - ) - - self.assertRaises( - Exception, - Statistics.spike_time_intervals( - spike_train=np.asarray(Exc_activity)), - ) - - self.assertRaises( - Exception, - Statistics.fanofactor( - spike_train=np.asarray(Exc_activity), neuron=10, window_size=10 - ), - ) - - self.assertRaises( - Exception, - Statistics.spike_source_entropy( - spike_train=np.asarray(Exc_activity), neurons_in_reservoir=200 - ), - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/sorn/utils.py b/sorn/utils.py index f72b638..3ab17ac 100644 --- a/sorn/utils.py +++ b/sorn/utils.py @@ -30,7 +30,7 @@ def generate_strong_inp(length: int, reservoir_size: int): inp (array): Input vector of length equals the number of neurons in the reservoir with randomly chosen neuron set active - idx (list): List of chosen input neurons """ + idx (list): List of chosen input neurons""" inp = [0] * reservoir_size x = [0] * length @@ -113,7 +113,7 @@ def normalize_weight_matrix(weight_matrix: np.array): # Applied only while initializing the weight. During simulation, Synaptic scaling applied on weight matrices - """ Normalize the weights in the matrix such that incoming connections to a neuron sum up to 1 + """Normalize the weights in the matrix such that incoming connections to a neuron sum up to 1 Args: weight_matrix (array): Incoming Weights from W_ee or W_ei or W_ie @@ -274,7 +274,7 @@ def generate_lambd_connections( @staticmethod def get_incoming_connection_dict(weights: np.array): - """ Get the non-zero entries in columns is the incoming connections for the neurons + """Get the non-zero entries in columns is the incoming connections for the neurons Args: weights (np.array): Connection/Synaptic weights @@ -329,7 +329,7 @@ def prune_small_weights(weights: np.array, cutoff_weight: float): @staticmethod def set_max_cutoff_weight(weights: np.array, cutoff_weight: float): - """ Set cutoff limit for the values in given array + """Set cutoff limit for the values in given array Args: weights (np.array): Synaptic strengths @@ -346,7 +346,7 @@ def set_max_cutoff_weight(weights: np.array, cutoff_weight: float): @staticmethod def get_unconnected_indexes(wee: np.array): - """ Helper function for Structural plasticity to randomly select the unconnected units + """Helper function for Structural plasticity to randomly select the unconnected units Args: wee (array): Weight matrix @@ -402,9 +402,7 @@ def zero_sum_incoming_check(weights: np.array): else: for zero_sum_incoming in zero_sum_incomings[-1]: - rand_indices = np.random.randint( - int(weights.shape[0] * 0.2), size=2 - ) + rand_indices = np.random.randint(int(weights.shape[0] * 0.2), size=2) rand_values = np.random.uniform(0.0, 0.1, 2) for i, idx in enumerate(rand_indices): @@ -414,8 +412,7 @@ def zero_sum_incoming_check(weights: np.array): class Plotter(object): - """Wrapper class to call plotting methods - """ + """Wrapper class to call plotting methods""" def __init__(self): pass @@ -446,14 +443,19 @@ def hist_incoming_conn( # Fit a normal distribution to the data mu, std = norm.fit(num_incoming_weights) - plt.hist(num_incoming_weights, bins=bin_size, density=True, alpha=0.6, color='b') + plt.hist( + num_incoming_weights, bins=bin_size, density=True, alpha=0.6, color="b" + ) # PDF xmin, xmax = plt.xlim() x = np.linspace(xmin, xmax, max(num_incoming_weights)) p = norm.pdf(x, mu, std) - plt.plot(x, p, 'k', linewidth=2) - title = "Distribution of presynaptic connections: mu = %.2f, std = %.2f" % (mu, std) + plt.plot(x, p, "k", linewidth=2) + title = "Distribution of presynaptic connections: mu = %.2f, std = %.2f" % ( + mu, + std, + ) plt.title(title) if savefig: @@ -461,7 +463,6 @@ def hist_incoming_conn( return plt.show() - @staticmethod def hist_outgoing_conn( weights: np.array, bin_size: int, histtype: str, savefig: bool @@ -478,7 +479,7 @@ def hist_outgoing_conn( savefig (bool): If True plot will be saved as png file in the cwd Returns: - plot object """ + plot object""" # Plot the histogram of distribution of number of incoming connections in the network @@ -490,14 +491,19 @@ def hist_outgoing_conn( # Fit a normal distribution to the data mu, std = norm.fit(num_outgoing_weights) - plt.hist(num_outgoing_weights, bins=bin_size, density=True, alpha=0.6, color='b') + plt.hist( + num_outgoing_weights, bins=bin_size, density=True, alpha=0.6, color="b" + ) # PDF xmin, xmax = plt.xlim() x = np.linspace(xmin, xmax, max(num_outgoing_weights)) p = norm.pdf(x, mu, std) - plt.plot(x, p, 'k', linewidth=2) - title = "Distribution of post synaptic connections: mu = %.2f, std = %.2f" % (mu, std) + plt.plot(x, p, "k", linewidth=2) + title = "Distribution of post synaptic connections: mu = %.2f, std = %.2f" % ( + mu, + std, + ) plt.title(title) if savefig: @@ -506,9 +512,7 @@ def hist_outgoing_conn( return plt.show() @staticmethod - def network_connection_dynamics( - connection_counts: np.array, savefig: bool - ): + def network_connection_dynamics(connection_counts: np.array, savefig: bool): """Plot number of positive connection in the excitatory pool Args: @@ -540,7 +544,7 @@ def network_connection_dynamics( @staticmethod def hist_firing_rate_network(spike_train: np.array, bin_size: int, savefig: bool): - """ Plot the histogram of firing rate (total number of neurons spike at each time step) + """Plot the histogram of firing rate (total number of neurons spike at each time step) Args: spike_train (array): Array of spike trains @@ -550,7 +554,7 @@ def hist_firing_rate_network(spike_train: np.array, bin_size: int, savefig: bool savefig (bool): If True, plot will be saved in the cwd Returns: - plot object """ + plot object""" fr = np.count_nonzero(spike_train.tolist(), 1) @@ -594,7 +598,7 @@ def scatter_plot(spike_train: np.array, savefig: bool): plt.legend(loc="upper left") plt.scatter(y, x, s=0.1, color="black") - plt.title('Spike Trains') + plt.title("Spike Trains") plt.xlabel("Time step") plt.ylabel("Neuron") plt.legend(loc="upper left") @@ -626,7 +630,7 @@ def raster_plot(spike_train: np.array, savefig: bool): firing_rates = Statistics.firing_rate_network(spike_train).tolist() plt.plot(firing_rates, label="Firing rate") plt.legend(loc="upper left") - plt.title('Spike Trains') + plt.title("Spike Trains") # Get the indices where spike_train is 1 x, y = np.argwhere(spike_train.T == 1).T @@ -697,8 +701,7 @@ def isi_exponential_fit( Returns: plot object""" - - isi = Statistics.spike_time_intervals(spike_train[:,neuron]) + isi = Statistics.spike_time_intervals(spike_train[:, neuron]) y, x = np.histogram(sorted(isi), bins=bin_size) @@ -716,7 +719,7 @@ def exponential_func(y, a, b, c): exponential_func(x[1:bin_size], *popt), label="Exponential fit", ) - plt.title('Distribution of Inter Spike Intervals and Exponential Curve Fit') + plt.title("Distribution of Inter Spike Intervals and Exponential Curve Fit") plt.scatter(x[1:bin_size], y[1:bin_size], s=2.0, color="black", label="ISI") plt.xlabel("ISI") plt.ylabel("Frequency") @@ -745,7 +748,7 @@ def weight_distribution(weights: np.array, bin_size: int, savefig: bool): weights >= 0.01 ] # Remove the weight values less than 0.01 # As reported in article SORN 2013 y, x = np.histogram(weights, bins=bin_size) # Create histogram with bin_size - plt.title('Synaptic weight distribution') + plt.title("Synaptic weight distribution") plt.scatter(x[:-1], y, s=2.0, c="black") plt.xlabel("Connection strength") plt.ylabel("Frequency") @@ -786,16 +789,12 @@ def linear_lognormal_fit(weights: np.array, num_points: int, savefig: bool): mode = np.exp(mu - sigma ** 2) # Note that mode depends on both M and s mean = np.exp(mu + (sigma ** 2 / 2)) # Note that mean depends on both M and s - x = np.linspace( - np.min(weights), np.max(weights), num=num_points - ) + x = np.linspace(np.min(weights), np.max(weights), num=num_points) - pdf = stats.lognorm.pdf( - x, shape, loc=0, scale=scale - ) + pdf = stats.lognorm.pdf(x, shape, loc=0, scale=scale) plt.figure(figsize=(12, 4.5)) - plt.title('Curve fit on connection weight distribution') + plt.title("Curve fit on connection weight distribution") # Figure on linear scale plt.subplot(121) plt.plot(x, pdf) @@ -917,7 +916,7 @@ def hamming_distance(hamming_dist: list, savefig: bool): class Statistics(object): - """ Wrapper class for statistical analysis methods """ + """Wrapper class for statistical analysis methods""" def __init__(self): pass @@ -935,7 +934,7 @@ def firing_rate_neuron(spike_train: np.array, neuron: int, bin_size: int): bin_size (int): Divide the spike trains into bins of size bin_size Returns: - int: firing_rate """ + int: firing_rate""" time_period = len(spike_train[:, 0]) @@ -965,7 +964,7 @@ def firing_rate_network(spike_train: np.array): spike_train (array): Array of spike trains Returns: - int: firing_rate """ + int: firing_rate""" firing_rate = np.count_nonzero(spike_train.tolist(), 1) @@ -997,7 +996,7 @@ def scale_independent_smoothness_measure(firing_rates: list): firing_rates (list): List of number of active neurons per time step Returns: - coeff_var (list):Float value signifies the smoothness of the semantic changes in firing rates """ + coeff_var (list):Float value signifies the smoothness of the semantic changes in firing rates""" diff = np.diff(firing_rates) mean_diff = np.mean(diff) @@ -1136,7 +1135,6 @@ def fanofactor(spike_train: np.array, neuron: int, window_size: int): return mean_firing_rate, variance_firing_rate, fano_factor - @staticmethod def spike_source_entropy(spike_train: np.array, num_neurons: int): diff --git a/test_sorn.py b/test_sorn.py index 709dd92..a6bd19e 100644 --- a/test_sorn.py +++ b/test_sorn.py @@ -2,7 +2,7 @@ import pickle import numpy as np from sorn.sorn import Trainer, Simulator -from sorn.utils import Plotter, Statistics, Initializer +from sorn.utils import Plotter, Statistics # Getting back the pickled matrices: with open("sample_matrices.pkl", "rb") as f: @@ -21,8 +21,8 @@ # Overriding defaults: Sample input num_features = 10 -time_steps = 1000 -inputs = np.random.rand(num_features, time_steps) +timesteps = 1000 +inputs = np.random.rand(num_features, timesteps) class TestSorn(unittest.TestCase): @@ -38,7 +38,7 @@ def test_runsorn(self): inputs=simulation_inputs, phase="plasticity", matrices=None, - time_steps=2, + timesteps=2, noise=True, nu=num_features, ), @@ -50,7 +50,7 @@ def test_runsorn(self): inputs=simulation_inputs, phase="plasticity", matrices=state_dict, - time_steps=2, + timesteps=2, noise=False, ), ) @@ -61,7 +61,7 @@ def test_runsorn(self): inputs=simulation_inputs, phase="plasticity", matrices=state_dict, - time_steps=2, + timesteps=2, noise=False, freeze=["ip"], ), @@ -74,7 +74,7 @@ def test_runsorn(self): inputs=simulation_inputs, phase="plasticity", matrices=state_dict, - time_steps=2, + timesteps=2, noise=False, freeze=["stdp", "istdp", "ss", "sp"], ), @@ -87,7 +87,7 @@ def test_runsorn(self): inputs=gym_input, phase="plasticity", matrices=state_dict, - time_steps=1, + timesteps=1, noise=True, ), ) @@ -98,7 +98,7 @@ def test_runsorn(self): inputs=sequence_input, phase="training", matrices=state_dict, - time_steps=1, + timesteps=1, noise=True, freeze=["stdp", "istdp", "ss", "sp"], ), @@ -112,7 +112,7 @@ def test_runsorn(self): phase="plasticity", matrices=None, noise=True, - time_steps=time_steps, + timesteps=timesteps, ne=26, lambda_ee=4, lambda_ei=4, @@ -128,13 +128,61 @@ def test_runsorn(self): phase="plasticity", matrices=None, noise=True, - time_steps=time_steps, + timesteps=timesteps, ne=40, lambda_ee=5, lambda_ei=5, nu=num_features, ), ) + # Test Callbacks + self.assertRaises( + Exception, + Simulator.simulate_sorn( + inputs=inputs, + phase="plasticity", + matrices=None, + noise=True, + timesteps=timesteps, + ne=50, + nu=10, + callbacks=[ + "ExcitatoryActivation", + "InhibitoryActivation", + "RecurrentActivation", + "WEE", + "EIConnectionCounts", + "TI", + "EEConnectionCounts", + "TE", + "WEI", + ], + ), + ), + + self.assertRaises( + Exception, + Trainer.train_sorn( + inputs=inputs, + phase="plasticity", + matrices=None, + noise=True, + timesteps=timesteps, + ne=50, + nu=10, + callbacks=[ + "ExcitatoryActivation", + "InhibitoryActivation", + "RecurrentActivation", + "WEE", + "EIConnectionCounts", + "TI", + "EEConnectionCounts", + "TE", + "WEI", + ], + ), + ) def test_plotter(self): """Test the Plotter class methods in utils module""" From fcde3459c7f8732ecba4cbdefe84c0c2ecb532d8 Mon Sep 17 00:00:00 2001 From: saran_nns Date: Mon, 27 Dec 2021 21:26:38 +0100 Subject: [PATCH 29/30] support py>=3.7 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd64730..c2bf2a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Build using Python ${{ matrix.python-version }} From ecc3198a766713fa1c4648b0b4371b3a26c268de Mon Sep 17 00:00:00 2001 From: saran_nns Date: Mon, 27 Dec 2021 21:28:04 +0100 Subject: [PATCH 30/30] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e524c27..8034dfa 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ pip install git+https://github.com/Saran-nns/sorn ``` ## Dependencies -SORN supports Python 3.7+ ONLY. For older Python versions please use the official Python client. +SORN supports `Python 3.7+` ONLY. For older Python versions please use the official Python client. To install all optional dependencies, ```python