【问题标题】:Tensorflow tf.data.Dataset.cache seems do not take the expected effectTensorflow tf.data.Dataset.cache 似乎没有起到预期的效果
【发布时间】:2022-01-12 18:38:35
【问题描述】:

我正在尝试按照Better performance with the tf.data API 指南提高我的模型训练性能。但是,我观察到,与没有.cache() 的相同设置相比,使用.cache() 的性能几乎相同甚至更差。

datafile_list = load_my_files()
RAW_BYTES = 403*4
BATCH_SIZE = 32

raw_dataset = tf.data.FixedLengthRecordDataset(filenames=datafile_list, record_bytes=RAW_BYTES, num_parallel_reads=10, buffer_size=1024*RAW_BYTES)
raw_dataset = raw_dataset.map(tf.autograph.experimental.do_not_convert(decode_and_prepare),
    num_parallel_calls=tf.data.AUTOTUNE)
raw_dataset = raw_dataset.cache()
raw_dataset = raw_dataset.shuffle(buffer_size=1024)
raw_dataset = raw_dataset.batch(BATCH_SIZE)
raw_dataset = raw_dataset.prefetch(tf.data.AUTOTUNE)

datafile_list 中的数据为 9.92GB,相当适合系统可用的总物理 RAM (100GB)。系统交换已禁用。

通过使用数据集训练模型:

model = build_model()
model.fit(raw_dataset, epochs=5, verbose=2)

结果:

Epoch 1/5
206247/206247 - 126s - loss: 0.0043 - mae: 0.0494 - mse: 0.0043
Epoch 2/5
206247/206247 - 125s - loss: 0.0029 - mae: 0.0415 - mse: 0.0029
Epoch 3/5
206247/206247 - 129s - loss: 0.0027 - mae: 0.0397 - mse: 0.0027
Epoch 4/5
206247/206247 - 125s - loss: 0.0025 - mae: 0.0386 - mse: 0.0025
Epoch 5/5
206247/206247 - 125s - loss: 0.0024 - mae: 0.0379 - mse: 0.0024

这个结果令人沮丧。由docs

第一次迭代数据集时,其元素将缓存在指定文件或内存中。后续迭代将使用缓存的数据。

来自this guide

当迭代这个数据集时,由于缓存,第二次迭代将比第一次快得多。

但是,所有 epoch 所用的时间几乎相同。此外,在训练期间,CPU 和 GPU 的使用率都非常低(见下图)。

通过注释掉raw_dataset = raw_dataset.cache() 行,结果没有显示出任何显着差异:

Epoch 1/5
206067/206067 - 129s - loss: 0.0042 - mae: 0.0492 - mse: 0.0042
Epoch 2/5
206067/206067 - 127s - loss: 0.0028 - mae: 0.0412 - mse: 0.0028
Epoch 3/5
206067/206067 - 134s - loss: 0.0026 - mae: 0.0393 - mse: 0.0026
Epoch 4/5
206067/206067 - 127s - loss: 0.0024 - mae: 0.0383 - mse: 0.0024
Epoch 5/5
206067/206067 - 126s - loss: 0.0023 - mae: 0.0376 - mse: 0.0023

正如文档中所指出的,我的期望是使用缓存会导致训练时间大大加快。我想知道我做错了什么。

附件

使用缓存进行训练期间的 GPU 使用情况:

在没有缓存的情况下训练期间的 GPU 使用情况:

使用缓存进行训练期间的系统统计信息(内存、CPU 等):

无缓存训练期间的系统统计信息(内存、CPU 等):

【问题讨论】:

  • cache 只有在读取和处理数据成为训练瓶颈时才会有所作为。如果数据在没有缓存的情况下已经足够快地提供给模型,那么它不会有任何影响。

标签: python tensorflow tensorflow2.0 tensorflow-datasets


【解决方案1】:

只是使用 Google Colab 的一个小观察。根据docs

注意:要最终确定缓存,必须对输入数据集进行整体迭代。否则,后续迭代将不会使用缓存数据。

注意:缓存将在每个过程中产生完全相同的元素 遍历数据集。如果您希望随机化迭代 order,请确保调用缓存后调用shuffle。

在使用缓存和事先迭代数据集时,我确实注意到了一些差异。这是一个例子。

准备数据

import random
import struct
import tensorflow as tf
import numpy as np

RAW_N = 2 + 20*20 + 1

bytess = random.sample(range(1, 5000), RAW_N*4)
with open('mydata.bin', 'wb') as f:
  f.write(struct.pack('1612i', *bytess))
