Thomas Piacentini
← Atelier

Fiche

CNN Architectures and Transfer Learning

Texte original · anglais

Size convolutions, build LeNet to ResNet, train a classifier and fine-tune a pretrained backbone. PyTorch and NumPy.

Formulas

Output size
H_{out} = \lfloor (H - K + 2P) / S \rfloor + 1
Same padding
P = (K - 1) / 2 for odd K and S = 1.
Conv parameters
C_out * C_in * K * K + C_out. The last term is the biases.
Receptive field
1 + L (K - 1) for L stacked convs at stride 1.
Two 3x3 or one 5x5
same 5x5 view. 18C^2 against 25C^2 parameters.
Residual block
y = F(x) + x. The block learns identity with F(x) = 0.
Cross-entropy
-z_y + \log \sum_j \exp(z_j) on raw logits.
Layer-wise LR decay
\mathrm{lr}_k = \mathrm{base} \cdot \mathrm{decay}^{L - k}
Sources

Four families

LeNet-5 (1998)
conv, avg pool, dense, tanh. 60,000 parameters.
AlexNet (2012)
ReLU, dropout, 5 conv and 3 dense layers. 60M parameters.
VGG (2014)
only 3x3 convs, 16 or 19 layers. 138M parameters.
Inception (2014)
1x1, 3x3, 5x5 and pool in parallel, then concatenate.
ResNet (2015)
identity skip connections. 100+ layers train.
BasicBlock
two 3x3 convs with a skip. ResNet-18 and ResNet-34.
Bottleneck
1x1 down, 3x3, 1x1 up. ResNet-50, -101, -152.
ResNet-18 or VGG-16
11.7M or 138M parameters. Top-1 69.8% or 71.6%.
Sources

TinyResNet for 32x32 input

Input(1, 3, 32, 32)stem3x3 conv, 32 ch, BN, ReLUlayer12 BasicBlocks, 32 chlayer2stride 2, 64 chlayer3stride 2, 128 chlayer4stride 2, 256 chheadAdaptiveAvgPool2d, LinearLogits10 classes
Nodes and connections
NodeRoleKind
Input(1, 3, 32, 32)
stem3x3 conv, 32 ch, BN, ReLU
layer12 BasicBlocks, 32 ch
layer2stride 2, 64 ch
layer3stride 2, 128 ch
layer4stride 2, 256 ch
headAdaptiveAvgPool2d, Linear
Logits10 classes
FromToConnection
Inputstem
stemlayer1
layer1layer2
layer2layer3
layer3layer4
layer4head
headLogits
Sources

ResNet BasicBlock

self.conv1 = nn.Conv2d(in_c, out_c, kernel_size=3, stride=stride, padding=1, bias=False)BN beta holds the bias
self.conv2 = nn.Conv2d(out_c, out_c, kernel_size=3, stride=1, padding=1, bias=False)
if stride != 1 or in_c != out_c:
self.shortcut = nn.Sequential(
nn.Conv2d(in_c, out_c, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_c))1x1 conv matches the shape
else:
self.shortcut = nn.Identity()no-op skip
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))no ReLU before the add
out = out + self.shortcut(x)F(x) + x
return F.relu(out)
Sources

Tasks in code

TaskCode
Output size of one convoutput_size(h_in, k, p, s)
Receptive field of a stackreceptive_field(layers)
Conv as one matmulconv2d_im2col(x, w, b, padding=1)
Apply a hand kernelapply_kernel(x[0], KERNELS["sobel_x"])
Mix a batchmixup_batch(x, y, num_classes, alpha=0.2)
Loss on soft labelssoft_cross_entropy(logits, soft_targets)
Frozen backbone, new headmake_feature_extractor(num_classes=10)
Keep BN running statsfreeze_bn_stats(model)
Sources

One training epoch

  1. Augment random_hflip() and random_crop(pad=4) with reflect pad. Train set only.
  2. Normalise standardize(mean, std) with the mean and std of your train set.
  3. DataLoader shuffle=True for train. shuffle=False for val.
  4. Model model.train() first. The model gives raw logits (N, C).
  5. Loss soft_cross_entropy with mixup, else cross_entropy with label_smoothing=0.1.
  6. Step optimizer.zero_grad(), loss.backward(), optimizer.step().
  7. Metrics add loss.item(), so no graph stays alive.
  8. Epoch end scheduler.step(). Then model.eval() and evaluate under torch.no_grad.
Sources

Fine-tune with discriminative LR

