睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

人工智能61

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

学习前言

一起来看看如何利用mobilenet系列搭建yolov4目标检测平台。睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

源码下载

https://github.com/bubbliiiing/mobilenet-yolov4-tf2
喜欢的可以点个star噢。

网络替换实现思路

1、网络结构解析与替换思路解析

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
对于YoloV4而言,其整个网络结构可以分为三个部分。
分别是:
1、主干特征提取网络Backbone,对应图像上的CSPdarknet53
2、加强特征提取网络,对应图像上的SPP和PANet
3、预测网络YoloHead,利用获得到的特征进行预测

其中:
第一部分 主干特征提取网络的功能是进行 初步的特征提取,利用主干特征提取网络,我们可以获得三个 初步的有效特征层
第二部分 加强特征提取网络的功能是进行 加强的特征提取,利用加强特征提取网络,我们可以对三个 初步的有效特征层进行特征融合,提取出更好的特征,获得三个 更有效的有效特征层
第三部分 预测网络的功能是利用更有效的有效特整层获得预测结果。

在这三部分中,第1部分和第2部分可以更容易去修改。第3部分可修改内容不大,毕竟本身也只是3x3卷积和1x1卷积的组合。

mobilenet系列网络可用于进行分类,其主干部分的作用是进行特征提取,我们可以使用mobilenet系列网络代替yolov4当中的CSPdarknet53进行特征提取,将三个 初步的有效特征层相同shape的特征层进行加强特征提取,便可以将mobilenet系列替换进yolov4当中了。

; 2、mobilenet系列网络介绍

本文共用到三个主干特征提取网络,分别是mobilenetV1、mobilenetV2、mobilenetV3。

a、mobilenetV1介绍

MobileNet模型是Google针对手机等嵌入式设备提出的一种轻量级的深层神经网络,其使用的核心思想便是depthwise separable convolution(深度可分离卷积块)。

对于一个卷积点而言:
假设有一个3×3大小的卷积层,其输入通道为16、输出通道为32。具体为,32个3×3大小的卷积核会遍历16个通道中的每个数据,最后可得到所需的32个输出通道,所需参数为16×32×3×3=4608个。

应用深度可分离卷积结构块,用16个3×3大小的卷积核分别遍历16通道的数据,得到了16个特征图谱。在融合操作之前,接着用32个1×1大小的卷积核遍历这16个特征图谱,所需参数为16×3×3+16×32×1×1=656个。
可以看出来depthwise separable convolution可以减少模型的参数。

如下这张图就是depthwise separable convolution的结构
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
在建立模型的时候,可以使用Keras中的DepthwiseConv2D层实现深度可分离卷积,然后再利用1x1卷积调整channels数。

通俗地理解就是3x3的卷积核厚度只有一层,然后在输入张量上一层一层地滑动,每一次卷积完生成一个输出通道,当卷积完成后,在利用1x1的卷积调整厚度。

如下就是MobileNet的结构,其中Conv dw就是分层卷积,在其之后都会接一个1x1的卷积进行通道处理,
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
上图所示是的mobilenetV1-1的结构,我们可以设置mobilenetV1的alpha值改变它的通道数。

对于yolov4来讲,我们需要取出它的最后三个shape的有效特征层进行加强特征提取。

在代码中,我们取出了out1、out2、out3。


import numpy as np
import tensorflow as tf
import tensorflow.keras.backend as backend
from tensorflow.keras import backend as K
from tensorflow.keras.layers import (Activation, Add, BatchNormalization,
                                     Concatenate, Conv2D, DepthwiseConv2D,
                                     LeakyReLU, MaxPooling2D, UpSampling2D,
                                     ZeroPadding2D)
from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l2

def _depthwise_conv_block(inputs, pointwise_conv_filters, alpha,
                          depth_multiplier=1, strides=(1, 1), block_id=1):

    pointwise_conv_filters = int(pointwise_conv_filters * alpha)

    x = DepthwiseConv2D((3, 3),
                        padding='same',
                        depth_multiplier=depth_multiplier,
                        strides=strides,
                        use_bias=False,
                        name='conv_dw_%d' % block_id)(inputs)
    x = BatchNormalization(name='conv_dw_%d_bn' % block_id)(x)
    x = Activation(relu6, name='conv_dw_%d_relu' % block_id)(x)

    x = Conv2D(pointwise_conv_filters, (1, 1),
               padding='same',
               use_bias=False,
               strides=(1, 1),
               name='conv_pw_%d' % block_id)(x)
    x = BatchNormalization(name='conv_pw_%d_bn' % block_id)(x)
    return Activation(relu6, name='conv_pw_%d_relu' % block_id)(x)

