specifies the rank dtype and shape of every input to a layer layers can expose if appropriate an inputspec attribute an instance of inputspec or a nested structure of inputspec instances one per input tensor these objects enable the layer to run input compatibility checks for input structure input rank input shape and input dtype for the first argument of layer call a none entry in a shape is compatible with any dimension args dtype expected datatype of the input shape shape tuple expected shape of the input may include none for unchecked axes includes the batch size ndim integer expected rank of the input maxndim integer maximum rank of the input minndim integer minimum rank of the input axes dictionary mapping integer axes to a specific dimension value allowlastaxissqueeze if true then allow inputs of rank n1 as long as the last axis of the input is 1 as well as inputs of rank n1 as long as the last axis of the spec is 1 name expected key corresponding to this input when passing data as a dictionary example python class mylayerlayer def initself supermylayer self init the layer will accept inputs with shape 28 28 28 28 1 and raise an appropriate error message otherwise self inputspec inputspec shapenone 28 28 1 allowlastaxissqueezetrue checks compatibility between the layer and provided inputs this checks that the tensors inputs verify the input assumptions of a layer if any if not a clear and actional exception gets raised args inputspec an inputspec instance list of inputspec instances a nested structure of inputspec instances or none inputs input tensor list of input tensors or a nested structure of input tensors layername string name of the layer for error message formatting raises valueerror in case of mismatch between the provided inputs and the expectations of the layer flatten inputs by reference order if input spec names are provided having a shapedtype is the only commonality of the various tensorlike objects that may be passed the most common kind of invalid type we are guarding for is a layer instance functional api which does not have a shape attribute check ndim check dtype check specific shape axes check shape specifies the rank dtype and shape of every input to a layer layers can expose if appropriate an input_spec attribute an instance of inputspec or a nested structure of inputspec instances one per input tensor these objects enable the layer to run input compatibility checks for input structure input rank input shape and input dtype for the first argument of layer __call__ a none entry in a shape is compatible with any dimension args dtype expected datatype of the input shape shape tuple expected shape of the input may include none for unchecked axes includes the batch size ndim integer expected rank of the input max_ndim integer maximum rank of the input min_ndim integer minimum rank of the input axes dictionary mapping integer axes to a specific dimension value allow_last_axis_squeeze if true then allow inputs of rank n 1 as long as the last axis of the input is 1 as well as inputs of rank n 1 as long as the last axis of the spec is 1 name expected key corresponding to this input when passing data as a dictionary example python class mylayer layer def __init__ self super mylayer self __init__ the layer will accept inputs with shape 28 28 28 28 1 and raise an appropriate error message otherwise self input_spec inputspec shape none 28 28 1 allow_last_axis_squeeze true checks compatibility between the layer and provided inputs this checks that the tensor s inputs verify the input assumptions of a layer if any if not a clear and actional exception gets raised args input_spec an inputspec instance list of inputspec instances a nested structure of inputspec instances or none inputs input tensor list of input tensors or a nested structure of input tensors layer_name string name of the layer for error message formatting raises valueerror in case of mismatch between the provided inputs and the expectations of the layer flatten inputs by reference order if input spec names are provided having a shape dtype is the only commonality of the various tensor like objects that may be passed the most common kind of invalid type we are guarding for is a layer instance functional api which does not have a shape attribute check ndim check dtype check specific shape axes check shape
import tree

from keras import backend
from keras.api_export import keras_export


@keras_export(["keras.InputSpec", "keras.layers.InputSpec"])
class InputSpec:
    

    def __init__(
        self,
        dtype=None,
        shape=None,
        ndim=None,
        max_ndim=None,
        min_ndim=None,
        axes=None,
        allow_last_axis_squeeze=False,
        name=None,
    ):
        self.dtype = (
            backend.standardize_dtype(dtype) if dtype is not None else None
        )
        if shape is not None:
            self.shape = backend.standardize_shape(shape)
            self.ndim = len(shape)
        else:
            self.ndim = ndim
            self.shape = None
        self.max_ndim = max_ndim
        self.min_ndim = min_ndim
        self.name = name
        self.allow_last_axis_squeeze = allow_last_axis_squeeze
        try:
            axes = axes or {}
            self.axes = {int(k): axes[k] for k in axes}
        except (ValueError, TypeError):
            raise TypeError(
                "Argument `axes` must be a dict with integer keys. "
                f"Received: axes={axes}"
            )

        if self.axes and (self.ndim is not None or self.max_ndim is not None):
            max_dim = (self.ndim if self.ndim else self.max_ndim) - 1
            max_axis = max(self.axes)
            if max_axis > max_dim:
                raise ValueError(
                    "Axis {} is greater than the maximum "
                    "allowed value: {}".format(max_axis, max_dim)
                )

    def __repr__(self):
        spec = [
            ("dtype=" + str(self.dtype)) if self.dtype else "",
            ("shape=" + str(self.shape)) if self.shape else "",
            ("ndim=" + str(self.ndim)) if self.ndim else "",
            ("max_ndim=" + str(self.max_ndim)) if self.max_ndim else "",
            ("min_ndim=" + str(self.min_ndim)) if self.min_ndim else "",
            ("axes=" + str(self.axes)) if self.axes else "",
        ]
        return f"InputSpec({', '.join(x for x in spec if x)})"

    def get_config(self):
        return {
            "dtype": self.dtype,
            "shape": self.shape,
            "ndim": self.ndim,
            "max_ndim": self.max_ndim,
            "min_ndim": self.min_ndim,
            "axes": self.axes,
        }

    @classmethod
    def from_config(cls, config):
        return cls(**config)


