很好的教程,感谢作者的分享

通俗易懂的解释Sparse Convolution过程 – 知乎

一、稀疏卷积是什么,为什么提出稀疏卷积?它有什么好处?

稀疏卷积和普通卷积的区别

spconv和普通卷积没有区别,最重要的区别在于卷积的数据的存储方式和计算方法,这种计算方法可以增加计算稀疏点云的效率,其他的都是完全相同的(但SubMConv3d还是稍微有点区别的),此外spconv的3D稀疏卷积和普通卷积使用类似,唯一多了一个indice_key,这是为了在indice相同的情况下重复利用计算好的’rulebook’和’hash表’,减少计算。

三维图像太稀疏了,比如我的教室的点云其中相当一部分都是空气,真正有点云的部分连一半都不到,不像二维图像,二维图像每个位置都有像素点。

对于三维图像我们想加快卷积速度,不去计算空的部分

二、具体计算原理(只是使用也可以不了解)

还是这个教程,对应二、三章节

通俗易懂的解释Sparse Convolution过程 – 知乎

这个位置写错了,底下应该是A1A2才对,不同底色代表了两张特征图。

三、如何使用

1.首先要初始化一个SparseConvTensor

SparseConvTensor的定义

class SparseConvTensor(object):def __init__(self, features, indices, spatial_shape, batch_size, grid=None):"""Args:grid: pre-allocated grid tensor. should be used when the volume of spatial shapeis very large."""self.features = features# 储存密集的featureself.indices = indices # 储存每个feature对应的voxel坐标系下的坐标if self.indices.dtype != torch.int32:self.indices.int()self.spatial_shape = spatial_shape #存储voxel的最大边界self.batch_size = batch_size# 储存batch sizeself.indice_dict = {}# 储存坐标之间的对应关系self.grid = grid...

示例

sinput = spconv.SparseConvTensor(feat, coord.int(), spatial_shape, args.batch_size)

参数

features[n,c]每个点的特征

indices是[n,4],第一列存储的是batch编号,后面才是该点对应的voxel的[x,y,z]坐标,因为batch号也算是坐标的一种,

spatial_shape [3],最大的voxel范围,也就是最右上角voxel的右上角的棱角坐标

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

方法

def replace_feature(self, feature: torch.Tensor):->tensor

实际上就是将一个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去接收返回值

2.进行卷积

共有两种卷积(深入了解见教程)

SubMConv3d与SparseConv3d(我们主要使用SubMConv3d)

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

解释:也就是SubMConv3d+padding=1可以让每一轮的activite-site都和上一轮一样,也就是保持了稀疏性,但是这种方案会限制感受野

SparseConv3d

一种是 regular output definition,就像普通的卷积一样,只要kernel 覆盖一个 active input site,就可以计算出output site。也就是输出的有效点的数量是比输入多的,有笑点数量会越变越多。

SubMConv3d

另一个称为submanifold output definition。只有当kernel的中心覆盖一个 active input site时,卷积输出才会被计算,也就是输入和输出的有效点的数量是一致的(有效点指的是该坐标下有数据,无效点的话就是该坐标下没有数据,没有点云),通常可以搭配kernel size为3,padding=1来使用可以保证输入和输出的有效点完全一致,空间大小spatial_shape也不会改变

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):

这就是普通conv的参数了

in_channels,out_channels输入输出特征
kernel_size为卷积核大小
stride步长
indice_key

这是为了在indice相同的情况下重复利用计算好的’rulebook’和’hash表’,

减少计算

只要保证第一个SubMConv3d输入输出的激活点和第二个SubMConv3d输入输出的激活点是完全相同的(且kenel size也要相同,但一般都是相同的,可忽略),就可以放心使用indice_key

注意:

这里的卷积操作实际上是按voxel为基本单位的,也就是voxel对应了二维的像素点,stride这种参数也是以voxel为度量

实例

1.首先创建该模块,和bn,linear啥的一样定义

self.input_conv = spconv.SparseSequential( spconv.SubMConv3d(input_c, m, kernel_size=3, padding=1, bias=False, indice_key='subm1'))

2.将SparseConvTensor给传入

output = self.input_conv(input)

这里output是新的SparseConvTensor,由于用的是SubMConv3d所以除了features变为了[n,out_features]其他的都不变

感受野比较

都假设padding为1,kernel为3
首先是SparseConv3d,由前两步可知在第三步就能出现123的感受野

其次是SubMConv3d,步长为1


可以看到无论多少轮都无法将感受野扩大到A3,可见SubMConv3d的感受野是十分有限的。

SubMConv3d在sienet中用的都是1步长,spconv有用两步长的,这个应该就是和普通的卷积没有区别的。

SparseSequential

SparseSequential — mmcv 2.0.0 documentation

参考nn.Sequential

pytorch接口_wa1ttinG的博客-CSDN博客

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

>>> # using Sequential:>>> from mmcv.ops import SparseSequential>>> model = SparseSequential(SparseConv2d(1,20,5),nn.ReLU(),SparseConv2d(20,64,5),nn.ReLU())>>> # using Sequential with OrderedDict>>> model = SparseSequential(OrderedDict([('conv1', SparseConv2d(1,20,5)),('relu1', nn.ReLU()),('conv2', SparseConv2d(20,64,5)),('relu2', nn.ReLU())]))>>> # 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())