Spaces:
Running
Running
| """Encoder. | |
| Structural tests run offline (random weights). The non-degenerate-features test needs | |
| ImageNet weights + a real subsample image and skips when either is absent. | |
| """ | |
| import pytest | |
| import torch | |
| from capit.config import config | |
| from capit.models.encoder import Encoder | |
| def encoder() -> Encoder: | |
| return Encoder(pretrained=False) | |
| def test_output_shape(encoder: Encoder): | |
| feats = encoder(torch.randn(2, 3, config.crop, config.crop)) | |
| assert feats.shape == (2, config.encoded_size**2, config.encoder_dim) | |
| def test_all_params_frozen(encoder: Encoder): | |
| total = sum(p.numel() for p in encoder.parameters()) | |
| frozen = sum(p.numel() for p in encoder.parameters() if not p.requires_grad) | |
| assert total > 0 | |
| assert frozen == total | |
| def test_no_nan_on_random_input(encoder: Encoder): | |
| feats = encoder(torch.randn(2, 3, config.crop, config.crop)) | |
| assert not torch.isnan(feats).any() | |
| def test_eval_mode_determinism(encoder: Encoder): | |
| encoder.eval() | |
| x = torch.randn(1, 3, config.crop, config.crop) | |
| with torch.no_grad(): | |
| assert torch.equal(encoder(x), encoder(x)) | |
| def test_eval_uses_running_stats_not_batch(encoder: Encoder): | |
| encoder.eval() | |
| x = torch.randn(1, 3, config.crop, config.crop) | |
| others = torch.randn(3, 3, config.crop, config.crop) | |
| with torch.no_grad(): | |
| alone = encoder(x) | |
| batched = encoder(torch.cat([x, others]))[:1] | |
| assert torch.allclose(alone, batched, atol=1e-5) | |
| def test_train_keeps_backbone_in_eval(encoder: Encoder): | |
| encoder.train() | |
| assert not encoder.backbone.training | |
| encoder.eval() | |
| def test_fine_tune_default_keeps_all_frozen(): | |
| enc = Encoder(pretrained=False) | |
| enc.fine_tune() | |
| assert all(not p.requires_grad for p in enc.parameters()) | |
| def test_fine_tune_unfreezes_exactly_that_block(): | |
| enc = Encoder(pretrained=False) | |
| enc.fine_tune((7,)) # index 7 = layer4 | |
| trainable = sum(p.numel() for p in enc.parameters() if p.requires_grad) | |
| layer4 = sum(p.numel() for p in enc.backbone[7].parameters()) | |
| assert trainable == layer4 > 0 | |
| def test_real_image_features_non_degenerate(): | |
| from capit.data.dataset import CaptionDataset, build_transform | |
| from capit.data.vocab import Vocab | |
| vocab = Vocab.load(config.vocab_path) | |
| ds = CaptionDataset(config.subsample_root, "test", vocab, build_transform()) | |
| _, image = next(ds.iter_images()) | |
| encoder = Encoder(pretrained=True) | |
| with torch.no_grad(): | |
| feats = encoder(image.unsqueeze(0)) | |
| assert not torch.isnan(feats).any() | |
| assert feats.std().item() > 0 | |