def _conv_block(inputs, filters, alpha, kernel=(3, 3), strides=(1, 1)):
    filters = int(filters * alpha)
    x = Conv2D(filters, kernel,
                      padding='same',
                      use_bias=False,
                      strides=strides,
                      name='conv1')(inputs)
    x = BatchNormalization(name='conv1_bn')(x)
    return Activation(relu6, name='conv1_relu')(x)

def relu6(x):
    return K.relu(x, max_value=6)

def MobileNetV1(inputs,alpha=1,depth_multiplier=1):
    if alpha not in [0.25, 0.5, 0.75, 1.0]:
        raise ValueError('Unsupported alpha - `{}` in MobilenetV1, Use 0.25, 0.5, 0.75, 1.0'.format(alpha))

    x = _conv_block(inputs, 32, alpha, strides=(2, 2))

    x = _depthwise_conv_block(x, 64, alpha, depth_multiplier, block_id=1)

    x = _depthwise_conv_block(x, 128, alpha, depth_multiplier,
                              strides=(2, 2), block_id=2)
    x = _depthwise_conv_block(x, 128, alpha, depth_multiplier, block_id=3)

    x = _depthwise_conv_block(x, 256, alpha, depth_multiplier,
                              strides=(2, 2), block_id=4)
    x = _depthwise_conv_block(x, 256, alpha, depth_multiplier, block_id=5)
    feat1 = x

    x = _depthwise_conv_block(x, 512, alpha, depth_multiplier,
                              strides=(2, 2), block_id=6)
    x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=7)
    x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=8)
    x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=9)
    x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=10)
    x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=11)
    feat2 = x

    x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier,
                              strides=(2, 2), block_id=12)
    x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier, block_id=13)
    feat3 = x

    return feat1,feat2,feat3

if __name__ == "__main__":
    from tensorflow.keras.layers import Input
    from tensorflow.keras.models import Model
    alpha   = 0.25
    inputs  = Input([None,None,3])
    outputs = MobileNetV1(inputs,alpha=alpha)
    model   = Model(inputs,outputs)
    model.summary()

b、mobilenetV2介绍

MobileNetV2是MobileNet的升级版,它具有一个非常重要的特点就是使用了Inverted resblock,整个mobilenetv2都由Inverted resblock组成。

Inverted resblock可以分为两个部分:
左边是主干部分, 首先利用1x1卷积进行升维,然后利用3x3深度可分离卷积进行特征提取,然后再利用1x1卷积降维
右边是残差边部分, 输入和输出直接相接
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

整体网络结构如下:(其中Inverted resblock进行的操作就是上述结构)
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台


import math

import numpy as np
import tensorflow as tf
from tensorflow.keras import backend
from tensorflow.keras.layers import (Activation, Add, BatchNormalization,
                                     Conv2D, Dense, DepthwiseConv2D, Dropout,
                                     GlobalAveragePooling2D,
                                     GlobalMaxPooling2D, Input, MaxPooling2D,
                                     ZeroPadding2D)
from tensorflow.keras.models import Model
from tensorflow.keras.preprocessing import image

def relu6(x):
    return backend.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 _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 MobileNetV2(inputs, alpha=1.0):
    if alpha not in [0.5, 0.75, 1.0, 1.3]:
        raise ValueError('Unsupported alpha - `{}` in MobilenetV2, Use 0.5, 0.75, 1.0, 1.3'.format(alpha))

    first_block_filters = _make_divisible(32 * alpha, 8)
    x = ZeroPadding2D(padding=correct_pad(inputs, 3),
                             name='Conv1_pad')(inputs)

    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)
    feat1 = x

    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)
    feat2 = x

    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)
    feat3 = x

    return feat1,feat2,feat3

c、mobilenetV3介绍

mobilenetV3使用了特殊的bneck结构。

bneck结构如下图所示:
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
它综合了以下四个特点:
a、MobileNetV2的具有线性瓶颈的逆残差结构(the inverted residual with linear bottleneck)。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
即先利用1x1卷积进行升维度,再进行下面的操作,并具有残差边。

b、MobileNetV1的深度可分离卷积(depthwise separable convolutions)。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
在输入1x1卷积进行升维度后,进行3x3深度可分离卷积。

c、轻量级的注意力模型。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
这个注意力机制的作用方式是调整每个通道的权重。

d、利用h-swish代替swish函数。
在结构中使用了h-swishj激活函数,代替swish函数,减少运算量,提高性能。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

下图为整个mobilenetV3的结构图:
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
如何看懂这个表呢?我们从每一列出发:
第一列Input代表mobilenetV3每个特征层的shape变化;
第二列Operator代表每次特征层即将经历的block结构,我们可以看到在MobileNetV3中,特征提取经过了许多的bneck结构;
第三、四列分别代表了bneck内逆残差结构上升后的通道数、输入到bneck时特征层的通道数。
第五列SE代表了是否在这一层引入注意力机制。
第六列NL代表了激活函数的种类,HS代表h-swish,RE代表RELU。
第七列s代表了每一次block结构所用的步长。

