SSD-tensorflow使用文档(二)——从数据处理到iOS移植

时间:2022-11-20 17:09:45

一、数据转换

1. VOC数据转换成tfrecord格式

1.1. 数据说明

  • tensorflow专用的数据格式为tfrecord.
  • /home/doctorimage/kindlehe/common/dataset是所有数据的存放目录,目录结构如下
coco : 
flower : 其中flower_photos包含5个文件夹,分别对应五种花的类型
Oxford-IIT_Pet:宠物的数据集
VOC2007: voc2007数据集
VOCdevkit: voc2012数据集
VOC0712: VOC2007与voc2012合并之后,转成tfrecord数据集后,所在的存放路径

1.2. voc2007与voc2012单独转为tfrecord格式

在目录/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell下运行bash tf_convert_data.sh会执行以下指令生成文件名为voc_2007_trainval_000.tfrecord的tfrecord格式的数据:

DATASET_DIR=../../../../common/dataset/VOC2007/VOCtrainval_06-Nov-2007/VOCdevkit/VOC2007/
OUTPUT_DIR=../../../../common/dataset/VOC2007/VOCtrainval_06-Nov-2007/VOCdevkit/VOC2007_tfrecord/
python ../tf_convert_data.py \ #所执行的脚本文件
--dataset_name=pascalvoc \ #数据名字,这里不能修改,因为在tf_convert_data.py里被写死了,修改之后会报错
--dataset_dir=${DATASET_DIR} \ #输入数据的存放目录
--output_name=voc_2007_trainval \ #输出数据名的前缀
--output_dir=${OUTPUT_DIR} #输出数据的存放目录

对于voc2012只需要将输入数据和输出数据的目录更改为voc2012的目录即可,如下所示:

DATASET_DIR=#/home/doctorimage/kindlehe/common/dataset/VOCdevkit/VOC2012/
OUTPUT_DIR=#/home/doctorimage/kindlehe/common/dataset/VOCdevkit/VOC2012_tf
python ../tf_convert_data.py \ #所执行的脚本文件
--dataset_name=pascalvoc \ #数据名字,这里不能修改,因为在tf_convert_data.py里被写死了,修改之后会报错
--dataset_dir=${DATASET_DIR} \ #输入数据的存放目录
--output_name=voc_2012_trainval \ #这里改成2012
--output_dir=${OUTPUT_DIR} #输出数据的存放目录

转换成功会出现如下提示:

>> Converting image 17125/17125
Finished converting the Pascal VOC dataset!

如何根据不同的GPU内存,==设置每个数据文件包含的图片个数==呢?
pascalvoc_to_tfrecords.py中有一个参数

SAMPLES_PER_FILES = 5011 #控制转出的TFrecord文件的个数:比如一共17125张图,要求每个文件保存5011个图,那么最终输出17125/5011=4个文件/

1.3. voc2007与voc2012合并之后转为tfrecord格式

ssd作者说自己用了voc2007中trainval和voc2012的数据一起训练的,那么如何将这两个数据一次性制作成tfrecord格式的数据呢?

当你生成完voc2007和voc2012后会生成以下文件:

#/home/doctorimage/kindlehe/common/dataset/VOC2007/VOCtrainval_06-Nov-2007/VOCdevkit/VOC2007_tfrecord/目录下生成1个文件:
voc_2007_trainval_000.tfrecord
#/home/doctorimage/kindlehe/common/dataset/VOCdevkit/VOC2012/目录下生成4个文件
voc_2012_trainval_000.tfrecord
voc_2012_trainval_001.tfrecord
voc_2012_trainval_002.tfrecord
voc_2012_trainval_003.tfrecord

不要怀疑你接下来看到的.没错
合并数据最难的方法是写自动化程序,一键搞定,但是没这个必要,因为有一个最简单的办法,就是把两个文件夹中的数据拷贝到同一个文件夹下,路径是:
/home/doctorimage/kindlehe/common/dataset/VOC0712/

但是,拷贝进去的5个文件,需要把voc_2007_trainval_000.tfrecord改成voc_2012_trainval_004.tfrecord,这时你就明白黄色部分为什么要着重突出了,因为如果单个文件包含的个数太少,生成的文件数量就会太多,改文件夹名字的时候就比较麻烦,当然也可以写自动化脚本批量修改也不是什么难事。==最重要在于增加单个文件包含的图片个数,可以减少内存的IO操作。==


2. 用自己的数据制作成tfrecord格式

2.1 分类任务数据集制作

