ludwig-ai--ludwig
593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
87 行
3.5 KiB
Python
87 行
3.5 KiB
Python
"""Tests for modality dropout."""
|
|
|
|
import torch
|
|
|
|
from ludwig.constants import ENCODER_OUTPUT
|
|
from ludwig.modules.modality_dropout import ModalityDropout
|
|
|
|
|
|
def _make_encoder_outputs(batch_size=8, hidden_size=16, num_features=3):
|
|
outputs = {}
|
|
for i in range(num_features):
|
|
name = f"feature_{i}"
|
|
outputs[name] = {ENCODER_OUTPUT: torch.randn(batch_size, hidden_size)}
|
|
return outputs
|
|
|
|
|
|
def _make_feature_shapes(hidden_size=16, num_features=3):
|
|
return {f"feature_{i}": torch.Size([hidden_size]) for i in range(num_features)}
|
|
|
|
|
|
class TestModalityDropout:
|
|
def test_eval_mode_passthrough(self):
|
|
"""In eval mode, outputs should be unchanged."""
|
|
md = ModalityDropout(_make_feature_shapes(), dropout_prob=1.0)
|
|
md.eval()
|
|
inputs = _make_encoder_outputs()
|
|
outputs = md(inputs)
|
|
for name in inputs:
|
|
assert torch.equal(inputs[name][ENCODER_OUTPUT], outputs[name][ENCODER_OUTPUT])
|
|
|
|
def test_train_mode_zero_prob_passthrough(self):
|
|
"""With dropout_prob=0, outputs should be unchanged even in train mode."""
|
|
md = ModalityDropout(_make_feature_shapes(), dropout_prob=0.0)
|
|
md.train()
|
|
inputs = _make_encoder_outputs()
|
|
outputs = md(inputs)
|
|
for name in inputs:
|
|
assert torch.equal(inputs[name][ENCODER_OUTPUT], outputs[name][ENCODER_OUTPUT])
|
|
|
|
def test_train_mode_full_dropout(self):
|
|
"""With dropout_prob=1.0 in train mode, all features should be replaced."""
|
|
md = ModalityDropout(_make_feature_shapes(), dropout_prob=1.0)
|
|
md.train()
|
|
inputs = _make_encoder_outputs()
|
|
outputs = md(inputs)
|
|
for name in inputs:
|
|
# Output should be the learned missing embedding, not the original
|
|
assert not torch.equal(inputs[name][ENCODER_OUTPUT], outputs[name][ENCODER_OUTPUT])
|
|
|
|
def test_missing_embeddings_are_learnable(self):
|
|
"""Missing embeddings should be in named_parameters and receive gradients."""
|
|
md = ModalityDropout(_make_feature_shapes(), dropout_prob=1.0)
|
|
md.train()
|
|
|
|
param_names = [n for n, _ in md.named_parameters()]
|
|
assert len(param_names) == 3
|
|
assert all("missing_embeddings" in n for n in param_names)
|
|
|
|
inputs = _make_encoder_outputs()
|
|
outputs = md(inputs)
|
|
loss = sum(o[ENCODER_OUTPUT].sum() for o in outputs.values())
|
|
loss.backward()
|
|
for name, param in md.named_parameters():
|
|
assert param.grad is not None, f"No gradient for {name}"
|
|
|
|
def test_output_shapes_preserved(self):
|
|
"""Output shapes should match input shapes."""
|
|
md = ModalityDropout(_make_feature_shapes(hidden_size=32), dropout_prob=0.5)
|
|
md.train()
|
|
inputs = _make_encoder_outputs(batch_size=4, hidden_size=32)
|
|
outputs = md(inputs)
|
|
for name in inputs:
|
|
assert outputs[name][ENCODER_OUTPUT].shape == inputs[name][ENCODER_OUTPUT].shape
|
|
|
|
def test_partial_dropout(self):
|
|
"""With intermediate dropout_prob, some features should be dropped and others kept."""
|
|
torch.manual_seed(42)
|
|
md = ModalityDropout(_make_feature_shapes(num_features=10), dropout_prob=0.5)
|
|
md.train()
|
|
inputs = _make_encoder_outputs(num_features=10)
|
|
outputs = md(inputs)
|
|
changed = sum(
|
|
1 for name in inputs if not torch.equal(inputs[name][ENCODER_OUTPUT], outputs[name][ENCODER_OUTPUT])
|
|
)
|
|
# With 10 features and p=0.5, expect roughly 5 changed (but stochastic)
|
|
assert 0 < changed < 10
|