torch_em.self_training.loss

  1from typing import Optional, Union
  2
  3import torch
  4import torch.nn as nn
  5
  6import torch_em
  7from torch_em.loss import DiceLoss
  8
  9
 10class DefaultSelfTrainingLoss(nn.Module):
 11    """Loss function for self training.
 12
 13    This loss takes as input a model and its input, as well as (pseudo) labels and potentially
 14    a mask for the labels. It then runs prediction with the model and compares the outputs
 15    to the (pseudo) labels using an internal loss function. Typically, the labels are derived
 16    from the predictions of a teacher model, and the model passed is the student model.
 17
 18    Args:
 19        loss: The internal loss function to use for comparing predictions of the teacher and student model.
 20        activation: The activation function to be applied to the prediction before passing it to the loss.
 21    """
 22    def __init__(self, loss: nn.Module = torch_em.loss.DiceLoss(), activation: Optional[nn.Module] = None):
 23        super().__init__()
 24        self.activation = activation
 25        self.loss = loss
 26        # TODO serialize the class names and kwargs instead
 27        self.init_kwargs = {}
 28
 29    def __call__(
 30        self, model: nn.Module, input_: torch.Tensor, labels: torch.Tensor, label_filter: Optional[torch.Tensor] = None
 31    ) -> torch.Tensor:
 32        """Compute the loss for self-training.
 33
 34        Args:
 35            model: The model.
 36            input_: The model inputs for this batch.
 37            labels: The (pseudo) labels for this batch.
 38            label_filter: A mask to exclude from the loss computation.
 39
 40        Returns:
 41            The loss value.
 42        """
 43        prediction = model(input_)
 44        if self.activation is not None:
 45            prediction = self.activation(prediction)
 46        if label_filter is None:
 47            loss = self.loss(prediction, labels)
 48        else:
 49            loss = self.loss(prediction * label_filter, labels * label_filter)
 50        return loss
 51
 52
 53class DefaultSelfTrainingLossAndMetric(nn.Module):
 54    """Loss and metric function for self training.
 55
 56    Similar to `DefaultSelfTrainingLoss`, but computes loss and metric value in one call
 57    to avoid running prediction with the model twice.
 58
 59    Args:
 60        loss: The internal loss function to use for comparing predictions of the teacher and student model.
 61        metric: The internal metric function to use for comparing predictions of the teacher and student model.
 62        activation: The activation function to be applied to the prediction before passing it to the loss.
 63    """
 64    def __init__(
 65        self,
 66        loss: nn.Module = torch_em.loss.DiceLoss(),
 67        metric: nn.Module = torch_em.loss.DiceLoss(),
 68        activation: Optional[nn.Module] = None
 69    ):
 70        super().__init__()
 71        self.activation = activation
 72        self.loss = loss
 73        self.metric = metric
 74        # TODO serialize the class names and dicts instead
 75        self.init_kwargs = {}
 76
 77    def __call__(self, model, input_, labels, label_filter=None):
 78        prediction = model(input_)
 79        if self.activation is not None:
 80            prediction = self.activation(prediction)
 81        if label_filter is None:
 82            loss = self.loss(prediction, labels)
 83        else:
 84            loss = self.loss(prediction * label_filter, labels * label_filter)
 85        metric = self.metric(prediction, labels)
 86        return loss, metric
 87
 88
 89def _l2_regularisation(m):
 90    l2_reg = None
 91    for W in m.parameters():
 92        if l2_reg is None:
 93            l2_reg = W.norm(2)
 94        else:
 95            l2_reg = l2_reg + W.norm(2)
 96    return l2_reg
 97
 98
 99class ProbabilisticUNetLoss(nn.Module):
