看Yolo3代码的笔记(写得也就自己能到懂)

对github上keras版本的yolo3程序的解读:https://github.com/experiencor/keras-yolo3   一定要对着代码看

第一部分 ——整体:

从train.py开始看,只要这个运行步骤看懂了,其它的就都懂了。

主函数(就两步):
1.创建了一个解释器,将config.json的文件读入
2.将读入的数据放到_main_函数中处理

_main_函数:

1.读入了config.json中的数据
得到一个 config 的字典,里面是config.json的参数。config:
{'model': {'max_input_size': 448, 'min_input_size': 288, 'labels': ['sit', 'stand', 'stretch', 'turn'], 'anchors': [55, 69, 75, 234, 133, 240, 136, 129, 142, 363, 203, 290, 228, 184, 285, 359, 341, 260]}, 'valid': {'valid_image_folder': '/home/jtl/keras-yolo3-master/data/img/', 'valid_times': 1, 'valid_annot_folder': '/home/jtl/keras-yolo3-master/data/ann_test/', 'cache_name': 'mouse.pkl'}, 'train': {'train_times': 4, 'nb_epochs': 100, 'noobj_scale': 1, 'tensorboard_dir': 'logs', 'batch_size': 1, 'grid_scales': [1, 1, 1], 'saved_weights_name': 'm.h5', 'train_annot_folder': '/home/jtl/keras-yolo3-master/data/ann/', 'class_scale': 1, 'xywh_scale': 1, 'ignore_thresh': 0.5, 'train_image_folder': '/home/jtl/keras-yolo3-master/data/img/', 'gpus': '0,1', 'obj_scale': 5, 'warmup_epochs': 3, 'learning_rate': 0.0001, 'debug': True, 'cache_name': 'mouse_train.pkl'}}

2.调用create_training_instances函数,进行数据处理。
输入是训练集图片文件夹、标注文件夹、pkl,测试及图片文件夹、标注文件夹、pkl,标签。
输出是 训练集所有xml文件的内容,测试集所有xml文件的内容,标签,每张图中最多box数量(即每张图片有几个bounding box,取最大的数)
xml文件的内容(字典):{'height': 0, 'width': 0, 'filename': '/home/jtl/keras-yolo3-master/data/img/000005.jpg', 'object': [{'xmax': 359, 'ymin': 152, 'ymax': 279, 'name': 'turn', 'xmin': 179}]}
标签(列表):['sit', 'stand', 'stretch', 'turn']
max_box_per_image:1(我每张图只标注了1个检测目标)

3.创建生成器分别创建了训练生成器和测试生成器的类。生成器里会对输入图片进行打乱,还有一些图像增强的方法,比如随机镜像,尺寸的变化,裁剪,HSV空间随机失真等等。
输入:    1.实例(第二步中返回得到的xml文件内容)
    2.anchors(最原始的anchors,从config字典中读入)
    3.labels
    4.下采样(神经网络输入与输出的比率)
    5.max_box_per_image
    6.batch_size(config中读入,每批量的数据包)
    7&8.网络最小尺寸和网络最大尺寸
    9.shuffle(是否随机打乱)
    10.jitter(一个比率,数据增强的随机参数)
    11.标准化norm函数(把图片除以255.,就是所有像素都在0-1)
输出:就是得到训练集生成器的类和测试集生成器的类,但是没见到调用类中的函数啊。
ps:看了一下生存器的长度,等于标注文件夹中标注文件的个数/config.json中的batch_size。即有100个输入 batch_size=4,生成器长度为25。有100个输入 batch_size=1,生成器长度为100。

4.创建模型网络,如果已经存在权重,则读入以及存在的模型(同时warmup_epochs=0),否则读入预训练模型backend.h5。 调用了create_model函数
输入:    1.标签的长度(几分类)
    2.anchors(最原始的anchors,从config字典中读入)
    3.max_box_per_image
    4.最大网格(一个二维列表,config中读入的[max_input_size,max_input_size])
    5.batch_size(config中读入)
    6.warmup_batches(预热的batch,readme中说预热部分会调整anchors。 warmup_batches=warmup_epochs(来自config)×train_times(来自config)×生成器长度 )
    7.忽略阈值(ignore_thresh来自config,判断最大IoU与ignore_thresh,若小于则忽略?)
    8.多GPU
    9.储存的权重(来自config,权重文件名)
    10.学习率(来自文件)
    11.网格尺寸(来自config[1,1,1])
    12&13&14&15. obj_scale,noobj_scale,xywh_scale,class_scale。都是来自config,具体看细节。
输出:得到一个训练模型和一个推理模型(用于保存)。训练模型是在推理模型后,增加了损失函数和优化器。

5.开始训练
创建callbacks,调用了create_callbacks函数,输入:权重模型,保存logs的文件夹,推理模型。
输出:[earlystopping(提前停止),ModelCheckpoint(用于保存最优模型),reduce_on_plateau(降低学习率),CustomTensorBoard(记录每个批次后的损失)]

调用train_model.fit_generator开始训练。
输入:    1.第三步中的训练集生成器(这里必须为一个生成器或者keras.utils.Sequence)
    2.steps_per_epoch 在这里等于生成器的长度乘以train_times。(原则上等于生成器的长度,这里乘以train_times,应该是每一轮次都训练了train_times次)
    3.epochs(来自config,加上了预热轮次)
    4.verbose(日志显示模式)
    5.callbacks(之前得到的回调函数)
    6.workers(最大进程数)
    7.max_queue_size(生成器队列最大尺寸)

6.评价
调用了evaluate函数
输入:    1.模型
    2.生成器
    以下为隐含输入:
    3.iou_threshold (用于考虑检测是 positive 还是 negative。 是否是一个正向的包围检测框)
    4.obj_thresh (用于区分对象还是非对象object,检测框中是否含有目标)
    5.nms_thresh (用于判断两个检测是否重复的阈值)------------多目标检测可以改这个阈值
    6&7.net_h和net_w (把图像输入到模型的尺寸)
    8.save_path=None
输出:

 

第二部分——生成器、模型、评价函数

接3:
生成器类BatchGenerator:
首先,BatchGenerator继承了keras.utils.Sequence 。查阅keras文档‘每一个 Sequence 必须实现 __getitem__ (看语法书)和 __len__ 方法。 如果你想在迭代之间修改你的数据集,你可以实现 on_epoch_end。 __getitem__ 方法应该范围一个完整的批次。’ 
__len__(self):计算每一个epoch的迭代次数
__getitem__(self, idx):用于生成每个batch数据
            1. self._get_net_size:每十个batch改一次size。
            2.确定每个batch的左右边界和初始化。r_bound-l_bound=batch_size,x_batch是四维的输入图片[r_bound-l_bound,长,宽,RGB]; 
            t_batch有6个维度[r_bound-l_bound,1(?),1(?),1(?),最多的box数,4个真实bounding box]; 初始化输入输出yolo_1,yolo_2,yolo_3都是5维,为yolo3的三个输出
            [r_bound-l_bound,采样后的长(网格的长),采样后的宽,anchors个数(3),4+1+len(labels)],dummy_yolo_1、2、3都只有两维[r_bound-l_bound,1]
            3.进入一个batch内部的循环,先对每个batch中的输入图片做数据增强,返回增强后的图片和增强后的真实bounding box坐标(输入原来图像和xml读取的值)
            4.要找到最优的先验anchor box, 标签的bounding box和先验anchor做IoU(无关坐标,只计算长宽最相近的,所以实际上ymin和xmin都为0,只有ymax和xmax有数),要知道每个
            先验的anchor对应相应的yolo特征图(9个先验的anchor,111222333分布)。
            5.根据第4步得到的最优先验的anchor,将ymin,ymax,xmin,xmax转换 sigma(t_x) + c_x 、sigma(t_y) + c_y、t_w、t_h。(yolo要计算得到 t_x,t_y,t_w,t_h)
            6.对应yolo特征网络中,对应batch中图片,对应网格内,对应先验anchor中,写入9个标签值
            7.真实包围框写入t_batch(这个为啥维数众多,而且取特征图下的中心坐标,和输入下的box长和宽???)
            8.返回    [正则+数据增强后的图片,中心点和长宽矛盾的t_batch,与最优anchor有关保存了真实9个标签的三个yolo×3] [只初始化别的啥也没干的dummy_yolo×3]
on_epoch_end:在每一次epoch结束是否需要进行一次随机,重新随机一下index;在yolo里没有用。

接4:
creat_model函数中主要调用了create_yolov3_model,这个函数搭建yolo模型,先看一下函数输入:1.nb_class标签的长度    2.原始anchors来自config中    3.max_box_per_image每张图最多的box    4.最大网格config中读入的[max_input_size,max_input_size](默认正方形)     5.batch_size     6.warmup_batches(预热的batch)    7.ignore_thresh        8.grid_scales    9.obj_scale    10.noobj_scale    11.xywh_scale    12.class_scale
    create_yolov3_model中:
            1.模型的输入,与生成器中第一个返回值一致。但是每次只读一张或一个: 输入图片, true_boxes,三种尺度的ture_yolo_1、ture_yolo_2、ture_yolo_3
            2.搭建网络,前74层(注意残差也算一层,还有1×1卷积)中有52层卷积(论文里叫darknet-53),中间有一个_conv_block函数,设置了残差位置,标准化数据(BatchNormalization,yolo2论文中说用这个代替了
            dropout),leakyReLU的激活函数。第74层后,通过几层卷积,在79层单独分出来一路,在第81层后变成3*(5+nb_class)维作为pred_yolo_1 —— 结合input_image,pred_yolo_1、ture_yolo_1、true_boxes得到loss_yolo1
                            79层后另一路做几个卷积,在86层中结合第61层的数据,继续卷积在91层除分出来一路,在93层后变成3*(5+nb_class)维作为pred_yolo_2-结合input_image,pred_yolo_2、ture_yolo_2、true_boxes得到loss_yolo2
                            91层后另一路做卷积,在98层处结合36层的数据,继续卷积,在105层后变成3*(5+nb_class)维作为pred_yolo_3-结合input_image,pred_yolo_3、ture_yolo_3、true_boxes得到loss_yolo3
                            具体说一下输入,从模型建立开始,只用到了输入图片即(input_image),然后在计算三个loss_yolo中,用到了true_boxes,以及分别用到了三个真实yolo。
            3.loss_yolo由YoloLayer这个类生成,这个咱单独说。
            4.keras.models 的Model类 ,训练网络输入输入图像,真实的包围框,分别属于三个yolo网络的标签,输出三个损失
                                                           推断模型:输入图像,输出三个特征图下的预测
返回训练模型和推测模型

接上文:
首先,YoloLayer 继承了keras中的Layer类。输入1.原始anchors来自config中    2.三个loss_layer读入不同的倍数的max_grid即[max_input_size,max_input_size],448×448,896×896,1792×1792    3.batch_size     4.warmup_batches(预热的batch)    5.ignore_thresh        6.grid_scales    7.obj_scale    8.noobj_scale    9.xywh_scale    10.class_scale;  然后最神奇的是后面还跟了([input_image, pred_yolo_1, true_yolo_1, true_boxes])这个小尾巴(以loss_yolo1为例)。
keras文档说:对于那些包含了可训练权重的自定义层,你应该自己实现这种层。自己搭建这个层,只要三种方法即可:
build(input_shape): 这是你定义权重的地方。这个方法必须设self.built = True,可以通过调用super([Layer], self).build()完成。/ 
call(x): 这里是编写层的功能逻辑的地方。你只需要关注传入call的第一个参数:输入张量,除非你希望你的层支持masking。/
compute_output_shape(input_shape): 如果你的层更改了输入张量的形状,你应该在这里定义形状变化的逻辑,这让Keras能够自动推断各层的形状。
一步步来
__init__:
    1.anchor输入是6个数字的列表(三个yolo,每个三个先验anchor),转变为张量,shape=(1,1,1,3,2)
    2.张量扩张和拼接,目的是得到self.cell_grid shape=(batch_size,max_grid,max_grid,3,2),注意不同loss_layer的max_grid不同,batch=4,分别得到(4,448,448,3,2)、(4,896,896,3,2)、(4,1792,1792,3,2)
    3.super继承于Layer,初始化Layer中的**kwarg,在这里没有kwarg的输入,所以没有用。topology.py中有Layer类的定义,复杂的很。
__build__:采用了官方给的定义方法 super(YoloLayer, self).build(input_shape),这个input_shape在keras的底层函数里,与输入有关。反正不用我们操心。
__call__:这个是这个类的核心了,用于计算损失loss。还记得构造类的时候的小尾巴([input_image, pred_yolo_1, true_yolo_1, true_boxes]),就是call的输入x,上来input_image, y_pred, y_true, true_boxes=[input_image, pred_yolo_1, true_yolo_1, true_boxes]
    1.调整y_pred ,变成[batch, grid_h, grid_w, 3, 4+1+nb_class]的张量,grid_h, grid_w是特征映射,由y_ture得到,同时得到输入的net_h,net_w,以及tf.cast得到grid和net的factor(tf.float32), [1,1,1,1,2])
    2.得到真实的t_x,t_y,t_w,t_h,confidence,class与预测的t_x,t_y,t_w,t_h,confidence,class_probabilities。true_box =[特征图中的坐标(浮点数比如(5.1,5.2),意思是网格加上偏移的结果),输入图像的宽和高],根据这个得到true_xy(?,1,1,1,1,2)维度,ture_wh,比较每个预测框与真实框:由预测得到pred_xy,pred_wh。通过一系列操作得到真实区域true_areas(?,1,1,1,1)维(?是batch)

    这里我们还是倒着看,要得到loss = loss_xy + loss_wh + loss_conf + loss_class,看计算损失的函数(yolo1的论文中有提到损失如何计算)
    object_mask:是在y_true第四个维度的基础上在加一个维度,代表[batch的第几个输入,特征图的长,特征图的宽,第几个anchor,是否有目标],这个是否有目标是增加的一个维度表示,非零即一,但是怎么传入的?
    true_box_xy, true_box_wh, xywh_mask:这几个值,通过tf_cond(相当于if,1则运行lambda1,0则运行lambda2)判断是否还在Warm-up training决定不同的调整,若不是在warm_up training,则true_box_xy和true_box_wh等于y_true中提取,xywh_mask=object_mask
    wh_scale:与true_box_wh有关,the smaller the box, the bigger the scale
    pred_box_xy、pred_box_wh、pred_box_conf:从y_pred中获得
    基本就通过真实的和网络标签中获取的数,压缩求和,得到四个loss
    obj_scale:有目标的损失权重
    noobj_scale:无目标的损失权重
    xywh_scale:定位的损失权重
    class_scale:分类的损失权重
3.最后loss乘以grid_scale(三个loss_yolo的loss权重不一样),可知grid_scale是三个特征图下损失的权重。
compute_output_shape:网络输出多行一列的。

接6:
子函数evaluate:
1.输入:模型、生成器、iou_threshold、obj_thresh、nms_thresh、net_h和net_w、save_path=None
2.读取测试集中每一张图片。调用get_yolo_boxes函数:,返回416×416图像中的bounding_box和class
            1.batch_input =[读入的数量,图片的高度,图片的宽度,3个通道]
            2.预处理图片preprocess_input,批量调整大小(416×416),返回值的shape(1,416,416,3)第一个维是第几张的图片
            3.model.predict_on_batch每个batch(以下以batch=1测试)进行预测,会得到一个三个array值的列表,分别(1, 13, 13, 27), (1, 26, 26, 27) ,(1, 52, 52, 27)
            ( 重要 yolo3中最后得到13×13的特征图,倒数的26×26特征图,52×52特征图, 3×(4(bounding box offsets)+1(objectness prediction)+4(分类))=27 )
            4.用decode_netout进行对三个特征图下的结果分别进行解码,每个grid_h×grid_w×3个anchor(9个数),前四个':4'是x,y,w,h,第五个'4'是objectness score
            (也是要大于obj_thresh才有效),后面是分类。x,y和objectness用了sigmod函数,分类还是用了softmax,大于obj_thresh的为1,否则为0。根据论文中的公式计算x,y,w,h。
            (x,y只是定位了网格的位置)    返回的boxes保存了认为有目标的anchor,包括三个特征图。
            5.correct_yolo_boxes在416×416的图片中得到正确的bounding_box的位置(anchor_box变为bounding_box)
            6.do_nms非最大抑制 输入为每张图片中得到的大于obj_thresh的bounding_box和非最大阈值。同一类别,IoU大于nms_thresh的,取分类分数最高框。
3.根据objectness score分数排序,读取检测的结果(只有分类,没有包围框)和标注的结果,all_detections是一个[需要检测的数据数量,标签的长度],10张图,4 个标签就是[10,4],每一维度的中都代表第几张图,label的检测,有几个。比如第一张图的第一个标签,all_detections[0][0]=[]即表示第一张图没有第一个标签的检测。all_detections[0][0]=[[ 237. -61. 488. 583. 0.99811834] [207. 125. 518. 396. 0.94932592]],表示第一张图有两个第一个标签的检测,前四项是位置,第五个是概率。all_annotations同样的,但是只包含坐标,不包含概率(第一张图第一个标签下有坐标就是有目标)
4.对每类标签:annotations是真实的框,detections是检测到的框和概率,scores是包围框的记录, num_annotations统计了每个label下测试集真实目标的个数。
scores 记录每一个检测detections中每检测到包围框(若因为阈值不到或者被非最大抑制,即有包围框概率为0,scores增加一个0;有包围框有目标概率,scores 增加一个概率的值),所以对每一个标签的循环,scores记录了检测到这个标签类别的包围框的数目(不论概率是多少)。对于检测到的每个包围框,检测有而标签没有,或者检测的包围框和标签的包围框重叠低于iou_threshold,则false_positives增加1,true_positives增加0;否则true_positives增加1,则false_positives增加0。
5.对每类标签:true_positives和false_positives,np.cumsum,计算recall和precision
false_positives [ 1.  0.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
true_positives [ 0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
false_positives_cumsum [  1.   1.   2.   3.   4.   5.   6.   7.   8.   9.  10.  11.  12.  13.  14.  15.  16.  17.  18.  19.  20.  21.  22.]
true_positives_cumsum [ 0.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
recall [ 0.   0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5]
precision [ 0.          0.5         0.33333333  0.25        0.2         0.16666667  0.14285714  0.125       0.11111111  0.1         0.09090909  0.08333333  0.07692308  0.07142857  0.06666667  0.0625      0.05882353  0.05555556  0.05263158  0.05        0.04761905  0.04545455  0.04347826]
通过召回率和准确率计算平均精度
输出:各个分类的检测精度的字典:average_precisions  {0: 0.22866917984160928, 1: 0.78000000000000003, 2: 0.17642920804685511, 3: 0.27806373346786883}

你可能感兴趣的:(人工智障)