from tensorflow.keras import backend
from tensorflow.keras.layers import (Activation, Add, BatchNormalization,
                                     Conv2D, Dense, DepthwiseConv2D,
                                     GlobalAveragePooling2D, Input, Multiply,
                                     Reshape)
from tensorflow.keras.models import Model

def _activation(x, name='relu'):
    if name == 'relu':
        return Activation('relu')(x)
    elif name == 'hardswish':
        return hard_swish(x)

def hard_sigmoid(x):
    return backend.relu(x + 3.0, max_value=6.0) / 6.0

def hard_swish(x):
    return Multiply()([Activation(hard_sigmoid)(x), x])

def _make_divisible(v, divisor=8, 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 _bneck(inputs, expansion, alpha, out_ch, kernel_size, stride, se_ratio, activation,
                        block_id):
    channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1

    in_channels = backend.int_shape(inputs)[channel_axis]
    out_channels = _make_divisible(out_ch * alpha, 8)
    exp_size = _make_divisible(in_channels * expansion, 8)
    x = inputs
    prefix = 'expanded_conv/'
    if block_id:

        prefix = 'expanded_conv_{}/'.format(block_id)
        x = Conv2D(exp_size,
                          kernel_size=1,
                          padding='same',
                          use_bias=False,
                          name=prefix + 'expand')(x)
        x = BatchNormalization(axis=channel_axis,
                                      name=prefix + 'expand/BatchNorm')(x)
        x = _activation(x, activation)

    x = DepthwiseConv2D(kernel_size,
                               strides=stride,
                               padding='same',
                               dilation_rate=1,
                               use_bias=False,
                               name=prefix + 'depthwise')(x)
    x = BatchNormalization(axis=channel_axis,
                                  name=prefix + 'depthwise/BatchNorm')(x)
    x = _activation(x, activation)

    if se_ratio:
        reduced_ch = _make_divisible(exp_size * se_ratio, 8)
        y = GlobalAveragePooling2D(name=prefix + 'squeeze_excite/AvgPool')(x)
        y = Reshape([1, 1, exp_size], name=prefix + 'reshape')(y)
        y = Conv2D(reduced_ch,
                          kernel_size=1,
                          padding='same',
                          use_bias=True,
                          name=prefix + 'squeeze_excite/Conv')(y)
        y = Activation("relu", name=prefix + 'squeeze_excite/Relu')(y)
        y = Conv2D(exp_size,
                          kernel_size=1,
                          padding='same',
                          use_bias=True,
                          name=prefix + 'squeeze_excite/Conv_1')(y)
        x = Multiply(name=prefix + 'squeeze_excite/Mul')([Activation(hard_sigmoid)(y), x])

    x = Conv2D(out_channels,
                      kernel_size=1,
                      padding='same',
                      use_bias=False,
                      name=prefix + 'project')(x)
    x = BatchNormalization(axis=channel_axis,
                                  name=prefix + 'project/BatchNorm')(x)

    if in_channels == out_channels and stride == 1:
        x = Add(name=prefix + 'Add')([inputs, x])
    return x

def MobileNetV3(inputs, alpha=1.0, kernel=5, se_ratio=0.25):
    if alpha not in [0.75, 1.0]:
        raise ValueError('Unsupported alpha - `{}` in MobilenetV3, Use 0.75, 1.0.'.format(alpha))

    x = Conv2D(16,kernel_size=3,strides=(2, 2),padding='same',
                      use_bias=False,
                      name='Conv')(inputs)
    x = BatchNormalization(axis=-1,
                            epsilon=1e-3,
                            momentum=0.999,
                            name='Conv/BatchNorm')(x)
    x = Activation(hard_swish)(x)

    x = _bneck(x, 1, 16, alpha, 3, 1, None, 'relu', 0)

    x = _bneck(x, 4, 24, alpha, 3, 2, None, 'relu', 1)
    x = _bneck(x, 3, 24, alpha, 3, 1, None, 'relu', 2)

    x = _bneck(x, 3, 40, alpha, kernel, 2, se_ratio, 'relu', 3)
    x = _bneck(x, 3, 40, alpha, kernel, 1, se_ratio, 'relu', 4)
    x = _bneck(x, 3, 40, alpha, kernel, 1, se_ratio, 'relu', 5)
    feat1 = x

    x = _bneck(x, 6, 80, alpha, 3, 2, None, 'hardswish', 6)
    x = _bneck(x, 2.5, 80, alpha, 3, 1, None, 'hardswish', 7)
    x = _bneck(x, 2.3, 80, alpha, 3, 1, None, 'hardswish', 8)
    x = _bneck(x, 2.3, 80, alpha, 3, 1, None, 'hardswish', 9)
    x = _bneck(x, 6, 112, alpha, 3, 1, se_ratio, 'hardswish', 10)
    x = _bneck(x, 6, 112, alpha, 3, 1, se_ratio, 'hardswish', 11)
    feat2 = x

    x = _bneck(x, 6, 160, alpha, kernel, 2, se_ratio, 'hardswish', 12)
    x = _bneck(x, 6, 160, alpha, kernel, 1, se_ratio, 'hardswish', 13)
    x = _bneck(x, 6, 160, alpha, kernel, 1, se_ratio, 'hardswish', 14)
    feat3 = x

    return feat1,feat2,feat3

3、将特征提取结果融入到yolov4网络当中

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
对于yolov4来讲,我们需要利用主干特征提取网络获得的 三个有效特征进行加强特征金字塔的构建

利用上一步定义的MobilenetV1、MobilenetV2、MobilenetV3三个函数我们可以获得每个Mobilenet网络对应的三个有效特征层。

我们可以利用这三个有效特征层替换原来yolov4主干网络CSPdarknet53的有效特征层。

为了进一步减少参数量,我们可以使用深度可分离卷积代替yoloV4中用到的普通卷积。

实现代码如下:

from functools import wraps

import numpy as np
import tensorflow as tf
from tensorflow.keras import backend as K
from tensorflow.keras.layers import (Activation, Add, BatchNormalization,
                                     Concatenate, Conv2D, DepthwiseConv2D,
                                     LeakyReLU, MaxPooling2D, UpSampling2D,
                                     ZeroPadding2D)
from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l2
from utils.utils import compose

from nets.mobilenet_v1 import MobileNetV1
from nets.mobilenet_v2 import MobileNetV2
from nets.mobilenet_v3 import MobileNetV3

def relu6(x):
    return K.relu(x, max_value=6)

@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):
    darknet_conv_kwargs = {}
    darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
    darknet_conv_kwargs.update(kwargs)
    return Conv2D(*args, **darknet_conv_kwargs)

def DarknetConv2D_BN_Leaky(*args, **kwargs):
    no_bias_kwargs = {'use_bias': False}
    no_bias_kwargs.update(kwargs)
    return compose(
        DarknetConv2D(*args, **no_bias_kwargs),
        BatchNormalization(),
        Activation(relu6))

def _depthwise_conv_block(inputs, pointwise_conv_filters, alpha = 1,
                          depth_multiplier=1, strides=(1, 1), block_id=1):

    pointwise_conv_filters = int(pointwise_conv_filters * alpha)

    x = DepthwiseConv2D((3, 3),
                        padding='same',
                        depth_multiplier=depth_multiplier,
                        strides=strides,
                        use_bias=False)(inputs)

    x = BatchNormalization()(x)
    x = Activation(relu6)(x)

    x = Conv2D(pointwise_conv_filters, (1, 1),
               padding='same',
               use_bias=False,
               strides=(1, 1))(x)
    x = BatchNormalization()(x)
    return Activation(relu6)(x)

def make_five_convs(x, num_filters):

    x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
    x = _depthwise_conv_block(x, num_filters*2,alpha=1)
    x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
    x = _depthwise_conv_block(x, num_filters*2,alpha=1)
    x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
    return x

def yolo_body(inputs, num_anchors, num_classes, backbone="mobilenetv1", alpha=1):

    if backbone=="mobilenetv1":

        feat1,feat2,feat3 = MobileNetV1(inputs, alpha=alpha)
    elif backbone=="mobilenetv2":

        feat1,feat2,feat3 = MobileNetV2(inputs, alpha=alpha)
    elif backbone=="mobilenetv3":

        feat1,feat2,feat3 = MobileNetV3(inputs, alpha=alpha)
    else:
        raise ValueError('Unsupported backbone - `{}`, Use mobilenetv1, mobilenetv2, mobilenetv3.'.format(backbone))

    P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(feat3)
    P5 = _depthwise_conv_block(P5, int(1024* alpha))
    P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5)
    maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5)
    maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5)
    maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5)
    P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5])
    P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5)
    P5 = _depthwise_conv_block(P5, int(1024* alpha))
    P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5)

    P5_upsample = compose(DarknetConv2D_BN_Leaky(int(256* alpha), (1,1)), UpSampling2D(2))(P5)

    P4 = DarknetConv2D_BN_Leaky(int(256* alpha), (1,1))(feat2)
    P4 = Concatenate()([P4, P5_upsample])
    P4 = make_five_convs(P4,int(256* alpha))

    P4_upsample = compose(DarknetConv2D_BN_Leaky(int(128* alpha), (1,1)), UpSampling2D(2))(P4)

    P3 = DarknetConv2D_BN_Leaky(int(128* alpha), (1,1))(feat1)
    P3 = Concatenate()([P3, P4_upsample])
    P3 = make_five_convs(P3,int(128* alpha))

    P3_output = _depthwise_conv_block(P3, int(256* alpha))
    P3_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P3_output)

    P3_downsample = _depthwise_conv_block(P3, int(256* alpha), strides=(2,2))
    P4 = Concatenate()([P3_downsample, P4])
    P4 = make_five_convs(P4,int(256* alpha))

    P4_output = _depthwise_conv_block(P4, int(512* alpha))
    P4_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P4_output)

    P4_downsample = _depthwise_conv_block(P4, int(512* alpha), strides=(2,2))
    P5 = Concatenate()([P4_downsample, P5])
    P5 = make_five_convs(P5,int(512* alpha))

    P5_output = _depthwise_conv_block(P5, int(1024* alpha))
    P5_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P5_output)

    return Model(inputs, [P5_output, P4_output, P3_output])

如何训练自己的mobilenet-yolo4

首先前往Github下载对应的仓库,下载完后利用解压软件解压,之后用编程软件打开文件夹。
注意打开的根目录必须正确,否则相对目录不正确的情况下,代码将无法运行。

一定要注意打开后的根目录是文件存放的目录。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

; 一、数据集的准备

本文使用VOC格式进行训练,训练前需要自己制作好数据集,如果没有自己的数据集,可以通过Github连接下载VOC12+07的数据集尝试下。
训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
此时数据集的摆放已经结束。

二、数据集的处理

在完成数据集的摆放之后,我们需要对数据集进行下一步的处理,目的是获得训练用的2007_train.txt以及2007_val.txt,需要用到根目录下的voc_annotation.py。

voc_annotation.py里面有一些参数需要设置。
分别是annotation_mode、classes_path、trainval_percent、train_percent、VOCdevkit_path,第一次训练可以仅修改classes_path

'''
annotation_mode用于指定该文件运行时计算的内容
annotation_mode为0代表整个标签处理过程,包括获得VOCdevkit/VOC2007/ImageSets里面的txt以及训练用的2007_train.txt、2007_val.txt
annotation_mode为1代表获得VOCdevkit/VOC2007/ImageSets里面的txt
annotation_mode为2代表获得训练用的2007_train.txt、2007_val.txt
'''
annotation_mode     = 0
'''
必须要修改,用于生成2007_train.txt、2007_val.txt的目标信息
与训练和预测所用的classes_path一致即可
如果生成的2007_train.txt里面没有目标信息
那么就是因为classes没有设定正确
仅在annotation_mode为0和2的时候有效
'''
classes_path        = 'model_data/voc_classes.txt'
'''
trainval_percent用于指定(训练集+验证集)与测试集的比例,默认情况下 (训练集+验证集):测试集 = 9:1
train_percent用于指定(训练集+验证集)中训练集与验证集的比例,默认情况下 训练集:验证集 = 9:1
仅在annotation_mode为0和1的时候有效
'''
trainval_percent    = 0.9
train_percent       = 0.9
'''
指向VOC数据集所在的文件夹
默认指向根目录下的VOC数据集
'''
VOCdevkit_path  = 'VOCdevkit'

classes_path用于指向检测类别所对应的txt,以voc数据集为例,我们用的txt为:
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
训练自己的数据集时,可以自己建立一个cls_classes.txt,里面写自己所需要区分的类别。

三、开始网络训练

通过voc_annotation.py我们已经生成了2007_train.txt以及2007_val.txt,此时我们可以开始训练了。
训练的参数较多,大家可以在下载库后仔细看注释,其中最重要的部分依然是train.py里的classes_path。

classes_path用于指向检测类别所对应的txt,这个txt和voc_annotation.py里面的txt一样!训练自己的数据集必须要修改!
睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
修改完classes_path后就可以运行train.py开始训练了,在训练多个epoch后,权值会生成在logs文件夹中。

另外,backbone参数用于指定所用的主干特征提取网络,可以在mobilenetv1, mobilenetv2, mobilenetv3中进行选择。

alpha参数用于指定当前所使用的mobilenet系列网络的通道变化情况,默认状态下为1。
mobilenetv1的alpha可选范围为0.25、0.5、0.75、1.0。
mobilenetv2的alpha可选范围为0.5、0.75、1.0、1.3。
mobilenetv3的alpha可选范围为0.75、1.0。

训练前需要注意所用mobilenet版本、alpha值和预训练权重的对齐。

其它参数的作用如下:


classes_path    = 'model_data/voc_classes.txt'

anchors_path    = 'model_data/yolo_anchors.txt'
anchors_mask    = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]