def decode_and_prepare(register):
  register = tf.io.decode_raw(register, out_type=tf.float32)
  inputs = register[2:402]
  label = tf.random.uniform(()) + register[402:]
  return inputs, label

raw_dataset = tf.data.FixedLengthRecordDataset(filenames=['/content/mydata.bin']*7000, record_bytes=RAW_N*4)
raw_dataset = raw_dataset.map(decode_and_prepare)

训练模型没有事先缓存和迭代

total_data_entries = len(list(raw_dataset.map(lambda x, y: (x, y))))
train_ds = raw_dataset.shuffle(buffer_size=total_data_entries).batch(32).prefetch(tf.data.AUTOTUNE)
inputs = tf.keras.layers.Input((400,))
x = tf.keras.layers.Dense(200, activation='relu', kernel_initializer='normal')(inputs)
x = tf.keras.layers.Dense(100, activation='relu', kernel_initializer='normal')(x)
outputs = tf.keras.layers.Dense(1, kernel_initializer='normal')(x)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(train_ds, epochs=5)
Epoch 1/5
875/875 [==============================] - 4s 3ms/step - loss: 0.1425
Epoch 2/5
875/875 [==============================] - 4s 3ms/step - loss: 0.0841
Epoch 3/5
875/875 [==============================] - 4s 3ms/step - loss: 0.0840
Epoch 4/5
875/875 [==============================] - 4s 3ms/step - loss: 0.0840
Epoch 5/5
875/875 [==============================] - 4s 3ms/step - loss: 0.0840
<keras.callbacks.History at 0x7fc41be037d0>

训练模型缓存但没有迭代

total_data_entries = len(list(raw_dataset.map(lambda x, y: (x, y))))
train_ds = raw_dataset.shuffle(buffer_size=total_data_entries).cache().batch(32).prefetch(tf.data.AUTOTUNE)
inputs = tf.keras.layers.Input((400,))
x = tf.keras.layers.Dense(200, activation='relu', kernel_initializer='normal')(inputs)
x = tf.keras.layers.Dense(100, activation='relu', kernel_initializer='normal')(x)
outputs = tf.keras.layers.Dense(1, kernel_initializer='normal')(x)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(train_ds, epochs=5)
Epoch 1/5
875/875 [==============================] - 4s 2ms/step - loss: 0.1428
Epoch 2/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0841
Epoch 3/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0840
Epoch 4/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0840
Epoch 5/5
875/875 [==============================] - 2s 3ms/step - loss: 0.0840
<keras.callbacks.History at 0x7fc41fa87810>

训练模型缓存和迭代

total_data_entries = len(list(raw_dataset.map(lambda x, y: (x, y))))
train_ds = raw_dataset.shuffle(buffer_size=total_data_entries).cache().batch(32).prefetch(tf.data.AUTOTUNE)
_ = list(train_ds.as_numpy_iterator()) # iterate dataset beforehand
inputs = tf.keras.layers.Input((400,))
x = tf.keras.layers.Dense(200, activation='relu', kernel_initializer='normal')(inputs)
x = tf.keras.layers.Dense(100, activation='relu', kernel_initializer='normal')(x)
outputs = tf.keras.layers.Dense(1, kernel_initializer='normal')(x)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(train_ds, epochs=5)
Epoch 1/5
875/875 [==============================] - 3s 3ms/step - loss: 0.1427
Epoch 2/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0841
Epoch 3/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0840
Epoch 4/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0840
Epoch 5/5
875/875 [==============================] - 2s 2ms/step - loss: 0.0840
<keras.callbacks.History at 0x7fc41ac9c850>

结论:数据集的缓存和之前的迭代似乎对训练有影响,但在这个例子中只使用了 7000 个文件。

【讨论】:

  • 感谢您的实验。我通过将 7000 替换为 100'000 来复制它,我发现调用 cache() 确实很重要。
  • 是的。您是否也事先进行了迭代?注意到那里有什么不同吗?
  • 是的,我只是在写它。所以我可以理解,事先迭代训练 ds 将在第一个 epoch 之前预加载缓存。凉爽的。我正在评估.prefetch(tf.data.AUTOTUNE) 在您的实验中的作用。在我的原始设置中,cache() 仅在我删除 .prefetch(tf.data.AUTOTUNE) 时才会显示效果
猜你喜欢
  • 2017-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-12
  • 2018-05-18
  • 1970-01-01
  • 1970-01-01
  • 2017-05-15
相关资源
最近更新 更多