Source code for bayesflow.utils.jacobian.jacobian
from collections.abc import Callable
import keras
import numpy as np
from bayesflow.types import Tensor
from .vjp import vjp
[docs]
def jacobian(f: Callable[[Tensor], Tensor], x: Tensor, return_output: bool = False):
"""
Compute the Jacobian matrix of f with respect to x.
Parameters
----------
f : callable
The function to be differentiated.
x : Tensor of shape (..., D_in)
The input tensor to f.
return_output : bool, optional
Whether to return the output of f(x) along with the Jacobian matrix.
Default: False
Returns
-------
Tensor of shape (..., D_out, D_in)
The Jacobian matrix of f with respect to x.
2-tuple of tensors
1. The output of f(x) (if return_output is True)
2. Tensor of shape (..., D_out, D_in)
The Jacobian matrix of f with respect to x.
"""
fx, vjp_fn = vjp(f, x, return_output=True)
batch_shape = keras.ops.shape(x)[:-1]
batch_size = np.prod(batch_shape)
rows = keras.ops.shape(fx)[-1]
cols = keras.ops.shape(x)[-1]
jac = keras.ops.zeros((*batch_shape, rows, cols))
for col in range(cols):
projector = np.zeros(keras.ops.shape(x), dtype=keras.ops.dtype(x))
projector[..., col] = 1.0
projector = keras.ops.convert_to_tensor(projector)
# jac[..., col] = vjp_fn(projector)
indices = np.stack(list(np.ndindex(batch_shape + (rows,))))
indices = np.concatenate([indices, np.full((batch_size * rows, 1), col)], axis=1)
indices = keras.ops.convert_to_tensor(indices)
updates = vjp_fn(projector)
updates = keras.ops.reshape(updates, (-1,))
jac = keras.ops.scatter_update(jac, indices, updates)
if return_output:
return fx, jac
return jac