model_path      = 'model_data/yolov4_mobilenet_v2_voc.h5'

input_shape     = [416, 416]

backbone        = "mobilenetv2"
alpha           = 1

mosaic              = False
Cosine_scheduler    = False
label_smoothing     = 0

Init_Epoch          = 0
Freeze_Epoch        = 50
Freeze_batch_size   = 16
Freeze_lr           = 1e-3

UnFreeze_Epoch      = 100
Unfreeze_batch_size = 8
Unfreeze_lr         = 1e-4

Freeze_Train        = True

num_workers         = 0

train_annotation_path   = '2007_train.txt'
val_annotation_path     = '2007_val.txt'

四、训练结果预测

训练结果预测需要用到两个文件,分别是yolo.py和predict.py。
我们首先需要去yolo.py里面修改model_path以及classes_path,这两个参数必须要修改。

另外,backbone参数用于指定所用的主干特征提取网络,可以在mobilenetv1, mobilenetv2, mobilenetv3中进行选择。

alpha参数用于指定当前所使用的mobilenet系列网络的通道变化情况,默认状态下为1。
mobilenetv1的alpha可选范围为0.25、0.5、0.75、1.0。
mobilenetv2的alpha可选范围为0.5、0.75、1.0、1.3。
mobilenetv3的alpha可选范围为0.75、1.0。

model_path指向训练好的权值文件,在logs文件夹里。
classes_path指向检测类别所对应的txt。

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。

Original: https://blog.csdn.net/weixin_44791964/article/details/115494297
Author: Bubbliiiing
Title: 睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台



相关阅读

Title: 论文解读:Open Domain Question Answering Using Early Fusion of Knowledge Bases and Text

论文解读:Open Domain Question Answering Using Early Fusion of Knowledge Bases and Text

知识库问答通常存在一个问题,就是由于知识库不充分导致在一定的推理范围内无法找到相应的答案,因此,可以通过引入额外非结构化文本做辅助增强。本文提出一种开放领域的知识库和文本问答方法。

一、简要信息

序号属性值1模型名称GRAFT-Nets2所属领域自然语言处理3研究内容知识库问答4核心内容Knowledge Base Embedding, KBQA5GitHub源码
https://github.com/OceanskySun/GraftNet

6论文PDF
https://www.aclweb.org/anthology/D18-1455.pdf

二、全文摘要翻译

开放领域的问答设计由原来的流水线模式到以深度学习为主的端到端模式,现如今大多数的以神经网络为主的问答均单独从知识库或者文本中抽取答案。本文,我们发现一个更有实在意义的尝试,即结合知识库和文本一起做问答,当知识库不充分时候,结合文本做问答最为合适。基于最新的图表征技术,我们提出一种新的模型叫做GRAFT-Net,用于从问题有关的子图以及链接的文本中抽取答案。我们为此构建一个评测任务数据,在该数据集我们发现,当同时考虑知识库和文本时,问答的效果达到最好。

三、问题介绍

在问答中,有一些问题可以从文本(Text)中获得答案,有一些问题则可以从知识库(Knowledge Base,KB)中获得答案,因此一个可以想到的问题是,如何将文本和知识库结合起来,因此本文更加关注的场景是,同时使用大规模的知识库和文本进行问答会比单独使用其中一个更加有效。
将文本和知识库结合起来有两种方法,包括:

late fusion(迟融合):即分别使用最好的模型对文本和知识库进行问答,然后启发式地将两个答案进行融合。通常这种想法比较简单,且可能会陷入局部最优;
early fusion(早融合):即使用单独的模型进行训练,但可以将知识库和文本提前进行结合。这种方法可以灵活地将不同资源结合起来。

因此GRAFT-Net(Graphs of Relations Among Facts and Text Networks)模型主要致力于early fusion。大体思路是:

  • 如何将文本和知识库结合起来? 通过实体链接实现知识库中的实体与文本进行关联。如果知识库中的实体在某一个文本中出现,则该实体与该文本可以相互连接。
  • 如何表示图? 这个图既包含原有的实体结点,又包含连接的文本结点,因此属于异构图,本文提出一种异构更新规则来学习异构表示,例如如图1所示。
  • 在单次传播时如何控制更新? 当给定一个问句以及其对应的种子实体时(即主题词),如何在更新图过程中,如何保证只更新在图中以及连接的文本中的种子实体以及邻接实体,本文提出一种异构更新规则(heterogeneous update rule)和有向传播方法(directed propagation method)

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

; 四、方法

任务描述:给定一个问句和对应的主题词(种子实体),根据主题词获得对应的子图,以及实体与文档链接后构成异构图,对异构图中的每个实体以及文档内的名词进行二分类,选择超过阈值的实体或名词。 因此本文将KBQA视为对图中各个实体结点的二分类问题。

