YOLOV3函数详细笔记(1)

重捋一遍YOLO-V3代码

  • 代码复写加记录
    • 训练需要的文件或函数
      • utils.py
      • yolov3_model
    • 后记

@如有侵权,联系我删除

代码复写加记录

代码来自 qqwweee,以及看了另一个博主Bubbliiiing的讲解.复现读懂以后也记录一下自己的理解,方便以后回顾。

训练需要的文件或函数

utils.py

首先是utils.py文件下的函数,除去letterbox_image这个函数是用在预测图片的时候的,其余这个文件中的函数在训练的时候都会用到。
首先是compose函数,用在模型搭建的时候,用于连接DarknetConv2D、BatchNormalization与LeakyReLU组成DBL块。

from functools import reduce
# 连接Darknet中的层,例如DBL块的组成
def compose(*funcs):
    if funcs:
        return reduce(lambda f, g: lambda *a, **kw: g(f(*a, **kw)), funcs)
    else:
        raise ValueError('Composition of empty sequence not supported.')

用这个函数的时候只需要将需要连接的操作放入其中即可,例如compose( DarkNetConv2D(), BatchNormalization(), LeakyReLU()),其实就是简化了手写序列模型一步一步将这三个操作连接到一起,功能是一样的,只是这个函数看着简单而且高级。
rand(a=0.0, b=1.0)函数只是用来随机出一个a-b区间的值。
get_random_data是对图片进行图片增强,大小调整,旋转,颜色抖动,归一化。涉及到的参数:

  • max_boxes:一张图片上最多有多少个真实框。
  • jitter:长宽比的随机比例系数,0~1之间。
  • hue,sat,val:颜色抖动是从RGB->HSV->RGB,这三个参数值给定了,有颜色抖动方面经验的可以修改,默认值也没什么问题。

yolov3_model

我将yolov3模型搭建所需要的用到的函数都放在了这里,方便。
先来写构建模型的基础网络块。
首先是DarkNet卷积操作,改写了Conv2D,进行了正则,并定义了padding在不同的strides时的值。
DarkNetConv2D

from functools import wraps
from tensorflow.keras.layers import Conv2D, BatchNormalization, LeakyReLU, ZeroPadding2D
from tensorflow.keras.regularizer import l2

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

DBL(简称:DarkNetConv2D + BatchNormalization + ReLU)
不使用偏差b

def DBL(*arg, **kwargs):
    no_bias_kwargs = {'use_bias': False}
    no_bias_kwargs.update(kwargs)
    return compose(
        DarkNetConv2D(*arg, **no_bias_kwargs),
        BatchNormalization(),
        LeakyReLU(alpha=0.1)
    )

residual(ResNet结构)

