Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Open In Colab   Open in Kaggle

Tutorial 3: Attention

Week 1, Day 5: Microcircuits

By Neuromatch Academy

Content creators: Saaketh Medepalli, Aditya Singh, Saeed Salehi, Xaq Pitkow

Content reviewers: Yizhou Chen, RyeongKyung Yoon, Ruiyi Zhang, Lily Chamakura, Hlib Solodzhuk, Alex Murphy

Production editors: Konstantine Tsafatinos, Ella Batty, Spiros Chavlis, Samuele Bolotta, Hlib Solodzhuk, Alex Murphy


Tutorial Objectives

Estimated timing of tutorial: 1 hour

By the end of this tutorial, we aim to:

  1. Learn how the brain and AI systems implement attention

  2. Understand how multiplicative interactions allow flexible gating of information

  3. Demonstrate the inductive bias of the self-attention mechanism towards learning functions of sparse subsets of variables

A key microarchitectural operation in brains and machines is attention. The essence of this operation is multiplication. Unlike the vanilla neural network operation -- a nonlinear function of a weighted sum of inputs f(∑jwjxj)f(\sum_j w_{j}x_j) -- the attention operation allows for some input elements to be up-weighted if they are considered relevant to the current operational step, or down-weighted if they are not relevant. This weighting of inputs is done by element-wise multiplication of the inputs. For example, if you are classifying sentiment of movie reviews (a NLP task), you would multiple tokens such as amazing, fantastic, awful by high values and downweight tokens such as the, if, director because they aren’t relevant to the task of detecting the sentiment of a movie review. These multiplicative interactions to gate information flow is a very important operation, which we will explore in greater detail in this tutorial.

In brains, the theory of the Spotlight Attention (Posner et al 1980) posited that gain modulation allowed brain computations to select information for later computation. What do we mean by gain modulation in this context? We mean the ability to up-weight or down-weight responses in a system. You can think of the gating mechanism in recurrent models like LSTMs (e.g. the forget gate, the recurrent gate, the output gate) as implementing a form of gain modulation. We take the input, which is transformed non-linearly and used to modulate the passing of information through the network. In machines, Rumelhart, Hinton, and McClelland (1986) described Sigma-Pi networks that included both sums (Σ\Sigma) and products (Π\Pi) as fundamental operations. The Transformer network (Vaswani et al 2017) used a specific architecture featuring layers of multiplication, sparsification, and normalization. Many machine learning systems since then have fruitfully applied this architecture to language, vision, and many more modalities.

In this tutorial we will isolate the central properties and generalization benefits of multiplicative attention shared by all of these applications. Exercises include simple attentional modulation of inputs, coding the self-attention mechanism, demonstrating its inductive bias, and interpreting the consequences of attention.

References:


Setup

Install and import feedback gadget

Source

Imports

Source

Figure settings

Source

Plotting functions

Source

Data retrieval

Source

Helper functions

Source

Set random seed

Source

Set device (GPU or CPU)

Source
GPU is not enabled in this notebook. 
If you want to enable it, in the menu under `Runtime` -> 
`Hardware accelerator.` and select `GPU` from the dropdown menu

Video 1: Introduction to attention

Youtube
Bilibili

Section 1: Intro to Multiplication

In this section, we will show how signals can be used to selectively gate inputs. This is, after all, the essence of attention. If this way of thinking about attention was already clear to you - great! If not, we hope this section can broaden your mind and help you to understand how multiplication can be used as a gating mechanism and that you’ll be able to see how this pattern generalizes broadly across many other diverse topics of interest. Let’s begin!

Video 2: Cross-attention

Youtube
Bilibili

Exercise 1: Dot product attention

We’ll implement simple dot product attention for input x\mathbf{x} and scalar output yy given by a weighted combination of the inputs,

y=w⋅xy = \mathbf{w}\cdot\mathbf{x}

for weights w\mathbf{w} (also called gains). Unlike the fixed weights in an MLP, attention can adjust the weights depending on some other signal (the input itself in the case of self-attention; an external signal in the case of cross-attention). Here we will use a two dimensional attentional gain, z1z_1 and z2z_2, each with a corresponding vector q\mathbf{q} that determines the spatial pattern to be attended:

w(z1,z2)=softmax(z1q1+z2q2)\mathbf{w}(z_1,z_2) = \text{softmax}(z_1 \mathbf{q}_1 + z_2 \mathbf{q}_2)

where softmax(x)=ex/∑iexi\text{softmax}(\mathbf{x})={e^\mathbf{x}}/{\sum_i e^{x_i}} is a combination of a sparsifying element-wise nonlinearity (exp⁡\exp) and a normalization.

You should code up this function to return the attentional weights w\mathbf{w} and the weighted output yy.

Figure after Edelman et al. 2022.

Now we plot the weights as a function of z\mathbf{z}. Manipulate the sliders to control the two attention gains zkz_k, which we’ve pre-assigned to two specific sigmoidal pattern vectors qk\mathbf{q}_k. (Feel free to change those functions and see how the sliders change what they attend.) Observe how these sliders affect which part of the input is amplified or attenuated. The x-axis represents the input variable to the functions that define q1\mathbf{q}_1 and q2\mathbf{q}_2 (observe that these are non-linear of this input).

Execute this cell to enable the widget

Source
output y: 0.018142078071832657
<Figure size 800x600 with 2 Axes>
Loading...

This plot illustrates the impact of the gain factor on the attention weights for different input dimensions. Observe that:

  • The exponential and normalization from the softmax create sparse weights that can select relevant features.

  • For small zz, the attention weights are evenly distributed across the input dimensions.

  • As zz increases, the attention weights become sparser.

Video 3: Exercise conclusions

Youtube
Bilibili

Section 2: Self-Attention

Estimated timing to here from start of tutorial: 20 minutes.

Where do these gain factors z\mathbf{z} come from?

In dot product attention, the weights were w=softmax(z⋅Q)\mathbf{w}=\text{softmax}(\mathbf{z}\cdot Q), a function of an external z\mathbf{z} and fixed matrix QQ.

In the self-attention mechanism used in Transformers, we again see a multiplicative weighting on inputs, but now with weights that are functions of the input x\mathbf{x}. Transformers also add a few other components we will describe, too, but the multiplicative weighting is the essential operation we are highlighting in this tutorial.

The rest of this Section goes through the math you need to understand the self-attention mechanism in Transformers and asks you to implement it in code. If you’re short on time or already very familiar with transformers, you may want to jump to Section 3. Even though we had previously used transformers and thought we understood them, there were some important structures that we didn’t appreciate until we created this tutorial! So you may still benefit from following along.

Video 4: Self-attention

Youtube
Bilibili

Section 2.1: Tokens

Hypothetically, a very simple version of input-dependent weights would simply set z=x\mathbf{z}=\mathbf{x}, so

w=softmax(x⋅Q)\mathbf{w}=\text{softmax}(\mathbf{x}\cdot Q)

Practically, it’s important to recognize that in Transformers, the input x\mathbf{x} is ‘tokenized’: the input is divided into separate elements, which could be words or image patches, and each element is assigned its own vector xi\mathbf{x}_i through some embedding which could be an arbitrary function (like in the mushroom body of the fly olfactory system, or like a hash function), or could be learned. The dot products in x⋅Q\mathbf{x}\cdot Q could apply to the concatenated vector of all tokens or separately to each token. Transformers take that second approach. The details here mean that it can be tricky to specify the sizes and dimensions of the different matrices, so be careful.

Ultimately, most of the novel machinery of attention in transformers ends up modifying tokens according to their context, leaving the subsequent remixing of tokens to other, more conventional layers. That specific way of applying attention is probably not central to the value of attention.

Section 2.2: More multiplications

In the following sections, we assume the dimensionalities of the matrix xx are (seq_len, embed_dim) where the first dimension is the sequence of tokens that are represented by their embeddings. Each embedding dimension is of embed_dim size. Therefore, if we assume an embedding dimension of 50 and we have the sequence, The cat is happy, then x.shape would be (5,50).

Now, let’s turn the Q matrix we referenced above. This is a fixed matrix and this is not very adaptable to current inputs, so in order to provide more flexibility than that very simple version above, we allow QQ also to depend on the input: Q=xWqQ=\mathbf{x}W_q. The matrix QQ takes a vector in the embedding input dimension to an embedding output dimension (in this case it should have embed_dim rows (e.g. 50) for our example to work).

Just like we were able to expand the flexibility for the input, we can also select specific aspects of the input tokens to serve as gain modulation,

z=xWk\mathbf{z}=\mathbf{x}W_k

so

w=softmax(xWqWk⊤x⊤)\mathbf{w}=\text{softmax}(\mathbf{x}W_qW_k^\top x^\top)

The weights themselves are applied to linearly transformed tokens, V=xWvV=\mathbf{x}W_v, with the final attention output being a weighted sum over different tokens, which is added back to each original token. The result is that every original token is modified by the attended context. Another way to say this is that a combination of all previous tokens are linearly weighted and summed and become the representation of that token’s slot in the next attention layer of the model.

