Sintonizar con las primeras mujeres en ML Simposio este martes, 19 de octubre a 09 a.m. PST Registrar ahora

Introducción a módulos, capas y modelos

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Para realizar aprendizaje automático en TensorFlow, es probable que deba definir, guardar y restaurar un modelo.

Un modelo es, de manera abstracta:

  • Una función que calcula algo en tensores (un pase hacia adelante)
  • Algunas variables que se pueden actualizar en respuesta al entrenamiento

En esta guía, irá por debajo de la superficie de Keras para ver cómo se definen los modelos de TensorFlow. Aquí se analiza cómo TensorFlow recopila variables y modelos, así como cómo se guardan y restauran.

Configuración

import tensorflow as tf
from datetime import datetime

%load_ext tensorboard

Definición de modelos y capas en TensorFlow

La mayoría de los modelos están hechos de capas. Las capas son funciones con una estructura matemática conocida que se pueden reutilizar y tienen variables entrenables. En TensorFlow, la mayoría de las implementaciones de alto nivel de las capas y modelos, como Keras o Sonnet , se basan en la misma clase fundamental: tf.Module .

Aquí está un ejemplo de una forma muy sencilla tf.Module que opera en un tensor escalar:

class SimpleModule(tf.Module):
  def __init__(self, name=None):
    super().__init__(name=name)
    self.a_variable = tf.Variable(5.0, name="train_me")
    self.non_trainable_variable = tf.Variable(5.0, trainable=False, name="do_not_train_me")
  def __call__(self, x):
    return self.a_variable * x + self.non_trainable_variable

simple_module = SimpleModule(name="simple")

simple_module(tf.constant(5.0))
<tf.Tensor: shape=(), dtype=float32, numpy=30.0>

Los módulos y, por extensión, las capas son terminología de aprendizaje profundo para "objetos": tienen un estado interno y métodos que utilizan ese estado.

No hay nada especial en __call__ excepto para actuar como un pitón exigible ; puede invocar sus modelos con las funciones que desee.

Puede activar y desactivar la capacidad de entrenamiento de las variables por cualquier motivo, incluida la congelación de capas y variables durante el ajuste fino.

Por subclases tf.Module , cualquier tf.Variable o tf.Module casos asignados a las propiedades de este objeto se recogen automáticamente. Esto le permite guardar y las variables de carga, y también crear colecciones de tf.Module s.

# All trainable variables
print("trainable variables:", simple_module.trainable_variables)
# Every variable
print("all variables:", simple_module.variables)
trainable variables: (<tf.Variable 'train_me:0' shape=() dtype=float32, numpy=5.0>,)
all variables: (<tf.Variable 'train_me:0' shape=() dtype=float32, numpy=5.0>, <tf.Variable 'do_not_train_me:0' shape=() dtype=float32, numpy=5.0>)
2021-09-22 20:41:24.398693: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.

Este es un ejemplo de un modelo de capa lineal de dos capas hecho de módulos.

Primero una capa densa (lineal):

class Dense(tf.Module):
  def __init__(self, in_features, out_features, name=None):
    super().__init__(name=name)
    self.w = tf.Variable(
      tf.random.normal([in_features, out_features]), name='w')
    self.b = tf.Variable(tf.zeros([out_features]), name='b')
  def __call__(self, x):
    y = tf.matmul(x, self.w) + self.b
    return tf.nn.relu(y)

Y luego el modelo completo, que crea instancias de dos capas y las aplica:

class SequentialModule(tf.Module):
  def __init__(self, name=None):
    super().__init__(name=name)

    self.dense_1 = Dense(in_features=3, out_features=3)
    self.dense_2 = Dense(in_features=3, out_features=2)

  def __call__(self, x):
    x = self.dense_1(x)
    return self.dense_2(x)

# You have made a model!
my_model = SequentialModule(name="the_model")

