PyTorch中的forward的理解

0. 前言

按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

1. 关于forward的两个小问题

1.1 为什么都用def forward,而不改个名字?

在Pytorch建立神经元网络模型的时候,经常用到forward方法,表示在建立模型后,进行神经元网络的前向传播。说的直白点,forward就是专门用来计算给定输入,得到神经元网络输出的方法。

在代码实现中,也是用def forward来写forward前向传播的方法,我原来以为这是一种约定熟成的名字,也可以换成任意一个自己喜欢的名字。

但是看的多了之后发现并非如此:Pytorch对于forward方法赋予了一些特殊“功能”

(这里不禁再吐槽,一些看起来挺厉害的Pytorch“大神”,居然不知道这个。。。只能草草解释一下:“就是这样的。。。”)

1.2 forward有什么特殊功能?
第一条:.forward()可以不写

我最开始发现forward()的与众不同之处就是在此,首先举个例子:

import torch.nn as nn
class test(nn.Module):
    def __init__(self, input):
        super(test,self).__init__()
        self.input = input

    def forward(self,x):
        return self.input * x

T = test(8)
print(T(6))

# print(T.forward(6))
--------------------------运行结果-------------------------
D:\Users\Lenovo\anaconda3\python.exe C:/Users/Lenovo/Desktop/DL/pythonProject/tt.py
48

Process finished with exit code 0

可以发现,T(6)是可以输出的!而且不用指定,默认了调用forward方法。当然如果非要写上.forward()这也是可以正常运行的,和不写是一样的。

如果不调用Pytorch(正常的Python语法规则),这样肯定会报错的

# import torch.nn as nn  #不再调用torch
class test():
    def __init__(self, input):
        self.input = input

    def forward(self,x):
        return self.input * x

T = test(8)
print(T.forward(6))
print("************************")
print(T(6))
--------------------------运行结果-------------------------
D:\Users\Lenovo\anaconda3\python.exe C:/Users/Lenovo/Desktop/DL/pythonProject/tt.py
48
************************
Traceback (most recent call last):
  File "C:\Users\Lenovo\Desktop\DL\pythonProject\tt.py", line 77, in <module>
    print(T(6))
TypeError: 'test' object is not callable

Process finished with exit code 1

这里会报:‘test’ object is not callable
因为class不能被直接调用,不知道你想调用哪个方法。

第二条:优先运行forward方法

如果在class中再增加一个方法:

import torch.nn as nn
class test(nn.Module):
    def __init__(self, input):
        super(test,self).__init__()
        self.input = input

    def byten(self):
        return self.input * 10

    def forward(self,x):
        return self.input * x

T = test(8)
print(T(6))
print(T.byten())
--------------------------运行结果-------------------------
D:\Users\Lenovo\anaconda3\python.exe C:/Users/Lenovo/Desktop/DL/pythonProject/tt.py
48
80

Process finished with exit code 0

可以见到,在class中有多个method的时候,如果不指定method,forward是会被优先执行的。

2. 总结

在Pytorch中,forward方法是一个特殊的方法,被专门用来进行前向传播。

20230605 更新

应评论要求,增加forward的官方定义,这块我就不搬运PyTorch官网的内容了,直接传送门走你:nn.Module.forward

20230919 大更新

首先非常感谢大家喜欢本文!这篇文章本来是我自己的“随手记”没想到有这么多C友浏览过!

其实在写完本文后我是有些遗憾的,因为本文仅是用了实验的方法探索出了.forward()的表象,而它的运作机理却没有说明白,知其然不知其所以然!

在此感谢下面 Mr·小鱼 的评论给了我启迪,因为魔术方法__call__()的特性确实很符合.forward()的表象,但是我对着nn.Module的源码一脸茫然,因为源码中压根没有__call__()方法的定义!!

于是我抱着试试的心态,在PyTorch官网上查了下PyTorch的历史版本,这一查确实查到了线索:
在这里插入图片描述
下面是从PyTorch的上古版本v0.1.12中截取forward()__call__()方法的源码:

class Module(object):
#...中间不相关代码省略...
    def forward(self, *input):
        """Defines the computation performed at every call.

        Should be overriden by all subclasses.
        """
        raise NotImplementedError
#...中间不相关代码省略...
    def __call__(self, *input, **kwargs):
        result = self.forward(*input, **kwargs)
        for hook in self._forward_hooks.values():
            hook_result = hook(self, input, result)
            if hook_result is not None:
                raise RuntimeError(
                    "forward hooks should never return any values, but '{}'"
                    "didn't return None".format(hook))
        var = result
        while not isinstance(var, Variable):
            var = var[0]
        creator = var.creator
        if creator is not None and len(self._backward_hooks) > 0:
            for hook in self._backward_hooks.values():
                wrapper = functools.partial(hook, self)
                functools.update_wrapper(wrapper, hook)
                creator.register_hook(wrapper)
        return result

我们可以看到在__call__()方法中直接把方法self.forward()作为函数的返回值,由于魔术方法__call__()可以被自动调用,这也就解释了为什么forward()可以自动运行。

至于该方法中的其他内容,都是与hook钩子函数的操作相关,这部分暂不做探索。。。

那我们回到现在的版本(我现在使用的是1.8.1):
在这里插入图片描述
通过源码可以看到经历了多个版本的更迭,forward()__call__()居然改名字了!!

    forward: Callable[..., Any] = _forward_unimplemented
    ...
    __call__ : Callable[..., Any] = _call_impl

这也就是为什么我之前在源码中没找到这两个方法定义的原因。。。准确来说这里也不能说是改名字了,而是多了一个名字,至于PyTorch为什么会有这样的更改,我确实也没想到原因。。。

其中_forward_unimplemented()倒是没变:

def _forward_unimplemented(self, *input: Any) -> None:
    r"""Defines the computation performed at every call.

    Should be overridden by all subclasses.

    .. note::
        Although the recipe for forward pass needs to be defined within
        this function, one should call the :class:`Module` instance afterwards
        instead of this since the former takes care of running the
        registered hooks while the latter silently ignores them.
    """
    raise NotImplementedError

_call_impl()相比于上古版本,已经复杂到了令人发指的地步!

    def _call_impl(self, *input, **kwargs):
        # Do not call functions when jit is used
        full_backward_hooks, non_full_backward_hooks = [], []
        if len(self._backward_hooks) > 0 or len(_global_backward_hooks) > 0:
            full_backward_hooks, non_full_backward_hooks = self._get_backward_hooks()

        for hook in itertools.chain(
                _global_forward_pre_hooks.values(),
                self._forward_pre_hooks.values()):
            result = hook(self, input)
            if result is not None:
                if not isinstance(result, tuple):
                    result = (result,)
                input = result

        bw_hook = None
        if len(full_backward_hooks) > 0:
            bw_hook = hooks.BackwardHook(self, full_backward_hooks)
            input = bw_hook.setup_input_hook(input)

        if torch._C._get_tracing_state():
            result = self._slow_forward(*input, **kwargs)
        else:
            result = self.forward(*input, **kwargs)
        for hook in itertools.chain(
                _global_forward_hooks.values(),
                self._forward_hooks.values()):
            hook_result = hook(self, input, result)
            if hook_result is not None:
                result = hook_result

        if bw_hook:
            result = bw_hook.setup_output_hook(result)

        # Handle the non-full backward hooks
        if len(non_full_backward_hooks) > 0:
            var = result
            while not isinstance(var, torch.Tensor):
                if isinstance(var, dict):
                    var = next((v for v in var.values() if isinstance(v, torch.Tensor)))
                else:
                    var = var[0]
            grad_fn = var.grad_fn
            if grad_fn is not None:
                for hook in non_full_backward_hooks:
                    wrapper = functools.partial(hook, self)
                    functools.update_wrapper(wrapper, hook)
                    grad_fn.register_hook(wrapper)
                self._maybe_warn_non_full_backward_hook(input, result, grad_fn)

        return result

其变复杂的原因是各种钩子函数_hook的调用,有兴趣的童鞋可以参考这篇文章:pytorch 中_call_impl()函数。这部分绝对是超纲了!

最后我想再做几个实验加深理解:
实验①

import torch.nn as nn
class test(nn.Module):
    def __init__(self, input):
        super(test,self).__init__()
        self.input = input

    def forward(self,x):
        return self.input * x

T = test(8)
print(T.__call__(6))
--------------------------运行结果-------------------------
D:\Users\Lenovo\anaconda3\python.exe C:\Users\Lenovo\Desktop\DL\Pytest\calc_graph\test.py 
48

Process finished with exit code 0

这里T.__call__(6)写法等价于T(6)

实验②

import torch.nn as nn
class test(nn.Module):
    def __init__(self, input):
        super(test,self).__init__()
        self.input = input

    def forward(self,x):
        return self.input * x

T = test(8)
print(T.forward(6))
--------------------------运行结果-------------------------
D:\Users\Lenovo\anaconda3\python.exe C:\Users\Lenovo\Desktop\DL\Pytest\calc_graph\test.py 
48

Process finished with exit code 0

这里T.forward(6)的写法虽然也能正确地计算出结果,但是不推荐这么写,因为这会导致__call__()调用一遍forward(),然后手动又调用了一遍forward(),造成forward()的重复计算,浪费计算资源。

实验③

import torch.nn as nn
class test(nn.Module):
    def __init__(self, input):
        super(test,self).__init__()
        self.input = input

    # def forward(self,x):
    #     return self.input * x

T = test(8)
print(T())
--------------------------运行结果-------------------------
D:\Users\Lenovo\anaconda3\python.exe C:\Users\Lenovo\Desktop\DL\Pytest\calc_graph\test.py 
Traceback (most recent call last):
  File "C:\Users\Lenovo\Desktop\DL\Pytest\calc_graph\test.py", line 11, in <module>
    print(T())
  File "D:\Users\Lenovo\anaconda3\lib\site-packages\torch\nn\modules\module.py", line 889, in _call_impl
    result = self.forward(*input, **kwargs)
  File "D:\Users\Lenovo\anaconda3\lib\site-packages\torch\nn\modules\module.py", line 201, in _forward_unimplemented
    raise NotImplementedError
NotImplementedError

forward()是必须要写的,因为__call__()要自动调用forward()。如果压根不写forward()__call__()将无方法可以调用。按照forward()的源码,这里会raise NotImplementedError

