| |
| |
|
|
| from typing import Literal, Protocol |
|
|
| import torch |
| from torch_scatter import scatter |
|
|
| from ...diffusion.corruption.corruption import Corruption |
| from ...diffusion.corruption.sde_lib import maybe_expand |
| from ...diffusion.d3pm.d3pm import compute_kl_reverse_process |
| from ...diffusion.data.batched_data import BatchedData |
| from ...diffusion.discrete_time import to_discrete_time |
| from ...diffusion.model_target import ModelTarget |
|
|
|
|
| def compute_noise_given_sample_and_corruption( |
| x: torch.Tensor, |
| x_noisy: torch.Tensor, |
| corruption: Corruption, |
| t: torch.Tensor, |
| batch_idx: torch.LongTensor | None, |
| batch: BatchedData, |
| ) -> torch.Tensor: |
| """ |
| Recover the (unit-Gaussian-distributed) raw noise that was used to corrupt a batch of samples. |
| We first obtain the mean and std of the noisy samples from the corruption via `t` and the clean batch. |
| Then we solve: |
| x_noisy = x_mean + noise * std w.r.t. `noise`: |
| noise = (x_noisy - x_mean) / std |
| """ |
| x_mean, std = corruption.marginal_prob( |
| x, |
| t=t, |
| batch_idx=batch_idx, |
| batch=batch, |
| ) |
| return (x_noisy - x_mean) / std |
|
|
|
|
| class FieldLoss(Protocol): |
| """Loss function for a single field. Because loss functions are defined different ways in different papers, |
| we pass loads of keyword arguments. Each loss function will only use a subset of these arguments. |
| """ |
|
|
| def __call__( |
| self, |
| *, |
| corruption: Corruption, |
| score_model_output: torch.Tensor, |
| t: torch.Tensor, |
| batch_idx: torch.LongTensor | None, |
| batch_size: int, |
| x: torch.Tensor, |
| noisy_x: torch.Tensor, |
| reduce: Literal["sum", "mean"], |
| batch: BatchedData, |
| ) -> torch.Tensor: |
| """Calculate loss per sample for a single field. Returns a loss tensor of shape (batch_size,).""" |
| pass |
|
|
|
|
| def denoising_score_matching( |
| *, |
| corruption: Corruption, |
| score_model_output: torch.Tensor, |
| t: torch.Tensor, |
| batch_idx: torch.LongTensor | None, |
| batch_size: int, |
| x: torch.Tensor, |
| noisy_x: torch.Tensor, |
| reduce: Literal["sum", "mean"], |
| batch: BatchedData, |
| model_target: ModelTarget, |
| node_is_unmasked: torch.LongTensor | None = None, |
| **_, |
| ) -> torch.Tensor: |
| """Mean square error in predicting raw noise, optionally reweighted.""" |
| assert score_model_output.ndim >= 2 |
| model_target = ModelTarget(model_target) |
|
|
| losses = get_losses( |
| corruption=corruption, |
| score_model_output=score_model_output, |
| t=t, |
| batch_idx=batch_idx, |
| x=x, |
| noisy_x=noisy_x, |
| batch=batch, |
| model_target=model_target, |
| ) |
|
|
| if node_is_unmasked is not None: |
| losses = node_is_unmasked.unsqueeze(-1) * losses |
| original_reduce = reduce |
| reduce = "sum" |
|
|
| loss_per_sample = aggregate_per_sample(losses, batch_idx, reduce=reduce, batch_size=batch_size) |
|
|
| if (node_is_unmasked is not None) and (original_reduce == "mean"): |
| nodes_per_sample = scatter(node_is_unmasked, batch_idx, dim=0, reduce="sum") |
| loss_per_sample /= nodes_per_sample |
|
|
| return loss_per_sample |
|
|
|
|
| def get_losses( |
| corruption: Corruption, |
| score_model_output: torch.Tensor, |
| t: torch.Tensor, |
| batch_idx: torch.LongTensor | None, |
| x: torch.Tensor, |
| noisy_x: torch.Tensor, |
| batch: BatchedData, |
| model_target: ModelTarget, |
| ) -> torch.Tensor: |
| if model_target == ModelTarget.score_times_std: |
| raw_noise = compute_noise_given_sample_and_corruption( |
| x=x, x_noisy=noisy_x, corruption=corruption, t=t, batch_idx=batch_idx, batch=batch |
| ) |
| target = -raw_noise |
| losses = (score_model_output - target).square() |
| else: |
| raise ValueError(f"Unknown model_target {model_target}") |
| return losses |
|
|
|
|
| def aggregate_per_sample( |
| loss_per_row: torch.Tensor, |
| batch_idx: torch.Tensor | None, |
| reduce: Literal["sum", "mean"], |
| batch_size: int, |
| ): |
| """ |
| Aggregate (potentially) batched input tensor to get a scalar for each sample in the batch. |
| E.g., (num_atoms, d1, d2, ..., dn) -> (batch_size, d1, d2, ..., dn) -> (batch_size,), |
| where the first aggregation only happens when batch_idx is provided. |
| |
| Args: |
| loss_per_row: shape (num_nodes, any_more_dims). May contain multiple nodes per sample. |
| batch_idx: shape (num_nodes,). Indicates which sample each row belongs to. If not provided, |
| then we assume the first dimension is the batch dimension. |
| reduce: determines how to aggregate over nodes within each sample. (Aggregation over samples |
| and within dims for one node is always mean.) |
| batch_size: number of samples in the batch. |
| |
| Returns: |
| Scalar for each sample, shape (batch_size,). |
| |
| """ |
| |
| loss_per_row = torch.mean(loss_per_row.reshape(loss_per_row.shape[0], -1), dim=1) |
|
|
| if batch_idx is None: |
| |
| loss_per_sample = loss_per_row |
| else: |
| |
| loss_per_sample = scatter( |
| src=loss_per_row, |
| index=batch_idx, |
| dim_size=batch_size, |
| reduce=reduce, |
| ) |
| return loss_per_sample |
|
|
|
|
| def d3pm_loss( |
| *, |
| corruption: Corruption, |
| score_model_output: torch.Tensor, |
| t: torch.Tensor, |
| batch_idx: torch.LongTensor | None, |
| batch_size: int, |
| x: torch.Tensor, |
| noisy_x: torch.Tensor, |
| reduce: Literal["sum", "mean"], |
| d3pm_hybrid_lambda: float = 0.0, |
| **_, |
| ) -> torch.Tensor: |
| assert hasattr(corruption, "N") |
| assert hasattr(corruption, "_to_zero_based") |
| assert hasattr(corruption, "d3pm") |
| t = maybe_expand(to_discrete_time(t, N=corruption.N, T=corruption.T), batch_idx) |
| metrics_dict = compute_kl_reverse_process( |
| corruption._to_zero_based(x.long()), |
| t, |
| diffusion=corruption.d3pm, |
| log_space=True, |
| denoise_fn=lambda targets, timestep: score_model_output, |
| hybrid_lambda=d3pm_hybrid_lambda, |
| x_t_plus_1=corruption._to_zero_based(noisy_x.long()), |
| ) |
| loss = metrics_dict.pop("loss") |
| loss_per_structure = aggregate_per_sample( |
| loss, batch_idx=batch_idx, reduce=reduce, batch_size=batch_size |
| ) |
| return loss_per_structure |
|
|