100    """Training loss for ProbabilisticUNet.
101
102    Computes the ELBO loss: reconstruction term plus beta-weighted KL divergence,
103    with L2 regularisation on the posterior, prior, and fcomb weights.
104    Labels have shape (B, R, H, W), with one binary mask or class-index map per rater.
105    The ELBO averages the contributions from all raters.
106
107    Args:
108        loss: Reserved. Must be None; the ELBO objective is always used.
109    """
110    def __init__(self, loss: Optional[nn.Module] = None) -> None:
111        super().__init__()
112        self.loss = loss
113
114    def __call__(
115        self, model: nn.Module, input_: torch.Tensor, labels: torch.Tensor, label_filter: Optional[torch.Tensor] = None
116    ) -> torch.Tensor:
117        model(input_, labels)
118
119        if self.loss is None:
120            elbo = model.elbo(labels, label_filter)
121            reg_loss = (
122                _l2_regularisation(model.posterior)
123                + _l2_regularisation(model.prior)
124                + _l2_regularisation(model.fcomb.layers)
125            )
126            loss = -elbo + 1e-5 * reg_loss
127        else:
128            raise NotImplementedError("Custom loss is not supported; pass loss=None to use the ELBO.")
129
130        return loss
131
132
133class ProbabilisticUNetLossAndMetric(nn.Module):
134    """Training loss and validation metric for ProbabilisticUNet.
135
136    Computes the ELBO loss and a sample-averaged Dice metric in a single forward pass.
137    Draws prior_samples segmentation hypotheses, averages them, and evaluates against labels.
138    Labels have shape (B, R, H, W). The loss and metric average the contributions from all raters.
139
140    Args:
141        loss: Reserved. Must be None; the ELBO objective is always used.
142        metric: Metric function applied to averaged prior samples vs. labels.
143        activation: Activation applied to prior samples. The default uses sigmoid for binary outputs
144            and softmax for multiclass outputs. Pass None to use logits.
145        prior_samples: Number of prior samples to average for the metric.
146    """
147    def __init__(
148        self,
149        loss: Optional[nn.Module] = None,
150        metric: nn.Module = DiceLoss(),
151        activation: Optional[Union[nn.Module, str]] = "auto",
152        prior_samples: int = 16,
153    ) -> None:
154        super().__init__()
155        self.activation = activation
156        self.metric = metric
157        self.loss = loss
158        self.prior_samples = prior_samples
159
160    def __call__(
161        self, model: nn.Module, input_: torch.Tensor, labels: torch.Tensor, label_filter: Optional[torch.Tensor] = None
162    ):
163        model(input_, labels)
164
165        if self.loss is None:
166            elbo = model.elbo(labels, label_filter)
167            reg_loss = (
168                _l2_regularisation(model.posterior)
169                + _l2_regularisation(model.prior)
170                + _l2_regularisation(model.fcomb.layers)
171            )
172            loss = -elbo + 1e-5 * reg_loss
173        else:
174            raise NotImplementedError("Custom loss is not supported; pass loss=None to use the ELBO.")
175
176        samples_per_distribution = []
177        for _ in range(self.prior_samples):
178            samples = model.sample()
179            if self.activation == "auto":
180                samples = samples.sigmoid() if model.output_channels == 1 else samples.softmax(dim=1)
181            elif self.activation is not None:
182                samples = self.activation(samples)
183            samples_per_distribution.append(samples)
184
185        avg_samples = torch.stack(samples_per_distribution, dim=0).mean(dim=0)
186        metrics = []
187        for target in labels.split(1, dim=1):
188            if model.output_channels > 1 and isinstance(self.metric, DiceLoss):
189                target = torch.nn.functional.one_hot(target.squeeze(1).long(), model.output_channels).movedim(-1, 1)
190                target = target.float()
191            metrics.append(self.metric(avg_samples, target))
192        metric = torch.stack(metrics).mean(dim=0)
193
194        return loss, metric
195
196
197class SelfTrainingLossWithInvertibleAugmentations(nn.Module):
198    """Loss function for self-training with invertible augmentations.
199
200    Variant of `DefaultSelfTrainingLoss` for use with `FixMatchTrainerWithInvertibleAugmentations`
201    and `MeanTeacherTrainerWithInvertibleAugmentations`. Unlike `DefaultSelfTrainingLoss`, this loss
202    receives pre-computed predictions directly rather than a model and input, because the trainer
203    already applies the model and invertible augmentations before calling the loss.
204
205    Args:
206        loss: The internal loss function used to compare student predictions to pseudo-labels.
207        activation: Optional activation applied to the prediction before the loss.
208    """
209    def __init__(self, loss: nn.Module = torch_em.loss.DiceLoss(), activation: Optional[nn.Module] = None):
210        super().__init__()
211        self.activation = activation
212        self.loss = loss
213        # TODO serialize the class names and kwargs instead
214        self.init_kwargs = {}
215
216    def __call__(
217        self,
218        prediction: torch.Tensor,
219        labels: torch.Tensor,
220        label_filter: Optional[torch.Tensor] = None,
221    ) -> torch.Tensor:
222        """Compute the self-training loss.
223
224        Args:
225            prediction: Student model predictions, already mapped to the reference frame
226                via the inverse augmentation transform.
227            labels: The (pseudo) labels, mapped to the same reference frame.
228            label_filter: Optional mask or weight tensor. Where provided, both prediction
229                and labels are multiplied by this tensor before the loss is computed.
230
231        Returns:
232            The loss value.
233        """
234
235        if self.activation is not None:
236            prediction = self.activation(prediction)
237        if label_filter is None:
238            loss = self.loss(prediction, labels)
239        else:
240            loss = self.loss(prediction * label_filter, labels * label_filter)
241        return loss
242
243
244class SelfTrainingLossAndMetricWithInvertibleAugmentations(nn.Module):
245    """Loss and metric function for self-training with invertible augmentations.
246
247    Variant of `DefaultSelfTrainingLossAndMetric` for use with
248    `FixMatchTrainerWithInvertibleAugmentations` and `MeanTeacherTrainerWithInvertibleAugmentations`.
249    Computes both loss and metric in a single call from pre-computed predictions, avoiding a
250    second forward pass. Used during validation where the trainer already holds the predictions.
251
252    Args:
253        loss: The internal loss function used to compare student predictions to pseudo-labels.
254        metric: The internal metric function used to evaluate student predictions against pseudo-labels.
255        activation: Optional activation applied to the prediction before the loss and metric.
256    """
257    def __init__(
258        self,
259        loss: nn.Module = torch_em.loss.DiceLoss(),
260        metric: nn.Module = torch_em.loss.DiceLoss(),
261        activation: Optional[nn.Module] = None
262    ):
263        super().__init__()
264        self.activation = activation
265        self.loss = loss
266        self.metric = metric
267        # TODO serialize the class names and dicts instead
268        self.init_kwargs = {}
269
270    def __call__(
271        self,
272        prediction: torch.Tensor,
273        labels: torch.Tensor,
274        label_filter: Optional[torch.Tensor] = None,
275    ):
276        """Compute the self-training loss.
277
278        Args:
279            prediction: Student model predictions, already mapped to the reference frame
280                via the inverse augmentation transform.
281            labels: The (pseudo) labels, mapped to the same reference frame.
282            label_filter: Optional mask or weight tensor. Where provided, both prediction
283                and labels are multiplied by this tensor before the loss is computed.
284
285        Returns:
286            The loss and metric value.
287        """
288        if self.activation is not None:
289            prediction = self.activation(prediction)
290        if label_filter is None:
291            loss = self.loss(prediction, labels)
292        else:
293            loss = self.loss(prediction * label_filter, labels * label_filter)
294        metric = self.metric(prediction, labels)
295        return loss, metric
296
297
298class UniMatchv2Loss(nn.Module):
299    """Loss function for `UniMatchv2Trainer`.
300
301    Extends `SelfTrainingLossWithInvertibleAugmentations` to support the two-student-view scheme
302    of UniMatch v2. When `pred_dim=2`, `prediction` is expected to be a stacked tensor of two
303    student predictions `[pred_s1_inv, pred_s2_inv]`, and the loss is averaged over both views.
304    When `pred_dim=1`, it falls back to the standard single-prediction behaviour.
305
306    Args:
307        loss: The internal loss function used to compare student predictions to pseudo-labels.
308        activation: Optional activation applied to the predictions before the loss.
309    """
310    def __init__(self, loss: nn.Module = DiceLoss(), activation: Optional[nn.Module] = None):
311        super().__init__()
312        self.activation = activation
313        self.loss = loss
314        self.init_kwargs = {}
315
316    def __call__(
317        self,
318        prediction: torch.Tensor,
319        labels: torch.Tensor,
320        label_filter: Optional[torch.Tensor] = None,
321        pred_dim: int = 1,
322    ) -> torch.Tensor:
323        """Compute the UniMatch v2 self-training loss.
324
325        Args:
326            prediction: Student predictions mapped to the reference frame. When `pred_dim=2`,
327                a stacked tensor of shape `(2, B, C, ...)` holding the two strong-view predictions.
328                When `pred_dim=1`, a standard `(B, C, ...)` prediction tensor.
329            labels: The (pseudo) labels, mapped to the same reference frame.
330            label_filter: Optional mask or weight tensor applied to both prediction and labels
331                before the loss is computed.
332            pred_dim: Number of student views. Use `2` for the standard UniMatch v2 dual-view
333                training and `1` for single-view inference or validation.
334
335        Returns:
336            The loss value.
337        """
338
339        assert pred_dim in (1, 2), "pred_dim must be either 1 or 2"
340
341        if self.activation is not None:
342            prediction = self.activation(prediction)
343
344        if pred_dim == 2:
345            if label_filter is None:
346                loss = (self.loss(prediction[0], labels) + self.loss(prediction[1], labels)) / 2
347            else:
348                loss = (self.loss(
349                    prediction[0] * label_filter, labels * label_filter
350                ) + self.loss(prediction[1] * label_filter, labels * label_filter)) / 2
351            return loss
352
353        else:
354            if label_filter is None:
355                loss = self.loss(prediction, labels)
356            else:
357                loss = self.loss(prediction * label_filter, labels * label_filter)
358            return loss
359
360
361class UniMatchv2LossAndMetric(nn.Module):
362    """Loss and metric function for `UniMatchv2Trainer`.
363
364    Extends `SelfTrainingLossAndMetricWithInvertibleAugmentations` to support the two-student-view scheme
365    of UniMatch v2. `pred_dim` depends on how many views the student model processes
366    at the same time. Supports the same dual-view `pred_dim=2` convention: when two student
367    predictions are stacked, loss and metric are each averaged over both views.
368    When `pred_dim=1`, it falls back to the standard single-prediction behaviour.
369
370    Args:
371        loss: The internal loss function used to compare student predictions to pseudo-labels.
372        metric: The internal metric function used to evaluate student predictions against pseudo-labels.
373        activation: Optional activation applied to the predictions before the loss and metric.
374    """
375    def __init__(
376        self,
377        loss: nn.Module = DiceLoss(),
378        metric: nn.Module = DiceLoss(),
379        activation: Optional[nn.Module] = None
380    ):
381        super().__init__()
382        self.activation = activation
383        self.loss = loss
384        self.metric = metric
385        self.init_kwargs = {}
386
387    def __call__(
388        self,
389        prediction: torch.Tensor,
390        labels: torch.Tensor,
391        label_filter: Optional[torch.Tensor] = None,
392        pred_dim: int = 1,
393    ):
394        """Compute the UniMatch v2 self-training loss.
395
396        Args:
397            prediction: Student predictions mapped to the reference frame. When `pred_dim=2`,
398                a stacked tensor of shape `(2, B, C, ...)` holding the two strong-view predictions.
399                When `pred_dim=1`, a standard `(B, C, ...)` prediction tensor.
400            labels: The (pseudo) labels, mapped to the same reference frame.
401            label_filter: Optional mask or weight tensor applied to both prediction and labels
402                before the loss is computed.
403            pred_dim: Number of student views. Use `2` for the standard UniMatch v2 dual-view
404                training and `1` for single-view inference or validation.
405
406        Returns:
407            The loss and metric value.
408        """
409
410        if self.activation is not None:
411            prediction = self.activation(prediction)
412
413        assert pred_dim in (1, 2), "pred_dim must be either 1 or 2"
414
415        if pred_dim == 2:
416            assert len(prediction) == 2, "only implemented for list of len 2"
417            if label_filter is None:
418                loss = (self.loss(prediction[0], labels) + self.loss(prediction[1], labels)) / 2
419            else:
420                loss = (self.loss(
421                    prediction[0] * label_filter, labels * label_filter
422                ) + self.loss(prediction[1] * label_filter, labels * label_filter)) / 2
423            metric = (self.metric(prediction[0], labels) + self.metric(prediction[1], labels)) / 2
424            return loss, metric
425
426        else:
427            if label_filter is None:
428                loss = self.loss(prediction, labels)
429            else:
430                loss = self.loss(prediction * label_filter, labels * label_filter)
431            metric = self.metric(prediction, labels)
432            return loss, metric
class DefaultSelfTrainingLoss(torch.nn.modules.module.Module):
11class DefaultSelfTrainingLoss(nn.Module):
12    """Loss function for self training.
13
14    This loss takes as input a model and its input, as well as (pseudo) labels and potentially
15    a mask for the labels. It then runs prediction with the model and compares the outputs
16    to the (pseudo) labels using an internal loss function. Typically, the labels are derived
17    from the predictions of a teacher model, and the model passed is the student model.
18
19    Args:
20        loss: The internal loss function to use for comparing predictions of the teacher and student model.
21        activation: The activation function to be applied to the prediction before passing it to the loss.
22    """
23    def __init__(self, loss: nn.Module = torch_em.loss.DiceLoss(), activation: Optional[nn.Module] = None):
24        super().__init__()
25        self.activation = activation
26        self.loss = loss
27        # TODO serialize the class names and kwargs instead
28        self.init_kwargs = {}
29
30    def __call__(
31        self, model: nn.Module, input_: torch.Tensor, labels: torch.Tensor, label_filter: Optional[torch.Tensor] = None
32    ) -> torch.Tensor:
33        """Compute the loss for self-training.
34
35        Args:
36            model: The model.
37            input_: The model inputs for this batch.
38            labels: The (pseudo) labels for this batch.
39            label_filter: A mask to exclude from the loss computation.
40
41        Returns:
42            The loss value.
43        """
44        prediction = model(input_)
45        if self.activation is not None:
46            prediction = self.activation(prediction)
47        if label_filter is None:
48            loss = self.loss(prediction, labels)
49        else:
50            loss = self.loss(prediction * label_filter, labels * label_filter)
51        return loss

Loss function for self training.

This loss takes as input a model and its input, as well as (pseudo) labels and potentially a mask for the labels. It then runs prediction with the model and compares the outputs to the (pseudo) labels using an internal loss function. Typically, the labels are derived from the predictions of a teacher model, and the model passed is the student model.

Arguments:
  • loss: The internal loss function to use for comparing predictions of the teacher and student model.
  • activation: The activation function to be applied to the prediction before passing it to the loss.
DefaultSelfTrainingLoss( loss: torch.nn.modules.module.Module = DiceLoss(), activation: Optional[torch.nn.modules.module.Module] = None)
23    def __init__(self, loss: nn.Module = torch_em.loss.DiceLoss(), activation: Optional[nn.Module] = None):
24        super().__init__()
25        self.activation = activation
26        self.loss = loss
27        # TODO serialize the class names and kwargs instead
28        self.init_kwargs = {}

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
loss
init_kwargs
class DefaultSelfTrainingLossAndMetric(torch.nn.modules.module.Module):
54class DefaultSelfTrainingLossAndMetric(nn.Module):
55    """Loss and metric function for self training.
56
57    Similar to `DefaultSelfTrainingLoss`, but computes loss and metric value in one call
58    to avoid running prediction with the model twice.
59
60    Args:
61        loss: The internal loss function to use for comparing predictions of the teacher and student model.
62        metric: The internal metric function to use for comparing predictions of the teacher and student model.
63        activation: The activation function to be applied to the prediction before passing it to the loss.
64    """
65    def __init__(
66        self,
67        loss: nn.Module = torch_em.loss.DiceLoss(),
68        metric: nn.Module = torch_em.loss.DiceLoss(),
69        activation: Optional[nn.Module] = None
70    ):
71        super().__init__()
72        self.activation = activation
73        self.loss = loss
74        self.metric = metric
75        # TODO serialize the class names and dicts instead
76        self.init_kwargs = {}
77
78    def __call__(self, model, input_, labels, label_filter=None):
79        prediction = model(input_)
80        if self.activation is not None:
81            prediction = self.activation(prediction)
82        if label_filter is None:
83            loss = self.loss(prediction, labels)
84        else:
85            loss = self.loss(prediction * label_filter, labels * label_filter)
86        metric = self.metric(prediction, labels)
87        return loss, metric

