Ayuda a proteger la Gran Barrera de Coral con TensorFlow en Kaggle Únete Challenge

TensorFlow 1.x vs TensorFlow 2 - Comportamientos y API

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

Bajo el capó, TensorFlow 2 sigue un paradigma de programación fundamentalmente diferente al de TF1.x.

Esta guía describe las diferencias fundamentales entre TF1.xy TF2 en términos de comportamientos y API, y cómo se relacionan todas con su viaje de migración.

Resumen de alto nivel de cambios importantes

Fundamentalmente, TF1.xy TF2 usan un conjunto diferente de comportamientos en tiempo de ejecución en torno a la ejecución (ansioso en TF2), variables, flujo de control, formas de tensor y comparaciones de igualdad de tensor. Para ser compatible con TF2, su código debe ser compatible con el conjunto completo de comportamientos de TF2. Durante la migración, se puede activar o desactivar la mayoría de estos comportamientos de forma individual a través de la tf.compat.v1.enable_* o tf.compat.v1.disable_* API. La única excepción es la eliminación de colecciones, que es un efecto secundario de habilitar / deshabilitar la ejecución ansiosa.

En un nivel alto, TensorFlow 2:

Las secciones siguientes proporcionan más contexto sobre las diferencias entre TF1.xy TF2. Para obtener más información sobre el proceso de diseño detrás de TF2, lea las RFC y los documentos de diseño .

Limpieza de API

Muchas API o bien se han ido o se movieron en TF2. Algunos de los principales cambios incluyen la eliminación de tf.app , tf.flags y tf.logging a favor de la empresa de código abierto ABSL-py , rehoming proyectos que vivían en tf.contrib , y la limpieza de la principal tf.* Espacio de nombres mover funciones menos utilizadas en subpaquetes como tf.math . Algunas API han sido reemplazados por sus equivalentes de TF2 - tf.summary , tf.keras.metrics y tf.keras.optimizers .

tf.compat.v1 : Criterios de valoración API anterior y compatibilidad

Símbolos bajo la tf.compat y tf.compat.v1 espacios de nombres no se consideran TF2 API. Estos espacios de nombres exponen una combinación de símbolos de compatibilidad, así como puntos finales de API heredados de TF 1.x. Estos están destinados a ayudar a la migración de TF1.xa TF2. Sin embargo, ya que ninguno de estos compat.v1 API son las API de TF2 idiomáticas, no utilizarlos para escribir código TF2 nuevo.

Individuales tf.compat.v1 símbolos pueden ser compatibles TF2 porque siguen trabajando incluso con habilitadas TF2 comportamientos (como tf.compat.v1.losses.mean_squared_error ), mientras que otros son incompatibles con TF2 (como tf.compat.v1.metrics.accuracy ). Muchos compat.v1 símbolos (aunque no todos) contienen información sobre la migración dedicada en su documentación que explica su grado de compatibilidad con TF2 comportamientos, así como la forma de migrar a las API de TF2.

El script de actualización de TF2 puede asignar muchos compat.v1 símbolos API a API TF2 equivalentes en el caso en el que son los alias o tienen los mismos argumentos, pero con un orden distinto. También puede utilizar el script de actualización para cambiar automáticamente el nombre de las API de TF1.x.

API de falso amigo

Hay un conjunto de símbolos "falso amigo" que se encuentra en el TF2 tf espacio de nombres (no bajo compat.v1 ) que en realidad ignoran TF2 comportamientos bajo el capó, y / o no son totalmente compatibles con el conjunto de comportamientos de TF2. Como tal, es probable que estas API se comporten mal con el código TF2, potencialmente de manera silenciosa.

  • tf.estimator.* : Estimadores crear y gráficos de uso y las sesiones bajo el capó. Como tal, estos no deben considerarse compatibles con TF2. Si su código está ejecutando estimadores, no está usando comportamientos TF2.
  • keras.Model.model_to_estimator(...) : Esto crea un estimador bajo el capó, que como se mencionó anteriormente, no es compatible con TF2.
  • tf.Graph().as_default() : Este entra comportamientos gráfico TF1.x y no sigue estándar compatible-TF2 tf.function comportamientos. El código que ingresa a gráficos como este generalmente los ejecutará a través de Sesiones y no debe considerarse compatible con TF2.
  • tf.feature_column.* Las API de columna de características generalmente se basan en estilo TF1 tf.compat.v1.get_variable creación variable y asumen que se tendrá acceso a las variables creadas a través de colecciones globales. Como TF2 no admite colecciones, es posible que las API no funcionen correctamente cuando se ejecutan con los comportamientos TF2 habilitados.