至此,我觉得PyTorch中的forward应该算是全说明白了。。。

  • 94
    点赞
  • 175
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
### 回答1: 在PyTorch,我们可以使用`torchsummary`库来可视化网络结构的总结(summary)。下面是一些步骤可以用来可视化网络结构: 首先,我们需要安装`torchsummary`库。在终端运行以下命令: ``` pip install torchsummary ``` 接下来,我们可以导入所需的库并在代码调用`summary`函数。例如,假设我们有一个名为`model`的PyTorch模型,我们可以按照以下方式进行可视化: ```python import torch import torchvision.models as models from torchsummary import summary # 创建一个模型 model = models.resnet18() # 定义输入维度 input_size = (3, 224, 224) # 将模型移动到特定的设备(GPU/CPU) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) # 使用torchsummary库的summary函数可视化网络结构 summary(model, input_size=input_size) ``` 以上代码将显示ResNet-18模型的网络结构摘要,包括每个层的输出形状和参数数量。可以在控制台看到类似下面的输出: ``` ---------------------------------------------------------------- Layer (type) Output Shape Param # ================================================================ Conv2d-1 [-1, 64, 112, 112] 9,408 BatchNorm2d-2 [-1, 64, 112, 112] 128 ReLU-3 [-1, 64, 112, 112] 0 MaxPool2d-4 [-1, 64, 56, 56] 0 Conv2d-5 [-1, 64, 56, 56] 36,864 BatchNorm2d-6 [-1, 64, 56, 56] 128 ReLU-7 [-1, 64, 56, 56] 0 Conv2d-8 [-1, 64, 56, 56] 36,864 BatchNorm2d-9 [-1, 64, 56, 56] 128 ReLU-10 [-1, 64, 56, 56] 0 BasicBlock-11 [-1, 64, 56, 56] 0 Conv2d-12 [-1, 64, 56, 56] 36,864 BatchNorm2d-13 [-1, 64, 56, 56] 128 ... ================================================================ Total params: 11,689,512 Trainable params: 11,689,512 Non-trainable params: 0 ---------------------------------------------------------------- Input size (MB): 0.57 Forward/backward pass size (MB): 34.00 Params size (MB): 44.56 Estimated Total Size (MB): 79.13 ---------------------------------------------------------------- ``` 以上是通过`torchsummary`库可视化PyTorch模型结构的一种简单方式。这对于在训练过程查看模型的层和参数很有用。 ### 回答2: 在PyTorch,我们可以使用`torchsummary`库来可视化神经网络的结构。首先,确保已经安装了`torchsummary`库,然后按照以下步骤进行操作: 1. 导入所需的库和模型: ```python import torch from torchsummary import summary from torchvision.models import ResNet ``` 2. 创建模型实例: ```python model = ResNet() ``` 3. 在模型输入上运行summary函数,以输出模型的摘要信息。摘要信息提供了每个层的输出形状、参数数量和总体参数数量等信息: ```python summary(model, (3, 224, 224)) ``` 这里的`(3, 224, 224)`是输入张量的形状,表示3个通道、224x224的图像输入。 运行上述代码后,你将在控制台上看到模型的可视化结构摘要信息,包括每个层的输出形状、参数数量和总体参数数量等等。例如: ``` ---------------------------------------------------------------- Layer (type) Output Shape Param # ================================================================ Conv2d-1 [-1, 64, 112, 112] 9,408 BatchNorm2d-2 [-1, 64, 112, 112] 128 ReLU-3 [-1, 64, 112, 112] 0 MaxPool2d-4 [-1, 64, 56, 56] 0 ``` 以上仅显示了部分摘要信息,你可以看到每个层的名称、类型、输出形状和参数数量等相关信息。 总而言之,通过使用`torchsummary`库的`summary`函数,我们可以方便地可视化和查看PyTorch神经网络的结构。 ### 回答3: 在PyTorch,我们可以使用torchsummary库来可视化模型的结构。torchsummary是一个简单而强大的库,可以帮助我们快速地查看模型的层级结构和参数数量。 首先,我们需要安装torchsummary库,可以使用以下命令: ``` !pip install torchsummary ``` 在PyTorch,我们需要首先导入torchsummary库和定义好的模型。然后使用summary函数来生成模型的可视化结果。以下是一个示例代码: ```python import torch import torch.nn as nn from torchsummary import summary # 定义一个示例模型 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1) self.relu = nn.ReLU() self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1) self.fc = nn.Linear(32 * 8 * 8, 10) def forward(self, x): x = self.conv1(x) x = self.relu(x) x = self.maxpool(x) x = self.conv2(x) x = self.relu(x) x = self.maxpool(x) x = x.view(x.size(0), -1) x = self.fc(x) return x # 创建模型的实例 model = Net() # 调用summary函数生成可视化结果 summary(model, (3, 32, 32)) ``` 在上述代码,我们首先导入需要的库,并定义一个示例的神经网络模型Net。然后,我们创建该模型的实例model,并调用summary函数生成可视化结果。summary函数需要两个参数,第一个参数是模型的实例,第二个参数是输入数据的维度。在上述示例,输入数据的维度是(3, 32, 32)。 运行以上代码后,我们可以得到一个包含模型层级结构和参数数量的可视化结果。可以看到,每一层的名称、输入形状、输出形状和参数数量都被清晰地显示出来了。这样的可视化结果有助于我们更好地理解和调试模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

使者大牙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值