File size: 14,352 Bytes
1f5470c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
import copy
import inspect
import typing
from keras.src import backend
from keras.src import tree
from keras.src.api_export import keras_export
from keras.src.backend.common import global_state
from keras.src.backend.common import standardize_shape
from keras.src.layers.core.input_layer import InputLayer
from keras.src.layers.layer import Layer
from keras.src.legacy.saving import saving_utils
from keras.src.legacy.saving import serialization as legacy_serialization
from keras.src.models.functional import Functional
from keras.src.models.model import Model
from keras.src.saving import serialization_lib
@keras_export(["keras.Sequential", "keras.models.Sequential"])
class Sequential(Model):
"""`Sequential` groups a linear stack of layers into a `Model`.
Examples:
```python
model = keras.Sequential()
model.add(keras.Input(shape=(16,)))
model.add(keras.layers.Dense(8))
# Note that you can also omit the initial `Input`.
# In that case the model doesn't have any weights until the first call
# to a training/evaluation method (since it isn't yet built):
model = keras.Sequential()
model.add(keras.layers.Dense(8))
model.add(keras.layers.Dense(4))
# model.weights not created yet
# Whereas if you specify an `Input`, the model gets built
# continuously as you are adding layers:
model = keras.Sequential()
model.add(keras.Input(shape=(16,)))
model.add(keras.layers.Dense(8))
len(model.weights) # Returns "2"
# When using the delayed-build pattern (no input shape specified), you can
# choose to manually build your model by calling
# `build(batch_input_shape)`:
model = keras.Sequential()
model.add(keras.layers.Dense(8))
model.add(keras.layers.Dense(4))
model.build((None, 16))
len(model.weights) # Returns "4"
# Note that when using the delayed-build pattern (no input shape specified),
# the model gets built the first time you call `fit`, `eval`, or `predict`,
# or the first time you call the model on some input data.
model = keras.Sequential()
model.add(keras.layers.Dense(8))
model.add(keras.layers.Dense(1))
model.compile(optimizer='sgd', loss='mse')
# This builds the model for the first time:
model.fit(x, y, batch_size=32, epochs=10)
```
"""
def __new__(cls, *args, **kwargs):
return typing.cast(cls, super().__new__(cls))
def __init__(self, layers=None, trainable=True, name=None):
super().__init__(trainable=trainable, name=name)
self._functional = None
self._layers = []
if layers:
for layer in layers:
self.add(layer, rebuild=False)
self._maybe_rebuild()
def add(self, layer, rebuild=True):
"""Adds a layer instance on top of the layer stack.
Args:
layer: layer instance.
"""
# Legacy case: if the first layer has an input_shape arg,
# use it to build an InputLayer.
if not self._layers:
if getattr(layer, "_input_shape_arg", None) is not None:
self.add(InputLayer(shape=layer._input_shape_arg))
# If we are passed a Keras tensor created by keras.Input(), we
# extract the input layer from its keras history and use that.
if hasattr(layer, "_keras_history"):
origin_layer = layer._keras_history[0]
if isinstance(origin_layer, InputLayer):
layer = origin_layer
if not isinstance(layer, Layer):
raise ValueError(
"Only instances of `keras.Layer` can be "
f"added to a Sequential model. Received: {layer} "
f"(of type {type(layer)})"
)
if not self._is_layer_name_unique(layer):
raise ValueError(
"All layers added to a Sequential model "
f"should have unique names. Name '{layer.name}' is already "
"the name of a layer in this model. Update the `name` argument "
"to pass a unique name."
)
if (
isinstance(layer, InputLayer)
and self._layers
and isinstance(self._layers[0], InputLayer)
):
raise ValueError(
f"Sequential model '{self.name}' has already been configured "
f"to use input shape {self._layers[0].batch_shape}. You cannot "
f"add a different Input layer to it."
)
self._layers.append(layer)
if rebuild:
self._maybe_rebuild()
else:
self.built = False
self._functional = None
def pop(self, rebuild=True):
"""Removes the last layer in the model.
Args:
rebuild: `bool`. Whether to rebuild the model after removing
the layer. Defaults to `True`.
Returns:
layer: layer instance.
"""
layer = self._layers.pop()
self.built = False
self._functional = None
if rebuild:
self._maybe_rebuild()
return layer
def _maybe_rebuild(self):
self.built = False
self._functional = None
if isinstance(self._layers[0], InputLayer) and len(self._layers) > 1:
input_shape = self._layers[0].batch_shape
self.build(input_shape)
elif hasattr(self._layers[0], "input_shape") and len(self._layers) > 1:
# We can build the Sequential model if the first layer has the
# `input_shape` property. This is most commonly found in Functional
# model.
input_shape = self._layers[0].input_shape
self.build(input_shape)
def _lock_state(self):
# Unlike other layers, Sequential is mutable after build.
pass
def _obj_type(self):
return "Sequential"
def build(self, input_shape=None):
try:
input_shape = standardize_shape(input_shape)
except:
# Do not attempt to build if the model does not have a single
# input tensor.
return
if not self._layers:
raise ValueError(
f"Sequential model {self.name} cannot be built because it has "
"no layers. Call `model.add(layer)`."
)
if isinstance(self._layers[0], InputLayer):
if self._layers[0].batch_shape != input_shape:
raise ValueError(
f"Sequential model '{self.name}' has already been "
"configured to use input shape "
f"{self._layers[0].batch_shape}. You cannot build it "
f"with input_shape {input_shape}"
)
else:
dtype = self._layers[0].compute_dtype
self._layers = [
InputLayer(batch_shape=input_shape, dtype=dtype)
] + self._layers
# Build functional model
inputs = self._layers[0].output
x = inputs
for layer in self._layers[1:]:
try:
x = layer(x)
except NotImplementedError:
# Can happen if shape inference is not implemented.
# TODO: consider reverting inbound nodes on layers processed.
return
except TypeError as e:
signature = inspect.signature(layer.call)
positional_args = [
param
for param in signature.parameters.values()
if param.default == inspect.Parameter.empty
]
if len(positional_args) != 1:
raise ValueError(
"Layers added to a Sequential model "
"can only have a single positional argument, "
f"the input tensor. Layer {layer.__class__.__name__} "
f"has multiple positional arguments: {positional_args}"
)
raise e
outputs = x
self._functional = Functional(inputs=inputs, outputs=outputs)
def call(self, inputs, training=None, mask=None, **kwargs):
if self._functional:
return self._functional.call(
inputs, training=training, mask=mask, **kwargs
)
# Fallback: Just apply the layer sequence.
# This typically happens if `inputs` is a nested struct.
for layer in self.layers:
# During each iteration, `inputs` are the inputs to `layer`, and
# `outputs` are the outputs of `layer` applied to `inputs`. At the
# end of each iteration `inputs` is set to `outputs` to prepare for
# the next layer.
layer_kwargs = {
k: kwargs[k]
# only inject if this layer’s signature actually has that arg
for k in getattr(layer, "_call_has_context_arg", {})
if k in kwargs
}
if layer._call_has_mask_arg:
layer_kwargs["mask"] = mask
if layer._call_has_training_arg and training is not None:
layer_kwargs["training"] = training
outputs = layer(inputs, **layer_kwargs)
inputs = outputs
mask = tree.map_structure(backend.get_keras_mask, outputs)
return outputs
@property
def layers(self):
# Historically, `sequential.layers` only returns layers that were added
# via `add`, and omits the auto-generated `InputLayer` that comes at the
# bottom of the stack.
layers = self._layers
if layers and isinstance(layers[0], InputLayer):
return layers[1:]
return layers[:]
@layers.setter
def layers(self, _):
raise AttributeError(
"`Sequential.layers` attribute is reserved and should not be used. "
"Use `add()` and `pop()` to change the layers in this model."
)
def compute_output_spec(self, inputs, training=None, mask=None, **kwargs):
if self._functional:
return self._functional.compute_output_spec(
inputs, training=training, mask=mask, **kwargs
)
# Direct application
for layer in self.layers:
outputs = layer.compute_output_spec(
inputs,
training=training,
**kwargs,
) # Ignore mask
inputs = outputs
return outputs
def compute_output_shape(self, input_shape):
if self._functional:
return self._functional.compute_output_shape(input_shape)
# Direct application
for layer in self.layers:
output_shape = layer.compute_output_shape(input_shape)
input_shape = output_shape
return output_shape
@property
def input_shape(self):
if self._functional:
return self._functional.input_shape
raise AttributeError(
f"Sequential model '{self.name}' has no defined input shape yet."
)
@property
def output_shape(self):
if self._functional:
return self._functional.output_shape
raise AttributeError(
f"Sequential model '{self.name}' has no defined output shape yet."
)
@property
def inputs(self):
if self._functional:
return self._functional.inputs
raise AttributeError(
f"Sequential model '{self.name}' has no defined inputs yet."
)
@property
def outputs(self):
if self._functional:
return self._functional.outputs
raise AttributeError(
f"Sequential model '{self.name}' has no defined outputs yet."
)
@property
def input_dtype(self):
# Sequential.__call__ will try to convert its inputs
# to the dtype expected by its input layer, if any.
layers = self._layers
if layers and isinstance(layers[0], InputLayer):
return layers[0].dtype
return super().input_dtype
def _is_layer_name_unique(self, layer):
for ref_layer in self._layers:
if layer.name == ref_layer.name and ref_layer is not layer:
return False
return True
def get_config(self):
serialize_fn = serialization_lib.serialize_keras_object
if global_state.get_global_attribute("use_legacy_config", False):
# Legacy format serialization used for H5 and SavedModel formats
serialize_fn = legacy_serialization.serialize_keras_object
layer_configs = []
for layer in super().layers:
# `super().layers` include the InputLayer if available (it is
# filtered out of `self.layers`).
layer_configs.append(serialize_fn(layer))
config = Model.get_config(self)
config["name"] = self.name
config["layers"] = copy.deepcopy(layer_configs)
if self._functional is not None:
config["build_input_shape"] = self._layers[0].batch_shape
return config
@classmethod
def from_config(cls, config, custom_objects=None):
if "name" in config:
name = config["name"]
build_input_shape = config.get("build_input_shape")
layer_configs = config["layers"]
else:
name = None
layer_configs = config
model = cls(name=name)
for layer_config in layer_configs:
if "module" not in layer_config:
# Legacy format deserialization (no "module" key)
# used for H5 and SavedModel formats
layer = saving_utils.model_from_config(
layer_config,
custom_objects=custom_objects,
)
else:
layer = serialization_lib.deserialize_keras_object(
layer_config,
custom_objects=custom_objects,
)
model.add(layer)
if (
not model._functional
and "build_input_shape" in locals()
and build_input_shape
and isinstance(build_input_shape, (tuple, list))
):
model.build(build_input_shape)
return model
|