数据存放目录为/home/doctorimage/kindlehe/project/CSDN/下面的flower_photos文件夹,可以按照这个文件夹的目录结构放入自己的数据

进入目录/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/slim
,运行bash convert_tfrecord.sh会执行以下命令:

python download_and_convert_data.py --dataset_name=flowers --dataset_dir=/home/doctorimage/kindlehe/project/CSDN/

download_and_convert_data.py最终会调用download_and_convert_flowers去完成文件转换功能,这个文件里面有几个地方要注意一下

L43、L49
_NUM_VALIDATION = 350 #表示验证集的个数,验证集一般占所有图片的10%
_NUM_SHARDS = 5 #表示训练集或者测试集生成的tfrecord文件的个数,官方建议单个Tfrecords文件放1023张,生成文件的个数就可以根据总图片的数量计算得到

L83:
flower_root= os.path.join(dataset_dir,‘flower_photos’) # 如果是制作自己的数据,这一行的flower_photos改成自己的文件名即可


L190:
dataset_utils.download_and_uncompress_tarball(_DATA_URL, dataset_dir) #如果下载好了,一定要注释掉这一行,否则会坑到自己

L210:
_clean_up_temporary_files(dataset_dir) #下载压缩包,解压成图片,再转成tfrecords,中间这些图片被认为是临时图片,系统会自动清除

2.1 检测任务数据集制作

主要是要将数据制作成VOC的格式,可以参考自动化工具制作PASCAL VOC 数据集

二、训练及测试

1. 训练

主要的训练过程在《SSD-tensorflow使用文档(一)》中已经详细讲解了,这里补充一些使用细节:

1.1 pyCharm环境配置及调试

要使用pyCharm进行调试,首先要将cuda的环境配置好,每个机器的环境都不一样,可以在linux终端输入vim ./bashrc查看相应的路径。
SSD-tensorflow使用文档(二)——从数据处理到iOS移植
SSD-tensorflow使用文档(二)——从数据处理到iOS移植

配置好就可以点击run -> debug ‘train_ssd_networ’进行单步调试了

也可以按照《SSD-tensorflow使用文档(一)》直接在终端中,进入ssd根目录下的shell目录,运行bash train_ssd_network.sh

1.2 训练细节

对于train_ssd_network.sh中的命令,通常存在以下几种训练方法:

方案1: 从vgg开始训练其中某些层的参数

# 通过加载预训练好的vgg16模型,对“voc07trainval+voc2012”进行训练
# 通过checkpoint_exclude_scopes指定哪些层的参数不需要从vgg16模型里面加载进来
# 通过trainable_scopes指定哪些层的参数是需要训练的,未指定的参数保持不变
DATASET_DIR=/home/doctorimage/kindlehe/common/dataset/VOC0712/
TRAIN_DIR=.././log_files/log_finetune/train_voc0712_20170816_1654_VGG16/
CHECKPOINT_PATH=../checkpoints/vgg_16.ckpt

python3 ../train_ssd_network.py \
--train_dir=${TRAIN_DIR} \ #训练生成模型的存放路径
--dataset_dir=${DATASET_DIR} \ #数据存放路径
--dataset_name=pascalvoc_2007 \ #数据名的前缀
--dataset_split_name=train \
--model_name=ssd_300_vgg \ #加载的模型的名字
--checkpoint_path=${CHECKPOINT_PATH} \ #所加载模型的路径
--checkpoint_model_scope=vgg_16 \ #所加载模型里面的作用域名
--checkpoint_exclude_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--trainable_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--save_summaries_secs=60 \ #每60s保存一下日志
--save_interval_secs=600 \ #每600s保存一下模型
--weight_decay=0.0005 \ #正则化的权值衰减的系数
--optimizer=adam \ #选取的最优化函数
--learning_rate=0.001 \ #学习率
--learning_rate_decay_factor=0.94 \ #学习率的衰减因子
--batch_size=24 \
--gpu_memory_fraction=0.9 #指定占用gpu内存的百分比
方案2: 从自己预训练好的模型开始训练(依然可以指定要训练哪些层)(当你的模型通过vgg训练的模型收敛到大概o.5mAP的时候,可以进行这一步的fine-tune)

# 通过加载预训练好的vgg16模型,对“voc07trainval+voc2012”进行训练
# 通过checkpoint_exclude_scopes指定哪些层的参数不需要从vgg16模型里面加载进来
# 通过trainable_scopes指定哪些层的参数是需要训练的,未指定的参数保持不变
DATASET_DIR=/home/doctorimage/kindlehe/common/dataset/VOC0712/
TRAIN_DIR=.././log_files/log_finetune/train_voc0712_20170816_1654_VGG16/
CHECKPOINT_PATH=./log_files/log_finetune/train_voc0712_20170712_1741_VGG16/model.ckpt-253287

python3 ../train_ssd_network.py \
--train_dir=${TRAIN_DIR} \ #训练生成模型的存放路径
--dataset_dir=${DATASET_DIR} \ #数据存放路径
--dataset_name=pascalvoc_2007 \ #数据名的前缀
--dataset_split_name=train \
--model_name=ssd_300_vgg \ #加载的模型的名字
--checkpoint_path=${CHECKPOINT_PATH} \ #所加载模型的路径
--checkpoint_model_scope=vgg_16 \ #所加载模型里面的作用域名
--checkpoint_exclude_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--trainable_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--save_summaries_secs=60 \ #每60s保存一下日志
--save_interval_secs=600 \ #每600s保存一下模型
--weight_decay=0.0005 \ #正则化的权值衰减的系数
--optimizer=adam \ #选取的最优化函数
--learning_rate=0.001 \ #学习率
--learning_rate_decay_factor=0.94 \ #学习率的衰减因子
--batch_size=24 \
--gpu_memory_fraction=0.9 #指定占用gpu内存的百分比
# 方案3:从头开始训练自己的模型
# 注释掉CHECKPOINT_PATH,不提供初始化模型,让模型自己随机初始化权重,从头训练
# 删除checkpoint_exclude_scopes和trainable_scopes,因为是从头开始训练
# CHECKPOINT_PATH=./log_files/log_finetune/train_voc0712_20170712_1741_VGG16/model.ckpt-253287

python3 ../train_ssd_network.py \
--train_dir=${TRAIN_DIR} \ #训练生成模型的存放路径
--dataset_dir=${DATASET_DIR} \ #数据存放路径
--dataset_name=pascalvoc_2007 \ #数据名的前缀
--dataset_split_name=train \
--model_name=ssd_300_vgg \ #加载的模型的名字
#--checkpoint_path=${CHECKPOINT_PATH} \ #所加载模型的路径,这里注释掉
--checkpoint_model_scope=vgg_16 \ #所加载模型里面的作用域名
--save_summaries_secs=60 \ #每60s保存一下日志
--save_interval_secs=600 \ #每600s保存一下模型
--weight_decay=0.0005 \ #正则化的权值衰减的系数
--optimizer=adam \ #选取的最优化函数
--learning_rate=0.00001 \ #学习率
--learning_rate_decay_factor=0.94 \ #学习率的衰减因子
--batch_size=32

出现如下错误,一般是保存模型的文件夹下之前已经有了模型,只需要把之前的模型删除,或者将模型保存在一个新的文件夹下就可以了

It was originally created here:
['File "../train_ssd_network.py", line 420, in <module>\n
tf.app.run()', 'File "/usr/local/lib/python3.5/dist-packages/tensorflow/python
/platform/app.py"
, line 48, in run\n _sys.exit(main(_sys.argv[:1] +
flags_passthrough))', 'File "../train_ssd_network.py", line 416, in main\n
sync_optimizer=None)', 'File "/usr/local/lib/python3.5/dist-packages/tensorflow
/contrib/slim/python/slim/learning.py"
, line 655, in train\n ready_op =
tf_variables.report_uninitialized_variables()', 'File "/usr/local/lib/python3.5
/dist-packages/tensorflow/python/util/tf_should_use.py"
, line 170, in
wrapped\n return _add_should_use_warning(fn(*args, **kwargs))',
'File "/usr/local/lib/python3.5/dist-packages/tensorflow/python/util
/tf_should_use.py", line 139, in _add_should_use_warning\n
wrapped = TFShouldUseWarningWrapper(x)',
'File "/usr/local/lib/python3.5/dist-packages/tensorflow/python/
/tf_should_use.py", line 96, in __init__\n
stack = [s.strip() for s in traceback.format_stack()]']

在TRAIN_DIR路径下会产生四中文件:
1. checkpoint :文本文件,包含所有model.ckpt-xxxx,相当于是不同时间节点生成的所有ckpt文件的一个索引。
2. model.ckpt-2124.data-000000-of-000001:模型文件,保存模型的权重
3. model.ckpt-2124.meta: 图文件,保存模型的网络图
4. model.ckpt-2124.index : 这个没搞太清楚
5. graph.pbtxt: 用protobuf格式保存的模型的图