# Call it, with random results
print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[0.         0.05766037]], shape=(1, 2), dtype=float32)

tf.Module casos serán automáticamente a cobro revertido, de forma recursiva, cualquier tf.Variable o tf.Module casos asignado a él. Esto le permite gestionar colecciones de tf.Module s con una sola instancia de modelo, y guardar y cargar modelos enteros.

print("Submodules:", my_model.submodules)
Submodules: (<__main__.Dense object at 0x7f68843f0610>, <__main__.Dense object at 0x7f693c112990>)
for var in my_model.variables:
  print(var, "\n")
<tf.Variable 'b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)> 

<tf.Variable 'w:0' shape=(3, 3) dtype=float32, numpy=
array([[ 1.3168278 , -0.04209378,  1.4812242 ],
       [-1.8976173 , -1.1280936 , -2.57399   ],
       [ 0.43279243,  1.2495825 ,  0.31452706]], dtype=float32)> 

<tf.Variable 'b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)> 

<tf.Variable 'w:0' shape=(3, 2) dtype=float32, numpy=
array([[ 0.70494235,  0.34362802],
       [-1.318382  ,  0.3631226 ],
       [ 0.870611  ,  0.6044129 ]], dtype=float32)>

Esperando crear variables

Es posible que haya notado aquí que debe definir los tamaños de entrada y salida para la capa. Esto es por lo que el w variable tiene una forma conocida y se puede asignar.

Al diferir la creación de variables hasta la primera vez que se llama al módulo con una forma de entrada específica, no es necesario especificar el tamaño de entrada por adelantado.

class FlexibleDenseModule(tf.Module):
  # Note: No need for `in_features`
  def __init__(self, out_features, name=None):
    super().__init__(name=name)
    self.is_built = False
    self.out_features = out_features

  def __call__(self, x):
    # Create variables on first call.
    if not self.is_built:
      self.w = tf.Variable(
        tf.random.normal([x.shape[-1], self.out_features]), name='w')
      self.b = tf.Variable(tf.zeros([self.out_features]), name='b')
      self.is_built = True

    y = tf.matmul(x, self.w) + self.b
    return tf.nn.relu(y)
# Used in a module
class MySequentialModule(tf.Module):
  def __init__(self, name=None):
    super().__init__(name=name)

    self.dense_1 = FlexibleDenseModule(out_features=3)
    self.dense_2 = FlexibleDenseModule(out_features=2)

  def __call__(self, x):
    x = self.dense_1(x)
    return self.dense_2(x)

my_model = MySequentialModule(name="the_model")
print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[0.       4.937734]], shape=(1, 2), dtype=float32)

Esta flexibilidad es la razón por capas TensorFlow menudo sólo necesitan especificar la forma de sus salidas, tal como en tf.keras.layers.Dense , en lugar de tanto el tamaño de entrada y de salida.

Ahorro de peso

Puede guardar un tf.Module tanto como un punto de control y una SavedModel .

Los puntos de control son solo los pesos (es decir, los valores del conjunto de variables dentro del módulo y sus submódulos):

chkp_path = "my_checkpoint"
checkpoint = tf.train.Checkpoint(model=my_model)
checkpoint.write(chkp_path)
'my_checkpoint'

Los puntos de control constan de dos tipos de archivos: los datos en sí y un archivo de índice para metadatos. El archivo de índice realiza un seguimiento de lo que realmente se guarda y la numeración de los puntos de control, mientras que los datos del punto de control contienen los valores de las variables y sus rutas de búsqueda de atributos.

ls my_checkpoint*
my_checkpoint.data-00000-of-00001  my_checkpoint.index

Puede mirar dentro de un punto de control para asegurarse de que se guarde toda la colección de variables, ordenadas por el objeto de Python que las contiene.

