tensorflow多线程批量读取文件

tensorflow多线程批量读取数据

总所周知,在深度学习中,tensorflow是非常好用的一个框架,也是比较常用的一个框架,而我这篇博客主要是讲述如何用tensorflow多线程批量读取数据。

在我们使用算法来预测或者分类数据时,都会使用大量的数据来训练模型,而这些数据往往都是使用文件来保存的。而我们在机器学习中往往会遇到数据量太大,读取数据的速度太慢了,这种普通的读取数据的方式会严重影响了我们训练模型的效率。所以tensorflow就推出了多线程读取数据的API,大大提高了我们训练模型的效率。

多线程读取csv文件数据

首先我们得知道这个多线程的流程是怎们样的。

流程步骤

首先是把要读取文件都放在一个列表当中,然后把文件列表存入队列当中,然后再用阅读器把队列里面的文件一个一个读取出来,这里读取数据默认是一条一条数据读取的,然后把读取的数据进行解码,解码后的数据再放入一个新的队列当中去,然后我们就可以从队列里面取出数据来训练了。

这里就实现了异步的过程,主线程是从队列里面读取数据,子线程就是从文件里面读取数据然后存入队列当中,两个线程互不干扰。

把文件放入队列当中

file_queue = tf.train.string_input_producer(["I:\\crack\\DATA\\submissions_metad\\train.csv"])

这里我只放了一个文件,也可以放多个文件,注意要把文件放入列表中,这里面还有几个参数
string_input_producer(string_tensor,
num_epochs=None,
shuffle=True,
seed=None,
capacity=32,
shared_name=None,
name=None,
cancel_op=None)
num_epochs:一个整数(可选)。如果指定,string_input_producer在产生OutOfRange错误之前从string_tensor中产生num_epochs次字符串。如果未指定,则可以无限次循环遍历字符串。

shuffle:布尔值。如果为true,则在每个epoch内随机打乱顺序。

seed:一个整数(可选)。如果shuffle==True,则使用种子。

capacity:一个整数。设置队列容量。

shared_name:(可选的)。如果设置,则此队列将在多个会话的给定名称下共享。对具有此队列的设备打开的所有会话都可以通过shared_name访问它。在分布式设置中使用它意味着只有能够访问此操作的其中一个会话才能看到每个名称。

name:此操作的名称(可选)。

cancel_op:取消队列的操作(可选)。

构造阅读器,读取数据

reader = tf.TextLineReader(skip_header_lines = 1)
key,value = reader.read(file_queue)

skip_header_lines参数是表示从第几行开始读取,因为我们通常csv文件的第一行是字段名,所以我们就要从第二行开始读取。

TextLineReader对像里面有read的这个方法,read里面传递的参数是文件的队列,而用这个方法读取的数据返回两个对象,key代表文件名,value代表读取的值,这里默认只是读取一行数据。

解码数据

record = [[0.],[0.],[0.],[0.],[0.],[0.]]
values = tuple()
values = tf.decode_csv(value,record_defaults=record)

这里的解码数据有一个非常重要的参数record_defaults,这个参数就是指定你每列数据的数据类型,如果当你的数据为空时就会自动补全。这里我用record = [[0.],[0.],[0.],[0.],[0.],[0.]]来补全数据,这里一定要是二维数组。

然后这里返回的是解码后的每个值,所以我用元组来接收,或者列表来接收都可以。

批量读取数据

values_batch = tuple()
values_batch = tf.train.batch(values,batch_size = 100,num_threads=1,capacity = 100000)

batch_size代表数据到达多少个就读取出来,num_threads代表线程数,capacity代表队列长度。

这里就是把一条条数据放入队列当中,然后队列的数量到达指定的数量,就读取出来。

这里一般都会出现重复值,因为数据读到最后的时候,如果队列最后的数据到达不了指定的数据量,就会从队列头重新读取数据,但是重复的数据对我们在训练的时候是没有多大的影响的。

开启会话读取数据