Loss and metric function for self training.

Similar to DefaultSelfTrainingLoss, but computes loss and metric value in one call to avoid running prediction with the model twice.

Arguments:
  • loss: The internal loss function to use for comparing predictions of the teacher and student model.
  • metric: The internal metric function to use for comparing predictions of the teacher and student model.
  • activation: The activation function to be applied to the prediction before passing it to the loss.
DefaultSelfTrainingLossAndMetric( loss: torch.nn.modules.module.Module = DiceLoss(), metric: torch.nn.modules.module.Module = DiceLoss(), activation: Optional[torch.nn.modules.module.Module] = None)
65    def __init__(
66        self,
67        loss: nn.Module = torch_em.loss.DiceLoss(),
68        metric: nn.Module = torch_em.loss.DiceLoss(),
69        activation: Optional[nn.Module] = None
70    ):
71        super().__init__()
72        self.activation = activation
73        self.loss = loss
74        self.metric = metric
75        # TODO serialize the class names and dicts instead
76        self.init_kwargs = {}

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
loss
metric
init_kwargs
class ProbabilisticUNetLoss(torch.nn.modules.module.Module):
100class ProbabilisticUNetLoss(nn.Module):
101    """Training loss for ProbabilisticUNet.
102
103    Computes the ELBO loss: reconstruction term plus beta-weighted KL divergence,
104    with L2 regularisation on the posterior, prior, and fcomb weights.
105    Labels have shape (B, R, H, W), with one binary mask or class-index map per rater.
106    The ELBO averages the contributions from all raters.
107
108    Args:
109        loss: Reserved. Must be None; the ELBO objective is always used.
110    """
111    def __init__(self, loss: Optional[nn.Module] = None) -> None:
112        super().__init__()
113        self.loss = loss
114
115    def __call__(
116        self, model: nn.Module, input_: torch.Tensor, labels: torch.Tensor, label_filter: Optional[torch.Tensor] = None
117    ) -> torch.Tensor:
118        model(input_, labels)
119
120        if self.loss is None:
121            elbo = model.elbo(labels, label_filter)
122            reg_loss = (
123                _l2_regularisation(model.posterior)
124                + _l2_regularisation(model.prior)
125                + _l2_regularisation(model.fcomb.layers)
126            )
127            loss = -elbo + 1e-5 * reg_loss
128        else:
129            raise NotImplementedError("Custom loss is not supported; pass loss=None to use the ELBO.")
130
131        return loss

Training loss for ProbabilisticUNet.

Computes the ELBO loss: reconstruction term plus beta-weighted KL divergence, with L2 regularisation on the posterior, prior, and fcomb weights. Labels have shape (B, R, H, W), with one binary mask or class-index map per rater. The ELBO averages the contributions from all raters.

Arguments:
  • loss: Reserved. Must be None; the ELBO objective is always used.
ProbabilisticUNetLoss(loss: Optional[torch.nn.modules.module.Module] = None)
111    def __init__(self, loss: Optional[nn.Module] = None) -> None:
112        super().__init__()
113        self.loss = loss

Initialize internal Module state, shared by both nn.Module and ScriptModule.

loss
class ProbabilisticUNetLossAndMetric(torch.nn.modules.module.Module):
134class ProbabilisticUNetLossAndMetric(nn.Module):
135    """Training loss and validation metric for ProbabilisticUNet.
136
137    Computes the ELBO loss and a sample-averaged Dice metric in a single forward pass.
138    Draws prior_samples segmentation hypotheses, averages them, and evaluates against labels.
139    Labels have shape (B, R, H, W). The loss and metric average the contributions from all raters.
140
141    Args:
142        loss: Reserved. Must be None; the ELBO objective is always used.
143        metric: Metric function applied to averaged prior samples vs. labels.
144        activation: Activation applied to prior samples. The default uses sigmoid for binary outputs
145            and softmax for multiclass outputs. Pass None to use logits.
146        prior_samples: Number of prior samples to average for the metric.
147    """
148    def __init__(
149        self,
150        loss: Optional[nn.Module] = None,
151        metric: nn.Module = DiceLoss(),
152        activation: Optional[Union[nn.Module, str]] = "auto",
153        prior_samples: int = 16,
154    ) -> None:
155        super().__init__()
156        self.activation = activation
157        self.metric = metric
158        self.loss = loss
159        self.prior_samples = prior_samples
160
161    def __call__(
162        self, model: nn.Module, input_: torch.Tensor, labels: torch.Tensor, label_filter: Optional[torch.Tensor] = None
163    ):
164        model(input_, labels)
165
166        if self.loss is None:
167            elbo = model.elbo(labels, label_filter)
168            reg_loss = (
169                _l2_regularisation(model.posterior)
170                + _l2_regularisation(model.prior)
171                + _l2_regularisation(model.fcomb.layers)
172            )
173            loss = -elbo + 1e-5 * reg_loss
174        else:
175            raise NotImplementedError("Custom loss is not supported; pass loss=None to use the ELBO.")
176
177        samples_per_distribution = []
178        for _ in range(self.prior_samples):
179            samples = model.sample()
180            if self.activation == "auto":
181                samples = samples.sigmoid() if model.output_channels == 1 else samples.softmax(dim=1)
182            elif self.activation is not None:
183                samples = self.activation(samples)
184            samples_per_distribution.append(samples)
185
186        avg_samples = torch.stack(samples_per_distribution, dim=0).mean(dim=0)
187        metrics = []
188        for target in labels.split(1, dim=1):
189            if model.output_channels > 1 and isinstance(self.metric, DiceLoss):
190                target = torch.nn.functional.one_hot(target.squeeze(1).long(), model.output_channels).movedim(-1, 1)
191                target = target.float()
192            metrics.append(self.metric(avg_samples, target))
193        metric = torch.stack(metrics).mean(dim=0)
194
195        return loss, metric

Training loss and validation metric for ProbabilisticUNet.

Computes the ELBO loss and a sample-averaged Dice metric in a single forward pass. Draws prior_samples segmentation hypotheses, averages them, and evaluates against labels. Labels have shape (B, R, H, W). The loss and metric average the contributions from all raters.

Arguments:
  • loss: Reserved. Must be None; the ELBO objective is always used.
  • metric: Metric function applied to averaged prior samples vs. labels.
  • activation: Activation applied to prior samples. The default uses sigmoid for binary outputs and softmax for multiclass outputs. Pass None to use logits.
  • prior_samples: Number of prior samples to average for the metric.
ProbabilisticUNetLossAndMetric( loss: Optional[torch.nn.modules.module.Module] = None, metric: torch.nn.modules.module.Module = DiceLoss(), activation: Union[torch.nn.modules.module.Module, str, NoneType] = 'auto', prior_samples: int = 16)
148    def __init__(
149        self,
150        loss: Optional[nn.Module] = None,
151        metric: nn.Module = DiceLoss(),
152        activation: Optional[Union[nn.Module, str]] = "auto",
153        prior_samples: int = 16,
154    ) -> None:
155        super().__init__()
156        self.activation = activation
157        self.metric = metric
158        self.loss = loss
159        self.prior_samples = prior_samples

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
metric
loss
prior_samples
class SelfTrainingLossWithInvertibleAugmentations(torch.nn.modules.module.Module):
198class SelfTrainingLossWithInvertibleAugmentations(nn.Module):
199    """Loss function for self-training with invertible augmentations.
200
201    Variant of `DefaultSelfTrainingLoss` for use with `FixMatchTrainerWithInvertibleAugmentations`
202    and `MeanTeacherTrainerWithInvertibleAugmentations`. Unlike `DefaultSelfTrainingLoss`, this loss
203    receives pre-computed predictions directly rather than a model and input, because the trainer
204    already applies the model and invertible augmentations before calling the loss.
205
206    Args:
207        loss: The internal loss function used to compare student predictions to pseudo-labels.
208        activation: Optional activation applied to the prediction before the loss.
209    """
210    def __init__(self, loss: nn.Module = torch_em.loss.DiceLoss(), activation: Optional[nn.Module] = None):
211        super().__init__()
212        self.activation = activation
213        self.loss = loss
214        # TODO serialize the class names and kwargs instead
215        self.init_kwargs = {}
216
217    def __call__(
218        self,
219        prediction: torch.Tensor,
220        labels: torch.Tensor,
221        label_filter: Optional[torch.Tensor] = None,
222    ) -> torch.Tensor:
223        """Compute the self-training loss.
224
225        Args:
226            prediction: Student model predictions, already mapped to the reference frame
227                via the inverse augmentation transform.
228            labels: The (pseudo) labels, mapped to the same reference frame.
229            label_filter: Optional mask or weight tensor. Where provided, both prediction
230                and labels are multiplied by this tensor before the loss is computed.
231
232        Returns:
233            The loss value.
234        """
235
236        if self.activation is not None:
237            prediction = self.activation(prediction)
238        if label_filter is None:
239            loss = self.loss(prediction, labels)
240        else:
241            loss = self.loss(prediction * label_filter, labels * label_filter)
242        return loss

Loss function for self-training with invertible augmentations.

Variant of DefaultSelfTrainingLoss for use with FixMatchTrainerWithInvertibleAugmentations and MeanTeacherTrainerWithInvertibleAugmentations. Unlike DefaultSelfTrainingLoss, this loss receives pre-computed predictions directly rather than a model and input, because the trainer already applies the model and invertible augmentations before calling the loss.

Arguments:
  • loss: The internal loss function used to compare student predictions to pseudo-labels.
  • activation: Optional activation applied to the prediction before the loss.
