前言
本文基于chainer实现MobileNet_V1网络结构,并基于torch的结构方式构建chainer版的,并计算MobileNet_V1的参数量。
代码实现
class Block(chainer.Chain):
def __init__(self, layers_num,in_planes, out_planes, stride=1,initialW=None):
super(Block, self).__init__()
self.layers=[]
# 深度卷积,通道数不变,用于缩小特征图大小 dw
self.layers += [('block{0}_conv1'.format(layers_num),L.Convolution2D(in_channels = in_planes, out_channels = in_planes, ksize=3, stride=stride, pad=1, groups=in_planes, nobias=True,initialW=initialW))]
self.layers += [('block{0}_bn1'.format(layers_num),L.BatchNormalization(in_planes))]
self.layers += [('_block{0}_relu1'.format(layers_num),ReLU())]
# 逐点卷积,用于增大通道数 pw
self.layers += [('block{0}_conv2'.format(layers_num),L.Convolution2D(in_channels = in_planes, out_channels = out_planes, ksize=1, stride=1, pad=0, nobias=True,initialW=initialW))]
self.layers += [('block{0}_bn2'.format(layers_num),L.BatchNormalization(out_planes))]
self.layers += [('_block{0}_relu2'.format(layers_num),ReLU())]
with self.init_scope():
for n in self.layers:
if not n[0].startswith('_'):
setattr(self, n[0], n[1])
def forward(self, x):
for n, f in self.layers:
if not n.startswith('_'):
x = getattr(self, n)(x)
else:
x = f.apply((x,))[0]
return x
class MobileNet_V1(chainer.Chain):
cfgs={
'mobilenetv1_1.0':{'alpha':1.0},
'mobilenetv1_0.75':{'alpha':0.75},
'mobilenetv1_0.5':{'alpha':0.5},
'mobilenetv1_0.25':{'alpha':0.25}
}
def _make_layers(self, model_name, in_planes,initialW=None,in_size=None):
list_layers = [64, (128,2), 128, (256,2), 256, (512,2), 512, 512, 512, 512, 512, (1024,2), 1024]
output_size = in_size
layers = []
layers_num=1
for x in list_layers:
out_planes = int((x if isinstance(x, int) else x[0])*self.cfgs[model_name]['alpha'])
stride = 1 if isinstance(x, int) else x[1]
layers += [("MVblock_{0}".format(layers_num),Block(layers_num,in_planes, out_planes, stride,initialW=initialW))]
in_planes = out_planes
layers_num += 1
output_size = int((output_size-3+2*1)/stride +1)
output_size = int((output_size-1+2*0)/1 +1)
return layers,output_size
def __init__(self, model_name='mobilenetv1_1.0',channels=3, num_classes=1000,image_size=224,initialW=chainer.initializers.HeNormal(), **kwargs):
super(MobileNet_V1, self).__init__()
self.image_size = image_size
self.features = []
# 首先是一个标准卷积:卷极+标准化+激活函数
self.features += [('conv2d_1',L.Convolution2D(in_channels = channels, out_channels = int(32*self.cfgs[model_name]['alpha']), ksize=3, stride=2, pad=1, nobias=True,initialW=initialW))]
output_size = int((self.image_size-3+2*1)/2 +1)
self.features += [('bn1',L.BatchNormalization(int(32*self.cfgs[model_name]['alpha'])))]
self.features += [('_relu1',ReLU())]
# 然后堆叠深度可分离卷积
layers,output_size = self._make_layers(model_name=model_name,in_planes=int(32*self.cfgs[model_name]['alpha']),initialW=initialW,in_size=output_size)
self.features += layers
# 全局平均池化层会将feature 展平
self.features += [('_avgpool',AveragePooling2D(ksize=output_size,stride=2,pad=0))]
self.features += [('fc',L.Linear(int(1024*self.cfgs[model_name]['alpha']), num_classes,initialW=initialW))]
with self.init_scope():
for n in self.features:
if not n[0].startswith('_'):
setattr(self, n[0], n[1])
def forward(self, x):
for n, f in self.features:
origin_size = x.shape
if not n.startswith('_'):
x = getattr(self, n)(x)
else:
x = f.apply((x,))[0]
print(n,origin_size,x.shape)
if chainer.config.train:
return x
return F.softmax(x)
注意此类就是MobileNet_V1的实现过程,注意网络的前向传播过程中,分了训练以及测试。
训练过程中直接返回x,测试过程中会进入softmax得出概率
调用方式
if __name__ == '__main__':
batch_size = 4
n_channels = 3
image_size = 224
num_classes = 123
model_simple = MobileNet_V1(num_classes=num_classes, channels=n_channels,image_size=image_size)
print(model_simple.count_params())
x = np.random.rand(batch_size, n_channels, image_size, image_size).astype(np.float32)
t = np.random.randint(0, num_classes, size=(batch_size,)).astype(np.int32)
with chainer.using_config('train', True):
y1 = model_simple(x)
loss1 = F.softmax_cross_entropy(y1, t)
print(loss1.data)