295 lines
10 KiB
Python
295 lines
10 KiB
Python
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# 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.
|
|
# ==============================================================================
|
|
# pylint: disable=protected-access
|
|
"""Home of the `Sequential` model.
|
|
"""
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import copy
|
|
|
|
from tensorflow.python.keras import backend as K
|
|
from tensorflow.python.keras import layers as layer_module
|
|
from tensorflow.python.keras.engine import base_layer
|
|
from tensorflow.python.keras.engine.input_layer import Input
|
|
from tensorflow.python.keras.engine.input_layer import InputLayer
|
|
from tensorflow.python.keras.engine.training import Model
|
|
from tensorflow.python.keras.utils import layer_utils
|
|
from tensorflow.python.platform import tf_logging as logging
|
|
from tensorflow.python.training.checkpointable import base as checkpointable
|
|
from tensorflow.python.util.tf_export import tf_export
|
|
|
|
|
|
@tf_export('keras.models.Sequential', 'keras.Sequential')
|
|
class Sequential(Model):
|
|
"""Linear stack of layers.
|
|
|
|
Arguments:
|
|
layers: list of layers to add to the model.
|
|
|
|
Example:
|
|
|
|
```python
|
|
# Optionally, the first layer can receive an `input_shape` argument:
|
|
model = Sequential()
|
|
model.add(Dense(32, input_shape=(500,)))
|
|
# Afterwards, we do automatic shape inference:
|
|
model.add(Dense(32))
|
|
|
|
# This is identical to the following:
|
|
model = Sequential()
|
|
model.add(Dense(32, input_dim=500))
|
|
|
|
# And to the following:
|
|
model = Sequential()
|
|
model.add(Dense(32, batch_input_shape=(None, 500)))
|
|
|
|
# Note that you can also omit the `input_shape` argument:
|
|
# In that case the model gets built the first time you call `fit` (or other
|
|
# training and evaluation methods).
|
|
model = Sequential()
|
|
model.add(Dense(32))
|
|
model.add(Dense(32))
|
|
model.compile(optimizer=optimizer, loss=loss)
|
|
# This builds the model for the first time:
|
|
model.fit(x, y, batch_size=32, epochs=10)
|
|
|
|
# Note that when using this delayed-build pattern (no input shape specified),
|
|
# the model doesn't have any weights until the first call
|
|
# to a training/evaluation method (since it isn't yet built):
|
|
model = Sequential()
|
|
model.add(Dense(32))
|
|
model.add(Dense(32))
|
|
model.weights # returns []
|
|
|
|
# Whereas if you specify the input shape, the model gets built continuously
|
|
# as you are adding layers:
|
|
model = Sequential()
|
|
model.add(Dense(32, input_shape=(500,)))
|
|
model.add(Dense(32))
|
|
model.weights # returns list of length 4
|
|
|
|
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 = Sequential()
|
|
model.add(Dense(32))
|
|
model.add(Dense(32))
|
|
model.build((None, 500))
|
|
model.weights # returns list of length 4
|
|
```
|
|
"""
|
|
|
|
def __init__(self, layers=None, name=None):
|
|
super(Sequential, self).__init__(name=name)
|
|
|
|
# Add to the model any layers passed to the constructor.
|
|
if layers:
|
|
for layer in layers:
|
|
self.add(layer)
|
|
|
|
@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.
|
|
if self._layers and isinstance(self._layers[0], InputLayer):
|
|
return self._layers[1:]
|
|
return self._layers
|
|
|
|
@checkpointable.no_automatic_dependency_tracking
|
|
def add(self, layer):
|
|
"""Adds a layer instance on top of the layer stack.
|
|
|
|
Arguments:
|
|
layer: layer instance.
|
|
|
|
Raises:
|
|
TypeError: If `layer` is not a layer instance.
|
|
ValueError: In case the `layer` argument does not
|
|
know its input shape.
|
|
ValueError: In case the `layer` argument has
|
|
multiple output tensors, or is already connected
|
|
somewhere else (forbidden in `Sequential` models).
|
|
"""
|
|
if not isinstance(layer, base_layer.Layer):
|
|
raise TypeError('The added layer must be '
|
|
'an instance of class Layer. '
|
|
'Found: ' + str(layer))
|
|
self.built = False
|
|
if not self._layers:
|
|
set_inputs = False
|
|
# First layer in model: check that it is an input layer.
|
|
if not isinstance(layer, InputLayer):
|
|
# Create an input tensor and call `layer` on the input tensor.
|
|
# First, we need to infer the expected input shape and dtype.
|
|
first_layer = layer
|
|
if isinstance(layer, (Model, Sequential)):
|
|
# We were passed a model as first layer.
|
|
# This requires a specific way to figure out the
|
|
# input shape and dtype.
|
|
if not layer.layers:
|
|
raise ValueError('Cannot add an empty model '
|
|
'to a `Sequential` model.')
|
|
# In case of nested models: recover the first layer
|
|
# of the deepest model to infer input shape and dtype.
|
|
first_layer = layer.layers[0]
|
|
while isinstance(first_layer, (Model, Sequential)):
|
|
first_layer = first_layer.layers[0]
|
|
|
|
if hasattr(first_layer, '_batch_input_shape'):
|
|
batch_shape = first_layer._batch_input_shape
|
|
dtype = first_layer.dtype
|
|
# Instantiate the input layer.
|
|
x = Input(
|
|
batch_shape=batch_shape,
|
|
dtype=dtype,
|
|
name=layer.name + '_input')
|
|
# This will build the current layer
|
|
# and create the node connecting the current layer
|
|
# to the input layer we just created.
|
|
layer(x)
|
|
set_inputs = True
|
|
else:
|
|
# The layer doesn't know about its expected shape. We will have to
|
|
# build the model lazily on `fit`/etc.
|
|
batch_shape = None
|
|
else:
|
|
# Corner case where the user passes an InputLayer layer via `add`.
|
|
assert len(layer._inbound_nodes[-1].output_tensors) == 1
|
|
set_inputs = True
|
|
|
|
if set_inputs:
|
|
if len(layer._inbound_nodes[-1].output_tensors) != 1:
|
|
raise ValueError('All layers in a Sequential model '
|
|
'should have a single output tensor. '
|
|
'For multi-output layers, '
|
|
'use the functional API.')
|
|
|
|
self.outputs = [layer._inbound_nodes[-1].output_tensors[0]]
|
|
self.inputs = layer_utils.get_source_inputs(self.outputs[0])
|
|
elif self.outputs:
|
|
output_tensor = layer(self.outputs[0])
|
|
if isinstance(output_tensor, list):
|
|
raise TypeError('All layers in a Sequential model '
|
|
'should have a single output tensor. '
|
|
'For multi-output layers, '
|
|
'use the functional API.')
|
|
self.outputs = [output_tensor]
|
|
if self.inputs:
|
|
self.build()
|
|
else:
|
|
self._layers.append(layer)
|
|
|
|
@checkpointable.no_automatic_dependency_tracking
|
|
def pop(self):
|
|
"""Removes the last layer in the model.
|
|
|
|
Raises:
|
|
TypeError: if there are no layers in the model.
|
|
"""
|
|
if not self.layers:
|
|
raise TypeError('There are no layers in the model.')
|
|
|
|
self._layers.pop()
|
|
self.built = False
|
|
if not self.layers:
|
|
self.outputs = None
|
|
self.inputs = None
|
|
elif self.outputs:
|
|
self.layers[-1]._outbound_nodes = []
|
|
self.outputs = [self.layers[-1].output]
|
|
self.build()
|
|
|
|
@checkpointable.no_automatic_dependency_tracking
|
|
def build(self, input_shape=None):
|
|
if input_shape and not self.inputs:
|
|
batch_shape = tuple(input_shape)
|
|
dtype = K.floatx()
|
|
x = Input(
|
|
batch_shape=batch_shape, dtype=dtype, name=self.name + '_input')
|
|
self.inputs = [x]
|
|
for layer in self._layers:
|
|
x = layer(x)
|
|
self.outputs = [x]
|
|
# Make sure that the model's input shape will be preserved during
|
|
# serialization.
|
|
if self._layers:
|
|
self._layers[0]._batch_input_shape = batch_shape
|
|
|
|
if self.inputs:
|
|
self._init_graph_network(self.inputs, self.outputs, name=self.name)
|
|
self.built = True
|
|
if self._layers:
|
|
self._track_layers(self._layers)
|
|
|
|
def predict_proba(self, x, batch_size=32, verbose=0):
|
|
"""Generates class probability predictions for the input samples.
|
|
|
|
The input samples are processed batch by batch.
|
|
|
|
Arguments:
|
|
x: input data, as a Numpy array or list of Numpy arrays
|
|
(if the model has multiple inputs).
|
|
batch_size: integer.
|
|
verbose: verbosity mode, 0 or 1.
|
|
|
|
Returns:
|
|
A Numpy array of probability predictions.
|
|
"""
|
|
preds = self.predict(x, batch_size, verbose)
|
|
if preds.min() < 0. or preds.max() > 1.:
|
|
logging.warning('Network returning invalid probability values. '
|
|
'The last layer might not normalize predictions '
|
|
'into probabilities '
|
|
'(like softmax or sigmoid would).')
|
|
return preds
|
|
|
|
def predict_classes(self, x, batch_size=32, verbose=0):
|
|
"""Generate class predictions for the input samples.
|
|
|
|
The input samples are processed batch by batch.
|
|
|
|
Arguments:
|
|
x: input data, as a Numpy array or list of Numpy arrays
|
|
(if the model has multiple inputs).
|
|
batch_size: integer.
|
|
verbose: verbosity mode, 0 or 1.
|
|
|
|
Returns:
|
|
A numpy array of class predictions.
|
|
"""
|
|
proba = self.predict(x, batch_size=batch_size, verbose=verbose)
|
|
if proba.shape[-1] > 1:
|
|
return proba.argmax(axis=-1)
|
|
else:
|
|
return (proba > 0.5).astype('int32')
|
|
|
|
def get_config(self):
|
|
config = []
|
|
for layer in self.layers:
|
|
config.append({
|
|
'class_name': layer.__class__.__name__,
|
|
'config': layer.get_config()
|
|
})
|
|
return copy.deepcopy(config)
|
|
|
|
@classmethod
|
|
def from_config(cls, config, custom_objects=None):
|
|
model = cls()
|
|
for conf in config:
|
|
layer = layer_module.deserialize(conf, custom_objects=custom_objects)
|
|
model.add(layer)
|
|
return model
|