ssd_pascal.py

 

本博文参考了网上一些其他博文资料,对此一并表示感谢!

from __future__ import print_function  #为了在老版本的Python中兼顾新特性

import caffe
from caffe.model_libs import *
from google.protobuf import text_format




import math
import os
import shutil
import stat
import subprocess
import sys




##给基准网络(VGGNet等)后面增加额外的卷积层(为了避免此处的卷积层的名称和基准网络卷积层的名称重复,这里可以用基准网络最后一个层的名称进行开始命名),具体实现方法可以对照caffe/python/caffe/model_libs.py
def AddExtraLayers(net, use_batchnorm=True, lr_mult=1):
    use_relu = True




    # Add additional convolutional layers.
    # 19 x 19
    #生成附加网络的第一个卷积层,卷积核的数量为256,卷积核的大小为1*1,pad的尺寸为0,stride为1.
    from_layer = net.keys()[-1] #获得基准网络的最后一层,作为conv6-1层的输入
    # TODO(weiliu89): Construct the name using the last layer to avoid duplication.
    # 10 x 10
    out_layer = "conv6_1"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 1, 0, 1,lr_mult=lr_mult)
    #conv6_1生成完毕


    ##生成附加网络的第一个卷积层,卷积核的数量为512,卷积核的大小为3*3,pad的尺寸为1,stride为2.
    from_layer = out_layer
    out_layer = "conv6_2"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 512, 3, 1, 2,lr_mult=lr_mult)
    ##conv6_2生成完毕


    ##5 x 5
    from_layer = out_layer
    out_layer = "conv7_1"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1,lr_mult=lr_mult)
    ##conv7_1生成完毕


    from_layer = out_layer
    out_layer = "conv7_2"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 1, 2,
      lr_mult=lr_mult)
    ##conv7_2生成完毕


    ##3 x 3
    from_layer = out_layer
    out_layer = "conv8_1"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1,lr_mult=lr_mult)
    ##conv8_1生成完毕


    from_layer = out_layer
    out_layer = "conv8_2"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 0, 1,lr_mult=lr_mult)
    ##conv8_2生成完毕


    ##1 x 1
    from_layer = out_layer
    out_layer = "conv9_1"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1,lr_mult=lr_mult)
    ##conv9_1生成完毕


    from_layer = out_layer
    out_layer = "conv9_2"
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 0, 1,lr_mult=lr_mult)
    ##conv9_2生成完毕


    return net


##相应地修改一下参数,包含caffe代码的路径,我们假设你是在caffe跟目录下运行代码
caffe_root = os.getcwd() ##获取caffe的根目录


##如果你想在生成所有训练文件之后就开始训练,这里run_soon给予参数Ture
run_soon = True 
   
##如果你想接着上次的训练,继续进行训练,这里的参数为Ture,(这个就是说可能你训练一般停止了,重新启动的时候,这里的Ture保证继续接着你上次的训练进行训练),否则为False,表示我们将从下面定义的预训练模型处进行加载。(这个表示就是不管你上次训练一半的模型了,我们直接从预训练好的基准模型哪里开始训练)
resume_training = True


##如果是Ture的话,表示我们要移除旧的模型训练文件,否则是不移除的
remove_old_models = False




##训练数据库文件. 由data/VOC0712/create_data.sh创建
train_data = "examples/VOC0712/VOC0712_trainval_lmdb"


##测试数据库文件. 由data/VOC0712/create_data.sh创建
test_data = "examples/VOC0712/VOC0712_test_lmdb"


##指定批量采样器
resize_width = 300
resize_height = 300
resize = "{}x{}".format(resize_width, resize_height)
batch_sampler = [
        {
                'sampler': {
                        },
                'max_trials': 1,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.1,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.3,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.5,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.7,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.9,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'max_jaccard_overlap': 1.0,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        ]
##以上是所谓的数据增强部分,具体可查看 caffe/src/caffe/utils/sampler.cpp文件中的详细定义


##以下是转换参数设置,可在caffe/src/caffe/proto/caffe.proto中查看 
train_transform_param = {
        'mirror': True,
        'mean_value': [104, 117, 123],   ##均值
        'resize_param': { ##存储数据转换器用于调整大小策略的参数的消息
                'prob': 1, ##使用这个调整策略的可能性
                'resize_mode': P.Resize.WARP, ##重定义大小的模式,caffe.proto中定义的枚举类型
                'height': resize_height,
                'width': resize_width,
                'interp_mode': [ ##插值模式用于调整大小,定义为枚举类型
                        P.Resize.LINEAR,
                        P.Resize.AREA,
                        P.Resize.NEAREST,
                        P.Resize.CUBIC,
                        P.Resize.LANCZOS4,
                        ],
                },
        'distort_param': { ##存储数据转换器用于失真策略的参数的消息
                'brightness_prob': 0.5, ##调整亮度的概率,默认为1
                'brightness_delta': 32,##要添加到[-delta,delta]内的像素值的数量,可能的值在[0,255]之内。 推荐32。  
                'contrast_prob': 0.5, ##调整对比度的概率
                'contrast_lower': 0.5, ##随机对比因子的下界。 推荐0.5
                'contrast_upper': 1.5, ##随机对比因子的上界。 推荐1.5
                'hue_prob': 0.5, ##调整色调的概率
                'hue_delta': 18, ##添加到[-delta,delta]内的色调通道的数量。 可能的值在[0,180]之内。 推荐36
                'saturation_prob': 0.5, ##调整饱和的概率
                'saturation_lower': 0.5,##随机饱和因子的下界。 推荐0.5
                'saturation_upper': 1.5,##随机饱和因子的上界。 推荐1.5
                'random_order_prob': 0.0,##随机排列图像通道的概率
                },
        'expand_param': { ##存储数据转换器用于扩展策略的参数的消息
                'prob': 0.5, ##使用这个扩展策略的可能性
                'max_expand_ratio': 4.0,##扩大图像的比例
                },
        'emit_constraint': { ##给定注释的条件
            'emit_type': caffe_pb2.EmitConstraint.CENTER, ##类型定义为枚举,此处选定为CENTER  
            }
        }
test_transform_param = { ##测试转换参数,类似于训练转换参数
        'mean_value': [104, 117, 123],
        'resize_param': {
                'prob': 1,
                'resize_mode': P.Resize.WARP,
                'height': resize_height,
                'width': resize_width,
                'interp_mode': [P.Resize.LINEAR],
                },
        }




##如果为true,则对所有新添加的图层使用批量标准化,目前只有非批量规范版本已经过测试
use_batchnorm = False ##是否使用批量标准化 
lr_mult = 1 ##基础学习率设定为1,用于下面的计算以改变初始学习率
##使用不同的初始学习率
if use_batchnorm:
    base_lr = 0.0004
else: ## 当batch_size = 1, num_gpus = 1时的学习率,由于上面use_batchnorm = false,所以我们一般调整初始学习率时只需更改这一部分,目前为0.001
    base_lr = 0.00004




## 如果需要你可以修改job名称.
job_name = "SSD_{}".format(resize)
print "job_name....is............................................",job_name
## 模型名称,可以修改
model_name = "VGG_VOC0712_{}".format(job_name)
print "model_name....is............................................",model_name
## 存储模型.prototxt的文件夹路径
save_dir = "models/VGGNet/VOC0712/{}".format(job_name)
## 存储模型快照的目录路径
snapshot_dir = "models/VGGNet/VOC0712/{}".format(job_name)
## 存储作业脚本和日志文件的目录路径
job_dir = "jobs/VGGNet/VOC0712/{}".format(job_name)
## 存储检测结果的目录路径
output_result_dir = "{}/data/VOCdevkit/results/VOC2007/{}/Main".format(os.environ['HOME'], job_name)
print "output_result_dir.....is......................................",output_result_dir
## 模型定义文件
train_net_file = "{}/train.prototxt".format(save_dir)
test_net_file = "{}/test.prototxt".format(save_dir)
deploy_net_file = "{}/deploy.prototxt".format(save_dir)
solver_file = "{}/solver.prototxt".format(save_dir)
## 快照前缀
snapshot_prefix = "{}/{}".format(snapshot_dir, model_name)
## 任务脚本路径
job_file = "{}/{}.sh".format(job_dir, model_name)




## 存储测试图像的名称和大小,Created by data/VOC0712/create_list.sh
name_size_file = "data/VOC0712/test_name_size.txt"
## 预训练模型。 我们使用全卷积截断的VGGNet
pretrain_model = "models/VGGNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel"
## 存储LabelMapItem
label_map_file = "data/VOC0712/labelmap_voc.prototxt"




## MultiBoxLoss的参数。在caffe/src/caffe/proto/caffe.proto可查找具体定义 
num_classes = 21 ##要预测的类的数量
share_location = True ##位置共享,如果为true,边框在不同的类中共享
background_label_id=0 ##是否使用先验匹配,一般为true
train_on_diff_gt = True ##是否考虑困难的ground truth,默认为true
normalization_mode = P.Loss.VALID ##如何规范跨越批次,空间维度或其他维度聚集的损失层的损失。 目前只在SoftmaxWithLoss和SigmoidCrossEntropyLoss图层中实现。按照批次中的示例数量乘以空间维度。 在计算归一化因子时,不会忽略接收忽略标签的输出。定义为枚举,四种类型分别是:FULL,除以不带ignore_label的输出位置总数。 如果未设置ignore_label,则表现为FULL;VALID;BATCH_SIZE,除以批量大小;NONE,不要规范化损失。
code_type = P.PriorBox.CENTER_SIZE ##bbox的编码方式。此参数定义在PriorBoxParameter参数定义解释中,为枚举类型,三种类型为:CORNER,CENTER_SIZE和CORNER_SIZE
ignore_cross_boundary_bbox = False ##如果为true,则在匹配期间忽略跨边界bbox。 跨界bbox是一个在图像区域之外的bbox。即将超出图像的预测边框剔除,这里我们不踢除,否则特征图边界点产生的先验框就没有任何意义。  
mining_type = P.MultiBoxLoss.MAX_NEGATIVE ##训练期间的挖掘类型。定义为枚举,分别为三种类型: 若为NONE则表示什么都不使用,这样会导致正负样本的严重不均衡;若为MAX_NEGATIVE则根据分数选择底片;若为HARD_EXAMPLE则选择基于“在线硬示例挖掘的基于训练区域的对象探测器”的硬实例,此类型即为SSD原文中所使用的Hard_negative_mining(负硬挖掘)策略。
neg_pos_ratio = 3. ##负/正比率1:3
loc_weight = (neg_pos_ratio + 1.) / 4. ##位置损失的权重
multibox_loss_param = { ##存储MultiBoxLossLayer使用的参数的消息
    'loc_loss_type': P.MultiBoxLoss.SMOOTH_L1,##位置损失类型,定义为枚举,有L2和SMOOTH_L1两种类型
    'conf_loss_type': P.MultiBoxLoss.SOFTMAX,##置信损失类型,定义为枚举,有SOFTMAX和LOGISTIC两种
    'loc_weight': loc_weight,
    'num_classes': num_classes,
    'share_location': share_location,
    'match_type': P.MultiBoxLoss.PER_PREDICTION, ##训练中的匹配方法。定义为枚举,有BIPARTITE和PER_PREDICTION两种。如果match_type为PER_PREDICTION(即每张图预测),则使用overlap_threshold来确定额外的匹配bbox 
    'overlap_threshold': 0.5, ##阀值大小,即IoU的大小
    'use_prior_for_matching': True, ##是否使用先验匹配,一般为true
    'background_label_id': background_label_id, ##背景标签类别编号,一般为0
##是否考虑困难的ground truth,默认为true
    'use_difficult_gt': train_on_diff_gt
    'mining_type': mining_type, ##训练期间的挖掘类型。定义为枚举,分别为三种类型: 若为NONE则表示什么都不使用,这样会导致正负样本的严重不均衡;若为MAX_NEGATIVE则根据分数选择底片;若为HARD_EXAMPLE则选择基于“在线硬示例挖掘的基于训练区域的对象探测器”的硬实例,此类型即为SSD原文中所使用的Hard_negative_mining(负硬挖掘)策略
    'neg_pos_ratio': neg_pos_ratio, ##负/正比率,1:3
    'neg_overlap': 0.5, ##对于不匹配的预测,上限为负的重叠。即如果重叠小于0.5则定义为负样本,Faster R-CNN设置为0.3。  
    'code_type': code_type,##bbox的编码方式。此参数定义在PriorBoxParameter参数定义解释中,为枚举类型,三种类型为:CORNER,CENTER_SIZE和CORNER_SIZE。
    'ignore_cross_boundary_bbox': ignore_cross_boundary_bbox, ##如果为true,则在匹配期间忽略跨边界bbox。 跨界bbox是一个在图像区域之外的bbox。即将超出图像的预测边框剔除,这里我们不踢除,否则特征图边界点产生的先验框就没有任何意义
    }
loss_param = { ##存储由损失层共享的参数的消息
    'normalization': normalization_mode, ##如何规范跨越批次,空间维度或其他维度聚集的损失层的损失。 目前只在SoftmaxWithLoss和SigmoidCrossEntropyLoss图层中实现。按照批次中的示例数量乘以空间维度。 在计算归一化因子时,不会忽略接收忽略标签的输出。定义为枚举,四种类型分别是:FULL,除以不带ignore_label的输出位置总数。 如果未设置ignore_label,则表现为FULL;VALID;BATCH_SIZE,除以批量大小;NONE,不要规范化损失。
    }




##预先生成参数
##输入图像的最小尺寸
min_dim = 300 ##300维
# conv4_3 ==> 38 x 38
# fc7 ==> 19 x 19
# conv6_2 ==> 10 x 10
# conv7_2 ==> 5 x 5
# conv8_2 ==> 3 x 3
# conv9_2 ==> 1 x 1
mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'conv9_2'] ##prior_box来源层,一共有6个,尺度分别为38*38、3*3、1*1、19*19、10*10、5*5,可以更改,很多改进都是基于此处的调整。
# in percent % 这里即是论文中所说的Smin=0.2,Smax=0.9的初始值,经过下面的运算即可得到min_sizes,max_sizes,这里产生很多改进
min_ratio = 20
max_ratio = 90
step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2))) ##取一个间距步长,即在下面for循环给ratio取值时起一个间距作用。可以用一个具体的数值代替,这里等于17;math.floor()函数表示:求一个最接近它的整数,它的值小于或等于这个浮点数
min_sizes = []
max_sizes = []
##以下从min_ratio至max_ratio+1每隔step=17取一个值赋值给ratio。注意xrange函数的作用,min_sizes.append()函数即把括号内部每次得到的值依次给了min_sizes。
for ratio in xrange(min_ratio, max_ratio + 1, step):
  min_sizes.append(min_dim * ratio / 100.)
  max_sizes.append(min_dim * (ratio + step) / 100.)
