points vanished during using spconv.SparseConv3d
yejr0229 opened this issue · 4 comments
Here is the detailed error:
ValueError: Your points vanished here, this usually because you provide
conv params that may ignore some input points. Example:
spatial_shape=[8, 200, 200]
ksize=3
stride=2
padding=[0, 1, 1]
dilation=1
Coordinates=[[0, 7, 153, 142]]
these params will cause ALL points in z == 7 dropped because of padding_z=0.
enlarge your spatial shape or change your conv param to make sure
every input point has a corresponding output point.
Your Conv Params:
spatial_shape=[96, 320, 384]
ksize=[3, 3, 3]
stride=[2, 2, 2]
padding=[1, 1, 1]
dilation=[1, 1, 1]
How should I do?
I have resolved this issue recently. The example bug shown in your console is confusing because the actual vanished point is in x==7
rather than z==7
. Image that there is a 3-D space with x
ranging from [0,7] (right closed), and a 3-D convolution with ksize=3, stride=2
operates on this plane. As a result, the sampled areas are [0,2], [2,4], [4,6], and the plane x==7
is not sampled. Therefore, to make it valid, you can choose to set the padding to all 1
and ensure the dimension of spatial_shape
are odd numbers.
Furthermore, check if all the input coordinates are non-negative values. If not, add a constant bias to them.
Use the following code if necessary:
grid_coord, feat, batch_size = data
amax = torch.amax(grid_coord[:,1:], dim=0)
amin = torch.amin(grid_coord[:,1:],dim=0)
drange = amax - amin
grid_coord[:, 1:] -= amin[None, :]
drange_bias = torch.zeros_like(drange)
drange_bias[drange % 2 ==0] = 1 # ensure spatial_shape are odd numbers
drange += drange_bias
x = spconv.SparseConvTensor(
features=feat,
indices=grid_coord.contiguous(),
spatial_shape=drange,
batch_size=batch_size,
)
# convolution parameters ksize=3, stride=2, padding=1 are abosolutely valid in this case
@gitouni May i ask, how can i change the padding ? In the config or in source code of backbone ?
Thank you
@BNTzHax The padding is the convolutional parameter and set in your model. I copied a snippet of code from pointcept and show how to modify it below.
class SpUNetNoSkipBase(nn.Module):
def __init__(
self,
in_channels,
out_channels,
base_channels=32,
channels=(32, 64, 128, 128, 96, 96),
layers=(2, 3, 4, 4, 2, 2),
):
super().__init__()
assert len(layers) % 2 == 0
assert len(layers) == len(channels)
self.in_channels = in_channels
self.out_channels = out_channels
self.base_channels = base_channels
self.channels = channels
self.layers = layers
self.num_stages = len(layers) // 2
norm_fn = partial(nn.BatchNorm1d, eps=1e-3, momentum=0.01)
block = BasicBlock
self.conv_input = spconv.SparseSequential(
spconv.SubMConv3d(
in_channels,
base_channels,
kernel_size=5,
padding=1,
bias=False,
indice_key="stem",
),
norm_fn(base_channels),
nn.ReLU(),
)
enc_channels = base_channels
dec_channels = channels[-1]
self.down = nn.ModuleList()
self.up = nn.ModuleList()
self.enc = nn.ModuleList()
self.dec = nn.ModuleList()
for s in range(self.num_stages):
# encode num_stages
self.down.append(
spconv.SparseSequential(
spconv.SparseConv3d(
enc_channels,
channels[s],
kernel_size=3,
padding=1,
stride=2,
bias=False,
indice_key=f"spconv{s + 1}",
),
norm_fn(channels[s]),
nn.ReLU(),
)
)
self.enc.append(
spconv.SparseSequential(
OrderedDict(
[
# (f"block{i}", block(enc_channels, channels[s], norm_fn=norm_fn, indice_key=f"subm{s + 1}"))
# if i == 0 else
(
f"block{i}",
block(
channels[s],
channels[s],
norm_fn=norm_fn,
indice_key=f"subm{s + 1}",
),
)
for i in range(layers[s])
]
)
)
)
# decode num_stages
self.up.append(
spconv.SparseSequential(
spconv.SparseInverseConv3d(
channels[len(channels) - s - 2],
dec_channels,
kernel_size=3,
bias=False,
indice_key=f"spconv{s + 1}",
),
norm_fn(dec_channels),
nn.ReLU(),
)
)
self.dec.append(
spconv.SparseSequential(
OrderedDict(
[
(
(
f"block{i}",
block(
dec_channels,
dec_channels,
norm_fn=norm_fn,
indice_key=f"subm{s}",
),
)
if i == 0
else (
f"block{i}",
block(
dec_channels,
dec_channels,
norm_fn=norm_fn,
indice_key=f"subm{s}",
),
)
)
for i in range(layers[len(channels) - s - 1])
]
)
)
)
enc_channels = channels[s]
dec_channels = channels[len(channels) - s - 2]
self.final = (
spconv.SubMConv3d(
channels[-1], out_channels, kernel_size=1, padding=1, bias=True
)
if out_channels > 0
else spconv.Identity()
)
self.apply(self._init_weights) # apply initiation through all children
@staticmethod
def _init_weights(m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=0.02)
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, spconv.SubMConv3d):
trunc_normal_(m.weight, std=0.02)
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm1d):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
def forward(self, data):
grid_coord, feat, batch_size = data
amax = torch.amax(grid_coord[:,1:], dim=0)
amin = torch.amin(grid_coord[:,1:],dim=0)
drange = amax - amin
grid_coord[:, 1:] -= amin[None, :]
drange_bias = torch.zeros_like(drange)
drange_bias[drange % 2 ==0] = 1
drange += drange_bias
x = spconv.SparseConvTensor(
features=feat,
indices=grid_coord.contiguous(),
spatial_shape=drange,
batch_size=batch_size,
)
x = self.conv_input(x)
skips = [x]
# enc forward
for s in range(self.num_stages):
x = self.down[s](x)
x = self.enc[s](x)
skips.append(x)
x = skips.pop(-1)
# dec forward
for s in reversed(range(self.num_stages)):
x = self.up[s](x)
# skip = skips.pop(-1)
# x = x.replace_feature(torch.cat((x.features, skip.features), dim=1))
x = self.dec[s](x)
x = self.final(x)
return x.features
The convolutional parameters are set in
self.down.append(
spconv.SparseSequential(
spconv.SparseConv3d(
enc_channels,
channels[s],
kernel_size=3,
padding=1,
stride=2,
bias=False,
indice_key=f"spconv{s + 1}",
),
norm_fn(channels[s]),
nn.ReLU(),
)
)
, illustrating that he padding size=1, kernel_size=3, stride=2