Otros cambios de API

  • TF2 ofrece mejoras significativas en los algoritmos de colocación del dispositivo que hace que el uso de tf.colocate_with innecesario. Si la eliminación provoca un degradar el rendimiento del informe de un error .

  • Reemplazar todo el uso de tf.v1.ConfigProto con funciones equivalentes de tf.config .

Ejecución ansiosa

TF1.x requiere que usted cosa manualmente juntos un árbol de sintaxis abstracta (el gráfico) al hacer tf.* Llamadas a la API y luego compilar manualmente el árbol de sintaxis abstracta que pasa por un conjunto de tensores y tensores de salida de entrada a un session.run llamada. TF2 se ejecuta con entusiasmo (como normalmente lo hace Python) y hace que los gráficos y las sesiones se sientan como detalles de implementación.

Uno de los subproductos notable de la ejecución ansioso es que tf.control_dependencies ya no se requiere, como todas las líneas de código se ejecutan en orden (dentro de un tf.function , código con ejecuta efectos secundarios en el orden escrito).

No más globales

TF1.x se basaba en gran medida en colecciones y espacios de nombres globales implícitos. Cuando llamó tf.Variable , que se pondría en una colección en el gráfico predeterminado, y que permanecería allí, incluso si ha perdido la pista de la variable de Python apuntando hacia él. A continuación, podría recuperar esa tf.Variable , pero sólo si se sabía el nombre que había sido creado con. Esto era difícil de hacer si no tenía el control de la creación de la variable. Como resultado, proliferaron todo tipo de mecanismos para intentar ayudarlo a encontrar sus variables nuevamente y para que los marcos encuentren variables creadas por el usuario. Algunos de estos incluyen: alcances variables, colecciones mundiales, métodos auxiliares como tf.get_global_step y tf.global_variables_initializer , optimizadores implícitamente computación gradientes sobre todas las variables entrenables, y así sucesivamente. TF2 elimina todos estos mecanismos ( Variables 2.0 RFC ) a favor del mecanismo por defecto - realizar un seguimiento de sus variables. Si se pierde la pista de un tf.Variable , se pone basura recogida.

El requisito de realizar un seguimiento de las variables crea un trabajo extra, pero con herramientas como las cuñas de modelado y comportamientos como colecciones variables orientadas a objetos implícitas en tf.Module s y tf.keras.layers.Layer s , se reduce al mínimo la carga.

Funciones, no sesiones

Un session.run llamada es casi como una llamada de función: permite especificar las entradas y la función que se llamará, y vuelvas un conjunto de salidas. En TF2, se puede decorar una función de Python usando tf.function para marcarlo para la compilación JIT para que TensorFlow funciona como un solo gráfico ( Funciones 2.0 RFC ). Este mecanismo permite que TF2 obtenga todos los beneficios del modo gráfico:

  • Rendimiento: la función se puede optimizar (poda de nodos, fusión de kernel, etc.)
  • Portabilidad: La función se puede exportar / reimported ( SavedModel 2.0 RFC ), lo que le permite volver a utilizar y las funciones modulares TensorFlow acciones.
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)

Con el poder de intercalar libremente código de Python y TensorFlow, puede aprovechar la expresividad de Python. Sin embargo, TensorFlow portátil se ejecuta en contextos sin un intérprete de Python, como dispositivos móviles, C ++ y JavaScript. Para ayudar a evitar volver a escribir el código cuando se añade tf.function , utilice AutoGraph para convertir un subconjunto de construcciones de Python en sus equivalentes TensorFlow:

  • for / while -> tf.while_loop ( break y continue son compatibles)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph admite anidamientos arbitrarios de flujo de control, lo que hace posible implementar de manera concisa y eficiente muchos programas de aprendizaje automático complejos, como modelos de secuencia, aprendizaje por refuerzo, ciclos de entrenamiento personalizados y más.

