Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/guide/keras/functional.ipynb
25118 views
Kernel: Python 3
#@title 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 # # https://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.

La API funcional

Preparación

import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers

Introducción

La API funcional de Keras es una forma de crear modelos más flexibles que la API tf.keras.Sequential API. La API funcional puede administrar modelos con topología no lineal, capas compartidas e incluso entradas o salidas múltiples.

La idea principal es que un modelo de aprendizaje profundo suele ser un grafo acíclico dirigido (DAG) de capas. Así que la API funcional es una manera de construir grafos de capas.

Considere el siguiente modelo:

(input: 784-dimensional vectors) [Dense (64 units, relu activation)] [Dense (64 units, relu activation)] [Dense (10 units, softmax activation)] (output: logits of a probability distribution over 10 classes)

Se trata de un grafo básico con tres capas. Para construir este modelo mediante la API funcional, empiece por crear un nodo de entrada:

inputs = keras.Input(shape=(784,))

La forma de los datos se establece como un vector de 784 dimensiones. El tamaño del lote siempre se omite, ya que solo se especifica la forma de cada muestra.

Si, por ejemplo, tiene una entrada de imagen con el formato (32, 32, 3), utilizaría:

# Just for demonstration purposes. img_inputs = keras.Input(shape=(32, 32, 3))

El inputs que se devuelve contiene información sobre la forma y el dtype de los datos de entrada que se introducen en el modelo. Aquí se muestra la forma:

inputs.shape

Este es el dtype:

inputs.dtype

Se crea un nuevo nodo en el grafo de capas que llama a una capa en este objeto inputs:

dense = layers.Dense(64, activation="relu") x = dense(inputs)

La acción "llamar capa" es como trazar una flecha desde las "entradas" a esta capa que creó. Está "pasando" las entradas a la capa dense, y obtiene x como salida.

Agreguemos algunas capas más al grafo de capas:

x = layers.Dense(64, activation="relu")(x) outputs = layers.Dense(10)(x)

En este punto, puede crear un Model al especificar sus entradas y salidas en el grafo de capas:

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

Echemos un vistazo al resumen del modelo:

model.summary()

También puede representar el modelo en forma de grafo:

keras.utils.plot_model(model, "my_first_model.png")

Y, opcionalmente, mostrar las formas de entrada y salida de cada capa en el grafo representado:

keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)

Esta figura y el código son casi idénticos. En la versión del código, las flechas de conexión se sustituyen por la operación de llamada.

Un "grafo de capas" es una imagen mental intuitiva para un modelo de aprendizaje profundo, y la API funcional es una forma de crear modelos que lo reflejen a la perfección.

Entrenamiento, evaluación e inferencia

El entrenamiento, la evaluación y la inferencia funcionan exactamente de la misma manera para los modelos creados utilizando la API funcional así como para los modelos Sequential.

La clase Model ofrece un bucle de entrenamiento integrado (el método fit()) y un bucle de evaluación integrado (el método evaluate()). Tenga en cuenta que puede personalizar fácilmente estos bucles para implementar rutinas de entrenamiento más allá del aprendizaje supervisado (por ejemplo, GANs).

En este caso, se cargan los datos de la imagen MNIST, se remodelan en vectores, se ajusta el modelo a los datos (mientras se supervisa el rendimiento en una división de validación) y, a continuación, se evalúa el modelo con los datos de prueba:

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data() x_train = x_train.reshape(60000, 784).astype("float32") / 255 x_test = x_test.reshape(10000, 784).astype("float32") / 255 model.compile( loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), optimizer=keras.optimizers.RMSprop(), metrics=["accuracy"], ) history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2) test_scores = model.evaluate(x_test, y_test, verbose=2) print("Test loss:", test_scores[0]) print("Test accuracy:", test_scores[1])

Para obtener más información, consulte la guía de formación y evaluación.

Guardar y serializar

Guardar el modelo y la serialización funcionan de la misma manera para los modelos construidos utilizando la API funcional que para los modelos Sequential. La forma estándar de guardar un modelo funcional es llamar a model.save() para guardar todo el modelo como un único archivo. Más tarde se puede volver a crear el mismo modelo a partir de este archivo, incluso si el código que construyó el modelo ya no está disponible.

Este archivo guardado incluye:

  • arquitectura modelo

  • valores de peso del modelo ( que se aprendieron durante el entrenamiento)

  • configuración de entrenamiento del modelo, si existe (tal como se pasó a compile)

  • optimizador y su estado, si lo hay (para reiniciar el entrenamiento donde lo dejó)

model.save("path_to_my_model") del model # Recreate the exact same model purely from the file: model = keras.models.load_model("path_to_my_model")

Para obtener más información, lea la guía del modelo serialización y guardado.

Utilice el mismo grafo de capas para definir varios modelos

En la API funcional, los modelos se crean especificando sus entradas y salidas en un grafo de capas. Esto significa que un solo grafo de capas puede utilizarse para generar múltiples modelos.

En el siguiente ejemplo, se utiliza la misma pila de capas para instanciar dos modelos: un modelo encoder que convierte las entradas de imagen en vectores de 16 dimensiones, y un modelo autoencoder de extremo a extremo para el entrenamiento.

encoder_input = keras.Input(shape=(28, 28, 1), name="img") x = layers.Conv2D(16, 3, activation="relu")(encoder_input) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.MaxPooling2D(3)(x) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.Conv2D(16, 3, activation="relu")(x) encoder_output = layers.GlobalMaxPooling2D()(x) encoder = keras.Model(encoder_input, encoder_output, name="encoder") encoder.summary() x = layers.Reshape((4, 4, 1))(encoder_output) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) x = layers.Conv2DTranspose(32, 3, activation="relu")(x) x = layers.UpSampling2D(3)(x) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x) autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder") autoencoder.summary()

Aquí, la arquitectura de descodificación es estrictamente simétrica a la arquitectura de codificación, por lo que la forma de salida es la misma que la forma de entrada (28, 28, 1).

El reverso de una capa Conv2D es una capa Conv2DTranspose, y el reverso de una capa MaxPooling2D es una capa UpSampling2D.

Todos los modelos se pueden llamar, al igual que las capas

Puedes tratar cualquier modelo como si fuera una capa llamándolo sobre un Input o sobre la salida de otra capa. Al llamar a un modelo no solo estás reutilizando la arquitectura del modelo, también estás reutilizando sus pesos.

To see this in action, here's a different take on the autoencoder example that creates an encoder model, a decoder model, and chains them in two calls to obtain the autoencoder model:

encoder_input = keras.Input(shape=(28, 28, 1), name="original_img") x = layers.Conv2D(16, 3, activation="relu")(encoder_input) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.MaxPooling2D(3)(x) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.Conv2D(16, 3, activation="relu")(x) encoder_output = layers.GlobalMaxPooling2D()(x) encoder = keras.Model(encoder_input, encoder_output, name="encoder") encoder.summary() decoder_input = keras.Input(shape=(16,), name="encoded_img") x = layers.Reshape((4, 4, 1))(decoder_input) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) x = layers.Conv2DTranspose(32, 3, activation="relu")(x) x = layers.UpSampling2D(3)(x) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x) decoder = keras.Model(decoder_input, decoder_output, name="decoder") decoder.summary() autoencoder_input = keras.Input(shape=(28, 28, 1), name="img") encoded_img = encoder(autoencoder_input) decoded_img = decoder(encoded_img) autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder") autoencoder.summary()

Como puede ver, el modelo puede anidarse: un modelo puede contener submodelos (ya que un modelo es como una capa). Un caso de uso común para el anidamiento de modelos es ensembling.. Por ejemplo, a continuación se muestra cómo ensamblar un conjunto de modelos en un único modelo que promedia sus predicciones:

def get_model(): inputs = keras.Input(shape=(128,)) outputs = layers.Dense(1)(inputs) return keras.Model(inputs, outputs) model1 = get_model() model2 = get_model() model3 = get_model() inputs = keras.Input(shape=(128,)) y1 = model1(inputs) y2 = model2(inputs) y3 = model3(inputs) outputs = layers.average([y1, y2, y3]) ensemble_model = keras.Model(inputs=inputs, outputs=outputs)

Cómo manipular topologías de grafos complejos

Modelos con múltiples entradas y salidas

La API funcional facilita la manipulación de múltiples entradas y salidas. Esto no se puede manejar con la API Sequential.

