TensorFLow object detection API训练数据集

TensorFLow object detection API训练数据集(VOC2005)

    • 一、数据说明
      • 数据的特性
      • 数据的拆分
    • 二、准备数据
      • 创建目录结构
    • 三、环境准备
      • 1.拷贝环境文件
      • 2.下载模型
      • 3.适配环境
        • 1.create_pascal_tf_record.py文件的修改
        • 2.修改pascal_label_map.pbtxt
        • 3.将训练集转换为TFRecord文件
        • 4.修改ssd_mobilenet_v1_coco.config
    • 四、执行训练
      • 查看日志
      • 查看训练曲线
    • 五、验证训练结果
    • 六、导出模型
    • 七、训练经验法则

  在搭建好Tensorflow以及TensorFlow的Object Detection API安装后,我们可以运行一些预训练好的模型进行推理运算,并看到一些效果。但有时候我们需要定制自己的分类。例如我只想识别人(person)和人脸(face),或者一些较多或较少的类别,那么这就需要训练自己的分类模型。

一、数据说明

  在开始之前,可能需要了解深度学习训练的数据。我们从以下内容进行粗略的理解:

数据的特性

  数据应该具有以下几个特性:

  • 数据应该从分布中随机抽取独立同分布 (i.i.d) 的样本。换言之,样本之间不会互相影响。(另一种解释:i.i.d. 是表示变量随机性的一种方式)。
  • 分布是平稳的;即分布在数据集内不会发生变化。
  • 数据是从同一分布的数据划分中抽取。

数据的拆分

  数据拆分,一般只有训练集和测试集两个子集:
TensorFLow object detection API训练数据集_第1张图片
  大致采用如下的工作流程:
TensorFLow object detection API训练数据集_第2张图片
  上图中,“调整模型”指的是调整您可以想到的关于模型的任何方面,从更改学习速率、添加或移除特征,到从头开始设计全新模型。该工作流程结束时,您可以选择在测试集上获得最佳效果的模型。

更加优化的拆分:
  通过将数据集划分为三个子集(如下图所示),可以大幅降低过拟合的发生几率:
在这里插入图片描述
  使用验证集评估训练集的效果。然后,在模型“通过”验证集之后,使用测试集再次检查评估结果。下图展示了这一新工作流程:
TensorFLow object detection API训练数据集_第3张图片

二、准备数据

  需要训练自己的数据集,首先需要先准备好数据。目前,深度学习方面有较多的公开的数据集。常用的有PASCAL VOC、COCO、MNIST、ImageNet等等。这些都是预训练好的数据集,但我们也可以自己训练,只是使用这些数据集中的图片数据,而且我们假定这些图片都是符合数据应该有的特性(来源都是同一个分布,分布随机独立,且稳定)。
参考来源:25个深度学习的开放数据集

创建目录结构

  首先,我们采用数据量较少的VOC2005的数据集。在准备好数据后,创建如下的目录结构
    TensorFLow object detection API训练数据集_第4张图片
以下的操作,都会逐个对应到某一个文件夹(请按照序号依次操作)。

  1. 存放图片
    将VOC2005的图片集放入到JPEGImage文件夹中。存放所有图片,不区分测试、验证和训练的分类。

  2. 图片标注
    将JPEGImage中的图片,使用labelImg进行逐一标注类似如下界面:

    框选需要训练识别的分类,并选择label然后保存(Ctrl+s),labelImg会在图片目录下生产对应文件名的xml文件。本次训练我只识别personface两个类,所以标注的label中也只有这两种分类(后续对应xxx_label_map.pbtxt中需要匹配修改)。
    在将所有图片标注完成后,将对应的xml文件移动到Annotations文件夹下。

  3. 创建图片文件分类脚本
    在VOC2005目录下创建 createclassificationfile.py (文件名不要求)文件,输入以下code:

import os
import random
 
trainval_percent = 0.66
train_percent = 0.5
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
total_xml = os.listdir(xmlfilepath)
print(total_xml)
num=len(total_xml)
list=range(num)
tv=int(num*trainval_percent)
tr=int(tv*train_percent)
trainval= random.sample(list,tv)
train=random.sample(trainval,tr)
 
ftrainval = open('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')
 
for i  in list:
    name=total_xml[i][:-4]+'\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)
 
