代码之家  ›  专栏  ›  技术社区  ›  Blue Bird

使用Sequential()或HybridSequential()仅作为容器是否有任何副作用?

  •  1
  • Blue Bird  · 技术社区  · 7 年前

    我正在阅读有关MXnet的教程。编写者使用__mxnet.gluon.nn.sequential()_trade作为容器来存储一些块(参见代码1);然后,他们在__def forward(self,x)_trade中重写块的连接(参见代码2和3)。这样做有什么副作用吗?顺便说一句,sequential()和hybridseconomic()之间的区别是什么?我尝试用一个列表来替换__Sequential_,然后在初始化过程中收到以下警告。

    下采样器是一个装有积木的容器。请注意,列表、元组或dict中的块不会自动注册。 确保使用register_child()或切换到 nn.sequential/nn.hybridsecquential。

    据我所知,如果在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
    
    1 回复  |  直到 7 年前
        1
  •  1
  •   Indhu Bharathi    7 年前

    在胶子中,网络是用 Block S.如果某物不是 街区 它不能是胶子网络的一部分。致密层为 街区 ,卷积是 街区 ,池层是 街区 等。

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

    Conv2D -> MaxPool2D -> Conv2D -> MaxPool2D -> Flatten -> Dense -> Dense

    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))
    

    当您创建这样的序列时,您可以使用 HybridSequential Sequential . 为了理解差异,你需要理解 difference between symbolic and imperative programming .

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

    块是否混合取决于如何实现。几乎所有预先定义的胶子块也是杂交块。有时,有些块不能混合是有原因的。 Tree LSTM 就是一个例子。更常见的是,有些东西不是混合型的,只是因为不管是谁写的,它并没有努力使它混合,有几个原因(例如:也许使它混合不会提供大的性能提升,或者可能是很难使块混合)。

    注意 相继的 混合序列 不仅仅是像蟒蛇这样的容器 list . 当您使用其中一个时,实际上是在创建一个新的 街区 使用预先存在的块。这就是为什么你不能替换 相继的 使用python 列表 .

    好吧,那么你知道如何通过把以前存在的块串在一起来创建你自己的块。很好。如果您不想只通过一系列块传递数据呢?如果您想有条件地通过这些块中的一个来传递数据,该怎么办?下面是来自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
    

    此代码使用先前存在的胶接块创建新块。但它不仅仅是通过一些以前存在的块运行数据。给定一些数据后,块将数据运行到主体中 block 阿威。然后,运行数据 downsample 仅当此块是用 降低采样 设置为真。然后确定 body 降低采样 创建输出。正如您所看到的,不仅仅是通过一系列块传递数据,还有更多的事情发生。这是通过子类化创建自己的块的时候 混合块 街区 .

    注意, __init__ 函数创建了必要的块和 forward 函数获取输入并通过在中创建的块运行输入。 _初始化__ . 向前地 不修改在中创建的块 _初始化__ . 它只通过在中创建的块运行数据。 _初始化__ .

    在引用的示例中,第一个代码块创建的块如下 downsamplers , class_predictors , box_predictors . 代码块2和3中的forward函数不修改这些块。它们只是通过这些块传递输入数据。