4.2 Module容器——Containers

容器的概念出现在日常生活的方方面面,每天喝水的杯子用来装一些水,书包用来装一些办公用品,衣柜用来装一些衣服。因此,我们可以很容易地抽象出容器的概念,它用于将一些物品放置在某个地方,并进行有效的管理和使用。

在深度学习模型里面,有一些网络层需要放在一起使用,如 conv + bn + relu 的组合。Module的容器是将一组操作捆绑在一起的工具,在pytorch官方文档中把Module也定义为Containers,或许是因为“Modules can also contain other Modules”。

对于Module类可查看4.1小结,这里详细介绍两个常用的容器SequentialModuleList,同时介绍ModuleDict,ParameterList,ParameterDict

Sequential

sequential是pytorch里使用最广泛的容器,它的作用是将一系列网络层按固定的先后顺序串起来,当成一个整体,调用时数据从第一个层按顺序执行到最后一个层。回顾一下transforms的Compose就可以体会到按顺序的含义了。

sequential可以直接传module,也可以传OrderedDict,OrderedDict可以让容器里的每个module都有名字,方便调用。

请看两段官方代码:

model = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )

# Using Sequential with OrderedDict. This is functionally the
# same as the above code
model = nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(1,20,5)),
          ('relu1', nn.ReLU()),
          ('conv2', nn.Conv2d(20,64,5)),
          ('relu2', nn.ReLU())
        ]))

来看一个实际案例:

AlexNet是新一代CNN的开山之作,也是这一轮深度学习潮流里,计算机视觉任务的开山之作。对于现代CNN,通常会把前面的卷积层、池化层称为特征提取部分,最后的全连接层当作分类器,这一点在代码编写上将有所体现。

例如,在D:\Anaconda_data\envs\pytorch_1.10_gpu\Lib\site-packages\torchvision\models\alexnet.py 下的AlexNet,它把前面的卷积和池化都放到Sequential这个容器当中,并且命名为self.features。在最后还有一个名为self.classifier的Sequential容器,它包含3个Linear层及激活函数、Dropout。这里正如下图所示,把模型大卸两块。

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

0

Sequential的调用

Sequential容器把一堆module包起来之后,它在forward中是如何使用的呢? 下面请看配套代码,主要采用debug模式,观察Alexnet的forward中,两个Sequential容器是如何forward的,同时也要看看Alexnet这个模型的属性。

  • 在 output = model(fake_input) 设置断点,step into,然后进入熟悉的_call_impl,关于module内部的代码这里直接略过,不熟悉的朋友请回到4.1小节阅读。

  • 这里直接跳到Alexnet类的forward函数,第一行就是执行x = self.features(x),继续step into观察这个sequential容器是如何工作的,进入它

  • 来到了Module类下的_call_impl函数:没错,又来到了_call_impl,因为sequential它也是一个module,因此在调用self.features的时候,会进入_call_impl, 下面需要大家有耐心的进入self.features的forward函数,其实就是1102行进去;

  • 来到Sequential类的forward函数,它十分简洁,如下所示:

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

这段代码是不是十分的熟悉呢? transforms当中也是这样去实现一个Compose里的变换的。

从此处可知道,Sequential类的功能是将一系列网络层按固定的先后顺序串起来,当成一个整体,调用时数据从第一个层按顺序执行到最后一个层,各层之间的数据必须能对接起来。

接着回到 Alexnet的forward函数下,观察一下Alexnet这个module的属性

alexnet-debug

重点看_modules属性,它有3个key-value,其中有2个是Sequential类,因为Sequential属于module类,继续展开一个Sequential来看看

alexent-debug2

可以看到该容器下的一系列网络层,并且是排了序的,这些对于后续理解网络结构、理解网络权重加载的key是十分重要的

ModuleList

ModuleList 是将各个网络层放到一个“列表”中,便于迭代的形式调用。

ModuleList与python List的区别

这里注意是“列表”而不是列表,因为ModuleList管理的module与python的List管理的module是有不同的,大家是否还记得module的setattr函数?在那里会对类属性进行判断管理,只有ModuleList里的网络层才会被管理,如果是List里的网络层则不会被管理,也就不能迭代更新了。

ModuleList 使用示例

假设要构建一个10层的全连接网络,如果用Sequential,那就要手写10行nn.Linear,而用ModuleList是这样的:

    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
            # self.linears = [nn.Linear(10, 10) for i in range(10)]    # 观察model._modules,将会是空的

        def forward(self, x):
            for sub_layer in self.linears:
                x = sub_layer(x)
            return x

需要对python的 list进行一个ModuleList封装,这样才可以在model的_modules属性下看到创建的10个Linear层。

推荐大家看看class ModuleList(Module)的实现,里边并不会像Sequential那样提供forward,即管理的网络层由用户自行调用,可以for循环全用,也可以通过if判断,有条件的选择部分网络层使用。同时ModuleList也提供了类似List的方法,insert\append\extend等。

ModuleDict

ModuleList可以像python的List一样管理各个module,但对于索引而言有一些不方便,因为它没有名字,需要记住是第几个元素才能定位到指定的层,这在深度神经网络中有一点不方便。

而ModuleDict就是可以像python的Dict一样为每个层赋予名字,可以根据网络层的名字进行选择性的调用网络层。

请看代码

    class MyModule2(nn.Module):
        def __init__(self):
            super(MyModule2, self).__init__()
            self.choices = nn.ModuleDict({
                    'conv': nn.Conv2d(3, 16, 5),
                    'pool': nn.MaxPool2d(3)
            })
            self.activations = nn.ModuleDict({
                    'lrelu': nn.LeakyReLU(),
                    'prelu': nn.PReLU()
            })

        def forward(self, x, choice, act):
            x = self.choices[choice](x)
            x = self.activations[act](x)
            return x

ParameterList & ParameterDict

除了Module有容器,Parameter也有容器。与ModuleList和ModuleDict类似的,Paramter也有List和Dict,使用方法一样,这里就不详细展开,可以参考Module的容器。

可以看两段官方文档代码感受一下

class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.params = nn.ParameterDict({
                'left': nn.Parameter(torch.randn(5, 10)),
                'right': nn.Parameter(torch.randn(5, 10))
        })

    def forward(self, x, choice):
        x = self.params[choice].mm(x)
        return x

# ParameterList
class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.params = nn.ParameterList([nn.Parameter(torch.randn(10, 10)) for i in range(10)])

    def forward(self, x):
        # ParameterList can act as an iterable, or be indexed using ints
        for i, p in enumerate(self.params):
            x = self.params[i // 2].mm(x) + p.mm(x)
        return x

小结

随着深度神经网络拓扑结构越来越复杂,网络模块多,杂,乱,因此需要Module容器来管理、组织各个网络层,便于forward函数中调用。

使用频率最高的是Sequential,其次是ModuleList,其余的均为进阶用法,在各类魔改网络中才会涉及。

这里深刻理解Sequential的机制、理解一个module是如何把Sequential里的module管理到自己的_modules属性中,对于后续使用模型是非常重要的。

熟悉了Module类,各种容器封装,下一小节将介绍一些常用的网络层,如卷积、池化、全连接、激活函数等。

Copyright © TingsongYu 2021 all right reserved,powered by Gitbook文件修订时间: 2024年04月26日21:48:10

results matching ""

    No results matching ""