| |
| |
| |
| |
| |
|
|
| |
|
|
|
|
| import torch |
| from pytorch3d import _C |
|
|
|
|
| def sample_pdf( |
| bins: torch.Tensor, |
| weights: torch.Tensor, |
| n_samples: int, |
| det: bool = False, |
| eps: float = 1e-5, |
| ) -> torch.Tensor: |
| """ |
| Samples probability density functions defined by bin edges `bins` and |
| the non-negative per-bin probabilities `weights`. |
| |
| Args: |
| bins: Tensor of shape `(..., n_bins+1)` denoting the edges of the sampling bins. |
| weights: Tensor of shape `(..., n_bins)` containing non-negative numbers |
| representing the probability of sampling the corresponding bin. |
| n_samples: The number of samples to draw from each set of bins. |
| det: If `False`, the sampling is random. `True` yields deterministic |
| uniformly-spaced sampling from the inverse cumulative density function. |
| eps: A constant preventing division by zero in case empty bins are present. |
| |
| Returns: |
| samples: Tensor of shape `(..., n_samples)` containing `n_samples` samples |
| drawn from each probability distribution. |
| |
| Refs: |
| [1] https://github.com/bmild/nerf/blob/55d8b00244d7b5178f4d003526ab6667683c9da9/run_nerf_helpers.py#L183 # noqa E501 |
| """ |
| if torch.is_grad_enabled() and (bins.requires_grad or weights.requires_grad): |
| raise NotImplementedError("sample_pdf differentiability.") |
| if weights.min() <= -eps: |
| raise ValueError("Negative weights provided.") |
| batch_shape = bins.shape[:-1] |
| n_bins = weights.shape[-1] |
| if n_bins + 1 != bins.shape[-1] or weights.shape[:-1] != batch_shape: |
| shapes = f"{bins.shape}{weights.shape}" |
| raise ValueError("Inconsistent shapes of bins and weights: " + shapes) |
| output_shape = batch_shape + (n_samples,) |
|
|
| if det: |
| u = torch.linspace(0.0, 1.0, n_samples, device=bins.device, dtype=torch.float32) |
| output = u.expand(output_shape).contiguous() |
| else: |
| output = torch.rand(output_shape, dtype=torch.float32, device=bins.device) |
|
|
| |
| _C.sample_pdf( |
| bins.reshape(-1, n_bins + 1), |
| weights.reshape(-1, n_bins), |
| output.reshape(-1, n_samples), |
| eps, |
| ) |
|
|
| return output |
|
|
|
|
| def sample_pdf_python( |
| bins: torch.Tensor, |
| weights: torch.Tensor, |
| N_samples: int, |
| det: bool = False, |
| eps: float = 1e-5, |
| ) -> torch.Tensor: |
| """ |
| This is a pure python implementation of the `sample_pdf` function. |
| It may be faster than sample_pdf when the number of bins is very large, |
| because it behaves as O(batchsize * [n_bins + log(n_bins) * n_samples] ) |
| whereas sample_pdf behaves as O(batchsize * n_bins * n_samples). |
| For 64 bins sample_pdf is much faster. |
| |
| Samples probability density functions defined by bin edges `bins` and |
| the non-negative per-bin probabilities `weights`. |
| |
| Note: This is a direct conversion of the TensorFlow function from the original |
| release [1] to PyTorch. It requires PyTorch 1.6 or greater due to the use of |
| torch.searchsorted. |
| |
| Args: |
| bins: Tensor of shape `(..., n_bins+1)` denoting the edges of the sampling bins. |
| weights: Tensor of shape `(..., n_bins)` containing non-negative numbers |
| representing the probability of sampling the corresponding bin. |
| N_samples: The number of samples to draw from each set of bins. |
| det: If `False`, the sampling is random. `True` yields deterministic |
| uniformly-spaced sampling from the inverse cumulative density function. |
| eps: A constant preventing division by zero in case empty bins are present. |
| |
| Returns: |
| samples: Tensor of shape `(..., N_samples)` containing `N_samples` samples |
| drawn from each probability distribution. |
| |
| Refs: |
| [1] https://github.com/bmild/nerf/blob/55d8b00244d7b5178f4d003526ab6667683c9da9/run_nerf_helpers.py#L183 # noqa E501 |
| """ |
|
|
| |
| weights = weights + eps |
| if weights.min() <= 0: |
| raise ValueError("Negative weights provided.") |
| pdf = weights / weights.sum(dim=-1, keepdim=True) |
| cdf = torch.cumsum(pdf, -1) |
| cdf = torch.cat([torch.zeros_like(cdf[..., :1]), cdf], -1) |
|
|
| |
| if det: |
| u = torch.linspace(0.0, 1.0, N_samples, device=cdf.device, dtype=cdf.dtype) |
| u = u.expand(list(cdf.shape[:-1]) + [N_samples]).contiguous() |
| else: |
| u = torch.rand( |
| list(cdf.shape[:-1]) + [N_samples], device=cdf.device, dtype=cdf.dtype |
| ) |
|
|
| |
| inds = torch.searchsorted(cdf, u, right=True) |
| |
| below = (inds - 1).clamp(0) |
| above = inds.clamp(max=cdf.shape[-1] - 1) |
| |
| |
|
|
| inds_g = torch.stack([below, above], -1).view( |
| *below.shape[:-1], below.shape[-1] * 2 |
| ) |
| cdf_g = torch.gather(cdf, -1, inds_g).view(*below.shape, 2) |
| bins_g = torch.gather(bins, -1, inds_g).view(*below.shape, 2) |
| |
| |
|
|
| denom = cdf_g[..., 1] - cdf_g[..., 0] |
| denom = torch.where(denom < eps, torch.ones_like(denom), denom) |
| t = (u - cdf_g[..., 0]) / denom |
| |
| |
|
|
| samples = bins_g[..., 0] + t * (bins_g[..., 1] - bins_g[..., 0]) |
|
|
| return samples |
|
|