2. 测试

EVAL_DIR=../log_files/log_eval/train_voc0712_20170712_1741_VGG16/ 
CHECKPOINT_PATH=.././log_files/log_finetune/train_voc0712_20170712_1741_VGG16/model.ckpt-197442
python3 ../eval_ssd_network.py \
--eval_dir=${EVAL_DIR} \
--dataset_dir=${DATASET_DIR} \
--dataset_name=pascalvoc_2007 \
--dataset_split_name=test \
--model_name=ssd_300_vgg \
--checkpoint_path=${CHECKPOINT_PATH} \
--batch_size=64 \
--gpu_memory_fraction=0.8

3. 查看络结构张量

打开/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell,运行bash inspect_checkpoint.sh,会执行:

#!/usr/bin/env bash

python inspect_checkpoint.py \
--file_name=log_files/log_finetune/train_voc0712_20170703_1104_pb/model.ckpt-3069

输出结果如下:

eta1_power (DT_FLOAT) []
beta2_power (DT_FLOAT) []
global_step (DT_INT64) []
ssd_300_vgg/block10/conv1x1/biases (DT_FLOAT) [128]
ssd_300_vgg/block10/conv1x1/biases/Adam (DT_FLOAT) [128]
ssd_300_vgg/block10/conv1x1/biases/Adam_1 (DT_FLOAT) [128]
ssd_300_vgg/block10/conv1x1/weights (DT_FLOAT) [1,1,256,128]
ssd_300_vgg/block10/conv1x1/weights/Adam (DT_FLOAT) [1,1,256,128]
ssd_300_vgg/block10/conv1x1/weights/Adam_1 (DT_FLOAT) [1,1,256,128]
......(此处省略1万字)
ssd_300_vgg/conv7/biases (DT_FLOAT) [1024]
ssd_300_vgg/conv7/biases/Adam (DT_FLOAT) [1024]
ssd_300_vgg/conv7/biases/Adam_1 (DT_FLOAT) [1024]
ssd_300_vgg/conv7/weights (DT_FLOAT) [1,1,1024,1024]
ssd_300_vgg/conv7/weights/Adam (DT_FLOAT) [1,1,1024,1024]
ssd_300_vgg/conv7/weights/Adam_1 (DT_FLOAT) [1,1,1024,1024]

三、模型导出

1. 生成ssd模型专用的pb文件

这里的pb文件不同于之前的model.ckpt-2124.meta文件,这个pb文件是一个graph, 它保存了ssd模型图的结构,不包含全权重信息,因此相比 .ckpt 模型文件,它的文件体积小。

打开/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell,运行bash export.sh,会执行:

# 代码参考slim库做出修改:/home/doctorimage/kindlehe/project/models/slim/export_inference_graph.py

python -u ../export_inference_graph.py \
--model_name=ssd_300_vgg \
--output_file=../log_files/log_finetune/train_voc0712_20170703_1104_pb/train_voc0712_20170703_1104.pb \
--dataset_name=pascalvoc_2007 \
--dataset_dir=/home/doctorimage/kindlehe/common/dataset/VOC0712/ #指定用那些数据训练的模型,因为分类数和labels要从这里面取

如何修改自己的模型并生成对应的pb文件呢?

a. --model_name=ssd_300_vgg意思是export_inference_graph.py最终会进入/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/nets目录,根据ssd_vgg_300.py里面定义好的模型,去生成对应的====pb文件====。所以想修改网络结构,只需要在ssd_vgg_300.py这里修改,然后进行在训练的时候把model_name改成你自己设置的名字即可。运行export.sh脚本即可生成修改的模型所对应的pb文件。

b. --dataset_dir这个参数用来指定章节“VOC数据转换成tfrecord格式”中生成的tfrecord文件及标签所在的路径,因为在生成pb文件的时候,会根据数据确定”分类类别的数目”并提取”label”.

c. 注意,这里生成的pb文件如果未设置tf.train.write_graph(,,as_text=False)当中as_text=true,那么会以二进制格式保存,在接下来的feeeze_graph.py固化操作中,应该加上一个参数--input_binary=true


2. 模型固化

2.1 模型固化小工具freeze_graph.py

实际上使用训练得到模型时,需要把权重固定,不然每次测试一张图片就相当于继续训练模型,权重也要重新变化一次,这会导致测试一张图片需要很久的时间。在固定权重的基础上,还需要集成图的定义。这个过程就是 freeze graph 。

