tensorflow之dataset详解

dataset是什么?

Dataset可以看作是相同类型“元素”的有序 列表。在实际使用时,单个“元素”可以是向量,也可以是字符串、图片,甚至是tuple或者dict。Dataset是google点名建议的读取数据的方式,所以在tf使用中有很重要的地位。

上面这个定义其实已经描述了dataset,他是一个可迭代对象,而且是有序的,而且每个元素的类型相同。在工作中为了方便理解,我一般把它看作java中的List,即声明了泛型的有序集合List。从python的角度看的话,就是规定了所有元素类型必须相同的list。

应该注意的是,他和List一样,是不能通过角标取元素的,也就是说dataset[0]这样的写法,是不行的。

Dataset是用来做什么的?

Dataset的作用是为了更加高效便捷的读取数据。知道这个目的,也就自然而然的知道Dataset应该怎么生成了:它应该在读取数据的过程中生成,即把数据读取成一个Dataset,以便后面的处理。

所以下面的这张图片也就不难理解了,TextLineDataset,TFRecordDataset和FixedLengthRecordDataset 分别是三种生成Dataset的方式。TextLineDataset用于读取文本数据,TFRecordDataset用于读取tfrecord数据,FixedLengthRecordDataset用于从二进制文件数据数据。

tensorflow之dataset详解_第1张图片

 

上述的三种方法的用法也都极其简单:

tf.data.TFRecordDataset(
    filenames, compression_type=None, buffer_size=None, num_parallel_reads=None
)

tf.data.TextLineDataset(
    filenames, compression_type=None, buffer_size=None, num_parallel_reads=None
)

tf.data.FixedLengthRecordDataset(
    filenames, record_bytes, header_bytes=None, footer_bytes=None, buffer_size=None,
    compression_type=None, num_parallel_reads=None
)

其中,TextLineDataset和TFRecordDataset 都只需要输入文件名,FixedLengthRecordDataset需要输入文件名和每个样本的字节长度。

除此之外,还有一些用的比较少,一般用于学习或者测试代码的生成Dataset的方法。例如从list或者numpy中生成:

dataset = tf.data.Dataset.from_tensor_slices([1, 2, 3])

直接生成一个依次递增1的dataset:

dataset = tf.data.Dataset.range(10)

还有一些更冷门的用的就更少了,这里不多介绍

Dataset的处理

通过TextLineDataset,TFRecordDataset和FixedLengthRecordDataset,其实数据读取工作就已经完成了。后面的工作全部都是对Dataset的处理。由于平时tfrecord用的最多,而且处理也最复杂,所以下面以TFRecordDataset为例介绍Dataset的后处理工作。中间会夹杂稍微介绍一点TextLineDataset的处理。

对Dataset的处理一般通过调用Dataset的各种方法来实现。这些方法在tensorflow的官方文档中都有介绍。但无论是对Dataset多么复杂的操作,我们都只需要记住:

Dataset,就是一个统一了元素类型的List

Dataset,就是一个统一了元素类型的List

Dataset,就是一个统一了元素类型的List

只要记住了这一点,无论对这个Dataset做了多么复杂的操作,我们都能对当前状态有一个清醒的了解。下面介绍常用的Dataset的操作

1. map

其实map方法并不是Dataset独有的方法,其本质是Python所有的可迭代对象的一个方法。map方法的用法如下:

map(
    map_func, num_parallel_calls=None, deterministic=None
)

 

在Python中,可迭代对象是指有__iter__属性的对象,这类对象可以用循环取迭代,所以可以放在for中迭代,例如list,dict,DataFrame等都是可迭代对象。而不可迭代对象例如整型,float等不是可迭代对象,放在循环中会报错 “object is not iterable”。

map方法只有一个必须的入参,是一个函数。map方法会对Dataset的每个元素调用这个函数。

举例来说,我们都知道读取了tfrecord以后,一般会用tf.io.parse_single_example来处理一下,代码如下所示

data3 = data.map(lambda x : tf.io.parse_single_example(x, features = feature))

那么这一行代码的是什么意思呢?在https://blog.csdn.net/kangshuangzhu/article/details/106814664 中我已经介绍了,tfrecord文件本质上还是一个二进制文件,所以读进来的就是二进制字符,所以我们要解析成example来操作。

tf.io.parse_single_example的作用就是把每一行的二进制字符解析成example。

其实如果我们在读进来tfrecord以后,完全可以直接把每个元素输出,可以看到是一行行的乱码字符串:

tfrecordpath = "./tfrecord" #假设有这么一个tfrecord文件
data = tf.data.TFRecordDataset(tfrecordpath)
for line in data:
    print(line)

我们完全不理会是不是乱码,就把它当成一个字符串来处理

def line_settle(x):
    tf.string_split(x,"\\")  # 把每一行的字符串张量用"\"分隔开

