El modo forward en la diferenciación automática, como su nombre lo dice, se enfoca en encontrar la derivada de una función representada por un grafo computacional haciendo cómputos en avance. En este sentido, el grafo computacional alamcena una traza tangente, donde mientras se computan los valores de la función, se obtienen también los valores de las diferentes derivadas. Posteriormente, las derivadas de nodos superiores en el grafo se computan utilizando la regla de la cadena.
De manera general, el modo forward computa una derivada en un nodo $f_i$ a partir de un nodo precedente $f_{i-1}$ por medio de la regla de la cadena de la siguiente forma:
$$\frac{\partial f_i}{\partial x} = \frac{\partial f_i}{f_{i-1}}\frac{\partial f_{i-1}}{\partial x}$$De esta forma, la derivada se propaga hacia adelante, en avance, hasta obtener la derivada de las salidas de la función. En un solo paso, se pueden obtener las derivadas parciales para todas las salidas sobre una de las entradas.
El modo forward se inicializa asignando un 1 a una de las variables de entrada. O de forma más general, se inicializa asignando un vector de entrada (de la misma dimensión que la entrada de la función), que generalmente es un vector base $e_j$, de tal forma que la derivada parcial se estime sobre la $j$-ésima entrada $\frac{\partial f}{\partial x_j}$. Cuando se inicializa con un vector que no es base, el modo forward se interpreta como el producto entre la matriz Jacobiana $J_f$ y el vector de entrada.
Para ejemplificar el modo forward definiremos un grafo computacional que compute la función $b:\mathbb{R}^3 \to \mathbb{R}$ dada como:
$$b = x(y-z)^2$$En el grafo, definiremos tres nodos que nos servirán para computar la función final. Estos tres nodos son en orden los siguientes:
$$a = y-z$$$$c = a^2$$$$b = xc$$Cada uno de estos nodos contiene una función más simple, cuyas derivadas son fáciles de estimar; asociaremos a cada nodo una variable con la respectiva derivada de la siguiente forma:
$$a' = y'-z'$$$$c' = (2a)(a')$$$$b' = x'c+xc'$$La derivada final será la variable $b'$. Para obtenerla, usaremos el procedimiento de avance, primero asignaremos variables de la traza tangente a las variables de entrada; como señalamos estas variables serán 1 o 0, siendo 1 el valor sobre el que obtendremos las derivadas parciales. De tal forma que para estimar el vector gradiante deberemos correr el modo forward tantas veces como variables de entrada tengamos.
class ForwardMode(object):
def __init__(self, x1, x2, x3):
# Inicializa los tensores de entrada
self.x = x1
self.y = x2
self.z = x3
# Define el avance en el grafo
self.a = self.y-self.z
self.c = self.a**2
self.b = self.x*self.c
def result(self):
# Regresa el valor de la función
return self.b
def forward(self, r=[0,0,0]):
"""Modo forward"""
# Variables para la entrada
dx, dy, dz = r[0], r[1], r[2]
# Variables para nodos
da = dy-dz
dc = 2*self.a*da
db = dx*self.c + self.x*dc
return db
Ahora, a partir del grafo computacional, podemos evaluar la función con valores específicos de entrada. Para obtener el vector gradiente d ela función, necesitamos obtener las derivadas parciales $\frac{\partial b}{\partial x_i}$ para cada uno de los tres valores de entrada. Como puede observarse, para cada variable tomamos un vector base, lo que nos da la derivada en esa entrada.
#Se asignan los valores de entrada de la función
f = ForwardMode(2,5,3)
#Se obtienen las derivadas por cada una de las variables de entrada
dx = f.forward(r=[1,0,0])
dy = f.forward(r=[0,1,0])
dz = f.forward(r=[0,0,1])
#Imprime valor de función
print('Resultado de evaluar la función: {}'.format(f.result()))
#Imprime derivadas parciales
print('Gradiente de la función: [{}, {}, {}]'.format(dx, dy, dz))
Resultado de evaluar la función: 8 Gradiente de la función: [4, 8, -8]
Por las características del modo forward, este es útil cuando se tiene una función $f:\mathbb{R}^n \to \mathbb{R}^m$ tal que $n < m$, pues en cada paso calcula las derivadas sobre todas las salidas, lo que requerirá de $n$ pasos.