- 计算机视觉
- 深度学习
- 人工智能
- 图像处理
【免费下载链接】kornia
🐍 空间人工智能的几何计算机视觉库
本文基于 Kornia 仓库 changelog 中 migration-095 的修复记录,深入剖析Boxes3D.to_tensor与Boxes3D.get_boxes_shape重新恢复可微性(#1396)背后的数学原理、代码实现与测试验证,帮助你理解amin/amax归约在顶点并列(tie)处的次梯度行为,以及为何修复后你可以在反向传播中使用 3D 边界框导出。
修复背景:一个被误判为"梯度 bug"的 RuntimeError
Boxes3D(定义于 kornia/geometry/boxes.py)将 3D 边界框存储为 8 个顶点(x, y, z)的十六面体数据,形状为(N, 8, 3)或(B, N, 8, 3)。当调用to_tensor或get_boxes_shape时,代码会通过amin/amax将这 8 个顶点归约为其最小/最大角点(见 kornia/geometry/boxes.py 和 kornia/geometry/boxes.py)。
在修复之前,to_tensor只要检测到输入requires_grad=True就会无条件抛出RuntimeError。原因在于:轴对齐(axis-aligned)的盒子每个面都有 4 个顶点在某一轴极值处完全并列,此时amin/amax的反向传播会把梯度均匀地分配给并列的顶点(每个1/4)。这是一个合法的次梯度(subgradient),可以用于优化,但torch.autograd.gradcheck的中心差分估计在这个精确点上无法匹配它——这与任何归约函数在拐点(kink)处的非唯一性相同(如torch.max、ReLU在各自拐点处的行为)。
当时的开发者把这种 gradcheck 不匹配误认为真正的梯度 bug,于是给to_tensor加上了无条件报错的守卫,导致所有需要对Boxes3D求导的场景都被一刀切地禁止——包括那些根本不会穿过并列点求导的常见情形,例如朝向任意(非轴对齐)的盒子,或损失函数根本不触及并列分量。
修复内容:移除守卫,文档化拐点行为
migration-095 的修复包含两部分核心动作:
- 移除无条件守卫:
to_tensor不再因输入requires_grad而抛错。现在无论输入是否要求梯度,导出流程都正常执行。 - 在 docstring 中记录并列行为:在 Boxes3D.to_tensor 的 Note 段落 中,将顶点并列处的梯度行为明确文档化,而不是称之为 bug,并给出了与
torch.max、ReLU拐点行为的一致性解释。
值得注意的是,Boxes(2D 版本)使用相同的归约却从未被守卫。原因是:轴对齐矩形在每一轴极值处总是2 路并列,此时1/2的梯度分配恰好与中心差分估计数值一致——同样的拐点,只是对 gradcheck 来说"不可见"。这意味着 2D 版本一直处于可微状态,从未受影响。
次梯度原理:为什么 1/4 分配是正确的
在数学上,amin/amax是分段线性函数,在多个顶点并列处不可微(经典意义下无导数)。PyTorch 的amin/amax反向传播在并列点采用均匀分配策略:对k个并列顶点,每个分得1/k的梯度。这构成了该点的一个合法次梯度,是凸优化理论中可用的下降方向。
具体到Boxes3D.to_tensor:
- 8 个顶点归约为
(xmin, ymin, zmin, xmax, ymax, zmax)6 个值; - 对轴对齐盒子,例如
xmin由顶点 0、3、4、7 共同决定(根据测试 tests/geometry/test_boxes.py 中的注释),反向传播时这 4 个顶点各得0.25的梯度; - 该次梯度可用于优化(例如梯度下降更新顶点坐标),只是中心差分 gradcheck 无法在精确并列点验证它。
关键洞察:真实世界中"非退化"的盒子(即不在轴极值处有精确并列的盒子)根本不会落在拐点上。测试注释明确指出:"Jittering every vertex breaks the ties without changing which corner is the true min/max, so gradcheck verifies the reduction everywhere else, which is everywhere a real (non-degenerate) box lives."(见 tests/geometry/test_boxes.py)。
代码级验证:tests 中的双重保障
修复在测试层面做了双重保障:
1. 带抖动(jitter)的 gradcheck 测试
TestBoxes3D.test_gradcheck 使用一个固定模式的小抖动(jitter = arange(...) * 1e-4)打乱每个顶点坐标,破坏精确并列,从而让 gradcheck 能够验证归约在"除拐点以外的所有地方"的梯度正确性。该测试覆盖了to_tensor(三种 mode)、get_boxes_shape以及from_tensor的多个路径。
2. 固定次梯度的回归测试
test_convention_to_tensor_tie_gradient_is_an_even_subgradient_1396 是专门为 #1396 新增的回归测试:它构造一个带requires_grad=True的轴对齐盒子,调用to_tensor(mode="xyzxyz"),对输出out[0, 0](即 xmin)反向传播,然后断言并列顶点 0、3、4、7 的梯度各为0.25。该测试将"均匀次梯度"钉死为实际、正确、且如今不再被守卫的行为,未来任何改变此行为(例如放弃amin/amax实现)的改动都必须同时改动这个测试。
迁移影响:你的代码会怎样变化
如果你此前因RuntimeError而绕过了Boxes3D的求导路径,修复后你可以:
- 直接对
Boxes3D对象调用to_tensor/get_boxes_shape并参与反向传播,无需先detach(); - 对非轴对齐(旋转)盒子求导是安全的——它们没有精确并列,梯度是经典意义下准确的;
- 对轴对齐盒子求导也是合法的——梯度是次梯度意义上的有效方向,只是数值上不与中心差分一致(这正是 gradcheck 会失败的地方,也是测试中使用抖动的原因)。
仍然不可微的边界:to_mask 的守卫保留
注意修复并没有改变to_mask的行为。Boxes.to_mask(kornia/geometry/boxes.py)和Boxes3D.to_mask(kornia/geometry/boxes.py)依然会对requires_grad=True的输入抛出RuntimeError("Boxes.to_tensor isn't differentiable...",见 kornia/geometry/boxes.py)。这是因为 mask 生成涉及不可微的取整与网格比较操作,与to_tensor的纯归约语义不同。测试 tests/geometry/test_boxes.py 和 tests/geometry/test_boxes.py 分别验证了 3D 与 2D 版本的这个守卫仍然生效。
最小复现示例
以下代码演示修复后的可微行为(与回归测试思路一致):
import torch from kornia.geometry.boxes import Boxes3D # 轴对齐盒子:8 个顶点 (x, y, z) vertices = torch.tensor( [ [ [0.0, 1.0, 2.0], [10.0, 1.0, 2.0], [10.0, 21.0, 2.0], [0.0, 21.0, 2.0], [0.0, 1.0, 32.0], [10.0, 1.0, 32.0], [10.0, 21.0, 32.0], [0.0, 21.0, 32.0], ] ], requires_grad=True, ) boxes = Boxes3D(vertices) out = boxes.to_tensor(mode="xyzxyz") # (N=1, 6),不再抛 RuntimeError out[0, 0].backward() # d(xmin)/d(vertices):xmin 在顶点 0、3、4、7 处并列 print(vertices.grad[0, 0, 0].item()) # 0.25 —— 均匀次梯度运行修复前的版本,最后一步会因requires_grad=True直接抛出RuntimeError;修复后则输出0.25。
总结
migration-095 修复的核心价值在于纠正了一个过于保守的错误判断:把amin/amax在并列点处的合法次梯度误认为梯度 bug,并因此禁止了所有带梯度的Boxes3D导出。修复移除了守卫、将拐点行为写入文档、并添加了双重测试(抖动 gradcheck + 固定次梯度回归)来锁定语义。如今,Boxes3D.to_tensor和Boxes3D.get_boxes_shape恢复了完整的可微性,3D 边界框可以自由参与端到端的梯度传播,同时 2DBoxes一贯的可微行为也通过同一原理得到了印证。
- 计算机视觉
- 深度学习
- 人工智能
- 图像处理
【免费下载链接】kornia
🐍 空间人工智能的几何计算机视觉库
相关推荐
Kornia 几何可微性修复:Boxes3D.to_tensor 与 get_boxes_shape 在顶点并列处的子梯度语义
Kornia 几何可微性修复:Boxes3D.to_tensor 与 get_boxes_shape 在顶点并列处的子梯度语义 Boxes3D.to_tenso
计算机视觉人工智能深度学习图像处理LeetCode-Go 题解:528. Random Pick with Weight —— 前缀和 + 二分查找实现权重随机采样
LeetCode Go 题解:528. Random Pick with Weight —— 前缀和 + 二分查找实现权重随机采样 导读 本文围绕 LeetCo
计算机视觉人工智能深度学习图像处理Kornia 相机去畸变数值稳定性修复解析:`undistort_points_kannala_brandt` 的 float16 精度与梯度修复
Kornia 相机去畸变数值稳定性修复解析: undistort_points_kannala_brandt 的 float16 精度与梯度修复 本篇技术指南围
计算机视觉人工智能深度学习图像处理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考