MediumResNet

Bottleneck Block

Deep Residual Learning

Medium

Problem

Implement a matrix-based ResNet bottleneck block. The three transformations reduce the feature width, process the narrow representation, and expand it again. This mirrors the 1 by 1, 3 by 3, 1 by 1 channel pattern in the original bottleneck architecture while omitting spatial convolution.

y = \operatorname{ReLU}\!\left(W_3\,\operatorname{ReLU}\!\left(W_2\,\operatorname{ReLU}(xW_1)\right) + s(x)\right)

Here, (W_1) reduces the width, (W_2) operates in the bottleneck width, (W_3) expands it, and (s(x)) is x when Ws is null or xWs otherwise. Return the result as a nested list rounded to four decimal places.

Theory

The bottleneck block is a three-layer residual building block introduced by He et al. (2015) in "Deep Residual Learning for Image Recognition." Instead of stacking two 3x3 convolutions like the BasicBlock used in ResNet-18/34, the bottleneck uses a 1x1-3x3-1x1 sequence that first compresses channels, performs spatial processing in the compressed space, and then expands channels back. This design makes deep networks like ResNet-50, ResNet-101, and ResNet-152 computationally tractable while maintaining representational power.


What It Is

The bottleneck block is a residual unit consisting of three convolutional layers arranged in a compress-process-expand pattern. The first layer is a 1x1 convolution that reduces the number of channels from the input dimension to a smaller "bottleneck" dimension. The second layer is a 3x3 convolution that performs spatial filtering in this compressed channel space. The third layer is a 1x1 convolution that expands the channels back to the output dimension. A skip connection adds the original input to the output of this three-layer stack, and ReLU is applied after the addition.

The term "bottleneck" refers to the narrow middle layer. If the input has 256 channels and the bottleneck dimension is 64, the 3x3 convolution operates on only 64 channels instead of 256. This dramatically reduces the number of multiply-add operations while the 1x1 layers handle the channel dimension changes cheaply. The paper states: "The three layers are 1x1, 3x3, and 1x1 convolutions, where the 1x1 layers are responsible for reducing and then increasing (restoring) dimensions."

The bottleneck block is the standard building block for all ResNet variants with 50 or more layers. ResNet-50 uses 16 bottleneck blocks, ResNet-101 uses 33, and ResNet-152 uses 50. The BasicBlock (two 3x3 convolutions) is only used in the shallower ResNet-18 and ResNet-34.


Key Equations

Let x \in \mathbb{R}^{H \times W \times C_{in}} be the input feature map. Let b denote the bottleneck width and C_{out} denote the output channels.

Layer 1 -- 1x1 Reduce. Compress channels from C_{in} to b:

y_1 = \text{ReLU}(\text{BN}(W_1 * x))

Here W_1 \in \mathbb{R}^{b \times C_{in} \times 1 \times 1} is a pointwise convolution mapping each spatial position from C_{in} to b channels. Batch normalization is applied before ReLU. Output: y_1 \in \mathbb{R}^{H \times W \times b}.

Layer 2 -- 3x3 Process. Spatial convolution in the compressed space:

y_2 = \text{ReLU}(\text{BN}(W_2 * y_1))

Here W_2 \in \mathbb{R}^{b \times b \times 3 \times 3} with padding 1 to preserve spatial dimensions. This is the only layer that performs spatial filtering. Because it operates on $$ channels rather than C_{in} or C_{out}, the computational cost is greatly reduced. Output: y_2 \in \mathbb{R}^{H \times W \times b}.

Layer 3 -- 1x1 Expand. Restore channels from b to C_{out}:

y_3 = \text{BN}(W_3 * y_2)

Here W_3 \in \mathbb{R}^{C_{out} \times b \times 1 \times 1}. There is no ReLU after this batch normalization. Output: y_3 \in \mathbb{R}^{H \times W \times C_{out}}.

Shortcut and Addition. The residual connection adds the input to the transformed output:

