Post ensemble firing before it theoretically should?

I am trying to implement unsupervised learning in a model using memristors as synapses by adapting the BCM learning rule. I have already successfully done the same for supervised learning by adapting PES.

I think I have the needed understanding of the theory down but I wonder if my model may not be working because of some strange effects I can see in Nengo; to be precise, my post population seems to be firing before it should.

My ensembles are set up as such:

nengo.Connection( inp, pre )
nengo.Connection( pre.neurons, learn[ :pre_nrn ], synapse=0.005 )
nengo.Connection( post.neurons, learn[ pre_nrn: ], synapse=0.005 )
nengo.Connection( learn, post.neurons, synapse=None )

with learn being a Node() object implementing my weight calculation. As you can see, I have a synapse of tau=0.005 on both the connection from pre and post (even though it’s None in the Nengo implementation, I have to add a delay here to avoid cycles) to learn.

The full code can be found here and here.

learn implements the following learning rule:

    def mBCM( self, t, x ):
        input_activities = x[ :self.input_size ]
        output_activities = x[ self.input_size: ]
        theta = self.theta_filter.filt( output_activities )
        alpha = self.learning_rate
        
        # function \phi( a, \theta ) that is the moving threshold
        update = alpha * output_activities * (output_activities - theta)
        
        if self.logging:
            self.history.append( np.sign( update ) )
            self.save_state()
        
        # squash spikes to False (0) or True (100/1000 ...) or everything is always adjusted
        spiked_pre = np.array( np.rint( input_activities ), dtype=bool )
        spiked_post = np.array( np.rint( output_activities ), dtype=bool )
        
        # we only need to update the weights for the neurons that spiked so we filter
        if spiked_pre.any() and spiked_post.any():
            for j in np.nditer( np.where( spiked_post ) ):
                for i in np.nditer( np.where( spiked_pre ) ):
                    self.weights[ j, i ] = self.memristors[ j, i ].pulse( update[ j ],
                                                                          value="conductance",
                                                                          method="same"
                                                                          )
        
        # calculate the output at this timestep
        return np.dot( self.weights, input_activities )

If I’m understanding things correctly, this should mean that I will see the first neural activities from pre in mBCM() at t=0.006 (i.e., after 5 timesteps) and the first ones from post around t=0.0012.
What I’m seeing instead is the following; at t=0.005 everything is as expected:


but at t=0.006 I instantly see that post is also firing:

The result of applying my rule is that the post neurons are always firing, could it be connected to this phenomenon I’m seeing?
Should I be specifying some initial weights like in the tutorial?

A synapse of 0.005 doesn’t introduce a 5 timestep delay, it means that you’re applying a Lowpass filter with a time constant of 0.005s. You can read more about the mathematics of filter time constants here https://en.wikipedia.org/wiki/Low-pass_filter. But the short answer is that information is transferred with a single timestep delay (like all filters in Nengo), with an exponential decay proportional to the time constant.

Ok, so it shouldn’t be that.

I see in the unsupervised learning tutorial that initial weights are specified. Is that an essential part to learning with BCM?

Yes it is probably important to have non-zero weights (although not strictly essential). The BCM rule is based on multiplying the pre and post activities to compute the weight update, so if your post activities are always zero then your weight update will always be zero. Now it is still possible to get non-zero output activities with zero weights (due to e.g. bias currents), which is why I say it’s not strictly essential. But the learning rule will probably perform better if there is a more interesting initial relationship between pre and post activities.