4.1 概览

定义问句 q q q 以及其对应的所有答案 { a } q {a}_q {a }q ​ ,因此对于问句 q q q 对应的子图 V q \mathcal{V}_q V q ​ 中的各个实体 v ∈ V q v\in\mathcal{V}_q v ∈V q ​,其标签满足(1)当 v ∈ { a } q v\in{a}_q v ∈{a }q ​ 时,y v = 1 y_v=1 y v ​=1,否则(2)当 v ∈ ( V q − { a } q ) v\in(\mathcal{V}_q - {a}_q)v ∈(V q ​−{a }q ​) 时,y v = 0 y_v=0 y v ​=0 。因此可以将本任务视为对每个结点的二分类,在传统的图表征任务中,结点表征通常为"聚集-应用-发散"的过程,即先将周围的实体的信息聚集在某个实体上使得该实体能够拥有周围实体的信息并进行相应的迭代计算,其次通过梯度向外扩散。

传统的图表征公式可表示为:

h v l = ϕ ( h v l − 1 , ∑ v ′ ∈ N r ( v ) h v ′ l − 1 ) h_v^{l} = \phi(h_v^{l-1},\sum_{v'\in N_r(v)}h_{v'}^{l-1})h v l ​=ϕ(h v l −1 ​,v ′∈N r ​(v )∑​h v ′l −1 ​)

其中 N r ( v ) N_r(v)N r ​(v ) 表示实体 v v v 的关于关系边 r r r 的邻接实体,ϕ \phi ϕ 则为前馈神经网络。

不同于传统的图表征方法,本文不同点在于:(1)我们的图包含异构结点、(2)我们希望在迭代训练时是基于问句进行的。

4.2 结点初始化

实体初始化:定义每个实体的向量为 x v x_v x v ​,其可以使用知识表示学习(Knowledge Base Embedding)进行预训练。网络结点初始化则表示为 h v ( 0 ) = x v h_v^{(0)} = x_v h v (0 )​=x v ​ 。

文档初始化:对于文档结点,需要对整个文档进行表示,因此定义 H d ( l ) ∈ R ∣ d ∣ × n H_d^{(l)}\in\mathcal{R}^{|d|\times n}H d (l )​∈R ∣d ∣×n 表示第 l l l 次迭代时文档的表征向量,初始化可使用一层长短期记忆神经网络:

H d ( 0 ) = L S T M ( w 1 , w 2 , . . . ) H_d^{(0)} = LSTM(w_1,w_2,...)H d (0 )​=L S T M (w 1 ​,w 2 ​,...)

为了便于表示,我们定义 H d , p ( l ) H_{d,p}^{(l)}H d ,p (l )​ 表示第 l l l 次迭代计算时,文档 d d d 的第 p p p 个单词(token)的输出表征向量。

4.3 异构更新

网络中包含原有的实体结点以及链接的文档结点,如何表示异构图是图表征任务中的一个研究点,本文希望能够充分学习到异构图中的信息。

实体表征:
在第 l l l 轮迭代更新时,实体结点表征 h v ( l ) h_v^{(l)}h v (l )​ 可综合为如下公式,从上往下其包含四个部分信息:

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

  • (1)来自于上一迭代时计算得到的实体表征h v ( l − 1 ) h_v^{(l-1)}h v (l −1 )​;
  • (2)来自于上一迭代时计算得到的问句表征h q ( l − 1 ) h_q^{(l-1)}h q (l −1 )​;
  • (3)来自当前实体v v v 有关所有关系r r r 的邻接实体的表征信息h v ′ ( l − 1 ) h_{v'}^{(l-1)}h v ′(l −1 )​,值得注意的是,其实两个求和,外层的求和表示对所有该实体所相连边的关系,内层的求和表示在某一关系下所有的相邻的实体,权重α r v ′ \alpha_r^{v'}αr v ′​ 则表示邻接的实体与当前实体的权重,ψ r \psi_r ψr ​ 表示线性函数,定义为:
    睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

