Lesson 2.2foundation3 h

NumPy and vectorized thinking

Shapes, broadcasting and the end of for-loops

Every tensor library — NumPy, PyTorch, JAX — shares one mental model: n-dimensional arrays with shapes, operations that apply to whole arrays at once, and broadcasting rules that let arrays of different shapes combine. Learning this model once in NumPy makes PyTorch (2.4 PyTorch I: tensors, autograd and the training loop) feel familiar, and it is how you will implement softmax (4.2 Activation functions), attention (7.2 Scaled dot-product and multi-head attention, fully derived) and pairwise distances for kNN (3.4 k-Nearest Neighbors) in a handful of lines that run 100× faster than loops.

You'll be able to

  • Create arrays with specific shapes and dtypes and read a shape tuple as 'what each axis means'
  • Apply the broadcasting rules to predict the output shape of an operation, or explain why it fails
  • Use boolean masks, fancy indexing and axis reductions without loops
  • Write matrix products with @ and einsum and translate between the two notations
  • Implement softmax, pairwise squared distances and a linear layer forward pass in vectorized NumPy

Key ideas

  1. An ndarray is one contiguous memory block plus shape/dtype/strides; reshape, transpose and slicing return views in O(1), fancy indexing copies.
  2. Read a shape as meanings (batch × time × features); printing shapes is the fastest way to find ML bugs.
  3. Broadcasting aligns shapes from the right; axes match if equal or 1; use keepdims=True or [:, None] to line axes up.
Show 5 more key ideas
  1. Reductions remove the axis you name; argmax(axis=1) on (B, C) logits gives per-example predictions.
  2. Stable softmax subtracts the row max before exponentiating; the result is mathematically identical.
  3. A @ B is matrix multiplication (batched over leading axes); einsum spells the index pattern for anything else.
  4. ‖a−b‖² = ‖a‖² + ‖b‖² − 2a·b turns pairwise distances into one matrix product; vectorized code is 10–100× faster than loops.
  5. Use np.random.default_rng(seed) and pass the generator around for reproducibility.

Suggested path through this lesson