Tensorflow数据读取

读取机制

Tensorflow中数据读取机制可见下图

关于这张图,这篇文章已经介绍的非常详细,简而言之,Tensorflow为了不让数据读取成为代码的事件瓶颈,用了两个队列来进行文件的读取:

  1. 文件队列,通过tf.train.string_input_producer()函数来创建,文件名队列不包含文件的具体内容,只是在队列中记录所有的文件名,所以可以在这个函数中对文件设置多个epoch,并对其进行shuffle。这个函数只是创建一个文件队列,并指定入队的操作由几个线程同时完成。真正的读取文件名内容是从执行了tf.train.start_queue_runners()开始的,start_queue_runners返回一个op,一旦执行这个op,文件名队列就开始被填充了。
  2. 内存队列,这个队列不需要用户手动创建,有了文件名队列后,start_queue_runners之后,Tensorflow会自己维护内存队列并保证用户时时有数据可读。
    典型的代码如下:
import tensorflow as tf 

# 新建一个Session
with tf.Session() as sess:
    # 我们要读三幅图片A.jpg, B.jpg, C.jpg
    filename = ['A.jpg', 'B.jpg', 'C.jpg']
    # string_input_producer会产生一个文件名队列
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=5)
    # reader从文件名队列中读数据。对应的方法是reader.read
    reader = tf.WholeFileReader()
    key, value = reader.read(filename_queue)
    # tf.train.string_input_producer定义了一个epoch变量,要对它进行初始化
    tf.local_variables_initializer().run()
    # 使用start_queue_runners之后,才会开始填充队列
    threads = tf.train.start_queue_runners(sess=sess)
    i = 0
    while True:
        i += 1
        # 获取图片数据并保存
        image_data = sess.run(value)
        with open('read/test_%d.jpg' % i, 'wb') as f:
            f.write(image_data)

注意string_input_producer()中的shuffle是文件级别的,如果要读取的文件是TFRecord文件,一个文件中就包含几千甚至更多条数据,那么这里的shuffle和我们平时训练数据时说的shuffle还是不一样的。

TODO: 把读取出的数据组成batch的代码

slim数据读取接口

用slim读取数据分为以下几步:

  1. 给出数据来源的文件名并据此建立slim.Dataset,逻辑上Dataset中是含有所有数据的,当然物理上并非如此。
  2. 根据slim.Dataset建立一个DatasetDataProvider,这个class提供接口可以让你从Dataset中一条一条的去取数据
  3. 通过DatasetDataProvider的get接口拿到获取数据的op,并对数据进行必要的预处理(如有)
  4. 利用从provider中get到的数据建立batch,此处可以对数据进行shuffle,确定batch_size等等
  5. 利用分好的batch建立一个prefetch_queue
  6. prefetch_queue中有一个dequeue的op,没执行一次dequeue则返回一个batch的数据。

下面我们通过代码来一一介绍具体如何使用。
1.建立slim.Dataset
根据官方文档,slim.Dataset包含data_sources,reader,decoder,num_samples,descriptions五个部分,其中data_sources是一系列文件名,代表组成数据集全体的文件名;reader,针对文件的类型,选择合适的reader;decoder,一个解释器,用于将文件中存储的数据转换为Tensor类型;num_samples,指明数据集中一共含有多少条数据;descriptions可以添加一些对于数据的额外备注和说明,非必须。下面是一段典型的建立Dataset的代码,假设我们的数据由多个TFRecord文件组成,每个TFRecord存储若干数据,在TFRecord中,每条数据都是一个TFExample类型:

def get_split(split_name, dataset_dir, file_pattern, num_samples, reader=None):
    dataset_dir = util.io.get_absolute_path(dataset_dir)
    
    if util.str.contains(file_pattern, '%'):
        # 处理有多个文件的情况,file_pattern是文件名list
        file_pattern = util.io.join_path(dataset_dir, file_pattern % split_name)
    else:
        file_pattern = util.io.join_path(dataset_dir, file_pattern)
    # Allowing None in the signature so that dataset_factory can use the default.
    if reader is None:
        reader = tf.TFRecordReader
    keys_to_features = {
        'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),
        'image/format': tf.FixedLenFeature((), tf.string, default_value='jpeg'),
        'image/filename': tf.FixedLenFeature((), tf.string, default_value=''),
        'image/shape': tf.FixedLenFeature([3], tf.int64),
        'image/object/bbox/label': int64_feature(labels),
    }
    items_to_handlers = {
        'image': slim.tfexample_decoder.Image('image/encoded', 'image/format'),
        'shape': slim.tfexample_decoder.Tensor('image/shape'),
        'filename': slim.tfexample_decoder.Tensor('image/filename'),
        'object/label': slim.tfexample_decoder.Tensor('image/object/bbox/label')
    }
    # slim.Decoder可以给两个参数,两个都是dict,第一个参数指定要如何解析每个Example,第二个参数可以把读取出的数据进一步简单处理或者组合成需要的数据
    decoder = slim.tfexample_decoder.TFExampleDecoder(keys_to_features, items_to_handlers)

    items_to_descriptions = {
        'image': 'A color image of varying height and width.',
        'shape': 'Shape of the image',
        'object/label': 'A list of labels, one per each object.',
    }
    ## 建立并返回一个Dataset
    return slim.dataset.Dataset(
            data_sources=file_pattern,
            reader=reader,
            decoder=decoder,
            num_samples=num_samples,
            items_to_descriptions=items_to_descriptions,
            num_classes=2,
            labels_to_names=labels_to_names)

2. 建立DatasetDataProvider

# 下面用到的dataset就是我们上面建立的slim.dataset.Dataset,num_readers是指定线程数目,即如果后续
# 要多线程读数据的话,最多可以有5个的get可以被同时调用来填充数据。capacity是provider自己维护的
# 队列的大小,get操作相当于dequeue操作,enqueue操作由provider自己完成
provider = slim.dataset_data_provider.DatasetDataProvider(dataset, num_readers=5, \
                common_queue_capacity=10, common_queue_min=1, shuffle=True)
# 每调用一次get,得到一条数据。同样,这里的get得到的依然是一个Tensor的op,不是一个实实在在的张量
[image, shape, label] = provider.get(['image', 'shape', 'object/label'])

3. 必要的预处理

# 此处可以做一些预处理,数据就一条,没有第一维的batch维度
[image, shape, label] = preprocess(image, shape, label)

4. 建立batch
根据官方文档,train.batch是维护有自己的队列的,所以它也可以开多个线程从provider中获取数据,num_threads就是这个意思,capacity自然就是队列大小。

# 官方还有tf.train.shuffle_batch等接口,提供shuffle数据等功能
b_image, b_label = tf.train.batch([image, label], batch_size=32, num_threads=4, capacity=200)

5. 建立prefetch_queue

batch_queue = slim.prefetch_queue.prefetch_queue([b_image, b_label], capacity = 20) 

其实这个地方我有一个不解,既然第四步已经将数据都分好的batch放进了队列,理论上只要执行batch返回的的op就可以直接得到数据,为了还要再包一层队列,产生一个batch_queue呢?根据官方的解释,prefetch_queue的作用是把batch后的数据聚合到一起(assemble),保证用户在读取数据时不需要再花时间assemble。
看来Tensorflow早就想到了这个,并且外面再包一层也是有道理的,但是我本人理解batch后的数据就是assemble之后的,不知道它的batch操作是怎么样的等研究过代码再说吧。(TODO)

6. 运行dequeue的op获取数据

b_images, b_labels = batch_queue.dequeue()
with tf.Sesstion() as sess:
    images, labels = sess.run(images, labels)
    print(images)
    print(labels)

tf.data.Dataset接口

slim提供的数据读取接口其实也不够简洁,看看生一部分的六个步骤就知道过程还有有些繁琐的,想要熟练运用,不了解一些Tensorflow的实现是有点难的。但是tf.data.Dataset则不然,他隐藏了所有Tensorflow处理数据流的细节,用户只需要几步简单的操作就可以轻松读到数据,这使得数据读取更加容易上手且写出的代码更加简洁、易懂。tf.data.Dataset的介绍将会在另外一篇文章中讲解。

参考文献

  1. 十图详解Tensorflow数据读取机制——知乎
  2. Tensorflow数据处理——CSDN
  3. Tensorflow中的README文档
  4. Tensorflow官网
  5. PixelLink源码
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容