其中 p r v ′ ( l − 1 ) pr_{v'}^{(l-1)}p r v ′(l −1 )​ 表示第 l l l 次迭代时每个实体自身的得分,当 l = 0 l=0 l =0 时则表示初始时每个实体的pagerank得分。

  • (4)上一迭代中文档的表征,特别说明的是,只取与文档相连接的实体名词对应的表征信息H d , p ( l − 1 ) H_{d,p}^{(l-1)}H d ,p (l −1 )​ ,其中( d , p ) ∈ M ( v ) (d,p)\in M(v)(d ,p )∈M (v ) 表示文档d d d 的第p p p 个单词(token)是与当前实体v v v 链接的。

我们可以发现,在对实体进行表示运算时,除了与传统邻接实体结点进行聚合运算之外,与传统方法主要有两个不同点:
(1)融入问句表征信息,说明不同问句的条件下,虽然是同一个实体但会有不同的表示形态;
(2)通过最初构建子图时,使用的pagerank得分,来控制后期迭代过程中的传播问题,当第 l = 0 l=0 l =0 层时,只对pagerank得分不为0的进行梯度传播;
(3)融入文档信息,但不同的是,并不全将整个文档信息聚合过来,而是只取文档中与当前实体相链接的名词,这也进一步保证我们要表征的信息时来自特定文档中的实体,而不会使得所有实体链接的同一个文档时都取相同的文档表征;

文档表征:
对于文档结点,同样也是只对文档中各个单词分别进行更新,公式表示为:

H ~ d , p ( l ) = F F N ( H d , p ( l − 1 ) , ∑ v ∈ L ( d , p ) h v ( l − 1 ) ) \tilde{H}{d,p}^{(l)} = FFN(H{d,p}^{(l-1)},\sum_{v\in L(d,p)}h_v^{(l-1)})H ~d ,p (l )​=F F N (H d ,p (l −1 )​,v ∈L (d ,p )∑​h v (l −1 )​)

直观的意思是当前文档 d d d 中第 p p p 个单词(token)的表征更新取决于该词在上一轮的表征信息以及上一迭代中与该词链接的所有实体结点的表征信息的和。

最后对于当前的文档,所有词都会做一次更新,然后再次通过LSTM进行表征:

H d ( l ) = L S T M ( H ~ d , p ( l ) ) H_d^{(l)} = LSTM(\tilde{H}_{d,p}^{(l)})H d (l )​=L S T M (H ~d ,p (l )​)

问句表征
在实体表征中所提及的第(3)个表征信息中,如何融入问句呢?首先初始化是使用一层LSTM对问句进行特征提取,记做:

h q ( 0 ) = L S T M ( w 1 q , . . . , w ∣ q ∣ q ) ∣ q ∣ ∈ R n h_q^{(0)} = LSTM(w_1^q,...,w_{|q|}^{q})_{|q|}\in\mathcal{R}^{n}h q (0 )​=L S T M (w 1 q ​,...,w ∣q ∣q ​)∣q ∣​∈R n

后续的每一次迭代过程中,则不再使用LSTM,而是只对问句中的主题词进行迭代表示:

h q ( l ) = F F N ( ∑ v ∈ S q h v ( l ) ) h_q^{(l)} = FFN(\sum_{v\in S_q}h_v^{(l)})h q (l )​=F F N (v ∈S q ​∑​h v (l )​)

其中 F F N FFN F F N 为前馈神经网络。

另外上面所提及的权重系数,则可以表示为:

α r v ′ = s o f t m a x ( x r T h q ( l − 1 ) ) \alpha_r^{v'} = softmax(x_r^Th_q^{(l-1)})αr v ′​=s o f t m a x (x r T ​h q (l −1 )​)

即表示当前问句与关系表征 x r x_r x r ​ 之间的相似度的权重。

上述所提及的实体表征更新(Entity Update)和文档表征更新(Text Update)可用如下图表示:

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
控制传播

为什么要设置控制传播机制呢,主要是在异构更新过程中,要确保只在当前问句的主题词以及相应的邻接实体上完成聚合,而不会让其他实体受到影响,因此本文通过pagerank确保控制。

初始化时,需要对原来的知识库根据问句中的主题词来缩小候选实体的规模,一些其他工作是直接将主题词相关联的多跳范围内的实体构建为一个子图,本文则利用了pagerank机制,将实体作为互联网中的网页,边则表示网页之间的超链接,因此可以得到得分,即其属于问句主题词的答案的得分:

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台
其中 p r v ( 0 ) pr_v^{(0)}p r v (0 )​ 表示实体 v v v 可以被作为答案的得分,初始时则为平均数,∣ S q ∣ |S_q|∣S q ​∣ 表示主题词的个数,在后面的更新中,pagerank得分也会进行加权更新。

睿智的目标检测50——Tensorflow2 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台

答案选择

由于是一个图结点的二分类问题,因此每个结点表示向量通过全连接层映射到二分类上:

P r ( v ∈ { a } q ∣ G q , q ) = σ ( w T h v L + b ) Pr(v\in{a}_q|\mathcal{G}_q,q) = \sigma(w^Th_v^{L}+b)P r (v ∈{a }q ​∣G q ​,q )=σ(w T h v L ​+b )

因此需要根据阈值,对每个候选的答案实体结点被预测为1的概率进行筛选。

Original: https://blog.csdn.net/qq_36426650/article/details/109195470
Author: 华师数据学院·王嘉宁
Title: 论文解读:Open Domain Question Answering Using Early Fusion of Knowledge Bases and Text