model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, 10)new head
groups = discriminative_param_groups(model, base_lr=1e-3, decay=0.3)
optimizer = SGD(groups, momentum=0.9, weight_decay=1e-4, nesterov=True)
scheduler = CosineAnnealingLR(optimizer, T_max=epochs)
for epoch in range(epochs):
model.train()
if freeze_bn:
freeze_bn_stats(model)after model.train()
start, unfreeze = progressive_unfreeze_schedule(model)
start()only fc trains
unfreeze(epoch)layer4 first, then layer3
Sources

Pick a transfer recipe

< 1k images, close to ImageNet
freeze the backbone. Train the head only.
1k-10k, close
freeze the first 2-3 stages. Fine-tune the rest.
10k-100k, any domain
fine-tune end-to-end with discriminative LR.
100k+, far domain
fine-tune everything. Consider training from scratch.
BN, >= 5k examples
keep BN in train mode. It updates its running statistics.
BN, small dataset
freeze BN in eval mode. Keep the ImageNet statistics.
Tiny batch per GPU
replace BN with GroupNorm.
Head to replace
backbone.fc (ResNet), backbone.classifier[1] (EfficientNet, MobileNet).
Sources

Training defaults

SettingDefaultNote
SGD lr0.1TinyResNet from scratch, cosine over 10 epochs
momentum0.9With nesterov=True
weight_decay5e-4Fine-tune uses 1e-4
label_smoothing0.1Target 1-eps for the true class
Mixup alpha0.2lambda from Beta(a, a)
base_lr1e-3The fc group. Earlier stages get less
decay0.3conv1 gets about 0.00243 times base_lr
batch_size128Val loader uses 256
Sources

Troubleshooting

SymptomCauseFix
Wrong gradient, flat curveSoftmax before the lossPass raw logits to CrossEntropyLoss
Looks like an unstable LRGradients accumulateCall optimizer.zero_grad() each step
About 1% accuracy lostImageNet mean and std on CIFAR-10Compute stats on your train set
A "6" becomes a "9"Augment changes the labelUse smaller rotation ranges
Fine-tuned below pretrained-onlyLR or BN bugUse discriminative LR, check BN mode
5-15% accuracy lost silentlyBN stats from another distributionFreeze BN stats or use GroupNorm
All images get one classLR destroys pretrained featuresLower the backbone LR
Deep plain net trains worseDegradation past ~20 layersAdd residual connections
Sources

Questions

Why 3x3 kernels?
It is the smallest odd kernel with a centre.
How to downsample without a pool?
Use a conv with stride=2. It halves H and W.
Why does mixup raise train loss?
The targets are soft. Test accuracy goes up.
Which two numbers for a transfer run?
Pretrained-only is the floor. Fine-tuned is the ceiling.
Why a smaller LR for early layers?
They hold generic edge and texture features.
When to rebuild the optimizer?
Each time the set of trainable parameters changes.
Sources

Conv layer shapes

conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)
tuple(conv.weight.shape)(C_out, C_in, K, K)
sum(p.numel() for p in conv.parameters())64 * 3 * 3 * 3 + 64 = 1,792
y = conv(torch.randn(8, 3, 224, 224))padding=0 gives 222x222
Sources
Original source
---
id: cnn-architectures
title: CNN Architectures and Transfer Learning
code: CA
subtitle: Size convolutions, build LeNet to ResNet, train a classifier and fine-tune a pretrained backbone. PyTorch and NumPy.
hue: 15
sources: phases/04-computer-vision/02-convolutions-from-scratch, phases/04-computer-vision/03-cnns-lenet-to-resnet, phases/04-computer-vision/04-image-classification, phases/04-computer-vision/05-transfer-learning
source_hash: d153dcbc9565a455
---

## Formulas
type: concepts
from: phases/04-computer-vision/02-convolutions-from-scratch/docs/en.md, phases/04-computer-vision/03-cnns-lenet-to-resnet/docs/en.md, phases/04-computer-vision/04-image-classification/docs/en.md, phases/04-computer-vision/05-transfer-learning/docs/en.md

- **Output size**: $H_{out} = \lfloor (H - K + 2P) / S \rfloor + 1$
- **Same padding**: $P = (K - 1) / 2$ for odd $K$ and $S = 1$.
- **Conv parameters**: `C_out * C_in * K * K + C_out`. The last term is the biases.
- **Receptive field**: $1 + L (K - 1)$ for $L$ stacked convs at stride 1.
- **Two 3x3 or one 5x5**: same 5x5 view. $18C^2$ against $25C^2$ parameters.
- **Residual block**: $y = F(x) + x$. The block learns identity with $F(x) = 0$.
- **Cross-entropy**: $-z_y + \log \sum_j \exp(z_j)$ on raw logits.
- **Layer-wise LR decay**: $\mathrm{lr}_k = \mathrm{base} \cdot \mathrm{decay}^{L - k}$

