Source code for neograd.autograd.ops.operation
import numpy as np
from ..node import Node
[docs]class Operation:
'''Transforms Tensors by applying some function
Used when some input is getting transformed into an output, for functions
where gradient calculation is required with the forward pass and the backward
pass defined
'''
[docs] def process_operands(self, operands):
'''All operands are converted to Tensors
Args:
operands (Tensor or int or float or list or np.ndarray): Operands of the Operation
Returns:
tuple of Tensors
'''
from ..tensor import Tensor
operands = list(operands)
for i,operand in enumerate(operands):
if not isinstance(operand, Tensor):
operands[i] = Tensor(operand)
return tuple(operands)
[docs] def get_tensors(self, *operands):
'''Returns the processed operands as tuple of Tensors
Args:
*operands (Tensor or int or float or list or np.ndarray): Operands of the Operation
Returns:
tuple of Tensors if len(tuple)>1 else returns the first Tensor
'''
tensors = self.process_operands(operands)
if len(tensors)==0:
return None
elif len(tensors)==1:
return tensors[0]
else:
return tensors
[docs] def get_broadcast_shape(self, *tensors):
'''Return broadcasted shape of Tensors
If the tensors can be broadcasted, then the broadcasted shape is returned
, else None.
Args:
*tensors (Tensor): Tensors that should be broadcasted
Returns:
Broadcasted shape if it can be broadcasted, if not None
Also even if atleast one of the Tensors has requires_broadcasting set to False,
it returns None
'''
for tens in tensors:
if not(tens.requires_broadcasting):
return None
try:
return np.broadcast_shapes(*(tens.data.shape for tens in tensors))
except ValueError:
return None
[docs] def result_requires_grad(self, tensors):
'''Checks if the result requires grad
Checks if the result requires gradient to be calculated given the operands of the
Operation, if atleast one operand requires_grad to True, then result will also have
requires_grad to True
Args:
tensors (Tensor): Tensors that are operated on
'''
for tens in tensors:
if tens.requires_grad:
return True
return False
[docs] def get_result_tensor(self, result, *tensors):
'''Returns the result tensor of the Operation
If tracking is enabled, then, it creates a Node for the result_tensor
with parent_broadcast_shape and adds edges to the graph
If tracking is disabled, then no Node creation and edge addition
occurs
Args:
result (np object): Result after performing a raw numpy operation
*tensors (Tensor): Operands of the operation
Returns:
Tensor of the result
'''
from ..tensor import Tensor
from ..utils import get_graph
graph = get_graph()
result = result.astype(np.ndarray)
result_tensor = Tensor(result, self.result_requires_grad(tensors))
if graph.track:
result_node = Node(result_tensor)
result_node.backward_fn = self.backward
result_node.parent_broadcast_shape = self.get_broadcast_shape(*tensors)
graph.add_edge(result_node, tensors)
return result_tensor
[docs] def backward(self, *args):
'''Abstract backward method
Raises:
NotImplementedError: If backward method isn't overridden
'''
raise NotImplementedError(f"Backward method not implemented for Operation {self}")