Skip to content
/ ad Public

Automatic differentiation in 16 lines of code.

License

Notifications You must be signed in to change notification settings

oelin/ad

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ad

# Automatic differentiation in 16 lines of code.

class Variable:
    def __init__(self, data, terminal=False):
        
        self.data = np.array(data)                        # x.
        self.grad = np.zeros_like(self.data)              # dL/dx.
        self.terminal = terminal
        self.backward = int 


class Function:
    def __call__(self, *variables):
        result = self.forward(*variables)                 # Forward pass, f(x0, ..., xn).
        
        def backward(grad = 1):                           # Backward pass.
            self.backward(*variables, grad + result.grad) # Accumulate gradients, dL/dxi += dL/df * df/dxi.

            for variable in variables:                    # Recurse.
                variable.backward(0)
                
            result.grad *= result.terminal                # Reset gradients.
        result.backward = backward 
        
        return result

Usage

This tiny library can be used to implement a complete automatic differentiation engine. For instance, the tensor dot product operation can be implemented as follows:

class TensorDotProduct(Function):

    def forward(self, x: Variable, y: Variable) -> Variable:
        return Variable(x.data @ y.data)
    
    def backward(self, x: Variable, y: Variable, grad) -> None:
    
        x.grad += x.data.T @ grad # Backpropagate to children.
        y.grad += grad @ y.data.T

To make code more concise, we can overload the @ operator:

Tensor.__matmul__ = TensorDotProduct()

Then we can use it to compute gradients in expressions involving @. For instance, here we compute the gradients of x and y with respect to x @ y:

x = tensor([[1.,2.,3.]]) 
y = tensor([[0.], [1.], [1.]])

z = x @ y
z.backward()

x.grad # [[0., 1., 1.]]
y.grad # [[1.], [2.], [3.]]

Other operations such as activation functions and neural network layers can be implemented using the same API. The only requirement is that forward() takes in some number of Variable instances and returns a new Variable instance. Meanwhile, backward() should take in the same number of tensors as forward(), and additionally take in a grad argument for the parent gradient.

Releases

No releases published

Packages

No packages published

Languages