很好的教程,感谢作者的分享
通俗易懂的解释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 shape
is very large.
"""
self.features = features # 储存密集的feature
self.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 size
self.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.features sinput.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
是类似于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()
)