神经网络学习小记录25——MobileNetV2模型的复现详解
学习前言什么是MobileNetV2模型MobileNetV2网络部分实现代码图片预测
学习前言
MobileNet它哥MobileNetV2也是很不错的呢。
什么是MobileNetV2模型
MobileNet模型是Google针对手机等嵌入式设备提出的一种轻量级的深层神经网络,其使用的核心思想便是depthwise separable convolution。
MobileNetV2是MobileNet的升级版,它具有两个特征点:
1、Inverted residuals,在ResNet50里我们认识到一个结构,bottleneck design结构,在3x3网络结构前利用1x1卷积降维,在3x3网络结构后,利用1x1卷积升维,相比直接使用3x3网络卷积效果更好,参数更少,先进行压缩,再进行扩张。而在MobileNetV2网络部分,其采用Inverted residuals结构,在3x3网络结构前利用1x1卷积升维,在3x3网络结构后,利用1x1卷积降维,先进行扩张,再进行压缩。
2、Linear bottlenecks,为了避免Relu对特征的破坏,在在3x3网络结构前利用1x1卷积升维,在3x3网络结构后,再利用1x1卷积降维后,不再进行Relu6层,直接进行残差网络的加法。
整体网络结构如下:(其中bottleneck进行的操作就是上述的创新操作)
MobileNetV2网络部分实现代码
import math
import numpy
as np
import tensorflow
as tf
from tensorflow
.keras
import backend
from keras
import backend
as K
from keras
.preprocessing
import image
from keras
.models
import Model
from keras
.layers
.normalization
import BatchNormalization
from keras
.layers
import Conv2D
, Add
, ZeroPadding2D
, GlobalAveragePooling2D
, Dropout
, Dense
from keras
.layers
import MaxPooling2D
,Activation
,DepthwiseConv2D
,Input
,GlobalMaxPooling2D
from keras
.applications
import imagenet_utils
from keras
.applications
.imagenet_utils
import decode_predictions
from keras
.utils
.data_utils
import get_file
BASE_WEIGHT_PATH
= ('https://github.com/JonathanCMitchell/mobilenet_v2_keras/'
'releases/download/v1.1/')
def relu6(x
):
return K
.relu
(x
, max_value
=6)
def correct_pad(inputs
, kernel_size
):
img_dim
= 1
input_size
= backend
.int_shape
(inputs
)[img_dim
:(img_dim
+ 2)]
if isinstance(kernel_size
, int):
kernel_size
= (kernel_size
, kernel_size
)
if input_size
[0] is None:
adjust
= (1, 1)
else:
adjust
= (1 - input_size
[0] % 2, 1 - input_size
[1] % 2)
correct
= (kernel_size
[0] // 2, kernel_size
[1] // 2)
return ((correct
[0] - adjust
[0], correct
[0]),
(correct
[1] - adjust
[1], correct
[1]))
def _make_divisible(v
, divisor
, min_value
=None):
if min_value
is None:
min_value
= divisor
new_v
= max(min_value
, int(v
+ divisor
/ 2) // divisor
* divisor
)
if new_v
< 0.9 * v
:
new_v
+= divisor
return new_v
def MobileNetV2(input_shape
=[224,224,3],
alpha
=1.0,
include_top
=True,
weights
='imagenet',
classes
=1000):
rows
= input_shape
[0]
img_input
= Input
(shape
=input_shape
)
first_block_filters
= _make_divisible
(32 * alpha
, 8)
x
= ZeroPadding2D
(padding
=correct_pad
(img_input
, 3),
name
='Conv1_pad')(img_input
)
x
= Conv2D
(first_block_filters
,
kernel_size
=3,
strides
=(2, 2),
padding
='valid',
use_bias
=False,
name
='Conv1')(x
)
x
= BatchNormalization
(epsilon
=1e-3,
momentum
=0.999,
name
='bn_Conv1')(x
)
x
= Activation
(relu6
, name
='Conv1_relu')(x
)
x
= _inverted_res_block
(x
, filters
=16, alpha
=alpha
, stride
=1,
expansion
=1, block_id
=0)
x
= _inverted_res_block
(x
, filters
=24, alpha
=alpha
, stride
=2,
expansion
=6, block_id
=1)
x
= _inverted_res_block
(x
, filters
=24, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=2)
x
= _inverted_res_block
(x
, filters
=32, alpha
=alpha
, stride
=2,
expansion
=6, block_id
=3)
x
= _inverted_res_block
(x
, filters
=32, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=4)
x
= _inverted_res_block
(x
, filters
=32, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=5)
x
= _inverted_res_block
(x
, filters
=64, alpha
=alpha
, stride
=2,
expansion
=6, block_id
=6)
x
= _inverted_res_block
(x
, filters
=64, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=7)
x
= _inverted_res_block
(x
, filters
=64, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=8)
x
= _inverted_res_block
(x
, filters
=64, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=9)
x
= _inverted_res_block
(x
, filters
=96, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=10)
x
= _inverted_res_block
(x
, filters
=96, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=11)
x
= _inverted_res_block
(x
, filters
=96, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=12)
x
= _inverted_res_block
(x
, filters
=160, alpha
=alpha
, stride
=2,
expansion
=6, block_id
=13)
x
= _inverted_res_block
(x
, filters
=160, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=14)
x
= _inverted_res_block
(x
, filters
=160, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=15)
x
= _inverted_res_block
(x
, filters
=320, alpha
=alpha
, stride
=1,
expansion
=6, block_id
=16)
if alpha
> 1.0:
last_block_filters
= _make_divisible
(1280 * alpha
, 8)
else:
last_block_filters
= 1280
x
= Conv2D
(last_block_filters
,
kernel_size
=1,
use_bias
=False,
name
='Conv_1')(x
)
x
= BatchNormalization
(epsilon
=1e-3,
momentum
=0.999,
name
='Conv_1_bn')(x
)
x
= Activation
(relu6
, name
='out_relu')(x
)
x
= GlobalAveragePooling2D
()(x
)
x
= Dense
(classes
, activation
='softmax',
use_bias
=True, name
='Logits')(x
)
inputs
= img_input
model
= Model
(inputs
, x
, name
='mobilenetv2_%0.2f_%s' % (alpha
, rows
))
if weights
== 'imagenet':
if include_top
:
model_name
= ('mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' +
str(alpha
) + '_' + str(rows
) + '.h5')
weight_path
= BASE_WEIGHT_PATH
+ model_name
weights_path
= get_file
(
model_name
, weight_path
, cache_subdir
='models')
else:
model_name
= ('mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' +
str(alpha
) + '_' + str(rows
) + '_no_top' + '.h5')
weight_path
= BASE_WEIGHT_PATH
+ model_name
weights_path
= get_file
(
model_name
, weight_path
, cache_subdir
='models')
model
.load_weights
(weights_path
)
elif weights
is not None:
model
.load_weights
(weights
)
return model
def _inverted_res_block(inputs
, expansion
, stride
, alpha
, filters
, block_id
):
in_channels
= backend
.int_shape
(inputs
)[-1]
pointwise_conv_filters
= int(filters
* alpha
)
pointwise_filters
= _make_divisible
(pointwise_conv_filters
, 8)
x
= inputs
prefix
= 'block_{}_'.format(block_id
)
if block_id
:
x
= Conv2D
(expansion
* in_channels
,
kernel_size
=1,
padding
='same',
use_bias
=False,
activation
=None,
name
=prefix
+ 'expand')(x
)
x
= BatchNormalization
(epsilon
=1e-3,
momentum
=0.999,
name
=prefix
+ 'expand_BN')(x
)
x
= Activation
(relu6
, name
=prefix
+ 'expand_relu')(x
)
else:
prefix
= 'expanded_conv_'
if stride
== 2:
x
= ZeroPadding2D
(padding
=correct_pad
(x
, 3),
name
=prefix
+ 'pad')(x
)
x
= DepthwiseConv2D
(kernel_size
=3,
strides
=stride
,
activation
=None,
use_bias
=False,
padding
='same' if stride
== 1 else 'valid',
name
=prefix
+ 'depthwise')(x
)
x
= BatchNormalization
(epsilon
=1e-3,
momentum
=0.999,
name
=prefix
+ 'depthwise_BN')(x
)
x
= Activation
(relu6
, name
=prefix
+ 'depthwise_relu')(x
)
x
= Conv2D
(pointwise_filters
,
kernel_size
=1,
padding
='same',
use_bias
=False,
activation
=None,
name
=prefix
+ 'project')(x
)
x
= BatchNormalization
(epsilon
=1e-3,
momentum
=0.999,
name
=prefix
+ 'project_BN')(x
)
if in_channels
== pointwise_filters
and stride
== 1:
return Add
(name
=prefix
+ 'add')([inputs
, x
])
return x
图片预测
建立网络后,可以用以下的代码进行预测。
def preprocess_input(x
):
x
/= 255.
x
-= 0.5
x
*= 2.
return x
if __name__
== '__main__':
model
= MobileNetV2
(input_shape
=(224, 224, 3))
model
.summary
()
img_path
= 'elephant.jpg'
img
= image
.load_img
(img_path
, target_size
=(224, 224))
x
= image
.img_to_array
(img
)
x
= np
.expand_dims
(x
, axis
=0)
x
= preprocess_input
(x
)
print('Input image shape:', x
.shape
)
preds
= model
.predict
(x
)
print(np
.argmax
(preds
))
print('Predicted:', decode_predictions
(preds
, 1))
预测所需的已经训练好的MobileNetV2模型会在运行时自动下载,下载后的模型位于C:\Users\Administrator.keras\models文件夹内。 可以修改MobileNetV2内不同的alpha值实现不同depth的MobileNetV2模型。可选的alpha值有:
Top-1Top-510-5SizeStem
MobileNetV2(alpha=0.35)39.91417.56815.4221.7M0.4MMobileNetV2(alpha=0.50)34.80613.93811.9762.0M0.7MMobileNetV2(alpha=0.75)30.46810.8249.1882.7M1.4MMobileNetV2(alpha=1.0)28.6649.8588.3223.5M2.3MMobileNetV2(alpha=1.3)25.3207.8786.7285.4M3.8M