Por ejemplo, si está creando un sistema para clasificar las incidencias de los clientes por prioridad y dirigirlas al departamento adecuado, el modelo tendrá tres entradas:

  • el título del billete (entrada de texto),

  • el cuerpo del texto del billete (entrada de texto), y

  • cualquier etiqueta que agregue el usuario (entrada categórica)

Este modelo tendrá dos resultados:

  • la puntuación de prioridad entre 0 y 1 (salida sigmoide escalar), y

  • el departamento que debe administrar el ticket (salida softmax sobre el conjunto de departamentos).

Puede crear este modelo en unas pocas líneas con la API funcional:

num_tags = 12 # Number of unique issue tags num_words = 10000 # Size of vocabulary obtained when preprocessing text data num_departments = 4 # Number of departments for predictions title_input = keras.Input( shape=(None,), name="title" ) # Variable-length sequence of ints body_input = keras.Input(shape=(None,), name="body") # Variable-length sequence of ints tags_input = keras.Input( shape=(num_tags,), name="tags" ) # Binary vectors of size `num_tags` # Embed each word in the title into a 64-dimensional vector title_features = layers.Embedding(num_words, 64)(title_input) # Embed each word in the text into a 64-dimensional vector body_features = layers.Embedding(num_words, 64)(body_input) # Reduce sequence of embedded words in the title into a single 128-dimensional vector title_features = layers.LSTM(128)(title_features) # Reduce sequence of embedded words in the body into a single 32-dimensional vector body_features = layers.LSTM(32)(body_features) # Merge all available features into a single large vector via concatenation x = layers.concatenate([title_features, body_features, tags_input]) # Stick a logistic regression for priority prediction on top of the features priority_pred = layers.Dense(1, name="priority")(x) # Stick a department classifier on top of the features department_pred = layers.Dense(num_departments, name="department")(x) # Instantiate an end-to-end model predicting both priority and department model = keras.Model( inputs=[title_input, body_input, tags_input], outputs=[priority_pred, department_pred], )

Ahora grafica el modelo:

keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)

Al compilar este modelo, puede asignar diferentes pérdidas a cada salida. Incluso puede asignar distintos pesos a cada pérdida, para modular su contribución a la pérdida total del entrenamiento.

model.compile( optimizer=keras.optimizers.RMSprop(1e-3), loss=[ keras.losses.BinaryCrossentropy(from_logits=True), keras.losses.CategoricalCrossentropy(from_logits=True), ], loss_weights=[1.0, 0.2], )

Dado que las capas de salida tienen nombres diferentes, también puede especificar las pérdidas y los pesos de pérdida con los nombres de capa correspondientes:

model.compile( optimizer=keras.optimizers.RMSprop(1e-3), loss={ "priority": keras.losses.BinaryCrossentropy(from_logits=True), "department": keras.losses.CategoricalCrossentropy(from_logits=True), }, loss_weights={"priority": 1.0, "department": 0.2}, )

Entrena el modelo pasando listas de matrices NumPy de entradas y objetivos:

# Dummy input data title_data = np.random.randint(num_words, size=(1280, 10)) body_data = np.random.randint(num_words, size=(1280, 100)) tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32") # Dummy target data priority_targets = np.random.random(size=(1280, 1)) dept_targets = np.random.randint(2, size=(1280, num_departments)) model.fit( {"title": title_data, "body": body_data, "tags": tags_data}, {"priority": priority_targets, "department": dept_targets}, epochs=2, batch_size=32, )

Cuando se llama a fit con un objeto Dataset, debería producir una tupla de listas como ([title_data, body_data, tags_data], [priority_targets, dept_targets]) o una tupla de diccionarios como ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}).

Para obtener una explicación más detallada, consulte la guía de entrenamiento y evaluación.

Un modelo ResNet de prueba

Además de los modelos con múltiples entradas y salidas, la API funcional facilita la manipulación de topologías de conectividad no lineales, es decir, modelos con capas que no están conectadas secuencialmente, que la API Sequential no puede administrar.

Un caso de uso común para esto son las conexiones residuales. Vamos a construir un modelo ResNet de prueba para CIFAR10 para demostrarlo:

inputs = keras.Input(shape=(32, 32, 3), name="img") x = layers.Conv2D(32, 3, activation="relu")(inputs) x = layers.Conv2D(64, 3, activation="relu")(x) block_1_output = layers.MaxPooling2D(3)(x) x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output) x = layers.Conv2D(64, 3, activation="relu", padding="same")(x) block_2_output = layers.add([x, block_1_output]) x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output) x = layers.Conv2D(64, 3, activation="relu", padding="same")(x) block_3_output = layers.add([x, block_2_output]) x = layers.Conv2D(64, 3, activation="relu")(block_3_output) x = layers.GlobalAveragePooling2D()(x) x = layers.Dense(256, activation="relu")(x) x = layers.Dropout(0.5)(x) outputs = layers.Dense(10)(x) model = keras.Model(inputs, outputs, name="toy_resnet") model.summary()

Grafica el modelo:

keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)

Ahora entrene el modelo:

(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data() x_train = x_train.astype("float32") / 255.0 x_test = x_test.astype("float32") / 255.0 y_train = keras.utils.to_categorical(y_train, 10) y_test = keras.utils.to_categorical(y_test, 10) model.compile( optimizer=keras.optimizers.RMSprop(1e-3), loss=keras.losses.CategoricalCrossentropy(from_logits=True), metrics=["acc"], ) # We restrict the data to the first 1000 samples so as to limit execution time # on Colab. Try to train on the entire dataset until convergence! model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)

Capas compartidas

Otro buen uso de la API funcional son los modelos que utilizan capas compartidas. Las capas compartidas son instancias de capas que se reutilizan varias veces en el mismo modelo: aprenden características que corresponden a varias rutas en el grafo de capas.

Las capas compartidas se utilizan frecuentemente para codificar entradas de espacios similares (por ejemplo, dos textos diferentes con un vocabulario similar). Permiten compartir la información entre las distintas entradas y entrenar el modelo con menos datos. Si una palabra determinada aparece en una de las entradas, eso beneficiará al procesamiento de todas las entradas que pasen por la capa compartida.

Para compartir una capa en la API funcional, llame a la misma instancia de capa varias veces. Por ejemplo, aquí hay una capa Embedding compartida en dos entradas de texto diferentes:

# Embedding for 1000 unique words mapped to 128-dimensional vectors shared_embedding = layers.Embedding(1000, 128) # Variable-length sequence of integers text_input_a = keras.Input(shape=(None,), dtype="int32") # Variable-length sequence of integers text_input_b = keras.Input(shape=(None,), dtype="int32") # Reuse the same layer to encode both inputs encoded_input_a = shared_embedding(text_input_a) encoded_input_b = shared_embedding(text_input_b)

Cómo extraer y reutilizar nodos en el grafo de capas

Dado que el grafo de capas que está manipulando una estructura de datos estática, se puede acceder a ella e inspeccionarla. Y así es como se pueden graficar modelos funcionales como imágenes.

Esto también significa que puede acceder a las activaciones de las capas intermedias ("nodos" en el grafo) y reutilizarlas en otro lugar, lo que resulta muy útil para algo como la extracción de características.

Echemos un vistazo a un ejemplo. Se trata de un modelo VGG19 con pesos preentrenados en ImageNet:

vgg19 = tf.keras.applications.VGG19()

Y estas son las activaciones intermedias del modelo, obtenidas mediante la consulta de la estructura de datos del grafo:

features_list = [layer.output for layer in vgg19.layers]

Utiliza estas características para crear un nuevo modelo de extracción de características que devuelva los valores de las activaciones de las capas intermedias:

feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list) img = np.random.random((1, 224, 224, 3)).astype("float32") extracted_features = feat_extraction_model(img)

Esto resulta útil para tareas como la transferencia de estilo neural, entre otras cosas.

Cómo ampliar la API mediante capas personalizadas

tf.keras incluye una amplia gama de capas incorporadas, por ejemplo:

  • Capas convolucionales: Conv1D, Conv2D, Conv3D, Conv2DTranspose

  • Capas de agrupamiento: MaxPooling1D, MaxPooling2D, MaxPooling3D, AveragePooling1D

  • Capas RNN: GRU, LSTM, ConvLSTM2D

  • BatchNormalization, Dropout, Embedding, etc.

Pero si no encuentra lo que necesita, es fácil ampliar la API creando sus propias capas. Todas las capas subclasifican la clase Layer e implementan:

  • call método, que especifica el cálculo realizado por la capa.

  • build, que crea los pesos de la capa (esto es solo una convención de estilo, ya que también se pueden crear pesos en __init__).

