仅使用“Sequential()”或“HybridSequential()”作为容器会有任何副作用吗?
Is there any side effect by using ‘Sequential()’ or ‘HybridSequential()’ as a container only?
我正在阅读有关 MxNet 的教程。编写者使用‘mxnet.gluon.nn.Sequential()’作为容器来存储一些块(见代码1);然后,他们重写了“def forward(self, x)”中块的连接(参见代码 2 和 3)。这样做有什么副作用吗?顺便问一下,‘Sequential()’和‘HybridSequential()’有什么区别。我尝试用一个列表来替换“顺序”,然后在初始化过程中收到以下警告。
“ToySSD.downsamplers” is a container with Blocks. Note that Blocks inside the list, tuple or dict will not be registered automatically.
Make sure to register them using register_child() or switching to
nn.Sequential/nn.HybridSequential instead.’
据我所知,如果你把一些块放在'mxnet.gluon.nn.Sequential()'或'mxnet.gluon.nn.HybridSequential()'中,这个动作就是告诉计算机这些块是连接的。但是,如果你在“forward”功能中设计块的关系,你就是在告诉计算机以另一种方式连接这些块。会导致混乱吗?如果我只在‘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
在 Gluon 中,网络是使用 Block
构建的。如果某物不是 Block
,则它不能成为 Gluon 网络的一部分。密集层是一个Block
,卷积层是一个Block
,池化层是一个Block
,等等
有时您可能需要一个 Block,它不是 Gluon 中的预定义块,而是一系列预定义的 Gluon 块。例如,
Conv2D -> MaxPool2D -> Conv2D -> MaxPool2D -> Flatten -> Dense -> 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))
当您创建这样的序列时,您可以使用 HybridSequential
或 Sequential
。要了解差异,您需要了解 difference between symbolic and imperative programming.
HybridBlock
是一个块,可以转换为符号图以加快执行速度。 HybridSequential
是 Hybrid
个块的序列。
Blocks
(不是混合的)是一个不能转换成符号图的块。 Sequential
是一系列非混合块。
一个区块是否是混合的取决于它是如何实现的。几乎所有预定义的 Gluon 块也是 HybridBlocks。有时有些块不能混合是有原因的。 Tree LSTM 就是一个例子。更常见的是,某些东西不是 Hybrid 只是因为编写它的人出于多种原因没有努力使其成为 Hybrid(例如:也许使其成为 Hybrid 不会带来很大的性能提升,或者可能很难使 block hybrid ).
请注意 Sequential
和 HybridSequential
不仅仅是像 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 块创建一个新块。但它不仅仅是 运行 通过一些预先存在的块来处理数据。给定一些数据,该块通过主体 block
运行数据。但是,仅当此块是在 downsample
设置为 true 的情况下创建时,才通过 downsample
运行数据。然后它连接 body
和 downsample
的输出以创建输出。正如您所看到的,发生的不仅仅是通过一系列块传递数据。这是当您通过子类化 HybridBlock
或 Block
.
创建自己的块时
请注意,__init__
函数创建了必要的块,forward
函数获取输入并通过在 __init__
中创建的块运行输入。 forward
不会修改在 __init__
中创建的块。它仅通过在 __init__
.
中创建的块运行数据
在您引用的示例中,第一个代码块创建了 downsamplers
、class_predictors
、box_predictors
等块。代码块 2 和 3 中的转发函数不会修改这些块。他们只是通过这些块传递输入数据。
我正在阅读有关 MxNet 的教程。编写者使用‘mxnet.gluon.nn.Sequential()’作为容器来存储一些块(见代码1);然后,他们重写了“def forward(self, x)”中块的连接(参见代码 2 和 3)。这样做有什么副作用吗?顺便问一下,‘Sequential()’和‘HybridSequential()’有什么区别。我尝试用一个列表来替换“顺序”,然后在初始化过程中收到以下警告。
“ToySSD.downsamplers” is a container with Blocks. Note that Blocks inside the list, tuple or dict will not be registered automatically. Make sure to register them using register_child() or switching to nn.Sequential/nn.HybridSequential instead.’
据我所知,如果你把一些块放在'mxnet.gluon.nn.Sequential()'或'mxnet.gluon.nn.HybridSequential()'中,这个动作就是告诉计算机这些块是连接的。但是,如果你在“forward”功能中设计块的关系,你就是在告诉计算机以另一种方式连接这些块。会导致混乱吗?如果我只在‘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
在 Gluon 中,网络是使用 Block
构建的。如果某物不是 Block
,则它不能成为 Gluon 网络的一部分。密集层是一个Block
,卷积层是一个Block
,池化层是一个Block
,等等
有时您可能需要一个 Block,它不是 Gluon 中的预定义块,而是一系列预定义的 Gluon 块。例如,
Conv2D -> MaxPool2D -> Conv2D -> MaxPool2D -> Flatten -> Dense -> 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))
当您创建这样的序列时,您可以使用 HybridSequential
或 Sequential
。要了解差异,您需要了解 difference between symbolic and imperative programming.
HybridBlock
是一个块,可以转换为符号图以加快执行速度。HybridSequential
是Hybrid
个块的序列。Blocks
(不是混合的)是一个不能转换成符号图的块。Sequential
是一系列非混合块。
一个区块是否是混合的取决于它是如何实现的。几乎所有预定义的 Gluon 块也是 HybridBlocks。有时有些块不能混合是有原因的。 Tree LSTM 就是一个例子。更常见的是,某些东西不是 Hybrid 只是因为编写它的人出于多种原因没有努力使其成为 Hybrid(例如:也许使其成为 Hybrid 不会带来很大的性能提升,或者可能很难使 block hybrid ).
请注意 Sequential
和 HybridSequential
不仅仅是像 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 块创建一个新块。但它不仅仅是 运行 通过一些预先存在的块来处理数据。给定一些数据,该块通过主体 block
运行数据。但是,仅当此块是在 downsample
设置为 true 的情况下创建时,才通过 downsample
运行数据。然后它连接 body
和 downsample
的输出以创建输出。正如您所看到的,发生的不仅仅是通过一系列块传递数据。这是当您通过子类化 HybridBlock
或 Block
.
请注意,__init__
函数创建了必要的块,forward
函数获取输入并通过在 __init__
中创建的块运行输入。 forward
不会修改在 __init__
中创建的块。它仅通过在 __init__
.
在您引用的示例中,第一个代码块创建了 downsamplers
、class_predictors
、box_predictors
等块。代码块 2 和 3 中的转发函数不会修改这些块。他们只是通过这些块传递输入数据。