torch_em.self_training.mean_teacher
1import time 2from copy import deepcopy 3from typing import Callable, Optional 4 5import torch 6import torch_em 7from torch_em.util import get_constructor_arguments 8 9from .logger import SelfTrainingTensorboardLogger 10from ..transform.invertible_augmentations import InvertibleAugmenter 11 12 13class Dummy(torch.nn.Module): 14 init_kwargs = {} 15 16 17class MeanTeacherTrainer(torch_em.trainer.DefaultTrainer): 18 """Trainer for semi-supervised learning and domain adaptation following the MeanTeacher approach. 19 20 Mean Teacher was introduced by Tarvainen & Vapola in https://arxiv.org/abs/1703.01780. 21 It uses a teacher model derived from the student model via EMA of weights 22 to predict pseudo-labels on unlabeled data. We support two training strategies: 23 - Joint training on labeled and unlabeled data (with a supervised and unsupervised loss function). 24 - Training only on the unsupervised data. 25 26 This class expects the following data loaders: 27 - unsupervised_train_loader: Returns two augmentations of the same input. 28 - supervised_train_loader (optional): Returns input and labels. 29 - unsupervised_val_loader (optional): Same as unsupervised_train_loader 30 - supervised_val_loader (optional): Same as supervised_train_loader 31 At least one of unsupervised_val_loader and supervised_val_loader must be given. 32 33 And the following elements to customize the pseudo labeling: 34 - pseudo_labeler: to compute the psuedo-labels 35 - Parameters: teacher, teacher_input 36 - Returns: pseudo_labels, label_filter (<- label filter can for example be mask, weight or None) 37 - unsupervised_loss: the loss between model predictions and pseudo labels 38 - Parameters: model, model_input, pseudo_labels, label_filter 39 - Returns: loss 40 - supervised_loss (optional): the supervised loss function 41 - Parameters: model, input, labels 42 - Returns: loss 43 - unsupervised_loss_and_metric (optional): the unsupervised loss function and metric 44 - Parameters: model, model_input, pseudo_labels, label_filter 45 - Returns: loss, metric 46 - supervised_loss_and_metric (optional): the supervised loss function and metric 47 - Parameters: model, input, labels 48 - Returns: loss, metric 49 At least one of unsupervised_loss_and_metric and supervised_loss_and_metric must be given. 50 51 If the parameter reinit_teacher is set to true, the teacher weights are re-initialized. 52 If it is None, the most appropriate initialization scheme for the training approach is chosen: 53 - semi-supervised training -> reinit, because we usually train a model from scratch 54 - unsupervised training -> do not reinit, because we usually fine-tune a model 55 56 Note: adjust the batch size ratio between the 'unsupervised_train_loader' and 'supervised_train_loader' 57 for setting the ratio between supervised and unsupervised training samples 58 59 Args: 60 model: The model to be trained. 61 unsupervised_train_loader: The loader for unsupervised training. 62 unsupervised_loss: The loss for unsupervised training. 63 pseudo_labeler: The pseudo labeler that predicts labels in unsupervised training. 64 supervised_train_loader: The loader for supervised training. 65 supervised_loss: The loss for supervised training. 66 unsupervised_loss_and_metric: The loss and metric for unsupervised training. 67 supervised_loss_and_metric: The loss and metrhic for supervised training. 68 logger: The logger. 69 momentum: The momentum value for the exponential moving weight average of the teacher model. 70 reinit_teacher: Whether to reinit the teacher model before starting the training. 71 sampler: A sampler for rejecting pseudo-labels according to a defined criterion. 72 separate_backward: Whether to backpropagate each loss term separately to reduce peak memory. 73 The default uses one backward pass on the combined loss. 74 kwargs: Additional keyword arguments for `torch_em.trainer.DefaultTrainer`. 75 """ 76 77 def __init__( 78 self, 79 model: torch.nn.Module, 80 unsupervised_train_loader: torch.utils.data.DataLoader, 81 unsupervised_loss: torch.utils.data.DataLoader, 82 pseudo_labeler: Callable, 83 supervised_train_loader: Optional[torch.utils.data.DataLoader] = None, 84 unsupervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 85 supervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 86 supervised_loss: Optional[Callable] = None, 87 unsupervised_loss_and_metric: Optional[Callable] = None, 88 supervised_loss_and_metric: Optional[Callable] = None, 89 logger=SelfTrainingTensorboardLogger, 90 momentum: float = 0.999, 91 reinit_teacher: Optional[bool] = None, 92 sampler: Optional[Callable] = None, 93 separate_backward: bool = False, 94 **kwargs, 95 ): 96 self.sampler = sampler 97 self.separate_backward = separate_backward 98 # Do we have supervised data or not? 99 if supervised_train_loader is None: 100 # No. -> We use the unsupervised training logic. 101 train_loader = unsupervised_train_loader 102 self._train_epoch_impl = self._train_epoch_unsupervised 103 else: 104 # Yes. -> We use the semi-supervised training logic. 105 assert supervised_loss is not None 106 train_loader = supervised_train_loader if len(supervised_train_loader) < len(unsupervised_train_loader)\ 107 else unsupervised_train_loader 108 self._train_epoch_impl = self._train_epoch_semisupervised 109 110 self.unsupervised_train_loader = unsupervised_train_loader 111 self.supervised_train_loader = supervised_train_loader 112 113 # Check that we have at least one of supvervised / unsupervised val loader. 114 assert sum(( 115 supervised_val_loader is not None, 116 unsupervised_val_loader is not None, 117 )) > 0 118 self.supervised_val_loader = supervised_val_loader 119 self.unsupervised_val_loader = unsupervised_val_loader 120 121 if self.unsupervised_val_loader is None: 122 val_loader = self.supervised_val_loader 123 else: 124 val_loader = self.unsupervised_train_loader 125 126 # Check that we have at least one of supvervised / unsupervised loss and metric. 127 assert sum(( 128 supervised_loss_and_metric is not None, 129 unsupervised_loss_and_metric is not None, 130 )) > 0 131 self.supervised_loss_and_metric = supervised_loss_and_metric 132 self.unsupervised_loss_and_metric = unsupervised_loss_and_metric 133 134 # train_loader, val_loader, loss and metric may be unnecessarily deserialized 135 kwargs.pop("train_loader", None) 136 kwargs.pop("val_loader", None) 137 kwargs.pop("metric", None) 138 kwargs.pop("loss", None) 139 super().__init__( 140 model=model, train_loader=train_loader, val_loader=val_loader, 141 loss=Dummy(), metric=Dummy(), logger=logger, **kwargs 142 ) 143 144 self.unsupervised_loss = unsupervised_loss 145 self.supervised_loss = supervised_loss 146 147 self.pseudo_labeler = pseudo_labeler 148 self.momentum = momentum 149 150 # determine how we initialize the teacher weights (copy or reinitialization) 151 if reinit_teacher is None: 152 # semisupervised training: reinitialize 153 # unsupervised training: copy 154 self.reinit_teacher = supervised_train_loader is not None 155 else: 156 self.reinit_teacher = reinit_teacher 157 158 with torch.no_grad(): 159 self.teacher = deepcopy(self.model) 160 if self.reinit_teacher: 161 for layer in self.teacher.children(): 162 if hasattr(layer, "reset_parameters"): 163 layer.reset_parameters() 164 for param in self.teacher.parameters(): 165 param.requires_grad = False 166 167 self._kwargs = kwargs 168 169 def _momentum_update(self): 170 # if we reinit the teacher we perform much faster updates (low momentum) in the first iterations 171 # to avoid a large gap between teacher and student weights, leading to inconsistent predictions 172 # if we don't reinit this is not necessary 173 if self.reinit_teacher: 174 current_momentum = min(1 - 1 / (self._iteration + 1), self.momentum) 175 else: 176 current_momentum = self.momentum 177 178 for param, param_teacher in zip(self.model.parameters(), self.teacher.parameters()): 179 param_teacher.data = param_teacher.data * current_momentum + param.data * (1. - current_momentum) 180 181 # 182 # functionality for saving checkpoints and initialization 183 # 184 185 def save_checkpoint(self, name, current_metric, best_metric, **extra_save_dict): 186 """@private 187 """ 188 train_loader_kwargs = get_constructor_arguments(self.train_loader) 189 val_loader_kwargs = get_constructor_arguments(self.val_loader) 190 extra_state = { 191 "teacher_state": self.teacher.state_dict(), 192 "init": { 193 "train_loader_kwargs": train_loader_kwargs, 194 "train_dataset": self.train_loader.dataset, 195 "val_loader_kwargs": val_loader_kwargs, 196 "val_dataset": self.val_loader.dataset, 197 "loss_class": "torch_em.self_training.mean_teacher.Dummy", 198 "loss_kwargs": {}, 199 "metric_class": "torch_em.self_training.mean_teacher.Dummy", 200 "metric_kwargs": {}, 201 }, 202 } 203 extra_state.update(**extra_save_dict) 204 super().save_checkpoint(name, current_metric, best_metric, **extra_state) 205 206 def load_checkpoint(self, checkpoint="best"): 207 """@private 208 """ 209 save_dict = super().load_checkpoint(checkpoint) 210 self.teacher.load_state_dict(save_dict["teacher_state"]) 211 self.teacher.to(self.device) 212 return save_dict 213 214 def _initialize(self, iterations, load_from_checkpoint, epochs=None): 215 best_metric = super()._initialize(iterations, load_from_checkpoint, epochs) 216 self.teacher.to(self.device) 217 return best_metric 218 219 # 220 # training and validation functionality 221 # 222 223 def _train_epoch_unsupervised(self, progress, forward_context, backprop): 224 self.model.train() 225 226 n_iter = 0 227 t_per_iter = time.time() 228 229 # Sample from both the supervised and unsupervised loader. 230 for xu1, xu2 in self.unsupervised_train_loader: 231 xu1, xu2 = xu1.to(self.device, non_blocking=True), xu2.to(self.device, non_blocking=True) 232 233 teacher_input, model_input = xu1, xu2 234 235 with forward_context(), torch.no_grad(): 236 # Compute the pseudo labels. 237 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 238 239 # If we have a sampler then check if the current batch matches the condition for inclusion in training. 240 if self.sampler is not None: 241 keep_batch = self.sampler(pseudo_labels, label_filter) 242 if not keep_batch: 243 continue 244 245 self.optimizer.zero_grad() 246 # Perform unsupervised training 247 with forward_context(): 248 loss = self.unsupervised_loss(self.model, model_input, pseudo_labels, label_filter) 249 backprop(loss) 250 251 if self.logger is not None: 252 with torch.no_grad(), forward_context(): 253 pred = self.model(model_input) if self._iteration % self.log_image_interval == 0 else None 254 self.logger.log_train_unsupervised( 255 self._iteration, loss, xu1, xu2, pred, pseudo_labels, label_filter 256 ) 257 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 258 self.logger.log_lr(self._iteration, lr) 259 if self.pseudo_labeler.confidence_threshold is not None: 260 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 261 262 with torch.no_grad(): 263 self._momentum_update() 264 265 self._iteration += 1 266 n_iter += 1 267 if self._iteration >= self.max_iteration: 268 break 269 progress.update(1) 270 271 t_per_iter = (time.time() - t_per_iter) / n_iter 272 return t_per_iter 273 274 def _train_epoch_semisupervised(self, progress, forward_context, backprop): 275 self.model.train() 276 277 n_iter = 0 278 t_per_iter = time.time() 279 280 # Sample from both the supervised and unsupervised loader. 281 for (xs, ys), (xu1, xu2) in zip(self.supervised_train_loader, self.unsupervised_train_loader): 282 xs, ys = xs.to(self.device, non_blocking=True), ys.to(self.device, non_blocking=True) 283 xu1, xu2 = xu1.to(self.device, non_blocking=True), xu2.to(self.device, non_blocking=True) 284 285 # Perform supervised training. 286 self.optimizer.zero_grad() 287 with forward_context(): 288 # We pass the model, the input and the labels to the supervised loss function, 289 # so that how the loss is calculated stays flexible, e.g. to enable ELBO for PUNet. 290 supervised_loss = self.supervised_loss(self.model, xs, ys) 291 292 if self.separate_backward: 293 # Free supervised activations before the unsupervised forward pass. 294 self._accumulate_gradients(supervised_loss / 2) 295 296 teacher_input, model_input = xu1, xu2 297 298 with forward_context(), torch.no_grad(): 299 # Compute the pseudo labels. 300 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 301 302 # Perform unsupervised training 303 with forward_context(): 304 unsupervised_loss = self.unsupervised_loss(self.model, model_input, pseudo_labels, label_filter) 305 306 loss = (supervised_loss + unsupervised_loss) / 2 307 backprop(unsupervised_loss / 2 if self.separate_backward else loss) 308 309 if self.logger is not None: 310 with torch.no_grad(), forward_context(): 311 unsup_pred = self.model(model_input) if self._iteration % self.log_image_interval == 0 else None 312 supervised_pred = self.model(xs) if self._iteration % self.log_image_interval == 0 else None 313 314 self.logger.log_train_supervised(self._iteration, supervised_loss, xs, ys, supervised_pred) 315 self.logger.log_train_unsupervised( 316 self._iteration, unsupervised_loss, xu1, xu2, unsup_pred, pseudo_labels, label_filter 317 ) 318 319 self.logger.log_combined_loss(self._iteration, loss) 320 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 321 self.logger.log_lr(self._iteration, lr) 322 if self.pseudo_labeler.confidence_threshold is not None: 323 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 324 325 with torch.no_grad(): 326 self._momentum_update() 327 328 self._iteration += 1 329 n_iter += 1 330 if self._iteration >= self.max_iteration: 331 break 332 progress.update(1) 333 334 t_per_iter = (time.time() - t_per_iter) / n_iter 335 return t_per_iter 336 337 def _validate_supervised(self, forward_context): 338 metric_val = 0.0 339 loss_val = 0.0 340 341 for x, y in self.supervised_val_loader: 342 x, y = x.to(self.device, non_blocking=True), y.to(self.device, non_blocking=True) 343 with forward_context(): 344 loss, metric = self.supervised_loss_and_metric(self.model, x, y) 345 loss_val += loss.item() 346 metric_val += metric.item() 347 348 metric_val /= len(self.supervised_val_loader) 349 loss_val /= len(self.supervised_val_loader) 350 351 if self.logger is not None: 352 with forward_context(): 353 pred = self.model(x) 354 self.logger.log_validation_supervised(self._iteration, metric_val, loss_val, x, y, pred) 355 356 return metric_val 357 358 def _validate_unsupervised(self, forward_context): 359 metric_val = 0.0 360 loss_val = 0.0 361 362 for x1, x2 in self.unsupervised_val_loader: 363 x1, x2 = x1.to(self.device, non_blocking=True), x2.to(self.device, non_blocking=True) 364 teacher_input, model_input = x1, x2 365 with forward_context(): 366 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 367 loss, metric = self.unsupervised_loss_and_metric(self.model, model_input, pseudo_labels, label_filter) 368 loss_val += loss.item() 369 metric_val += metric.item() 370 371 metric_val /= len(self.unsupervised_val_loader) 372 loss_val /= len(self.unsupervised_val_loader) 373 374 if self.logger is not None: 375 with forward_context(): 376 pred = self.model(model_input) 377 self.logger.log_validation_unsupervised( 378 self._iteration, metric_val, loss_val, x1, x2, pred, pseudo_labels, label_filter 379 ) 380 381 self.pseudo_labeler.step(metric_val, self._epoch) # NOTE: scheduler added in validation step 382 383 return metric_val 384 385 def _validate_impl(self, forward_context): 386 self.model.eval() 387 388 with torch.no_grad(): 389 390 if self.supervised_val_loader is None: 391 supervised_metric = None 392 else: 393 supervised_metric = self._validate_supervised(forward_context) 394 395 if self.unsupervised_val_loader is None: 396 unsupervised_metric = None 397 else: 398 unsupervised_metric = self._validate_unsupervised(forward_context) 399 400 if unsupervised_metric is None: 401 metric = supervised_metric 402 elif supervised_metric is None: 403 metric = unsupervised_metric 404 else: 405 metric = (supervised_metric + unsupervised_metric) / 2 406 407 return metric 408 409 410class MeanTeacherTrainerWithInvertibleAugmentations(MeanTeacherTrainer): 411 """Trainer for semi-supervised learning and domain adaptation following the MeanTeacher approach. 412 413 Mean Teacher was introduced by Tarvainen & Vapola in https://arxiv.org/abs/1703.01780. 414 It uses a teacher model derived from the student model via EMA of weights 415 to predict pseudo-labels on unlabeled data. We support two training strategies: 416 - Joint training on labeled and unlabeled data (with a supervised and unsupervised loss function). 417 - Training only on the unsupervised data. 418 419 This class expects the following data loaders: 420 - unsupervised_train_loader: Returns two augmentations of the same input. 421 - supervised_train_loader (optional): Returns input and labels. 422 - unsupervised_val_loader (optional): Same as unsupervised_train_loader 423 - supervised_val_loader (optional): Same as supervised_train_loader 424 At least one of unsupervised_val_loader and supervised_val_loader must be given. 425 426 The augmenter defines separate invertible transforms for teacher and student inputs. 427 Teacher and student views are generated independently, and the corresponding inverse 428 transforms map predictions and pseudo-labels back into a shared reference frame. 429 430 And the following elements to customize the pseudo labeling: 431 - pseudo_labeler: to compute the psuedo-labels 432 - Parameters: teacher, teacher_input 433 - Returns: pseudo_labels, label_filter (<- label filter can for example be mask, weight or None) 434 - unsupervised_loss: the loss between model predictions and pseudo labels 435 - Parameters: model, model_input, pseudo_labels, label_filter 436 - Returns: loss 437 - supervised_loss (optional): the supervised loss function 438 - Parameters: model, input, labels 439 - Returns: loss 440 - unsupervised_loss_and_metric (optional): the unsupervised loss function and metric 441 - Parameters: model, model_input, pseudo_labels, label_filter 442 - Returns: loss, metric 443 - supervised_loss_and_metric (optional): the supervised loss function and metric 444 - Parameters: model, input, labels 445 - Returns: loss, metric 446 At least one of unsupervised_loss_and_metric and supervised_loss_and_metric must be given. 447 448 If the parameter reinit_teacher is set to true, the teacher weights are re-initialized. 449 If it is None, the most appropriate initialization scheme for the training approach is chosen: 450 - semi-supervised training -> reinit, because we usually train a model from scratch 451 - unsupervised training -> do not reinit, because we usually fine-tune a model 452 453 Note: adjust the batch size ratio between the 'unsupervised_train_loader' and 'supervised_train_loader' 454 for setting the ratio between supervised and unsupervised training samples 455 456 Args: 457 model: The model to be trained. 458 unsupervised_train_loader: The loader for unsupervised training. 459 unsupervised_loss: The loss for unsupervised training. 460 pseudo_labeler: The pseudo labeler that predicts labels in unsupervised training. 461 augmenter: Invertible augmenter providing separate teacher and student transforms, 462 including corresponding inverse transforms to align predictions in a common frame. 463 supervised_train_loader: The loader for supervised training. 464 supervised_loss: The loss for supervised training. 465 unsupervised_loss_and_metric: The loss and metric for unsupervised training. 466 supervised_loss_and_metric: The loss and metrhic for supervised training. 467 logger: The logger. 468 momentum: The momentum value for the exponential moving weight average of the teacher model. 469 reinit_teacher: Whether to reinit the teacher model before starting the training. 470 sampler: A sampler for rejecting pseudo-labels according to a defined criterion. 471 separate_backward: Whether to backpropagate each loss term separately to reduce peak memory. 472 The default uses one backward pass on the combined loss. 473 kwargs: Additional keyword arguments for `torch_em.trainer.DefaultTrainer`. 474 """ 475 476 def __init__( 477 self, 478 model: torch.nn.Module, 479 unsupervised_train_loader: torch.utils.data.DataLoader, 480 unsupervised_loss: torch.utils.data.DataLoader, 481 pseudo_labeler: Callable, 482 augmenter: InvertibleAugmenter, 483 supervised_train_loader: Optional[torch.utils.data.DataLoader] = None, 484 unsupervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 485 supervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 486 supervised_loss: Optional[Callable] = None, 487 unsupervised_loss_and_metric: Optional[Callable] = None, 488 supervised_loss_and_metric: Optional[Callable] = None, 489 logger=SelfTrainingTensorboardLogger, 490 momentum: float = 0.999, 491 reinit_teacher: Optional[bool] = None, 492 sampler: Optional[Callable] = None, 493 separate_backward: bool = False, 494 **kwargs, 495 ): 496 super().__init__( 497 model=model, 498 unsupervised_train_loader=unsupervised_train_loader, 499 unsupervised_loss=unsupervised_loss, 500 pseudo_labeler=pseudo_labeler, 501 supervised_train_loader=supervised_train_loader, 502 unsupervised_val_loader=unsupervised_val_loader, 503 supervised_val_loader=supervised_val_loader, 504 supervised_loss=supervised_loss, 505 unsupervised_loss_and_metric=unsupervised_loss_and_metric, 506 supervised_loss_and_metric=supervised_loss_and_metric, 507 logger=logger, 508 momentum=momentum, 509 reinit_teacher=reinit_teacher, 510 sampler=sampler, 511 separate_backward=separate_backward, 512 **kwargs, 513 ) 514 self.augmenter = augmenter 515 516 # 517 # training and validation functionality 518 # 519 520 def _train_epoch_unsupervised(self, progress, forward_context, backprop): 521 self.model.train() 522 523 n_iter = 0 524 t_per_iter = time.time() 525 526 # Sample from both the supervised and unsupervised loader. 527 for xu in self.unsupervised_train_loader: 528 self.augmenter.reset_all() 529 xu = xu.to(self.device, non_blocking=True) 530 531 xu1, xu2 = self.augmenter.teacher.transform(xu), self.augmenter.student.transform(xu) 532 teacher_input, model_input = xu1, xu2 533 534 with forward_context(), torch.no_grad(): 535 # Compute the pseudo labels. 536 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 537 pseudo_labels_inv = self.augmenter.teacher.reverse_transform(pseudo_labels) 538 label_filter_inv = ( 539 self.augmenter.teacher.reverse_transform(label_filter) 540 if label_filter is not None else None 541 ) 542 543 # If we have a sampler then check if the current batch matches the condition for inclusion in training. 544 if self.sampler is not None: 545 keep_batch = self.sampler(pseudo_labels, label_filter) 546 if not keep_batch: 547 continue 548 549 self.optimizer.zero_grad() 550 # Perform unsupervised training 551 with forward_context(): 552 pred = self.model(model_input) 553 pred_inv = self.augmenter.student.reverse_transform(pred) 554 loss = self.unsupervised_loss(pred_inv, pseudo_labels_inv, label_filter_inv) 555 backprop(loss) 556 557 if self.logger is not None: 558 with torch.no_grad(), forward_context(): 559 pred = pred if self._iteration % self.log_image_interval == 0 else None 560 self.logger.log_train_unsupervised( 561 self._iteration, loss, xu, xu, pred_inv, pseudo_labels_inv, label_filter_inv 562 ) 563 self.logger.log_train_augmentations( 564 self._iteration, xu1, xu2, pseudo_labels, pred, 565 ) 566 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 567 self.logger.log_lr(self._iteration, lr) 568 if self.pseudo_labeler.confidence_threshold is not None: 569 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 570 571 with torch.no_grad(): 572 self._momentum_update() 573 574 self._iteration += 1 575 n_iter += 1 576 if self._iteration >= self.max_iteration: 577 break 578 progress.update(1) 579 580 t_per_iter = (time.time() - t_per_iter) / n_iter 581 return t_per_iter 582 583 def _train_epoch_semisupervised(self, progress, forward_context, backprop): 584 self.model.train() 585 586 n_iter = 0 587 t_per_iter = time.time() 588 589 # Sample from both the supervised and unsupervised loader. 590 for (xs, ys), xu in zip(self.supervised_train_loader, self.unsupervised_train_loader): 591 self.augmenter.reset_all() 592 xs, ys = xs.to(self.device, non_blocking=True), ys.to(self.device, non_blocking=True) 593 xu = xu.to(self.device, non_blocking=True) 594 595 xu1, xu2 = self.augmenter.teacher.transform(xu), self.augmenter.student.transform(xu) 596 teacher_input, model_input = xu1, xu2 597 598 # Perform supervised training. 599 self.optimizer.zero_grad() 600 with forward_context(): 601 # We pass the model, the input and the labels to the supervised loss function, 602 # so that how the loss is calculated stays flexible, e.g. to enable ELBO for PUNet. 603 supervised_pred = self.model(xs) 604 supervised_loss = self.supervised_loss(supervised_pred, ys) 605 606 if self.separate_backward: 607 # Free supervised activations before the unsupervised forward pass. 608 self._accumulate_gradients(supervised_loss / 2) 609 610 with forward_context(), torch.no_grad(): 611 # Compute the pseudo labels. 612 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 613 pseudo_labels_inv = self.augmenter.teacher.reverse_transform(pseudo_labels) 614 label_filter_inv = ( 615 self.augmenter.teacher.reverse_transform(label_filter) 616 if label_filter is not None else None 617 ) 618 619 # Perform unsupervised training 620 with forward_context(): 621 unsup_pred = self.model(model_input) 622 unsup_pred_inv = self.augmenter.student.reverse_transform(unsup_pred) 623 unsupervised_loss = self.unsupervised_loss(unsup_pred_inv, pseudo_labels_inv, label_filter_inv) 624 625 loss = (supervised_loss + unsupervised_loss) / 2 626 backprop(unsupervised_loss / 2 if self.separate_backward else loss) 627 628 if self.logger is not None: 629 with torch.no_grad(), forward_context(): 630 unsup_pred = unsup_pred if self._iteration % self.log_image_interval == 0 else None 631 supervised_pred = supervised_pred if self._iteration % self.log_image_interval == 0 else None 632 633 self.logger.log_train_supervised(self._iteration, supervised_loss, xs, ys, supervised_pred) 634 self.logger.log_train_unsupervised( 635 self._iteration, unsupervised_loss, xu, xu, unsup_pred_inv, pseudo_labels_inv, label_filter_inv 636 ) 637 self.logger.log_train_augmentations( 638 self._iteration, xu1, xu2, pseudo_labels, unsup_pred, 639 ) 640 641 self.logger.log_combined_loss(self._iteration, loss) 642 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 643 self.logger.log_lr(self._iteration, lr) 644 if self.pseudo_labeler.confidence_threshold is not None: 645 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 646 647 with torch.no_grad(): 648 self._momentum_update() 649 650 self._iteration += 1 651 n_iter += 1 652 if self._iteration >= self.max_iteration: 653 break 654 progress.update(1) 655 656 t_per_iter = (time.time() - t_per_iter) / n_iter 657 return t_per_iter 658 659 def _validate_supervised(self, forward_context): 660 metric_val = 0.0 661 loss_val = 0.0 662 663 for x, y in self.supervised_val_loader: 664 x, y = x.to(self.device, non_blocking=True), y.to(self.device, non_blocking=True) 665 with forward_context(): 666 pred = self.model(x) 667 loss, metric = self.supervised_loss_and_metric(pred, y) 668 loss_val += loss.item() 669 metric_val += metric.item() 670 671 metric_val /= len(self.supervised_val_loader) 672 loss_val /= len(self.supervised_val_loader) 673 674 if self.logger is not None: 675 self.logger.log_validation_supervised(self._iteration, metric_val, loss_val, x, y, pred) 676 677 return metric_val 678 679 def _validate_unsupervised(self, forward_context): 680 metric_val = 0.0 681 loss_val = 0.0 682 683 for x in self.unsupervised_val_loader: 684 self.augmenter.reset_all() 685 x = x.to(self.device, non_blocking=True) 686 x1, x2 = self.augmenter.teacher.transform(x), self.augmenter.student.transform(x) 687 teacher_input, model_input = x1, x2 688 with forward_context(): 689 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 690 pseudo_labels_inv = self.augmenter.teacher.reverse_transform(pseudo_labels) 691 label_filter_inv = ( 692 self.augmenter.teacher.reverse_transform(label_filter) 693 if label_filter is not None else None 694 ) 695 696 pred = self.model(model_input) 697 pred_inv = self.augmenter.student.reverse_transform(pred) 698 loss, metric = self.unsupervised_loss_and_metric(pred_inv, pseudo_labels_inv, label_filter_inv) 699 loss_val += loss.item() 700 metric_val += metric.item() 701 702 metric_val /= len(self.unsupervised_val_loader) 703 loss_val /= len(self.unsupervised_val_loader) 704 705 if self.logger is not None: 706 self.logger.log_validation_unsupervised( 707 self._iteration, metric_val, loss_val, x, x, pred_inv, pseudo_labels_inv, label_filter_inv 708 ) 709 self.logger.log_validation_augmentations( 710 self._iteration, x1, x2, pseudo_labels, pred, 711 ) 712 713 self.pseudo_labeler.step(metric_val, self._epoch) 714 715 return metric_val 716 717 def _validate_impl(self, forward_context): 718 self.model.eval() 719 720 with torch.no_grad(): 721 722 if self.supervised_val_loader is None: 723 supervised_metric = None 724 else: 725 supervised_metric = self._validate_supervised(forward_context) 726 727 if self.unsupervised_val_loader is None: 728 unsupervised_metric = None 729 else: 730 unsupervised_metric = self._validate_unsupervised(forward_context) 731 732 if unsupervised_metric is None: 733 metric = supervised_metric 734 elif supervised_metric is None: 735 metric = unsupervised_metric 736 else: 737 metric = (supervised_metric + unsupervised_metric) / 2 738 739 return metric
Base class for all neural network modules.
Your models should also subclass this class.
Modules can also contain other Modules, allowing them to be nested in a tree structure. You can assign the submodules as regular attributes::
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self) -> None:
super().__init__()
self.conv1 = nn.Conv2d(1, 20, 5)
self.conv2 = nn.Conv2d(20, 20, 5)
def forward(self, x):
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))
Submodules assigned in this way will be registered, and will also have their
parameters converted when you call to(), etc.
As per the example above, an __init__() call to the parent class
must be made before assignment on the child.
:ivar training: Boolean represents whether this module is in training or evaluation mode. :vartype training: bool
18class MeanTeacherTrainer(torch_em.trainer.DefaultTrainer): 19 """Trainer for semi-supervised learning and domain adaptation following the MeanTeacher approach. 20 21 Mean Teacher was introduced by Tarvainen & Vapola in https://arxiv.org/abs/1703.01780. 22 It uses a teacher model derived from the student model via EMA of weights 23 to predict pseudo-labels on unlabeled data. We support two training strategies: 24 - Joint training on labeled and unlabeled data (with a supervised and unsupervised loss function). 25 - Training only on the unsupervised data. 26 27 This class expects the following data loaders: 28 - unsupervised_train_loader: Returns two augmentations of the same input. 29 - supervised_train_loader (optional): Returns input and labels. 30 - unsupervised_val_loader (optional): Same as unsupervised_train_loader 31 - supervised_val_loader (optional): Same as supervised_train_loader 32 At least one of unsupervised_val_loader and supervised_val_loader must be given. 33 34 And the following elements to customize the pseudo labeling: 35 - pseudo_labeler: to compute the psuedo-labels 36 - Parameters: teacher, teacher_input 37 - Returns: pseudo_labels, label_filter (<- label filter can for example be mask, weight or None) 38 - unsupervised_loss: the loss between model predictions and pseudo labels 39 - Parameters: model, model_input, pseudo_labels, label_filter 40 - Returns: loss 41 - supervised_loss (optional): the supervised loss function 42 - Parameters: model, input, labels 43 - Returns: loss 44 - unsupervised_loss_and_metric (optional): the unsupervised loss function and metric 45 - Parameters: model, model_input, pseudo_labels, label_filter 46 - Returns: loss, metric 47 - supervised_loss_and_metric (optional): the supervised loss function and metric 48 - Parameters: model, input, labels 49 - Returns: loss, metric 50 At least one of unsupervised_loss_and_metric and supervised_loss_and_metric must be given. 51 52 If the parameter reinit_teacher is set to true, the teacher weights are re-initialized. 53 If it is None, the most appropriate initialization scheme for the training approach is chosen: 54 - semi-supervised training -> reinit, because we usually train a model from scratch 55 - unsupervised training -> do not reinit, because we usually fine-tune a model 56 57 Note: adjust the batch size ratio between the 'unsupervised_train_loader' and 'supervised_train_loader' 58 for setting the ratio between supervised and unsupervised training samples 59 60 Args: 61 model: The model to be trained. 62 unsupervised_train_loader: The loader for unsupervised training. 63 unsupervised_loss: The loss for unsupervised training. 64 pseudo_labeler: The pseudo labeler that predicts labels in unsupervised training. 65 supervised_train_loader: The loader for supervised training. 66 supervised_loss: The loss for supervised training. 67 unsupervised_loss_and_metric: The loss and metric for unsupervised training. 68 supervised_loss_and_metric: The loss and metrhic for supervised training. 69 logger: The logger. 70 momentum: The momentum value for the exponential moving weight average of the teacher model. 71 reinit_teacher: Whether to reinit the teacher model before starting the training. 72 sampler: A sampler for rejecting pseudo-labels according to a defined criterion. 73 separate_backward: Whether to backpropagate each loss term separately to reduce peak memory. 74 The default uses one backward pass on the combined loss. 75 kwargs: Additional keyword arguments for `torch_em.trainer.DefaultTrainer`. 76 """ 77 78 def __init__( 79 self, 80 model: torch.nn.Module, 81 unsupervised_train_loader: torch.utils.data.DataLoader, 82 unsupervised_loss: torch.utils.data.DataLoader, 83 pseudo_labeler: Callable, 84 supervised_train_loader: Optional[torch.utils.data.DataLoader] = None, 85 unsupervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 86 supervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 87 supervised_loss: Optional[Callable] = None, 88 unsupervised_loss_and_metric: Optional[Callable] = None, 89 supervised_loss_and_metric: Optional[Callable] = None, 90 logger=SelfTrainingTensorboardLogger, 91 momentum: float = 0.999, 92 reinit_teacher: Optional[bool] = None, 93 sampler: Optional[Callable] = None, 94 separate_backward: bool = False, 95 **kwargs, 96 ): 97 self.sampler = sampler 98 self.separate_backward = separate_backward 99 # Do we have supervised data or not? 100 if supervised_train_loader is None: 101 # No. -> We use the unsupervised training logic. 102 train_loader = unsupervised_train_loader 103 self._train_epoch_impl = self._train_epoch_unsupervised 104 else: 105 # Yes. -> We use the semi-supervised training logic. 106 assert supervised_loss is not None 107 train_loader = supervised_train_loader if len(supervised_train_loader) < len(unsupervised_train_loader)\ 108 else unsupervised_train_loader 109 self._train_epoch_impl = self._train_epoch_semisupervised 110 111 self.unsupervised_train_loader = unsupervised_train_loader 112 self.supervised_train_loader = supervised_train_loader 113 114 # Check that we have at least one of supvervised / unsupervised val loader. 115 assert sum(( 116 supervised_val_loader is not None, 117 unsupervised_val_loader is not None, 118 )) > 0 119 self.supervised_val_loader = supervised_val_loader 120 self.unsupervised_val_loader = unsupervised_val_loader 121 122 if self.unsupervised_val_loader is None: 123 val_loader = self.supervised_val_loader 124 else: 125 val_loader = self.unsupervised_train_loader 126 127 # Check that we have at least one of supvervised / unsupervised loss and metric. 128 assert sum(( 129 supervised_loss_and_metric is not None, 130 unsupervised_loss_and_metric is not None, 131 )) > 0 132 self.supervised_loss_and_metric = supervised_loss_and_metric 133 self.unsupervised_loss_and_metric = unsupervised_loss_and_metric 134 135 # train_loader, val_loader, loss and metric may be unnecessarily deserialized 136 kwargs.pop("train_loader", None) 137 kwargs.pop("val_loader", None) 138 kwargs.pop("metric", None) 139 kwargs.pop("loss", None) 140 super().__init__( 141 model=model, train_loader=train_loader, val_loader=val_loader, 142 loss=Dummy(), metric=Dummy(), logger=logger, **kwargs 143 ) 144 145 self.unsupervised_loss = unsupervised_loss 146 self.supervised_loss = supervised_loss 147 148 self.pseudo_labeler = pseudo_labeler 149 self.momentum = momentum 150 151 # determine how we initialize the teacher weights (copy or reinitialization) 152 if reinit_teacher is None: 153 # semisupervised training: reinitialize 154 # unsupervised training: copy 155 self.reinit_teacher = supervised_train_loader is not None 156 else: 157 self.reinit_teacher = reinit_teacher 158 159 with torch.no_grad(): 160 self.teacher = deepcopy(self.model) 161 if self.reinit_teacher: 162 for layer in self.teacher.children(): 163 if hasattr(layer, "reset_parameters"): 164 layer.reset_parameters() 165 for param in self.teacher.parameters(): 166 param.requires_grad = False 167 168 self._kwargs = kwargs 169 170 def _momentum_update(self): 171 # if we reinit the teacher we perform much faster updates (low momentum) in the first iterations 172 # to avoid a large gap between teacher and student weights, leading to inconsistent predictions 173 # if we don't reinit this is not necessary 174 if self.reinit_teacher: 175 current_momentum = min(1 - 1 / (self._iteration + 1), self.momentum) 176 else: 177 current_momentum = self.momentum 178 179 for param, param_teacher in zip(self.model.parameters(), self.teacher.parameters()): 180 param_teacher.data = param_teacher.data * current_momentum + param.data * (1. - current_momentum) 181 182 # 183 # functionality for saving checkpoints and initialization 184 # 185 186 def save_checkpoint(self, name, current_metric, best_metric, **extra_save_dict): 187 """@private 188 """ 189 train_loader_kwargs = get_constructor_arguments(self.train_loader) 190 val_loader_kwargs = get_constructor_arguments(self.val_loader) 191 extra_state = { 192 "teacher_state": self.teacher.state_dict(), 193 "init": { 194 "train_loader_kwargs": train_loader_kwargs, 195 "train_dataset": self.train_loader.dataset, 196 "val_loader_kwargs": val_loader_kwargs, 197 "val_dataset": self.val_loader.dataset, 198 "loss_class": "torch_em.self_training.mean_teacher.Dummy", 199 "loss_kwargs": {}, 200 "metric_class": "torch_em.self_training.mean_teacher.Dummy", 201 "metric_kwargs": {}, 202 }, 203 } 204 extra_state.update(**extra_save_dict) 205 super().save_checkpoint(name, current_metric, best_metric, **extra_state) 206 207 def load_checkpoint(self, checkpoint="best"): 208 """@private 209 """ 210 save_dict = super().load_checkpoint(checkpoint) 211 self.teacher.load_state_dict(save_dict["teacher_state"]) 212 self.teacher.to(self.device) 213 return save_dict 214 215 def _initialize(self, iterations, load_from_checkpoint, epochs=None): 216 best_metric = super()._initialize(iterations, load_from_checkpoint, epochs) 217 self.teacher.to(self.device) 218 return best_metric 219 220 # 221 # training and validation functionality 222 # 223 224 def _train_epoch_unsupervised(self, progress, forward_context, backprop): 225 self.model.train() 226 227 n_iter = 0 228 t_per_iter = time.time() 229 230 # Sample from both the supervised and unsupervised loader. 231 for xu1, xu2 in self.unsupervised_train_loader: 232 xu1, xu2 = xu1.to(self.device, non_blocking=True), xu2.to(self.device, non_blocking=True) 233 234 teacher_input, model_input = xu1, xu2 235 236 with forward_context(), torch.no_grad(): 237 # Compute the pseudo labels. 238 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 239 240 # If we have a sampler then check if the current batch matches the condition for inclusion in training. 241 if self.sampler is not None: 242 keep_batch = self.sampler(pseudo_labels, label_filter) 243 if not keep_batch: 244 continue 245 246 self.optimizer.zero_grad() 247 # Perform unsupervised training 248 with forward_context(): 249 loss = self.unsupervised_loss(self.model, model_input, pseudo_labels, label_filter) 250 backprop(loss) 251 252 if self.logger is not None: 253 with torch.no_grad(), forward_context(): 254 pred = self.model(model_input) if self._iteration % self.log_image_interval == 0 else None 255 self.logger.log_train_unsupervised( 256 self._iteration, loss, xu1, xu2, pred, pseudo_labels, label_filter 257 ) 258 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 259 self.logger.log_lr(self._iteration, lr) 260 if self.pseudo_labeler.confidence_threshold is not None: 261 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 262 263 with torch.no_grad(): 264 self._momentum_update() 265 266 self._iteration += 1 267 n_iter += 1 268 if self._iteration >= self.max_iteration: 269 break 270 progress.update(1) 271 272 t_per_iter = (time.time() - t_per_iter) / n_iter 273 return t_per_iter 274 275 def _train_epoch_semisupervised(self, progress, forward_context, backprop): 276 self.model.train() 277 278 n_iter = 0 279 t_per_iter = time.time() 280 281 # Sample from both the supervised and unsupervised loader. 282 for (xs, ys), (xu1, xu2) in zip(self.supervised_train_loader, self.unsupervised_train_loader): 283 xs, ys = xs.to(self.device, non_blocking=True), ys.to(self.device, non_blocking=True) 284 xu1, xu2 = xu1.to(self.device, non_blocking=True), xu2.to(self.device, non_blocking=True) 285 286 # Perform supervised training. 287 self.optimizer.zero_grad() 288 with forward_context(): 289 # We pass the model, the input and the labels to the supervised loss function, 290 # so that how the loss is calculated stays flexible, e.g. to enable ELBO for PUNet. 291 supervised_loss = self.supervised_loss(self.model, xs, ys) 292 293 if self.separate_backward: 294 # Free supervised activations before the unsupervised forward pass. 295 self._accumulate_gradients(supervised_loss / 2) 296 297 teacher_input, model_input = xu1, xu2 298 299 with forward_context(), torch.no_grad(): 300 # Compute the pseudo labels. 301 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 302 303 # Perform unsupervised training 304 with forward_context(): 305 unsupervised_loss = self.unsupervised_loss(self.model, model_input, pseudo_labels, label_filter) 306 307 loss = (supervised_loss + unsupervised_loss) / 2 308 backprop(unsupervised_loss / 2 if self.separate_backward else loss) 309 310 if self.logger is not None: 311 with torch.no_grad(), forward_context(): 312 unsup_pred = self.model(model_input) if self._iteration % self.log_image_interval == 0 else None 313 supervised_pred = self.model(xs) if self._iteration % self.log_image_interval == 0 else None 314 315 self.logger.log_train_supervised(self._iteration, supervised_loss, xs, ys, supervised_pred) 316 self.logger.log_train_unsupervised( 317 self._iteration, unsupervised_loss, xu1, xu2, unsup_pred, pseudo_labels, label_filter 318 ) 319 320 self.logger.log_combined_loss(self._iteration, loss) 321 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 322 self.logger.log_lr(self._iteration, lr) 323 if self.pseudo_labeler.confidence_threshold is not None: 324 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 325 326 with torch.no_grad(): 327 self._momentum_update() 328 329 self._iteration += 1 330 n_iter += 1 331 if self._iteration >= self.max_iteration: 332 break 333 progress.update(1) 334 335 t_per_iter = (time.time() - t_per_iter) / n_iter 336 return t_per_iter 337 338 def _validate_supervised(self, forward_context): 339 metric_val = 0.0 340 loss_val = 0.0 341 342 for x, y in self.supervised_val_loader: 343 x, y = x.to(self.device, non_blocking=True), y.to(self.device, non_blocking=True) 344 with forward_context(): 345 loss, metric = self.supervised_loss_and_metric(self.model, x, y) 346 loss_val += loss.item() 347 metric_val += metric.item() 348 349 metric_val /= len(self.supervised_val_loader) 350 loss_val /= len(self.supervised_val_loader) 351 352 if self.logger is not None: 353 with forward_context(): 354 pred = self.model(x) 355 self.logger.log_validation_supervised(self._iteration, metric_val, loss_val, x, y, pred) 356 357 return metric_val 358 359 def _validate_unsupervised(self, forward_context): 360 metric_val = 0.0 361 loss_val = 0.0 362 363 for x1, x2 in self.unsupervised_val_loader: 364 x1, x2 = x1.to(self.device, non_blocking=True), x2.to(self.device, non_blocking=True) 365 teacher_input, model_input = x1, x2 366 with forward_context(): 367 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 368 loss, metric = self.unsupervised_loss_and_metric(self.model, model_input, pseudo_labels, label_filter) 369 loss_val += loss.item() 370 metric_val += metric.item() 371 372 metric_val /= len(self.unsupervised_val_loader) 373 loss_val /= len(self.unsupervised_val_loader) 374 375 if self.logger is not None: 376 with forward_context(): 377 pred = self.model(model_input) 378 self.logger.log_validation_unsupervised( 379 self._iteration, metric_val, loss_val, x1, x2, pred, pseudo_labels, label_filter 380 ) 381 382 self.pseudo_labeler.step(metric_val, self._epoch) # NOTE: scheduler added in validation step 383 384 return metric_val 385 386 def _validate_impl(self, forward_context): 387 self.model.eval() 388 389 with torch.no_grad(): 390 391 if self.supervised_val_loader is None: 392 supervised_metric = None 393 else: 394 supervised_metric = self._validate_supervised(forward_context) 395 396 if self.unsupervised_val_loader is None: 397 unsupervised_metric = None 398 else: 399 unsupervised_metric = self._validate_unsupervised(forward_context) 400 401 if unsupervised_metric is None: 402 metric = supervised_metric 403 elif supervised_metric is None: 404 metric = unsupervised_metric 405 else: 406 metric = (supervised_metric + unsupervised_metric) / 2 407 408 return metric
Trainer for semi-supervised learning and domain adaptation following the MeanTeacher approach.
Mean Teacher was introduced by Tarvainen & Vapola in https://arxiv.org/abs/1703.01780. It uses a teacher model derived from the student model via EMA of weights to predict pseudo-labels on unlabeled data. We support two training strategies:
- Joint training on labeled and unlabeled data (with a supervised and unsupervised loss function).
- Training only on the unsupervised data.
This class expects the following data loaders:
- unsupervised_train_loader: Returns two augmentations of the same input.
- supervised_train_loader (optional): Returns input and labels.
- unsupervised_val_loader (optional): Same as unsupervised_train_loader
- supervised_val_loader (optional): Same as supervised_train_loader At least one of unsupervised_val_loader and supervised_val_loader must be given.
And the following elements to customize the pseudo labeling:
- pseudo_labeler: to compute the psuedo-labels
- Parameters: teacher, teacher_input
- Returns: pseudo_labels, label_filter (<- label filter can for example be mask, weight or None)
- unsupervised_loss: the loss between model predictions and pseudo labels
- Parameters: model, model_input, pseudo_labels, label_filter
- Returns: loss
- supervised_loss (optional): the supervised loss function
- Parameters: model, input, labels
- Returns: loss
- unsupervised_loss_and_metric (optional): the unsupervised loss function and metric
- Parameters: model, model_input, pseudo_labels, label_filter
- Returns: loss, metric
- supervised_loss_and_metric (optional): the supervised loss function and metric
- Parameters: model, input, labels
- Returns: loss, metric At least one of unsupervised_loss_and_metric and supervised_loss_and_metric must be given.
If the parameter reinit_teacher is set to true, the teacher weights are re-initialized. If it is None, the most appropriate initialization scheme for the training approach is chosen:
- semi-supervised training -> reinit, because we usually train a model from scratch
- unsupervised training -> do not reinit, because we usually fine-tune a model
Note: adjust the batch size ratio between the 'unsupervised_train_loader' and 'supervised_train_loader' for setting the ratio between supervised and unsupervised training samples
Arguments:
- model: The model to be trained.
- unsupervised_train_loader: The loader for unsupervised training.
- unsupervised_loss: The loss for unsupervised training.
- pseudo_labeler: The pseudo labeler that predicts labels in unsupervised training.
- supervised_train_loader: The loader for supervised training.
- supervised_loss: The loss for supervised training.
- unsupervised_loss_and_metric: The loss and metric for unsupervised training.
- supervised_loss_and_metric: The loss and metrhic for supervised training.
- logger: The logger.
- momentum: The momentum value for the exponential moving weight average of the teacher model.
- reinit_teacher: Whether to reinit the teacher model before starting the training.
- sampler: A sampler for rejecting pseudo-labels according to a defined criterion.
- separate_backward: Whether to backpropagate each loss term separately to reduce peak memory. The default uses one backward pass on the combined loss.
- kwargs: Additional keyword arguments for
torch_em.trainer.DefaultTrainer.
78 def __init__( 79 self, 80 model: torch.nn.Module, 81 unsupervised_train_loader: torch.utils.data.DataLoader, 82 unsupervised_loss: torch.utils.data.DataLoader, 83 pseudo_labeler: Callable, 84 supervised_train_loader: Optional[torch.utils.data.DataLoader] = None, 85 unsupervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 86 supervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 87 supervised_loss: Optional[Callable] = None, 88 unsupervised_loss_and_metric: Optional[Callable] = None, 89 supervised_loss_and_metric: Optional[Callable] = None, 90 logger=SelfTrainingTensorboardLogger, 91 momentum: float = 0.999, 92 reinit_teacher: Optional[bool] = None, 93 sampler: Optional[Callable] = None, 94 separate_backward: bool = False, 95 **kwargs, 96 ): 97 self.sampler = sampler 98 self.separate_backward = separate_backward 99 # Do we have supervised data or not? 100 if supervised_train_loader is None: 101 # No. -> We use the unsupervised training logic. 102 train_loader = unsupervised_train_loader 103 self._train_epoch_impl = self._train_epoch_unsupervised 104 else: 105 # Yes. -> We use the semi-supervised training logic. 106 assert supervised_loss is not None 107 train_loader = supervised_train_loader if len(supervised_train_loader) < len(unsupervised_train_loader)\ 108 else unsupervised_train_loader 109 self._train_epoch_impl = self._train_epoch_semisupervised 110 111 self.unsupervised_train_loader = unsupervised_train_loader 112 self.supervised_train_loader = supervised_train_loader 113 114 # Check that we have at least one of supvervised / unsupervised val loader. 115 assert sum(( 116 supervised_val_loader is not None, 117 unsupervised_val_loader is not None, 118 )) > 0 119 self.supervised_val_loader = supervised_val_loader 120 self.unsupervised_val_loader = unsupervised_val_loader 121 122 if self.unsupervised_val_loader is None: 123 val_loader = self.supervised_val_loader 124 else: 125 val_loader = self.unsupervised_train_loader 126 127 # Check that we have at least one of supvervised / unsupervised loss and metric. 128 assert sum(( 129 supervised_loss_and_metric is not None, 130 unsupervised_loss_and_metric is not None, 131 )) > 0 132 self.supervised_loss_and_metric = supervised_loss_and_metric 133 self.unsupervised_loss_and_metric = unsupervised_loss_and_metric 134 135 # train_loader, val_loader, loss and metric may be unnecessarily deserialized 136 kwargs.pop("train_loader", None) 137 kwargs.pop("val_loader", None) 138 kwargs.pop("metric", None) 139 kwargs.pop("loss", None) 140 super().__init__( 141 model=model, train_loader=train_loader, val_loader=val_loader, 142 loss=Dummy(), metric=Dummy(), logger=logger, **kwargs 143 ) 144 145 self.unsupervised_loss = unsupervised_loss 146 self.supervised_loss = supervised_loss 147 148 self.pseudo_labeler = pseudo_labeler 149 self.momentum = momentum 150 151 # determine how we initialize the teacher weights (copy or reinitialization) 152 if reinit_teacher is None: 153 # semisupervised training: reinitialize 154 # unsupervised training: copy 155 self.reinit_teacher = supervised_train_loader is not None 156 else: 157 self.reinit_teacher = reinit_teacher 158 159 with torch.no_grad(): 160 self.teacher = deepcopy(self.model) 161 if self.reinit_teacher: 162 for layer in self.teacher.children(): 163 if hasattr(layer, "reset_parameters"): 164 layer.reset_parameters() 165 for param in self.teacher.parameters(): 166 param.requires_grad = False 167 168 self._kwargs = kwargs
Inherited Members
- torch_em.trainer.default_trainer.DefaultTrainer
- name
- id_
- train_loader
- val_loader
- model
- loss
- optimizer
- metric
- device
- lr_scheduler
- log_image_interval
- save_root
- compile_model
- rank
- mixed_precision
- mixed_precision_dtype
- early_stopping
- train_time
- logger_class
- logger_kwargs
- checkpoint_folder
- iteration
- epoch
- Deserializer
- Serializer
- fit
411class MeanTeacherTrainerWithInvertibleAugmentations(MeanTeacherTrainer): 412 """Trainer for semi-supervised learning and domain adaptation following the MeanTeacher approach. 413 414 Mean Teacher was introduced by Tarvainen & Vapola in https://arxiv.org/abs/1703.01780. 415 It uses a teacher model derived from the student model via EMA of weights 416 to predict pseudo-labels on unlabeled data. We support two training strategies: 417 - Joint training on labeled and unlabeled data (with a supervised and unsupervised loss function). 418 - Training only on the unsupervised data. 419 420 This class expects the following data loaders: 421 - unsupervised_train_loader: Returns two augmentations of the same input. 422 - supervised_train_loader (optional): Returns input and labels. 423 - unsupervised_val_loader (optional): Same as unsupervised_train_loader 424 - supervised_val_loader (optional): Same as supervised_train_loader 425 At least one of unsupervised_val_loader and supervised_val_loader must be given. 426 427 The augmenter defines separate invertible transforms for teacher and student inputs. 428 Teacher and student views are generated independently, and the corresponding inverse 429 transforms map predictions and pseudo-labels back into a shared reference frame. 430 431 And the following elements to customize the pseudo labeling: 432 - pseudo_labeler: to compute the psuedo-labels 433 - Parameters: teacher, teacher_input 434 - Returns: pseudo_labels, label_filter (<- label filter can for example be mask, weight or None) 435 - unsupervised_loss: the loss between model predictions and pseudo labels 436 - Parameters: model, model_input, pseudo_labels, label_filter 437 - Returns: loss 438 - supervised_loss (optional): the supervised loss function 439 - Parameters: model, input, labels 440 - Returns: loss 441 - unsupervised_loss_and_metric (optional): the unsupervised loss function and metric 442 - Parameters: model, model_input, pseudo_labels, label_filter 443 - Returns: loss, metric 444 - supervised_loss_and_metric (optional): the supervised loss function and metric 445 - Parameters: model, input, labels 446 - Returns: loss, metric 447 At least one of unsupervised_loss_and_metric and supervised_loss_and_metric must be given. 448 449 If the parameter reinit_teacher is set to true, the teacher weights are re-initialized. 450 If it is None, the most appropriate initialization scheme for the training approach is chosen: 451 - semi-supervised training -> reinit, because we usually train a model from scratch 452 - unsupervised training -> do not reinit, because we usually fine-tune a model 453 454 Note: adjust the batch size ratio between the 'unsupervised_train_loader' and 'supervised_train_loader' 455 for setting the ratio between supervised and unsupervised training samples 456 457 Args: 458 model: The model to be trained. 459 unsupervised_train_loader: The loader for unsupervised training. 460 unsupervised_loss: The loss for unsupervised training. 461 pseudo_labeler: The pseudo labeler that predicts labels in unsupervised training. 462 augmenter: Invertible augmenter providing separate teacher and student transforms, 463 including corresponding inverse transforms to align predictions in a common frame. 464 supervised_train_loader: The loader for supervised training. 465 supervised_loss: The loss for supervised training. 466 unsupervised_loss_and_metric: The loss and metric for unsupervised training. 467 supervised_loss_and_metric: The loss and metrhic for supervised training. 468 logger: The logger. 469 momentum: The momentum value for the exponential moving weight average of the teacher model. 470 reinit_teacher: Whether to reinit the teacher model before starting the training. 471 sampler: A sampler for rejecting pseudo-labels according to a defined criterion. 472 separate_backward: Whether to backpropagate each loss term separately to reduce peak memory. 473 The default uses one backward pass on the combined loss. 474 kwargs: Additional keyword arguments for `torch_em.trainer.DefaultTrainer`. 475 """ 476 477 def __init__( 478 self, 479 model: torch.nn.Module, 480 unsupervised_train_loader: torch.utils.data.DataLoader, 481 unsupervised_loss: torch.utils.data.DataLoader, 482 pseudo_labeler: Callable, 483 augmenter: InvertibleAugmenter, 484 supervised_train_loader: Optional[torch.utils.data.DataLoader] = None, 485 unsupervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 486 supervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 487 supervised_loss: Optional[Callable] = None, 488 unsupervised_loss_and_metric: Optional[Callable] = None, 489 supervised_loss_and_metric: Optional[Callable] = None, 490 logger=SelfTrainingTensorboardLogger, 491 momentum: float = 0.999, 492 reinit_teacher: Optional[bool] = None, 493 sampler: Optional[Callable] = None, 494 separate_backward: bool = False, 495 **kwargs, 496 ): 497 super().__init__( 498 model=model, 499 unsupervised_train_loader=unsupervised_train_loader, 500 unsupervised_loss=unsupervised_loss, 501 pseudo_labeler=pseudo_labeler, 502 supervised_train_loader=supervised_train_loader, 503 unsupervised_val_loader=unsupervised_val_loader, 504 supervised_val_loader=supervised_val_loader, 505 supervised_loss=supervised_loss, 506 unsupervised_loss_and_metric=unsupervised_loss_and_metric, 507 supervised_loss_and_metric=supervised_loss_and_metric, 508 logger=logger, 509 momentum=momentum, 510 reinit_teacher=reinit_teacher, 511 sampler=sampler, 512 separate_backward=separate_backward, 513 **kwargs, 514 ) 515 self.augmenter = augmenter 516 517 # 518 # training and validation functionality 519 # 520 521 def _train_epoch_unsupervised(self, progress, forward_context, backprop): 522 self.model.train() 523 524 n_iter = 0 525 t_per_iter = time.time() 526 527 # Sample from both the supervised and unsupervised loader. 528 for xu in self.unsupervised_train_loader: 529 self.augmenter.reset_all() 530 xu = xu.to(self.device, non_blocking=True) 531 532 xu1, xu2 = self.augmenter.teacher.transform(xu), self.augmenter.student.transform(xu) 533 teacher_input, model_input = xu1, xu2 534 535 with forward_context(), torch.no_grad(): 536 # Compute the pseudo labels. 537 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 538 pseudo_labels_inv = self.augmenter.teacher.reverse_transform(pseudo_labels) 539 label_filter_inv = ( 540 self.augmenter.teacher.reverse_transform(label_filter) 541 if label_filter is not None else None 542 ) 543 544 # If we have a sampler then check if the current batch matches the condition for inclusion in training. 545 if self.sampler is not None: 546 keep_batch = self.sampler(pseudo_labels, label_filter) 547 if not keep_batch: 548 continue 549 550 self.optimizer.zero_grad() 551 # Perform unsupervised training 552 with forward_context(): 553 pred = self.model(model_input) 554 pred_inv = self.augmenter.student.reverse_transform(pred) 555 loss = self.unsupervised_loss(pred_inv, pseudo_labels_inv, label_filter_inv) 556 backprop(loss) 557 558 if self.logger is not None: 559 with torch.no_grad(), forward_context(): 560 pred = pred if self._iteration % self.log_image_interval == 0 else None 561 self.logger.log_train_unsupervised( 562 self._iteration, loss, xu, xu, pred_inv, pseudo_labels_inv, label_filter_inv 563 ) 564 self.logger.log_train_augmentations( 565 self._iteration, xu1, xu2, pseudo_labels, pred, 566 ) 567 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 568 self.logger.log_lr(self._iteration, lr) 569 if self.pseudo_labeler.confidence_threshold is not None: 570 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 571 572 with torch.no_grad(): 573 self._momentum_update() 574 575 self._iteration += 1 576 n_iter += 1 577 if self._iteration >= self.max_iteration: 578 break 579 progress.update(1) 580 581 t_per_iter = (time.time() - t_per_iter) / n_iter 582 return t_per_iter 583 584 def _train_epoch_semisupervised(self, progress, forward_context, backprop): 585 self.model.train() 586 587 n_iter = 0 588 t_per_iter = time.time() 589 590 # Sample from both the supervised and unsupervised loader. 591 for (xs, ys), xu in zip(self.supervised_train_loader, self.unsupervised_train_loader): 592 self.augmenter.reset_all() 593 xs, ys = xs.to(self.device, non_blocking=True), ys.to(self.device, non_blocking=True) 594 xu = xu.to(self.device, non_blocking=True) 595 596 xu1, xu2 = self.augmenter.teacher.transform(xu), self.augmenter.student.transform(xu) 597 teacher_input, model_input = xu1, xu2 598 599 # Perform supervised training. 600 self.optimizer.zero_grad() 601 with forward_context(): 602 # We pass the model, the input and the labels to the supervised loss function, 603 # so that how the loss is calculated stays flexible, e.g. to enable ELBO for PUNet. 604 supervised_pred = self.model(xs) 605 supervised_loss = self.supervised_loss(supervised_pred, ys) 606 607 if self.separate_backward: 608 # Free supervised activations before the unsupervised forward pass. 609 self._accumulate_gradients(supervised_loss / 2) 610 611 with forward_context(), torch.no_grad(): 612 # Compute the pseudo labels. 613 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 614 pseudo_labels_inv = self.augmenter.teacher.reverse_transform(pseudo_labels) 615 label_filter_inv = ( 616 self.augmenter.teacher.reverse_transform(label_filter) 617 if label_filter is not None else None 618 ) 619 620 # Perform unsupervised training 621 with forward_context(): 622 unsup_pred = self.model(model_input) 623 unsup_pred_inv = self.augmenter.student.reverse_transform(unsup_pred) 624 unsupervised_loss = self.unsupervised_loss(unsup_pred_inv, pseudo_labels_inv, label_filter_inv) 625 626 loss = (supervised_loss + unsupervised_loss) / 2 627 backprop(unsupervised_loss / 2 if self.separate_backward else loss) 628 629 if self.logger is not None: 630 with torch.no_grad(), forward_context(): 631 unsup_pred = unsup_pred if self._iteration % self.log_image_interval == 0 else None 632 supervised_pred = supervised_pred if self._iteration % self.log_image_interval == 0 else None 633 634 self.logger.log_train_supervised(self._iteration, supervised_loss, xs, ys, supervised_pred) 635 self.logger.log_train_unsupervised( 636 self._iteration, unsupervised_loss, xu, xu, unsup_pred_inv, pseudo_labels_inv, label_filter_inv 637 ) 638 self.logger.log_train_augmentations( 639 self._iteration, xu1, xu2, pseudo_labels, unsup_pred, 640 ) 641 642 self.logger.log_combined_loss(self._iteration, loss) 643 lr = [pm["lr"] for pm in self.optimizer.param_groups][0] 644 self.logger.log_lr(self._iteration, lr) 645 if self.pseudo_labeler.confidence_threshold is not None: 646 self.logger.log_ct(self._iteration, self.pseudo_labeler.confidence_threshold) 647 648 with torch.no_grad(): 649 self._momentum_update() 650 651 self._iteration += 1 652 n_iter += 1 653 if self._iteration >= self.max_iteration: 654 break 655 progress.update(1) 656 657 t_per_iter = (time.time() - t_per_iter) / n_iter 658 return t_per_iter 659 660 def _validate_supervised(self, forward_context): 661 metric_val = 0.0 662 loss_val = 0.0 663 664 for x, y in self.supervised_val_loader: 665 x, y = x.to(self.device, non_blocking=True), y.to(self.device, non_blocking=True) 666 with forward_context(): 667 pred = self.model(x) 668 loss, metric = self.supervised_loss_and_metric(pred, y) 669 loss_val += loss.item() 670 metric_val += metric.item() 671 672 metric_val /= len(self.supervised_val_loader) 673 loss_val /= len(self.supervised_val_loader) 674 675 if self.logger is not None: 676 self.logger.log_validation_supervised(self._iteration, metric_val, loss_val, x, y, pred) 677 678 return metric_val 679 680 def _validate_unsupervised(self, forward_context): 681 metric_val = 0.0 682 loss_val = 0.0 683 684 for x in self.unsupervised_val_loader: 685 self.augmenter.reset_all() 686 x = x.to(self.device, non_blocking=True) 687 x1, x2 = self.augmenter.teacher.transform(x), self.augmenter.student.transform(x) 688 teacher_input, model_input = x1, x2 689 with forward_context(): 690 pseudo_labels, label_filter = self.pseudo_labeler(self.teacher, teacher_input) 691 pseudo_labels_inv = self.augmenter.teacher.reverse_transform(pseudo_labels) 692 label_filter_inv = ( 693 self.augmenter.teacher.reverse_transform(label_filter) 694 if label_filter is not None else None 695 ) 696 697 pred = self.model(model_input) 698 pred_inv = self.augmenter.student.reverse_transform(pred) 699 loss, metric = self.unsupervised_loss_and_metric(pred_inv, pseudo_labels_inv, label_filter_inv) 700 loss_val += loss.item() 701 metric_val += metric.item() 702 703 metric_val /= len(self.unsupervised_val_loader) 704 loss_val /= len(self.unsupervised_val_loader) 705 706 if self.logger is not None: 707 self.logger.log_validation_unsupervised( 708 self._iteration, metric_val, loss_val, x, x, pred_inv, pseudo_labels_inv, label_filter_inv 709 ) 710 self.logger.log_validation_augmentations( 711 self._iteration, x1, x2, pseudo_labels, pred, 712 ) 713 714 self.pseudo_labeler.step(metric_val, self._epoch) 715 716 return metric_val 717 718 def _validate_impl(self, forward_context): 719 self.model.eval() 720 721 with torch.no_grad(): 722 723 if self.supervised_val_loader is None: 724 supervised_metric = None 725 else: 726 supervised_metric = self._validate_supervised(forward_context) 727 728 if self.unsupervised_val_loader is None: 729 unsupervised_metric = None 730 else: 731 unsupervised_metric = self._validate_unsupervised(forward_context) 732 733 if unsupervised_metric is None: 734 metric = supervised_metric 735 elif supervised_metric is None: 736 metric = unsupervised_metric 737 else: 738 metric = (supervised_metric + unsupervised_metric) / 2 739 740 return metric
Trainer for semi-supervised learning and domain adaptation following the MeanTeacher approach.
Mean Teacher was introduced by Tarvainen & Vapola in https://arxiv.org/abs/1703.01780. It uses a teacher model derived from the student model via EMA of weights to predict pseudo-labels on unlabeled data. We support two training strategies:
- Joint training on labeled and unlabeled data (with a supervised and unsupervised loss function).
- Training only on the unsupervised data.
This class expects the following data loaders:
- unsupervised_train_loader: Returns two augmentations of the same input.
- supervised_train_loader (optional): Returns input and labels.
- unsupervised_val_loader (optional): Same as unsupervised_train_loader
- supervised_val_loader (optional): Same as supervised_train_loader At least one of unsupervised_val_loader and supervised_val_loader must be given.
The augmenter defines separate invertible transforms for teacher and student inputs. Teacher and student views are generated independently, and the corresponding inverse transforms map predictions and pseudo-labels back into a shared reference frame.
And the following elements to customize the pseudo labeling:
- pseudo_labeler: to compute the psuedo-labels
- Parameters: teacher, teacher_input
- Returns: pseudo_labels, label_filter (<- label filter can for example be mask, weight or None)
- unsupervised_loss: the loss between model predictions and pseudo labels
- Parameters: model, model_input, pseudo_labels, label_filter
- Returns: loss
- supervised_loss (optional): the supervised loss function
- Parameters: model, input, labels
- Returns: loss
- unsupervised_loss_and_metric (optional): the unsupervised loss function and metric
- Parameters: model, model_input, pseudo_labels, label_filter
- Returns: loss, metric
- supervised_loss_and_metric (optional): the supervised loss function and metric
- Parameters: model, input, labels
- Returns: loss, metric At least one of unsupervised_loss_and_metric and supervised_loss_and_metric must be given.
If the parameter reinit_teacher is set to true, the teacher weights are re-initialized. If it is None, the most appropriate initialization scheme for the training approach is chosen:
- semi-supervised training -> reinit, because we usually train a model from scratch
- unsupervised training -> do not reinit, because we usually fine-tune a model
Note: adjust the batch size ratio between the 'unsupervised_train_loader' and 'supervised_train_loader' for setting the ratio between supervised and unsupervised training samples
Arguments:
- model: The model to be trained.
- unsupervised_train_loader: The loader for unsupervised training.
- unsupervised_loss: The loss for unsupervised training.
- pseudo_labeler: The pseudo labeler that predicts labels in unsupervised training.
- augmenter: Invertible augmenter providing separate teacher and student transforms, including corresponding inverse transforms to align predictions in a common frame.
- supervised_train_loader: The loader for supervised training.
- supervised_loss: The loss for supervised training.
- unsupervised_loss_and_metric: The loss and metric for unsupervised training.
- supervised_loss_and_metric: The loss and metrhic for supervised training.
- logger: The logger.
- momentum: The momentum value for the exponential moving weight average of the teacher model.
- reinit_teacher: Whether to reinit the teacher model before starting the training.
- sampler: A sampler for rejecting pseudo-labels according to a defined criterion.
- separate_backward: Whether to backpropagate each loss term separately to reduce peak memory. The default uses one backward pass on the combined loss.
- kwargs: Additional keyword arguments for
torch_em.trainer.DefaultTrainer.
477 def __init__( 478 self, 479 model: torch.nn.Module, 480 unsupervised_train_loader: torch.utils.data.DataLoader, 481 unsupervised_loss: torch.utils.data.DataLoader, 482 pseudo_labeler: Callable, 483 augmenter: InvertibleAugmenter, 484 supervised_train_loader: Optional[torch.utils.data.DataLoader] = None, 485 unsupervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 486 supervised_val_loader: Optional[torch.utils.data.DataLoader] = None, 487 supervised_loss: Optional[Callable] = None, 488 unsupervised_loss_and_metric: Optional[Callable] = None, 489 supervised_loss_and_metric: Optional[Callable] = None, 490 logger=SelfTrainingTensorboardLogger, 491 momentum: float = 0.999, 492 reinit_teacher: Optional[bool] = None, 493 sampler: Optional[Callable] = None, 494 separate_backward: bool = False, 495 **kwargs, 496 ): 497 super().__init__( 498 model=model, 499 unsupervised_train_loader=unsupervised_train_loader, 500 unsupervised_loss=unsupervised_loss, 501 pseudo_labeler=pseudo_labeler, 502 supervised_train_loader=supervised_train_loader, 503 unsupervised_val_loader=unsupervised_val_loader, 504 supervised_val_loader=supervised_val_loader, 505 supervised_loss=supervised_loss, 506 unsupervised_loss_and_metric=unsupervised_loss_and_metric, 507 supervised_loss_and_metric=supervised_loss_and_metric, 508 logger=logger, 509 momentum=momentum, 510 reinit_teacher=reinit_teacher, 511 sampler=sampler, 512 separate_backward=separate_backward, 513 **kwargs, 514 ) 515 self.augmenter = augmenter
Inherited Members
- MeanTeacherTrainer
- sampler
- separate_backward
- unsupervised_train_loader
- supervised_train_loader
- supervised_val_loader
- unsupervised_val_loader
- supervised_loss_and_metric
- unsupervised_loss_and_metric
- unsupervised_loss
- supervised_loss
- pseudo_labeler
- momentum
- torch_em.trainer.default_trainer.DefaultTrainer
- name
- id_
- train_loader
- val_loader
- model
- loss
- optimizer
- metric
- device
- lr_scheduler
- log_image_interval
- save_root
- compile_model
- rank
- mixed_precision
- mixed_precision_dtype
- early_stopping
- train_time
- logger_class
- logger_kwargs
- checkpoint_folder
- iteration
- epoch
- Deserializer
- Serializer
- fit