Para obtener más información sobre la creación de capas desde cero, lea la guía capas y modelos personalizados.

La siguiente es una implementación básica de tf.keras.layers.Dense:

class CustomDense(layers.Layer): def __init__(self, units=32): super(CustomDense, self).__init__() self.units = units def build(self, input_shape): self.w = self.add_weight( shape=(input_shape[-1], self.units), initializer="random_normal", trainable=True, ) self.b = self.add_weight( shape=(self.units,), initializer="random_normal", trainable=True ) def call(self, inputs): return tf.matmul(inputs, self.w) + self.b inputs = keras.Input((4,)) outputs = CustomDense(10)(inputs) model = keras.Model(inputs, outputs)

Para admitir la serialización en su capa personalizada, defina un método get_config que devuelva los argumentos del constructor de la instancia de la capa:

class CustomDense(layers.Layer): def __init__(self, units=32): super(CustomDense, self).__init__() self.units = units def build(self, input_shape): self.w = self.add_weight( shape=(input_shape[-1], self.units), initializer="random_normal", trainable=True, ) self.b = self.add_weight( shape=(self.units,), initializer="random_normal", trainable=True ) def call(self, inputs): return tf.matmul(inputs, self.w) + self.b def get_config(self): return {"units": self.units} inputs = keras.Input((4,)) outputs = CustomDense(10)(inputs) model = keras.Model(inputs, outputs) config = model.get_config() new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})

Opcionalmente, implemente el método de clase from_config(cls, config) que se utiliza para recrear una instancia de capa dado su diccionario config. La implementación predeterminada de from_config es:

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

Cuándo utilizar la API funcional

¿Debería utilizar la API funcional de Keras para crear un nuevo modelo, o simplemente subclasificar la clase Model directamente? En general, la API funcional es de más alto nivel, más fácil y más segura, y tiene una serie de características que los modelos subclasificados no admiten.

Sin embargo, la subclase de modelos proporciona una mayor flexibilidad cuando se construyen modelos que no se expresan fácilmente como grafos acíclicos dirigidas por capas. Por ejemplo, no se podría implementar una Tree-RNN con la API funcional y se debería subclasificar Model de forma directa.

Para profundizar en las diferencias entre la API funcional y la subclase de modelos, lea ¿Qué son las API Symbolic e Imperative en TensorFlow 2.0?.

Fortalezas funcionales de la API:

Las siguientes propiedades también son válidas para los modelos secuenciales (que también son estructuras de datos), pero no para los modelos subclasificados (que son código de bytes de Python, pero no estructuras de datos).

Menos verborrea

No hay super(MyClass, self).__init__(...), no hay def call(self, ...):, etc.

Comparar:

inputs = keras.Input(shape=(32,)) x = layers.Dense(64, activation='relu')(inputs) outputs = layers.Dense(10)(x) mlp = keras.Model(inputs, outputs)

Con la versión subclasificada:

class MLP(keras.Model): def __init__(self, **kwargs): super(MLP, self).__init__(**kwargs) self.dense_1 = layers.Dense(64, activation='relu') self.dense_2 = layers.Dense(10) def call(self, inputs): x = self.dense_1(inputs) return self.dense_2(x) # Instantiate the model. mlp = MLP() # Necessary to create the model's state. # The model doesn't have a state until it's called at least once. _ = mlp(tf.zeros((1, 32)))

Validación del modelo mientras se define su grafo de conectividad

En la API funcional, la especificación de entrada (forma y tipo) se crea por adelantado (utilizando Input). Cada vez que se llama a una capa, ésta comprueba que la especificación que se ha recibido coincide con sus suposiciones y, en caso contrario, mostrará un mensaje de error.

Esto garantiza que cualquier modelo que puedas construir con la API funcional se ejecutará. Toda la depuración, excepto la relacionada con la convergencia, se realiza estáticamente durante la construcción del modelo y no en tiempo de ejecución. Esto es similar a la verificación de tipos en un compilador.

Un modelo funcional se puede graficar e inspeccionar

Puede representar el modelo como un grafo, y puede acceder fácilmente a los nodos intermedios de este grafo. Por ejemplo, para extraer y reutilizar las activaciones de las capas intermedias (como se vio en un ejemplo anterior):

features_list = [layer.output for layer in vgg19.layers] feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)

Un modelo funcional puede serializarse o clonarse