Adaptación a los cambios de comportamiento de TF 2.x

Su migración a TF2 solo se completará una vez que haya migrado al conjunto completo de comportamientos de TF2. El conjunto completo de comportamientos puede ser activado o desactivado a través de tf.compat.v1.enable_v2_behaviors y tf.compat.v1.disable_v2_behaviors . Las secciones siguientes discuten en detalle cada cambio importante de comportamiento.

Usando tf.function s

Los mayores cambios en sus programas durante la migración es probable que lleguen a partir del modelo de programación cambio de paradigma fundamental de los gráficos y sesiones para la ejecución ansiosos y tf.function . Consulte las guías de migración TF2 para aprender más acerca de pasar de API que son incompatibles con la ejecución ansiosos y tf.function a las API que son compatibles con ellos.

A continuación se presentan algunos patrones de programas comunes no vinculados a ninguna API que pueden causar problemas cuando se cambia de tf.Graph s y tf.compat.v1.Session s para la ejecución ansiosos con tf.function s.

Patrón 1: la manipulación de objetos de Python y la creación de variables debe realizarse solo una vez que se ejecutan varias veces

En los programas TF1.x que se basan en gráficos y sesiones, la expectativa suele ser que toda la lógica de Python en su programa solo se ejecute una vez. Sin embargo, la ejecución y ansiosos tf.function es justo esperar que la lógica de Python se llevará a cabo al menos una vez, pero posiblemente más veces (ya sea varias veces con impaciencia posible, o varias veces a través de diferentes tf.function trazas). A veces, tf.function incluso trazar dos veces en la misma entrada, causando comportamientos inesperados (véase el Ejemplo 1 y 2). Consulte la tf.function guía para más detalles.

Ejemplo 1: creación de variable

Considere el siguiente ejemplo, donde la función crea una variable cuando se llama:

def f():
  v = tf.Variable(1.0)
  return v

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    res = f()
    sess.run(tf.compat.v1.global_variables_initializer())
    sess.run(res)

Sin embargo, envolviendo ingenuamente la función anterior que contiene la creación de variables con tf.function no está permitido. tf.function sólo es compatible con creaciones variables simples en la primera llamada . Para hacer cumplir esto, cuando tf.function detecta la creación de variables en la primera llamada, intentará rastrear nuevamente y generará un error si hay creación de variables en el segundo rastreo.

@tf.function
def f():
  print("trace") # This will print twice because the python body is run twice
  v = tf.Variable(1.0)
  return v

try:
  f()
except ValueError as e:
  print(e)

Una solución alternativa es almacenar en caché y reutilizar la variable después de que se crea en la primera llamada.

class Model(tf.Module):
  def __init__(self):
    self.v = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    return self.v

m = Model()
m()

Ejemplo 2: Los tensores llegada fuera del alcance debido a tf.function retracing

Como se ha demostrado en el Ejemplo 1, tf.function será volver cuando detecta la creación de variables en la primera llamada. Esto puede causar una confusión adicional, porque los dos trazados crearán dos gráficos. Cuando el segundo gráfico del rastreo intenta acceder a un tensor desde el gráfico generado durante el primer rastreo, Tensorflow generará un error quejándose de que el tensor está fuera de alcance. Para demostrar el escenario, el código siguiente crea un conjunto de datos en el primer tf.function llamada. Esto funcionaría como se esperaba.

class Model(tf.Module):
  def __init__(self):
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print once: only traced once
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return next(it)

m = Model()
m()

Sin embargo, si también intento de crear una variable en la primera tf.function llamada, el código generará un error quejándose de que el conjunto de datos está fuera de alcance. Esto se debe a que el conjunto de datos está en el primer gráfico, mientras que el segundo gráfico también intenta acceder a él.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
try:
  m()
except TypeError as e:
  print(e) # <tf.Tensor ...> is out of scope and cannot be used here.

La solución más straightfoward es asegurar que la creación variable y conjunto de datos son la creación tanto en el exterior de la tf.funciton llamada. Por ejemplo:

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    if self.v is None:
      self.v = tf.Variable(0)

  @tf.function
  def __call__(self):
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Sin embargo, a veces no es evitable para crear variables en tf.function (tales como variables de ranura en algunos Keras TF optimizadores ). Aún así, podemos simplemente mover el conjunto de datos fuera de la creación de la tf.function llamada. La razón por la que podemos depender de esto se debe a tf.function recibirá el conjunto de datos como una entrada implícita y ambos gráficos se puede acceder a él correctamente.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])

  @tf.function
  def __call__(self):
    if self.v is None:
      self.v = tf.Variable(0)
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Ejemplo 3: recreaciones inesperadas de objetos de Tensorflow debido al uso de dictados

tf.function tiene muy poco apoyo para los efectos secundarios tales como pitón añadiendo a una lista, o la comprobación / añadir a un diccionario. Hay más detalles en "Mejor rendimiento con tf.function" . En el siguiente ejemplo, el código usa diccionarios para almacenar en caché conjuntos de datos e iteradores. Para la misma clave, cada llamada al modelo devolverá el mismo iterador del conjunto de datos.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.compat.v1.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = self.datasets[key].make_initializable_iterator()
    return self.iterators[key]

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    m = Model()
    it = m('a')
    sess.run(it.initializer)
    for _ in range(3):
      print(sess.run(it.get_next())) # prints 1, 2, 3

Sin embargo, el patrón anterior no trabajo como se esperaba en tf.function . Durante el rastreo, tf.function ignorará el efecto secundario pitón de la adición a los diccionarios. En cambio, solo recuerda la creación de un nuevo conjunto de datos e iterador. Como resultado, cada llamada al modelo siempre devolverá un nuevo iterador. Este problema es difícil de notar a menos que los resultados numéricos o el rendimiento sean lo suficientemente significativos. Por lo tanto, se recomienda a los usuarios piensan sobre el código cuidadosamente antes de envolver tf.function ingenuamente en el código Python.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 1, 1

Podemos utilizar tf.init_scope para levantar el conjunto de datos y iterador fuera de creación de la gráfica, para lograr el comportamiento esperado:

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      # Lifts ops out of function-building graphs
      with tf.init_scope():
        self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
        self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 2, 3

La regla general es evitar depender de los efectos secundarios de Python en su lógica y usarlos solo para depurar sus rastros.

Ejemplo 4: manipular una lista global de Python

El siguiente código TF1.x usa una lista global de pérdidas que usa para mantener solo la lista de pérdidas generadas por el paso de entrenamiento actual. Tenga en cuenta que la lógica de Python que agrega pérdidas a la lista solo se llamará una vez, independientemente de la cantidad de pasos de entrenamiento para los que se ejecute la sesión.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

g = tf.Graph()
with g.as_default():
  ...
  # initialize all objects
  model = Model()
  optimizer = ...
  ...
  # train step
  model(...)
  total_loss = tf.reduce_sum(all_losses)
  optimizer.minimize(total_loss)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)  

Sin embargo, si esta lógica de Python se asigna ingenuamente a TF2 con una ejecución ansiosa, la lista global de pérdidas tendrá nuevos valores agregados en cada paso de entrenamiento. Esto significa que el código del paso de entrenamiento que anteriormente esperaba que la lista solo contuviera pérdidas del paso de entrenamiento actual ahora ve la lista de pérdidas de todos los pasos de entrenamiento ejecutados hasta el momento. Este es un cambio de comportamiento no intencionado, y la lista deberá borrarse al comienzo de cada paso o hacerse local al paso de entrenamiento.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

# initialize all objects
model = Model()
optimizer = ...

def train_step(...)
  ...
  model(...)
  total_loss = tf.reduce_sum(all_losses) # global list is never cleared,
  # Accidentally accumulates sum loss across all training steps
  optimizer.minimize(total_loss)
  ...

