项目文件夹

文件
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

901 行
35 KiB
Python

#! /usr/bin/env python
# Copyright (c) 2022 Predibase, 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 copy
import logging
import warnings
from collections.abc import Callable
from typing import Any
from ludwig.api_annotations import DeveloperAPI
from ludwig.constants import (
AUDIO,
BIAS,
CLASS_WEIGHTS,
COLUMN,
CONV_BIAS,
CONV_USE_BIAS,
DECODER,
DEFAULT_BIAS,
DEFAULT_USE_BIAS,
DEFAULTS,
ENCODER,
EVAL_BATCH_SIZE,
EXECUTOR,
FORCE_SPLIT,
HEIGHT,
HYPEROPT,
IMAGE,
INPUT_FEATURES,
LOSS,
MISSING_VALUE_STRATEGY,
NAME,
NUM_SAMPLES,
NUMBER,
OUTPUT_FEATURES,
PARAMETERS,
PREPROCESSING,
PROBABILITIES,
RANDOM,
RAY,
SAMPLER,
SCHEDULER,
SEARCH_ALG,
SEQUENCE,
SPLIT,
SPLIT_PROBABILITIES,
STRATIFY,
TEXT,
TIMESERIES,
TRAINER,
TRAINING,
TYPE,
USE_BIAS,
WIDTH,
)
from ludwig.features.feature_registries import get_base_type_registry, get_input_type_registry, get_output_type_registry
from ludwig.globals import LUDWIG_VERSION
from ludwig.types import (
FeatureConfigDict,
FeatureTypeDefaultsDict,
HyperoptConfigDict,
ModelConfigDict,
PreprocessingConfigDict,
TrainerConfigDict,
TrainingSetMetadataDict,
)
from ludwig.utils.metric_utils import TrainerMetric
from ludwig.utils.misc_utils import get_from_registry, merge_dict
from ludwig.utils.version_transformation import VersionTransformation, VersionTransformationRegistry
config_transformation_registry = VersionTransformationRegistry()
@DeveloperAPI
def register_config_transformation(version: str, prefixes: str | list[str] = []) -> Callable:
"""This decorator registers a transformation function for a config version. Version is the first version which
requires the transform. For example, since "training" is renamed to "trainer" in 0.5, this change should be
registered with 0.5. from_version < version <= to_version.
Args:
version: The version to register this transformation with. The earliest ludwig version which requires this
transformation.
prefixes: A list of keypath prefixes to apply this transformation to. If not specified, transforms the entire
config dict. If a prefix indicates a list, i.e. "input_features", the transformation is applied to
each element of the list (each input feature).
"""
if isinstance(prefixes, str):
prefixes = [prefixes]
def wrap(fn: Callable[[dict], dict]):
config_transformation_registry.register(VersionTransformation(transform=fn, version=version, prefixes=prefixes))
return fn
return wrap
@DeveloperAPI
def upgrade_config_dict_to_latest_version(config: ModelConfigDict) -> ModelConfigDict:
"""Updates config from an older version of Ludwig to the current version. If config does not have a
"ludwig_version" key, all updates are applied.
Args:
config: A config saved by an older version of Ludwig.
Returns A new copy of config, upgraded to the current Ludwig version. Returns config if config has no
"ludwig_version".
"""
return config_transformation_registry.update_config(
config, from_version=config.get("ludwig_version", "0.0"), to_version=LUDWIG_VERSION
)
def upgrade_model_progress(model_progress: dict) -> dict:
"""Updates model progress info to be compatible with latest ProgressTracker implementation.
Notably, we convert epoch-based stats to their step-based equivalents and reformat metrics into `TrainerMetric`
tuples.
"""
ret = copy.deepcopy(model_progress)
if "last_improvement_epoch" in ret:
ret["last_improvement_steps"] = ret["last_improvement_epoch"] * ret["batch_size"]
del ret["last_improvement_epoch"]
if "last_learning_rate_reduction_epoch" in ret:
ret["last_learning_rate_reduction_steps"] = ret["last_learning_rate_reduction_epoch"] * ret["batch_size"]
del ret["last_learning_rate_reduction_epoch"]
if "last_increase_batch_size_epoch" in ret:
ret["last_increase_batch_size_steps"] = ret["last_increase_batch_size_epoch"] * ret["batch_size"]
del ret["last_increase_batch_size_epoch"]
if "vali_metrics" in ret:
ret["validation_metrics"] = ret["vali_metrics"]
del ret["vali_metrics"]
for metric_group in ("train_metrics", "test_metrics", "validation_metrics"):
if metric_group not in ret:
continue
for tgt in ret[metric_group]:
for metric in ret[metric_group][tgt]:
if len(ret[metric_group][tgt][metric]) == 0 or isinstance(
ret[metric_group][tgt][metric][0], (tuple, list)
):
continue
ret[metric_group][tgt][metric] = [
TrainerMetric(i + 1, (i + 1) * ret["batch_size"], val)
for i, val in enumerate(ret[metric_group][tgt][metric])
]
if "tune_checkpoint_num" not in ret:
ret["tune_checkpoint_num"] = 0
# Upgrades related to extending progress tracker with explicit bests.
if "checkpoint_number" not in ret:
ret["checkpoint_number"] = 0
if "best_eval_metric_steps" not in ret:
ret["best_eval_metric_steps"] = 0
if "best_eval_metric_epoch" not in ret:
ret["best_eval_metric_epoch"] = 0
if "best_eval_metric_checkpoint_number" not in ret:
ret["best_eval_metric_checkpoint_number"] = 0
if "best_eval_train_metrics" not in ret:
ret["best_eval_train_metrics"] = {}
if "best_eval_validation_metrics" not in ret:
ret["best_eval_validation_metrics"] = {}
if "best_eval_test_metrics" not in ret:
ret["best_eval_test_metrics"] = {}
if "best_eval_metric" in ret:
ret["best_eval_metric_value"] = ret["best_eval_metric"]
del ret["best_eval_metric"]
if "last_improvement" in ret:
del ret["last_improvement"]
# Delete learning-rate related fields removed in https://github.com/ludwig-ai/ludwig/pull/2877.
if "best_reduce_learning_rate_eval_metric" in ret:
del ret["best_reduce_learning_rate_eval_metric"]
if "last_reduce_learning_rate_eval_metric_improvement" in ret:
del ret["last_reduce_learning_rate_eval_metric_improvement"]
return ret
def _traverse_dicts(config: Any, f: Callable[[dict], None]):
"""Recursively applies function f to every dictionary contained in config.
f should in-place modify the config dict. f will be called on leaves first, root last.
"""
if isinstance(config, dict):
for _k, v in config.items():
_traverse_dicts(v, f)
f(config)
elif isinstance(config, list):
for v in config:
_traverse_dicts(v, f)
@register_config_transformation("0.6", "backend")
def _update_backend_cache_credentials(backend: dict[str, Any]) -> dict[str, Any]:
if "cache_credentials" in backend:
credentials = backend.get("credentials", {})
if "cache" in credentials:
warnings.warn("`cache` already found in `backend.credentials`, ignoring `cache_credentials`")
else:
warnings.warn(
"`backend.cache_credentials` has been renamed `backend.credentials.cache`", DeprecationWarning
)
credentials["cache"] = backend.pop("cache_credentials")
backend["credentials"] = credentials
return backend
@register_config_transformation("0.6", ["output_features"])
def update_class_weights_in_features(feature: FeatureConfigDict) -> FeatureConfigDict:
if LOSS in feature:
class_weights = feature[LOSS].get(CLASS_WEIGHTS, None)
if not isinstance(class_weights, (list, dict)):
class_weights = None
feature[LOSS][CLASS_WEIGHTS] = class_weights
return feature
@register_config_transformation("0.4")
def _update_level_metadata(config: ModelConfigDict) -> ModelConfigDict:
# Replace parameters represented as keys with params represented as values.
# Precedence is defined by first in the dictionary order, so if multiple
# provided keys map to the same value, the one that appears earlier in this
# dictionary will take priority.
drop_params = {
"sequence_length_limit": "max_sequence_length",
"word_most_common": "most_common",
"word_sequence_length_limit": "max_sequence_length",
"word_tokenizer": "tokenizer",
"word_vocab_file": "vocab_file",
"char_most_common": "most_common",
"char_sequence_length_limit": "max_sequence_length",
"char_tokenizer": "tokenizer",
"char_vocab_file": "vocab_file",
}
def upgrade_params(params):
for key, value in drop_params.items():
if key in params:
if value in params:
warnings.warn(
f"Removing deprecated config preprocessing parameter {key} as new param {value} already "
f"present in the config",
DeprecationWarning,
)
else:
warnings.warn(
f"Renaming deprecated config preprocessing parameter {key} to {value}",
DeprecationWarning,
)
params[value] = params[key]
del params[key]
sequence_types = [SEQUENCE, TEXT, AUDIO, TIMESERIES]
for dtype in sequence_types:
params = config.get(PREPROCESSING, {}).get(dtype, {})
upgrade_params(params)
for feature in config[INPUT_FEATURES]:
if feature.get(TYPE) not in sequence_types:
continue
params = feature.get(PREPROCESSING, {})
upgrade_params(params)
return config
@register_config_transformation("0.5")
def rename_training_to_trainer(config: ModelConfigDict) -> ModelConfigDict:
if TRAINING in config:
warnings.warn(
'Config section "training" renamed to "trainer" and will be removed in a future version', DeprecationWarning
)
config[TRAINER] = config[TRAINING]
del config[TRAINING]
return config
@register_config_transformation("0.5", ["input_features", "output_features"])
def _upgrade_use_bias_in_features(feature: FeatureConfigDict) -> FeatureConfigDict:
def upgrade_use_bias(config):
if BIAS in config:
warnings.warn(
'Parameter "bias" renamed to "use_bias" and will be removed in a future version', DeprecationWarning
)
config[USE_BIAS] = config[BIAS]
del config[BIAS]
if CONV_BIAS in config:
warnings.warn(
'Parameter "conv_bias" renamed to "conv_use_bias" and will be removed in a future version',
DeprecationWarning,
)
config[CONV_USE_BIAS] = config[CONV_BIAS]
del config[CONV_BIAS]
if DEFAULT_BIAS in config:
warnings.warn(
'Parameter "default_bias" renamed to "default_use_bias" and will be removed in a future version',
DeprecationWarning,
)
config[DEFAULT_USE_BIAS] = config[DEFAULT_BIAS]
del config[DEFAULT_BIAS]
_traverse_dicts(feature, upgrade_use_bias)
return feature
@register_config_transformation("0.5", ["input_features", "output_features"])
def _upgrade_feature(feature: FeatureConfigDict) -> FeatureConfigDict:
"""Upgrades feature config (in-place)"""
if feature.get(TYPE) == "numerical":
warnings.warn(
'Feature type "numerical" renamed to "number" and will be removed in a future version', DeprecationWarning
)
feature[TYPE] = NUMBER
if feature.get(TYPE) == AUDIO:
if PREPROCESSING in feature:
feature[PREPROCESSING] = upgrade_audio_preprocessing(feature[PREPROCESSING])
warnings.warn(
"Parameters specified at the `audio_feature` parameter level have been unnested and should now "
"be specified at the preprocessing level. Support for `audio_feature` will be removed in a future version",
DeprecationWarning,
)
return feature
def upgrade_audio_preprocessing(preproc_dict: PreprocessingConfigDict) -> PreprocessingConfigDict:
if "audio_feature" in preproc_dict:
for k, v in preproc_dict["audio_feature"].items():
preproc_dict[k] = v
del preproc_dict["audio_feature"]
return preproc_dict
@register_config_transformation("0.6", ["input_features"])
def _upgrade_encoder_params(feature: FeatureConfigDict) -> FeatureConfigDict:
return _upgrade_encoder_decoder_params(feature, True)
@register_config_transformation("0.6", ["output_features"])
def _upgrade_decoder_params(feature: FeatureConfigDict) -> FeatureConfigDict:
return _upgrade_encoder_decoder_params(feature, False)
def _upgrade_encoder_decoder_params(feature: FeatureConfigDict, input_feature: bool) -> FeatureConfigDict:
"""
This function nests un-nested encoder/decoder parameters to conform with the new config structure for 0.6
Args:
feature (Dict): Feature to nest encoder/decoder params for.
input_feature (Bool): Whether this feature is an input feature or not.
"""
if TYPE not in feature:
return feature
try:
if input_feature:
module_type = ENCODER
feature_cls = get_from_registry(feature[TYPE], get_input_type_registry())
else:
module_type = DECODER
feature_cls = get_from_registry(feature[TYPE], get_output_type_registry())
except ValueError:
logging.exception("Failed to obtain encoder / decoder from registry")
return feature
feature_schema_cls = feature_cls.get_schema_cls()
feature_keys = feature_schema_cls.get_valid_field_names()
# These keys have been renamed from the form below to `fc_<key>` in the new config
fc_layer_keys = [
"fc_layers",
"output_size",
"use_bias",
"weights_initializer",
"bias_initializer",
"norm",
"norm_params",
"activation",
"dropout",
]
module = feature.get(module_type, {})
warn = False
if isinstance(module, str):
module = {TYPE: module}
feature[module_type] = module
warn = True
nested_params = []
for k, v in feature.items():
if k not in feature_keys:
module[k] = v
if k in fc_layer_keys and module_type == DECODER:
module[f"fc_{k}"] = v
nested_params.append(k)
warn = True
if module:
if module_type in feature:
feature[module_type].update(module)
else:
feature[module_type] = module
for k in nested_params:
del feature[k]
if warn:
warnings.warn(
f"{module_type} specific parameters should now be nested within a dictionary under the '{module_type}' "
f"parameter. Support for un-nested {module_type} specific parameters will be removed in a future version",
DeprecationWarning,
)
return feature
@register_config_transformation("0.5", ["hyperopt"])
def _upgrade_hyperopt(hyperopt: HyperoptConfigDict) -> HyperoptConfigDict:
"""Upgrades hyperopt config (in-place)"""
# check for use of legacy "training" reference, if any found convert to "trainer"
if PARAMETERS in hyperopt:
hparams = hyperopt[PARAMETERS]
for k, v in list(hparams.items()):
substr = "training."
if k.startswith(substr):
warnings.warn(
'Config section "training" renamed to "trainer" and will be removed in a future version',
DeprecationWarning,
)
hparams["trainer." + k[len(substr) :]] = v
del hparams[k]
# check for legacy parameters in "executor"
if EXECUTOR in hyperopt:
hpexecutor = hyperopt[EXECUTOR]
executor_type = hpexecutor.get(TYPE, None)
if executor_type is not None and executor_type != RAY:
warnings.warn(
f'executor type "{executor_type}" not supported, converted to "ray" will be flagged as error '
"in a future version",
DeprecationWarning,
)
hpexecutor[TYPE] = RAY
# if search_alg not at top level and is present in executor, promote to top level
if SEARCH_ALG in hpexecutor:
# promote only if not in top-level, otherwise use current top-level
if SEARCH_ALG not in hyperopt:
hyperopt[SEARCH_ALG] = hpexecutor[SEARCH_ALG]
if isinstance(hyperopt[SEARCH_ALG], str):
hyperopt[SEARCH_ALG] = {TYPE: hyperopt[SEARCH_ALG]}
del hpexecutor[SEARCH_ALG]
else:
warnings.warn(
'Missing "executor" section, adding "ray" executor will be flagged as error in a future version',
DeprecationWarning,
)
hyperopt[EXECUTOR] = {TYPE: RAY}
# check for legacy "sampler" section
if SAMPLER in hyperopt:
warnings.warn(
f'"{SAMPLER}" is no longer supported, converted to "{SEARCH_ALG}". "{SAMPLER}" will be flagged as '
"error in a future version",
DeprecationWarning,
)
if SEARCH_ALG in hyperopt[SAMPLER]:
if SEARCH_ALG not in hyperopt:
hyperopt[SEARCH_ALG] = hyperopt[SAMPLER][SEARCH_ALG]
if isinstance(hyperopt[SEARCH_ALG], str):
hyperopt[SEARCH_ALG] = {TYPE: hyperopt[SEARCH_ALG]}
warnings.warn('Moved "search_alg" to hyperopt config top-level', DeprecationWarning)
# if num_samples or scheduler exist in SAMPLER move to EXECUTOR Section
if NUM_SAMPLES in hyperopt[SAMPLER] and NUM_SAMPLES not in hyperopt[EXECUTOR]:
hyperopt[EXECUTOR][NUM_SAMPLES] = hyperopt[SAMPLER][NUM_SAMPLES]
warnings.warn('Moved "num_samples" from "sampler" to "executor"', DeprecationWarning)
if SCHEDULER in hyperopt[SAMPLER] and SCHEDULER not in hyperopt[EXECUTOR]:
hyperopt[EXECUTOR][SCHEDULER] = hyperopt[SAMPLER][SCHEDULER]
warnings.warn('Moved "scheduler" from "sampler" to "executor"', DeprecationWarning)
if SCHEDULER in hyperopt[EXECUTOR] and len(hyperopt[EXECUTOR][SCHEDULER].keys()) == 0:
del hyperopt[EXECUTOR][SCHEDULER]
# remove legacy section
del hyperopt[SAMPLER]
if SEARCH_ALG not in hyperopt:
# make top-level as search_alg, if missing put in default value
hyperopt[SEARCH_ALG] = {TYPE: "variant_generator"}
warnings.warn(
'Missing "search_alg" at hyperopt top-level, adding in default value, will be flagged as error '
"in a future version",
DeprecationWarning,
)
return hyperopt
@register_config_transformation("0.5", ["trainer"])
def _upgrade_trainer(trainer: TrainerConfigDict) -> TrainerConfigDict:
"""Upgrades trainer config (in-place)"""
eval_batch_size = trainer.get(EVAL_BATCH_SIZE)
if eval_batch_size == 0:
warnings.warn(
"`trainer.eval_batch_size` value `0` changed to `None`, will be unsupported in a future version",
DeprecationWarning,
)
trainer[EVAL_BATCH_SIZE] = None
return trainer
@register_config_transformation("0.5")
def _upgrade_preprocessing_defaults(config: ModelConfigDict) -> ModelConfigDict:
"""Move feature-specific preprocessing parameters into defaults in config (in-place)"""
type_specific_preprocessing_params = {}
# If preprocessing section specified and it contains feature specific preprocessing parameters,
# make a copy and delete it from the preprocessing section
for parameter in list(config.get(PREPROCESSING, {})):
if parameter in get_base_type_registry():
warnings.warn(
f"Moving preprocessing configuration for `{parameter}` feature type from `preprocessing` section"
" to `defaults` section in Ludwig config. This will be unsupported in a future version.",
DeprecationWarning,
)
type_specific_preprocessing_params[parameter] = config[PREPROCESSING].pop(parameter)
if parameter == "numerical":
warnings.warn(
f"Moving preprocessing configuration for `{parameter}` feature type from `preprocessing` section"
" to `defaults` section in Ludwig config. This will be unsupported in a future version.",
DeprecationWarning,
)
type_specific_preprocessing_params[NUMBER] = config[PREPROCESSING].pop(parameter)
# Delete empty preprocessing section if no other preprocessing parameters specified
if PREPROCESSING in config and not config[PREPROCESSING]:
del config[PREPROCESSING]
# Update defaults with the default feature specific preprocessing parameters
defaults = config.get(DEFAULTS, {})
for feature_type, preprocessing_param in type_specific_preprocessing_params.items():
if PREPROCESSING in preprocessing_param:
preprocessing_param = preprocessing_param[PREPROCESSING]
if feature_type == AUDIO:
preprocessing_param = upgrade_audio_preprocessing(preprocessing_param)
# If defaults was empty, then create a new key with feature type
if feature_type not in defaults:
defaults[feature_type] = {PREPROCESSING: preprocessing_param}
# Feature type exists but preprocessing hasn't be specified
elif PREPROCESSING not in defaults[feature_type]:
defaults[feature_type][PREPROCESSING] = preprocessing_param
# Update default feature specific preprocessing with parameters from config
else:
defaults[feature_type][PREPROCESSING].update(
merge_dict(defaults[feature_type][PREPROCESSING], preprocessing_param)
)
if defaults:
config[DEFAULTS] = defaults
return config
@register_config_transformation("0.5", "preprocessing")
def _upgrade_preprocessing_split(preprocessing: PreprocessingConfigDict) -> PreprocessingConfigDict:
"""Upgrade split related parameters in preprocessing."""
split_params = {}
force_split = preprocessing.pop(FORCE_SPLIT, None)
split_probabilities = preprocessing.pop(SPLIT_PROBABILITIES, None)
stratify = preprocessing.pop(STRATIFY, None)
if split_probabilities is not None:
split_params[PROBABILITIES] = split_probabilities
warnings.warn(
"`preprocessing.split_probabilities` has been replaced by `preprocessing.split.probabilities`, "
"will be flagged as error in a future version",
DeprecationWarning,
)
if stratify is not None:
split_params[TYPE] = STRATIFY
split_params[COLUMN] = stratify
warnings.warn(
"`preprocessing.stratify` has been replaced by `preprocessing.split.column` "
'when setting `preprocessing.split.type` to "stratify", '
"will be flagged as error in a future version",
DeprecationWarning,
)
if force_split is not None:
warnings.warn(
"`preprocessing.force_split` has been replaced by `preprocessing.split.type`, "
"will be flagged as error in a future version",
DeprecationWarning,
)
if TYPE not in split_params:
split_params[TYPE] = RANDOM
if split_params:
preprocessing[SPLIT] = split_params
if AUDIO in preprocessing:
if "audio_feature" in preprocessing[AUDIO]:
for k, v in preprocessing[AUDIO]["audio_feature"].items():
preprocessing[AUDIO][k] = v
del preprocessing[AUDIO]["audio_feature"]
warnings.warn(
"Parameters specified at the `audio_feature` parameter level have been unnested and should now "
"be specified at the preprocessing level. Support for `audio_feature` will be removed in a future version",
DeprecationWarning,
)
return preprocessing
@register_config_transformation("0.5")
def update_training(config: ModelConfigDict) -> ModelConfigDict:
if TRAINING in config:
warnings.warn(
'Config section "training" renamed to "trainer" and will be removed in a future version', DeprecationWarning
)
config[TRAINER] = config[TRAINING]
del config[TRAINING]
return config
@register_config_transformation("0.6")
def upgrade_missing_value_strategy(config: ModelConfigDict) -> ModelConfigDict:
for input_feature in config.get(INPUT_FEATURES, []):
if _is_old_missing_value_strategy(input_feature):
_update_old_missing_value_strategy(input_feature)
for output_feature in config.get(OUTPUT_FEATURES, []):
if _is_old_missing_value_strategy(output_feature):
_update_old_missing_value_strategy(output_feature)
for feature, feature_defaults in config.get(DEFAULTS, {}).items():
if _is_old_missing_value_strategy(feature_defaults):
_update_old_missing_value_strategy(config.get(DEFAULTS).get(feature))
return config
@register_config_transformation("0.6", ["trainer"])
def _upgrade_max_batch_size(trainer: TrainerConfigDict) -> TrainerConfigDict:
if "increase_batch_size_on_plateau_max" in trainer:
warnings.warn(
'Config param "increase_batch_size_on_plateau_max" renamed to "max_batch_size" and will be '
"removed in a future version",
DeprecationWarning,
)
increase_batch_size_on_plateau_max_val = trainer.pop("increase_batch_size_on_plateau_max")
if "max_batch_size" in trainer:
warnings.warn('"max_batch_size" config param already set. Discarding "increase_batch_size_on_plateau_max".')
else:
warnings.warn(
f'Setting "max_batch_size" config param to "increase_batch_size_on_plateau_max" value '
f'({increase_batch_size_on_plateau_max_val}) and discarding "increase_batch_size_on_plateau_max"'
)
trainer["max_batch_size"] = increase_batch_size_on_plateau_max_val
return trainer
@register_config_transformation("0.6")
def remove_trainer_type(config: ModelConfigDict) -> ModelConfigDict:
# LLM Model types support different trainer types
if config.get("model_type", None) == "llm":
return config
if TYPE in config.get("trainer", {}):
warnings.warn(
"Config param `type` has been removed from the trainer. The trainer type is determined by the top level "
" `model_type` parameter. Support for the `type` params in trainer will be removed in a future version",
DeprecationWarning,
)
del config["trainer"][TYPE]
return config
@register_config_transformation("0.7", ["trainer"])
def learning_rate_scheduler(trainer: TrainerConfigDict) -> TrainerConfigDict:
key_mapping = {
"reduce_learning_rate_on_plateau": "reduce_on_plateau",
"reduce_learning_rate_on_plateau_patience": "reduce_on_plateau_patience",
"reduce_learning_rate_on_plateau_rate": "reduce_on_plateau_rate",
"reduce_learning_rate_eval_metric": "reduce_eval_metric",
"reduce_learning_rate_eval_split": "reduce_eval_split",
"decay": "decay",
"decay_steps": "decay_steps",
"decay_rate": "decay_rate",
"staircase": "staircase",
"learning_rate_warmup_epochs": "warmup_evaluations",
}
lr_scheduler = trainer.get("learning_rate_scheduler", {})
for old_key, new_key in key_mapping.items():
if old_key in trainer:
warnings.warn(
f"Config param `trainer.{old_key}` has been moved to `trainer.learning_rate_scheduler.{new_key}`.",
DeprecationWarning,
)
if new_key in lr_scheduler:
warnings.warn(
f"`trainer.learning_rate_scheduler.{new_key}` config param already set. "
f"Discarding `trainer.{old_key}`."
)
else:
value = trainer[old_key]
if old_key == "decay" and isinstance(value, bool):
# Decay has changed from a bool to an optional enum
lr_scheduler[new_key] = "exponential" if value else None
elif old_key == "reduce_learning_rate_on_plateau":
lr_scheduler[new_key] = int(value)
else:
lr_scheduler[new_key] = value
del trainer[old_key]
if lr_scheduler:
trainer["learning_rate_scheduler"] = lr_scheduler
return trainer
@register_config_transformation("0.7", ["input_features"])
def _upgrade_legacy_image_encoders(feature: FeatureConfigDict) -> FeatureConfigDict:
"""Upgrade legacy image encoder references.
The old _resnet_legacy and _vit_legacy encoders have been removed. The "resnet" and "vit" encoder type names now map
directly to the TorchVision-based encoders, so no transformation is needed. Any legacy-specific parameters (e.g.
resnet_size, num_hidden_layers) that are not recognized by the new encoders will be caught by schema validation.
"""
if feature.get(TYPE) != IMAGE:
return feature
encoder = feature.get(ENCODER, {})
encoder_type = encoder.get(TYPE)
# Map old internal names to the current torchvision encoder names
legacy_internal_mapping = {
"_resnet_legacy": "resnet",
"_vit_legacy": "vit",
}
if encoder_type in legacy_internal_mapping:
warnings.warn(
f"Encoder type '{encoder_type}' has been removed. "
f"Mapping to TorchVision encoder '{legacy_internal_mapping[encoder_type]}'.",
DeprecationWarning,
)
encoder[TYPE] = legacy_internal_mapping[encoder_type]
return feature
@register_config_transformation("0.7")
def upgrade_missing_hyperopt(config: ModelConfigDict) -> ModelConfigDict:
hyperopt = config.get(HYPEROPT)
if hyperopt == {}:
# This is a deprecated form of providing a missing hyperopt section, as it violates the schema definition
warnings.warn(
"Config section `hyperopt: {}` is deprecated, please set `hyperopt: null` to disable hyperopt.",
DeprecationWarning,
)
del config[HYPEROPT]
return config
@register_config_transformation("0.7", "defaults")
def remove_extra_type_param_in_defaults_config(defaults: FeatureTypeDefaultsDict) -> FeatureTypeDefaultsDict:
"""Fixes a bug introduced before 0.7.3.
[1] and subsequent refactors accidentally introduced a bug where a `type` param was added to every feature in the
defaults config. It was removed by [2], but made it into one of the patch releases. This transformation removes that
`type` param from each section of the defaults config if it exists.
[1]: https://github.com/ludwig-ai/ludwig/pull/3223
[2]: https://github.com/ludwig-ai/ludwig/pull/3258
"""
defaults_copy = copy.deepcopy(defaults)
for feature_type, feature_config in defaults.items():
if TYPE in feature_config:
del defaults_copy[feature_type][TYPE]
return defaults_copy
def upgrade_metadata(metadata: TrainingSetMetadataDict) -> TrainingSetMetadataDict:
# TODO(travis): stopgap solution, we should make it so we don't need to do this
# by decoupling config and metadata
metadata = copy.deepcopy(metadata)
_upgrade_metadata_missing_values(metadata)
return metadata
def _upgrade_metadata_missing_values(metadata: TrainingSetMetadataDict):
for _k, v in metadata.items():
if isinstance(v, dict) and _is_old_missing_value_strategy(v):
_update_old_missing_value_strategy(v)
elif isinstance(v, dict) and _is_image_feature(v):
_update_old_image_preprocessing(v)
def _update_old_missing_value_strategy(feature_config: FeatureConfigDict):
missing_value_strategy = feature_config.get(PREPROCESSING).get(MISSING_VALUE_STRATEGY)
replacement_strategy = "bfill" if missing_value_strategy == "backfill" else "ffill"
feature_name = feature_config.get(NAME)
warnings.warn(
f"Using `{replacement_strategy}` instead of `{missing_value_strategy}` as the missing value strategy"
f" for `{feature_name}`. These are identical. `{missing_value_strategy}` will be removed in a future version",
DeprecationWarning,
)
feature_config[PREPROCESSING].update({MISSING_VALUE_STRATEGY: replacement_strategy})
def _is_old_missing_value_strategy(feature_config: FeatureConfigDict):
if PREPROCESSING not in feature_config:
return False
missing_value_strategy = feature_config.get(PREPROCESSING).get(MISSING_VALUE_STRATEGY, None)
if not missing_value_strategy or missing_value_strategy not in ("backfill", "pad"):
return False
return True
def _is_image_feature(feature_config: FeatureConfigDict):
preproc = feature_config.get(PREPROCESSING, {})
return HEIGHT in preproc and WIDTH in preproc
def _update_old_image_preprocessing(feature_config: FeatureConfigDict):
preprocessing = feature_config.get(PREPROCESSING)
if not preprocessing:
return
preprocessing["standardize_image"] = preprocessing.get("standardize_image")
@register_config_transformation("0.11", ["input_features"])
def _upgrade_removed_text_encoders(feature: FeatureConfigDict) -> FeatureConfigDict:
"""Upgrade configs that reference removed text encoder types.
TransformerXL, CTRL, and FlauBERT encoders have been removed from Ludwig because they are
discontinued or superseded by other models (HuggingFace deprecated TransformerXL; CTRL has
no active community use; FlauBERT is superseded by CamemBERT and multilingual models like
XLM-RoBERTa). Configs using these types are automatically remapped to `auto_transformer`.
Note: the `pretrained_model_name_or_path` field must be set explicitly since `auto_transformer`
has no built-in default model name.
"""
if feature.get(TYPE) != TEXT:
return feature
encoder = feature.get(ENCODER, {})
encoder_type = encoder.get(TYPE)
removed_encoder_mapping = {
"transformer_xl": "auto_transformer",
"ctrl": "auto_transformer",
"flaubert": "auto_transformer",
}
if encoder_type in removed_encoder_mapping:
replacement = removed_encoder_mapping[encoder_type]
warnings.warn(
f"Text encoder type '{encoder_type}' has been removed and is no longer supported. "
f"Remapping to '{replacement}'. You may need to set `pretrained_model_name_or_path` "
f"explicitly in the encoder config to specify the model to use.",
DeprecationWarning,
)
encoder[TYPE] = replacement
return feature