Writing Your Own Operations#
- Let’s write our own “multiply” operation. There are two components to doing this:
- Defining an operation class (a subclass of - Operation)
- Writing a function that ultimately calls - mygrad.execute_op(YourOp, ...)
 
import numpy as np
import mygrad as mg
from mygrad import execute_op
from mygrad.operation_base import Operation
from mygrad.typing import ArrayLike
# All operations should inherit from Operation, or one of its subclasses
class CustomMultiply(Operation):
    """ Performs f(x, y) = x * y """
    def __call__(self, x: mg.Tensor, y: mg.Tensor) -> np.ndarray:
        # This method defines the "forward pass" of the operation.
        # It must bind the variable tensors to the op and compute
        # the output of the operation as a numpy array
        # All tensors must be bound as a tuple to the `variables`
        # instance variable.
        self.variables = (x, y)
        # The forward pass should be performed using numpy arrays,
        # not the tensors themselves.
        x_arr = x.data
        y_arr = y.data
        return x_arr * y_arr
    def backward_var(self, grad, index, **kwargs):
        """Given ``grad = dℒ/df``, computes ``∂ℒ/∂x`` and ``∂ℒ/∂y``
        ``ℒ`` is assumed to be the terminal node from which ``ℒ.backward()`` was
        called.
        Parameters
        ----------
        grad : numpy.ndarray
            The back-propagated total derivative with respect to the present
            operation: dℒ/df. This will have the same shape as f, the result
            of the forward pass.
        index : Literal[0, 1]
            The index-location of ``var`` in ``self.variables``
        Returns
        -------
        numpy.ndarray
            ∂ℒ/∂x_{i}
        Raises
        ------
        SkipGradient"""
        x, y = self.variables
        x_arr = x.data
        y_arr = y.data
        # The operation need not incorporate specialized logic for
        # broadcasting. The appropriate sum-reductions will be performed
        # by MyGrad's autodiff system.
        if index == 0:  # backprop through a
            return grad * y.data  # ∂ℒ/∂x = (∂ℒ/∂f)(∂f/∂x)
        elif index == 1:  # backprop through b
            return grad * x.data  # ∂ℒ/∂y = (∂ℒ/∂f)(∂f/∂y)
# Our function stitches together our operation class with the
# operation arguments via `mygrad.prepare_op`
def custom_multiply(x: ArrayLike, y: ArrayLike, constant=None) -> mg.Tensor:
    # `execute_op` will take care of:
    #  - casting `x` and `y` to tensors if they are instead array-likes
    #  - propagating 'constant' status to the resulting output based on the inputs
    #  - handling in-place operations (specified via the `out` parameter)
    return execute_op(CustomMultiply, x, y, constant=constant)
We can now use our differentiable function!
>>> x = mg.tensor(2.0)
>>> y = mg.tensor([1.0, 2.0, 3.0])
>>> custom_multiply(x, y).backward()
>>> x.grad, y.grad
(array(6.), array([2., 2., 2.]))
Documentation for mygrad.Operation#
| Base class for all tensor operations that support back-propagation of gradients. | |
| 
 | Back-propagates the gradient through all of the operation's inputs, which are stored in the tuple self.variables. | 
| 
 | Given  |