Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/guide/autodiff.ipynb
25115 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

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

import numpy as np import matplotlib.pyplot as plt import tensorflow as tf

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:

x = tf.Variable(3.0) with tf.GradientTape() as tape: y = x**2

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):

# dy = 2x * dx dy_dx = tape.gradient(y, x) dy_dx.numpy()

El ejemplo anterior usa escalares, pero tf.GradientTape funciona igual de fáci en cualquier tensor:

w = tf.Variable(tf.random.normal((3, 2)), name='w') b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b') x = [[1., 2., 3.]] with tf.GradientTape(persistent=True) as tape: y = x @ w + b loss = tf.reduce_mean(y**2)

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).

[dl_dw, dl_db] = tape.gradient(loss, [w, b])

El gradiente con respecto a cada origen tiene la forma del origen:

print(w.shape) print(dl_dw.shape)

Aquí tiene el cálculo del gradiente de nuevo, pero esta vez se pasa un diccionario de variables:

my_vars = { 'w': w, 'b': b } grad = tape.gradient(loss, my_vars) grad['b']

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:

layer = tf.keras.layers.Dense(2, activation='relu') x = tf.constant([[1., 2., 3.]]) with tf.GradientTape() as tape: # Forward pass y = layer(x) loss = tf.reduce_mean(y**2) # Calculate gradients with respect to every trainable variable grad = tape.gradient(loss, layer.trainable_variables)
for var, g in zip(layer.trainable_variables, grad): print(f'{var.name}, shape: {g.shape}')

Controlar lo que observa la cinta

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:

# A trainable variable x0 = tf.Variable(3.0, name='x0') # Not trainable x1 = tf.Variable(3.0, name='x1', trainable=False) # Not a Variable: A variable + tensor returns a tensor. x2 = tf.Variable(2.0, name='x2') + 1.0 # Not a variable x3 = tf.constant(3.0, name='x3') with tf.GradientTape() as tape: y = (x0**2) + (x1**2) + (x2**2) grad = tape.gradient(y, [x0, x1, x2, x3]) for g in grad: print(g)

Puede enumerar las variables que monitorea la cinta con el método GradientTape.watched_variables:

[var.name for var in tape.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):

x = tf.constant(3.0) with tf.GradientTape() as tape: tape.watch(x) y = x**2 # dy = 2x * dx dy_dx = tape.gradient(y, x) print(dy_dx.numpy())

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:

x0 = tf.Variable(0.0) x1 = tf.Variable(10.0) with tf.GradientTape(watch_accessed_variables=False) as tape: tape.watch(x1) y0 = tf.math.sin(x0) y1 = tf.nn.softplus(x1) y = y0 + y1 ys = tf.reduce_sum(y)

Como no se llamó el GradientTape.watch en x0, no se calcula ningún gradiente con respecto a éste:

# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1) grad = tape.gradient(ys, {'x0': x0, 'x1': x1}) print('dy/dx0:', grad['x0']) print('dy/dx1:', grad['x1'].numpy())

Resultados intermedios

También puede solicitar gradientes de la salida con respecto a valores intermedios calculados dentro del contexto tf.GradientTape.

x = tf.constant(3.0) with tf.GradientTape() as tape: tape.watch(x) y = x * x z = y * y # Use the tape to compute the gradient of z with respect to the # intermediate value y. # dz_dy = 2 * y and y = x ** 2 = 9 print(tape.gradient(z, y).numpy())

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:

x = tf.constant([1, 3.0]) with tf.GradientTape(persistent=True) as tape: tape.watch(x) y = x * x z = y * y print(tape.gradient(z, x).numpy()) # [4.0, 108.0] (4 * x**3 at x = [1.0, 3.0]) print(tape.gradient(y, x).numpy()) # [2.0, 6.0] (2 * x at x = [1.0, 3.0])
del tape # Drop the reference to the tape

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 usa persistent=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.

x = tf.Variable(2.0) with tf.GradientTape(persistent=True) as tape: y0 = x**2 y1 = 1 / x print(tape.gradient(y0, x).numpy()) print(tape.gradient(y1, x).numpy())

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

x = tf.Variable(2.0) with tf.GradientTape() as tape: y0 = x**2 y1 = 1 / x print(tape.gradient({'y0': y0, 'y1': y1}, x).numpy())

De manera similar, si los objetivos no son escalares, se calcula el gradiente de la suma:

x = tf.Variable(2.) with tf.GradientTape() as tape: y = x * [3., 4.] print(tape.gradient(y, x).numpy())

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:

x = tf.linspace(-10.0, 10.0, 200+1) with tf.GradientTape() as tape: tape.watch(x) y = tf.nn.sigmoid(x) dy_dx = tape.gradient(y, x)
plt.plot(x, y, label='y') plt.plot(x, dy_dx, label='dy/dx') plt.legend() _ = plt.xlabel('x')

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ó:

x = tf.constant(1.0) v0 = tf.Variable(2.0) v1 = tf.Variable(2.0) with tf.GradientTape(persistent=True) as tape: tape.watch(x) if x > 0.0: result = v0 else: result = v1**2 dv0, dv1 = tape.gradient(result, [v0, v1]) print(dv0) print(dv1)

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.

dx = tape.gradient(result, x) print(dx)

Casos en los que gradient devuelve None

Cuando un objetivo no está conectado a un origen, gradient devolverá None.

x = tf.Variable(2.) y = tf.Variable(3.) with tf.GradientTape() as tape: z = y * y print(tape.gradient(z, x))

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:

x = tf.Variable(2.0) for epoch in range(2): with tf.GradientTape() as tape: y = x+1 print(type(x).__name__, ":", tape.gradient(y, x)) x = x + 1 # This should be `x.assign_add(1)`

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:

x = tf.Variable([[1.0, 2.0], [3.0, 4.0]], dtype=tf.float32) with tf.GradientTape() as tape: x2 = x**2 # This step is calculated with NumPy y = np.mean(x2, axis=0) # Like most ops, reduce_mean will cast the NumPy array to a constant tensor # using `tf.convert_to_tensor`. y = tf.reduce_mean(y, axis=0) print(tape.gradient(y, x))

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.

x = tf.constant(10) with tf.GradientTape() as g: g.watch(x) y = x * x print(g.gradient(y, x))

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:

x0 = tf.Variable(3.0) x1 = tf.Variable(0.0) with tf.GradientTape() as tape: # Update x1 = x1 + x0. x1.assign_add(x0) # The tape starts recording from x1. y = x1**2 # y = (x1 + x0)**2 # This doesn't work. print(tape.gradient(y, x0)) #dy/dx0 = 2*(x1 + x0)

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:

image = tf.Variable([[[0.5, 0.0, 0.0]]]) delta = tf.Variable(0.1) with tf.GradientTape() as tape: new_image = tf.image.adjust_contrast(image, delta) try: print(tape.gradient(new_image, [image, delta])) assert False # This should not happen. except LookupError as e: print(f'{type(e).__name__}: {e}')

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:

x = tf.Variable([2., 2.]) y = tf.Variable(3.) with tf.GradientTape() as tape: z = y**2 print(tape.gradient(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO))