十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PointNet与PointNet++:点云深度学习的核心原理与PyTorch实战

PointNet与PointNet++:点云深度学习的核心原理与PyTorch实战 做3D计算机视觉的人基本都绕不开PointNet这个名字。2017年Charles Qi等人提出PointNet的时候很多人第一次意识到原来无序的、没有网格结构的点云数据也能直接端到端地喂给神经网络去做分类、分割、物体检测。几年过去了PointNet和它的升级版PointNet依然是点云深度学习绕不开的两个基石很多后来的工作比如Point Transformer、PointMLP、各种3D检测框架底层多多少少都留着它们的影子。这篇内容我会把这两篇论文的理论和PyTorch实现放在一起讲重点说明几个关键设计“为什么要这么做”然后用一套能跑起来的分类代码把训练链路串起来。不管你是刚接触3D视觉的学生还是准备把点云分类、分割接到自己项目里的工程同学这篇内容都值得花半小时读一遍。环境搭建、数据集准备、代码结构和常见坑我都会按实操逻辑写清楚你可以把它当成一份能直接照着做的复现笔记。1. 先搞清楚为什么点云让深度学习这么头疼1.1 点云和图像的本质差异图像是规整的网格结构每个像素有固定的坐标位置相邻像素有明确的邻域关系。卷积神经网络能直接处理图像靠的就是这种排列规整、局部相关的特性。卷积核就像一个小窗口在图像上滑过天然就能捕捉局部的纹理和形状信息。点云完全不是这样。点云本质上是三维空间中的一组坐标点集合它没有固定的拓扑结构。同样一个物体激光雷达扫描出来的点和三维重建出来的点密度、顺序、间距都可能完全不同。更麻烦的是点云是无序的——你把一堆点的顺序打乱它们代表的还是同一个物体点云还是稀疏且不均匀的——同一个物体表面有的区域可能密密麻麻几千个点有的区域可能只有几十个点。这就导致一个直接问题不能把点云像图像那样直接拉直输入全连接网络也不能直接把2D卷积搬过来用。早期有人把点云转成体素栅格再去跑3D CNN但体素化会带来严重的量化误差且空间复杂度是立方的分辨率一高内存直接爆炸。也有人把点云渲染成多视角图再用2D CNN处理但视角选择本身就是一个玄学问题而且渲染过程会丢失三维几何信息。1.2 PointNet的破局思路我不管你顺序怎么变结果必须一样PointNet的核心贡献是第一次用非常简洁的方式解决了点云这两个本质难题。它没有去尝试把点云变成规整的结构而是直接处理原始点云。网络先对每个点分别做MLP变换把每个点从三维空间映射到高维特征空间然后通过一个Max Pooling操作把所有点的特征聚合成一个全局特征向量。关键就在这个Max Pooling因为max操作本身不依赖顺序不管你把点云里的点怎么打乱最终取到的每个特征维度的最大值都相同。这就天然满足了点云的无序性要求。整个网络没有任何卷积操作纯粹靠共享权重的逐点MLP加全局池化结构极其简单但效果在当时已经远超传统手工特征方法。后续的PointNet则解决了PointNet的另一个短板它缺乏对不同尺度局部结构的捕捉能力。PointNet只生成一个全局特征相当于对整个场景“一句总结”但很多任务比如室内场景分割需要知道每个点周围的细粒度几何关系。PointNet通过分层采样和局部邻域聚合把PointNet的“全局视野”扩展成了“由局部到全局的层次结构”效果提升非常明显。2. PointNet理论拆解三个关键设计你要弄懂2.1 置换不变性N个点的顺序怎么变结果都一样要理解PointNet第一个绕不开的概念就是置换不变性。数学上假设输入是N×3的点云矩阵网络函数是f那么对于任意置换操作π都必须满足f{x_1, x_2, ..., x_N} f{x_{π1}, x_{π2}, ..., x_{πN}}PointNet实现这个性质的方式很朴素先用一个共享的MLP对N个点分别做特征提取也就是把每个点独立地从3维映射到64维、128维再到1024维然后对高维特征做Max Pooling得到1024维的全局特征。因为max操作不会受顺序影响所以整体网络天然满足置换不变性。你可能会问为什么是Max Pooling而不是Average Pooling或者Sum Pooling平均池化和求和池化同样满足置换不变性但在实际点云任务中Max Pooling的效果普遍更好。原因在于点云中有大量背景点、噪声点和稀疏区域点它们往往贡献了大量“平庸”的特征。平均池化会把有效特征淹没在噪声平均里而Max Pooling只关注每个特征维度上最显著的那个点相当于自动学会了“哪个点在这个特征维度上最有话语权”这在几何形状识别上更接近人类的直觉一个物体的特征常常是由最突出的几何结构决定的。2.2 T-Net对齐网络让点云在空间变换下保持鲁棒PointNet的作者还设计了一个特别有巧思的模块T-Net也就是空间变换网络。它做的工作是根据输入点云动态预测一个3×3的旋转矩阵把输入点云做一个坐标变换让整个网络对物体的空间姿态更鲁棒。实现上T-Net本身也是一个微型PointNet——它对输入点云逐点MLP提取特征做全局池化再通过全连接层输出一个9维向量reshape成3×3矩阵。论文里还提到了一个细节为了让这个矩阵接近正交矩阵训练时会对变换矩阵增加一个正则化约束也就是让预测的矩阵A尽量满足 A·A^T ≈ I这样能保证坐标变换不会过度拉伸或者扭曲。除了输入点云的对齐PointNet还在特征提取到64维之后又加了一个特征空间的对齐网络输出一个64×64的对齐矩阵。这个特征对齐的T-Net可以保留输入变换的内在结构效果上会提升一点精度但代价是额外增加了不少参数量。在工程复现时如果你想压缩模型体积可以先去掉特征变换那个T-Net分类精度通常只掉零点几个点。2.3 PointNet的本质缺陷全局特征丢失了局部结构这里必须说清楚PointNet的致命伤它生成全局特征的方式天然忽略了局部几何信息。想象一下一个场景里有椅子和桌子两种物体的点云混合在一起。PointNet的Max Pooling会把整个场景压缩成一个全局特征向量但这个特征向量只保留了“整体最显著的响应”完全没有不同区域之间的空间关联。在做语义分割时PointNet的做法是把全局特征和每个点的逐点特征拼接在一起再通过MLP预测每个点的类别。这意味着每个点被分类时参考的都是同一个“全局上下文”很难区分出“这个点属于椅子靠背还是桌面”。一批针对这个问题的改进工作想做的是让每个点既能看到自己的局部邻域又能感知更大范围的上下文。PointNet就是其中最经典的一个它的思路非常符合直觉像卷积神经网络一样逐层地聚合局部信息从近邻到远邻从局部到全局逐层抽象出不同尺度的特征。3. PointNet把局部结构一点点“抽”出来3.1 Set AbstractionPointNet的层次化改造PointNet的核心结构叫作Set Abstraction集合抽象层它把PointNet的网络结构嵌入到了一个分层的框架里。每一层Set Abstraction做三件事采样、分组、局部PointNet聚合。每一层开始时先用最远点采样Farthest Point Sampling, FPS从输入点云中选出一部分中心点。然后以这些中心点为圆心在半径r内找到附近的邻居点组成一个个局部点组。每个局部点组再输入一个共享的微型PointNet得到这个区域的特征向量。这样每一层输出的点数量更少但每个点携带的特征抽象层级更高感受野也更大。经过两到三层Set Abstraction后点云会被抽象成极少量的点每个点都代表了整个点云中某一片区域的高维特征。分类任务中最后再对这些点做一次全局池化输出全局特征分割任务中则需要把逐层抽象的特征再传回原始点这个回传过程通常用最近邻插值加跳跃连接实现。3.2 最远点采样FPS为什么不用随机采样最远点采样是PointNet里一个看似不起眼但非常关键的细节。它的算法逻辑很简单先随机选一个点然后每次选择“离已有采样点集合最远”的点加入采样集重复直到采样数量满足要求。这样做的目的是让采样出来的中心点尽量均匀地覆盖整个形状保证不同局部区域的几何结构都有机会被覆盖到。随机采样很容易出现某一块区域点特别密、另一块区域完全没有采样点的情况这样抽象出来的局部特征会非常不均匀。而网格均匀采样虽然覆盖均匀但依赖点云的全局坐标范围对密度变化和数据增强不够鲁棒。我在实际实现FPS时遇到过性能问题。朴素的FPS实现是ON^2的N是输入点数量。在分类任务中第一层输入是2048个点速度还能接受但如果你的输入是几万点的室外点云FPS会非常慢。工程上常见的优化手段是用并行计算的方式做批量采样或者对输入点先做一次体素粗采样降低规模。由于PyTorch里循环访问GPU非常低效很多开源实现干脆在CPU上用C实现FPS再和GPU上的后续计算衔接这个方案兼容性最好速度也快。3.3 球查询Ball Query分组半径定多大选好中心点之后要把每个中心点周围的邻居点“圈”成一个组。PointNet提供了两种分组方式K近邻kNN和球查询Ball Query。论文和官方代码默认使用的是球查询因为它能保证一个固定的空间尺度不受点云密度变化影响。球查询的逻辑是给定中心点和一个半径r找出所有在半径范围内的点如果点数超过K只取最近的K个如果点数不足K就用最近的点重复采样补充到K个。实际复现时要注意半径的选择对不同尺度物体的效果影响很大。室内场景中坐标尺度通常在几米到几十米第一层半径取0.2到0.4比较合适小物体分类中ModelNet40这类归一化到单位立方体的点云第一层半径取0.1到0.2。如果你在用自己的数据调试策略是可先对点云做坐标归一化让最大坐标范围是1然后半径从0.05开始试观察不同层的有效覆盖区域是否约等于物体上一片有辨识度的几何结构。3.4 MSG和MRG应对密度不均匀的锦囊PointNet论文还专门考虑了真实点云密度不均匀的问题提出了两种变体多尺度分组Multi-Scale Grouping, MSG和多分辨率分组Multi-Resolution Grouping, MRG。MSG的做法是在每个中心点处同时使用多个不同半径做球查询每个半径的邻居点组分别输入一个微型PointNet然后把多尺度的特征拼接在一起。这样如果一个区域点很密小半径的局部细节特征会占据主导如果区域很稀疏大半径的上下文特征能补足信息。代价是计算量成倍增加训练和推理都会慢不少。MRG则是把当前层的特征和上一层传给当前点的特征拼接起来用“前一层的信息当前层的局部信息”来弥补密度变化带来的偏差。MRG比MSG省计算量但对实现细节要求更高代码里还需要处理上一层采样点的索引映射关系。我在分类任务里一般先用单尺度的网络跑通流程确认一切正常后再考虑升级到MSG。想直接复现论文最佳效果的话用PyTorch实现时可以按官方的pointnet2_cls_msg代码来组织它已经帮你把这些细节处理好了。4. 数据集准备与预处理ModelNet40实战4.1 ModelNet40是什么格式做点云分类复现最常用的数据集就是ModelNet40。它包含40个类别的CAD模型每个模型是一个以off或obj格式存储的三维网格官方还提供了采样好的点云版本数据格式是H5文件。H5文件用h5py就能读取。数据结构很简单训练集和测试集各有一个h5文件列表每个样本包含两个字段data是N×3的点云坐标label是对应的类别id。论文默认的做法是把每个网格模型表面均匀采样1024或2048个点采样后的点云用作输入数据。第一次跑这个数据集时建议直接下载官方已经做好的H5版本省去自己从off文件采样点云的时间。如果你的网络能直接读H5先把数据完整加载进内存因为ModelNet40点云文件总大小只有几百MB内存扛得住。后续如果要扩展到更大规模的数据集比如ShapeNet再考虑懒加载策略。4.2 数据预处理和增广归一化、抖动和随机丢失读入点云后第一步是归一化。论文和所有主流复现里都通行的做法是把所有点的坐标减去中心点再除以整个点云的最大坐标绝对值让点云大致落在一个以原点为中心、边长为2的立方体里。归一化之后是数据增广。PointNet原文里使用的增广方式包括对每个点坐标加上随机的小高斯噪声这样相当于对物体表面做了轻微的形变以及随机丢失一部分点模拟真实传感器遮挡的情况。这两步增广对模型泛化能力的提升非常关键尤其在数据量不大的ModelNet40上如果完全不做增广测试精度很可能会掉一两个百分点。有个小细节我踩过坑增广生成的随机噪声不要每次都重置随机种子否则每个epoch看到的数据完全一样增广等于没做。另外在测试时不要加任何增广只做归一化。这是老生常谈但复现点云模型时真的常有人把增广写到验证逻辑里精度看着死活上不去。4.3 Dataset类怎么写一份可以直接用的DataLoader骨架用PyTorch写ModelNet40的数据加载器并不复杂我直接给出一份可以参考的骨架代码。核心是继承torch.utils.data.Dataset在getitem里完成读取、归一化和增广然后配合DataLoader使用。import h5py import numpy as np import torch from torch.utils.data import Dataset class ModelNet40Dataset(Dataset): def __init__(self, h5_path, num_points1024, trainTrue, transformTrue): super().__init__() # 这里按H5文件的组织方式读取通常是多个h5文件名拼接 self.num_points num_points self.train train self.transform transform all_data [] all_label [] for file in h5_path: with h5py.File(file, r) as f: data f[data][:] label f[label][:] all_data.append(data) all_label.append(label) self.data np.concatenate(all_data, axis0).astype(np.float32) self.label np.concatenate(all_label, axis0).astype(np.int64) self.label self.label.squeeze() def __len__(self): return len(self.data) def __getitem__(self, idx): points self.data[idx] # 中心化并缩放到单位立方体附近 centroid np.mean(points, axis0, keepdimsTrue) points points - centroid max_dist np.max(np.sqrt(np.sum(points ** 2, axis1))) points points / max_dist # 训练时随机选择固定数量的点 if self.num_points len(points): choice np.random.choice(len(points), self.num_points, replaceFalse) points points[choice] # 数据增广 if self.train and self.transform: points points 0.001 * np.random.randn(*points.shape) # 高斯抖动 # 随机丢失若干点 drop_idx np.random.choice(len(points), int(len(points) * 0.1), replaceFalse) points[drop_idx] points[0] # 用第一个点填充保持点数不变 return torch.from_numpy(points), int(self.label[idx])这里有一个细节值得说明随机丢失点之后为什么要用第一个点填充而不是直接删除因为PyTorch的DataLoader要求一个batch内的所有点云张量shape必须完全一致取2048个点就是2048个点不能删除。用重复点填充会引入一点噪声但在增广场景下反而有利于模型对遮挡的鲁棒性。如果你想更规范也可以改写transform逻辑直接删除点后再补采样效果差别不大。5. PyTorch环境搭建把坑提前踩平5.1 用Anaconda隔离环境是第一原则点云相关的研究代码依赖特别杂PyTorch、h5py、tqdm、open3d这些库之间有时会有版本冲突。如果直接装在系统Python里今天升级这个包、明天装那个包很容易把环境搞坏。最佳实践是第一步就用Anaconda创建一个独立的虚拟环境。conda create -n pointnet python3.9 conda activate pointnet我习惯把环境名起得具体一点比如pointnet这样在一个机器上同时维护多个项目环境时不会搞混。Python版本选3.9或者3.10都是稳妥的选择再高版本有时会遇到一些老代码的兼容问题。另外提醒一下启用了conda环境之后后面所有的pip install都要在这个环境下执行确认方法是在终端里看到命令行前有pointnet字样。5.2 PyTorch选型先搞清楚你要不要GPU安装PyTorch之前先明确两件事机器上有没有可用的NVIDIA GPU以及驱动能支持的CUDA版本。这些信息可以用两条命令查nvidia-smi python -c import torch; print(torch.__version__)如果你是纯学习或者跑的是ModelNet40这种小数据集CPU版本也能跑起来只是训练速度会慢不少。PointNet的模型很小参数量只有几MBCPU上用一万个点做推理也只是几百毫秒的水平。但如果你想跑PointNet的MSG版本或者输入点云数量超过4096强烈建议用GPU版本否则训练一个epoch可能要等很久。安装命令方面最稳妥的方式是去PyTorch官网的安装页面选好你的平台、包管理器、CUDA版本官网会给出对应的conda或pip命令。这里我要单独说一个很多人都会遇到的问题从官方源下载PyTorch包经常很慢甚至超时。这种情况不用慌解决办法是切换包管理器源。conda用户可以通过修改conda配置加入第三方镜像加速pip用户则可以在命令后面加上-i参数指定镜像源。这种方法在很多社区里被反复推荐唯一要注意的是确认镜像源支持你需要的CUDA版本否则可能遇到找不到对应wheel包的情况。依赖库方面除了torch和torchvision建议顺手装好numpy、h5py、tqdm、tensorboard。这些在后面的训练脚本里基本都会用到。5.3 常见环境坑c10.dll报错和路径冲突刚开始用Anaconda装PyTorch的人大概率会遇到一个让人崩溃的报错OSError: [WinError 1114] 动态链接库(DLL)初始化例程失败。 Error loading C:\Users\xxx\.conda\envs\pointnet\lib\site-packages\torch\lib\c10.dll or one of its dependencies.这个报错看起来像是torch文件损坏但绝大多数情况下是因为缺少VC运行库或者系统里同时存在多个不兼容的运行时版本。解决办法是安装对应版本的Visual C Redistributable安装后重启终端再试。如果你在Windows上同时装了Anaconda和独立的Python发行版还可能因为PATH里的dll路径冲突导致torch加载失败这时候可以检查一下系统环境变量里是否混入了不同版本的依赖库把无关的Python路径从PATH中临时移除再测试。另一个高频问题是用conda安装PyTorch后在jupyter或IDE里import torch时提示找不到模块。这种情况通常是IDE选择的解释器还是系统Python而不是conda环境里的Python。解决思路很简单在IDE的项目设置里把Python解释器切换到pointnet环境的路径一般位于conda安装目录下的envs/pointnet/python.exe。我在vscode里一般直接用Python: Select Interpreter命令一键切换省心。6. PointNet/PointNet代码实现与训练细节6.1 训练脚本应该怎么组织一个可复现的PointNet分类项目代码结构不用太复杂但目录一定要清晰。我习惯这样组织data/存放ModelNet40的数据文件models/模型定义pointnet.py、pointnet2.py等utils/数据加载、可视化、指标计算的工具函数train.py / test.py训练和验证脚本训练脚本的核心循环和其他深度学习项目没有本质区别加载数据、前向传播、计算交叉熵损失、反向传播、更新参数、周期性验证。唯一需要多留意的是点云数据的维度。输入张量shape是[B, N, 3]B是batch sizeN是点云点数。很多新手第一次写PointNet都容易把输入的N和通道数搞混结果在MLP那一层直接报维度错误。为了让损失下降更稳我习惯在训练初期使用较小的学习率比如1e-3配合余弦退火或者StepLR调度器。batch size方面如果显存有限可以先从8开始跑模型很小大部分显卡都能跑到32甚至64。6.2 PointNet分类主干逐点MLP加全局池化PointNet分类网络实现起来非常简洁核心代码去掉T-Net也就二三十行。下面我贴一个结构清晰的简化版帮你建立整体印象import torch import torch.nn as nn def mlp_channels(in_channels, out_channels): layers [] for i in range(len(out_channels)): layers.append(nn.Conv1d(in_channels, out_channels[i], 1)) layers.append(nn.BatchNorm1d(out_channels[i])) layers.append(nn.ReLU()) in_channels out_channels[i] return nn.Sequential(*layers) class PointNetClassifier(nn.Module): def __init__(self, num_classes40): super().__init__() self.input_transform TNet(3) # 输入点云对齐 self.mlp1 mlp_channels(3, [64, 64]) self.feature_transform TNet(64) # 特征空间对齐 self.mlp2 mlp_channels(64, [64, 128, 1024]) self.classifier nn.Sequential( nn.Linear(1024, 512), nn.BatchNorm1d(512), nn.ReLU(), nn.Dropout(0.4), # 分类头最好加Dropout nn.Linear(512, 256), nn.BatchNorm1d(256), nn.ReLU(), nn.Dropout(0.4), nn.Linear(256, num_classes) ) def forward(self, points): # points: [B, N, 3] points points.transpose(2, 1) # 变成 [B, 3, N] points self.input_transform(points) x self.mlp1(points) x self.feature_transform(x) x self.mlp2(x) # [B, 1024, N] x torch.max(x, dim2, keepdimFalse)[0] # 全局最大池化 x x.view(x.size(0), -1) return self.classifier(x)注意这里的Conv1dkernel_size固定为1本质上就是逐点共享的MLP。用Conv1d实现不是因为想用卷积而是因为Conv1d在PyTorch里对B×C×N这种格式特别高效比循环逐点算MLP快得多。分类头的Dropout一定要加。点云分类任务因为输入点云是稀疏的很容易过拟合训练集PyTorch官方复现里默认在分类头加了两个0.4的Dropout这个参数基本不用再调。我之前漏掉了input transform的正则化损失结果分类精度一直卡在85%左右上不去。加上T-Net正交正则化后涨了一个多点。6.3 PointNet核心算子FPS、Ball Query和局部聚合PointNet的代码比PointNet要复杂一些核心难点在采样、分组和特征聚合。PyTorch原生API里没有直接提供最远点采样和球查询所以复现时要么自己实现要么直接用PointNet官方开源代码里的pointnet2_utils.py。下面是我认为比较容易理解的FPS实现思路用多次矩阵运算找出最远点代码不追求极致性能但逻辑很清楚def farthest_point_sample(xyz, npoint): xyz: [B, N, 3], 返回中心点索引 [B, npoint] B, N, C xyz.shape device xyz.device centroids torch.zeros(B, npoint, dtypetorch.long, devicedevice) distance torch.ones(B, N, devicedevice) * 1e10 farthest torch.randint(0, N, (B,), dtypetorch.long, devicedevice) for i in range(npoint): centroids[:, i] farthest centroid_xyz xyz[torch.arange(B), farthest].unsqueeze(1) dist torch.sum((xyz - centroid_xyz) ** 2, dim-1) mask dist distance distance[mask] dist[mask] farthest torch.max(distance, dim1)[1] return centroids这个实现里最核心的是每次选出“当前距离集合中距离最远的点作为下一个中心点”然后更新所有点到当前中心点的最小距离。因为内部有一个npoint次循环当npoint很大时会比较慢这也是为什么很多工程实现会单独写C或者CUDA算子。好在PyTorch官方复现里提供了专门的扩展模块在编译好后可以直接用速度和稳定性都比纯Python版好很多。球查询的核心逻辑也是类似的对每个中心点计算它与其他所有点的距离选出半径内的点再按距离排序取前K个。遇到距离不足K的情况用最近点补足。官方代码用CUDA实现了这个逻辑纯PyTorch版本也能跑但速度大概会慢三到五倍。如果只是在ModelNet40上做实验纯PyTorch版本完全可以接受数据集只有2048个点batch size设成16也没什么压力。6.4 训练参数和收敛策略PointNet和PointNet在ModelNet40分类任务上的超参数官方论文和社区复现都已经验证得很成熟了我直接给出一份参考配置输入点数1024或2048。1024点时训练速度更快2048点通常能涨0.5到1个点的精度显存允许就选2048。优化器Adam初始学习率0.001权重衰减1e-4。学习率调度StepLR每20个epoch衰减0.5总共训练100到200个epoch。Batch sizeGPU上建议16或32CPU训练建议8以下。损失函数标准交叉熵。用这套配置PointNet在ModelNet40上能跑到约89%的分类准确率PointNet的单尺度版本大约能到92%MSG版本接近93%。如果你发现训练很久但精度比这个数字差很多大概率不是网络结构问题而是数据预处理或者增广环节写错了。训练过程里可以多看一眼loss曲线。收敛正常的PointNet训练曲线前10个epoch损失会从2.5左右快速掉到1.0附近之后进入平稳下降阶段。如果loss在第3个epoch还基本不动优先排查学习率和数据归一化。另外把TensorBoard加上每个epoch记录一下训练和验证的loss与准确率省去很多盲调的时间。7. 常见问题与排查技巧实录7.1 环境类报错速查表Windows上复现PointNet遇到最多的就是环境问题。我把自己遇到过的和身边同学遇到过的典型报错整理成了一个排查表你可以直接对着查报错现象可能原因解决办法ImportError: DLL load failed缺少VC运行库或dll依赖冲突安装对应版本VC Redistributable检查系统PATH中是否有不同版本的Python依赖混入CUDA out of memorybatch size过大或点云点数过多降低batch size或把输入点数从2048减到1024也可以开启梯度累积RuntimeError: shape mismatch点云张量维度打错了确认输入是[B, N, 3]输入MLP前要transpose成[B, C, N]h5py无法读取数据文件h5py版本太老pip install --upgrade h5py多个模型文件互相影响工作目录混乱严格按目录结构组织代码模型文件统一放在models/下7.2 训练不收敛先检查数据别急着调模型训练不收敛或者loss跳来跳去绝大多数时候问题不在网络结构而在数据。第一个要检查的是标签和类别数是否对齐。ModelNet40的label是从0到39的整数如果你的分类头输出维度是40输入数据里却混入了不合法标签loss就会直接报错或者诡异震荡。第二个要检查的是归一化是否正确。有时候点云里混入了NaN值einsum和max操作一碰到NaN梯度就全乱了这种情况基本只能通过数据清洗解决。第三个是增广强度是否过大。太多的高斯噪声会让模型学到“去噪”而不是“分类”增广力度要往小调。7.3 如果是分割任务PointNet的插值是怎么接的PointNet做分割任务时在最后一层Set Abstraction之后需要把高层稀疏点的特征传回原始密集点。官方实现用的是距离加权最近邻插值对每个原始点找到它在高层点云中的三个最近邻点按距离的倒数加权求和得到插值特征然后再与编码阶段对应层的特征进行跳跃拼接。这个插值过程必须保证高层点的坐标和原始点的坐标在同一个坐标系下。如果中间做了任何坐标缩放或平移记得要同步处理。我在自己做室内场景分割时因为预处理里对整组点云做了中心化忘记对高层中心点也做同样操作导致插值结果完全错乱分割精度惨不忍睹排查了好久才发现是坐标没有对齐。最后再分享一个小技巧如果你想快速验证自己写的PointNet代码是否基本正确可以不用急着下载完整数据集先随机生成一小批单位立方体里的点云给它随机打上类别标签训练几个step看看loss是否能下降、模型能否在训练集上过拟合。能过拟合说明网络结构没有大问题再换真实数据精调也不迟。从PointNet一路看到这里你基本已经掌握了点云深度学习的第一块敲门砖剩下的就是在自己的数据上不断试错把细节磨合出来。
返回列表