## Four families
type: concepts
from: phases/04-computer-vision/03-cnns-lenet-to-resnet/docs/en.md

- **LeNet-5 (1998)**: conv, avg pool, dense, tanh. 60,000 parameters.
- **AlexNet (2012)**: ReLU, dropout, 5 conv and 3 dense layers. 60M parameters.
- **VGG (2014)**: only 3x3 convs, 16 or 19 layers. 138M parameters.
- **Inception (2014)**: 1x1, 3x3, 5x5 and pool in parallel, then concatenate.
- **ResNet (2015)**: identity skip connections. 100+ layers train.
- **BasicBlock**: two 3x3 convs with a skip. ResNet-18 and ResNet-34.
- **Bottleneck**: 1x1 down, 3x3, 1x1 up. ResNet-50, -101, -152.
- **ResNet-18 or VGG-16**: 11.7M or 138M parameters. Top-1 69.8% or 71.6%.

## TinyResNet for 32x32 input
type: architecture
from: phases/04-computer-vision/03-cnns-lenet-to-resnet/docs/en.md, phases/04-computer-vision/03-cnns-lenet-to-resnet/code/main.py

```arch
node | in | 1,1 | Input | (1, 3, 32, 32)
node | stem | 2,1 | stem | 3x3 conv, 32 ch, BN, ReLU
node | l1 | 3,1 | layer1 | 2 BasicBlocks, 32 ch
node | l2 | 4,1 | layer2 | stride 2, 64 ch
node | l3 | 4,2 | layer3 | stride 2, 128 ch
node | l4 | 3,2 | layer4 | stride 2, 256 ch
node | head | 2,2 | head | AdaptiveAvgPool2d, Linear
node | out | 1,2 | Logits | 10 classes
edge | in | stem |
edge | stem | l1 |
edge | l1 | l2 |
edge | l2 | l3 |
edge | l3 | l4 |
edge | l4 | head |
edge | head | out |
```

## ResNet BasicBlock
type: annotated-code
lang: python
from: phases/04-computer-vision/03-cnns-lenet-to-resnet/docs/en.md, phases/04-computer-vision/03-cnns-lenet-to-resnet/code/main.py

```python
self.conv1 = nn.Conv2d(in_c, out_c, kernel_size=3, stride=stride, padding=1, bias=False)  # BN beta holds the bias
self.conv2 = nn.Conv2d(out_c, out_c, kernel_size=3, stride=1, padding=1, bias=False)
if stride != 1 or in_c != out_c:
    self.shortcut = nn.Sequential(
        nn.Conv2d(in_c, out_c, kernel_size=1, stride=stride, bias=False),
        nn.BatchNorm2d(out_c))               # 1x1 conv matches the shape
else:
    self.shortcut = nn.Identity()            # no-op skip
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))              # no ReLU before the add
out = out + self.shortcut(x)                 # F(x) + x
return F.relu(out)
```

## Tasks in code
type: task-table
from: phases/04-computer-vision/02-convolutions-from-scratch/code/main.py, phases/04-computer-vision/04-image-classification/code/main.py, phases/04-computer-vision/05-transfer-learning/code/main.py

| Task | Code |
|---|---|
| Output size of one conv | `output_size(h_in, k, p, s)` |
| Receptive field of a stack | `receptive_field(layers)` |
| Conv as one matmul | `conv2d_im2col(x, w, b, padding=1)` |
| Apply a hand kernel | `apply_kernel(x[0], KERNELS["sobel_x"])` |
| Mix a batch | `mixup_batch(x, y, num_classes, alpha=0.2)` |
| Loss on soft labels | `soft_cross_entropy(logits, soft_targets)` |
| Frozen backbone, new head | `make_feature_extractor(num_classes=10)` |
| Keep BN running stats | `freeze_bn_stats(model)` |

## One training epoch
type: flow
from: phases/04-computer-vision/04-image-classification/docs/en.md

1. **Augment**: `random_hflip()` and `random_crop(pad=4)` with reflect pad. Train set only.
2. **Normalise**: `standardize(mean, std)` with the mean and std of your train set.
3. **DataLoader**: `shuffle=True` for train. `shuffle=False` for val.
4. **Model**: `model.train()` first. The model gives raw logits (N, C).
5. **Loss**: `soft_cross_entropy` with mixup, else `cross_entropy` with `label_smoothing=0.1`.
6. **Step**: `optimizer.zero_grad()`, `loss.backward()`, `optimizer.step()`.
7. **Metrics**: add `loss.item()`, so no graph stays alive.
8. **Epoch end**: `scheduler.step()`. Then `model.eval()` and evaluate under `torch.no_grad`.

## Fine-tune with discriminative LR
type: annotated-code
lang: python
from: phases/04-computer-vision/05-transfer-learning/docs/en.md, phases/04-computer-vision/05-transfer-learning/code/main.py

```python
model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, 10)   # new head
groups = discriminative_param_groups(model, base_lr=1e-3, decay=0.3)
optimizer = SGD(groups, momentum=0.9, weight_decay=1e-4, nesterov=True)
scheduler = CosineAnnealingLR(optimizer, T_max=epochs)
for epoch in range(epochs):
    model.train()
    if freeze_bn:
        freeze_bn_stats(model)                    # after model.train()
start, unfreeze = progressive_unfreeze_schedule(model)
start()                                           # only fc trains
unfreeze(epoch)                                   # layer4 first, then layer3
```

## Pick a transfer recipe
type: concepts
from: phases/04-computer-vision/05-transfer-learning/docs/en.md

- **< 1k images, close to ImageNet**: freeze the backbone. Train the head only.
- **1k-10k, close**: freeze the first 2-3 stages. Fine-tune the rest.
- **10k-100k, any domain**: fine-tune end-to-end with discriminative LR.
- **100k+, far domain**: fine-tune everything. Consider training from scratch.
- **BN, >= 5k examples**: keep BN in train mode. It updates its running statistics.
- **BN, small dataset**: freeze BN in eval mode. Keep the ImageNet statistics.
- **Tiny batch per GPU**: replace BN with GroupNorm.
- **Head to replace**: `backbone.fc` (ResNet), `backbone.classifier[1]` (EfficientNet, MobileNet).

## Training defaults
type: defaults
span: 1
from: phases/04-computer-vision/04-image-classification/docs/en.md, phases/04-computer-vision/05-transfer-learning/docs/en.md

| Setting | Default | Note |
|---|---|---|
| SGD `lr` | 0.1 | TinyResNet from scratch, cosine over 10 epochs |
| `momentum` | 0.9 | With `nesterov=True` |
| `weight_decay` | 5e-4 | Fine-tune uses 1e-4 |
| `label_smoothing` | 0.1 | Target 1-eps for the true class |
| Mixup `alpha` | 0.2 | lambda from Beta(a, a) |
| `base_lr` | 1e-3 | The fc group. Earlier stages get less |
| `decay` | 0.3 | conv1 gets about 0.00243 times `base_lr` |
| `batch_size` | 128 | Val loader uses 256 |

## Troubleshooting
type: troubleshooting
from: phases/04-computer-vision/03-cnns-lenet-to-resnet/docs/en.md, phases/04-computer-vision/04-image-classification/docs/en.md, phases/04-computer-vision/05-transfer-learning/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| Wrong gradient, flat curve | Softmax before the loss | Pass raw logits to `CrossEntropyLoss` |
| Looks like an unstable LR | Gradients accumulate | Call `optimizer.zero_grad()` each step |
| About 1% accuracy lost | ImageNet mean and std on CIFAR-10 | Compute stats on your train set |
| A "6" becomes a "9" | Augment changes the label | Use smaller rotation ranges |
| Fine-tuned below pretrained-only | LR or BN bug | Use discriminative LR, check BN mode |
| 5-15% accuracy lost silently | BN stats from another distribution | Freeze BN stats or use GroupNorm |
| All images get one class | LR destroys pretrained features | Lower the backbone LR |
| Deep plain net trains worse | Degradation past ~20 layers | Add residual connections |

## Questions
type: qa
from: phases/04-computer-vision/02-convolutions-from-scratch/docs/en.md, phases/04-computer-vision/03-cnns-lenet-to-resnet/docs/en.md, phases/04-computer-vision/04-image-classification/docs/en.md, phases/04-computer-vision/05-transfer-learning/docs/en.md

- **Why 3x3 kernels?** It is the smallest odd kernel with a centre.
- **How to downsample without a pool?** Use a conv with `stride=2`. It halves H and W.
- **Why does mixup raise train loss?** The targets are soft. Test accuracy goes up.
- **Which two numbers for a transfer run?** Pretrained-only is the floor. Fine-tuned is the ceiling.
- **Why a smaller LR for early layers?** They hold generic edge and texture features.
- **When to rebuild the optimizer?** Each time the set of trainable parameters changes.

## Conv layer shapes
type: annotated-code
lang: python
from: phases/04-computer-vision/02-convolutions-from-scratch/docs/en.md

```python
conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)
tuple(conv.weight.shape)                    # (C_out, C_in, K, K)
sum(p.numel() for p in conv.parameters())   # 64 * 3 * 3 * 3 + 64 = 1,792
y = conv(torch.randn(8, 3, 224, 224))       # padding=0 gives 222x222
```
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