\n", "\n", "\\begin{equation}\n", "\\mathbf{W} = \\begin{bmatrix} w_{1, 1} & w_{2, 1} \\\\ w_{1, 2} & w_{2, 2} \\end{bmatrix}, \\mathbf{a}_{t} = \\begin{bmatrix} a_{1, t} \\\\ a_{2, t} \\end{bmatrix}\n", "\\end{equation}\n", "\n", "

\n",
"## Click here for text recap of video

\n",
"\n",
"In previous tutorials, we have looked at static models of LGN neurons based on the responses of retinal neurons. By static, I mean that we just looked at a single time point.\n",
"\n",
"Let's now introduce the concept of time. We will chop time up into little bins and look at the activity of neurons in each bin. That is, we will work in a **discrete** time framework. For example, if each bin is 1 second long, we will look at the firing rate of each neuron at intervals of a second.\n",
"\n",
"Instead of examining pre- and post- synaptic neurons, we will examine two neurons in one area that are connected. In our model, the activity of neuron 1 at one time bin depends on the activities of both neurons during the previous time bin multiplied by the respective weights from itself and neuron 2. It might seem weird for a neuron to have a weight to itself - this is abstracting away some biological details but basically conveys how much the neural activity depends on its history. (Throughout this course, we'll see lots of neuron models and how some model biological detail more faithfully while others abstract.)\n",
"\n",
"We will refer to the activity of neuron i during time bin j as $a_{i, j}$. The weight from neuron y to neuron x will be $w_{y, x}$. With this helpful notation, we can write an equation for the activity of neuron 1 at time bin t:\n",
"\n",
"\\begin{equation}\n",
"a_{1, t} = w_{1, 1}a_{1, t-1} + w_{2, 1}a_{2, t-1}\n",
"\\end{equation}\n",
"\n",
"And the symmetric model is true of neuron 2:\n",
"\\begin{equation}\n",
"a_{2, t} = w_{1, 2}a_{1, t-1} + w_{2, 2}a_{2, t-1}\n",
"\\end{equation}\n",
"\n",
"This is already a mess of subscript numbers - luckily we can use matrices and vectors once again and our model becomes: \n",
"\n",
"\\begin{equation}\n",
"\\mathbf{a}_{t} = \\mathbf{W}\\mathbf{a}_{t-1}\n",
"\\end{equation}\n",
"\n",
"where:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{W} = \\begin{bmatrix} w_{1, 1} & w_{2, 1} \\\\ w_{1, 2} & w_{2, 2} \\end{bmatrix}, \\mathbf{a}_{t} = \\begin{bmatrix} a_{1, t} \\\\ a_{2, t} \\end{bmatrix}\\end{equation}\n",
"\n",
"It turns out that this is a **discrete dynamical system**. Dynamical systems are concerned with how quantities evolve other time, in this case our neural firing rates. When we model the evolution of quantities over time using a discrete time framework, it is, unsurprisingly, a discrete dynamical system. We will see continuous dynamical systems (where we embrace the full continuity of time) tomorrow and later in the comp neuro course during W2D2: Linear Dynamics.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Coding Exercise 1: Implementing the circuit\n",
"\n",
"In this exercise, you will implement the function `circuit_implementation`. Given a weight matrix, initial activities at time 0, and a number of time bins to model, this function calculates the neural firing rates at each time bin.\n",
"\n",
"We will use initial firing rates of 1 for both neurons:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{a}_0 =\n",
"\\begin{bmatrix}\n",
"1 \\\\\n",
"1\n",
"\\end{bmatrix}\n",
"\\end{equation}\n",
"\n",
"and the weight matrix:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{W} =\n",
"\\begin{bmatrix}\n",
"1 & 0.2 \\\\\n",
"0.1 & 1\n",
"\\end{bmatrix}\n",
"\\end{equation}\n",
"\n",
"We will look at activity over 30 time steps. As before, we will allow our firing rates to be negative, despite this not being possible biologically.\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"def circuit_implementation(W, a0, T):\n",
" \"\"\" Simulate the responses of N neurons over time given their connections\n",
"\n",
" Args:\n",
" W (ndarray): weight matrix of synaptic connections, should be N x N\n",
" a0 (ndarray): initial condition or input vector, should be N,\n",
" T (scalar): number of time steps to run simulation for\n",
"\n",
" Returns:\n",
" a (ndarray): the neural responses over time, should be N x T\n",
"\n",
" \"\"\"\n",
"\n",
" # Compute the number of neurons\n",
" N = W.shape[0]\n",
"\n",
" # Initialize empty response array and initial condition\n",
" a = np.zeros((N, T))\n",
" a[:, 0] = a0\n",
"\n",
" #################################################\n",
" ## TODO for students ##\n",
" # Fill out function and remove\n",
" raise NotImplementedError(\"Student exercise: Complete circuit_implementation\")\n",
" #################################################\n",
"\n",
" # Loop over time steps and compute u(t+1)\n",
" for i_t in range(1, T):\n",
" a[:, i_t] = ...\n",
"\n",
" return a\n",
"\n",
"\n",
"# Define W, u0, T\n",
"W = np.array([[1, .2], [.1, 1]])\n",
"a0 = np.array([1, 1])\n",
"T = 30\n",
"\n",
"# Get neural activities\n",
"a = circuit_implementation(W, a0, T)\n",
"\n",
"# Visualize neural activities\n",
"plot_circuit_responses(a, W)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"execution": {}
},
"source": [
"[*Click for solution*](https://github.com/NeuromatchAcademy/precourse/tree/main//tutorials/W0D3_LinearAlgebra/solutions/W0D3_Tutorial3_Solution_9b2ef069.py)\n",
"\n",
"*Example output:*\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"The firing rates of both neurons are exploding to infinity over time. Let's now see what happens with a different weight matrix:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{W} =\n",
"\\begin{bmatrix}\n",
"0.2 & 0.1 \\\\\n",
"1 & 0.2\n",
"\\end{bmatrix}\n",
"\\end{equation}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute this cell to visualize activity over time\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute this cell to visualize activity over time\n",
"\n",
"# Define W, a0, T\n",
"W = np.array([[.2, .1], [1, .2]])\n",
"a0 = np.array([1, 1])\n",
"T = 30\n",
"\n",
"# Get neural activities\n",
"a = circuit_implementation(W, a0, T)\n",
"\n",
"# Visualize neural activities\n",
"plot_circuit_responses(a, W)"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"We can see that with this weight matrix, the firing rates are decaying towards zero. It turns out that we could have predicted this by looking at the eigenvalues of the weight matrices, as we'll see in the next section."
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"---\n",
"# Section 2: Understanding dynamics using eigenstuff\n",
"\n",
"As we'll see in this section, eigenvectors and eigenvalues are incredibly useful for understanding the evolution of the neural firing rates, and discrete dynamical systems in general.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Section 2.1: Initial firing rates along an eigenvector\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"**Please note this correction to the video: the equation at the bottom of slide 115 (at 2:31) should be: $a_i = W^ia_0$**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Video 2: Looking at activity along an eigenvector\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @title Video 2: Looking at activity along an eigenvector\n",
"from ipywidgets import widgets\n",
"from IPython.display import YouTubeVideo\n",
"from IPython.display import IFrame\n",
"from IPython.display import display\n",
"\n",
"\n",
"class PlayVideo(IFrame):\n",
" def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n",
" self.id = id\n",
" if source == 'Bilibili':\n",
" src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n",
" elif source == 'Osf':\n",
" src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n",
" super(PlayVideo, self).__init__(src, width, height, **kwargs)\n",
"\n",
"\n",
"def display_videos(video_ids, W=400, H=300, fs=1):\n",
" tab_contents = []\n",
" for i, video_id in enumerate(video_ids):\n",
" out = widgets.Output()\n",
" with out:\n",
" if video_ids[i][0] == 'Youtube':\n",
" video = YouTubeVideo(id=video_ids[i][1], width=W,\n",
" height=H, fs=fs, rel=0)\n",
" print(f'Video available at https://youtube.com/watch?v={video.id}')\n",
" else:\n",
" video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n",
" height=H, fs=fs, autoplay=False)\n",
" if video_ids[i][0] == 'Bilibili':\n",
" print(f'Video available at https://www.bilibili.com/video/{video.id}')\n",
" elif video_ids[i][0] == 'Osf':\n",
" print(f'Video available at https://osf.io/{video.id}') \n",
" display(video)\n",
" tab_contents.append(out)\n",
" return tab_contents\n",
"\n",
"\n",
"video_ids = [('Youtube', 'BnSvcWbYf8g'), ('Bilibili', 'BV1Tf4y1b7de')]\n",
"tab_contents = display_videos(video_ids, W=730, H=410)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
"for i in range(len(tab_contents)):\n",
" tabs.set_title(i, video_ids[i][0])\n",
"display(tabs)"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"This video covers what happens if our initial firing rates fall along one of the eigenvectors of the weight matrix.\n",
"\n",
"

\n",
"## Click here for text recap of video

\n",
"\n",
"**Rewriting our circuit equation**\n",
"\n",
"In our neural circuit, we are modeling the activities at a time step as:\n",
"$$\\mathbf{a}_{t} = \\mathbf{W}\\mathbf{a}_{t-1} $$\n",
"\n",
"Let's start at time step 1:\n",
"$$\\mathbf{a}_{1} = \\mathbf{W}\\mathbf{a}_{0} $$\n",
"\n",
"And move on to time step 2:\n",
"$$\\mathbf{a}_{2} = \\mathbf{W}\\mathbf{a}_{1} $$\n",
"\n",
"In the above equation, we can subsitute in $\\mathbf{a}_{1} = \\mathbf{W}\\mathbf{a}_{0}$:\n",
"$$\\mathbf{a}_{2} = \\mathbf{W}\\mathbf{W}\\mathbf{a}_{0} = \\mathbf{W}^2 \\mathbf{a}_{0}$$\n",
"\n",
"We can keep doing this with subsequent time steps:\n",
"$$\\mathbf{a}_{3} = \\mathbf{W}\\mathbf{a}_{2} = \\mathbf{W}\\mathbf{W}^2 \\mathbf{a}_{0} = \\mathbf{W}^3\\mathbf{a}_{0} $$\n",
"$$\\mathbf{a}_{4} = \\mathbf{W}\\mathbf{a}_{3} = \\mathbf{W}\\mathbf{W}^3 \\mathbf{a}_{0} = \\mathbf{W}^4\\mathbf{a}_{0} $$\n",
"\n",
"This means that we can write the activity at any point as:\n",
"$$\\mathbf{a}_{i} = \\mathbf{W}^i\\mathbf{a}_{0} $$\n",
"\n",
"**Initial firing rates along an eigenvector**\n",
"\n",
"Remember from the last tutorial, that an eigenvector of matrix $\\mathbf{W}$ is a vector that becomes a scalar multiple (eigenvalue) of itself when multiplied by that matrix:\n",
"\n",
"$$\\mathbf{W}\\mathbf{v} = \\lambda\\mathbf{v}$$\n",
"\n",
"Let's look at what happens if the initial firing rates in our neural circuit lie along that eigenvector, using the same substitution method as in the previous section:\n",
"$$\\mathbf{a}_{0} = \\mathbf{v} $$\n",
"$$\\mathbf{a}_{1} = \\mathbf{W}\\mathbf{a}_0 = \\mathbf{W}\\mathbf{v} = \\lambda\\mathbf{v} $$\n",
"$$\\mathbf{a}_{2} = \\mathbf{W}\\mathbf{a}_1 = \\mathbf{W}\\lambda\\mathbf{v} = \\lambda\\mathbf{W}\\mathbf{v} = \\lambda^2\\mathbf{v}$$\n",
"$$\\mathbf{a}_{3} = \\mathbf{W}\\mathbf{a}_2 = \\mathbf{W}\\lambda^2\\mathbf{v} = \\lambda^2\\mathbf{W}\\mathbf{v} = \\lambda^3\\mathbf{v}$$\n",
"$$...$$\n",
"$$\\mathbf{a}_i = \\lambda^i\\mathbf{v}$$\n",
"\n",
"The activities at any time step equal a scalar times the initial activities. In other words, if the initial activities lie along an eigenvector, the activities will only evolve along that eigenvector. "
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"### Interactive demo 2.1: Changing the eigenvalue\n",
"\n",
"Let's visualize what happens if the initial activities of the neurons lie along an eigenvector and think about how this depends on the eigenvalue.\n",
"\n",
"The interactive demo below is the same visualization you saw in Section 1, but now we also plot the eigenvectors $\\mathbf{v}_1$ and $\\mathbf{v}_2$.\n",
"\n",
"Questions:\n",
"1. What happens if the eigenvalue is large (2)?\n",
"2. What happens if you move the eigenvalue from 2 to towards 0? \n",
"3. What happens with negative eigenvalues?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute this cell to enable the widget. Please be patient for a few seconds after you change the slider\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute this cell to enable the widget. Please be patient for a few seconds after you change the slider\n",
"\n",
"@widgets.interact(eigenvalue = widgets.FloatSlider(value=0.5, min=-2, max=2, step=0.2))\n",
"def plot_system(eigenvalue):\n",
"\n",
" # Get weight matrix with specified eigenvalues\n",
" W = get_eigval_specified_matrix([eigenvalue, eigenvalue])\n",
"\n",
" # Get initial condition\n",
" u0 = np.array([1, 1])\n",
"\n",
" # Get neural activities\n",
" u = circuit_implementation(W, u0, 10)\n",
"\n",
" # Visualize neural activities\n",
" plot_circuit_responses(u, W, eigenstuff = True, xlim = [-15, 15], ylim = [-15, 15])"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"execution": {}
},
"source": [
"[*Click for solution*](https://github.com/NeuromatchAcademy/precourse/tree/main//tutorials/W0D3_LinearAlgebra/solutions/W0D3_Tutorial3_Solution_3c096483.py)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Section 2.2: Understanding general dynamics using eigenstuff"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Video 3: Understanding general dynamics using eigenstuff\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @title Video 3: Understanding general dynamics using eigenstuff\n",
"from ipywidgets import widgets\n",
"from IPython.display import YouTubeVideo\n",
"from IPython.display import IFrame\n",
"from IPython.display import display\n",
"\n",
"\n",
"class PlayVideo(IFrame):\n",
" def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n",
" self.id = id\n",
" if source == 'Bilibili':\n",
" src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n",
" elif source == 'Osf':\n",
" src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n",
" super(PlayVideo, self).__init__(src, width, height, **kwargs)\n",
"\n",
"\n",
"def display_videos(video_ids, W=400, H=300, fs=1):\n",
" tab_contents = []\n",
" for i, video_id in enumerate(video_ids):\n",
" out = widgets.Output()\n",
" with out:\n",
" if video_ids[i][0] == 'Youtube':\n",
" video = YouTubeVideo(id=video_ids[i][1], width=W,\n",
" height=H, fs=fs, rel=0)\n",
" print(f'Video available at https://youtube.com/watch?v={video.id}')\n",
" else:\n",
" video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n",
" height=H, fs=fs, autoplay=False)\n",
" if video_ids[i][0] == 'Bilibili':\n",
" print(f'Video available at https://www.bilibili.com/video/{video.id}')\n",
" elif video_ids[i][0] == 'Osf':\n",
" print(f'Video available at https://osf.io/{video.id}') \n",
" display(video)\n",
" tab_contents.append(out)\n",
" return tab_contents\n",
"\n",
"\n",
"video_ids = [('Youtube', '66DExLQPzPI'), ('Bilibili', 'BV1fX4y1c7y7')]\n",
"tab_contents = display_videos(video_ids, W=730, H=410)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
"for i in range(len(tab_contents)):\n",
" tabs.set_title(i, video_ids[i][0])\n",
"display(tabs)"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"This video covers how the eigenvalues and eigenvectors of the weight matrix affect the neural activities more generally.\n",
"\n",
"

\n",
"## Click here for text recap of video

\n",
"\n",
"We now know that if our initial activities (or initial condition) fall on an eigenvector of $\\mathbf{W}$, the activities will evolve along that line, either exploding to infinity if the absolute value of the eigenvalue is above 1 or decaying to the origin it it is below 1. What if our initial condition doesn't fall along the eigenvector though?\n",
"\n",
"To understand what will happen, we will use the ideas of basis vectors and linear combinations from Tutorial 1.\n",
"\n",
"Let's assume for now that our weight matrix has two distinct eigenvectors ($\\mathbf{v}_1$ and $\\mathbf{v}_2$) with corresponding eigenvalues $\\lambda_1$ and $\\lambda_2$, and that these eigenvectors form a basis for 2D space. That means we can write any vector in 2D space as a linear combination of our eigenvectors, including our initial activity vector:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{a}_0 = c_1\\mathbf{v}_1 + c_2\\mathbf{v}_2\n",
"\\end{equation}\n",
"\n",
"Let's compute the next time step, using our previous strategy of substitution:\n",
"\n",
"\\begin{align}\n",
"\\mathbf{a}_1 &= \\mathbf{W}\\mathbf{a}_0 \\\\\n",
"&= \\mathbf{W}(c_1\\mathbf{v}_1 + c_2\\mathbf{v}_2) \\\\\n",
"&= c_1\\mathbf{W}\\mathbf{v}_1 + c_2\\mathbf{W}\\mathbf{v}_2 \\\\\n",
"&= c_1\\lambda_1\\mathbf{v}_1 + c_2\\lambda_2\\mathbf{v}_2\n",
"\\end{align}\n",
"\n",
"All activities can be written as:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{a}_i = c_1\\lambda_1^i\\mathbf{v}_1 + c_2\\lambda_2^i\\mathbf{v}_2\n",
"\\end{equation}\n",
"\n",
"We'll see what this means for our system in the next demo."
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"Before we get to the neural circuit, refresh your memory on linear combinations briefly by looking at our widget from tutorial 1 below. What happens when the absolute values of both scalar multiples of both vectors is big? What about when one is big and one is small?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Make sure you execute this cell to enable the widget! Move the sliders for “a” and “b”. After releasing the slider, be patient for a couple of seconds to see the desired change.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
" #@markdown Make sure you execute this cell to enable the widget! Move the sliders for “a” and “b”. After releasing the slider, be patient for a couple of seconds to see the desired change.\n",
"\n",
"\n",
"def plot_arrows(x, y, a_times_x, b_times_y):\n",
" fig, ax = plt.subplots(figsize=(10, 7))\n",
"\n",
" ax.spines['top'].set_color('none')\n",
" ax.spines['bottom'].set_position('zero')\n",
" ax.spines['left'].set_position('zero')\n",
" ax.spines['right'].set_color('none')\n",
"\n",
" ax.set_aspect('equal', adjustable='box')\n",
" ax.set(xlim = [-10, 10], ylim = [-10, 10], xticks = np.arange(-10, 10), yticks = np.arange(-10, 10), xticklabels = [], yticklabels = [])\n",
"\n",
" ax.grid(alpha=.4)\n",
"\n",
" z = a_times_x + b_times_y\n",
" z_arr = ax.arrow(0, 0, z[0], z[1], width=.08, color='k', length_includes_head = True);\n",
"\n",
" x_orig, = ax.plot([0, x[0]], [0, x[1]], '--', color='#648FFF')\n",
" y_orig, = ax.plot([0, y[0]], [0, y[1]], '--', color='#DC267F')\n",
"\n",
" ax_arr = ax.arrow(0, 0, a_times_x[0], a_times_x[1], width=.08, color='#648FFF', length_includes_head = True);\n",
" by_arr = ax.arrow(0, 0, b_times_y[0], b_times_y[1], width=.08, color='#DC267F', length_includes_head = True);\n",
"\n",
" ax.plot([a_times_x[0], z[0]], [a_times_x[1], z[1]], '--k')\n",
" ax.plot([b_times_y[0], z[0]], [b_times_y[1], z[1]], '--k')\n",
"\n",
"\n",
" leg = ax.legend([x_orig, y_orig, ax_arr, by_arr, z_arr], [r\"$\\mathbf{x}$\", r\"$\\mathbf{y}$\", r\"$a\\mathbf{x}$\", r\"$b\\mathbf{y}$\", r\"$\\mathbf{z} = a\\mathbf{x} + b\\mathbf{y}$\"], handlelength = 2, fontsize = 25, loc = 'center left', bbox_to_anchor=(1.05, .5))\n",
" for handle, label in zip(leg.legendHandles, leg.texts):\n",
" try:\n",
" label.set_color(handle.get_facecolor())\n",
" except:\n",
" label.set_color(handle.get_color())\n",
" #handle.set_visible(False)\n",
"\n",
"@widgets.interact(a = widgets.FloatSlider(value=1.0, min=-4, max=4, step=0.1), b = widgets.FloatSlider(value=1.0, min=-4, max=4, step=0.1))\n",
"def plot_linear_combination(a, b):\n",
" x = np.array([3, 1])\n",
" y = np.array([-1, 2])\n",
"\n",
" plot_arrows(x, y, a*x, b*y)"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"### Interactive demo 2.2: Changing both eigenvalues\n",
"\n",
"In the demo below, you can now change both eigenvalues and the initial condition (with `a0_1` setting neuron 1 initial activity and `a0_2` setting neuron 2 initial activity). We will only look at positive eigenvalues to keep things a little more simple. We also make sure the second eigenvalue is always the smaller one (just for better visualization purposes).\n",
"\n",
"Think each of the following questions through based on the equation we just arrived at and then play with the demo to see if you are correct.\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{a}_i = c_1\\lambda_1^i\\mathbf{v}_1 + c_2\\lambda_2^i\\mathbf{v}_2\n",
"\\end{equation}\n",
"\n",
"1. What will happen when both eigenvalues are greater than 1? Does this depend on initial condition? Set eigenvalue1 to 2 and eigenvalue2 to 1.2 and try out different initial conditions. What do you see? \n",
"2. What will happen when both eigenvalues are less than 1?\n",
"3. What happens if one eigenvalue is below 1 and the other is above 1?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute this cell to enable the widget (there is a small lag so be patient after changing sliders)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute this cell to enable the widget (there is a small lag so be patient after changing sliders)\n",
"eigenvalue1 = widgets.FloatSlider(value=0.5, min=0.2, max=2, step=0.2)\n",
"eigenvalue2 = widgets.FloatSlider(value=0.2, min=0.2, max=0.5, step=0.2)\n",
"a0_1 = widgets.FloatSlider(value=1, min=-5, max=5, step=0.2)\n",
"a0_2 = widgets.FloatSlider(value=2, min=-5, max=5, step=0.2)\n",
"\n",
"def update_range(*args):\n",
" eigenvalue2.max = eigenvalue1.value - 0.2\n",
"eigenvalue1.observe(update_range, 'value')\n",
"\n",
"\n",
"def plot_system(eigenvalue1, eigenvalue2, a0_1, a0_2):\n",
"\n",
" # Get initial condition\n",
" a0 = np.array([a0_1, a0_2])\n",
"\n",
" # Get weight matrix with specified eigenvalues\n",
" W = get_eigval_specified_matrix([eigenvalue1, eigenvalue2])\n",
"\n",
" # Get neural activities\n",
" u = circuit_implementation(W, a0, 10)\n",
"\n",
" # Visualize neural activities\n",
" plot_circuit_responses(u, W, eigenstuff = True, xlim = [-15, 15], ylim = [-15, 15])\n",
"\n",
"widgets.interact(plot_system, eigenvalue1 = eigenvalue1, eigenvalue2 = eigenvalue2, a0_1 = a0_1, a0_2 = a0_2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"execution": {}
},
"source": [
"[*Click for solution*](https://github.com/NeuromatchAcademy/precourse/tree/main//tutorials/W0D3_LinearAlgebra/solutions/W0D3_Tutorial3_Solution_4c4ae26d.py)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Section 2.3: Complex eigenvalues\n",
"\n",
"We've been hiding some complexity from you up until now, namely that eigenvalues can be complex. Complex eigenvalues result in a very specific type of dynamics: rotations.\n",
"\n",
"We will not delve into the proof or intuition behind this here as you'll encounter complex eigenvalues in dynamical systems in W2D2: Linear Dynamics. \n",
"\n",
"Instead, we will simply demonstrate how the nature of the rotations depends on the complex eigenvalues in the animation below. We plot a 3-neuron circuit to better show the rotations. We illustrate each of the following:\n",
"\n",
"\n",
"* Complex eigenvalues with an absolute value equal to 1 result in a sustained rotation in 3D space.\n",
"\n",
"* Complex eigenvalues with an absolute value below 1 result in a rotation towards the origin.\n",
"\n",
"* Complex eigenvalues with an absolute value above 1 result in a rotation towards the positive/negative infinity.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"---\n",
"# Summary\n",
"\n",
"You have seen how we can predict what happens in a discrete dynamical system with an update rule of:\n",
"\n",
"\\begin{equation}\n",
"\\mathbf{a}_t = \\mathbf{W}\\mathbf{a}_{t-1}\n",
"\\end{equation}\n",
"\n",
"

\n", "\n", "The most important takeaway is that inspecting eigenvalues and eigenvectors enables you to predict how discrete dynamical systems evolve. Specifically:\n", "\n", "* If all eigenvalues are real and have absolute values above 1, the neural activities explode to infinity or negative infinity. \n", "\n", "* If all eigenvalues are real and have absolute values below 1, the neural activities decay to 0. \n", "\n", "* If all eigenvalues are real and at least one has an absolute value above 1, the neural activities explode to infinity or negative infinity, except for special cases where the initial condition lies along an eigenvector with an eigenvalue whose absolute value is below 1. \n", "\n", "* If eigenvalues are complex, the neural activities rotate in space and decay or explode depending on the amplitude of the complex eigenvalues.\n", "\n", "* Even finer details of the trajectories can be predicted by examining the exact relationship of eigenvalues and eigenvectors.\n", "\n", "

\n", "\n", "Importantly, these ideas extend far beyond our toy neural circuit. Discrete dynamical systems with the same structure of update rule are common. While the exact dependencies on eigenvalues will change, we will see that we can still use eigenvalues/vectors to understand continuous dynamical systems in W2D2: Linear Dynamics." ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "name": "W0D3_Tutorial3", "provenance": [], "toc_visible": true }, "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "kernelspec": { "display_name": "Python 3", "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.7.13" }, "toc-autonumbering": true }, "nbformat": 4, "nbformat_minor": 0 }

