@@ -61,6 +61,44 @@ def test_destvi():
6161 spatial_model .get_normalized_expression ()
6262
6363
64+ def test_destvi_validation ():
65+ # DestVI must be trainable with a validation set: `early_stopping`,
66+ # `check_val_every_n_epoch` and `train_size < 1.0` previously crashed in the loss because the
67+ # augmentation branch was not gated on `self.training` (validation produces no augmentation
68+ # tensors). See `MRDeconv.loss`.
69+ n_latent = 2
70+ n_labels = 5
71+ dataset = synthetic_iid (n_labels = n_labels )
72+ CondSCVI .setup_anndata (dataset , labels_key = "labels" , batch_key = "batch" )
73+ sc_model = CondSCVI (dataset , n_latent = n_latent , n_layers = 2 , prior = "mog" , num_classes_mog = 10 )
74+ sc_model .train (1 , train_size = 0.9 )
75+
76+ DestVI .setup_anndata (dataset , layer = None )
77+
78+ # train_size < 1.0 with validation evaluated every epoch
79+ model = DestVI .from_rna_model (dataset , sc_model , amortization = "both" )
80+ model .train (max_epochs = 2 , train_size = 0.9 , check_val_every_n_epoch = 1 )
81+ assert "validation_loss" in model .history
82+ assert not np .isnan (model .history ["validation_loss" ].values [0 ][0 ])
83+
84+ # early stopping (requires a working validation pass)
85+ model = DestVI .from_rna_model (dataset , sc_model , amortization = "both" )
86+ model .train (max_epochs = 2 , train_size = 0.9 , early_stopping = True )
87+ assert "validation_loss" in model .history
88+
89+ # Non-amortized spot parameters (here `V`, since amortization="latent") are only trained on the
90+ # training spots, so a validation set would be evaluated on randomly-initialized parameters.
91+ # Reject it. ("proportion"/"none" are not exercised here because mog priors require amortized
92+ # latents, but they hit the same `amortization != 'both'` guard.)
93+ model = DestVI .from_rna_model (dataset , sc_model , amortization = "latent" )
94+ with pytest .raises (ValueError , match = "amortization='both'" ):
95+ model .train (max_epochs = 2 , train_size = 0.9 )
96+ with pytest .raises (ValueError , match = "amortization='both'" ):
97+ model .train (max_epochs = 2 , early_stopping = True )
98+ # train_size=1.0 (all spots trained) must still work
99+ model .train (max_epochs = 1 )
100+
101+
64102@pytest .mark .internet
65103def test_destvi_new (save_path : str ):
66104 CELL_TYPE_ID = "broad_cell_types"
0 commit comments