项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

373 行
15 KiB
Python

import copy
import logging
import os
from pprint import pformat
import pandas as pd
import yaml
from tabulate import tabulate
from ludwig.api import LudwigModel
from ludwig.backend import Backend, initialize_backend, LocalBackend
from ludwig.callbacks import Callback
from ludwig.constants import (
AUTO,
COMBINED,
EXECUTOR,
GOAL,
HYPEROPT,
LOSS,
MAX_CONCURRENT_TRIALS,
METRIC,
NAME,
OUTPUT_FEATURES,
PARAMETERS,
PREPROCESSING,
SEARCH_ALG,
SPLIT,
TEST,
TRAINING,
TYPE,
VALIDATION,
)
from ludwig.data.split import get_splitter
from ludwig.hyperopt.results import HyperoptResults
from ludwig.hyperopt.utils import (
log_warning_if_all_grid_type_parameters,
print_hyperopt_results,
save_hyperopt_stats,
should_tune_preprocessing,
update_hyperopt_params_with_defaults,
)
from ludwig.schema.model_config import ModelConfig
from ludwig.utils.backward_compatibility import upgrade_config_dict_to_latest_version
from ludwig.utils.dataset_utils import generate_dataset_statistics
from ludwig.utils.defaults import default_random_seed
from ludwig.utils.fs_utils import makedirs, open_file
try:
from ray.tune import Callback as TuneCallback
from ludwig.backend.ray import RayBackend
except ImportError:
TuneCallback = object
class RayBackend:
pass
logger = logging.getLogger(__name__)
def hyperopt(
config: str | dict,
dataset: str | dict | pd.DataFrame = None,
training_set: str | dict | pd.DataFrame = None,
validation_set: str | dict | pd.DataFrame = None,
test_set: str | dict | pd.DataFrame = None,
training_set_metadata: str | dict | None = None,
data_format: str | None = None,
experiment_name: str = "hyperopt",
model_name: str = "run",
resume: bool | None = None,
skip_save_training_description: bool = False,
skip_save_training_statistics: bool = False,
skip_save_model: bool = False,
skip_save_progress: bool = False,
skip_save_log: bool = False,
skip_save_processed_input: bool = True,
skip_save_unprocessed_output: bool = False,
skip_save_predictions: bool = False,
skip_save_eval_stats: bool = False,
skip_save_hyperopt_statistics: bool = False,
output_directory: str = "results",
gpus: str | int | list[int] | None = None,
gpu_memory_limit: float | None = None,
allow_parallel_threads: bool = True,
callbacks: list[Callback] | None = None,
tune_callbacks: list[TuneCallback] | None = None,
backend: Backend | str = None,
random_seed: int = default_random_seed,
hyperopt_log_verbosity: int = 3,
**kwargs,
) -> HyperoptResults:
"""Run hyperparameter optimization.
Args:
config: Config dict or path to a YAML config file.
dataset: Source containing the entire dataset. If it has a split
column, it will be used for splitting (0: train, 1: validation,
2: test); otherwise the dataset will be randomly split.
training_set: Source containing training data.
validation_set: Source containing validation data.
test_set: Source containing test data.
training_set_metadata: Metadata JSON file or loaded metadata dict.
Intermediate preprocessed structure containing feature mappings
created the first time an input file is used.
data_format: Format to interpret data sources. Inferred automatically
if not specified. Valid values: ``'auto'``, ``'csv'``,
``'excel'``, ``'feather'``, ``'fwf'``, ``'hdf5'``,
``'html'``, ``'json'``, ``'jsonl'``, ``'parquet'``,
``'pickle'``, ``'sas'``, ``'spss'``, ``'stata'``, ``'tsv'``.
experiment_name: Name for the experiment.
model_name: Name of the model being used.
resume: If ``True``, resume from the previous run in ``output_directory``
with the same experiment name. If ``False``, create new trials
ignoring any prior state. Defaults to resuming when a matching
experiment exists, creating new trials otherwise.
skip_save_training_description: Disable saving the description JSON
file.
skip_save_training_statistics: Disable saving training statistics
JSON file.
skip_save_model: Disable saving model weights after each epoch the
validation metric improves. The returned model will have weights
from the final epoch rather than the best epoch.
skip_save_progress: Disable saving weights and stats after each epoch
(disables training resumption).
skip_save_log: Disable saving TensorBoard logs.
skip_save_processed_input: Disable caching preprocessed input as
HDF5/JSON files.
skip_save_unprocessed_output: If ``True``, skip saving raw numpy
output files; only postprocessed CSV files are saved.
skip_save_predictions: Disable saving test prediction CSV files.
skip_save_eval_stats: Disable saving test statistics JSON file.
skip_save_hyperopt_statistics: Disable saving hyperopt stats file.
output_directory: Directory that will contain training statistics,
TensorBoard logs, the saved model, and training progress files.
gpus: List of GPUs available for training.
gpu_memory_limit: Maximum memory fraction ``[0, 1]`` allowed to
allocate per GPU device.
allow_parallel_threads: Allow PyTorch to use multithreading
parallelism (improves performance at the cost of determinism).
callbacks: List of ``Callback`` objects providing hooks into the
Ludwig pipeline.
tune_callbacks: Additional Ray Tune callbacks.
backend: Backend or string name of the backend to use for
preprocessing and training.
random_seed: Random seed for weights initialization, splits, and
shuffling.
hyperopt_log_verbosity: Verbosity of Ray Tune log messages.
0 = silent, 1 = status only, 2 = status + brief results,
3 = status + detailed results.
Returns:
Trial results ordered by descending performance on the target metric.
"""
from ludwig.hyperopt.execution import get_build_hyperopt_executor, RayTuneExecutor
# check if config is a path or a dict
if isinstance(config, str): # assume path
with open_file(config, "r") as def_file:
config_dict = yaml.safe_load(def_file)
else:
config_dict = config
if HYPEROPT not in config_dict:
raise ValueError("Hyperopt Section not present in config")
# backwards compatibility
upgraded_config = upgrade_config_dict_to_latest_version(config_dict)
# Initialize config object
config_obj = ModelConfig.from_dict(upgraded_config)
# Retain pre-merged config for hyperopt schema generation
premerged_config = copy.deepcopy(upgraded_config)
# Get full config with defaults
full_config = config_obj.to_dict() # TODO (Connor): Refactor to use config object
hyperopt_config = full_config[HYPEROPT]
# Explicitly default to a local backend to avoid picking up Ray
# backend from the environment.
backend = backend or config_dict.get("backend") or "local"
backend = initialize_backend(backend)
update_hyperopt_params_with_defaults(hyperopt_config)
# Check if all features are grid type parameters and log UserWarning if needed
log_warning_if_all_grid_type_parameters(hyperopt_config)
# Infer max concurrent trials
if hyperopt_config[EXECUTOR].get(MAX_CONCURRENT_TRIALS) == AUTO:
hyperopt_config[EXECUTOR][MAX_CONCURRENT_TRIALS] = backend.max_concurrent_trials(hyperopt_config)
logger.info(f"Setting max_concurrent_trials to {hyperopt_config[EXECUTOR][MAX_CONCURRENT_TRIALS]}")
# Print hyperopt config
logger.info("Hyperopt Config")
logger.info(pformat(hyperopt_config, indent=4))
logger.info("\n")
search_alg = hyperopt_config[SEARCH_ALG]
executor = hyperopt_config[EXECUTOR]
parameters = hyperopt_config[PARAMETERS]
split = hyperopt_config[SPLIT]
output_feature = hyperopt_config["output_feature"]
metric = hyperopt_config[METRIC]
goal = hyperopt_config[GOAL]
######################
# check validity of output_feature / metric/ split combination
######################
splitter = get_splitter(**full_config[PREPROCESSING]["split"])
if split == TRAINING:
if training_set is None and not splitter.has_split(0):
raise ValueError(
f'The data for the specified split for hyperopt "{split}" '
"was not provided, "
"or the split amount specified in the preprocessing section "
"of the config is not greater than 0"
)
elif split == VALIDATION:
if validation_set is None and not splitter.has_split(1):
raise ValueError(
f'The data for the specified split for hyperopt "{split}" '
"was not provided, "
"or the split amount specified in the preprocessing section "
"of the config is not greater than 0"
)
elif split == TEST:
if test_set is None and not splitter.has_split(2):
raise ValueError(
f'The data for the specified split for hyperopt "{split}" '
"was not provided, "
"or the split amount specified in the preprocessing section "
"of the config is not greater than 0"
)
else:
raise ValueError(
f'unrecognized hyperopt split "{split}". Please provide one of: { ({TRAINING, VALIDATION, TEST}) }'
)
if output_feature == COMBINED:
if metric != LOSS:
raise ValueError('The only valid metric for "combined" output feature is "loss"')
else:
output_feature_names = {of[NAME] for of in full_config[OUTPUT_FEATURES]}
if output_feature not in output_feature_names:
raise ValueError(
f'The output feature specified for hyperopt "{output_feature}" '
"cannot be found in the config. "
f'Available ones are: {output_feature_names} and "combined"'
)
hyperopt_executor = get_build_hyperopt_executor(executor[TYPE])(
parameters, output_feature, metric, goal, split, search_alg=search_alg, **executor
)
# Explicitly default to a local backend to avoid picking up Ray
# backend from the environment.
backend = backend or config_dict.get("backend") or "local"
backend = initialize_backend(backend)
from ludwig.hyperopt.optuna_executor import OptunaExecutor
if not (
isinstance(backend, LocalBackend)
or isinstance(hyperopt_executor, OptunaExecutor)
or (isinstance(hyperopt_executor, RayTuneExecutor) and isinstance(backend, RayBackend))
):
raise ValueError(
"Hyperopt requires using a `local` backend at this time, or "
"`ray` backend with `ray` executor, or `optuna` executor."
)
for callback in callbacks or []:
callback.on_hyperopt_init(experiment_name)
if not should_tune_preprocessing(full_config):
# preprocessing is not being tuned, so generate it once before starting trials
for callback in callbacks or []:
callback.on_hyperopt_preprocessing_start(experiment_name)
model = LudwigModel(
config=full_config,
backend=backend,
gpus=gpus,
gpu_memory_limit=gpu_memory_limit,
allow_parallel_threads=allow_parallel_threads,
callbacks=callbacks,
)
preprocessed = model.preprocess(
dataset=dataset,
training_set=training_set,
validation_set=validation_set,
test_set=test_set,
training_set_metadata=training_set_metadata,
data_format=data_format,
skip_save_processed_input=skip_save_processed_input,
random_seed=random_seed,
)
training_set = preprocessed.training_set
validation_set = preprocessed.validation_set
test_set = preprocessed.test_set
training_set_metadata = preprocessed.training_set_metadata
dataset = None
dataset_statistics = generate_dataset_statistics(training_set, validation_set, test_set)
logger.info("\nDataset Statistics")
logger.info(tabulate(dataset_statistics, headers="firstrow", tablefmt="fancy_grid"))
for callback in callbacks or []:
callback.on_hyperopt_preprocessing_end(experiment_name)
for callback in callbacks or []:
callback.on_hyperopt_start(experiment_name)
hyperopt_results = hyperopt_executor.execute(
premerged_config,
dataset=dataset,
training_set=training_set,
validation_set=validation_set,
test_set=test_set,
training_set_metadata=training_set_metadata,
data_format=data_format,
experiment_name=experiment_name,
model_name=model_name,
resume=resume,
skip_save_training_description=skip_save_training_description,
skip_save_training_statistics=skip_save_training_statistics,
skip_save_model=skip_save_model,
skip_save_progress=skip_save_progress,
skip_save_log=skip_save_log,
skip_save_processed_input=skip_save_processed_input,
skip_save_unprocessed_output=skip_save_unprocessed_output,
skip_save_predictions=skip_save_predictions,
skip_save_eval_stats=skip_save_eval_stats,
output_directory=output_directory,
gpus=gpus,
gpu_memory_limit=gpu_memory_limit,
allow_parallel_threads=allow_parallel_threads,
callbacks=callbacks,
tune_callbacks=tune_callbacks,
backend=backend,
random_seed=random_seed,
hyperopt_log_verbosity=hyperopt_log_verbosity,
**kwargs,
)
if backend.is_coordinator():
print_hyperopt_results(hyperopt_results)
if not skip_save_hyperopt_statistics:
with backend.storage.artifacts.use_credentials():
results_directory = os.path.join(output_directory, experiment_name)
makedirs(results_directory, exist_ok=True)
hyperopt_stats = {
"hyperopt_config": hyperopt_config,
"hyperopt_results": [t.to_dict() for t in hyperopt_results.ordered_trials],
}
save_hyperopt_stats(hyperopt_stats, results_directory)
logger.info(f"Hyperopt stats saved to: {results_directory}")
for callback in callbacks or []:
callback.on_hyperopt_end(experiment_name)
callback.on_hyperopt_finish(experiment_name)
logger.info("Finished hyperopt")
return hyperopt_results