tf.train.list_variables(chkp_path)
[('_CHECKPOINTABLE_OBJECT_GRAPH', []),
 ('model/dense_1/b/.ATTRIBUTES/VARIABLE_VALUE', [3]),
 ('model/dense_1/w/.ATTRIBUTES/VARIABLE_VALUE', [3, 3]),
 ('model/dense_2/b/.ATTRIBUTES/VARIABLE_VALUE', [2]),
 ('model/dense_2/w/.ATTRIBUTES/VARIABLE_VALUE', [3, 2])]

Durante el entrenamiento distribuido (multi-máquina), se pueden fragmentar, por lo que están numerados (por ejemplo, '00000-of-00001'). En este caso, sin embargo, solo hay un fragmento.

Cuando vuelves a cargar modelos, sobrescribes los valores en tu objeto Python.

new_model = MySequentialModule()
new_checkpoint = tf.train.Checkpoint(model=new_model)
new_checkpoint.restore("my_checkpoint")

# Should be the same result as above
new_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[0.      , 4.937734]], dtype=float32)>

Funciones de ahorro

TensorFlow puede ejecutar modelos sin los objetos originales de Python, como se demuestra por TensorFlow Sirviendo y TensorFlow Lite , incluso cuando se descarga un modelo entrenado desde TensorFlow Hub .

TensorFlow necesita saber cómo hacer los cálculos descritos en Python, pero sin el código original. Para ello, se puede hacer una gráfica, que se describe en la Introducción a los gráficos y funciones de guía .

Este gráfico contiene operaciones, ops, o que implementan la función.

Se puede definir un gráfico en el modelo anterior, añadiendo el @tf.function decorador para indicar que el código debe funcionar como un gráfico.

class MySequentialModule(tf.Module):
  def __init__(self, name=None):
    super().__init__(name=name)

    self.dense_1 = Dense(in_features=3, out_features=3)
    self.dense_2 = Dense(in_features=3, out_features=2)

  @tf.function
  def __call__(self, x):
    x = self.dense_1(x)
    return self.dense_2(x)

# You have made a model with a graph!
my_model = MySequentialModule(name="the_model")

El módulo que ha realizado funciona exactamente igual que antes. Cada firma única que se pasa a la función crea un gráfico independiente. Compruebe la Introducción a los gráficos y funciones de guía para más detalles.

print(my_model([[2.0, 2.0, 2.0]]))
print(my_model([[[2.0, 2.0, 2.0], [2.0, 2.0, 2.0]]]))
tf.Tensor([[0.57633626 0.8910464 ]], shape=(1, 2), dtype=float32)
tf.Tensor(
[[[0.57633626 0.8910464 ]
  [0.57633626 0.8910464 ]]], shape=(1, 2, 2), dtype=float32)

Puede visualizar el gráfico trazándolo dentro de un resumen de TensorBoard.

# Set up logging.
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = "logs/func/%s" % stamp
writer = tf.summary.create_file_writer(logdir)

# Create a new model to get a fresh trace
# Otherwise the summary will not see the graph.
new_model = MySequentialModule()

# Bracket the function call with
# tf.summary.trace_on() and tf.summary.trace_export().
tf.summary.trace_on(graph=True)
tf.profiler.experimental.start(logdir)
# Call only one tf.function when tracing.
z = print(new_model(tf.constant([[2.0, 2.0, 2.0]])))
with writer.as_default():
  tf.summary.trace_export(
      name="my_func_trace",
      step=0,
      profiler_outdir=logdir)
tf.Tensor([[0. 0.]], shape=(1, 2), dtype=float32)

Inicie TensorBoard para ver el seguimiento resultante:

%tensorboard --logdir logs/func

Una captura de pantalla del gráfico en TensorBoard

La creación de un SavedModel

La forma recomendada de compartir modelos completamente entrenados es utilizar SavedModel . SavedModel contiene tanto una colección de funciones y una colección de pesas.

Puede guardar el modelo que acaba de entrenar de la siguiente manera:

tf.saved_model.save(my_model, "the_saved_model")
INFO:tensorflow:Assets written to: the_saved_model/assets
# Inspect the SavedModel in the directory
ls -l the_saved_model
total 24
drwxr-sr-x 2 kbuilder kokoro  4096 Sep 22 20:41 assets
-rw-rw-r-- 1 kbuilder kokoro 14702 Sep 22 20:41 saved_model.pb
drwxr-sr-x 2 kbuilder kokoro  4096 Sep 22 20:41 variables
# The variables/ directory contains a checkpoint of the variables
ls -l the_saved_model/variables
total 8
-rw-rw-r-- 1 kbuilder kokoro 408 Sep 22 20:41 variables.data-00000-of-00001
-rw-rw-r-- 1 kbuilder kokoro 356 Sep 22 20:41 variables.index

El saved_model.pb archivo es un protocolo de tampón que describe el funcional tf.Graph .

Los modelos y capas se pueden cargar desde esta representación sin realmente hacer una instancia de la clase que la creó. Esto es lo que se desea en situaciones en las que no tiene (o no desea) un intérprete de Python, como el servicio a escala o en un dispositivo periférico, o en situaciones en las que el código original de Python no está disponible o no es práctico de usar.

Puede cargar el modelo como nuevo objeto:

new_model = tf.saved_model.load("the_saved_model")

new_model , creado a partir de la carga de un modelo guardado, es un objeto de usuario TensorFlow interno sin ninguno de los conocimientos clase. No es de tipo SequentialModule .

isinstance(new_model, SequentialModule)
False

Este nuevo modelo funciona con las firmas de entrada ya definidas. No puede agregar más firmas a un modelo restaurado como este.

print(my_model([[2.0, 2.0, 2.0]]))
print(my_model([[[2.0, 2.0, 2.0], [2.0, 2.0, 2.0]]]))
tf.Tensor([[0.57633626 0.8910464 ]], shape=(1, 2), dtype=float32)
tf.Tensor(
[[[0.57633626 0.8910464 ]
  [0.57633626 0.8910464 ]]], shape=(1, 2, 2), dtype=float32)

Por lo tanto, el uso de SavedModel , que son capaces de ahorrar peso y gráficos utilizando TensorFlow tf.Module , y luego cargarlos de nuevo.

Modelos y capas de Keras

Tenga en cuenta que hasta este punto, no se menciona a Keras. Usted puede construir su propia API de alto nivel en la parte superior de tf.Module , y la gente tiene.

En esta sección, se examinará cómo Keras utiliza tf.Module . Una guía de usuario completa a modelos Keras se puede encontrar en la guía Keras .

Capas de Keras

tf.keras.layers.Layer es la clase base de todas las capas Keras, y se hereda de tf.Module .

Puede convertir un módulo en una capa Keras simplemente mediante el canje de los padres y luego cambiar __call__ de call :

class MyDense(tf.keras.layers.Layer):
  # Adding **kwargs to support base Keras layer arguments
  def __init__(self, in_features, out_features, **kwargs):
    super().__init__(**kwargs)

    # This will soon move to the build step; see below
    self.w = tf.Variable(
      tf.random.normal([in_features, out_features]), name='w')
    self.b = tf.Variable(tf.zeros([out_features]), name='b')
  def call(self, x):
    y = tf.matmul(x, self.w) + self.b
    return tf.nn.relu(y)

simple_layer = MyDense(name="simple", in_features=3, out_features=3)

Keras capas tienen su propia __call__ que hace algo de contabilidad se describe en la siguiente sección y luego llama a call() . No debería notar ningún cambio en la funcionalidad.

simple_layer([[2.0, 2.0, 2.0]])
<tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[4.441887  , 0.00987494, 1.2474661 ]], dtype=float32)>

La build paso

Como se señaló, en muchos casos es conveniente esperar para crear variables hasta que esté seguro de la forma de entrada.

