MediumLinear Algebra

Implement Positional Encoding (sin/cos)

Linear Algebra · Transformers

Medium

Problem

Implement sinusoidal positional encodings as described in "Attention Is All You Need" to inject sequence order into token embeddings.

Given a sequence length and model dimension, compute the positional encoding matrix using the sin/cos formulation.

Mathematical Definition

For position pos and dimension index i:

PE(pos, 2i) = \sin\!\left(\frac{pos}{base^{\,2i/d_{model}}}\right)

PE(pos, 2i+1) = \cos\!\left(\frac{pos}{base^{\,2i/d_{model}}}\right)

Even-indexed columns use sine, odd-indexed columns use cosine, and the frequency decreases with dimension index.

Theory

Self-attention, the core mechanism in transformers, treats the input as a set. If you shuffle the tokens, the attention outputs just shuffle correspondingly. The model has no inherent sense of word order.

But word order matters! "The cat sat on the mat" means something different from "The mat sat on the cat."

Positional encodings inject position information into the model by adding position-dependent vectors to the token embeddings.


The Sinusoidal Encoding Scheme

The original "Attention Is All You Need" paper introduced sinusoidal positional encodings:

PE(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d}}\right)

PE(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d}}\right)

Where:

Even dimensions use sine, odd dimensions use cosine.


Understanding the Frequencies

Each dimension pair (2i, 2i+1) oscillates at a different frequency:

\omega_i = \frac{1}{10000^{2i/d}}

Low dimensions (small i): High frequency, completes many cycles over the sequence.

High dimensions (large i): Low frequency, changes slowly across positions.

This creates a spectrum of frequencies, like different notes in music. Each position has a unique "chord" of sine and cosine values.


Why This Scheme Works

Unique encodings: No two positions have the same encoding vector. The combination of frequencies ensures uniqueness.

Smooth interpolation: Nearby positions have similar encodings (their sine/cosine values are close). This provides a notion of locality.

Relative positions: The encoding allows the model to learn attention patterns based on relative position. The paper shows that PE(pos + k) can be written as a linear function of PE(pos).

Extrapolation: Sinusoidal encodings extend naturally beyond the training sequence length. (Though in practice, performance degrades on much longer sequences.)


A Concrete Example

Settings: d_model = 4, position = 3

Frequencies:

Encodings:

Position 3 encoding: [0.141, -0.990, 0.030, 1.000]

Compare to position 4:

Position 4 encoding: [-0.757, -0.654, 0.040, 0.999]

The high-frequency components (indices 0, 1) change significantly between positions 3 and 4. The low-frequency components (indices 2, 3) change only slightly.


Adding Encodings to Embeddings

The positional encoding is simply added to the token embedding:

\text{input}_i = \text{embedding}(\text{token}_i) + PE(i)

This assumes embeddings and positional encodings have the same dimension.

For a batch of sequences with shape (batch_size, seq_len, d_model):

  1. Create PE matrix of shape (seq_len, d_model)
  2. Add it to each sequence in the batch (broadcasting)

Learned vs. Sinusoidal Encodings

Sinusoidal (fixed):

Learned positional embeddings:

Relative positional encodings:


The Role of 10000

The base 10000 controls the range of frequencies:

Smaller base (e.g., 100):

Larger base (e.g., 100000):

10000 was chosen empirically and works well for typical sequence lengths (up to a few thousand tokens).


Handling Odd Dimensions

If d_{model} is odd, there is one extra dimension without a pair. The standard approach:


Positional Encodings in Vision

Transformers for images (like ViT) also need positional information since patches are treated as tokens. Options:

2D sinusoidal: Separate encodings for row and column, concatenated.

Learned 2D: A learnable embedding for each (row, column) position.

The same principle applies: inject position information so the model knows where each patch came from.


Why Not Concatenate?

An alternative to adding is concatenating position encodings to embeddings:

\text{input}_i = [\text{embedding}(\text{token}_i); PE(i)]

This doubles the input dimension. Adding is preferred because:

The model learns to use the added positional signal to modulate attention.

Examples

Example 1

Input
seq_len = 3, d_model = 4
Output
[[0.0000, 1.0000, 0.0000, 1.0000], [0.8415, 0.5403, 0.0100, 0.9999], [0.9093, -0.4161, 0.0200, 0.9998]]
Explanation
Columns alternate sine and cosine values. For the second frequency, the divisor is 100, producing the smaller angles 0.01 and 0.02.

Example 2

Input
seq_len = 5, d_model = 7, base = 10000.0
Output
NumPy array of shape (5, 7)

Hints

  1. Build positions with shape (seq_len, 1) and frequencies with shape (1, ceil(d_model / 2)) for broadcasting.
  2. Fill even columns with sine values and odd columns with the corresponding cosine values.

Requirements

Constraints

Starter Code

import numpy as np

def positional_encoding(seq_len: int, d_model: int, base: float = 10000.0) -> np.ndarray:
    """
    Returns a NumPy array of shape (seq_len, d_model).
    """
    # Write code here
    pass

Test Cases

CaseMatches
Small even d modelpublic
Odd d modelExample 2public