Cellular and subcellular specialization enables biology-constrained deep learning.
The 10 matches
- [1] § RESULTS › Dendritic target propagation can be flexibly combined with multiple biological synaptic plasticity rules ↔ EIANN/rules/btsp.py, lines 8 to 47 · score 0.71 · eligibility traces, instructive signals, presynaptic activity, sigmoidal, decaying, weight updates · Report a mismatch
- [2] § STAR★METHODS › METHOD DETAILS › Learning rules ↔ EIANN/rules/btsp.py, lines 8 to 47 · score 0.64 · eligibility traces, instructive signals, sigmoidal, BTSP, bounded, presynaptic · Report a mismatch
- [3] § RESULTS › Ineffective learning without top-down feedback ↔ EIANN/rules/hebbian.py, lines 8 to 89 · score 0.61 · postsynaptic activity, Hebbian learning rules, unsupervised learning, component, connections, EIANN · Report a mismatch
- [4] § RESULTS › Ineffective learning without top-down feedback ↔ EIANN/rules/hebbian.py, lines 8 to 89 · score 0.59 · principal component, unsupervised learning rule, correlation, Hebbian, weight update, postsynaptic · Report a mismatch
- [5] § RESULTS › Dendritic target propagation can be flexibly combined with multiple biological synaptic plasticity rules ↔ EIANN/rules/backprop_like.py, lines 356 to 468 · score 0.59 · nudge activities, firing rates, local error, weight updates, post, computation · Report a mismatch
- [6] § STAR★METHODS › METHOD DETAILS › Network architecture and dynamics ↔ EIANN/rules/backprop_like.py, lines 356 to 468 · score 0.56 · somatic states, firing rates, top layer, nudge, equilibrated, phase · Report a mismatch
- [7] § STAR★METHODS › METHOD DETAILS › Learning rules ↔ EIANN/network.py, lines 25 to 165 · score 0.55 · outgoing projections, loss function, incoming, global, error, networks · Report a mismatch
- [8] § STAR★METHODS › METHOD DETAILS › Network training and hyperparameter optimization ↔ EIANN/simulate/simulate_EIANN_fashion_mnist.py, lines 302 to 376 · score 0.54 · Fashion MNIST, random seeds, serially, class, network · Report a mismatch
- [9] § STAR★METHODS › METHOD DETAILS › Network architecture and dynamics ↔ EIANN/rules/backprop_like.py, lines 684 to 815 · score 0.53 · ReLU, firing rates, equilibrated, phase, activation, somatic · Report a mismatch
- [10] § STAR★METHODS › QUANTIFICATION AND STATISTICAL ANALYSIS › Analysis of neuronal stimulus selectivity ↔ EIANN/utils/representation_analysis.py, lines 1223 to 1256 · score 0.53 · spatial autocorrelation, receptive fields, Moran, dimensional · Report a mismatch
Paper
Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 815 lines · 43 KB · MIT · 3 matches
- from .base_classes import LearningRule, BiasLearningRule
- import torch
- import math
- class BP_like_1E(LearningRule):
- def __init__(self, projection, max_pop_fraction=1., stochastic=False, learning_rate=None, relu_gate=False):
- """
- Output units are nudged to target. Hidden dendrites locally compute an error as the difference between
- forward and backward dendritic state.
- Weight updates are proportional to local error and (forward) presynaptic firing rate.
- If unit selection is stochastic, hidden units are selected for a weight update with a probability proportional
- to dendritic state by sampling a Bernoulli distribution. Otherwise, units are sorted by dendritic state. A fixed
- maximum fraction of hidden units are updated at each train step. Hidden units are "nudged" by dendritic state
- when selected for a weight update. Nudges to somatic state are applied instantaneously, rather than being
- subject to slow equilibration. Nudged activity is only passed top-down, but not laterally, with no
- equilibration.
- :param projection: :class:'nn.Linear'
- :param max_pop_fraction: float in [0, 1]
- :param stochastic: bool
- :param learning_rate: float
- :param relu_gate: bool
- """
- super().__init__(projection, learning_rate)
- self.max_pop_fraction = max_pop_fraction
- self.stochastic = stochastic
- self.relu_gate = relu_gate
- projection.post.register_attribute_history('plateau')
- projection.post.register_attribute_history('backward_activity')
- projection.post.register_attribute_history('backward_dendritic_state')
- def step(self):
- if self.projection.direction in ['forward', 'F']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.forward_activity, min=0, max=1))
- elif self.projection.direction in ['recurrent', 'R']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.forward_prev_activity, min=0, max=1))
- self.projection.weight.data += self.learning_rate * delta_weight
- @classmethod
- def backward_nudge_activity(cls, layer, store_dynamics=False):
- """
- Update somatic state and activity for all populations that receive a nudge.
- :param layer:
- :param store_dynamics: bool
- """
- for post_pop in layer:
- if post_pop.backward_projections or post_pop is post_pop.network.output_pop:
- if hasattr(post_pop, 'dend_to_soma'):
- post_pop.prev_activity = post_pop.activity
- post_pop.activity = post_pop.activation(post_pop.state + post_pop.dend_to_soma)
- if store_dynamics:
- post_pop.backward_steps_activity.append(post_pop.activity.detach().clone())
- @classmethod
- def backward_update_layer_dendritic_state(cls, layer):
- """
- Update dendritic state for all populations that receive projections that target the dendritic
- compartment.
- """
- for post_pop in layer:
- if post_pop.backward_projections:
- # update dendritic state
- init_dend_state = False
- for projection in post_pop:
- pre_pop = projection.pre
- if projection.compartment == 'dend':
- if not init_dend_state:
- post_pop.dendritic_state = torch.zeros(post_pop.size, device=post_pop.network.device)
- init_dend_state = True
- if projection.direction in ['forward', 'F']:
- post_pop.dendritic_state = post_pop.dendritic_state + projection(pre_pop.activity)
- elif projection.direction in ['recurrent', 'R']:
- post_pop.dendritic_state = post_pop.dendritic_state + projection(pre_pop.prev_activity)
- @classmethod
- def backward(cls, network, output, target, store_history=False, store_dynamics=False):
- """
- Integrate top-down inputs and update dendritic state variables.
- :param network:
- :param output:
- :param target:
- :param store_history: bool
- :param store_dynamics: bool
- """
- reversed_layers = list(network)[1:]
- reversed_layers.reverse()
- output_pop = network.output_pop
- # initialize populations that are updated during the backward phase
- for layer in reversed_layers:
- for pop in layer:
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.backward_steps_activity = []
- # initialize dendritic state variables
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.plateau = torch.zeros(pop.size, device=network.device)
- pop.dend_to_soma = torch.zeros(pop.size, device=network.device)
- break
- for layer in reversed_layers:
- # update dendritic state variables
- cls.backward_update_layer_dendritic_state(layer)
- for pop in layer:
- for projection in pop:
- # compute plateau events and nudge somatic state
- if projection.learning_rule.__class__ == cls:
- if pop is output_pop:
- local_loss = torch.clamp(target - output_pop.activity, min=-1, max=1)
- output_pop.dendritic_state = local_loss.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[output_pop.forward_activity == 0.] = 0.
- output_pop.plateau[:] = local_loss.detach().clone()
- output_pop.dend_to_soma[:] = local_loss.detach().clone()
- else:
- max_units = math.ceil(projection.learning_rule.max_pop_fraction * pop.size)
- local_loss = (pop.dendritic_state - pop.forward_dendritic_state).clamp_(-1., 1.)
- if projection.learning_rule.relu_gate:
- local_loss[pop.forward_activity == 0.] = 0.
- pos_avail_indexes = (local_loss > 0.).nonzero().squeeze(1)
- if projection.learning_rule.stochastic:
- pos_candidate_rel_indexes = (
- torch.bernoulli(local_loss[pos_avail_indexes])).nonzero().squeeze(1)
- else:
- sorted, pos_candidate_rel_indexes = torch.sort(local_loss[pos_avail_indexes],
- descending=True, stable=True)
- pos_event_indexes = pos_avail_indexes[pos_candidate_rel_indexes][:max_units]
- neg_avail_indexes = (local_loss < 0.).nonzero().squeeze(1)
- if projection.learning_rule.stochastic:
- neg_candidate_rel_indexes = (
- torch.bernoulli(-local_loss[neg_avail_indexes])).nonzero().squeeze(1)
- else:
- sorted, neg_candidate_rel_indexes = torch.sort(local_loss[neg_avail_indexes],
- descending=True, stable=True)
- neg_event_indexes = neg_avail_indexes[neg_candidate_rel_indexes][-max_units:]
- pop.plateau[pos_event_indexes] = local_loss[pos_event_indexes].detach().clone()
- pop.plateau[neg_event_indexes] = local_loss[neg_event_indexes].detach().clone()
- pop.dend_to_soma[pos_event_indexes] = (
- local_loss[pos_event_indexes].detach().clone())
- pop.dend_to_soma[neg_event_indexes] = (
- local_loss[neg_event_indexes].detach().clone())
- break
- # update activities
- cls.backward_nudge_activity(layer, store_dynamics=store_dynamics)
- if store_history:
- for layer in network:
- for pop in layer:
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.append_attribute_history('plateau', pop.plateau.detach().clone())
- pop.append_attribute_history('backward_dendritic_state',
- pop.dendritic_state.detach().clone())
- break
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.append_attribute_history('backward_activity', pop.backward_steps_activity)
- else:
- pop.append_attribute_history('backward_activity', pop.activity.detach().clone())
- class BP_like_2E(LearningRule):
- def __init__(self, projection, max_pop_fraction=1., stochastic=False, learning_rate=None, relu_gate=False):
- """
- Initialize BP_like_2E learning rule.
- Output units are nudged to target. Hidden dendrites locally compute an error as the difference between
- excitation and inhibition. Weight updates are proportional to local error and (forward) presynaptic firing rate.
- If unit selection is stochastic, hidden units are selected for a weight update with a probability proportional
- to dendritic state by sampling a Bernoulli distribution. Otherwise, units are sorted by dendritic state. A fixed
- maximum fraction of hidden units are updated at each train step. Hidden units are "nudged" by dendritic state
- when selected for a weight update. Nudges to somatic state are applied instantaneously, rather than being
- subject to slow equilibration. Nudged activity is only passed top-down, but not laterally, with no
- equilibration.
- Parameters
- ----------
- projection : EIANN.Projection object (inherits from torch.nn.Linear)
- The neural projection/connection.
- max_pop_fraction : float, default=1.0
- Maximum fraction of population to update, must be in [0, 1].
- stochastic : bool, default=False
- Whether to use stochastic unit selection.
- learning_rate : float, optional
- Learning rate for weight updates.
- relu_gate : bool, default=False
- Whether to apply ReLU gating.
- """
- super().__init__(projection, learning_rate)
- self.max_pop_fraction = max_pop_fraction
- self.stochastic = stochastic
- self.relu_gate = relu_gate
- projection.post.register_attribute_history('plateau')
- projection.post.register_attribute_history('backward_activity')
- projection.post.register_attribute_history('backward_dendritic_state')
- def step(self):
- if self.projection.direction in ['forward', 'F']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.forward_activity, min=0, max=1))
- elif self.projection.direction in ['recurrent', 'R']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.forward_prev_activity, min=0, max=1))
- self.projection.weight.data += self.learning_rate * delta_weight
- @classmethod
- def backward_nudge_activity(cls, layer, store_dynamics=False):
- """
- Update somatic state and activity for all populations that receive a nudge.
- Parameters
- ----------
- layer : EIANN.Layer object
- The network layer to update.
- store_dynamics : bool, default=False
- Whether to store activity dynamics during the backward pass.
- """
- for post_pop in layer:
- if post_pop.backward_projections or post_pop is post_pop.network.output_pop:
- if hasattr(post_pop, 'dend_to_soma'):
- post_pop.prev_activity = post_pop.activity
- post_pop.activity = post_pop.activation(post_pop.state + post_pop.dend_to_soma)
- if store_dynamics:
- post_pop.backward_steps_activity.append(post_pop.activity.detach().clone())
- @classmethod
- def backward_update_layer_dendritic_state(cls, layer):
- """
- Update dendritic state for all populations that receive projections targeting the dendritic compartment.
- Parameters
- ----------
- layer : object
- The network layer to update.
- """
- for post_pop in layer:
- if post_pop.backward_projections:
- # update dendritic state
- init_dend_state = False
- for projection in post_pop:
- pre_pop = projection.pre
- if projection.compartment == 'dend':
- if not init_dend_state:
- post_pop.dendritic_state = torch.zeros(post_pop.size, device=post_pop.network.device)
- init_dend_state = True
- if projection.direction in ['forward', 'F']:
- post_pop.dendritic_state = post_pop.dendritic_state + projection(pre_pop.activity)
- elif projection.direction in ['recurrent', 'R']:
- post_pop.dendritic_state = post_pop.dendritic_state + projection(pre_pop.prev_activity)
- if init_dend_state:
- post_pop.dendritic_state.clamp_(min=-1, max=1)
- @classmethod
- def backward(cls, network, output, target, store_history=False, store_dynamics=False):
- """
- Integrate top-down inputs and update dendritic state variables.
- Parameters
- ----------
- network : EIANN.Network object (inherits from torch.nn.Module)
- The neural network.
- output : torch.Tensor
- Output of the network.forward() method
- target : torch.Tensor
- Target values.
- store_history : bool, default=False
- Whether to store computation history.
- store_dynamics : bool, default=False
- Whether to store activity dynamics.
- """
- reversed_layers = list(network)[1:]
- reversed_layers.reverse()
- output_pop = network.output_pop
- # Initialize populations that are updated during the backward phase
- for layer in reversed_layers:
- for pop in layer:
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.backward_steps_activity = []
- # Initialize dendritic state variables
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.plateau = torch.zeros(pop.size, device=network.device)
- pop.dend_to_soma = torch.zeros(pop.size, device=network.device)
- break
- for layer in reversed_layers:
- # Update dendritic state variables
- cls.backward_update_layer_dendritic_state(layer)
- for pop in layer:
- for projection in pop:
- # Compute plateau events and nudge somatic state
- if projection.learning_rule.__class__ == cls:
- if pop is output_pop:
- local_loss = torch.clamp(target - output_pop.activity, min=-1, max=1)
- output_pop.dendritic_state = local_loss.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[output_pop.forward_activity == 0.] = 0
- output_pop.plateau[:] = local_loss.detach().clone()
- output_pop.dend_to_soma[:] = local_loss.detach().clone()
- else:
- max_units = math.ceil(projection.learning_rule.max_pop_fraction * pop.size)
- local_loss = pop.dendritic_state.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[pop.forward_activity == 0.] = 0
- pos_avail_indexes = (local_loss > 0.).nonzero().squeeze(1)
- if projection.learning_rule.stochastic:
- pos_candidate_rel_indexes = (
- torch.bernoulli(local_loss[pos_avail_indexes])).nonzero().squeeze(1)
- else:
- sorted, pos_candidate_rel_indexes = torch.sort(local_loss[pos_avail_indexes],
- descending=True, stable=True)
- pos_event_indexes = pos_avail_indexes[pos_candidate_rel_indexes][:max_units]
- neg_avail_indexes = (local_loss < 0.).nonzero().squeeze(1)
- if projection.learning_rule.stochastic:
- neg_candidate_rel_indexes = (
- torch.bernoulli(-local_loss[neg_avail_indexes])).nonzero().squeeze(1)
- else:
- sorted, neg_candidate_rel_indexes = torch.sort(local_loss[neg_avail_indexes],
- descending=True, stable=True)
- neg_event_indexes = neg_avail_indexes[neg_candidate_rel_indexes][-max_units:]
- pop.plateau[pos_event_indexes] = local_loss[pos_event_indexes]
- pop.plateau[neg_event_indexes] = local_loss[neg_event_indexes]
- pop.dend_to_soma[pos_event_indexes] = local_loss[pos_event_indexes]
- pop.dend_to_soma[neg_event_indexes] = local_loss[neg_event_indexes]
- break
- # Update activities
- cls.backward_nudge_activity(layer, store_dynamics=store_dynamics)
- if store_history:
- for layer in network:
- for pop in layer:
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.append_attribute_history('plateau', pop.plateau.detach().clone())
- pop.append_attribute_history('backward_dendritic_state',
- pop.dendritic_state.detach().clone())
- break
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.append_attribute_history('backward_activity', pop.backward_steps_activity)
- else:
- pop.append_attribute_history('backward_activity', pop.activity.detach().clone())
- class Top_Layer_BP_like_2E(LearningRule):
- def __init__(self, projection, learning_rate=None, relu_gate=True):
- """
- Initialize Top_Layer_BP_like_2E learning rule.
- Output units are nudged to target. Weight updates are proportional to local error and presynaptic firing rate
- (no backward phase equilibration).
- Parameters
- ----------
- projection : EIANN.Projection (inherits from torch.nn.Linear)
- The neural projection/connection.
- learning_rate : float, optional
- Learning rate for weight updates.
- relu_gate : bool, default=True
- Whether to apply ReLU gating.
- """
- super().__init__(projection, learning_rate)
- self.relu_gate = relu_gate
- projection.post.register_attribute_history('plateau')
- projection.post.register_attribute_history('backward_activity')
- projection.post.register_attribute_history('backward_dendritic_state')
- def step(self):
- if self.projection.direction in ['forward', 'F']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.forward_activity, min=0, max=1))
- elif self.projection.direction in ['recurrent', 'R']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.forward_prev_activity, min=0, max=1))
- self.projection.weight.data += self.learning_rate * delta_weight
- @classmethod
- def backward_nudge_activity(cls, layer, store_dynamics=False):
- """
- Update somatic state and activity for all populations that receive a nudge.
- Parameters
- ----------
- layer : EIANN.Layer object
- The network layer to update.
- store_dynamics : bool, default=False
- Whether to store activity dynamics during the backward pass.
- """
- for post_pop in layer:
- if post_pop.backward_projections or post_pop is post_pop.network.output_pop:
- if hasattr(post_pop, 'dend_to_soma'):
- post_pop.prev_activity = post_pop.activity
- post_pop.activity = post_pop.activation(post_pop.state + post_pop.dend_to_soma)
- if store_dynamics:
- post_pop.backward_steps_activity.append(post_pop.activity.detach().clone())
- @classmethod
- def backward(cls, network, output, target, store_history=False, store_dynamics=False):
- """
- Integrate top-down inputs and update dendritic state variables.
- Parameters
- ----------
- network : EIANN.Network object (inherits from torch.nn.Module)
- The neural network.
- output : torch.Tensor
- Output from the network.forward() method
- target : torch.Tensor
- Target values.
- store_history : bool, default=False
- Whether to store computation history.
- store_dynamics : bool, default=False
- Whether to store activity dynamics.
- """
- output_pop = network.output_pop
- output_layer = output_pop.layer
- # Initialize populations that are updated during the backward phase
- for pop in output_layer:
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.backward_steps_activity = []
- # Initialize dendritic state variables
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.plateau = torch.zeros(pop.size, device=network.device)
- pop.dend_to_soma = torch.zeros(pop.size, device=network.device)
- break
- for pop in output_layer:
- for projection in pop:
- # Compute plateau events and nudge somatic state
- if projection.learning_rule.__class__ == cls:
- if pop is output_pop:
- local_loss = torch.clamp(target - output_pop.activity, min=-1, max=1)
- output_pop.dendritic_state = local_loss.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[output_pop.forward_activity == 0.] = 0
- output_pop.plateau[:] = local_loss.detach().clone()
- output_pop.dend_to_soma[:] = local_loss.detach().clone()
- break
- cls.backward_nudge_activity(output_layer, store_dynamics=store_dynamics)
- if store_history:
- for pop in output_layer:
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.append_attribute_history('plateau', pop.plateau.detach().clone())
- pop.append_attribute_history('backward_dendritic_state',
- pop.dendritic_state.detach().clone())
- break
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.append_attribute_history('backward_activity', pop.backward_steps_activity)
- else:
- pop.append_attribute_history('backward_activity', pop.activity.detach().clone())
- class BP_like_2L(LearningRule):
- def __init__(self, projection, max_pop_fraction=1., stochastic=False, learning_rate=None, relu_gate=False, forward_only=False):
- """
- Initialize BP_like_2L learning rule.
- Output units are nudged to target. Hidden dendrites locally compute an error as the difference between
- excitation and inhibition. Weight updates are proportional to local error and presynaptic firing rate (after
- backward phase equilibration).
- If unit selection is stochastic, hidden units are selected for a weight update with a probability proportional
- to dendritic state by sampling a Bernoulli distribution. Otherwise, units are sorted by dendritic state. A fixed
- maximum fraction of hidden units are updated at each train step. Hidden units are "nudged" by dendritic state
- when selected for a weight update. Nudges to somatic state are applied instantaneously, rather than being
- subject to slow equilibration. During nudging, activities are re-equilibrated across all layers.
- Parameters
- ----------
- projection : EIANN.Projection object (inherits from torch.nn.Linear)
- The neural projection/connection.
- max_pop_fraction : float, default=1.0
- Maximum fraction of population to update, must be in [0, 1].
- stochastic : bool, default=False
- Whether to use stochastic unit selection.
- learning_rate : float, optional
- Learning rate for weight updates.
- relu_gate : bool, default=False
- Whether to apply ReLU gating.
- forward_only : bool, default=False
- Whether to consult forward activity in weight update.
- """
- super().__init__(projection, learning_rate)
- self.max_pop_fraction = max_pop_fraction
- self.stochastic = stochastic
- self.relu_gate = relu_gate
- self.forward_only = forward_only
- projection.post.register_attribute_history('plateau')
- projection.post.register_attribute_history('backward_activity')
- projection.post.register_attribute_history('backward_dendritic_state')
- def step(self):
- if self.projection.direction in ['forward', 'F']:
- if self.forward_only:
- pre_activity = torch.clamp(self.projection.pre.forward_activity, min=0, max=1)
- else:
- pre_activity = torch.clamp(self.projection.pre.activity, min=0, max=1)
- elif self.projection.direction in ['recurrent', 'R']:
- if self.forward_only:
- pre_activity = torch.clamp(self.projection.pre.forward_prev_activity, min=0, max=1)
- else:
- pre_activity = torch.clamp(self.projection.pre.prev_activity, min=0, max=1)
- delta_weight = torch.outer(self.projection.post.plateau, pre_activity)
- self.projection.weight.data += self.learning_rate * delta_weight
- @classmethod
- def backward_update_layer_activity(cls, layer, store_dynamics=False):
- """
- Update somatic state and activity for populations with backward projections.
- Updates somatic state and activity for all populations that receive projections with update_phase in
- ['B', 'backward', 'A', 'all'].
- Parameters
- ----------
- layer : EIANN.Layer object
- The network layer to update.
- store_dynamics : bool, default=False
- Whether to store activity dynamics during the backward pass.
- """
- for post_pop in layer:
- post_pop.prev_activity = post_pop.activity
- for post_pop in layer:
- if post_pop.backward_projections or post_pop is post_pop.network.output_pop or store_dynamics:
- # Update somatic state and activity
- delta_state = -post_pop.state + post_pop.bias
- for projection in post_pop:
- pre_pop = projection.pre
- if projection.compartment in [None, 'soma']:
- if projection.direction in ['forward', 'F']:
- delta_state = delta_state + projection(pre_pop.activity)
- elif projection.direction in ['recurrent', 'R']:
- delta_state = delta_state + projection(pre_pop.prev_activity)
- post_pop.state = post_pop.state + delta_state / post_pop.tau
- if hasattr(post_pop, 'dend_to_soma'):
- post_pop.activity = post_pop.activation(post_pop.state + post_pop.dend_to_soma)
- else:
- post_pop.activity = post_pop.activation(post_pop.state)
- if store_dynamics:
- post_pop.backward_steps_activity.append(post_pop.activity.detach().clone())
- @classmethod
- def backward_update_layer_dendritic_state(cls, layer):
- """
- Update dendritic state for all populations that receive projections targeting the dendritic compartment.
- Parameters
- ----------
- layer : EIANN.Layer object
- The network layer to update.
- """
- for post_pop in layer:
- if post_pop.backward_projections:
- # Update dendritic state
- init_dend_state = False
- for projection in post_pop:
- pre_pop = projection.pre
- if projection.compartment == 'dend':
- if not init_dend_state:
- post_pop.dendritic_state = torch.zeros(post_pop.size, device=post_pop.network.device)
- init_dend_state = True
- if projection.direction in ['forward', 'F']:
- post_pop.dendritic_state = post_pop.dendritic_state + projection(pre_pop.activity)
- elif projection.direction in ['recurrent', 'R']:
- post_pop.dendritic_state = post_pop.dendritic_state + projection(pre_pop.prev_activity)
- if init_dend_state:
- post_pop.dendritic_state.clamp_(min=-1, max=1)
- @classmethod
- def backward(cls, network, output, target, store_history=False, store_dynamics=False):
- """
- Integrate top-down inputs and update dendritic state variables.
- Parameters
- ----------
- network : EIANN.Network object (inherits from torch.nn.Module)
- The neural network.
- output : torch.Tensor
- Output from the network.forward() method
- target : torch.Tensor
- Target values.
- store_history : bool, default=False
- Whether to store computation history.
- store_dynamics : bool, default=False
- Whether to store activity dynamics.
- """
- reversed_layers = list(network)[1:]
- reversed_layers.reverse()
- output_pop = network.output_pop
- # Initialize populations that are updated during the backward phase
- for layer in reversed_layers:
- for pop in layer:
- if store_dynamics:
- pop.backward_steps_activity = []
- # Initialize dendritic state variables
- for projection in pop:
- if cls.shared_backward_methods(projection.learning_rule):
- pop.plateau = torch.zeros(pop.size, device=network.device)
- pop.dend_to_soma = torch.zeros(pop.size, device=network.device)
- break
- for t in range(network.forward_steps):
- for layer in reversed_layers:
- # Update dendritic state variables
- cls.backward_update_layer_dendritic_state(layer)
- if t == 0:
- for pop in layer:
- for projection in pop:
- # Compute plateau events and nudge somatic state
- if cls.shared_backward_methods(projection.learning_rule):
- if pop is output_pop:
- local_loss = torch.clamp(target - output_pop.activity, min=-1, max=1)
- output_pop.dendritic_state = local_loss.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[output_pop.forward_activity == 0.] = 0
- output_pop.plateau[:] = local_loss.detach().clone()
- output_pop.dend_to_soma[:] = local_loss.detach().clone()
- else:
- max_units = math.ceil(projection.learning_rule.max_pop_fraction * pop.size)
- local_loss = pop.dendritic_state.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[pop.forward_activity == 0.] = 0
- pos_avail_indexes = (local_loss > 0.).nonzero().squeeze(1)
- if projection.learning_rule.stochastic:
- pos_candidate_rel_indexes = (
- torch.bernoulli(local_loss[pos_avail_indexes])).nonzero().squeeze(1)
- else:
- sorted, pos_candidate_rel_indexes = torch.sort(local_loss[pos_avail_indexes],
- descending=True, stable=True)
- pos_event_indexes = pos_avail_indexes[pos_candidate_rel_indexes][:max_units]
- neg_avail_indexes = (local_loss < 0.).nonzero().squeeze(1)
- if projection.learning_rule.stochastic:
- neg_candidate_rel_indexes = (
- torch.bernoulli(-local_loss[neg_avail_indexes])).nonzero().squeeze(1)
- else:
- sorted, neg_candidate_rel_indexes = torch.sort(local_loss[neg_avail_indexes],
- descending=True, stable=True)
- neg_event_indexes = neg_avail_indexes[neg_candidate_rel_indexes][-max_units:]
- pop.plateau[pos_event_indexes] = local_loss[pos_event_indexes]
- pop.plateau[neg_event_indexes] = local_loss[neg_event_indexes]
- pop.dend_to_soma[pos_event_indexes] = local_loss[pos_event_indexes]
- pop.dend_to_soma[neg_event_indexes] = local_loss[neg_event_indexes]
- break
- # Update activities
- cls.backward_update_layer_activity(layer, store_dynamics=store_dynamics)
- if store_history:
- for layer in network:
- for pop in layer:
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.append_attribute_history('plateau', pop.plateau.detach().clone())
- pop.append_attribute_history('backward_dendritic_state',
- pop.dendritic_state.detach().clone())
- break
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.append_attribute_history('backward_activity', pop.backward_steps_activity)
- else:
- pop.append_attribute_history('backward_activity', pop.activity.detach().clone())
- class Top_Layer_BP_like_2L(LearningRule):
- def __init__(self, projection, learning_rate=None, relu_gate=True):
- """
- Initialize Top_Layer_BP_like_2L learning rule.
- Output units are nudged to target. Weight updates are proportional to local error and presynaptic firing rate
- (after backward phase equilibration).
- Parameters
- ----------
- projection : EIANN.Projection (inherits from torch.nn.Linear)
- The neural projection/connection.
- learning_rate : float, optional
- Learning rate for weight updates.
- relu_gate : bool, default=True
- Whether to apply ReLU gating.
- """
- super().__init__(projection, learning_rate)
- self.relu_gate = relu_gate
- projection.post.register_attribute_history('plateau')
- projection.post.register_attribute_history('backward_activity')
- projection.post.register_attribute_history('backward_dendritic_state')
- def step(self):
- if self.projection.direction in ['forward', 'F']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.activity, min=0, max=1))
- elif self.projection.direction in ['recurrent', 'R']:
- delta_weight = torch.outer(self.projection.post.plateau,
- torch.clamp(self.projection.pre.prev_activity, min=0, max=1))
- self.projection.weight.data += self.learning_rate * delta_weight
- @classmethod
- def backward_update_layer_activity(cls, layer, store_dynamics=False):
- """
- Update somatic state and activity for populations with backward projections.
- Updates somatic state and activity for all populations that receive projections with update_phase in
- ['B', 'backward', 'A', 'all'].
- Parameters
- ----------
- layer : EIANN.Layer object
- The network layer to update.
- store_dynamics : bool, default=False
- Whether to store activity dynamics during the backward pass.
- """
- for post_pop in layer:
- post_pop.prev_activity = post_pop.activity
- for post_pop in layer:
- if post_pop.backward_projections or post_pop is post_pop.network.output_pop:
- # update somatic state and activity
- delta_state = -post_pop.state + post_pop.bias
- for projection in post_pop:
- pre_pop = projection.pre
- if projection.compartment in [None, 'soma']:
- if projection.direction in ['forward', 'F']:
- delta_state = delta_state + projection(pre_pop.activity)
- elif projection.direction in ['recurrent', 'R']:
- delta_state = delta_state + projection(pre_pop.prev_activity)
- post_pop.state = post_pop.state + delta_state / post_pop.tau
- if hasattr(post_pop, 'dend_to_soma'):
- post_pop.activity = post_pop.activation(post_pop.state + post_pop.dend_to_soma)
- else:
- post_pop.activity = post_pop.activation(post_pop.state)
- if store_dynamics:
- post_pop.backward_steps_activity.append(post_pop.activity.detach().clone())
- @classmethod
- def backward(cls, network, output, target, store_history=False, store_dynamics=False):
- """
- Integrate top-down inputs and update dendritic state variables.
- Parameters
- ----------
- network : EIANN.Network object (inherits from torch.nn.Module)
- The neural network.
- output : torch.Tensor
- Output from the network.forward() method
- target : torch.Tensor
- Target values.
- store_history : bool, default=False
- Whether to store computation history.
- store_dynamics : bool, default=False
- Whether to store activity dynamics.
- """
- output_pop = network.output_pop
- output_layer = output_pop.layer
- # Initialize populations that are updated during the backward phase
- for pop in output_layer:
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.backward_steps_activity = []
- # Initialize dendritic state variables
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.plateau = torch.zeros(pop.size, device=network.device)
- pop.dend_to_soma = torch.zeros(pop.size, device=network.device)
- break
- for t in range(network.forward_steps):
- if t == 0:
- for pop in output_layer:
- for projection in pop:
- # Compute plateau events and nudge somatic state
- if projection.learning_rule.__class__ == cls:
- if pop is output_pop:
- local_loss = torch.clamp(target - output_pop.activity, min=-1, max=1)
- output_pop.dendritic_state = local_loss.detach().clone()
- if projection.learning_rule.relu_gate:
- local_loss[output_pop.forward_activity == 0.] = 0
- output_pop.plateau[:] = local_loss.detach().clone()
- output_pop.dend_to_soma[:] = local_loss.detach().clone()
- break
- cls.backward_update_layer_activity(output_layer, store_dynamics=store_dynamics)
- if store_history:
- for pop in output_layer:
- for projection in pop:
- if projection.learning_rule.__class__ == cls:
- pop.append_attribute_history('plateau', pop.plateau.detach().clone())
- pop.append_attribute_history('backward_dendritic_state',
- pop.dendritic_state.detach().clone())
- break
- if pop.backward_projections or pop is output_pop:
- if store_dynamics:
- pop.append_attribute_history('backward_activity', pop.backward_steps_activity)
- else:
- pop.append_attribute_history('backward_activity', pop.activity.detach().clone())
backprop_like.py at commit 5df3405, under MIT · at the source
Overview
- Center for Advanced Biotechnology and Medicine and Department of Neuroscience and Biology, Rutgers Biomedical and Health Sciences, Rutgers, The State University of New Jersey, Piscataway, NJ 08854, USA
- Department of Computer Science, Rutgers, The State University of New Jersey, Piscataway, NJ 08854, USA
- Lead contact
Abstract
Learning depends on changes in neuronal synaptic strengths. While cellular mechanisms of plasticity have been extensively studied experimentally, our understanding of how plasticity is organized across neural populations comes largely from training artificial neural networks (ANNs). However, most modern ANN architectures and algorithms are not compatible with fundamental principles of neuroscience. Here we leverage recent experimental evidence to test an emergent theory that biological learning depends on neuronal cell type specialization and compartmentalized signaling within neuronal dendrites. We demonstrate that multi-layer ANNs comprised of separate excitatory and inhibitory cell types, and neuronal units with separate dendrite compartments, can be trained to accurately classify images using a fully biology-compatible deep learning algorithm called dendritic target propagation. By adhering to strict biological constraints, this model provides insight into biological mechanisms of learning and makes experimentally testable predictions regarding the roles of specific cell types in coordinating learning across multiple circuit layers.
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 10 matches between paragraphs and lines of code.
milstein-lab.github.io/eiann
Availability: 1 check, the latest on 30 September 2026: the link answers (HTTP 200)
- 30 September 2026: the link answers (HTTP 200)
Milstein-Lab/EIANN
5df3405ff38229ab0a10b830570de3638bf86f54, 26 May 2026Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
109 files
- EIANN/
__init__.py , Python, 7 lines - EIANN/
_network.py , Python, 6 lines - EIANN/
external.py , Python, 14 lines - EIANN/
generate_figures.py , Python, 641 lines - EIANN/
network.py , Python, 2,082 lines, 1 match - EIANN/
notebooks/ , Jupyter, 291 linesexplore_MNIST.ipynb - EIANN/
notebooks/ , Jupyter, 67 linesexplore_spirals.ipynb - EIANN/
optimize/ , Python, 1 line__init__.py - EIANN/
optimize/ , Shell, 88 linesjobscripts/ amarel/ generate_pkl_EIANN.sh - EIANN/
optimize/ , Shell, 61 linesjobscripts/ amarel/ optimize_EIANN_amarel.sh - EIANN/
optimize/ , Shell, 56 linesjobscripts/ amarel/ test.sh - EIANN/
optimize/ , Shell, 52 linesjobscripts/ batch_export_optimized_E IANN_frontera_fmnist.sh - EIANN/
optimize/ , Shell, 25 linesjobscripts/ optimize_EIANN.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_bridges_c ifar10.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_bridges_c ifar10_dev.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_bridges_m nist.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_bridges_m nist_dev.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_bridges_s piral.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_bridges_s piral_dev.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_cori_dev. sh - EIANN/
optimize/ , Shell, 25 linesjobscripts/ optimize_EIANN_dev.sh - EIANN/
optimize/ , Shell, 27 linesjobscripts/ optimize_EIANN_frontera_ fmnist.sh - EIANN/
optimize/ , Shell, 27 linesjobscripts/ optimize_EIANN_frontera_ mnist.sh - EIANN/
optimize/ , Shell, 27 linesjobscripts/ optimize_EIANN_frontera_ mnist_dev.sh - EIANN/
optimize/ , Shell, 27 linesjobscripts/ optimize_EIANN_frontera_ spirals.sh - EIANN/
optimize/ , Shell, 28 linesjobscripts/ optimize_EIANN_mnist_fro ntera_hot_start.sh - EIANN/
optimize/ , Python, 455 linesnested_optimize_EIANN_1_ hidden_mnist.py - EIANN/
optimize/ , Python, 491 linesnested_optimize_EIANN_ci far10.py - EIANN/
optimize/ , Python, 437 linesnested_optimize_EIANN_fa shion_mnist.py - EIANN/
optimize/ , Python, 411 linesnested_optimize_EIANN_sp iral_2_hidden.py - EIANN/
optimize/ , Python, 3,220 linesnetwork_config_updates.p y - EIANN/
plot.py , Python, 1,494 lines - EIANN/
rules/ , Python, 11 lines__init__.py - EIANN/
rules/ , Python, 74 linesbackprop.py - EIANN/
rules/ , Python, 815 lines, 3 matchesbackprop_like.py - EIANN/
rules/ , Python, 50 linesbase_classes.py - EIANN/
rules/ , Python, 256 lines, 2 matchesbtsp.py - EIANN/
rules/ , Python, 29 linesdendritic_loss.py - EIANN/
rules/ , Python, 699 lines, 2 matcheshebbian.py - EIANN/
rules/ , Python, 106 linesweight_functions.py - EIANN/
scripts/ , Python, 138 linesplot_parameter_degenerac y_and_importance.py - EIANN/
scripts/ , Python, 113 linespre_process_convnet_cifa r10.py - EIANN/
simulate/ , Shell, 37 linesjobscripts/ batch_export_optimized_E IANN_cifar10_10_epochs_f rontera.sh - EIANN/
simulate/ , Shell, 37 linesjobscripts/ batch_export_optimized_E IANN_cifar10_extended_fr ontera.sh - EIANN/
simulate/ , Shell, 37 linesjobscripts/ batch_export_optimized_E IANN_cifar10_frontera.sh - EIANN/
simulate/ , Shell, 35 linesjobscripts/ batch_export_optimized_E IANN_mnist_extended_fron tera.sh - EIANN/
simulate/ , Shell, 35 linesjobscripts/ batch_export_optimized_E IANN_mnist_frontera.sh - EIANN/
simulate/ , Shell, 34 linesjobscripts/ batch_export_optimized_e xtended_EIANN_bridges_fm nist.sh - EIANN/
simulate/ , Shell, 18 linesjobscripts/ batch_export_optimized_e xtended_EIANN_cifar10_10 _epochs_bridges.sh - EIANN/
simulate/ , Shell, 36 linesjobscripts/ batch_export_optimized_e xtended_EIANN_frontera_f mnist.sh - EIANN/
simulate/ , Shell, 29 linesjobscripts/ export_optimized_EIANN_m nist_bridges.sh - EIANN/
simulate/ , Shell, 27 linesjobscripts/ export_optimized_EIANN_m nist_frontera.sh - EIANN/
simulate/ , Shell, 29 linesjobscripts/ export_optimized_extende d_EIANN_cifar10_10_epoch s_bridges.sh - EIANN/
simulate/ , Shell, 29 linesjobscripts/ export_optimized_extende d_EIANN_mnist_bridges.sh - EIANN/
simulate/ , Shell, 27 linesjobscripts/ export_optimized_extende d_EIANN_mnist_frontera.s h - EIANN/
simulate/ , Shell, 45 linesjobscripts/ run_EIANN_cpu_bridges_mn ist_mpi.sh - EIANN/
simulate/ , Shell, 69 linesjobscripts/ run_EIANN_gpu_amarel_mni st_ray.sh - EIANN/
simulate/ , Shell, 53 linesjobscripts/ run_EIANN_gpu_bridges_mn ist_mpi.sh - EIANN/
simulate/ , Shell, 54 linesjobscripts/ run_EIANN_gpu_bridges_mn ist_parallel.sh - EIANN/
simulate/ , Shell, 44 linesjobscripts/ run_EIANN_gpu_bridges_mn ist_ray.sh - EIANN/
simulate/ , Shell, 54 linesjobscripts/ run_EIANN_gpu_frontera_m nist_ray.sh - EIANN/
simulate/ , Shell, 74 linesjobscripts/ run_EIANN_gpu_multi_node _bridges_mnist_ray.sh - EIANN/
simulate/ , Shell, 84 linesjobscripts/ run_EIANN_gpu_multi_node _frontera_mnist_ray.sh - EIANN/
simulate/ , Shell, 44 linesjobscripts/ simulate_EIANN_cpu_bridg es_mnist.sh - EIANN/
simulate/ , Shell, 53 linesjobscripts/ simulate_EIANN_frontera_ mnist_export_extended_tr aining.sh - EIANN/
simulate/ , Shell, 37 linesjobscripts/ simulate_EIANN_frontera_ mnist_export_extended_tr aining_dev.sh - EIANN/
simulate/ , Shell, 69 linesjobscripts/ simulate_EIANN_gpu_amare l_mnist.sh - EIANN/
simulate/ , Shell, 51 linesjobscripts/ simulate_EIANN_gpu_bridg es_mnist.sh - EIANN/
simulate/ , Shell, 51 linesjobscripts/ simulate_EIANN_gpu_front era_mnist.sh - EIANN/
simulate/ , Python, 148 linesrun_EIANN_mnist.py - EIANN/
simulate/ , Python, 205 linesrun_EIANN_mnist_mpi.py - EIANN/
simulate/ , Python, 256 linesrun_EIANN_mnist_parallel .py - EIANN/
simulate/ , Python, 178 linesrun_EIANN_mnist_ray.py - EIANN/
simulate/ , Python, 85 linesrun_EIANN_mnist_ray2.py - EIANN/
simulate/ , Python, 386 linessimulate_EIANN_cifar10.p y - EIANN/
simulate/ , Python, 380 lines, 1 matchsimulate_EIANN_fashion_m nist.py - EIANN/
simulate/ , Python, 390 linessimulate_EIANN_mnist.py - EIANN/
simulate/ , Python, 27 linesview_network.py - EIANN/
utils/ , Python, 9 lines__init__.py - EIANN/
utils/ , Python, 103 linesactivations.py - EIANN/
utils/ , Python, 946 linesdata_utils.py - EIANN/
utils/ , Python, 880 linesnetwork_utils.py - EIANN/
utils/ , Python, 49 linesrename_pkls.py - EIANN/
utils/ , Python, 1,767 lines, 1 matchrepresentation_analysis. py - EIANN/
utils/ , Python, 19 linesweight_inits.py - docs/
Galloni_etal_2025/ , Jupyter, 16 linesFigure_1.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 98 linesFigure_2.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 394 linesFigure_3.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 142 linesFigure_4.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 152 linesFigure_5.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 74 linesFigure_6.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 174 linesFigure_7.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 67 linesFigure_8.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 123 linesFigure_S1.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 94 linesFigure_S2.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 52 linesFigure_S3.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 22 linesFigure_S4.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 143 linesFigure_S5.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 31 linesFigure_S6.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 32 linesFigure_S7.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 238 linesSupplementary_Accuracy_T ables.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 344 linesSupplementary_Params_Tab les.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 108 linesmodel_summary1_feedforwa rd_ANN_Backprop.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 112 linesmodel_summary2_EIANN_lea rned_somaI_Backprop.ipyn b - docs/
Galloni_etal_2025/ , Jupyter, 114 linesmodel_summary3_EIANN_fix ed_somaI_Backprop.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 127 linesmodel_summary4_EIANN_lea rned_somaI_Hebbian.ipynb - docs/
Galloni_etal_2025/ , Jupyter, 129 linesmodel_summary5_EIANN_Den dTargetProp_LDS.ipynb - repository limit reached (2,000 files or 30 MB): the rest is at the source (8 files)
- LICENSE, License, 21 lines
- README.md, Text, 47 lines
neurosutras/nested
42d484faf91a9339417ecff7cd2a0c4fd810fd15, 19 August 2026Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
33 files
- 20260202 explore nested.ipynb, Jupyter, 118 lines
- __init__.py, Python, 11 lines
- alternate_source.py, Python, 3 lines
- analyze.py, Python, 195 lines
- archived/
lsa_deprecated.py , Python, 1,622 lines - archived/
lsa_parallel.py , Python, 388 lines - archived/
test_mpi_futures.py , Python, 50 lines - archived/
test_mpi_futures_comm_ex , Python, 59 linesecutor.py - archived/
test_mpi_futures_cori.py , Python, 324 lines - basic_example.py, Python, 77 lines
- full_example.py, Python, 102 lines
- jobscripts/
batch_apply_example_ipyp , Shell, 27 lines.sh - jobscripts/
batch_test_parallel_inte , Shell, 19 linesrfaces_KNL_pc.sh - jobscripts/
batch_test_parallel_inte , Shell, 30 linesrfaces_cori_ipyp.sh - jobscripts/
batch_test_parallel_inte , Shell, 19 linesrfaces_cori_pc.sh - jobscripts/
batch_test_parallel_inte , Shell, 21 linesrfaces_pc.sh - jobscripts/
batch_test_pc_subworlds_ , Shell, 19 linescori.sh - jobscripts/
test_parallel_interfaces , Shell, 18 lines_comet_pc.sh - lsa.py, Python, 821 lines
- optimize.py, Python, 327 lines
- optimize_utils.py, Python, 4,095 lines
- parallel.py, Python, 1,140 lines
- population_annealing_pc_
subworlds_example.py , Python, 77 lines - sa_sobol_plot_tutorial.i
pynb , Jupyter, 61 lines - sa_sobol_tutorial.py, Python, 13 lines
- tests/
test_comm_gather.py , Python, 16 lines - tests/
test_mpi_futures_intel19 , Python, 43 lines.py - tests/
test_parallel_interactiv , Python, 59 linese.py - tests/
test_parallel_interfaces , Python, 191 lines.py - tests/
test_pc_subworlds.py , Python, 40 lines - tests/
test_phdf5.py , Python, 8 lines - utils.py, Python, 481 lines
- README.md, Text, 2 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 139 scripts, each with its path and the digest of its content;
- 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data and code availability
All large data files containing simulation results from training networks with optimized hyperparameters are available from the lead contact upon request.
All code required to reproduce the results in this paper have been deposited on GitHub155 and archived on Zenodo and are publicly available as of the date of publication. Accession numbers are listed in the key resources table.
Any additional information required to reanalyze the data reported in this paper is available from the lead contact upon request.
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 30 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 4 authors, 6 keywords, 3 funders, 137 references.
Cite
This paper
Galloni, A. R., Peddada, A., Chennawar, Y., & Milstein, A. D. (2026). Cellular and subcellular specialization enables biology-constrained deep learning. Cell reports, 45(4), 117159. https://
BibTeX
@article{galloni2026cell
author = {Galloni, Alessandro R. and Peddada, Ajay and Chennawar, Yash and Milstein, Aaron D.},
title = {{Cellular and subcellular specialization enables biology-constrained deep learning}},
journal = {Cell reports},
year = {2026},
month = mar,
volume = {45},
number = {4},
pages = {117159},
publisher = {Cell Press},
issn = {2211-1247},
doi = {10.1016/
url = {https://
pmid = {41894388},
pmcid = {PMC13109822}
}
RIS
TY - JOUR
AU - Galloni, Alessandro R.
AU - Peddada, Ajay
AU - Chennawar, Yash
AU - Milstein, Aaron D.
TI - Cellular and subcellular specialization enables biology-constrained deep learning
T2 - Cell reports
J2 - Cell Rep
PY - 2026
DA - 2026/
VL - 45
IS - 4
SP - 117159
SN - 2211-1247
PB - Cell Press
DO - 10.1016/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1016/
"type": "article-journal",
"title": "Cellular and subcellular specialization enables biology-constrained deep learning",
"container-title": "Cell reports",
"author": [
{
"family": "Galloni",
"given": "Alessandro R."
},
{
"family": "Peddada",
"given": "Ajay"
},
{
"family": "Chennawar",
"given": "Yash"
},
{
"family": "Milstein",
"given": "Aaron D."
}
],
"container-title-short":
"volume": "45",
"issue": "4",
"page": "117159",
"DOI": "10.1016/
"PMID": "41894388",
"PMCID": "PMC13109822",
"ISSN": "2211-1247",
"publisher": "Cell Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
26
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1016/j.celrep.2022.111962 [code]
- Lateral entorhinal cortex inputs modulate hippocampal dendritic excitability by recruiting a local disinhibitory microcircuit.Journal: Cell reportsIn common: NEURON, seaborn, pandas, 3 other tools, 11 references
- [2] doi:10.1038/s41467-025-64859-0 [code]
- Hippocampal OLM interneurons regulate CA1 place cell plasticity and remapping.Journal: Nature communicationsIn common: h5py, seaborn, pandas, 3 other tools, 12 references
- [3] doi:10.1038/s41586-026-10190-7 [code]
- Vectorized instructive signals in cortical dendrites.Journal: NatureIn common: h5py, NumPy, 14 references
- [4] doi:10.7554/elife.89674 [code]
- A neuronal least-action principle for real-time learning in cortical circuits.Journal: eLifeIn common: PyTorch, seaborn, scikit-learn, 3 other tools, 12 references
- [5] doi:10.1038/s41467-026-74460-8 [code]
- Spike-based alignment learning solves the weight transport problem.Journal: Nature communicationsIn common: PyTorch, seaborn, scikit-learn, 4 other tools, 11 references
- [6] doi:10.1038/s41467-025-56297-9 [code]
- Dendrites endow artificial neural networks with accurate, robust and parameter-efficient learning.Journal: Nature communicationsIn common: PyTorch, seaborn, scikit-learn, 4 other tools, 10 references
- [7] doi:10.1101/2025.05.12.653626 [code]
- Breaking Balance: Encoding local error signals in perturbations of excitation-inhibition balanceJournal: bioRxiv (preprint)In common: cellular / molecular, 13 references
- [8] doi:10.1371/journal.pcbi.1012754 [code]
- The calcitron: A simple neuron model that implements many learning rules via the calcium control hypothesis.Journal: PLoS computational biologyIn common: seaborn, scikit-learn, pandas, 3 other tools, cellular / molecular, 11 references
- [9] doi:10.1038/s41467-024-50546-z [code]
- Functional architecture of intracellular oscillations in hippocampal dendrites.Journal: Nature communicationsIn common: 14 references
- [10] doi:10.1016/j.celrep.2026.117793 [code]
- Clustered inputs engage dendritic nonlinearities and calcium signaling to support efficient place-field formation in CA1 pyramidal neurons.Journal: Cell reportsIn common: NEURON, seaborn, scikit-learn, 4 other tools, cellular / molecular, 9 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 3 repositories of the authors' code, each at its verified commit and with its license, 139 scripts, and 10 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:5e4b7b97a8cef527…
Add the badge to the code's README
If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.
Markdown
<!-- oscr-badge:start -->
[](https://openscicode.org/badge/doi/10.1016/j.celrep.2026.117159)
<!-- oscr-badge:end -->reStructuredText
.. oscr-badge:start
.. image:: https://openscicode.org/badge/doi/10.1016/j.celrep.2026.117159.svg
:target: https://openscicode.org/badge/doi/10.1016/j.celrep.2026.117159
:alt: OSCR
.. oscr-badge:endHTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1016/j.celrep.2026.117159"><img src="https://openscicode.org/badge/doi/10.1016/j.celrep.2026.117159.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".
If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.
Report a mismatch between the code and the paper · Report a code error (signed in)
Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.
Report a reproduction failure (signed in)
Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
