← All tutorials

On this page

1 Depthwise separable convolution (MobileNet) 2 MobileNetV2 inverted residual 3 Squeeze-and-excitation 4 EfficientNet compound scaling 5 Efficient CNN problems and fixes 6 References

Efficient CNNs: MobileNet and EfficientNet

ResNet-50 holds about 25.6 million weights. The networks here give up a few points of accuracy, or none, for far fewer weights and multiply-adds, small enough for a phone. Four ideas do it: split a convolution into a cheap spatial step and a cheap channel step, invert the residual block so the cheap step works on a wide map, reweight channels with a small gate, and scale depth, width and image size together.

This page builds on Classic CNN Architectures: AlexNet to ResNet, which teaches the 1×1 convolution, batch norm and the residual shortcut that the blocks here reuse.

1 splitthe kernel2 invertthe block3 reweightchannels4 scaleall three7× fewer weights, 13× less compute than ResNet-50
Figure 1. Each box links to its section; the payoff line is torchvision’s MobileNetV2.

1 Depthwise separable convolution (MobileNet)

A standard kernel filters space and mixes channels in one step; doing the two jobs one after the other costs far fewer weights. MobileNet (Howard et al. 2017) builds a whole network from this depthwise separable convolution, an idea used earlier by Sifre (2014) and in Xception (Chollet 2017), and ends with global average pooling and one fully connected layer.

standard 3×318,432 weightsdepthwise + 1×1288+2,0482,336 weights
Figure 2. 32 channels in, 64 out, 3×3 kernels; areas are drawn to scale.

1.1 Depthwise and pointwise steps

The depthwise step gives every input channel its own 3×3 kernel and never mixes channels. The pointwise step is a 1×1 convolution, which blends the channels at each pixel and sees no neighbors (1×1 convolution, in Classic CNN Architectures), so it does all the mixing. In PyTorch, with ci and co the Cin = 32 and Cout = 64 channels, groups=ci is what makes dw depthwise:

import torch
from torch.nn import Conv2d
from functools import partial

Conv = partial(Conv2d, bias=False)
ci, co = 32, 64
opts = dict(padding=1, groups=ci)
dw = Conv(ci, ci, 3, **opts)
pw = Conv(ci, co, 1)
x = torch.randn(1, ci, 56, 56)
y = pw(dw(x))
print(y.shape)
# [1, 64, 56, 56]
D = dw.weight.numel()  # 288
P = pw.weight.numel()  # 2048
  1. 1The depthwise step D: one 3×3 kernel per channel.
  2. 2The pointwise step P: a 1×1 that mixes the channels.
  3. 3Runs both in order on a 56×56 map.
  4. 4Counts the weights of D and P (288 and 2,048), which Section 1.2 turns into a formula.

1.2 Weight count of the split

The split replaces one large weight count with two small ones, so the running example needs about eight times fewer weights.

standard Cout · Cin · k² every output reads every input, k×k each depthwise Cin · k² one k×k filter per input channel pointwise Cout · Cin mixes channels, no spatial work vs standard Cout · Cin · k² every output reads every input, k×k each vs depthwise Cin · k² one k×k filter per input channel pointwise Cout · Cin mixes channels, no spatial work
Figure 3. Blue holds a weight per channel pair per cell; orange splits space from channels.
Weights of a standard convolution against its depthwise separable split.
standard = Cout · Cin · k²
With Cin input channels, Cout output channels and kernel size k, a standard convolution gives every output channel a k by k kernel over every input channel, so its count multiplies all three factors, and the kernel size counts twice.
D = Cin · k²
The depthwise step D keeps one k by k kernel per input channel and never mixes channels, so Cout drops out of its count.
P = Cout · Cin
The pointwise step P is a 1×1 convolution from Cin to Cout channels. It does all the mixing, so k drops out of its count.
separable = D + P
The split counts weights for space and for channels separately, adding two counts instead of multiplying, so the saving grows with k and Cout; Cin multiplies both sides and cancels. In the running example that is 2,336 weights against 18,432, 7.9 times fewer. At k = 1 the split costs more, so 1×1 layers are never split.
Kernel
standard convdepthwise separablebars on a log scale

Figure 4. The gap widens with kernel size and output channels; inputs stay at 32.


2 MobileNetV2 inverted residual

MobileNetV2 (Sandler et al. 2018) builds its block from the same split convolution and adds a ResNet-style shortcut around it, so the block adds its input back to its output (ResNet, in Classic CNN Architectures): a 1×1 widens the channels, the depthwise 3×3 filters the wide map cheaply, and a 1×1 narrows it back.

MobileNetV2 inverted blockin1×13×31×1
Figure 5. MobileNetV2 is wide in the middle, the reverse of the ResNet bottleneck.

④ The shortcut adds the block’s input to the projected output, so the block only has to learn a change.

The block’s output is its input plus three layers applied in turn.
y = x + P(D(E(x)))
E expands the channels t times with a 1×1, D is the depthwise step of Section 1 on the wide map, a 3×3 that filters each channel on its own, P is the pointwise step, a 1×1 that projects back to the input width with no activation, and the shortcut adds the input x.
purple, the wide layers E and Dblue, the narrow endsorange, the shortcut

The expansion ratio t sets the middle width, t times the input channels. Raising it gives the depthwise filter more channels at proportionally more cost; t = 1 skips the widening 1×1. The paper and torchvision use t = 6 in every stage but the first. The last 1×1 has no ReLU (which keeps positive values and sets negative ones to zero; Convolution, in CNN Basics), because a ReLU on the narrow output would erase information, and the shortcut is used only when the stride (the step between kernel positions; Size, stride and padding, in CNN Basics) is 1 and the width is unchanged.

In PyTorch, branch builds P(D(E(x))), widening with ReLU6 (a ReLU capped at 6), filtering depthwise and projecting back with no activation. The example is a stride-1 block of the 24-channel stage.

import torch
import torch.nn as nn
from torch.nn import Conv2d
from functools import partial

Conv = partial(Conv2d, bias=False)
BN = nn.BatchNorm2d

def branch(c, t):
    h = c * t  # wide middle
    # groups=h: depthwise 3×3
    dw = Conv(h, h, 3, 1, 1, 1, h)
    return nn.Sequential(
        Conv(c, h, 1), BN(h),
        nn.ReLU6(),
        dw, BN(h), nn.ReLU6(),
        Conv(h, c, 1), BN(c))

class InvRes(nn.Module):
    def __init__(self, c, t=6):
        super().__init__()
        self.f = branch(c, t)

    def forward(self, x):
        return x + self.f(x)

x = torch.randn(1, 24, 56, 56)
print(InvRes(24)(x).shape)
# [1, 24, 56, 56]
  1. 1The middle width, t times the input channels.
  2. 2Widens with a 1×1, the first tall bar of Figure 5.
  3. 3Filters the wide map depthwise, the second tall bar.
  4. 4Projects back to c channels with no ReLU.
  5. 5Adds the shortcut around the whole branch.

The positional arguments of Conv are in, out, kernel, stride, padding, dilation and groups. BN is batch norm, which rescales each channel to mean 0 and spread 1 over the batch, then applies a learned scale and shift (Batch normalization, in Classic CNN Architectures). ImageNet is a labeled photo set with 1,000 classes, and top-1 accuracy is the share of test photos whose highest-scoring class is the right one (AlexNet, in Classic CNN Architectures). The full torchvision model needs a fraction of ResNet-50’s weights and compute for a few points of top-1 (Figure 6; torchvision mobilenetv2.py, torchvision resnet50 weights).

MobileNetV2ResNet-50weights3.5M25.6MGFLOPs0.304.09top-171.9%76.1%
Figure 6. torchvision MobileNetV2 vs ResNet-50: 7× fewer weights, 13× less compute.

3 Squeeze-and-excitation

Squeeze-and-excitation (Hu et al. 2018) is a small add-on that scores each channel of a feature map from its average and rescales the channel by that score, so useful channels get louder and others quieter; many CNN blocks carry it, including the B0 blocks of EfficientNet (Section 4).

0.90.20.70.1before, with scoresrescaleafter
Figure 7. Illustrative scores: each channel is multiplied by its own score between 0 and 1.

In the block below, for maps x of shape [B, C, H, W] (batch, channels, height, width), the mean squeezes each map to one number s, two small linear layers turn those into a 0 to 1 weight w per channel, and the last line rescales the maps. The ratio r sets the gate’s hidden width C/r: raising it shrinks the gate and its cost, lowering it gives the gate more capacity, and r = 1 keeps the full width; the paper uses 16.

import torch
import torch.nn as nn

