【问题标题】:How to use tf.data.Dataset.from_generator() to load only one batch at a time from the dataset?如何使用 tf.data.Dataset.from_generator() 从数据集中一次只加载一个批次?
【发布时间】:2020-10-28 19:43:00
【问题描述】:

我想训练一个 CNN,我试图一次用一个批次为模型提供数据,直接从 numpy memmap 中,不必使用 tf.data.Dataset.from_generator() 将整个日期集加载到内存中。我正在使用tf2.2 和 GPU 进行拟合。数据集是一系列 3D 矩阵(NCHW 格式)。每个案例的标签是下一个 3D 矩阵。问题是它仍然将整个数据集加载到内存中。

这是一个可重复的简短示例:

import numpy as np
from numpy.lib.format import open_memmap
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

tf.config.list_physical_devices("GPU")


# create and initialize the memmap
ds_shape = (20000, 3, 50, 50)
ds_mmap = open_memmap("ds.npy",
                      mode='w+',
                      dtype=np.dtype("float64"),
                      shape=ds_shape)
ds_mmap = np.random.rand(*ds_shape)

len_ds = len(ds_mmap)          # 20000
len_train = int(0.6 * len_ds)  # 12000
len_val = int(0.2 * len_ds)    # 4000
len_test = int(0.2 * len_ds)   # 4000
batch_size = 32
epochs = 50

我尝试了 2 种方法来生成 train-val-test 数据集(另外,如果有人可以评论利弊,那将非常受欢迎)

1.

def gen(ds_mmap, start, stop):
  for i in range(start, stop):
    yield (ds_mmap[i], ds_mmap[i + 1])

tvt = {"train": None, "val": None, "test": None}
tvt_limits = {
  "train": (0, len_train),
  "val": (len_train, len_train + len_val),
  "test": (len_train + len_val, len_ds -1)  # -1 because the last case does not have a label
}

for ds_type, ds in tvt.items():
  start, stop = tvt_limits[ds_type]
  ds = tf.data.Dataset.from_generator(
    generator=gen,
    output_types=(tf.float64, tf.float64),
    output_shapes=(ds_shape[1:], ds_shape[1:]),
    args=[ds_mmap, start, stop]
  )

train_ds = (
  tvt["train"]
  .shuffle(len_ds, reshuffle_each_iteration=False)
  .batch(batch_size)
)
val_ds = tvt["val"].batch(batch_size)
test_ds = tvt["test"].batch(batch_size)
def gen(ds_mmap):
  for i in range(len(ds_mmap) - 1):
    yield (ds_mmap[i], ds_mmap[i + 1])

ds = tf.data.Dataset.from_generator(
  generator=gen,
  output_types=(tf.float64, tf.float64),
  output_shapes=(ds_shape[1:], ds_shape[1:])
  args=[ds_mmap]
)

train_ds = (
  ds
  .take(len_train)
  .shuffle(len_ds, reshuffle_each_iteration=False)
  .batch(batch_size)
)
val_ds = ds.skip(len_train).take(len_val).batch(batch_size)
test_ds = ds.skip(len_train + len_val).take(len_test - 1).batch(batch_size)

两种方式都有效,但会将整个数据集带入内存。

model = keras.Sequential([
  layers.Conv2D(64, (3, 3), input_shape=ds_shape[1:],
                activation="relu", data_format="channels_first"),
  layers.MaxPooling2D(data_format="channels_first"),
  layers.Conv2D(128, (3, 3),
                activation="relu", data_format="channels_first"),
  layers.MaxPooling2D(data_format="channels_first"),
  layers.Flatten(),
  layers.Dense(8182, activation="relu"),
  layers.Dense(np.prod(ds_shape[1:])),
  layers.Reshape(ds_shape[1:])
])

model.compile(loss="mean_aboslute_error",
              optimizer="adam",
              metrics=[tf.keras.metrics.MeanSquaredError()])

hist = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs,
  # steps_per_epoch=len_train // batch_size,
  # validation_steps=len_val // batch_size,
  shuffle=True
)

【问题讨论】:

    标签: python tensorflow keras deep-learning


    【解决方案1】:

    另一种方法是继承keras.utils.Sequence。这个想法是生成整个批次。

    引用文档:

    序列是一种更安全的多处理方式。这种结构保证了网络在每个 epoch 的每个样本上只训练一次,而生成器则不是这样。

    为此,需要提供__len__()__getitem__()方法。

    对于当前示例:

    class DS(keras.utils.Sequence):
      
      def __init__(self, ds_mmap, start, stop, batch_size):
        self.ds = ds_mmap[start: stop]
        self.batch_size = batch_size
    
      def __len__(self):
        # divide-ceil
        return -(-len(self.ds) // self.batch_size)
    
      def __getitem__(self, idx):
        start = idx * self.batch_size
        stop = (idx + 1) * self.batch_size
        batch_y = self.ds[start + 1: stop + 1]
        batch_x = self.ds[start: stop][: len(batch_y)]
        return batch_x, batch_y
    
    for ds_type, ds in tvt.items():
      start, stop = tvt_limits[ds_type]
      ds = DS(ds_mmap, start, stop, batch_size)
    

    在这种情况下,需要明确定义步数,而不是传递batch_size

    hist = model.fit(
      tvt["train"],
      validation_data=tvt["val"],
      epochs=epochs,
      steps_per_epoch=len_train // batch_size,
      validation_steps=len_val // batch_size,
      shuffle=True
    )
    

    不过,我没有让from_generator() 工作,我想知道如何工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-07
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 2020-06-04
      • 1970-01-01
      • 1970-01-01
      • 2020-07-30
      相关资源
      最近更新 更多