PyTorch Tensors: Creation, Operations, and GPU Transfer
Learners will create tensors from Python lists and NumPy arrays, perform element-wise and matrix operations, and move tensors to GPU with .to('cuda').
PyTorch Tensors: Creation, Operations, and GPU Transfer is a free Machine Learning Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Machine Learning Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a PyTorch Tensor?
A tensor is the fundamental data structure in PyTorch — essentially an n-dimensional array similar to a NumPy array but with built-in GPU support and automatic differentiation. Tensors can be scalars (0D), vectors (1D), matrices (2D), or higher-dimensional structures. PyTorch tensors track computation history, enabling automatic gradient computation for training neural networks.
import torch
# Scalar (0-dimensional tensor)
scalar = torch.tensor(3.14)
print(scalar.shape) # torch.Size([])
# Vector (1D)
vector = torch.tensor([1.0, 2.0, 3.0])
print(vector.shape) # torch.Size([3])
# Matrix (2D)
matrix = torch.tensor([[1, 2], [3, 4]])
print(matrix.shape) # torch.Size([2, 2])Creating Tensors: Common Methods
PyTorch provides many factory functions to create tensors with specific values or shapes. torch.zeros and torch.ones fill tensors with constants; torch.rand samples from a uniform distribution; torch.randn samples from a standard normal distribution. These are the building blocks for weight initialisation and synthetic data generation.
import torch
zeros = torch.zeros(3, 4) # 3x4 matrix of zeros
ones = torch.ones(2, 3) # 2x3 matrix of ones
rand_uniform = torch.rand(3, 3) # uniform in [0, 1)
rand_normal = torch.randn(3, 3) # standard normal N(0,1)
arange = torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
print(zeros.dtype) # torch.float32 (default)
print(arange) # tensor([0, 2, 4, 6, 8])Creating Tensors from NumPy Arrays
You will often start with a NumPy array (from Pandas, scikit-learn, etc.) and need to convert it to a PyTorch tensor. torch.from_numpy shares memory with the NumPy array — modifying one modifies the other. Alternatively, torch.tensor makes a copy. Knowing which to use prevents surprising bugs in data pipelines.
import torch
import numpy as np
arr = np.array([1.0, 2.0, 3.0])
# Shares memory with arr
t_shared = torch.from_numpy(arr)
# Makes an independent copy
t_copy = torch.tensor(arr)
arr[0] = 99.0
print(t_shared) # tensor([99., 2., 3.]) <- changed
print(t_copy) # tensor([1., 2., 3.]) <- unchangedTensor Data Types (dtypes)
Tensors have a dtype that controls numeric precision and memory usage. The default is torch.float32, which is the standard for neural network weights. torch.float64 offers higher precision at double the memory; torch.int64 is used for integer labels. Mismatched dtypes cause runtime errors, so always check and cast explicitly with .float() or .to(dtype).
import torch
f32 = torch.tensor([1.0, 2.0]) # float32 by default
f64 = torch.tensor([1.0, 2.0], dtype=torch.float64)
i64 = torch.tensor([1, 2, 3]) # int64 by default
print(f32.dtype) # torch.float32
print(i64.dtype) # torch.int64
# Cast to float32
labels = i64.float()
print(labels.dtype) # torch.float32Element-Wise Arithmetic Operations
PyTorch overloads the standard Python arithmetic operators for element-wise operations on tensors of the same shape. Addition, subtraction, multiplication, and division all work element-wise. These operations are highly optimised and will run on the GPU when tensors are on a CUDA device. In-place operations (e.g., add_) modify the tensor directly without allocating new memory.
import torch
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
print(a + b) # tensor([5., 7., 9.])
print(a * b) # tensor([ 4., 10., 18.])
print(b / a) # tensor([4., 2.5, 2.])
print(a ** 2) # tensor([1., 4., 9.])
# In-place add
a.add_(1.0)
print(a) # tensor([2., 3., 4.])Matrix Multiplication with torch.matmul
Matrix multiplication is the core operation inside every neural network layer. torch.matmul (or the @ operator) performs matrix multiplication and handles batches of matrices automatically with broadcasting. For 2D inputs it computes standard matrix product; for 3D or higher it performs batched matrix multiply. This is how a linear layer computes output = input @ weight.T + bias.
import torch
A = torch.randn(3, 4) # 3x4
B = torch.randn(4, 5) # 4x5
C = torch.matmul(A, B) # 3x5
print(C.shape) # torch.Size([3, 5])
# Equivalent using @ operator
C2 = A @ B
print(torch.allclose(C, C2)) # True
# Batched matmul
batch_A = torch.randn(8, 3, 4) # batch of 8 matrices
batch_B = torch.randn(8, 4, 5)
result = batch_A @ batch_B # torch.Size([8, 3, 5])Reshaping Tensors: view and reshape
Changing the shape of a tensor without changing its data is one of the most common operations in deep learning. view requires the tensor to be contiguous in memory and returns a view (shared data); reshape works on non-contiguous tensors by copying if needed. Use -1 as a wildcard dimension and PyTorch infers the correct size. Flattening a 2D feature map to a 1D vector before a linear layer is a typical use case.
import torch
t = torch.arange(12).float() # tensor of 12 elements
print(t.shape) # torch.Size([12])
m = t.view(3, 4) # reshape to 3x4
print(m.shape) # torch.Size([3, 4])
m2 = t.view(2, -1) # PyTorch infers 6 columns
print(m2.shape) # torch.Size([2, 6])
# Flatten to 1D
flat = m.reshape(-1)
print(flat.shape) # torch.Size([12])Broadcasting: Operating on Different Shapes
Broadcasting allows PyTorch to perform operations on tensors with different shapes by implicitly expanding dimensions. The rules are borrowed from NumPy: dimensions are aligned from the right, and a size-1 dimension can be stretched to match the other tensor. Broadcasting avoids explicit tiling of data, saving memory. It is used constantly in neural network layers to add bias vectors to batched output matrices.
import torch
# Matrix (3x4) + vector (4,) -- vector broadcast over rows
matrix = torch.ones(3, 4)
bias = torch.tensor([1.0, 2.0, 3.0, 4.0]) # shape (4,)
result = matrix + bias
print(result.shape) # torch.Size([3, 4])
print(result[0]) # tensor([2., 3., 4., 5.])
# Column vector (3,1) * row vector (1,4) -> (3,4)
col = torch.arange(1, 4).float().unsqueeze(1) # (3,1)
row = torch.arange(1, 5).float().unsqueeze(0) # (1,4)
print((col * row).shape) # torch.Size([3, 4])Checking Devices: CPU vs CUDA
Each tensor lives on a device: either cpu or a CUDA GPU such as cuda:0. Checking device availability with torch.cuda.is_available() lets you write device-agnostic code. All tensors involved in a computation must be on the same device — attempting to add a CPU tensor and a GPU tensor raises a runtime error. The standard pattern is to create a device variable and move everything to it.
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print('Using device:', device)
# Create tensor directly on the chosen device
t = torch.randn(3, 3, device=device)
print(t.device)
# Move an existing CPU tensor to the device
cpu_tensor = torch.tensor([1.0, 2.0, 3.0])
gpu_tensor = cpu_tensor.to(device)
print(gpu_tensor.device)Moving Tensors to GPU with .to('cuda')
Training neural networks on a GPU can be 10-100x faster than on CPU for large models. After confirming CUDA availability, move tensors to the GPU with .to('cuda') or .cuda(). Move them back to CPU for NumPy conversion with .cpu() (NumPy cannot access GPU memory directly). The .detach() call removes a tensor from the computation graph before converting to NumPy.
import torch
# Simulate GPU workflow (falls back to CPU gracefully)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Move model weights and data to GPU
weights = torch.randn(256, 128).to(device)
inputs = torch.randn(32, 128).to(device) # batch of 32
outputs = inputs @ weights.T # matmul on device
print(outputs.shape) # torch.Size([32, 256])
# Convert back to NumPy for visualization
np_out = outputs.detach().cpu().numpy()
print(type(np_out)) # <class 'numpy.ndarray'>Useful Tensor Attributes and Utilities
Several tensor attributes and utility functions are essential for debugging and shaping data. .shape gives the dimensions; .numel() returns total element count; .dtype shows the data type; .requires_grad indicates whether gradients will be tracked. Functions like torch.cat, torch.stack, and torch.squeeze are used constantly to assemble batches and remove size-1 dimensions.
import torch
t = torch.randn(4, 3, 2)
print(t.shape) # torch.Size([4, 3, 2])
print(t.numel()) # 24
print(t.dtype) # torch.float32
# Concatenate along dim 0
a = torch.ones(2, 3)
b = torch.zeros(3, 3)
cat = torch.cat([a, b], dim=0) # shape (5, 3)
print(cat.shape)
# Remove size-1 dimensions
x = torch.randn(1, 5, 1)
print(x.squeeze().shape) # torch.Size([5])Quick Check
Test your understanding of Machine Learning with Python concepts from this lesson.
Lesson Recap
In this lesson you learned: tensors are PyTorch's core data structure supporting n-dimensional arrays on CPU or GPU, creation functions like torch.zeros, torch.randn, and torch.from_numpy give flexible ways to initialise data, and moving tensors to GPU with .to(device) is the key step to accelerate deep learning training. Next up we explore automatic differentiation with Autograd.
Frequently asked questions
Is the “PyTorch Tensors: Creation, Operations, and GPU Transfer” lesson free?
Yes — the full text of “PyTorch Tensors: Creation, Operations, and GPU Transfer” is free to read here on the web, and the Machine Learning Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Machine Learning Academy course, upgrade to CoddyKit PRO.
What will I learn in “PyTorch Tensors: Creation, Operations, and GPU Transfer”?
Learners will create tensors from Python lists and NumPy arrays, perform element-wise and matrix operations, and move tensors to GPU with .to('cuda'). You practise Machine Learning Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Machine Learning Academy?
No prior experience is required. Machine Learning Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “PyTorch Tensors: Creation, Operations, and GPU Transfer” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Machine Learning Academy lesson?
Yes. Every Machine Learning Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- PyTorch Tensors: Creation, Operations, and GPU Transfer
- Autograd: Automatic Differentiation for Backpropagation
- Building a Feedforward Network with nn.Module
- Training Loop: Loss, Optimizer, and Epochs