\text{output} = \text{ReLU}(y_3 + \text{shortcut}(x))

If C_{in} = C_{out} and spatial dimensions are unchanged, the shortcut is identity: \text{shortcut}(x) = x. If C_{in} \neq C_{out} or stride > 1, a projection shortcut is used: \text{shortcut}(x) = \text{BN}(W_s * x) where W_s \in \mathbb{R}^{C_{out} \times C_{in} \times 1 \times 1}. ReLU is applied after the addition, ensuring the skip connection provides a clean linear path for gradient flow.


Why Bottleneck

The fundamental motivation is computational efficiency. A 3x3 convolution is the most expensive operation in a residual block because its kernel has 3 \times 3 = 9 elements versus a single element for 1x1. By reducing channels before the 3x3 layer, the bottleneck ensures this expensive operation runs on far fewer channels.

Consider a block operating on 256 output channels. A BasicBlock with two 3x3 convolutions at 256 channels has 2 \times (256 \times 256 \times 9) = 1{,}179{,}648 kernel parameters. The bottleneck with b = 64 has 256 \times 64 + 64 \times 64 \times 9 + 64 \times 256 = 69{,}632 kernel parameters, roughly 17 times fewer.

The paper frames this practically: ResNet-50 has similar computational cost to the 34-layer BasicBlock network despite having 16 more layers and greater accuracy. Without the bottleneck design, building networks of 50+ layers would be prohibitively expensive. The standard compression ratio is 4:1 (C_{out} = 4b), balancing computational savings against representational capacity.


The Three Layers

Layer 1: 1x1 Reduce

The first 1x1 convolution is a channel-mixing operation with no spatial receptive field. It projects each spatial position independently from C_{in} dimensions to b dimensions, where b is typically C_{in} / 4. This layer acts as a learned dimensionality reduction. Followed by batch normalization and ReLU, it produces a compact representation at every spatial location. The 1x1 convolution is extremely cheap: its FLOPs scale as $ \times W \times C_{in} \times b$, with no spatial kernel multiplier.

Layer 2: 3x3 Process

The 3x3 convolution is the only layer with a spatial receptive field. It uses padding 1 to preserve spatial dimensions and detects edges, textures, and patterns at the current resolution. Because it operates on b channels rather than C_{in}, the cost is reduced by a factor of (C_{in}/b)^2 = 16 compared to a 3x3 conv on the full channel width. When the block needs to downsample at a stage transition, stride 2 is applied at this layer, halving the spatial dimensions.

Layer 3: 1x1 Expand

The final 1x1 convolution projects the b-channel representation back to C_{out} = 4b channels. This expansion is necessary so the output can be added to the skip connection. Critically, there is no ReLU after this layer's batch normalization. ReLU comes only after the residual addition. Placing ReLU here would mean the residual branch output is always non-negative, preventing the block from learning to subtract from the identity mapping.


Computational Savings

Compare a bottleneck block against an equivalent BasicBlock for a 56 \times 56 feature map with 256 output channels.

BasicBlock (two 3x3 convolutions at 256 channels). FLOPs per layer: 56^2 \times 256 \times 256 \times 9 = 1{,}849{,}688{,}064. Two layers: 3{,}699{,}376{,}128 total FLOPs.

Bottleneck (1x1 reduce to 64, 3x3 at 64, 1x1 expand to 256):

Bottleneck total: 218{,}365{,}952 FLOPs, roughly 5.9\% of the BasicBlock's cost. This 17x reduction is what makes 50+ layer networks feasible. The savings come from avoiding the 3x3 convolution on full-width channels: the bottleneck's 3x3 layer costs $$ FLOPs versus 1{,}850M per 3x3 layer in the BasicBlock.


Paper Context

He et al. (2015) introduced the bottleneck block as a practical solution for building very deep networks. The paper demonstrated that networks with 50, 101, and 152 layers could be trained successfully using residual connections, achieving state-of-the-art results on ImageNet (top-5 error of 3.57% with an ensemble).

