#!/usr/bin/env python # coding: utf-8 # # SYDE556/750 Assignment 3: Connecting Neurons # # - Due Date: March 12th # - Total marks: 10 (10% of final grade) # - Late penalty: 1 mark per day # # - It is recommended that you use a language with a matrix library and graphing capabilities. Two main suggestions are Python and MATLAB. # - You can (and are encouraged to) use code from your previous assignments. # - If you are having difficulties re-using code from your previous assignments, you can optionally use the following functions implemented using nengo (to install nengo, run `pip install nengo`): # In[68]: import nengo import numpy as np def compute_lif_decoder(n_neurons, dimensions, encoders, max_rates, intercepts, tau_ref, tau_rc, radius, x_pts, function): """ Parameters: n_neurons: number of neurons (integer) dimensions: number of dimensions (integer) encoders: the encoders for the neurons (array of shape (n_neurons, dimensions)) max_rates: the maximum firing rate for each neuron (array of shape (n_neurons)) intercepts: the x-intercept for each neuron (array of shape (n_neurons)) tau_ref: refractory period for the neurons (float) tau_rc: membrane time constant for the neurons (float) radius: the range of values the neurons are optimized over (float) x_pts: the x-values to use to solve for the decoders (array of shape (S, dimensions)) function: the function to approximate Returns: A (the tuning curve matrix) dec (the decoders) """ model = nengo.Network() with model: ens = nengo.Ensemble(n_neurons=n_neurons, dimensions=dimensions, encoders=encoders, max_rates=max_rates, intercepts=[x/radius for x in intercepts], neuron_type=nengo.LIF(tau_rc=tau_rc, tau_ref=tau_ref), radius=radius) sim = nengo.Simulator(model) x_pts = np.array(x_pts) if len(x_pts.shape) == 1: x_pts.shape = x_pts.shape[0], 1 _, A = nengo.utils.ensemble.tuning_curves(ens, sim, inputs=x_pts) target = [function(xx) for xx in x_pts] solver = nengo.solvers.LstsqL2() dec, info = solver(A, target) return A, dec def generate_signal(T, max_freq, rms, dt): """ Parameters: T: the period of time to generate a random signal for max_freq: the highest frequency in the signal rms: the RMS power of the signal dt: the time step (usually 0.001) Returns: signal (an array of length T/dt containing the random signal) """ signal = nengo.processes.WhiteSignal(period=T, high=max_freq, rms=rms) return signal.run(T, dt=dt) def generate_spikes(n_neurons, dimensions, encoders, max_rates, intercepts, tau_ref, tau_rc, radius, x, dt): """ Parameters: n_neurons: number of neurons (integer) dimensions: number of dimensions (integer) encoders: the encoders for the neurons (array of shape (n_neurons, dimensions)) max_rates: the maximum firing rate for each neuron (array of shape (n_neurons)) intercepts: the x-intercept for each neuron (array of shape (n_neurons)) tau_ref: refractory period for the neurons (float) tau_rc: membrane time constant for the neurons (float) radius: the range of values the neurons are optimized over (float) x: the input signal to feed into the neurons (array of shape (T, dimensions)) dt: the time step of the simulation (usually 0.001) Returns: spikes (a (timesteps, n_neurons) array of the spiking outputs) """ model = nengo.Network() with model: stim = nengo.Node(lambda t: x[int(t/dt)-1]) ens = nengo.Ensemble(n_neurons=n_neurons, dimensions=dimensions, encoders=encoders, max_rates=max_rates, intercepts=[x/radius for x in intercepts], neuron_type=nengo.LIF(tau_rc=tau_rc, tau_ref=tau_ref), radius=radius) nengo.Connection(stim, ens, synapse=None) p = nengo.Probe(ens.neurons) sim = nengo.Simulator(model, dt=dt) T = len(x)*dt sim.run(T) return sim.data[p] # ## 1) Decoding from a population # # As you did in previous assignments, make a population of 20 LIF neurons representing a 1-dimensional value, and compute a decoder for them. For parameters, $\tau_{ref}$=0.002s, $\tau_{RC}$=0.02s, the maximum firing rates are chosen randomly from a uniform distribution between 100 and 200Hz (at the max radius), and the x-intercepts are chosen randomly from a uniform distribution between -2 and 2. Remember that the $\alpha$ and $J^{bias}$ terms are computed based on these x-intercepts and maximum firing rates. # # It is generally easiest to compute decoders using the original method from Assignment 1, where we use the rate-mode approximation for the neurons to generate the $A$ matrix, then find $\Gamma=A^T A + \sigma^2 I$. You can use this approach to find decoders, and these decoders should work even when you simulate the neurons in terms of spikes (in question 2 on). The only difference will be that they will need to be scaled by ``dt``, your simulation time step. # # Use this same method for computing decoders for this whole assignment. # #
    #
  1. [0.5 marks] Plot the tuning curves (firing rate of each neuron for different $x$ values between -2 and 2)
  2. #
  3. [0.5 marks] Compute the decoders and plot $(x-\hat{x})$. When computing decoders, take into account noise ($\sigma$=0.1 times 200Hz). When computing $\hat{x}$, add random gaussian noise with $\sigma$=0.1 times 200Hz to the activity. Report the Root Mean-Squared Error (RMSE).
  4. #
