Basic ConvNet
A comprehensive introduction to Convolutional Neural Networks (CNNs) and training them with DeepSpeed for image classification.
Introduction to Convolutional Neural Networks
Convolutional Neural Networks (CNNs) are a specialized class of neural networks designed specifically for processing structured grid data, such as images. They have revolutionized computer vision, achieving superhuman performance in tasks like image classification, object detection, and segmentation.
Why CNNs for Images?
Traditional fully-connected neural networks have significant limitations when applied to images:
-
Parameter explosion: A 224×224 RGB image has 150,528 input features. A fully-connected layer with 1000 neurons would require over 150 million parameters!
-
No spatial awareness: Fully-connected networks treat each pixel independently, ignoring the spatial structure and local patterns in images.
-
No translation invariance: A cat in the top-left corner looks completely different to a fully-connected network than the same cat in the bottom-right corner.
CNNs address these issues through three key architectural innovations:
- Local connectivity (receptive fields)
- Parameter sharing (convolution)
- Spatial hierarchies (pooling)
The Convolution Operation
Mathematical Origins
The convolution operation has deep roots in mathematics, signal processing, and statistics. It describes how one function modifies another through a "sliding" operation.
Continuous Convolution (Integral Form)
For two continuous functions and , their convolution is defined as:
This integral computes a weighted average of at each point , where the weights are given by "flipped" and "shifted" to position .
Intuition: Imagine as a "template" that slides across , computing an overlap at each position.
Discrete Convolution (Summation Form)
For discrete signals (like digital images), the convolution becomes a summation:
For finite signals of length and :
2D Convolution for Images
For images, we extend to two dimensions. Given an input image and a kernel (filter) :
Where:
- is the input image of size
- is the kernel of size
- is the output position
- indexes the kernel elements
True convolution flips the kernel before sliding it:
What nn.Conv2d computes is cross-correlation, with no flip:
The distinction is immaterial for learning — since is learned, the network simply learns the flipped kernel, and the two hypothesis classes are identical. It matters in exactly two places: when you port hand-designed kernels from a signal-processing reference (a Sobel operator must be flipped to behave as documented), and when you invoke the convolution theorem, , which holds for true convolution and underlies FFT-based conv implementations. Cross-correlation is associative-unfriendly and non-commutative; convolution is both.
Why convolution and not some other local operator?
The choice is not heuristic. It is forced by a symmetry requirement.
Let denote translation of an image by vector . An operator is translation-equivariant if
— shifting the input shifts the output identically. The relevant theorem: a linear operator is translation-equivariant if and only if it is a convolution. Convolution is not a way to build a shift-equivariant linear layer; it is the only way.
This is why CNNs work on images. The statistics of natural images are approximately stationary — an edge is an edge wherever it appears — so equivariance is the correct inductive bias, and imposing it as a hard architectural constraint is far more sample-efficient than hoping a fully-connected layer learns it from data.
The convolution layer is equivariant: move the cat, the cat's feature map moves. The classifier needs invariance: move the cat, the label does not change. Invariance is manufactured downstream — by pooling, by strided subsampling, and ultimately by global average pooling, which sums over all spatial positions and so discards location entirely. Conflating the two is the most common conceptual error about CNNs.
Note also that this invariance is only approximate. Azulay & Weiss (2019) and Zhang (2019) showed that strided downsampling violates the Nyquist criterion, so modern CNNs are not shift-invariant in practice: a one-pixel translation can change the predicted class. Anti-aliased (blur-pooled) downsampling substantially repairs this.
What parameter sharing actually buys
Return to the 224×224×3 image and a hypothetical first layer producing 64 feature maps.
| Layer type | Parameter count | |
|---|---|---|
| Fully connected to | ||
| Conv2d, , kernel |
Eight orders of magnitude, from two constraints: local connectivity (each output depends on a patch, not all pixels) and parameter sharing (the same kernel is reused at every spatial position). Crucially, the parameter count is independent of and — the same layer processes any resolution. A fully-connected layer cannot.
The Sliding Window Mechanism
The convolution operation works by sliding a small window (the kernel) across the input image:
Step-by-step example:
Consider a 5×5 input and a 3×3 kernel:
For position :
This particular kernel is a vertical edge detector (Sobel-like filter).
Understanding Digital Images
Image as a Tensor
Digital images are represented as multi-dimensional arrays (tensors):
Grayscale Images
A grayscale image is a 2D matrix where each element represents pixel intensity:
where is height, is width, and values typically range from 0 (black) to 255 (white) or 0.0 to 1.0 when normalized.
RGB Color Images
Color images have three channels (Red, Green, Blue):
The second format (channels-first) is the PyTorch convention.
Batch of Images
In deep learning, we process batches of images:
Where:
- = batch size (number of images)
- = channels (1 for grayscale, 3 for RGB)
- = height
- = width
Example: A batch of 32 RGB images of size 224×224 has shape [32, 3, 224, 224].
Convolution Layer Parameters
Stride
Stride determines how many pixels the kernel moves between positions.
Where:
- = input size
- = kernel size
- = stride
Example: 7×7 input, 3×3 kernel
- Stride 1: Output =
- Stride 2: Output =
Padding
Padding adds pixels around the input border, allowing control over output size.
Where = padding size.
Common padding strategies:
| Padding Type | Value | Purpose |
|---|---|---|
| Valid (no padding) | Output smaller than input | |
| Same | Output same size as input (stride=1) | |
| Full | Output larger than input |
Multiple Channels and Filters
For multi-channel inputs (like RGB), each filter spans all input channels:
Where:
- = number of input channels
- = one filter
- = bias term
Multiple filters produce multiple output channels (feature maps):
Dimension calculation:
Where:
Pooling Layers
Pooling reduces spatial dimensions while retaining important features.
Max Pooling
Takes the maximum value in each window:
Where is the pooling region at position .
Average Pooling
Takes the average value in each window:
Global Average Pooling
Reduces each feature map to a single value:
Example: 2×2 Max Pooling on a 4×4 input:
Why Pooling?
- Dimensionality reduction: Reduces computation and memory
- Translation invariance: Small shifts don't change output much
- Feature abstraction: Captures "presence" of features, not exact location
Feature Hierarchy and Receptive Fields
Receptive Field
The receptive field is the region in the input image that affects a particular neuron's output.
Receptive field calculation (for stacked 3×3 convolutions with stride 1):
For layers of 3×3 convolutions with stride 1:
Note the term: with stride-1 layers the receptive field grows linearly in depth, but each stride-2 layer doubles the growth rate thereafter. This is why architectures interleave downsampling — reaching a 224-pixel receptive field with stride-1 3×3 convolutions alone would need 112 layers.
Stacking two 3×3 layers gives a 5×5 receptive field using parameters instead of — 28% fewer — while inserting an extra nonlinearity between them, increasing expressiveness. Three stacked 3×3 layers reach 7×7 with against . This observation is the entire architectural thesis of VGG (Simonyan & Zisserman, 2015) and is why 3×3 became the default kernel size.
The formula above gives the set of input pixels that can influence an output. Luo et al. (2016) showed that the actual influence, , is distributed approximately Gaussian over that region and decays quickly from the centre — the effective receptive field grows only as in depth, not , and occupies a small fraction of the theoretical area.
The practical consequence: computing a theoretical receptive field that covers your object and concluding the network can see it is unsound. It is a necessary condition, not a sufficient one — which is part of why dilated convolutions, and later self-attention, were introduced to obtain genuine long-range dependence.
Feature Hierarchy
CNNs learn hierarchical features:
| Layer Depth | Features Learned | Example |
|---|---|---|
| Layer 1-2 | Edges, colors, gradients | Vertical lines, blobs |
| Layer 3-5 | Textures, patterns | Fur, fabric, eyes |
| Layer 6+ | Object parts, objects | Faces, wheels, buildings |
Common Kernel Types
Edge Detection Kernels
Sobel Horizontal:
Sobel Vertical:
Blur Kernels
Gaussian Blur (3×3 approximation):
Sharpening Kernel
In CNNs, we don't hand-design these kernels—they are learned from data through backpropagation!
Weight Initialization for CNNs
The Importance of Initialization
Proper weight initialization is crucial for training deep networks. Poor initialization leads to:
- Vanishing gradients: Signals shrink to zero
- Exploding gradients: Signals blow up to infinity
- Symmetry breaking: All neurons must start different
The variance-propagation argument
Both standard schemes come from the same one-line calculation. For a layer with i.i.d. zero-mean and independent of ,
Signal magnitude is preserved layer-to-layer exactly when . Every initializer below is a different answer to "what should be?"
Xavier/Glorot initialization
For symmetric, roughly linear activations (sigmoid near the origin, tanh), Glorot & Bengio (2010) compromise between preserving forward variance () and backward variance (), taking the harmonic-mean-like average:
Realized either as a normal or a uniform distribution — note , which is where the 6 comes from:
Kaiming/He initialization
ReLU zeros the negative half of a symmetric pre-activation distribution, so it halves the variance: . The condition therefore becomes , and He et al. (2015) correct by exactly the factor of 2:
For a convolutional layer the fan-in counts the whole receptive volume, ; the fan-out is .
Many write these as , which reads as a variance of and is wrong by a square root. The variance is ; the standard deviation is . PyTorch's kaiming_normal_ takes the correct convention internally, so the bug is usually confined to hand-rolled initializers — where it silently mis-scales every layer.
Why this compounds. Getting the factor wrong by per layer scales activations by over layers. He et al. show a 30-layer network that trains fine under Kaiming but does not train at all under Xavier: the missing factor of 2 per layer decays the signal by by the output.
mode='fan_out' on a Linear layer is unusualThe example below calls kaiming_normal_(..., mode='fan_out') on both Conv2d and Linear. For convolutions fan_out is a defensible choice (it preserves variance in the backward pass, and is what the original ResNet code used). For nn.Linear, whose weight is stored as [out_features, in_features], fan_out computes the fan from in_features... which makes it behave like fan_in for the forward pass. It works, but if you want the textbook behaviour on linear layers, use the default mode='fan_in' and be explicit about it.
CNN Architecture Patterns
The Classic Pattern: Conv → ReLU → Pool
Batch Normalization
Normalizes activations to have zero mean and unit variance:
Where:
- = batch mean and variance
- = learnable scale and shift
- = small constant for numerical stability
Benefits:
- Allows higher learning rates
- Reduces sensitivity to initialization
- Acts as regularization
Ioffe & Szegedy (2015) motivated BatchNorm as reducing internal covariate shift — the drift in each layer's input distribution as earlier layers update. Santurkar et al. (2018) tested this directly: they injected explicit, severe distributional noise after each BatchNorm layer, deliberately restoring covariate shift, and the networks still trained faster than unnormalized baselines.
Their alternative account, supported by both theory and measurement, is that BatchNorm smooths the optimization landscape — it improves the Lipschitz constants of the loss and of its gradient, so gradients become more predictive of the loss at the points actually reached by a step. That is what permits larger learning rates. Worth knowing, because the covariate-shift story is still repeated widely and leads people to reach for BatchNorm in settings where the smoothing argument does not apply.
BatchNorm computes and over the local micro-batch on each GPU. Two consequences under DeepSpeed:
The effective normalization batch is train_micro_batch_size_per_gpu, not train_batch_size. Splitting a batch of 256 across 8 GPUs means each BatchNorm layer sees 32 samples. Push the micro-batch to 2 or 4 — which is exactly what memory pressure and gradient accumulation encourage — and the batch statistics become so noisy that training degrades. Gradient accumulation does not help: it accumulates gradients, not batch statistics, so 8 accumulation steps of size 4 still normalizes over 4 samples.
The fix depends on why the batch is small. Use nn.SyncBatchNorm.convert_sync_batchnorm(model) to compute statistics across all ranks — correct, but it adds an all-reduce at every BatchNorm layer in both passes. Or switch to a batch-independent normalizer: GroupNorm (Wu & He, 2018) or LayerNorm, whose statistics do not depend on the batch axis at all and are therefore immune to this whole class of problem. That independence is a major reason transformers use LayerNorm.
Dropout for Regularization
Randomly zeros activations during training:
The factor ensures expected value remains unchanged. This is inverted dropout: the scaling happens at training time so that inference is a plain forward pass with no rescaling — which is why model.eval() must be called, and why forgetting it silently degrades your reported accuracy.
Li et al. (2019) identify a variance shift: dropout changes the variance of its output between train mode (where units are dropped and rescaled) and eval mode (where they are not). A downstream BatchNorm accumulates running statistics under the training-mode variance, then normalizes with them under the eval-mode variance. The mismatch degrades test accuracy in a way that looks like overfitting but is not.
The practical rule, and the reason modern CNNs use little or no dropout in convolutional stacks: put dropout after all BatchNorm layers, typically only in the classifier head. ResNet and its descendants rely on BatchNorm plus weight decay for regularization and omit dropout from the trunk entirely.
Computational Cost: Where CNN Training Actually Spends Resources
CNNs invert the memory profile of the language models discussed in ZeRO Stages. Knowing which regime you are in determines which optimization is worth applying.
FLOPs
A convolutional layer performs, per forward pass:
The factor 2 counts a multiply and an add. Note that cost scales with spatial resolution while parameter count does not — the parameter-sharing property that makes CNNs so compact is exactly what makes their compute cost resolution-dependent.
How convolution is actually executed
cuDNN does not run a naive sextuple loop. The dominant strategy is im2col + GEMM: each input patch is flattened into a column, producing a matrix of shape , and the convolution becomes a single dense matrix multiply against the filter bank reshaped to . This trades memory — patches overlap, so im2col duplicates data by a factor of up to — for the ability to call a maximally-tuned GEMM kernel on Tensor Cores.
Alternatives that cuDNN benchmarks against at runtime: FFT-based convolution (via the convolution theorem, efficient for large kernels), and Winograd minimal-filtering algorithms (fewer multiplies for small kernels, which is why 3×3 stride-1 is so fast on NVIDIA hardware).
torch.backends.cudnn.benchmark = TrueThis lets cuDNN time every available algorithm on the first call for each input shape and cache the winner. It typically buys 5–20% on a CNN — but only if your input shapes are fixed. With varying shapes it re-benchmarks constantly and is a net loss. Fixed-size image batches are the ideal case.
Memory: activations dominate
For the two-layer CNN below at batch 32, model states are MB. The retained activations for the backward pass are:
| Tensor | Shape | Elements at |
|---|---|---|
| Input | 25,088 | |
| Conv1 out | 401,408 | |
| Pool1 out | 100,352 | |
| Conv2 out | 200,704 | |
| Pool2 out | 50,176 |
Roughly 778,000 elements against 208,000 parameters — and that ratio grows linearly with batch size while the parameter count stays fixed.
This is the general CNN situation. Early layers hold high-resolution, many-channel feature maps; a ResNet-50 at batch 256 spends the large majority of its memory on activations. The consequences for DeepSpeed:
- ZeRO Stage 3 helps far less than it does for LLMs. It partitions model states, which are not the bottleneck, while adding of communication. Stage 1 or 2 is usually the right choice for CNNs.
- Activation checkpointing is the high-value lever, precisely inverting the LLM advice.
channels_lastmemory format (model.to(memory_format=torch.channels_last)) lets Tensor Cores read NHWC directly instead of transposing NCHW, often a 10–30% speedup on convolutions in mixed precision, at no accuracy cost.
DeepSpeed Implementation
Now let's implement a CNN using DeepSpeed for distributed training optimization.
Overview
This example demonstrates:
- CNN architecture with DeepSpeed
- Kaiming/He weight initialization
- Learning rate scheduling (warmup + cosine decay)
- Early stopping and gradient monitoring
- Real-time accuracy tracking
Task: 10-class classification on 28x28 grayscale images
Quick Start
cd 01_basics/02_convnet
# Single GPU
deepspeed --num_gpus=1 train_ds.py
# Multi-GPU
deepspeed --num_gpus=2 train_ds.py
Model Architecture
class CNNModelEnhanced(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 16, kernel_size=5, padding=2)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(16, 32, kernel_size=5, padding=2)
self.fc1 = nn.Linear(32 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
self._initialize_weights() # Kaiming initialization
Architecture Flow with Dimensions:
Dimension calculations:
| Layer | Input Shape | Output Shape | Parameters |
|---|---|---|---|
| Conv1 | [N, 1, 28, 28] | [N, 16, 28, 28] | |
| Pool1 | [N, 16, 28, 28] | [N, 16, 14, 14] | 0 |
| Conv2 | [N, 16, 14, 14] | [N, 32, 14, 14] | |
| Pool2 | [N, 32, 14, 14] | [N, 32, 7, 7] | 0 |
| FC1 | [N, 1568] | [N, 128] | |
| FC2 | [N, 128] | [N, 10] | |
| Total | ~208,000 |
Training Enhancements
Kaiming Initialization
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
nn.init.constant_(m.bias, 0)
This ensures:
Learning Rate Schedule
def get_lr_schedule(epoch, initial_lr=0.001, warmup_epochs=5, total_epochs=50):
if epoch < warmup_epochs:
# Linear warmup
return initial_lr * (epoch + 1) / warmup_epochs
else:
# Cosine decay
progress = (epoch - warmup_epochs) / (total_epochs - warmup_epochs)
return initial_lr * 0.5 * (1 + cos(progress * pi))
Mathematical formulation:
Early Stopping
patience_limit = 15
min_improvement = 1e-5
if avg_loss < best_loss - min_improvement:
best_loss = avg_loss
patience_counter = 0
else:
patience_counter += 1
if patience_counter >= patience_limit:
break # Stop training
DeepSpeed Configuration
{
"train_batch_size": 32,
"train_micro_batch_size_per_gpu": 32,
"gradient_accumulation_steps": 1,
"optimizer": {
"type": "Adam",
"params": {
"lr": 1e-3
}
},
"fp16": {
"enabled": true
}
}
Training Parameters
| Parameter | Value | Description |
|---|---|---|
| Learning Rate | Initial learning rate | |
| LR Schedule | Warmup + Cosine | Gradual warmup, then decay |
| Warmup Epochs | 5 | Linear warmup period |
| Total Epochs | 50 | Maximum training epochs |
| Early Stopping | 15 epochs | Patience before stopping |
| Batch Size | 32 | Samples per gradient update |
| Parameters | ~208,000 | Total trainable parameters |
Gradient Monitoring
The script tracks gradient norms to detect training issues:
total_norm = 0.0
for p in model_engine.module.parameters():
if p.grad is not None:
param_norm = p.grad.data.norm(2)
total_norm += param_norm.item() ** 2
total_norm = total_norm ** 0.5
This computes the L2 norm:
Healthy patterns:
- Gradual decrease and stabilization
- Values typically 0.01 - 1.0
Problem indicators:
- Sudden spikes: gradient explosion
- Near zero: vanishing gradients
Expected Output
Epoch 49 Summary:
- Avg Loss: 2.145678
- Accuracy: 15.75%
- Avg Grad Norm: 0.118765
Note: With synthetic random data, expect "Poor" quality.
With real MNIST, expect 95-99% accuracy.
Using Real MNIST
Replace synthetic data with actual MNIST:
from torchvision import datasets, transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(
'./data',
train=True,
download=True,
transform=transform
)
The normalization values are the mean (0.1307) and standard deviation (0.3081) of the MNIST dataset. Standardizing inputs matters for the same reason as in the linear-regression example: it conditions the Hessian, so gradient descent does not have to traverse a badly-scaled valley.
Where This Architecture Sits
The LeNet-style stack above — conv → relu → pool, repeated, then a classifier head — is the 1998 design. It is worth knowing what changed and why, because each step was driven by a specific failure of the previous one.
Residual connections deserve the emphasis. He et al. (2016) observed degradation: a 56-layer plain CNN had higher training error than a 20-layer one — not overfitting, an optimization failure. The fix is to have each block learn a residual and output . The identity path makes the block's Jacobian , so the Jacobian product from the backprop analysis has singular values near 1 by construction and gradients reach early layers intact.
Depthwise separable convolution factorizes the standard operation into a per-channel spatial convolution followed by a channel mixing, reducing cost from to — roughly a saving, about 9× for .
On CNNs versus Vision Transformers. ViT (Dosovitskiy et al., 2021) discards the convolutional prior for self-attention. It wins at very large data scale, where the weaker inductive bias becomes an advantage rather than a liability, and loses on smaller datasets, where convolution's built-in equivariance is worth more than flexibility. ConvNeXt (Liu et al., 2022) then showed that much of ViT's reported advantage came from training recipes rather than architecture: a pure CNN modernized with the same augmentation, optimizer, and schedule matches ViT on ImageNet. The honest summary is that architecture and training protocol are badly confounded in this literature.
Summary
In this tutorial, you learned:
-
Convolution Fundamentals
- Mathematical definition (continuous and discrete)
- 2D convolution for images
- The sliding window mechanism
-
Image Representation
- Grayscale vs. RGB images
- Tensor formats (NCHW)
- Batch processing
-
CNN Components
- Convolutional layers with stride and padding
- Pooling layers (max, average, global)
- Receptive fields and feature hierarchies
-
Training Techniques
- Kaiming initialization for ReLU networks
- Batch normalization and dropout
- Learning rate scheduling
-
DeepSpeed Integration
- Model setup and configuration
- Mixed precision training
- Gradient monitoring
Next Steps
- CIFAR-10 CNN - Real dataset with color images
- Basic RNN - Sequence modeling with LSTMs
- DeepSpeed ZeRO Stages - Memory optimization
References
Foundational architectures
- LeCun, Y., Bottou, L., Bengio, Y., & Haffner, P. (1998). Gradient-based learning applied to document recognition. Proceedings of the IEEE, 86(11), 2278–2324. — LeNet-5.
- Krizhevsky, A., Sutskever, I., & Hinton, G. E. (2012). ImageNet Classification with Deep Convolutional Neural Networks. NeurIPS 2012. — AlexNet.
- Simonyan, K., & Zisserman, A. (2015). Very Deep Convolutional Networks for Large-Scale Image Recognition. ICLR 2015. arXiv:1409.1556 — the stacked-3×3 argument.
- He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep Residual Learning for Image Recognition. CVPR 2016. arXiv:1512.03385 — degradation and residual connections.
- Howard, A. G., Zhu, M., Chen, B., et al. (2017). MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications. arXiv:1704.04861 — depthwise separable convolution.
- Liu, Z., Mao, H., Wu, C.-Y., Feichtenhofer, C., Darrell, T., & Xie, S. (2022). A ConvNet for the 2020s. CVPR 2022. arXiv:2201.03545
- Dosovitskiy, A., Beyer, L., Kolesnikov, A., et al. (2021). An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale. ICLR 2021. arXiv:2010.11929
Convolution, equivariance, receptive fields
- Dumoulin, V., & Visin, F. (2016). A Guide to Convolution Arithmetic for Deep Learning. arXiv:1603.07285 — the definitive reference for the output-size formulas.
- Luo, W., Li, Y., Urtasun, R., & Zemel, R. (2016). Understanding the Effective Receptive Field in Deep Convolutional Neural Networks. NeurIPS 2016. arXiv:1701.04128
- Cohen, T. S., & Welling, M. (2016). Group Equivariant Convolutional Networks. ICML 2016. arXiv:1602.07576 — generalizes equivariance beyond translation.
- Zhang, R. (2019). Making Convolutional Networks Shift-Invariant Again. ICML 2019. arXiv:1904.11486
- Azulay, A., & Weiss, Y. (2019). Why do deep convolutional networks generalize so poorly to small image transformations? JMLR, 20(184). arXiv:1805.12177
- Yu, F., & Koltun, V. (2016). Multi-Scale Context Aggregation by Dilated Convolutions. ICLR 2016. arXiv:1511.07122
Initialization and normalization
- Glorot, X., & Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. AISTATS 2010.
- He, K., Zhang, X., Ren, S., & Sun, J. (2015). Delving Deep into Rectifiers. ICCV 2015. arXiv:1502.01852 — the factor-of-2 ReLU correction.
- Ioffe, S., & Szegedy, C. (2015). Batch Normalization. ICML 2015. arXiv:1502.03167
- Santurkar, S., Tsipras, D., Ilyas, A., & Madry, A. (2018). How Does Batch Normalization Help Optimization? NeurIPS 2018. arXiv:1805.11604 — refutes the internal-covariate-shift account.
- Wu, Y., & He, K. (2018). Group Normalization. ECCV 2018. arXiv:1803.08494 — batch-independent normalization for small micro-batches.
- Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR, 15(56), 1929–1958.
- Li, X., Chen, S., Hu, X., & Yang, J. (2019). Understanding the Disharmony between Dropout and Batch Normalization by Variance Shift. CVPR 2019. arXiv:1801.05134
Implementation and systems
- Chetlur, S., Woolley, C., Vandermersch, P., et al. (2014). cuDNN: Efficient Primitives for Deep Learning. arXiv:1410.0759 — im2col + GEMM.
- Lavin, A., & Gray, S. (2016). Fast Algorithms for Convolutional Neural Networks. CVPR 2016. arXiv:1509.09308 — Winograd minimal filtering.
- Loshchilov, I., & Hutter, F. (2017). SGDR: Stochastic Gradient Descent with Warm Restarts. ICLR 2017. arXiv:1608.03983 — the cosine schedule used above.
- Goyal, P., Dollár, P., Girshick, R., et al. (2017). Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour. arXiv:1706.02677 — linear LR scaling, warmup, and the SyncBN discussion of §BatchNorm.