ftrainval.close()
ftrain.close()
fval.close()
ftest .close()

  执行:
    $ python createclassificationfile.py
  运行完成后,查看ImageSets/Main/目录下是否有生成如下文件:
  在这里插入图片描述
  如果生成成功,则表示已经将图片集以及对应标注好的xml文件进行了分类。每个txt中都包含了图片集的文件名,不同的txt是不同类别,文件名也很直观。

三、环境准备

  虽然我们可以把这些训练集和标注好的xml文件放入到Tensorflow object detection API的环境目录下进行训练。但为了保证环境目录的简洁,还是选择将object detection API中的关键文件复制到我们的train_project下对应的目录中,这样就类似把object detection API的整体环境筛选性的复制出来了。

1.拷贝环境文件

$cp */tensorflow/models/research/object_detection/dataset_tools/create_pascal_tf_record.py train_project/dataset/
$cp */tensorflow/models/research/object_detection/data/pascal_label_map.pbtxt train_project/dataset/
ps:因为使用的pascal voc数据,所以选择create_pascal_tf_record.pypascal_label_map.pbtxt。如果使用MSCOCO数据集,可选择对应的create_coco_tf_record.pymscoco_label_map.pbtxt文件。

为了保证在train_project下拥有完整的object detection API环境,我们需要把object_dection中的ssd_inception_v2_coco.configexport_inference_graph.pytrain.pyeval.py和目录utils拷贝到train_project目录层中,其中:
  ssd_inception_v2_coco.config: 模型的配置文件,可设置训练模型的参数,例如LearnRate、batch_size等。
  train.py:object detection启动模型训练的脚本
  eval.py:验证的脚本
  export_inference_graph.py :导出训练图模型成pb文件使用
  utils:相关工具脚本
PS:以上文件或目录都在*/tensorflow/models/research/object_detection/中,可直接搜索到。

2.下载模型

  我们选择使用ssd_mobilenet_v1_coco_2018_01_28的模型进行训练。
  下载模型,并将模型加压到train_project/models中,在train_project/models中则会有文件夹ssd_mobilenet_v1_coco_2018_01_28,打开可以看到如下文件:
TensorFLow object detection API训练数据集_第5张图片
  这是一个完整的预训练好的模型,可直接使用。但我们需要训练自己的模型,所以只会用到其中的model.ckpt.indexmodel.ckpt.meta。其余的文件都和使用的数据和需要识别的分类有关,这些文件我们需要自己生成。

3.适配环境

  我们准备好了自己的训练数据和标注好的xml文件,同时也复制了object detection API的环境。这时,我们需要去修改这些复制过来的环境文件,适配我们现在准备好的新环境,为训练做最后一步准备。

1.create_pascal_tf_record.py文件的修改

  我在修改的地方都注释了原来的代码,从而替换为新的代码,并且添加了中文说明,文件修改如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

