Path: blob/master/site/es-419/guide/advanced_autodiff.ipynb
25115 views
Copyright 2020 The TensorFlow Authors.
Diferenciación automática avanzada
La guía Introducción a los gradientes y la diferenciación automática incluye todo lo necesario para calcular gradientes en TensorFlow. Esta guía se enfoca en características más profundas y menos comunes de la API tf.GradientTape
.
Preparación
Controlar el registro de gradiente
En la guía de diferenciación automática vio cómo controlar qué variables y tensores son observados por la cinta mientras se construye el cálculo del gradiente.
La cinta también dispone de métodos para manipular la grabación.
Detener la grabación
Si desea detener la grabación de gradientes, puede usar tf.GradientTape.stop_recording
para suspender temporalmente la grabación.
Esto puede ser útil para reducir la sobrecarga si no desea diferenciar una operación complicada a mitad de su modelo. Puede incluir calcular una métrica o un resultado intermedio:
Reiniciar/empezar a grabar desde cero
Si desea empezar de cero por completo, use tf.GradientTape.reset
. Si simplemente sale del bloque de cinta de gradiente y vuelve a empezar, suele ser más fácil de leer, pero puede usar el método reset
cuando salir del bloque de cinta sea difícil o imposible.
Detener el flujo de gradiente con precisión
A diferencia de los controles globales de cinta anteriores, la función tf.stop_gradient
es mucho más precisa. Puede usarse para detener el flujo de gradientes a lo largo de una ruta concreta, sin necesidad de acceder a la propia cinta:
Gradientes personalizados
En algunos casos, es posible que desee controlar exactamente cómo se calculan los gradientes en lugar de usar el valor por defecto. Estas situaciones incluyen:
No hay un gradiente definido para la nueva op que está escribiendo.
Los cálculos por defecto son numéricamente inestables.
Desea almacenar en caché un cálculo costoso de la pasada hacia delante.
Quiere modificar un valor (por ejemplo, usando
tf.clip_by_value
otf.math.round
) sin modificar el gradiente.
En el primer caso, para escribir una nueva op puede usar tf.RegisterGradient
para configurar la suya propia (consulte los documentos de la API para más detalles y tenga en cuenta que el registro de gradientes es global, así que modifíquelo con precaución).
Para los tres últimos casos, puede usar tf.custom_gradient
.
Aquí tiene un ejemplo que aplica tf.clip_by_norm
al gradiente intermedio:
Consulte la documentación de la API del decorador tf.custom_gradient
para obtener más detalles.
Gradientes personalizados en SavedModel
Nota: Esta función está disponible a partir de TensorFlow 2.6.
Los gradientes personalizados pueden guardarse en SavedModel usando la opción tf.saved_model.SaveOptions(experimental_custom_gradients=True)
.
Para guardarse en el SavedModel, la función de gradiente debe ser trazable (para saber más, consulte la guía Mejor rendimiento con tf.function).
Una nota sobre el ejemplo anterior: Si intenta reemplazar el código anterior por tf.saved_model.SaveOptions(experimental_custom_gradients=False)
, el gradiente seguirá produciendo el mismo resultado al cargar. El motivo es que el registro de gradientes aún contiene el gradiente personalizado usado en la función call_custom_op
. Sin embargo, si reinicia el runtime después de guardar sin gradientes personalizados, al ejecutar el modelo cargado bajo la función tf.GradientTape
se producirá el error: LookupError: No gradient defined for operation 'IdentityN' (op type: IdentityN)
.
Múltiples cintas
Las cintas múltiples interactúan a la perfección.
Por ejemplo, aquí cada cinta vigila un conjunto diferente de tensores:
Gradientes de orden alto
Las operaciones dentro del administrador de contexto tf.GradientTape
se registran para la diferenciación automática. Si se calculan gradientes en ese contexto, también se registra el cálculo del gradiente. Como resultado, la misma API funciona también para gradientes de orden alto.
Por ejemplo:
Aunque eso le da la segunda derivada de una función escalar, este patrón no se generaliza para producir una matriz hessiana, ya que tf.GradientTape.gradient
sólo calcula el gradiente de un escalar. Para construir una matriz hessiana, vaya al ejemplo hessiano en la sección jacobiana.
"Llamadas anidadas a tf.GradientTape.gradient
" es un buen patrón cuando se calcula un escalar a partir de un gradiente, y luego el escalar resultante actúa como fuente para un segundo cálculo de gradiente, como en el siguiente ejemplo.
Ejemplo: Regularización del gradiente de entrada
Muchos modelos son susceptibles a los "ejemplos adversariales". Esta colección de técnicas modifica la entrada del modelo para confundir la salida del mismo. La implementación más sencilla (como el Ejemplo adversarial que utiliza el ataque del Método de Gradiente Rápido por Signos) toma un solo paso a lo largo del gradiente de la salida con respecto a la entrada; el "gradiente de entrada".
Una técnica para aumentar la robustez ante ejemplos adversariales es la regularización del gradiente de entrada (Finlay & Oberman, 2019), que intenta minimizar la magnitud del gradiente de entrada. Si el gradiente de entrada es pequeño, entonces el cambio en la salida también debería serlo.
Aquí tiene una implementación ingenua de la regularización por gradiente de entrada. La implementación es:
Calcule el gradiente de la salida con respecto a la entrada usando una cinta interior.
Calcule la magnitud de ese gradiente de entrada.
Calcule el gradiente de esa magnitud con respecto al modelo.
Jacobianos
Todos los ejemplos anteriores tomaban los gradientes de un objetivo escalar con respecto a algún(os) tensor(es) fuente.
La matriz Jacobiana representa los gradientes de una función con valor vectorial. Cada fila contiene el gradiente de uno de los elementos del vector.
El método tf.GradientTape.jacobian
permite calcular eficazmente una matriz jacobiana.
Tenga en cuenta que:
Como
gradient
: El argumentosources
puede ser un tensor o un contenedor de tensores.A diferencia de
gradient
: El tensortarget
debe ser un único tensor.
Fuente escalar
Como primer ejemplo, he aquí la jacobiana de un vector-objetivo con respecto a un escalar-fuente.
Cuando se toma el jacobiano con respecto a un escalar el resultado tiene la forma del objetivo, y da el gradiente de cada elemento con respecto al origen:
Fuente de tensor
Tanto si la entrada es escalar como tensorial, tf.GradientTape.jacobian
calcula eficazmente el gradiente de cada elemento de la fuente con respecto a cada elemento del objetivo u objetivos.
Por ejemplo, la salida de esta capa tiene una forma de (10, 7)
:
Y la forma del kernel de la capa es (5, 10)
:
La forma del jacobiano de la salida con respecto al kernel son esas dos formas concatenadas:
Si suma sobre las dimensiones del objetivo, le queda el gradiente de la suma que habría calculado tf.GradientTape.gradient
:
Aunque tf.GradientTape
no da un método explícito para construir una matriz hessiana es posible construir una usando el método tf.GradientTape.jacobian
.
Nota: La matriz hessiana contiene N**2
parámetros. Por este y otros motivos no es práctico para la mayoría de los modelos. Este ejemplo se incluye más como demostración de cómo usar el método tf.GradientTape.jacobian
, y no para apoyar que la optimización directa se base en la hessiana. Un producto vectorial hessiano puede calcularse eficientemente con cintas anidadas, y es un enfoque mucho más eficiente para la optimización de segundo orden.
Para usar esta hessiana para un paso del método de Newton, primero habría que aplanar sus ejes en una matriz, y aplanar el gradiente en un vector:
La matriz hessiana debe ser simétrica:
A continuación se muestra el paso de actualización del método de Newton:
Aunque esto es relativamente sencillo para una sola tf.Variable
, aplicarlo a un modelo no trivial requeriría una cuidadosa concatenación y corte para producir un hessiano completo a través de múltiples variables.
Jacobiano de lote
En algunos casos, se desea tomar la jacobiana de cada uno de un apilamiento de objetivos con respecto a un apilamiento de fuentes, donde las jacobianas de cada par objetivo-origen son independientes.
Por ejemplo, aquí la entrada x
tiene forma (batch, ins)
y la salida y
tiene forma (batch, outs)
:
La jacobiana completa de y
con respecto a x
tiene una forma de (batch, ins, batch, outs)
, aunque sólo quiera (batch, ins, outs)
:
Si los gradientes de cada artículo de la pila son independientes, entonces cada (batch, batch)
corte de este tensor es una matriz diagonal:
Para obtener el resultado deseado, puede sumar sobre la dimensión duplicada batch
, o bien seleccionar las diagonales utilizando tf.einsum
:
Sería mucho más eficiente hacer el cálculo sin la dimensión extra en primer lugar. El método tf.GradientTape.batch_jacobian
hace exactamente eso:
Precaución: tf.GradientTape.batch_jacobian
sólo comprueba que la primera dimensión del origen y del objetivo coincidan. No comprueba que los gradientes sean realmente independientes. Depende de usted asegurarse de que sólo usa batch_jacobian
cuando tiene sentido. Por ejemplo, añadir un tf.keras.layers.BatchNormalization
destruye la independencia, ya que normaliza a través de la dimensión batch
:
En este caso, batch_jacobian
aún se ejecuta y devuelve algo con la forma esperada, pero su contenido tiene un significado poco claro: