下面的对模型代码的解析是转载的,yolo3的模型--用keras写的,是我从github上下载的,带训练好的权重。
关于视频检测和图片检测的预测部份,我没有实验。只跑了train.py部分,我跑的是不是tiny,是3个输出的model。
YOLO3百度云盘链接地址:https://pan.baidu.com/s/1Haf5DcnEyLkY7vFZDw--cg
分割线
******************************************************************************************************************************************************
YOLO: You Only Look Once
V2的一些改进:(https://blog.csdn.net/jesse_mx/article/details/53925356)
1.batch normalization(批量规范化): Keras中BatchNormalization层:该层在每个batch上将前一层的激活值重新规范化,即使得其输出数据的均值接近0,其标准差接近1.解决了网络每层输入的分布一直在改变, 使训练过程难度加大的问题。batch-normalization 也有助于规范化模型,可以在舍弃dropout优化后依然不会过拟合。
2.High Resolution Classifier(高分辨率分类器):使用ImageNet预训练过的模型(classifier)来提取特征,新的YOLO网络把分辨率直接提升到了448 * 448。
3.Convolutional With Anchor Boxes(使用锚框卷积):借鉴了Faster R-CNN中的anchor思想,在卷积特征图上进行滑窗操作,每一个中心可以预测9种不同大小的建议框。
4.Dimension Clusters(维度聚类):使用了K-means聚类方法类训练bounding boxes,可以自动找到更好的boxes宽高维度。作者采用的评判标准是IOU得分(也就是boxes之间的交集除以并集),这样error就和box的尺度无关.
5.Direct location prediction(直接位置预测):定位预测值被归一化后,参数就更容易得到学习,模型就更稳定。
6.Fine-Grained Features(细粒度特征):转移层也就是把高低两种分辨率的特征图做了一次连结,连接方式是叠加特征到不同的通道而不是空间位置,类似于Resnet中的identity mappings。
7.Multi-Scale Training:原来的YOLO网络使用固定的448 * 448的图片作为输入,现在加入anchor boxes后,输入变成了416 * 416。目前的网络只用到了卷积层和池化层,那么就可以进行动态调整。
V3的改进:
1.使用多标签分类;用多个独立的逻辑分类器替换softmax函数,以计算输入属于特定标签的可能性
2. 使用逻辑回归预测每个边界框的目标性得分,改变了计算代价函数的方式
3.使用3种不同的尺度进行融合预测
*********************************************************************************************************************************************
一. 基于keras的tiny-yolov3
1.1 tiny-yolov3 的网络结构,主干网络采用一个7层conv+max网络提取特征(和darknet19类似),嫁接网络采用的是13*13、26*26的分辨率探测网络,精度比较低。共24层:
1.2训练过程如下:
1).主函数main(): 加载annotation_path,classes_path,anchors_path, 图片尺寸input_shape
2).第一阶段: create_model(input_shape, anchors, len(class_names) ) #构建模型以及是否预加载模型参数
-定义y_true形状是[(13, 13, 2, class+5), (26, 26, 2, class+5)]#len(anchors)//3=2
-model_body = tiny_yolo_body(image_input, num_anchors//3, num_classes)# 构建CNN网络,返回Model(input,[y1,y2])
--使用compse函数构建CNN网络Con-Max-Con-Max-Con-Max-Con-Max-Con....,其中y1,y2构成两个不同尺度feature组
--返回定义Model,输入为inputs,输出为[y1,y2], return Model(inputs, [y1,y2])
-如果预加载训练模型:加载权重参数best_weights0.h5,是否冻结,是则将前面层参数冻结,否则全部都优化
-构建model_loss=Lambda(yolo_loss, output_shape,arguments)([*model_body.output, *y_true]),
--其中yolo_loss:要实现的函数,仅接受一个变量即上一层的输出,output_shape函数应该返回的值的shape
--损失层Lambda的输入是已有模型的输出model_body.output和真值y_true,输出是1个值,即损失值。
--yolo_loss除了接收Lambda层的输入model_body.output和y_true,还接收锚框anchors、类别数和过滤阈值3个参数。
-构建完整的算法模型model=Model([model_body.input, *y_true], model_loss)
--把model_body.input和y_true作为输入层,输出层为model_loss层
--model_body.input是任意(?)个(416,416,3)的图片;y_true是已标注数据所转换的真值结构
--把目标当成一个输入构成多输入模型,把loss写成一个层作为最后的输出,搭建模型的时候,只需将模型的output定义为loss,而compile的时候直接将loss设置为y_pred(因为模型的输出就是loss,所以y_pred就是loss)
-返回model ,完成算法模型model的构建
**总结: 构建模型Model([model_body.input, *y_true], model_loss),其中inputs=[model_body.input, *y_true], outputs=model_loss.
3).第二阶段:train(model, annotation_path, input_shape, anchors, len(class_names), log_dir)#使用构建的模型继续训练
-使用第一阶段已完成的网络权重继续训练
-编译创建好的模型model.compile(optimizer='adam', loss={'yolo_loss': lambda y_true, y_pred: y_pred})
--优化器Adam,模型compile时传递的是自定义的loss,把loss写成一个层融合到model里后,y_pred就是loss。
自定义损失函数yolo_loss规定要以y_true, y_pred为参数。
-可视化tensorboard = TensorBoard(log_dir=log_dir)
-定义检查模型权重参数checkpoint=ModelCheckpoint("best_weights.h5",monitor="val_loss",mode='min'...period=1)
--设置成当验证数据集的分类损失减小时保存网络权重(mode=min),每隔一定步数记录最大值
-定义回调函数callback_lists=[tensorboard,checkpoint]实时监测训练动态,并能根据训练情况及时对模型采取一定的措施
--在调用fit时传入模型的一个对象,它在训练过程中的不同时间点都会被模型调用。
--可以访问关于模型状态和性能的所有可用数据,还可以采取行动:中断训练\保存模型\加载一组不同的权重或者改变模型状态
-定义batch_size、训练和验证的比例val_split
-读取annotation文件保存在lines中并随机打乱秩序np.random.shuffle(lines)
-开始训练model.fit_generator(generator,steps_perepoch,epochs,verbose,callbacks,validation_data,validation_steps,initial)
--逐个生成数据的batch并进行训练,生成器与模型将并行执行以提高效率,生成器将无限在数据集上循环
--generator生成器函数,值为data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes)返回结果
---data_generator()#循环输出固定批次数batch_size=16的图片数据image_data和标注框数据box_data
----数据的总行数是n=len(annotation)
----在第0次时将数据洗牌shuffle(annotation)
----在每batch_size循环中调用get_random_data解析第i行annotation生成image和box,添加至各自image/box_data中
-------get_random_data调整输入的每个图片尺寸image_data,取图片中标注框取前20个调整图片后box_data
----索引值递增i+1,当完成n个一轮之后即循环一轮,重新将i置0,再次调用shuffle洗牌数据
----将image_data和box_data都转换为np数组
----生成真值y_true=preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes)
-----将真实坐标转化为yolo需要输入的坐标真值y_true,维度是[[batchsize,13,13,2,6],[batchsize, 26,26,2,6]]
-----grid_shapes=[[13,13],[26,26]]
-----在每个box_data中,只取长宽大于0的数据,然后取anchor与这些数据最接近的best_anchor
-----y_true最后一维度的6位中0/1位是中心点xy,范围0-13/26,2/3位是宽高wh,范围是0-1,4位是置信度1/0,5位是类别0/1
----返回[image_data, *y_true], np.zeros(batch_size)
--steps_per_epoch当生成器返回steps_per_epoch次数据时计一个epoch结束,执行下一个epoch
--epochs:整数,数据迭代的轮数
--verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
--validation_data生成验证集的生成器,上文的generator是生成训练集生成器
--validation_steps指定验证集的生成器返回次数
--initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用
--callbacks=callback_lists回调函数是一组在训练特定阶段调用的函数集,用来观察训练过程中网络内部的状态和统计信息,也在这一步保存模型,以后直接载入模型与训练数据即可开始训练或者识别
-model.save_weights()保存模型权重,只保存模型的权重,可以看作是保存模型的一部分(本程序没有被调用)
**总结:编译模型model.compile(optimizer='adam', loss={'yolo_loss': lambda y_true, y_pred: y_pred})
**** 训练模型model.fit_generator(generator,epochs,callbacks,validation_data,...)
**** 保存训练模型权重文件 best_weights.h5,提供给检测过程使用
1.3 检测过程如下:
1.定义相关参数
-定义参数解析器parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
-添加参数model,anchors,classes,gpu_num,image
-将变量以标签-值的字典形式存入args字典FLAGS = parser.parse_args()
2.开始检测detect_frame(YOLO(**vars(FLAGS)))
-使用OpenCV打开USB相机cap = cv2.VideoCapture(0),ret, frame = cap.read()
-OpenCV转换成PIL.Image格式 Image.fromarray(frame[...,::-1])
-开始对每帧进行检测yolo.detect_frame(image)并返回边框boxes,置信度score和类别名称
--首先确定定义的输入图片尺寸是32的倍数assert model_image_size[0]%32 == 0
--boxed_image = letterbox_image改变图片尺寸为[416,416]#这里不改变图片比例,较小的用灰色填充
--加载参数并运行self.sess.run([output],feed_dict={})其中output=self.boxes, self.scores, self.classes
---self.boxes, self.scores, self.classes = self.generate()#构建检测模型,下载模型数据
----self.yolo_model = load_model##载入模型参数文件
----boxes, scores, classes=yolo_eval()#使用yolo模型进行图片的检测,进行坐标转化和nms处理
-----yolo_eval()完成预测逻辑的封装
-----yolo_eval(yolo_outputs,anchors,num_classes,image_shape,max_boxes,score_threshold,iou_threshold)
-----yolo_outputs:YOLO模型的输出,2个尺度,13/26,最后1维是预测值=2×(5+1)=12,为[(?,13,13,12),(?,26,26,12)]
-----max_boxes:图中最大的检测框数,20个
-----score_threshold:框置信度阈值,小于阈值的框被删除,需要的框较多则调低阈值,需要的框较少则调高阈值;
-----iou_threshold:同类别框的IoU阈值,大于阈值的重叠框被删除,重叠物体较多则调高阈值,重叠物体较少则调低阈值;
-----在两个尺度中调用yolo_boxes_and_scores(),提取框_boxes和置信度_box_scores,将2层的框数据放入列表boxes和box_scores,再拼接展平concatenate,输出的数据就是适合输入图片的所有的框和置信度
-----mask过滤小于置信度阈值的框,只保留大于置信度的框,mask掩码
-----max_boxes_tensor每张图片的最大检测框数,max_boxes是20
-----通过掩码mask和类别c,筛选框class_boxes和置信度class_box_scores;
-----通过NMS非极大值抑制tf.image.non_max_suppression(class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold),筛选出框boxes的NMS索引nms_index,把同一分类重合度过高的候选框给筛选掉;
-----根据索引,选择gather输出的框class_boxes和置信class_box_scores度,再生成类别信息classes
-----将多个类别的数据组合,生成最终的检测数据框并返回
---feed_dict={self.yolo_model.input: image_data,self.input_image_shape: [image.size[1], image.size[0]]...}传入图数据、图片尺寸等参数
-绘制检测框和类别cv2.rectangle(),cv2.putText()
-关闭session: yolo.close_session()
*********************************************************************************************************************************************
二. 基于keras的yolov3
2.1 网络结构:依次为网络类型-卷积核-尺寸-输出。(参考https://blog.csdn.net/dz4543/article/details/90049377)
上图左边是一个Darknet-53模型结构,网络中有53个Convolutional层。算上所有的层是108层。其中Residual层是输入层与输出层叠加起来作为输出层,叠加后的特征图作为新的输入输入到下一层。YOLO主体是由许多这种残差模块组成,减小了梯度爆炸的风险,加强了网络的学习能力。输入图片大小是416X416,右边预测的三个特征层scale1,scale2,scale3大小分别是52×52,26×26, 13×13。都是奇数使得网格会有个中心位置。同时YOLO输出为3个尺度,每个尺度之间还有联系。比如说,13×13这个尺度输出用于检测大型目标,对应的26×26为中型的,52×52用于检测小型目标。生成预测层的三层的最后一层都是Conv2d。
2.2 目标边界框的预测(??)
YOLOv3网络在三个特征图中分别通过(4+1+c)*k个大小为1*1的卷积核进行卷积预测,k为预设边界框的个数(默认取3),c为预测目标的类别数,其中4k个参数负责预测目标边界框的偏移量,k个参数负责预测目标边界框内包含目标的概率,ck个参数负责预测这k个预设边界框对应c个目标类别的概率。。。。。预设边界框的中心坐标固定在一个cell当中,作者说这样能够加快网络收敛....没懂?
假设根据聚类得到的anchors文件内容为54,315, 85,28, 112,38, 142,57, 272,129, 351,178, 381,98, 410,211, 504,129,三个预测层的特征图大小以及每个特征图上预设边界框的尺寸如下表:
特征图层 特征图大小 预设边界框尺寸 预设边界框数量
图层1 13x13 (381,98);(410,211); (504,129) 13x13x3
图层2 26x26 (142,57); (272,129; (351,178) 26x26x3
图层3 52x52 (54,315); (85,28); (112,38) 52x52x3
2.3损失函数计算(??)
YOLOv3的损失函数主要分为三个部分:目标定位偏移量损失,目标置信度损失以及目标分类损失,其中是平衡系数。
目标置信度可以理解为预测目标矩形框内存在目标的概率,目标置信度损失采用的是二值交叉熵损失,其中,表示预测目标边界框i中是否真实存在目标,0表示不存在,1表示存在。表示预测目标矩形框i内是否存在目标的Sigmoid概率(将预测值通过sigmoid函数得到)。
目标定位损失采用的是真实偏差值与预测偏差值差的平方和,其中表示预测矩形框坐标偏移量(注意网络预测的是偏移量,不是直接预测坐标),表示与之匹配的GTbox与默认框之间的坐标偏移量,为预测的目标矩形框参数,为默认矩形框参数,为与之匹配的真实目标矩形框参数,这些参数都是映射在预测特征图上的。
***********************************************************************************************************************
三.关于keras创建、编译、训练模型
1.使用Model()(函数式模型)搭建模型
Keras 函数式模型Model()接口是用户定义多输出模型、非循环有向模型或具有共享层的模型等复杂模型的途径。只要你的模型不是类似 VGG 一条路走到黑的模型,或者你的模型需要多于一个的输出,那么总应该选择函数式模型。函数式模型是最广泛的一类模型,序贯模型(Sequential)只是它的一种特殊情况。
Model()提供了接口供使用,很便利的调用已经训练好的模型,像 VGG,Inception 这些强大的网络。但要注意,调用模型的同时,也调用了它的权重数据。函数式模型创建好之后进行编译 compile 和训练 fit。
from keras.models import Model
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])
model.fit(data, labels)
2.编译创建好的模型
网络模型搭建完后,需要对网络的学习过程进行配置,否则在调用 fit 或 evaluate 时会抛出异常。可以使用 compile(self, optimizer, loss, metrics=None, sample_weight_mode=None, weighted_metrics=None, target_tensors=None) 来完成配置
compile() 主要接收前三个参数:
loss:字符串类型,用来指定损失函数,如:categorical_crossentropy或者自己定义
optimizer:字符串类型,用来指定优化方式,如:rmsprop,adam,sgd
metrics:列表类型,用来指定衡量模型的指标,如:accuracy
3.训练模型
训练模型一般使用fit()/fit_generator()函数,fit_generator使用生成器喂数据可以并行预处理数据
fit(self, x, y, batch_size=16, epochs=10, verbose=1, callbacks=None, validation_split=0.0,validation_data=None, shuffle=True,class_weight=None, sample_weight=None, initial_epoch=0)
x: 训练数据数组。如果输入的是框架本地的张量(如 Tensorflow 的数据 tensors ), x 可以是 None (默认) 。
y: 目标(标签)数据数组。如果输入的是框架本地的张量(如 Tensorflow 的数据 tensors ), y 可以是 None (默认) 。
batch_size: 指定 batch 的大小,为整数或者为 None。如果没有指定,默认为 32。
epochs: 指定训练时全部样本的迭代次数,为整数。
四、关于pb、h5、weights文件
通常使用 TensorFlow时保存模型都使用 ckpt 格式的模型文件,tf.train.Saver().save(sess,ckpt_file_path,max_to_keep...) 使用下语句来恢复所有变量信息saver.restore(sess,tf.train.latest_checkpoint('./ckpt'))。这种方式有几个缺点,首先这种模型文件是依赖 TensorFlow 的,只能在其框架下使用;其次,在恢复模型之前还需要再定义一遍网络结构,然后才能把变量的值恢复到网络中。
pb文件是表示 MetaGraph 的 protocol buffer格式的文件,MetaGraph包括计算图、数据流,以及相关的变量和输入输出signature以及 asserts 指创建计算图时额外的文件。谷歌推荐的保存模型的方式是保存模型为 PB 文件,它具有语言独立性,可独立运行,封闭的序列化格式,任何语言都可以解析它,它允许其他语言和深度学习框架读取、继续训练和迁移 TensorFlow 的模型。它的主要使用场景是实现创建模型与使用模型的解耦, 使得前向推导 inference的代码统一。另外保存为 PB 文件时候,模型的变量都会变成固定的,导致模型的大小会大大减小,适合在手机端运行。
h5文件是层次数据格式第5代的版本(Hierarchical Data Format,HDF5),它是用于存储科学数据的一种文件格式和库文件。Keras的模型是用hdf5存储的。h5文件将包含模型的结构,以便重构该模型;模型的权重;训练配置(损失函数,优化器等);优化器的状态,以便于从上次训练中断的地方开始。
weights文件是权值存储文件,训练所得到的权值参数都存在这个文件中。只保存权重不保存模型。
————————————————
版权声明:本文为CSDN博主「xran」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xranmail/article/details/103702186