Let’s take an interpretable example. You might have a sentence using the word “cookies” and its own embedding. But what kind of cookies? It means two very different things in the sentences “I ate two cookies” or “This website uses cookies.” The self-attention mechanism will use the sentence context to change the representation of the token “cookies.” The word website will have a large impact on the updated representation of cookie, as will the word ate, to signal this is a cookie that can be eaten.

This is commonly described as a query-key-value framework. These various learnable projections of the input are given names:

Q=xWq    queryK=xWk      keyV=xWv    valueQ=\mathbf{x}W_q\ \ \ \ \text{query}\\ K=\mathbf{x}W_k\ \ \ \ \ \ \text{key}\\ V=\mathbf{x}W_v\ \ \ \ \text{value}

The self-attention mechanism is then usually written as:

y=softmax (QK⊤d)V\mathbf{y}=\text{softmax}\,(\frac{QK^\top}{\sqrt{d}})V

We get a big weight on inputs with a strong match between Query and Key, and that weight is applied to a particular set of Values. (For different dimensions dd of attended features, there’s also a scaling by d\sqrt{d} in the softmax for numeric stability.)

Putting all this together, the self-attention is then:

y=softmax(1dxWqWk⊤x⊤)xWv\mathbf{y}=\text{softmax}(\tfrac{1}{\sqrt{d}}\mathbf{x}W_q W_k^\top\mathbf{x}^\top)\mathbf{x}W_v

This equation shows that the attention mechanism in transformers is a composition of multiplication, sparsification, normalization, and another multiplication --- all special microarchitecture operations from this tutorial!

Bonus Section 2.3: Dimensions of Self-Attention

This section’s guidance should help you understand and implement self-attention in Bonus Exercise 2.

Self-attention in transformers can seem intimidating because there are so many elements (i.e., tensors and matrices) to consider. Here, we try to explain the dimensions and provide you with a smoother entry into the architecture. The following figure should help you navigate this text. Note that here, we remove the batch dimension (and multiple “heads”) for simplicity.

Starting from the left, the input x\mathbf{x} is a sequence of length TT (also called context length). Each row of x\mathbf{x} is called a token, and it has dimension dmd_m, also known as the model dimension or embedding dimension (e.g. (seq_len, embed_dim)).

Tokens are largely treated separately in Transformers, so crucial information about their spatial or sequential relationships is missing. To fix this, Transformers change the raw token embedding by adding a position-dependent term p\mathbf{p} to the tokens: x′=x+p\mathbf{x}' = \mathbf{x} + \mathbf{p}. The positional matrix could be a hard-coded matrix (called “Positional Encoding”) or can be a trainable (learned) matrix (called “Positional Embedding”).

Given these tokens, the transformer architecture selects some dimensions for further processing. It computes Keys (K\mathbf{K}), Queries (Q\mathbf{Q}), and Values (V\mathbf{V}) by linear projections of the tokens:

K=xWk\mathbf{K} = \mathbf{x} \mathbf{W}_k
Q=xWq\mathbf{Q} = \mathbf{x} \mathbf{W}_q
V=xWv\mathbf{V} = \mathbf{x} \mathbf{W}_v

where Wk\mathbf{W}_k, Wq \mathbf{W}_q, and Wv \mathbf{W}_v are learnable weights. As you can see from the figure, the dimensions of these weights do not depend on TT: these transformations are applied to each token separately.

We then calculate which Keys are similar to which Queries by the dot-product QK⊤\mathbf{QK^\top} (and scale by 1dk\frac{1}{\sqrt{d_k}}).

It can be helpful to consider just one row of this similarity matrix QK⊤\mathbf{QK^\top}, i.e. consider the keys for just one token’s query (token ii). We take a softmax operation over this row by applying an element-wise exponential, which sparsifies the entries and then normalizes them to give a probability distribution. This normalized vector is called the attentional weights pi\mathbf{pi}, and it determines which other tokens should be attended for token ii. This is the self-attention version of the gains we used in the first exercise.

How are these weights used? The Value V\mathbf{V} selected certain dimensions for each token, and these dimensions are then combined by the weighted sum Δx=∑jπjVj\Delta\mathbf{x}=\sum_j \pi_j\mathbf{V}_j and, typically, added to the original token representation, x∗=x+Δx\mathbf{x}^*=\mathbf{x}+\Delta\mathbf{x}. This leads to a new version of the original token, which is refined by the other tokens that attention deems relevant.

This refinement process is repeated for all tokens, and these new tokens can then be processed by other layers in the neural network.

Bonus Exercise 2: Implement self-attention

In this exercise, you will implement the Attention operation from the transformer architecture and test it against pre-written code.

