Kiru Lab / Foundations: Python as an Instrument / Python for Experiments
Arrays and Vectorization
NumPy is the language every later library speaks. Shapes are the grammar.
Code Walkthrough · about 35 minutes
A NumPy array is a contiguous block of numbers plus a shape describing how to interpret it. Operations apply to the whole block at once, in compiled code, without a Python loop. This is not only faster — it is a different way of thinking, where you describe a transformation of a whole dataset rather than of one element.
import numpy as np
# Element mindset
out = []
for row in data: # data: 1000 rows of 64 numbers
out.append(sum(r * w for r, w in zip(row, weights)))
# Array mindset
X = np.asarray(data) # shape (1000, 64)
w = np.asarray(weights) # shape (64,)
out = X @ w # shape (1000,) — one line, ~100x fasterShapes are the grammar
Almost every error you will hit in NumPy, PyTorch, or JAX is a shape error, and almost every shape error is legible if you write the shapes down. Adopt the habit now: annotate every array line with its resulting shape in a comment.
The last line is the shape of an attention projection. You already know how to read it.
Broadcasting
When shapes do not match, NumPy will stretch size-1 dimensions to fit rather than erroring. This is enormously convenient and occasionally catastrophic: a (1000, 1) array added to a (1000,) array silently produces a (1000, 1000) array, and your memory disappears.
a = np.zeros((1000, 1))
b = np.zeros((1000,))
print((a + b).shape) # (1000, 1000) — one million entries, not one thousandA tensor's shape is its anatomy. Change the arrangement of the same numbers and you change what the operation means — the numbers alone never tell you what a layer does.
Hold on to
- Vectorized operations describe transformations of whole datasets
- Annotating shapes turns most bugs into reading comprehension
- Broadcasting is convenient and silently expensive when misused
Work through
Try each one before opening the solution. Getting it wrong first is most of where the learning happens.
-
Rewrite a nested-loop matrix multiply as `@` and measure the speedup on 500x500 matrices.
Hint
Time both with `time.perf_counter()`; use `np.allclose` to confirm they agree.
Solution
The vectorized version is typically 50–500x faster at 500x500, because the work happens in compiled BLAS code rather than in the Python interpreter. Always confirm the results match before believing a speedup — a fast wrong answer is the usual outcome of a rushed vectorization.
import numpy as np, time rng = np.random.default_rng(0) A, B = rng.normal(size=(500, 500)), rng.normal(size=(500, 500)) start = time.perf_counter() naive = [[sum(A[i, k] * B[k, j] for k in range(500)) for j in range(500)] for i in range(500)] naive_time = time.perf_counter() - start start = time.perf_counter() fast = A @ B fast_time = time.perf_counter() - start assert np.allclose(np.array(naive), fast) print(f"speedup: {naive_time / fast_time:.0f}x") -
Deliberately trigger a bad broadcast, then fix it with `reshape` and with `keepdims`. Explain which is clearer.
Hint
A (1000, 1) and a (1000,) array broadcast to (1000, 1000).
Solution
`reshape(-1)` flattens the column into a 1-D array; `keepdims=True` is what you want when the extra axis came from a reduction you intend to broadcast back against the original. Use `keepdims` when the shape is deliberate and `reshape` when you are fixing an accident — the distinction tells a later reader which it was.
import numpy as np a = np.zeros((1000, 1)) b = np.zeros((1000,)) print((a + b).shape) # (1000, 1000) — the accident print((a.reshape(-1) + b).shape) # (1000,) — flattened X = np.ones((1000, 5)) means = X.mean(axis=1, keepdims=True) # (1000, 1) on purpose print((X - means).shape) # (1000, 5) — intended broadcastCheck your work
Paste this after your own code. If it runs without raising, you have it.
import numpy as np assert (np.zeros((1000, 1)) + np.zeros((1000,))).shape == (1000, 1000) assert (np.zeros((1000, 1)).reshape(-1) + np.zeros((1000,))).shape == (1000,) print("ok")
In the Bio Mirror
The Cortical ColumnSign in to track your progress through the lab.