PyTorch Geometric 3D 点云实战:DGCNN 与 Point Transformer 从数据加载到调优
【免费下载链接】pytorch_geometricGraph Neural Network Library for PyTorch项目地址: https://gitcode.com/GitHub_Trending/py/pytorch_geometric
假设你手头有几十万张 3D 点云样本,要按类别识别(椅子、桌子、飞机……),还要支持逐点分割。手动处理这类数据会非常琐碎:点云没有固定的网格结构,点数不定,卷积核没法直接搬。PyTorch Geometric(PyG)就是为这类非欧几里得数据准备的图库,torch_geometric包里内置了点云数据集、建图变换、DynamicEdgeConv、PointTransformerConv等模型组件,以及面向大图的分布式数据加载。本文按"先跑通 → 按任务拆 → 调优"的顺序,带你看清这条链路。
能力地图:PyG 点云能力的四层结构
PyG 在 3D 点云方向的能力可以分成四层,从下往上依次是:
- 数据层:
ModelNet、MedShapeNet、ShapeNet、S3DIS等数据集类(torch_geometric/datasets/),统一以Data对象暴露,点坐标存在pos字段 - 变换层:
T.NormalizeScale、T.SamplePoints(1024)负责归一化和固定点数;KNNGraph、RadiusGraph把无序点云变成近邻图(见 docs/source/tutorial/point_cloud.rst) - 模型层:消息传递基类之上实现
DynamicEdgeConv(torch_geometric/nn/conv/edge_conv.py)、PointTransformerConv(torch_geometric/nn/conv/point_transformer_conv.py),配fps、knn、global_max_pool等池化算子 - 训练/部署层:
DataLoader自动拼接变长点云并维护batch向量;torch_geometric/distributed/提供多机采样加载;torch.jit支持 TorchScript 导出
点云处理的核心流程如下图所示:采样分组 → 邻域聚合 → 降采样,循环若干层后得到整云表示。DGCNN、PointNet++、Point Transformer 的差异主要体现在"邻域聚合"这一步用什么算子。
最小上手路径:ModelNet10 三步跑通
第一步,安装。除了 PyG 本体外,点云模型依赖pyg-lib(提供fps、knn、DynamicEdgeConv的 C++ 后端):
pip install torch_geometric pyg-lib若同时用torch-scatter、torch-sparse等 CUDA 扩展,需按你本地 torch 版本对应的 wheel 安装(见 docs/source/get_started/),版本不匹配是新手最常见的报错来源。
第二步,加载数据 + 写模型 + 训练,下面这个 ≤15 行的最小示例可直接复制运行(结构取自 examples/dgcnn_classification.py):
import torch, torch.nn.functional as F import torch_geometric.transforms as T from torch_geometric.datasets import ModelNet from torch_geometric.loader import DataLoader from torch_geometric.nn import MLP, DynamicEdgeConv, global_max_pool pre, tra = T.NormalizeScale(), T.SamplePoints(1024) ds = ModelNet('./data', '10', True, transform=tra, pre_transform=pre) dl = DataLoader(ds, batch_size=32, shuffle=True) class Net(torch.nn.Module): def __init__(self): super().__init__() self.conv1 = DynamicEdgeConv(MLP([6, 64, 64, 64]), k=20) self.conv2 = DynamicEdgeConv(MLP([128, 128]), k=20) self.head = MLP([128 + 64, 256, ds.num_classes], dropout=0.5, norm=None) def forward(self, data): x1 = self.conv1(data.pos, data.batch) x2 = self.conv2(x1, data.batch) return F.log_softmax( self.head(global_max_pool(torch.cat([x1, x2], dim=1), data.batch)), dim=1) model, opt = Net(), torch.optim.Adam(Net().parameters(), lr=0.001) for data in dl: opt.zero_grad(); loss = F.nll_loss(model(data), data.y); loss.backward(); opt.step()跑起来后每个 epoch 约 400 步(ModelNet10 训练集 3870 个模型、batch_size=32),单张消费级 GPU 一个 epoch 数分钟到十几分钟,量级上完全可日常迭代。
任务拆解:先想清楚要解决什么问题
点云分类:DGCNN 的"动态建边"思路
问题:DGCNN 论文的核心是每一层都在特征空间里重新找近邻,图的连接关系随训练变化。
一句话直觉:把它类比成"每次卷积都重画一张局部邻居图"——第一层用坐标当特征找 KNN,之后用学到的特征找 KNN,邻居关系跟着语义走。
关键 API:DynamicEdgeConv(nn, k, aggr='max')内部自动做 KNN 建边再消息传递,你只需给出边权 MLP(输入维度是2 * in_channels,因为两两拼接):
conv = DynamicEdgeConv(MLP([2 * 3, 64, 64, 64]), k=20, aggr='max') x1 = conv(data.pos, data.batch) # 第一层用坐标 x2 = DynamicEdgeConv(MLP([2 * 64, 128]), 20)(x1, data.batch) out = global_max_pool(x2, data.batch) # 整云 max 池化 → 每图一个向量分类头接 2~3 层MLP+F.log_softmax即可。效果:这套结构在 ModelNet10 上收敛到约 92% top-1(论文报告 92.2%),是点云分类的基线级选择;换成 ModelNet40 会明显更难。分割任务则是同一套卷积去掉全局池化、逐点接分类头,可参考 examples/dgcnn_segmentation.py(ShapeNet 上按类别训练,用 jaccard 指标评估)。
长程依赖建模与分割:Point Transformer 的"全局注意力"思路
问题:KNN 局部聚合只看得见 20 个近邻,整云级的形状关系(比如椅背和椅腿的相对布局)要靠堆很深才能传到位。
一句话直觉:PointTransformerConv把 KNN 子图当作注意力的"候选集"——每个点只对邻居做 Q/K/V 加权,等价于在局部做了一次 softmax 归一化,比 max 聚合保留了"谁贡献多少"的信息。
关键 API:PointTransformerConv需要自己用knn_graph提供边,再用fps按比例降采样,构成编码器式结构(完整实现见 examples/point_transformer_classification.py):
edge_index = knn_graph(pos, k=16, batch=batch) # KNN 子图 x = PointTransformerConv(dim, dim, pos_nn=pos_nn, attn_nn=attn_nn)(x, pos, edge_index) id = fps(pos, ratio=0.5, batch=batch) # FPS 降采样一半点 edge_index = knn_graph(pos[id], k=16, batch=batch) x = x[id]; pos, batch = pos[id], batch[id]效果:在同样的 ModelNet10 设置下,Point Transformer 论文报告约 93% top-1,略高于同设置 DGCNN;代价是注意力算子比 max 池化更吃显存,且该示例要求pyg-lib>=0.6.0(脚本开头会检查WITH_PYG_LIB)。分割场景把末层global_mean_pool换成逐点MLP输出即可。
进阶与调优:分布式、推理、内存三个痛点
- 痛点:单机装不下/跑不快大图大点云→ 解法:PyG 自带分布式采样框架
torch_geometric/distributed/,把图按节点分片存到各 worker,邻居加载时自动跨机拉取远端特征(机制示意见 docs/source/_figures/dist_sampling.png),训练入口参考 examples/multi_gpu/distributed_sampling.py,多机提交脚本在同目录.sbatch文件里。点云任务上更常见的是单机多卡,用标准DistributedDataParallel+DistributedSampler即可 - 痛点:推理延迟高→ 解法:模型走
torch.jit.script导出静态图,PyG 消息传递层均支持 TorchScript,示例见 examples/jit/ 目录;导出前先把SamplePoints之类的随机变换挪到离线预处理(pre_transform),推理期只做确定性计算 - 痛点:点云变长,显存波动大→ 解法:控制
T.SamplePoints(n)的点数上限(1024 是分类常用值,分割需要更密);k不必贪大,KNN 建边是 O(nk) 级别的开销,k=16~30已够用;批大小按单卡显存实测,点云任务的 batch 往往比 CV 小(示例里是 10~32) - 痛点:评估口径→ 解法:分类看
top-1 acc(model(data).max(dim=1)[1]),分割用jaccard_index(examples/dgcnn_segmentation.py 的做法);两者都按data.batch或data.num_nodes归一,别按 batch 数归一
避坑清单
- CUDA 扩展与 torch 版本不匹配:
torch-scatter/pyg-lib等 wheel 是按 torch 主版本编译的,报错多为undefined symbol或 import 失败。解法:对照 docs/source/get_started/ 里与你 torch 版本对应的安装源,不要混装 - 忘记传
batch向量:DynamicEdgeConv、fps、global_max_pool都要求batch参数。漏传后 KNN 会跨图建边——第 1 张点云的点被算成第 2 张的邻居,loss 正常但 acc 异常低 fps的ratio设置过激:ratio=0.25意味着点云逐层缩到 1/4,若初始只采样了 256 个点,两层之后剩 16 个点,特征严重不足。分类任务建议首层 ≥1024 点,ratio从 0.5 起步SamplePoints放在transform里导致每次访问结果不同:采样是随机的,训练时可以当增广,但做确定性评估或缓存时要用pre_transform(缓存后不再随机)- 把点云
pos当特征x用:Data.pos是坐标、Data.x才承载特征;DynamicEdgeConv的边权 MLP 输入维度按"两两拼接"算(2 * in_channels),只有一路输入是它自动复制的,别在维度上少算一倍
资源与延伸
- 点云教程(含 KNNGraph 建图与 PointNet++ 消息传递公式推导):docs/source/tutorial/point_cloud.rst
- 可直接运行的示例:examples/dgcnn_classification.py、examples/dgcnn_segmentation.py、examples/point_transformer_classification.py,点云基准在 benchmark/points/
- 相关论文:DGCNN(Dynamic Graph CNN for Learning on Point Clouds)、Point Transformer、PointNet++,均为上述示例文件头部引用的原始工作
下一步建议:先按上面的最小示例在 ModelNet10 上把 DGCNN 复现出来,记录收敛曲线;再切换到 ModelNet40 复现同一模型,随后把DynamicEdgeConv替换为PointTransformerConv(参考 point_transformer 示例的TransformerBlock),在相同 epoch、相同SamplePoints(1024)设置下对比 top-1 acc,你会直观看到长程注意力带来的提升幅度及其显存代价。
【免费下载链接】pytorch_geometricGraph Neural Network Library for PyTorch项目地址: https://gitcode.com/GitHub_Trending/py/pytorch_geometric
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考