(In this exercise, we use Positional Encoding, but in future models and exercises, we will use Positional Embedding. The difference is that an Encoding is fixed, like a hash code, whereas an Embedding is learned.)

Here is a function to test whether your function matches the correct output for self-attention.

---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
Cell In[12], line 2
      1 # Testing the function scaled_dot_product
----> 2 test_sdpa(ScaledDotProductAttention)

Cell In[6], line 169, in test_sdpa(your_implementation)
    167 your_model.Wk = our_model.Wk
    168 your_model.Wv = our_model.Wv
--> 169 your_results = your_model(toy_x.clone())
    170 our_results = our_model(toy_x.clone())
    171 if (your_results == our_results).all():

File /opt/hostedtoolcache/Python/3.10.22/x64/lib/python3.10/site-packages/torch/nn/modules/module.py:1518, in Module._wrapped_call_impl(self, *args, **kwargs)
   1516     return self._compiled_call_impl(*args, **kwargs)  # type: ignore[misc]
   1517 else:
-> 1518     return self._call_impl(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.10.22/x64/lib/python3.10/site-packages/torch/nn/modules/module.py:1527, in Module._call_impl(self, *args, **kwargs)
   1522 # If we don't have any hooks, we want to skip the rest of the logic in
   1523 # this function, and just call forward.
   1524 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
   1525         or _global_backward_pre_hooks or _global_backward_hooks
   1526         or _global_forward_hooks or _global_forward_pre_hooks):
-> 1527     return forward_call(*args, **kwargs)
   1529 try:
   1530     result = None

Cell In[11], line 38, in ScaledDotProductAttention.forward(self, x)
     32 x = x + self.position()
     34 #################################################
     35 # # TODO Scaled dot product attention
     36 # # Remove or comment out the lines you don't need
     37 # Fill remove the following line of code once you have completed the exercise:
---> 38 raise NotImplementedError("Student exercise: implement full version of scaled dot product attention.")
     39 #################################################
     40 # (Scaled Dot-Product Attention)
     41 Q = self.Wq(x)  # Query

NotImplementedError: Student exercise: implement full version of scaled dot product attention.

Section 3: Inductive bias of self-attention: Sparse variable creation

Estimated timing to here from start of tutorial: 40 minutes.

Please note that in the video below, cells that are black are considered to be “ON” and white cells are considered to be “OFF”. Therefore, in the 2-sparse AND function, if both the selected context indices are “ON” (black) then the label will also be black (=1, “ON”).

Video 5: Sparse variable

Youtube
Bilibili

What is the self-attention mechanism especially good at learning?

In the next exercise, we will follow the results from the paper Inductive Biases and Variable Creation in Self-Attention Mechanisms. They show that a simple transformer can successfully learn to represent a sparse function with low sample complexity.

The sparse functions we will use in this tutorial are s-sparse AND functions: For TT binary inputs (‘context length’) and s pre-selected unique indices, the sequence is labeled as True if the value of TT at all of the chosen indices is 1, and is otherwise False. This means that the sequence label only depends on s elements out of the whole input.

As an intuitive example, imagine an attribute vector that describes a “thing” on earth. Such vectors can be very, very long. If we are interested in classifying a “thing” as living or not based on a given vector, only 6 binary features could be enough: can grow, reproduce, metabolize, excrete, respire, and react to the change in the environment. It must do all of these things to be alive. The function that takes the vector and returns whether the “thing” is alive or not is a 6-Sparse AND function. Make sure you can see for yourself that the 2nd and 4th row contain “ON” (true) settings for those values.

Here, we will compare two architectures, Multi-Layer Perceptron (MLP) vs Self-Attention, when applied to learning this sparse boolean function ff.

We will test how well they perform with few training samples and assess their generalization error to try to understand how differences in their inductive biases via architectural differences might lead to different generalization errors.

Dataset

For this tutorial, we have already defined a s-sparse AND dataset generator class s_Sparse_AND for you. It generates mm sequences with context length TT and sparsity ss. (note that Each input element is more likely to be 1 (black) than 0 (white) so that the output labels have equal probability.) Here, we visualize a few input samples and their corresponding labels. The red rectangles show the relevant indices for this dataset.

Target (function-relevant indices) indices: [9, 10]
<Figure size 1600x250 with 2 Axes>

Exercise 3: MLP vs. Self-Attention

Let’s see which of the two architectures generalizes better in this problem. We will test both on sparse functions (s = 3) and denser functions (s = 15) for the context length of T = 30. We use a helper-function make_train that takes the model and hyper-parameters and returns the trained model and some results.

