Residual Connections and ResNets
Residual connections (also called skip connections) are architectural shortcuts in neural networks that add a layer's input directly to its output, allowing gradients to flow unimpeded through hundreds or thousands of layers during backpropagation. Introduced in ResNets (Residual Networks) by He et al. in 2015, they solved the degradation problem — the counterintuitive observation that adding more layers to a plain network made training worse, not just harder — and enabled networks of unprecedented depth (152+ layers) to be trained reliably. The remarkable fact: a residual connection adds essentially zero parameters yet unlocks depth scaling that was previously impossible, making it one of the highest-leverage architectural decisions in deep learning history.
Instead of asking a stack of layers to learn a mapping directly, residual connections reframe the problem: let the layers learn the residual , then reconstruct the target as . If the optimal transformation is close to the identity (which it often is in deep networks), learning a small residual perturbation near zero is far easier than learning the full mapping from scratch. The "aha moment" is this: the network now has a free gradient highway — the identity shortcut — so even if the learned layers are poorly initialized or temporarily unhelpful, the signal still flows backward without vanishing.
How It Works
The Residual Block
The fundamental building block of a ResNet defines its output as:
Explanation: The output is the sum of the learned residual function (parameterized by weights ) and the original input passed through unchanged via the shortcut. Here is the input to the block, is the transformation computed by the stacked layers (typically Conv → BN → ReLU → Conv → BN), and addition is performed element-wise. When dimensions change (e.g., after strided convolutions), a linear projection replaces the identity:
Explanation: The projection matrix (a convolution in practice) matches the spatial and channel dimensions of the shortcut to those of the residual branch so the addition is well-defined.
Gradient Flow Analysis
For a network of residual blocks where each block satisfies , the feature at any layer can be written as a direct sum of all preceding residuals:
Explanation: This telescoping sum shows that the output at depth is the input plus an accumulated sum of learned residuals — every block contributes additively rather than multiplicatively. Taking the gradient of the loss with respect to an early activation via the chain rule:
Explanation: The gradient always contains the term multiplied by — meaning the shortcut path guarantees at least this much gradient reaches regardless of what the residual branches do.
PyTorch Implementation
A standard BasicBlock (used in ResNet-18/34) looks like: ```python import torch import torch.nn as nn class BasicBlock(nn.Module): expansion = 1 def __init__(self, in_channels, out_channels, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.relu = nn.ReLU(inplace=True) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels)
# Projection shortcut when dims change self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) ) def forward(self, x): out = self.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out += self.shortcut(x) # <-- The residual connection out = self.relu(out) return out ``` For deeper variants (ResNet-50/101/152), He et al. use a bottleneck block that first reduces channel dimensions with a conv, applies a conv in the reduced space, then expands back — cutting FLOPs by ~3× while preserving representational power: ```python class Bottleneck(nn.Module): expansion = 4 # output channels = in_channels 4 def __init__(self, in_channels, bottleneck_channels, stride=1): super().__init__() out_channels = bottleneck_channels self.expansion self.layers = nn.Sequential( nn.Conv2d(in_channels, bottleneck_channels, 1, bias=False), nn.BatchNorm2d(bottleneck_channels), nn.ReLU(inplace=True), nn.Conv2d(bottleneck_channels, bottleneck_channels, 3, stride=stride, padding=1, bias=False), nn.BatchNorm2d(bottleneck_channels), nn.ReLU(inplace=True), nn.Conv2d(bottleneck_channels, out_channels, 1, bias=False), nn.BatchNorm2d(out_channels), ) self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) ) def forward(self, x): return nn.functional.relu(self.layers(x) + self.shortcut(x)) ```
Kaiming He completed his PhD at the Chinese University of Hong Kong and joined Microsoft Research Asia, where he authored both the ResNet paper (He et al., CVPR 2016, ~200,000 citations) and the He weight initialization scheme that made training deep ReLU networks tractable. His ResNet-152 won first place in ILSVRC 2015 classification, detection, and localization — a sweep unprecedented in the competition's history. Surprisingly, He has stated in interviews that the core skip-connection idea came from observing that even randomly wired shortcut paths improved optimization, long before the theoretical gradient-flow explanation was formalized.
Intuition
Imagine you're hiking up a mountain (optimizing a loss landscape) and you can either take a single steep direct path or a network of switchbacks with convenient rope-handholds at every turn. Plain deep networks are the steep path: if you stumble at any point, you lose all your momentum. Residual connections are the handholds — they don't change where you're trying to go, but they guarantee you always have something stable to grab. A more computational analogy: think of an ensemble of shallow networks sharing parameters. The residual structure effectively lets the network behave like many different-depth models trained simultaneously. At test time, it averages over all these implicit depths — shallow paths dominate early in training when residuals are near zero, and the network gradually specializes deeper paths as capacity is needed. One more way to see it: if the ideal transformation between two layers really is close to the identity (e.g., features barely need changing at that depth), the network just has to push . Zeroing out weights is easy — it's the default for near-zero initialization. Without skip connections, learning the identity mapping through two conv layers and batch norms is a non-trivial, fragile optimization problem.