diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Intro.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Intro.ipynb
index 677b0c1a2..cc307c389 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Intro.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Intro.ipynb
@@ -61,7 +61,9 @@
"\n",
"Though it is not required, you will benefit from these tutorials more if you have had the opportunity to take linear algebra and multivariate statistics courses in the past, as most of the computations and expressed ideas use the tools from these disciplines.\n",
"\n",
- "Note that the intro video for this day is longer than usual; it is the only day that has this property - feel free to return to it later if you don't have enough time before taking the tutorials."
+ "The intro video for this day in the last iteration of the course (2024) was relatively long and for this year (2025) it has been split into two videos. Please watch the video below to guide you through the tutorials. The additional part of the video is available in the first bonus tutorial (Tutorial 4), which goes into more detail related to the topics you will cover if you complete Tutorials 1-3. You are also kindly advised to revisit the bonus tutorials in your own time. We have added a new (non-Bonus) tutorial all about the exciting topic of Dynamic Similarity Analysis in this year's iteration of the course. This complements the topic of geometric (spatial) similarity with further similarity measures that take into account temporal information.\n",
+ "\n",
+ "We hope you enjoy the topics today. We'll now move on to Heiko and Niko to guide you through today's material."
]
},
{
@@ -123,7 +125,7 @@
" return tab_contents\n",
"\n",
"\n",
- "video_ids = [('Youtube', 'RGOB0LRnLME'), ('Bilibili', 'BV1Kb421p77j')]\n",
+ "video_ids = [('Youtube', 'a66lOCB4oFw'), ('Bilibili', 'BV12eVjz3EbK')]\n",
"tab_contents = display_videos(video_ids, W=854, H=480)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
@@ -208,7 +210,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial1.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial1.ipynb
index dc36ab0d4..10d865eed 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial1.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial1.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ JohnMark Taylor & Zhuofan Josh Ying\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault\n"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy\n"
]
},
{
@@ -41,7 +41,7 @@
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
- "Welcome to Tutorial 1 on Generalization and Representational Geometry. Building on the Intro Lecture, this tutorial aims to help you get your feet wet and characterize representations in terms of their geometry, as captured by the distances among inputs at different stages of processing. The key point of this tutorial is that the way machine learning models generalize depends on how they represent their inputs and, in particular, on how similar a new input is to other inputs in the internal representations. We focus on linear models, where the close relationship between similarity and generalization can be most easily understood from an intuitive and mathematical perspective. \n",
+ "Welcome to Tutorial 1 on Generalization and Representational Geometry. Building on the Intro Lecture, this tutorial aims to help you get your feet wet and characterize representations in terms of their geometry, as captured by the distances among inputs at different stages of processing. **The key point of this tutorial is that the way machine learning models generalize depends on how they represent their inputs and, in particular, on how similar a new input is to other inputs in the internal representations**. We focus on linear models, where the close relationship between similarity and generalization can be most easily understood from an intuitive and mathematical perspective.\n",
"\n",
"By the end of this tutorial, you will:\n",
"\n",
@@ -89,17 +89,28 @@
},
"source": [
"---\n",
- "# Setup\n",
- "\n"
+ "# Setup (Please Read)\n",
+ "\n",
+ "This tutorial involves an interactive component as the final element, which depends on some tools that have undergone a change since the last time this course was run (2024). Therefore, if you're running this on Google Colab, you need to:\n",
+ "\n",
+ "* uncomment the cell below\n",
+ "* run the cell\n",
+ "\n",
+ "This will initially cause the notebook to restart after a few moments, but then you can run all the cells **below** (in Google Colab: *Runtime -> Run Cell and Below*) as necessary and the intended functionally will exist. As of the time of writing, this issue has not been corrected and this workaround is required. Please wait until you see the notification of the notebook having restarted before selecting the 'Run all and below' menu option.\n",
+ "\n",
+ "Additionally, this tutorial uses `Graphviz` for additional visualizations. For proper execution of the functions, you should install it locally. Please visit this [link](https://graphviz.org/download/) to get the instructions depending on your local OS configurations (if you are running this notebook on Google Colab, just skip it)."
]
},
{
- "cell_type": "markdown",
+ "cell_type": "code",
+ "execution_count": null,
"metadata": {
"execution": {}
},
+ "outputs": [],
"source": [
- "This tutorial uses `Graphviz` for additional visualizations. For proper execution of the functions, you should install it locally. Please visit this [link](https://graphviz.org/download/) to get the instructions depending on your local OS configurations (if you are running this notebook on Google Colab, just skip it)."
+ "#!pip install -q ipympl\n",
+ "#get_ipython().kernel.do_shutdown(restart=True)"
]
},
{
@@ -119,7 +130,22 @@
"\n",
"# To install jupyter-matplotlib (ipympl) via pip\n",
"!pip install -q torchlens\n",
- "!pip install -q ipympl ipywidgets matplotlib numpy scikit-learn torch torchvision rsatoolbox scipy\n",
+ "!pip install -q numpy scikit-learn torch torchvision scipy matplotlib\n",
+ "\n",
+ "# Import future dependencies first\n",
+ "from __future__ import print_function\n",
+ "\n",
+ "# Configure matplotlib ONCE before any other matplotlib imports\n",
+ "import matplotlib\n",
+ "%matplotlib inline\n",
+ "\n",
+ "# Now import pyplot AFTER setting the backend\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "# Configure other matplotlib settings\n",
+ "plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = False\n",
+ "plt.rcParams['xtick.top'] = plt.rcParams['xtick.labeltop'] = True\n",
+ "plt.style.use(\"https://raw.githubusercontent.com/NeuromatchAcademy/course-content/main/nma.mplstyle\")\n",
"\n",
"\n",
"# To install jupyter-matplotlib via conda (comment out if you are not using conda)\n",
@@ -182,7 +208,6 @@
"\n",
"# Third-party library imports\n",
"import numpy as np\n",
- "import matplotlib.pyplot as plt\n",
"from matplotlib.offsetbox import AnnotationBbox, OffsetImage\n",
"from scipy import stats\n",
"import ipywidgets as widgets\n",
@@ -204,10 +229,7 @@
"# rsatoolbox imports\n",
"import rsatoolbox\n",
"from rsatoolbox.data import Dataset\n",
- "from rsatoolbox.rdm.calc import calc_rdm\n",
- "\n",
- "# Jupyter-specific imports\n",
- "%matplotlib inline"
+ "from rsatoolbox.rdm.calc import calc_rdm"
]
},
{
@@ -989,7 +1011,7 @@
"args = build_args()\n",
"train_loader, test_loader = fetch_dataloaders(args)\n",
"path = \"standard_model.pth\"\n",
- "model = torch.load(path, map_location=args.device)"
+ "model = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1041,7 +1063,7 @@
"* $\\text{image}$ is the original input image\n",
"* $\\epsilon$ is the magnitude of the perturbation\n",
"* $\\nabla_{\\text{image}} J(\\text{image}, \\text{true label})$ is the gradient of the loss with respect to the input image\n",
- "* $\\text{sign}(\\cdot)$ returns the sign of the argument"
+ "* $\\text{sign}(\\cdot)$ returns the sign of the argument (either `1` or `-1`)"
]
},
{
@@ -1067,6 +1089,15 @@
"plt.axis('off')"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "execution": {}
+ },
+ "source": [
+ "As you can see, the black background from the **clean** and original images has changed a bit to include intricate patterns that do not affect our interpretation of what digit is being represented overall (because humans focus on the white pixels that make up the MNIST digit). However, this new fluctuating pattern in the background of each image will demonstrate that the models can be tricked into **perceiving** different digits based on this background pattern. This is the issue we want to solve when training an *adversarially robust* model."
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {
@@ -1109,7 +1140,7 @@
"# @title Grab an adversarially pretrained model\n",
"\n",
"path = \"adversarial_model.pth\"\n",
- "model_robust = torch.load(path, map_location=args.device)"
+ "model_robust = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1172,7 +1203,7 @@
"execution": {}
},
"source": [
- "You should observe that the adversarially trained model has much higher accuracy on the adversarial images."
+ "You should observe that the adversarially trained model has much higher accuracy on the adversarial images (98%) than the standard model does (60%) on the same images."
]
},
{
@@ -1292,6 +1323,8 @@
"execution": {}
},
"source": [
+ "Is it clear to you that the block associated with indices 20-25 in the figure above represent similarity of the 5th class label (the digit '4')? If not, go back and re-read the paragraphs above and take care to relate the block structure to the 5 samples per image class.\n",
+ "\n",
"Now we compute and visualize the RDMs for **standard images** for both the standard model and the adversarially trained model."
]
},
@@ -1526,17 +1559,10 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
- "cellView": "form",
"execution": {}
},
"outputs": [],
"source": [
- "# @title Execute to see the widget!\n",
- "%matplotlib widget\n",
- "\n",
- "plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = False\n",
- "plt.rcParams['xtick.top'] = plt.rcParams['xtick.labeltop'] = True\n",
- "\n",
"transform=transforms.Compose([\n",
" transforms.ToTensor(),\n",
" transforms.Normalize((0.1307,), (0.3081,))\n",
@@ -1553,7 +1579,28 @@
"test_data = torch.stack([test_dataset[i][0] for i in range(num_test_samples)])\n",
"\n",
"train_patterns = tl.log_forward_pass(model, train_data)\n",
- "test_patterns = tl.log_forward_pass(model, test_data)\n",
+ "test_patterns = tl.log_forward_pass(model, test_data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "execution": {}
+ },
+ "outputs": [],
+ "source": [
+ "# @title Execute to see the widget!\n",
+ "\n",
+ "skip_cell = False\n",
+ "\n",
+ "try:\n",
+ " matplotlib.use('ipympl')\n",
+ " print('Switched to interactive backend')\n",
+ "except:\n",
+ " print('Could not switch to interactive backend. Please see Setup instructions at the top of the notebook.')\n",
+ " skip_cell = True\n",
"\n",
"out = widgets.Output()\n",
"with plt.ioff():\n",
@@ -1627,24 +1674,26 @@
"\n",
"\n",
"w = widgets.interactive(update_and_visualize,\n",
- " layer = widgets.Dropdown(\n",
- " options=['conv1', 'conv2', 'dropout1', 'fc1', 'dropout2', 'fc2'],\n",
- " value='fc2',\n",
- " description='Layer',\n",
- " disabled=False,\n",
- " layout = widgets.Layout(margin='40px 10px 0 0')),\n",
- " rating1 = widgets.FloatSlider(description='Rating', value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='50px 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating2 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating3 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating4 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating5 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating6 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating7 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating8 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating9 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating10 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'})\n",
- " )\n",
- "widgets.HBox([w, fig.canvas], layout=widgets.Layout(width='100%', display='flex', align_items='stretch'))"
+ " layer = widgets.Dropdown(\n",
+ " options=['conv1', 'conv2', 'dropout1', 'fc1', 'dropout2', 'fc2'],\n",
+ " value='fc2',\n",
+ " description='Layer',\n",
+ " disabled=False,\n",
+ " layout = widgets.Layout(margin='40px 10px 0 0')),\n",
+ " rating1 = widgets.FloatSlider(description='Rating', value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='50px 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating2 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating3 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating4 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating5 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating6 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating7 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating8 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating9 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating10 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'})\n",
+ " )\n",
+ "\n",
+ "if not skip_cell:\n",
+ " display(widgets.HBox([w, fig.canvas], layout=widgets.Layout(width='100%', display='flex', align_items='stretch')))"
]
},
{
@@ -1667,14 +1716,18 @@
},
"source": [
"---\n",
- "# Summary\n",
+ "# The Big Picture\n",
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
"In this tutorial, we explored a foundational issue in artificial intelligence: how can we understand how a system generalizes to new inputs that it wasn’t trained on? We did this through the lens of representational similarity analysis (RSA), the characterization of a system based on the pattern of dissimilarities among a set of stimuli. This tutorial has two parts:\n",
"\n",
"- In part 1, you used RSA to understand how networks trained in different ways handle adversarial stimuli designed to fool the network.\n",
- "- In part 2, you used an interactive widget to understand how a network’s response to a new stimulus depends on its similarity to training stimuli."
+ "- In part 2, you used an interactive widget to understand how a network’s response to a new stimulus depends on its similarity to training stimuli.\n",
+ "\n",
+ "The theme of today is tying together **similarity measures** with **generalization**. If you've struggled in any part with the material so far, the key message to take away from this tutorial is that it makes sense that a system which is trained on a specific distribution has an easier time to generalize to new inputs that look very similar to what was seen during training. If you had a cat-dog classifier and later input images that were **dissimilar** to the images used in training, this would be more difficult for the model to generalize to. It's a conceptually simple idea, but until now resisted a comprehensive set of methods to quantify and demonstrate this mathematically.\n",
+ "\n",
+ "With that said - Let's move on to Tutorial 2!"
]
}
],
@@ -1708,7 +1761,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial2.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial2.ipynb
index dc8ba63f9..d788fd7be 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial2.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial2.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ Hossein Adeli\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy"
]
},
{
@@ -1024,7 +1024,7 @@
"execution": {}
},
"source": [
- "We are going to use the same models, standard and adversarially trained, as in the previous tutorial."
+ "We are going to use the same models as in the previous tutorial, namely (i) the standard model and (ii) the adversarially trained model."
]
},
{
@@ -1041,7 +1041,7 @@
"args = build_args()\n",
"train_loader, test_loader = fetch_dataloaders(args)\n",
"path = \"standard_model.pth\"\n",
- "model = torch.load(path, map_location=args.device)"
+ "model = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1073,7 +1073,7 @@
"execution": {}
},
"source": [
- "First, let's look at the computational steps in the model."
+ "First, let's look at the computational steps in the model. Specifically, this step relates to the layer sequences in the model, e.g. convolutional layers, activation functions, fully connected layers, max pooling. These computational steps differentially affect the representations as the input passes through (and is transformed by) via the model structure."
]
},
{
@@ -1131,7 +1131,7 @@
"execution": {}
},
"source": [
- "Now, for each of the test images, we have the model features from each layer of the trained network. We can look at how the representational (dis)similarity between these samples changes across the layers of the network. For each layer, we flatten the features so that each test image is represented by a vector (i.e., a point in space), and then we use the `rsatooblox` to compute the Euclidean distances between the points in that representational space.\n",
+ "For each of the test images, we have the model features from each layer of the trained network. We can look at how the representational (dis)similarity between these samples changes across the layers of the network. For each layer, we flatten the features so that each test image is represented by a vector (i.e., a point in space), and then we use the `rsatooblox` to compute the Euclidean distances between the points in that representational space.\n",
"\n",
"The result is a matrix of distances between each pair of samples for each layer."
]
@@ -1144,7 +1144,7 @@
"source": [
"We can now plot the RDMs for the input pixels, the two convolutional, and the two fully connected layers below.\n",
"\n",
- "The RDMs are of dimension `#stimuli` by `#stimuli`. The brighter cells show smaller dissimilarity between the two stimuli. The diagonal has a dissimilarity of 0, as each image is maximally similar to itself.\n",
+ "The RDMs are of dimension `n_stimuli` by `n_stimuli`. The brighter cells show smaller dissimilarity between the two stimuli. The diagonal has a dissimilarity of 0, as each image is maximally similar to itself.\n",
"\n",
"Since the instances of each category are next to each other, the brighter blocks emerging around the diagonal show that the representations for category instances become similar to one another across the levels of the hierarchy."
]
@@ -1175,7 +1175,7 @@
"execution": {}
},
"source": [
- "Computation in the network can be understood as the transformation of representational geometries. As we see in the above RDMs, the geometry captures how the representation in later layers becomes more similar for instances of the same category since this model was trained for classification."
+ "Computation in the network can be understood as the transformation of representational geometries as input is transformed into output through the neural network. As we see in the above RDMs, the geometry captures how the representation in later layers becomes more similar for instances of the same category since this model was trained for classification."
]
},
{
@@ -1188,9 +1188,9 @@
"\n",
"Another way to examine the geometry of the representation changing across layers is by reducing the dimensionality and displaying the samples in 2D space.\n",
"\n",
- "The representational spaces of each layer of a deep network project each stimulus to a point in a very high-dimensional space. Methods such as PCA (principal component analysis), MDS (Multidimensional_scaling), and t-SNE (t-distributed stochastic neighbor embedding) attempt to capture the same geometry in lower dimensions.\n",
+ "The representational spaces of each layer of a deep network project each stimulus to a point in a very high-dimensional space. Methods such as PCA (principal component analysis), MDS (Multi-dimensional Scaling), and t-SNE (t-distributed stochastic neighbor embedding) attempt to preserve the same geometry that exists in higher dimensions, in lower dimensions.\n",
"\n",
- "Below, we look at the representational geometry of the same network layers using these methods."
+ "Below, we look at the representational geometry of corresponding network layers using these dimensionality reduction methods."
]
},
{
@@ -1199,9 +1199,9 @@
"execution": {}
},
"source": [
- "For this visualization, we take 500 samples from the test set and color-code them based on their category. The figure below shows the scatter plots for these samples across the layers of the network. Each panel is the 2D projection of the feature representations using a specific method.\n",
+ "For this visualization, we take 500 samples from the test set and color-code them based on their category (class label). The figure below shows the scatter plots for these samples across the layers of the network. Each panel is the 2D projection of the feature representations using a specific dimensionality reduction method method (PCA, MDS, t-SNE).\n",
"\n",
- "The methods all show that image representations progressively cluster based on category across the layers of the network."
+ "The methods all show that image representations progressively cluster based on category across the layers of the network as the input is transformed into the output prediction."
]
},
{
@@ -1233,19 +1233,19 @@
"source": [
"## Representational path: from comparing representations to comparing RDMs\n",
"\n",
- "We can now go a step further and think of the computational steps in a network as steps in a path in the space of representational geometries. Each layer in the network is changing the geometry to make it more and more like the desired geometry, which are the labels for a network trained for classification. What we'll do is to *[compare RDM matrices](https://rsatoolbox.readthedocs.io/en/stable/comparing.html)* across layers. \n",
+ "We can now go a step further and think of the computational steps in a network as steps in a path in the space of representational geometries. Each layer in the network is changing the geometry to make it more and more like the desired geometry, which is a highly similar set of relationships among items from the same class/category, but each distinct from other classes. What we'll do is to *[compare RDM matrices](https://rsatoolbox.readthedocs.io/en/stable/comparing.html)* across layers.\n",
"\n",
"## Comparing RDMs\n",
"\n",
- "The first step, as before, is to calculate RDMs based on the Euclidean distances between the representations of images for each layer. We stack these RDMs into a 3d tensor:\n",
+ "The first step, as before, is to calculate RDMs based on the Euclidean distances between the representations of images for each layer. We stack these 2D RDMs into a 3D tensor:\n",
"\n",
"$$M(\\text{layer i}, \\text{image j}, \\text{image k})$$\n",
"\n",
- "The next step is to quantify how the geometries change across the layers. To perform this operation, we first flatten the RDMs to obtain a 2d matrix:\n",
+ "The next step is to quantify how the geometries change across the layers. To perform this operation, we first flatten the RDMs to obtain a 2D matrix:\n",
"\n",
"$$\\hat M(\\text{layer i}, \\text{image j x image k})$$\n",
"\n",
- "We can then calculate the cosine similarity between the different rows of this new matrix. By taking the arccos of this measure, we obtain a proper distance between representational geometries of different layers (Williams et al. 2021). So, we are now creating an RDM matrix of the RDM matrices! The resulting matrix has the dimensions of `#layers` by `#layers`.\n",
+ "We can then calculate the cosine similarity between the different rows of this new matrix. By taking the *arccos* (arc cosine, or the *inverse cosine function*) of this measure, we obtain a proper distance between representational geometries of different layers (Williams et al. 2021). So, we are now creating an RDM between the existing RDMs! The resulting matrix has the dimensions of `n_layers` by `n_layers`.\n",
"\n",
"The last step to visualize the path is to embed the distances between the geometries in a lower dimensional space. We use MDS to reduce the dimensions to 2 in order to show each computational step as a point in a 2D space."
]
@@ -1291,11 +1291,11 @@
"\n",
"The figure on the right shows the embedding of these distances in a 2D space using MDS. This figure captures the same distances between the layers shown in the matrix on the left in a 2D space. Connecting the steps starting from the input (shown as the black square) forms a path in the space of representional geometries. The last step in this path (the softmax layer) is similar to the embedding of the labels (denoted by an asterisk) in the same 2D space.\n",
"\n",
- "There are a few things to note about the path: \n",
+ "There are a few things to note about the path:\n",
"\n",
- "1. The path is generally smooth. This shows that each layer only slightly changes the representations, which we could also observe from the distance matrix on the left. \n",
- "2. The distance between the representational geometry of the input and the labels is large, but we see these two points somewhat close to each other in the path. This is partly because MDS tries to optimize to capture all the distances in the 2D space; they cannot all be veridically captured, and a stronger curvature is introduced to the path. Therefore, details of the curvature in 2D should be interpreted with caution. \n",
- "3. The size of the steps is generally informative. For example, we see in the left matrix that there is a big change in the representational geometry going from the convolutional to the fully connected layers, corresponding to a larger step in the path.\n",
+ "1. The path is generally smooth. This shows that each layer only slightly changes the representations, which we could also observe from the distance matrix on the left.\n",
+ "2. The distance between the representational geometry of the input and the labels is large, but we see these two points somewhat close to each other in the path. This is partly because MDS tries to optimize to capture all the distances in the 2D space; they cannot all be veridically captured, and a stronger curvature is introduced to the path. Therefore, details of the curvature in 2D should be interpreted with caution.\n",
+ "3. The size of the steps is generally informative. For example, we see in the left matrix that there is a big change in the representational geometry going from the convolutional to the fully connected layers, corresponding to a larger step in the path in the figure on the right.\n",
"\n",
"Thus, the computation performed by this model is a path from the geometry of the input pixels to the geometry of the ground truth labels."
]
@@ -1356,7 +1356,7 @@
"source": [
"## Adversarial images\n",
"\n",
- "Adversarial images pose a generalization challenge to DNNs. Here, we examine how this challenge and lower performance are reflected in the representational geometry path. First, we use FGSM (Fast Gradient Sign method) to generate adversarial images for the trained feedforward model. This is a \"white-box\" attack that uses the gradients from the model loss with respect to an image to adjust the image in order to maximize the loss. "
+ "Adversarial images pose a generalization challenge to DNNs. Here, we examine how this challenge and lower performance are reflected in the representational geometry path. First, we use FGSM (Fast Gradient Sign method) to generate adversarial images for the trained feedforward model. This is a \"white-box\" attack that uses the gradients from the model loss with respect to an image to adjust the image in order to maximize the loss."
]
},
{
@@ -1412,7 +1412,7 @@
"# @title Grab an adversarially robust model\n",
"\n",
"path = \"adversarial_model.pth\"\n",
- "model_robust = torch.load(path, map_location=args.device)"
+ "model_robust = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1584,7 +1584,7 @@
"source": [
"When we use adversarial images, the two networks are taking diverging paths in the space of representational geometries. Only the robust model's representation converges towards the embedding of the labels.\n",
"\n",
- "We can see from this example that these paths depend on the chosen stimuli!"
+ "We can see from this example that these paths depend on the chosen stimuli. Try to recall what we looked at in the last tutorial. When images were more similar, there was better generalization. The standard model, when given adversarial inputs, is fooled and struggles to map correctly and complete the path in the rightmost figures above."
]
},
{
@@ -1689,221 +1689,16 @@
},
"source": [
"---\n",
- "# Summary\n",
+ "# The Big Picture\n",
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
"In this tutorial, we:\n",
"\n",
- "- Characterized the computation that happens across different layers of a network as a path, with each step changing the geometry of the representation to go from input pixels to target labels,\n",
- "- Examined the paths for different model architectures and different inputs and learned how to interpret them."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "---\n",
- "# Bonus Section: Representational geometry of recurrent models\n",
+ "- Characterized the computation that happens across different layers of a network as a path, with each step changing the geometry of the representation to go from input pixels to target labels\n",
+ "- Examined the representational geometry paths for different model architectures and different inputs and learned how to interpret them\n",
"\n",
- "Transformations of representations can occur across space and time, e.g., layers of a neural network and steps of recurrent computation."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Just as the layers in a feedforward DNN can change the representational geometry to perform a task, steps in a recurrent network can reuse the same layer to reach the same computational depth.\n",
- "\n",
- "In this section, we look at a very simple recurrent network with only 2650 trainable parameters."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Here is a diagram of this network:\n",
- "\n",
- ""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "execution": {}
- },
- "outputs": [],
- "source": [
- "# @title Grab a recurrent model\n",
- "\n",
- "args = build_args()\n",
- "train_loader, test_loader = fetch_dataloaders(args)\n",
- "path = \"recurrent_model.pth\"\n",
- "model_recurrent = torch.load(path, map_location=args.device)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "
We can first look at the computational steps in this network. As we see below, the `conv2` operation is repeated for 5 times."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "train_nodes, _ = get_graph_node_names(model_recurrent)\n",
- "print('The computational steps in the network are: \\n', train_nodes)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Plotting the RDMs after each application of the `conv2` operation shows the same progressive emergence of the blockwise structure around the diagonal, mediating the correct classification in this task."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=20)\n",
- "return_layers = ['conv2', 'conv2_1', 'conv2_2', 'conv2_3', 'conv2_4']\n",
- "model_features = extract_features(model_recurrent, imgs.to(device), return_layers)\n",
- "\n",
- "rdms, rdms_dict = calc_rdms(model_features)\n",
- "plot_rdms(rdms_dict)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can also look at how the different dimensionality reduction techniques capture the dynamics of changing geometry."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "return_layers = ['conv2', 'conv2_1', 'conv2_2', 'conv2_3', 'conv2_4']\n",
- "\n",
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features = extract_features(model_recurrent, imgs.to(device), return_layers)\n",
- "\n",
- "plot_dim_reduction(model_features, labels, transformer_funcs =['PCA', 'MDS', 't-SNE'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "## Representational geometry paths for recurrent models"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can look at the model's recurrent computational steps as a path in the representational geometry space."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features_recurrent = extract_features(model_recurrent, imgs.to(device), return_layers='all')\n",
- "\n",
- "#rdms, rdms_dict = calc_rdms(model_features)\n",
- "features = {'recurrent model': model_features_recurrent}\n",
- "model_colors = {'recurrent model': 'y'}\n",
- "\n",
- "rep_path(features, model_colors, labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can also look at the paths taken by the feedforward and the recurrent models and compare them."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features = extract_features(model, imgs.to(device), return_layers='all')\n",
- "model_features_recurrent = extract_features(model_recurrent, imgs.to(device), return_layers='all')\n",
- "\n",
- "features = {'feedforward model': model_features, 'recurrent model': model_features_recurrent}\n",
- "model_colors = {'feedforward model': 'b', 'recurrent model': 'y'}\n",
- "\n",
- "rep_path(features, model_colors, labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "What we can see here is that different models can take very different paths in the space of representational geometries to map images to labels. This is because there exist many different functional mappings to do a classification task."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "execution": {}
- },
- "outputs": [],
- "source": [
- "# @title Submit your feedback\n",
- "content_review(f\"{feedback_prefix}_recurrent_models\")"
+ "We used this method to examine how models trained on adversarial stimulu (vs control) differentially treat inputs that are both normal and adversarial. We saw that the category / class level similarity structure, which was different for the standard model on adversarial stimuli, resulting in lower accuracies, actually has a divergent path during the conversion from input data to output labels. This is another link into the idea of **similarity** as a lens that helps us understand **generalization**."
]
}
],
@@ -1937,7 +1732,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial3.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial3.ipynb
index f2b842ff6..d91cbb8f0 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial3.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/W1D3_Tutorial3.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ Veronica Bossio, Eivinas Butkus, Jasper van den Bosch\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault\n"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy\n"
]
},
{
@@ -2360,6 +2360,17 @@
"\n",
"5. Addressed two sources of model-performance estimation error that statistical inference must account for in addition to the error due to measurement noise: stimulus sampling and subject sampling, using the 2-factor bootstrap method."
]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "execution": {}
+ },
+ "source": [
+ "# The Big Picture\n",
+ "\n",
+ "Generalization can arise across multiple dimensions, whether that be generalization of an experiment to a new set of subjects or whether a new set of stimuli generalize to the same subjects. More likely, we would want to know how much an experiment would generalize to novel subjects across novel stimuli. We can test these ideas statistically by using the bootstreap method in statistics. This notebook highlights some issues with naive approaches to statistics when assessing generalization in this way. We explored the 2-factor bootstrap method and used a toolbox that explicitly takes care of the calculation so that we don't overestimate the variance involved. It's important to be aware of factors of generalization and how multiple overlapping factors might interact."
+ ]
}
],
"metadata": {
@@ -2390,7 +2401,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Intro.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Intro.ipynb
index 677b0c1a2..cc307c389 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Intro.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Intro.ipynb
@@ -61,7 +61,9 @@
"\n",
"Though it is not required, you will benefit from these tutorials more if you have had the opportunity to take linear algebra and multivariate statistics courses in the past, as most of the computations and expressed ideas use the tools from these disciplines.\n",
"\n",
- "Note that the intro video for this day is longer than usual; it is the only day that has this property - feel free to return to it later if you don't have enough time before taking the tutorials."
+ "The intro video for this day in the last iteration of the course (2024) was relatively long and for this year (2025) it has been split into two videos. Please watch the video below to guide you through the tutorials. The additional part of the video is available in the first bonus tutorial (Tutorial 4), which goes into more detail related to the topics you will cover if you complete Tutorials 1-3. You are also kindly advised to revisit the bonus tutorials in your own time. We have added a new (non-Bonus) tutorial all about the exciting topic of Dynamic Similarity Analysis in this year's iteration of the course. This complements the topic of geometric (spatial) similarity with further similarity measures that take into account temporal information.\n",
+ "\n",
+ "We hope you enjoy the topics today. We'll now move on to Heiko and Niko to guide you through today's material."
]
},
{
@@ -123,7 +125,7 @@
" return tab_contents\n",
"\n",
"\n",
- "video_ids = [('Youtube', 'RGOB0LRnLME'), ('Bilibili', 'BV1Kb421p77j')]\n",
+ "video_ids = [('Youtube', 'a66lOCB4oFw'), ('Bilibili', 'BV12eVjz3EbK')]\n",
"tab_contents = display_videos(video_ids, W=854, H=480)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
@@ -208,7 +210,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial1.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial1.ipynb
index dc36ab0d4..10d865eed 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial1.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial1.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ JohnMark Taylor & Zhuofan Josh Ying\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault\n"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy\n"
]
},
{
@@ -41,7 +41,7 @@
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
- "Welcome to Tutorial 1 on Generalization and Representational Geometry. Building on the Intro Lecture, this tutorial aims to help you get your feet wet and characterize representations in terms of their geometry, as captured by the distances among inputs at different stages of processing. The key point of this tutorial is that the way machine learning models generalize depends on how they represent their inputs and, in particular, on how similar a new input is to other inputs in the internal representations. We focus on linear models, where the close relationship between similarity and generalization can be most easily understood from an intuitive and mathematical perspective. \n",
+ "Welcome to Tutorial 1 on Generalization and Representational Geometry. Building on the Intro Lecture, this tutorial aims to help you get your feet wet and characterize representations in terms of their geometry, as captured by the distances among inputs at different stages of processing. **The key point of this tutorial is that the way machine learning models generalize depends on how they represent their inputs and, in particular, on how similar a new input is to other inputs in the internal representations**. We focus on linear models, where the close relationship between similarity and generalization can be most easily understood from an intuitive and mathematical perspective.\n",
"\n",
"By the end of this tutorial, you will:\n",
"\n",
@@ -89,17 +89,28 @@
},
"source": [
"---\n",
- "# Setup\n",
- "\n"
+ "# Setup (Please Read)\n",
+ "\n",
+ "This tutorial involves an interactive component as the final element, which depends on some tools that have undergone a change since the last time this course was run (2024). Therefore, if you're running this on Google Colab, you need to:\n",
+ "\n",
+ "* uncomment the cell below\n",
+ "* run the cell\n",
+ "\n",
+ "This will initially cause the notebook to restart after a few moments, but then you can run all the cells **below** (in Google Colab: *Runtime -> Run Cell and Below*) as necessary and the intended functionally will exist. As of the time of writing, this issue has not been corrected and this workaround is required. Please wait until you see the notification of the notebook having restarted before selecting the 'Run all and below' menu option.\n",
+ "\n",
+ "Additionally, this tutorial uses `Graphviz` for additional visualizations. For proper execution of the functions, you should install it locally. Please visit this [link](https://graphviz.org/download/) to get the instructions depending on your local OS configurations (if you are running this notebook on Google Colab, just skip it)."
]
},
{
- "cell_type": "markdown",
+ "cell_type": "code",
+ "execution_count": null,
"metadata": {
"execution": {}
},
+ "outputs": [],
"source": [
- "This tutorial uses `Graphviz` for additional visualizations. For proper execution of the functions, you should install it locally. Please visit this [link](https://graphviz.org/download/) to get the instructions depending on your local OS configurations (if you are running this notebook on Google Colab, just skip it)."
+ "#!pip install -q ipympl\n",
+ "#get_ipython().kernel.do_shutdown(restart=True)"
]
},
{
@@ -119,7 +130,22 @@
"\n",
"# To install jupyter-matplotlib (ipympl) via pip\n",
"!pip install -q torchlens\n",
- "!pip install -q ipympl ipywidgets matplotlib numpy scikit-learn torch torchvision rsatoolbox scipy\n",
+ "!pip install -q numpy scikit-learn torch torchvision scipy matplotlib\n",
+ "\n",
+ "# Import future dependencies first\n",
+ "from __future__ import print_function\n",
+ "\n",
+ "# Configure matplotlib ONCE before any other matplotlib imports\n",
+ "import matplotlib\n",
+ "%matplotlib inline\n",
+ "\n",
+ "# Now import pyplot AFTER setting the backend\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "# Configure other matplotlib settings\n",
+ "plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = False\n",
+ "plt.rcParams['xtick.top'] = plt.rcParams['xtick.labeltop'] = True\n",
+ "plt.style.use(\"https://raw.githubusercontent.com/NeuromatchAcademy/course-content/main/nma.mplstyle\")\n",
"\n",
"\n",
"# To install jupyter-matplotlib via conda (comment out if you are not using conda)\n",
@@ -182,7 +208,6 @@
"\n",
"# Third-party library imports\n",
"import numpy as np\n",
- "import matplotlib.pyplot as plt\n",
"from matplotlib.offsetbox import AnnotationBbox, OffsetImage\n",
"from scipy import stats\n",
"import ipywidgets as widgets\n",
@@ -204,10 +229,7 @@
"# rsatoolbox imports\n",
"import rsatoolbox\n",
"from rsatoolbox.data import Dataset\n",
- "from rsatoolbox.rdm.calc import calc_rdm\n",
- "\n",
- "# Jupyter-specific imports\n",
- "%matplotlib inline"
+ "from rsatoolbox.rdm.calc import calc_rdm"
]
},
{
@@ -989,7 +1011,7 @@
"args = build_args()\n",
"train_loader, test_loader = fetch_dataloaders(args)\n",
"path = \"standard_model.pth\"\n",
- "model = torch.load(path, map_location=args.device)"
+ "model = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1041,7 +1063,7 @@
"* $\\text{image}$ is the original input image\n",
"* $\\epsilon$ is the magnitude of the perturbation\n",
"* $\\nabla_{\\text{image}} J(\\text{image}, \\text{true label})$ is the gradient of the loss with respect to the input image\n",
- "* $\\text{sign}(\\cdot)$ returns the sign of the argument"
+ "* $\\text{sign}(\\cdot)$ returns the sign of the argument (either `1` or `-1`)"
]
},
{
@@ -1067,6 +1089,15 @@
"plt.axis('off')"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "execution": {}
+ },
+ "source": [
+ "As you can see, the black background from the **clean** and original images has changed a bit to include intricate patterns that do not affect our interpretation of what digit is being represented overall (because humans focus on the white pixels that make up the MNIST digit). However, this new fluctuating pattern in the background of each image will demonstrate that the models can be tricked into **perceiving** different digits based on this background pattern. This is the issue we want to solve when training an *adversarially robust* model."
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {
@@ -1109,7 +1140,7 @@
"# @title Grab an adversarially pretrained model\n",
"\n",
"path = \"adversarial_model.pth\"\n",
- "model_robust = torch.load(path, map_location=args.device)"
+ "model_robust = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1172,7 +1203,7 @@
"execution": {}
},
"source": [
- "You should observe that the adversarially trained model has much higher accuracy on the adversarial images."
+ "You should observe that the adversarially trained model has much higher accuracy on the adversarial images (98%) than the standard model does (60%) on the same images."
]
},
{
@@ -1292,6 +1323,8 @@
"execution": {}
},
"source": [
+ "Is it clear to you that the block associated with indices 20-25 in the figure above represent similarity of the 5th class label (the digit '4')? If not, go back and re-read the paragraphs above and take care to relate the block structure to the 5 samples per image class.\n",
+ "\n",
"Now we compute and visualize the RDMs for **standard images** for both the standard model and the adversarially trained model."
]
},
@@ -1526,17 +1559,10 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
- "cellView": "form",
"execution": {}
},
"outputs": [],
"source": [
- "# @title Execute to see the widget!\n",
- "%matplotlib widget\n",
- "\n",
- "plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = False\n",
- "plt.rcParams['xtick.top'] = plt.rcParams['xtick.labeltop'] = True\n",
- "\n",
"transform=transforms.Compose([\n",
" transforms.ToTensor(),\n",
" transforms.Normalize((0.1307,), (0.3081,))\n",
@@ -1553,7 +1579,28 @@
"test_data = torch.stack([test_dataset[i][0] for i in range(num_test_samples)])\n",
"\n",
"train_patterns = tl.log_forward_pass(model, train_data)\n",
- "test_patterns = tl.log_forward_pass(model, test_data)\n",
+ "test_patterns = tl.log_forward_pass(model, test_data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "execution": {}
+ },
+ "outputs": [],
+ "source": [
+ "# @title Execute to see the widget!\n",
+ "\n",
+ "skip_cell = False\n",
+ "\n",
+ "try:\n",
+ " matplotlib.use('ipympl')\n",
+ " print('Switched to interactive backend')\n",
+ "except:\n",
+ " print('Could not switch to interactive backend. Please see Setup instructions at the top of the notebook.')\n",
+ " skip_cell = True\n",
"\n",
"out = widgets.Output()\n",
"with plt.ioff():\n",
@@ -1627,24 +1674,26 @@
"\n",
"\n",
"w = widgets.interactive(update_and_visualize,\n",
- " layer = widgets.Dropdown(\n",
- " options=['conv1', 'conv2', 'dropout1', 'fc1', 'dropout2', 'fc2'],\n",
- " value='fc2',\n",
- " description='Layer',\n",
- " disabled=False,\n",
- " layout = widgets.Layout(margin='40px 10px 0 0')),\n",
- " rating1 = widgets.FloatSlider(description='Rating', value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='50px 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating2 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating3 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating4 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating5 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating6 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating7 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating8 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating9 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating10 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'})\n",
- " )\n",
- "widgets.HBox([w, fig.canvas], layout=widgets.Layout(width='100%', display='flex', align_items='stretch'))"
+ " layer = widgets.Dropdown(\n",
+ " options=['conv1', 'conv2', 'dropout1', 'fc1', 'dropout2', 'fc2'],\n",
+ " value='fc2',\n",
+ " description='Layer',\n",
+ " disabled=False,\n",
+ " layout = widgets.Layout(margin='40px 10px 0 0')),\n",
+ " rating1 = widgets.FloatSlider(description='Rating', value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='50px 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating2 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating3 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating4 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating5 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating6 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating7 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating8 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating9 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating10 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'})\n",
+ " )\n",
+ "\n",
+ "if not skip_cell:\n",
+ " display(widgets.HBox([w, fig.canvas], layout=widgets.Layout(width='100%', display='flex', align_items='stretch')))"
]
},
{
@@ -1667,14 +1716,18 @@
},
"source": [
"---\n",
- "# Summary\n",
+ "# The Big Picture\n",
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
"In this tutorial, we explored a foundational issue in artificial intelligence: how can we understand how a system generalizes to new inputs that it wasn’t trained on? We did this through the lens of representational similarity analysis (RSA), the characterization of a system based on the pattern of dissimilarities among a set of stimuli. This tutorial has two parts:\n",
"\n",
"- In part 1, you used RSA to understand how networks trained in different ways handle adversarial stimuli designed to fool the network.\n",
- "- In part 2, you used an interactive widget to understand how a network’s response to a new stimulus depends on its similarity to training stimuli."
+ "- In part 2, you used an interactive widget to understand how a network’s response to a new stimulus depends on its similarity to training stimuli.\n",
+ "\n",
+ "The theme of today is tying together **similarity measures** with **generalization**. If you've struggled in any part with the material so far, the key message to take away from this tutorial is that it makes sense that a system which is trained on a specific distribution has an easier time to generalize to new inputs that look very similar to what was seen during training. If you had a cat-dog classifier and later input images that were **dissimilar** to the images used in training, this would be more difficult for the model to generalize to. It's a conceptually simple idea, but until now resisted a comprehensive set of methods to quantify and demonstrate this mathematically.\n",
+ "\n",
+ "With that said - Let's move on to Tutorial 2!"
]
}
],
@@ -1708,7 +1761,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial2.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial2.ipynb
index dc8ba63f9..d788fd7be 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial2.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial2.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ Hossein Adeli\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy"
]
},
{
@@ -1024,7 +1024,7 @@
"execution": {}
},
"source": [
- "We are going to use the same models, standard and adversarially trained, as in the previous tutorial."
+ "We are going to use the same models as in the previous tutorial, namely (i) the standard model and (ii) the adversarially trained model."
]
},
{
@@ -1041,7 +1041,7 @@
"args = build_args()\n",
"train_loader, test_loader = fetch_dataloaders(args)\n",
"path = \"standard_model.pth\"\n",
- "model = torch.load(path, map_location=args.device)"
+ "model = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1073,7 +1073,7 @@
"execution": {}
},
"source": [
- "First, let's look at the computational steps in the model."
+ "First, let's look at the computational steps in the model. Specifically, this step relates to the layer sequences in the model, e.g. convolutional layers, activation functions, fully connected layers, max pooling. These computational steps differentially affect the representations as the input passes through (and is transformed by) via the model structure."
]
},
{
@@ -1131,7 +1131,7 @@
"execution": {}
},
"source": [
- "Now, for each of the test images, we have the model features from each layer of the trained network. We can look at how the representational (dis)similarity between these samples changes across the layers of the network. For each layer, we flatten the features so that each test image is represented by a vector (i.e., a point in space), and then we use the `rsatooblox` to compute the Euclidean distances between the points in that representational space.\n",
+ "For each of the test images, we have the model features from each layer of the trained network. We can look at how the representational (dis)similarity between these samples changes across the layers of the network. For each layer, we flatten the features so that each test image is represented by a vector (i.e., a point in space), and then we use the `rsatooblox` to compute the Euclidean distances between the points in that representational space.\n",
"\n",
"The result is a matrix of distances between each pair of samples for each layer."
]
@@ -1144,7 +1144,7 @@
"source": [
"We can now plot the RDMs for the input pixels, the two convolutional, and the two fully connected layers below.\n",
"\n",
- "The RDMs are of dimension `#stimuli` by `#stimuli`. The brighter cells show smaller dissimilarity between the two stimuli. The diagonal has a dissimilarity of 0, as each image is maximally similar to itself.\n",
+ "The RDMs are of dimension `n_stimuli` by `n_stimuli`. The brighter cells show smaller dissimilarity between the two stimuli. The diagonal has a dissimilarity of 0, as each image is maximally similar to itself.\n",
"\n",
"Since the instances of each category are next to each other, the brighter blocks emerging around the diagonal show that the representations for category instances become similar to one another across the levels of the hierarchy."
]
@@ -1175,7 +1175,7 @@
"execution": {}
},
"source": [
- "Computation in the network can be understood as the transformation of representational geometries. As we see in the above RDMs, the geometry captures how the representation in later layers becomes more similar for instances of the same category since this model was trained for classification."
+ "Computation in the network can be understood as the transformation of representational geometries as input is transformed into output through the neural network. As we see in the above RDMs, the geometry captures how the representation in later layers becomes more similar for instances of the same category since this model was trained for classification."
]
},
{
@@ -1188,9 +1188,9 @@
"\n",
"Another way to examine the geometry of the representation changing across layers is by reducing the dimensionality and displaying the samples in 2D space.\n",
"\n",
- "The representational spaces of each layer of a deep network project each stimulus to a point in a very high-dimensional space. Methods such as PCA (principal component analysis), MDS (Multidimensional_scaling), and t-SNE (t-distributed stochastic neighbor embedding) attempt to capture the same geometry in lower dimensions.\n",
+ "The representational spaces of each layer of a deep network project each stimulus to a point in a very high-dimensional space. Methods such as PCA (principal component analysis), MDS (Multi-dimensional Scaling), and t-SNE (t-distributed stochastic neighbor embedding) attempt to preserve the same geometry that exists in higher dimensions, in lower dimensions.\n",
"\n",
- "Below, we look at the representational geometry of the same network layers using these methods."
+ "Below, we look at the representational geometry of corresponding network layers using these dimensionality reduction methods."
]
},
{
@@ -1199,9 +1199,9 @@
"execution": {}
},
"source": [
- "For this visualization, we take 500 samples from the test set and color-code them based on their category. The figure below shows the scatter plots for these samples across the layers of the network. Each panel is the 2D projection of the feature representations using a specific method.\n",
+ "For this visualization, we take 500 samples from the test set and color-code them based on their category (class label). The figure below shows the scatter plots for these samples across the layers of the network. Each panel is the 2D projection of the feature representations using a specific dimensionality reduction method method (PCA, MDS, t-SNE).\n",
"\n",
- "The methods all show that image representations progressively cluster based on category across the layers of the network."
+ "The methods all show that image representations progressively cluster based on category across the layers of the network as the input is transformed into the output prediction."
]
},
{
@@ -1233,19 +1233,19 @@
"source": [
"## Representational path: from comparing representations to comparing RDMs\n",
"\n",
- "We can now go a step further and think of the computational steps in a network as steps in a path in the space of representational geometries. Each layer in the network is changing the geometry to make it more and more like the desired geometry, which are the labels for a network trained for classification. What we'll do is to *[compare RDM matrices](https://rsatoolbox.readthedocs.io/en/stable/comparing.html)* across layers. \n",
+ "We can now go a step further and think of the computational steps in a network as steps in a path in the space of representational geometries. Each layer in the network is changing the geometry to make it more and more like the desired geometry, which is a highly similar set of relationships among items from the same class/category, but each distinct from other classes. What we'll do is to *[compare RDM matrices](https://rsatoolbox.readthedocs.io/en/stable/comparing.html)* across layers.\n",
"\n",
"## Comparing RDMs\n",
"\n",
- "The first step, as before, is to calculate RDMs based on the Euclidean distances between the representations of images for each layer. We stack these RDMs into a 3d tensor:\n",
+ "The first step, as before, is to calculate RDMs based on the Euclidean distances between the representations of images for each layer. We stack these 2D RDMs into a 3D tensor:\n",
"\n",
"$$M(\\text{layer i}, \\text{image j}, \\text{image k})$$\n",
"\n",
- "The next step is to quantify how the geometries change across the layers. To perform this operation, we first flatten the RDMs to obtain a 2d matrix:\n",
+ "The next step is to quantify how the geometries change across the layers. To perform this operation, we first flatten the RDMs to obtain a 2D matrix:\n",
"\n",
"$$\\hat M(\\text{layer i}, \\text{image j x image k})$$\n",
"\n",
- "We can then calculate the cosine similarity between the different rows of this new matrix. By taking the arccos of this measure, we obtain a proper distance between representational geometries of different layers (Williams et al. 2021). So, we are now creating an RDM matrix of the RDM matrices! The resulting matrix has the dimensions of `#layers` by `#layers`.\n",
+ "We can then calculate the cosine similarity between the different rows of this new matrix. By taking the *arccos* (arc cosine, or the *inverse cosine function*) of this measure, we obtain a proper distance between representational geometries of different layers (Williams et al. 2021). So, we are now creating an RDM between the existing RDMs! The resulting matrix has the dimensions of `n_layers` by `n_layers`.\n",
"\n",
"The last step to visualize the path is to embed the distances between the geometries in a lower dimensional space. We use MDS to reduce the dimensions to 2 in order to show each computational step as a point in a 2D space."
]
@@ -1291,11 +1291,11 @@
"\n",
"The figure on the right shows the embedding of these distances in a 2D space using MDS. This figure captures the same distances between the layers shown in the matrix on the left in a 2D space. Connecting the steps starting from the input (shown as the black square) forms a path in the space of representional geometries. The last step in this path (the softmax layer) is similar to the embedding of the labels (denoted by an asterisk) in the same 2D space.\n",
"\n",
- "There are a few things to note about the path: \n",
+ "There are a few things to note about the path:\n",
"\n",
- "1. The path is generally smooth. This shows that each layer only slightly changes the representations, which we could also observe from the distance matrix on the left. \n",
- "2. The distance between the representational geometry of the input and the labels is large, but we see these two points somewhat close to each other in the path. This is partly because MDS tries to optimize to capture all the distances in the 2D space; they cannot all be veridically captured, and a stronger curvature is introduced to the path. Therefore, details of the curvature in 2D should be interpreted with caution. \n",
- "3. The size of the steps is generally informative. For example, we see in the left matrix that there is a big change in the representational geometry going from the convolutional to the fully connected layers, corresponding to a larger step in the path.\n",
+ "1. The path is generally smooth. This shows that each layer only slightly changes the representations, which we could also observe from the distance matrix on the left.\n",
+ "2. The distance between the representational geometry of the input and the labels is large, but we see these two points somewhat close to each other in the path. This is partly because MDS tries to optimize to capture all the distances in the 2D space; they cannot all be veridically captured, and a stronger curvature is introduced to the path. Therefore, details of the curvature in 2D should be interpreted with caution.\n",
+ "3. The size of the steps is generally informative. For example, we see in the left matrix that there is a big change in the representational geometry going from the convolutional to the fully connected layers, corresponding to a larger step in the path in the figure on the right.\n",
"\n",
"Thus, the computation performed by this model is a path from the geometry of the input pixels to the geometry of the ground truth labels."
]
@@ -1356,7 +1356,7 @@
"source": [
"## Adversarial images\n",
"\n",
- "Adversarial images pose a generalization challenge to DNNs. Here, we examine how this challenge and lower performance are reflected in the representational geometry path. First, we use FGSM (Fast Gradient Sign method) to generate adversarial images for the trained feedforward model. This is a \"white-box\" attack that uses the gradients from the model loss with respect to an image to adjust the image in order to maximize the loss. "
+ "Adversarial images pose a generalization challenge to DNNs. Here, we examine how this challenge and lower performance are reflected in the representational geometry path. First, we use FGSM (Fast Gradient Sign method) to generate adversarial images for the trained feedforward model. This is a \"white-box\" attack that uses the gradients from the model loss with respect to an image to adjust the image in order to maximize the loss."
]
},
{
@@ -1412,7 +1412,7 @@
"# @title Grab an adversarially robust model\n",
"\n",
"path = \"adversarial_model.pth\"\n",
- "model_robust = torch.load(path, map_location=args.device)"
+ "model_robust = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1584,7 +1584,7 @@
"source": [
"When we use adversarial images, the two networks are taking diverging paths in the space of representational geometries. Only the robust model's representation converges towards the embedding of the labels.\n",
"\n",
- "We can see from this example that these paths depend on the chosen stimuli!"
+ "We can see from this example that these paths depend on the chosen stimuli. Try to recall what we looked at in the last tutorial. When images were more similar, there was better generalization. The standard model, when given adversarial inputs, is fooled and struggles to map correctly and complete the path in the rightmost figures above."
]
},
{
@@ -1689,221 +1689,16 @@
},
"source": [
"---\n",
- "# Summary\n",
+ "# The Big Picture\n",
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
"In this tutorial, we:\n",
"\n",
- "- Characterized the computation that happens across different layers of a network as a path, with each step changing the geometry of the representation to go from input pixels to target labels,\n",
- "- Examined the paths for different model architectures and different inputs and learned how to interpret them."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "---\n",
- "# Bonus Section: Representational geometry of recurrent models\n",
+ "- Characterized the computation that happens across different layers of a network as a path, with each step changing the geometry of the representation to go from input pixels to target labels\n",
+ "- Examined the representational geometry paths for different model architectures and different inputs and learned how to interpret them\n",
"\n",
- "Transformations of representations can occur across space and time, e.g., layers of a neural network and steps of recurrent computation."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Just as the layers in a feedforward DNN can change the representational geometry to perform a task, steps in a recurrent network can reuse the same layer to reach the same computational depth.\n",
- "\n",
- "In this section, we look at a very simple recurrent network with only 2650 trainable parameters."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Here is a diagram of this network:\n",
- "\n",
- ""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "execution": {}
- },
- "outputs": [],
- "source": [
- "# @title Grab a recurrent model\n",
- "\n",
- "args = build_args()\n",
- "train_loader, test_loader = fetch_dataloaders(args)\n",
- "path = \"recurrent_model.pth\"\n",
- "model_recurrent = torch.load(path, map_location=args.device)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "
We can first look at the computational steps in this network. As we see below, the `conv2` operation is repeated for 5 times."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "train_nodes, _ = get_graph_node_names(model_recurrent)\n",
- "print('The computational steps in the network are: \\n', train_nodes)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Plotting the RDMs after each application of the `conv2` operation shows the same progressive emergence of the blockwise structure around the diagonal, mediating the correct classification in this task."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=20)\n",
- "return_layers = ['conv2', 'conv2_1', 'conv2_2', 'conv2_3', 'conv2_4']\n",
- "model_features = extract_features(model_recurrent, imgs.to(device), return_layers)\n",
- "\n",
- "rdms, rdms_dict = calc_rdms(model_features)\n",
- "plot_rdms(rdms_dict)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can also look at how the different dimensionality reduction techniques capture the dynamics of changing geometry."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "return_layers = ['conv2', 'conv2_1', 'conv2_2', 'conv2_3', 'conv2_4']\n",
- "\n",
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features = extract_features(model_recurrent, imgs.to(device), return_layers)\n",
- "\n",
- "plot_dim_reduction(model_features, labels, transformer_funcs =['PCA', 'MDS', 't-SNE'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "## Representational geometry paths for recurrent models"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can look at the model's recurrent computational steps as a path in the representational geometry space."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features_recurrent = extract_features(model_recurrent, imgs.to(device), return_layers='all')\n",
- "\n",
- "#rdms, rdms_dict = calc_rdms(model_features)\n",
- "features = {'recurrent model': model_features_recurrent}\n",
- "model_colors = {'recurrent model': 'y'}\n",
- "\n",
- "rep_path(features, model_colors, labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can also look at the paths taken by the feedforward and the recurrent models and compare them."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features = extract_features(model, imgs.to(device), return_layers='all')\n",
- "model_features_recurrent = extract_features(model_recurrent, imgs.to(device), return_layers='all')\n",
- "\n",
- "features = {'feedforward model': model_features, 'recurrent model': model_features_recurrent}\n",
- "model_colors = {'feedforward model': 'b', 'recurrent model': 'y'}\n",
- "\n",
- "rep_path(features, model_colors, labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "What we can see here is that different models can take very different paths in the space of representational geometries to map images to labels. This is because there exist many different functional mappings to do a classification task."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "execution": {}
- },
- "outputs": [],
- "source": [
- "# @title Submit your feedback\n",
- "content_review(f\"{feedback_prefix}_recurrent_models\")"
+ "We used this method to examine how models trained on adversarial stimulu (vs control) differentially treat inputs that are both normal and adversarial. We saw that the category / class level similarity structure, which was different for the standard model on adversarial stimuli, resulting in lower accuracies, actually has a divergent path during the conversion from input data to output labels. This is another link into the idea of **similarity** as a lens that helps us understand **generalization**."
]
}
],
@@ -1937,7 +1732,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial3.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial3.ipynb
index 8924750d4..368761ca9 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial3.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/instructor/W1D3_Tutorial3.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ Veronica Bossio, Eivinas Butkus, Jasper van den Bosch\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault\n"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy\n"
]
},
{
@@ -2364,6 +2364,17 @@
"\n",
"5. Addressed two sources of model-performance estimation error that statistical inference must account for in addition to the error due to measurement noise: stimulus sampling and subject sampling, using the 2-factor bootstrap method."
]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "execution": {}
+ },
+ "source": [
+ "# The Big Picture\n",
+ "\n",
+ "Generalization can arise across multiple dimensions, whether that be generalization of an experiment to a new set of subjects or whether a new set of stimuli generalize to the same subjects. More likely, we would want to know how much an experiment would generalize to novel subjects across novel stimuli. We can test these ideas statistically by using the bootstreap method in statistics. This notebook highlights some issues with naive approaches to statistics when assessing generalization in this way. We explored the 2-factor bootstrap method and used a toolbox that explicitly takes care of the calculation so that we don't overestimate the variance involved. It's important to be aware of factors of generalization and how multiple overlapping factors might interact."
+ ]
}
],
"metadata": {
@@ -2394,7 +2405,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Intro.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Intro.ipynb
index 677b0c1a2..cc307c389 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Intro.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Intro.ipynb
@@ -61,7 +61,9 @@
"\n",
"Though it is not required, you will benefit from these tutorials more if you have had the opportunity to take linear algebra and multivariate statistics courses in the past, as most of the computations and expressed ideas use the tools from these disciplines.\n",
"\n",
- "Note that the intro video for this day is longer than usual; it is the only day that has this property - feel free to return to it later if you don't have enough time before taking the tutorials."
+ "The intro video for this day in the last iteration of the course (2024) was relatively long and for this year (2025) it has been split into two videos. Please watch the video below to guide you through the tutorials. The additional part of the video is available in the first bonus tutorial (Tutorial 4), which goes into more detail related to the topics you will cover if you complete Tutorials 1-3. You are also kindly advised to revisit the bonus tutorials in your own time. We have added a new (non-Bonus) tutorial all about the exciting topic of Dynamic Similarity Analysis in this year's iteration of the course. This complements the topic of geometric (spatial) similarity with further similarity measures that take into account temporal information.\n",
+ "\n",
+ "We hope you enjoy the topics today. We'll now move on to Heiko and Niko to guide you through today's material."
]
},
{
@@ -123,7 +125,7 @@
" return tab_contents\n",
"\n",
"\n",
- "video_ids = [('Youtube', 'RGOB0LRnLME'), ('Bilibili', 'BV1Kb421p77j')]\n",
+ "video_ids = [('Youtube', 'a66lOCB4oFw'), ('Bilibili', 'BV12eVjz3EbK')]\n",
"tab_contents = display_videos(video_ids, W=854, H=480)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
@@ -208,7 +210,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial1.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial1.ipynb
index e4d876a98..f1c4d52a6 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial1.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial1.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ JohnMark Taylor & Zhuofan Josh Ying\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault\n"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy\n"
]
},
{
@@ -41,7 +41,7 @@
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
- "Welcome to Tutorial 1 on Generalization and Representational Geometry. Building on the Intro Lecture, this tutorial aims to help you get your feet wet and characterize representations in terms of their geometry, as captured by the distances among inputs at different stages of processing. The key point of this tutorial is that the way machine learning models generalize depends on how they represent their inputs and, in particular, on how similar a new input is to other inputs in the internal representations. We focus on linear models, where the close relationship between similarity and generalization can be most easily understood from an intuitive and mathematical perspective. \n",
+ "Welcome to Tutorial 1 on Generalization and Representational Geometry. Building on the Intro Lecture, this tutorial aims to help you get your feet wet and characterize representations in terms of their geometry, as captured by the distances among inputs at different stages of processing. **The key point of this tutorial is that the way machine learning models generalize depends on how they represent their inputs and, in particular, on how similar a new input is to other inputs in the internal representations**. We focus on linear models, where the close relationship between similarity and generalization can be most easily understood from an intuitive and mathematical perspective.\n",
"\n",
"By the end of this tutorial, you will:\n",
"\n",
@@ -89,17 +89,28 @@
},
"source": [
"---\n",
- "# Setup\n",
- "\n"
+ "# Setup (Please Read)\n",
+ "\n",
+ "This tutorial involves an interactive component as the final element, which depends on some tools that have undergone a change since the last time this course was run (2024). Therefore, if you're running this on Google Colab, you need to:\n",
+ "\n",
+ "* uncomment the cell below\n",
+ "* run the cell\n",
+ "\n",
+ "This will initially cause the notebook to restart after a few moments, but then you can run all the cells **below** (in Google Colab: *Runtime -> Run Cell and Below*) as necessary and the intended functionally will exist. As of the time of writing, this issue has not been corrected and this workaround is required. Please wait until you see the notification of the notebook having restarted before selecting the 'Run all and below' menu option.\n",
+ "\n",
+ "Additionally, this tutorial uses `Graphviz` for additional visualizations. For proper execution of the functions, you should install it locally. Please visit this [link](https://graphviz.org/download/) to get the instructions depending on your local OS configurations (if you are running this notebook on Google Colab, just skip it)."
]
},
{
- "cell_type": "markdown",
+ "cell_type": "code",
+ "execution_count": null,
"metadata": {
"execution": {}
},
+ "outputs": [],
"source": [
- "This tutorial uses `Graphviz` for additional visualizations. For proper execution of the functions, you should install it locally. Please visit this [link](https://graphviz.org/download/) to get the instructions depending on your local OS configurations (if you are running this notebook on Google Colab, just skip it)."
+ "#!pip install -q ipympl\n",
+ "#get_ipython().kernel.do_shutdown(restart=True)"
]
},
{
@@ -119,7 +130,22 @@
"\n",
"# To install jupyter-matplotlib (ipympl) via pip\n",
"!pip install -q torchlens\n",
- "!pip install -q ipympl ipywidgets matplotlib numpy scikit-learn torch torchvision rsatoolbox scipy\n",
+ "!pip install -q numpy scikit-learn torch torchvision scipy matplotlib\n",
+ "\n",
+ "# Import future dependencies first\n",
+ "from __future__ import print_function\n",
+ "\n",
+ "# Configure matplotlib ONCE before any other matplotlib imports\n",
+ "import matplotlib\n",
+ "%matplotlib inline\n",
+ "\n",
+ "# Now import pyplot AFTER setting the backend\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "# Configure other matplotlib settings\n",
+ "plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = False\n",
+ "plt.rcParams['xtick.top'] = plt.rcParams['xtick.labeltop'] = True\n",
+ "plt.style.use(\"https://raw.githubusercontent.com/NeuromatchAcademy/course-content/main/nma.mplstyle\")\n",
"\n",
"\n",
"# To install jupyter-matplotlib via conda (comment out if you are not using conda)\n",
@@ -182,7 +208,6 @@
"\n",
"# Third-party library imports\n",
"import numpy as np\n",
- "import matplotlib.pyplot as plt\n",
"from matplotlib.offsetbox import AnnotationBbox, OffsetImage\n",
"from scipy import stats\n",
"import ipywidgets as widgets\n",
@@ -204,10 +229,7 @@
"# rsatoolbox imports\n",
"import rsatoolbox\n",
"from rsatoolbox.data import Dataset\n",
- "from rsatoolbox.rdm.calc import calc_rdm\n",
- "\n",
- "# Jupyter-specific imports\n",
- "%matplotlib inline"
+ "from rsatoolbox.rdm.calc import calc_rdm"
]
},
{
@@ -989,7 +1011,7 @@
"args = build_args()\n",
"train_loader, test_loader = fetch_dataloaders(args)\n",
"path = \"standard_model.pth\"\n",
- "model = torch.load(path, map_location=args.device)"
+ "model = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1041,7 +1063,7 @@
"* $\\text{image}$ is the original input image\n",
"* $\\epsilon$ is the magnitude of the perturbation\n",
"* $\\nabla_{\\text{image}} J(\\text{image}, \\text{true label})$ is the gradient of the loss with respect to the input image\n",
- "* $\\text{sign}(\\cdot)$ returns the sign of the argument"
+ "* $\\text{sign}(\\cdot)$ returns the sign of the argument (either `1` or `-1`)"
]
},
{
@@ -1067,6 +1089,15 @@
"plt.axis('off')"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "execution": {}
+ },
+ "source": [
+ "As you can see, the black background from the **clean** and original images has changed a bit to include intricate patterns that do not affect our interpretation of what digit is being represented overall (because humans focus on the white pixels that make up the MNIST digit). However, this new fluctuating pattern in the background of each image will demonstrate that the models can be tricked into **perceiving** different digits based on this background pattern. This is the issue we want to solve when training an *adversarially robust* model."
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {
@@ -1109,7 +1140,7 @@
"# @title Grab an adversarially pretrained model\n",
"\n",
"path = \"adversarial_model.pth\"\n",
- "model_robust = torch.load(path, map_location=args.device)"
+ "model_robust = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1172,7 +1203,7 @@
"execution": {}
},
"source": [
- "You should observe that the adversarially trained model has much higher accuracy on the adversarial images."
+ "You should observe that the adversarially trained model has much higher accuracy on the adversarial images (98%) than the standard model does (60%) on the same images."
]
},
{
@@ -1292,6 +1323,8 @@
"execution": {}
},
"source": [
+ "Is it clear to you that the block associated with indices 20-25 in the figure above represent similarity of the 5th class label (the digit '4')? If not, go back and re-read the paragraphs above and take care to relate the block structure to the 5 samples per image class.\n",
+ "\n",
"Now we compute and visualize the RDMs for **standard images** for both the standard model and the adversarially trained model."
]
},
@@ -1507,17 +1540,10 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
- "cellView": "form",
"execution": {}
},
"outputs": [],
"source": [
- "# @title Execute to see the widget!\n",
- "%matplotlib widget\n",
- "\n",
- "plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = False\n",
- "plt.rcParams['xtick.top'] = plt.rcParams['xtick.labeltop'] = True\n",
- "\n",
"transform=transforms.Compose([\n",
" transforms.ToTensor(),\n",
" transforms.Normalize((0.1307,), (0.3081,))\n",
@@ -1534,7 +1560,28 @@
"test_data = torch.stack([test_dataset[i][0] for i in range(num_test_samples)])\n",
"\n",
"train_patterns = tl.log_forward_pass(model, train_data)\n",
- "test_patterns = tl.log_forward_pass(model, test_data)\n",
+ "test_patterns = tl.log_forward_pass(model, test_data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "execution": {}
+ },
+ "outputs": [],
+ "source": [
+ "# @title Execute to see the widget!\n",
+ "\n",
+ "skip_cell = False\n",
+ "\n",
+ "try:\n",
+ " matplotlib.use('ipympl')\n",
+ " print('Switched to interactive backend')\n",
+ "except:\n",
+ " print('Could not switch to interactive backend. Please see Setup instructions at the top of the notebook.')\n",
+ " skip_cell = True\n",
"\n",
"out = widgets.Output()\n",
"with plt.ioff():\n",
@@ -1608,24 +1655,26 @@
"\n",
"\n",
"w = widgets.interactive(update_and_visualize,\n",
- " layer = widgets.Dropdown(\n",
- " options=['conv1', 'conv2', 'dropout1', 'fc1', 'dropout2', 'fc2'],\n",
- " value='fc2',\n",
- " description='Layer',\n",
- " disabled=False,\n",
- " layout = widgets.Layout(margin='40px 10px 0 0')),\n",
- " rating1 = widgets.FloatSlider(description='Rating', value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='50px 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating2 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating3 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating4 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating5 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating6 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating7 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating8 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating9 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
- " rating10 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'})\n",
- " )\n",
- "widgets.HBox([w, fig.canvas], layout=widgets.Layout(width='100%', display='flex', align_items='stretch'))"
+ " layer = widgets.Dropdown(\n",
+ " options=['conv1', 'conv2', 'dropout1', 'fc1', 'dropout2', 'fc2'],\n",
+ " value='fc2',\n",
+ " description='Layer',\n",
+ " disabled=False,\n",
+ " layout = widgets.Layout(margin='40px 10px 0 0')),\n",
+ " rating1 = widgets.FloatSlider(description='Rating', value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='50px 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating2 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating3 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating4 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating5 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating6 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating7 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating8 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating9 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'}),\n",
+ " rating10 = widgets.FloatSlider(description='Rating',value=5, min=1, max=10, step=1, layout = widgets.Layout(margin='0 0 43px 0'), style={'font_weight': 'bold'})\n",
+ " )\n",
+ "\n",
+ "if not skip_cell:\n",
+ " display(widgets.HBox([w, fig.canvas], layout=widgets.Layout(width='100%', display='flex', align_items='stretch')))"
]
},
{
@@ -1648,14 +1697,18 @@
},
"source": [
"---\n",
- "# Summary\n",
+ "# The Big Picture\n",
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
"In this tutorial, we explored a foundational issue in artificial intelligence: how can we understand how a system generalizes to new inputs that it wasn’t trained on? We did this through the lens of representational similarity analysis (RSA), the characterization of a system based on the pattern of dissimilarities among a set of stimuli. This tutorial has two parts:\n",
"\n",
"- In part 1, you used RSA to understand how networks trained in different ways handle adversarial stimuli designed to fool the network.\n",
- "- In part 2, you used an interactive widget to understand how a network’s response to a new stimulus depends on its similarity to training stimuli."
+ "- In part 2, you used an interactive widget to understand how a network’s response to a new stimulus depends on its similarity to training stimuli.\n",
+ "\n",
+ "The theme of today is tying together **similarity measures** with **generalization**. If you've struggled in any part with the material so far, the key message to take away from this tutorial is that it makes sense that a system which is trained on a specific distribution has an easier time to generalize to new inputs that look very similar to what was seen during training. If you had a cat-dog classifier and later input images that were **dissimilar** to the images used in training, this would be more difficult for the model to generalize to. It's a conceptually simple idea, but until now resisted a comprehensive set of methods to quantify and demonstrate this mathematically.\n",
+ "\n",
+ "With that said - Let's move on to Tutorial 2!"
]
}
],
@@ -1689,7 +1742,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial2.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial2.ipynb
index dc8ba63f9..d788fd7be 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial2.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial2.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ Hossein Adeli\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy"
]
},
{
@@ -1024,7 +1024,7 @@
"execution": {}
},
"source": [
- "We are going to use the same models, standard and adversarially trained, as in the previous tutorial."
+ "We are going to use the same models as in the previous tutorial, namely (i) the standard model and (ii) the adversarially trained model."
]
},
{
@@ -1041,7 +1041,7 @@
"args = build_args()\n",
"train_loader, test_loader = fetch_dataloaders(args)\n",
"path = \"standard_model.pth\"\n",
- "model = torch.load(path, map_location=args.device)"
+ "model = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1073,7 +1073,7 @@
"execution": {}
},
"source": [
- "First, let's look at the computational steps in the model."
+ "First, let's look at the computational steps in the model. Specifically, this step relates to the layer sequences in the model, e.g. convolutional layers, activation functions, fully connected layers, max pooling. These computational steps differentially affect the representations as the input passes through (and is transformed by) via the model structure."
]
},
{
@@ -1131,7 +1131,7 @@
"execution": {}
},
"source": [
- "Now, for each of the test images, we have the model features from each layer of the trained network. We can look at how the representational (dis)similarity between these samples changes across the layers of the network. For each layer, we flatten the features so that each test image is represented by a vector (i.e., a point in space), and then we use the `rsatooblox` to compute the Euclidean distances between the points in that representational space.\n",
+ "For each of the test images, we have the model features from each layer of the trained network. We can look at how the representational (dis)similarity between these samples changes across the layers of the network. For each layer, we flatten the features so that each test image is represented by a vector (i.e., a point in space), and then we use the `rsatooblox` to compute the Euclidean distances between the points in that representational space.\n",
"\n",
"The result is a matrix of distances between each pair of samples for each layer."
]
@@ -1144,7 +1144,7 @@
"source": [
"We can now plot the RDMs for the input pixels, the two convolutional, and the two fully connected layers below.\n",
"\n",
- "The RDMs are of dimension `#stimuli` by `#stimuli`. The brighter cells show smaller dissimilarity between the two stimuli. The diagonal has a dissimilarity of 0, as each image is maximally similar to itself.\n",
+ "The RDMs are of dimension `n_stimuli` by `n_stimuli`. The brighter cells show smaller dissimilarity between the two stimuli. The diagonal has a dissimilarity of 0, as each image is maximally similar to itself.\n",
"\n",
"Since the instances of each category are next to each other, the brighter blocks emerging around the diagonal show that the representations for category instances become similar to one another across the levels of the hierarchy."
]
@@ -1175,7 +1175,7 @@
"execution": {}
},
"source": [
- "Computation in the network can be understood as the transformation of representational geometries. As we see in the above RDMs, the geometry captures how the representation in later layers becomes more similar for instances of the same category since this model was trained for classification."
+ "Computation in the network can be understood as the transformation of representational geometries as input is transformed into output through the neural network. As we see in the above RDMs, the geometry captures how the representation in later layers becomes more similar for instances of the same category since this model was trained for classification."
]
},
{
@@ -1188,9 +1188,9 @@
"\n",
"Another way to examine the geometry of the representation changing across layers is by reducing the dimensionality and displaying the samples in 2D space.\n",
"\n",
- "The representational spaces of each layer of a deep network project each stimulus to a point in a very high-dimensional space. Methods such as PCA (principal component analysis), MDS (Multidimensional_scaling), and t-SNE (t-distributed stochastic neighbor embedding) attempt to capture the same geometry in lower dimensions.\n",
+ "The representational spaces of each layer of a deep network project each stimulus to a point in a very high-dimensional space. Methods such as PCA (principal component analysis), MDS (Multi-dimensional Scaling), and t-SNE (t-distributed stochastic neighbor embedding) attempt to preserve the same geometry that exists in higher dimensions, in lower dimensions.\n",
"\n",
- "Below, we look at the representational geometry of the same network layers using these methods."
+ "Below, we look at the representational geometry of corresponding network layers using these dimensionality reduction methods."
]
},
{
@@ -1199,9 +1199,9 @@
"execution": {}
},
"source": [
- "For this visualization, we take 500 samples from the test set and color-code them based on their category. The figure below shows the scatter plots for these samples across the layers of the network. Each panel is the 2D projection of the feature representations using a specific method.\n",
+ "For this visualization, we take 500 samples from the test set and color-code them based on their category (class label). The figure below shows the scatter plots for these samples across the layers of the network. Each panel is the 2D projection of the feature representations using a specific dimensionality reduction method method (PCA, MDS, t-SNE).\n",
"\n",
- "The methods all show that image representations progressively cluster based on category across the layers of the network."
+ "The methods all show that image representations progressively cluster based on category across the layers of the network as the input is transformed into the output prediction."
]
},
{
@@ -1233,19 +1233,19 @@
"source": [
"## Representational path: from comparing representations to comparing RDMs\n",
"\n",
- "We can now go a step further and think of the computational steps in a network as steps in a path in the space of representational geometries. Each layer in the network is changing the geometry to make it more and more like the desired geometry, which are the labels for a network trained for classification. What we'll do is to *[compare RDM matrices](https://rsatoolbox.readthedocs.io/en/stable/comparing.html)* across layers. \n",
+ "We can now go a step further and think of the computational steps in a network as steps in a path in the space of representational geometries. Each layer in the network is changing the geometry to make it more and more like the desired geometry, which is a highly similar set of relationships among items from the same class/category, but each distinct from other classes. What we'll do is to *[compare RDM matrices](https://rsatoolbox.readthedocs.io/en/stable/comparing.html)* across layers.\n",
"\n",
"## Comparing RDMs\n",
"\n",
- "The first step, as before, is to calculate RDMs based on the Euclidean distances between the representations of images for each layer. We stack these RDMs into a 3d tensor:\n",
+ "The first step, as before, is to calculate RDMs based on the Euclidean distances between the representations of images for each layer. We stack these 2D RDMs into a 3D tensor:\n",
"\n",
"$$M(\\text{layer i}, \\text{image j}, \\text{image k})$$\n",
"\n",
- "The next step is to quantify how the geometries change across the layers. To perform this operation, we first flatten the RDMs to obtain a 2d matrix:\n",
+ "The next step is to quantify how the geometries change across the layers. To perform this operation, we first flatten the RDMs to obtain a 2D matrix:\n",
"\n",
"$$\\hat M(\\text{layer i}, \\text{image j x image k})$$\n",
"\n",
- "We can then calculate the cosine similarity between the different rows of this new matrix. By taking the arccos of this measure, we obtain a proper distance between representational geometries of different layers (Williams et al. 2021). So, we are now creating an RDM matrix of the RDM matrices! The resulting matrix has the dimensions of `#layers` by `#layers`.\n",
+ "We can then calculate the cosine similarity between the different rows of this new matrix. By taking the *arccos* (arc cosine, or the *inverse cosine function*) of this measure, we obtain a proper distance between representational geometries of different layers (Williams et al. 2021). So, we are now creating an RDM between the existing RDMs! The resulting matrix has the dimensions of `n_layers` by `n_layers`.\n",
"\n",
"The last step to visualize the path is to embed the distances between the geometries in a lower dimensional space. We use MDS to reduce the dimensions to 2 in order to show each computational step as a point in a 2D space."
]
@@ -1291,11 +1291,11 @@
"\n",
"The figure on the right shows the embedding of these distances in a 2D space using MDS. This figure captures the same distances between the layers shown in the matrix on the left in a 2D space. Connecting the steps starting from the input (shown as the black square) forms a path in the space of representional geometries. The last step in this path (the softmax layer) is similar to the embedding of the labels (denoted by an asterisk) in the same 2D space.\n",
"\n",
- "There are a few things to note about the path: \n",
+ "There are a few things to note about the path:\n",
"\n",
- "1. The path is generally smooth. This shows that each layer only slightly changes the representations, which we could also observe from the distance matrix on the left. \n",
- "2. The distance between the representational geometry of the input and the labels is large, but we see these two points somewhat close to each other in the path. This is partly because MDS tries to optimize to capture all the distances in the 2D space; they cannot all be veridically captured, and a stronger curvature is introduced to the path. Therefore, details of the curvature in 2D should be interpreted with caution. \n",
- "3. The size of the steps is generally informative. For example, we see in the left matrix that there is a big change in the representational geometry going from the convolutional to the fully connected layers, corresponding to a larger step in the path.\n",
+ "1. The path is generally smooth. This shows that each layer only slightly changes the representations, which we could also observe from the distance matrix on the left.\n",
+ "2. The distance between the representational geometry of the input and the labels is large, but we see these two points somewhat close to each other in the path. This is partly because MDS tries to optimize to capture all the distances in the 2D space; they cannot all be veridically captured, and a stronger curvature is introduced to the path. Therefore, details of the curvature in 2D should be interpreted with caution.\n",
+ "3. The size of the steps is generally informative. For example, we see in the left matrix that there is a big change in the representational geometry going from the convolutional to the fully connected layers, corresponding to a larger step in the path in the figure on the right.\n",
"\n",
"Thus, the computation performed by this model is a path from the geometry of the input pixels to the geometry of the ground truth labels."
]
@@ -1356,7 +1356,7 @@
"source": [
"## Adversarial images\n",
"\n",
- "Adversarial images pose a generalization challenge to DNNs. Here, we examine how this challenge and lower performance are reflected in the representational geometry path. First, we use FGSM (Fast Gradient Sign method) to generate adversarial images for the trained feedforward model. This is a \"white-box\" attack that uses the gradients from the model loss with respect to an image to adjust the image in order to maximize the loss. "
+ "Adversarial images pose a generalization challenge to DNNs. Here, we examine how this challenge and lower performance are reflected in the representational geometry path. First, we use FGSM (Fast Gradient Sign method) to generate adversarial images for the trained feedforward model. This is a \"white-box\" attack that uses the gradients from the model loss with respect to an image to adjust the image in order to maximize the loss."
]
},
{
@@ -1412,7 +1412,7 @@
"# @title Grab an adversarially robust model\n",
"\n",
"path = \"adversarial_model.pth\"\n",
- "model_robust = torch.load(path, map_location=args.device)"
+ "model_robust = torch.load(path, map_location=args.device, weights_only=False)"
]
},
{
@@ -1584,7 +1584,7 @@
"source": [
"When we use adversarial images, the two networks are taking diverging paths in the space of representational geometries. Only the robust model's representation converges towards the embedding of the labels.\n",
"\n",
- "We can see from this example that these paths depend on the chosen stimuli!"
+ "We can see from this example that these paths depend on the chosen stimuli. Try to recall what we looked at in the last tutorial. When images were more similar, there was better generalization. The standard model, when given adversarial inputs, is fooled and struggles to map correctly and complete the path in the rightmost figures above."
]
},
{
@@ -1689,221 +1689,16 @@
},
"source": [
"---\n",
- "# Summary\n",
+ "# The Big Picture\n",
"\n",
"*Estimated timing of tutorial: 40 minutes*\n",
"\n",
"In this tutorial, we:\n",
"\n",
- "- Characterized the computation that happens across different layers of a network as a path, with each step changing the geometry of the representation to go from input pixels to target labels,\n",
- "- Examined the paths for different model architectures and different inputs and learned how to interpret them."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "---\n",
- "# Bonus Section: Representational geometry of recurrent models\n",
+ "- Characterized the computation that happens across different layers of a network as a path, with each step changing the geometry of the representation to go from input pixels to target labels\n",
+ "- Examined the representational geometry paths for different model architectures and different inputs and learned how to interpret them\n",
"\n",
- "Transformations of representations can occur across space and time, e.g., layers of a neural network and steps of recurrent computation."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Just as the layers in a feedforward DNN can change the representational geometry to perform a task, steps in a recurrent network can reuse the same layer to reach the same computational depth.\n",
- "\n",
- "In this section, we look at a very simple recurrent network with only 2650 trainable parameters."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Here is a diagram of this network:\n",
- "\n",
- ""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "execution": {}
- },
- "outputs": [],
- "source": [
- "# @title Grab a recurrent model\n",
- "\n",
- "args = build_args()\n",
- "train_loader, test_loader = fetch_dataloaders(args)\n",
- "path = \"recurrent_model.pth\"\n",
- "model_recurrent = torch.load(path, map_location=args.device)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "
We can first look at the computational steps in this network. As we see below, the `conv2` operation is repeated for 5 times."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "train_nodes, _ = get_graph_node_names(model_recurrent)\n",
- "print('The computational steps in the network are: \\n', train_nodes)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "Plotting the RDMs after each application of the `conv2` operation shows the same progressive emergence of the blockwise structure around the diagonal, mediating the correct classification in this task."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=20)\n",
- "return_layers = ['conv2', 'conv2_1', 'conv2_2', 'conv2_3', 'conv2_4']\n",
- "model_features = extract_features(model_recurrent, imgs.to(device), return_layers)\n",
- "\n",
- "rdms, rdms_dict = calc_rdms(model_features)\n",
- "plot_rdms(rdms_dict)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can also look at how the different dimensionality reduction techniques capture the dynamics of changing geometry."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "return_layers = ['conv2', 'conv2_1', 'conv2_2', 'conv2_3', 'conv2_4']\n",
- "\n",
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features = extract_features(model_recurrent, imgs.to(device), return_layers)\n",
- "\n",
- "plot_dim_reduction(model_features, labels, transformer_funcs =['PCA', 'MDS', 't-SNE'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "## Representational geometry paths for recurrent models"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can look at the model's recurrent computational steps as a path in the representational geometry space."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features_recurrent = extract_features(model_recurrent, imgs.to(device), return_layers='all')\n",
- "\n",
- "#rdms, rdms_dict = calc_rdms(model_features)\n",
- "features = {'recurrent model': model_features_recurrent}\n",
- "model_colors = {'recurrent model': 'y'}\n",
- "\n",
- "rep_path(features, model_colors, labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "We can also look at the paths taken by the feedforward and the recurrent models and compare them."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "execution": {}
- },
- "outputs": [],
- "source": [
- "imgs, labels = sample_images(test_loader, n=50) #grab 500 samples from the test set\n",
- "model_features = extract_features(model, imgs.to(device), return_layers='all')\n",
- "model_features_recurrent = extract_features(model_recurrent, imgs.to(device), return_layers='all')\n",
- "\n",
- "features = {'feedforward model': model_features, 'recurrent model': model_features_recurrent}\n",
- "model_colors = {'feedforward model': 'b', 'recurrent model': 'y'}\n",
- "\n",
- "rep_path(features, model_colors, labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "execution": {}
- },
- "source": [
- "What we can see here is that different models can take very different paths in the space of representational geometries to map images to labels. This is because there exist many different functional mappings to do a classification task."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "execution": {}
- },
- "outputs": [],
- "source": [
- "# @title Submit your feedback\n",
- "content_review(f\"{feedback_prefix}_recurrent_models\")"
+ "We used this method to examine how models trained on adversarial stimulu (vs control) differentially treat inputs that are both normal and adversarial. We saw that the category / class level similarity structure, which was different for the standard model on adversarial stimuli, resulting in lower accuracies, actually has a divergent path during the conversion from input data to output labels. This is another link into the idea of **similarity** as a lens that helps us understand **generalization**."
]
}
],
@@ -1937,7 +1732,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,
diff --git a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial3.ipynb b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial3.ipynb
index 703608e1c..4745fa409 100644
--- a/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial3.ipynb
+++ b/tutorials/W1D3_ComparingArtificialAndBiologicalNetworks/student/W1D3_Tutorial3.ipynb
@@ -23,9 +23,9 @@
"\n",
"__Content creators:__ Veronica Bossio, Eivinas Butkus, Jasper van den Bosch\n",
"\n",
- "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk\n",
+ "__Content reviewers:__ Samuele Bolotta, Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Patrick Mineault, Hlib Solodzhuk, Alex Murphy\n",
"\n",
- "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault\n"
+ "__Production editors:__ Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Patrick Mineault, Alex Murphy\n"
]
},
{
@@ -2353,6 +2353,17 @@
"\n",
"5. Addressed two sources of model-performance estimation error that statistical inference must account for in addition to the error due to measurement noise: stimulus sampling and subject sampling, using the 2-factor bootstrap method."
]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "execution": {}
+ },
+ "source": [
+ "# The Big Picture\n",
+ "\n",
+ "Generalization can arise across multiple dimensions, whether that be generalization of an experiment to a new set of subjects or whether a new set of stimuli generalize to the same subjects. More likely, we would want to know how much an experiment would generalize to novel subjects across novel stimuli. We can test these ideas statistically by using the bootstreap method in statistics. This notebook highlights some issues with naive approaches to statistics when assessing generalization in this way. We explored the 2-factor bootstrap method and used a toolbox that explicitly takes care of the calculation so that we don't overestimate the variance involved. It's important to be aware of factors of generalization and how multiple overlapping factors might interact."
+ ]
}
],
"metadata": {
@@ -2383,7 +2394,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.19"
+ "version": "3.9.22"
}
},
"nbformat": 4,