Skip to content

Python PyTorch: How to Upsample a Tensor in PyTorch

Learn the three key PyTorch upsampling methods (torch.nn.Upsample, torch.nn.functional.interpolate, and torch.nn.ConvTranspose2d) with code examples for 1D/2D tensors and best practices for interpolation modes.

Upsampling is the process of increasing the spatial resolution of a tensor - transforming a low-resolution input into a higher-resolution output. This operation is essential in deep learning for tasks like image super-resolution, semantic segmentation (where decoder networks need to restore spatial dimensions), generative models, and signal processing. PyTorch provides multiple approaches for upsampling, each with different characteristics.

This guide covers the three main methods: torch.nn.Upsample, torch.nn.functional.interpolate, and torch.nn.ConvTranspose2d.

Understanding Upsampling Modes

Before diving into the code, here are the key interpolation modes available:

Mode Input Dimensions Description
nearest 1D, 2D, 3D Copies the nearest pixel value. Fast but produces blocky results
linear 1D only Linear interpolation between neighboring values
bilinear 2D only Weighted average of 4 nearest pixels. Smooth results
bicubic 2D only Weighted average of 16 nearest pixels. Smoothest results
trilinear 3D only Interpolation across 3 spatial dimensions
area Any Adaptive average pooling in reverse. Good for integer scale factors

Method 1: Using torch.nn.Upsample

torch.nn.Upsample is a module-based approach that can be added as a layer in a neural network. It supports various interpolation modes and can specify either a target size or a scale factor.

Syntax

torch.nn.Upsample(size=None, scale_factor=None, mode='nearest', align_corners=None)
Parameter Description
size Target output size (tuple or int)
scale_factor Multiplier for the spatial dimensions
mode Interpolation algorithm
align_corners Aligns corner pixels of input and output (for linear, bilinear, bicubic, trilinear modes)

Upsampling a 1D Tensor

import torch

x = torch.tensor([1., 2.]).view(1, 1, 2)
print("Input shape:", x.shape)
print("Input:", x)

upsample_nearest = torch.nn.Upsample(scale_factor=2, mode='nearest')
output = upsample_nearest(x)
print("\nNearest (2x):", output)

upsample_linear = torch.nn.Upsample(scale_factor=2, mode='linear')
output = upsample_linear(x)
print("Linear (2x): ", output)

Output:

Input shape: torch.Size([1, 1, 2])
Input: tensor([[[1., 2.]]])

Nearest (2x): tensor([[[1., 1., 2., 2.]]])
Linear (2x):  tensor([[[1.0000, 1.2500, 1.7500, 2.0000]]])

With nearest mode, each value is simply duplicated. With linear mode, intermediate values are interpolated smoothly between neighbors.

Upsampling a 2D Tensor

For 2D data (like images), the input must have shape (batch, channels, height, width):

import torch

x = torch.tensor([
    [1., 2., 3.],
    [4., 5., 6.]
]).view(1, 1, 2, 3)

print("Input shape:", x.shape)

upsample = torch.nn.Upsample(scale_factor=2, mode='nearest')
output = upsample(x)
print("\nNearest (2x) shape:", output.shape)
print(output)

upsample_bi = torch.nn.Upsample(scale_factor=2, mode='bilinear')
output_bi = upsample_bi(x)
print("\nBilinear (2x) shape:", output_bi.shape)
print(output_bi)

Output:

Input shape: torch.Size([1, 1, 2, 3])

Nearest (2x) shape: torch.Size([1, 1, 4, 6])
tensor([[[[1., 1., 2., 2., 3., 3.],
          [1., 1., 2., 2., 3., 3.],
          [4., 4., 5., 5., 6., 6.],
          [4., 4., 5., 5., 6., 6.]]]])

Bilinear (2x) shape: torch.Size([1, 1, 4, 6])
tensor([[[[1.0000, 1.2500, 1.7500, 2.2500, 2.7500, 3.0000],
          [1.7500, 2.0000, 2.5000, 3.0000, 3.5000, 3.7500],
          [3.2500, 3.5000, 4.0000, 4.5000, 5.0000, 5.2500],
          [4.0000, 4.2500, 4.7500, 5.2500, 5.7500, 6.0000]]]])

