3D稀疏卷积理解与使用


3D稀疏卷积理解与使用

通俗易懂的解释Sparse Convolution过程
sparse conv稀疏卷积
3d稀疏卷积——spconv源码剖析系列(1-6)
3D稀疏卷积粗略理解
spconv官方使用技巧描述
spconv官方源代码

常用的稀疏卷积分类

Layer APIs Common Usage Dense Version Note
spconv.SparseConv3d Downsample nn.Conv3d Use indice_key to save data for inverse
spconv.SubMConv3d Convolution N/A Use indice_key to save data for reuse
spconv.SparseInverseConv3d Upsample N/A Use pre-saved indice_key to upsample
spconv.SparseConvTranspose3d Upsample (for generative model) nn.ConvTranspose3d VERY SLOW and CAN’T RECOVER ORIGIN POINT CLOUD
spconv.SparseMaxPool3d Downsample nn.MaxPool3d Use indice_key to save data for inverse
spconv.SparseSequential Container nn.Sequential support layers above and nn.ReLU, nn.BatchNorm, ...
spconv.SparseGlobalMaxPool global pool N/A return dense tensor instead of SparseConvTensor
spconv.SparseGlobalAvgPool global pool N/A return dense tensor instead of SparseConvTensor

如何使用

初始化一个SparseConvTensor

  • 特征:[N, num_channels]
  • 索引:[N, (batch_idx + x + y + z)]带有批处理轴的坐标张量,坐标 xyz 顺序必须匹配空间形状和卷积参数
import spconv.pytorch as spconv
features = # 储存密集的feature [N, num_channels]
indices = # 储存每个feature对应的voxel坐标系下的坐标 [N, ndim + 1], batch 索引必须放在indices[:, 0]
spatial_shape = # 稀疏张量的空间形状, spatial_shape[i] 是indices[:, 1 + i]的形状.如spatial_shape=[60,1000,1000]代表z,y,x的空间形状,则indices[:,1:4]代表z,y,x坐标信息
batch_size = # 稀疏张量的batch_size.
x = spconv.SparseConvTensor(features, indices, spatial_shape, batch_size)
x_dense_NCHW = x.dense() # 转换稀疏张量成dense NCHW tensor.

features, indices, spatial_shape, batch_size传入之后都是可以在外部进行调用的,比如有SparseConvTensor对象sinput,使用sinput.dense(),sinput.features,sinput.indices等即可取出数据。

使用两种3D稀疏卷积

常用的两种3D稀疏卷积SparseConv3d(稀疏卷积)和SubMConv3d(子流形卷积):

SubMConv3d输入与输出feature map上不为空的位置相同,保持了稀疏性(sparity=不为空的位置/所有位置和),也就保持了计算量。而SparseConv3d会增加稀疏性,从而也就增加了计算量。但是如果只用SubMConv3d,卷积核的感受野会限制在一定范围内,所以要结合stride=2的SparseConv3d一起使用,在尽量保持稀疏性的同时增大感受野。

class SparseConv3d(SparseConvolution):
    def __init__(self,
                 in_channels, 
                 out_channels, 
                 kernel_size, 
                 stride=1, 
                 padding=0, 
                 dilation=1, 
                 groups=1, 
                 bias=True, 
                 indice_key=None, 
                 use_hash=False, 
                 algo=ops.ConvAlgo.Native):

class SubMConv3d(SparseConvolution):
    def __init__(self,
                 in_channels,
                 out_channels,
                 kernel_size,
                 stride=1,
                 padding=0,
                 dilation=1,
                 groups=1,
                 bias=True,
                 indice_key=None,
                 algo: Optional[ConvAlgo] = None,
                 fp32_accum: Optional[bool] = None,
                 name=None):

class SparseSequential(SparseModule):
    def __init__(self, *args, **kwargs):
        super(SparseSequential, self).__init__()
        if len(args) == 1 and isinstance(args[0], OrderedDict):
            for key, module in args[0].items():
                self.add_module(key, module)
        else:
            for idx, module in enumerate(args):
                self.add_module(str(idx), module)
        for name, module in kwargs.items():
            if sys.version_info < (3, 6):
                raise ValueError("kwargs only supported in py36+")
            if name in self._modules:
                raise ValueError("name exists.")
            self.add_module(name, module)
        self._sparity_dict = {}

使用spconv.SparseSequential构建3D稀疏卷积网络更便捷

类似于nn.Sequential,都是将多个模块连接起来,将上一个模块的输出作为输入传入下一个模块。且SparseSequential是可以传入torch.nn中的模块的,内部做了封装,包括取出feature,feature赋值replace_feature等等,直接将SparseConvTensor给传入torch.nn的模块是不行的
,用于实现不同的操作,使用方法如下面三种:

# Example of using Sequential
model = SparseSequential(
            SparseConv2d(1,20,5),
            nn.ReLU(),
            SparseConv2d(20,64,5),
            nn.ReLU()
        )

# Example of using Sequential with OrderedDict
model = SparseSequential(OrderedDict([
            ('conv1', SparseConv2d(1,20,5)),
            ('relu1', nn.ReLU()),
            ('conv2', SparseConv2d(20,64,5)),
            ('relu2', nn.ReLU())
        ]))

# Example of using Sequential with kwargs(python 3.6+)
model = SparseSequential(
            conv1=SparseConv2d(1,20,5),
            relu1=nn.ReLU(),
            conv2=SparseConv2d(20,64,5),
            relu2=nn.ReLU()
        )

replace_feature方法

实际上就是将一个tensor读入,替换原来的feature,比如

对于F.relu,我们要传入features,之前要这样写

x.features = F.relu(x.features)

但这样写会导致torch.fx出问题,replace_feature就是为了解决这个问题而出现的

x = x.replace_feature(F.relu(x.features))

注意该函数并非在x上进行更改了,而是创建了一个全新的SparseConvTensor,也就是说要用x去接收返回值

下面给一个示例:

def forward(self, x):
    identity = self.layers_in(x)
    output = self.layers(x)
    return output.replace_feature(F.leaky_relu(output.features + identity.features, 0.1))

文章作者: oceanechy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 oceanechy !
  目录