
Keras tiene dos API para construir rápidamente arquitecturas de redes neuronales secuenciales y funcionales. Si el primero le permite construir solo arquitecturas secuenciales de redes neuronales, entonces, utilizando la API funcional, puede definir una red neuronal en forma de un gráfico acíclico dirigido arbitrario, que ofrece muchas más oportunidades para construir modelos complejos. Este artículo es una traducción de la Guía de funciones de API funcional del sitio web de TensorFlow.
Introduccion
La API funcional le permite crear modelos de manera más flexible que la API secuencial; puede procesar modelos con topología no lineal, modelos con capas comunes y modelos con múltiples entradas o salidas.
Se basa en el hecho de que el modelo de aprendizaje profundo suele ser un gráfico acíclico dirigido (DAG) de capas
API funcional es un conjunto de herramientas para
trazar capas .
Considere el siguiente modelo:
(entrada: vector de 784 dimensiones)
↧
[Capa densa (64 elementos, activación de relu)]
↧
[Capa densa (64 elementos, activación de relu)]
↧
[Capa densa (10 elementos, activación de softmax)]
↧
(salida: distribución de probabilidad en 10 clases)
Este es un gráfico simple de 3 capas.
Para construir este modelo utilizando la API funcional, debe comenzar creando un nodo de entrada:
from tensorflow import keras inputs = keras.Input(shape=(784,))
Aquí simplemente indicamos la dimensión de nuestros datos: vectores de 784 dimensiones. Tenga en cuenta que la cantidad de datos siempre se omite, indicamos solo la dimensión de cada elemento. Para ingresar el tamaño destinado a las imágenes `(32, 32, 3)`, usaríamos:
img_inputs = keras.Input(shape=(32, 32, 3))
Lo que devuelve las
inputs
contiene información sobre el tamaño y el tipo de datos que planea transferir a su modelo:
inputs.shape
TensorShape([None, 784])
inputs.dtype
tf.float32
Puede crear un nuevo nodo en el gráfico de capa llamando a la capa en este objeto de entrada:
from tensorflow.keras import layers dense = layers.Dense(64, activation='relu') x = dense(inputs)
"Llamar a una capa" es similar a dibujar una flecha desde la "entrada" a la capa que creamos. "Pasamos" la entrada a la capa
dense
, y obtenemos
x
.
Agreguemos algunas capas más a nuestro gráfico de capas:
x = layers.Dense(64, activation='relu')(x) outputs = layers.Dense(10, activation='softmax')(x)
Ahora podemos crear un
Model
especificando sus entradas y salidas en el gráfico de capas:
model = keras.Model(inputs=inputs, outputs=outputs)
Veamos nuevamente el proceso completo de definición del modelo:
inputs = keras.Input(shape=(784,), name='img') x = layers.Dense(64, activation='relu')(inputs) x = layers.Dense(64, activation='relu')(x) outputs = layers.Dense(10, activation='softmax')(x) model = keras.Model(inputs=inputs, outputs=outputs, name='mnist_model')
Veamos cómo se ve el resumen del modelo:
model.summary()
Model: "mnist_model" _________________________________________________________________ Layer (type) Output Shape Param
También podemos dibujar el modelo como un gráfico:
keras.utils.plot_model(model, 'my_first_model.png')

Y opcionalmente deriva las dimensiones de la entrada y salida de cada capa en el gráfico construido:
keras.utils.plot_model(model, 'my_first_model_with_shape_info.png', show_shapes=True)

Esta imagen y el código que escribimos son idénticos. En la versión de código, las flechas de enlace simplemente se reemplazan por operaciones de llamada.
El "gráfico de capas" es una imagen mental muy intuitiva para el modelo de aprendizaje profundo, y la API funcional es una forma de crear modelos que reflejan de cerca esta imagen mental.
Formación, evaluación y conclusión.
Aprender, evaluar y derivar el trabajo para modelos creados con la API funcional al igual que en los modelos secuenciales.
Considere una demostración rápida.
Aquí cargamos el conjunto de datos de imagen MNIST, lo convertimos en vectores, entrenamos el modelo en los datos (mientras monitoreamos la calidad del trabajo en la muestra de prueba) y finalmente evaluamos nuestro modelo en 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='sparse_categorical_crossentropy', optimizer=keras.optimizers.RMSprop(), metrics=['accuracy']) history = model.fit(x_train, y_train, batch_size=64, epochs=5, 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])
Guardar y serializar
El almacenamiento y la serialización para modelos creados con la API funcional funciona exactamente igual que para los modelos secuenciales.
La forma estándar de guardar un modelo funcional es llamar a
model.save(
), que le permite guardar todo el modelo en un archivo.
Posteriormente puede restaurar el mismo modelo desde este archivo, incluso si ya no tiene acceso al código que creó el modelo.
Este archivo incluye:
- Arquitectura modelo
- Pesas modelo (que se obtuvieron durante el entrenamiento)
- Configuración de entrenamiento del modelo (lo que pasó en la
compile
) - El optimizador y su estado, si lo fuera (esto le permite reanudar el entrenamiento desde donde lo dejó)
model.save('path_to_my_model.h5') del model
Usando el mismo gráfico de capa para definir múltiples modelos
En la API funcional, los modelos se crean especificando datos de entrada y salida en un gráfico de capa. Esto significa que se puede usar un gráfico de una sola capa para generar varios modelos.
En el siguiente ejemplo, usamos la misma pila de capas para crear dos modelos:
un modelo de
(encoder)
que convierte imágenes de entrada en vectores de 16 dimensiones, y un modelo de
(autoencoder)
extremo a extremo para 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()
Tenga en cuenta que hacemos que la arquitectura de decodificación sea estrictamente simétrica a la arquitectura de codificación, de modo que obtengamos la dimensión de los datos de salida igual que los datos de entrada
(28, 28, 1)
. La capa
Conv2D
está
Conv2D
a la capa
Conv2D
, y la capa
MaxPooling2D
será la parte posterior a la capa
MaxPooling2D
.
Los modelos se pueden llamar como capas
Puede usar cualquier modelo como si fuera una capa, llamándolo en
Input
o en la salida de otra capa.
Tenga en cuenta que al invocar un modelo, no solo reutiliza su arquitectura, sino que también reutiliza sus pesos. Vamos a verlo en acción. Aquí hay otro vistazo a un ejemplo de codificador automático cuando se crea un modelo de codificador, un modelo de decodificador y se conectan en dos llamadas para obtener un modelo de codificador automático:
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, un modelo puede estar anidado: un modelo puede contener un submodelo (ya que el modelo puede considerarse como una capa).
Un caso de uso común para los modelos de anidamiento es el
ensamblaje .
Como ejemplo, aquí se explica cómo combinar un conjunto de modelos en un modelo que promedia sus pronósticos:
def get_model(): inputs = keras.Input(shape=(128,)) outputs = layers.Dense(1, activation='sigmoid')(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)
Manipulación de topologías gráficas complejas.
Modelos con múltiples entradas y salidas.
La API funcional simplifica la manipulación de múltiples entradas y salidas. Esto no se puede hacer con la API secuencial.
Aquí hay un ejemplo simple.
Suponga que está creando un sistema para clasificar las aplicaciones de los clientes por prioridad y enviarlas al departamento correcto.
Su modelo tendrá 3 entradas:
- Encabezado de aplicación (entrada de texto)
- Contenido de texto de la aplicación (entrada de texto)
- Cualquier etiqueta agregada por el usuario (entrada categórica)
El modelo tendrá 2 salidas:
- Puntuación de prioridad entre 0 y 1 (salida sigmoidea escalar)
- El departamento que debe procesar la solicitud (salida softmax con respecto a muchos departamentos)
Construyamos un modelo en varias líneas usando la API funcional.
num_tags = 12
Dibujemos un gráfico modelo:
keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True)

Al compilar este modelo, podemos asignar diferentes funciones de pérdida a cada salida.
Incluso puede asignar diferentes pesos a cada función de pérdida para variar su contribución a la función general de pérdida de aprendizaje.
model.compile(optimizer=keras.optimizers.RMSprop(1e-3), loss=['binary_crossentropy', 'categorical_crossentropy'], loss_weights=[1., 0.2])
Como le dimos nombres a nuestras capas de salida, también podemos especificar funciones de pérdida:
model.compile(optimizer=keras.optimizers.RMSprop(1e-3), loss={'priority': 'binary_crossentropy', 'department': 'categorical_crossentropy'}, loss_weights=[1., 0.2])
Podemos entrenar el modelo pasando listas de matrices Numpy de datos de entrada y etiquetas:
import numpy as np
Al llamar a Fit con un objeto
Dataset
, se debe
([title_data, body_data, tags_data], [priority_targets, dept_targets])
una tupla de listas como
([title_data, body_data, tags_data], [priority_targets, dept_targets])
o una tupla de diccionarios
({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})
deben devolverse
({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})
.
Modelo de entrenamiento cursivo
Además de los modelos con múltiples entradas y salidas, la API funcional simplifica la manipulación de topologías con conectividad no lineal, es decir, modelos en los que las capas no están conectadas en serie. Dichos modelos tampoco pueden implementarse utilizando la API secuencial (como su nombre lo indica).
Un caso de uso común para esto son las conexiones residuales.
Construyamos un modelo de entrenamiento ResNet para CIFAR10 para demostrar esto.
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, activation='softmax')(x) model = keras.Model(inputs, outputs, name='toy_resnet') model.summary()
Dibujemos un gráfico modelo:
keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True)

Y enséñale:
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data() x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. 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='categorical_crossentropy', metrics=['acc']) model.fit(x_train, y_train, batch_size=64, epochs=1, validation_split=0.2)
Compartir capa
Otro buen uso de la API funcional son los modelos que usan capas comunes. Las capas comunes son instancias de capas que se reutilizan en el mismo modelo: estudian características que se relacionan con varias rutas en un gráfico de capas.
Las capas comunes a menudo se usan para codificar datos de entrada que provienen de los mismos espacios (por ejemplo, de dos piezas de texto diferentes que tienen el mismo diccionario), ya que proporcionan el intercambio de información entre estos datos diferentes, lo que permite que dichos modelos se entrenen con menos datos. Si aparece una palabra determinada en una de las entradas, esto facilitará su procesamiento en todas las entradas que pasan por el nivel general.
Para compartir una capa en la API funcional, simplemente llame a la misma instancia de la capa varias veces. Por ejemplo, aquí la capa de
Embedding
se comparte en dos entradas de texto:
Recuperando y reutilizando nodos en un gráfico de capas
Dado que el gráfico de capa que manipula en la API funcional es una estructura de datos estática, puede acceder a ella y verificarla. Así es como construimos modelos funcionales, por ejemplo, en forma de imágenes.
También significa que podemos acceder a las activaciones de las capas intermedias ("nodos" en el gráfico) y usarlas en otros lugares. ¡Esto es extremadamente útil para extraer rasgos, por ejemplo!
Veamos un ejemplo. Este es un modelo VGG19 con escalas pre-entrenadas en ImageNet:
from tensorflow.keras.applications import VGG19 vgg19 = VGG19()
Y estas son activaciones de modelo intermedias obtenidas al consultar la estructura de datos del gráfico:
features_list = [layer.output for layer in vgg19.layers]
Podemos usar estas características para crear un nuevo modelo de extracción de características que devuelva valores de activación de nivel intermedio, y podemos hacerlo todo en 3 líneas
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 es conveniente cuando se implementa la transferencia de estilo neural, como en otros casos.
Extendiendo la API escribiendo capas personalizadas
tf.keras
tiene una amplia gama de capas incorporadas. Aquí hay algunos ejemplos:
Capas convolucionales:
Conv1D
,
Conv2D
,
Conv3D
,
Conv2DTranspose
, etc.
MaxPooling1D
capas:
MaxPooling1D
,
MaxPooling2D
,
MaxPooling3D
,
AveragePooling1D
, etc.
Capas RNN:
GRU
,
LSTM
,
ConvLSTM2D
, etc.
BatchNormalization
,
Dropout
,
BatchNormalization
, etc.
Si no ha encontrado lo que necesita, es fácil extender la API creando su propia capa.
Todas las capas subclasifican la clase
Layer
e implementan:
El método de
call
que define los cálculos realizados por la capa.
El método de
build
que crea los pesos de las capas (tenga en cuenta que esto es solo una convención de estilo; también puede crear pesos en
__init__
).
Aquí hay una implementación simple de la capa
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)
Si desea que su capa personalizada admita la serialización, también debe definir el método
get_config
que devuelve 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, también puede implementar el método de clase
from_config (cls, config)
, que es responsable de volver a crear la instancia de capa, dado su diccionario de configuración. La
from_config
predeterminada
from_config
ve así:
def from_config(cls, config): return cls(**config)
Cuándo usar la API funcional
¿Cómo determinar cuándo es mejor usar la API funcional para crear un nuevo modelo, o simplemente subclasificar el
Model
directamente?
En general, la API funcional es más de alto nivel y fácil de usar, tiene una serie de funciones que no son compatibles con modelos subclasificados.
Sin embargo, la subclasificación del modelo le brinda una gran flexibilidad al crear modelos que no se describen fácilmente como un gráfico acíclico dirigido de capas (por ejemplo, no puede implementar Tree-RNN con la API funcional, debe subclasificar el
Model
directamente).
Fortalezas de API funcional:
Las propiedades enumeradas a continuación son verdaderas para los modelos secuenciales (que también son estructuras de datos), pero son verdaderas para los modelos subclasificados (que son código Python, no estructuras de datos).
La API funcional produce código más corto.
Sin
super(MyClass, self).__init__(...)
, sin
def call(self, ...):
etc.
Compara:
inputs = keras.Input(shape=(32,)) x = layers.Dense(64, activation='relu')(inputs) outputs = layers.Dense(10)(x) mlp = keras.Model(inputs, outputs)
Con versión subclaseada:
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)
Su modelo se valida como está escrito.
En la API funcional, las especificaciones de entrada (forma y dtype) se crean de antemano (a través de `Entrada`), y cada vez que llama a la capa, la capa verifica que las especificaciones que se le pasan coincidan con sus supuestos; si este no es el caso, recibirá un mensaje de error útil .
Esto garantiza que cualquier modelo que cree con la API funcional se inicie. Toda la depuración (no relacionada con la depuración de convergencia) ocurrirá 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 el compilador.
Su modelo funcional se puede representar gráficamente y también es comprobable.
Puede dibujar el modelo en forma de gráfico, y puede acceder fácilmente a los nodos intermedios del gráfico, por ejemplo, para extraer y reutilizar la activación de las capas intermedias, como vimos en el ejemplo anterior:
features_list = [layer.output for layer in vgg19.layers] feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
Dado que el modelo funcional es más una estructura de datos que un fragmento de código, se puede serializar de forma segura y se puede guardar como un solo archivo que le permite recrear exactamente el mismo modelo sin acceso al código fuente.
Debilidades funcionales de API
No es compatible con arquitecturas dinámicas.
La API funcional procesa modelos como capas DAG. Esto es cierto para la mayoría de las arquitecturas de aprendizaje profundo, pero no para todos: por ejemplo, las redes recursivas o los RNN de árbol no cumplen con este supuesto y no se pueden implementar en la API funcional.
A veces solo necesitas escribir todo desde cero.
Al escribir arquitecturas avanzadas, es posible que desee hacer algo que vaya más allá de "definir capas DAG": por ejemplo, puede usar varios métodos de entrenamiento y salida personalizados en una instancia de su modelo. Esto requiere subclases.
Combinando y combinando varios estilos de API
Es importante tener en cuenta que elegir entre la API funcional o subclasificar el modelo no es una solución binaria que lo limite a una categoría de modelos.
Todos los modelos en la API tf.keras pueden interactuar entre sí, ya sean modelos secuenciales, modelos funcionales o modelos / capas subclasificados escritos desde cero.Siempre puede usar el modelo funcional o el modelo secuencial como parte del modelo / capa subclasificado: units = 32 timesteps = 10 input_dim = 5
Por el contrario, puede usar cualquier capa o modelo subclasificado en la API funcional si implementa un método call
que coincida con uno de los siguientes patrones:call(self, inputs, **kwargs)
dónde inputs
está la estructura tensora o tensora anidada (por ejemplo, lista de tensores) y dónde **kwargs
están los argumentos no tensoriales (no de entrada) .call(self, inputs, training=None, **kwargs)
donde training
es un valor booleano que indica en qué modo debe comportarse la capa, el aprendizaje o la salida.call(self, inputs, mask=None, **kwargs)
donde mask
está el tensor de máscara booleana (útil para RNN, por ejemplo).call(self, inputs, training=None, mask=None, **kwargs)
- por supuesto, puede tener ambos parámetros que definen el comportamiento de la capa al mismo tiempo.Además, si implementa el método `get_config` en su Capa o Modelo personalizado, los modelos funcionales que cree con él serán serializables y clonados.A continuación se muestra un pequeño ejemplo en el que utilizamos modelos RNN personalizados escritos desde cero Modelos funcionales: 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, activation='sigmoid') 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)
¡Esto concluye nuestra Guía API funcional!Ahora tiene a mano un poderoso conjunto de herramientas para construir modelos de aprendizaje profundo.Después de la verificación, la traducción también aparecerá en Tensorflow.org. Si desea participar en la traducción de la documentación del sitio web de Tensorflow.org al ruso, comuníquese personalmente o envíe sus comentarios. Cualquier corrección o comentario son apreciados. Como ilustración, utilizamos la imagen del modelo GoogLeNet, que también es un gráfico acíclico dirigido.