Path: blob/master/site/es-419/guide/autodiff.ipynb
25115 views
Copyright 2020 The TensorFlow Authors.
Introducción a los gradientes y la diferenciación automática
Diferenciación automática y gradientes
La diferenciación automática es útil para implementar algoritmos de aprendizaje automático, como la retropropagación, para entrenar redes neuronales.
En esta guía, exploraremos formas de calcular gradientes con TensorFlow, especialmente en ejecución eager.
Preparación
Calcular gradientes
Para diferenciar automáticamente, TensorFlow necesita recordar qué operaciones ocurren y en qué orden durante el pase hacia adelante. Luego, durante el pase hacia atrás, TensorFlow recorre esta lista de operaciones en orden inverso para calcular los gradientes.
Cintas de gradientes
TensorFlow proporciona la API tf.GradientTape
para la diferenciación automática; es decir, calcular el gradiente de un cálculo con respecto a algunas entradas, generalmente las tf.Variable
. TensorFlow "registra" las operaciones relevantes que se ejecutan dentro del contexto de tf.GradientTape
en una "cinta". Luego, TensorFlow usa esa cinta para calcular los gradientes de un cálculo "grabado" con la diferenciación de modo inverso.
Aquí tiene un ejemplo simple:
Una vez que se hayan registrado algunas operaciones, use GradientTape.gradient(target, sources)
para calcular el gradiente de algún objetivo (a menudo una pérdida) en relación con algún origen (a menudo las variables del modelo):
El ejemplo anterior usa escalares, pero tf.GradientTape
funciona igual de fáci en cualquier tensor:
Para obtener el gradiente de loss
con respecto a ambas variables, se pueden pasar las dos como origen al método gradient
. La cinta es flexible en cuanto a cómo se pasan los orígenes y aceptará cualquier combinación anidada de listas o diccionarios y devolverá el gradiente estructurado de la misma manera (consulte tf.nest
).
El gradiente con respecto a cada origen tiene la forma del origen:
Aquí tiene el cálculo del gradiente de nuevo, pero esta vez se pasa un diccionario de variables:
Gradientes con respecto a un modelo
Se suelen recopilar tf.Variables
en un tf.Module
o en una de sus subclases (layers.Layer
, keras.Model
) para guardar puntos de verificación y exportar.
En la mayoría de los casos, deberá calcular gradientes con respecto a las variables entrenables de un modelo. Dado que todas las subclases de tf.Module
agregan sus variables en la propiedad Module.trainable_variables
, se pueden calcular estos gradientes en unas pocas líneas de código:
El comportamiento predeterminado es registrar todas las operaciones después de acceder a una tf.Variable
entrenable. Se hace así por los siguientes motivos:
La cinta necesita saber qué operaciones grabar en el pase hacia adelante para calcular los gradientes en el pase hacia atrás.
La cinta contiene referencias a salidas intermedias, por lo que no se recomienda grabar operaciones innecesarias.
El caso de uso más común implica calcular el gradiente de una pérdida con respecto a todas las variables entrenables de un modelo.
Por ejemplo, lo siguiente no puede calcular un gradiente porque no se "monitorea" el tf.Tensor
de forma predeterminada, y no se puede entrenar tf.Variable
:
Puede enumerar las variables que monitorea la cinta con el método GradientTape.watched_variables
:
tf.GradientTape
proporciona enlaces que le dan control al usuario para decidir qué se observa y qué no.
Para registrar gradientes con respecto a un tf.Tensor
, debe llamar GradientTape.watch(x)
:
Por el contrario, para deshabilitar el comportamiento predeterminado de observar todos los tf.Variables
, configure watch_accessed_variables=False
cuando se crea la cinta del gradiente. Este cálculo usa dos variables, pero solo conecta el gradiente de una de las variables:
Como no se llamó el GradientTape.watch
en x0
, no se calcula ningún gradiente con respecto a éste:
Resultados intermedios
También puede solicitar gradientes de la salida con respecto a valores intermedios calculados dentro del contexto tf.GradientTape
.
De forma predeterminada, los recursos que retiene GradientTape
se liberan tan pronto como se llama al método GradientTape.gradient
. Para calcular varios gradientes durante el mismo cálculo, cree una cinta de gradiente con persistent=True
. Esto permite varias llamadas al método gradient
a medida que se liberan recursos cuando el objeto de la cinta se recolecta como elemento no utilizado. Por ejemplo:
Notas sobre el rendimiento
Hay una pequeña sobrecarga asociada con las operaciones que ocurren dentro de un contexto de cinta de gradiente. Para una mayor ejecución eager, no le costará tanto, pero igual se debería usar un contexto de cinta en las áreas, solo donde sea necesario.
Las cintas de gradiente usan la memoria para almacenar resultados intermedios, incluidas las entradas y salidas, para usarlos durante el pase hacia atrás.
Para mayor eficiencia, algunas operaciones (como
ReLU
) no necesitan guardar sus resultados intermedios y se eliminan durante el pase hacia adelante. Sin embargo, si usapersistent=True
en su cinta, no se descarta nada y su uso máximo de memoria será mayor.
Gradientes de objetivos no escalares
Un gradiente es fundamentalmente una operación en un escalar.
Por lo tanto, si se solicita el gradiente de varios objetivos, el resultado para cada origen será:
El gradiente de la suma de los objetivos, o un equivalente
La suma de los gradientes de cada objetivo
De manera similar, si los objetivos no son escalares, se calcula el gradiente de la suma:
Esto facilita que se tome el gradiente de la suma de una colección de pérdidas, o el gradiente de la suma de un cálculo de pérdida por elementos.
Si necesita un gradiente separado para cada elemento, consulte Jacobianos.
En algunos casos, se puede omitis el jacobiano. Para un cálculo por elementos, el gradiente de la suma resulta en la derivada de cada elemento con respecto a su elemento de entrada, ya que cada elemento es independiente:
Flujo de control
Debido a que una cinta de gradiente registra las operaciones a medida que se ejecutan, el flujo de control de Python se manipula de forma natural (por ejemplo, declaraciones if
y while
).
Aquí se usa una variable diferente en cada rama de un if
. El gradiente solo se conecta a la variable que se usó:
Solo recuerde que las declaraciones de control no son diferenciables en sí mismas, por lo que son invisibles para los optimizadores que se basan en gradientes.
Según el valor de x
en el ejemplo anterior, la cinta graba result = v0
o result = v1**2
. El gradiente con respecto a x
siempre es None
.
Casos en los que gradient
devuelve None
Cuando un objetivo no está conectado a un origen, gradient
devolverá None
.
Claramente, aquí z
no está conectado a x
, pero hay varias formas menos obvias en las que un gradiente no está conectado.
1. Se reemplazó una variable con un tensor
En la sección sobre "controlar lo que observa la cinta", vimos que la cinta observa automáticamente un tf.Variable
pero no un tf.Tensor
.
Un error común es reemplazar un tf.Variable
con un tf.Tensor
sin querer, en lugar de usar Variable.assign
para actualizar tf.Variable
. A continuación, tiene un ejemplo:
2. Se hicieron cálculos fuera de TensorFlow
La cinta no puede grabar la ruta del gradiente si el cálculo sucede fuera de TensorFlow. Por ejemplo:
3. Se tomaron gradientes a través de un número entero o una cadena de texto
Los números enteros y las cadenas de texto no son diferenciables. Si una ruta de cálculo usa estos tipos de datos, no habrá gradiente.
Nadie espera que las cadenas de texto sean diferenciables, pero es fácil crear una constante o variable int
por accidente si no se especifica el dtype
.
TensorFlow no convierte de forma automática de un tipo a otro, por lo que, en la práctica, a menudo se obtendrá un error de tipo en lugar de un gradiente faltante.
4. Se tomaron gradientes a través de un objeto con estado
El estado detiene los gradientes. Cuando se lee un objeto con estado, la cinta solo puede observar el estado actual, no el historial de cómo se logró.
Un tf.Tensor
es inmutable. No se puede cambiar un tensor después de haber sido creado. Tiene un valor, pero no un estado. Todas las operaciones analizadas hasta ahora tampoco tienen estado: la salida de un tf.matmul
solo depende de sus entradas.
Una tf.Variable
tiene un estado interno: su valor. Cuando se usa la variable, se lee el estado. Es normal calcular un gradiente con respecto a una variable, pero el estado de la variable impide que los cálculos del gradiente vayan más atrás. Por ejemplo:
De manera similar, los elementos de iteración tf.data.Dataset
y tf.queue
tienen estado y detendrán a todos los gradientes en los tensores que los atraviesen.
No se registró un gradiente
Algunas tf.Operation
están registradas como no diferenciables y devolverán None
. Otras no tienen ningún gradiente registrado.
La página tf.raw_ops
muestra qué operaciones de bajo nivel tienen gradientes registrados.
Si intenta tomar un gradiente a través de una operación flotante que no tiene ningún gradiente registrado, la cinta arrojará un error en lugar de devolver None
de forma silenciosa. Así sabremos que algo salió mal.
Por ejemplo, la función tf.image.adjust_contrast
empaqueta raw_ops.AdjustContrastv2
, que podría tener un gradiente pero el gradiente no se ha implementado:
Si necesita diferenciar a través de esta operación, deberá implementar el gradiente y registrarlo (con tf.RegisterGradient
) o deberá volver a implementar la función con otras operaciones.
Ceros en lugar de None
En algunos casos sería conveniente obtener el número 0 en lugar de None
para gradientes no conectados. Se puede elegir qué devolver cuando se tiene gradientes desconectados con el argumento unconnected_gradients
: