CodeDynasty Explore Projects
CodeDynasty

© 2025 CodeDynasty. All rights reserved.

PyTorch fundamentals

Starting PyTorch From the Middle

Friday Candour
Friday Candour Software Developer
9 minutes
Starting PyTorch From the Middle

Starting PyTorch From the Middle

Most introductions to PyTorch begin with types, APIs, and catalogues of functions. That ordering is tidy. It is also wrong.

The tension is familiar. We want to build models, but we are asked to study tools without context. The result is fluency without purpose. PyTorch, unfortunately, encourages this mistake by being extremely good at low-level mechanics.

I prefer to start where the pressure is: the model.

Tensors Are Not Arrays

A tensor is PyTorch’s fundamental unit of computation. It resembles an array. That resemblance is misleading.

An array stores values. A tensor stores values and intent. Shape, device placement, gradient history. These attributes exist so the system can reason about change.

Formally, a tensor is an element of a vector space with structure sufficient to support automatic differentiation. Practically, it is how PyTorch avoids asking you to write Jacobians by hand.

import torch

x = torch.ones(5, 5)
y = x + 1

The addition looks trivial. It is not. The operation constructs a graph. The graph will matter later.

Higher dimensions are mundane.

torch.ones(5, 5, 5)

Nothing interesting happens here. That is the point.

A Neural Network, Compressed

A neural network is a parameterized function.

Given inputs ( x \in \mathbb{R}^n ), it computes:

[ y = f(x; \theta) ]

Training is the process of choosing ( \theta ).

Everything else is ceremony.

A single hidden layer network is sufficient for illustration. Inputs flow forward. Each layer computes:

[ h = Wx + b ]

Sometimes followed by a nonlinearity. We will ignore that for now. Linear models are honest.

Modules as Design Units

nn.Module is PyTorch’s real abstraction. Everything else is scaffolding.

A module is a component with parameters and a forward computation. This is not accidental. It mirrors how we reason about systems: state plus behavior.

import torch.nn as nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(3, 4)
        self.output = nn.Linear(4, 1)

    def forward(self, x):
        h = self.hidden(x)
        return self.output(h)

This code is dull. That is its strength.

The input layer is implicit. The hidden layer exists because we chose to have one. The output layer exists because the problem demands a scalar.

PyTorch does not force architectural opinions. It exposes algebra.

Calling Is Forward

You never call forward directly. You call the module.

model = SimpleModel()
prediction = model(torch.tensor([1.0, 2.0, 3.0]))

This indirection matters. Hooks, tracing, and gradient tracking depend on it. The API nudges you toward correct usage without explaining why. Accept the nudge.

What Is Missing

Nonlinearities. Loss functions. Optimizers. Data.

They are orthogonal concerns. PyTorch keeps them separate because coupling them early leads to brittle designs. Beginners often want completeness. Systems prefer composability.

The unpleasant truth is that an untrained model is useless. Structure precedes learning, but learning dominates outcomes. PyTorch does not protect you from this imbalance.

That is acceptable.

The framework optimizes for explicitness, not safety. You are expected to know when linearity is insufficient. The library will not remind you.