Path: blob/master/site/es-419/guide/keras/custom_layers_and_models.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
Cómo crear nuevas capas y modelos mediante subclases
Preparación
La clase Layer
: la combinación de estado (ponderaciones) y algún cálculo
Una de las abstracciones centrales en Keras es la clase Layer
. Una capa encapsula tanto un estado (las "ponderaciones" de la capa) como una transformación de entradas a salidas (una "llamada", el paso siguiente de la capa).
A continuación se muestra una capa densamente conectada. Tiene un estado: las variables w
y b
.
Para utilizar una capa, es necesario llamarla a una entrada o entradas de tensor, como si se tratara de una función de Python.
Tenga en cuenta que los pesos w
y b
son rastreados automáticamente por la capa al establecerse como atributos de la capa:
Tenga en cuenta que también tiene acceso a un atajo más rápido para agregar ponderación a una capa: el método add_weight()
:
Las capas pueden tener ponderaciones que no se pueden entrenar
Además de las ponderaciones que se pueden entrenar, también se pueden agregar ponderaciones que no se pueden entrenar en una capa. Se supone que estas ponderaciones no se tendrán en cuenta durante la retropropagación, cuando la capa esté en entrenamiento.
A continuación se explica cómo agregar y utilizar una ponderación que no se puede entrenar:
Es parte de layer.weights
, pero se clasifica como una ponderación que no se puede entrenar:
Práctica recomendada: aplazar la creación de las ponderaciones hasta que se conozca la forma de las entradas.
Nuestra capa Linear
anterior tomó un argumento input_dim
que se utilizó para calcular la forma de las ponderaciones w
y b
en __init__()
:
En muchos casos, es posible que no conozca de antemano el tamaño de sus entradas, y le gustaría crear ponderaciones libremente cuando se conozca ese valor, un poco después de establecer la instancia de la capa.
En la API de Keras, recomendamos crear ponderaciones de capa en el método build(self, inputs_shape)
de su capa. De la siguiente manera:
El método __call__()
de su capa ejecutará automáticamente la creación la primera vez que se llame. Ahora tiene una capa que es lenta y por lo tanto más fácil de usar:
Implementar build()
por separado como se muestra arriba separa bien la creación de ponderaciones solo una vez de su uso en cada llamada. Sin embargo, para algunas capas personalizadas avanzadas, puede resultar poco práctico separar la creación del estado y el cálculo. Los implementadores de capas pueden diferir en la creación de las ponderaciones a la primera __call__()
, pero deben tener cuidado de que las llamadas posteriores utilicen las mismas ponderaciones. Además, dado que __call__()
es probable que se ejecute por primera vez dentro de una tf.function
, cualquier creación de una variable que tenga lugar en __call__()
debería estar envuelta en untf.init_scope
.
Las capas se pueden componer recursivamente
Si asigna una instancia de una capa como atributo de otra capa, la capa externa comenzará a seguir las ponderaciones creadas por la capa interna.
Le recomendamos crear dichas subcapas en el método __init__()
y dejar que sea el primer __call__()
el que active la creación de sus ponderaciones.
El método add_loss()
Cuando escriba el método call()
de una capa, puede crear tensores de pérdida que querrá utilizar más tarde, cuando escriba su bucle de entrenamiento. Esto se puede hacer llamando a self.add_loss(value)
:
Estas pérdidas (incluyendo las creadas por cualquier capa interna) pueden recuperarse mediante layer.losses
. Esta propiedad se restablece al inicio de cada __call__()
a la capa de nivel superior, de modo que layer.losses
siempre contiene los valores de pérdidas creados durante el último pase siguiente.
Además, la propiedad loss
también contiene pérdidas obtenidas por regularización para las ponderaciones de cualquier capa interna:
Estas pérdidas deben tenerse en cuenta al escribir bucles de entrenamiento, como éste:
Para obtener una guía detallada sobre la escritura de bucles de entrenamiento, consulte la guía para escribir un bucle de entrenamiento desde cero.
Estas pérdidas también funcionan perfectamente con fit()
(se suman automáticamente y se agregan a la pérdida principal, si la hay):
El método add_metric()
De forma similar a add_loss()
, las capas también tienen un método add_metric()
para seguir el promedio móvil de una cantidad durante el entrenamiento.
Consideremos la siguiente capa: una capa de "punto final logístico". Toma como entradas predicciones y objetivos, calcula una pérdida que rastrea mediante add_loss()
, y calcula un escalar de precisión, que se rastrea mediante add_metric()
.
Las métricas rastreadas de este modo son accesibles mediante layer.metrics
:
Al igual que en add_loss()
, estas métricas se rastrean mediante fit()
:
Puede activar opcionalmente la serialización en sus capas
Si es necesario que sus capas personalizadas se puedan serializar como parte de un {Functional model, puede implementar opcionalmente un método get_config()
:
Tenga en cuenta que el método __init__()
de la clase base Layer
toma algunos argumentos de la palabra clave, en particular un name
y un dtype
. Es una práctica recomendada pasar estos argumentos a la clase padre en __init__()
e incluirlos en la configuración de la capa:
Si necesita mayor flexibilidad para deserializar la capa desde su configuración, también puede sobreescribir el método de la clase from_config()
. Esta es la implementación base de from_config()
:
Para obtener más información sobre la serialización y el guardado, consulte la guía completa para guardar y serializar modelos.
Argumento training
privilegiado en el método call()
Algunas capas, en particular la capa BatchNormalization
y la capa Dropout
, tienen comportamientos diferentes durante el entrenamiento y la inferencia. Para estas capas, es una práctica habitual como introducir un argumento training
(booleano) en el método call()
.
Al exponer este argumento en call()
, permite que los bucles de entrenamiento y evaluación incorporados (por ejemplo fit()
) utilicen correctamente la capa en el entrenamiento y la inferencia.
Argumento mask
privilegiado en el método call()
El otro argumento privilegiado soportado por call()
es el argumento mask
.
Lo encontrará en todas las capas RNN de Keras. Una máscara es un tensor booleano (un valor booleano por paso de tiempo en la entrada) utilizado para omitir ciertos pasos de tiempo de entrada al procesar datos de series temporales.
Keras pasará automáticamente el argumento mask
correcto a __call__()
para las capas que lo admitan, cuando una capa anterior genere una máscara. Las capas que generan máscaras son la capa Embedding
configurada con mask_zero=True
, y la capa Masking
.
Para obtener más información sobre el enmascaramiento y cómo escribir capas con enmascaramiento, consulte la guía "cómo comprender el relleno y el enmascaramiento".
La clase Model
En general, utilizará la clase Layer
para definir los bloques del cálculo interno y utilizará la clase Model
para definir el modelo externo, el objeto que entrenará.
Por ejemplo, en un modelo ResNet50, tendría varios bloques ResNet que subclasificarían Layer
, y un único Model
que abarcaría toda la red ResNet50.
La clase Model
tiene la misma API que Layer
, con las siguientes diferencias:
Expone bucles de entrenamiento, evaluación y predicción incorporados (
model.fit()
,model.evaluate()
,model.predict()
).Expone la lista de sus capas internas, mediante la propiedad
model.layers
.Expone las API de guardado y serialización (
save()
,save_weights()
...)
En efecto, la clase Layer
corresponde a lo que en la literatura se denomina "capa" (como en "capa de convolución" o "capa recurrente") o "bloque" (como en "bloque ResNet" o "bloque Inception").
Por otra parte, la clase Model
corresponde a lo que en la literatura se denomina "modelo" (como en "modelo de aprendizaje profundo") o "red" (como en "red neuronal profunda").
Así que si se pregunta "¿debería usar la clase Layer
o la clase Model
?", pregúntese: ¿necesitaré llamar a fit()
en ella? ¿Tendré que llamar a save()
? Si es así, utilice Model
. Si no es así (ya sea porque su clase es sólo un bloque en un sistema más grande, o porque está escribiendo el código de entrenamiento y guardado usted mismo), utilice Layer
.
Por ejemplo, podríamos tomar nuestro ejemplo de mini-resnet anterior, y utilizarlo para construir un Model
que podríamos entrenar con fit()
, y que podríamos guardar con save_weights()
:
Todo en uno: un ejemplo de principio a fin
Esto es lo que ha aprendido hasta ahora:
A
Layer
encapsulate a state (created in__init__()
orbuild()
) and some computation (defined incall()
).Las capas pueden anidarse recursivamente para crear nuevos bloques de cálculo más grandes.
Las capas pueden crear y rastrear pérdidas (normalmente pérdidas de regularización), así como métricas, mediante
add_loss()
yadd_metric()
.El contenedor externo, lo que desea entrenar, es un
Model
. UnModel
es igual que unLayer
, pero con utilidades adicionales de entrenamiento y serialización.
Pongamos todas estas cosas juntas en un ejemplo de principio a fin: vamos a implementar un Autoencoder Variacional (VAE). Lo entrenaremos con dígitos MNIST.
Nuestra VAE será una subclase de Model
, construida como una composición anidada de capas que subclasifican a Layer
. Incluirá una pérdida de regularización (divergencia KL).
Escriba un bucle de entrenamiento simple en MNIST:
Tenga en cuenta que, dado que la VAE es una subclase de Model
, incorpora bucles de entrenamiento. Así que también podría haberlo entrenado así:
Más allá del desarrollo orientado a objetos: la API funcional
¿Le resultó este ejemplo demasiado orientado a objetos? También puede construir modelos utilizando la API Funcional. Es importante destacar que la elección de un estilo u otro no le impide aprovechar los componentes escritos en el otro estilo: siempre puede mezclar y hacer las combinaciones que desee.
Por ejemplo, el siguiente ejemplo de API funcional reutiliza la misma capa Sampling
que definimos en el ejemplo anterior:
Para obtener más información, asegúrese de leer la Guía funcional de la API.