学习记录
一 图像
① 使用多线程并行化读取数据
AUTOTUNE = tf.data.experimental.AUTOTUNE
表示tf.data模块运行时,框架会根据可用的CPU自动设置最大的可用线程数,以使用多线程进行数据通道处理,将机器的算力拉满。注意返回的变脸其实是个常量,表示可用的线程数目。np.set_printoptions(precision=4)
表示对numpy对象在控制台上的输出数据格式与方式进行规定。比较有意思的方法,参考链接。
② 数据集准备–Pathlib库
tf.keras.utils.get_file:表示Downloads a file from a URL if it not already in the cache.
;另外,方法的返回结果是一个path变量Path to the downloaded file
,比如,在windows中的路径为:C:\Users\14376\.keras\datasets\flower_photos
data_root = pathlib.Path(data_root_orig)
返回的即是C:\Users\14376\.keras\datasets\flower_photos
。
data_root.iterdir():
:迭代对象,包含了子文件夹的路径。list(data_root.glob('*/*'))
:包含了子文件夹下的各个文件的绝对路径。
for item in data_root.iterdir():
print(item)
### 输出###########3
/root/.keras/datasets/flower_photos/daisy
/root/.keras/datasets/flower_photos/roses
/root/.keras/datasets/flower_photos/sunflowers
/root/.keras/datasets/flower_photos/dandelion
/root/.keras/datasets/flower_photos/tulips
/root/.keras/datasets/flower_photos/LICENSE.txt
all_image_paths = list(data_root.glob('*/*'))
all_image_paths
####################### =====================
[PosixPath('/root/.keras/datasets/flower_photos/daisy/8671824531_64b816949e_m.jpg'),
PosixPath('/root/.keras/datasets/flower_photos/daisy/8706810197_17b6c1f1e7.jpg'),
PosixPath('/root/.keras/datasets/flower_photos/daisy/2621723097_736febb4a4_n.jpg'),
PosixPath('/root/.keras/datasets/flower_photos/daisy/158869618_f1a6704236_n.jpg'),
PosixPath('/root/.keras/datasets/flower_photos/daisy/6480809771_b1e14c5cc2_m.jpg')]
[str(path) for path in all_image_paths]
#######################===========================
['/root/.keras/datasets/flower_photos/daisy/3758221664_b19116d61f.jpg',
'/root/.keras/datasets/flower_photos/daisy/2561352120_7961d8263f.jpg',
'/root/.keras/datasets/flower_photos/daisy/3750250718_eb61146c5f.jpg',
'/root/.keras/datasets/flower_photos/daisy/14569895116_32f0dcb0f9.jpg',
'/root/.keras/datasets/flower_photos/daisy/14907815010_bff495449f.jpg']
可以看得出来,与all_image_paths
的内容有些许不一样,并且后者的输出可能更加实用。
③ tf.data.Dataset
Hypothetical scenario:,数据集就是list(range(0,5))
,而真实场景中其实也就是围绕样本的索引进行思考的。
dataset_orig = list(range(0, 5))
- repeat
dataset = tf.data.Dataset.from_tensor_slices(dataset_orig)
dataset = dataset.repeat(3)
result = list(dataset.as_numpy_iterator())
print(result)
################==============
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
- shuffle
dataset = tf.data.Dataset.range(0, 5)
dataset = dataset.shuffle(3, reshuffle_each_iteration=True)
dataset = dataset.repeat(2)
result = list(dataset.as_numpy_iterator())
print(result)
################==============
[2, 3, 4, 1, 0, 0, 2, 3, 1, 4]
注意: 首先,shuffle()
中的buffer_sizes
是指声明的缓冲区读取的样本的数目。假设缓冲区的大小是100,而模型训练时的数据从缓冲区中每每读取一个样本,则缓冲区中的样本就要把数据集datasets
中的第101个样本送到缓冲区中;其次,reshuffle_each_iteration
在每一次全部的数据集被模型训练后,开启第二波模型训练时,数据集中的样本是否再做一次shuffle,无疑,这种选择更具优越性,
- Prefetch
dataset = tf.data.Dataset.range(0,8)
dataset = dataset.prefetch(2)
result1 = list(dataset.as_numpy_iterator())
print("result1:", result1)
dataset = dataSset.batch(3).prefetch(2)
result2 = list(dataset.as_numpy_iterator())
print("result2:", result2)
################==============
result1: [0, 1, 2, 3, 4, 5, 6, 7]
result2: [array([0, 1, 2], dtype=int64), array([3, 4, 5], dtype=int64), array([6, 7], dtype=int64)]
prefetch与element|batch之间的关系examples.prefetch(2) will prefetch two elements (2 examples), while examples.batch(20).prefetch(2) will prefetch 2 elements (2 batches, of 20 examples each).s
- range
a = list(tf.data.Dataset.range(5).as_numpy_iterator()) # basic
b = list(tf.data.Dataset.range(1, 5, 2).as_numpy_iterator()) # step
c = list(tf.data.Dataset.range(5, 1).as_numpy_iterator()) # inverse
d = list(tf.data.Dataset.range(1, 5, 2, output_type=tf.float32).as_numpy_iterator()) # format
print(a, b, c, d)
################==============
[0, 1, 2, 3, 4] [1, 3] [] [1.0, 3.0]
注意: output_type
参数在2.1版本中不能使用,2.3中可以。
5. apply
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())
################==============
[0, 1, 2, 3, 4]
注意: apply方法应用的transformation_func
的输入是整个dataset
而非单个样本。
6. map
dataset = Dataset.range(1, 6) # ==> [ 1, 2, 3, 4, 5 ]
dataset = dataset.map(lambda x: x + 1)
list(dataset.as_numpy_iterator())
# Each element is a tuple containing two `tf.Tensor` objects.
elements = [(1, "foo"), (2, "bar"), (3, "baz")]
dataset = tf.data.Dataset.from_generator(
lambda: elements, (tf.int32, tf.string))
# `map_func` takes two arguments of type `tf.Tensor`. This function
# projects out just the first component.
result = dataset.map(lambda x_int, y_str: x_int)
list(result.as_numpy_iterator())
################==============
[ 2, 3, 4, 5, 6]
[ 1, 2, 3]
注意: map_func
函数面向的是数据集中单个元素|样本。
dataset = Dataset.range(1, 6) # ==> [ 1, 2, 3, 4, 5 ]
dataset = dataset.map(lambda x: x + 1,
num_parallel_calls=tf.data.experimental.AUTOTUNE,
deterministic=False)
注意: num_parallel_calls
参数表示要并行异步处理的数量元素;如果未指定,则将按顺序处理元素。如果指定tf.data.experimental.AUTOTUNE
,则根据可用CPU动态设置并行调用的数量;deterministic
参数通过允许元素无序地产生来控制是否应该用“顺序确定性”来换取性能。因此利用map
函数的两个参数,在合理的场景下,可以实现两次性能的提升。
7. batch
dataset_orig = list(range(0, 5))
dataset = tf.data.Dataset.from_tensor_slices(dataset_orig)
dataset = dataset.repeat(1)
result = list(dataset.as_numpy_iterator())
print(result)
dataset = dataset.repeat(1).batch(2)
result = list(dataset.as_numpy_iterator())
print(result)
##################=====================
[0, 1, 2, 3, 4]
[array([0, 1]), array([2, 3]), array([4])]
重点是要表达出,如果没有batch,结果是一个列表,子元素为一个样本,而有了batch,多个元素做了组合,结果就是一个列表,但子元素为一个batch,每个batch是个样本的集合。
④ tf.data.Dataset的方法混合使用的情况
以tensorflow中的“图像”教程中某部分代码为例。
BATCH_SIZE = 32
# 设置一个和数据集大小一致的 shuffle buffer size(随机缓冲区大小)以保证数据
# 被充分打乱。
ds = image_label_ds.shuffle(buffer_size=image_count)
ds = ds.repeat()
ds = ds.batch(BATCH_SIZE)
# 当模型在训练的时候,`prefetch` 使数据集在后台取得 batch。
ds = ds.prefetch(buffer_size=AUTOTUNE)
ds
- 顺序很重要:
- 在
.repeat
之后.shuffle
,会在epoch
之间打乱数据(会发生一种情况:当有些数据出现两次的时候,其他数据还没有出现过)。
dataset = tf.data.Dataset.range(0, 6) # ==> [ 1, 2, 3, 4, 5 ]
dataset1 = dataset.repeat(2)
print(list(dataset1.as_numpy_iterator()))
dataset2 = dataset.repeat(2).shuffle(10)
print(list(dataset2.as_numpy_iterator()))
#######################============================
[0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]
[4, 4, 5, 2, 5, 0, 3, 3, 2, 1, 0, 1]
- 在
.batch
之后.shuffle
,会打乱batch
的顺序,但是不会在batch
之间打乱数据。
dataset = tf.data.Dataset.range(1, 14) # ==> [ 1, 2, 3, 4, 5 ]
dataset = dataset.batch(3).shuffle(3)
print(list(dataset.as_numpy_iterator()))
#######################============================
[array([1, 2, 3], dtype=int64), array([10, 11, 12], dtype=int64), array([4, 5, 6], dtype=int64), array([7, 8, 9], dtype=int64), array([13], dtype=int64)]
dataset = tf.data.Dataset.range(1, 14) # ==> [ 1, 2, 3, 4, 5 ]
dataset = dataset.batch(3).shuffle(1)
print(list(dataset.as_numpy_iterator()))
#######################============================
[array([1, 2, 3], dtype=int64), array([4, 5, 6], dtype=int64), array([7, 8, 9], dtype=int64), array([10, 11, 12], dtype=int64), array([13], dtype=int64)]
注意: batch后跟shuffle时,shuffle作用的对象是一个个的batch,而非数据集的一个个的element元素。而如果想使shuffle作用于batch,打乱batch与batch之间的顺序,需要将buffer-size设置为大于1的数目。因为buffer代表缓存器,shuffle的打乱操作是面向buffer中的batchs,而batchs的数目即是buffer_size。
如此,下面的一句话就可以明白。
2. 你在完全打乱中使用和数据集大小一样的 buffer_size(缓冲区大小)。较大的缓冲区大小提供更好的随机化,但使用更多的内存,直到超过数据集大小。
3. 在从随机缓冲区中拉取任何元素前,要先填满它。所以当你的 Dataset(数据集)启动的时候一个大的 buffer_size(缓冲区大小)可能会引起延迟
4. 在随机缓冲区完全为空之前,被打乱的数据集不会报告数据集的结尾。Dataset(数据集)由 .repeat 重新启动,导致需要再次等待随机缓冲区被填满。
最后一点问题就引出了一个预先载入的机制prefetch
。
API给出的介绍:Creates a Dataset that prefetches elements from this dataset. Most dataset input pipelines should end with a call to prefetch. This allows later elements to be prepared while the current element is being processed. This often improves latency and throughput, at the cost of using additional memory to store prefetched elements.
很重要的一段话。prefetch可能是新建了一个数据集,用来预先承载一些数据。大多时候数据集的buffer中没有或者近乎没有数据的时候给主程序一个反馈,通知其应该开始往buffer缓存器中加载数据。以使得训练程序能一直稳定进行。(这么理解会存在很多问题,暂时不管)
dataset = tf.data.Dataset.range(3)
dataset = dataset.prefetch(2)
list(dataset.as_numpy_iterator())
最后展示一下今后要使用的流程:
dataset = tf.data.Dataset.range(3)
ds = dataset.shuffle(buffer_size=别太大也别太小, reshuffle_each_iteration=True)
# ds = ds.repeat() # 如果设置了它,则实际的 epoch_num = max_epoch * repeat_num,不利于论文的写作统计
ds = ds.batch(BATCH_SIZE)
# 当模型在训练的时候,`prefetch` 使数据集在后台取得 batch。
ds = ds.prefetch(buffer_size=tf.data.experimental.AUTOTUNE) # 让系统自行判断吧
list(dataset.as_numpy_iterator())