Las capas de Keras vienen con un paso de ciclo de vida adicional que le permite más flexibilidad en la forma en que define sus capas. Esto se define en la build de funciones.

build se llama exactamente una vez, y se llama a la forma de la entrada. Suele utilizarse para crear variables (ponderaciones).

Puede volver a escribir MyDense capa por encima de ser flexible con el tamaño de sus entradas:

class FlexibleDense(tf.keras.layers.Layer):
  # Note the added `**kwargs`, as Keras supports many arguments
  def __init__(self, out_features, **kwargs):
    super().__init__(**kwargs)
    self.out_features = out_features

  def build(self, input_shape):  # Create the state of the layer (weights)
    self.w = tf.Variable(
      tf.random.normal([input_shape[-1], self.out_features]), name='w')
    self.b = tf.Variable(tf.zeros([self.out_features]), name='b')

  def call(self, inputs):  # Defines the computation from inputs to outputs
    return tf.matmul(inputs, self.w) + self.b

# Create the instance of the layer
flexible_dense = FlexibleDense(out_features=3)

En este punto, el modelo no se ha construido, por lo que no hay variables:

flexible_dense.variables
[]

Llamar a la función asigna variables de tamaño apropiado:

# Call it, with predictably random results
print("Model results:", flexible_dense(tf.constant([[2.0, 2.0, 2.0], [3.0, 3.0, 3.0]])))
Model results: tf.Tensor(
[[ 0.26693314 -4.5170803  -2.1615696 ]
 [ 0.40039957 -6.7756205  -3.2423544 ]], shape=(2, 3), dtype=float32)
flexible_dense.variables
[<tf.Variable 'flexible_dense/w:0' shape=(3, 3) dtype=float32, numpy=
 array([[ 0.69237417, -1.9560792 , -0.8260334 ],
        [-0.5022879 , -1.314342  ,  0.6498058 ],
        [-0.05661968,  1.0118811 , -0.90455717]], dtype=float32)>,
 <tf.Variable 'flexible_dense/b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]

Dado que build sólo se le llama una vez, las entradas serán rechazadas si la forma de entrada no es compatible con las variables de la capa:

try:
  print("Model results:", flexible_dense(tf.constant([[2.0, 2.0, 2.0, 2.0]])))
except tf.errors.InvalidArgumentError as e:
  print("Failed:", e)
Failed: In[0] mismatch In[1] shape: 4 vs. 3: [1,4] [3,3] 0 0 [Op:MatMul]

Las capas de Keras tienen muchas más características adicionales que incluyen:

  • Pérdidas opcionales
  • Soporte para métricas
  • El soporte integrado para una opcional training argumento para diferenciar entre la formación y el uso de inferencia
  • get_config y from_config métodos que le permiten almacenar configuraciones con precisión para permitir la clonación modelo en Python

Leer sobre ellos en la guía completa a las capas y los modelos personalizados.

Modelos Keras

Puede definir su modelo como capas de Keras anidadas.

Sin embargo, Keras también proporciona una clase modelo completo llamado tf.keras.Model . Se hereda de tf.keras.layers.Layer , por lo que un modelo Keras se puede utilizar, anidada, y se guarda en la misma forma que las capas Keras. Los modelos de Keras vienen con una funcionalidad adicional que los hace fáciles de entrenar, evaluar, cargar, guardar e incluso entrenar en varias máquinas.

Se puede definir el SequentialModule desde arriba con código casi idéntica, de nuevo la conversión __call__ de call() y el cambio de la matriz:

class MySequentialModel(tf.keras.Model):
  def __init__(self, name=None, **kwargs):
    super().__init__(**kwargs)

    self.dense_1 = FlexibleDense(out_features=3)
    self.dense_2 = FlexibleDense(out_features=2)
  def call(self, x):
    x = self.dense_1(x)
    return self.dense_2(x)

# You have made a Keras model!
my_sequential_model = MySequentialModel(name="the_model")

# Call it on a tensor, with random results
print("Model results:", my_sequential_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[13.535655 -1.076561]], shape=(1, 2), dtype=float32)

