pointnet中采用tensor进行点云批量归一化(pytorch)

pointnet在训练点云前会将点云进行归一化,这个归一化是在dataloader中进行,且采用numpy进行处理,源码如下:

def pc_normalize(pc):
    l = pc.shape[0]
    centroid = np.mean(pc, axis=0)#求这个batch点云的均值
    pc = pc - centroid
    m = np.max(np.sqrt(np.sum(pc**2, axis=1)))#求这个batch点云的模的最大值
    pc = pc / m
    return pc

代码和原理当然都很简单。但是当网络训练完成后进行网络推理测试时,如果不再希望把点云放在dataloader中进行前处理,而是希望直接加载到tensor中进行批量归一化。这个过程当然也十分简单,但其中有容易出错的地方,此处做一个记录,也给大家提个醒。

先举一个简单的例子,此处定义一个mini-batch的点云:

p = torch.FloatTensor( [[[0, 3, 5],[0, 6, 8]],[[0, 3, 3],[0, 4, 4]]] )#torch.size([2, 2, 3])

即此处有batch_size=2, 每个batch中有两个点,我们打算对这个mini-batch的点云进行归一化。
中心点的求法当然很简单,如下:

centroid = torch.mean(p, axis=1)
p = p-centroid.unsqueeze(1)

此处为什么要unsqueeze(1)?此时得到的centroid为

tensor([[0.0000, 4.5000, 6.5000],
        [0.0000, 7.0000, 3.5000]])

是一个torch.size([2, 3])的tensor,首先需要改变维度成torch.size([2, 1, 3]), 然后pytorch将其广播成torch.size([2, 2, 3]), 即在第一个维度上为我们复制点,每个batch中的点减去同一个中心点。

接下来就要求每个batch中点云的模了。求模的公式代码非常绕,我们先不上代码。此处我们先假设第一个batch的模为2, 第二个batch的模为3。接下来就需要在p中让每个batch的点云分别除以它们的模。我们当然想通过广播的机制去实现。我们定义一个模的tensor:

n = torch.tensor([[[2], [3]]])#torch.Size([2, 1])
print(p/n)#为了方便对比数值,此处我们先不减去中心值

结果为:

tensor([[[0.0000, 1.5000, 2.5000],
         [0.0000, 2.0000, 2.6667]],

        [[0.0000, 1.5000, 1.5000],
         [0.0000, 1.3333, 1.3333]]])

此时我们发现,与我们预期不一致。结果为对每个batch的第一个点除以2,对第2个点除以3。为什么会出现这样的结果呢?

实际上此处的n会被pytorch做如下的维度变换:

torch.Size([2,  1])-->torch.Size([1, 2,  1])-->torch.Size([2, 2, 3])

pytorch会首先在第0维度unsqueeze一个维度,再用广播机制补全tensor。为了验证,我们可以把n的维度改变一下:

n = torch.tensor([[[2], [3]]])#torch.Size([1, 2, 1])
print(p/n)#为了方便对比数值,此处我们先不减去中心值

得到的结果与前述相同。

由此,我们应该改变一下n的维度,将n改成如下维度:

n = torch.tensor([[[2]],[[3]]])#torch.Size([2, 1, 1])
print(p/n)#为了方便对比数值,此处我们先不减去中心值

此时得到的结果如下:

tensor([[[0.0000, 1.5000, 2.5000],
         [0.0000, 3.0000, 4.0000]],

        [[0.0000, 1.0000, 1.0000],
         [0.0000, 1.3333, 1.3333]]])

此时得到我们想要的结果,即对第1batch中的点除以2,对第2个batch中的点除以3。因为n中第0个维度为2,pytorch会将torch.Size([2, 1, 1])广播torch.Size([2, 2, 3])。

接下来我们将整个归一化过程完整代码写出:

p = torch.FloatTensor( [[[0, 3, 5],[0, 6, 8]],[[0, 10, 3],[0, 4, 4]]] )
centroid = torch.mean(p, axis=1)
p = p - centroid.unsqueeze(1)
m = torch.max(torch.sqrt(torch.sum(p**2, axis=2)), axis=1,keepdim=True)[0]
print(p/m.unsqueeze(1))

归一化结果为:

tensor([[[ 0.0000, -0.7071, -0.7071],
         [ 0.0000,  0.7071,  0.7071]],

        [[ 0.0000,  0.9864, -0.1644],
         [ 0.0000, -0.9864,  0.1644]]])

中心点为:

tensor([[0.0000, 4.5000, 6.5000],
        [0.0000, 7.0000, 3.5000]])

原始点减去中心点为:

p = p - centroid.unsqueeze(1)
#tensor([[[ 0.0000, -1.5000, -1.5000],
        # [ 0.0000,  1.5000,  1.5000]],

        #[[ 0.0000,  3.0000, -0.5000],
        # [ 0.0000, -3.0000,  0.5000]]])

模为:

tensor([[[2.1213]],

        [[3.0414]]])

整个过程原理很简单,但由于tensor有三个维度,在操作时容易出错,在确定代码前还是仔细调试为好,否则送入网络的数据不对很难找出问题出在哪里。各位有什么更好的方法欢迎提出。

你可能感兴趣的:(深度学习,人工智能,深度学习,python,pytorch,机器学习)