Paper · 2019-03-28 · cs.CV
FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation
FastFCN replaces the dilated convolutions of a segmentation backbone with Joint Pyramid Upsampling (JPU): a learned module that upsamples the last three ResNet stages to output stride 8 using parallel dilated separable convolutions. Author-reported: 51.05 mIoU on PASCAL-Context (ResNet-50, single scale) at 37.56 FPS, against 49.91 mIoU and 18.77 FPS for dilated EncNet.
Semantic segmentationDilated convolutionFeature upsamplingsemantic-segmentationupsamplingbackbone-efficiency
arXivPDFCodeMarkdown12 min read
AI-compiled by claude-haiku-5-5 on 2026-11-01, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Segmentation models label every pixel of an image. To keep fine detail, the usual trick is to stop the image network from shrinking the picture, which keeps the computation high. That is slow and uses a lot of memory.
This paper keeps the normal, shrinking backbone and adds a small learned module, Joint Pyramid Upsampling (JPU). JPU rebuilds a sharp, high-resolution feature map from the last three layers of the backbone, using several differently spaced filters to look at context at several scales. The authors report that this reports that accuracy holds while the network runs faster.
TL;DR
Dilated convolutions keep segmentation feature maps sharp but cost compute and memory. FastFCN uses an ordinary backbone and a joint upsampling module, JPU, to rebuild the high-resolution map. Author-reported on PASCAL-Context with ResNet-50, single scale: 51.05 mIoU and 37.56 FPS, against 49.91 mIoU and 18.77 FPS for dilated EncNet tab. 1 tab. 2. The official MIT-licensed code differs from the paper in its losses and learning rates (see Paper vs code).
Problem
A classification backbone downsamples the image 32 times, so the last feature map is too coarse for boundaries §1. Dilated (atrous) convolutions avoid that by removing the last two downsampling steps. Their cost grows with the enlarged maps: with ResNet-101, the 23 residual blocks of the dilated stage need about four times the computation and memory of the original, and 3 further blocks about sixteen times §1 §3.2. The paper targets this cost and assumes that the dilated stage can be replaced without losing accuracy §1.
Method
Reformulation. The paper treats the dilated backbone as a target feature map to approximate. Both the dilated and the stride-based layers can be written with the same regular-convolution stack, applied to differently split inputs §3.1 eq. 2 eq. 3. This makes the job a joint upsampling problem: given a low-resolution map and a high-resolution guide, find a function that maps the guide into the target §3.3 eq. 1. The paper solves it with a CNN instead of iterative optimisation §3.3.3 eq. 4.
Network. The backbone is the standard FCN (ResNet-50 or ResNet-101) with stride convolutions and no dilation §3.2. JPU takes the last three stages, Conv3, Conv4 and Conv5 §3.2. A context module on top of JPU produces the label map: PSP [zhao2017pyramid], ASPP [chen2017rethinking] or Encoding [zhang2018context] §3.2. The paper says JPU's output is at output stride 8 §1.
JPU. Each of the three inputs goes through a regular convolution block that maps it to a common width §3.3.3 fig. 4a. Conv4 and Conv5 are upsampled to the size of Conv3 and concatenated with it §3.3.3 fig. 4b. Four separable convolutions with dilation rates 1, 2, 4 and 8 read this concatenation in parallel §3.3.3. The rate-1 branch captures the relation between the Conv3 map and the rest of the fused map. The rate-2, 4 and 8 branches learn the mapping that produces the stride-reduced Conv4-level output §3.3.3 fig. 5. Their outputs are merged, and a final regular convolution block turns the result into predictions §3.3.3 fig. 4c.
# Joint Pyramid Upsampling (JPU) [§3.3] [fig. 4] [eq. 4]
# Input: conv3 (highest resolution), conv4, conv5
# Output: (B, 4d, H3, W3); width d is not stated in the paper
def joint_pyramid_upsampling(conv3, conv4, conv5):
# Map each input to the same width d with a regular conv block.
# Code uses a 3x3 kernel, not stated in the paper.
# [§3.3] [code: encoding/nn/customize.py#L158-L169]
low3 = conv_block(conv3) # (B, d, H3, W3)
low4 = conv_block(conv4) # (B, d, H4, W4)
low5 = conv_block(conv5) # (B, d, H5, W5)
# Upsample Conv4 and Conv5 to Conv3's size, then concatenate.
# Code uses bilinear resizing.
# [§3.3] [code: encoding/nn/customize.py#L185-L190]
low4 = resize_to(low4, (H3, W3))
low5 = resize_to(low5, (H3, W3))
fused = concat([low5, low4, low3]) # (B, 3d, H3, W3)
# Four separable convs with dilation 1, 2, 4, 8 read the same map.
# Each branch outputs d channels.
# [§3.3] [code: encoding/nn/customize.py#L171-L182]
branch1 = separable_conv(fused, dilation=1)
branch2 = separable_conv(fused, dilation=2)
branch4 = separable_conv(fused, dilation=4)
branch8 = separable_conv(fused, dilation=8)
# Concatenate the four branches along the channel axis.
# [code: encoding/nn/customize.py#L190-L192]
return concat([branch1, branch2, branch4, branch8])Training loss in the released EncNet scripts. The paper names only pixel-wise cross-entropy §4.1. The released EncNet scripts also add an auxiliary loss and a semantic-encoding loss. They are shown here as a separate step, because the paper does not describe them.
# EncNet training loss as set by the released scripts
# [code: encoding/nn/customize.py#L37-L57] [code: experiments/segmentation/option.py#L38-L43]
# Inputs: main logits, aux logits, SE logits, target labels
def total_loss(main, aux, se_logits, target):
# Main pixel-wise cross-entropy; the only term the paper names [§4.1]
loss = cross_entropy(main, target)
# Auxiliary cross-entropy from the Conv3 branch, weight 0.2 by default
# [code: encoding/models/encnet.py#L37-L40]
loss = loss + 0.2 * cross_entropy(aux, target)
# Binary loss on the classes present in the target, weight 0.2
se_target = classes_present(target) # (B, nclass), multi-hot
loss = loss + 0.2 * bce(sigmoid(se_logits), se_target)
return lossDilated and stride convolutions. The paper's analysis, in one dimension, says a dilated convolution equals a split, a regular convolution and a merge §3.3.2 fig. 3. A stride convolution equals a regular convolution followed by dropping every other element §3.3.2 fig. 3. This analysis gives the reason for the reformulation and is not needed to implement JPU.
Hyperparameters
Values are as stated in the paper unless marked "code". Where the paper says
nothing, the entry is "not stated". Code values come from the official repo
at code_commit.
| Name | Value | Where | Notes |
|---|---|---|---|
| Backbone | ResNet-50 or ResNet-101, standard FCN stride (output stride 32) | §3.2 §4.1 | Dilation is removed; JPU replaces it |
| Pretraining of backbone | not stated | §4.1 | Code loads ImageNet-pretrained weights (pretrained=True) base.py |
| JPU input and output width d | not stated | §3.3 | Code uses 512 base.py |
| JPU first conv block kernel | not stated | §3.3 | Code uses 3x3, BN, ReLU customize.py |
| JPU separable dilation rates | 1, 2, 4, 8 | §3.3 fig. 4 | Code sets padding = dilation customize.py |
| Context head (EncNet) | Encoding module, codeword count not stated | §3.2 | Code default 32 encnet.py |
| PASCAL-Context train / val | 4,998 / 5,105 images | §4.1 | 59 object classes plus background for the SOTA table tab. 3 |
| PASCAL-Context label set, ablation | 59 classes, background excluded | §4.1 §4.2 | Following EncNet [zhang2018context] |
| ADE20K train / val / test | 20K / 2K / 3K images, 150 classes | §4.3 | |
| Batch size | 16 | §4.1 | Code default 16 option.py |
| Hardware | 4 Titan-Xp GPUs, 12 GB each | §4.1 | |
| Crop size | 480 x 480 | §4.1 | Code default 480 base.py |
| Base size | not stated in paper | Code default 520 base.py | |
| Scale augmentation | random scale 0.5 to 2.0 | §4.1 | Code: short side drawn from [0.5, 2.0] x 520 base.py |
| Horizontal flip | p = 0.5 | §4.1 | Code base.py |
| Gaussian blur | not stated | Code applies it with p = 0.5 base.py | |
| Optimizer | SGD | §4.1 | |
| Momentum | 0.9 | §4.1 | Code default option.py |
| Weight decay | 1e-4 | §4.1 | Code default option.py |
| Learning rate, PASCAL-Context | 0.001 initial | §4.1 | Code default at batch 16 option.py |
| Learning rate, ADE20K | 0.01 | §4.3 | Code default at batch 16 option.py |
| LR schedule | poly, power 0.9, decays to 0 | §4.1 | Code lr_scheduler.py |
| LR for JPU, head and aux layer | not stated | Code uses 10 x base LR train.py | |
| Epochs, PASCAL-Context | 80 | §4.1 | Code default option.py |
| Epochs, ADE20K | 120 on train | §4.3 | Code default option.py |
| ADE20K fine-tune | 20 epochs on train+val at LR 0.001 | §4.3 | Code script encnet_res50_ade20k_trainval.sh |
| Loss | pixel-wise cross-entropy | §4.1 | Released scripts add aux and SE losses (see Paper vs code) |
| Auxiliary loss weight | not stated | Code 0.2 option.py; PSP script 0.4 psp_res50_pcontext.sh | |
| SE-loss weight | not stated | Code 0.2 option.py | |
| Evaluation, ablation | single scale, no flip | §4.2 | |
| Evaluation, SOTA tables | multi-scale and flip, 60 classes with background | tab. 3 footnote | |
| Multi-scale test scales | not stated in paper | Code: 0.5, 0.75, 1.0, 1.25, 1.5, 1.75 with flip test.py | |
| FPS measurement | Titan-Xp, 512 x 512 input, mean of 100 runs | tab. 2 | Batch size not stated; code uses batch 1 test_fps_params.py |
| ADE20K final score | mean of pixAcc and mIoU | §4.3 |
Results
Author-reported. Setup: PyTorch §4.1; 4 Titan-Xp GPUs §4.1; dataset and backbone as shown. Baselines are the methods named in each table.
Ablation, PASCAL-Context val, ResNet-50, single scale, 59 classes tab. 1
| Head | Output stride | Upsampling | pixAcc % | mIoU % | Source |
|---|---|---|---|---|---|
| Encoding | 8 | None (dilated, = EncNet) | 78.39 | 49.91 | tab. 1 |
| Encoding | 32 | Bilinear | 76.10 | 46.47 | tab. 1 |
| Encoding | 32 | FPN | 78.16 | 49.59 | tab. 1 |
| Encoding | 32 | JPU (ours) | 78.98 | 51.05 | tab. 1 |
| ASPP | 8 | None | 78.27 | 49.19 | tab. 1 |
| ASPP | 32 | JPU (ours) | 78.79 | 50.07 | tab. 1 |
| PSP | 8 | None | 78.60 | 50.58 | tab. 1 |
| PSP | 32 | JPU (ours) | 78.91 | 50.89 | tab. 1 |
Speed, Titan-Xp, 512 x 512 input, mean of 100 runs tab. 2
| Backbone | Head | Upsampling | FPS | Source |
|---|---|---|---|---|
| ResNet-50 | Encoding | None | 18.77 | tab. 2 |
| ResNet-50 | Encoding | Bilinear | 45.67 | tab. 2 |
| ResNet-50 | Encoding | FPN | 37.87 | tab. 2 |
| ResNet-50 | Encoding | JPU (ours) | 37.56 | tab. 2 |
| ResNet-50 | ASPP | None | 15.99 | tab. 2 |
| ResNet-50 | ASPP | JPU (ours) | 20.67 | tab. 2 |
| ResNet-50 | PSP | None | 18.08 | tab. 2 |
| ResNet-50 | PSP | JPU (ours) | 28.48 | tab. 2 |
| ResNet-101 | Encoding | None | 10.51 | tab. 2 |
| ResNet-101 | Encoding | Bilinear | 35.20 | tab. 2 |
| ResNet-101 | Encoding | FPN | 32.40 | tab. 2 |
| ResNet-101 | Encoding | JPU (ours) | 32.02 | tab. 2 |
| ResNet-101 | ASPP | None | 10.46 | tab. 2 |
| ResNet-101 | ASPP | JPU (ours) | 18.08 | tab. 2 |
| ResNet-101 | PSP | None | 11.36 | tab. 2 |
| ResNet-101 | PSP | JPU (ours) | 23.87 | tab. 2 |
PASCAL-Context val, state of the art, 60 classes with background, multi-scale and flip for ours tab. 3
| Method | Backbone | mIoU % | Source |
|---|---|---|---|
| FCN-8s | not stated | 37.8 | tab. 3 |
| CRF-RNN | not stated | 39.3 | tab. 3 |
| ParseNet | not stated | 40.4 | tab. 3 |
| BoxSup | not stated | 40.5 | tab. 3 |
| HO_CRF | not stated | 41.3 | tab. 3 |
| Piecewise | not stated | 43.3 | tab. 3 |
| VeryDeep | not stated | 44.5 | tab. 3 |
| DeepLabV2 | ResNet-101 + COCO | 45.7 | tab. 3 |
| RefineNet | ResNet-152 | 47.3 | tab. 3 |
| EncNet | ResNet-101 | 51.7 | tab. 3 |
| DUpsampling | Xception-71 | 52.5 | tab. 3 |
| EncNet+JPU (ours) | ResNet-50 | 51.2 | tab. 3 |
| EncNet+JPU (ours) | ResNet-101 | 53.1 | tab. 3 |
The abstract reports 53.13% for the same ResNet-101 result [abstract]. The paper's text gives 52.10% (ResNet-50) and 54.03% (ResNet-101) on the 59-class protocol without background §4.3.
ADE20K val tab. 4
| Method | Backbone | pixAcc % | mIoU % | Source |
|---|---|---|---|---|
| FCN | not stated | 71.32 | 29.39 | tab. 4 |
| SegNet | not stated | 71.00 | 21.64 | tab. 4 |
| DilatedNet | not stated | 73.55 | 32.31 | tab. 4 |
| CascadeNet | not stated | 74.52 | 34.90 | tab. 4 |
| RefineNet | ResNet-152 | not stated | 40.7 | tab. 4 |
| PSPNet | ResNet-101 | 81.39 | 43.29 | tab. 4 |
| PSPNet | ResNet-269 | 81.69 | 44.94 | tab. 4 |
| EncNet | ResNet-50 | 79.73 | 41.11 | tab. 4 |
| EncNet | ResNet-101 | 81.69 | 44.65 | tab. 4 |
| Ours | ResNet-50 | 80.39 | 42.75 | tab. 4 |
| Ours | ResNet-101 | 80.99 | 44.34 | tab. 4 |
ADE20K test, evaluation server tab. 5
| Rank | Team | Single model | Final score | Source |
|---|---|---|---|---|
| 1 | CASIA_IVA_JD | no | 0.5547 | tab. 5 |
| 2 | WinterIsComing | no | 0.5544 | tab. 5 |
| - | PSPNet | ResNet-269 | 0.5538 | tab. 5 |
| - | EncNet | ResNet-101 | 0.5567 | tab. 5 |
| - | Ours | ResNet-101 | 0.5584 | tab. 5 |
The first two entries ranked first and second in the COCO-Place challenge 2017 tab. 5.
Paper vs code
Repository: https://github.com/wuhuikai/FastFCN. The owner is one of the
paper's authors (Huikai Wu). The default branch (master) is the pure-Python
rewrite at commit 126661b, cited below. The README says the original code
that produced the arXiv results is branch v1.0.0 README.md. I did not
clone that branch; a WebFetch of its encoding/nn/customize.py (commit
107bd864) shows the same JPU and separable-convolution classes as master,
so the JPU discrepancies below apply to both. The rest of v1.0.0 (losses,
training script) was not checked. Lines cited here are from master.
- JPU first block. Paper: a regular convolution block, kernel and width not stated §3.3 fig. 4a. Code: 3x3 conv, BN, ReLU, width 512 customize.py base.py.
- Final block. Paper: a further regular convolution block after the separable convolutions turns the features into predictions §3.3 fig. 4c. Code: JPU returns the four-branch concatenation with no further conv customize.py. The next layer is the head's 1x1 conv, 2048 to 512 channels encnet.py.
- Upsampling mode. Paper: not stated. Code: bilinear with
align_corners=Truebase.py. - Loss. Paper: pixel-wise cross-entropy only §4.1. Code: the EncNet
scripts pass
--aux --se-loss, adding an auxiliary cross-entropy from the Conv3-level features (weight 0.2 by default, 0.4 in the PSP script) and a binary loss on the classes present (weight 0.2) customize.py encnet.py option.py psp_res50_pcontext.sh. - Learning rate per module. Paper: a single LR for the network §4.1. Code: 10 times the base LR for the JPU, the head and the auxiliary layer train.py. The backbone uses the base LR.
- Scale augmentation. Paper: scale from 0.5 to 2.0 §4.1. Code: the short side is drawn uniformly from 0.5 x 520 to 2.0 x 520, i.e. 260 to 1040 pixels, then a 480 crop base.py base.py. Code also applies a Gaussian blur with probability 0.5, which the paper does not mention base.py.
- Multi-scale test. Paper: multi-scale and flip for SOTA only; scales not
stated tab. 3 footnote. Code: scales 0.5 to 1.75 in steps of 0.25, plus flip,
when
--msis set test.py. - ADE20K fine-tuning. Paper: 120 epochs at LR 0.01, then 20 epochs at 0.001 on train+val §4.3. Code defaults match option.py option.py encnet_res50_ade20k_trainval.sh. The paper does not state the LR schedule for ADE20K; the code uses poly decay train.py.
- FPS. Paper: 512 x 512 input, 100 runs, Titan-Xp tab. 2. Code uses a single-image batch of random data test_fps_params.py.
- Released checkpoints. The README lists EncNet+JPU ResNet-101 at 55.84 (MS) for the ADE20K train+val model, while the paper reports 0.5584 on the test server README.md. These are different splits and should not be compared directly.
- Crop size. The README and paper both say EncNet with ResNet-101 was trained at 576 and EncNet+JPU at 480 to fit 12 GB GPUs §4.3 README.md.
No discrepancies were found for the backbone depth, batch size, crop size, momentum, weight decay, poly schedule power or the PASCAL-Context epoch count.
Searched: the abstract and the paper's links (the project page at
wuhuikai.me/FastFCNProject is linked from the README, not the arXiv abstract).
The GitHub link in the abstract is the repo above.
Pitfalls
- Missing JPU details. The paper gives neither the width d nor the kernel of the first conv block, nor the BN and ReLU placement. Implement the code's values (width 512, 3x3, BN, ReLU) and note that the paper does not fix them.
- Dilation and padding. In the code each separable branch uses padding equal to its dilation rate, so the spatial size is kept customize.py. Implementing the branches with another padding changes the output size.
- Output resolution. JPU's output has Conv3's resolution, not the input's. The EncNet head then bilinearly upsamples the prediction to the input size encnet.py.
- Auxiliary and SE losses are not in the paper. A faithful reimplementation of the paper's loss is pixel cross-entropy only; the reported numbers come from the released scripts, which include the extra losses customize.py.
- Learning-rate multiplier. The 10 times LR for the JPU and head is a code choice. Without it, the JPU trains from the backbone's LR, which the paper does not discuss train.py.
- Speed measurement. The FPS in the paper compares modules within a common setup. The speedup depends on the head and backbone: FPS for ResNet-101 with JPU versus EncNet is 32.02 against 10.51 tab. 2.
- Computation claims. The "more than three times" reduction in computation is stated in the text §1 §5. The paper gives no FLOP or memory table for it; the FPS table is the only measurement.
- Crop asymmetry. ResNet-101 EncNet was trained at 576 but EncNet+JPU at 480, which the paper attributes to GPU memory §4.3. Results from the two crop sizes are not a clean comparison.
- Class counts. The ablation uses 59 classes without background, and the SOTA table uses 60 with background. The same model gives different mIoU on each protocol tab. 3 footnote §4.3.
- Upsampling in JPU_X. The code also contains a JPU_X variant with a second, stacked module customize.py. It is not in the paper. Do not implement it as part of the described method.
Lineage
- Builds on: dilated convolutions of DeepLab [chen2018deeplab], the PSP pyramid pooling module [zhao2017pyramid], ASPP of DeepLabV3 [chen2017rethinking], and the Encoding module of EncNet [zhang2018context] §2 §3.1.
- Joint upsampling and guided filtering [li2016deep] [wu2018fast] are cited as related, but for 3-channel images; the paper says JPU is built for high-dimensional feature maps §2.
- Compared against: FPN [lin2017feature] tab. 1; bilinear upsampling tab. 1; DUpsampling [tian2019decoders], which the paper says depends on the label space §2 tab. 3.
- Superseded by: not known from content in this wiki.
Source and license
Adapted from “FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation” by Huikai Wu, Junge Zhang, Kaiqi Huang, Kongming Liang, Yizhou Yu (arXiv:1903.11816), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/wuhuikai/FastFCN at commit 126661b0eb60 (MIT). Any quoted code is shown under that license.