这里freeze_graph.py的作用就是将上一个步骤生成的pb图文件和训练得到的ckpt参数文件固化到一个文件中,将变量参数类型替换成常量参数类型,模型大小从 降到

打开/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell,运行bash export.sh,会执行:

#!/usr/bin/env bash

python ../freeze_graph.py \
--input_binary=true\
--input_graph=../log_files/log_finetune/train_voc0712_20170703_1104_pb/train_voc0712_20170703_1104.pb \
--input_checkpoint=../log_files/log_finetune/train_voc0712_20170703_1104_pb/model.ckpt-3069 \
--output_graph= ../log_files/log_finetune/train_voc0712_20170703_1104_pb/frozen_train_voc0712_20170703_1104.pb \
--output_node_names=output/output
参数说明
--input_binary=true :参见freeze_graph.py的bug清单
--input_graph: 模型的图的定义文件 train_voc0712_20170703_1104.pb(不包含权重);
--input_checkpoint: 模型的参数文件 model.ckpt-3069
--output_graph: 绑定后包含参数的图模型文件 frozen_train_voc0712_20170703_1104.pb;
-- output_node_names: 输出待计算的tensor名字【重要】;

《freeze_graph.py的bug清单》
发现tensorflow不同版本下运行freeze_graph.py 脚本时可能遇到的Bug挺多的,列举一下:

# Bug1: google.protobuf.text_format.ParseError: 2:1 : Message type "tensorflow.GraphDef" has no field named "J". 
# 原因: tf.train.write_graph(,,as_text=False) 之前写出的模型文件是Binary时,
# 读入文件格式应该对应之前设置参数 python freeze_graph.py [***] --input_binary=true,
# 如果as_text=True则可以忽略,因为默认值 --input_binary=false。
# 参考: https://github.com/tensorflow/tensorflow/issues/5780

# Bug2: Input checkpoint '...' doesn't exist!
# 原因: 可能是命令行用了 --input_checkpoint=data.ckpt ,
# 运行 freeze_graph.py 脚本,要在路径参数前加上 "./" 貌似才能正确识别路径。
# 如文件的路径 --input_checkpoint=data.ckpt 变为 --input_checkpoint=./data.ckpt
# 参考: http://www.it1me.seriousdigitalmedia.com/it-answers?id=42439233&ttl=How+to+use+freeze_graph.py+tool+in+TensorFlow+v1

# Bug3: google.protobuf.text_format.ParseError: 2:1 : Expected identifier or number.
# 原因: --input_checkpoint 需要找到 .ckpt.data-000*** 和 .ckpt.meta等多个文件,
# 因为在 --input_checkpoint 参数只需要添加 ckpt的前缀, 如: nn_model.ckpt,而不是完整的路径nn_model.ckpt.data-000***
# .meta .index .data checkpoint 4个文件

# Bug4: # you need to use a different restore operator?
# tensorflow.python.framework.errors_impl.DataLossError: Unable to open table file ./pos.ckpt.data-00000-of-00001: Data loss: not an sstable (bad magic number): perhaps your file is in a different file format and you need to use a different restore operator?
# Saver 保存的文件用格式V2,解决方法更新tensorflow....

# 欢迎补充

2.2 查看output_node_names小工具

==如何确定一个模型的output_node_names呢?==
如果不指定,会自动选择节点名
训练模型的时候,会生成一个graph.pbtxt文件,里面包含了所有的节点名,阅读ssd_300_vgg.py文件里的网络结构,找到希望输出的节点即可。对于ssd,我们希望输出预测框的位置,节点为:

打开/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell,运行bash print_node_name.sh,根据上一个步骤生成的train_voc0712_20170703_1104.pb文件,输出ssd网络结构实际用到的所有节点,保存到save_dir目录下

#!/usr/bin/env bash

python ../print_node_name.py\
--graph_file=../log_files/log_finetune/train_voc0712_20170703_1104_pb/train_voc0712_20170703_1104.pb \
--save_dir=../log_files/log_finetune/train_voc0712_20170703_1104_pb/

3. 固化、简化、量化模型

参考《kindle教你手把手跑通ios-tensorflow版SSD模型(三)—— 模型裁剪》

四、iOS怒编译tensorflow

参考《kindle教你手把手跑通ios-tensorflow版SSD模型(一)—— 编译ios-tensorflow》

五、iOS怒配置Xcode路径

参考《kindle教你手把手跑通ios-tensorflow版SSD模型(二)—— 配置Xcode》