1 创建
对于创建Dataset对象,官方文档中总结为两种方式,我将这两种方式细化后总结为4中方式:
(1)通过Dataset中的range()方法创建包含一定序列的Dataset对象。
- range()
range()方法是Dataset内部定义的一个的静态方法,可以直接通过类名调用。另外,Dataset中的range()方法与Python本身内置的range()方法接受参数形式是一致的,可以接受range(begin)、range(begin, end)、range(begin, end, step)等多种方式传参。
import tensorflow as tfimport numpy as np
dataset1 = tf.data.Dataset.range(5)type(dataset1)
tensorflow.python.data.ops.dataset_ops.RangeDataset
注:RangeDataset是Dataset的一个子类。Dataset对象属于可迭代对象, 可通过循环进行遍历:
for i in dataset1: print(i) print(i.numpy())
tf.Tensor(0, shape=(), dtype=int64) 0 tf.Tensor(1, shape=(), dtype=int64) 1 tf.Tensor(2, shape=(), dtype=int64) 2 tf.Tensor(3, shape=(), dtype=int64) 3 tf.Tensor(4, shape=(), dtype=int64) 4
可以看到,range()方法创建的Dataset对象内部每一个元素都以Tensor对象的形式存在,可以通过numpy()方法访问真实值。
- from_generator()
如果你觉得range()方法不够灵活,功能不够强大,那么你可以尝试使用from_generator()方法。from_generator()方法接收一个可调用的生成器函数最为参数,在遍历from_generator()方法返回的Dataset对象过程中不断生成新的数据,减少内存占用,这在大数据集中很有用。
def count(stop): i = 0 while i<stop: print('第%s次调用……'%i) yield i i += 1
dataset2 = tf.data.Dataset.from_generator(count, args=[3], output_types=tf.int32, output_shapes = (), )
a = iter(dataset2)
next(a)
第0次调用…… <tf.Tensor: id=46, shape=(), dtype=int32, numpy=0>
next(a)
第1次调用…… <tf.Tensor: id=47, shape=(), dtype=int32, numpy=1>
for i in dataset2: print(i) print(i.numpy())
第0次调用…… tf.Tensor(0, shape=(), dtype=int32) 0 第1次调用…… tf.Tensor(1, shape=(), dtype=int32) 1 第2次调用…… tf.Tensor(2, shape=(), dtype=int32) 2
**(2)通过接收其他类型的集合类对象创建Dataset对象。**这里所说的集合类型对象包含Python内置的list、tuple,numpy中的ndarray等等。这种创建Dataset对象的方法大多通过from_tensors()和from_tensor_slices()两个方法实现。这两个方法很常用,重点说一说。
- from_tensors()
from_tensors()方法接受一个集合类型对象作为参数,返回值为一个TensorDataset类型对象,对象内容、shape因传入参数类型而异。
当接收参数为list或Tensor对象时,返回的情况是一样的,因为TensorFlow内部会将list先转为Tensor对象,然后实例化一个Dataset对象:
a = [0,1,2,3,4]dataset1 = tf.data.Dataset.from_tensors(a)dataset1_n = tf.data.Dataset.from_tensors(np.array(a))dataset1_t = tf.data.Dataset.from_tensors(tf.constant(a))
dataset1,next(iter(dataset1))
(<TensorDataset shapes: (5,), types: tf.int32>, <tf.Tensor: id=67, shape=(5,), dtype=int32, numpy=array([0, 1, 2, 3, 4], dtype=int32)>)
dataset1_n,next(iter(dataset1_n))
(<TensorDataset shapes: (5,), types: tf.int64>, <tf.Tensor: id=73, shape=(5,), dtype=int64, numpy=array([0, 1, 2, 3, 4])>)
dataset1_t,next(iter(dataset1_t))
(<TensorDataset shapes: (5,), types: tf.int32>, <tf.Tensor: id=79, shape=(5,), dtype=int32, numpy=array([0, 1, 2, 3, 4], dtype=int32)>)
多维结构也是一样的:
a = [0,1,2,3,4]b = [5,6,7,8,9]dataset2 = tf.data.Dataset.from_tensors([a,b])dataset2_n = tf.data.Dataset.from_tensors(np.array([a,b]))dataset2_t = tf.data.Dataset.from_tensors(tf.constant([a,b]))
dataset2,next(iter(dataset2))
(<TensorDataset shapes: (2, 5), types: tf.int32>, <tf.Tensor: id=91, shape=(2, 5), dtype=int32, numpy= array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=int32)>)
dataset2_n,next(iter(dataset2_n))
(<TensorDataset shapes: (2, 5), types: tf.int64>, <tf.Tensor: id=97, shape=(2, 5), dtype=int64, numpy= array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])>)
dataset2_t,next(iter(dataset2_t))
(<TensorDataset shapes: (2, 5), types: tf.int32>, <tf.Tensor: id=103, shape=(2, 5), dtype=int32, numpy= array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=int32)>)
当接收参数为数组就不一样了,此时Dataset内部内容为一个tuple,tuple的元素是原来tuple元素转换为的Tensor对象:
a = [0,1,2,3,4]b = [5,6,7,8,9]dataset3 = tf.data.Dataset.from_tensors((a,b))
for i in dataset3: print(type(i)) print(i) for j in i: print(j)
<class 'tuple'> (<tf.Tensor: id=112, shape=(5,), dtype=int32, numpy=array([0, 1, 2, 3, 4], dtype=int32)>, <tf.Tensor: id=113, shape=(5,), dtype=int32, numpy=array([5, 6, 7, 8, 9], dtype=int32)>) tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int32) tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int32)
- from_tensor_slices()
from_tensor_slices()方法返回一个TensorSliceDataset类对象,TensorSliceDataset对象比from_tensors()方法返回的TensorDataset对象支持更加丰富的操作,例如batch操作等,因此在实际应用中更加广泛。返回的TensorSliceDataset对象内容、shape因传入参数类型而异。
当传入一个list时,时将list中元素逐个转换为Tensor对象然后依次放入Dataset中,所以Dataset中有多个Tensor对象:
a = [0,1,2,3,4]dataset1 = tf.data.Dataset.from_tensor_slices(a)
dataset1
<TensorSliceDataset shapes: (), types: tf.int32>
for i,elem in enumerate(dataset1): print(i, '-->', elem)
0 --> tf.Tensor(0, shape=(), dtype=int32) 1 --> tf.Tensor(1, shape=(), dtype=int32) 2 --> tf.Tensor(2, shape=(), dtype=int32) 3 --> tf.Tensor(3, shape=(), dtype=int32) 4 --> tf.Tensor(4, shape=(), dtype=int32)
a = [0,1,2,3,4]b = [5,6,7,8,9]dataset2 = tf.data.Dataset.from_tensor_slices([a,b])
dataset2
<TensorSliceDataset shapes: (5,), types: tf.int32>
for i,elem in enumerate(dataset2): print(i, '-->', elem)
0 --> tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int32) 1 --> tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int32)
当传入参数为tuple时,会将tuple中各元素转换为Tensor对象,然后将第一维度对应位置的切片进行重新组合成一个tuple依次放入到Dataset中,所以在返回的Dataset中有多个tuple。这种形式在对训练集和测试集进行重新组合是非常实用。
a = [0,1,2,3,4]b = [5,6,7,8,9]dataset1 = tf.data.Dataset.from_tensor_slices((a,b))
dataset1
<TensorSliceDataset shapes: ((), ()), types: (tf.int32, tf.int32)>
for i in dataset1: print(i)
(<tf.Tensor: id=143, shape=(), dtype=int32, numpy=0>, <tf.Tensor: id=144, shape=(), dtype=int32, numpy=5>) (<tf.Tensor: id=145, shape=(), dtype=int32, numpy=1>, <tf.Tensor: id=146, shape=(), dtype=int32, numpy=6>) (<tf.Tensor: id=147, shape=(), dtype=int32, numpy=2>, <tf.Tensor: id=148, shape=(), dtype=int32, numpy=7>) (<tf.Tensor: id=149, shape=(), dtype=int32, numpy=3>, <tf.Tensor: id=150, shape=(), dtype=int32, numpy=8>) (<tf.Tensor: id=151, shape=(), dtype=int32, numpy=4>, <tf.Tensor: id=152, shape=(), dtype=int32, numpy=9>)
c = ['a','b','c','d','e']dataset3 = tf.data.Dataset.from_tensor_slices((a,b,c))
dataset3
<TensorSliceDataset shapes: ((), (), ()), types: (tf.int32, tf.int32, tf.string)>
for i in dataset3: print(i)
(<tf.Tensor: id=162, shape=(), dtype=int32, numpy=0>, <tf.Tensor: id=163, shape=(), dtype=int32, numpy=5>, <tf.Tensor: id=164, shape=(), dtype=string, numpy=b'a'>) (<tf.Tensor: id=165, shape=(), dtype=int32, numpy=1>, <tf.Tensor: id=166, shape=(), dtype=int32, numpy=6>, <tf.Tensor: id=167, shape=(), dtype=string, numpy=b'b'>) (<tf.Tensor: id=168, shape=(), dtype=int32, numpy=2>, <tf.Tensor: id=169, shape=(), dtype=int32, numpy=7>, <tf.Tensor: id=170, shape=(), dtype=string, numpy=b'c'>) (<tf.Tensor: id=171, shape=(), dtype=int32, numpy=3>, <tf.Tensor: id=172, shape=(), dtype=int32, numpy=8>, <tf.Tensor: id=173, shape=(), dtype=string, numpy=b'd'>) (<tf.Tensor: id=174, shape=(), dtype=int32, numpy=4>, <tf.Tensor: id=175, shape=(), dtype=int32, numpy=9>, <tf.Tensor: id=176, shape=(), dtype=string, numpy=b'e'>)
对比总结一下from_generator()、from_tensor()、from_tensor_slices()这三个方法:
- from_tensors()在形式上与from_tensor_slices()很相似,但其实from_tensors()方法出场频率上比from_tensor_slices()差太多,因为from_tensor_slices()的功能更加符合实际需求,且返回的TensorSliceDataset对象也提供更多的数据处理功能。from_tensors()方法在接受list类型参数时,将整个list转换为Tensor对象放入Dataset中,当接受参数为tuple时,将tuple内元素转换为Tensor对象,然后将这个tuple放入Dataset中。
- from_generator()方法接受一个可调用的生成器函数作为参数,在遍历Dataset对象时,通过通用生成器函数继续生成新的数据供训练和测试模型使用,这在大数据集合中很实用。
- from_tensor_slices()方法接受参数为list时,将list各元素依次转换为Tensor对象,然后依次放入Dataset中;更为常见的情况是接受的参数为tuple,在这种情况下,要求tuple中各元素第一维度长度必须相等,from_tensor_slices()方法会将tuple各元素第一维度进行拆解,然后将对应位置的元素进行重组成一个个tuple依次放入Dataset中,这一功能在重新组合数据集属性和标签时很有用。另外,from_tensor_slices()方法返回的TensorSliceDataset对象支持batch、shuffle等等功能对数据进一步处理。
(3)通过读取磁盘中的文件(文本、图片等等)来创建Dataset。**tf.data中提供了TextLineDataset、TFRecordDataset等对象来实现此功能。这部分内容比较多,也比较重要,我打算后续用专门一篇博客来总结这部分内容。