Debido a que un modelo funcional es una estructura de datos en vez de un fragmento de código, es serializable de forma segura y puede guardarse como un único archivo que le permite recrear exactamente el mismo modelo sin tener acceso a nada del código original. Consulte la guía de serialización y guardado.

Para serializar un modelo de una subclase, es necesario que el implementador especifique un método get_config() y from_config() a nivel de modelo.

Debilidad funcional de la API:

No es compatible con arquitecturas dinámicas

La API funcional considera los modelos como DAGs de capas. Esto es cierto para la mayoría de las arquitecturas de aprendizaje profundo, pero no para todas; por ejemplo, las redes recursivas o las RNN de árbol no siguen este supuesto y no pueden implementarse en la API funcional.

Mezclar y combinar estilos de la API

Elegir entre la API funcional o la subclase Model no es una decisión binaria que te restrinja a una categoría de los modelos. Todos los modelos de la API tf.keras API pueden interactuar entre sí, ya sean modelos Sequential, modelos funcionales o modelos subclasificados escritos desde cero.

Siempre se puede utilizar un modelo funcional o Sequential como parte de un modelo o capa subclase:

units = 32 timesteps = 10 input_dim = 5 # Define a Functional model inputs = keras.Input((None, units)) x = layers.GlobalAveragePooling1D()(inputs) outputs = layers.Dense(1)(x) model = keras.Model(inputs, outputs) class CustomRNN(layers.Layer): def __init__(self): super(CustomRNN, self).__init__() self.units = units self.projection_1 = layers.Dense(units=units, activation="tanh") self.projection_2 = layers.Dense(units=units, activation="tanh") # Our previously-defined Functional model self.classifier = model def call(self, inputs): outputs = [] state = tf.zeros(shape=(inputs.shape[0], self.units)) for t in range(inputs.shape[1]): x = inputs[:, t, :] h = self.projection_1(x) y = h + self.projection_2(state) state = y outputs.append(y) features = tf.stack(outputs, axis=1) print(features.shape) return self.classifier(features) rnn_model = CustomRNN() _ = rnn_model(tf.zeros((1, timesteps, input_dim)))

Puede utilizar cualquier capa o modelo subclasificado en la API funcional siempre que implemente un método call que siga uno de los siguientes patrones:

  • call(self, inputs, **kwargs) -- Donde inputs es un tensor o una estructura anidada de tensores (por ejemplo, una lista de tensores), y donde **kwargs son argumentos que no son tensores (que no son entradas).

  • call(self, inputs, training=None, **kwargs) -- Donde training es un booleano que indica si la capa debe comportarse en modo entrenamiento y en modo inferencia.

  • call(self, inputs, mask=None, **kwargs) -- Donde mask es un tensor booleano de máscaras (por ejemplo, es muy útil para RNNs).

  • call(self, inputs, training=None, mask=None, **kwargs) -- Por supuesto, puede tener un comportamiento específico de enmascaramiento y entrenamiento al mismo tiempo.

Además, si implementa el método get_config en su capa o modelo personalizado, los modelos funcionales que cree seguirán siendo serializables y clonables.

A continuación se muestra un ejemplo rápido de una RNN personalizada, escrita desde cero, que se utiliza en un modelo funcional:

units = 32 timesteps = 10 input_dim = 5 batch_size = 16 class CustomRNN(layers.Layer): def __init__(self): super(CustomRNN, self).__init__() self.units = units self.projection_1 = layers.Dense(units=units, activation="tanh") self.projection_2 = layers.Dense(units=units, activation="tanh") self.classifier = layers.Dense(1) def call(self, inputs): outputs = [] state = tf.zeros(shape=(inputs.shape[0], self.units)) for t in range(inputs.shape[1]): x = inputs[:, t, :] h = self.projection_1(x) y = h + self.projection_2(state) state = y outputs.append(y) features = tf.stack(outputs, axis=1) return self.classifier(features) # Note that you specify a static batch size for the inputs with the `batch_shape` # arg, because the inner computation of `CustomRNN` requires a static batch size # (when you create the `state` zeros tensor). inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim)) x = layers.Conv1D(32, 3)(inputs) outputs = CustomRNN()(x) model = keras.Model(inputs, outputs) rnn_model = CustomRNN() _ = rnn_model(tf.zeros((1, 10, 5)))