with tf.Session() as sess:
    #开启线程协调器
    coord = tf.train.Coordinator()

    #开启子线程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印数据,用循环不断地读取数据
    for i in range(100000):
        print(sess.run(values_batch))

    #回收资源
    coord.request_stop()
    coord.join(threads)

这里面一定要开启线程协调器调节子线程和回收资源。
这里最主要的是开启子线程
threads = tf.train.start_queue_runners(sess,coord=coord,start=True)
start是代表在开启子线程的默认运行子线程,如果为False就又得手动运行子线程。

在会话这里可能有些疑惑,为什么前面有些op操作没有在会话里面运行,首先有关于队列和线程都在开启子线程的时候已经运行了,而其他那些操作也是属于子线程里面的操作,这个有点像python的函数调用,一环扣一环。

源码

import tensorflow as tf
import numpy as np
from time import time

'''
多线程读取csv数据的流程
1、首先把文件放入一个队列当中
2、然后开始读取数据,默认只读取一列数据
3、解码,因为读取的数据可以有编码在里面
4、把读取的数据再放入一个新的队列当中去,然后进行下面的处理
'''

#构造文件队列,注意传入的文件一定要是列表
file_queue = tf.train.string_input_producer(["I:\\crack\\DATA\\submissions_metad\\train.csv"])

#构造阅读器,默认按行读取,skip_header_lines指定从第几行开始读取
reader = tf.TextLineReader(skip_header_lines = 1)

#开始阅读,用阅读器阅读,返回两个值,一个是key就是文件名,另外那个是value是读取的值
key,value = reader.read(file_queue)

#对内容进行解码,用的方法是decode_csv,首先里面有一个参数是record_defaults,这个是可以用来指定读取的每一列的数据类型,和默认值,如果读取的值是空值的话就会默认填入默认值,解码返回的值是每一列一列的,所以也可以用元组对象来接收
record = [["None"],["None"],["None"],["None"],["None"],["None"]]
values = tuple()
values = tf.decode_csv(value,record_defaults=record)

#把数据放入一个队列当中,用batch方法,里面有batch_size就是一次性批处理多少个数据,num_threads子线程的数量,capacity队列大小,同样返回的是批量的数据,最好的方式用元组接收
values_batch = tuple
values_batch = tf.train.batch(values,batch_size = 100,num_threads=3,capacity = 100000)

start = time()
#先开启会话读取数据
with tf.Session() as sess:
    #开启线程协调器
    coord = tf.train.Coordinator()

    #开启子线程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印数据,用循环不断地读取数据
    for i in range(100000):
        print(sess.run(values_batch))

    #回收资源
    coord.request_stop()
    coord.join(threads)
end = time()
print(end - start)

批量读取图片文件

图片也是有特征值和目标值的,图片的特征值就是像素点,我们都知道像素点是又长和宽和通道数相关的,而我们如果要对图片进行学习的话,就要保证特征值的数量是一样的,所以我们都要将图片缩放成一样大小

步骤

其实图片批量读取的步骤和csv文件是基本一样的,只是它在每一块的处理方式不一样。
1、建立文件队列
2、读取数据,默认是按照一张一张图片读取的
3、解码
4、批量读取

建立文件队列

用os来建立文件列表先

file_list = os.listdir(r"I:\crack\图片读取")
file_list = [os.path.join("I:\crack\图片读取",file) for file in file_list]

再建立文件队列

file_queue = tf.train.string_input_producer([file_list])

构造阅读器,阅读文件

read = tf.WholeFileReader()
key, value = read.read(file_queue)

解码

这里解码比csv文件解码方式简单地多

image = tf.image.decode_jpeg(value)

然后在将图片进行统一缩放

image_resize = tf.image.resize_images(image,size=[1020,1500])

到这一步我们可以先打印缩放后的图片对象,可以发现这里的形状是不确定的
Tensor(“resize/Squeeze:0”, shape=(1020, 1500, ?), dtype=float32)

所以我们要确定它的形状是怎么样的,用静态修改形状也可以,动态也可以,因为我们这里的形状是固定的,所以就用静态的方式来修改形状

image_resize.set_shape([1020,1500,3])

开启会话运行

with tf.Session() as sess:
    #开启线程协调器
    coord = tf.train.Coordinator()

    #开启子线程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印数据,就是每个像素点的值
    # print(sess.run(images))
    print(sess.run(image_resize))

    #回收资源
    coord.request_stop()
    coord.join(threads)

源码

import tensorflow as tf
import os

'''
图片的特征值就三个,长、宽、通道数,黑白图片就有一个通道数,彩色图片有三个通道数,
多线程读取图片和csv文件是一样的,但是所用的API不同
'''

#构造图片文件列表
file_list = os.listdir(r"I:\crack\图片读取")
file_list = [os.path.join("I:\crack\图片读取",file) for file in file_list]

#构造文件列表队列
file_queue = tf.train.string_input_producer(file_list)

#构造阅读器,默认是按照一张一张图片读取的
read = tf.WholeFileReader()
key, value = read.read(file_queue)

#对图片进行解码,这里解码就比csv文件解码简单地多
image = tf.image.decode_jpeg(value)

#因为对图片进行处理要求长和宽是一致的才行,所以要对图片进行缩放
image_resize = tf.image.resize_images(image,size=[1020,1500])

#因为现在的形状还是不固定的,通道数还是问号,所以要对形状进行修改,修改成三个通道数,可以用前面的静态修改或者动态修改都可以
image_resize.set_shape([1020,1500,3])

#然后就可以对图片进行批处理了,这里的图片信息一定要用列表存储
images = tf.train.batch([image_resize],batch_size=15,num_threads=1,capacity=1000)

#然后开始会话运行
with tf.Session() as sess:
    #开启线程协调器
    coord = tf.train.Coordinator()

    #开启子线程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印数据,就是每个像素点的值
    # print(sess.run(images))
    print(sess.run(image_resize))

    #回收资源
    coord.request_stop()
    coord.join(threads)

多线程读取二进制文件

我们有些文件是以二进制文件的方式存储的,比如图片分类预测就是用二进制文件存储的,下面我们就使用一个比赛数据来读取,网址路径如下:
(https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz)

建立文件队列

file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]

#构造文件列表队列
file_queue = tf.train.string_input_producer(file_list)

构造阅读器

read = tf.FixedLengthRecordReader(3073)

key,value = read.read(file_queue)

在阅读器里面传递的参数,就是每个样本的字节数,因为我们的样本图片是[32,32,3]的三阶数据,所以字节数就是三个相乘为3072,因为这个文件数据里面还有分类的数据在里面,所以就是3703个字节数

解码

这里的二进制文件解码前,还有很多前奏,首先是转换数据类型,一般图片数据就是三阶的数组,所以我们要把数据转换为uint8

image_label = tf.decode_raw(value,tf.uint8)

因为数据里面包含了特征值和目标值,所以要把特征值和目标值给切割出来

image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])

顺便把目标值转换成int32

label = tf.cast(label,dtype=tf.int32)

因为现在的特征值是一阶的数据,为了方便图片数据的运算,所以我们要把数据转换成三阶的,这里要改变形状阶数,所以要用动态修改方式

image = tf.reshape(image,[32,32,3])

批处理

images,labels = tf.train.batch([image,label],batch_size=10,num_threads=1,capacity=1000)

开启会话运行

with tf.Session() as sess:
    #开启线程协调器
    coord = tf.train.Coordinator()

    #开启子线程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印数据,就是每个像素点的值
    print(sess.run([images,labels]))

    #回收资源
    coord.request_stop()
    coord.join(threads)

源码

import tensorflow as tf
import os

'''
二进制批量读取的流程跟csv和图片读取的流程差不多相同,只是有些地方有差异
'''

#先建立文件列表
file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]

#构造文件列表队列
file_queue = tf.train.string_input_producer(file_list)

#构造阅读器,这里要注意阅读器默认是读取每个文件的byte数目,所以我们要提前知道每个文件的的byte数量,这里是图片数目,所以像素数量就是byte数量,加上目标值的数目
read = tf.FixedLengthRecordReader(3073)

