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
188 lines
6.0 KiB
Python
188 lines
6.0 KiB
Python
import os
|
|
import tempfile
|
|
|
|
import torch
|
|
from torch import nn, Tensor
|
|
|
|
from ludwig.api import LudwigModel
|
|
from ludwig.combiners.combiners import Combiner, register_combiner
|
|
from ludwig.constants import BATCH_SIZE, ENCODER_OUTPUT, LOGITS, MINIMIZE, NUMBER, TRAINER
|
|
from ludwig.decoders.base import Decoder
|
|
from ludwig.decoders.registry import register_decoder
|
|
from ludwig.encoders.base import Encoder
|
|
from ludwig.encoders.registry import register_encoder
|
|
from ludwig.modules.loss_modules import LogitsInputsMixin, register_loss
|
|
from ludwig.modules.metric_modules import LossMetric, register_metric
|
|
from ludwig.schema import utils as schema_utils
|
|
from ludwig.schema.combiners.base import BaseCombinerConfig
|
|
from ludwig.schema.combiners.utils import register_combiner_config
|
|
from ludwig.schema.decoders.base import BaseDecoderConfig
|
|
from ludwig.schema.decoders.utils import register_decoder_config
|
|
from ludwig.schema.encoders.base import BaseEncoderConfig
|
|
from ludwig.schema.encoders.utils import register_encoder_config
|
|
from ludwig.schema.features.loss.loss import BaseLossConfig
|
|
from ludwig.schema.features.loss.loss import register_loss as register_loss_schema
|
|
from tests.integration_tests.utils import (
|
|
category_feature,
|
|
generate_data,
|
|
LocalTestBackend,
|
|
number_feature,
|
|
sequence_feature,
|
|
)
|
|
|
|
|
|
@register_encoder_config("custom_number_encoder", NUMBER)
|
|
class CustomNumberEncoderConfig(BaseEncoderConfig):
|
|
type: str = "custom_number_encoder"
|
|
|
|
input_size: int = schema_utils.PositiveInteger(default=1, description="")
|
|
|
|
|
|
@register_decoder_config("custom_number_decoder", NUMBER)
|
|
class CustomNumberDecoderConfig(BaseDecoderConfig):
|
|
type: str = "custom_number_decoder"
|
|
|
|
input_size: int = schema_utils.PositiveInteger(default=1, description="")
|
|
|
|
|
|
@register_loss_schema([NUMBER])
|
|
class CustomLossConfig(BaseLossConfig):
|
|
type: str = "custom_loss"
|
|
|
|
|
|
@register_combiner_config("custom_combiner")
|
|
class CustomTestCombinerConfig(BaseCombinerConfig):
|
|
type: str = "custom_combiner"
|
|
|
|
foo: bool = schema_utils.Boolean(default=False, description="")
|
|
|
|
|
|
@register_combiner(CustomTestCombinerConfig)
|
|
class CustomTestCombiner(Combiner):
|
|
def __init__(self, input_features: dict = None, config: CustomTestCombinerConfig = None, **kwargs):
|
|
super().__init__(input_features)
|
|
self.foo = config.foo
|
|
|
|
def forward(self, inputs: dict) -> dict: # encoder outputs
|
|
if not self.foo:
|
|
raise ValueError("expected foo to be True")
|
|
|
|
# minimal transformation from inputs to outputs
|
|
encoder_outputs = [inputs[k][ENCODER_OUTPUT] for k in inputs]
|
|
hidden = torch.cat(encoder_outputs, 1)
|
|
return_data = {"combiner_output": hidden}
|
|
|
|
return return_data
|
|
|
|
|
|
@register_encoder("custom_number_encoder", NUMBER)
|
|
class CustomNumberEncoder(Encoder):
|
|
def __init__(self, input_size, **kwargs):
|
|
super().__init__()
|
|
self.input_size = input_size
|
|
|
|
def forward(self, inputs, **kwargs):
|
|
return {ENCODER_OUTPUT: inputs}
|
|
|
|
@property
|
|
def input_shape(self) -> torch.Size:
|
|
return torch.Size([self.input_size])
|
|
|
|
@property
|
|
def output_shape(self) -> torch.Size:
|
|
return self.input_shape
|
|
|
|
@staticmethod
|
|
def get_schema_cls():
|
|
return CustomNumberEncoderConfig
|
|
|
|
|
|
@register_decoder("custom_number_decoder", NUMBER)
|
|
class CustomNumberDecoder(Decoder):
|
|
def __init__(self, input_size, **kwargs):
|
|
super().__init__()
|
|
self.input_size = input_size
|
|
|
|
@property
|
|
def input_shape(self):
|
|
return torch.Size([self.input_size])
|
|
|
|
def forward(self, inputs, **kwargs):
|
|
return torch.mean(inputs, 1)
|
|
|
|
@staticmethod
|
|
def get_schema_cls():
|
|
return CustomNumberDecoderConfig
|
|
|
|
|
|
@register_loss(CustomLossConfig)
|
|
class CustomLoss(nn.Module, LogitsInputsMixin):
|
|
def __init__(self, config: CustomLossConfig):
|
|
super().__init__()
|
|
|
|
def forward(self, preds: Tensor, target: Tensor) -> Tensor:
|
|
return torch.mean(torch.square(preds - target))
|
|
|
|
@staticmethod
|
|
def get_schema_cls():
|
|
return CustomLossConfig
|
|
|
|
|
|
@register_metric("custom_loss", [NUMBER], MINIMIZE, LOGITS)
|
|
class CustomLossMetric(LossMetric):
|
|
def __init__(self, config: CustomLossConfig, **kwargs):
|
|
super().__init__()
|
|
self.loss_fn = CustomLoss(config)
|
|
|
|
def get_current_value(self, preds: Tensor, target: Tensor):
|
|
return self.loss_fn(preds, target)
|
|
|
|
|
|
def test_custom_combiner():
|
|
_run_test(combiner={"type": "custom_combiner", "foo": True})
|
|
|
|
|
|
def test_custom_encoder_decoder():
|
|
input_features = [
|
|
sequence_feature(encoder={"reduce_output": "sum"}),
|
|
number_feature(encoder={"type": "custom_number_encoder"}),
|
|
]
|
|
output_features = [
|
|
number_feature(decoder={"type": "custom_number_decoder"}),
|
|
]
|
|
_run_test(input_features=input_features, output_features=output_features)
|
|
|
|
|
|
def test_custom_loss_metric():
|
|
output_features = [
|
|
number_feature(loss={"type": "custom_loss"}),
|
|
]
|
|
_run_test(output_features=output_features)
|
|
|
|
|
|
def _run_test(input_features=None, output_features=None, combiner=None):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
input_features = input_features or [
|
|
sequence_feature(encoder={"reduce_output": "sum"}),
|
|
number_feature(),
|
|
]
|
|
output_features = output_features or [category_feature(decoder={"vocab_size": 2}, reduce_input="sum")]
|
|
combiner = combiner or {"type": "concat"}
|
|
|
|
csv_filename = os.path.join(tmpdir, "training.csv")
|
|
data_csv = generate_data(input_features, output_features, csv_filename)
|
|
|
|
config = {
|
|
"input_features": input_features,
|
|
"output_features": output_features,
|
|
"combiner": combiner,
|
|
TRAINER: {"epochs": 2, BATCH_SIZE: 128},
|
|
}
|
|
|
|
model = LudwigModel(config, backend=LocalTestBackend())
|
|
_, _, output_directory = model.train(
|
|
dataset=data_csv,
|
|
output_directory=tmpdir,
|
|
)
|
|
model.predict(dataset=data_csv, output_directory=output_directory)
|