This exercise has 5 parts:

  1. Create training and validation datasets.

  2. Train an MLP and self-attention model on task.

  3. Plot to compare the results for the two models and two datasets.

  4. Change the sample complexity of the MLP dataset, to get to 100% accuracy.

  5. Plot the attention score (attention weights) for the transformer!

ABBREVIATIONS:

  • suffix _s = sparse

  • suffix _d = dense

  • prefix t_ = training

  • prefix v_ = validation

  • model_np = number of parameters

  • sat = Self-Attention Transformer

Exercise 3.1: MLP

Training an MLP on the “s-Sparse AND” task. How does the model do?

Number of model's learnable parameters: 16385
Training loss: 0.084, accuracy: 0.992
Validation loss: 0.075, accuracy: 0.997
Number of model's learnable parameters: 16385
Training loss: 0.100, accuracy: 0.982
Validation loss: 0.107, accuracy: 0.980

Exercise 3.2: Self-Attention

Build a Transformer model and train it on the given dataset. How do the training results compare to MLP?

Number of model's learnable parameters: 104
Training loss: 0.012, accuracy: 1.000
Validation loss: 0.013, accuracy: 1.000
Number of model's learnable parameters: 104
Training loss: 0.088, accuracy: 0.982
Validation loss: 0.068, accuracy: 0.987

Did you notice which of these models performed the best? We can easily see that the self-attention module working on the 2_Sparse_AND data performed perfectly on the training and validation data, confirming our suggested finding earlier that the self-attention mechanism is very good at learning to generalize across this type of task.

Exercise 3.3: Coding Exercise Comparing results

Here, we ask you to plot the results of the trainings above and the hyper-parameters you think are important. The goal is to show in one plot how Self-Attention Transformers and Multi-Layer Perceptons compare for both sparse and dense boolean tasks. You can use any or all of the following information in your plot:

  • number of parameters

  • training samples

  • validation accuracy

  • validation loss

We have provided the results in an ordered dictionary, ordered_results, for your convenience.

Discuss: what components of self-attention may be responsible for this improved performance for sparse functions? What could you add to an MLP that might make it more competitive?

Hint: Use your creativity and information-communication skills :)

OrderedDict([('Validation Loss SAT dense', 0.068), ('Validation Accuracy SAT dense', 0.987), ('Number of Parameters SAT dense', 104), ('Validation Loss SAT sparse', 0.013), ('Validation Accuracy SAT sparse', 1.0), ('Number of Parameters SAT sparse', 104), ('Validation Loss MLP dense', 0.107), ('Validation Accuracy MLP dense', 0.98), ('Number of Parameters MLP dense', 16385), ('Validation Loss MLP sparse', 0.075), ('Validation Accuracy MLP sparse', 0.997), ('Number of Parameters MLP sparse', 16385)])

Extra: Context-dependent Attention Weights

The S-Sparse AND task is not the best task to demonstrate the power of self-attention transformers in learning representations because even a linear network can learn a single projection of the inputs. Here explain a new task that would be a bit more challenging but also expose more core structure of attention.

For a given binary sequence, we can include a special “Rule” token that specifies which tokens should be included for the AND function. For example: if xrule=0x_{\rm rule} = 0, then the label of sequence should be x3 AND x7x_3~\text{AND}~ x_7, and if xrule=1x_{\rm rule} = 1, then the label should be x2 AND x5x_2~\text{AND}~ x_5. Therefore the self-attention should learn to “attend” to (x3,x7)(x_3,x_7) OR (x2,x5)(x_2,x_5), depending on xrulex_{\rm rule}.

A model can be trained on such a task and then we can plot how each token affects the output label. Check out the video a bit further down to see what the results look like when plotting the Transformer attention weights!

Video 6: Results discussion & GNN

Youtube
Bilibili

The Big Picture

Estimated timing of tutorial: 1 hour

This tutorial introduced multiplicative attention as a canonical operation in brains and machines and showed how it has an inductive bias that can help with context-dependent tasks.

The self-attention mechanism of Transformers actually includes all of the canonical NeuroAI operations from this tutorial: sparsifying, normalization, and multiplicative attention. These operations make it easier to generalize in natural tasks. You might have been familiar with the attention mechanism from a purely Machine Learning approach, but we hope you have been able to consider this operation as a more generic implementation of multiplicative gating and the idea of modulatory signals acting upon another. As we explored in the introduction, synapses do this in the brain, so there are parallels that can be drawn. How much might we be able to take inspiration from the brain and apply changes to solutions used in NeuroAI or AI more generally? This might be something you will be able to contribute towards in your future NeuroAI career!