(2)YOLOv1源码

YOLOv1的网络结构还是比较简单的,因为他的关键部分在于他的逻辑,就是他的输入输出的映射和损失函数设计,先看一下yolov1的整体结构:

 就是简单的卷积网络的结构。源码来自https://github.com/TowardsNorth/yolo_v1_tensorflow_guiyu,首先看一下文件结构:

 那么看一下训练文件train.py里面定义了训练的main函数:

 可以看到里面有个默认的权重Yolo_small.ckpt,这个并不是哪里来的,其实也是自己训练的,就是先使用这个网络在imagenet训练一个分类模型作为预训练模型,然后在用目标检测的数据微调。

直接看Sovlver类里的train方法:

 很简单,就是一个获得数据,然后训练计算loss的过程,data是一个pascal_voc类,看一下他的get方法:

 看来这个gt_labels很重要,他是在初始化的时候通过prepare赋值的:

 真的是一环套一环,还是要看load_labels:

好吧继续看load_opascal_annotation:

 返回的label是这样的cell_size*cell_size*25的张量:

 这个label的shape是7*7*25,与输出的维度7*7*30不对应,这个会在计算loss的时候处理。训练数据获取完毕,看一下计算loss部分,这个net是个YOLO类,他里面定义了total_loss:

 所以只要看tf.losses里面都add了哪些loss就好:

 我们也分这几部分看,先看处理网络输出和label的部分:
因为网络输出的是全连接层的输出,所以要对predict进行reshape成我们需要的7*7*30的形式, 他没有把他们组合在一起,因为待会计算loss是分开计算的,所以是从predict中提取了各部分的信息,加起来20+2+2*4=30:

然后是从label提取信息:

 同样得到了置信度response、boxes、和类别classes,注意他对boxes进行了归一化,除以了图像的尺寸。而且为了和predict每个cell预测的两个predict_box对应,把label里的box都进行复制操作。

接下来对预测的predict_boxes进行处理:

 这表示输出的predict_boxes里面的(center_x,center_y,w,h)里面的center_x,center_y是相对于那个cell进行归一化的,现在要把他和label里的boxes对应起来,相对于全图进行归一化。一张图表明:

 先计算每个cell对应的2个box和label里的iou,然后选取有目标的cell里的iou最大的作为目标object_mask存储这些信息,而其他的作为没有目标的noobject_mask:

 最后还要处理label中目标框的center_x,center_y,宽度w和高度h,这也定义了输出的含义,他输出的是w和h的平方根,要和计算loss公式里的保持一致,loss里进行了开方操作:

 这就是最后得到的用于计算位置损失的boxes_tran.

 这里有个tips,就是计算置信度的时候使用了公式:

对于label计算这个公式,Pr(Object)=1,最终使用的是iou_predict_truth来作为置信度目标,这样有更深层的含义,就是希望学习到的是如何计算当前预测的box与ground_truth的iou来作为置信度,这个真的好厉害啊,但是也好复杂,他想要学习到这个信息,是不是最起码得对目标位置有个准确的认识,然后还要学习到iou计算公式,最终才会计算到这个iou。

最终loss就都计算完了。

定义的模型训练的train_op在Solver里的初始化部分:

 在测试部分就不用了计算loss,而是要对输出进行筛选的操作,在test文件里的interpret_output函数:

首先把预测的boxes计算成为相对于原图的尺寸,就是上面那个图里面的计算,最后在*image_size就好:

 总共得到7*7*2个bbox。然后计算相对于类别的score:

 这样会得到7*7*2*20个分数对应着7*7*2个框在20个类别上的表现:

首先对所有的7*7*2*20个score进行筛选,选出满足要求的score对应的box得到boxes_filtered ,注意这里可能一个box有好多个类别的score都满足,但是一个box应该对应一个类别,这个类别就记录在class_num_filtered,。最后排序得到score从大到小的排序box表示为boxes_filtered,并且选取对应的置信度pros_filtered,这里一个cell可能两个box都留下来(我也不知道为什么不一个cell留一个,不是说好一个cell预测一个类别吗):

 进行nms并保存结果:

 看看论文中最后筛选的方法:

 

 

 看了好多版本代码,都没有按照论文中把各个类别分开对待,而且无论是论文还是代码,也都没有对一个cell对应的两个bbox进行筛选,不知道为什么,代码中只是保证一个bbox对应一个类,可是并没有保证一个cell一类,后面在看看吧。

最后看看YOLONET的定义吧:

 来看看他的结构吧:

 1     def build_network(self,     #用slim构建网络,简单高效
 2                       images,
 3                       num_outputs,
 4                       alpha,
 5                       keep_prob=0.5,
 6                       is_training=True,
 7                       scope='yolo'):
 8         with tf.variable_scope(scope):
 9             with slim.arg_scope(
10                 [slim.conv2d, slim.fully_connected],  #卷积层加上全连接层
11                 activation_fn=leaky_relu(alpha),   #用的是leaky_relu激活函数
12                 weights_regularizer=slim.l2_regularizer(0.0005), #L2正则化,防止过拟合
13                 weights_initializer=tf.truncated_normal_initializer(0.0, 0.01)  #权重初始化
14             ):
15                 net = tf.pad(
16                     images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]),
17                     name='pad_1')
18                 net = slim.conv2d(
19                     net, 64, 7, 2, padding='VALID', scope='conv_2')  #这里的64是指卷积核个数,7是指卷积核的高度和宽度,2是指步长,valid表示没有填充
20                 net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_3')  #max_pool, 大小2*2, stride:2
21                 net = slim.conv2d(net, 192, 3, scope='conv_4')   #这里的192是指卷积核的个数,3是指卷积核的高度和宽度,默认的步长为1
22                 net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_5')  #max_pool,大小为2*2,strides:2
23                 net = slim.conv2d(net, 128, 1, scope='conv_6') #128个卷积核,大小为1*1,默认步长为1
24                 net = slim.conv2d(net, 256, 3, scope='conv_7') #256个卷积核,大小为3*3,默认步长为1
25                 net = slim.conv2d(net, 256, 1, scope='conv_8') #256个卷积核,大小为1*1,默认步长为1
26                 net = slim.conv2d(net, 512, 3, scope='conv_9') #512个卷积核,大小为3*3,默认步长为3
27                 net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_10') #max_pool, 大小为2*2,stride:2
28                 net = slim.conv2d(net, 256, 1, scope='conv_11')  #256个卷积核,大小为1*1, 默认步长为1
29                 net = slim.conv2d(net, 512, 3, scope='conv_12')  #512个卷积核,大小为3*3,默认步长为1
30                 net = slim.conv2d(net, 256, 1, scope='conv_13')  #256个卷积核,大小为1*1, 默认步长为1
31                 net = slim.conv2d(net, 512, 3, scope='conv_14')   #512个卷积核,大小为3*3, 默认步长为1
32                 net = slim.conv2d(net, 256, 1, scope='conv_15')  #256个卷积核,大小为1*1, 默认步长为1
33                 net = slim.conv2d(net, 512, 3, scope='conv_16')  #512个卷积核,大小为3*3, 默认步长为1
34                 net = slim.conv2d(net, 256, 1, scope='conv_17')  #256个卷积核,大小为1*1, 默认步长为1
35                 net = slim.conv2d(net, 512, 3, scope='conv_18')   #512个卷积核,大小为3*3, 默认步长为1
36                 net = slim.conv2d(net, 512, 1, scope='conv_19')  #256个卷积核,大小为1*1, 默认步长为1
37                 net = slim.conv2d(net, 1024, 3, scope='conv_20')  #1024个卷积核,大小为3*3,默认步长为1
38                 net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_21') # max_pool, 大小为2*2,strides: 2
39                 net = slim.conv2d(net, 512, 1, scope='conv_22')  #512卷积核,大小为1*1,默认步长为1
40                 net = slim.conv2d(net, 1024, 3, scope='conv_23') #1024卷积核,大小为3*3,默认步长1
41                 net = slim.conv2d(net, 512, 1, scope='conv_24')  #512卷积核,大小为1*1,默认步长1
42                 net = slim.conv2d(net, 1024, 3, scope='conv_25')  #1024卷积核,大小为3*3, 默认步长为1
43                 net = slim.conv2d(net, 1024, 3, scope='conv_26')  #1024卷积核,大小为3*3,默认步长为1
44                 net = tf.pad(
45                     net, np.array([[0, 0], [1, 1], [1, 1], [0, 0]]),
46                     name='pad_27')     #padding, 第一个维度batch和第四个维度channels不用管,只padding卷积核的高度和宽度
47                 net = slim.conv2d(
48                     net, 1024, 3, 2, padding='VALID', scope='conv_28')  #1024卷积核,大小3*3,步长为2
49                 net = slim.conv2d(net, 1024, 3, scope='conv_29')   #1024卷积核,大小为3*3,默认步长为1
50                 net = slim.conv2d(net, 1024, 3, scope='conv_30')   #1024卷积核,大小为3*3,默认步长为1
51                 net = tf.transpose(net, [0, 3, 1, 2], name='trans_31') #转置,由[batch, image_height,image_width,channels]变成[bacth, channels, image_height,image_width]
52                 net = slim.flatten(net, scope='flat_32')  #将输入扁平化,但保留batch_size, 假设第一位是batch,实际上第一维也是batch
53                 net = slim.fully_connected(net, 512, scope='fc_33')   #全连接层,神经元个数
54                 net = slim.fully_connected(net, 4096, scope='fc_34')  #全连接层,神经元个数
55                 net = slim.dropout(  #dropout,防止过拟合
56                     net, keep_prob=keep_prob, is_training=is_training,
57                     scope='dropout_35')
58                 net = slim.fully_connected(    #全连接层
59                     net, num_outputs, activation_fn=None, scope='fc_36')
60         return net

很平淡,就和我们上面展示的网络结构一样,其实主要部分是最开始介绍的的计算loss和制作label部分。

 

西工大陈飞宇还在成长,如有错误还请批评指教。

 

posted @ 2020-06-26 15:56  西工大陈飞宇  阅读(1670)  评论(0编辑  收藏  举报