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
662 lines
24 KiB
Python
662 lines
24 KiB
Python
# Copyright (c) 2023 Predibase, Inc., 2019 Uber Technologies, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
import json
|
|
import os
|
|
import os.path
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from ludwig.backend import initialize_backend
|
|
from ludwig.constants import (
|
|
ACCURACY,
|
|
AUTO,
|
|
BATCH_SIZE,
|
|
CATEGORY,
|
|
COMBINER,
|
|
EXECUTOR,
|
|
HYPEROPT,
|
|
INPUT_FEATURES,
|
|
MAX_CONCURRENT_TRIALS,
|
|
MODEL_ECD,
|
|
MODEL_TYPE,
|
|
NAME,
|
|
OUTPUT_FEATURES,
|
|
RAY,
|
|
TEXT,
|
|
TRAINER,
|
|
TYPE,
|
|
VALIDATION,
|
|
)
|
|
from ludwig.globals import HYPEROPT_STATISTICS_FILE_NAME, MODEL_FILE_NAME
|
|
from ludwig.hyperopt.results import HyperoptResults
|
|
from ludwig.hyperopt.run import hyperopt
|
|
from ludwig.hyperopt.utils import update_hyperopt_params_with_defaults
|
|
from ludwig.schema.model_config import ModelConfig
|
|
from ludwig.utils import fs_utils
|
|
from ludwig.utils.data_utils import load_json, use_credentials
|
|
from tests.integration_tests.utils import category_feature, generate_data, minio_test_creds, remote_tmpdir, text_feature
|
|
|
|
ray = pytest.importorskip("ray")
|
|
|
|
from ludwig.hyperopt.execution import get_build_hyperopt_executor, RayTuneExecutor # noqa
|
|
|
|
pytestmark = [pytest.mark.distributed, pytest.mark.distributed_c, pytest.mark.integration_tests_c]
|
|
|
|
RANDOM_SEARCH_SIZE = 2
|
|
|
|
HYPEROPT_CONFIG = {
|
|
"parameters": {
|
|
# using only float parameter as common in all search algorithms
|
|
"trainer.learning_rate": {"space": "loguniform", "lower": 0.001, "upper": 0.1},
|
|
},
|
|
"goal": "minimize",
|
|
"executor": {TYPE: "ray", "num_samples": 2, "scheduler": {TYPE: "fifo"}},
|
|
"search_alg": {TYPE: "variant_generator"},
|
|
}
|
|
|
|
SEARCH_ALGS_FOR_TESTING = [
|
|
# None,
|
|
# "variant_generator",
|
|
"random",
|
|
"bohb",
|
|
# "hyperopt",
|
|
# "ax",
|
|
# "bayesopt",
|
|
# "blendsearch",
|
|
# "cfo",
|
|
# "dragonfly",
|
|
# "hebo",
|
|
# "skopt",
|
|
# "optuna",
|
|
]
|
|
|
|
SCHEDULERS_FOR_TESTING = [
|
|
"fifo",
|
|
"asynchyperband",
|
|
# "async_hyperband",
|
|
# "median_stopping_rule",
|
|
# "medianstopping",
|
|
# "hyperband",
|
|
# "hb_bohb",
|
|
# "pbt",
|
|
# "pb2", commented out for now: https://github.com/ray-project/ray/issues/24815
|
|
# "resource_changing",
|
|
]
|
|
|
|
|
|
def _setup_ludwig_config(dataset_fp: str, model_type: str = MODEL_ECD) -> tuple[dict, str]:
|
|
input_features = [category_feature(encoder={"vocab_size": 3})]
|
|
output_features = [category_feature(decoder={"vocab_size": 3})]
|
|
|
|
rel_path = generate_data(input_features, output_features, dataset_fp, num_examples=30)
|
|
|
|
trainer_cfg = {"learning_rate": 0.001}
|
|
if model_type == MODEL_ECD:
|
|
trainer_cfg["epochs"] = 2
|
|
else:
|
|
trainer_cfg["num_boost_round"] = 2
|
|
# Disable feature filtering to avoid having no features due to small test dataset,
|
|
# see https://stackoverflow.com/a/66405983/5222402
|
|
trainer_cfg["feature_pre_filter"] = False
|
|
|
|
config = {
|
|
MODEL_TYPE: model_type,
|
|
INPUT_FEATURES: input_features,
|
|
OUTPUT_FEATURES: output_features,
|
|
COMBINER: {TYPE: "concat"},
|
|
TRAINER: trainer_cfg,
|
|
}
|
|
|
|
config = ModelConfig.from_dict(config).to_dict()
|
|
|
|
return config, rel_path
|
|
|
|
|
|
@pytest.mark.parametrize("search_alg", SEARCH_ALGS_FOR_TESTING)
|
|
@pytest.mark.parametrize("model_type", [MODEL_ECD])
|
|
def test_hyperopt_search_alg(
|
|
search_alg,
|
|
model_type,
|
|
csv_filename,
|
|
tmpdir,
|
|
ray_cluster_7cpu,
|
|
validate_output_feature=False,
|
|
validation_metric=None,
|
|
split="validation",
|
|
):
|
|
config, rel_path = _setup_ludwig_config(csv_filename, model_type)
|
|
|
|
hyperopt_config = HYPEROPT_CONFIG.copy()
|
|
|
|
# finalize hyperopt config settings
|
|
if search_alg == "dragonfly":
|
|
hyperopt_config["search_alg"] = {
|
|
TYPE: search_alg,
|
|
"domain": "euclidean",
|
|
"optimizer": "random",
|
|
}
|
|
elif search_alg is None:
|
|
hyperopt_config["search_alg"] = {}
|
|
else:
|
|
hyperopt_config["search_alg"] = {
|
|
TYPE: search_alg,
|
|
}
|
|
|
|
if validate_output_feature:
|
|
hyperopt_config["output_feature"] = config[OUTPUT_FEATURES][0][NAME]
|
|
if validation_metric:
|
|
hyperopt_config["validation_metric"] = validation_metric
|
|
|
|
update_hyperopt_params_with_defaults(hyperopt_config)
|
|
|
|
backend = initialize_backend("local")
|
|
if hyperopt_config[EXECUTOR].get(MAX_CONCURRENT_TRIALS) == AUTO:
|
|
hyperopt_config[EXECUTOR][MAX_CONCURRENT_TRIALS] = backend.max_concurrent_trials(hyperopt_config)
|
|
|
|
parameters = hyperopt_config["parameters"]
|
|
output_feature = hyperopt_config["output_feature"]
|
|
metric = hyperopt_config["metric"]
|
|
goal = hyperopt_config["goal"]
|
|
executor = hyperopt_config["executor"]
|
|
search_alg = hyperopt_config["search_alg"]
|
|
|
|
hyperopt_executor = get_build_hyperopt_executor(RAY)(
|
|
parameters, output_feature, metric, goal, split, search_alg=search_alg, **executor
|
|
)
|
|
results = hyperopt_executor.execute(config, dataset=rel_path, output_directory=tmpdir)
|
|
assert isinstance(results, HyperoptResults)
|
|
|
|
with hyperopt_executor._get_best_model_path(
|
|
results.experiment_analysis.best_trial, results.experiment_analysis
|
|
) as path:
|
|
assert path is not None
|
|
assert isinstance(path, str)
|
|
|
|
|
|
@pytest.mark.parametrize("model_type", [MODEL_ECD])
|
|
def test_hyperopt_executor_with_metric(model_type, csv_filename, tmpdir, ray_cluster_7cpu):
|
|
test_hyperopt_search_alg(
|
|
"variant_generator",
|
|
model_type,
|
|
csv_filename,
|
|
tmpdir,
|
|
ray_cluster_7cpu,
|
|
validate_output_feature=True,
|
|
validation_metric=ACCURACY,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("split", [VALIDATION])
|
|
def test_hyperopt_with_split(split, csv_filename, tmpdir, ray_cluster_7cpu):
|
|
test_hyperopt_search_alg(
|
|
search_alg="variant_generator",
|
|
model_type=MODEL_ECD,
|
|
csv_filename=csv_filename,
|
|
tmpdir=tmpdir,
|
|
ray_cluster_7cpu=ray_cluster_7cpu,
|
|
split=split,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("scheduler", SCHEDULERS_FOR_TESTING)
|
|
@pytest.mark.parametrize("model_type", [MODEL_ECD])
|
|
def test_hyperopt_scheduler(
|
|
scheduler, model_type, csv_filename, tmpdir, ray_cluster_7cpu, validate_output_feature=False, validation_metric=None
|
|
):
|
|
config, rel_path = _setup_ludwig_config(csv_filename, model_type)
|
|
|
|
hyperopt_config = HYPEROPT_CONFIG.copy()
|
|
|
|
# finalize hyperopt config settings
|
|
if scheduler == "pb2":
|
|
# setup scheduler hyperparam_bounds parameter
|
|
min = hyperopt_config["parameters"]["trainer.learning_rate"]["lower"]
|
|
max = hyperopt_config["parameters"]["trainer.learning_rate"]["upper"]
|
|
hyperparam_bounds = {
|
|
"trainer.learning_rate": [min, max],
|
|
}
|
|
hyperopt_config["executor"]["scheduler"] = {
|
|
TYPE: scheduler,
|
|
"hyperparam_bounds": hyperparam_bounds,
|
|
}
|
|
else:
|
|
hyperopt_config["executor"]["scheduler"] = {
|
|
TYPE: scheduler,
|
|
}
|
|
|
|
if validate_output_feature:
|
|
hyperopt_config["output_feature"] = config[OUTPUT_FEATURES][0][NAME]
|
|
if validation_metric:
|
|
hyperopt_config["validation_metric"] = validation_metric
|
|
|
|
backend = initialize_backend("local")
|
|
update_hyperopt_params_with_defaults(hyperopt_config)
|
|
if hyperopt_config[EXECUTOR].get(MAX_CONCURRENT_TRIALS) == AUTO:
|
|
hyperopt_config[EXECUTOR][MAX_CONCURRENT_TRIALS] = backend.max_concurrent_trials(hyperopt_config)
|
|
|
|
parameters = hyperopt_config["parameters"]
|
|
split = hyperopt_config["split"]
|
|
output_feature = hyperopt_config["output_feature"]
|
|
metric = hyperopt_config["metric"]
|
|
goal = hyperopt_config["goal"]
|
|
executor = hyperopt_config["executor"]
|
|
search_alg = hyperopt_config["search_alg"]
|
|
|
|
# TODO: Determine if we still need this if-then-else construct
|
|
if search_alg[TYPE] in {""}:
|
|
with pytest.raises(ImportError):
|
|
get_build_hyperopt_executor(RAY)(
|
|
parameters, output_feature, metric, goal, split, search_alg=search_alg, **executor
|
|
)
|
|
else:
|
|
hyperopt_executor = get_build_hyperopt_executor(RAY)(
|
|
parameters, output_feature, metric, goal, split, search_alg=search_alg, **executor
|
|
)
|
|
raytune_results = hyperopt_executor.execute(config, dataset=rel_path, output_directory=tmpdir)
|
|
assert isinstance(raytune_results, HyperoptResults)
|
|
|
|
|
|
def _run_hyperopt_run_hyperopt(csv_filename, search_space, tmpdir, backend, ray_cluster_7cpu):
|
|
input_features = [category_feature(encoder={"vocab_size": 3})]
|
|
output_features = [category_feature(decoder={"vocab_size": 3})]
|
|
|
|
rel_path = generate_data(input_features, output_features, csv_filename, num_examples=30)
|
|
|
|
config = {
|
|
INPUT_FEATURES: input_features,
|
|
OUTPUT_FEATURES: output_features,
|
|
COMBINER: {TYPE: "concat"},
|
|
TRAINER: {"epochs": 1, "learning_rate": 0.001, BATCH_SIZE: 128},
|
|
"backend": backend,
|
|
}
|
|
|
|
output_feature_name = output_features[0][NAME]
|
|
|
|
if search_space == "random":
|
|
# random search will be size of num_samples
|
|
search_parameters = {
|
|
"trainer.learning_rate": {
|
|
"lower": 0.0001,
|
|
"upper": 0.01,
|
|
"space": "loguniform",
|
|
},
|
|
output_feature_name + ".decoder.fc_layers": {
|
|
"space": "choice",
|
|
"categories": [
|
|
[{"output_size": 8}, {"output_size": 4}],
|
|
[{"output_size": 8}],
|
|
[{"output_size": 4}],
|
|
],
|
|
},
|
|
output_feature_name + ".decoder.fc_output_size": {"space": "choice", "categories": [4, 8, 12]},
|
|
}
|
|
else:
|
|
# grid search space will be product each parameter size
|
|
search_parameters = {
|
|
"trainer.learning_rate": {"space": "grid_search", "values": [0.001, 0.01]},
|
|
output_feature_name + ".decoder.fc_output_size": {"space": "grid_search", "values": [4, 8]},
|
|
}
|
|
|
|
hyperopt_configs = {
|
|
"parameters": search_parameters,
|
|
"goal": "minimize",
|
|
"output_feature": output_feature_name,
|
|
"validation_metrics": "loss",
|
|
"executor": {
|
|
TYPE: "ray",
|
|
"num_samples": 1 if search_space == "grid" else RANDOM_SEARCH_SIZE,
|
|
"max_concurrent_trials": 1,
|
|
},
|
|
"search_alg": {TYPE: "variant_generator"},
|
|
}
|
|
|
|
# add hyperopt parameter space to the config
|
|
config[HYPEROPT] = hyperopt_configs
|
|
|
|
experiment_name = f"test_hyperopt_{uuid.uuid4().hex}"
|
|
hyperopt_results = hyperopt(config, dataset=rel_path, output_directory=tmpdir, experiment_name=experiment_name)
|
|
if search_space == "random":
|
|
assert hyperopt_results.experiment_analysis.results_df.shape[0] == RANDOM_SEARCH_SIZE
|
|
else:
|
|
# compute size of search space for grid search
|
|
grid_search_size = 1
|
|
for k, v in search_parameters.items():
|
|
grid_search_size *= len(v["values"])
|
|
assert hyperopt_results.experiment_analysis.results_df.shape[0] == grid_search_size
|
|
|
|
# check for return results
|
|
assert isinstance(hyperopt_results, HyperoptResults)
|
|
|
|
# check for existence of the hyperopt statistics file
|
|
with use_credentials(minio_test_creds()):
|
|
assert fs_utils.path_exists(os.path.join(tmpdir, experiment_name, HYPEROPT_STATISTICS_FILE_NAME))
|
|
for trial in hyperopt_results.experiment_analysis.trials:
|
|
assert fs_utils.path_exists(
|
|
os.path.join(tmpdir, experiment_name, f"trial_{trial.trial_id}"),
|
|
)
|
|
|
|
# Verify best trial has a valid checkpoint
|
|
best_trial = hyperopt_results.experiment_analysis.best_trial
|
|
assert best_trial is not None
|
|
|
|
|
|
@pytest.mark.slow
|
|
@pytest.mark.parametrize("search_space", ["random", "grid"])
|
|
def test_hyperopt_run_hyperopt(csv_filename, search_space, tmpdir, ray_cluster_7cpu):
|
|
_run_hyperopt_run_hyperopt(csv_filename, search_space, tmpdir, "local", ray_cluster_7cpu)
|
|
|
|
|
|
@pytest.mark.xfail(
|
|
reason="PyArrow S3 C++ client uses chunked transfer encoding for multipart uploads, "
|
|
"which MinIO rejects with HTTP 411 MissingContentLength. Requires real AWS S3.",
|
|
strict=False,
|
|
)
|
|
def test_hyperopt_sync_remote(csv_filename, ray_cluster_7cpu, monkeypatch):
|
|
"""Test hyperopt with remote S3 (MinIO) storage for trial results."""
|
|
# Override AWS env vars so PyArrow's S3 client (used by Ray Tune internally)
|
|
# connects to MinIO instead of real AWS S3
|
|
minio_endpoint = os.environ.get("LUDWIG_MINIO_ENDPOINT", "http://localhost:9000")
|
|
monkeypatch.setenv("AWS_ACCESS_KEY_ID", os.environ.get("LUDWIG_MINIO_ACCESS_KEY", "minio"))
|
|
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", os.environ.get("LUDWIG_MINIO_SECRET_KEY", "minio123"))
|
|
monkeypatch.setenv("AWS_ENDPOINT_URL", minio_endpoint)
|
|
monkeypatch.setenv("AWS_EC2_METADATA_DISABLED", "true")
|
|
|
|
backend = {
|
|
"type": "local",
|
|
"credentials": {
|
|
"artifacts": minio_test_creds(),
|
|
},
|
|
}
|
|
|
|
with remote_tmpdir("s3", "test") as tmpdir:
|
|
_run_hyperopt_run_hyperopt(
|
|
csv_filename,
|
|
"random",
|
|
tmpdir,
|
|
backend,
|
|
ray_cluster_7cpu,
|
|
)
|
|
|
|
|
|
def test_hyperopt_with_feature_specific_parameters(csv_filename, tmpdir, ray_cluster_7cpu):
|
|
input_features = [
|
|
text_feature(name="utterance", reduce_output="sum"),
|
|
category_feature(vocab_size=3),
|
|
]
|
|
|
|
output_features = [category_feature(vocab_size=3, output_feature=True)]
|
|
|
|
rel_path = generate_data(input_features, output_features, csv_filename, num_examples=30)
|
|
|
|
filter_size_search_space = [5, 7]
|
|
embedding_size_search_space = [4, 8, 12]
|
|
|
|
config = {
|
|
INPUT_FEATURES: input_features,
|
|
OUTPUT_FEATURES: output_features,
|
|
COMBINER: {TYPE: "concat", "num_fc_layers": 2},
|
|
TRAINER: {"epochs": 1, "learning_rate": 0.001, BATCH_SIZE: 128},
|
|
HYPEROPT: {
|
|
"parameters": {
|
|
input_features[0][NAME] + ".encoder.filter_size": {
|
|
"space": "choice",
|
|
"categories": filter_size_search_space,
|
|
},
|
|
input_features[1][NAME] + ".encoder.embedding_size": {
|
|
"space": "choice",
|
|
"categories": embedding_size_search_space,
|
|
},
|
|
},
|
|
"goal": "minimize",
|
|
"output_feature": output_features[0][NAME],
|
|
"validation_metrics": "loss",
|
|
"executor": {TYPE: "ray", "num_samples": 1},
|
|
"search_alg": {TYPE: "variant_generator"},
|
|
},
|
|
}
|
|
|
|
hyperopt_results = hyperopt(config, dataset=rel_path, output_directory=tmpdir, experiment_name="test_hyperopt")
|
|
hyperopt_results_df = hyperopt_results.experiment_analysis.results_df
|
|
|
|
model_parameters = json.load(
|
|
open(
|
|
os.path.join(
|
|
hyperopt_results_df.iloc[0]["trial_dir"],
|
|
"test_hyperopt_run",
|
|
MODEL_FILE_NAME,
|
|
"model_hyperparameters.json",
|
|
)
|
|
)
|
|
)
|
|
|
|
for input_feature in model_parameters[INPUT_FEATURES]:
|
|
if input_feature[TYPE] == TEXT:
|
|
assert input_feature["encoder"]["filter_size"] in filter_size_search_space
|
|
elif input_feature[TYPE] == CATEGORY:
|
|
assert input_feature["encoder"]["embedding_size"] in embedding_size_search_space
|
|
|
|
|
|
def test_hyperopt_old_config(csv_filename, tmpdir, ray_cluster_7cpu):
|
|
old_config = {
|
|
"ludwig_version": "0.4",
|
|
INPUT_FEATURES: [
|
|
{"name": "cat1", TYPE: "category", "encoder": {"vocab_size": 2}},
|
|
{"name": "num1", TYPE: "number"},
|
|
],
|
|
OUTPUT_FEATURES: [
|
|
{"name": "bin1", TYPE: "binary"},
|
|
],
|
|
TRAINER: {"epochs": 2, BATCH_SIZE: 128},
|
|
HYPEROPT: {
|
|
EXECUTOR: {
|
|
TYPE: "ray",
|
|
"time_budget_s": 200,
|
|
"cpu_resources_per_trial": 1,
|
|
},
|
|
"sampler": {
|
|
TYPE: "ray",
|
|
"scheduler": {
|
|
TYPE: "async_hyperband",
|
|
"max_t": 200,
|
|
"time_attr": "time_total_s",
|
|
"grace_period": 72,
|
|
"reduction_factor": 5,
|
|
},
|
|
"search_alg": {
|
|
TYPE: "variant_generator",
|
|
},
|
|
"num_samples": 2,
|
|
},
|
|
"parameters": {
|
|
"trainer.batch_size": {
|
|
"space": "choice",
|
|
"categories": [64, 128, 256],
|
|
},
|
|
"trainer.learning_rate": {
|
|
"space": "loguniform",
|
|
"lower": 0.001,
|
|
"upper": 0.1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
input_features = old_config[INPUT_FEATURES]
|
|
output_features = old_config[OUTPUT_FEATURES]
|
|
rel_path = generate_data(input_features, output_features, csv_filename, num_examples=30)
|
|
|
|
hyperopt(old_config, dataset=rel_path, output_directory=tmpdir, experiment_name="test_hyperopt")
|
|
|
|
|
|
def test_hyperopt_nested_parameters(csv_filename, tmpdir, ray_cluster_7cpu):
|
|
config = {
|
|
INPUT_FEATURES: [
|
|
{"name": "cat1", TYPE: "category", "encoder": {"vocab_size": 2}},
|
|
{"name": "num1", TYPE: "number"},
|
|
],
|
|
OUTPUT_FEATURES: [
|
|
{"name": "bin1", TYPE: "binary"},
|
|
],
|
|
TRAINER: {"epochs": 2, BATCH_SIZE: 128},
|
|
HYPEROPT: {
|
|
EXECUTOR: {
|
|
TYPE: "ray",
|
|
"time_budget_s": 200,
|
|
"cpu_resources_per_trial": 1,
|
|
"num_samples": 2,
|
|
"scheduler": {TYPE: "fifo"},
|
|
},
|
|
"search_alg": {TYPE: "variant_generator"},
|
|
"parameters": {
|
|
".": {
|
|
"space": "choice",
|
|
"categories": [
|
|
{
|
|
"combiner": {
|
|
"type": "tabnet",
|
|
"bn_virtual_bs": 32,
|
|
},
|
|
"trainer": {
|
|
"learning_rate_scaling": "sqrt",
|
|
"learning_rate_scheduler": {
|
|
"decay": "exponential",
|
|
"decay_steps": 20000,
|
|
"decay_rate": 0.8,
|
|
},
|
|
"optimizer": {"type": "adam"},
|
|
},
|
|
},
|
|
{
|
|
"combiner": {"type": "concat"},
|
|
"trainer": {"learning_rate_scaling": "linear"},
|
|
},
|
|
],
|
|
},
|
|
"trainer.learning_rate": {"space": "choice", "categories": [0.7, 0.42]},
|
|
},
|
|
},
|
|
}
|
|
|
|
input_features = config[INPUT_FEATURES]
|
|
output_features = config[OUTPUT_FEATURES]
|
|
rel_path = generate_data(input_features, output_features, csv_filename, num_examples=30)
|
|
|
|
results = hyperopt(
|
|
config,
|
|
dataset=rel_path,
|
|
output_directory=tmpdir,
|
|
experiment_name="test_hyperopt_nested_params",
|
|
)
|
|
|
|
results_df = results.experiment_analysis.results_df
|
|
assert len(results_df) == 2
|
|
|
|
for _, trial_meta in results_df.iterrows():
|
|
trial_dir = trial_meta["trial_dir"]
|
|
trial_config = load_json(
|
|
os.path.join(trial_dir, "test_hyperopt_nested_params_run", MODEL_FILE_NAME, "model_hyperparameters.json")
|
|
)
|
|
|
|
assert len(trial_config[INPUT_FEATURES]) == len(config[INPUT_FEATURES])
|
|
assert len(trial_config[OUTPUT_FEATURES]) == len(config[OUTPUT_FEATURES])
|
|
|
|
assert trial_config[COMBINER][TYPE] in {"tabnet", "concat"}
|
|
if trial_config[COMBINER][TYPE] == "tabnet":
|
|
assert trial_config[COMBINER]["bn_virtual_bs"] == 32
|
|
assert trial_config[TRAINER]["learning_rate_scaling"] == "sqrt"
|
|
assert trial_config[TRAINER]["learning_rate_scheduler"]["decay"] == "exponential"
|
|
assert trial_config[TRAINER]["learning_rate_scheduler"]["decay_steps"] == 20000
|
|
assert trial_config[TRAINER]["learning_rate_scheduler"]["decay_rate"] == 0.8
|
|
assert trial_config[TRAINER]["optimizer"]["type"] == "adam"
|
|
else:
|
|
assert trial_config[TRAINER]["learning_rate_scaling"] == "linear"
|
|
|
|
assert trial_config[TRAINER]["learning_rate"] in {0.7, 0.42}
|
|
|
|
|
|
@pytest.mark.slow
|
|
def test_hyperopt_without_config_defaults(csv_filename, tmpdir, ray_cluster_7cpu):
|
|
input_features = [category_feature(encoder={"vocab_size": 3})]
|
|
output_features = [category_feature(decoder={"vocab_size": 3})]
|
|
|
|
rel_path = generate_data(input_features, output_features, csv_filename, num_examples=30)
|
|
|
|
config = {
|
|
INPUT_FEATURES: input_features,
|
|
OUTPUT_FEATURES: output_features,
|
|
COMBINER: {TYPE: "concat"},
|
|
TRAINER: {"train_steps": 5, "learning_rate": 0.001, BATCH_SIZE: 128},
|
|
# Missing search_alg and executor, but should still work
|
|
HYPEROPT: {
|
|
"parameters": {
|
|
"trainer.learning_rate": {
|
|
"lower": 0.0001,
|
|
"upper": 0.01,
|
|
"space": "loguniform",
|
|
}
|
|
},
|
|
"goal": "minimize",
|
|
"output_feature": output_features[0]["name"],
|
|
"metric": "loss",
|
|
"executor": {"type": "ray", "num_samples": 2},
|
|
},
|
|
}
|
|
|
|
experiment_name = f"test_hyperopt_{uuid.uuid4().hex}"
|
|
hyperopt_results = hyperopt(config, dataset=rel_path, output_directory=tmpdir, experiment_name=experiment_name)
|
|
assert hyperopt_results.experiment_analysis.results_df.shape[0] == 2
|
|
|
|
|
|
@pytest.mark.slow
|
|
def test_hyperopt_with_time_budget(csv_filename, tmpdir, ray_cluster_7cpu):
|
|
"""Tests that incomplete checkpoints created by RayTune when time budget is hit doesn't throw errors because of
|
|
missing .tune_metadata files in the checkpoint directories."""
|
|
input_features = [text_feature()]
|
|
output_features = [category_feature(output_feature=True)]
|
|
|
|
rel_path = generate_data(input_features, output_features, csv_filename, num_examples=30)
|
|
|
|
config = {
|
|
INPUT_FEATURES: input_features,
|
|
OUTPUT_FEATURES: output_features,
|
|
COMBINER: {TYPE: "concat"},
|
|
HYPEROPT: {
|
|
"goal": "minimize",
|
|
"metric": "loss",
|
|
"output_feature": output_features[0]["name"],
|
|
"search_alg": {TYPE: "variant_generator"},
|
|
"executor": {
|
|
"type": "ray",
|
|
# Ensure there is enough time for some trials to start and also for some to terminate
|
|
# to reproduce the exact issue of missing .tune_metadata files.
|
|
"time_budget_s": 30,
|
|
"cpu_resources_per_trial": 1,
|
|
"num_samples": 4,
|
|
"scheduler": {TYPE: "fifo"},
|
|
},
|
|
"parameters": {
|
|
"trainer.learning_rate": {
|
|
"lower": 0.0001,
|
|
"upper": 0.01,
|
|
"space": "loguniform",
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
experiment_name = f"test_hyperopt_{uuid.uuid4().hex}"
|
|
hyperopt(config, dataset=rel_path, output_directory=tmpdir, experiment_name=experiment_name)
|