neonforestmist's picture
Document and package context-aware inpainting v2
95bf78b verified
Raw
History Blame Contribute Delete
2.53 kB
"""Loss helpers for context-aware inpainting distillation."""
from __future__ import annotations
import torch
import torch.nn.functional as F
def min_snr_weights(
alphas_cumprod: torch.Tensor,
timesteps: torch.Tensor,
*,
gamma: float,
prediction_type: str,
) -> torch.Tensor:
"""Return the Min-SNR weighting from diffusion fine-tuning literature."""
alpha = alphas_cumprod.to(device=timesteps.device, dtype=torch.float32)[timesteps]
snr = alpha / (1.0 - alpha).clamp_min(1e-8)
clipped = torch.minimum(snr, torch.full_like(snr, gamma))
if prediction_type == "epsilon":
return clipped / snr.clamp_min(1e-8)
if prediction_type == "v_prediction":
return clipped / (snr + 1.0)
raise ValueError(f"Unsupported prediction type: {prediction_type}")
def spatial_loss_weights(
mask: torch.Tensor,
*,
context_weight: float,
masked_weight: float,
boundary_weight: float,
boundary_radius: int = 2,
) -> torch.Tensor:
"""Emphasize regenerated pixels and the context-sensitive mask boundary."""
if mask.ndim != 4 or mask.shape[1] != 1:
raise ValueError(f"Expected BCHW single-channel mask, got {tuple(mask.shape)}")
if boundary_radius < 1:
raise ValueError("boundary_radius must be positive")
binary = (mask >= 0.5).to(dtype=torch.float32)
kernel = boundary_radius * 2 + 1
dilated = F.max_pool2d(binary, kernel_size=kernel, stride=1, padding=boundary_radius)
eroded = 1.0 - F.max_pool2d(
1.0 - binary,
kernel_size=kernel,
stride=1,
padding=boundary_radius,
)
boundary = (dilated - eroded).clamp(0.0, 1.0)
weights = torch.full_like(binary, float(context_weight))
weights = weights + binary * (float(masked_weight) - float(context_weight))
weights = weights + boundary * float(boundary_weight)
return weights / weights.mean(dim=(1, 2, 3), keepdim=True).clamp_min(1e-8)
def weighted_mse(
prediction: torch.Tensor,
target: torch.Tensor,
*,
spatial_weights: torch.Tensor,
sample_weights: torch.Tensor,
) -> torch.Tensor:
"""Compute channel-averaged, spatially and per-sample weighted MSE."""
if prediction.shape != target.shape:
raise ValueError("prediction and target must have identical shapes")
per_pixel = (prediction.float() - target.float()).square().mean(dim=1, keepdim=True)
per_sample = (per_pixel * spatial_weights.float()).mean(dim=(1, 2, 3))
return (per_sample * sample_weights.float()).mean()