Sahaj.blogs
3 Apr 2025 · StudyPhase 8 · Deep Learning

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: ⌊(W−K+2P)/S⌋+1\lfloor (W - K + 2P)/S \rfloor + 1.

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.978

#deep-learning#cnn

Sources

  1. CS231n: Convolutional Neural Networks — Stanford CS231n
  2. Dive into Deep Learning: Convolutions for Images — d2l.ai
  3. torch.nn.Conv2d — PyTorch