min_sizes = [min_dim * 10 / 100.] + min_sizes
max_sizes = [min_dim * 20 / 100.] + max_sizes
steps = [8, 16, 32, 64, 100, 300] ##box映射回原图的比例因子,即default box在feature map上的尺度*steps中的值,对应到输入的原图上的尺寸,这里原图时300*300。比如conv4_3输出特征图大小为38*38,而输入的图片为300*300,所以38*8约等于300,所以映射步长为8。

aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]] ##这里指的是横纵比,六种尺度对应六个产生prior_box的卷积层。具体可查看生成的train.prototxt文件,对应每层的aspect_ratio参数,此参数在caffe.proto中有定义,关于aspect_ratios如何把其内容传递给了aspect_ratio,在model_libs.py文件中有详细定义。第一个[2]即有[1,2,1/2]三个比例,其中1对应最大和最小的正方形,所以box有4个尺度,[2,3]有[1,2,1/2,3,1/3],1同样对应最大和最小的正方形,所以有6个尺度。ssd中每个map中的box的ratio:38*38、3*3、1*1都是4个,19*19、10*10、5*5都是6个。

## L2标准化conv4_3.
normalizations = [20, -1, -1, -1, -1, -1] ##对卷积层conv4_3做归一化。model_libs.py里产生了normallize层,具体的层定义,参看底层代码caffe/src/layers/Normalize_layer.cpp,这里每个数对应每个先验层,只要哪个层对应的数不为-1则产生normal。
## 用于对之前的bbox进行编码/解码的方差
if code_type == P.PriorBox.CENTER_SIZE: ##两种选择,根据参数code_type的选择决定,由于上面已经将code_type选定。有人理解为变量variance用来对bbox的回归目标进行放大,从而加快对应滤波器参数的收敛。除以variance是对预测box和真实box的误差进行放大,从而增加loss,增大梯度,加快收敛。另外,top_data += top[0]->offset(0, 1);已经使指针指向新的地址,所以variance不会覆盖前面的结果。prior_variance在model_libs.py中传递给了variance变量,然后利用prior_box_layer.cpp将其运算定义至priorbox_layer层中,具体可查看train.prototxt中的每一个先验卷积层层中产生先验框的层中,即**_mbox_priorbox。
  prior_variance = [0.1, 0.1, 0.2, 0.2]
else:
  prior_variance = [0.1]
flip = True ##如果为true,则会翻转每个宽高比。例如,如果有纵横比“r”,我们也会产生纵横比“1.0 / r”。故产生{1,2,3,1/2,1/3}
clip = False ##做clip操作是为了让prior的候选坐标位置保持在[0,1]范围内。在caffe.proto文件中有关于参数clip的解释,为“如果为true,则将先验框裁剪为[0,1]”
##以上两个参数所产生的结果均在prior_box_layer.cpp中实现


##求解参数
gpus = "0,1,2,3" ##GPU编号,从0开始
gpulist = gpus.split(",") ##获取GPU的列表
num_gpus = len(gpulist) ##获取GPU编号




##将batch_size分配给不同的GPU

batch_size = 32 ##训练样本输入数量,也就是训练时每次进入网络的图片数

accum_batch_size = 32 ##与batch_size搭配产生下面的iter_size

iter_size = accum_batch_size / batch_size ##迭代次数,如果iter_size=1,则前向传播一次后进行一次反向传递,如果=2,则两次前传后进行一次反传,这样做是减少每次传播所占用的内存空间,有的硬件不行的话就无法训练,但是增加iter会使训练时间增加,但是总的迭代次数不变。运算资源有限时可以通过改变batch_size和iter_size降低计算资源。在使用多个GPU时,每个GPU都是并行运行,并不会使缓存降下来,但是理论上可以缩短运行时间。博主在训练时出现”out of memory“,显存溢出,将batch_size降到8通过。

solver_mode = P.Solver.CPU
device_id = 0
batch_size_per_device = batch_size  ##批次传递
if num_gpus > 0:
  batch_size_per_device = int(math.ceil(float(batch_size) / num_gpus))  ##如果你有多块GPU则可以将这些训练任务均分给多块GPU训练,从而加快训练速度
  iter_size = int(math.ceil(float(accum_batch_size) / (batch_size_per_device * num_gpus))) ##多块GPU的iter_size大小计算,上面的是一块的时候
  solver_mode = P.Solver.GPU
  device_id = int(gpulist[0])




if normalization_mode == P.Loss.NONE: ##如果损失层的参数NormalizationMode选择NONE,即没有归一化模式,则基础学习率为本文件之上的base_lr=0.0004除以batch_size_per_device=32得到新的base_lr=1.25*10^(-5)
  base_lr /= batch_size_per_device
elif normalization_mode == P.Loss.VALID: ##同理,根据不同的归一化模式选择不同的base_lr。在上面我们看到了normalization_mode = P.Loss.VALID,而loc_weight = (neg_pos_ratio + 1.) / 4==1,所以新的base_lr=25*0.0004=0.001,这就是为什么我们最后生成的solver.prototxt文件中的base_lr=0.001的原因,所以如果训练发散想通过减小base_lr来实验,则要更改最上面的base_lr=0.0004才可以。
  base_lr *= 25. / loc_weight
elif normalization_mode == P.Loss.FULL:
  ##每幅图像大概有2000个先验bbox
  ## TODO(weiliu89):确切估计的先验数量
  base_lr *= 2000. ##base_lr=2000*0.0004=0.8 




##评估整个测试集
num_test_image = 4952 ##整个测试集图像的数量
test_batch_size = 8 ##测试时的batch_size
##理想情况下,test_batch_size应该整除num_test_image,否则mAP会略微偏离真实值。
test_iter = int(math.ceil(float(num_test_image) / test_batch_size))
##这里计算每测试迭代多少次可以覆盖整个测试集,和分类网络中的是一致的。这里4952/8=619,如果你的测试图片除以你的test_batch_size不等于整数,那么这里会取一个近似整数。




solver_param = { ##solver.prototxt文件中的各参数的取值
    # 训练参数 
    'base_lr': base_lr, ##上面的solver
    'weight_decay': 0.0005,
    'lr_policy': "multistep",
    'stepvalue': [80000, 100000, 120000], ##多步衰减
    'gamma': 0.1,
    'momentum': 0.9,
    'iter_size': iter_size,
    'max_iter': 120000,
    'snapshot': 80000,
    'display': 10,
    'average_loss': 10,
    'type': "SGD",
    'solver_mode': solver_mode,
    'device_id': device_id,
    'debug_info': False,
    'snapshot_after_train': True,
    ## 测试参数
    'test_iter': [test_iter],
    'test_interval': 10000,  ##测试10000此输出一次结果
    'eval_type': "detection",
    'ap_version': "11point",
    'test_initialization': False,
    }




##生成检测输出的参数
det_out_param = {
    'num_classes': num_classes,  ##类别数
    'share_location': share_location, ##位置共享
    'background_label_id': background_label_id, ##背景编号0
    'nms_param': {'nms_threshold': 0.45, 'top_k': 400}, ##非最大抑制参数,阀值为0.45,top_k表示最大数量的结果要保留,参数解释在caffe.proto中有介绍
    'save_output_param': { ##用于保存检测结果的参数,这一部分参数在caffe.proto中的SaveOutputParameter有定义
        'output_directory': output_result_dir, ##输出目录。 如果不是空的,我们将保存结果。前面我们有定义结果保存的路径
        'output_name_prefix': "comp4_det_test_", ##输出名称前缀
        'output_format': "VOC", ##输出格式。VOC  -  PASCAL VOC输出格式。COCO  -  MS COCO输出格式
        'label_map_file': label_map_file, ##如果要输出结果,还必须提供以下两个文件。否则,我们将忽略保存结果。标签映射文件。这在前面中有给label_map_file附文件,也就是我们在训练的时候所做的labelmap.prototxt文件的位置,详情参看博文《基于caffe使用SSD训练自己的数据》
        'name_size_file': name_size_file, ##即我们在训练时定义的test_name_size.txt文件的路径。该文件表示测试图片的大小
        'num_test_image': num_test_image, ##测试图片的数量
        },
    'keep_top_k': 200, ##nms步之后每个图像要保留的bbox总数。-1表示在nms步之后保留所有的bbox
    'confidence_threshold': 0.01, ##只考虑可信度大于阈值的检测。 如果没有提供,请考虑所有的框
    'code_type': code_type, ##bbox的编码方式
    }




##评估检测结果的参数
det_eval_param = { ##位于caffe.proto文件中的DetectionEvaluateParameter定义
    'num_classes': num_classes, ##类别数
    'background_label_id': background_label_id, ##背景编号,默认0
    'overlap_threshold': 0.5, ##IOU阈值,0.5
    'evaluate_difficult_gt': False, ##如果为true,也要考虑难以评估的grountruth
    'name_size_file': name_size_file, ##test_name_size.txt路径
    }




##以下不需要改变##
##检查文件,这一部分是检查你的所有训练验证过程必须有的文件与数据提供
check_if_exist(train_data)
check_if_exist(test_data)
check_if_exist(label_map_file)
check_if_exist(pretrain_model)
make_if_not_exist(save_dir)
make_if_not_exist(job_dir)
make_if_not_exist(snapshot_dir)




##创建训练网络,主要是在model_libs.py中完成
net = caffe.NetSpec()
##调用model_libs.py中的CreateAnnotatedDataLayer()函数,创建标注数据传递层,将括号中的参数传递进去。model_libs.py文件中提供了四种基础网络,即VGG、ZF、ResNet101和ResNet152。
net.data, net.label = CreateAnnotatedDataLayer(train_data, batch_size=batch_size_per_device,
        train=True, output_label=True, label_map_file=label_map_file,
        transform_param=train_transform_param, batch_sampler=batch_sampler)
#调用model_libs.py中的VGGNetBody()函数创建截断的VGG基础网络。参数传递进去。model_libs.py文件中提供了四种基础网络,即VGG、ZF、ResNet101和ResNet152。可以分别查看不同基础网络的调用方式。


VGGNetBody(net, from_layer='data', fully_conv=True, reduced=True, dilated=True,
    dropout=False)
##from_layer表示本基础网络的数据源来自data层的输出,
##fully_conv=Ture表示使用全卷积,
##reduced=Ture在该文件中可以发现是负责选用全卷积层的某几个参数的取值和最后选择不同参数的全链接层,
##dilated=True表示是否需要fc6和fc7间的pool5层以及选择其参数,还有配合reduced共同选择全卷积层的参数选择,
##dropout表示是否需要dropout层,flase表示不需要。


##以下为添加特征提取的层,即调用我们本文件最上面定义的需要额外添加的几个层,即conv6_1,conv6_2等等
AddExtraLayers(net, use_batchnorm, lr_mult=lr_mult)


##调用CreateMultiBoxHead()函数创建先验框的提取及匹配等层数,下面这些参数在上面全部都有解释,具体仍然可以参照caffe.proto和model_libs.py以及该层对应的cpp实现文件去阅读理解。这些层包括conv_mbox_conf、conv_mbox_loc、对应前两者的perm和flat层(这两层的作用可参考博文“SSD详解”)、还有conv_mbox_priorbox先验框产生层等。
mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,
        use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,
        aspect_ratios=aspect_ratios, steps=steps, normalizations=normalizations,
        num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
        prior_variance=prior_variance, kernel_size=3, pad=1, lr_mult=lr_mult)




##创建MultiBoxLossLayer。即创建损失层。这里包括置信损失和位置损失的叠加。具体计算的实现在multibox_loss_layer.cpp中实现,其中的各个参数如multi_loss_param和loss_param等参数在前面均有定义。
name = "mbox_loss"
mbox_layers.append(net.label)
net[name] = L.MultiBoxLoss(*mbox_layers, multibox_loss_param=multibox_loss_param,
        loss_param=loss_param, include=dict(phase=caffe_pb2.Phase.Value('TRAIN')),
        propagate_down=[True, True, False, False])
##参数propagate_down,指定是否反向传播到每个底部。如果未指定,Caffe会自动推断每个输入是否需要反向传播来计算参数梯度。如果对某些输入设置为true,则强制向这些输入反向传播; 如果对某些输入设置为false,则会跳过对这些输入的反向传播。大小必须是0或等于底部的数量。具体解读cpp文件中的参数propagate_down[0]~[3]。




with open(train_net_file, 'w') as f:
    print('name: "{}_train"'.format(model_name), file=f)
    print(net.to_proto(), file=f)
shutil.copy(train_net_file, job_dir)




##创建测试网络。前一部分基本上与训练网络一致
net = caffe.NetSpec()
net.data, net.label = CreateAnnotatedDataLayer(test_data, batch_size=test_batch_size,
        train=False, output_label=True, label_map_file=label_map_file,
        transform_param=test_transform_param)


VGGNetBody(net, from_layer='data', fully_conv=True, reduced=True, dilated=True,
    dropout=False)


AddExtraLayers(net, use_batchnorm, lr_mult=lr_mult)


mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,
        use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,
        aspect_ratios=aspect_ratios, steps=steps, normalizations=normalizations,
        num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
        prior_variance=prior_variance, kernel_size=3, pad=1, lr_mult=lr_mult)


conf_name = "mbox_conf" ##置信的交叉验证
if multibox_loss_param["conf_loss_type"] == P.MultiBoxLoss.SOFTMAX:
  reshape_name = "{}_reshape".format(conf_name)
  net[reshape_name] = L.Reshape(net[conf_name], shape=dict(dim=[0, -1, num_classes]))
  softmax_name = "{}_softmax".format(conf_name)
  net[softmax_name] = L.Softmax(net[reshape_name], axis=2)
  flatten_name = "{}_flatten".format(conf_name)
  net[flatten_name] = L.Flatten(net[softmax_name], axis=1)
  mbox_layers[1] = net[flatten_name]
elif multibox_loss_param["conf_loss_type"] == P.MultiBoxLoss.LOGISTIC:
  sigmoid_name = "{}_sigmoid".format(conf_name)
  net[sigmoid_name] = L.Sigmoid(net[conf_name])
  mbox_layers[1] = net[sigmoid_name]


##下面是test网络独有的,为检测输出和评估网络
net.detection_out = L.DetectionOutput(*mbox_layers,
    detection_output_param=det_out_param,
    include=dict(phase=caffe_pb2.Phase.Value('TEST')))
net.detection_eval = L.DetectionEvaluate(net.detection_out, net.label,
    detection_evaluate_param=det_eval_param,
    include=dict(phase=caffe_pb2.Phase.Value('TEST')))


with open(test_net_file, 'w') as f: ##写入test.txt
    print('name: "{}_test"'.format(model_name), file=f)
    print(net.to_proto(), file=f)
shutil.copy(test_net_file, job_dir)


## 创建deploy网络。  
## 从测试网中删除第一层和最后一层。
deploy_net = net
with open(deploy_net_file, 'w') as f:
    net_param = deploy_net.to_proto()
## 从测试网中删除第一个(AnnotatedData)和最后一个(DetectionEvaluate)层
    del net_param.layer[0] ##删除首层 
    del net_param.layer[-1] ##删除尾层
    net_param.name = '{}_deploy'.format(model_name) ##创建网络名
    net_param.input.extend(['data']) ##输入扩展为data
    net_param.input_shape.extend([
        caffe_pb2.BlobShape(dim=[1, 3, resize_height, resize_width])]) ##deploy.prototxt文件中特有的输入数据维度信息,这里应该为[1,3,300,300]。
    print(net_param, file=f)
shutil.copy(deploy_net_file, job_dir) ##复制一份到job_dir中


##创建Slover.prototxt.
solver = caffe_pb2.SolverParameter( ##将上面定义的solver参数统统拿下来
        train_net=train_net_file,
        test_net=[test_net_file],
        snapshot_prefix=snapshot_prefix,
       **solver_param)


with open(solver_file, 'w') as f: ##参数统统写入solver.prototxt中
    print(solver, file=f)
shutil.copy(solver_file, job_dir) ##复制一份到job_dir中


max_iter = 0 ##最大迭代次数首先初始化为0
##找到最近的快照。即如果中途中断训练,再次训练首先寻找上次中断时保存的模型继续训练
for file in os.listdir(snapshot_dir): ##依次在快照模型所保存的文件中查找相对应的模型
  if file.endswith(".solverstate"): ##如果存在此模型,则继续往下训练
    basename = os.path.splitext(file)[0]
    iter = int(basename.split("{}_iter_".format(model_name))[1])
    if iter > max_iter: ##如果已迭代的次数大于max_iter,则赋值给max_iter
      max_iter = iter


##训练命令
train_src_param = '--weights="{}" \\\n'.format(pretrain_model) ##加载与训练微调模型命令
if resume_training:
  if max_iter > 0:
    train_src_param = '--snapshot="{}_iter_{}.solverstate" \\\n'.format(snapshot_prefix, max_iter) ##权重的初始参数即从我们定义的imagenet训练VGG16模型中获取




if remove_old_models:
##删除任何小于max_iter的快照。上一段和本段程序主要的目的是随着训练的推进,max_iter随之逐渐增大,直到训练至120000次后把前面生成的快照模型都删除了,就是保存下一次的模型后删除上一次的模型。
  for file in os.listdir(snapshot_dir): ##遍历查找模型文件
    if file.endswith(".solverstate"): ##找到后缀为solverstate的模型文件
      basename = os.path.splitext(file)[0]
      iter = int(basename.split("{}_iter_".format(model_name))[1])##获取已迭代的次数
      if max_iter > iter: ##如果迭代满足条件
        os.remove("{}/{}".format(snapshot_dir, file)) ##删除
    if file.endswith(".caffemodel"): ##找到后缀为caffemodel的模型文件
      basename = os.path.splitext(file)[0]
      iter = int(basename.split("{}_iter_".format(model_name))[1])##获取迭代次数iter
      if max_iter > iter: ##如果满足条件则删除已存在的模型
        os.remove("{}/{}".format(snapshot_dir, file))




##创建工作文件
with open(job_file, 'w') as f: ##将训练文件写入执行文件中生成.sh可执行文件后执行命令训练
  f.write('cd {}\n'.format(caffe_root))
  f.write('./build/tools/caffe train \\\n')
  f.write('--solver="{}" \\\n'.format(solver_file))
  f.write(train_src_param)
  if solver_param['solver_mode'] == P.Solver.GPU:
    f.write('--gpu {} 2>&1 | tee {}/{}.log\n'.format(gpus, job_dir, model_name))
  else:
    f.write('2>&1 | tee {}/{}.log\n'.format(job_dir, model_name))




##复制本脚本到job_dir中
py_file = os.path.abspath(__file__)
shutil.copy(py_file, job_dir)




##运行
os.chmod(job_file, stat.S_IRWXU)
if run_soon:
  subprocess.call(job_file, shell=True)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值