mobilenet v2 keras实现
from keras import backend, Input, Model from keras.layers import Conv2D, BatchNormalization, Activation, ZeroPadding2D, DepthwiseConv2D, Add # relu6 relu6与relu的区别是relu6在relu的基础上增加了最大值抑制 即经过激活函数函数relu,最大值为6 def relu6(x): return backend.relu(x, max_value=6) # 计算padding的大小 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])) # 使其结果可以被8整除,因为使用到了膨胀系数α 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 _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, 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+ 'deptwise')(x) x = BatchNormalization(epsilon=1e-3, name=prefix + 'depthwise_BN')(x) x = Activation(relu6, name=prefix + 'deptwise_relu')(x) # part3 压缩特征,而且不使用relu函数,保证特征不被破坏 x = Conv2D(pointwise_filters, kernel_size=1, padding='same', use_bias=False, activation=None, name=prefix + 'project')(x) x = BatchNormalization(epsilon=1e-3, name=prefix + 'project_BN')(x) if in_channels == pointwise_filters and stride == 1: return Add(name=prefix + 'add')([inputs, x]) return x def MobileNetV2(inputs, alpha=1.0): # stem部分 first_block_filters = _make_divisible(32 * alpha, 8) x = ZeroPadding2D(padding=correct_pad(inputs=inputs, kernel_size=3), name='Conv_pad')(inputs) # 416, 416, 3 -> 208,208,32 x = Conv2D(first_block_filters, kernel_size=3, strides=(2, 2), padding='valid', use_bias=False, name='Conv1')(x) x = BatchNormalization(epsilon=1e-3, name='bn_Conv1')(x) x = Activation(relu6, name='Conv_relu')(x) # 208,208, 32 -> 208,208,16 x = _inverted_res_block(x, filters=16, alpha=alpha, stride=1, expansion=1,block_id=0) # 208, 208, 16 -> 104,104,24 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) # 104,104,24 -> 52,52,32 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) feat1 = x # 52,52,32 -> 26,26,96 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=96, 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) feat2 = x # 26,26,96 -> 13,13,320 x = _inverted_res_block(x, filters=160, alpha=alpha, stride=2, expansion=6, block_id=13) x = _inverted_res_block(x, filters=1600, 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) feat3 = x return feat1, feat2, feat3 if __name__ == '__main__': # 代码来自@Bubbliiiing inputs = Input(shape=(416,416, 3)) model = MobileNetV2(inputs, ) model = Model(inputs, model) print(model.output[0])
在之前的yolo v3 tensorflow代码中,将model.py中的yolo_body替换成下面代码
def yolo_body(inputs, num_anchors, num_classes): ''' 自定义 yolobody 替换成mobilenet v2 ''' darknet = Model(inputs, MobileNetV2(inputs)) x, y1 = make_last_layers(darknet.output[2], 512, num_anchors * (num_classes + 5)) x = compose( DarknetConv2D_BN_Leaky(256, (1, 1)), UpSampling2D(2))(x) x = Concatenate()([x, darknet.output[1]]) x, y2 = make_last_layers(x, 256, num_anchors * (num_classes + 5)) x = compose( DarknetConv2D_BN_Leaky(128, (1, 1)), UpSampling2D(2))(x) x = Concatenate()([x, darknet.output[0]]) x, y3 = make_last_layers(x, 128, num_anchors * (num_classes + 5)) return Model(inputs, [y1, y2, y3])
在train.py中替换代码
# 原始代码 model = create_model(input_shape, anchors, num_classes, freeze_body=2, weights_path='model_data/yolo_weights.h5') # make sure you know what you freeze # 替换之后的代码 model = create_model(input_shape, anchors, num_classes, load_pretrained=False ,freeze_body=0)
重新训练
结果如下图: