【问题标题】:Is there any side effect by using ‘Sequential()’ or ‘HybridSequential()’ as a container only?仅将“Sequential()”或“HybridSequential()”用作容器是否有任何副作用?
【发布时间】:2018-05-22 01:04:44
【问题描述】:

我正在阅读有关 MxNet 的教程。作者使用“mxnet.gluon.nn.Sequential()”作为容器来存储一些块(参见代码 1);然后,他们重写了“def forward(self, x)”中的块连接(参见代码 2 和 3)。这样做有什么副作用吗?顺便说一下,‘Sequential()’和‘HybridSequential()’有什么区别。我尝试使用一个列表来替换“顺序”,并且在初始化过程中收到以下警告。

“ToySSD.downsamplers”是一个带有块的容器。请注意,列表、元组或字典中的块不会自动注册。 确保使用 register_child() 注册它们或切换到 nn.Sequential/nn.HybridSequential 代替。'

据我所知,如果你在 'mxnet.gluon.nn.Sequential()' 或 'mxnet.gluon.nn.HybridSequential()' 中放入一些块,这个动作是告诉计算机这些块是连接的.但是,如果您在“前进”功能中设计块的关系,您就是在告诉计算机以另一种方式连接这些块。会不会造成混乱?如果我只在'forward'中设计一些块连接,'Sequential()'中没有在'forward'函数中设计的其他块的关系是什么?

整个教程可以在here找到。

代码1:

def toy_ssd_model(num_anchors, num_classes):
    downsamplers = nn.Sequential()
    for _ in range(3):
        downsamplers.add(down_sample(128))

    class_predictors = nn.Sequential()
    box_predictors = nn.Sequential()    
    for _ in range(5):
        class_predictors.add(class_predictor(num_anchors, num_classes))
        box_predictors.add(box_predictor(num_anchors))

    model = nn.Sequential()
    model.add(body(), downsamplers, class_predictors, box_predictors)
    return model

代码 2:

def toy_ssd_forward(x, model, sizes, ratios, verbose=False):    
    body, downsamplers, class_predictors, box_predictors = model
    anchors, class_preds, box_preds = [], [], []
    # feature extraction    
    x = body(x)
    for i in range(5):
        # predict
        anchors.append(MultiBoxPrior(
            x, sizes=sizes[i], ratios=ratios[i]))
        class_preds.append(
            flatten_prediction(class_predictors[i](x)))
        box_preds.append(
            flatten_prediction(box_predictors[i](x)))
        if verbose:
            print('Predict scale', i, x.shape, 'with', 
                  anchors[-1].shape[1], 'anchors')
        # down sample
        if i < 3:
            x = downsamplers[i](x)
        elif i == 3:
            x = nd.Pooling(
                x, global_pool=True, pool_type='max', 
                kernel=(x.shape[2], x.shape[3]))
    # concat data
    return (concat_predictions(anchors),
            concat_predictions(class_preds),
            concat_predictions(box_preds))

代码 3:

from mxnet import gluon
class ToySSD(gluon.Block):
    def __init__(self, num_classes, verbose=False, **kwargs):
        super(ToySSD, self).__init__(**kwargs)
        # anchor box sizes and ratios for 5 feature scales
        self.sizes = [[.2,.272], [.37,.447], [.54,.619], 
                      [.71,.79], [.88,.961]]
        self.ratios = [[1,2,.5]]*5
        self.num_classes = num_classes
        self.verbose = verbose
        num_anchors = len(self.sizes[0]) + len(self.ratios[0]) - 1
        # use name_scope to guard the names
        with self.name_scope():
            self.model = toy_ssd_model(num_anchors, num_classes)

    def forward(self, x):
        anchors, class_preds, box_preds = toy_ssd_forward(
            x, self.model, self.sizes, self.ratios, 
            verbose=self.verbose)
        # it is better to have class predictions reshaped for softmax computation       
        class_preds = class_preds.reshape(shape=(0, -1, self.num_classes+1))
        return anchors, class_preds, box_preds

【问题讨论】:

    标签: python deep-learning computer-vision mxnet


    【解决方案1】:

    在 Gluon 中,网络是使用 Blocks 构建的。如果某些东西不是Block,它就不能成为 Gluon 网络的一部分。密集层是Block,卷积层是Block,池化层是Block,等等

    有时您可能需要一个不是 Gluon 中预定义块而是一系列预定义 Gluon 块的块。例如,

    Conv2D -&gt; MaxPool2D -&gt; Conv2D -&gt; MaxPool2D -&gt; Flatten -&gt; Dense -&gt; Dense

    Gluon 没有执行上述操作序列的预定义块。但是 Gluon 确实具有执行每个单独操作的块。因此,您可以通过将预定义的 Gluon 块串在一起来创建自己的块来执行上述操作序列。示例:

    net = gluon.nn.HybridSequential()
    
    with net.name_scope():
    
        # First convolution
        net.add(gluon.nn.Conv2D(channels=20, kernel_size=5, activation='relu'))
        net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    
        # Second convolution
        net.add(gluon.nn.Conv2D(channels=50, kernel_size=5, activation='relu'))
        net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    
        # Flatten the output before the fully connected layers
        net.add(gluon.nn.Flatten())
    
        # First fully connected layers with 512 neurons
        net.add(gluon.nn.Dense(512, activation="relu"))
    
        # Second fully connected layer with as many neurons as the number of classes
        net.add(gluon.nn.Dense(num_outputs))
    

    当您创建这样的序列时,您可以使用HybridSequentialSequential。要了解区别,您需要了解difference between symbolic and imperative programming

    • HybridBlock 是一个可以转换为符号图以加快执行速度的块。 HybridSequentialHybrid 块的序列。
    • Blocks(不是混合的)是一个不能转换为符号图的块。 Sequential 是一系列非混合块。

    一个块是否是混合的取决于它是如何实现的。几乎所有预定义的 Gluon 块也是 HybridBlocks。有时有些块不能混合是有原因的。 Tree LSTM 就是一个例子。更常见的情况是,某些东西不是混合的,只是因为编写它的人没有努力使其成为混合,原因有几个(例如:可能使其混合不会带来很大的性能提升,或者可能很难使块混合)。

    请注意,SequentialHybridSequential 不仅仅是像 Python list 这样的容器。当您使用其中一个时,您实际上是在使用预先存在的块创建一个新的Block。这就是为什么您不能使用 Python list 替换 Sequential

    好的,所以您知道如何通过将预先存在的块串在一起来创建自己的块。好的。如果您不想只通过一系列块传递数据怎么办?如果您想有条件地通过这些块之一传递数据怎么办。这是来自 ResNet 的示例:

    class BasicBlockV1(HybridBlock):
        def __init__(self, channels, stride, downsample=False, in_channels=0, **kwargs):
            super(BasicBlockV1, self).__init__(**kwargs)
            self.body = nn.HybridSequential(prefix='')
            self.body.add(_conv3x3(channels, stride, in_channels))
            self.body.add(nn.BatchNorm())
            self.body.add(nn.Activation('relu'))
            self.body.add(_conv3x3(channels, 1, channels))
            self.body.add(nn.BatchNorm())
            if downsample:
                self.downsample = nn.HybridSequential(prefix='')
                self.downsample.add(nn.Conv2D(channels, kernel_size=1, strides=stride,
                                              use_bias=False, in_channels=in_channels))
                self.downsample.add(nn.BatchNorm())
            else:
                self.downsample = None
    
        def hybrid_forward(self, F, x):
            residual = x
    
            x = self.body(x)
    
            if self.downsample:
                residual = self.downsample(residual)
    
            x = F.Activation(residual+x, act_type='relu')
    
            return x
    

    此代码使用预先存在的 Gluon 块创建一个新块。但它不仅仅是通过一些预先存在的块运行数据。给定一些数据,该块通过 body block aways 运行数据。但是,只有在创建此 Block 且将 downsample 设置为 true 时,才通过 downsample 运行数据。然后它连接bodydownsample 的输出以创建输出。就像您可以看到的那样,发生的不仅仅是通过一系列块传递数据。这是当您通过子类化 HybridBlockBlock 创建自己的块时。

    请注意,__init__ 函数创建了必要的块,forward 函数获取输入并通过在__init__ 中创建的块运行输入。 forward 不会修改在 __init__ 中创建的块。它仅通过__init__ 中创建的块运行数据。

    在您引用的示例中,第一个代码块创建了 downsamplersclass_predictorsbox_predictors 之类的块。代码块 2 和 3 中的转发函数不会修改这些块。它们只是通过这些块传递输入数据。

    【讨论】:

    • 感谢符号和命令式编程的介绍。我推断“mxnet”试图将符号编程和命令式编程的优点结合起来。它是名称“mxnet”的派生吗?令人兴奋的是,当使用“顺序”构建特定层时,我可以使用索引来绘制它。 “mxnet”主页中的教程显示了一个类似的示例 (gluon-crash-course.mxnet.io/nn.html)。但是,具有设计前向功能的层不能支持索引。 net[0]->>'ToySSD' 对象不支持索引我是不是误会了什么?
    • 而且,我可以使用索引检索特定层的中间输出,例如权重吗?
    • 另一个原因是 mxNet 神奇地处理了导数。在 MATLAB、Maple 或 Mathematica 中,当我们写下符号方程时,它就会被设计出来。但是,在 mxNet 中执行计算时,正在设计符号方程。当您将某些内容放入“mxnet.gluon.nn.Sequential()”或“mxnet.gluon.nn.HybridSequential()”时,mxNet 不知道如何计算导数,直到您向它展示如何执行前向计算。该文档包含一个位于“gluon.mxnet.io/chapter01_crashcourse/autograd.html”的示例。
    猜你喜欢
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    • 2012-07-10
    • 1970-01-01
    • 2023-04-08
    • 2021-03-24
    • 2013-07-14
    • 2015-06-03
    相关资源
    最近更新 更多