An n-dimensional array that records operations for reverse-mode autodiff.
Wraps a NumPy array. When requires_grad=True, every operation builds a node
in the computation graph; calling .backward() on a scalar walks that graph
in reverse and fills in .grad on the leaves.
x = Tensor([1.0, 2.0, 3.0], requires_grad=True)
y = (x ** 2).sum()
y.backward()
x.grad # array([2., 4., 6.])
Source code in minitorch/tensor.py
| def __init__(self, data, *, requires_grad=False):
if isinstance(data, np.ndarray):
self.data = data if data.dtype in (np.float32, np.float64) else data.astype(np.float32)
elif isinstance(data, np.floating):
self.data = np.array(data, dtype=data.dtype)
else:
self.data = np.array(data, dtype=np.float32)
self.requires_grad = requires_grad and _grad_enabled
self.grad = None
self._backward = lambda: None
self._prev = set()
self._op = ''
|
sum
sum(axis=None, keepdims=False)
Sum over axis (or all elements). Differentiable.
Source code in minitorch/tensor.py
| def sum(self, axis=None, keepdims=False):
"""Sum over `axis` (or all elements). Differentiable."""
data = self.data.sum(axis=axis, keepdims=keepdims)
out = Tensor(data, requires_grad=self.requires_grad and _grad_enabled)
def _backward():
if self.requires_grad:
grad = out.grad
if axis is not None and not keepdims:
grad = np.expand_dims(grad, axis=axis)
_accum_grad(self, np.broadcast_to(grad, self.data.shape))
out._backward = _backward
out._prev = {self}
out._op = 'sum'
return out
|
mean
mean(axis=None, keepdims=False)
Mean over axis (or all elements). Differentiable.
Source code in minitorch/tensor.py
| def mean(self, axis=None, keepdims=False):
"""Mean over `axis` (or all elements). Differentiable."""
data = self.data.mean(axis=axis, keepdims=keepdims)
out = Tensor(data, requires_grad=self.requires_grad and _grad_enabled)
def _backward():
if self.requires_grad:
if axis is None:
count = self.data.size
elif isinstance(axis, int):
count = self.data.shape[axis]
else:
count = 1
for a in axis:
count *= self.data.shape[a]
grad = out.grad
if axis is not None and not keepdims:
grad = np.expand_dims(grad, axis=axis)
_accum_grad(self, np.broadcast_to(grad, self.data.shape) / count)
out._backward = _backward
out._prev = {self}
out._op = 'mean'
return out
|
reshape
Return a view with a new shape. Differentiable.
Source code in minitorch/tensor.py
| def reshape(self, *shape):
"""Return a view with a new shape. Differentiable."""
data = self.data.reshape(*shape)
out = Tensor(data, requires_grad=self.requires_grad and _grad_enabled)
def _backward():
if self.requires_grad:
_accum_grad(self, out.grad.reshape(self.data.shape))
out._backward = _backward
out._prev = {self}
out._op = 'reshape'
return out
|
transpose
transpose(dim0=-2, dim1=-1)
Swap two axes (last two by default). Differentiable.
Source code in minitorch/tensor.py
| def transpose(self, dim0=-2, dim1=-1):
"""Swap two axes (last two by default). Differentiable."""
axes = list(range(self.data.ndim))
axes[dim0], axes[dim1] = axes[dim1], axes[dim0]
data = self.data.transpose(axes)
out = Tensor(data, requires_grad=self.requires_grad and _grad_enabled)
def _backward():
if self.requires_grad:
_accum_grad(self, out.grad.transpose(axes))
out._backward = _backward
out._prev = {self}
out._op = 'transpose'
return out
|
exp
Elementwise e ** x. Differentiable.
Source code in minitorch/tensor.py
| def exp(self):
"""Elementwise `e ** x`. Differentiable."""
out = Tensor(np.exp(self.data), requires_grad=self.requires_grad and _grad_enabled)
def _backward():
if self.requires_grad:
_accum_grad(self, out.data * out.grad)
out._backward = _backward
out._prev = {self}
out._op = 'exp'
return out
|
backward
Run reverse-mode autodiff from this scalar, filling .grad on every leaf.
Source code in minitorch/tensor.py
| def backward(self):
"""Run reverse-mode autodiff from this scalar, filling `.grad` on every leaf."""
assert self.data.size == 1, "backward() only works on scalar tensors - call .sum() or .mean() first"
if self.grad is None:
self.grad = np.ones_like(self.data)
# iterative post-order DFS so deep graphs don't blow the recursion limit
topo = []
visited = set()
stack = [(self, False)]
while stack:
node, processed = stack.pop()
if processed:
topo.append(node)
continue
if node in visited:
continue
visited.add(node)
stack.append((node, True))
for child in node._prev:
if child not in visited:
stack.append((child, False))
for node in reversed(topo):
node._backward()
|
detach
Return a tensor sharing this data but cut out of the graph.
Source code in minitorch/tensor.py
| def detach(self):
"""Return a tensor sharing this data but cut out of the graph."""
return Tensor(self.data, requires_grad=False)
|
clone
Return a copy of this tensor's data, keeping requires_grad.
Source code in minitorch/tensor.py
| def clone(self):
"""Return a copy of this tensor's data, keeping `requires_grad`."""
return Tensor(self.data.copy(), requires_grad=self.requires_grad)
|