tensorflow数据读取之tfrecords

掌握一个深度学习框架的用法,从训练一个模型的流程来看,需要掌握以下几个步骤:
1. 数据的处理,包括训练数据转成网络的输入,模型参数的存储与读取
2. 网络结构的定义,包括网络主体的搭建以及loss的定义
3. solver的定义,也就是如何对网络进行优化
4. 模型评估的定义,也就是对模型训练结果进行评测

这篇博文主要介绍第一部分,数据处理中的训练数据读取部分
tensorflow当中读取数据的方式一共有三种:
1. 供给数据(feeding):在定义graph的时候,可以用placeholder来表示其中的某些节点,在运行的时候,通过向这些节点填入数据来运行整个graph
2. 预加载数据:仅仅适用于可以完全加载到存储器中的小的数据集
3. 从文件中加载数据:在图运行的时候,通过一个pipeline从文件中读取数据,作为网络的输入

前面两种方法并不适用于平时的大数据集处理的情况,因此这里我只介绍第三种方法:从文件中加载数据,重点介绍通过tensorflow特有的tfrecords进行存储和读取的方法

1.tfrecords

tfrecords是一种二进制文件,我们可以实现把image文件夹里面的图片和label文件夹里面的标签读进来制作成tfrecords文件,之后就都使用tfrecords进行数据的读取,能够更好的利用内存,更方便复制和移动,类似caffe里面的hdf5,下面我们将详细介绍如果制作tfrecords文件,以及如何从tfrecords文件里面读取数据

2.tfrecords文件的制作

每一个训练样例在tfrecords里面叫一个example,tensorflow使用名为tf.train.Example的协议来存储训练样例,一个example就类似一个词典(dict),它的key名为feature,每一个feature对应的值是tensorflow预定义好的Feature message(必须要是ByteList,FloatList以及Int64List中的一种数据类型),这样的话一个训练样例就可以表示为很多键-值对的组合。

example通过SerializeToString()方法将样例序列化成字符串存储,tensorflow通过TFRecordWriter将这些序列化之后的字符串存成tfrecord形式

下面表示一个制作tfrecords的例子

    #image_set可以选择'训练'或者'测试',images表示所有的图片数据,labels表示所有的标签,包括训练和测试
    def convert(self, image_set, images, labels):
        filename = os.path.join(self._image_set_path, 'dianli_{:s}.tfrecords'.format(image_set))
        print('writing ', filename)

    #创建一个tfrecords的writer
        writer = tf.python_io.TFRecordWriter(filename)
        if image_set == 'train':
            names = self.train_list
        elif image_set == 'test':
            names = self.test_list
        else:
            raise ValueError

    #max_object表示一张图最多有多少个object,设置这个参数是为了保证写入时所有label项的形状都是固定的
        max_object_num = cfg.TRAIN.MAX_OBJECT

        for ix, name in enumerate(names):
            image_raw = images[ix].tostring()
            single_label = labels[ix]
            if single_label.shape[0] > max_object_num:
                raise ValueError('number of gt object is {:d}, more than max object num!'.format(single_label.shape[0]))

    #加0确保label维度固定,这样后面就可以使用tf.train.shuffle_batch
            non_obj_label = np.zeros((max_object_num - single_label.shape[0], 6))
            comp_label = np.vstack((single_label, non_obj_label))

    #这里用(cls,xc,yc,xita,w,h)表示一个斜框标签,cls表示框里面目标的类别
            cls = comp_label[:, 0].tolist()
            xc = comp_label[:, 1].tolist()
            yc = comp_label[:, 2].tolist()
            xita = comp_label[:, 3].tolist()
            w = comp_label[:, 4].tolist()
            h = comp_label[:, 5].tolist()

    #image_name和image序列化成bytelist,其他的按照本来的数据类型选择序列化成float或者int64
            example = tf.train.Example(features=tf.train.Features(feature={
                'image_name': self._bytes_feature(names[ix].encode('utf8')),
                'height': self._int64_feature(images[ix].shape[0]),
                'width': self._int64_feature(images[ix].shape[1]),
                'depth': self._int64_feature(images[ix].shape[2]),
                'xc': self._float_feature(xc),
                'yc': self._float_feature(yc),
                'xita': self._float_feature(xita),
                'w': self._float_feature(w),
                'h': self._float_feature(h),
                'cls': self._int64_feature([int(c) for c in cls]),
                'image_raw': self._bytes_feature(image_raw)
            }))

    #存储一个example
            writer.write(example.SerializeToString())

        writer.close()
        print('finish writing ', filename)

为了验证我们的tfrecords文件是否写入正确,我们可以写一个检测的测试程序,读取里面的example看看

    #使用tf_record_iterator来遍历每一个example
    with tf.Session() as sess:
        example = tf.train.Example()

    #train_record表示训练的tfrecords文件的路径
        record_iterator = tf.python_io.tf_record_iterator(path=train_record)
        for record in record_iterator:
            example.ParseFromString(record)
            f = example.features.feature

    #解析一个example
            image_name = f['image_name'].bytes_list.value[0]
            image_raw = f['image_raw'].bytes_list.value[0]
            xc = np.array(f['xc'].float_list.value)[:, np.newaxis]
            yc = np.array(f['yc'].float_list.value)[:, np.newaxis]
            xita = np.array(f['xita'].float_list.value)[:, np.newaxis]
            w = np.array(f['w'].float_list.value)[:, np.newaxis]
            h = np.array(f['h'].float_list.value)[:, np.newaxis]
            label = np.hstack((xc, yc, xita, w, h))

    #将label画在image上,同时打印image_name,查看三者是不是对应上的
            print(image_name.encode('utf-8'))
            img_1d = np.fromstring(image_raw, dtype=np.uint8)
            img_2d = img_1d.reshape((480, 480, -1))
            draw_bboxes(img_2d, label)

3.tfrecords文件的解析和读取

我们其实可以直接用上面测试的程序来一个一个读取tfrecords文件里面的example,作为网络的输入,但是这样一来,网络训练一个batch的时间就包括从tfrecords里面读取一个batch数据的时间,以及这个batch做前向反向的时间,显然数据读取的时间是可以避免的。

最好的情况是,网络每次做完一次前向反向之后,不需要等待数据的读取,也就是说我们可以开两个线程,一个线程在进行网络的前向反向计算,另外一个线程专门进行数据的读取,这样的话两个线程都不会存在所谓的等待状态。

下面将分别介绍tfrecords文件的解析以及如何高效得进行数据的读取

解析部分代码如下所示:

    #filename_queue表示一个文件名队列,后面会讲到,比如我需要解析train.tfrecords文件的话,传入的就应该是训练的文件名队列
    def read_and_decode(self, filename_queue):
        """
        read and decode
        """

    #创建一个tfrecords文件读取器并读取文件
        reader = tf.TFRecordReader()
        _, serialized_example = reader.read(filename_queue)

    #tensorflow使用parse_single_example来解析example
        features = tf.parse_single_example(
            serialized_example,
            features={

    #对于单个元素的变量,我们使用FixlenFeature来读取,需要指明变量存储的数据类型
                # meta data
                'image_name': tf.FixedLenFeature([], tf.string),
                'height': tf.FixedLenFeature([], tf.int64),
                'width': tf.FixedLenFeature([], tf.int64),
                'depth': tf.FixedLenFeature([], tf.int64),

    #对于list类型的变量,我们使用VarLenFeature来读取,同样需要指明读取变量的类型
                # label part
                'xc': tf.VarLenFeature(tf.float32),
                'yc': tf.VarLenFeature(tf.float32),
                'xita': tf.VarLenFeature(tf.float32),
                'w': tf.VarLenFeature(tf.float32),
                'h': tf.VarLenFeature(tf.float32),
                'cls': tf.VarLenFeature(tf.int64),

                # dense data
                'image_raw': tf.FixedLenFeature([], tf.string),
            }
        )

        # meta data
        image_name = tf.cast(features['image_name'], tf.string)
        height = tf.cast(features['height'], tf.int32)
        width = tf.cast(features['width'], tf.int32)
        depth = tf.cast(features['depth'], tf.int32)

    #VarLenFeature得到的是sparse tensor需要转换一下
        # label part
        xc = tf.sparse_tensor_to_dense(features['xc'])
        yc = tf.sparse_tensor_to_dense(features['yc'])
        xita = tf.sparse_tensor_to_dense(features['xita'])
        w = tf.sparse_tensor_to_dense(features['w'])
        h = tf.sparse_tensor_to_dense(features['h'])
        cls = tf.sparse_tensor_to_dense(features['cls'])

        xc = tf.reshape(xc, shape=[cfg.TRAIN.MAX_OBJECT, 1])
        yc = tf.reshape(yc, shape=[cfg.TRAIN.MAX_OBJECT, 1])
        xita = tf.reshape(xita, shape=[cfg.TRAIN.MAX_OBJECT, 1])
        w = tf.reshape(w, shape=[cfg.TRAIN.MAX_OBJECT, 1])
        h = tf.reshape(h, shape=[cfg.TRAIN.MAX_OBJECT, 1])
        cls = tf.reshape(cls, shape=[cfg.TRAIN.MAX_OBJECT, 1])

        cls = tf.cast(cls, tf.float32)
        label = tf.concat(values=[cls, xc, yc, xita, w, h], axis=1)

    #set_shape是为了固定label的维度,方便后面使用tf.train.shuffle_batch
        label.set_shape([cfg.TRAIN.MAX_OBJECT, 6])

    #对于图像数据需要使用decode_raw解码,同样也需要set_shape
        # dense data
        image = tf.decode_raw(features['image_raw'], tf.uint8)
        image_shape = tf.stack([height, width, 3])
        image = tf.reshape(image, image_shape)
        image.set_shape([cfg.TRAIN.IMG_SIZE[0], cfg.TRAIN.IMG_SIZE[1], 3])

        return image, label

下面再详细介绍一下高效读取的方法
前面说了,最高效的读取方法应该是一个线程专门读取数据,一个线程专门做训练(前向反向传播),读取数据的线程应该维护一个队列(queue),不断得读取数据,压入队列,tensorflow里面常用的是FIFO queue。训练的线程每次从这个队列里面读取一个batch的训练样例用来训练。

tensorflow里面用的方法比上面的方法要稍微复杂一点,区别在于其维护了两个队列,第一个队列存的是训练样例的文件名,第二个队列存的才是真正的训练样例。整个读取的流程如下图所示:

我们可以使用tf.string_input_produce创建上述的文件名队列(filename queue),在创建的时候,我们可以指定参数num_epoch的值,这个值显然是控制文件名队列里的文件名一共用来训练几个epoch,然后我们可以通过上面read_and_decode函数读取文件名队列的文件名,进行解析,将解析得到的训练样例压入训练样例队列(example queue)。最后我们进行训练的时候每次可以从训练样例队列里面读取一个batch,可以使用tf.train.shuffle_batch来获取一个随机打乱顺序的batch,代码样例如下:

def inputs(dataset_, image_set, batch_size, num_epochs):
    """
    read batch of data fro tf-record files for epoch
    :param dataset_: constructed dataset
    :param image_set:train or test
    :param batch_size:
    :param num_epochs:
    :return:images and labels for a single batch
    """
    train_record = os.path.join(dataset_.image_set_path, 'dianli_train.tfrecords')
    test_record = os.path.join(dataset_.image_set_path, 'dianli_test.tfrecords')
    if not (os.path.exists(train_record) and os.path.exists(test_record)):
        dataset_.create_tfrecord()

    filename = train_record if image_set == 'train' else test_record
    filename_queue = tf.train.string_input_producer([filename], num_epochs=num_epochs)

    image, label = dataset_.read_and_decode(filename_queue)

    #num_thread可以选择用几个线程同时读取example queue,min_after_dequeue表示读取一次之后队列至少需要剩下的样例数目,capacity表示队列的容量
    images, sparse_labels = tf.train.shuffle_batch([image, label], batch_size=batch_size, num_threads=1, capacity=1000 + 3 * batch_size, min_after_dequeue=1000)
    return images, sparse_labels

if __name__ == '__main__':
    image_path = '/home/user/myDocuments/tf-yolo/data/image/'
    label_path = '/home/user/myDocuments/tf-yolo/data/annotation/'
    image_set_path = '/home/user/myDocuments/tf-yolo/data/'

    d = Dianli(image_path, label_path, image_set_path)
    image, label = inputs(d, 'train', batch_size=1, num_epochs=2)
    # de_file = inputs(d, 'train', batch_size=1, num_epochs=1)

    init_op = tf.group(tf.global_variables_initializer(), tf.local_variables_initializer())

    sess = tf.Session()
    sess.run(init_op)

    #要让程序运行起来,一定要先start_queue_runners向队列里面填入数据
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)

    try:
        step = 0
        while not coord.should_stop():

            images, labels = sess.run([image, label])
            images = np.squeeze(images, axis=0)
            labels = np.squeeze(labels, axis=0)

            ind = np.where(np.sum(labels, axis=1) != 0)[0]
            labels = labels[ind, 1:]

            draw_bboxes(images, labels)

    except tf.errors.OutOfRangeError:
        print('done')
    finally:
        coord.request_stop()

    #join表示等待各个线程关闭
    coord.join(threads)
    sess.close()

在上面的例子当中,我们使用了tf.Coordinator来协调各个线程,各个线程正常情况下完成各自的任务,如果出现异常则向coordinator报告异常(should_stop==1),这个时候coordinator会将所有的线程关闭(request_stop==1)

注意在训练开始之前,一定要调用start_queue_runners来开启各个队列的线程,否则队列的内容一直为空,训练的进程会一直挂着无法运行

以上就是整个从文件数据制作tfrecords,再到tfrecords读取以及训练样例的输入的整个过程

你可能感兴趣的:(Tensorflow)