The paper uses two building block types. The BasicBlock for ResNet-18 and ResNet-34 contains two 3x3 convolutions with the same channel count. The Bottleneck Block for ResNet-50, ResNet-101, and ResNet-152 uses the 1x1-3x3-1x1 sequence. Replacing BasicBlocks with bottlenecks in a 34-layer network produces the 50-layer ResNet, which has more layers yet similar computational cost.

The channel progression across stages follows a consistent pattern. Stage 1: C_{out} = 256, b = 64. Stage 2: $ = 512$, b = 128. Stage 3: $ = 1024$, b = 256. Stage 4: $ = 2048$, b = 512. Each stage begins with a stride-2 block to halve spatial dimensions (except stage 1). The number of blocks per stage: ResNet-50 uses [3, 4, 6, 3], ResNet-101 uses [3, 4, 23, 3], and ResNet-152 uses [3, 8, 36, 3].

The projection shortcut (option B in the paper) is used whenever channel dimensions change between stages. The paper compared three shortcut strategies: (A) zero-padding for dimension increases, (B) projection shortcuts only for dimension changes, (C) projection shortcuts everywhere. Option B was adopted for bottleneck networks as it provides a clean dimension match without excessive parameters.


Numerical Example

Trace through a bottleneck block with input x of shape (1, 56, 56, 256): batch size 1, spatial size $ \times 56$, C_{in} = 256. Bottleneck width b = 64, output channels C_{out} = 256 (identity shortcut).

Layer 1: 1x1 Reduce (256 to 64). Weight shape: (64, 256, 1, 1). At each spatial position, a 64 \times 256 matrix multiplies the 256-channel vector to produce a 64-channel vector. Output after conv, BN, ReLU: (1, 56, 56, 64). Parameters: 64 \times 256 = 16{,}384 weights + 128 BN parameters = $$.

Layer 2: 3x3 Process (64 to 64). Weight shape: (64, 64, 3, 3). Each of 64 output channels has a $ \times 3 \times 3$ filter. Padding 1 preserves spatial size. Output after conv, BN, ReLU: (1, 56, 56, 64). Parameters: 64 \times 64 \times 9 = 36{,}864 weights + 128 BN parameters = $$.

Layer 3: 1x1 Expand (64 to 256). Weight shape: (256, 64, 1, 1). Projects 64 channels back to 256. Output after conv, BN (no ReLU): (1, 56, 56, 256). Parameters: 256 \times 64 = 16{,}384 weights + 512 BN parameters = $$.

Shortcut. C_{in} = C_{out} = 256, spatial size unchanged, so \text{shortcut}(x) = x with shape (1, 56, 56, 256). No additional parameters.

Addition and ReLU. Element-wise add y_3 + x, both (1, 56, 56, 256). Apply ReLU. Output: (1, 56, 56, 256). Total block parameters: 16{,}512 + 36{,}992 + 16{,}896 = 70{,}400.

Now consider the first block of stage 2: C_{in} = 256, C_{out} = 512, stride 2. Layer 1: 1x1 reduces (256 \to 128), output (1, 56, 56, 128). Layer 2: 3x3 with stride 2 produces (1, 28, 28, 128). Layer 3: 1x1 expands (128 \to 512), output (1, 28, 28, 512). The shortcut requires a projection: 1x1 conv (256 \to 512) with stride 2, producing (1, 28, 28, 512). Both branches now match for element-wise addition.


BasicBlock vs Bottleneck

Structure. The BasicBlock has two 3x3 convolutions with the same channel count: \text{Conv}_{3 \times 3}(C, C) \to \text{Conv}_{3 \times 3}(C, C). The bottleneck has three layers with a compress-expand pattern: \text{Conv}_{1 \times 1}(C, b) \to \text{Conv}_{3 \times 3}(b, b) \to \text{Conv}_{1 \times 1}(b, C) where C = 4b.