\n", "\n", "The most important takeaway is that inspecting eigenvalues and eigenvectors enables you to predict how discrete dynamical systems evolve. Specifically:\n", "\n", "* If all eigenvalues are real and have absolute values above 1, the neural activities explode to infinity or negative infinity. \n", "\n", "* If all eigenvalues are real and have absolute values below 1, the neural activities decay to 0. \n", "\n", "* If all eigenvalues are real and at least one has an absolute value above 1, the neural activities explode to infinity or negative infinity, except for special cases where the initial condition lies along an eigenvector with an eigenvalue whose absolute value is below 1. \n", "\n", "* If eigenvalues are complex, the neural activities rotate in space and decay or explode depending on the amplitude of the complex eigenvalues.\n", "\n", "* Even finer details of the trajectories can be predicted by examining the exact relationship of eigenvalues and eigenvectors.\n", "\n", "

\n", "\n", "Importantly, these ideas extend far beyond our toy neural circuit. Discrete dynamical systems with the same structure of update rule are common. While the exact dependencies on eigenvalues will change, we will see that we can still use eigenvalues/vectors to understand continuous dynamical systems in W2D2: Linear Dynamics." ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "name": "W0D3_Tutorial3", "provenance": [], "toc_visible": true }, "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "kernelspec": { "display_name": "Python 3", "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.7.13" }, "toc-autonumbering": true }, "nbformat": 4, "nbformat_minor": 0 }