caffe-ssd所有的训练时的参数,全部由ssd_pascal.py来定义,之后再去调用相关的脚本和函数,所以想要训练自己的数据,首先要明白ssd_pascal.py各个定义参数的大体意思。
from __future__ import print_function
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
# 给基准网络后面增加额外的卷积层(为了避免此处的卷积层的名称和基准网络卷积层的名称重复,
#这里可以用基准网络最后一个层的名称进行开始命名),这一部分的具体实现方法可以对照文件
#~/caffe/python/caffe/model_libs.py查看,SSD的实现基本上就是ssd_pascal.py和model_libs.py
#两个文件在控制,剩下的则是caffe底层代码中编写各个功能模块。
def AddExtraLayers(net, use_batchnorm=True):
use_relu = True
#生成附加网络的第一个卷积层,卷积核的数量为256,卷积核的大小为1*1,pad的尺寸为0,stride为1.
# 获得基准网络的最后一层,作为conv6-1层的输入
from_layer = net.keys()[-1]
# TODO(weiliu89): Construct the name using the last layer to avoid duplication.
out_layer = "conv6_1"
#conv6_1生成完毕
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 1, 0, 1)
#生成附加网络的第一个卷积层,卷积核的数量为512,卷积核的大小为3*3,pad的尺寸为1,stride为2.
from_layer = out_layer
out_layer = "conv6_2"
#conv6_2生成完毕
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 512, 3, 1, 2)
#conv7_1到conv9_2的生成
for i in xrange(7, 9):
from_layer = out_layer
out_layer = "conv{}_1".format(i)
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1)
from_layer = out_layer
out_layer = "conv{}_2".format(i)
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 1, 2)
#添加全局池层
name = net.keys()[-1]
net.pool6 = L.Pooling(net[name], pool=P.Pooling.AVE, global_pooling=True)
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
#如果想用CPU进行训练,设置为TRUE
use_cpu = False
#训练数据的数据库文件,就是create_data.sh生成的trainval_lmdb文件
train_data = "data/VOC0712/trainval_lmdb"
#测试数据的数据库文件,就是create_data.sh生成的test_lmdb文件
test_data = "data/VOC0712/test_lmdb"
# 指定批量采样器,可以改成500X500
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,
},
]
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,
],
},
'emit_constraint': {
'emit_type': caffe_pb2.EmitConstraint.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],
},
}
#如果为ture,则对所有新添加的层使用批处理规范。
#目前只测试了非批处理规范版本。
use_batchnorm = False
#使用不同的初始学习率
if use_batchnorm:
base_lr = 0.0004
else:
# 当batch_size = 1, num_gpus = 1时的学习率.
base_lr = 0.00004 #由于上面use_batchnorm = false,所以我们一般调整初始学习率时只需更改这一部分,目前为0.001。
#可以在这里更改工作路径与名称.
job_name = "SSD_{}".format(resize)
#更改生成的模型名称.
model_name = "VGG_VOC0712_{}".format(job_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/VOC0712/results/{}/Main".format(job_name)
# 模型定义文件.
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 = "{}/{}_train.bat".format(job_dir, model_name)
#存储测试图像的名称和大小,是create_list.sh生成的test_name_size.txt文件路径
name_size_file = "data/VOC0712/test_name_size.txt"
#预训练模型。 使用完卷积截断的VGGNet,使用官方或者别从成熟模型参数
pretrain_model = "models/VGGNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel"
#存储类型的文件
label_map_file = "data/VOC0712/labelmap_voc.prototxt"
#要预测的类的数量。VOC所标注的分类数加背景图.
num_classes = 21
#位置共享,如果为true,边框在不同的类中共享
share_location = True
#背景图的标签名称
background_label_id=0
#是否考虑困难的ground truth,默认为true
train_on_diff_gt = True
#如何规范跨越批次,空间维度或其他维度聚集的损失层的损失。
#目前只在SoftmaxWithLoss和SigmoidCrossEntropyLoss图层中实现。
#按照批次中的示例数量乘以空间维度。 在计算归一化因子时,
#不会忽略接收忽略标签的输出。定义为枚举,四种类型分别是:FULL,
#除以不带ignore_label的输出位置总数。 如果未设置ignore_label,则表现为FULL;VALID;
normalization_mode = P.Loss.VALID
#bbox的编码方式。此参数定义在PriorBoxParameter参数定义解释中,
#为枚举类型,三种类型为:CORNER,CENTER_SIZE和CORNER_SIZE。
code_type = P.PriorBox.CENTER_SIZE
#负/正比率,即文中所说的1:3
neg_pos_ratio = 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
'use_difficult_gt': train_on_diff_gt,#是否考虑困难的ground truth,默认为true。
'do_neg_mining': True,
'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。
}
loss_param = {#存储由损失层共享的参数的消息
'normalization': normalization_mode,#如何规范跨越批次,空间维度或其他维度聚集的损失层的损失。目前只在SoftmaxWithLoss和SigmoidCrossEntropyLoss图层中实现。按照批次中的示例数量乘以空间维度。 在计算归一化因子时,不会忽略接收忽略标签的输出。定义为枚举,四种类型分别是:FULL,除以不带ignore_label的输出位置总数。 如果未设置ignore_label,则表现为FULL;VALID;BATCH_SIZE,除以批量大小;NONE,不要规范化损失。
}
#参数生成先验。
#输入图像的最小尺寸
min_dim = 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
# pool6 ==> 1 x 1
#prior_box来源层,可以更改。很多改进都是基于此处的调整。
mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'pool6']
#这里即是论文中所说的Smin=0.2,Smax=0.9的初始值,经过下面的运算即可得到min_sizes,max_sizes。
min_ratio = 20
max_ratio = 95
#取一个间距步长,即在下面for循环给ratio取值时起一个间距作用。可以用一个具体的数值代替,这里等于17
step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2)))
#经过以下运算得到min_sizes和max_sizes。
min_sizes = []
max_sizes = []
#从min_ratio至max_ratio+1每隔step=17取一个值赋值给ratio。注意xrange函数的作用。
for ratio in xrange(min_ratio, max_ratio + 1, step):
#min_sizes.append()函数即把括号内部每次得到的值依次给了min_sizes。
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 = [[]] + max_sizes
#这里指的是横纵比,六种尺度对应六个产生prior_box的卷积层。
#具体可查看生成的train.prototxt文件一一对应每层的aspect_ratio参数,
#此参数在caffe.proto中有定义,关于aspect_ratios如何把其内容传递
#给了aspect_ratio,在model_libs.py文件中有详细定义。
aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3]]
#对卷积层conv4_3做归一化。model_libs.py里产生了normallize层,具体的层定义,
#参看底层代码~/caffe/src/layers/Normalize_layer.cpp,
#为什么这里设置conv4_3为20我也没看懂,原谅C++太渣,这里每个数对应每个先验层,
#只要哪个层对应的数不为-1则产生normal。
normalizations = [20, -1, -1, -1, -1, -1]
#两种选择,根据参数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]
#如果为true,则会翻转每个宽高比。例如,
#如果有纵横比“r”,我们也会产生纵横比“1.0 / r”。故产生{1,2,3,1/2,1/3}。
flip = True
#做clip操作是为了让prior的候选坐标位置保持在[0,1]范围内。
#在caffe.proto文件中有关于参数clip的解释,为”如果为true,则将先验框裁剪为[0,1]
clip = True
# 求解参数。
# 定义要使用的GPU。
gpus = "0" #"0,1,2,3" #多块GPU的编号,如果只有一块,这里只需保留0,否则会出错。
gpulist = gpus.split(",")#获取GPU的列表。
num_gpus = len(gpulist) #获取GPU编号。
#使用CPU进行训练
use_cpu = False
# 将小批次划分为不同的GPU
if use_cpu:
num_gpus = 0
#设置训练样本输入的数量,不要超出内存就好。
batch_size = 2 # 32
#这里与batch_size相搭配产生下面的iter_size。在看了下一行你就知道它的作用了。
accum_batch_size = 32
#如果iter_size=1,则前向传播一次后进行一次反向传递,如果=2,
#则两次前传后进行一次反传,这样做是减少每次传播所占用的内存空间,
#有的硬件不行的话就无法训练,但是增加iter会使训练时间增加,但是总的迭代次数不变。
iter_size = accum_batch_size / batch_size
solver_mode = P.Solver.CPU
device_id = 0
#批次传递
batch_size_per_device = batch_size
if num_gpus > 0:
#这里指如果你有多块GPU则可以将这些训练任务均分给多块GPU训练,从而加快训练速度。
batch_size_per_device = int(math.ceil(float(batch_size) / num_gpus))
#多块GPU的iter_size大小计算,上面的是一块的时候。
iter_size = int(math.ceil(float(accum_batch_size) / (batch_size_per_device * num_gpus)))
solver_mode = P.Solver.GPU
device_id = int(gpulist[0])
#如果损失层的参数NormalizationMode选择NONE,即没有归一化模式,则基础学习率为本文件之
#上的base_lr=0.0004除以batch_size_per_device=32得到新的base_lr=1.25*10^(-5)。
if normalization_mode == P.Loss.NONE:
base_lr /= batch_size_per_device
#同理,根据不同的归一化模式选择不同的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才可以。
elif normalization_mode == P.Loss.VALID:
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。
# Which layers to freeze (no backward) during training.
freeze_layers = ['conv1_1', 'conv1_2', 'conv2_1', 'conv2_2']
# 评估整个测试集。
num_test_image = 4952#整个测试集图像的数量。
test_batch_size = 1 #测试时的batch_size。理想情况下,test_batch_size应该被num_test_image整除,否则mAP会略微偏离真实值。这里计算每测试迭代多少次可以覆盖整个测试集,和分类网络中的是一致的。这里4952/8=619,如果你的测试图片除以你的test_batch_size不等于整数,那么这里会取一个近似整数。
test_iter = num_test_image / test_batch_size
#solver.prototxt文件中的各参数的取值,这里相信做过caffe训练的人应该大致有了解。
solver_param = {
# 训练参数
'base_lr': base_lr,#网络的基础学习速率,一般设一个很小的值,然后根据迭代到不同次数,对学习速率做相应的变化.lr过大不会收敛,过小收敛过慢
'weight_decay': 0.0005,#权衰量,用于防止过拟合
'lr_policy': "step", #学习速率的衰减策略,详细见后面
'stepsize': 40000,#每40000次迭代减少学习率(这一项和lr_policy有关)
'gamma': 0.1,#学习率变化的比率(这一项和lr_policy有关)
'momentum': 0.9, #网络的冲量;学习的参数,不用变;上一次梯度更新的权重
'iter_size': iter_size, #实际使用的batch size。 相当于读取batchsize*itersize个图像才做一下gradient decent。这个参数可以规避由于gpu不足而导致的batchsize的限制 因为你可以用多个iteration做到很大的batch 即使单次batch有限
'max_iter': 60000,#最大迭代次数,告诉网络何时停止训练.太小达不到收敛,太大会导致震荡
'snapshot': 40000,#每40000次迭代打印一次快照(就是把当前数据保存下来,方便下次重用,如果电源不稳定容易意外关机建
'display': 10,#每经过10次迭代,在屏幕上打印一次运行log(告诉你当前的loss之类的...)
'average_loss': 10,#取多次foward的loss作平均,进行显示输出
'type': "SGD",#选择一种优化算法
'solver_mode': solver_mode,#选择CPU or GPU
'device_id': device_id#选择几块GPU
'debug_info': False,
'snapshot_after_train': True,#表示在训练完后把最后一次的训练结果保存下来
# 测试参数
'test_iter': [test_iter],
'test_interval': 10000,#测试10000次输出一次测试结果
'eval_type': "detection",
'ap_version': "11point",
'test_initialization': False,#表示可以用上次保存的snapshot来继续训练
}
# 生成检测输出的参数。
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中有介绍。
#用于保存检测结果的参数,这一部分参数在caffe.proto中的SaveOutputParameter有定义。
'save_output_param': {
#输出目录。 如果不是空的,我们将保存结果。前面我们有定义结果保存的路径.
'output_directory': output_result_dir,
#输出名称前缀。
'output_name_prefix': "comp4_det_test_",
#输出格式。VOC - PASCAL VOC输出格式。COCO - MS COCO输出格式.
'output_format': "VOC",
#如果要输出结果,还必须提供以下两个文件。否则,我们将忽略保存结果。
#标签映射文件。这在前面中有给label_map_file附文件,也就是我们在训练
#的时候所做的labelmap.prototxt文件的位置
'label_map_file': label_map_file,
#即我们在训练时定义的test_name_size.txt文件的路径。该文件表示测试图片的大小。
'name_size_file': name_size_file,
#测试图片的数量。
'num_test_image': num_test_image,
},
#nms步之后每个图像要保留的bbox总数。-1表示在nms步之后保留所有的bbox.
'keep_top_k': 200,
#只考虑可信度大于阈值的检测。 如果没有提供,请考虑所有的框。
'confidence_threshold': 0.01,
#bbox的编码方式。
'code_type': code_type,
}
# parameters for evaluating detection results.
det_eval_param = {
'num_classes': num_classes,
'background_label_id': background_label_id,
'overlap_threshold': 0.5,
'evaluate_difficult_gt': False,
'name_size_file': name_size_file,
}
###不需要改变以下参数 ###
#检查文件。这一部分是检查你的所有训练验证过程必须有的文件与数据提供。
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,
#这些参数分别表示:from_layer表示本基础网络的数据源来自data层的输出,fully_conv=Ture表示使用全卷积,
#reduced=Ture在该文件中可以发现是负责选用全卷积层的某几个参数的取值和最后选择不同参数的全链接层,
#dilated=True表示是否需要fc6和fc7间的pool5层以及选择其参数还有配合reduced共同选择全卷积层的参数选择,
#dropout表示是否需要dropout层flase表示不需要。
dropout=False, freeze_layers=freeze_layers)
#以下为添加特征提取的层,即调用我们本文件最上面定义的需要额外添加的几个层,即conv6_1,conv6_2等等。
AddExtraLayers(net, use_batchnorm)
#调用CreateMultiBoxHead()函数创建先验框的提取及匹配等层数,下面这些参数其实我们在上面全部都有解释,
#具体仍然可以参照caffe.proto和model_libs.py以及该层对应的cpp实现文件去阅读理解。
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, normalizations=normalizations,
num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
prior_variance=prior_variance, kernel_size=3, pad=1)
#创建MultiBoxLossLayer。即创建损失层。这里包括置信损失和位置损失的叠加。
#具体计算的实现在multibox_loss_layer.cpp中实现,其中的哥哥参数想multi_loss_param
#和loss_param等参数在前面均有定义。
name = "mbox_loss"
mbox_layers.append(net.label)
#这里重点讲一下参数propagate_down,指定是否反向传播到每个底部。如果未指定,
#Caffe会自动推断每个输入是否需要反向传播来计算参数梯度。如果对某些输入设置为true,
#则强制向这些输入反向传播; 如果对某些输入设置为false,则会跳过对这些输入的反向传播。
#大小必须是0或等于底部的数量。具体解读cpp文件中的参数propagate_down[0]~[3]
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])
#打开文件将上面编辑的这些层写入到prototxt文件中。
with open(train_net_file, 'w') as f:
print('name: "{}_train"'.format(model_name), file=f)
print(net.to_proto(), file=f)
#将写入的训练文件train.prototxt复制一份给目录job_dir。
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, freeze_layers=freeze_layers)
AddExtraLayers(net, use_batchnorm)
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, normalizations=normalizations,
num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
prior_variance=prior_variance, kernel_size=3, pad=1)
#置信的交叉验证
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:
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。
#deploy.prototxt文件中特有的输入数据维度信息,这里应该为[1,3,300,300]。
net_param.input_shape.extend([
caffe_pb2.BlobShape(dim=[1, 3, resize_height, resize_width])])
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)
#将拿下来的参数统统写入solver.prototxt中。
with open(solver_file, 'w') as f:
print(solver, file=f)
#复制一份到job_dir中。
shutil.copy(solver_file, job_dir)
#最大迭代次数首先初始化为0。
max_iter = 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 = ''
if os.path.isfile(pretrain_model):
#权重的初始参数即从我们定义的imagenet训练VGG16模型中获取。
train_src_param = '\t--weights={} ^\n'.format(os.path.normpath(pretrain_model))
if resume_training:
if max_iter > 0:
train_src_param = '\t--snapshot={}_iter_{}.solverstate ^\n'.format(os.path.normpath(snapshot_prefix), max_iter)
#删除任何小于max_iter的快照。上一段和本段程序主要的目的是随着训练的推进,
##max_iter随之逐渐增大,知道训练至120000次后把前面生成的快照模型都删除了,
就#是保存下一次的模型后删除上一次的模型。
if remove_old_models:
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('SET GLOG_logtostderr=1\n')
f.write('set Datum=%DATE:~6,4%_%DATE:~3,2%_%DATE:~0,2%\n')
f.write('set Uhrzeit=%TIME:~0,2%_%TIME:~3,2%_%TIME:~6,2%\n')
f.write('set TIMESTAMP=%Datum%_%Uhrzeit%\n')
f.write('\n'.format(caffe_root))
f.write('cd {}\n'.format(caffe_root))
f.write('"Build\{}\Release\caffe" train ^\n'.format('x64'))
f.write('\t--solver={} ^\n'.format(os.path.normpath(solver_file)))
f.write(train_src_param)
if solver_param['solver_mode'] == P.Solver.GPU:
f.write('\t--gpu {} 2>&1 | "tools\mtee" "{}\{}-train-%TIMESTAMP%.log"\n'.format(gpus, os.path.normpath(job_dir), model_name))
else:
f.write('\t2>&1 | "tools\mtee" "{}\{}-train-%TIMESTAMP%.log"\n'.format(os.path.normpath(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(os.path.normpath(job_file), shell=True)
1.以上是关于ssd_pascal.py源码的注示。
2.关于ssd_pascal.py源码理解,都可以加这个群(487350510)互相讨论学。