BMXNet的Stem Conv层显然更大一些,用了7x7的kernel,具体的顺序是BN-Conv-BN-ReLU-Maxpooling-BN
,这和原文描述的把一个Conv层拆成两个小Conv层的设置不一样啊。
AWNAS中的Stem Conv是拆成两个3x3的小kernel,具体顺序是Conv-BN-ReLU-Conv-BN
。
果然cell里也出现了非常明显的不一致,甚至感觉非常合理。
AWNAS中的一个cell很大(而且有两个op?我需要好好看看ResNet18的结构了),相比之下BMXNet的cell就显得非常干瘪。 原因找到了,借用一下一张网图(准确性存疑),AWNAS是把skip connection连接的层layer合成了一个cell,BMXNet是裸着把Conv layer堆在一起:
使用ipdb
打断点看net variable以及检查model.log发现了一些异常,需要看代码确认一下:
net
中看不到,需要进一步确认。A:preprocess是DARTS里的。net
的问题还是写的代码就是如此,原文中是BN-ReLU-Conv-Pool
,net
中显示的是BN-QActivation-Conv-BN
Pool
?而且AWNAS里Stem后面的BN
能不能和第一个Block开始的BN
合并到一起?最后一个layer的顺序就有问题了,AWNAS依然是BN-ReLU-Conv
最后跟一个global pooling,BMXNet最后是qact-qconv-BN-ReLU
最后接global pooling。shortcut
感觉有点奇怪,需要再看看到底有没有起作用。还是很有点奇怪。
downsaple
不是一个(64,128)的Conv
,是两个(64,64)的Conv
并列??之后又分别接了两个AvePool
。这里连接64/128的skip op是FP的。BMXNet的情况就很有些奇怪,他已经有个qconv
是64 -> 128的操作了,后面又来了个downsample layer?而且还是64 -> 128,同时使用FP Conv
?这边暂时猜测它是描述的cell-wise skip op,但是出现的位置还是感觉很奇怪(在必经之路上?)。resnet_spec = {18: ([2, 2, 2, 2], [64, 64, 128, 256, 512]),
34: ([3, 4, 6, 3], [64, 64, 128, 256, 512]),
50: ([3, 4, 6, 3], [64, 256, 512, 1024, 2048]),
101: ([3, 4, 23, 3], [64, 256, 512, 1024, 2048]),
152: ([3, 8, 36, 3], [64, 256, 512, 1024, 2048])}
第一个list是"layers",第二个list是"channels",观察上面的大网图,layers(的每一个element)应该是里面不同颜色的块(对应AWNAS里的cell),channels指的大概是算上stem各个layer中的channel。
class ResNetE1(ResNetE)
和class ResNetE(HybridBlock)
其实是一回事,只是有个分开定义的关系,ResNetE
里定义了make_layer
,和forward
方法,ResNetE1
里反复调用了四次make_layer
。但是实际上input还是从ResNetE1
中贯穿过去的,在block之间没有shortcut(起码没写在这里,即x = self.features(x)
和x = self.output(x)
,其中self.output()
是 flatten
之后的FC层)。一个make_layer
对应示意图中的一个颜色色块(两个layer,每个layer又是一层conv,因为[2, 2, 2, 2])。make_layer
具体实现的时候是把layer * 2 变成4,每个layer用一次class BasicBlockE1
,所以一个BasicBlockE1
表示一个layer。BMXNet-v2/example/bmxnet-examples/image_classification.py(86)get_model()
。原文是: with gluon.nn.set_binary_layer_config(bits=opt.bits, bits_a=opt.bits_a,approximation=opt.approximation,
grad_cancel=opt.clip_threshold,activation=opt.activation_method,
weight_quantization=optweight_quantization):
net = binary_models.get_model(opt.model, **kwargs)
binary_models.get_model(opt.model, **kwargs)
的方法写在了/BMXNet-v2/example/bmxnet-examples/binary_models/__init__.py
里,里面写了个dict,根据name索引对应的class,返回class的构造方法。用法和下一节中的 New perspective of Python Class 描述一致。(不是很一致,见下)
通过字典索引了方法resnet18_e1
,方法里进一步套了一个方法get_resnet_e
,在这个方法里才用到了下节说的字典存类名,即net = resnet_class(block_class, layers, channels, **kwargs)
。
self.features
是在ResNetE
类的__init__()
方法里添加的。add_initial_layers
方法完成的,它在BMXNet-v2/example/bmxnet-examples/binary_models/common_layers.py
里。nn.activated_conv
的位置在BMXNet-v2/python/mxnet/gluon/nn/binary_layers.py(343)__call__()
。QActivation
和QConv2D
都在BMXNet-v2/python/mxnet/gluon/nn/binary_layers.py
里。在单步看BMXNet的时候发现了奇怪的现象:
上面用resnet_class
作为function,但是上下文并没有这个方法或者类。将断点打到这句的上面:
发现这个变量是一个Class,来源是这里:
这里写了一个装有两个元组的List作为索引,元组中的第一个元素就是上文出现的类,后面把这个类赋给了变量resnet_class
(应该是作为func用了?用到了它的__init__
),就在这里神不知鬼不觉地把model给建起来了。
收获:原来类名可以当值传来传去啊,也合理,像个指针一样?
2021/4/7补充:原来binary_models/__init__.py
的字典也是这种用法!通过name索引类名,返回的时候是通过类名使用类的构造方法!
有点不一样,这里是用字典索引方法名!
在resnet_e.py
文件中又这么一段:
···
def _init(self):
self.body.add(nn.activated_conv(self.slice_width, kernel_size=3, stride=self.stride,
padding=1, in_channels=self.in_channels))
···
这里在self.body
(model的一部分)里加了一层nn.activated_conv
,但实际上并没有这个东西?用ipdb单步进去看,发现程序流跳到了BMXNet-v2/python/mxnet/gluon/nn/binary_layers.py(343)__call__()
,到这个文件里看,找到了这么个Factory:
class ActivatedConvolutionFactory:
def __call__(self, *args, **kwargs):
if binary_layer_config.approximation == "binet":
return ScaledWeightsBinaryConv(*args, **kwargs)
if binary_layer_config.approximation == "xnor":
return ScaledBinaryConv(*args, **kwargs)
return BinaryConvolution(*args, **kwargs)
activated_conv = ActivatedConvolutionFactory()
有点酷,把这个Factory类写成了可调用的形式,然后起了个别名activated_conv
,在里面根据索引binary_layer_config
(这个应该就是with关键字后面跟的,见BMXNet-v2/example/bmxnet-examples/image_classification.py
)挑选对应的Conv。