# ## 2) Decoding from two spiking neurons # # Choose a neuron from part 1 that has a firing rate of somewhere between 20-50Hz for $x$=0. Using that neuron's $\alpha$ and $J^{bias}$ value, construct two neurons: both with the same $\alpha$ and $J^{bias}$, but one with $e$=+1 and the other with $e$=-1. With the function from the last assignment, generate a random input $x(t)$ that is 1 second long, with rms=1, dt=0.001, and an upper limit of 5Hz. Feed that signal into the two neurons and generate spikes. Decode the spikes back into $\hat{x}(t)$ using a post-synaptic current filter $h(t)$ with a time constant of $\tau$=0.005. # #
    #
  1. [0.5 marks] Plot the post-synaptic current $h(t)=e^{-t/\tau}/ \int e^{-t'/\tau} dt'$
  2. #
  3. [0.5 marks] Plot the original signal $x(t)$, the spikes, and the decoded $\hat{x}(t)$ all on the same graph
  4. #
  5. [0.5 marks] Compute the RMSE of the decoding
  6. #
# ## 3) Decoding from many neurons # # Repeat question 2, but with more neurons. Instead of picking particular neurons, randomly generate them with x-intercepts uniformly distributed between -2 and 2 and with maximum firing rates between 100 and 200 Hz. Randomly choose encoder values to be either -1 or +1. # #
    #
  1. [2 marks] Plot Root Mean-Squared Error as the number of neurons increases, on a log plot. Try 8 neurons, 16 neurons, 32, 64, 128, up to 256. For the RMSE for a particular number of neurons, average over at least 5 randomly generated groups of neurons. For each group of neurons, randomly generate the signal $x(t)$. Use the same parameters as in question 2. Note: the RMSE should go down as the number of neurons increases
  2. #
# ## 4) Connecting two groups of neurons # # For this question, use two groups of neurons with intercepts between [-1, 1] (i.e. radius = 1) to compute $y = 2x+1$. The first group of neurons will represent $x$ and the second group will represent $y$. # # Start by computing decoders. You will need two decoders: one to decode $f(x)=2x+1$ from the first population, and one to decode $f(y)=y$ (the standard representational decoder) from the second population. Remember that $\Upsilon$ can change depending on what function you want to decode. # # Use the same neuron parameters as for previous questions (other than the radius), and use 200 randomly generated neurons in each population. # #
    #
  1. [1 mark] Show the behaviour of the system with an input of $x(t)=t-1$ for 1 second (a linear ramp from -1 to 0). Plot the ideal $x(t)$ and $y(t)$ values, along with $\hat{y}(t)$.
  2. # #

    #

  3. [0.5 marks] Repeat part (a) with an input that is ten randomly chosen values between -1 and 0, each one held for 0.1 seconds (a randomly varying step input)
  4. # #
  5. [0.5 marks] Repeat part (a) with an input that is $x(t)=0.2sin(6\pi t)$. Briefly discuss the results for this question.
  6. #
# ## 5) Connecting three groups of neurons # # For this question, use three groups of neurons with intercepts from [-1, 1] to compute $z = 2y+0.5x$. Follow the same steps as question 4, but take the decoded outputs from the first two groups of neurons ($f(y)=2y$ and $f(x)=0.5x$), add them together, and feed that into the third group of neurons. # #
    #
  1. [1 mark] Plot $x(t)$, $y(t)$, the ideal $z(t)$, and the decoded $\hat{z}(t)$ for an input of $x(t)=cos(3\pi t)$ and $y(t)=0.5 sin (2 \pi t)$ (over 1.0 seconds)
  2. # #
  3. [0.5 marks] Plot $x(t)$, $y(t)$, the ideal $z(t)$, and the decoded $\hat{z}(t)$ for a random input over 1 second. For $x(t)$ use a random signal with a limit of 8 Hz and `rms`=1. For $y(t)$ use a random signal with a limit of 5 Hz and `rms`=0.5.
  4. #
# # ## 6) Computing with vectors # # Do the same thing as questions 4 and 5, but with 2-dimensional vectors instead of scalars. Everything else is the same. For your encoders $e$, randomly generate them over the unit circle. # # The function to compute is $w = x-3y+2z-2q$. This requires five groups of neurons: $x$, $y$, $z$, $q$, and $w$. Each of them represents a 2-dimensional value. The outputs from $x$, $y$, $z$, and $q$ all feed into $w$. # #
    #
  1. [1 mark] Plot the decoded output $\hat{w}(t)$ and the ideal $w$ for $x=[0.5,1], y=[0.1,0.3], z=[0.2,0.1], q = [0.4,-0.2]$. (Note that these are all constants so they don't change over time, but still plot it for 1.0 seconds on one or more 2D graphs)
  2. #
  3. [0.5 marks] Produce the same plot for $x=[0.5,1], y=[sin(4\pi t),0.3], z=[0.2,0.1], q = [sin(4\pi t),-0.2]$.
  4. #
  5. [0.5 marks] Describe your results and discuss why and how they stray from the expected answer.
# In[ ]: