商品物体检测项目

1. 项目概述

1.1 项目架构设计

在这里插入图片描述
整个项目的开发流程可以分为两大部分:

  • 模型的训练与测试
    • 数据读取
    • preprocess(数据预处理)
    • 网络构建预测结果
    • 损失计算并训练
    • 模型保存
  • 测试
    • 测试数据
    • preprocess(数据预处理)
    • 模型加载
    • preprocess(预测结果与后期处理处理)
    • 预测结果显式(matplotlib)
  • 模型部署与 小程序
    • 模型导出
    • Tensorflow Serving部署模型
    • Serving客户端 + Flask Web
    • 小程序前端
      在这里插入图片描述

1.2 训练与测试整体结构设计

在这里插入图片描述

  • 数据工厂:为了使项目能够读取不同的数据集
  • 预处理工厂:为了处理不同模型要求的处理需求
  • 模型工厂:为了项目训练数据,能够使用不同的模型
  • 代码结构:
    • chekpoints:微调初始模型目录
    • datasets:数据读取的模块
    • preprocessing:预处理模块
    • nets:网络模块
    • logs:保存训练打印日志
    • utils:训练的公共组件,工具
    • IMAGE:数据集
    • noteboos:测试工具
  • 设计意义:
    • 1.网络模型与网络模型之间不交差,模型与数据集之间解耦合,数据集与预处理逻辑解耦合
    • 2.训练代码可以调用不同的模型与不同的数据集训练不同的模型结果.

2. 接口设计

在这里插入图片描述

2.1数据接口设计

在这里插入图片描述

  • 数据接口模块需要完成的功能
    • 原始数据集(图片+XML) 转换成TFRecords文件格式
    • 读取TFRecords数据
  • 文件目录:
    在这里插入图片描述
    • dataset_factory:数据模块工厂路由,找到不同的数据集读取逻辑(供训练调用)
    • dataset_init:保存不同数据集的TFRecords格式读取逻辑
    • utils:数据模块的公共组件
    • data_config:数据模块的一些数据集配置文件
    • data_to_tfrecords:原始数据集格式转换逻辑

2.1.1 商品格式转换实现

  • dataset_to_tfrecord.py
from datasets import dataset_to_tfrecords


if __name__ == '__main__':
    dataset_to_tfrecords.run("./IMAGE/commodity/", "./IMAGE/tfrecords/commodity_tfrecords/", "commodity_2018_train")

  • dataset_tfrecords.py
import os
import tensorflow as tf
import xml.etree.ElementTree as ET
from datasets.utils.dataset_utils import int64_feature, float_feature, bytes_feature
from datasets.dataset_config import DIRECTORY_ANNOTATIONS, DIRECTORY_IMAGES, SAMPLES_PER_FILES, VOC_LABELS


def _process_image(dataset_dir, img_name):
    """
    读取图片内容以及XML文件内容
    :param dataset_dir: 文件目录
    :param img_name: 文件名字 编号
    :return:
    """
    # 1、读取图片
    # 构造图片路径+要读取的文件名
    filename = dataset_dir + DIRECTORY_IMAGES + img_name + '.jpg'

    # 读取图片数据
    image_data = tf.gfile.FastGFile(filename, 'rb').read()

    # 2、读取XML
    # 需要使用ET工具读取
    # 构造XML文件名字
    filename_xml = dataset_dir + DIRECTORY_ANNOTATIONS + img_name + '.xml'

    # 将文件内容变成一个树状结构
    tree = ET.parse(filename_xml)

    root = tree.getroot()

    # 获取root下的一些子节点
    # (1)获取size 信息
    size = root.find('size')

    # 把三个宽、高、通道数存在一个shape里
    shape = [int(size.find('height').text),
             int(size.find('width').text),
             int(size.find('depth').text)]

    # (2)获取object信息
    # 每一个obj都含有,name, truncated, difficult, bndbox[xmin, ymin, xmax, ymax]
    labels = []
    labels_text = []
    difficult = []
    truncated = []
    bboxes = []
    for obj in root.findall('object'):

        # 解析每一个obj
        # 取出目标label,具体的物体类别
        # 动物:猫、狗、等等     数字与之一一对应
        label = obj.find('name').text

        # 取出与之对应的物体大类别
        labels.append(int(VOC_LABELS[label][0]))
        labels_text.append(label.encode('ascii'))

        # 取出diffcult
        if obj.find('difficult'):
            difficult.append(int(obj.find('difficult').text))
        else:
            difficult.append(0)

        # 取出truncated
        if obj.find('truncated'):
            truncated.append(int(obj.find('truncated').text))
        else:
            truncated.append(0)

        # 取出每一个对象四个位置,存储顺序按照ymin, xmin, ymax, xmax存储
        bbox = obj.find('bndbox')

        bboxes.append((float(bbox.find('ymin').text) / shape[0],
                       float(bbox.find('xmin').text) / shape[1],
                       float(bbox.find('ymax').text) / shape[0],
                       float(bbox.find('xmax').text) / shape[1]))

    return image_data, shape, labels, difficult, truncated, bboxes, labels_text


def _convert_to_example(image_data, shape, labels, difficult, truncated, bboxes, labels_text):
    """ 将一张图片数据转使用example转换成protobuffer格式
    """
    # 为了转换需求,对bboxes进行格式调整,从一个obj的四个属性列表,编程四个位置单独的列表
    # [ [23,46,234,13], [34,564,2,465]  ] ----> ymin [23, 34] xmin [46, 564],ymax [234,2] xmax[12,465]
    ymin = []
    xmin = []
    ymax = []
    xmax = []

    for b in bboxes:
        ymin.append(b[0])
        xmin.append(b[1])
        ymax.append(b[2])
        xmax.append(b[3])

    # 将所有信息封装成exmaple
    # 每一个存入的属性都要指定类型
    image_format = b'JPEG'
    example = tf.train.Example(features=tf.train.Features(feature={
        'image/height': int64_feature(shape[0]),
        'image/width': int64_feature(shape[1]),
        'image/channels': int64_feature(shape[2]),
        'image/shape': int64_feature(shape),
        'image/object/bbox/xmin': float_feature(xmin),
        'image/object/bbox/xmax': float_feature(xmax),
        'image/object/bbox/ymin': float_feature(ymin),
        'image/object/bbox/ymax': float_feature(ymax),
        'image/object/bbox/label': int64_feature(labels),
        'image/object/bbox/label_text': bytes_feature(labels_text),
        'image/object/bbox/difficult': int64_feature(difficult),
        'image/object/bbox/truncated': int64_feature(truncated),
        'image/format': bytes_feature(image_format),
        'image/encoded': bytes_feature(image_data)}))

    return example


def _add_to_tfrecord(dataset_dir, img_name, tfrecord_writer):
    """添加一个图片文件内容以及XML内容写入到文件当中
    :param outputdir: 输出文件目录
    :param img_name: 图片编号
    :param tfrecord_writer: 文件写入实例
    :return:
    """
    # 1、读取每张图片的内容,以及每张图片对应的XML内容
    image_data, shape, labels, difficult, truncated, bboxes, labels_text = _process_image(dataset_dir, img_name)

    # print(image_data)
    # 2、将每一张图片封装成一个example
    example = _convert_to_example(image_data, shape, labels, difficult, truncated, bboxes, labels_text)

    # 3、用tfrecord_writer写入example的序列化结果
    tfrecord_writer.write(example.SerializeToString())

    return None


def _get_output_filename(outputdir, name, fdx):
    """获取输出的文件名字
    :param outputdir: 输出路径
    :param name: 数据集名字,参考 VOC_2007_test or VOC_2007_train
    :param fdx: 0 ~
    :return:
    """
    return "%s/%s_%03d.tfrecord" % (outputdir, name, fdx)


def run(dataset_dir, outputdir, name="data"):
    """
    运行转换代码逻辑,存入多个tfrecord文件,每个文件固定N个样本
    :param dataset_dir: 数据集的目录, 里面存在annotations , Jpegimages
    :param outputdir: TFRecords存储目录
    :param name: 数据集名字,指定名字以及train or test
    :return:
    """
    # 1、判断数据集目录是否存在,创建一个目录
    if tf.gfile.Exists(dataset_dir):
        tf.gfile.MakeDirs(dataset_dir)

    # 2、去读取某个文件夹下的所有文件名字列表
    path = os.path.join(dataset_dir, DIRECTORY_ANNOTATIONS)

    # 读取所有文件时,会打乱顺序,
    filenames = sorted(os.listdir(path))

    # 3、循环这个名字列表
    # 每200个图片以及XML信息就存储到一个tfrecord文件当中
    i = 0
    fdx = 0
    while i < len(filenames):

        # 1、创建一个TFRecord文件,有第几个文件的序号
        tf_filename = _get_output_filename(outputdir, name, fdx)

        # 2、循环标记每隔200个图片内容,存储一次
        # 新建一个TFRecord文件的存储器
        with tf.python_io.TFRecordWriter(tf_filename) as tfrecord_writer:

            j = 0

            while i < len(filenames) and j < SAMPLES_PER_FILES:

                print("转换图片进度 %d/%d " % (i+1, len(filenames)))

                # 取出图片的名字以及XML的名字
                # single_filename *.xml
                single_filename = filenames[i]

                img_name = single_filename[:-4]

                # 读取图片内容以及XML文件内容,存入文件
                # 默认每次构造一个图片文件存储指定文件
                # 每个图片构造一个example存储到tf_filename当中
                _add_to_tfrecord(dataset_dir, img_name, tfrecord_writer)

                i += 1
                j += 1

            # 存储文件的序号也要进行增加
            fdx += 1

    print("完成所有图片数据集 %s 的转换" % name)
  • dataset_config.py
from collections import namedtuple
"""
数据集格式转换配置
"""
# 指定原始图片的XML和图片的文件夹名字
DIRECTORY_ANNOTATIONS = "Annotations/"

DIRECTORY_IMAGES = "JPEGImages/"

# 指定每个TFRecord文件存储图片数量
SAMPLES_PER_FILES = 200

# 商品数据集的类别
VOC_LABELS = {
   'none': (0, 'Background'),
   'clothes': (1, 'clothes'),
   'pants': (2, 'pants'),
   'shoes': (3, 'shoes'),
   'watch': (4, 'watch'),
   'phone': (5, 'phone'),
   'audio': (6, 'audio'),
   'computer': (7, 'computer'),
   'books': (8, 'books'),
}

# VOC 2007物体类别
# VOC_LABELS = {
#     'none': (0, 'Background'),
#     'aeroplane': (1, 'Vehicle'),
#     'bicycle': (2, 'Vehicle'),
#     'bird': (3, 'Animal'),
#     'boat': (4, 'Vehicle'),
#     'bottle': (5, 'Indoor'),
#     'bus': (6, 'Vehicle'),
#     'car': (7, 'Vehicle'),
#     'cat': (8, 'Animal'),
#     'chair': (9, 'Indoor'),
#     'cow': (10, 'Animal'),
#     'diningtable': (11, 'Indoor'),
#     'dog': (12, 'Animal'),
#     'horse': (13, 'Animal'),
#     'motorbike': (14, 'Vehicle'),
#     'person': (15, 'Person'),
#     'pottedplant': (16, 'Indoor'),
#     'sheep': (17, 'Animal'),
#     'sofa': (18, 'Indoor'),
#     'train': (19, 'Vehicle'),
#     'tvmonitor': (20, 'Indoor'),
# }

"""
数据集读取的配置
"""
# 创建命名字典
DataSetParams = namedtuple('DataSetParameters', ['FILE_PATTERN',
                                                'NUM_CLASSES',
                                                'SPLITS_TO_SIZES',
                                                'ITEMS_TO_DESCRIPTIONS'
                                               ])

# 定义commodity_2018数据属性配置
Cm2018 = DataSetParams(
   FILE_PATTERN='commodity_2018_%s_*.tfrecord',
   NUM_CLASSES=8,
   SPLITS_TO_SIZES={
       'train': 88,
       'test': 0
   },
   ITEMS_TO_DESCRIPTIONS={
       'image': '图片数据',
       'shape': '图篇形状',
       'object/bbox': '若干物体对象的bbox框组成的列表',
       'object/label': '若干物体的编号'
   }
)

  • utils.py

import tensorflow as tf


class TFRecordsReaderBase(object):
   """数据集基类
   """
   def __init__(self, param):
       # param给不同数据集的属性配置
       self.param = param

   def get_data(self, train_or_test, dataset_dir):
       """
       获取数据规范
       :param train_or_test: train  or test数据文件
       :param dataset_dir: 数据集目录
       :return:
       """
       return None


def int64_feature(value):
   """包裹int64型特征到Example
   """
   if not isinstance(value, list):
       value = [value]
   return tf.train.Feature(int64_list=tf.train.Int64List(value=value))


def float_feature(value):
   """包裹浮点型特征到Example
   """
   if not isinstance(value, list):
       value = [value]
   return tf.train.Feature(float_list=tf.train.FloatList(value=value))


def bytes_feature(value):
   """包裹字节类型特征到Example
   """
   if not isinstance(value, list):
       value = [value]
   return tf.train.Feature(bytes_list=tf.train.BytesList(value=value))

  • 输出结果
/home/yuyang/anaconda3/envs/tensor1-6/bin/python3.5 /media/yuyang/Yinux/24期python就业班课件/物体检测项目/物体检测项目(三)--项目实现与部署/物体检测项目(三)--项目实现与部署/代码/online_class_V2.0/online_class_V2.0/dataset_to_tfrecord.py
转换图片进度 1/88 
......
转换图片进度 88/88 
完成所有图片数据集 commodity_2018_train 的转换

Process finished with exit code 0

2.1.2 读取数据接口设计

数据模块含有TFRecords的读取逻辑,需要对应到不同的数据集类型处理如(Pascalvoc和Commodity).类的设计如下:
在这里插入图片描述
完整代码在2.1.1节中的utils.py中.如下为构建基类代码:

class TFRecordsReaderBase(object):
    """数据集基类
    """
    def __init__(self, param):
        # param给不同数据集的属性配置
        self.param = param

    def get_data(self, train_or_test, dataset_dir):
        """
        获取数据规范
        :param train_or_test: train  or test数据文件
        :param dataset_dir: 数据集目录
        :return:
        """
        return None

建立一个基类,TFRecordsReaderBase类
PascalvocTFRecords和CommodityTFRecords继承数据读取基类,实现属性方法和细节.

2.1.3 商品数据读取子类

  • commodity_2018.py

import os
import tensorflow as tf
from datasets.utils import dataset_utils

slim = tf.contrib.slim


class CommodityTFRecords(dataset_utils.TFRecordsReaderBase):
    """商品数据集读取类
    """
    def __init__(self, param):
        self.param = param

    def get_data(self, train_or_test, dataset_dir):
        """获取数据方法
        """
        # 异常抛出
        if train_or_test not in ['train', 'test']:
            raise ValueError("训练/测试的名字 %s 指定错误" % train_or_test)

        # 判断数据集目录
        if not tf.gfile.Exists(dataset_dir):
            raise ValueError("数据集目录不存在")

        # 构造第一个参数:数据目录+文件名
        file_pattern = os.path.join(dataset_dir, self.param.FILE_PATTERN % train_or_test)

        # 准备第二个参数:
        reader = tf.TFRecordReader

        # 准备第三个参数:decoder
        # 反序列化的格式
        keys_to_features = {
            'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),
            'image/format': tf.FixedLenFeature((), tf.string, default_value='jpeg'),
            'image/height': tf.FixedLenFeature([1], tf.int64),
            'image/width': tf.FixedLenFeature([1], tf.int64),
            'image/channels': tf.FixedLenFeature([1], tf.int64),
            'image/shape': tf.FixedLenFeature([3], tf.int64),
            'image/object/bbox/xmin': tf.VarLenFeature(dtype=tf.float32),
            'image/object/bbox/ymin': tf.VarLenFeature(dtype=tf.float32),
            'image/object/bbox/xmax': tf.VarLenFeature(dtype=tf.float32),
            'image/object/bbox/ymax': tf.VarLenFeature(dtype=tf.float32),
            'image/object/bbox/label': tf.VarLenFeature(dtype=tf.int64),
            'image/object/bbox/difficult': tf.VarLenFeature(dtype=tf.int64),
            'image/object/bbox/truncated': tf.VarLenFeature(dtype=tf.int64),
        }
        # 2、反序列化成高级的格式
        # 其中bbox框ymin [23] xmin [46],ymax [234] xmax[12]--->[23,46,234,13]
        items_to_handlers = {
            'image': slim.tfexample_decoder.Image('image/encoded', 'image/format'),
            'shape': slim.tfexample_decoder.Tensor('image/shape'),
            'object/bbox': slim.tfexample_decoder.BoundingBox(
                ['ymin', 'xmin', 'ymax', 'xmax'], 'image/object/bbox/'),
            'object/label': slim.tfexample_decoder.Tensor('image/object/bbox/label'),
            'object/difficult': slim.tfexample_decoder.Tensor('image/object/bbox/difficult'),
            'object/truncated': slim.tfexample_decoder.Tensor('image/object/bbox/truncated'),
        }

        # 构造decoder
        decoder = slim.tfexample_decoder.TFExampleDecoder(keys_to_features, items_to_handlers)

        return slim.dataset.Dataset(
            data_sources=file_pattern,
            reader=reader,
            decoder=decoder,
            num_samples=self.param.SPLITS_TO_SIZES[train_or_test],
            items_to_descriptions=self.param.ITEMS_TO_DESCRIPTIONS,
            num_classes=self.param.NUM_CLASSES)


  • pasvalvoc_2007.py
import os
import tensorflow as tf

slim = tf.contrib.slim


def get_dataset(dataset_dir):
   """
   获取pascalvoc2007数据集
   :param dataset_dir: 数据集目录
   :return: Dataset
   """
   # 构造第一个参数:数据目录+文件名
   file_pattern = os.path.join(dataset_dir, "VOC_2007_test_*.tfrecord")

   # 准备第二个参数:
   reader = tf.TFRecordReader

   # 准备第三个参数:decoder
   # 反序列化的格式
   keys_to_features = {
       'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),
       'image/format': tf.FixedLenFeature((), tf.string, default_value='jpeg'),
       'image/height': tf.FixedLenFeature([1], tf.int64),
       'image/width': tf.FixedLenFeature([1], tf.int64),
       'image/channels': tf.FixedLenFeature([1], tf.int64),
       'image/shape': tf.FixedLenFeature([3], tf.int64),
       'image/object/bbox/xmin': tf.VarLenFeature(dtype=tf.float32),
       'image/object/bbox/ymin': tf.VarLenFeature(dtype=tf.float32),
       'image/object/bbox/xmax': tf.VarLenFeature(dtype=tf.float32),
       'image/object/bbox/ymax': tf.VarLenFeature(dtype=tf.float32),
       'image/object/bbox/label': tf.VarLenFeature(dtype=tf.int64),
       'image/object/bbox/difficult': tf.VarLenFeature(dtype=tf.int64),
       'image/object/bbox/truncated': tf.VarLenFeature(dtype=tf.int64),
   }
   # 2、反序列化成高级的格式
   # 其中bbox框ymin [23] xmin [46],ymax [234] xmax[12]--->[23,46,234,13]
   items_to_handlers = {
       'image': slim.tfexample_decoder.Image('image/encoded', 'image/format'),
       'shape': slim.tfexample_decoder.Tensor('image/shape'),
       'object/bbox': slim.tfexample_decoder.BoundingBox(
           ['ymin', 'xmin', 'ymax', 'xmax'], 'image/object/bbox/'),
       'object/label': slim.tfexample_decoder.Tensor('image/object/bbox/label'),
       'object/difficult': slim.tfexample_decoder.Tensor('image/object/bbox/difficult'),
       'object/truncated': slim.tfexample_decoder.Tensor('image/object/bbox/truncated'),
   }

   # 构造decoder
   decoder = slim.tfexample_decoder.TFExampleDecoder(keys_to_features, items_to_handlers)

   return slim.dataset.Dataset(
           data_sources=file_pattern,
           reader=reader,
           decoder=decoder,
           num_samples=4921,
           items_to_descriptions={
               'image': 'A color image of varying height and width.',
               'shape': 'Shape of the image',
               'object/bbox': 'A list of bounding boxes, one per each object.',
               'object/label': 'A list of labels, one per each object.'
           },  # 数据集返回的格式描述字典
           num_classes=20)

  • dataset_config.py完整代码在2.1.1,如下是数据读取的配置
数据集读取的配置
"""
# 创建命名字典
DataSetParams = namedtuple('DataSetParameters', ['FILE_PATTERN',
                                                 'NUM_CLASSES',
                                                 'SPLITS_TO_SIZES',
                                                 'ITEMS_TO_DESCRIPTIONS'
                                                ])

# 定义commodity_2018数据属性配置
Cm2018 = DataSetParams(
    FILE_PATTERN='commodity_2018_%s_*.tfrecord',
    NUM_CLASSES=8,
    SPLITS_TO_SIZES={
        'train': 88,
        'test': 0
    },
    ITEMS_TO_DESCRIPTIONS={
        'image': '图片数据',
        'shape': '图篇形状',
        'object/bbox': '若干物体对象的bbox框组成的列表',
        'object/label': '若干物体的编号'
    }
)
  • untils.py完整代码在2.1.1,如下为数据获取代码.
class TFRecordsReaderBase(object):
   """数据集基类
   """
   def __init__(self, param):
       # param给不同数据集的属性配置
       self.param = param

   def get_data(self, train_or_test, dataset_dir):
       """
       获取数据规范
       :param train_or_test: train  or test数据文件
       :param dataset_dir: 数据集目录
       :return:
       """
       return None

2.1.4 数据读取工厂逻辑

  • dataset_factory.py
from datasets.dataset_init import commodity_2018
from datasets.dataset_config import Cm2018

# 定义dataset种类的字典
datasets_map = {
    'commodity_2018': commodity_2018.CommodityTFRecords

}

# 逻辑
# 1、数据集名称
# 2、指定训练还是测试
# 3、数据集目录指定


def get_dataset(dataset_name, train_or_test, dataset_dir):
    """
    获取指定数据名称的数据文件
    :param dataset_name: 数据集名称(数据当中要存在)
    :param train_or_test: train or test数据集
    :param dataset_dir: 数据集目录
    :return: Dataset 数据规范
    """
    if dataset_name not in datasets_map:
        raise ValueError("输入的数据集名称 %s 不存在" % dataset_name)

    return datasets_map[dataset_name](Cm2018).get_data(train_or_test, dataset_dir)

2.1.5 代码运行与数据模块

  • tf_read_tfrecord.py
from datasets import dataset_factory
import tensorflow as tf

slim = tf.contrib.slim


if __name__ == '__main__':

    # 获取dataset
    dataset = dataset_factory.get_dataset("commodity_2018", 'train', './IMAGE/tfrecords/commodity_tfrecords')

    # 2、通过provider去取出数据
    provider = slim.dataset_data_provider.DatasetDataProvider(
        dataset,
        num_readers=3
    )

    # 通过get方法获取指定名称的数据(是在准备规范数据dataset时高级格式的名称)
    [image, shape, bbox, label] = provider.get(
        ['image', 'shape', 'object/bbox', 'object/label'])

    print(image, shape, bbox, label)

读取结果:

/home/yuyang/anaconda3/envs/tensor1-6/bin/python3.5 /media/yuyang/Yinux/24期python就业班课件/物体检测项目/物体检测项目(三)--项目实现与部署/物体检测项目(三)--项目实现与部署/代码/online_class_V2.0/online_class_V2.0/tf_read_tfrecord.py
Tensor("case/cond/Merge:0", shape=(?, ?, 3), dtype=uint8) Tensor("Reshape_4:0", shape=(3,), dtype=int64) Tensor("transpose:0", shape=(?, 4), dtype=float32) Tensor("SparseToDense:0", shape=(?,), dtype=int64)

Process finished with exit code 0

2.2 模型接口

在这里直接用Tensorflow官方封装过的接口,在程序当中做一些偶和操作.
首先在整个项目中添加一个nets文件夹,并在其中进行如下文件简建立:

在这里插入图片描述

  • ssd_vgg300:SSD网络模块
  • utils:网络用到的公用组件(现有的代码)
    • layers_utils.py:网络处理工具
    • ssd_utils.py:SSD boxes编解码工具
      由于网络中用到的基础代码组件较多,将项目中涉及到的源码放在basic_tools中,我们将其放在整个项目的utils中,在根目录下创建utils目录:
      在这里插入图片描述
  • nets_factory.py
from nets.nets_model import ssd_vgg_300

networks_obj = {
   'ssd_vgg_300': ssd_vgg_300.SSDNet
}


# 1、训练网络名称
def get_network(network_name):
   """
   获取不同的网络模型
   :param network_name: 网络模型的名称
   :return: 网络
   """
   return networks_obj[network_name]

2.3 预处理接口

  • 目的
    • 在图像的深度学习中,对数入的数据进行数据增强(Data Augmentation),为了丰富图像训练结果,更好的提取图像特征,泛化模型(防止模型过拟合).
    • 还有一个最根本的目的就是要把图片变成符合大小的要求.
      • RCNN对输入的图片没有要求,但是网络当中卷积之前需要227*227同一大小
      • YOLO算法:输入图片大小变成448*448
      • SSD算法:输入图片大小变换为300*300

对图像进行平移,缩放,翻转,裁剪等操作.

  • 预处理模块结构图
    在这里插入图片描述
    其中ssd_vgg_preprocessing.py,image_tools.py是源代码.
    只需写preprocessing_factory.py进行调用.
  • preprocessing_factory.py
from preprocessing.processing import ssd_vgg_preprocessing

preprocessing_fn_map = {
    "ssd_vgg_300": ssd_vgg_preprocessing
}


# 定义函数:预处理逻辑名称,提供是否是训练的处理过程

def get_preprocessing(name, is_training=True):
    """预处理工厂获取不同的模型数据增强(预处理)方式
    :param name: 模型预处理名称
    :param is_training: 是否训练
    :return: 返回预处理的函数
    """
    if name not in preprocessing_fn_map:
        raise ValueError("选择的预处理名称 %s 不在预处理模型库当中,请提供该模型预处理代码" % name)

    # 返回一个处理的函数,后续再去调用这个函数
    def preprocessing_fn(image, labels, bboxes,
                         out_shape, data_format='NHWC', **kwargs):
        return preprocessing_fn_map[name].preprocess_image(image, labels, bboxes,
                                                           out_shape, data_format=data_format,
                                                           is_training=is_training, **kwargs)
    return preprocessing_fn

3.训练流程

3.1 训练流程与设备部署

在这里插入图片描述
在计算过程中,CPU与GPU之间进行分工.Tensorflow会通过标号来区分不同的GPU和CPU,如"/device:CPU:0","/device:GPU:0","/device:GPU:1","/device:GPU:2".

  • 步骤
    • 数据读取
    • 数据预处理
    • 网络构建
    • 损失计算
    • 添加到Tensorboard
    • 模型训练,保存
  • 部署要求:整个训练在多GPU,多计算机环境下进行.
  • 模型训练部署原理:
    在这里插入图片描述
  • model_deploy:
    • model_deploy位于TensorFlow slim模块的目录下,可以使用多个GPU/CPU在同一台机器或多台机器上执行同步或异步训练.
      • clone:指的是每个GPU都会的到一个完整的模型统一计算.
        在这里插入图片描述

3.2 训练过程

3.2.1 训练逻辑梳理:

  • 第一步:DeploymentConfig
    • 需要在训练之前配置所有的设备信息(几台电脑,几块GPU)
    • 定义全局步数
  • 第二步:获取图片队列数据
    • 在config.inputs_device()指定的设备下进行
  • 第三步:数据输入,网络计算结果,定义损失并复制模型到clones,添加变量到Tensorboard
  • model_deploy.creat_clones
  • 第四步:定义学习率,优化器
    • config.optimizer_device()下指定
  • 第五步:计算所有CPU/GPU设备的平均损失和每个变量的梯度总和,定义训OP,summries OP
    • model_deploy.optimize_clones
  • 第六步:配置训练的config进行训练
    • slim.learning.train

3.2.2 代码编写

  • 直接建立一个train_ssd_network.py的文件,在项目的根目录下用于训练.

在这里插入图片描述
在这里插入图片描述
其中utils当中的train_tools.py为训练所用到的一些API函数,组件.训练要导入的模块以及参数.train_tools当中API将要用到的方法如下:
在这里插入图片描述

3.2.2.1 在训练之前确定基本的配置和参数
  • 代码运行基本参数和文件夹
PRE_TRAINED_PATH=./ckpt/pre_trained/ssd_300_vgg.ckpt
TRAIN_MODEL_DIR=./ckpt/fine_tuning/
DATASET_DIR=./IMAGE/tfrecords/commodity_tfrecords/
  • 训练参数:
    每批次训练样本数:32或者更小
    网络误差函数惩罚项值:0.0005
    学习率:0.001
    终止学习率:0.0001
    优化器选择:adam优化器
    模型名称:ssd_vgg_300
3.2.2.2 获取图片队列数据以及处理进行样本的GT标记
  • 将每个模块结果先获取
    • 1.数据工厂返回数据规范信息
    • 2.定义SSD网络,并通过参数初始化获取每一层默认计算出来的defaultanchors(使用GT样本标记)
  • 通过deploy_config.inputs_device()指定输入数据的Tensor设备,并在设备下获取数据
    # 1,slim.dataset_data_provider.DatasetDataProvider通过GET方法获取数据
    # 2,对数据进行预处理
    # 3,对获取出来的groundtruth标签和bbox。进行编码
    # 4,获取的单个样本数据,要进行批处理以及返回队列
3.2.2.3 赋值模型到不同的GPU设备,以及损失、变量的观察
update_ops, first_clone_scope, clones = train_tools.deploy_loss_summary(deploy_config,
                                                                                batch_queue,
                                                                                ssd_net,
                                                                                summaries,
                                                                                batch_shape,
                                                                                FLAGS)

在这里插入图片描述

3.2.2.4 定义学习率及优化器

在这里插入图片描述

3.2.2.5 计算所有GPU设备的平均损失与每个变量的梯度总和

在这里插入图片描述

3.2.2.6 train_ssd_network.py
"""训练初始确定事项
PRE_TRAINED_PATH=./ckpt/pre_trained/ssd_300_vgg.ckpt
TRAIN_MODEL_DIR=./ckpt/fine_tuning/
DATASET_DIR=./IMAGE/tfrecords/commodity_tfrecords/
python train_ssd_network.py --train_model_dir=${TRAIN_MODEL_DIR} --dataset_dir=${DATASET_DIR} --dataset_name="commodity_2018" --train_or_test=train --model_name=ssd_vgg_300 --pre_trained_path=${PRE_TRAINED_PATH} --weight_decay=0.0005 --optimizer=adam --learning_rate=0.001 --batch_size=2
训练学习率等值的确定:
每批次训练样本数:32或者更小
网络误差函数惩罚项值:0.0005
学习率:0.001
终止学习率:0.0001
优化器选择:adam优化器
模型名称:ssd_vgg_300

"""
import tensorflow as tf

from datasets import dataset_factory
from deployment import model_deploy
from nets import nets_factory
from preprocessing import preprocessing_factory
from utils import train_tools

slim = tf.contrib.slim

DATA_FORMAT = "NHWC"

# 设备的命令行参数配置
tf.app.flags.DEFINE_integer('num_clones', 1, "可用设备的GPU数量")
tf.app.flags.DEFINE_boolean('clone_on_cpu', False, "是否只在CPU上运行")

# 数据集相关命令行参数设置
tf.app.flags.DEFINE_string('dataset_dir', ' ', "训练数据集目录")
tf.app.flags.DEFINE_string('dataset_name', 'commodity_2018', '数据集名称参数')
tf.app.flags.DEFINE_string('train_or_test', 'train', '是否是训练集还是测试集')

# 网络相关配置
tf.app.flags.DEFINE_float(
    'weight_decay', 0.00004, '网络误差函数惩罚项值,越小越防止过拟合.')
tf.app.flags.DEFINE_string(
    'model_name', 'ssd_vgg_300', '用于训练的网络模型名称')
# 设备的命令行参数配置
tf.app.flags.DEFINE_integer('batch_size', 32, "每批次获取样本数")

# 训练学习率相关参数
tf.app.flags.DEFINE_string(
    'optimizer', 'rmsprop', '优化器种类 可选"adadelta", "adagrad", "adam","ftrl", "momentum", "sgd" or "rmsprop".')
tf.app.flags.DEFINE_string(
    'learning_rate_decay_type', 'exponential','学习率迭代种类  "fixed", "exponential", "polynomial"')
tf.app.flags.DEFINE_float(
    'learning_rate', 0.01, '模型初始学习率')
tf.app.flags.DEFINE_float(
    'end_learning_rate', 0.0001, '模型训练迭代后的终止学习率')
tf.app.flags.DEFINE_integer(
    'max_number_of_steps', None, '训练的最大步数')
tf.app.flags.DEFINE_string(
    'train_model_dir', ' ', '训练输出的模型目录')
# pre-trained模型路径.
tf.app.flags.DEFINE_string(
    'pre_trained_model', None, '用于fine-tune的已训练好的基础参数文件位置')

FLAGS = tf.app.flags.FLAGS


def main(_):

    if not FLAGS.dataset_dir:
        raise ValueError('必须指定一个TFRecords的数据集目录')

        # 设置打印级别
    tf.logging.set_verbosity(tf.logging.DEBUG)

    with tf.Graph().as_default():
        # 在默认的图当中进行编写训练逻辑
        # DeploymentConfig以及全部参数
        # 配置计算机相关情况
        deploy_config = model_deploy.DeploymentConfig(
            num_clones=FLAGS.num_clones,  # GPU设备数量
            clone_on_cpu=FLAGS.clone_on_cpu, #是否只用CPU
            replica_id=0,
            num_replicas=1,  # 配置1台计算机
            num_ps_tasks=0   #默认使用第一台
        )
        # 定义一个全局步长参数(网络训练都会这么去进行配置)
        # 使用指定设备 tf.device
        with tf.device(deploy_config.variables_device()):
            global_step = tf.train.create_global_step()

        # 2、获取图片数据,做一些处理
        # 图片有什么?image, shape, bbox, label
        # image会做一些数据增强,大小变换
        # 直接训练?需要将anchor bbox进行样本标记正负样本,目的使的GT目标样本的数量与default bboxes数量一致
        # 将每个模块结果先获取
        # (1) 通过数据工厂取出规范信息
        dataset = dataset_factory.get_dataset(FLAGS.dataset_name, FLAGS.train_or_test, FLAGS.dataset_dir)

        # (2)获取网络计算的anchors结果
        ssd_class = nets_factory.get_network(FLAGS.model_name)

        # 获取默认网络参数
        ssd_params = ssd_class.default_params._replace(num_classes=9)

        # 初始化网络init函数
        ssd_net = ssd_class(ssd_params)

        # 获取形状,用于输入到anchors函数参数当中
        ssd_shape = ssd_net.params.img_shape

        # 获取anchors, SSD网络当中6层的所有计算出来的默认候选框
        ssd_anchors = ssd_net.anchors(ssd_shape)

        # (3)获取预处理函数
        image_preprocessing_fn = preprocessing_factory.get_preprocessing(
            FLAGS.model_name, is_training=True
        )

        # 打印网络相关参数
        train_tools.print_configuration(ssd_params, dataset.data_sources)

        # 2.2
        # 1,slim.dataset_data_provider.DatasetDataProvider通过GET方法获取数据
        # 2,对数据进行预处理
        # 3,对获取出来的groundtruth标签和bbox。进行编码
        # 4,获取的单个样本数据,要进行批处理以及返回队列
        with tf.device(deploy_config.inputs_device()):

            # 给当前操作取一个作用域名称
            with tf.name_scope(FLAGS.model_name + '_data_provider'):

                # slim.dataset_data_provider.DatasetDataProvider通过GET方法获取数据
                provider = slim.dataset_data_provider.DatasetDataProvider(
                    dataset,
                    num_readers=4,
                    common_queue_capacity=20 * FLAGS.batch_size,
                    common_queue_min=10 * FLAGS.batch_size,
                    shuffle=True
                )
                # 通过get获取数据
                # 真正获取参数
                [image, shape, glabels, gbboxes] = provider.get(['image', 'shape', 'object/label', 'object/bbox'])

                # 直接进行数据预处理
                # image [?, ?, 3]---->[300, 300, 3]
                image, glabels, gbboxes = image_preprocessing_fn(image, glabels, gbboxes,
                                                                 out_shape=ssd_shape,
                                                                 data_format=DATA_FORMAT)

                # 对原始anchor bboxes进行正负样本标记
                # 得到目标值,编码之后,返回?
                # 训练?  预测值类别,物体位置,物体类别概率,目标值
                # 8732 anchor,   得到8732个与GT 对应的标记的anchor
                # gclasses:目标类别
                # glocalisations:目标类别的真是位置
                # gscores:是否是正负样本
                gclasses, glocalisations, gscores = ssd_net.bboxes_encode(glabels, gbboxes, ssd_anchors)

                # print(gclasses, glocalisations)

                # 特征值、目标
                # 批处理以及队列处理
                # tensor_list:tensor列表 [tensor, tensor, ]
                # tf.train.batch(tensor_list, batch_size, num_threads, capacity)
                # [Tensor, [6], [6], [6]]  嵌套的列表要转换成单列表形式
                r = tf.train.batch(train_tools.reshape_list([image, gclasses, glocalisations, gscores]),
                                   batch_size=FLAGS.batch_size,
                                   num_threads=4,
                                   capacity=5 * FLAGS.batch_size)
                # r应该是一个19个Tensor组成的一个列表
                # print(r)
                # 批处理数据放入队列
                # 1个r:批处理的样本, 5个设备,5个r, 5组32张图片
                # 队列的目的是为了不同设备需求
                batch_queue = slim.prefetch_queue.prefetch_queue(r,
                                                                 capacity=deploy_config.num_clones)

        # 3、赋值模型到不同的GPU设备,以及损失、变量的观察
        # train_tools.deploy_loss_summary(deploy_config,batch_queue,ssd_net,summaries,batch_shape,FLAGS)
        # summarties:摘要
        summaries = set(tf.get_collection(tf.GraphKeys.SUMMARIES))

        # batch_shape:解析这个batch_queue的大小,指的是获取的一个默认队列大小,指上面r的大小

        batch_shape = [1] + 3 * [len(ssd_anchors)]

        update_ops, first_clone_scope, clones = train_tools.deploy_loss_summary(deploy_config,
                                                                                batch_queue,
                                                                                ssd_net,
                                                                                summaries,
                                                                                batch_shape,
                                                                                FLAGS)

        # 4、定义学习率以及优化器
        # 学习率:0.001
        # 终止学习率:0.0001
        # 优化器选择:adam优化器
        # learning_rate = tf_utils.configure_learning_rate(FLAGS, num_samples, global_step)
        # FLAGS:将会用到学习率设置相关参数
        # global_step: 全局步数
        # optimizer = tf_utils.configure_optimizer(FLAGS, learning_rate)
        # learning_rate: 学习率
        with tf.device(deploy_config.optimizer_device()):

            # 定义学习率和优化器
            learning_rate = train_tools.configure_learning_rate(FLAGS,
                                                                dataset.num_samples,
                                                                global_step)
            optimizer = train_tools.configure_optimizer(FLAGS, learning_rate)

            # 观察学习率的变化情况,添加到summaries
            summaries.add(tf.summary.scalar('learning_rate', learning_rate))

        # 5、计算所有设备的平均损失以及每个变量的梯度总和
        train_op, summaries_op = train_tools.get_trainop(optimizer,
                                                         summaries,
                                                         clones,
                                                         global_step,
                                                         first_clone_scope, update_ops)


        # 配置config以及saver

        gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.8)   #占用GPU80%
        config = tf.ConfigProto(log_device_placement=False,    # 若果打印会有许多变量的设备信息出现
                                gpu_options=gpu_options)
        # saver
        saver = tf.train.Saver(max_to_keep=5,  # 默认保留最近几个模型文件
                               keep_checkpoint_every_n_hours=1.0,
                               write_version=2,
                               pad_step_number=False)

        # 6、进行训练
        slim.learning.train(
            train_op,  # 训练优化器tensor
            logdir=FLAGS.train_model_dir,  # 模型存储目录
            master='',
            is_chief=True,
            init_fn=train_tools.get_init_fn(FLAGS),  # 初始化参数的逻辑,预训练模型的读取和微调模型判断
            summary_op=summaries_op,  # 摘要
            number_of_steps=FLAGS.max_number_of_steps,  # 最大步数
            log_every_n_steps=10,  # 打印频率
            save_summaries_secs=60,  # 保存摘要频率
            saver=saver,  # 保存模型参数
            save_interval_secs=600,  # 保存模型间隔
            session_config=config,  # 会话参数配置
            sync_optimizer=None)





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

3.3 训练结果显示

  • 在项目根目录下执行以下指令:
PRE_TRAINED_PATH=./ckpt/pre_trained/ssd_300_vgg.ckpt
TRAIN_MODEL_DIR=./ckpt/fine_tuning/
DATASET_DIR=./IMAGE/tfrecords/commodity_tfrecords/
python train_ssd_network.py --train_model_dir=${TRAIN_MODEL_DIR} --dataset_dir=${DATASET_DIR} --dataset_name="commodity_2018" --train_or_test=train --model_name=ssd_vgg_300 --pre_trained_path=${PRE_TRAINED_PATH} --weight_decay=0.0005 --optimizer=adam --learning_rate=0.001 --batch_size=2
  • 输出结果:
# =========================================================================== #
# SSD 网络参数:
# =========================================================================== #
{'anchor_offset': 0.5,
 'anchor_ratios': [[2, 0.5],
                   [2, 0.5, 3, 0.3333333333333333],
                   [2, 0.5, 3, 0.3333333333333333],
                   [2, 0.5, 3, 0.3333333333333333],
                   [2, 0.5],
                   [2, 0.5]],
 'anchor_size_bounds': [0.15, 0.9],
 'anchor_sizes': [(21.0, 45.0),
                  (45.0, 99.0),
                  (99.0, 153.0),
                  (153.0, 207.0),
                  (207.0, 261.0),
                  (261.0, 315.0)],
 'anchor_steps': [8, 16, 32, 64, 100, 300],
 'feat_layers': ['block4', 'block7', 'block8', 'block9', 'block10', 'block11'],
 'feat_shapes': [(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)],
 'img_shape': (300, 300),
 'no_annotation_label': 21,
 'normalizations': [20, -1, -1, -1, -1, -1],
 'num_classes': 9,
 'prior_scaling': [0.1, 0.1, 0.2, 0.2]}

# =========================================================================== #
# 训练数据dataset files:
# =========================================================================== #
['./IMAGE/tfrecords/commodity_tfrecords/commodity_2018_train_000.tfrecord']

Tensor("fifo_queue_Dequeue:0", shape=(2, 300, 300, 3), dtype=float32)
INFO:tensorflow:global_step/sec: 0
INFO:tensorflow:Recording summary at step 0.
INFO:tensorflow:global step 10: loss = 38.3997 (0.086 sec/step)
INFO:tensorflow:global step 20: loss = 155.8330 (0.087 sec/step)
INFO:tensorflow:global step 30: loss = 231.1888 (0.086 sec/step)
INFO:tensorflow:global step 40: loss = 62.8824 (0.087 sec/step)
.......
  • 在ckpt/fine_tuning中生成的模型文件
    在这里插入图片描述
  • 训练过程:
    进入到ckpt/fine_tuning文件夹:
tensorboard --logdir="./"

在这里插入图片描述
观察loss:
在这里插入图片描述

3.4 训练流程总结

在这里插入图片描述

4. 测试流程

4.1代码

  • 测试数据准备
  • preprocess(数据预处理)
  • 模型加载
  • postprocess(预测结果后续处理)
    • 通过scores筛选bbox.
    • 通过nms算法筛选bbox.
    • 注意bbox边界与原始图片bbox,按需修改bbox
  • 预测结果显示(matplotlib)
import numpy as np
import tensorflow as tf
from PIL import Image
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import visualization
# 需要调用处理模块,我们运行以上级目录运行调用的包的名字可以不变
import sys
sys.path.append('../')
from utils.basic_tools import np_methods
slim = tf.contrib.slim
# 需要用到预处理工厂,模型工厂
from nets import nets_factory
from preprocessing import preprocessing_factory
# 使用feed_dict 与 placeholder的形式,运行时输入数据
# 1、定义输入图片数据的占位符
image_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
# 定义一个输出的形状,元组表示
net_shape = (300, 300)
data_format = "NHWC"
# 2、数据输入到预处理工厂当中,进行处理得到结果
preprocessing_fn = preprocessing_factory.get_preprocessing("ssd_vgg_300", is_training=False)

img_pre, _, _, bbox_img = preprocessing_fn(image_input, None, None, net_shape, data_format)
# img_pre是三维形状,(300, 300, 3)
# 卷积神经网络要求都是四维的数据计算(1, 300, 300, 3)
# 维度的扩充
image_4d = tf.expand_dims(img_pre, 0)
# 3、定义SSD模型, 并输出预测结果
# reuse作用:在notebook当中运行,第一次创建新的变量为FALSE,但是重复运行cell,保留这些变量的命名,选择重用这些命名,已经存在内存当中了
# 没有消除,设置reuse=True
reuse = True if 'ssd_net' in locals() else False
# 网络工厂获取
ssd_class = nets_factory.get_network("ssd_vgg_300")
# 网络类当中参数,类别总数(商品数据集8+1)?
ssd_params = ssd_class.default_params._replace(num_classes=9)
# 初始化网络
ssd_net = ssd_class(ssd_params)
#获取每一层的default boxes
ssd_anchors = ssd_net.anchors(net_shape)
# 通过网络的方法获取结果
# 使用slim指定共有参数data_format,net函数里面有很多函数需要使用data_format

with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
   predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)
# 4、定义交互式会话,初始化变量,加载模型
config = tf.ConfigProto(log_device_placement=False)
sess = tf.InteractiveSession(config=config)
#初始化变量
sess.run(tf.global_variables_initializer())

# 名字步数
ckpt_filepath = "../ckpt/fine_tuning/model.ckpt-0"

# 创建saver
saver = tf.train.Saver()
saver.restore(sess, ckpt_filepath)
INFO:tensorflow:Restoring parameters from ../ckpt/fine_tuning/model.ckpt-0
# 会话运行图片,输出结果
# 读取一张图片
img = Image.open("../IMAGE/commodity/JPEGImages/000035.jpg").convert('RGB')

# 走一个数组转换
img = np.array(img)

i, p, l, box_img = sess.run([image_4d, predictions, localisations, bbox_img], feed_dict={image_input:img})
# 进行结果的筛选了,排序、NMS
# 通过 predictions 与 select_threshold 筛选bbox
classes, scores, bboxes = np_methods.ssd_bboxes_select(
   p, l, ssd_anchors,
   select_threshold=0.1, img_shape=(300, 300), num_classes=9, decode=True)
# bbox边框不能超过原图片 默认原图的相对于bbox大小比例 [0, 0, 1, 1]
bboxes = np_methods.bboxes_clip(box_img, bboxes)
# 根据 scores 从大到小排序,并改变classes rbboxes的顺序
classes, scores, bboxes = np_methods.bboxes_sort(classes, scores, bboxes, top_k=400)
# 使用nms算法筛选bbox
classes, scores, bboxes = np_methods.bboxes_nms(classes, scores, bboxes, nms_threshold=.45)
# 根据原始图片的bbox,修改所有bbox的范围 [.0, .0, .1, .1]
bboxes = np_methods.bboxes_resize(box_img, bboxes)
visualization.plt_bboxes(img, classes, scores, bboxes)

输出结果:
在这里插入图片描述

  • 将select_threshold=0.1改为0.4,过滤掉小框,检测不出来,因为这个模型只训练了5分钟.
    在这里插入图片描述

4.2 总结

  • 测试
    • 定义数据输入的占位符tf.placeholder
    • 进行预处理:preprocessing_factory.get_preprocessing(“ssd_vgg_300”, is_training=False)
    • 网络定义输出结果
      • ssd_net.net(image_4d, is_training=False, reuse=reuse)
    • 定义交互式会话,初始化变量,加载模型
    • 输入图片,sess.run,运行结果
      • 过滤:固定np_methods方法去进行过滤
      • np_methods.ssd_bboxes_select
      • np_methods.bboxes_clip
      • np_methods.bboxes_sort
      • np_methods.bboxes_nms
      • np_methods.bboxes_resize

5.Web服务与模型部署

在这里插入图片描述

5.1 安装Tensorflow Serving

docker pull tensorflow/serving

这样想当于安装好了serving服务,建议在服务器上安装,后续开启模型服务,并进行操作后,能被外网访问.
如果要使用Serving服务,需要我们将模型上传到服务其中,才能继续开启,接下来进行模型导出.
在这里插入图片描述

5.1 模型导出

  • export_serving_model.py
import os
import tensorflow as tf
slim = tf.contrib.slim
import sys
sys.path.append("../")

from nets.nets_model import ssd_vgg_300

data_format = "NHWC"

ckpt_filepath = "../ckpt/fine_tuning/model.ckpt-0"


def main(_):

   # 1、定义好完整的模型图,去定义输入输出结果
   # 输入:SSD 模型要求的数据(不是预处理的输入)
   img_input = tf.placeholder(tf.float32, shape=(300, 300, 3))

   # [300,300,3]--->[1,300,300,3]
   img_4d = tf.expand_dims(img_input, 0)

   # 输出:SSD 模型的输出结果
   ssd_class = ssd_vgg_300.SSDNet

   ssd_params = ssd_class.default_params._replace(num_classes=9)

   ssd_net = ssd_class(ssd_params)

   # 得出模型输出
   with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
       predictions, localisations, _, _ = ssd_net.net(img_4d, is_training=False)

   # 开启会话,加载最后保存的模型文件是的模型预测效果达到最好
   with tf.Session() as sess:
       sess.run(tf.global_variables_initializer())

       # 创建saver
       saver = tf.train.Saver()

       # 加载模型
       saver.restore(sess, ckpt_filepath)

       # 2、导出模型过程
       # 路径+模型名字:"./model/commodity/"
       export_path = os.path.join(
           tf.compat.as_bytes("./model/commodity/"),
           tf.compat.as_bytes(str(1)))

       print("正在导出模型到 %s" % export_path)

       # 建立builder
       builder = tf.saved_model.builder.SavedModelBuilder(export_path)

       # 通过该函数建立签名映射(协议)
       # tf.saved_model.utils.build_tensor_info(img_input):填入的参数必须是一个Tensor
       prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(
           inputs={
               # 给输入数据起一个别名,用在客户端读取的时候需要指定
               "images": tf.saved_model.utils.build_tensor_info(img_input)
           },
           outputs={
               'predict0': tf.saved_model.utils.build_tensor_info(predictions[0]),
               'predict1': tf.saved_model.utils.build_tensor_info(predictions[1]),
               'predict2': tf.saved_model.utils.build_tensor_info(predictions[2]),
               'predict3': tf.saved_model.utils.build_tensor_info(predictions[3]),
               'predict4': tf.saved_model.utils.build_tensor_info(predictions[4]),
               'predict5': tf.saved_model.utils.build_tensor_info(predictions[5]),
               'local0': tf.saved_model.utils.build_tensor_info(localisations[0]),
               'local1': tf.saved_model.utils.build_tensor_info(localisations[1]),
               'local2': tf.saved_model.utils.build_tensor_info(localisations[2]),
               'local3': tf.saved_model.utils.build_tensor_info(localisations[3]),
               'local4': tf.saved_model.utils.build_tensor_info(localisations[4]),
               'local5': tf.saved_model.utils.build_tensor_info(localisations[5]),
           },
           method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME,
       )

       # 建立元图格式,写入文件
       builder.add_meta_graph_and_variables(
           sess, [tf.saved_model.tag_constants.SERVING],
           signature_def_map={
               'detected_model':
                   prediction_signature,
           },
           main_op=tf.tables_initializer(),
           strip_default_attrs=True)

       # 保存
       builder.save()

       print("Serving模型结构导出结束")


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

导出目录:
在这里插入图片描述

  • saved_model.pb:是序列化的tensorflow::SavedModel,它包括模型的一个或多个图形定义以及模型的元数据(如签名)
  • variables:是包含图形的序列化文件
    将导出的模型上传服务器等固定文件夹即可.
  • 运行结果:
/home/yuyang/anaconda3/envs/tensor1-6/bin/python3.5 /media/yuyang/Yinux/online_class_V5.0/test/export_serving_model.py
2019-06-06 15:05:42.998243: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA
2019-06-06 15:05:43.091183: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:897] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-06-06 15:05:43.091738: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1405] Found device 0 with properties: 
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.62
pciBusID: 0000:01:00.0
totalMemory: 10.91GiB freeMemory: 10.30GiB
2019-06-06 15:05:43.091749: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1484] Adding visible gpu devices: 0
2019-06-06 15:05:43.282062: I tensorflow/core/common_runtime/gpu/gpu_device.cc:965] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-06-06 15:05:43.282089: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971]      0 
2019-06-06 15:05:43.282094: I tensorflow/core/common_runtime/gpu/gpu_device.cc:984] 0:   N 
2019-06-06 15:05:43.282251: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1097] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 9955 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1080 Ti, pci bus id: 0000:01:00.0, compute capability: 6.1)
正在导出模型到 b'./model/commodity/1'
Serving模型结构导出结束

Process finished with exit code 0

5.2 开启模型服务

  • Docker运行的介绍:
    在这里插入图片描述

  • 使用Docker开启Serving服务,运行服务器

docker run -p 8500:8500 -p 8501:8501 --mount type=bind,source=/media/yuyang/Yinux/commodity_detect_proj/model_detector/test/model/commodity,target=/models/commodity/ -e MODEL_NAME=commodity -t tensorflow/serving

在这里插入图片描述

5.3 Web Server + Tensorflow Serving Client

  • 了解Flask与Client的逻辑对接
  • 用用Tensorflow Serving完成客户端的流程编写
    模型的服务开启后,通过gRPC和REST两种接口方式.我们选择gRPC + protobufs接口,进行获取,主要考虑的是性能.
  • 将Web环境与Tensorflow环境放在一起.使得Web服务器托管Tensorflow服务客户端
    • 需要是用tendorflow_serving.apis编写一个获取结果的客户端,也还是属于Tensorflow的程序.

5.3.1 环境安装

在这里插入图片描述

docker build -t tf-web .

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 数据模块接口->数据集名称commodity_2018

    • commodity_2018_train,commodity_2018_test

    • 读取图片和XML数据

      • (1)把之前的类别进行修改,数据集名字commodity_2018_train

      • 读取数据集

    • (2)读取数据集

      • 设计一个读取数据的基类,

      • class TFRecordsReaderBase(object):
            """数据集基类
            """
            def __init__(self, param):
                # param给不同数据集的属性配置
                self.param = param
        
            def get_data(self, train_or_test, dataset_dir):
                """
                获取数据规范
                :param train_or_test: train  or test数据文件
                :param dataset_dir: 数据集目录
                :return:
                """
                return None
        
      • 继承基类,处理不同数据集类

        • 建立一个dataset_init目录,实现不同数据集读取逻辑

        • CommodityTFRecords(dataset_utils.TFRecordsReaderBase):

        • param:命名字典去定义数据结构

        • 'FILE_PATTERN',
                                                           'NUM_CLASSES',
                                                           'SPLITS_TO_SIZES',
                                                           'ITEMS_TO_DESCRIPTIONS'
          
      • 创建一个数据工厂作用的文件

        • 去调用不同数据集名称的逻辑
    • nets目录,nets_factory文件,获取不同模型

  • 预处理模块

    • 目的:对数据集进行数据增强, 丰富图像训练集,更好的提取图像特征,泛化模型(防止模型过拟合
    • 给图片的大小进行调整到算法需求的大小
    • 数据增强
      • 通过一系列的操作来增加数据集的大小
      • 为什么?
        • 数据集太少!!!!!!!
        • 神经网络会寻找最为明显的特征对数据集来讲,如果数据少,代表性就很弱
          • 例子?解决方式:
            • 1.去多拍几张(收集)每个车型向左向右的图片
            • 2.Jin性数据集的变换操作,增加更多的数据集了
      • 数据增强类型
        • 在数据输入模型之前去做处理
        • 离线增强:直接做变化之后,保存在本地,物理上增加数据集的大小
        • 在线增强:不是物理增加,输入模型之前给每张图片执行一些随机的变换
    • 数据预处理过程
      • 提供一个preprocessing_factory给训练使用
  • 多GPU训练

      • 数据读取
      • preprocess(数据预处理)
      • 网络构建预测结果
      • 损失计算
      • 添加变量到TensorBoard
      • 模型训练、保存
    • model_deploy

      • clone:指的是每个GPU,每个GPU会得到一个完整的复制模型,主要进行计算

      • DeploymentConfig为文件中的一个类,主要用于给保存变量配置选择的设备
        • 指定设备环境,计算数量,GPU数量,哪些作为参数服务器,哪些作为工作服务器
        • model_deploy.
          • create_clones:定义给每个GPU复制一个模型
          • optimize_clones:定义每个模型运算优化的操作设备
    • 训练逻辑代码

      • 相关配置:

        • 输出模型文件夹,预训练模型以及微调模型文件夹。
        • 确定网络训练时候的一些相关参数:学习率等通过一些经验值指定
      • 步骤编写1:

        • 1、配置集群环境数量介绍,创建一个全局步长变量

        • 2、

          获取图片数据,做一些处理
          # 图片有什么?image, shape, bbox, label
          # image会做一些数据增强,大小变换
          # 直接训练?需要将anchor bbox进行样本标记正负样本,目的使的GT目标样本的数量与default bboxes数量一致
          
          
          • 将每个模块结果先获取

          • dataset_factory获取数据集描述信息, nets_factory获取anchors box进行正负样本标记

          • 指定deploy_config.inputs_device()去通过provider获取单个图片的信息

          • 进行数据的处理,image, glabels, gbboxes

          • 做目标值的样本标记处理,ssd_net.bboxes_encode

          • 批处理以及队列设置

        • 3、定义模型计算结果,损失复制到不同设备clone,观察默认一个clone变量情况(GPU设备进行)

          • train_tools.deploy_loss_summary(deploy_config,batch_queue,ssd_net,summaries,batch_shape,FLAGS)
            
            tf.app.flags.DEFINE_float(
                'weight_decay', 0.00004, '网络误差函数惩罚项值,越小越防止过拟合.')
            
        • 4、定义学习率优化器(在默认的CPU设备进行)

          • tf.device(deploy_config.optimizer_device()):

          • train_tools.configure_learning_rate

          • train_tools.configure_optimizer(FLAGS, learning_rate)

          • 学习率先关参数的添加

          • tf.app.flags.DEFINE_string(
                'optimizer', 'rmsprop', '优化器种类 可选"adadelta", "adagrad", "adam","ftrl", "momentum", "sgd" or "rmsprop".')
            tf.app.flags.DEFINE_string(
                'learning_rate_decay_type', 'exponential','学习率迭代种类  "fixed", "exponential", "polynomial"')
            tf.app.flags.DEFINE_float(
                'learning_rate', 0.01, '模型初始学习率')
            tf.app.flags.DEFINE_float(
                'end_learning_rate', 0.0001, '模型训练迭代后的终止学习率')
            
        • 5、进行不同所有模型的损失平均值计算,然后变量的总梯度,

        • train_tools.get_trainop(optimizer,
                                                           summaries,
                                                           clones,
                                                           global_step,
                                                           first_clone_scope, update_ops)
          
        • 6、设置配置、saver去进行训练

        • tf.app.flags.DEFINE_integer(
              'max_number_of_steps', None, '训练的最大步数')
          tf.app.flags.DEFINE_string(
              'train_model_dir', ' ', '训练输出的模型目录')
          # pre-trained模型路径.
          tf.app.flags.DEFINE_string(
              'pre_trained_model', None, '用于fine-tune的已训练好的基础参数文件位置')
          
          • log_every_n_steps=10,  # 打印频率
            save_summaries_secs=60,  # 保存摘要频率
            saver=saver,  # 保存模型参数
            save_interval_secs=600,  # 保存模型间隔
            session_config=config,  # 会话参数配置
            init_fn=train_tools.get_init_fn(FLAGS)
            
    • 测试

      • 定义数据输入的占位符tf.placeholder
      • 进行预处理:preprocessing_factory.get_preprocessing(“ssd_vgg_300”, is_training=False)
      • 网络定义输出结果
        • ssd_net.net(image_4d, is_training=False, reuse=reuse)
      • 定义交互式会话,初始化变量,加载模型
      • 输入图片,sess.run,运行结果
        • 过滤:固定np_methods方法去进行过滤
        • np_methods.ssd_bboxes_select
        • np_methods.bboxes_clip
        • np_methods.bboxes_sort
        • np_methods.bboxes_nms
        • np_methods.bboxes_resize
    • TensorFlow serving

      • 导出模型
        • 模型:用户–>图片—>预处理—>输入—>输出—>后期处理标记—>用户
        • SSD模型输入输出就是我们要导出编程的接口输入输出
        • 模型图定义:
          • img_input = tf.placeholder(tf.float32, shape=(300, 300, 3))
          • predictions, localisations, _, _ = ssd_net.net(img_4d, is_training=False)
        • 加载最新的模型ckpt文件
        • 导出模型
          • 路径+模型名字, 版本号
          • 建立Builder: builder = tf.saved_model.builder.SavedModelBuilder(export_path)
          • 建立元图格式,写入文件:builder.add_meta_graph_and_variables()
            • signature_def_map:签名协议
              • tf.saved_model.signature_def_utils.build_signature_def(inputs={}.outputs={},method_name)
      • 开启服务
        • 安装好docker, docker pull tensorflow/serving
        • 8500:grpc
        • 8501:rest
        • docker run -p 8500:8500 -p 8501:8501 --mount type=bind,source=/Users/huxinghui/workspace/ml/online_class/online_class_V5.0/test/model/commodity,target=/models/commodity/ -e MODEL_NAME=commodity -t tensorflow/serving
      • 客户端获取结果
        • 1、获取用户,处理格式,预处理过程
        • 2、发送请求获取结果
          • grpc建立连接
          • serving建立通道,通道发送请求
            • 封装一个请求
            • 请求的模型名称
            • 模型的签名
            • 图片数据
          • 获取结果
  • 服务器的部署

    • 两个服务
    • tensorflow serving服务:提供模型服务
    • web服务:serving客户端内嵌在web服务当中
    • 模型文件、webcode,web以及client代码
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值