Source code for embodichain.learning.sl.types

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

"""Tensor-tree batches and objective results for supervised learning."""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field
from typing import Any, Protocol

import torch
from torch import nn

__all__ = ["SupervisedBatch", "LossResult", "SupervisedObjective"]


def _map_tensors(value: Any, function: Any) -> Any:
    if isinstance(value, torch.Tensor):
        return function(value)
    if isinstance(value, Mapping):
        return {key: _map_tensors(item, function) for key, item in value.items()}
    if isinstance(value, tuple):
        return tuple(_map_tensors(item, function) for item in value)
    if isinstance(value, list):
        return [_map_tensors(item, function) for item in value]
    return value


[docs] @dataclass(frozen=True) class SupervisedBatch: """Separate measured inputs, supervision and masks without imposing axes. Args: inputs: Tensor or nested tensor tree supplied to the objective. targets: Supervision tree. Its history may differ from the input history. masks: Named masks interpreted by the objective, not the optimizer. metadata: Source identities, units, timing and other host-side context. The producer owns storage and causal alignment. This container does not detach tensors: differentiable multi-step objectives retain their graph. """ inputs: Any targets: Any masks: Mapping[str, torch.Tensor] = field(default_factory=dict) metadata: Mapping[str, Any] = field(default_factory=dict)
[docs] def to(self, device: str | torch.device) -> SupervisedBatch: """Move tensor trees, preserving dtypes and host metadata. Args: device: Destination for inputs, targets and masks. Returns: A batch whose differentiable tensor transfers preserve gradients. """ move = lambda tensor: tensor.to(device) return SupervisedBatch( _map_tensors(self.inputs, move), _map_tensors(self.targets, move), _map_tensors(self.masks, move), self.metadata, )
[docs] @dataclass(frozen=True) class LossResult: """An objective-owned scalar loss and sample-weighted observations. Args: loss: Scalar connected to the parameters to optimize. metrics: Scalar diagnostics; they do not enter backward automatically. weight: Effective observation count for evaluation aggregation. Loss reduction and sequence weighting remain the objective's decision. """ loss: torch.Tensor metrics: Mapping[str, torch.Tensor | float] = field(default_factory=dict) weight: float = 1.0
[docs] class SupervisedObjective(Protocol): """Compute task-specific losses without owning optimizer or data lifecycle."""
[docs] def compute_loss(self, model: nn.Module, batch: SupervisedBatch) -> LossResult: """Build a scalar objective, optionally unrolling a differentiable model. Args: model: Trainable module, already on the batch's device. batch: Explicit inputs, targets, masks and sample context. Returns: Loss and statistics using this objective's numerical conventions. """ ...