beginner · ~12 min
About NumPy
The array library underneath almost every ML tool in Python — ndarrays, reshaping, slicing, and broadcasting.
Before TensorFlow, before Keras, before scikit-learn — there's NumPy.
Almost every Python machine-learning library, TensorFlow and Keras included, either builds directly
on NumPy arrays or borrows their conventions wholesale — the shape, reshape, and broadcasting
you'll see below are the exact same ideas the tensors elsewhere in this course use, because
TensorFlow's tensor design deliberately mirrors NumPy's.
At its core, NumPy adds one thing to Python: the ndarray.
A Python list can hold anything and grows one slow, individually-boxed object at a time; an
ndarray is a fixed-type, contiguous block of memory, and every operation on it runs as a tight
loop in compiled C — not a Python for loop. That's why NumPy code is written to avoid loops
entirely wherever possible, using vectorized operations
instead.
import numpy as np
arr = np.arange(12).reshape(3, 4) # a 3x4 ndarray, values 0..11
arr.shape # (3, 4)
arr.ndim # 2
arr.dtype # dtype('int64')
Beginner tip
If the words shape, rank (NumPy calls it ndim), and broadcasting sound familiar, that's
because you've already met them in the "What is a Tensor?" module — a NumPy ndarray and a
TensorFlow tensor are the same underlying idea, just from two different libraries.
Three operations account for most of the NumPy code you'll ever write:
- Reshape — reinterpret the same underlying data as a different shape, as long as the total
element count matches.
np.arange(12).reshape(3, 4)and.reshape(4, 3)hold the same 12 numbers, arranged differently. - Slice — pull out a sub-array with
arr[row, colStart:colEnd]syntax, without copying data. - Broadcast — combine arrays (or an array and a scalar) of different shapes by "stretching" the smaller one, with no explicit loop.
Play with all three below: reshape an array, slice a row out of it, then broadcast a scalar across the whole thing and watch the values update live.
🔍 Deep dive: Why vectorization is the whole point
A pure-Python loop over a million elements pays Python's interpreter overhead — type checks,
reference counting, dynamic dispatch — a million separate times. arr * 2 on a NumPy array pays
that overhead exactly once, then runs a single compiled C loop over contiguous memory. In
practice this is routinely 10-100x faster, and it's the entire reason data science in Python
is viable at all — plain Python alone is far too slow for numeric work at this scale. Every
.mul(), .add(), and .sum() you've used on a TensorFlow tensor elsewhere in this course is
the same idea again, just running on TensorFlow's own compiled backend instead of NumPy's.
Production note
In real training code, X is almost always a 2D array shaped (num_examples, num_features) —
one row per example — and NumPy arrays are usually the last stop before that data becomes a
TensorFlow or PyTorch tensor: tf.convert_to_tensor(X) and torch.from_numpy(X) both wrap a
NumPy array directly, often without copying its underlying memory at all.
🔍 Deep dive: Broadcasting rules, precisely
Two shapes are compatible for broadcasting if, comparing dimensions from the right, every pair
is either equal or one of them is 1. A (3, 4) array and a scalar (shape ()) always broadcast,
because a missing dimension is treated as 1 everywhere. A (3, 4) array and a (4,) array also
broadcast — the 1D array is treated as (1, 4) and stretched down all 3 rows — but a (3, 4)
array and a (3,) array do not, because comparing from the right, 4 vs 3 matches neither
the "equal" nor the "one of them is 1" rule. This is the single most common source of
ValueError: operands could not be broadcast together in real code.
Beginner tip
arr.reshape(...) never changes how many numbers you have — only how they're grouped. If your
reshape raises ValueError: cannot reshape array of size 12 into shape (5,3), it's because
5 × 3 = 15 ≠ 12. The dropdown above only ever offers shapes whose rows × cols equals the total
element count, for exactly this reason.
Playground
1. Create & reshape a ndarray
arr = np.arange(12).reshape(3, 4)
2. Slice a row
arr[0, 0:4]
3. Broadcast a scalar across the whole array
arr + 2 — no loop written, NumPy broadcasts the scalar to every element.