添加 (3)(5) nn.Sequential。怎么运行的?

Add(3)(5) nn.Sequential. How it works?

class Add(nn.Module):
    def __init__(self, value):
        super().__init__()
        self.value = value

    def forward(self, x):
        return x + self.value

calculator = nn.Sequential(
    Add(3),
    Add(2),
    Add(5),
)


x = torch.tensor([1])
output = calculator(x)
print(output) # tensor([11])

我添加了模型。但我无法理解 'nn.Sequential' 是如何工作的。

第一次这么理解

add = Add(torch.tensor([1]))
add(3) # tensor([4])
add = Add(add(3))
add(2) # tensor([6])
add = Add(add(2))
add(5) # tensor([11])

但 Add(3)(1) 也有效。 我不明白为什么 'Add(3)(1)' 有效。请帮帮我

要记住的核心是,当您 实例化 一个 Module class 时,您正在创建一个可调用对象,即可以表现得像一个函数。

用通俗易懂的英语一步一步:

  • 当你写类似 add5 = Add(5) 的东西时,你所做的是将 PyTorch 模型 Add 的“实例”分配给 add5
  • 更具体地说,您将 5 传递给 Add class 的 __init__ 方法,因此其 value 属性设置为 5.
  • PyTorch Modules 是“可调用的”,这意味着您可以像调用函数一样调用它们。使用 Module 实例时调用的函数是该实例的 forward 方法。所以具体来说,对于我们的 add5 对象,如果我们传递一个值,x = 10,通过写类似 add5(10) 的东西,就像我们 运行 x + add5.value,它等于10 + 5 = 15.

现在将它们放在一起,我们应该将 Sequential 用于构建没有 b运行ching 结构的神经网络模型的界面视为 sequentially 调用每个实例化的 Modules' forward 方法。

省略 Add 的定义,只关注 calculator 作为一系列计算,我们有以下内容(我添加了注释以向您展示在每个步骤中应该考虑的内容)

calculator = nn.Sequential(
            # given some input tensor x...
    Add(3), # run: x = x + self.value with self.value = 3
    Add(2), # run: x = x + self.value with self.value = 2 
    Add(5), # run: x = x + self.value with self.value = 5
)

现在我们可以看到,如果我们传递值 1(尽管包装为 PyTorch Tensor),我们只是在做 1 + 3 + 2 + 5,这当然是合理的等于 11。 PyTorch return 将值作为 Tensor 对象返回给我们。

x = torch.tensor([1])
output = calculator(x)
print(output) # tensor([11])

最后,Add(3)(5)* 的工作原理完全相同!使用 Add(3) 我们得到 Add class 的一个实例,要添加的值为 3。然后我们立即使用它,语法有点不直观 Add(3)(5) 到 return 值 3 + 5 = 8.

*我认为您想要大写的 class 名称,而不是 class

的实例

你没看错,Sequential class简单来说就是一个一个调用提供的模块。这是转发方法的代码

    def forward(self, input):
        for module in self:
            input = module(input)
        return input

此处,for module in self 只是遍历构造函数中提供的模块(Sequential.__iter__ 负责它的方法)。

当您使用 () 语法调用它时,顺序模块会调用它。

calculator = nn.Sequential(
    Add(3),
    Add(2),
    Add(5),
)
output = calculator(torch.tensor([1]))

但是它是如何工作的呢?在 python 中,如果将 __call__ 方法添加到 class 定义中,则可以创建 class callable 的对象。如果 class 没有明确包含这个方法,这意味着它可能是从一个 superclass 继承的。在 AddSequential 的情况下,Module class 实现了 __call__ 方法。 __call__方法调用用户定义的'public'forward方法。

python 对对象实例化和函数或方法调用使用相同的语法,这可能会造成混淆。为了使 reader 可见差异,python 使用命名约定。 类 应该以 CamelCase 命名,首字母大写,对象以 snake_case 命名(这不是强制性的,但最好遵循此规则)。

就像你的例子一样,Add 是一个 class 而 add 是这个 class 的一个 callable 对象:

add = Add(torch.tensor([1]))

因此,您可以调用 add,就像您在示例中调用计算器一样。

>>> add = Add(torch.tensor([1]))
>>> add(2)
Out: tensor([3]) 

但这行不通:

>>> add = Add(torch.tensor([1]))
>>> add(2)(1)
Out: 
----> 3 add(2)(1)
TypeError: 'Tensor' object is not callable

这意味着 add(2) returns 一个没有实现 __call__ 方法的 Tensor 对象。

将此代码与

进行比较
>>> Add(torch.tensor([1]))(2)
Out:
tensor([3])  

此代码与第一个示例相同,但重新排列了一点。

--

为了避免混淆,我通常用不同的方式命名对象:比如 add_obj = Add(1)。它帮助我突出差异。

如果您不确定自己在使用什么,请使用函数 typeisinstance。他们会帮助找出发生了什么事。

例如,如果您检查 add 对象,您会发现它是一个可调用对象(即它实现了 __call__

>>> from typing import Callable
>>> isinstance(add, Callable)
True

对于张量:

>>> from typing import Callable
>>> isinstance(add, torch.tensor(1))
False

因此,如果你调用它,它会上升TypeError: 'Tensor' object is not callable

如果您想了解 python 双底方法如 initcall 的工作原理,您可以阅读描述 python data model

的页面

(可能有点乏味,所以你可能更愿意阅读 Fluent Python 或其他书籍)