前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解PyTorch中的ModuleList和Sequential的区别

详解PyTorch中的ModuleList和Sequential的区别

作者头像
狼啸风云
修改2022-09-02 22:36:23
1.6K0
修改2022-09-02 22:36:23
举报

一、官方文档

首先看官方文档的解释,仅列出了容器(Containers)中几个比较常用的CLASS。

CLASS torch.nn.Module:

  • Base class for all neural network modules.
  • Your models should also subclass this class.

A sequential container. Modules will be added to it in the order they are passed in the constructor. Alternatively, an ordered dict of modules can also be passed in.

代码语言:javascript
复制
# Example of using Sequential
model = nn.Sequential(
    nn.Conv2d(1, 20, 5),
    nn.ReLU(),
    nn.Conv2d(20, 64, 5),
    nn.ReLU()
    )
# Example of using Sequential with OrderedDict
model = nn.Sequential(OrderedDict([
    ('conv1', nn.Conv2d(1, 20, 5)),
    ('ReLU1', nn.ReLU()),
    ('conv2', nn.Conv2d(20, 64, 5)),
    ('ReLU2', nn.ReLU())
    ]))

CLASS torch.nn.ModuleList(modules=None)

Holds submodules in a list.

ModuleList can be indexed like a regular Python list, but modules it contains are properly registered, and will be visible by all Module methods.

代码语言:javascript
复制
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.linears = nn.ModuleList([nn.linear for i in range(10)])

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

二、nn.Sequential与nn.ModuleList简介

1、nn.Sequential

代码语言:javascript
复制
#首先导入torch相关包
import torch
import torch.nn as nn
import torch.nn.functional as F
class net_seq(nn.Module):
    def __init__(self):
        super(net2, self).__init__()
        self.seq = nn.Sequential(
                        nn.Conv2d(1,20,5),
                         nn.ReLU(),
                          nn.Conv2d(20,64,5),
                       nn.ReLU()
                       )      
    def forward(self, x):
        return self.seq(x)
net_seq = net_seq()
print(net_seq)
#net_seq(
#  (seq): Sequential(
#    (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#    (1): ReLU()
#    (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#    (3): ReLU()
#  )
#)

nn.Sequential中可以使用OrderedDict来指定每个module的名字,而不是采用默认的命名方式(按序号 0,1,2,3...)。例子如下:

代码语言:javascript
复制
from collections import OrderedDict

class net_seq(nn.Module):
    def __init__(self):
        super(net_seq, self).__init__()
        self.seq = nn.Sequential(OrderedDict([
                        ('conv1', nn.Conv2d(1,20,5)),
                         ('relu1', nn.ReLU()),
                          ('conv2', nn.Conv2d(20,64,5)),
                       ('relu2', nn.ReLU())
                       ]))
    def forward(self, x):
        return self.seq(x)
net_seq = net_seq()
print(net_seq)
#net_seq(
#  (seq): Sequential(
#    (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#    (relu1): ReLU()
#    (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#    (relu2): ReLU()
#  )
#)

 2、nn.ModuleList

nn.ModuleList,它是一个储存不同 module,并自动将每个 module 的 parameters 添加到网络之中的容器。你可以把任意 nn.Module 的子类 (比如 nn.Conv2d, nn.Linear 之类的) 加到这个 list 里面,方法和 Python 自带的 list 一样,无非是 extend,append 等操作。但不同于一般的 list,加入到 nn.ModuleList 里面的 module 是会自动注册到整个网络上的,同时 module 的 parameters 也会自动添加到整个网络中。若使用python的list,则会出问题。下面看一个例子:

代码语言:javascript
复制
class net_modlist(nn.Module):
    def __init__(self):
        super(net_modlist, self).__init__()
        self.modlist = nn.ModuleList([
                       nn.Conv2d(1, 20, 5),
                       nn.ReLU(),
                        nn.Conv2d(20, 64, 5),
                        nn.ReLU()
                        ])

    def forward(self, x):
        for m in self.modlist:
            x = m(x)
        return x

net_modlist = net_modlist()
print(net_modlist)
#net_modlist(
#  (modlist): ModuleList(
#    (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#    (1): ReLU()
#    (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#    (3): ReLU()
#  )
#)

for param in net_modlist.parameters():
    print(type(param.data), param.size())
#<class 'torch.Tensor'> torch.Size([20, 1, 5, 5])
#<class 'torch.Tensor'> torch.Size([20])
#<class 'torch.Tensor'> torch.Size([64, 20, 5, 5])
#<class 'torch.Tensor'> torch.Size([64])

可以看到,这个网络权重 (weithgs) 和偏置 (bias) 都在这个网络之内。接下来看看另一个作为对比的网络,它使用 Python 自带的 list:

代码语言:javascript
复制
class net_modlist(nn.Module):
    def __init__(self):
        super(net_modlist, self).__init__()
        self.modlist = [
                       nn.Conv2d(1, 20, 5),
                       nn.ReLU(),
                        nn.Conv2d(20, 64, 5),
                        nn.ReLU()
                        ]

    def forward(self, x):
        for m in self.modlist:
            x = m(x)
        return x

net_modlist = net_modlist()
print(net_modlist)
#net_modlist()
for param in net_modlist.parameters():
    print(type(param.data), param.size())
#None

 显然,使用 Python 的 list 添加的卷积层和它们的 parameters 并没有自动注册到我们的网络中。当然,我们还是可以使用 forward 来计算输出结果。但是如果用其实例化的网络进行训练的时候,因为这些层的parameters不在整个网络之中,所以其网络参数也不会被更新,也就是无法训练。

