Foundations

Python, NumPy, and PyTorch in 20 minutes

A compact workflow covers arrays, tensors, broadcasting, automatic differentiation, modules, and safe experiment habits.

Updated

1

Concept

Python is the coordination layer of much machine-learning work. You use ordinary variables, functions, loops, imports, and exceptions, while numerical libraries execute heavy operations in compiled kernels. The important habit is to make data flow explicit. Name shapes, avoid hidden global state, check assumptions at boundaries, and keep a small experiment reproducible before scaling it.

NumPy’s central object is the multidimensional array. This runnable example creates two matrices and multiplies them:

import numpy as np

x = np.array([[1.0, 2.0], [3.0, 4.0]])
w = np.array([[0.5, -1.0], [1.5, 2.0]])
y = x @ w
print(y.shape, y)

Elementwise x * w and matrix multiplication x @ w are different operations. Inspect .shape, .dtype, and sometimes .strides. Broadcasting lets a compatible smaller array act across a larger one: adding shape (features,) to (batch, features) adds the same feature bias to every row. Convenient broadcasting can also hide mistakes, so reason about intended dimensions first.

PyTorch tensors offer similar array operations plus devices and automatic differentiation. Tensors participating in one operation normally need compatible dtypes and the same device. Moving a tensor to a GPU does not move every related object automatically. Start on CPU for tiny examples; introduce acceleration when computation and transfer costs justify it.

Autograd records operations involving tensors that require gradients. The following learns one scalar weight:

import torch

torch.manual_seed(7)
x = torch.tensor([1.0, 2.0, 3.0])
target = torch.tensor([2.0, 4.0, 6.0])
w = torch.nn.Parameter(torch.tensor(0.0))
optimizer = torch.optim.SGD([w], lr=0.1)

for _ in range(20):
    optimizer.zero_grad()
    prediction = w * x
    loss = ((prediction - target) ** 2).mean()
    loss.backward()
    optimizer.step()

print(w.item())

The loop has a stable rhythm: clear accumulated gradients, run the forward computation, reduce errors to a scalar loss, call backward, then update parameters. backward computes gradients; it does not change parameters. step changes them; it does not compute the loss. Keeping those responsibilities separate makes debugging easier.

Larger models subclass torch.nn.Module or compose existing modules. Registered parameters appear in model.parameters(). model.train() and model.eval() change behavior for modules such as dropout; they do not enable or disable gradients. Use with torch.no_grad(): for evaluation when derivatives are unnecessary. Save a state_dict plus enough configuration to reconstruct the model rather than serializing opaque execution state.

Reproducibility needs more than one seed. Record library versions, data splits, preprocessing, hyperparameters, device, precision, and evaluation procedure. Some accelerator operations can remain nondeterministic. Always compare against a simple baseline, examine individual examples, and assert shapes and finite losses. A green training loop can optimize the wrong target perfectly.

The portable skill is not memorizing every API. It is reading tensor programs as shape transformations with explicit state. Ask what each axis means, which values require gradients, where randomness enters, which reduction creates the loss, and when parameters change. With that discipline, NumPy and PyTorch become transparent instruments rather than magical syntax.

Keep the smallest failing tensor and its expected shape when debugging; a ten-number reproduction usually teaches more than rerunning an opaque full training job.

2

Explain it like I am five

Think of Python as a workshop language, NumPy as a precise rack of measuring and cutting tools, and PyTorch as the same workshop with a camera recording every differentiable operation. After you compute a loss, autograd rewinds the recording to determine how each adjustable knob affected the result. The camera does not choose a good project or detect a wrong measurement; it faithfully differentiates the operations you actually performed.

3

Teach it back

Describe a minimal PyTorch training step and explain the roles of tensor shapes, autograd, zero_grad, backward, and optimizer.step.

Minimum: 80 characters and 15 words. Your text stays only in this browser.

Saved only on this device.

Show a model answer

Inputs and targets are tensors with deliberate shapes. The model computes predictions and a scalar loss while autograd records differentiable operations. optimizer.zero_grad clears gradients accumulated from earlier steps. loss.backward computes parameter gradients through the recorded graph. optimizer.step updates parameters from those gradients. Shape, dtype, device, train/eval mode, and random seeds must be controlled because autograd cannot tell whether the experiment itself is logically correct.

4

Check your understanding

1. Why is optimizer.zero_grad normally called before backward?
Answer and explanation

PyTorch accumulates gradients by default — Accumulation is useful for some workflows, but an ordinary independent step must clear previous gradients.

2. What does broadcasting do?
Answer and explanation

Applies compatible smaller shapes across larger dimensions without manual copies — Broadcasting aligns dimensions under precise compatibility rules; inspecting resulting shapes remains essential.

Complete the teach-back and answer the quiz correctly to finish this lesson.

Sources

  1. Charles R. Harris et al. (2020). Array programming with NumPy.
  2. Adam Paszke et al. (2019). PyTorch: An Imperative Style, High-Performance Deep Learning Library.