Rate this Page

Source code for torchrl.objectives.diffusion_bc

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import annotations

from dataclasses import dataclass

import torch
from tensordict import TensorDict, TensorDictBase, TensorDictParams
from tensordict.nn import TensorDictModule
from tensordict.utils import NestedKey

from torchrl.objectives.common import LossModule


[docs] class DiffusionBCLoss(LossModule): """Behavioural Cloning loss for diffusion-based policies. Implements the ε-prediction (noise-prediction) denoising loss from `Diffusion Policy: Visuomotor Policy Learning via Action Diffusion <https://arxiv.org/abs/2303.04137>`_ (Chi et al., RSS 2023). Given a batch of (observation, clean_action) pairs from a demonstration dataset, the loss: 1. Samples a random diffusion timestep ``t`` for each item in the batch. 2. Corrupts the clean action with Gaussian noise via the DDPM forward process: ``noisy_action = sqrt(ᾱ_t) * action + sqrt(1 - ᾱ_t) * ε``. 3. Asks the score network to predict the noise ``ε``. 4. Returns the MSE between the predicted and actual noise. This loss is designed to be used together with :class:`~torchrl.modules.DiffusionActor`. The actor's inner :class:`~torchrl.modules.tensordict_module.actors._DDPMModule` is accessed via ``actor_network.module`` and its ``add_noise`` method is used for step 2. Args: actor_network (TensorDictModule): a :class:`~torchrl.modules.DiffusionActor` (or any :class:`~tensordict.nn.TensorDictModule` whose ``.module`` exposes ``add_noise(clean_action, t)`` and a ``score_network`` attribute). Keyword Args: reduction (str, optional): Specifies the reduction to apply to the output: ``"none"`` | ``"mean"`` | ``"sum"``. Defaults to ``"mean"``. .. note:: The tensordict passed to :meth:`forward` must contain: * ``self.tensor_keys.action`` — the *clean* (demonstration) action. * ``self.tensor_keys.observation`` — the conditioning observation. Examples: >>> import torch >>> from tensordict import TensorDict >>> from torchrl.modules import DiffusionActor >>> from torchrl.objectives import DiffusionBCLoss >>> actor = DiffusionActor(action_dim=2, obs_dim=4, num_steps=10) >>> loss_fn = DiffusionBCLoss(actor) >>> td = TensorDict( ... { ... "observation": torch.randn(8, 4), ... "action": torch.randn(8, 2), ... }, ... batch_size=[8], ... ) >>> loss_td = loss_fn(td) >>> loss_td["loss_diffusion_bc"].backward() """ @dataclass class _AcceptedKeys: """Configurable tensordict keys. Attributes: action (NestedKey): Key for the clean demonstration action. Defaults to ``"action"``. observation (NestedKey): Key for the conditioning observation. Defaults to ``"observation"``. """ action: NestedKey = "action" observation: NestedKey = "observation" actor_network: TensorDictModule actor_network_params: TensorDictParams | None target_actor_network_params: TensorDictParams | None def _forward_value_estimator_keys(self, **kwargs) -> None: pass def __init__( self, actor_network: TensorDictModule, *, reduction: str = "mean", ) -> None: self._in_keys = None self._out_keys = None super().__init__() self.convert_to_functional(actor_network, "actor_network") self.reduction = reduction @property def in_keys(self): if self._in_keys is None: self._in_keys = [ self.tensor_keys.observation, self.tensor_keys.action, ] return self._in_keys @in_keys.setter def in_keys(self, value): self._in_keys = value @property def out_keys(self): if self._out_keys is None: self._out_keys = ["loss_diffusion_bc"] return self._out_keys @out_keys.setter def out_keys(self, value): self._out_keys = value
[docs] def forward(self, tensordict: TensorDictBase) -> TensorDict: """Compute the diffusion BC loss. Args: tensordict (TensorDictBase): input data containing observations and clean demonstration actions. Returns: TensorDict with key ``"loss_diffusion_bc"``. """ clean_action = tensordict[self.tensor_keys.action] observation = tensordict[self.tensor_keys.observation] batch_shape = clean_action.shape[:-1] device = clean_action.device with self.actor_network_params.to_module( self.actor_network, preserve_module_state=False ): # Access the underlying _DDPMModule ddpm = self.actor_network.module # Sample a random timestep per batch element t = torch.randint(0, ddpm.num_steps, tuple(batch_shape), device=device) # Forward diffusion: corrupt the clean action noisy_action, noise = ddpm.add_noise(clean_action, t) # Build the score network input: (noisy_action || observation || t) t_float = t.to(dtype=noisy_action.dtype).unsqueeze(-1) model_input = torch.cat([noisy_action, observation, t_float], dim=-1) # Predict the noise predicted_noise = ddpm.score_network(model_input) # ε-prediction MSE loss loss = torch.nn.functional.mse_loss( predicted_noise, noise, reduction=self.reduction ) return TensorDict({"loss_diffusion_bc": loss}, batch_size=[])