TensorFlow的tfrecords文件详解
进入正文
当前是数据爆炸的时代,深度学习与大数据更是相辅相成,在使用TensorFlow构建深度学习模型的时候,可能会涉及到海量的数据,可能会用到数G、T甚至P级别的训练数据,很显然,要将如此庞大的数据一次性加载进内存,显然当前的硬件条件还远远不能够。幸好TensorFlow也提供了非常有好的大数据处理方式。
TF数据读取的方式数据读取方式
1.1
对于深度学习而言,因为数据量庞大,在提高运算能力的同时,更高效的处理数据I/O对于提高整体的性能也非常重要。在使用TensorFlow训练模型的时候,有三种数据加载的方式
(1)使用Python代码为TensorFlow提供数据
(2)预先加载数据,将需要训练的数据以变量的形式预先保存在计算机的内存中
(3)利用管道从文件中读取数据
对于数据较小的情况,直接将数据加载到计算机内存,然后每次取一个batch放进网络里面加以训练,问题,但是对于大数据而言,一方面如果直接全部将数据放进内存肯定不可能;另一方面,我可以每次需要多少数据就从硬盘中读取,但是这样做的后果就是频繁的I/O操作,使得执行效率大打折扣。
1.2
对于比较小的数据,我们可以直接加载进内存,对于这种级别的数量,常用的一些数据格式有以下几种:
CSV格式;
npy npz格式:这是numpy的数据保存格式
pkl: 这是python的序列化保存格式
hdf: 以HDF5为最新的系列
1.3
对于大数据而言,TensorFlow推荐使用自带的tfrcords文件。tfrecords文件是以二进制进行存储的,适合以串行的方式读取大批量的数据。
对于训练数据而言,我们可以编写程序将普通的训练数据保存为tfrecords数据格式。
本节总结
本节主要介绍TensorFlow常用的一些数据格式
2.1
tfrecords的创建很简单,就是将每一组“样本数据”组装成一个Example对象,这个对象是遵循protocol buffer协议的;然后将这个Example对象序列化成字符串;最后用tf.python_io.TFRecordWriter写入相应的tfrecords文件即可。大致步骤如下:
第一步:获取原始数据,一般使用numpy或者是pandas进行一些处理
第二步:使用tf.python_io.TFRecordWriter类定义一个tfrecords文件
第三步:将每一条样本数据按照相应的特征组织好,即将样本数据组织成Example的过程,这是整个操作流程的核心部分,相对较复杂
第四步:将组织好的Example写入进tfrecords文件,并关闭tfrecords文件即可
下面以titanic数据为例加以说明:因为titanic数据是一个CSV文件,里面有不少空余的,本文只选择前面的50条数据,并且已经填充了空格(数据的预处理)
2.2
import tensorflow as tf
import pandas as pd
#第一步:获取原始数据
data=pd.read_csv('Titanic dataset/titanic_train_01.csv')
print(data.shape)
#第二步:定义record文件
tfrecord_file='titanic_train.tfrecords'
writer=tf.python_io.TFRecordWriter(tfrecord_file)
#第三步:每一次写入一条样本记录
for i in range(len(data)):
features=tf.train.Features(feature={'Age':tf.train.Feature(float_list=tf.train.FloatList(value=[data['age'][i]])),
'Sex':tf.train.Feature(int64_list=tf.train.Int64List(value=[1 if data['sex'][i]=='male' else 0])),
'Pclass':tf.train.Feature(int64_list=tf.train.Int64List(value=[data['pclass'][i]])),
'Parch':tf.train.Feature(int64_list=tf.train.Int64List(value=[data['parch'][i]])),
'Sibsp':tf.train.Feature(int64_list=tf.train.Int64List(value=[data['sibsp'][i]])),
'Fare':tf.train.Feature(float_list=tf.train.FloatList(value=[data['fare'][i]])),
'Survived':tf.train.Feature(int64_list=tf.train.Int64List(value=[data['survived'][i]]))
})
#每一条样本的特征,将一系列特征组织成一条样本
example=tf.train.Example(features=features)
#将每一条样本写入到tfrecord文件
writer.write(example.SerializeToString())
#第四步:写入后关闭文件
writer.close()
print('写入tfrecords文件完毕!')
核心函数解析:
(1)Features()
features=tf.train.Features(feature={*****})
该函数传入一个关键字参数feature,表示的是一系列的特征。
(2)Fearture()
'Age':tf.train.Feature(float_list=tf.train.FloatList(value=[data['age'][i]]))
该函数是对应于一系列特征中的每一个特征,它有三个可选的关键字参数,float_list、int64_list、byteslist分别对应于取值为浮点数的特征、整数的特征、二进制数的特征。
(3)FloatList()、Int64List()、BytesList()
float_list=tf.train.FloatList(value=[data['age'][i]]))
这三个函数是将每个特征进行转化的函数,分别对应特征的取值为浮点数、整数、二进制数。这里有一个注意事项,这三个函数都有一个命名参数value,这个参数的的赋值一定要使用value=【data】的方式,这里的中括号不能丢哦!
(4)Example()
example=tf.train.Example(features=features)
这个函数就是核心,是将上面组织好的一系列特征进行包装,包装成一个Example对象,然后将该对象写入到tfrecords文件,关闭该文件即可。
Example补充:
使用TFRecord时,一般以tf.train.Example和tf.train.SequenceExample作为基本单位来进行数据读取。
(1)Example()
example=tf.train.Example(features=features)
tf.train.Example一般用于数值、图像等有固定大小的数据,同时使用tf.train.Feature指定每个记录各特征的名称和数据类型,用法如下:
tf.train.Example(features=tf.train.Features(feature={
'height': tf.train.Feature(int64_list=tf.train.Int64List(value=[height])),
'width' : tf.train.Feature(int64_list=tf.train.Int64List(value=[width])),
'depth' : tf.train.Feature(int64_list=tf.train.Int64List(value=[depth])),
'image' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[image]))
}))
(2)SequenceExample()
tf.train.SequenceExample一般用于文本、时间序列等没有固定长度大小的数据,用法如下:
example = tf.train.SequenceExample()
# 通过context来指定数据量的大小
example.context.feature["length"].int64_list.value.append(len(data))
# 通过feature_lists来加载数据
words_list = example.feature_lists.feature_list["words"]
for word in words:
words_list.feature.add().int64_list.value.append(word_id(word))
2.3
上面是对titanic的CSV文件进行的操作,那如果是图像数据呢,难道是对图像的每一个像素进行Feature的转换吗?那么当一个图片很大的时候,像素很多,这样显然不合理,对于图像的操作这里就关键用到了BytesList去实现,下面以图像为例加以说明
本文没有选择很多的图片,仅仅以三张图片为例,在百度图片下载任意三张图片,因为大小不一样,使用Photoshop将三张图片简单更改为500x500大小。
import tensorflow as tf
import pandas as pd
from PIL import Image
import numpy as np
#第一步:获取原始数据,此处为原始图像
img1=Image.open('picture dataset\img1.jpg')
img2=Image.open('picture dataset\img2.jpg')
img3=Image.open('picture dataset\img3.jpg')
images=[img1,img2,img3]
print(img1.size,img2.size,img3.size)
label1=np.array([1,0,0])
label2=np.array([0,1,0])
label3=np.array([0,0,1])
labels=[label1,label2,label3]
#第二步:定义record文件
tfrecord_file='picture_train.tfrecords'
writer=tf.python_io.TFRecordWriter(tfrecord_file)
#第三步:每一次写入一条样本记录
for i in range(len(images)):
features=tf.train.Features(feature={'Picture':tf.train.Feature(bytes_list=tf.train.BytesList(value=[images[i].tobytes()])),
'Label':tf.train.Feature(bytes_list=tf.train.BytesList(value=[labels[i].tobytes()]))
})
#每一条样本的特征,将一系列特征组织成一条样本
example=tf.train.Example(features=features)
#将每一条样本写入到tfrecord文件
writer.write(example.SerializeToString())
writer.close()
print('写入tfrecords文件完毕!')
运行上面的代码,已经有一个2198KB大小的picture_train.tfrecords文件。上面的代码可以看出,对于图像数据,规则基本上是一模一样的,区别在于数据的初始化处理,另外,图片数据不是一个一个像素进行存取的,需要将图片以及独热编码的标签转化为原生的bytes数据格式即可。
2.4
通过前面两个方法,我们知道可以把你想要的文件或者记录通过或多或少的方法转为TFRecord格式.
那么数据量很大的时候,你会发现,单个TFRecord文件是非常非常大的,这对于硬盘是不小的负担,所以,可以通过存储多个TFRecord文件来解决问题.其实保存为多个tfrecords文件并没有新的操作,完全和上面一样,只不过因为数据量巨大,需要对样本进行划分,然后分别保存在不同的tfrecords文件里面即可
比如一共有30000张图片,即30000个样本,前面10000个保存在picture_01.tfrecords文件里,中间10000个样本保存在picture_02.tfrecords文件里,最后10000组样本保存在picture_03.tfrecords文件里。
2.3.1 通过配置文件
matplotlibrc是matplotlib resource configurations的简称。matplotlib的图形配置方式有很多,主要是从以下三个方面进行配置的。
(1)通过配置文件进行配置——查看+设置
(2)通过rcParams['参数名']动态配置——查 看+设置
(3)通过matplotlib.rc()函数配置
本节总结
从上面的几个例子可以看出,创建tfrecords文件的步骤是比较简单的,按照固定的格式组织数据,然后写入进tfrecords文件即可,数据是分层组织的,可以有外向内一次看成,Examples—>Example—>Features—>Feature(int64、float、bytes)
Next
3.1
我们可以简单的查看一下我们所保存的tfrecords文件是否符合我们的预期,我们可以使用tf.train.Example.FromString()进行简单的查看,代码如下:
import tensorflow as tf
#确认tfrecord的内容
ex=next(tf.python_io.tf_record_iterator('titanic_train.tfrecords'))
print(tf.train.Example.FromString(ex))
上面程序的运行结果如下:
features {
feature {
key: "Age"
value {
float_list {
value: 30.0
}
}
}
feature {
key: "Fare"
value {
float_list {
value: 7.73330020904541
}
}
}
feature {
key: "Parch"
value {
int64_list {
value: 0
}
}
}
feature {
key: "Pclass"
value {
int64_list {
value: 3
}
}
}
feature {
key: "Sex"
value {
int64_list {
value: 0
}
}
}
feature {
key: "Sibsp"
value {
int64_list {
value: 0
}
}
}
feature {
key: "Survived"
value {
int64_list {
value: 1
}
}
}
}
从上面返回的结果可以查看到保存的特征,特征的数据类型,第一组样本的特征取值。
3.2
tfrecords文件的读取和加载是相对比较复杂的,本文也总结了几个固定的步骤:
第一步:定义一个reader对象,和定义tfrecords文件从哪里来。
filename_queue=tf.train.string_input_producer(['titanic_train.tfrecords'])
reader = tf.TFRecordReader()
后面会解析这两句话的含义
第二步:从tfrecords文件中解析保存的样本数据格式
第三步:从样本数据中一次性读取一个批次的数据,即填充满一个batch。因为在深度学习进行训练的时候,往往都是一次训练多少组,以多少组为一个batch,所以需要包装。
上面三个步骤的核心函数解析:
第一步:
filename_queue=tf.train.string_input_producer(['titanic_train.tfrecords'])
它告诉我们tfrecords文件从哪里来,注意,参数里面的中括号不能丢!
reader = tf.TFRecordReader()
定义一个reader对象,该对象负责从tfrecords文件中读取。
_,serialized_example=reader.read(filename_queue)
它返回的是(key,value)的元祖形式。上面的serialized_example是无法直接查看的,需要去按照特征进行解析。
第二步:解析数据
featurestf.parse_single_example(serialized_example,features={...})
将数据的特征解析出来
第三步:每次将数据包装成一个batch。
tf.train.batch([age,sex,pclass,parch,sibsp,fare,label],
batch_size=16,
capacity=500)
第一个参数就是特征的名称,中括号不能掉,第二个是batch_size的大小,这个capacity后面会解释到。
但是上面的步骤完成之后,我还只能够看到每一个特征的维度信息,还不能够获取具体的数值,要想获取具体的数值,依然需要在会话对象Session里面进行查看,而且步骤分为以下几步(续接前面):
第四步:首先在session里面创建Coordinator对象,他负责实现数据输入线程的同步,实现如下
coord = tf.train.Coordinator()
第五步:启动队列
threads=tf.train.start_queue_runners(sess=sess, coord)
第六步:这里就可以查看样本数据,将获取的样本数据“喂”给网络进行训练。
第七步:线程同步
coord.request_stop()
coord.join(threads=threads)
3.3
按照前面的步骤,代码如下:
import tensorflow as tf
#第一步:定义reader对象以及tfrecords文件的输入部分
filename_queue = tf.train.string_input_producer(['titanic_train.tfrecords'])
reader = tf.TFRecordReader()
#第二步:使用reader函数读入tfrecords内容,它返回的是(key,value)
_, serialized_example = reader.read(filename_queue)
#print(serialized_example.shape)
#第三步:数据的解析parse
features = tf.parse_single_example(serialized_example,
features={'Age':tf.FixedLenFeature([],tf.float32),
'Sex':tf.FixedLenFeature([],tf.int64),
'Pclass':tf.FixedLenFeature([],tf.int64),
'Parch':tf.FixedLenFeature([],tf.int64),
'Sibsp':tf.FixedLenFeature([],tf.int64),
'Fare':tf.FixedLenFeature([],tf.float32),
'Survived':tf.FixedLenFeature([],tf.int64)
})
age=features['Age']
sex=features['Sex']
pclass=features['Pclass']
parch=features['Parch']
sibsp=features['Sibsp']
fare=features['Fare']
label=features['Survived']
#image = tf.reshape(image, [28, 28, 1])
#label = tf.reshape(label, [10])
#第三步:将样本包装成一个一个的batch
age,sex,pclass,parch,sibsp,fare,label = tf.train.batch([age,sex,pclass,parch,sibsp,fare,label],batch_size=16,capacity=500)
print(age.shape)#在这就可以查看特征的数据维度了,为(16,)因为batch_size为16
with tf.Session() as sess:
tf.global_variables_initializer().run()
#第四步
coord = tf.train.Coordinator()
#第五步:启动队列
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
'''第六步,这里面就可以查看数据,将数据“喂“给网络了 '''
age_=sess.run(age)
print(age_)
#第七步
coord.request_stop()
coord.join(threads=threads)
print('完结!')
所获得的age_变量的结果为(16,)维度:
[30. 38. 30. 54. 40. 28. 19. 30. 22. 21. 27. 60. 56. 20. 16. 48.]
3.3
import tensorflow as tf
import matplotlib.pyplot as plt
#第一步:定义reader对象以及tfrecords文件的输入部分
filename_queue = tf.train.string_input_producer(['picture_train.tfrecords'])
reader = tf.TFRecordReader()
#第二步:使用reader函数读入tfrecords内容,它返回的是(key,value)
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(serialized_example,
features={'Picture':tf.FixedLenFeature([],tf.string),
'Label':tf.FixedLenFeature([],tf.string)
})
image = tf.decode_raw(features['Picture'], tf.float32) #需要解码,因为不是单个的数值
label = tf.decode_raw(features['Label'], tf.float64)
image = tf.reshape(image, [500,500])
label = tf.reshape(label, [3])
#第三步:将样本包装成一个一个的batch
img,lab = tf.train.shuffle_batch([image,label], batch_size=3,capacity=32,min_after_dequeue=10)
print(img.shape) #形状为(3,500,500)
print(lab.shape) #形状为(3,3)
with tf.Session() as sess:
tf.global_variables_initializer().run()
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
img_=sess.run(lab[1])
print(img_)
coord.request_stop()
coord.join(threads=threads)
print('完结!')
本节总结
tfrecords文件的数据的读取步骤基本上是大同小异的,上面给出了详细的总结。需要注意的是,tfrecords文件中的数据的查看需要定义在session会话中。在session运行中,shuffle_batch和batch函数生成“一个batch的数据包”的过程是作为线程独立运行的,数据输入线程的挂起和运行时机由batch数据的生成函数控制的。shuffle函数指定内存保存样本数量的上限capacity和下限min_after_dequeue。当内存中的保存的样本数量大于上限capacity时,数据输入线程挂起。反之,当样本数据小于min_after_dequeue时,训练程序挂起。函数start_queue_runners()开启对应会话session的所有线程队列并返回线程句柄。Coordinator类对象负责实现数据输入线程的同步。当string_input_producer()函数产生无限循环队列时,应取消数据输入与训练程序的线程同步。
Next
本文全面总结了tfrecords文件的写入与读取过程,配上相应的实现代码,阅读起来清晰易懂
END
往期回顾