key,value = read.read(file_queue)

#解码,这里解码要加上转换成什么类型数据,一般都是转换成uint8类型,这里返回的是一个一阶张量数据,里面包含目标值和特征值
image_label = tf.decode_raw(value,tf.uint8)

#因为这里的数据包含了图片的特征值和目标值,所以要将这两个数据给分割出来了,用tf.slice方法分割
image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])
#顺便把目标值的数据类型给转换一下
label = tf.cast(label,dtype=tf.int32)

#因为图片数据一般都是三阶的数据,所以要把形状给改变一下
image = tf.reshape(image,[32,32,3])

#然后进行批处理
images,labels = tf.train.batch([image,label],batch_size=10,num_threads=1,capacity=1000)

#开启会话进行读取
with tf.Session() as sess:
    #开启线程协调器
    coord = tf.train.Coordinator()

    #开启子线程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印数据,就是每个像素点的值
    print(sess.run([images,labels]))

    #回收资源
    coord.request_stop()
    coord.join(threads)

tfrecords的存储与读取

tfrecords是tesorflow这个框架单独开发的一种文件格式,这个文件是一个类字典的格式文件,这个文件比较小,也非常方便读取和移动,但是存储的时候就偏麻烦一点。

存储步骤

1、读取二进制文件数据
2、构造tfrecords存储器
3、把每一个样本的数据转换成example协议块
4、把每一个example协议快存储进tfrecords存储器中

读取二进制文件

这里的读取方式跟上面的是一样的

file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]
file_queue = tf.train.string_input_producer(file_list)
read = tf.FixedLengthRecordReader(3073)
key,value = read.read(file_queue)
image_label = tf.decode_raw(value,tf.uint8)
image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])
label = tf.cast(label,dtype=tf.int32)
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=50000,num_threads=10,capacity=100000)

构造tfrecords存储器

write = tf.python_io.TFRecordWriter(r"I:\crack\tfrecords\cifar.tfrecords")

这里面的参数要把文件路径传入进去,后缀名一定要是tfrecords

把数据转换成example协议块

    for i in range(10):
        image = images[i].eval().tostring() 
        label = labels[i].eval()[0]  
        example = tf.train.Example(features=tf.train.Features(feature={
            "image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
            "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
        }))

因为我们已经知道了读取多少个二进制文件,所以循环的次数也就知道了,如果我们不知道读取了多少个二进制文件,就可以用形状或者其他的方式来获取数目。

这里我们用image = images[i].eval().tostring()
label = labels[i].eval()[0] 来提取每个数据的特征值和目标值,因为特征值在转换的过程中要转换成bytes类型,所以事先要先转换成string类型,这个eval()获取值的方式要在会话中才可以运行,所以这整个流程都要放在一个函数里面,然后再会话里面运行。

example = tf.train.Example(features=tf.train.Features(feature={
“image”: tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
“label”: tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
}))这个转换的过程大部分都是固定写法的。
只有键值对里面又不同的写法,但是也只是又三种写法
bytes_list = tf.train.BytesList(value = [])
int64_list = tf.train.Int64List(value = [])
float_list = tf.train_FloatList(value = [])

写入和关闭存储器

write.write(example.SerializeToString())
write.close()

因为不能把协议直接写入文件当中,所以要把example转换成序列化格式才可以

源码

file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]
file_queue = tf.train.string_input_producer(file_list)
read = tf.FixedLengthRecordReader(3073)
key,value = read.read(file_queue)
image_label = tf.decode_raw(value,tf.uint8)
image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])
label = tf.cast(label,dtype=tf.int32)
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=50000,num_threads=10,capacity=100000)

