legacy lambda arguments not found in keras 3 instantiates a keras model from its config args config configuration dictionary customobjects optional dictionary mapping names strings to custom classes or functions to be considered during deserialization returns a keras model instance uncompiled raises typeerror if config is not a dictionary handle backwards compatibility for keras lambdas todonkovela swap find and replace args during keras 3 0 release replace keras refs with keras returns a dictionary containing the model metadata from keras import version as kerasversion modelconfig classname model class name try modelconfigconfig model getconfig except notimplementederror as e if requireconfig raise e metadata dict kerasversionstrkerasversion backendbackend backend modelconfigmodelconfig if getattrmodel optimizer false and includeoptimizer if model compiled trainingconfig model compileconfig config trainingconfig popoptimizer none handled separately metadatatrainingconfig serializenestedconfig trainingconfig optimizerconfig classname objectregistration getregisteredname model optimizer class config model optimizer getconfig metadatatrainingconfigoptimizerconfig optimizerconfig return metadata def compileargsfromtrainingconfigtrainingconfig customobjectsnone ensure backwards compatibility for optimizers in legacy h5 files recover losses ensure backwards compatibility for losses in legacy h5 files recover metrics ensure backwards compatibility for metrics in legacy h5 files recover weighted metrics serialized a nested structure of keras objects def serializefnobj if callableobj return serialization serializekerasobjectobj return obj return tree mapstructureserializefn config def deserializenestedconfigdeserializefn config deserialize metrics leaving special strings untouched if metricconfig in accuracy acc crossentropy ce do not deserialize accuracy and crossentropy strings as we have special case handling for these in compile based on model output shape return metricconfig return metricsmodule deserializemetricconfig def findreplacenesteddictconfig find replace dictstr json dumpsconfig dictstr dictstr replacefind replace config json loadsdictstr return config def resolvecompileargumentscompatobj objconfig module if isinstanceobj str and obj not in module allobjectsdict obj module getobjconfigconfigname return obj def trybuildcompiledargumentsmodel try if not model compiledloss built model compiledloss buildmodel outputs if not model compiledmetrics built model compiledmetrics buildmodel outputs model outputs except logging warning compiled the loaded model but the compiled metrics have yet to be built model compilemetrics will be empty until you train or evaluate the model legacy lambda arguments not found in keras 3 instantiates a keras model from its config args config configuration dictionary custom_objects optional dictionary mapping names strings to custom classes or functions to be considered during deserialization returns a keras model instance uncompiled raises typeerror if config is not a dictionary handle backwards compatibility for keras lambdas todo nkovela swap find and replace args during keras 3 0 release replace keras refs with keras returns a dictionary containing the model metadata handled separately return model compile arguments from training config ensure backwards compatibility for optimizers in legacy h5 files recover losses ensure backwards compatibility for losses in legacy h5 files recover metrics ensure backwards compatibility for metrics in legacy h5 files recover weighted metrics serialized a nested structure of keras objects deserializes arbitrary keras config using deserialize_fn serialized keras object serialized function or string deserialize metrics leaving special strings untouched do not deserialize accuracy and cross entropy strings as we have special case handling for these in compile based on model output shape resolves backwards compatiblity issues with training config arguments this helper function accepts built in keras modules such as optimizers losses and metrics to ensure an object being deserialized is compatible with keras 3 built ins for legacy h5 files saved within keras 3 this does nothing
import json
import threading

import tree
from absl import logging

from keras import backend
from keras import layers
from keras import losses
from keras import metrics as metrics_module
from keras import models
from keras import optimizers
from keras.legacy.saving import serialization
from keras.saving import object_registration

MODULE_OBJECTS = threading.local()


LAMBDA_DEP_ARGS = (
    "module",
    "function_type",
    "output_shape_type",
    "output_shape_module",
)


def model_from_config(config, custom_objects=None):
    
    if isinstance(config, list):
        raise TypeError(
            "`model_from_config` expects a dictionary, not a list. "
            f"Received: config={config}. Did you meant to use "
            "`Sequential.from_config(config)`?"
        )

    global MODULE_OBJECTS

    if not hasattr(MODULE_OBJECTS, "ALL_OBJECTS"):
        MODULE_OBJECTS.ALL_OBJECTS = layers.__dict__
        MODULE_OBJECTS.ALL_OBJECTS["InputLayer"] = layers.InputLayer
        MODULE_OBJECTS.ALL_OBJECTS["Functional"] = models.Functional
        MODULE_OBJECTS.ALL_OBJECTS["Model"] = models.Model
        MODULE_OBJECTS.ALL_OBJECTS["Sequential"] = models.Sequential

    batch_input_shape = config["config"].pop("batch_input_shape", None)
    if batch_input_shape is not None:
        if config["class_name"] == "InputLayer":
            config["config"]["batch_shape"] = batch_input_shape
        else:
            config["config"]["input_shape"] = batch_input_shape

    axis = config["config"].pop("axis", None)
    if axis is not None and isinstance(axis, list) and len(axis) == 1:
        config["config"]["axis"] = int(axis[0])

    
    if config["class_name"] == "Lambda":
        for dep_arg in LAMBDA_DEP_ARGS:
            _ = config["config"].pop(dep_arg, None)
        function_config = config["config"]["function"]
        if isinstance(function_config, list):
            function_dict = {"class_name": "__lambda__", "config": {}}
            function_dict["config"]["code"] = function_config[0]
            function_dict["config"]["defaults"] = function_config[1]
            function_dict["config"]["closure"] = function_config[2]
            config["config"]["function"] = function_dict

    
    
    config = _find_replace_nested_dict(config, "keras.", "keras.")

    return serialization.deserialize_keras_object(
        config,
        module_objects=MODULE_OBJECTS.ALL_OBJECTS,
        custom_objects=custom_objects,
        printable_module_name="layer",
    )