class SEBlock(nn.Module):
    def __init__(self, C, r=16):
        super().__init__()
        self.gate = nn.Sequential(
            nn.Linear(C, C // r),
            nn.ReLU(),
            nn.Linear(C // r, C),
            nn.Sigmoid())

    def forward(self, x):
        s = x.mean((2, 3))
        # s: [B, C]
        w = self.gate(s)  # 0 to 1
        w = w[:, :, None, None]
        return x * w

x = torch.randn(2, 64, 28, 28)
print(SEBlock(64)(x).shape)
# [2, 64, 28, 28]
  1. 1The gate: C to C/r to C, then a sigmoid to 0 to 1.
  2. 2Squeezes each map to its mean s.
  3. 3Turns s into one weight w per channel.
  4. 4Rescales every map by its weight.

It runs on every forward pass, in training and at test time. Added to every ResNet-50 block with r = 16, it adds almost no compute but about a tenth more weights, most of them in the last, widest stage (Figure 8). The authors’ code is at github.com/hujie-frank/SENet.

compute +0.26% weights +10%
Figure 8. In ResNet-50 it adds 0.01 GFLOPs to 3.86 and 2.5M weights to 25M (the paper).

4 EfficientNet compound scaling

EfficientNet (Tan and Le 2019) makes a small network larger by raising one coefficient that grows depth, width and image size together. A larger image needs more layers to widen the receptive field (the window of input one output sees; Receptive field, in Receptive Fields) and more channels to hold its finer detail, so growing only one of the three leaves the other two too small for it. It starts from a small network, B0, found by architecture search (neural architecture search) and built from MobileNetV2 blocks (Section 2).

At the same compute, growing all three beats growing any one of them (Figure 9).

76%78%80%82%79.0%depth ×478.9%width ×279.1%resolution ×281.1%compoundeach about 1.8B FLOPs; dashed line B0, 77.3%
Figure 9. B0 scaled four ways at equal compute (the paper, Table 7).

B0 is then grown with one coefficient, φ (phi). The three bases come from a small grid search on B0 at φ = 1:

Compound scaling grows depth, width and image size with one coefficient.
depth multiplier = 1.2φ
Each step of the coefficient φ multiplies the number of layers by 1.2, so depth grows exponentially in φ.
width multiplier = 1.1φ
Each step multiplies the number of channels by 1.1. The base is smaller than depth's because compute grows with the square of width, so a small width step already costs as much as a larger depth step.
resolution multiplier = 1.15φ
Each step multiplies the image side by 1.15, a smaller base than depth's for the same reason as width: compute grows with its square.
1.2 · 1.1² · 1.15² ≈ 2
Compute grows in proportion to depth but with the square of width and of resolution: a wider layer has more channels both in and out, and a larger image grows in both directions. The bases are picked so this product is about 2, so each step of φ about doubles the FLOPs (floating-point operations) while the three parts grow in balance.

Compound scaling is applied once, at design time: φ picks a model before training and nothing changes while it trains. Raising φ by 1 about doubles the compute, and φ = 0 is B0 itself; the bases 1.2, 1.1 and 1.15 stay fixed. Raising φ step by step gives the family B0 to B7.

ModelTop-1ParamsFLOPs
B077.1%5.3M0.39B
B179.1%7.8M0.70B
B280.1%9.2M1.0B
B381.6%12M1.8B
B482.9%19M4.2B
B583.6%30M9.9B
B684.0%43M19B
B784.3%66M37B

B0 to B7 by ImageNet top-1 accuracy (the paper, Table 2); the released models round these sizes.

torchvision stores a width and a depth multiplier per model (B1 1.0 and 1.1, B4 1.4 and 1.8, B6 1.8 and 2.6), and rounds layer counts up and channels to multiples of 8 (torchvision efficientnet.py).

from torchvision.models import efficientnet_b4

# random weights, nothing is downloaded
model = efficientnet_b4(weights=None)
  1. 1Builds B4 with random weights; each block is MobileNetV2’s inverted residual with SiLU and the squeeze-and-excitation step of Section 3, and training skips blocks at random with probability up to 0.2 (stochastic depth).

B7 matches GPipe (Huang et al. 2019) at 84.3% top-1 with about 8 times fewer weights (Figure 10).

EfficientNet-B7 66M GPipe 557M
Figure 10. Weights at the same 84.3% top-1 (the EfficientNet paper).

At the small end, B1 is 7.6 times smaller and 5.7 times faster than ResNet-152 (the EfficientNet paper). The paper reports B0 and B1 a little differently in different tables (Figure 11).

77%78%79%B077.1 Table 277.3 Table 7B179.1 Table 278.8 Table 4
Figure 11. The same models in two tables of the paper differ by 0.2 and 0.3 points.

Other ways to make a model cheaper are quantization, pruning and knowledge distillation.


5 Efficient CNN problems and fixes

Each failure here comes from a cheap block used where its saving or its design does not hold.

SymptomLikely causeFix
A separable layer saves littleFew output channels, or a 1×1 kernel, where the split adds a layer and saves nothingUse a standard convolution, as MobileNet does for its first layer and its 1×1s
Accuracy drops after the narrow projectionA ReLU on the low-channel output discards informationNo activation on the projection
Scaling one dimension stops paying offDepth, width or resolution alone saturatesScale all three with one φ

How can a network carry memory across a sequence of any length? RNN Basics: Recurrence and Backpropagation Through Time answers that.


6 References

  1. Sifre, L. (2014). Rigid-Motion Scattering for Image Classification. PhD thesis, Ecole Polytechnique.
  2. Chollet, F. (2017). Xception: Deep Learning with Depthwise Separable Convolutions. CVPR.
  3. Howard, A. G., et al. (2017). MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications. arXiv:1704.04861.
  4. Sandler, M., et al. (2018). MobileNetV2: Inverted Residuals and Linear Bottlenecks. CVPR.
  5. Hu, J., et al. (2018). Squeeze-and-Excitation Networks. CVPR.
  6. Tan, M., and Le, Q. V. (2019). EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. ICML. arXiv:1905.11946. The B1 against ResNet-152 comparison is from the paper's Figure 1.
  7. Huang, Y., et al. (2019). GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism. NeurIPS.