scripts/modules/residual.py (215 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates.
from collections import OrderedDict
import torch.nn as nn
import torch.nn.functional as functional
from inplace_abn import ABN
class ResidualBlock(nn.Module):
"""Configurable residual block
Parameters
----------
in_channels : int
Number of input channels.
channels : list of int
Number of channels in the internal feature maps. Can either have two or three elements: if three construct
a residual block with two `3 x 3` convolutions, otherwise construct a bottleneck block with `1 x 1`, then
`3 x 3` then `1 x 1` convolutions.
stride : int
Stride of the first `3 x 3` convolution
dilation : int
Dilation to apply to the `3 x 3` convolutions.
groups : int
Number of convolution groups. This is used to create ResNeXt-style blocks and is only compatible with
bottleneck blocks.
norm_act : callable
Function to create normalization / activation Module.
dropout: callable
Function to create Dropout Module.
"""
def __init__(
self,
in_channels,
channels,
stride=1,
dilation=1,
groups=1,
norm_act=ABN,
dropout=None,
):
super(ResidualBlock, self).__init__()
# Check parameters for inconsistencies
if len(channels) != 2 and len(channels) != 3:
raise ValueError("channels must contain either two or three values")
if len(channels) == 2 and groups != 1:
raise ValueError("groups > 1 are only valid if len(channels) == 3")
is_bottleneck = len(channels) == 3
need_proj_conv = stride != 1 or in_channels != channels[-1]
if not is_bottleneck:
bn2 = norm_act(channels[1])
bn2.activation = "identity"
layers = [
(
"conv1",
nn.Conv2d(
in_channels,
channels[0],
3,
stride=stride,
padding=dilation,
bias=False,
dilation=dilation,
),
),
("bn1", norm_act(channels[0])),
(
"conv2",
nn.Conv2d(
channels[0],
channels[1],
3,
stride=1,
padding=dilation,
bias=False,
dilation=dilation,
),
),
("bn2", bn2),
]
if dropout is not None:
layers = layers[0:2] + [("dropout", dropout())] + layers[2:]
else:
bn3 = norm_act(channels[2])
bn3.activation = "identity"
layers = [
(
"conv1",
nn.Conv2d(
in_channels, channels[0], 1, stride=1, padding=0, bias=False
),
),
("bn1", norm_act(channels[0])),
(
"conv2",
nn.Conv2d(
channels[0],
channels[1],
3,
stride=stride,
padding=dilation,
bias=False,
groups=groups,
dilation=dilation,
),
),
("bn2", norm_act(channels[1])),
(
"conv3",
nn.Conv2d(
channels[1], channels[2], 1, stride=1, padding=0, bias=False
),
),
("bn3", bn3),
]
if dropout is not None:
layers = layers[0:4] + [("dropout", dropout())] + layers[4:]
self.convs = nn.Sequential(OrderedDict(layers))
if need_proj_conv:
self.proj_conv = nn.Conv2d(
in_channels, channels[-1], 1, stride=stride, padding=0, bias=False
)
self.proj_bn = norm_act(channels[-1])
self.proj_bn.activation = "identity"
def forward(self, x):
if hasattr(self, "proj_conv"):
residual = self.proj_conv(x)
residual = self.proj_bn(residual)
else:
residual = x
x = self.convs(x) + residual
if self.convs.bn1.activation == "leaky_relu":
return functional.leaky_relu(
x, negative_slope=self.convs.bn1.activation_param, inplace=True
)
elif self.convs.bn1.activation == "elu":
return functional.elu(
x, alpha=self.convs.bn1.activation_param, inplace=True
)
elif self.convs.bn1.activation == "identity":
return x
class IdentityResidualBlock(nn.Module):
def __init__(
self,
in_channels,
channels,
stride=1,
dilation=1,
groups=1,
norm_act=ABN,
dropout=None,
):
"""Configurable identity-mapping residual block
Parameters
----------
in_channels : int
Number of input channels.
channels : list of int
Number of channels in the internal feature maps. Can either have two or three elements: if three construct
a residual block with two `3 x 3` convolutions, otherwise construct a bottleneck block with `1 x 1`, then
`3 x 3` then `1 x 1` convolutions.
stride : int
Stride of the first `3 x 3` convolution
dilation : int
Dilation to apply to the `3 x 3` convolutions.
groups : int
Number of convolution groups. This is used to create ResNeXt-style blocks and is only compatible with
bottleneck blocks.
norm_act : callable
Function to create normalization / activation Module.
dropout: callable
Function to create Dropout Module.
"""
super(IdentityResidualBlock, self).__init__()
# Check parameters for inconsistencies
if len(channels) != 2 and len(channels) != 3:
raise ValueError("channels must contain either two or three values")
if len(channels) == 2 and groups != 1:
raise ValueError("groups > 1 are only valid if len(channels) == 3")
is_bottleneck = len(channels) == 3
need_proj_conv = stride != 1 or in_channels != channels[-1]
self.bn1 = norm_act(in_channels)
if not is_bottleneck:
layers = [
(
"conv1",
nn.Conv2d(
in_channels,
channels[0],
3,
stride=stride,
padding=dilation,
bias=False,
dilation=dilation,
),
),
("bn2", norm_act(channels[0])),
(
"conv2",
nn.Conv2d(
channels[0],
channels[1],
3,
stride=1,
padding=dilation,
bias=False,
dilation=dilation,
),
),
]
if dropout is not None:
layers = layers[0:2] + [("dropout", dropout())] + layers[2:]
else:
layers = [
(
"conv1",
nn.Conv2d(
in_channels,
channels[0],
1,
stride=stride,
padding=0,
bias=False,
),
),
("bn2", norm_act(channels[0])),
(
"conv2",
nn.Conv2d(
channels[0],
channels[1],
3,
stride=1,
padding=dilation,
bias=False,
groups=groups,
dilation=dilation,
),
),
("bn3", norm_act(channels[1])),
(
"conv3",
nn.Conv2d(
channels[1], channels[2], 1, stride=1, padding=0, bias=False
),
),
]
if dropout is not None:
layers = layers[0:4] + [("dropout", dropout())] + layers[4:]
self.convs = nn.Sequential(OrderedDict(layers))
if need_proj_conv:
self.proj_conv = nn.Conv2d(
in_channels, channels[-1], 1, stride=stride, padding=0, bias=False
)
def forward(self, x):
if hasattr(self, "proj_conv"):
bn1 = self.bn1(x)
shortcut = self.proj_conv(bn1)
else:
shortcut = x.clone()
bn1 = self.bn1(x)
out = self.convs(bn1)
out.add_(shortcut)
return out