Deep Learning · CNN intro
Convolutions as sliding filters with shared weights, padding, stride, pooling, output-size arithmetic, and a small CNN versus an MLP on digits.
An MLP treats an image as a flat vector, which ignores that nearby pixels belong together. A convolutional layer slides a small filter (say 3×3) across the image and computes a dot product at each position:
- Weight sharing: the same filter is used at every position, so there are far fewer parameters, and a pattern is detected wherever it appears.
- Padding keeps the borders. Stride skips positions.
- Pooling (max or average) downsamples and adds a bit of translation tolerance.
- Output size: .
First I applied a hand-made vertical-edge filter, then trained a tiny CNN on the 8×8 digits.
import torch, torch.nn as nn, torch.nn.functional as F
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
img = torch.zeros(1, 1, 6, 6); img[..., 3:] = 1.0 # left dark, right bright
edge = torch.tensor([[[[-1., 0., 1.]] * 3]]).reshape(1, 1, 3, 3)
print("edge response:\n", F.conv2d(img, edge)[0, 0])
conv = nn.Conv2d(1, 8, kernel_size=3, stride=2, padding=1)
print("shape (1,1,28,28) ->", tuple(conv(torch.zeros(1, 1, 28, 28)).shape), "= (28-3+2)/2+1 = 14")
torch.manual_seed(0)
X, y = load_digits(return_X_y=True)
X = torch.tensor(X / 16.0, dtype=torch.float32).reshape(-1, 1, 8, 8); y = torch.tensor(y)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
cnn = nn.Sequential(nn.Conv2d(1, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Flatten(), nn.Linear(32 * 2 * 2, 10))
opt = torch.optim.Adam(cnn.parameters(), lr=3e-3)
for epoch in range(15):
for i in range(0, len(X_tr), 64):
opt.zero_grad(); F.cross_entropy(cnn(X_tr[i:i + 64]), y_tr[i:i + 64]).backward(); opt.step()
with torch.no_grad():
print("CNN params:", sum(p.numel() for p in cnn.parameters()), "| test accuracy:", round((cnn(X_te).argmax(1) == y_te).float().mean().item(), 3))
Output:
edge response:
tensor([[0., 3., 3., 0.],
[0., 3., 3., 0.],
[0., 3., 3., 0.],
[0., 3., 3., 0.]])
shape (1,1,28,28) -> (1, 8, 14, 14) = (28-3+2)/2+1 = 14
CNN params: 6090 | test accuracy: 0.978Sources
- CS231n: Convolutional Neural Networks — Stanford CS231n
- Dive into Deep Learning: Convolutions for Images — d2l.ai
- torch.nn.Conv2d — PyTorch