The classic models you built with scikit-learn have a ceiling. Once the data turns huge and tangled - millions of animal photographs collected across the whole reserve - you need neural networks, and for those you need heavier equipment: PyTorch. Before you stack a single layer, get to know the two pillars the entire library rests on - the tensor and autograd. Everything else is detail.
1pip install torch torchvisionOne command brings in both packages:
torch is the library itself, and torchvision carries the image datasets and transforms you will reach for the moment your safari photographs enter the story. Install once, and the whole toolkit is on the truck.A tensor is the basic container for data in PyTorch - a multidimensional array much like a NumPy array, but with GPU support and one more superpower you will meet shortly: it remembers how it came to be. It is not a sorting algorithm, not a kind of database and not a file format - it is simply the box your numbers ride in. For now, treat it as the familiar array of numbers.
1import torch
2
3tensor = torch.tensor([1, 2, 3, 4, 5])
4matrix = torch.tensor([[1, 2], [3, 4], [5, 6]])
5
6zeros = torch.zeros(3, 4) # 3x4 matrix of zeros
7random = torch.rand(3, 3) # random values 0-1
8
9print(tensor.shape) # size
10print(tensor.device) # cpu or cuda (GPU)You build tensors almost the way you build NumPy arrays:
torch.tensor(...) takes a plain Python list, torch.zeros and torch.rand fill a shape for you. The import line is worth a second look, because Python lets you rename a module on the way in - the keyword import, then the module name, then as, then the alias you choose, as in import torch as pt. Most projects keep the plain import torch, but recognise that four-part shape when you see it. And note the last attribute, device: it reveals the first superpower, because a tensor knows whether it sits in processor memory (cpu) or on the graphics card (cuda). We come back to that in a moment.Operations on tensors feel like NumPy too - you add, you multiply, you compute statistics:
1a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
2b = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)
3
4c = a + b # element-wise addition
5c = a @ b # matrix multiplication (the @ operator)
6
7print(a.mean()) # mean
8print(a.argmax()) # index of the largest value
9
10reshaped = torch.arange(12).reshape(3, 4) # change the shapeOne distinction matters more than the rest:
+ and * work element by element, while @ is real matrix multiplication - and in neural networks it is the second one you lean on constantly. mean and argmax summarise a tensor the way you would summarise a column of measurements. reshape rearranges the shape without touching the underlying numbers, which is exactly what you need when pushing images through a network.Here is why deep learning is feasible at all in reasonable time. A network performs billions of matrix multiplications, and a graphics card (GPU) chews through them hundreds of times faster than a processor. Moving a tensor there takes a single method call.
1device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
2
3tensor = torch.tensor([1, 2, 3]).to(device) # move to the GPU if there is one
4tensor_gpu = torch.rand(3, 3, device='cuda') # or create it there right awayThe
device = 'cuda' if ... else 'cpu' pattern is the standard: the same code runs on the GPU when one is available and on an ordinary processor when it is not, with nothing rewritten. To move a tensor onto the card you write tensor.to('cuda'), or the shorthand tensor.cuda() - both do the same job, and tensor.cpu() brings it back. There is no tensor.gpu(), no tensor.accelerate() and no global gpu(tensor) function; those names simply do not exist in PyTorch. Remember one rule as well: for two tensors to be added or multiplied, they must live on the same device. Mixing CPU and GPU is the classic beginner error.Now the second superpower - the one that lets a model learn at all. Learning means nudging parameters towards a smaller error, and that requires derivatives, better known as gradients. Autograd is PyTorch's engine for automatic gradient computation, the machinery behind backpropagation: mark a tensor with
requires_grad=True and PyTorch records every operation applied to it, then replays the arithmetic backwards. It does not generate code for you, it does not load your data - that is the DataLoader's job, coming up shortly - and it does not run tests. It computes derivatives, nothing else.1x = torch.tensor([2.0], requires_grad=True)
2y = x ** 2 + 3 * x + 1
3
4y.backward() # compute the derivative backwards
5print(x.grad) # dy/dx = 2x + 3 = 7This is the heart of PyTorch. You only wrote the formula going forward (
y = x**2 + 3x + 1), and backward() worked out the derivative on its own - 7, exactly what you would get on paper. You differentiated nothing by hand. In a real model the very same mechanism figures out how each weight should change so the error shrinks:1w = torch.tensor([1.0], requires_grad=True)
2b = torch.tensor([0.0], requires_grad=True)
3
4y_pred = w * torch.tensor([2.0]) + b # the model prediction
5loss = (y_pred - torch.tensor([5.0])) ** 2 # how badly we are wrong
6
7loss.backward()
8print(w.grad, b.grad) # which way to nudge w and bFollow the logic: you compute a prediction, you compute the error (
loss), you call backward() - and w.grad and b.grad tell you which way to push each parameter so that the error drops. Training a network is nothing more than repeating that step thousands of times. That is why autograd is a pillar and not a curiosity.One practical question is left: how do you feed the model? Networks learn from small portions called batches, so PyTorch hands you two building blocks -
Dataset wraps your data, and DataLoader slices it into batches and shuffles them.1from torch.utils.data import Dataset, DataLoader
2
3class SafariDataset(Dataset):
4 def __init__(self, X, y):
5 self.X = torch.tensor(X, dtype=torch.float32)
6 self.y = torch.tensor(y, dtype=torch.long)
7
8 def __len__(self):
9 return len(self.X)
10
11 def __getitem__(self, idx):
12 return self.X[idx], self.y[idx]
13
14dataset = SafariDataset(X_train, y_train)
15dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
16
17for batch_X, batch_y in dataloader:
18 print(batch_X.shape, batch_y.shape)
19 breakYour
Dataset has to do exactly two things: report how many examples it holds (__len__) and return one of them by index (__getitem__). DataLoader takes it from there and serves the data in packs of 32 (batch_size), reshuffling the order at every epoch (shuffle=True) - so the model learns the animals rather than the accidental order in which you photographed them.Take two pillars away from this lesson: the tensor is data that can compute on the GPU, and autograd works out the derivatives that let the model learn. Once those two make sense, building the network itself is only a matter of stacking layers.