#把二进制文件存入tfrecords
def writer(images,labels):

    #构造tfrecords存储器,在里面要写入tfrecords的文件目录
    write = tf.python_io.TFRecordWriter(r"I:\crack\tfrecords\cifar.tfrecords")
    # 将二进制文件每条数据转换成example协议,这里我们要用for循环写入,这里知道了是10张图片,如果不知道,就得用形状或者其他的方式来获取数码
    for i in range(10):
        # 把每条数据提取出来,用切片的方式获取,我们是要获取它的值而不是获取它的对象,所以这一步骤要再会话里面运行
        image = images[i].eval().tostring()  # 因为在转换的时候一般都是把特征值转换为bytes类型数据,所以要把特征值转换成字符串
        label = labels[i].eval()[0]  # 这里要把目标值提取出来

        '''
        转换成example,下面大部分都是固定写法。
        就键值对那里又三种写法,bytes_list = tf.train.BytesList(value = [])
        int64_list = tf.train.Int64List(value = [])
        float_list = tf.train_FloatList(value = [])
        '''
        example = tf.train.Example(features=tf.train.Features(feature={
            "image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
            "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
        }))

        # 写入tfrecords存储器中,这里不能直接把example写入存储器,要将example转换成序列化格式才能写入
        write.write(example.SerializeToString())

    # 关闭存储器
    write.close()

#这里要注意,也要运行上面二进制文件的读取
with tf.Session() as sess:
    coord = tf.train.Coordinator()
    # 开启子线程
    threads = tf.train.start_queue_runners(sess, coord=coord, start=True)
    # 打印数据,就是每个像素点的值
    print(sess.run([images, labels]))

    writer(images,labels)
    # 回收资源
    coord.request_stop()
    coord.join(threads)

批量读取tfrecords文件

在上面我们已经把tfrecords文件保存好了,所以我们就来读取这个文件,步骤都差不多,但是多了一个解析的过程,因为我们存储的时候是example序列化格式的数据,所以需要把它解析出来。

构造文件队列

file_queue = tf.train.input_producer(["I:\\crack\\tfrecords\\cifar.tfrecords"])

构造阅读器

reader = tf.TFRecordReader()
key,value = reader.read(file_queue)

解析数据

feature = tf.parse_single_example(value,features={
    "image":tf.FixedLenFeature([],tf.string),
    "label":tf.FixedLenFeature([],tf.int64)
})

这里要指定形状,但是一般都不会指定形状给它,这里也要指定数据类型,指定的类型与我们存储时写入的类型一样既可。

解析过后就可以直接用字典的方式提取出数据既可,这里就是非常方便了。

解码,批量读取

这里的解码基本给二进制文件数据的解析方式差不多一样

image = tf.decode_raw(feature["image"],tf.uint8)
label = feature["label"]
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=10000,num_threads=1,capacity=100000)

源码

file_queue = tf.train.input_producer(["I:\\crack\\tfrecords\\cifar.tfrecords"])

reader = tf.TFRecordReader()
key,value = reader.read(file_queue)

#解析序列化的example,这里面都要指定形状,但是一般都是不指定的
feature = tf.parse_single_example(value,features={
    "image":tf.FixedLenFeature([],tf.string),
    "label":tf.FixedLenFeature([],tf.int64)
})

#解码,除了要把特征值bytes解码成uint8之外,所有的步骤都和二进制解码步骤一样
image = tf.decode_raw(feature["image"],tf.uint8)
label = feature["label"]
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=10000,num_threads=1,capacity=100000)

with tf.Session() as sess:
    # 开启线程协调器
    coord = tf.train.Coordinator()

    # 开启子线程
    threads = tf.train.start_queue_runners(sess, coord=coord, start=True)

    # 打印数据,就是每个像素点的值
    print(sess.run([images, labels]))

    # 回收资源
    coord.request_stop()
    coord.join(threads)

这里我们可以发现tfrecords文件的存储时非常小的,所以非常方便移动,而且读取的时候也比二进制的文件方便许多,但是存储的时候就比较麻烦一点了。

如果有不懂的地方,欢迎添加我的QQ 1693490575,一起讨论进步。

发布了28 篇原创文章 · 获赞 14 · 访问量 6786

猜你喜欢

转载自blog.csdn.net/weixin_42304193/article/details/100528330