factory.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. """Construction and loading of ensemble models for evaluation.
  2. Training saves each member's ``state_dict`` to ``model_N.pt``. Evaluation must
  3. rebuild an identically-structured module before ``load_state_dict``. For Bayesian
  4. members this means applying the DNN->BNN conversion (with the same prior params
  5. used at training time) *before* loading weights, otherwise the parameter names
  6. won't match.
  7. """
  8. import pathlib as pl
  9. from typing import Any, Dict
  10. import torch
  11. from torch import nn
  12. from model.cnn import CNN3D
  13. from model.dnn_mod import DEFAULT_BNN_PRIOR_PARAMETERS, dnn_to_bnn_mod
  14. # Model-kind strings (mirror evaluation.schema.MODEL_KIND_*). Kept here too so
  15. # model/ has no dependency on the evaluation package.
  16. KIND_NORMAL = "normal"
  17. KIND_BAYESIAN = "bayesian"
  18. def build_model(config: Dict[str, Any], kind: str) -> CNN3D:
  19. """Build an (untrained) CNN3D of the requested kind on CPU.
  20. For ``kind == "bayesian"`` the conv/linear layers are converted to their
  21. Bayesian counterparts using :data:`DEFAULT_BNN_PRIOR_PARAMETERS`.
  22. """
  23. data_cfg = config["data"]
  24. train_cfg = config["training"]
  25. model = CNN3D(
  26. image_channels=data_cfg["image_channels"],
  27. clin_data_channels=data_cfg["clin_data_channels"],
  28. num_classes=data_cfg["num_classes"],
  29. droprate=train_cfg["droprate"],
  30. ).float()
  31. if kind == KIND_BAYESIAN:
  32. dnn_to_bnn_mod(model, DEFAULT_BNN_PRIOR_PARAMETERS)
  33. elif kind != KIND_NORMAL:
  34. raise ValueError(f"Unknown model kind {kind!r}; expected 'normal'/'bayesian'.")
  35. return model
  36. def load_model(
  37. path: pl.Path,
  38. kind: str,
  39. config: Dict[str, Any],
  40. device: str,
  41. ) -> nn.Module:
  42. """Rebuild a model of ``kind`` and load ``path``'s weights onto ``device``.
  43. The returned model is in ``eval()`` mode. Bayesian models remain stochastic
  44. in eval mode (their reparameterization layers sample every forward pass),
  45. which is exactly what Monte-Carlo evaluation relies on.
  46. """
  47. model = build_model(config, kind)
  48. state_dict = torch.load(path, map_location="cpu")
  49. model.load_state_dict(state_dict)
  50. model.to(device)
  51. model.eval()
  52. return model