不透明度梯度计算

在上一篇文章中,我们完成了颜色贡献权重 \alpha 的计算。在这篇文章中,我们继续完成对最终高斯点不透明度的梯度计算。

一个高斯点对某个像素颜色的最终贡献权重(\alpha_n)由两部分相乘得到:

\alpha_n = o_n \cdot \exp(-\sigma_n)

这里 o_n 是高斯的基础不透明度(Opacity),\exp(-\sigma_n) 是高斯函数在二维空间上的衰减系数 。

在反向传播(Backward Pass)中,我们需要计算损失函数 \mathcal{L} 对不透明度 o_n 的梯度 \frac{\partial \mathcal{L}}{\partial o_n}

\alpha_no_n 的偏导数为:

\frac{\partial \alpha_n}{\partial o_n} = \exp(-\sigma_n)

根据链式法则,损失 \mathcal{L} 经过 \alpha_n 传递给 o_n 的梯度就是:

\frac{\partial \mathcal{L}}{\partial o_n} = \frac{\partial \mathcal{L}}{\partial \alpha_n} \cdot \frac{\partial \alpha_n}{\partial o_n} = \frac{\partial \mathcal{L}}{\partial \alpha_n} \cdot \exp(-\sigma_n)

但我们此处,为了防止不透明度在梯度下降时越界(比如变成负数或大于 1),代码对“原始不透明度” o_{\text{raw}} 施加了一个 Sigmoid 激活函数,将其映射到 [0, 1] 的物理有效区间:

o_n = \text{Sigmoid}(o_{\text{raw}}) = \frac{1}{1 + \exp(-o_{\text{raw}})}

Sigmoid 函数有一个非常优美的导数性质:

\frac{\partial o_n}{\partial o_{\text{raw}}} = o_n \cdot (1 - o_n)

因此,完整的链式法则必须将这层激活函数的导数也乘上去:

\frac{\partial \mathcal{L}}{\partial o_{\text{raw}}} = \frac{\partial \mathcal{L}}{\partial o_n} \cdot \frac{\partial o_n}{\partial o_{\text{raw}}} = \left( \frac{\partial \mathcal{L}}{\partial \alpha_n} \cdot \exp(-\sigma_n) \right) \cdot o_n(1 - o_n)


代码实现和验证

对应的代码如下所示。其中的 G 对应 \exp(-\sigma_n),这是空间高斯衰减项,我们先前已经实现过。

  1. tile_grad_opacity_raw = (tile_grad_alpha * G).sum(dim=1) * opacity_tile * (1.0 - opacity_tile)  # (N_tile,)
  2.  
  3. # 直接使用 scatter_add_ 将梯度累加回原始形状的不透明度梯度上
  4. orig_ids_tile = indices_onscreen[ids_tile]
  5. grad_opacity_raw.scatter_add_(0, orig_ids_tile, tile_grad_opacity_raw)

我们将不透明度参数加入优化。从可视化结果上看,相比于之前,背景上开始多了一些细节。