Patrón 2: Un tensor simbólico destinado a ser recalculado cada paso en TF1.x se almacena accidentalmente en caché con el valor inicial cuando se cambia a ansioso.

Este patrón por lo general hace que su código de silencio con el mal comportamiento cuando se ejecuta con entusiasmo fuera de tf.functions, pero plantea una InaccessibleTensorError si el valor inicial de almacenamiento en caché se produce en el interior de un tf.function . Sin embargo, tenga en cuenta que el fin de evitar el Patrón 1 anterior se estructurará a menudo sin darse cuenta su código de tal manera que este valor inicial de almacenamiento en caché va a pasar fuera de cualquier tf.function que sería capaz de generar un error. Por lo tanto, tenga especial cuidado si sabe que su programa puede ser susceptible a este patrón.

La solución general a este patrón es reestructurar el código o usar Python invocables si es necesario para asegurarse de que el valor se vuelva a calcular cada vez en lugar de almacenarlo en caché accidentalmente.

Ejemplo 1: tasa de aprendizaje / hiperparámetro / etc. horarios que dependen del paso global

En el siguiente fragmento de código, la expectativa es que cada vez que la sesión se ejecuta el más reciente global_step valor será leído y una nueva tasa de aprendizaje será computado.

g = tf.Graph()
with g.as_default():
  ...
  global_step = tf.Variable(0)
  learning_rate = 1.0 / global_step
  opt = tf.compat.v1.train.GradientDescentOptimizer(learning_rate)
  ...
  global_step.assign_add(1)
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Sin embargo, cuando intente cambiar a ansioso, tenga cuidado de terminar con la tasa de aprendizaje que solo se calcule una vez y luego se reutilice, en lugar de seguir el cronograma previsto:

global_step = tf.Variable(0)
learning_rate = 1.0 / global_step # Wrong! Only computed once!
opt = tf.keras.optimizers.SGD(learning_rate)

def train_step(...):
  ...
  opt.apply_gradients(...)
  global_step.assign_add(1)
  ...

Debido a que este ejemplo específico es un patrón común y optimizadores sólo debe ser inicializado una vez más que en cada etapa de formación, TF2 optimizadores de apoyo tf.keras.optimizers.schedules.LearningRateSchedule horarios o callables Python como argumentos para la tasa de aprendizaje y otros hiperparámetros.

Ejemplo 2: Las inicializaciones de números aleatorios simbólicos asignados como atributos de objeto y luego reutilizados a través del puntero se almacenan accidentalmente en caché cuando se cambia a ansioso

Considere el siguiente NoiseAdder módulo:

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution + input) * self.trainable_scale

Usarlo de la siguiente manera en TF1.x calculará un nuevo tensor de ruido aleatorio cada vez que se ejecute la sesión:

g = tf.Graph()
with g.as_default():
  ...
  # initialize all variable-containing objects
  noise_adder = NoiseAdder(shape, mean)
  ...
  # computation pass
  x_with_noise = noise_adder.add_noise(x)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Sin embargo, en TF2 inicializar el noise_adder al principio hará que el noise_distribution sólo se computará una vez y se congelan para todas las etapas de formación:

...
# initialize all variable-containing objects
noise_adder = NoiseAdder(shape, mean) # Freezes `self.noise_distribution`!
...
# computation pass
x_with_noise = noise_adder.add_noise(x)
...

Para solucionar este problema, refactor NoiseAdder para llamar tf.random.normal cada vez que se necesita un nuevo tensor de azar, en lugar de referirse al mismo objeto tensor de cada vez.

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = lambda: tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution() + input) * self.trainable_scale

Patrón 3: el código TF1.x se basa directamente en los tensores y los busca por su nombre

Es común que las pruebas de código TF1.x se basen en verificar qué tensores u operaciones están presentes en un gráfico. En algunos casos raros, el código de modelado también se basará en estas búsquedas por nombre.

