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
141 lines
6.1 KiB
Python
141 lines
6.1 KiB
Python
import logging
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
from ludwig.combiners.combiners import ConcatCombiner
|
|
from ludwig.constants import CATEGORY, DECODER, NUMBER, SEQUENCE, TYPE
|
|
from ludwig.models.base import BaseModel
|
|
from ludwig.modules.reduction_modules import SequenceReducer
|
|
from ludwig.schema.model_config import ModelConfig
|
|
from ludwig.utils import output_feature_utils
|
|
from tests.integration_tests.utils import generate_output_features_with_dependencies, number_feature
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.setLevel(logging.INFO)
|
|
logging.getLogger("ludwig").setLevel(logging.INFO)
|
|
|
|
BATCH_SIZE = 16
|
|
SEQ_SIZE = 12
|
|
HIDDEN_SIZE = 128
|
|
OTHER_HIDDEN_SIZE = 32
|
|
OTHER_HIDDEN_SIZE2 = 64
|
|
|
|
|
|
# unit test for dependency concatenation
|
|
# tests both single and multiple dependencies
|
|
@pytest.mark.parametrize(
|
|
"dependent_hidden_shape2",
|
|
[
|
|
None,
|
|
[BATCH_SIZE, OTHER_HIDDEN_SIZE2],
|
|
[BATCH_SIZE, SEQ_SIZE, OTHER_HIDDEN_SIZE2],
|
|
[BATCH_SIZE, SEQ_SIZE, OTHER_HIDDEN_SIZE],
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"dependent_hidden_shape", [[BATCH_SIZE, OTHER_HIDDEN_SIZE], [BATCH_SIZE, SEQ_SIZE, OTHER_HIDDEN_SIZE]]
|
|
)
|
|
@pytest.mark.parametrize("hidden_shape", [[BATCH_SIZE, HIDDEN_SIZE], [BATCH_SIZE, SEQ_SIZE, HIDDEN_SIZE]])
|
|
@pytest.mark.parametrize(
|
|
# todo: re-add 'attention' after further research in implication of torch
|
|
# migration
|
|
"reduce_dependencies",
|
|
["sum", "mean", "avg", "max", "concat", "last"],
|
|
)
|
|
def test_multiple_dependencies(reduce_dependencies, hidden_shape, dependent_hidden_shape, dependent_hidden_shape2):
|
|
# setup at least for a single dependency
|
|
hidden_layer = torch.randn(hidden_shape, dtype=torch.float32)
|
|
other_hidden_layer = torch.randn(dependent_hidden_shape, dtype=torch.float32)
|
|
other_dependencies = {
|
|
"feature_name": other_hidden_layer,
|
|
}
|
|
|
|
# setup dummy output feature to be root of dependency list
|
|
num_feature_defn = number_feature()
|
|
num_feature_defn["loss"] = {"type": "mean_squared_error"}
|
|
num_feature_defn["dependencies"] = ["feature_name"]
|
|
if len(dependent_hidden_shape) > 2:
|
|
num_feature_defn["reduce_dependencies"] = reduce_dependencies
|
|
|
|
# Based on specification calculate expected resulting hidden size for
|
|
# with one dependencies
|
|
if reduce_dependencies == "concat" and len(hidden_shape) == 2 and len(dependent_hidden_shape) == 3:
|
|
expected_hidden_size = HIDDEN_SIZE + OTHER_HIDDEN_SIZE * SEQ_SIZE
|
|
else:
|
|
expected_hidden_size = HIDDEN_SIZE + OTHER_HIDDEN_SIZE
|
|
|
|
# set up if multiple dependencies specified, setup second dependent feature
|
|
if dependent_hidden_shape2:
|
|
other_hidden_layer2 = torch.randn(dependent_hidden_shape2, dtype=torch.float32)
|
|
other_dependencies["feature_name2"] = other_hidden_layer2
|
|
num_feature_defn["dependencies"].append("feature_name2")
|
|
if len(dependent_hidden_shape2) > 2:
|
|
num_feature_defn["reduce_dependencies"] = reduce_dependencies
|
|
|
|
# Based on specification calculate marginal increase in resulting
|
|
# hidden size with two dependencies
|
|
if reduce_dependencies == "concat" and len(hidden_shape) == 2 and len(dependent_hidden_shape2) == 3:
|
|
expected_hidden_size += dependent_hidden_shape2[-1] * SEQ_SIZE
|
|
else:
|
|
expected_hidden_size += dependent_hidden_shape2[-1]
|
|
|
|
# Set up dependency reducers.
|
|
dependency_reducers = torch.nn.ModuleDict()
|
|
for feature_name in other_dependencies:
|
|
dependency_reducers[feature_name] = SequenceReducer(reduce_mode=reduce_dependencies)
|
|
|
|
# test dependency concatenation
|
|
num_feature_defn["input_size"] = expected_hidden_size
|
|
results = output_feature_utils.concat_dependencies(
|
|
"num_feature", num_feature_defn["dependencies"], dependency_reducers, hidden_layer, other_dependencies
|
|
)
|
|
|
|
# confirm size of resulting concat_dependencies() call
|
|
if len(hidden_shape) > 2:
|
|
assert results.shape == (BATCH_SIZE, SEQ_SIZE, expected_hidden_size)
|
|
else:
|
|
assert results.shape == (BATCH_SIZE, expected_hidden_size)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"output_feature_defs",
|
|
[
|
|
generate_output_features_with_dependencies("number_feature", ["category_feature"]),
|
|
generate_output_features_with_dependencies("number_feature", ["category_feature", "sequence_feature"]),
|
|
generate_output_features_with_dependencies("sequence_feature", ["category_feature", "number_feature"]),
|
|
],
|
|
)
|
|
def test_construct_output_features_with_dependencies(output_feature_defs):
|
|
# Add keys to output_feature_defs which would have been derived from data.
|
|
def add_data_derived_keys(output_feature_def):
|
|
if DECODER not in output_feature_def:
|
|
output_feature_def[DECODER] = {}
|
|
if output_feature_def[TYPE] == CATEGORY:
|
|
output_feature_def["num_classes"] = 2
|
|
elif output_feature_def[TYPE] == NUMBER:
|
|
output_feature_def[DECODER][TYPE] = "regressor"
|
|
elif output_feature_def[TYPE] == SEQUENCE:
|
|
output_feature_def[DECODER]["max_sequence_length"] = 5
|
|
return output_feature_def
|
|
|
|
output_feature_defs = [add_data_derived_keys(of) for of in output_feature_defs]
|
|
# Gets name of output feature which has dependencies.
|
|
dep_feature_name = [of for of in output_feature_defs if len(of.get("dependencies", [])) > 0][0]["name"]
|
|
# Creates a dummy input feature and combiner.
|
|
config = {
|
|
"input_features": [number_feature()],
|
|
"output_features": output_feature_defs,
|
|
"combiner": {"type": "concat", "output_size": 1},
|
|
}
|
|
config_obj = ModelConfig.from_dict(config)
|
|
input_features = BaseModel.build_inputs(config_obj.input_features)
|
|
combiner = ConcatCombiner(input_features=input_features, config=config_obj.combiner)
|
|
output_features = BaseModel.build_outputs(config_obj.output_features, combiner)
|
|
# Gets the output feature object which has dependencies.
|
|
feature_with_deps = output_features[dep_feature_name]
|
|
n_dependencies = len(feature_with_deps.dependencies)
|
|
assert n_dependencies > 0
|
|
# Each synthetic output feature has output size 1, so total size is 1 + n_dependencies.
|
|
assert feature_with_deps.fc_stack.input_shape == torch.Size([1 + n_dependencies])
|