tfrecordpath = "./tfrecord" #假设有这么一个tfrecord文件
data = tf.data.TFRecordDataset(tfrecordpath)
data.map(line_settle)

 同样的道理,现在我们有一个文本他的内容是这样的

name:john age:20 gender:male address:beijing
name:joe age:12 gender:female address:beijing
name:mike age:23 gender:male address:shanghai
name:wolf age:33 gender:female address:shenzhen

把上面的内容保存成一个文本,名为info,并且把里面的空格替换成tab,用TextLineDataset读取后的Dataset每个元素同样得到的就是上面每一行的string tensor。我们可以通过下面的代码进行处理,处理以后得Dataset每个元素是tuple[tensor],格式如下:

def line_settle(x):
    info_str = str(x.numpy(), encoding = "utf-8")
    info_list = info_str.split(" ")
    return info_list 
 
infopath = "info.txt" #文本文件
# infopath = "cowper.txt" #文本文件
data = tf.data.TextLineDataset(infopath)
data2 = data.map(labmda x: tf.py_function(line_settle.[x],[tf.string,tf.string,tf.string])
for batch in data2:
    print(batch)

这里有一个非常复杂的语法

data2 = data.map(labmda x: tf.py_function(line_settle.[x],[tf.string,tf.string,tf.string])

之所以这么写,是因为tensorflow2.0留下了一个非常大的坑。如果Dataset的每个元素是tensor,那么Dataset进行for循环的时候,得到的每个元素是eagerTensor,可以直接读取其中的内容。但是,在使用map的时候却是通过Tensor来实现的。也就是说,map的时候,实际上还是用tensorflow1.x那一套静态图的逻辑,这就导致我们是无法得到每个元素中的内容的。tensorflow2.0 中的一些语法,例如tensor.numpy()这类写法失效了。这时候就需要用tf.py_function把静态图中的tensor进行eager excution。

py_function的语法如下:

tf.py_function(
    func, inp, Tout, name=None
)

三个必须入参是

Args

func A Python function which accepts a list of Tensor objects having element types that match the corresponding tf.Tensor objects in inp and returns a list of Tensor objects (or a single Tensor, or None) having element types that match the corresponding values in Tout.
inp A list of Tensor objects.
Tout A list or tuple of tensorflow data types or a single tensorflow data type if there is only one, indicating what func returns; an empty list if no value is returned (i.e., if the return value is None).
name A name for the operation (optional).

 其中,比较值得注意的是Tout规定了输出只能是tensorflow的类型,不能是dict或者其他类型。这就导致tensor为元素的Dataset的map方法没有非常灵活的输出。

 总结一下,map其实就是对Dataset的每个元素都执行了同一个函数。但,如果Dataset的元素是tensor那么map会通过静态图的方法调用函数。

apply

apply很容易和map搞混,他们的区别是map堆Dataset的每个元素进行同样的操作,而apply则是对每个Dataset做一个操作。例如

dataset = tf.data.Dataset.range(100)
def dataset_fn(ds):
  return ds.filter(lambda x: x < 5)
dataset = dataset.apply(dataset_fn)
list(dataset.as_numpy_iterator())

batch

batch的作用是把dataset的元素合并,我们可以理解成这样,原有一个list

a = [1,2,3,4,5,6,7,8,9,10]
a.batch(3)
[ [1,2,3], [4,5,6], [7,8,9], [10] ]

如果Dataset的元素本社那就是一个集合,由于dataset的每个元素类型相同,会把相应位置的进行合并,例如:

a = [[john,20,male,beijing],
     [joe,12,female,beijing], 
     [mike,23 ,male,shanghai],
     [wolf,33,female,shenzhen]]
a.batch(2) = [ [ [john,joe],[20,12], [male,female],[beijing,beijing] ],
               [ [mike,wolf],[23,33], [mael,female],[shanghai,shenzhen] ]
             ]

应该注意的是,用了batch以后,dataset就只能用parse_example 来解析了

filter

与map非常类似,同样用静态图的方法调用函数。

repeat

dataset.repeate(n),  dataset重复n遍,是123,123,123的重复,而不是111,222,333的重复

如果省略n那么是无穷次重复

list_file

这个函数用于处理文件路径,假设我们的文件都放在文件夹holder 中,其中有文件a.text,b.text,c.text。如果我们想把所有的数据都读取进来,如果只写data = tf.data.TextLineDataset("./holder/*")是不行的。首先应该先读取所有的文件名:

filename = tf.data.Dataset.list_files(["./holder/*"])
for batch in filename :
    print(batch)

tf.Tensor(b'.\\holder\\a.txt', shape=(), dtype=string)
tf.Tensor(b'.\\holder\\b.txt', shape=(), dtype=string)
tf.Tensor(b'.\\holder\\c.txt', shape=(), dtype=string)

那如果我们的训练文件放在多个文件夹中,也可以用这种方式把所有的文件名都读出来。假设有一个文件夹holder2,里面放着d.txt, e.txt

filename = tf.data.Dataset.list_files(["./holder/*","./holder2/*"])
for batch in filename :
    print(batch)

tf.Tensor(b'.\\holder\\a.txt', shape=(), dtype=string)
tf.Tensor(b'.\\holder\\b.txt', shape=(), dtype=string)
tf.Tensor(b'.\\holder\\c.txt', shape=(), dtype=string)
tf.Tensor(b'.\\holder\\e.txt', shape=(), dtype=string)
tf.Tensor(b'.\\holder\\d.txt', shape=(), dtype=string)

 interleave

用法

interleave(
    map_func, cycle_length=None, block_length=None, num_parallel_calls=None,
    deterministic=None
)

上面list_file可以把文件夹中的文件都列举出来,interleave可以把这些文件全部读取出来。假设a.txt中内容就是字母a,b.txt中内容就是字母b。。。那么:

word = rfile.interleave(lambda x: tf.data.TextLineDataset(x))
for batch in word:
    print(batch)
tf.Tensor(b'e', shape=(), dtype=string)
tf.Tensor(b'd', shape=(), dtype=string)
tf.Tensor(b'b', shape=(), dtype=string)
tf.Tensor(b'a', shape=(), dtype=string)
tf.Tensor(b'c', shape=(), dtype=string)

当然,interleave并不是专门为了读取数据而准备的 ,interleave也是一个对dataset元素进行操作的方法。下面举一个tensorflow2.3官方的例子:

dataset = Dataset.range(1, 6)  # ==> [ 1, 2, 3, 4, 5 ]
# NOTE: New lines indicate "block" boundaries.
dataset = dataset.interleave(
    lambda x: Dataset.from_tensors(x).repeat(6),
    cycle_length=2, block_length=4)
list(dataset.as_numpy_iterator())

结果为:

tensorflow之dataset详解_第2张图片

interleave命令中cycle_length和block_length虽然是可选入参,但实际应用也很多。

interleave是根据dataset的每个元素生产新的元素,那么新的元素是怎么排列的呢?

比如原来dataset的元素是 [1,2,3]。现在产生的新元素是:

"1" 产生了 [a, a, a, a, a] 五个元素

"2"产生了 [b, b, b, b, b] 

"3" 产生了[c, c, c, c, c]

那这新产生的15个元素怎么排列,是[a,b,c,a,b,c.....] 还是[a,a,a,a,a,b,b,b,......] 还是 [a,a,b,b,a,a,b,b,a,b]。就是通过cycle_length和block_length来控制。

cycle_length:

cycle_length:n ,原dataset的元素每n个一组,上一组的元素产生的新元素输出完了,才开始输出下一组。

还是刚才的例子,cycle=2的时候,原dataset元素每2个一组,就是  [1,2]一组,[3]一组。那么产生的新元素顺序就是:

a, b, a, b, a, b, a, b, a, b,  #第一组的元素产生的新元素输出完了

c, c, c, c, c                  #第二组元素产生的新元素

 所以新元素的排列顺序就是[ a, b, a, b, a, b, a, b, a, b, c, c, c, c, c] 

block_length:

block_length:m  同一组内元素,先输出第一个元素产生的m个新元素,再输出第二个元素产生的m个新元素,依次循环,知道把所有元素生成完。

还是刚才的例子  cycle=2,block_length=2的时候。产生的新元素顺序是:

#block_length=2
#cycle_length=2
a,a    ##第一组第一个元素1产生的新元素输出block_length=2个
b,b    ##第一组第二个元素2产生的新元素输出block_length=2个
a,a    ##第一组第一个元素1产生的新元素再输出block_length=2个
b,b    ##第一组第二个元素2产生的新元素再输出block_length=2个
a      ##第一组第一个元素1产生的新元素全部输出完毕
b      ##第一组第二个元素2产生的新元素全部输出完毕
c,c    ##第二组第一个元素1产生的新元素输出block_length=2个
c,c    ##第二组第一个元素1产生的新元素再输出block_length=2个
c      ##第二组第一个元素2产生的新元素全部输出完毕

所以新元素的排列顺序就是[ a, a, b, b, a, a, b, b, a, b, c, c, c, c, c] 

cycle_length与block_length和是否多线程没有关系

map和interleave的区别

map和interleave的用法十分类似,他们的共同点是他们都是堆Tensor做的操作,也就是说interleave也是通过静态图的方法进行操作。

他们的区别是

map是对dataset的每个元素做处理,原dataset的一个元素“变成了”一个新的元素。这个过程中,dataset的元素个数不会发生变化

而interleave则是根据dataset 的元素,原dataset 的一个元素“生成了”n个新的元素,新生成的元素组成了新的dataset。

注意这2个过程的区别,map是对元素进行了变换,所以新老dataset的元素是一样多的,可以意义对应。而interleave的中文就是插入新的,所以新的dataset是由新生成的元素拼装起来的,新老dataset的元素不一定一样多。

 

 

你可能感兴趣的:(数据挖掘)