Depth vs width trade-off. The BasicBlock uses 2 layers per block, so a 34-layer network has 16 blocks. The bottleneck uses 3 layers per block, so a 50-layer network also has 16 blocks. Despite having 16 more layers, the 50-layer bottleneck network has similar FLOPs to the 34-layer BasicBlock network because the 3x3 convolution in the bottleneck operates on b = C/4 channels.

When to use which. The BasicBlock is appropriate for shallower networks (18, 34 layers) where channel counts are modest (64, 128, 256, 512). At these widths, the overhead of two extra 1x1 convolutions per block outweighs the savings from channel compression. The bottleneck becomes advantageous when output channel counts reach 256 or higher, which is the case for deeper variants.

Output channel expansion. In BasicBlock networks, output channels per stage are [64, 128, 256, 512]. In bottleneck networks, they are [256, 512, 1024, 2048], four times larger at every stage. This is because the 1x1 expand layer outputs 4b channels. The wider representations in bottleneck networks contribute to their superior accuracy beyond just additional depth.


Pitfalls


Examples

Example 1

Input
x = [[-0.7426, -0.1301, -0.7123, -0.4457], [0.3806, -0.3494, -0.1983, 0.6219]], W1 = [[0.0373, -0.1615], [0.7307, 0.6209], [0.1297, 0.5358], [-0.0651, 0.9429]], W2 = [[-0.3527, 0.1678], [0.2844, -0.4584]], W3 = [[-0.5299, -0.9595, 0.8399, -0.5658], [-0.5844, -0.1505, 1.6222, 0.8047]], Ws = null
Output
[[-0.7426, -0.1301, -0.7123, -0.4457], [0.3806, -0.3494, -0.1501, 0.6219]]
Explanation
The bottleneck reduces channels via W1, processes in the compressed space via W2, then expands back via W3. When in_channels != out_channels, a projection Ws is applied to the skip path.

Example 2

Input
x = [[-0.6618, -0.508, -0.4297]], W1 = [[0.5457, -0.0239], [0.0839, 0.8292], [-0.0259, -0.535]], W2 = [[0.0063, -0.2333], [0.5687, 0.3594]], W3 = [[-0.2986, -0.3825, -0.4176], [0.6148, -0.1181, 0.677]], Ws = null
Output
[[-0.6618, -0.508, -0.4297]]

Example 3

Input
x = [[0.445, 0.4881, 0.3688, 0.6601], [-0.5888, 0.7906, 0.4086, 1.1758]], W1 = [[-0.0572, 0.1029], [-1.2073, -0.2187], [0.3029, 0.0361], [-0.5501, -0.4557]], W2 = [[0.5818, -0.1294], [1.5347, -0.2352]], W3 = [[0.8051, 0.2357, 0.303, -0.1986, -0.1059, -0.5624], [0.2734, 0.6371, 0.2357, -0.1436, -0.1602, 0.0571]], Ws = [[0.5644, -0.1447, 1.179, 0.9419, -0.7793, 0.4806], [-0.3896, -0.0531, -0.3855, -0.6134, -0.9262, -0.6544], [-0.261, 0.5411, -0.4857, -0.5554, -0.0268, 0.4359], [-0.2159, 0.1356, 0.4377, -0.0175, -0.391, -0.0503]]
Output
[[-0.1778, 0.1988, 0.4463, -0.0966, -1.0668, 0.022], [-1.0008, 0.4238, -0.6828, -1.2871, -0.7441, -0.6814]]

Hints

  1. Compute the shortcut before changing the main path.
  2. The main path is ReLU(x @ W1), then ReLU(out @ W2), then out @ W3.
  3. Apply the last ReLU after adding the shortcut.

Requirements

Constraints

Starter Code

import numpy as np

def bottleneck_block(x, W1, W2, W3, Ws):
    """
    Returns the bottleneck residual-block output as a nested list.
    """
    pass

Test Cases

CaseMatches
4-2-4 with projectionpublic
3-2-3 no projection neededpublic
4-2-6 expand with projectionpublic