Build the mental model
Filtering an image means sliding a small numeric kernel across it and, at each position, computing a weighted sum of the pixels underneath — exactly the sliding-window operation you already know as convolution. What changes filter to filter is just the kernel's weights: a kernel of all-equal small positive values acts as a blur (it averages a neighborhood), while a kernel with negative weights on one side and positive on the other, like a Sobel kernel, produces a large output only where intensity changes sharply in that direction — i.e., at an edge. Sobel kernels come in a horizontal and vertical pair; applying both to the same image and combining the results (typically via square-root-of-sum-of-squares) gives a gradient-magnitude map that lights up wherever there's a strong edge in any direction, regardless of its orientation.
The key realization for this lesson is that this is not a different operation from what a CNN's Conv2d layer does — F.conv2d is the literal function a Conv2d module calls internally. The only difference between a hand-designed Sobel filter and a randomly-initialized Conv2d layer at the start of training is where the weights came from: one was chosen by a person who understood edge geometry, the other starts random and is shaped by gradient descent to minimize a loss. In fact, when researchers visualize what the very first convolutional layer of a trained image classifier actually learned, it frequently looks strikingly similar to hand-designed Sobel/Gabor-like edge and blob detectors — the network rediscovers the same useful primitives classical computer vision engineers found by hand, just from data instead of insight.
Connect it to a real scenario
The Tutorial Platform's screenshot uploader for exercise walkthroughs runs a cheap blur check before accepting an image: it applies a Sobel-style filter (exactly like this lesson's code) to the uploaded screenshot and looks at the average gradient magnitude — a crisp, in-focus code screenshot has plenty of sharp text edges and scores high, while an accidentally blurry or heavily-compressed upload has washed-out edges and scores low, triggering a 'this looks blurry, re-upload?' warning before the image ever gets attached to a lesson.
Try the working example
import torch
import torch.nn.functional as F
torch.manual_seed(0)
# A synthetic single-channel "image" -- think of it as a grayscale
# screenshot, batch of 1, 1 channel, 16x16 pixels.
image = torch.rand(1, 1, 16, 16)
# Sobel kernels: hand-designed weights that respond strongly to
# vertical and horizontal intensity changes (edges).
sobel_x = torch.tensor([[-1., 0., 1.],
[-2., 0., 2.],
[-1., 0., 1.]]).view(1, 1, 3, 3)
sobel_y = torch.tensor([[-1., -2., -1.],
[ 0., 0., 0.],
[ 1., 2., 1.]]).view(1, 1, 3, 3)
# torch.nn.functional.conv2d is the exact same op a Conv2d layer
# uses internally -- we're just supplying the weights ourselves
# instead of letting backprop learn them.
edges_x = F.conv2d(image, sobel_x, padding=1)
edges_y = F.conv2d(image, sobel_y, padding=1)
print("edges_x shape:", edges_x.shape)
print("edges_y shape:", edges_y.shape)
# Combine both directions into a single gradient-magnitude map.
magnitude = torch.sqrt(edges_x ** 2 + edges_y ** 2)
print("gradient magnitude shape:", magnitude.shape)
print("mean edge strength is positive:", (magnitude.mean() > 0).item())Prints edges_x shape: torch.Size([1, 1, 16, 16]), edges_y shape: torch.Size([1, 1, 16, 16]) (unchanged spatial size thanks to padding=1), gradient magnitude shape: torch.Size([1, 1, 16, 16]), and finally mean edge strength is positive: True — the mean is guaranteed positive since magnitude is built from a square root of squared values, so it can never be negative, and for random non-constant input it will be strictly greater than zero.5-minute try-it
Wrap sobel_x as the weight of an nn.Conv22d(1, 1, kernel_size=3, padding=1, bias=False) layer (assign it via .weight = nn.Parameter(sobel_x)), confirm .weight.requires_grad is True by default, and explain in a comment what would happen to these weights if this layer sat inside a model being trained with a real loss — would the Sobel structure survive?
One important caution
Forgetting padding=1 with a 3x3 kernel shrinks the output spatial size (16x16 in becomes 14x14 out), so a resulting edge map no longer lines up pixel-for-pixel with the original image if you try to overlay them.
Assuming F.conv2d performs 'true' mathematical convolution (which flips the kernel) when PyTorch actually implements cross-correlation (no flip) — this doesn't matter for a learned layer since the network adapts its weights either way, but it does matter if you're porting a classical filter defined via true convolution from a textbook or another library, where you'd need to flip the kernel manually to match.
Wikipedia — Edge detection — Computer Vision