From 33c10f2fabde5ca120fc3b554697ea3c8d0bc370 Mon Sep 17 00:00:00 2001 From: Saeed Date: Sat, 11 May 2024 02:06:29 +0330 Subject: [PATCH 1/8] feat: move trace to synapses --- conex/behaviors/neurons/axon.py | 15 +--- conex/behaviors/neurons/specs.py | 26 ------ conex/behaviors/synapses/dendrites.py | 42 +++------ conex/behaviors/synapses/learning.py | 125 ++++++-------------------- conex/behaviors/synapses/specs.py | 82 +++++++++++++++++ conex/nn/priority.py | 39 ++++---- 6 files changed, 147 insertions(+), 182 deletions(-) diff --git a/conex/behaviors/neurons/axon.py b/conex/behaviors/neurons/axon.py index 1002dd7..9416a61 100644 --- a/conex/behaviors/neurons/axon.py +++ b/conex/behaviors/neurons/axon.py @@ -10,14 +10,13 @@ class NeuronAxon(Behavior): """ Propagate the spikes and apply the delay mechanism. - Note: should be added after fire and trace behavior. + Note: should be added after fire. Args: max_delay (int): Maximum delay of all dendrites connected to the neurons. This value determines the delay buffer size. proximal_min_delay (int): Minimum delay of proximal dendrites. The default is 0. distal_min_delay (int): Minimum delay of distal dendrites. The default is 0. apical_min_delay (int): Minimum delay of apical dendrites. The default is 0. - have_trace (boolean): whether to calculate trace or not. None checks if trace is available. """ def __init__( @@ -27,7 +26,6 @@ def __init__( proximal_min_delay=0, distal_min_delay=0, apical_min_delay=0, - have_trace=None, **kwargs, ): super().__init__( @@ -36,7 +34,6 @@ def __init__( proximal_min_delay=proximal_min_delay, distal_min_delay=distal_min_delay, apical_min_delay=apical_min_delay, - have_trace=have_trace, **kwargs, ) @@ -45,11 +42,8 @@ def initialize(self, neurons): self.proximal_min_delay = self.parameter("proximal_min_delay", 0) self.distal_min_delay = self.parameter("distal_min_delay", 0) self.apical_min_delay = self.parameter("apical_min_delay", 0) - self.have_trace = self.parameter("have_trace", hasattr(neurons, "trace")) self.spike_history = neurons.vector_buffer(self.max_delay, dtype=torch.bool) - if self.have_trace: - self.trace_history = neurons.vector_buffer(self.max_delay) neurons.axon = self @@ -70,14 +64,7 @@ def update_min_delay(self, neurons): def get_spike(self, neurons, delay): return self.spike_history.gather(0, delay.unsqueeze(0)).squeeze(0) - def get_spike_trace(self, neurons, delay): - return self.trace_history.gather(0, delay.unsqueeze(0)).squeeze(0) - def forward(self, neurons): self.spike_history = neurons.buffer_roll( mat=self.spike_history, new=neurons.spikes ) - if self.have_trace: - self.trace_history = neurons.buffer_roll( - mat=self.trace_history, new=neurons.trace - ) diff --git a/conex/behaviors/neurons/specs.py b/conex/behaviors/neurons/specs.py index 94d9474..f0da675 100644 --- a/conex/behaviors/neurons/specs.py +++ b/conex/behaviors/neurons/specs.py @@ -28,32 +28,6 @@ def forward(self, neurons): neurons.v += neurons.vector(mode=self.mode, scale=self.scale) + self.offset -class SpikeTrace(Behavior): - """ - Calculates the spike trace. - - Note : should be placed after Fire behavior. - - Args: - tau_s (float): decay term for spike trace. The default is None. - """ - - def __init__(self, tau_s, *args, **kwargs): - super().__init__(*args, tau_s=tau_s, **kwargs) - - def initialize(self, neurons): - """ - Sets the trace attribute for the neural population. - """ - self.tau_s = self.parameter("tau_s", None, required=True) - neurons.trace = neurons.vector(0.0) - - def forward(self, neurons): - """ - Calculates the spike trace of each neuron by adding current spike and decaying the trace so far. - """ - neurons.trace += neurons.spikes - neurons.trace -= (neurons.trace / self.tau_s) * neurons.network.dt class Fire(Behavior): diff --git a/conex/behaviors/synapses/dendrites.py b/conex/behaviors/synapses/dendrites.py index 58e3a4e..2865774 100644 --- a/conex/behaviors/synapses/dendrites.py +++ b/conex/behaviors/synapses/dendrites.py @@ -18,7 +18,7 @@ class BaseDendriticInput(Behavior): of pre-synaptic neurons and sets a coefficient accordingly. Note: weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -62,7 +62,7 @@ class SparseDendriticInput(BaseDendriticInput): of pre-synaptic neurons and sets a coefficient, accordingly. Note: weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -78,8 +78,7 @@ def initialize(self, synapse): raise RuntimeError("Network should've made with SxD mode for synapses") def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) - return torch.matmul(spikes.to(synapse.weights.dtype), synapse.weights) + return torch.matmul(synapse.pre_spike.to(synapse.weights.dtype), synapse.weights) class One2OneDendriticInput(BaseDendriticInput): @@ -88,7 +87,7 @@ class One2OneDendriticInput(BaseDendriticInput): of pre-synaptic neurons and sets a coefficient, accordingly. Note: weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -106,8 +105,7 @@ def initialize(self, synapse): ) def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) - return spikes * synapse.weights + return synapse.pre_spike * synapse.weights class SimpleDendriticInput(BaseDendriticInput): @@ -116,7 +114,7 @@ class SimpleDendriticInput(BaseDendriticInput): of pre-synaptic neurons and sets a coefficient, accordingly. Note: weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -132,8 +130,7 @@ def initialize(self, synapse): raise RuntimeError(f"Network should've made with SxD mode for synapses") def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) - return torch.sum(synapse.weights[spikes], axis=0) + return torch.sum(synapse.weights[synapse.pre_spike], axis=0) class AveragePool2D(BaseDendriticInput): @@ -159,8 +156,7 @@ def initialize(self, synapse): ) def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) - spikes = spikes.view(synapse.src_shape).to(self.def_dtype) + spikes = synapse.pre_spike.view(synapse.src_shape).to(self.def_dtype) I = F.adaptive_avg_pool2d(spikes, self.output_shape) return I.view((-1,)) @@ -171,7 +167,7 @@ class LateralDendriticInput(BaseDendriticInput): Note: weight shape = (1, 1, kernel_depth, kernel_height, kernel_width) weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -205,11 +201,7 @@ def initialize(self, synapse): ) def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay).to( - self.def_dtype - ) - spikes = spikes.view(1, *synapse.src_shape) - + spikes = synapse.pre_spike.to(self.def_dtype).view(1, *synapse.src_shape) I = F.conv3d(input=spikes, weight=synapse.weights, padding=self.padding) return I.view((-1,)) @@ -221,7 +213,7 @@ class Conv2dDendriticInput(BaseDendriticInput): Note: Weight shape = (out_channel, in_channel, kernel_height, kernel_width) weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -295,10 +287,7 @@ def initialize(self, synapse): ) def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay).to( - self.def_dtype - ) - spikes = spikes.view(synapse.src_shape) + spikes = synapse.pre_spike.to(self.def_dtype).view(synapse.src_shape) I = F.conv2d( input=spikes, @@ -325,7 +314,7 @@ class Local2dDendriticInput(BaseDendriticInput): and connection_size = input_channel * connection_height * connection_width. Kernel shape = (out_channel, out_height, out_width, in_channel, connection_height, connection_width) weights must be initialize by others behaviors. - Also, Axon paradigm should be added to the neurons. + Also, Spike Catcher paradigm should be added to synapse group. Connection type (Proximal, Distal, Apical) should be specified by the tag of the synapse. and Dendrite behavior of the neurons group should access the `I` of each synapse to apply them. @@ -418,10 +407,7 @@ def initialize(self, synapse): ) def calculate_input(self, synapse): - spikes = synapse.src.axon.get_spike(synapse.src, synapse.src_delay).to( - self.def_dtype - ) - spikes = spikes.view(synapse.src_shape) + spikes = synapse.pre_spike.to(self.def_dtype).view(synapse.src_shape) spikes = F.unfold( spikes, kernel_size=synapse.kernel_shape[-2:], diff --git a/conex/behaviors/synapses/learning.py b/conex/behaviors/synapses/learning.py index 61f63a8..999dc07 100644 --- a/conex/behaviors/synapses/learning.py +++ b/conex/behaviors/synapses/learning.py @@ -32,19 +32,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.add_tag("weight_learning") - def get_spike_and_trace(self, synapse): - src_spike = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) - dst_spike = synapse.dst.axon.get_spike(synapse.dst, synapse.dst_delay) - - src_spike_trace = synapse.src.axon.get_spike_trace( - synapse.src, synapse.src_delay - ) - dst_spike_trace = synapse.dst.axon.get_spike_trace( - synapse.dst, synapse.dst_delay - ) - - return src_spike, dst_spike, src_spike_trace, dst_spike_trace - def compute_dw(self, synapse): ... @@ -111,20 +98,13 @@ def initialize(self, synapse): ) def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - dw_minus = ( - torch.outer(src_spike, dst_spike_trace) + torch.outer(synapse.pre_spike, synapse.post_trace) * self.a_minus * self.n_bound(synapse.weights, self.w_min, self.w_max) ) dw_plus = ( - torch.outer(src_spike_trace, dst_spike) + torch.outer(synapse.pre_trace, synapse.post_spike) * self.a_plus * self.p_bound(synapse.weights, self.w_min, self.w_max) ) @@ -148,23 +128,16 @@ class SparseSTDP(SimpleSTDP): """ def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - weight_data = synapse.weights.values()[:] dw_minus = ( - src_spike[synapse.src_idx] - * dst_spike_trace[synapse.dst_idx] + synapse.pre_spike[synapse.src_idx] + * synapse.post_trace[synapse.dst_idx] * self.a_minus * self.n_bound(weight_data, self.w_min, self.w_max) ) dw_plus = ( - src_spike_trace[synapse.src_idx] - * dst_spike[synapse.dst_idx] + synapse.pre_trace[synapse.src_idx] + * synapse.post_spike[synapse.dst_idx] * self.a_plus * self.p_bound(weight_data, self.w_min, self.w_max) ) @@ -191,22 +164,15 @@ class One2OneSTDP(SimpleSTDP): """ def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - dw_minus = ( - src_spike - * dst_spike_trace + synapse.pre_spike + * synapse.post_trace * self.a_minus * self.n_bound(synapse.weights, self.w_min, self.w_max) ) dw_plus = ( - src_spike_trace - * dst_spike + synapse.pre_trace + * synapse.post_spike * self.a_plus * self.p_bound(synapse.weights, self.w_min, self.w_max) ) @@ -267,17 +233,10 @@ def initialize(self, synapse): self.alpha = 2 * self.rho * pre_tau / 1000 def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - pre_spike_changes = torch.outer( - src_spike, (self.alpha - dst_spike_trace) * self.change_sign + synapse.pre_spike, (self.alpha - synapse.post_trace) * self.change_sign ) - post_spike_changes = torch.outer(src_spike_trace, dst_spike) + post_spike_changes = torch.outer(synapse.pre_trace, synapse.post_spike) return self.lr * (pre_spike_changes + post_spike_changes) @@ -296,17 +255,10 @@ class One2OneiSTDP(SimpleiSTDP): """ def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - pre_spike_changes = ( - src_spike * (self.alpha - dst_spike_trace) * self.change_sign + synapse.pre_spike * (self.alpha - synapse.post_trace) * self.change_sign ) - post_spike_changes = src_spike_trace * dst_spike + post_spike_changes = synapse.pre_trace * synapse.post_spike return self.lr * (pre_spike_changes + post_spike_changes) @@ -325,22 +277,13 @@ class SparseiSTDP(SimpleiSTDP): """ def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - - weight_data = synapse.weights.values()[:] - pre_spike_changes = ( - src_spike[synapse.src_idx] - * (self.alpha - dst_spike_trace)[synapse.dst_idx] + synapse.pre_spike[synapse.src_idx] + * (self.alpha - synapse.post_trace)[synapse.dst_idx] * self.change_sign ) post_spike_changes = ( - src_spike_trace[synapse.src_idx] * dst_spike[synapse.dst_idx] + synapse.pre_trace[synapse.src_idx] * synapse.post_spike[synapse.dst_idx] ) return self.lr * (pre_spike_changes + post_spike_changes) @@ -364,14 +307,7 @@ def initialize(self, synapse): self.weight_divisor = synapse.dst_shape[2] * synapse.dst_shape[1] def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - - src_spike = src_spike.view(synapse.src_shape).to(self.def_dtype) + src_spike = synapse.pre_spike.view(synapse.src_shape).to(self.def_dtype) src_spike = F.unfold( src_spike, kernel_size=synapse.weights.size()[-2:], @@ -380,13 +316,13 @@ def compute_dw(self, synapse): ) src_spike = src_spike.expand(synapse.dst_shape[0], *src_spike.shape) - dst_spike_trace = dst_spike_trace.view((synapse.dst_shape[0], -1, 1)) + dst_spike_trace = synapse.post_trace.view((synapse.dst_shape[0], -1, 1)) dw_minus = torch.bmm(src_spike, dst_spike_trace).view( synapse.weights.shape ) * self.n_bound(synapse.weights, self.w_min, self.w_max) - src_spike_trace = src_spike_trace.view(synapse.src_shape) + src_spike_trace = synapse.pre_trace.view(synapse.src_shape) src_spike_trace = F.unfold( src_spike_trace, kernel_size=synapse.weights.size()[-2:], @@ -397,7 +333,9 @@ def compute_dw(self, synapse): synapse.dst_shape[0], *src_spike_trace.shape ) - dst_spike = dst_spike.view((synapse.dst_shape[0], -1, 1)).to(self.def_dtype) + dst_spike = synapse.post_spike.view((synapse.dst_shape[0], -1, 1)).to( + self.def_dtype + ) dw_plus = torch.bmm(src_spike_trace, dst_spike).view( synapse.weights.shape @@ -412,14 +350,7 @@ class Local2dSTDP(SimpleSTDP): """ def compute_dw(self, synapse): - ( - src_spike, - dst_spike, - src_spike_trace, - dst_spike_trace, - ) = self.get_spike_and_trace(synapse) - - src_spike = src_spike.view(synapse.src_shape).to(self.def_dtype) + src_spike = synapse.pre_spike.view(synapse.src_shape).to(self.def_dtype) src_spike = F.unfold( src_spike, kernel_size=synapse.kernel_shape[-2:], @@ -429,7 +360,7 @@ def compute_dw(self, synapse): src_spike = src_spike.transpose(0, 1) src_spike = src_spike.expand(synapse.dst_shape[0], *src_spike.shape) - dst_spike_trace = dst_spike_trace.view((synapse.dst_shape[0], -1, 1)) + dst_spike_trace = synapse.post_trace.view((synapse.dst_shape[0], -1, 1)) dst_spike_trace = dst_spike_trace.expand(synapse.weights.shape) dw_minus = ( @@ -438,7 +369,7 @@ def compute_dw(self, synapse): * self.n_bound(synapse.weights, self.w_min, self.w_max) ) - src_spike_trace = src_spike_trace.view(synapse.src_shape) + src_spike_trace = synapse.pre_trace.view(synapse.src_shape) src_spike_trace = F.unfold( src_spike_trace, kernel_size=synapse.kernel_shape[-2:], @@ -450,7 +381,9 @@ def compute_dw(self, synapse): synapse.dst_shape[0], *src_spike_trace.shape ) - dst_spike = dst_spike.view((synapse.dst_shape[0], -1, 1)).to(self.def_dtype) + dst_spike = synapse.pre_spike.view((synapse.dst_shape[0], -1, 1)).to( + self.def_dtype + ) dst_spike = dst_spike.expand(synapse.weights.shape) dw_plus = ( diff --git a/conex/behaviors/synapses/specs.py b/conex/behaviors/synapses/specs.py index 85dce35..56938a7 100644 --- a/conex/behaviors/synapses/specs.py +++ b/conex/behaviors/synapses/specs.py @@ -288,3 +288,85 @@ def forward(self, synapses): synapses (SynapseGroup): The synapses whose weight should be bound. """ synapses.weights = torch.clip(synapses.weights, self.w_min, self.w_max) + + +class SrcSpikeCatcher(Behavior): + """ + Get the spikes from pre synaptic neuron group and set as src_spike attribute for the synapse group. + + Note: Axon should be added to pre synaptice neuron group + """ + + def forward(self, synapse): + synapse.pre_spike = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) + + +class DstSpikeCatcher(Behavior): + """ + Get the spikes from post synaptic neuron group and set as dst_spike attribute for the synapse group. + + Note: Axon should be added to post synaptice neuron group + """ + + def forward(self, synapse): + synapse.post_spike = synapse.dst.axon.get_spike(synapse.dst, synapse.dst_delay) + + +class PreTrace(Behavior): + """ + Calculates the pre synaptic spike trace. + + Note : should be placed after spike catcher behavior. + + Args: + tau_s (float): decay term for spike trace. The default is None. + spike_scale (float): the increase effect of spikes on the trace. + """ + + def __init__(self, tau_s, *args, spike_scale=1.0, **kwargs): + super().__init__(*args, tau_s=tau_s, spike_scale=spike_scale, **kwargs) + + def initialize(self, synapse): + """ + Sets the trace attribute for the neural population. + """ + self.tau_s = self.parameter("tau_s", None, required=True) + self.spike_scale = self.parameter("spike_scale", 1.0) + synapse.pre_trace = synapse.src.vector(0.0) + + def forward(self, synapse): + """ + Calculates the spike trace of each neuron by adding current spike and decaying the trace so far. + """ + synapse.pre_trace += synapse.src_spikes * self.spike_scale + synapse.pre_trace -= (synapse.pre_trace / self.tau_s) * synapse.network.dt + + +class PostTrace(Behavior): + """ + Calculates the post synaptic spike trace. + + Note : should be placed after spike catcher behavior. + + Args: + tau_s (float): decay term for spike trace. The default is None. + spike_scale (float): the increase effect of spikes on the trace. + """ + + def __init__(self, tau_s, *args, spike_scale=1.0, **kwargs): + super().__init__(*args, tau_s=tau_s, spike_scale=spike_scale, **kwargs) + + def initialize(self, synapse): + """ + Sets the trace attribute for the neural population. + """ + self.tau_s = self.parameter("tau_s", None, required=True) + self.spike_scale = self.parameter("spike_scale", 1.0) + synapse.post_trace = synapse.src.vector(0.0) + + def forward(self, synapse): + """ + Calculates the spike trace of each neuron by adding current spike and decaying the trace so far. + """ + synapse.post_trace += synapse.dst_spikes * self.spike_scale + synapse.post_trace -= (synapse.post_trace / self.tau_s) * synapse.network.dt diff --git a/conex/nn/priority.py b/conex/nn/priority.py index 02bb93f..1f0be91 100644 --- a/conex/nn/priority.py +++ b/conex/nn/priority.py @@ -26,7 +26,6 @@ "Fire": 340, "LocationSetter": 340, "SensorySetter": 340, - "SpikeTrace": 360, "NeuronAxon": 380, "ActivityBaseHomeostasis": 341, "VoltageBaseHomeostasis": 301, @@ -47,23 +46,27 @@ "SimpleDendriticInput": 180, "SparseDendriticInput": 180, "CurrentNormalization": 200, - "BaseLearning": 400, - "Conv2dRSTDP": 400, - "Conv2dSTDP": 400, - "Local2dRSTDP": 400, - "Local2dSTDP": 400, - "One2OneRSTDP": 400, - "One2OneSTDP": 400, - "One2OneiSTDP": 400, - "SimpleRSTDP": 400, - "SimpleSTDP": 400, - "SimpleiSTDP": 400, - "SparseRSTDP": 400, - "SparseSTDP": 400, - "SparseiSTDP": 400, - "LearningRule": 400, - "WeightNormalization": 420, - "WeightClip": 440, + "SrcSpikeCatcher": 420, + "DstSpikeCatcher": 440, + "PreTrace": 460, + "PostTrace": 480, + "BaseLearning": 480, + "Conv2dRSTDP": 480, + "Conv2dSTDP": 480, + "Local2dRSTDP": 480, + "Local2dSTDP": 480, + "One2OneRSTDP": 480, + "One2OneSTDP": 480, + "One2OneiSTDP": 480, + "SimpleRSTDP": 480, + "SimpleSTDP": 480, + "SimpleiSTDP": 480, + "SparseRSTDP": 480, + "SparseSTDP": 480, + "SparseiSTDP": 480, + "LearningRule": 480, + "WeightNormalization": 500, + "WeightClip": 520, } ALL_PRIORITIES = { From fac20350a2a9bf6d7184e9b27fb79573b18ccf0d Mon Sep 17 00:00:00 2001 From: Saeed Date: Sat, 11 May 2024 15:36:08 +0330 Subject: [PATCH 2/8] fix: Example and bugs --- Example/test/layer4.json | 70 +++++++++++++++++----------- Example/test/mnist.py | 39 ++++++++++++---- conex/behaviors/synapses/learning.py | 14 +++--- conex/behaviors/synapses/specs.py | 18 +++++-- conex/nn/priority.py | 4 +- conex/nn/structure/io_layer.py | 21 --------- 6 files changed, 94 insertions(+), 72 deletions(-) diff --git a/Example/test/layer4.json b/Example/test/layer4.json index 36dab7c..b435386 100644 --- a/Example/test/layer4.json +++ b/Example/test/layer4.json @@ -65,18 +65,6 @@ "init_s": null } }, - { - "key": 360, - "class": [ - "python_callable", - "conex.behaviors.neurons.specs", - "SpikeTrace" - ], - "parameters_args": [], - "parameters_kwargs": { - "tau_s": 10.0 - } - }, { "key": 380, "class": [ @@ -89,8 +77,7 @@ "max_delay": 1, "proximal_min_delay": 0, "distal_min_delay": 0, - "apical_min_delay": 0, - "have_trace": null + "apical_min_delay": 0 } }, { @@ -169,18 +156,6 @@ "init_s": null } }, - { - "key": 360, - "class": [ - "python_callable", - "conex.behaviors.neurons.specs", - "SpikeTrace" - ], - "parameters_args": [], - "parameters_kwargs": { - "tau_s": 10.0 - } - }, { "key": 380, "class": [ @@ -193,8 +168,7 @@ "max_delay": 1, "proximal_min_delay": 0, "distal_min_delay": 0, - "apical_min_delay": 0, - "have_trace": null + "apical_min_delay": 0 } } ], @@ -250,6 +224,16 @@ "parameters_kwargs": { "current_coef": 1 } + }, + { + "key": 420, + "class": [ + "python_callable", + "conex.behaviors.synapses.specs", + "PreSpikeCatcher" + ], + "parameters_args": [], + "parameters_kwargs": {} } ], "device": "cpu", @@ -305,6 +289,16 @@ "parameters_kwargs": { "current_coef": 1 } + }, + { + "key": 420, + "class": [ + "python_callable", + "conex.behaviors.synapses.specs", + "PreSpikeCatcher" + ], + "parameters_args": [], + "parameters_kwargs": {} } ], "device": "cpu", @@ -360,6 +354,16 @@ "parameters_kwargs": { "current_coef": 1 } + }, + { + "key": 420, + "class": [ + "python_callable", + "conex.behaviors.synapses.specs", + "PreSpikeCatcher" + ], + "parameters_args": [], + "parameters_kwargs": {} } ], "device": "cpu", @@ -415,6 +419,16 @@ "parameters_kwargs": { "current_coef": 1 } + }, + { + "key": 420, + "class": [ + "python_callable", + "conex.behaviors.synapses.specs", + "PreSpikeCatcher" + ], + "parameters_args": [], + "parameters_kwargs": {} } ], "device": "cpu", diff --git a/Example/test/mnist.py b/Example/test/mnist.py index b788b8b..9104876 100644 --- a/Example/test/mnist.py +++ b/Example/test/mnist.py @@ -20,11 +20,14 @@ SimpleDendriteStructure, SimpleDendriteComputation, LIF, - SpikeTrace, NeuronAxon, ) from conex.behaviors.synapses import ( SynapseInit, + PreTrace, + PostTrace, + PreSpikeCatcher, + PostSpikeCatcher, WeightInitializer, SimpleDendriticInput, SimpleSTDP, @@ -55,7 +58,6 @@ MNIST_ROOT = "~/Temp/MNIST/" SENSORY_SIZE_HEIGHT = 28 SENSORY_SIZE_WIDTH = 28 # MNIST's image size -SENSORY_TRACE_TAU_S = 2.7 # Layer 4 L4_EXC_DEPTH = 4 @@ -66,7 +68,6 @@ L4_EXC_TAU = 10.0 L4_EXC_V_RESET = 0.0 L4_EXC_V_REST = 0.0 -L4_EXC_TRACE_TAU = 10.0 L4_INH_SIZE = 576 L4_INH_R = 5.0 @@ -95,11 +96,18 @@ INP_CC_MODE = "random" -INP_CC_WEIGHT_SHAPE = (4,1,5,5) +INP_CC_WEIGHT_SHAPE = (4, 1, 5, 5) INP_CC_COEF = 1 INP_CC_A_PLUS = 0.01 INP_CC_A_MINUS = 0.002 +L4_EXC_L23_EXC_PRE_TRACE = 10.0 +L4_EXC_L23_EXC_POST_TRACE = 10.0 + + +SENSORY_L4_PRE_TRACE = 10.0 +SENSORY_L4_POST_TRACE = 10.0 + ################################################## # making dataloader @@ -134,9 +142,8 @@ sensory_size=NeuronDimension( depth=1, height=SENSORY_SIZE_HEIGHT, width=SENSORY_SIZE_WIDTH ), - sensory_trace=SENSORY_TRACE_TAU_S, instance_duration=POISSON_TIME, - output_ports={"data_out": (None,[("sensory_pop", {})])} + output_ports={"data_out": (None, [("sensory_pop", {})])}, ) ################################################## @@ -159,7 +166,6 @@ v_reset=L4_EXC_V_RESET, v_rest=L4_EXC_V_REST, ), - SpikeTrace(tau_s=L4_EXC_TRACE_TAU), NeuronAxon(), ] ), @@ -180,7 +186,6 @@ v_reset=L4_INH_V_RESET, v_rest=L4_INH_V_REST, ), - SpikeTrace(tau_s=L4_INH_TRACE_TAU), NeuronAxon(), ] ), @@ -196,6 +201,7 @@ SynapseInit(), WeightInitializer(mode=L4_EXC_EXC_MODE), SimpleDendriticInput(current_coef=L4_EXC_EXC_COEF), + PreSpikeCatcher(), ] ), ) @@ -210,6 +216,7 @@ SynapseInit(), WeightInitializer(mode=L4_EXC_INH_MODE), SimpleDendriticInput(current_coef=L4_EXC_INH_COEF), + PreSpikeCatcher(), ] ), ) @@ -224,6 +231,7 @@ SynapseInit(), WeightInitializer(mode=L4_INH_EXC_MODE), SimpleDendriticInput(current_coef=L4_INH_EXC_COEF), + PreSpikeCatcher(), ] ), ) @@ -238,6 +246,7 @@ SynapseInit(), WeightInitializer(mode=L4_INH_INH_MODE), SimpleDendriticInput(current_coef=L4_INH_INH_COEF), + PreSpikeCatcher(), ] ), ) @@ -314,6 +323,10 @@ WeightInitializer(mode=L4_L2_MODE), SimpleDendriticInput(current_coef=L4_L2_COEF), SimpleSTDP(a_plus=L4_L2_A_PLUS, a_minus=L4_L2_A_MINUS), + PreSpikeCatcher(), + PostSpikeCatcher(), + PreTrace(tau_s=L4_EXC_L23_EXC_PRE_TRACE), + PostTrace(tau_s=L4_EXC_L23_EXC_POST_TRACE), ] ), "Proximal", @@ -357,9 +370,17 @@ synapsis_behavior=prioritize_behaviors( [ SynapseInit(), - WeightInitializer(mode=INP_CC_MODE, weight_shape=INP_CC_WEIGHT_SHAPE, kernel_shape=INP_CC_WEIGHT_SHAPE), + WeightInitializer( + mode=INP_CC_MODE, + weight_shape=INP_CC_WEIGHT_SHAPE, + kernel_shape=INP_CC_WEIGHT_SHAPE, + ), Conv2dDendriticInput(current_coef=INP_CC_COEF), Conv2dSTDP(a_plus=INP_CC_A_PLUS, a_minus=INP_CC_A_MINUS), + PreSpikeCatcher(), + PostSpikeCatcher(), + PreTrace(tau_s=SENSORY_L4_PRE_TRACE), + PostTrace(tau_s=SENSORY_L4_POST_TRACE), ] ), synaptic_tag="Proximal", diff --git a/conex/behaviors/synapses/learning.py b/conex/behaviors/synapses/learning.py index 999dc07..69a5fa6 100644 --- a/conex/behaviors/synapses/learning.py +++ b/conex/behaviors/synapses/learning.py @@ -7,7 +7,7 @@ import torch import torch.nn.functional as F -from conex.behaviors.neurons.specs import SpikeTrace +from conex.behaviors.synapses.specs import PreTrace, PostTrace # TODO docstring for bound functions @@ -215,14 +215,14 @@ def initialize(self, synapse): # messy till I move trace to synapse. pre_tau = [ - synapse.src.behavior[key_behavior] - for key_behavior in synapse.src.behavior - if isinstance(synapse.src.behavior[key_behavior], SpikeTrace) + synapse.behavior[key_behavior] + for key_behavior in synapse.behavior + if isinstance(synapse.behavior[key_behavior], PreTrace) ][0].tau_s post_tau = [ - synapse.dst.behavior[key_behavior] - for key_behavior in synapse.dst.behavior - if isinstance(synapse.dst.behavior[key_behavior], SpikeTrace) + synapse.behavior[key_behavior] + for key_behavior in synapse.behavior + if isinstance(synapse.behavior[key_behavior], PostTrace) ][0].tau_s assert ( diff --git a/conex/behaviors/synapses/specs.py b/conex/behaviors/synapses/specs.py index 56938a7..fa3b3bc 100644 --- a/conex/behaviors/synapses/specs.py +++ b/conex/behaviors/synapses/specs.py @@ -290,23 +290,31 @@ def forward(self, synapses): synapses.weights = torch.clip(synapses.weights, self.w_min, self.w_max) -class SrcSpikeCatcher(Behavior): +class PreSpikeCatcher(Behavior): """ Get the spikes from pre synaptic neuron group and set as src_spike attribute for the synapse group. Note: Axon should be added to pre synaptice neuron group """ + initialize_last = True + + def initialize(self, synapse): + synapse.pre_spike = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) def forward(self, synapse): synapse.pre_spike = synapse.src.axon.get_spike(synapse.src, synapse.src_delay) -class DstSpikeCatcher(Behavior): +class PostSpikeCatcher(Behavior): """ Get the spikes from post synaptic neuron group and set as dst_spike attribute for the synapse group. Note: Axon should be added to post synaptice neuron group """ + initialize_last = True + + def initialize(self, synapse): + synapse.post_spike = synapse.dst.axon.get_spike(synapse.dst, synapse.dst_delay) def forward(self, synapse): synapse.post_spike = synapse.dst.axon.get_spike(synapse.dst, synapse.dst_delay) @@ -338,7 +346,7 @@ def forward(self, synapse): """ Calculates the spike trace of each neuron by adding current spike and decaying the trace so far. """ - synapse.pre_trace += synapse.src_spikes * self.spike_scale + synapse.pre_trace += synapse.pre_spike * self.spike_scale synapse.pre_trace -= (synapse.pre_trace / self.tau_s) * synapse.network.dt @@ -362,11 +370,11 @@ def initialize(self, synapse): """ self.tau_s = self.parameter("tau_s", None, required=True) self.spike_scale = self.parameter("spike_scale", 1.0) - synapse.post_trace = synapse.src.vector(0.0) + synapse.post_trace = synapse.dst.vector(0.0) def forward(self, synapse): """ Calculates the spike trace of each neuron by adding current spike and decaying the trace so far. """ - synapse.post_trace += synapse.dst_spikes * self.spike_scale + synapse.post_trace += synapse.post_spike * self.spike_scale synapse.post_trace -= (synapse.post_trace / self.tau_s) * synapse.network.dt diff --git a/conex/nn/priority.py b/conex/nn/priority.py index 1f0be91..46c6863 100644 --- a/conex/nn/priority.py +++ b/conex/nn/priority.py @@ -46,8 +46,8 @@ "SimpleDendriticInput": 180, "SparseDendriticInput": 180, "CurrentNormalization": 200, - "SrcSpikeCatcher": 420, - "DstSpikeCatcher": 440, + "PreSpikeCatcher": 420, + "PostSpikeCatcher": 440, "PreTrace": 460, "PostTrace": 480, "BaseLearning": 480, diff --git a/conex/nn/structure/io_layer.py b/conex/nn/structure/io_layer.py index 3034468..35a5ffe 100644 --- a/conex/nn/structure/io_layer.py +++ b/conex/nn/structure/io_layer.py @@ -1,7 +1,6 @@ from pymonntorch import NetworkObject, Network, NeuronDimension, Behavior, NeuronGroup from conex.behaviors.neurons.axon import NeuronAxon from conex.behaviors.neurons.setters import SensorySetter, LocationSetter -from conex.behaviors.neurons.specs import SpikeTrace from conex.behaviors.neurons.dendrite import SimpleDendriteStructure from torch.utils.data.dataloader import DataLoader from typing import Union, Dict, Callable, Tuple, List @@ -26,8 +25,6 @@ class InputLayer(NetworkObject): location_axon_params (dict): Parameters for axon class of location neurongroup. silent_interval (int): Empty interval between two samples. instance_duration (int): Each sample duraiton - sensory_trace (float): The spike trace of sensory neurongroup. - location_trace (float): The spike trace of location neurongroup. sensory_data_dim (int): The number of dimension of sensory data. location_data_dim (int): The number of dimension of location data. behavior (dict): The behavior for the InputLayer itself. @@ -53,8 +50,6 @@ def __init__( location_axon_params: dict = None, silent_interval: int = 0, instance_duration: int = 0, - sensory_trace: float = None, - location_trace: float = None, sensory_data_dim: int = 2, location_data_dim: int = 2, tag: str = None, @@ -94,7 +89,6 @@ def __init__( net=net, size=sensory_size, tag=sensory_tag, - trace=sensory_trace, setter=SensorySetter, axon=sensory_axon, axon_params=sensory_axon_params, @@ -113,7 +107,6 @@ def __init__( net=net, size=location_size, tag=location_tag, - trace=location_trace, setter=LocationSetter, axon=location_axon, axon_params=location_axon_params, @@ -131,7 +124,6 @@ def __get_ng( net: Network, size: Union[int, NeuronDimension], tag: Union[str, None], - trace: Union[float, None], setter: Callable, axon: Behavior = NeuronAxon, axon_params: dict = None, @@ -145,9 +137,6 @@ def __get_ng( params = axon_params if axon_params is not None else {} behavior[NEURON_PRIORITIES["NeuronAxon"]] = axon(**params) - if trace is not None: - behavior[NEURON_PRIORITIES["SpikeTrace"]] = SpikeTrace(tau_s=trace) - if user_defined is not None: behavior.update(user_defined) @@ -175,8 +164,6 @@ class OutputLayer(NetworkObject): Args: representation_size (int or behavior): The size of each representation neurongroup. motor_size (int or behavior): The size of each motor neurongroup. - representation_trace (float): The spike trace of representation neurongroup. - motor_trace (float): The spike trace of motor neurongroup. representation_dendrite_structure (Callable): Dendrite structure for representation population. representation_dendrite_structure_params (dict): The parameters for dendrite structure of representation population. motor_dendrite_structure (Callable): Dendrite structure for motor population. @@ -194,8 +181,6 @@ def __init__( net: Network, representation_size: Union[int, NeuronDimension] = None, motor_size: Union[int, NeuronDimension] = None, - representation_trace: Union[float, None] = None, - motor_trace: Union[float, None] = None, representation_dendrite_structure: Callable = SimpleDendriteStructure, representation_dendrite_structure_params: dict = None, motor_dendrite_structure: Callable = SimpleDendriteStructure, @@ -226,7 +211,6 @@ def __init__( net=net, size=representation_size, tag=representation_tag, - trace=representation_trace, dendrite_structure=representation_dendrite_structure, dendrite_structure_params=representation_dendrite_structure_params, user_defined=representation_user_defined, @@ -242,7 +226,6 @@ def __init__( net=net, size=motor_size, tag=motor_tag, - trace=motor_trace, dendrite_structure=motor_dendrite_structure, dendrite_structure_params=motor_dendrite_structure_params, user_defined=motor_user_defined, @@ -261,7 +244,6 @@ def __get_ng( net: Network, size: Union[int, NeuronDimension], tag: Union[str, None], - trace: Union[float, None] = None, dendrite_structure: Callable = SimpleDendriteStructure, dendrite_structure_params: dict = None, user_defined: Dict[int, Behavior] = None, @@ -276,9 +258,6 @@ def __get_ng( **dendrite_structure_params ) - if trace is not None: - behavior[NEURON_PRIORITIES["Trace"]] = SpikeTrace(tau_s=trace) - if user_defined is not None: behavior.update(user_defined) From 4059992404b7667de3cf848ba5e2c295bdc78bd7 Mon Sep 17 00:00:00 2001 From: Saeed Date: Sun, 19 May 2024 03:22:08 +0330 Subject: [PATCH 3/8] fix: wrong priority --- conex/nn/priority.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/conex/nn/priority.py b/conex/nn/priority.py index 46c6863..7df1eba 100644 --- a/conex/nn/priority.py +++ b/conex/nn/priority.py @@ -50,23 +50,23 @@ "PostSpikeCatcher": 440, "PreTrace": 460, "PostTrace": 480, - "BaseLearning": 480, - "Conv2dRSTDP": 480, - "Conv2dSTDP": 480, - "Local2dRSTDP": 480, - "Local2dSTDP": 480, - "One2OneRSTDP": 480, - "One2OneSTDP": 480, - "One2OneiSTDP": 480, - "SimpleRSTDP": 480, - "SimpleSTDP": 480, - "SimpleiSTDP": 480, - "SparseRSTDP": 480, - "SparseSTDP": 480, - "SparseiSTDP": 480, - "LearningRule": 480, - "WeightNormalization": 500, - "WeightClip": 520, + "BaseLearning": 500, + "Conv2dRSTDP": 500, + "Conv2dSTDP": 500, + "Local2dRSTDP": 500, + "Local2dSTDP": 500, + "One2OneRSTDP": 500, + "One2OneSTDP": 500, + "One2OneiSTDP": 500, + "SimpleRSTDP": 500, + "SimpleSTDP": 500, + "SimpleiSTDP": 500, + "SparseRSTDP": 500, + "SparseSTDP": 500, + "SparseiSTDP": 500, + "LearningRule": 500, + "WeightNormalization": 520, + "WeightClip": 540, } ALL_PRIORITIES = { From 7f3937a37524fcf0ba8517344c46af43bc5769b7 Mon Sep 17 00:00:00 2001 From: Saeed Date: Mon, 20 May 2024 04:37:37 +0330 Subject: [PATCH 4/8] feat: gap for masks --- conex/helpers/transforms/masks.py | 55 +++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/conex/helpers/transforms/masks.py b/conex/helpers/transforms/masks.py index 883be5b..21c8b10 100644 --- a/conex/helpers/transforms/masks.py +++ b/conex/helpers/transforms/masks.py @@ -15,12 +15,14 @@ class GridEraseMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. + gap (tuple(int)): left, bottom, up, right gaps for the cell. """ - def __init__(self, m, n, random=False): + def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): self.m = m self.n = n self.random = random + self.gap = gap def __call__(self, img): _, h, w = img.shape @@ -33,7 +35,20 @@ def __call__(self, img): ) for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij - result.append(TF.erase(img, i * h_grid, j * w_grid, h_grid, w_grid, v=0)) + h_cor = i * h_grid + self.gap[0] + dh = h_cor if h_cor < 0 else 0 + w_cor = j * w_grid + self.gap[2] + dw = w_cor if w_cor < 0 else 0 + result.append( + TF.erase( + img, + max(h_cor, 0), + max(w_cor, 0), + h_grid - self.gap[1] - self.gap[2] + dh, + w_grid - self.gap[3] - self.gap[0] + dw, + v=0, + ) + ) location[index, ij[0], ij[1]] = False result = torch.stack(result) @@ -54,12 +69,14 @@ class GridKeepMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. + gap (tuple(int)): left, bottom, up, right gaps for the cell. """ - def __init__(self, m, n, random=False): + def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): self.m = m self.n = n self.random = random + self.gap = gap def __call__(self, img): _, h, w = img.shape @@ -73,8 +90,22 @@ def __call__(self, img): for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij bg = torch.zeros_like(img) - bg[:, i * h_grid : (i + 1) * h_grid, j * w_grid : (j + 1) * w_grid] = img[ - :, i * h_grid : (i + 1) * h_grid, j * w_grid : (j + 1) * w_grid + bg[ + :, + max(i * h_grid + self.gap[0], 0) : min( + (i + 1) * h_grid - self.gap[3], img.size(1) + ), + max(j * w_grid + self.gap[2], 0) : min( + (j + 1) * w_grid - self.gap[1], img.size(2) + ), + ] = img[ + :, + max(i * h_grid + self.gap[0], 0) : min( + (i + 1) * h_grid - self.gap[3], img.size(1) + ), + max(j * w_grid + self.gap[2], 0) : min( + (j + 1) * w_grid - self.gap[1], img.size(2) + ), ] result.append(bg) location[index, ij[0], ij[1]] = True @@ -97,12 +128,14 @@ class GridCropMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. + gap (tuple(int)): left, bottom, up, right gaps for the cell. """ - def __init__(self, m, n, random=False): + def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): self.m = m self.n = n self.random = random + self.gap = gap def __call__(self, img): _, h, w = img.shape @@ -115,7 +148,15 @@ def __call__(self, img): ) for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij - result.append(TF.crop(img, i * h_grid, j * w_grid, h_grid, w_grid)) + result.append( + TF.crop( + img, + i * h_grid + self.gap[0], + j * w_grid + self.gap[2], + h_grid - self.gap[1] - self.gap[2], + w_grid - self.gap[3] - self.gap[0], + ) + ) location[index, ij[0], ij[1]] = True result = torch.stack(result) From a0455fb18429d3faeaddd1f7526c81ac60d41a4f Mon Sep 17 00:00:00 2001 From: Saeed Date: Mon, 20 May 2024 04:37:37 +0330 Subject: [PATCH 5/8] feat: gap for masks --- Example/helpers/mask_example.ipynb | 172 +++++++++++++++++++++++++++++ conex/helpers/transforms/masks.py | 55 +++++++-- 2 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 Example/helpers/mask_example.ipynb diff --git a/Example/helpers/mask_example.ipynb b/Example/helpers/mask_example.ipynb new file mode 100644 index 0000000..074b174 --- /dev/null +++ b/Example/helpers/mask_example.ipynb @@ -0,0 +1,172 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import conex\n", + "import matplotlib.pyplot as plt\n", + "from torchvision import datasets\n", + "from torchvision.transforms import ToTensor, Compose" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "m = 2\n", + "n = 2\n", + "gap = (-5,-5,-5,-5)\n", + "sample_index = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "grid_erase_transforms = Compose([ToTensor(), conex.GridEraseMask(m=m, n=n, gap=gap)])\n", + "grid_keep_transforms = Compose([ToTensor(), conex.GridKeepMask(m=m, n=n, gap=gap)])\n", + "grid_crop_transforms = Compose([ToTensor(), conex.GridCropMask(m=m, n=n, gap=gap)])" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "mnist = datasets.MNIST(root=\"Dataset\", transform=ToTensor(), download=True)\n", + "mnist_erase = datasets.MNIST(root=\"Dataset\", transform=grid_erase_transforms, download=True)\n", + "mnist_keep = datasets.MNIST(root=\"Dataset\", transform=grid_keep_transforms, download=True)\n", + "mnist_crop = datasets.MNIST(root=\"Dataset\", transform=grid_crop_transforms, download=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(mnist[sample_index][0][0])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(m,n, figsize=(10,10))\n", + "for i in range(m):\n", + " for j in range(n):\n", + " axes[i][j].imshow(mnist_erase[sample_index][0][0][i*n+j][0])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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rbsRznk9qefH6LHoTb/WiOc/ojGep/SmvHrTKX+F5qyzzopx48YdkXx37dp1REKudxMnTozzzz8/tm7dWutSxszhb85x/n+lvb09urq66uYxsGDBgnjyySfjmWeeialTpw7vr/fzfqx1H029nfN6oTf9mt70a414/iPq73lKb8qvN53ygeb000+PmTNnRl9f34j9fX19MWvWrBpVVRtDQ0Px4osvRnt7e61LGTPd3d3R1tY24vzv378/1q5d23DnPyJi9+7dsWPHjuwfA0VRxG233RaPP/54rF69Orq7u0fcX6/n/d3WfTT1cs7rjd70a3rTr9TDc9Ro1cvzlN6UcW+qxTcRpHr00UeL8ePHF3/+539e/PSnPy0WLlxYTJw4sXjllVdqXVpV3X777cWaNWuKl19+udiwYUPxkY98pGhubq67de/Zs6fYvHlzsXnz5iIiimXLlhWbN28ufv7znxdFURT33HNP0dLSUjz++OPFli1biptvvrlob28vBgcHa1z5yTve2vfs2VPcfvvtxfr164tt27YVzzzzTHHppZcW73nPe7Jf++c///mipaWlWLNmTbFz587h7a233hoeU4/n/d3WXc/nvB7pTXpTvT1HHaY36U259aYsAk1RFMVXv/rVoqurqzj99NOLD33oQyO+Sq5e3XTTTUV7e3sxfvz4oqOjo5gzZ07xwgsv1LqsinvmmWeKiDhimzt3blEUv/qaxLvuuqtoa2sryuVyccUVVxRbtmypbdEVcry1v/XWW0VPT09x9tlnF+PHjy/OOeecYu7cucX27dtrXfZJO9qaI6JYuXLl8Jh6PO/vtu56Puf1Sm/Sm+rpOeowvUlvyq03lYqiKCp/3QcAAKD6TvnP0AAAAByLQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsvX/AyGD5nId0GllAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(m,n, figsize=(10,10))\n", + "for i in range(m):\n", + " for j in range(n):\n", + " axes[i][j].imshow(mnist_keep[sample_index][0][0][i*n+j][0])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzQAAAMtCAYAAABJqzQkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA9gklEQVR4nO3df5SV9Z0n+M+FggvYRU1opH6sQNcaiInQTgJGYVXQXmutmXESSeZg7LUxk2S0FTtMtW0HnbOymQykndbx5KBmzGyMdsfEs2fS6mzsGBIUTRMSJDrxMMbGFZvKanW1HFOFiMWvZ/+wraQsrKK+3OLWt+7rdc494f748P348Dz1yfs+9z5VKoqiCAAAgAxNqHYDAAAAqQQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZqqt2A+929OjReOWVV6K+vj5KpVK12wEY94qiiH379kVLS0tMmOB9rmMxmwBOrpHMpjEXaF555ZWYPXt2tdsAqDmdnZ1x2mmnVbuNMclsAqiO45lNYy7Q1NfXR0TEefHPoi4mVbkbgPHvcByKH8Wj/T9/GcxsAji5RjKbxlygeedUfl1MirqSoQEw6oq3/8dHqd6b2QRwko1gNvmwNAAAkK1RCzR33XVXtLa2xpQpU2LRokXx1FNPjdZSADAscwlgfBqVQPPggw/GmjVr4uabb45nnnkmzj///Ghvb489e/aMxnIAMCRzCWD8GpVAc/vtt8dnPvOZ+OxnPxsf/OAH44477ojZs2fH3XffPRrLAcCQzCWA8avigebgwYOxY8eOaGtrG/B4W1tbbN26ddDr+/r6ore3d8ANACplpHMpwmwCyEnFA81rr70WR44cicbGxgGPNzY2RldX16DXb9iwIRoaGvpvrvMPQCWNdC5FmE0AORm1iwK8+xJrRVEc87Jra9eujZ6env5bZ2fnaLUEQA073rkUYTYB5KTiv4dm5syZMXHixEHvenV3dw96dywiolwuR7lcrnQbABARI59LEWYTQE4qfoZm8uTJsWjRoti0adOAxzdt2hRLly6t9HIAMCRzCWB8q/gZmoiIjo6OuPLKK2Px4sWxZMmSuOeee2LPnj1xzTXXjMZyADAkcwlg/BqVQLNy5crYu3dvfPGLX4xXX301FixYEI8++mjMnTt3NJYDgCGZSwDjV6koiqLaTfym3t7eaGhoiOXxsagrTap2OwDj3uHiUDwRD0dPT09Mnz692u2MSWYTwMk1ktk0alc5AwAAGG0CDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZqnigWbduXZRKpQG3pqamSi8DAMfNbAIYv+pG4y8988wz4wc/+EH//YkTJ47GMgBw3MwmgPFpVAJNXV3dcb/z1dfXF319ff33e3t7R6MlAGqc2QQwPo3Kd2h27doVLS0t0draGpdffnm89NJL7/naDRs2RENDQ/9t9uzZo9ESADXObAIYn0pFURSV/Av/+q//Ot58882YP39+/P3f/3186Utfil/84hexc+fO+O3f/u1Brz/Wu2CzZ8+O5fGxqCtNqmRrABzD4eJQPBEPR09PT0yfPr3a7YwKswkgLyOZTRX/yFl7e3v/nxcuXBhLliyJ008/Pe67777o6OgY9PpyuRzlcrnSbQBAP7MJYPwa9cs2n3LKKbFw4cLYtWvXaC8FAMfFbAIYP0Y90PT19cXzzz8fzc3No70UABwXswlg/Kh4oLnhhhtiy5YtsXv37vjJT34Sn/zkJ6O3tzdWrVpV6aUA4LiYTQDjV8W/Q/PLX/4yPvWpT8Vrr70Wp556apx77rmxbdu2mDt3bqWXAoDjYjYBjF8VDzTf/va3K/1XAsAJMZsAxq9R/w4NAADAaBFoAACAbAk0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLbqqt0AwFh2+KJFSXVd172VvObOJd9Mqjvzx7+fVHfkzb6I//3hpFqohNTjLCLi1Wv7kmv/+5L7kurO+vGq5DVb7pycVDfx8Z8lrwnjnTM0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALJVV+0GAEbb0WUfTq69+96vJNWdXjc1ec0jRVrdz8/9i6S63n1HY2bakjBA6rH2la9vTF7z/ZPS/6/M0cS6Z5bcm7zmC4uPJNX9ye+cm7wmjHfO0AAAANkSaAAAgGwJNAAAQLZGHGiefPLJuPTSS6OlpSVKpVI89NBDA54viiLWrVsXLS0tMXXq1Fi+fHns3LmzUv0CwADmEkBtG3Gg2b9/f5x11lmxceOxv8B36623xu233x4bN26M7du3R1NTU1x88cWxb9++E24WAN7NXAKobSO+NEh7e3u0t7cf87miKOKOO+6Im2++OVasWBEREffdd180NjbGAw88EFdfffWgmr6+vujr6+u/39vbO9KWAKhhlZ5LEWYTQE4q+h2a3bt3R1dXV7S1tfU/Vi6XY9myZbF169Zj1mzYsCEaGhr6b7Nnz65kSwDUsJS5FGE2AeSkooGmq6srIiIaGxsHPN7Y2Nj/3LutXbs2enp6+m+dnZ2VbAmAGpYylyLMJoCcjMov1iyVSgPuF0Ux6LF3lMvlKJfLo9EGAETEyOZShNkEkJOKnqFpamqKiBj0rld3d/egd8cAYLSZSwDjX0UDTWtrazQ1NcWmTZv6Hzt48GBs2bIlli5dWsmlAGBY5hLA+Dfij5y98cYb8eKLL/bf3717dzz77LMxY8aMmDNnTqxZsybWr18f8+bNi3nz5sX69etj2rRpccUVV1S0cQCIMJcAat2IA83TTz8dF154Yf/9jo6OiIhYtWpVfOMb34gbb7wxDhw4ENdee228/vrrcc4558T3v//9qK+vr1zXAPCPzCWA2lYqiqKodhO/qbe3NxoaGmJ5fCzqSpOq3Q4wRhxqW5xce/Pd9ybX/t7UI0l1R4qjyWu+fPjNpLpfHZ2cVPfGvqNx0cJfRk9PT0yfPj3p7xjvamk2ncixduNdf5FU93tT0/b5iIijkX6svXToUFJdz9H0C0Z8uJzW7//22WuT15z6+HPJtUffeiu5Fk7E4eJQPBEPH9dsquh3aAAAAE4mgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbNVVuwEgPxOnT0+u3X/BGUl1N95xf/Kay6ccSq6txvs+976+JKnue3eel1R35OBbEXFzUi2jqxrH2r/9Tw8kr3nh1DcSK6vz/uo3Xl+aVPfDu9KO0YiIv1n3laS6Tf/lq8lrfugvVyfX/s9/+uPkWjhZnKEBAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkK26ajcA5Of/u/9/Sq792dlfrWAn49P/eep/T6p7pP78pLojfaWkOkbfL0/gWNt+9p0V7GR8+uKs7Ul13/utpclrfvrltqS6+37nB8lrTv/Q3uRayIEzNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtgQaAAAgWwINAACQLYEGAADIlkADAABkq67aDQDVc/iiRUl1//eHv5K85sTSKcm1qT695/zk2q2bFiTVvfCZu5PXfOJA2ntNjdsPJNUdPvxW/CKpkuOVeqx9659uTF5zQkxOrk316b/7vaS6p3/wweQ1n/tM+jZ6/MCUpLpZT6cdaxERL75+RlLdpPWPJ685oZRcCllwhgYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtuqq3QBwYo4u+3By7d33fiWp7vS6qclrHimOJtVd+rf/InnNWHEgufR9/7xIqpv3F3+YvOb8jZ1JdRM6n0mrKw4l1dWio+f9bhytmzLiuq98fWPSeu+flD6mj0basfYvf3FZ8poTP7k/qe6fJB5nEREf+ovVybXz7zy5x1pExPueSqs79B+OJK/5X3/368m1//rCP0qqm/j4z5LXhJFyhgYAAMiWQAMAAGRLoAEAALI14kDz5JNPxqWXXhotLS1RKpXioYceGvD8VVddFaVSacDt3HPPrVS/ADCAuQRQ20YcaPbv3x9nnXVWbNz43l9wvOSSS+LVV1/tvz366KMn1CQAvBdzCaC2jfjyKe3t7dHe3j7ka8rlcjQ1NR3X39fX1xd9fX3993t7e0faEgA1rNJzKcJsAsjJqHyH5oknnohZs2bF/Pnz43Of+1x0d3e/52s3bNgQDQ0N/bfZs2ePRksA1LCRzKUIswkgJxUPNO3t7fHNb34zNm/eHLfddlts3749LrroogHvdP2mtWvXRk9PT/+tszPtmvAAcCwjnUsRZhNATir+izVXrlzZ/+cFCxbE4sWLY+7cufHd7343VqxYMej15XI5yuVypdsAgIgY+VyKMJsAcjLql21ubm6OuXPnxq5du0Z7KQAYlrkEML6MeqDZu3dvdHZ2RnNz82gvBQDDMpcAxpcRf+TsjTfeiBdffLH//u7du+PZZ5+NGTNmxIwZM2LdunXxiU98Ipqbm+Pll1+Om266KWbOnBmXXXZZRRsHgAhzCaDWjTjQPP3003HhhRf23+/o6IiIiFWrVsXdd98dzz33XNx///3xq1/9Kpqbm+PCCy+MBx98MOrr6yvXNQD8I3MJoLaNONAsX748iqJ4z+cfe+yxE2oIalVp0ZlJdXv/+M3kNedPOiWpbkffweQ1N+//YFJd9wNzk9f87dd/nFzb8Jfb0uqSV4w4fAK1tehkzqW9q9+KidPee633Mn/S5KT1drz3hdiGtfmNDyXV7f12+iWqU4+11OMswrF2PBonpl/gYu+atBkz6/HkJWHERv07NAAAAKNFoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbAk0AABAtgQaAAAgW3XVbgDGkwnTpiXXFv/xV0l12z/w35LX3H3ozaS669fekLzm+57ak1Q365Tu5DWPJFfCQD/88Ldjev3I3wvcffhg0nodN/1xUl2EY43K+Gjz3yXVvVzZNmBIztAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkK26ajcA48mBZWcm1/7wA/+5gp0cn1Wf70iqq39oW/Kah5MrIV+f/fy/TapzrAEMzxkaAAAgWwINAACQLYEGAADIlkADAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANmqq3YDMJ4s+tKO5NqJpbT3Fz695/zkNac+9NPkWqhFqzsvjEmnTB5xnWONEzWpNDG59lCRvu7E0gkUw0niDA0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkC2BBgAAyJZAAwAAZEugAQAAsiXQAAAA2RJoAACAbNVVuwEYa3515ZLk2n8367bk2iPFlKS6Hz+2MHnNubE1uRZq0TM/PCMmThn5sTrHscYJOlQcSa49GkeTa7/3/IeS6ubFz5LXhJFyhgYAAMiWQAMAAGRrRIFmw4YNcfbZZ0d9fX3MmjUrPv7xj8cLL7ww4DVFUcS6deuipaUlpk6dGsuXL4+dO3dWtGkAeIfZBFDbRhRotmzZEtddd11s27YtNm3aFIcPH462trbYv39//2tuvfXWuP3222Pjxo2xffv2aGpqiosvvjj27dtX8eYBwGwCqG0juijA9773vQH377333pg1a1bs2LEjLrjggiiKIu644464+eabY8WKFRERcd9990VjY2M88MADcfXVV1eucwAIswmg1p3Qd2h6enoiImLGjBkREbF79+7o6uqKtra2/teUy+VYtmxZbN167Cu89PX1RW9v74AbAKQymwBqS3KgKYoiOjo64rzzzosFCxZERERXV1dERDQ2Ng54bWNjY/9z77Zhw4ZoaGjov82ePTu1JQBqnNkEUHuSA83q1avj5z//eXzrW98a9FypVBpwvyiKQY+9Y+3atdHT09N/6+zsTG0JgBpnNgHUnqRfrHn99dfHI488Ek8++WScdtpp/Y83NTVFxNvvhjU3N/c/3t3dPeidsXeUy+Uol8spbQBAP7MJoDaN6AxNURSxevXq+M53vhObN2+O1tbWAc+3trZGU1NTbNq0qf+xgwcPxpYtW2Lp0qWV6RgAfoPZBFDbRnSG5rrrrosHHnggHn744aivr+//7HFDQ0NMnTo1SqVSrFmzJtavXx/z5s2LefPmxfr162PatGlxxRVXjMp/AAC1zWwCqG0jCjR33313REQsX758wOP33ntvXHXVVRERceONN8aBAwfi2muvjddffz3OOeec+P73vx/19fUVaRgAfpPZBFDbRhRoiqIY9jWlUinWrVsX69atS+0JAI6b2QRQ25IuCgDj2eFp6bXTJ0xJrt3Wl1Z3+n2vJK95OLmSsWjCtLSdd0JxMOLNCjczTrV++9WomzDyiwU41saX1GMtIuIXf74gsXJH8pq//1J7cu0Zn9+dVHckeUUYuRP6xZoAAADVJNAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLYEGgAAIFsCDQAAkK26ajcAvG3vkd9Kqjv80suVbYSqmzBtWlLdC3+2MKnu6IG3Iv4kqbTmHN69J6I0qdptUCHJx9qX0461iIhffGxjUt1fv9mQvOYrd74/ubb+9W3JtXCyOEMDAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLbqqt0A8LY1f/OppLp5saPCnVAJR5d9OLn2H/74zaS6XYvuTqrr3Xc0Zv5JUilU3Ykca90dB5Lqnl+8MXnN33tuZVLdKZe8lLxmfWxLroUcOEMDAABkS6ABAACyJdAAAADZEmgAAIBsCTQAAEC2BBoAACBbAg0AAJAtgQYAAMiWQAMAAGRLoAEAALIl0AAAANkSaAAAgGwJNAAAQLbqqt3AuxVFERERh+NQRFHlZqhJRw6+lVzbu+9ocu3RA2nrHi4OJa/J6Dl6OH0/OvJmX1Jd6v6374236975+ctgZtPYldOxFhFxeH/amn7WU2sOx9v7/PHMplIxxibYL3/5y5g9e3a12wCoOZ2dnXHaaadVu40xyWwCqI7jmU1jLtAcPXo0Xnnllaivr49SqTTo+d7e3pg9e3Z0dnbG9OnTq9Dh2GcbDc82Gp5tNLTxtH2Kooh9+/ZFS0tLTJjgk8jHMtRsGk/7wmixjYZnGw3PNhreeNpGI5lNY+4jZxMmTDiudwinT5+e/T/UaLONhmcbDc82Gtp42T4NDQ3VbmFMO57ZNF72hdFkGw3PNhqebTS88bKNjnc2eSsOAADIlkADAABkK7tAUy6X45ZbbolyuVztVsYs22h4ttHwbKOh2T68w74wPNtoeLbR8Gyj4dXqNhpzFwUAAAA4XtmdoQEAAHiHQAMAAGRLoAEAALIl0AAAANkSaAAAgGxlFWjuuuuuaG1tjSlTpsSiRYviqaeeqnZLY8a6deuiVCoNuDU1NVW7rap68skn49JLL42WlpYolUrx0EMPDXi+KIpYt25dtLS0xNSpU2P58uWxc+fO6jRbJcNto6uuumrQfnXuuedWp9kq2bBhQ5x99tlRX18fs2bNio9//OPxwgsvDHiNfam2mU3vzWwazGwantk0NHNpsGwCzYMPPhhr1qyJm2++OZ555pk4//zzo729Pfbs2VPt1saMM888M1599dX+23PPPVftlqpq//79cdZZZ8XGjRuP+fytt94at99+e2zcuDG2b98eTU1NcfHFF8e+fftOcqfVM9w2ioi45JJLBuxXjz766EnssPq2bNkS1113XWzbti02bdoUhw8fjra2tti/f3//a+xLtctsGp7ZNJDZNDyzaWjm0jEUmfjoRz9aXHPNNQMeO+OMM4ovfOELVepobLnllluKs846q9ptjFkRUfzVX/1V//2jR48WTU1NxZe//OX+x956662ioaGh+OpXv1qFDqvv3duoKIpi1apVxcc+9rGq9DNWdXd3FxFRbNmypSgK+1KtM5uGZjYNzWwantk0PHOpKLI4Q3Pw4MHYsWNHtLW1DXi8ra0ttm7dWqWuxp5du3ZFS0tLtLa2xuWXXx4vvfRStVsas3bv3h1dXV0D9qlyuRzLli2zT73LE088EbNmzYr58+fH5z73ueju7q52S1XV09MTEREzZsyICPtSLTObjo/ZdPz8PDl+ZtOvmUuZfOTstddeiyNHjkRjY+OAxxsbG6Orq6tKXY0t55xzTtx///3x2GOPxde+9rXo6uqKpUuXxt69e6vd2pj0zn5jnxpae3t7fPOb34zNmzfHbbfdFtu3b4+LLroo+vr6qt1aVRRFER0dHXHeeefFggULIsK+VMvMpuGZTSPj58nxMZt+zVx6W121GxiJUqk04H5RFIMeq1Xt7e39f164cGEsWbIkTj/9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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(m,n, figsize=(10,10))\n", + "for i in range(m):\n", + " for j in range(n):\n", + " axes[i][j].imshow(mnist_crop[sample_index][0][0][i*n+j][0])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "wnestml", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/conex/helpers/transforms/masks.py b/conex/helpers/transforms/masks.py index 883be5b..21c8b10 100644 --- a/conex/helpers/transforms/masks.py +++ b/conex/helpers/transforms/masks.py @@ -15,12 +15,14 @@ class GridEraseMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. + gap (tuple(int)): left, bottom, up, right gaps for the cell. """ - def __init__(self, m, n, random=False): + def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): self.m = m self.n = n self.random = random + self.gap = gap def __call__(self, img): _, h, w = img.shape @@ -33,7 +35,20 @@ def __call__(self, img): ) for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij - result.append(TF.erase(img, i * h_grid, j * w_grid, h_grid, w_grid, v=0)) + h_cor = i * h_grid + self.gap[0] + dh = h_cor if h_cor < 0 else 0 + w_cor = j * w_grid + self.gap[2] + dw = w_cor if w_cor < 0 else 0 + result.append( + TF.erase( + img, + max(h_cor, 0), + max(w_cor, 0), + h_grid - self.gap[1] - self.gap[2] + dh, + w_grid - self.gap[3] - self.gap[0] + dw, + v=0, + ) + ) location[index, ij[0], ij[1]] = False result = torch.stack(result) @@ -54,12 +69,14 @@ class GridKeepMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. + gap (tuple(int)): left, bottom, up, right gaps for the cell. """ - def __init__(self, m, n, random=False): + def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): self.m = m self.n = n self.random = random + self.gap = gap def __call__(self, img): _, h, w = img.shape @@ -73,8 +90,22 @@ def __call__(self, img): for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij bg = torch.zeros_like(img) - bg[:, i * h_grid : (i + 1) * h_grid, j * w_grid : (j + 1) * w_grid] = img[ - :, i * h_grid : (i + 1) * h_grid, j * w_grid : (j + 1) * w_grid + bg[ + :, + max(i * h_grid + self.gap[0], 0) : min( + (i + 1) * h_grid - self.gap[3], img.size(1) + ), + max(j * w_grid + self.gap[2], 0) : min( + (j + 1) * w_grid - self.gap[1], img.size(2) + ), + ] = img[ + :, + max(i * h_grid + self.gap[0], 0) : min( + (i + 1) * h_grid - self.gap[3], img.size(1) + ), + max(j * w_grid + self.gap[2], 0) : min( + (j + 1) * w_grid - self.gap[1], img.size(2) + ), ] result.append(bg) location[index, ij[0], ij[1]] = True @@ -97,12 +128,14 @@ class GridCropMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. + gap (tuple(int)): left, bottom, up, right gaps for the cell. """ - def __init__(self, m, n, random=False): + def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): self.m = m self.n = n self.random = random + self.gap = gap def __call__(self, img): _, h, w = img.shape @@ -115,7 +148,15 @@ def __call__(self, img): ) for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij - result.append(TF.crop(img, i * h_grid, j * w_grid, h_grid, w_grid)) + result.append( + TF.crop( + img, + i * h_grid + self.gap[0], + j * w_grid + self.gap[2], + h_grid - self.gap[1] - self.gap[2], + w_grid - self.gap[3] - self.gap[0], + ) + ) location[index, ij[0], ij[1]] = True result = torch.stack(result) From 635d45f779b062dd72050d86b49d5b4b304a9cdb Mon Sep 17 00:00:00 2001 From: Saeed Date: Sat, 25 May 2024 02:45:30 +0330 Subject: [PATCH 6/8] fix: None connection for synapse --- conex/behaviors/synapses/specs.py | 45 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/conex/behaviors/synapses/specs.py b/conex/behaviors/synapses/specs.py index fa3b3bc..81bed74 100644 --- a/conex/behaviors/synapses/specs.py +++ b/conex/behaviors/synapses/specs.py @@ -16,27 +16,30 @@ class SynapseInit(Behavior): """ def initialize(self, synapse): - synapse.src_shape = (1, 1, synapse.src.size) - if hasattr(synapse.src, "depth"): - synapse.src_shape = ( - synapse.src.depth, - synapse.src.height, - synapse.src.width, - ) - synapse.dst_shape = (1, 1, synapse.dst.size) - if hasattr(synapse.dst, "depth"): - synapse.dst_shape = ( - synapse.dst.depth, - synapse.dst.height, - synapse.dst.width, - ) - - synapse.src_delay = synapse.tensor( - mode="zeros", dim=(1,), dtype=torch.long - ).expand(synapse.src.size) - synapse.dst_delay = synapse.tensor( - mode="zeros", dim=(1,), dtype=torch.long - ).expand(synapse.dst.size) + if hasattr(synapse, "src"): + synapse.src_shape = (1, 1, synapse.src.size) + if hasattr(synapse.src, "depth"): + synapse.src_shape = ( + synapse.src.depth, + synapse.src.height, + synapse.src.width, + ) + + synapse.src_delay = synapse.tensor( + mode="zeros", dim=(1,), dtype=torch.long + ).expand(synapse.src.size) + + if hasattr(synapse, "dst"): + synapse.dst_shape = (1, 1, synapse.dst.size) + if hasattr(synapse.dst, "depth"): + synapse.dst_shape = ( + synapse.dst.depth, + synapse.dst.height, + synapse.dst.width, + ) + synapse.dst_delay = synapse.tensor( + mode="zeros", dim=(1,), dtype=torch.long + ).expand(synapse.dst.size) class DelayInitializer(Behavior): From 67bf34c0eb794a70f0486edbb3900421c888d02a Mon Sep 17 00:00:00 2001 From: Saeed Date: Sat, 25 May 2024 04:32:54 +0330 Subject: [PATCH 7/8] style: improvement --- conex/behaviors/synapses/specs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conex/behaviors/synapses/specs.py b/conex/behaviors/synapses/specs.py index 81bed74..1a46aba 100644 --- a/conex/behaviors/synapses/specs.py +++ b/conex/behaviors/synapses/specs.py @@ -16,7 +16,7 @@ class SynapseInit(Behavior): """ def initialize(self, synapse): - if hasattr(synapse, "src"): + if synapse.src is not None: synapse.src_shape = (1, 1, synapse.src.size) if hasattr(synapse.src, "depth"): synapse.src_shape = ( @@ -29,7 +29,7 @@ def initialize(self, synapse): mode="zeros", dim=(1,), dtype=torch.long ).expand(synapse.src.size) - if hasattr(synapse, "dst"): + if synapse.dst is not None: synapse.dst_shape = (1, 1, synapse.dst.size) if hasattr(synapse.dst, "depth"): synapse.dst_shape = ( From a4a517acaeea46a61c40284e3cf8b9dbc19edba6 Mon Sep 17 00:00:00 2001 From: Saeed Date: Fri, 27 Sep 2024 15:54:36 +0330 Subject: [PATCH 8/8] style: gap order --- conex/helpers/transforms/masks.py | 41 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/conex/helpers/transforms/masks.py b/conex/helpers/transforms/masks.py index 21c8b10..d146930 100644 --- a/conex/helpers/transforms/masks.py +++ b/conex/helpers/transforms/masks.py @@ -15,7 +15,7 @@ class GridEraseMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. - gap (tuple(int)): left, bottom, up, right gaps for the cell. + gap (tuple(int)): left, right, up, bottom gaps for the cell. """ def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): @@ -28,6 +28,7 @@ def __call__(self, img): _, h, w = img.shape w_grid = math.ceil(w / self.n) h_grid = math.ceil(h / self.m) + gap_left, gap_right, gap_top, gap_bottom = self.gap result = [] location = torch.ones( @@ -35,17 +36,17 @@ def __call__(self, img): ) for index, ij in enumerate(product(range(self.m), range(self.n))): i, j = ij - h_cor = i * h_grid + self.gap[0] + h_cor = i * h_grid + gap_left dh = h_cor if h_cor < 0 else 0 - w_cor = j * w_grid + self.gap[2] + w_cor = j * w_grid + gap_top dw = w_cor if w_cor < 0 else 0 result.append( TF.erase( img, max(h_cor, 0), max(w_cor, 0), - h_grid - self.gap[1] - self.gap[2] + dh, - w_grid - self.gap[3] - self.gap[0] + dw, + h_grid - gap_bottom - gap_top + dh, + w_grid - gap_right - gap_left + dw, v=0, ) ) @@ -69,7 +70,7 @@ class GridKeepMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. - gap (tuple(int)): left, bottom, up, right gaps for the cell. + gap (tuple(int)): left, right, up, bottom gaps for the cell. """ def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): @@ -82,6 +83,7 @@ def __call__(self, img): _, h, w = img.shape w_grid = math.ceil(w / self.n) h_grid = math.ceil(h / self.m) + gap_left, gap_right, gap_top, gap_bottom = self.gap result = [] location = torch.zeros( @@ -92,19 +94,19 @@ def __call__(self, img): bg = torch.zeros_like(img) bg[ :, - max(i * h_grid + self.gap[0], 0) : min( - (i + 1) * h_grid - self.gap[3], img.size(1) + max(i * h_grid + gap_left, 0) : min( + (i + 1) * h_grid - gap_right, img.size(1) ), - max(j * w_grid + self.gap[2], 0) : min( - (j + 1) * w_grid - self.gap[1], img.size(2) + max(j * w_grid + gap_top, 0) : min( + (j + 1) * w_grid - gap_bottom, img.size(2) ), ] = img[ :, - max(i * h_grid + self.gap[0], 0) : min( - (i + 1) * h_grid - self.gap[3], img.size(1) + max(i * h_grid + gap_left, 0) : min( + (i + 1) * h_grid - gap_right, img.size(1) ), - max(j * w_grid + self.gap[2], 0) : min( - (j + 1) * w_grid - self.gap[1], img.size(2) + max(j * w_grid + gap_top, 0) : min( + (j + 1) * w_grid - gap_bottom, img.size(2) ), ] result.append(bg) @@ -128,7 +130,7 @@ class GridCropMask: m (int): The grid row size. n (int): The grid column size. random (bool): If true, shuffles the order of the masks. - gap (tuple(int)): left, bottom, up, right gaps for the cell. + gap (tuple(int)): left, right, up, bottom gaps for the cell. """ def __init__(self, m, n, random=False, gap=(0, 0, 0, 0)): @@ -141,6 +143,7 @@ def __call__(self, img): _, h, w = img.shape w_grid = math.ceil(w / self.n) h_grid = math.ceil(h / self.m) + gap_left, gap_right, gap_top, gap_bottom = self.gap result = [] location = torch.zeros( @@ -151,10 +154,10 @@ def __call__(self, img): result.append( TF.crop( img, - i * h_grid + self.gap[0], - j * w_grid + self.gap[2], - h_grid - self.gap[1] - self.gap[2], - w_grid - self.gap[3] - self.gap[0], + i * h_grid + gap_left, + j * w_grid + gap_top, + h_grid - gap_bottom - gap_top, + w_grid - gap_right - gap_left, ) ) location[index, ij[0], ij[1]] = True