Tensor nombres no se generan cuando se ejecuta con entusiasmo fuera de tf.function en absoluto, por lo que todos los usos de tf.Tensor.name deben ocurrir dentro de un tf.function . Tenga en cuenta los nombres generados reales son muy probable que difieren entre TF1.x y TF2, incluso dentro de la misma tf.function y API garantiza ni asegura la estabilidad de los nombres generados a través de las versiones TF.

Patrón 4: la sesión TF1.x ejecuta selectivamente solo una parte del gráfico generado

En TF1.x, puede construir un gráfico y luego elegir ejecutar solo selectivamente un subconjunto del mismo con una sesión eligiendo un conjunto de entradas y salidas que no requieran ejecutar todas las operaciones en el gráfico.

Por ejemplo, es posible que tenga tanto un generador y un discriminador en el interior de un único gráfico, y utilizar distintos tf.compat.v1.Session.run llamadas para alternar entre el discriminador único entrenamiento o solamente el entrenamiento del generador.

En TF2, debido a las dependencias de control automático en tf.function y ejecución ansioso, no hay poda selectiva de tf.function trazas. Un gráfico completo que contiene todas las actualizaciones variables serían ubicadas incluso si, por ejemplo, sólo la salida del discriminador o el generador se emite desde los tf.function .

Por lo tanto, lo que tendría que o bien el uso múltiple tf.function que contienen diferentes partes s del programa, o un argumento condicional al tf.function que se ramifican en el fin de ejecutar sólo las cosas que realmente quieren tener plazo.

Eliminación de colecciones

Cuando está habilitada la ejecución ansiosos, relacionados con la colección gráfica compat.v1 APIs (incluyendo aquellos que leen o escriben a las colecciones bajo el capó, tales como tf.compat.v1.trainable_variables ) ya no están disponibles. Algunos pueden aumentar ValueError s, mientras que otros pueden regresar en silencio listas vacías.

El uso más estándar de colecciones en TF1.x es mantener inicializadores, la etapa global, Pesas, pérdidas de regularización, pérdidas de producto modelo, y actualiza las variables que necesita que se ejecute tal como de BatchNormalization capas.

Para manejar cada uno de estos usos estándar:

  1. Inicializadores: ignorar. No se requiere la inicialización manual de variables con la ejecución ansiosa habilitada.
  2. Paso Global - Consulte la documentación de tf.compat.v1.train.get_or_create_global_step para la migración instrucciones.
  3. Pesos - Mapa sus modelos a tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s, siguiendo las instrucciones de la guía del modelo de asignación y luego usar sus respectivos mecanismos de seguimiento de peso, tales como tf.module.trainable_variables .
  4. Las pérdidas de regularización - Mapa sus modelos a tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s, siguiendo las instrucciones de la guía del modelo de asignación y luego usar tf.keras.losses . Alternativamente, también puede realizar un seguimiento manual de sus pérdidas de regularización.
  5. Pérdidas de producto Modelo - Uso tf.keras.Model mecanismos de gestión de la pérdida o por separado el seguimiento de sus pérdidas sin el uso de colecciones.
  6. Actualizaciones de peso: ignore esta colección. Ejecución y ansiosos tf.function (con autógrafo y auto-control de las dependencias) significa todas las actualizaciones variables conseguirán que se ejecute automáticamente. Por lo tanto, no tendrá que ejecutar explícitamente todas las actualizaciones de peso al final, pero tenga en cuenta que esto significa que las actualizaciones de peso pueden ocurrir en un momento diferente al que ocurrieron en su código TF1.x, dependiendo de cómo estaba usando las dependencias de control.
  7. Resúmenes - Consulte la guía API Resumen de migración .

El uso de colecciones más complejas (como el uso de colecciones personalizadas) puede requerir que refactorice su código para mantener sus propias tiendas globales o para que no dependa en absoluto de las tiendas globales.

ResourceVariables en lugar de ReferenceVariables

ResourceVariables tienen mayores garantías de consistencia de lectura-escritura que ReferenceVariables . Esto conduce a una semántica más predecible y más fácil de razonar acerca de si observará o no el resultado de una escritura anterior al usar sus variables. Es muy poco probable que este cambio provoque que el código existente genere errores o se rompa silenciosamente.

Sin embargo, es posible, aunque poco probable que estas garantías de consistencia más fuertes pueden aumentar el uso de la memoria de su programa específico. Por favor, presentar un problema si encuentra que este sea el caso. Además, si tiene pruebas unitarias que se basan en comparaciones de cadenas exactas con los nombres de los operadores en un gráfico correspondiente a las lecturas de variables, tenga en cuenta que la activación de las variables de recursos puede cambiar ligeramente el nombre de estos operadores.

Para aislar el impacto de este cambio de comportamiento en su código, si la ejecución se desactiva ansiosos puede utilizar tf.compat.v1.disable_resource_variables() y tf.compat.v1.enable_resource_variables() Para desactivar globalmente o habilitar este cambio de comportamiento. ResourceVariables siempre se utilizará si está habilitada la ejecución ansiosos.

Control de flujo v2

En TF1.x, de control de operaciones de flujo tales como tf.cond y tf.while_loop línea de bajo nivel ops tales como Switch , Merge etc. TF2 proporciona una mejor ops de control de flujo funcionales que se implementan con separadas tf.function huellas para cada rama y el apoyo diferenciación de orden superior.

Para aislar el impacto de este cambio de comportamiento en su código, si la ejecución se desactiva ansiosos puede utilizar tf.compat.v1.disable_control_flow_v2() y tf.compat.v1.enable_control_flow_v2() para deshabilitar globalmente o habilitar este cambio de comportamiento. Sin embargo, solo puede deshabilitar el flujo de control v2 si la ejecución ansiosa también está deshabilitada. Si está habilitado, siempre se utilizará el flujo de control v2.

Este cambio de comportamiento puede cambiar drásticamente la estructura de los programas TF generados que utilizan el flujo de control, ya que contendrán varias funciones anidadas en lugar de un gráfico plano. Por lo tanto, cualquier código que dependa en gran medida de la semántica exacta de los rastros producidos puede requerir alguna modificación. Esto incluye:

  • Código que depende de los nombres de operadores y tensores
  • Código que hace referencia a tensores creados dentro de una rama de flujo de control de TensorFlow desde fuera de esa rama. Esto es probable que produzca un InaccessibleTensorError

Este cambio de comportamiento se pretende que sea un rendimiento neutro a positivo, pero si llegas a tener un problema que lleva a cabo el control de flujo v2 peor para ti que el control de flujo, por favor TF1.x presentar una cuestión con los pasos de reproducción.

Cambios en el comportamiento de la API de TensorShape

El TensorShape clase se simplificó para sostener int s, en lugar de tf.compat.v1.Dimension objetos. Así que no hay necesidad de llamar a .value para obtener una int .

Persona tf.compat.v1.Dimension objetos siguen siendo accesibles desde tf.TensorShape.dims .

Para aislar el impacto de este cambio de comportamiento en su código, puede utilizar tf.compat.v1.disable_v2_tensorshape() y tf.compat.v1.enable_v2_tensorshape() para deshabilitar globalmente o habilitar este cambio de comportamiento.

A continuación se muestran las diferencias entre TF1.xy TF2.

import tensorflow as tf
# Create a shape and choose an index
i = 0
shape = tf.TensorShape([16, None, 256])
shape
TensorShape([16, None, 256])

Si tenía esto en TF1.x:

value = shape[i].value

Luego haz esto en TF2:

value = shape[i]
value
16

Si tenía esto en TF1.x:

for dim in shape:
    value = dim.value
    print(value)

Luego, haz esto en TF2:

for value in shape:
  print(value)
16
None
256

Si tenía esto en TF1.x (o utilizó cualquier otro método de dimensión):

dim = shape[i]
dim.assert_is_compatible_with(other_dim)

Luego haz esto en TF2:

other_dim = 16
Dimension = tf.compat.v1.Dimension

if shape.rank is None:
  dim = Dimension(None)
else:
  dim = shape.dims[i]
dim.is_compatible_with(other_dim) # or any other dimension method
True
shape = tf.TensorShape(None)

