Key ideas
- Shapes first. Image batches are
(N, C, H, W): 32 RGB images of are(32, 3, 64, 64).ToTensor()turns a PIL image oruint8array of shape(H, W, C)with values 0–255 into a float(C, H, W)tensor in 7; Matplotlib wants(H, W, C), so useimg.permute(1, 2, 0). Areshapeto that shape keeps the memory order and scrambles pixels and channels. - Broadcasting compares shapes from the last dimension backwards; two sizes fit when they are equal, one of them is 1, or one is missing1. It never raises on
(32, 1) - (32,): that silently becomes(32, 32), a classic loss bug. In-place operations cannot change the shape of the tensor they write into1. - Reductions remove the reduced dimension: for
(2, 3, 4),sum(dim=0)is(3, 4);keepdim=Truekeeps it as size 1, which is what you need to broadcast the result back (A / A.sum(dim=1, keepdim=True)normalises rows). Withoutdim,argmaxreturns one index over all elements, i.e. into the flattened tensor8.len(X)isX.shape[0]. - view vs reshape.
viewreturns a tensor sharing the same data and only works if the new shape is compatible with the existing strides; aftertranspose/permuteit often fails.reshapereturns a view when possible and copies otherwise2.squeeze()drops every size-1 dimension,squeeze(0)only the first. - Autograd records the operations on tensors with
requires_grad=Trueand saves what the backward pass needs3.loss.backward()(on a scalar) accumulates gradients into the.gradof leaf tensors, so calloptimizer.zero_grad()every step; the saved tensors are freed by the backward pass, so a secondbackward()through the same graph fails unlessretain_graph=True3. A layer used twice (tied weights) gets the sum of the gradients of both uses (chain rule). - No-grad vs eval mode.
torch.no_grad()stops recording, so no graph and no saved tensors are kept3, which saves memory and time during evaluation;model.eval()only switches layers such as dropout and batch norm to inference behaviour and does not disable gradients3,4. A net of onlyLinearandReLUlayers behaves identically in both modes. nn.Moduleregistration. Submodules assigned as attributes are registered: they appear inparameters(),state_dict()and are moved by.to()4. Layers kept in a plain Python list are invisible; usenn.ModuleListornn.Sequential.parameters()skips duplicates, so a shared layer is counted once4.nn.Linear(in, out)stores a weight of shape(out, in)plusoutbiases.- Devices.
Module.to(device)modifies the module in place4, butTensor.to(...)returns a converted copy and leaves the original unchanged, so writex = x.to(device). Model and data must be on the same device. - Losses.
nn.CrossEntropyLossexpects raw logits of shape(N, C)and class indices of shape(N,)(dtypelong, values in ), and then equalsLogSoftmaxfollowed byNLLLoss(float targets of shape(N, C)are read as class probabilities instead); it ignores targets equal toignore_index = -100and averages by default (reduction='mean')5. So do not apply softmax first, and pair a model ending inLogSoftmaxwithNLLLoss. - Data. A
DataLoaderover examples yields batches, the last one smaller;drop_last=Truedrops that incomplete batch6. - Saving.
state_dict()holds parameters and persistent buffers, not the code, so you rebuild the model class before loading;load_state_dictis strict by default and needs exactly matching keys4.
A training step, in order: model.train(), move x, y to the device, optimizer.zero_grad(), loss = loss_fn(model(x), y), loss.backward(), optimizer.step(). Evaluate with model.eval() inside torch.no_grad().
Formulas
- Parameters of
nn.Linear(n_in, n_out): . - Samples from :
mu + sigma * torch.randn(...), scaled by the standard deviation, not the variance;a + (b - a) * torch.rand(...)gives . reduction='sum'makes the loss and its gradient times larger than'mean'for a batch of (one loss term per example), so SGD needs learning rate for the same steps.- First loss of a -class classifier with near-zero logits: (1.099 for 3 classes), a quick sanity check.
torch.matmulmultiplies the last two dimensions and broadcasts the rest:(4, 1, 3, 5) @ (6, 5, 2)gives(4, 6, 3, 2).
Worked example
net = nn.Sequential(nn.Linear(10, 32), nn.ReLU(), nn.Linear(32, 3)) on 1000 examples, batch size 64:
- parameters: ;
ReLUhas none; - after
loss.backward(),net[0].weight.gradhas the shape of the weight,(32, 10); - the loader gives 16 batches per epoch (15 of 64, one of 40), or 15 with
drop_last=True; - for logits and class 0, cross-entropy is ; feeding softmax outputs instead of logits gives a wrong .
In code
import torch
from torch import nn
y_hat, y = torch.zeros(32, 1), torch.zeros(32)
print((y_hat - y).shape) # torch.Size([32, 32]): silent broadcast bug
t = torch.randn(2, 3, 4).transpose(1, 2)
print(t.is_contiguous(), t.reshape(2, 12).shape) # False torch.Size([2, 12]); t.view(2, 12) raises
class ListNet(nn.Module):
def __init__(self):
super().__init__()
self.layers = [nn.Linear(4, 4)] # plain list: not registered
print(len(list(ListNet().parameters())), len(list(nn.ModuleList([nn.Linear(4, 4)]).parameters()))) # 0 2
shared = nn.Linear(8, 8)
print(sum(p.numel() for p in nn.Sequential(shared, nn.ReLU(), shared).parameters())) # 72, counted once
x = torch.zeros(2); x.to(torch.float64); print(x.dtype) # torch.float32: Tensor.to returned a copy
Common pitfalls
A / A.sum(dim=1)on a(2, 3)matrix raises, and on a square matrix silently divides column by the sum of row ; keep the dimension withkeepdim=True1.- Predictions of shape
(N, 1)against targets of shape(N,)broadcast to(N, N)without any error1. model.eval()is nottorch.no_grad(), and neither freezes parameters (that isrequires_grad=False)3.- Forgetting
zero_grad()accumulates gradients across steps; callingbackward()twice on one graph raises an error3. t.to("cuda")without reassignment leavestwhere it was;model.to("cuda")does move the model4.- Class-index targets for
CrossEntropyLossare of dtypelongand shape(N,), not floats of shape(N, 1)and not labels 1 to 5. load_state_dict(..., strict=False)only tolerates missing or unexpected keys; a tensor whose shape differs from the model's still raises a size-mismatch error, so filter the dictionary to the layers you want to transfer4.viewon a transposed or permuted tensor fails; usereshapeor.contiguous().view(...)2.
References
- PyTorch documentation: Broadcasting semantics — General semantics; In-place semantics
- PyTorch documentation: torch.Tensor.view — torch.Tensor.view (description and note on reshape)
- PyTorch documentation: Autograd mechanics — Saved tensors; Locally disabling gradient computation (Setting requires_grad; No-grad Mode; Evaluation Mode); Multithreaded Autograd (Graph retaining)
- PyTorch source (v2.14.1): torch/nn/modules/module.py — class Module docstring; add_module; to; state_dict; load_state_dict and _load_from_state_dict (size-mismatch check); parameters; eval
- PyTorch documentation: torch.nn.CrossEntropyLoss — Description; Parameters (ignore_index, reduction); Shape
- PyTorch documentation: torch.utils.data — torch.utils.data.DataLoader (batch_size, shuffle, drop_last)
- torchvision documentation: ToTensor — torchvision.transforms.ToTensor (class description)
- PyTorch documentation: torch.argmax — torch.argmax(input) (first signature)