def resblock_body(x, num_filters: int, num_blocks: int):
    x = ZeroPadding2D(((1, 0), (1, 0)))(x)
    x = DBL(num_filters, (3, 3), strides=(2, 2))(x)
    for _ in range(num_blocks):
        y = DBL(num_filters // 2, (1, 1))(x)
        y = DBL(num_filters, (3, 3))(y)
        x = Add()([x, y])
    return x

至此构建网络模型的基础函数就写完了,接下来就是安网络图开始构建模型了。
先写DarkNet53部分的函数,依据网络图,返回的是DarkNet需要向外连接的层。

def DarkNet_body(x):
    x = DBL(32, (3, 3))(x)
    x = resblock_body(x, 64, 1)
    x = resblock_body(x, 128, 2)
    x = resblock_body(x, 256, 8)
    feat1 = x  # (batch_size, 52, 52, 256) 非最后的输出层,而是darknet主体中需要向外连接的层
    x = resblock_body(x, 512, 8)
    feat2 = x  # (batch_size, 26, 26, 512)
    x = resblock_body(x, 1024, 4)
    feat3 = x  # (batch_size, 13, 13, 1024)
    return feat1, feat2, feat3

对于返回的三个feat,我们要分别对其进行处理,进行上采样和拼接等操作,就需要写一个make_last_layers函数。

def make_last_layers(x, num_filters: int, out_filters: int):
    x = DBL(num_filters, (1, 1))(x)
    x = DBL(num_filters * 2, (3, 3))(x)
    x = DBL(num_filters, (1, 1))(x)
    x = DBL(num_filters * 2, (3, 3))(x)
    x = DBL(num_filters, (1, 1))(x)

    y = DBL(num_filters * 2, (3, 3))(x)
    y = DBL(out_filters, (1, 1))(y)
    return x, y

上面的函数写完终于到最后的网络模型图整体还原了。yolo_body就是对整个yoloV3网络图的还原,它的输出即网络图的三个输出特征层。

def yolo_body(inputs, num_anchors: int, num_classes: int):
    darnet_body = Model(inputs, DarkNet_body(inputs))
    feat1, feat2, feat3 = darnet_body.output

    # y1 (batch_size, 13, 13, 3*25)
    x, y1 = make_last_layers(feat3, 512, num_anchors * (num_classes + 5))

    x = compose(
        DBL(256, (1, 1)),
        UpSampling2D(2)
    )(x)
    x = Concatenate()([x, feat2])

    # y2 (batch, 26, 26, 3*25)
    x, y2 = make_last_layers(x, 256, num_anchors * (num_classes + 5))

    x = compose(
        DBL(128, (1, 1)),
        UpSampling2D(2)
    )(x)
    x = Concatenate()([x, feat1])

    # y3 (batch_size, 52, 52, 3*25)
    x, y3 = make_last_layers(x, 128, num_anchors * (num_classes + 5))

    return Model(inputs, [y1, y2, y3])

inputs:网络的输入,一般为网络模型图上的(416, 416, 3)
y1, y2, y3:对应网络模型图的三个输出特征层
tiny_yolo_body
这是额外的模型,为什么说是额外的?因为上面是一个完整的yoloV3的构建,但由于DarkNet53网络层相对有一定的深度,若没有好一点的GPU支持,很难有效运行,因此就有一个简单版本的yoloV3,思想上借鉴了YOLOV3的思想,但模型相对简单了许多,参数量也小了很多。
如果用tiny_yolo_body,就不需要写Darknet_bodymake_last_layers这两个函数了,因为不需要DarkNet53这个主体了。

def tiny_yolo_body(inputs, num_anchors, num_classes):
    x1 = compose(
            DBL(16, (3,3)),
            MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'),
            DBL(32, (3,3)),
            MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'),
            DBL(64, (3,3)),
            MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'),
            DBL(128, (3,3)),
            MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'),
            DBL(256, (3,3)))(inputs)
    x2 = compose(
            MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'),
            DBL(512, (3,3)),
            MaxPooling2D(pool_size=(2,2), strides=(1,1), padding='same'),
            DBL(1024, (3,3)),
            DBL(256, (1,1)))(x1)
    y1 = compose(
            DBL(512, (3,3)),
            DarkNetConv2D(num_anchors*(num_classes+5), (1,1)))(x2)

    x2 = compose(
            DBL(128, (1,1)),
            UpSampling2D(2))(x2)
    y2 = compose(
            Concatenate(),
            DBL(256, (3,3)),
            DarkNetConv2D(num_anchors*(num_classes+5), (1, 1)))([x2, x1])

    return Model(inputs, [y1, y2])

后记

这就是模型的构建代码过程及代码了,模型图网上很多,就不放了。主要还是对每个函数的理解是最重要的,想要做什么事情,怎样去完善其中的细节,理解代码的含义,然后就是对代码的改进了。
这个代码是github上一位大神的代码,也就是我开头写的那位,不过由于他当时写的时候是使用的TensorFlow1作为后端,前端代码使用的是keras,而现在这个代码也是经过修改以后可以适配TensorFlow2版本了,网上也有一些大佬开源了TensorFlow2版本的YOLOV3,我感觉那些代码还是看着比较费劲(个人能力不行,怪我自己)。还是这个代码比较便于理解,以后还会改进代码,让代码更适应于TensorFlow2版本,并且性能效率上也要有一定的提升,毕竟上层代码灵活性较低,对于没有好GPU的我来说确实难受,VGG网络是根本就跑不动的,直接炸显存。
代码从大佬那学的,本篇博主要还是分享一下代码的详细过程,因为自己在看YOLOV3的时候苦于没有详细的一步一步诱导的那种教程,啃的极其费劲,一度想放弃。略懂了一点还是要分享一下的,共同促进成长。
兴趣使然~

你可能感兴趣的:(YOLOV3函数详细笔记(1))