Están disponibles las mismas funciones, incluidas las variables de seguimiento y los submódulos.

my_sequential_model.variables
[<tf.Variable 'my_sequential_model/flexible_dense_1/w:0' shape=(3, 3) dtype=float32, numpy=
 array([[ 0.45659065,  0.38672394, -1.4717953 ],
        [-0.60538477, -0.7337349 , -1.9595573 ],
        [ 0.56756437, -0.7816317 , -0.43361524]], dtype=float32)>,
 <tf.Variable 'my_sequential_model/flexible_dense_1/b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>,
 <tf.Variable 'my_sequential_model/flexible_dense_2/w:0' shape=(3, 2) dtype=float32, numpy=
 array([[-0.66438836, -0.6865299 ],
        [-0.5305109 , -0.68015355],
        [-1.6681372 ,  0.2635035 ]], dtype=float32)>,
 <tf.Variable 'my_sequential_model/flexible_dense_2/b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)>]
my_sequential_model.submodules
(<__main__.FlexibleDense at 0x7f691bf92d10>,
 <__main__.FlexibleDense at 0x7f691ad189d0>)

Anulando tf.keras.Model es un enfoque muy Pythonic a la construcción de modelos TensorFlow. Si está migrando modelos desde otros marcos, esto puede ser muy sencillo.

Si usted está construyendo modelos que son simples conjuntos de capas y las entradas existentes, puede ahorrar tiempo y espacio mediante el uso de la API funcional , que viene con características adicionales alrededor de la reconstrucción de modelo y de la arquitectura.

Aquí está el mismo modelo con la API funcional:

inputs = tf.keras.Input(shape=[3,])

x = FlexibleDense(3)(inputs)
x = FlexibleDense(2)(x)

my_functional_model = tf.keras.Model(inputs=inputs, outputs=x)

my_functional_model.summary()
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 3)]               0         
_________________________________________________________________
flexible_dense_3 (FlexibleDe (None, 3)                 12        
_________________________________________________________________
flexible_dense_4 (FlexibleDe (None, 2)                 8         
=================================================================
Total params: 20
Trainable params: 20
Non-trainable params: 0
_________________________________________________________________
my_functional_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[ 0.5620958, -0.3502033]], dtype=float32)>

La principal diferencia aquí es que la forma de entrada se especifica por adelantado como parte del proceso de construcción funcional. El input_shape argumento en este caso no tiene que ser completamente especificada; se puede dejar algunas dimensiones que None .

Guardar modelos de Keras

Keras modelos se pueden checkpoints, y que tienen el mismo aspecto como tf.Module .

Keras modelos también se pueden guardar con tf.saved_model.save() , ya que son los módulos. Sin embargo, los modelos de Keras tienen métodos de conveniencia y otras funcionalidades:

my_sequential_model.save("exname_of_file")
INFO:tensorflow:Assets written to: exname_of_file/assets

Con la misma facilidad, se pueden volver a cargar en:

reconstructed_model = tf.keras.models.load_model("exname_of_file")
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.

Keras SavedModels también ahorrar estados métrica, la pérdida y optimizador.

Este modelo reconstruido se puede utilizar y producirá el mismo resultado cuando se llame a los mismos datos:

reconstructed_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[13.535655, -1.076561]], dtype=float32)>

Hay más información sobre cómo guardar y serializar modelos de Keras, incluido el suministro de métodos de configuración para capas personalizadas para compatibilidad con funciones. Echa un vistazo a la guía para el ahorro y la serialización .

Que sigue

Si desea conocer más detalles sobre Keras, puede seguir las guías existentes Keras aquí .

Otro ejemplo de una API de alto nivel construido sobre tf.module es Soneto de DeepMind, que está cubierta en su sitio .