Convolutional Networks & ResNet Skip Connections

Derive 2D spatial convolution output geometry, receptive field arithmetic, the gradient highway proof of ResNet residual connections, and zero-dependency implementations in Python and Rust.

03. Deep Learning, CNNs & Transformer Architectures9 min read

Fully connected Multi-Layer Perceptrons treat each pixel in an image independently, ignoring spatial translation equivariance and incurring an explosion in weight parameters (O(H⋅W⋅C)O(H \cdot W \cdot C)).

Convolutional Neural Networks (CNNs) enforce spatial locality and weight sharing. To scale depth past dozens of layers without optimization degradation, He et al. introduced Residual Networks (ResNet), opening an unattenuated gradient highway via identity skip connections.

Gradient HighwayDeep Architecture
1 + dF/dx

The additive identity term prevents backpropagated gradients from vanishing regardless of network depth.

1. 2D Discrete Convolution & Spatial Geometry#

In deep learning, the "convolution" operation is technically 2D discrete cross-correlation between input feature map I∈RH×WI \in \mathbb{R}^{H \times W} and kernel filter K∈Rkh×kwK \in \mathbb{R}^{k_h \times k_w}:

S(i,j)=(I∗K)(i,j)=∑m=0kh−1∑n=0kw−1I(i+m,j+n)K(m,n)S(i, j) = (I * K)(i, j) = \sum_{m=0}^{k_h - 1} \sum_{n=0}^{k_w - 1} I(i + m, j + n) K(m, n)
Architecture Diagram
Rendering diagram...

Derivation of Output Spatial Dimensions#

Let WW be input width, KK be kernel filter width, PP be zero-padding applied to both borders, and SS be stride.

1

Step 1: Padded Input Dimension

Adding PP padding pixels to both left and right edges expands the effective width to:

Wpadded=W+2PW_{\text{padded}} = W + 2P
2

Step 2: Available Kernel Translation Window

A kernel of width KK spans from coordinate 00 to K−1K - 1. The maximum distance the kernel can slide before exceeding the boundary is:

Wspan=Wpadded−K=W+2P−KW_{\text{span}} = W_{\text{padded}} - K = W + 2P - K
3

Step 3: Divide by Stride and Account for Initial Position

Because the kernel steps by stride SS, the number of discrete steps is ⌊W+2P−KS⌋\lfloor \frac{W + 2P - K}{S} \rfloor. Adding the initial placement gives:

Wout=⌊W−K+2PS⌋+1W_{\text{out}} = \left\lfloor \frac{W - K + 2P}{S} \right\rfloor + 1

2. Receptive Field Arithmetic#

The Receptive Field (RF) of a unit in layer ll is the spatial region in input image x0x_0 that affects that unit's activation.

Let:

  • rlr_l: Receptive field size at layer ll (with r0=1r_0 = 1)
  • klk_l: Kernel filter size at layer ll
  • sls_l: Stride at layer ll
  • jlj_l: Feature jump (cumulative stride) at layer ll, where jl=jl−1⋅slj_l = j_{l-1} \cdot s_l (with j0=1j_0 = 1)

The receptive field expands recursively:

rl=rl−1+(kl−1)⋅jl−1r_l = r_{l-1} + (k_l - 1) \cdot j_{l-1}

This explains why stacking two 3×33 \times 3 conv layers (each with stride 1) yields an effective receptive field of 1+(3−1)+(3−1)=5×51 + (3-1) + (3-1) = 5 \times 5, while using 2⋅(32)=182 \cdot (3^2) = 18 parameters rather than 52=255^2 = 25 parameters.

3. Mathematical Proof: The ResNet Gradient Highway#

Prior to 2015, stacking additional layers in deep networks led to the Degradation Problem: a 56-layer network exhibited higher training error and test error than a 20-layer network, despite having higher expressive capacity.

He et al. (2015) hypothesized that optimizing an identity mapping H(x)=x\mathcal{H}(x) = x across multiple non-linear layers is difficult. They reformulated the problem into fitting a residual mapping F(x)≡H(x)−x\mathcal{F}(x) \equiv \mathcal{H}(x) - x:

H(x)=F(x)+x\mathcal{H}(x) = \mathcal{F}(x) + x
Architecture Diagram
Rendering diagram...

Derivation of Unattenuated Gradient Flow#

Consider a general residual block from layer ll to a deeper layer LL:

xL=xl+∑i=lL−1F(xi,Wi)x_L = x_l + \sum_{i=l}^{L-1} \mathcal{F}(x_i, \mathcal{W}_i)

Let E\mathcal{E} denote the scalar loss function. Applying the multivariate chain rule to differentiate with respect to earlier activation xlx_l:

∂E∂xl=∂E∂xL∂xL∂xl\frac{\partial \mathcal{E}}{\partial x_l} = \frac{\partial \mathcal{E}}{\partial x_L} \frac{\partial x_L}{\partial x_l}

Differentiating xLx_L with respect to xlx_l:

∂xL∂xl=∂∂xl(xl+∑i=lL−1F(xi,Wi))=I+∂∂xl∑i=lL−1F(xi,Wi)\frac{\partial x_L}{\partial x_l} = \frac{\partial}{\partial x_l} \left( x_l + \sum_{i=l}^{L-1} \mathcal{F}(x_i, \mathcal{W}_i) \right) = I + \frac{\partial}{\partial x_l} \sum_{i=l}^{L-1} \mathcal{F}(x_i, \mathcal{W}_i)

Substituting into the chain rule expression:

∂E∂xl=∂E∂xL(I+∂∂xl∑i=lL−1F(xi,Wi))\frac{\partial \mathcal{E}}{\partial x_l} = \frac{\partial \mathcal{E}}{\partial x_L} \left( I + \frac{\partial}{\partial x_l} \sum_{i=l}^{L-1} \mathcal{F}(x_i, \mathcal{W}_i) \right)
Why Gradients Cannot Vanish in ResNet
The total gradient decomposes into two additive terms: 1. Direct gradient flow: ∂E∂xL⋅I\frac{\partial \mathcal{E}}{\partial x_L} \cdot I 2. Non-linear residual flow: ∂E∂xL(∂∂xl∑i=lL−1F)\frac{\partial \mathcal{E}}{\partial x_L} \left( \frac{\partial}{\partial x_l} \sum_{i=l}^{L-1} \mathcal{F} \right) Even if the learned weights vanish (∂F∂xl→0\frac{\partial \mathcal{F}}{\partial x_l} \to 0), the additive identity term II guarantees that the gradient ∂E∂xL\frac{\partial \mathcal{E}}{\partial x_L} propagates directly back to layer ll without any attenuation.

4. From-Scratch Implementation: Zero Dependencies#

The following implementations construct a 2D Convolution Operator and an Additive Residual Block from scratch in pure Python and pure Rust.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from typing import List class Conv2DFromScratch: @staticmethod def conv2d( image: List[List[float]], kernel: List[List[float]], padding: int = 0, stride: int = 1 ) -> List[List[float]]: h = len(image) w = len(image[0]) kh = len(kernel) kw = len(kernel[0]) # Apply zero-padding padded_h = h + 2 * padding padded_w = w + 2 * padding padded = [[0.0] * padded_w for _ in range(padded_h)] for r in range(h): for c in range(w): padded[r + padding][c + padding] = image[r][c] # Calculate output spatial shape out_h = (h - kh + 2 * padding) // stride + 1 out_w = (w - kw + 2 * padding) // stride + 1 output = [[0.0] * out_w for _ in range(out_h)] # Slide convolution kernel for i in range(out_h): for j in range(out_w): r_start = i * stride c_start = j * stride val = 0.0 for kr in range(kh): for kc in range(kw): val += padded[r_start + kr][c_start + kc] * kernel[kr][kc] output[i][j] = val return output @classmethod def residual_block( cls, x: List[List[float]], kernel: List[List[float]] ) -> List[List[float]]: # F(x): same-padding convolution (padding=1, kernel 3x3) fx = cls.conv2d(x, kernel, padding=1, stride=1) h = len(x) w = len(x[0]) # Residual Addition: H(x) = ReLU(F(x) + x) output = [[0.0] * w for _ in range(h)] for r in range(h): for c in range(w): output[r][c] = max(0.0, fx[r][c] + x[r][c]) return output if __name__ == "__main__": img = [ [1.0, 2.0, 3.0, 0.0], [4.0, 5.0, 6.0, 1.0], [7.0, 8.0, 9.0, 0.0], [1.0, 1.0, 1.0, 1.0] ] # Edge detection kernel sobel_kernel = [ [-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0] ] conv_out = Conv2DFromScratch.conv2d(img, sobel_kernel, padding=0, stride=1) print("Conv2D Output (2x2):") for row in conv_out: print(" ", [round(v, 2) for v in row]) res_out = Conv2DFromScratch.residual_block(img, sobel_kernel) print("\nResidual Block Output (4x4):") for row in res_out: print(" ", [round(v, 2) for v in row])

5. Active Knowledge Checks#

Concept check

In a ResNet architecture, why does the gradient expression dE/dx_l = (dE/dx_L) * [I + d(sum F)/dx_l] solve the vanishing gradient degradation problem?

Concept check

Given an input image of width W = 32, a convolution kernel filter of size K = 5, padding P = 2, and stride S = 2, what is the exact spatial width W_out of the resulting feature map?