SelfTrainingLossWithInvertibleAugmentations( loss: torch.nn.modules.module.Module = DiceLoss(), activation: Optional[torch.nn.modules.module.Module] = None)
210    def __init__(self, loss: nn.Module = torch_em.loss.DiceLoss(), activation: Optional[nn.Module] = None):
211        super().__init__()
212        self.activation = activation
213        self.loss = loss
214        # TODO serialize the class names and kwargs instead
215        self.init_kwargs = {}

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
loss
init_kwargs
class SelfTrainingLossAndMetricWithInvertibleAugmentations(torch.nn.modules.module.Module):
245class SelfTrainingLossAndMetricWithInvertibleAugmentations(nn.Module):
246    """Loss and metric function for self-training with invertible augmentations.
247
248    Variant of `DefaultSelfTrainingLossAndMetric` for use with
249    `FixMatchTrainerWithInvertibleAugmentations` and `MeanTeacherTrainerWithInvertibleAugmentations`.
250    Computes both loss and metric in a single call from pre-computed predictions, avoiding a
251    second forward pass. Used during validation where the trainer already holds the predictions.
252
253    Args:
254        loss: The internal loss function used to compare student predictions to pseudo-labels.
255        metric: The internal metric function used to evaluate student predictions against pseudo-labels.
256        activation: Optional activation applied to the prediction before the loss and metric.
257    """
258    def __init__(
259        self,
260        loss: nn.Module = torch_em.loss.DiceLoss(),
261        metric: nn.Module = torch_em.loss.DiceLoss(),
262        activation: Optional[nn.Module] = None
263    ):
264        super().__init__()
265        self.activation = activation
266        self.loss = loss
267        self.metric = metric
268        # TODO serialize the class names and dicts instead
269        self.init_kwargs = {}
270
271    def __call__(
272        self,
273        prediction: torch.Tensor,
274        labels: torch.Tensor,
275        label_filter: Optional[torch.Tensor] = None,
276    ):
277        """Compute the self-training loss.
278
279        Args:
280            prediction: Student model predictions, already mapped to the reference frame
281                via the inverse augmentation transform.
282            labels: The (pseudo) labels, mapped to the same reference frame.
283            label_filter: Optional mask or weight tensor. Where provided, both prediction
284                and labels are multiplied by this tensor before the loss is computed.
285
286        Returns:
287            The loss and metric value.
288        """
289        if self.activation is not None:
290            prediction = self.activation(prediction)
291        if label_filter is None:
292            loss = self.loss(prediction, labels)
293        else:
294            loss = self.loss(prediction * label_filter, labels * label_filter)
295        metric = self.metric(prediction, labels)
296        return loss, metric

Loss and metric function for self-training with invertible augmentations.

Variant of DefaultSelfTrainingLossAndMetric for use with FixMatchTrainerWithInvertibleAugmentations and MeanTeacherTrainerWithInvertibleAugmentations. Computes both loss and metric in a single call from pre-computed predictions, avoiding a second forward pass. Used during validation where the trainer already holds the predictions.

Arguments:
  • loss: The internal loss function used to compare student predictions to pseudo-labels.
  • metric: The internal metric function used to evaluate student predictions against pseudo-labels.
  • activation: Optional activation applied to the prediction before the loss and metric.
SelfTrainingLossAndMetricWithInvertibleAugmentations( loss: torch.nn.modules.module.Module = DiceLoss(), metric: torch.nn.modules.module.Module = DiceLoss(), activation: Optional[torch.nn.modules.module.Module] = None)
258    def __init__(
259        self,
260        loss: nn.Module = torch_em.loss.DiceLoss(),
261        metric: nn.Module = torch_em.loss.DiceLoss(),
262        activation: Optional[nn.Module] = None
263    ):
264        super().__init__()
265        self.activation = activation
266        self.loss = loss
267        self.metric = metric
268        # TODO serialize the class names and dicts instead
269        self.init_kwargs = {}

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
loss
metric
init_kwargs
class UniMatchv2Loss(torch.nn.modules.module.Module):
299class UniMatchv2Loss(nn.Module):
300    """Loss function for `UniMatchv2Trainer`.
301
302    Extends `SelfTrainingLossWithInvertibleAugmentations` to support the two-student-view scheme
303    of UniMatch v2. When `pred_dim=2`, `prediction` is expected to be a stacked tensor of two
304    student predictions `[pred_s1_inv, pred_s2_inv]`, and the loss is averaged over both views.
305    When `pred_dim=1`, it falls back to the standard single-prediction behaviour.
306
307    Args:
308        loss: The internal loss function used to compare student predictions to pseudo-labels.
309        activation: Optional activation applied to the predictions before the loss.
310    """
311    def __init__(self, loss: nn.Module = DiceLoss(), activation: Optional[nn.Module] = None):
312        super().__init__()
313        self.activation = activation
314        self.loss = loss
315        self.init_kwargs = {}
316
317    def __call__(
318        self,
319        prediction: torch.Tensor,
320        labels: torch.Tensor,
321        label_filter: Optional[torch.Tensor] = None,
322        pred_dim: int = 1,
323    ) -> torch.Tensor:
324        """Compute the UniMatch v2 self-training loss.
325
326        Args:
327            prediction: Student predictions mapped to the reference frame. When `pred_dim=2`,
328                a stacked tensor of shape `(2, B, C, ...)` holding the two strong-view predictions.
329                When `pred_dim=1`, a standard `(B, C, ...)` prediction tensor.
330            labels: The (pseudo) labels, mapped to the same reference frame.
331            label_filter: Optional mask or weight tensor applied to both prediction and labels
332                before the loss is computed.
333            pred_dim: Number of student views. Use `2` for the standard UniMatch v2 dual-view
334                training and `1` for single-view inference or validation.
335
336        Returns:
337            The loss value.
338        """
339
340        assert pred_dim in (1, 2), "pred_dim must be either 1 or 2"
341
342        if self.activation is not None:
343            prediction = self.activation(prediction)
344
345        if pred_dim == 2:
346            if label_filter is None:
347                loss = (self.loss(prediction[0], labels) + self.loss(prediction[1], labels)) / 2
348            else:
349                loss = (self.loss(
350                    prediction[0] * label_filter, labels * label_filter
351                ) + self.loss(prediction[1] * label_filter, labels * label_filter)) / 2
352            return loss
353
354        else:
355            if label_filter is None:
356                loss = self.loss(prediction, labels)
357            else:
358                loss = self.loss(prediction * label_filter, labels * label_filter)
359            return loss

