Module 3: Tensor Operations — Part 4: Practice Projects
This module provides hands-on practice with PyTorch tensor operations, including matrix calculations, tensor reshaping, dimension manipulation, concatenation, stacking, splitting, and real-world deep learning workflows.
The goal is to move from learning individual PyTorch tensor functions to using them together in practical problems.
Learning Objectives
After completing this module, you will be able to:
- Build a matrix calculator using PyTorch tensors.
- Perform arithmetic and matrix operations with PyTorch.
- Apply tensor reduction operations such as sum, mean, maximum, and minimum.
- Reshape and flatten tensors for neural network processing.
- Add and remove tensor dimensions using
unsqueeze()andsqueeze(). - Rearrange tensor dimensions using
permute()andtranspose(). - Concatenate, stack, split, and chunk tensors.
- Prepare image tensors for convolutional neural networks.
- Work with batch dimensions correctly.
- Analyze tensor data using reduction operations.
- Solve practical PyTorch tensor manipulation problems.
- Apply tensor operations commonly used in CNN and Transformer workflows.
Why Tensor Operations Matter in PyTorch
PyTorch tensor operations are the foundation of almost every deep learning computation.
A neural network continuously transforms tensors:
1Input Data 2 ↓ 3Tensor 4 ↓ 5Shape Transformation 6 ↓ 7Neural Network Layer 8 ↓ 9Matrix Multiplication 10 ↓ 11Activation 12 ↓ 13Loss Calculation 14 ↓ 15Gradient Computation 16 ↓ 17Updated Tensor
For example, an image classification model may process data through shapes such as:
1Image 2(224, 224, 3) 3 ↓ 4Permute 5(3, 224, 224) 6 ↓ 7Add Batch Dimension 8(1, 3, 224, 224) 9 ↓ 10Convolution 11 ↓ 12Feature Maps 13 ↓ 14Flatten 15 ↓ 16Fully Connected Layer
Understanding tensor shapes and operations is therefore essential for learning PyTorch, deep learning, computer vision, neural networks, CNNs, and Transformers.
Project 1: PyTorch Matrix Calculator
Project Overview
In this project, you will build a simple matrix calculator with PyTorch.
The calculator demonstrates:
- Matrix addition
- Matrix subtraction
- Element-wise multiplication
- Matrix multiplication
- Matrix division
- Transpose
- Tensor shape inspection
- Sum
- Mean
- Maximum
- Minimum
This project combines several PyTorch tensor operations into a single practical program.
Step 1: Import PyTorch
1import torch
Step 2: Create Two Matrices
We will create two 2 × 2 floating-point matrices.
1A = torch.tensor( 2 [[1, 2], 3 [3, 4]], 4 dtype=torch.float32 5) 6 7B = torch.tensor( 8 [[5, 6], 9 [7, 8]], 10 dtype=torch.float32 11) 12 13print("Matrix A:") 14print(A) 15 16print("\nMatrix B:") 17print(B)
Output:
1Matrix A: 2tensor([[1., 2.], 3 [3., 4.]]) 4 5Matrix B: 6tensor([[5., 6.], 7 [7., 8.]])
Using float32 is common in deep learning because floating-point tensors are required by many neural network operations.
Matrix Addition
Matrix addition adds corresponding elements.
1result = A + B 2 3print(result)
Output:
1tensor([[ 6., 8.], 2 [10., 12.]])
Calculation:
11 + 5 = 6 22 + 6 = 8 33 + 7 = 10 44 + 8 = 12
You can also use:
1result = torch.add(A, B)
Matrix Subtraction
1result = A - B 2 3print(result)
Output:
1tensor([[-4., -4.], 2 [-4., -4.]])
Subtraction is also an element-wise tensor operation.
Element-wise Multiplication
The * operator performs element-wise multiplication.
1result = A * B 2 3print(result)
Output:
1tensor([[ 5., 12.], 2 [21., 32.]])
The calculation is:
11 × 5 = 5 22 × 6 = 12 33 × 7 = 21 44 × 8 = 32
This is different from matrix multiplication.
Matrix Multiplication
For matrix multiplication, use @ or torch.matmul().
1result = A @ B 2 3print(result)
Output:
1tensor([[19., 22.], 2 [43., 50.]])
The first element is calculated as:
1(1 × 5) + (2 × 7) 2 3= 5 + 14 4 5= 19
The complete matrix multiplication is:
1 [1 2] [5 6] 2A × B = [3 4] × [7 8] 3 4 [19 22] 5 = [43 50]
The same operation can be written as:
1result = torch.matmul(A, B)
Matrix Division
The / operator performs element-wise division.
1result = A / B 2 3print(result)
Output:
1tensor([[0.2000, 0.3333], 2 [0.4286, 0.5000]])
For example:
11 / 5 = 0.2 22 / 6 ≈ 0.3333 33 / 7 ≈ 0.4286 44 / 8 = 0.5
Matrix Transpose
Transpose swaps the rows and columns of a 2D tensor.
1result = A.T 2 3print(result)
Output:
1tensor([[1., 3.], 2 [2., 4.]])
For a 2D tensor:
1Original: 2 31 2 43 4 5 6Transpose: 7 81 3 92 4
You can also use:
1result = torch.transpose(A, 0, 1)
Inspect Tensor Shape
Use .shape to inspect the dimensions of a tensor.
1print(A.shape)
Output:
1torch.Size([2, 2])
This means the matrix contains:
12 rows × 2 columns
Calculate the Sum
1result = torch.sum(A) 2 3print(result)
Output:
1tensor(10.)
Calculation:
11 + 2 + 3 + 4 = 10
Calculate the Mean
1result = torch.mean(A) 2 3print(result)
Output:
1tensor(2.5000)
Calculation:
1(1 + 2 + 3 + 4) / 4 = 2.5
Find the Maximum Value
1result = torch.max(A) 2 3print(result)
Output:
1tensor(4.)
Find the Minimum Value
1result = torch.min(A) 2 3print(result)
Output:
1tensor(1.)
Complete PyTorch Matrix Calculator
The following program combines all the operations into one example.
1import torch 2 3A = torch.tensor( 4 [[1, 2], 5 [3, 4]], 6 dtype=torch.float32 7) 8 9B = torch.tensor( 10 [[5, 6], 11 [7, 8]], 12 dtype=torch.float32 13) 14 15print("Matrix A:") 16print(A) 17 18print("\nMatrix B:") 19print(B) 20 21print("\nAddition:") 22print(A + B) 23 24print("\nSubtraction:") 25print(A - B) 26 27print("\nElement-wise Multiplication:") 28print(A * B) 29 30print("\nMatrix Multiplication:") 31print(A @ B) 32 33print("\nElement-wise Division:") 34print(A / B) 35 36print("\nTranspose of A:") 37print(A.T) 38 39print("\nShape of A:") 40print(A.shape) 41 42print("\nSum of A:") 43print(torch.sum(A)) 44 45print("\nMean of A:") 46print(torch.mean(A)) 47 48print("\nMaximum of A:") 49print(torch.max(A)) 50 51print("\nMinimum of A:") 52print(torch.min(A))
This example gives you practical experience with several important PyTorch tensor functions.
Project 2: Tensor Manipulation Toolkit
The second project demonstrates common PyTorch tensor manipulation techniques.
We will work with:
arange()reshape()flatten()unsqueeze()squeeze()permute()transpose()stack()cat()split()
These operations are frequently used when preparing data for neural networks.
Create a Tensor with torch.arange()
1import torch 2 3x = torch.arange(24) 4 5print(x)
Output:
1tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 2 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23])
The tensor contains 24 elements.
Reshape a Tensor
We can convert the one-dimensional tensor into a 3D tensor.
1y = x.reshape(2, 3, 4) 2 3print(y) 4print("Shape:", y.shape)
Output shape:
1Shape: torch.Size([2, 3, 4])
The number of elements must remain unchanged:
12 × 3 × 4 = 24
Therefore:
1(24) 2 ↓ 3(2, 3, 4)
A reshape cannot change the total number of elements.
Flatten a Tensor
Flatten converts multiple dimensions into a single dimension.
1flat = torch.flatten(y) 2 3print(flat) 4print("Shape:", flat.shape)
Output shape:
1torch.Size([24])
A common neural network pattern is:
1features = torch.flatten(x, start_dim=1)
This preserves the batch dimension while flattening the remaining dimensions.
Add a Dimension with unsqueeze()
unsqueeze() inserts a dimension of size 1.
1z = y.unsqueeze(0) 2 3print("Original shape:", y.shape) 4print("New shape:", z.shape)
Output:
1Original shape: torch.Size([2, 3, 4]) 2New shape: torch.Size([1, 2, 3, 4])
This is commonly used to add a batch dimension.
For example:
1Image: 2 3(3, 224, 224) 4 5 ↓ unsqueeze(0) 6 7(1, 3, 224, 224)
The 1 represents a batch containing one image.
Remove a Dimension with squeeze()
squeeze() removes dimensions whose size is 1.
1result = z.squeeze() 2 3print(result.shape)
Output:
1torch.Size([2, 3, 4])
The dimension added by unsqueeze() has been removed.
Rearrange Dimensions with permute()
permute() changes the order of multiple dimensions.
Suppose:
1y.shape
is:
1(2, 3, 4)
We can rearrange it:
1result = y.permute(2, 0, 1) 2 3print(result.shape)
Output:
1torch.Size([4, 2, 3])
The dimensions are reordered according to:
1Original: 2 3(2, 3, 4) 4 5permute(2, 0, 1) 6 7↓ 8 9(4, 2, 3)
Transpose Tensor Dimensions
transpose() swaps two dimensions.
1result = y.transpose(1, 2) 2 3print(result.shape)
Output:
1torch.Size([2, 4, 3])
The dimensions at positions 1 and 2 were exchanged.
permute() vs transpose()
| Operation | Purpose |
|---|---|
permute() | Reorders multiple dimensions |
transpose() | Swaps two dimensions |
Example:
1x.permute(2, 0, 1)
can reorder all three dimensions.
Whereas:
1x.transpose(1, 2)
only swaps dimensions 1 and 2.
Stack Tensors
torch.stack() combines tensors by creating a new dimension.
1a = torch.tensor([1, 2, 3]) 2b = torch.tensor([4, 5, 6]) 3 4result = torch.stack((a, b)) 5 6print(result) 7print("Shape:", result.shape)
Output:
1tensor([[1, 2, 3], 2 [4, 5, 6]]) 3 4Shape: torch.Size([2, 3])
The original tensors have shape:
1(3)
After stacking:
1(2, 3)
The rank increases by one.
Concatenate Tensors
torch.cat() joins tensors along an existing dimension.
1result = torch.cat((a, b)) 2 3print(result) 4print("Shape:", result.shape)
Output:
1tensor([1, 2, 3, 4, 5, 6]) 2 3Shape: torch.Size([6])
Unlike stack(), concatenation does not create a new dimension.
torch.cat() vs torch.stack()
This distinction is important in PyTorch tensor manipulation.
1a = [1, 2, 3] 2b = [4, 5, 6]
Using cat():
1[1, 2, 3, 4, 5, 6] 2 3Shape: (6)
Using stack():
1[[1, 2, 3], 2 [4, 5, 6]] 3 4Shape: (2, 3)
Therefore:
torch.cat() | torch.stack() |
|---|---|
| Concatenates along an existing dimension | Creates a new dimension |
| Rank normally stays the same | Rank increases by one |
| Useful for joining tensors | Useful for creating batches |
Split a Tensor
torch.split() divides a tensor into smaller tensors.
1parts = torch.split(x, 6) 2 3for part in parts: 4 print(part)
Output:
1tensor([0, 1, 2, 3, 4, 5]) 2tensor([6, 7, 8, 9, 10, 11]) 3tensor([12, 13, 14, 15, 16, 17]) 4tensor([18, 19, 20, 21, 22, 23])
The original tensor contains 24 elements.
Splitting it into groups of 6 creates:
124 / 6 = 4 parts
Split with Different Sizes
You can specify the size of every section.
1parts = torch.split(x, [4, 8, 12]) 2 3for part in parts: 4 print(part)
This creates:
1Part 1 → 4 elements 2Part 2 → 8 elements 3Part 3 → 12 elements
Total:
14 + 8 + 12 = 24
Chunk a Tensor
torch.chunk() divides a tensor into a specified number of chunks.
1x = torch.arange(12) 2 3parts = torch.chunk(x, 3) 4 5for part in parts: 6 print(part)
Output:
1tensor([0, 1, 2, 3]) 2tensor([4, 5, 6, 7]) 3tensor([8, 9, 10, 11])
Here, 12 elements are divided into 3 chunks.
split() vs chunk()
torch.split() | torch.chunk() |
|---|---|
| Specify chunk size or section sizes | Specify number of chunks |
| Useful when exact section sizes are known | Useful when desired number of parts is known |
Real-World Example: Preparing an Image for a CNN
Computer vision is one of the most important applications of PyTorch tensor operations.
An image may initially be represented as:
1Height × Width × Channels 2 3(224, 224, 3)
Many PyTorch computer vision models use:
1Channels × Height × Width 2 3(3, 224, 224)
We can rearrange the dimensions using permute().
1image = torch.randn(224, 224, 3) 2 3print("Original:", image.shape) 4 5image = image.permute(2, 0, 1) 6 7print("CHW:", image.shape)
Output:
1Original: torch.Size([224, 224, 3]) 2CHW: torch.Size([3, 224, 224])
Add the Batch Dimension
Neural networks generally process batches of samples.
A single image has:
1(3, 224, 224)
Add a batch dimension:
1image = image.unsqueeze(0) 2 3print(image.shape)
Output:
1torch.Size([1, 3, 224, 224])
Now the tensor represents:
1Batch = 1 2Channels = 3 3Height = 224 4Width = 224
Flatten Image Features
Suppose a later stage of the model produces a tensor that needs to be passed into a fully connected layer.
You can flatten everything except the batch dimension:
1features = torch.flatten(image, start_dim=1) 2 3print(features.shape)
For this example:
1(1, 3, 224, 224)
becomes:
1(1, 150528)
because:
13 × 224 × 224 = 150528
The batch dimension remains unchanged.
Complete Image Tensor Pipeline
1import torch 2 3image = torch.randn(224, 224, 3) 4 5print("Original:", image.shape) 6 7# Convert HWC to CHW 8image = image.permute(2, 0, 1) 9 10print("After permute:", image.shape) 11 12# Add batch dimension 13image = image.unsqueeze(0) 14 15print("After unsqueeze:", image.shape) 16 17# Flatten all dimensions except batch 18features = torch.flatten(image, start_dim=1) 19 20print("After flatten:", features.shape)
Output:
1Original: torch.Size([224, 224, 3]) 2After permute: torch.Size([3, 224, 224]) 3After unsqueeze: torch.Size([1, 3, 224, 224]) 4After flatten: torch.Size([1, 150528])
This demonstrates how PyTorch tensor shape manipulation is used in real deep learning pipelines.
Project 3: Student Marks Analysis
Tensor operations are not limited to images and neural networks. They can also be used for numerical data analysis.
Consider the following marks:
1import torch 2 3marks = torch.tensor( 4 [ 5 [80, 75, 90], 6 [85, 95, 88], 7 [70, 60, 78] 8 ], 9 dtype=torch.float32 10) 11 12print(marks)
The rows represent students:
1Student 1 2Student 2 3Student 3
The columns represent subjects:
1Math 2Science 3English
Calculate Total Marks for Each Student
Use dim=1 to reduce each row.
1total = torch.sum(marks, dim=1) 2 3print(total)
Output:
1tensor([245., 268., 208.])
Calculations:
1Student 1: 280 + 75 + 90 = 245 3 4Student 2: 585 + 95 + 88 = 268 6 7Student 3: 870 + 60 + 78 = 208
Calculate Average Marks
1average = torch.mean(marks, dim=1) 2 3print(average)
Output:
1tensor([81.6667, 89.3333, 69.3333])
Find the Highest Mark
1highest = torch.max(marks) 2 3print(highest)
Output:
1tensor(95.)
Find the Best Student
First calculate the total marks:
1total = torch.sum(marks, dim=1)
Then find the index of the largest total:
1best_student = torch.argmax(total) 2 3print(best_student)
Output:
1tensor(1)
Because PyTorch uses zero-based indexing:
1Index 0 → Student 1 2Index 1 → Student 2 3Index 2 → Student 3
Therefore, Student 2 has the highest total score.
Find the Best Student's Score
You can also retrieve the highest total directly:
1best_score = torch.max(total) 2 3print(best_score)
Output:
1tensor(268.)
This illustrates an important distinction:
1torch.max(total)
returns the maximum value.
Whereas:
1torch.argmax(total)
returns the index of the maximum value.
Interview Questions
What is the difference between reshape() and view()?
reshape() returns a tensor with the requested shape and may return a view when possible or create a copy when necessary.
view() returns a view of the same underlying data and generally requires the tensor's memory layout to be compatible.
Example:
1x = torch.arange(12) 2 3y = x.reshape(3, 4) 4 5print(y.shape)
Output:
1torch.Size([3, 4])
A practical rule for beginners is to use reshape() when you simply need to change the shape.
What is the difference between torch.cat() and torch.stack()?
torch.cat() joins tensors along an existing dimension.
1a = torch.tensor([1, 2]) 2b = torch.tensor([3, 4]) 3 4print(torch.cat((a, b)))
Result:
1tensor([1, 2, 3, 4])
torch.stack() creates a new dimension.
1print(torch.stack((a, b)))
Result:
1tensor([[1, 2], 2 [3, 4]])
What is the difference between permute() and transpose()?
permute() can rearrange multiple dimensions:
1x.permute(2, 0, 1)
transpose() swaps two dimensions:
1x.transpose(1, 2)
Why is unsqueeze() useful in PyTorch?
unsqueeze() adds a dimension of size 1.
A common use is adding a batch dimension:
1image = image.unsqueeze(0)
For example:
1(3, 224, 224) 2 ↓ 3(1, 3, 224, 224)
What is the difference between A * B and A @ B?
A * B performs element-wise multiplication.
1A * B
A @ B performs matrix multiplication.
1A @ B
These operations have different mathematical meanings.
What is broadcasting?
Broadcasting allows PyTorch to perform operations on tensors with compatible shapes without explicitly creating copies of the smaller tensor.
For example:
1x = torch.tensor([1, 2, 3]) 2 3print(x + 10)
Output:
1tensor([11, 12, 13])
The scalar is logically applied to every element.
Common Tensor Operation Mistakes
Mistake 1: Using * for Matrix Multiplication
Incorrect when matrix multiplication is intended:
1A * B
Correct:
1A @ B
or:
1torch.matmul(A, B)
Mistake 2: Invalid Reshape
This is invalid:
1x = torch.arange(10) 2 3x.reshape(3, 4)
Why?
The original tensor has 10 elements, while the requested shape requires:
13 × 4 = 12
The total number of elements must remain the same.
Mistake 3: Forgetting the Batch Dimension
A single image may have:
1(3, 224, 224)
while a batch of images has:
1(batch, 3, 224, 224)
For a single image:
1image = image.unsqueeze(0)
adds the batch dimension.
Mistake 4: Confusing argmax() with max()
max() returns the maximum value:
1torch.max(x)
argmax() returns the position of the maximum value:
1torch.argmax(x)
For example:
1x = torch.tensor([10, 50, 20]) 2 3print(torch.max(x)) 4print(torch.argmax(x))
Output:
1tensor(50) 2tensor(1)
Mistake 5: Ignoring Tensor Shapes
Before performing tensor operations, inspect the shape:
1print(x.shape)
For more detailed debugging:
1print("Shape:", x.shape) 2print("Dimensions:", x.ndim) 3print("Dtype:", x.dtype) 4print("Device:", x.device)
Mistake 6: Using view() After Dimension Reordering Without Considering Memory Layout
Operations such as permute() can produce a tensor whose memory layout is non-contiguous.
For example:
1x = torch.arange(12).reshape(3, 4) 2 3x = x.permute(1, 0)
When a view operation is not compatible with the resulting memory layout, use:
1x = x.reshape(...)
or:
1x = x.contiguous().view(...)
PyTorch Tensor Operations Best Practices
- Check tensor shapes frequently with
.shape. - Use
.ndimwhen you need to know the number of dimensions. - Use
reshape()when changing tensor shape. - Use
permute()when changing the order of multiple dimensions. - Use
transpose()when swapping two dimensions. - Use
flatten(start_dim=1)when flattening model features while preserving the batch dimension. - Use
torch.cat()when joining tensors along an existing dimension. - Use
torch.stack()when a new dimension is required. - Use
torch.split()when exact split sizes are useful. - Use
torch.chunk()when you know how many pieces you want. - Make sure tensors have compatible shapes before performing operations.
- Keep tensors on the same device before combining them.
- Prefer clear shape names and comments when working with complex neural network tensors.
Practice Exercises
Beginner Level
Create two 2 × 2 tensors and perform:
- Addition
- Subtraction
- Element-wise multiplication
- Element-wise division
- Matrix multiplication
- Transpose
- Sum
- Mean
- Maximum
- Minimum
Print the result of every operation.
Tensor Shape Practice
Create:
1x = torch.arange(24)
Perform the following:
- Reshape it to
(2, 3, 4). - Flatten it.
- Add a dimension using
unsqueeze(). - Remove the dimension using
squeeze(). - Rearrange its dimensions using
permute(). - Swap two dimensions using
transpose().
Print the shape after every operation.
Intermediate Level
Create a batch of ten random RGB images:
1images = torch.randn(10, 3, 64, 64)
Perform the following:
- Print the shape.
- Flatten each image while preserving the batch dimension.
- Split the batch into two parts.
- Concatenate the parts again.
- Verify that the final tensor has the original shape.
Useful operations:
1torch.flatten() 2torch.split() 3torch.cat()
Advanced Level: Transformer Tensor Shapes
Simulate Transformer input with:
1batch_size = 4 2sequence_length = 128 3embedding_dimension = 768 4 5x = torch.randn( 6 batch_size, 7 sequence_length, 8 embedding_dimension 9) 10 11print(x.shape)
The resulting shape is:
1(4, 128, 768)
Now convert it to:
1(sequence_length, batch_size, embedding_dimension)
using:
1x = x.permute(1, 0, 2) 2 3print(x.shape)
Expected shape:
1torch.Size([128, 4, 768])
Convert it back:
1x = x.permute(1, 0, 2) 2 3print(x.shape)
Expected:
1torch.Size([4, 128, 768])
This exercise helps develop the tensor-shape skills required for Transformer models, large language models, and modern deep learning architectures.
Module 3 Final Summary
You have now completed the practical portion of PyTorch Tensor Operations.
You learned how to combine individual tensor operations into complete programs and practical data-processing pipelines.
Key concepts include:
- PyTorch arithmetic operations
- Element-wise tensor operations
- Broadcasting
- Matrix multiplication
- Dot products
- Reduction operations
- Tensor concatenation
- Tensor stacking
- Tensor splitting
- Tensor chunking
- Tensor reshaping
- Tensor flattening
- Adding and removing dimensions
- Dimension permutation
- Tensor transposition
- Image tensor preprocessing
- Batch dimension handling
- Tensor analysis with reduction functions
The most important skill is not memorizing every PyTorch function. It is learning to reason about tensor shapes, dimensions, data types, devices, and mathematical operations.
For example:
1Image 2(224, 224, 3) 3 ↓ 4permute() 5(3, 224, 224) 6 ↓ 7unsqueeze() 8(1, 3, 224, 224) 9 ↓ 10Neural Network 11 ↓ 12flatten() 13(1, features) 14 ↓ 15Linear Layer 16 ↓ 17Prediction
Once you understand this type of tensor flow, many PyTorch errors become easier to diagnose.
Key PyTorch Tensor Functions to Remember
| Function | Main Purpose |
|---|---|
torch.tensor() | Create tensors |
torch.arange() | Create sequences |
reshape() | Change tensor shape |
view() | Create a view with a different shape |
flatten() | Flatten dimensions |
unsqueeze() | Add a dimension |
squeeze() | Remove dimensions of size 1 |
permute() | Rearrange dimensions |
transpose() | Swap two dimensions |
torch.cat() | Concatenate tensors |
torch.stack() | Stack tensors along a new dimension |
torch.split() | Split tensors by size |
torch.chunk() | Divide tensors into chunks |
torch.sum() | Calculate sum |
torch.mean() |
Next Module
In the next module, Tensor Indexing and Slicing, you will learn how to access individual tensor elements, select rows and columns, slice multidimensional tensors, modify tensor values, use boolean masks, and perform advanced indexing operations.
These techniques are essential for PyTorch data preprocessing, dataset manipulation, model debugging, feature extraction, and deep learning workflows.