if shape:
  dim = shape.dims[i]
  dim.is_compatible_with(other_dim) # or any other dimension method

El valor booleano de un tf.TensorShape es True si se conoce el rango, False de lo contrario.

print(bool(tf.TensorShape([])))      # Scalar
print(bool(tf.TensorShape([0])))     # 0-length vector
print(bool(tf.TensorShape([1])))     # 1-length vector
print(bool(tf.TensorShape([None])))  # Unknown-length vector
print(bool(tf.TensorShape([1, 10, 100])))       # 3D tensor
print(bool(tf.TensorShape([None, None, None]))) # 3D tensor with no known dimensions
print()
print(bool(tf.TensorShape(None)))  # A tensor with unknown rank.
True
True
True
True
True
True

False

Posibles errores debido a los cambios de TensorShape

Es poco probable que los cambios de comportamiento de TensorShape rompan silenciosamente su código. Sin embargo, puede ver el código de forma relacionada comienzan a aumentar AttributeError s como int s y None s no tiene los mismos atributos que tf.compat.v1.Dimension s do. A continuación se presentan algunos ejemplos de estos AttributeError s:

try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  value = shape[0].value
except AttributeError as e:
  # 'int' object has no attribute 'value'
  print(e)
'int' object has no attribute 'value'
try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  dim = shape[1]
  other_dim = shape[2]
  dim.assert_is_compatible_with(other_dim)
except AttributeError as e:
  # 'NoneType' object has no attribute 'assert_is_compatible_with'
  print(e)
'NoneType' object has no attribute 'assert_is_compatible_with'

Igualdad tensorial por valor

El binario == y != Operadores sobre las variables y tensores se cambiaron para comparar por valor de TF2 en lugar de comparar por referencia de objeto como en TF1.x. Además, los tensores y las variables ya no se pueden usar directamente con hash ni se pueden usar en conjuntos o claves de dictado, porque puede que no sea posible hacerlo por valor. En su lugar, se exponen una .ref() método que se puede utilizar para obtener una referencia hashable al tensor o variable.

Para aislar el impacto de este cambio de comportamiento, puede utilizar tf.compat.v1.disable_tensor_equality() y tf.compat.v1.enable_tensor_equality() para deshabilitar globalmente o habilitar este cambio de comportamiento.

Por ejemplo, en TF1.x, dos variables con el mismo valor volverán falsa cuando se utiliza el == operador:

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
False

Mientras que en TF2 con tensor de cheques igualdad habilitadas, x == y volverá True .

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
<tf.Tensor: shape=(), dtype=bool, numpy=True>

Así, en TF2, si es necesario comparar por objeto hacer referencia seguro para su uso is y is not

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x is y
False

Hashing de tensores y variables

Con comportamientos TF1.x que solía ser capaz de añadir directamente las variables y los tensores de las estructuras de datos que requieren de hash, como set y dict llaves.

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
set([x, tf.constant(2.0)])
{<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.0>}

Sin embargo, en TF2 con activar tensor de la igualdad, los tensores y las variables se hacen unhashable debido a la == y != Semántica del operador cambiantes de los controles de igualdad de valor.

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

try:
  set([x, tf.constant(2.0)])
except TypeError as e:
  # TypeError: Variable is unhashable. Instead, use tensor.ref() as the key.
  print(e)
Variable is unhashable. Instead, use tensor.ref() as the key.

Así, en TF2 si necesita utilizar tensores o variables objetos como llaves o set contenido, puede utilizar tensor.ref() para obtener una referencia hashable que puede ser utilizado como una clave:

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

tensor_set = set([x.ref(), tf.constant(2.0).ref()])
assert x.ref() in tensor_set

tensor_set
{<Reference wrapping <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>>,
 <Reference wrapping <tf.Tensor: shape=(), dtype=float32, numpy=2.0>>}

Si es necesario, también se puede obtener el tensor o variable de la referencia mediante el uso de reference.deref() :

referenced_var = x.ref().deref()
assert referenced_var is x
referenced_var
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>

Recursos y lectura adicional