def model_metadata(model, include_optimizer=True, require_config=True):
    
    from keras import __version__ as keras_version

    model_config = {"class_name": model.__class__.__name__}
    try:
        model_config["config"] = model.get_config()
    except NotImplementedError as e:
        if require_config:
            raise e

    metadata = dict(
        keras_version=str(keras_version),
        backend=backend.backend(),
        model_config=model_config,
    )
    if getattr(model, "optimizer", False) and include_optimizer:
        if model.compiled:
            training_config = model._compile_config.config
            training_config.pop("optimizer", None)  
            metadata["training_config"] = _serialize_nested_config(
                training_config
            )
            optimizer_config = {
                "class_name": object_registration.get_registered_name(
                    model.optimizer.__class__
                ),
                "config": model.optimizer.get_config(),
            }
            metadata["training_config"]["optimizer_config"] = optimizer_config
    return metadata


def compile_args_from_training_config(training_config, custom_objects=None):
    
    if custom_objects is None:
        custom_objects = {}

    with object_registration.CustomObjectScope(custom_objects):
        optimizer_config = training_config["optimizer_config"]
        optimizer = optimizers.deserialize(optimizer_config)
        
        optimizer = _resolve_compile_arguments_compat(
            optimizer, optimizer_config, optimizers
        )

        
        loss = None
        loss_config = training_config.get("loss", None)
        if loss_config is not None:
            loss = _deserialize_nested_config(losses.deserialize, loss_config)
            
            loss = _resolve_compile_arguments_compat(loss, loss_config, losses)

        
        metrics = None
        metrics_config = training_config.get("metrics", None)
        if metrics_config is not None:
            metrics = _deserialize_nested_config(
                _deserialize_metric, metrics_config
            )
            
            metrics = _resolve_compile_arguments_compat(
                metrics, metrics_config, metrics_module
            )

        
        weighted_metrics = None
        weighted_metrics_config = training_config.get("weighted_metrics", None)
        if weighted_metrics_config is not None:
            weighted_metrics = _deserialize_nested_config(
                _deserialize_metric, weighted_metrics_config
            )

        loss_weights = training_config["loss_weights"]

    return dict(
        optimizer=optimizer,
        loss=loss,
        metrics=metrics,
        weighted_metrics=weighted_metrics,
        loss_weights=loss_weights,
    )


def _serialize_nested_config(config):
    

    def _serialize_fn(obj):
        if callable(obj):
            return serialization.serialize_keras_object(obj)
        return obj

    return tree.map_structure(_serialize_fn, config)


def _deserialize_nested_config(deserialize_fn, config):
    

    def _is_single_object(obj):
        if isinstance(obj, dict) and "class_name" in obj:
            return True  
        if isinstance(obj, str):
            return True  
        return False

    if config is None:
        return None
    if _is_single_object(config):
        return deserialize_fn(config)
    elif isinstance(config, dict):
        return {
            k: _deserialize_nested_config(deserialize_fn, v)
            for k, v in config.items()
        }
    elif isinstance(config, (tuple, list)):
        return [
            _deserialize_nested_config(deserialize_fn, obj) for obj in config
        ]

    raise ValueError(
        "Saved configuration not understood. Configuration should be a "
        f"dictionary, string, tuple or list. Received: config={config}."
    )


def _deserialize_metric(metric_config):
    
    if metric_config in ["accuracy", "acc", "crossentropy", "ce"]:
        
        
        
        return metric_config
    return metrics_module.deserialize(metric_config)


def _find_replace_nested_dict(config, find, replace):
    dict_str = json.dumps(config)
    dict_str = dict_str.replace(find, replace)
    config = json.loads(dict_str)
    return config


def _resolve_compile_arguments_compat(obj, obj_config, module):
    
    if isinstance(obj, str) and obj not in module.ALL_OBJECTS_DICT:
        obj = module.get(obj_config["config"]["name"])
    return obj


def try_build_compiled_arguments(model):
    try:
        if not model.compiled_loss.built:
            model.compiled_loss.build(model.outputs)
        if not model.compiled_metrics.built:
            model.compiled_metrics.build(model.outputs, model.outputs)
    except:
        logging.warning(
            "Compiled the loaded model, but the compiled metrics have "
            "yet to be built. `model.compile_metrics` will be empty "
            "until you train or evaluate the model."
        )