r"""Convert raw PASCAL dataset to TFRecord for object_detection.

Example usage:
    python object_detection/dataset_tools/create_pascal_tf_record.py \
        --data_dir=/home/user/VOCdevkit \
        --year=VOC2012 \
        --output_path=/home/user/pascal.record
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import hashlib
import io
import logging
import os

from lxml import etree
import PIL.Image
import tensorflow as tf

from object_detection.utils import dataset_util
from object_detection.utils import label_map_util

#执行参数,我们可以在这里修改,也可以在执行时候带上参数修改,建议在执行时带上参数,这部分没有代码修改
flags = tf.app.flags
flags.DEFINE_string('data_dir', '', 'Root directory to raw PASCAL VOC dataset.')
flags.DEFINE_string('set', 'train', 'Convert training set, validation set or '
                    'merged set.')
flags.DEFINE_string('annotations_dir', 'Annotations',
                    '(Relative) path to annotations directory.')
flags.DEFINE_string('year', 'VOC2007', 'Desired challenge year.')
flags.DEFINE_string('output_path', '', 'Path to output TFRecord')
flags.DEFINE_string('label_map_path', 'data/pascal_label_map.pbtxt',
                    'Path to label map proto')
flags.DEFINE_boolean('ignore_difficult_instances', False, 'Whether to ignore '
                     'difficult instances')
FLAGS = flags.FLAGS

SETS = ['train', 'val', 'trainval', 'test']
#增加我们自己的数据集VOC2005
#YEARS = ['VOC2007', 'VOC2012', 'merged']
YEARS = ['VOC2007', 'VOC2012', 'merged', 'VOC2005']

#注意最后一个参数image_subdirectory的缺省值,可以在此修改
def dict_to_tf_example(data,
                       dataset_directory,
                       label_map_dict,
                       ignore_difficult_instances=False,
                       image_subdirectory='PNGImages'):
  """Convert XML derived dict to tf.Example proto.

  Notice that this function normalizes the bounding box coordinates provided
  by the raw data.

  Args:
    data: dict holding PASCAL XML fields for a single image (obtained by
      running dataset_util.recursive_parse_xml_to_dict)
    dataset_directory: Path to root directory holding PASCAL dataset
    label_map_dict: A map from string label names to integers ids.
    ignore_difficult_instances: Whether to skip difficult instances in the
      dataset  (default: False).
    image_subdirectory: String specifying subdirectory within the
      PASCAL dataset directory holding the actual image data.

  Returns:
    example: The converted tf.Example.

  Raises:
    ValueError: if the image pointed to by data['filename'] is not a valid JPEG
  """
  #确定照片的路径,建议将路径输出,看看是否正确。
  #这里有个大坑,官方的XML标注里面,filename字段是后面有文件类型的,但是用labelImg标注是没有的
  #我们在img_path里手动拼接 +'.png'
  #img_path = os.path.join(data['folder'],image_subdirectory, data['filename'])
  img_path = os.path.join('VOC2005', image_subdirectory, data['filename'])
  full_path = os.path.join(dataset_directory, img_path)
  #手动输入查看路径是否正确,这是为了在执行可能报错的情况下检查路径是否出错
  print('dataset_directory',dataset_directory)
  print('full_path',full_path)
  
  with tf.gfile.GFile(full_path, 'rb') as fid:
    encoded_jpg = fid.read()
  encoded_jpg_io = io.BytesIO(encoded_jpg)
  image = PIL.Image.open(encoded_jpg_io)
  #if image.format != 'JPEG':
  #  raise ValueError('Image format not JPEG')
  key = hashlib.sha256(encoded_jpg).hexdigest()

  width = int(data['size']['width'])
  height = int(data['size']['height'])

  xmin = []
  ymin = []
  xmax = []
  ymax = []
  classes = []
  classes_text = []
  truncated = []
  poses = []
  difficult_obj = []
  if 'object' in data:
    for obj in data['object']:
      difficult = bool(int(obj['difficult']))
      if ignore_difficult_instances and difficult:
        continue

      difficult_obj.append(int(difficult))

      xmin.append(float(obj['bndbox']['xmin']) / width)
      ymin.append(float(obj['bndbox']['ymin']) / height)
      xmax.append(float(obj['bndbox']['xmax']) / width)
      ymax.append(float(obj['bndbox']['ymax']) / height)
      classes_text.append(obj['name'].encode('utf8'))
      classes.append(label_map_dict[obj['name']])
      truncated.append(int(obj['truncated']))
      poses.append(obj['pose'].encode('utf8'))

  example = tf.train.Example(features=tf.train.Features(feature={
      'image/height': dataset_util.int64_feature(height),
      'image/width': dataset_util.int64_feature(width),
      'image/filename': dataset_util.bytes_feature(
          data['filename'].encode('utf8')),
      'image/source_id': dataset_util.bytes_feature(
          data['filename'].encode('utf8')),
      'image/key/sha256': dataset_util.bytes_feature(key.encode('utf8')),
      'image/encoded': dataset_util.bytes_feature(encoded_jpg),
      'image/format': dataset_util.bytes_feature('jpeg'.encode('utf8')),
      'image/object/bbox/xmin': dataset_util.float_list_feature(xmin),
      'image/object/bbox/xmax': dataset_util.float_list_feature(xmax),
      'image/object/bbox/ymin': dataset_util.float_list_feature(ymin),
      'image/object/bbox/ymax': dataset_util.float_list_feature(ymax),
      'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
      'image/object/class/label': dataset_util.int64_list_feature(classes),
      'image/object/difficult': dataset_util.int64_list_feature(difficult_obj),
      'image/object/truncated': dataset_util.int64_list_feature(truncated),
      'image/object/view': dataset_util.bytes_list_feature(poses),
  }))
  return example


def main(_):
  if FLAGS.set not in SETS:
    raise ValueError('set must be in : {}'.format(SETS))
  if FLAGS.year not in YEARS:
    raise ValueError('year must be in : {}'.format(YEARS))

  data_dir = FLAGS.data_dir
  #新增我们的数据集到years中
  #years = ['VOC2007', 'VOC2012']
  years = ['VOC2007', 'VOC2012', 'VOC2005']
  if FLAGS.year != 'merged':
    years = [FLAGS.year]

  writer = tf.python_io.TFRecordWriter(FLAGS.output_path)

  label_map_dict = label_map_util.get_label_map_dict(FLAGS.label_map_path)

  for year in years:
    logging.info('Reading from PASCAL %s dataset.', year)
	#修改成如下代码,这里只需要用到Main下面的train.txt,val.txt等4个文件
    #原来的代码是用了官方下面的XX_train.txt等文件
	#examples_path = os.path.join(data_dir, year, 'ImageSets', 'Main',
    #                             'aeroplane_' + FLAGS.set + '.txt')
    examples_path = os.path.join(data_dir, year, 'ImageSets', 
                                 'Main', FLAGS.set + '.txt')
    annotations_dir = os.path.join(data_dir, year, FLAGS.annotations_dir)
    examples_list = dataset_util.read_examples_list(examples_path)
    for idx, example in enumerate(examples_list):
      if idx % 100 == 0:
        logging.info('On image %d of %d', idx, len(examples_list))
      path = os.path.join(annotations_dir, example + '.xml')
      with tf.gfile.GFile(path, 'r') as fid:
        xml_str = fid.read()
      xml = etree.fromstring(xml_str)
      data = dataset_util.recursive_parse_xml_to_dict(xml)['annotation']

      tf_example = dict_to_tf_example(data, FLAGS.data_dir, label_map_dict,
                                      FLAGS.ignore_difficult_instances)
      writer.write(tf_example.SerializeToString())

  writer.close()


if __name__ == '__main__':
  tf.app.run()

2.修改pascal_label_map.pbtxt

  清空文件原来的内容,添加如下代码:

item {
  id: 1
  name: 'face'
}

item {
  id: 2
  name: 'person'
}

  这里只是针对本次训练的修改,如果你的分类和原本的分类无差别,则不需要清空,只需要将自己添加的类放到文件中即可。

3.将训练集转换为TFRecord文件

  在train_project文件夹中执行以下命令,生成训练集的record文件pascal_train.record验证集的record文件pascal_val.record
  生成pascal_train.record
  $ python dataset/create_pascal_tf_record.py --data_dir=/home/alpha_gpu/train_project/dataset --year=VOC2005 --set=train --output_path=/home/alpha_gpu/train_project/record/pascal_train.record --label_map_path=/home/alpha_gpu/train_project/dataset/pascal_label_map.pbtxt
  生成pascal_val.record
    $ python dataset/create_pascal_tf_record.py --data_dir=/alpha_gpu/train_project/dataset --year=VOC2005 --set=val --output_path=/home/alpha_gpu/train_project/record/pascal_val.record --label_map_path=/home/alpha_gpu/train_project/dataset/pascal_label_map.pbtxt
  ps:运行过程中可能有报错,大多是路径不匹配造成的。
  执行完成后,可以在*/train_project/record/下看到pascal_train.recordpascal_val.record两个文件,如下。
在这里插入图片描述

4.修改ssd_mobilenet_v1_coco.config

  同上,我在修改的地方都注释了原来的代码,从而替换为新的代码,并且添加了中文说明,文件修改如下:

model {
  ssd {
    #修改分类的个数
	#num_classes: 90
	num_classes: 2
    image_resizer {
      fixed_shape_resizer {
        height: 300
        width: 300
      }
    }
    feature_extractor {
      type: "ssd_mobilenet_v1"
      depth_multiplier: 1.0
      min_depth: 16
      conv_hyperparams {
        regularizer {
          l2_regularizer {
            weight: 3.99999989895e-05
          }
        }
        initializer {
          truncated_normal_initializer {
            mean: 0.0
            stddev: 0.0299999993294
          }
        }
        activation: RELU_6
        batch_norm {
          decay: 0.999700009823
          center: true
          scale: true
          epsilon: 0.0010000000475
          train: true
        }
      }
    }
···
···
train_config {
  batch_size: 64
  data_augmentation_options {
    random_horizontal_flip {
    }
  }
  data_augmentation_options {
    ssd_random_crop {
    }
  }
  optimizer {
    rms_prop_optimizer {
      learning_rate {
        exponential_decay_learning_rate {
          initial_learning_rate: 0.0005
          decay_steps: 800720
          decay_factor: 0.949999988079
        }
      }
      momentum_optimizer_value: 0.899999976158
      decay: 0.899999976158
      epsilon: 1.0
    }
  }
  #修改模型文件路径
  #fine_tune_checkpoint: "PATH_TO_BE_CONFIGURED/model.ckpt"
  fine_tune_checkpoint: "models/ssd_mobilenet_v1_coco_2018_01_28/model.ckpt"
  from_detection_checkpoint: true
  num_steps: 200000
}
train_input_reader {
  #修改label_map的文件路径
  #label_map_path: "PATH_TO_BE_CONFIGURED/mscoco_label_map.pbtxt"
  label_map_path: "dataset/pascal_label_map.pbtxt"
  tf_record_input_reader {
    #input_path: "PATH_TO_BE_CONFIGURED/mscoco_train.record"
	input_path: "record/pascal_train.record"
  }
}
eval_config {
  #用例个数,在训练完成后用来验证的参数,num_examples的值请打开train_project/dataset/VOC2005/ImageSets/Main/val.txt,查看验证集的数量。
  #num_examples: 8000
  num_examples: 68
  max_evals: 10
  use_moving_averages: false
}
eval_input_reader {
  #修改label_map的文件路径
  #label_map_path: "PATH_TO_BE_CONFIGURED/mscoco_label_map.pbtxt"
  label_map_path: "dataset/pascal_label_map.pbtxt"
  shuffle: ture
  num_readers: 1
  tf_record_input_reader {
    #input_path: "PATH_TO_BE_CONFIGURED/mscoco_val.record"
	input_path: "record/pascal_val.record"
  }
}

四、执行训练

在train_project目录下执行:

$ nohup python train.py --train_dir=train \
--model_config_path=/home/alpha_gpu/train_project/ssd_mobilenet_v1_coco.config \
--logtostderr &

这样可以将任务挂在后台运行,且将训练的模型文件保存在/train_project/train/下。

查看日志

  执行上述命令后,任务会将日志输出到train_project目录下的nohup.out文件。
  打开终端,执行tail -f nohup.out即可查看实时日志。

查看训练曲线

  在成功安装了Tensorflow object detection API后,会有一个tensorboard工具。
  在train_project目录下执行:
    $ tensorboard --logdir=train
  则会出现以下显示:
    TensorBoard 1.10.0 at http://alpha-gpu-server:6060 (Press CTAL+C to quit)
  打开游览器,输入http://alpha-gpu-server:6060,即可查看到训练曲线。

五、验证训练结果

  在train_project运行
    $ python eval.py --checkpoint_dir=train --eval_dir=eval --pipeline_config_path=train/pipeline.config
  运行结束后会看到类似如下的验证结果:

Running eval batches done
# success:68
# skipped:0

六、导出模型

  在train_project运行

$ python export_inference_graph.py --input_type=image_tensor \
--pipeline_config_path=/home/alpha_gpu/train_project/train/pipeline.config  \
--trained_checkpoint_prefix=train/model.ckpt-10250 \
--output_directory=resultmodel

  则会在train_project/resultmodel下保存如下文件则为保存成功:
  TensorFLow object detection API训练数据集_第6张图片

七、训练经验法则

  下面列出了几条可为您提供指导的经验法则:

  • 训练误差应该稳步减小,刚开始是急剧减小,最终应随着训练收敛达到平稳状态。
  • 如果训练尚未收敛,尝试运行更长的时间。
  • 如果训练误差减小速度过慢,则提高学习速率也许有助于加快其减小速度。
  • 但有时如果学习速率过高,训练误差的减小速度反而会变慢。
  • 如果训练误差变化很大,尝试降低学习速率。
  • 较低的学习速率和较大的步数/较大的批量大小通常是不错的组合。
  • 批量大小过小也会导致不稳定情况。不妨先尝试 100 或 1000 等较大的值,然后逐渐减小值的大小,直到出现性能降低的情况。
  • 重申一下,切勿严格遵循这些经验法则,因为效果取决于数据。请始终进行试验和验证。

你可能感兴趣的:(TensorFLow object detection API训练数据集)