Loss function for UniMatchv2Trainer.

Extends SelfTrainingLossWithInvertibleAugmentations to support the two-student-view scheme of UniMatch v2. When pred_dim=2, prediction is expected to be a stacked tensor of two student predictions [pred_s1_inv, pred_s2_inv], and the loss is averaged over both views. When pred_dim=1, it falls back to the standard single-prediction behaviour.

Arguments:
  • loss: The internal loss function used to compare student predictions to pseudo-labels.
  • activation: Optional activation applied to the predictions before the loss.
UniMatchv2Loss( loss: torch.nn.modules.module.Module = DiceLoss(), activation: Optional[torch.nn.modules.module.Module] = None)
311    def __init__(self, loss: nn.Module = DiceLoss(), activation: Optional[nn.Module] = None):
312        super().__init__()
313        self.activation = activation
314        self.loss = loss
315        self.init_kwargs = {}

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
loss
init_kwargs
class UniMatchv2LossAndMetric(torch.nn.modules.module.Module):
362class UniMatchv2LossAndMetric(nn.Module):
363    """Loss and metric function for `UniMatchv2Trainer`.
364
365    Extends `SelfTrainingLossAndMetricWithInvertibleAugmentations` to support the two-student-view scheme
366    of UniMatch v2. `pred_dim` depends on how many views the student model processes
367    at the same time. Supports the same dual-view `pred_dim=2` convention: when two student
368    predictions are stacked, loss and metric are each averaged over both views.
369    When `pred_dim=1`, it falls back to the standard single-prediction behaviour.
370
371    Args:
372        loss: The internal loss function used to compare student predictions to pseudo-labels.
373        metric: The internal metric function used to evaluate student predictions against pseudo-labels.
374        activation: Optional activation applied to the predictions before the loss and metric.
375    """
376    def __init__(
377        self,
378        loss: nn.Module = DiceLoss(),
379        metric: nn.Module = DiceLoss(),
380        activation: Optional[nn.Module] = None
381    ):
382        super().__init__()
383        self.activation = activation
384        self.loss = loss
385        self.metric = metric
386        self.init_kwargs = {}
387
388    def __call__(
389        self,
390        prediction: torch.Tensor,
391        labels: torch.Tensor,
392        label_filter: Optional[torch.Tensor] = None,
393        pred_dim: int = 1,
394    ):
395        """Compute the UniMatch v2 self-training loss.
396
397        Args:
398            prediction: Student predictions mapped to the reference frame. When `pred_dim=2`,
399                a stacked tensor of shape `(2, B, C, ...)` holding the two strong-view predictions.
400                When `pred_dim=1`, a standard `(B, C, ...)` prediction tensor.
401            labels: The (pseudo) labels, mapped to the same reference frame.
402            label_filter: Optional mask or weight tensor applied to both prediction and labels
403                before the loss is computed.
404            pred_dim: Number of student views. Use `2` for the standard UniMatch v2 dual-view
405                training and `1` for single-view inference or validation.
406
407        Returns:
408            The loss and metric value.
409        """
410
411        if self.activation is not None:
412            prediction = self.activation(prediction)
413
414        assert pred_dim in (1, 2), "pred_dim must be either 1 or 2"
415
416        if pred_dim == 2:
417            assert len(prediction) == 2, "only implemented for list of len 2"
418            if label_filter is None:
419                loss = (self.loss(prediction[0], labels) + self.loss(prediction[1], labels)) / 2
420            else:
421                loss = (self.loss(
422                    prediction[0] * label_filter, labels * label_filter
423                ) + self.loss(prediction[1] * label_filter, labels * label_filter)) / 2
424            metric = (self.metric(prediction[0], labels) + self.metric(prediction[1], labels)) / 2
425            return loss, metric
426
427        else:
428            if label_filter is None:
429                loss = self.loss(prediction, labels)
430            else:
431                loss = self.loss(prediction * label_filter, labels * label_filter)
432            metric = self.metric(prediction, labels)
433            return loss, metric

Loss and metric function for UniMatchv2Trainer.

Extends SelfTrainingLossAndMetricWithInvertibleAugmentations to support the two-student-view scheme of UniMatch v2. pred_dim depends on how many views the student model processes at the same time. Supports the same dual-view pred_dim=2 convention: when two student predictions are stacked, loss and metric are each averaged over both views. When pred_dim=1, it falls back to the standard single-prediction behaviour.

Arguments:
  • loss: The internal loss function used to compare student predictions to pseudo-labels.
  • metric: The internal metric function used to evaluate student predictions against pseudo-labels.
  • activation: Optional activation applied to the predictions before the loss and metric.
UniMatchv2LossAndMetric( loss: torch.nn.modules.module.Module = DiceLoss(), metric: torch.nn.modules.module.Module = DiceLoss(), activation: Optional[torch.nn.modules.module.Module] = None)
376    def __init__(
377        self,
378        loss: nn.Module = DiceLoss(),
379        metric: nn.Module = DiceLoss(),
380        activation: Optional[nn.Module] = None
381    ):
382        super().__init__()
383        self.activation = activation
384        self.loss = loss
385        self.metric = metric
386        self.init_kwargs = {}

Initialize internal Module state, shared by both nn.Module and ScriptModule.

activation
loss
metric
init_kwargs