Bilinear interpolation produces smoother transitions between values compared to nearest neighbor.

Method 2: Using torch.nn.functional.interpolate

This is the functional version of upsampling - a standalone function rather than a module. It provides the same functionality but is used directly in the forward pass without being registered as a layer.

Syntax

torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None)

Example

import torch
import torch.nn.functional as F

x = torch.tensor([
    [1., 2., 3.],
    [4., 5., 6.]
]).view(1, 1, 2, 3)

print("Input shape:", x.shape)

output = F.interpolate(x, scale_factor=2, mode='bilinear', align_corners=False)
print("Output shape:", output.shape)
print(output)

Output:

Input shape: torch.Size([1, 1, 2, 3])
Output shape: torch.Size([1, 1, 4, 6])
tensor([[[[1.0000, 1.2500, 1.7500, 2.2500, 2.7500, 3.0000],
          [1.7500, 2.0000, 2.5000, 3.0000, 3.5000, 3.7500],
          [3.2500, 3.5000, 4.0000, 4.5000, 5.0000, 5.2500],
          [4.0000, 4.2500, 4.7500, 5.2500, 5.7500, 6.0000]]]])

Specifying Target Size Instead of Scale Factor

You can specify the exact output size instead of a scale factor:

import torch
import torch.nn.functional as F

x = torch.randn(1, 1, 4, 4)
print("Input shape:", x.shape)

output = F.interpolate(x, size=(8, 12), mode='bilinear', align_corners=False)
print("Output shape:", output.shape)

Output:

Input shape: torch.Size([1, 1, 4, 4])
Output shape: torch.Size([1, 1, 8, 12])

Method 3: Using torch.nn.ConvTranspose2d (Learnable Upsampling)

Unlike the previous methods that use fixed interpolation formulas, transposed convolution (also called deconvolution) uses learnable filters to upsample. This means the network learns how to best upsample during training, which can produce better results for specific tasks.

Syntax

torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0)

Example

import torch

x = torch.randn(1, 3, 2, 4)
print("Input shape:", x.shape)

transposed_conv = torch.nn.ConvTranspose2d(
    in_channels=3,
    out_channels=3,
    kernel_size=3,
    stride=2,
    padding=1,
    output_padding=1
)

output = transposed_conv(x)
print("Output shape:", output.shape)

Output:

Input shape: torch.Size([1, 3, 2, 4])
Output shape: torch.Size([1, 3, 4, 8])

The spatial dimensions are doubled: 2→4 (height) and 4→8 (width).

Common Mistake: Wrong Input Dimensions

A frequent error is passing a tensor without the batch and channel dimensions:

import torch

x = torch.tensor([[1., 2.], [3., 4.]])

upsample = torch.nn.Upsample(scale_factor=2)
try:
    output = upsample(x)
except RuntimeError as e:
    print(f"Error: {e}")

Output:

Error: Input Error: Only 3D, 4D and 5D input Tensors supported (got 2D) for the modes: nearest | linear | bilinear | bicubic | trilinear | area | nearest-exact (got nearest)

The correct approach - add batch and channel dimensions:

import torch

x = torch.tensor([[1., 2.], [3., 4.]]).view(1, 1, 2, 2)

upsample = torch.nn.Upsample(scale_factor=2)
output = upsample(x)
print("Output shape:", output.shape)

Output:

Output shape: torch.Size([1, 1, 4, 4])

Method Comparison

Method Learnable Use Case Pros Cons
nn.Upsample No Fixed upsampling in nn.Sequential Simple, no parameters to train Fixed interpolation only
F.interpolate No Functional upsampling in forward() Flexible, supports target size Same as Upsample, functional style
nn.ConvTranspose2d Yes Decoder networks (U-Net, GANs) Learns optimal upsampling More parameters, possible checkerboard artifacts

For most applications, F.interpolate with bilinear mode provides a good balance of quality and simplicity. Use ConvTranspose2d when you need the model to learn the upsampling strategy, and be aware of potential artifact issues.

Navigation