三、nn.Sequential与nn.ModuleList的区别

不同点1:

nn.Sequential内部实现了forward函数,因此可以不用写forward函数。而nn.ModuleList则没有实现内部forward函数。

对于nn.Sequential:

代码语言:javascript
复制
#例1:这是来自官方文档的例子
seq = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )
print(seq)
# Sequential(
#   (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#   (1): ReLU()
#   (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#   (3): ReLU()
# )

#对上述seq进行输入
input = torch.randn(16, 1, 20, 20)
print(seq(input))
#torch.Size([16, 64, 12, 12])

#例2:或者继承nn.Module类的话,就要写出forward函数
class net1(nn.Module):
    def __init__(self):
        super(net1, self).__init__()
        self.seq = nn.Sequential(
                        nn.Conv2d(1,20,5),
                         nn.ReLU(),
                          nn.Conv2d(20,64,5),
                       nn.ReLU()
                       )      
    def forward(self, x):
        return self.seq(x)

    #注意:按照下面这种利用for循环的方式也是可以得到同样结果的
    #def forward(self, x):
    #    for s in self.seq:
    #        x = s(x)
    #    return x

 #对net1进行输入
input = torch.randn(16, 1, 20, 20)
net1 = net1()
print(net1(input).shape)
#torch.Size([16, 64, 12, 12])

而对于nn.ModuleList:

代码语言:javascript
复制
#例1:若按照下面这么写,则会产生错误
modlist = nn.ModuleList([
         nn.Conv2d(1, 20, 5),
         nn.ReLU(),
         nn.Conv2d(20, 64, 5),
         nn.ReLU()
         ])
print(modlist)
#ModuleList(
#  (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#  (1): ReLU()
#  (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#  (3): ReLU()
#)

input = torch.randn(16, 1, 20, 20)
print(modlist(input))
#产生NotImplementedError

#例2:写出forward函数
class net2(nn.Module):
    def __init__(self):
        super(net2, self).__init__()
        self.modlist = nn.ModuleList([
                       nn.Conv2d(1, 20, 5),
                       nn.ReLU(),
                        nn.Conv2d(20, 64, 5),
                        nn.ReLU()
                        ])

    #这里若按照这种写法则会报NotImplementedError错
    #def forward(self, x):
    #    return self.modlist(x)

    #注意:只能按照下面利用for循环的方式
    def forward(self, x):
        for m in self.modlist:
            x = m(x)
        return x

input = torch.randn(16, 1, 20, 20)
net2 = net2()
print(net2(input).shape)
#torch.Size([16, 64, 12, 12])

如果完全直接用 nn.Sequential,确实是可以的,但这么做的代价就是失去了部分灵活性,不能自己去定制 forward 函数里面的内容了。

一般情况下 nn.Sequential 的用法是来组成卷积块 (block),然后像拼积木一样把不同的 block 拼成整个网络,让代码更简洁,更加结构化。

不同点2:

nn.Sequential可以使用OrderedDict对每层进行命名,上面已经阐述过了;

不同点3:

nn.Sequential里面的模块按照顺序进行排列的,所以必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。而nn.ModuleList 并没有定义一个网络,它只是将不同的模块储存在一起,这些模块之间并没有什么先后顺序可言。见下面代码:

代码语言:javascript
复制
class net3(nn.Module):
    def __init__(self):
        super(net3, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(10,20), nn.Linear(20,30), nn.Linear(5,10)])
    def forward(self, x):
        x = self.linears[2](x)
        x = self.linears[0](x)
        x = self.linears[1](x)

        return x

net3 = net3()
print(net3)
#net3(
#  (linears): ModuleList(
#    (0): Linear(in_features=10, out_features=20, bias=True)
#    (1): Linear(in_features=20, out_features=30, bias=True)
#    (2): Linear(in_features=5, out_features=10, bias=True)
#  )
#)

input = torch.randn(32, 5)
print(net3(input).shape)
#torch.Size([32, 30])

根据 net5 的结果,可以看出来这个 ModuleList 里面的顺序不能决定什么,网络的执行顺序是根据 forward 函数来决定的。若将forward函数中几行代码互换,使输入输出之间的大小不一致,则程序会报错。此外,为了使代码具有更高的可读性,最好把ModuleList和forward中的顺序保持一致。

不同点4:

有的时候网络中有很多相似或者重复的层,我们一般会考虑用 for 循环来创建它们,而不是一行一行地写,比如:

代码语言:javascript
复制
layers = [nn.Linear(10, 10) for i in range(5)]

那么这里我们使用ModuleList:

代码语言:javascript
复制
class net4(nn.Module):
    def __init__(self):
        super(net4, self).__init__()
        layers = [nn.Linear(10, 10) for i in range(5)]
        self.linears = nn.ModuleList(layers)

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

net = net4()
print(net)
# net4(
#   (linears): ModuleList(
#     (0): Linear(in_features=10, out_features=10, bias=True)
#     (1): Linear(in_features=10, out_features=10, bias=True)
#     (2): Linear(in_features=10, out_features=10, bias=True)
#   )
# )
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、官方文档
  • CLASS torch.nn.ModuleList(modules=None)
  • 二、nn.Sequential与nn.ModuleList简介
    • 1、nn.Sequential
      •  2、nn.ModuleList
      • 三、nn.Sequential与nn.ModuleList的区别
        • 不同点1:
          • 不同点2:
            • 不同点3:
              • 不同点4:
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档