def assert_input_compatibility(input_spec, inputs, layer_name):
    
    if not input_spec:
        return

    input_spec = tree.flatten(input_spec)
    if isinstance(inputs, dict):
        
        names = [spec.name for spec in input_spec]
        if all(names):
            list_inputs = []
            for name in names:
                if name not in inputs:
                    raise ValueError(
                        f'Missing data for input "{name}". '
                        "You passed a data dictionary with keys "
                        f"{list(inputs.keys())}. "
                        f"Expected the following keys: {names}"
                    )
                list_inputs.append(inputs[name])
            inputs = list_inputs

    inputs = tree.flatten(inputs)
    if len(input_spec) != len(inputs):
        raise ValueError(
            f"Layer '{layer_name}' expected {len(input_spec)} input(s). "
            f"Received {len(inputs)} instead."
        )
    for x in inputs:
        
        
        
        
        if not hasattr(x, "shape"):
            raise ValueError(
                f"Inputs to a layer should be tensors. Got '{x}' "
                f"(of type {type(x)}) as input for layer '{layer_name}'."
            )

    if len(inputs) != len(input_spec):
        raise ValueError(
            f'Layer "{layer_name}" expects {len(input_spec)} input(s),'
            f" but it received {len(inputs)} input tensors. "
            f"Inputs received: {inputs}"
        )
    for input_index, (x, spec) in enumerate(zip(inputs, input_spec)):
        if spec is None:
            continue

        shape = backend.standardize_shape(x.shape)
        ndim = len(shape)
        
        if spec.ndim is not None and not spec.allow_last_axis_squeeze:
            if ndim != spec.ndim:
                raise ValueError(
                    f'Input {input_index} of layer "{layer_name}" '
                    "is incompatible with the layer: "
                    f"expected ndim={spec.ndim}, found ndim={ndim}. "
                    f"Full shape received: {shape}"
                )
        if spec.max_ndim is not None:
            if ndim is not None and ndim > spec.max_ndim:
                raise ValueError(
                    f'Input {input_index} of layer "{layer_name}" '
                    "is incompatible with the layer: "
                    f"expected max_ndim={spec.max_ndim}, "
                    f"found ndim={ndim}"
                )
        if spec.min_ndim is not None:
            if ndim is not None and ndim < spec.min_ndim:
                raise ValueError(
                    f'Input {input_index} of layer "{layer_name}" '
                    "is incompatible with the layer: "
                    f"expected min_ndim={spec.min_ndim}, "
                    f"found ndim={ndim}. "
                    f"Full shape received: {shape}"
                )
        
        if spec.dtype is not None:
            dtype = backend.standardize_dtype(x.dtype)
            if dtype != spec.dtype:
                raise ValueError(
                    f'Input {input_index} of layer "{layer_name}" '
                    "is incompatible with the layer: "
                    f"expected dtype={spec.dtype}, "
                    f"found dtype={dtype}"
                )

        
        if spec.axes:
            for axis, value in spec.axes.items():
                if value is not None and shape[axis] not in {
                    value,
                    None,
                }:
                    raise ValueError(
                        f'Input {input_index} of layer "{layer_name}" is '
                        f"incompatible with the layer: expected axis {axis} "
                        f"of input shape to have value {value}, "
                        "but received input with "
                        f"shape {shape}"
                    )
        
        if spec.shape is not None:
            spec_shape = spec.shape
            if spec.allow_last_axis_squeeze:
                if shape and shape[-1] == 1:
                    shape = shape[:-1]
                if spec_shape and spec_shape[-1] == 1:
                    spec_shape = spec_shape[:-1]
            for spec_dim, dim in zip(spec_shape, shape):
                if spec_dim is not None and dim is not None:
                    if spec_dim != dim:
                        raise ValueError(
                            f'Input {input_index} of layer "{layer_name}" is '
                            "incompatible with the layer: "
                            f"expected shape={spec.shape}, "
                            f"found shape={shape}"
                        )