Convolutional Filters and Feature Maps
Convolutional filters are small, learnable matrices that slide across an input image to detect patterns like edges, textures, and shapes — the outputs of this process are called feature maps. They are the foundational building block of Convolutional Neural Networks (CNNs), which power nearly every modern computer vision system from facial recognition to medical imaging. The counterintuitive fact: a filter trained to detect vertical edges in photos of cats will also detect vertical edges in photos of cars — filters learn geometry, not objects.
A convolutional filter is just a small grid of numbers (say 3×3) that acts like a spotlight, scanning across an image and asking "does this pattern exist here?" at every position. The filter's numbers are learned during training, not hand-crafted. When the filter slides over the image and produces a score at every location, the resulting grid of scores is the feature map — a spatial record of "where did I find this pattern?" This is why CNNs are so powerful: stacking many filters lets the network build up from simple edges to complex object parts automatically.
How It Works
A convolutional filter performs a mathematical operation called a cross-correlation: it slides across the input, and at each position multiplies its values element-wise with the overlapping patch of the input, then sums everything up into a single number.
Explanation: At position (i,j) in the output feature map, we multiply each element of the kernel K with the corresponding element of the image patch at that location, then sum all those products into one number.
- — the single output value (one pixel in the feature map) at row , column
- — the input image (or input feature map from a previous layer)
- — the kernel / filter (a small matrix of learned weights, e.g. 3×3)
- — index variables stepping over the kernel's rows and columns
After the convolution, a bias term is added and an activation function (usually ReLU) is applied:
Explanation: We shift the raw score by a learned bias b, then clip any negative values to zero — this introduces nonlinearity and lets the network decide "yes, this pattern is present" (positive) vs. "no" (zero).
- — the activated output pixel in the feature map
- — a learned scalar bias, one per filter
- — the Rectified Linear Unit activation
The output feature map size depends on the input size, kernel size, padding, and stride:
Explanation: This tells you exactly how tall the output feature map will be given an input of height H_in, a kernel of size K, padding P rows of zeros around the border, and a stride S (how many pixels the filter jumps each step).
- — output height (width formula is identical, using )
- — input height in pixels
- — kernel size (e.g. 3 for a 3×3 kernel)
- — padding (0 = "valid", = "same" to preserve size)
- — stride (1 = move one pixel at a time, 2 = skip every other pixel)
Here is the complete operation in Python using NumPy, then PyTorch: ```python import numpy as np def convolve2d(image, kernel): """Manual 2D convolution (no padding, stride=1).""" H, W = image.shape K = kernel.shape[0] # assume square kernel H_out = H - K + 1 W_out = W - K + 1 feature_map = np.zeros((H_out, W_out)) for i in range(H_out): for j in range(W_out): patch = image[i:i+K, j:j+K] # extract local patch feature_map[i, j] = np.sum(patch * kernel) # element-wise multiply & sum return feature_map
# A hand-crafted vertical edge detector (Sobel filter) vertical_edge_kernel = np.array([ [-1, 0, 1], [-2, 0, 2], [-1, 0, 1] ], dtype=float)
# Tiny 5x5 grayscale image (values 0–255) image = np.array([ [10, 10, 80, 80, 80], [10, 10, 80, 80, 80], [10, 10, 80, 80, 80], [10, 10, 80, 80, 80], [10, 10, 80, 80, 80], ], dtype=float) fmap = convolve2d(image, vertical_edge_kernel) print("Feature map:\n", fmap)
# High values appear where the vertical edge (column 1→2) exists ``` ```python import torch import torch.nn as nn
# Define a Conv layer: 1 input channel, 8 output filters, 3x3 kernel conv = nn.Conv2d(in_channels=1, out_channels=8, kernel_size=3, padding=1)
# Fake grayscale image: batch of 1, 1 channel, 28x28 pixels x = torch.randn(1, 1, 28, 28)
# Forward pass feature_maps = conv(x) print(feature_maps.shape)
# → torch.Size([1, 8, 28, 28])
# 1 image, 8 feature maps (one per filter), same 28x28 size (padding=1) ``` ```python import torch import torch.nn as nn import matplotlib.pyplot as plt
# A simple CNN to visualize feature maps class TinyCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1) self.relu = nn.ReLU() self.pool = nn.MaxPool2d(2) # halves spatial size self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) def forward(self, x): x = self.pool(self.relu(self.conv1(x))) # → [B, 16, 14, 14] x = self.pool(self.relu(self.conv2(x))) # → [B, 32, 7, 7] return x model = TinyCNN() dummy_input = torch.randn(1, 1, 28, 28) output = model(dummy_input) print("Final feature map shape:", output.shape)
# → torch.Size([1, 32, 7, 7]) ```
Yann LeCun, a French-born computer scientist now at NYU and Meta AI, developed the first practical convolutional neural network — LeNet-5 — in 1989 while at Bell Labs, using it to read handwritten digits on checks for the US Postal Service. His key insight was that weight sharing across spatial positions (the same filter applied everywhere) could dramatically reduce parameters while encoding translation invariance. Surprisingly, LeCun's ideas were largely ignored by the mainstream ML community for nearly 20 years until AlexNet's 2012 ImageNet victory — a victory that used the exact same core idea he had demonstrated decades earlier.
Intuition
Imagine you are a detective examining a crime scene photo with a magnifying glass that has a special filter — but instead of one magnifying glass, you have 64 of them, each one sensitive to a different clue. The first magnifying glass lights up when it sees a vertical line. The second reacts to horizontal lines. A third one reacts to diagonal lines at 45 degrees. Each one slides across the entire photo, position by position, and makes a mark on its own notepad wherever it found what it was looking for. At the end, you have 64 "notepads" — those are your feature maps. Now hand those 64 notepads to a second set of detectives (the next Conv layer), and they can start combining clues: "a vertical line next to a horizontal line could be a corner... two corners could be a window... four windows could be a building." That is the entire story of how CNNs go from raw pixels to recognizing a cat. The detective gets better the more crime scenes (training images) they see — they refine exactly what patterns their magnifying glasses are sensitive to.