DataLens

Tensors: creation and basics

Meet the tensor, PyTorch's array, create tensors in the common ways, and read their shape and dtype, with NumPy practice that runs right here.

Level: Foundations · 9 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

PyTorch is the most popular library for deep learning: the neural networks behind image recognition, translation and chat assistants. Its core data structure is the tensor: an n-dimensional array of numbers, just like a NumPy array, with two superpowers:

  1. it can live on a GPU, where maths on millions of numbers runs dramatically faster;
  2. it can remember how it was computed, so PyTorch can calculate gradients automatically (autograd, module 4).

Everything you learned in the NumPy track carries over almost one to one.

How this track works: PyTorch is too large to run in your browser, so PyTorch code in these lessons is shown with its real output (checked against a real PyTorch installation) but has no Try it button. You practise the same ideas in NumPy, which does run here, and the API itself in quizzes. To run the PyTorch code yourself: uv add torch, then uv run python.

Creating tensors

If you know NumPy arrays, you already know most of what a tensor is: a grid of numbers of one type, with a shape. The simplest way to make one is from a Python list:

import torch

daily_orders = torch.tensor([42, 38, 51, 47, 60, 75, 68])
prices = torch.tensor([185.0, 320.0, 95.0])
print(daily_orders)
print(prices)
tensor([42, 38, 51, 47, 60, 75, 68])
tensor([185., 320.,  95.])

The NumPy equivalent, which you can run:

import numpy as np

daily_orders = np.array([42, 38, 51, 47, 60, 75, 68])
prices = np.array([185.0, 320.0, 95.0])
print(daily_orders)
print(prices)
[42 38 51 47 60 75 68]
[185. 320.  95.]

The familiar creation functions all exist:

print(torch.zeros(2, 3))
print(torch.ones(3))
print(torch.arange(0, 10, 2))
print(torch.linspace(0, 1, 5))
print(torch.full((2, 2), 7.0))
tensor([[0., 0., 0.],
        [0., 0., 0.]])
tensor([1., 1., 1.])
tensor([0, 2, 4, 6, 8])
tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])
tensor([[7., 7.],
        [7., 7.]])

One small difference from NumPy: PyTorch takes shapes as separate arguments (torch.zeros(2, 3)) or a tuple (torch.zeros((2, 3))); both work.

Random tensors and seeds

Neural networks start from random weights, so random tensors are everywhere. Set a seed for reproducibility:

torch.manual_seed(42)
print(torch.rand(2, 3))        # uniform in [0, 1)
print(torch.randn(3))          # standard normal: mean 0, std 1
tensor([[0.8823, 0.9150, 0.3829],
        [0.9593, 0.3904, 0.6009]])
tensor([ 1.1561,  0.3965, -2.4661])

torch.manual_seed(42) plays the role of np.random.default_rng(42). The numbers differ between the two libraries, but the idea is identical: same seed, same numbers, every run.

Shape, dtype, size

When a PyTorch model complains, the error is almost always about shapes or types, so checking them is the first habit to build. These attributes tell you what you're holding:

units = torch.tensor([[120, 135, 150], [80, 85, 90]])
print(units.shape)
print(units.ndim, units.numel())
print(units.dtype)
print(torch.tensor([1.5, 2.0]).dtype)
torch.Size([2, 3])
2 6
torch.int64
torch.float32
Key idea

A tensor is a NumPy-style array with a shape and a dtype. Models expect float32 inputs. Mixing dtypes (an int64 tensor into a layer with float32 weights) is one of the most common beginner errors.

Convert with .float(), .long() or .to(torch.float32):

counts = torch.tensor([3, 1, 4])
print(counts.float(), counts.float().dtype)
tensor([3., 1., 4.]) torch.float32

Indexing and single values

Indexing and slicing work exactly like NumPy:

print(units[0])          # first row
print(units[:, 1])       # second column
print(units[1, 2])       # one element, still a tensor
print(units[1, 2].item())  # a plain Python number
tensor([120, 135, 150])
tensor([135,  85])
tensor(90)
90

.item() converts a one-element tensor to a Python number: you'll call it constantly to print or log a loss value.

Reshaping

Models are fussy about shapes. A layer might expect a batch of examples with shape (batch, features) when you have a single example with shape (features,). Reshaping rearranges the same numbers into the shape you need:

monthly = torch.arange(12)
print(monthly.reshape(4, 3))
print(monthly.reshape(-1, 6).shape)
print(monthly.unsqueeze(0).shape, monthly.unsqueeze(1).shape)
tensor([[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8],
        [ 9, 10, 11]])
torch.Size([2, 6])
torch.Size([1, 12]) torch.Size([12, 1])

reshape and -1 work as in NumPy. unsqueeze(dim) inserts a dimension of size 1, the PyTorch name for np.newaxis / np.expand_dims; squeeze() removes size-1 dimensions. Adding a "batch" dimension with unsqueeze(0) before feeding a single example into a model is a daily routine.

Why this matters for clean code

Shapes and dtypes are the contract between your data and your model. The habits from the NumPy track (print .shape and .dtype when unsure, document what each dimension means, convert explicitly) matter even more here, because a shape mistake inside a network often produces a confusing error ten layers away from its cause.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Tensor operations and broadcasting →