论文:https://arxiv.org/pdf/2212.01173
Q: 这篇论文试图解决什么问题?
这篇论文试图解决的问题是如何高效地获取多尺度上下文信息以提高实时语义分割的特征提取效率。具体来说,论文指出当前的一些实时语义分割方法直接采用多速率深度可分离卷积(depth-wise dilated convolutions)来同时从单一输入特征图中捕获多尺度上下文信息,但这种设计可能由于不合理的结构和超参数设置而导致难以获取多尺度上下文信息。
为了降低获取多尺度上下文信息的难度,论文提出了一种高效的多尺度特征提取方法,将原始的单步方法分解为两个步骤:区域残差化(Region Residualization)和语义残差化(Semantic Residualization)。在这种方法中,多速率深度可分离卷积在特征提取中扮演更简单的角色:基于第一步提供的每个区域形式的简洁特征图,在第二步中执行简单的基于语义的形态学滤波,以提高效率。
此外,论文还详细阐述了每个网络阶段的扩张率和扩张卷积的容量,以充分利用所有可实现的区域大小的特征图。相应地,论文设计了一种新颖的扩张卷积残差(Dilation-wise Residual, DWR)模块和一种简单的反卷积残差(Simple Inverted Residual, SIR)模块,分别用于高层和低层网络,并构建了一个强大的DWR分割(DWRSeg)网络。
通过在Cityscapes和CamVid数据集上的广泛实验,论文证明了该方法的有效性,实现了准确性和推理速度之间的最佳权衡,并且模型更轻量。在不使用预训练或任何训练技巧的情况下,论文的方法在单个NVIDIA GeForce GTX 1080 Ti卡上以319.5 FPS的速度在Cityscapes测试集上达到了72.7%的mIoU,超过了最新方法的速度69.5 FPS和0.8%的mIoU。
论文如何解决这个问题?
论文通过提出一种新颖的两步特征提取方法来解决实时语义分割中多尺度上下文信息获取的问题。这个方法包括以下几个关键点:
两步特征提取方法:将传统的单步多尺度特征提取分解为两个步骤,即区域残差化(Region Residualization)和语义残差化(Semantic Residualization)。
区域残差化:在第一步中,通过3x3卷积结合批量归一化(BN)和ReLU激活函数生成具有不同区域表达的简洁特征图,为第二步的形态学滤波提供基础。
语义残差化:在第二步中,对每个区域特征图使用单一期望接收场的深度可分离卷积进行语义基础的形态学滤波,避免了不必要的冗余接收场。
**Dilation-wise Residual (DWR) 模块:**为高层网络设计了一种新的模块,采用上述两步特征提取方法,并通过精心设计的扩张率和扩张卷积的容量来充分利用不同区域大小的特征图。
**Simple Inverted Residual (SIR) 模块:**为低层网络设计了一种简化的模块,从DWR模块调整而来,以满足更小接收场的需求。
网络架构:构建了一个编码器-解码器结构的网络,包括一个干线块、SIR模块和DWR模块。输出特征图进入一个简单的全卷积网络(FCN)风格的解码器,最终预测由解码器生成。
实验验证:在Cityscapes和CamVid数据集上进行广泛的实验,验证了所提方法的有效性,并与现有方法进行了比较,展示了在准确性和推理速度之间取得的先进权衡。
通过这些方法,论文成功地提高了实时语义分割中多尺度上下文信息的获取效率,并且在不使用预训练或任何训练技巧的情况下,实现了轻量级模型和高性能的结合。
DWR module
DWR block
import torch
import torch.nn as nn
import torch.nn.functional as F
# Assuming Conv is a custom class, we'll define a simple placeholder for it.
# This needs to be replaced with actual implementations as per user's setup.
class Conv(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, s=1, d=1, act=True, padding=None):
super().__init__()
if padding is None:
padding = ((kernel_size - 1) // 2) * d # Adjust padding based on dilation
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=s, padding=padding, dilation=d)
self.act = nn.ReLU() if act else nn.Identity()
def forward(self, x):
return self.act(self.conv(x))
# Setting random seed for reproducibility
torch.manual_seed(42)
class DWR(nn.Module):
def __init__(self, dim, act=True) -> None:
super().__init__()
self.conv_3x3 = Conv(dim, dim // 2, 3, act=act)
self.conv_3x3_d1 = Conv(dim // 2, dim, 3, d=1, act=act)
self.conv_3x3_d3 = Conv(dim // 2, dim // 2, 3, d=3, act=act)
self.conv_3x3_d5 = Conv(dim // 2, dim // 2, 3, d=5, act=act)
self.conv_1x1 = Conv(dim * 2, dim, 1, act=act)
def forward(self, x):
conv_3x3 = self.conv_3x3(x)
x1 = self.conv_3x3_d1(conv_3x3)
x2 = self.conv_3x3_d3(conv_3x3)
x3 = self.conv_3x3_d5(conv_3x3)
x_out = torch.cat([x1, x2, x3], dim=1)
return self.conv_1x1(x_out) + x
class DWRC3(nn.Module):
def __init__(self, c1, c2, n=3, e=1.0, act='relu'):
super().__init__()
c_ = int(c2 * e)
self.conv_s2 = Conv(c1, c1, 3, s=2, act=act)
self.cv1 = Conv(c1, c_, 1, act=act)
self.cv2 = Conv(c1, c_, 1, act=act)
self.m = nn.Sequential(*[DWR(c_, act) for _ in range(n)])
self.cv3 = Conv(c_, c2, 1, act=act) if c_ != c2 else nn.Identity()
def forward(self, x):
x = self.conv_s2(x)
return self.cv3(self.m(self.cv1(x)) + self.cv2(x))
def main():
# Create model instance
dwrc3_model = DWRC3(c1=64, c2=128, n=3, e=1.0, act='relu')
# Create a random input tensor
x = torch.randn(1, 64, 224, 224)
# Run model
output = dwrc3_model(x)
# Print output shape
print("Output shape:", output.shape)
if __name__ == "__main__":
main()
打印结果图
DWRC3(
(conv_s2): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(act): ReLU()
)
(cv1): Conv(
(conv): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1))
(act): ReLU()
)
(cv2): Conv(
(conv): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1))
(act): ReLU()
)
(m): Sequential(
(0): DWR(
(conv_3x3): Conv(
(conv): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(act): ReLU()
)
(conv_3x3_d1): Conv(
(conv): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(act): ReLU()
)
(conv_3x3_d3): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(3, 3), dilation=(3, 3))
(act): ReLU()
)
(conv_3x3_d5): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(5, 5), dilation=(5, 5))
(act): ReLU()
)
(conv_1x1): Conv(
(conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
(act): ReLU()
)
)
(1): DWR(
(conv_3x3): Conv(
(conv): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(act): ReLU()
)
(conv_3x3_d1): Conv(
(conv): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(act): ReLU()
)
(conv_3x3_d3): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(3, 3), dilation=(3, 3))
(act): ReLU()
)
(conv_3x3_d5): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(5, 5), dilation=(5, 5))
(act): ReLU()
)
(conv_1x1): Conv(
(conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
(act): ReLU()
)
)
(2): DWR(
(conv_3x3): Conv(
(conv): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(act): ReLU()
)
(conv_3x3_d1): Conv(
(conv): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(act): ReLU()
)
(conv_3x3_d3): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(3, 3), dilation=(3, 3))
(act): ReLU()
)
(conv_3x3_d5): Conv(
(conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(5, 5), dilation=(5, 5))
(act): ReLU()
)
(conv_1x1): Conv(
(conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
(act): ReLU()
)
)
)
(cv3): Identity()
)
Output shape: torch.Size([1, 128, 112, 112])