Как разорвать циклическую зависимость в классе Python при попытке отделить простой класс-оболочку от более тяжелой реалиPython

Программы на Python
Ответить
Anonymous
 Как разорвать циклическую зависимость в классе Python при попытке отделить простой класс-оболочку от более тяжелой реали

Сообщение Anonymous »

Я пытаюсь создать небольшую систему глубокого обучения. Я определил свой тензорный класс следующим образом:

Код: Выделить всё

class Tensor:
__slots__ = (
"_backend",
"_data",
"_requires_grad",
"_node",
"_grad"
)

def __init__(
self,
data: BackendArray,
backend: Backend | None = None,
requires_grad: bool = False,
) -> None:
# Data
self._backend = backend if backend else get_backend()
self._data = self._backend.as_array(data)

# Data for autograd
self._requires_grad = requires_grad
self._node: Node | None = None  # For function (autograd graph) nodes
self._grad: "Tensor | None" = None  # Leaf gradient accumulator

def backward(self):
self._grad = backward(self)

def _apply(self, f: Type[Function], *args, **kwargs):
return _apply(self, f, *args, **kwargs)
Функции Back и _apply должны вызывать мой настоящий движок Autograd. Функция _apply быстро создает граф автоградации во время прямого прохода, а функция Back организует обратный проход. Я попытался сделать это таким образом, чтобы сделать Tensor довольно тонким классом, который должен просто хранить данные.
Позже в том же файле я предоставляю реализации для back и _apply как функций, а не методов класса

Код: Выделить всё

def _apply(t: Tensor, f: Type[Function], *args, **kwargs):
"""
Apply a Function to a tensor.
Build the computation graph eagerly.
"""

ctx = Context(t._backend)
# We have to unwrap the args to get the raw data, else we create an infinite recursion
# due to the way that functions are currently implemented
raw_args = [
a._data if isinstance(a, Tensor) else a for a in args
]
out_data: BackendArray = f.forward(ctx, *raw_args, **kwargs)

# determine requires_grad: True if any tensor arg requires grad
requires_grad = any(isinstance(a, Tensor) and a._requires_grad for a in args)
out = Tensor(out_data, requires_grad=requires_grad)

# If requires_grad, add to the graph for backprop
if requires_grad:
parents: list[Edge] = []
for idx, a in enumerate(args):
if isinstance(a, Tensor) and a._requires_grad:
if a._requires_grad:
parents.append(Edge(
a,
idx
))
out._node = Node(
grad_buffer = None,
op = f,
ctx = ctx,
parents = parents,
# Placeholder - will be computed just before doing backward as some nodes may not participate
in_degree = None
)

return out

Код: Выделить всё

def backward(t: Tensor) -> Tensor:
if t._node is None:
raise RuntimeError("Tensor not attached to graph.")
# Initial gradient
root_grad = F.ones(t.shape, requires_grad=True, backend=t._backend).data
t._node.grad_buffer = root_grad
_backward(t._node)
# return a Tensor wrapping the original root grad
return Tensor(root_grad, backend=t._backend, requires_grad=False)
Теперь в моем коде существует естественная циклическая зависимость: тензор должен знать об обратном и _apply, а реализации обратного и _apply должны знать о Tensor.
Я хочу переместить фактическую логику движка autograd в другой файл и не хранить все это в одном классе, но эта естественная циклическая зависимость мешает мне это сделать это.
Причина, по которой я хочу, чтобы класс Tensor имел их в качестве методов, заключается в том, что я хочу включить естественный синтаксис, например вызов x.backward(), если x является тензором, а не Backward(x). Действительно, если бы я хотел отказаться от этого синтаксиса, у меня не было бы этой проблемы циклических зависимостей, потому что Tensor больше не нуждался бы в дескрипторах функций, содержащих логику автоградации.
Итак, каков питонический способ решения этой проблемы? Моя конечная цель — отделить тензор (тонкий класс-оболочку вокруг некоторых данных) от основной логики автограда, но при этом иметь возможность иметь синтаксис типа x.backward(), если x, например, является тензором.

Подробнее здесь: https://stackoverflow.com/questions/798 ... te-a-simpl
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»