将 .npy(numpy 文件)送入 tensorflow 数据管道

Feeding .npy (numpy files) into tensorflow data pipeline

Tensorflow 似乎缺少“.npy”文件的 reader。 如何将我的数据文件读入新的 tensorflow.data.Dataset 管道? 我的数据不适合内存。

每个对象都保存在一个单独的“.npy”文件中。每个文件包含 2 个不同的 ndarrays 作为特征和一个标量作为它们的标签。

你的数据适合内存吗?如果是这样,您可以按照文档 Consuming NumPy Arrays 部分中的说明进行操作:

Consuming NumPy arrays

If all of your input data fit in memory, the simplest way to create a Dataset from them is to convert them to tf.Tensor objects and use Dataset.from_tensor_slices().

# Load the training data into two NumPy arrays, for example using `np.load()`.
with np.load("/var/data/training_data.npy") as data:
  features = data["features"]
  labels = data["labels"]

# Assume that each row of `features` corresponds to the same row as `labels`.
assert features.shape[0] == labels.shape[0]

dataset = tf.data.Dataset.from_tensor_slices((features, labels))

在文件不适合内存的情况下,似乎唯一推荐的方法是先将 npy 数据转换为 TFRecord 格式,然后使用 TFRecord数据集格式,无需完全载入内存即可流式传输。

Here is a post with some instructions.

FWIW,TFRecord 不能直接用 npy 文件的目录名或文件名实例化,这对我来说似乎很疯狂,但这似乎是普通 Tensorflow 的限制。

如果您可以将单个大型 npy 文件拆分为较小的文件,每个文件大致代表一批用于训练,那么您可以在 Keras 中编写一个自定义数据生成器,它只会生成当前批次所需的数据。

一般来说,如果您的数据集无法容纳在内存中,将其存储为一个大型 npy 文件会非常难以处理,最好您首先重新格式化数据,作为 TFRecord 或多个 npy 文件,然后使用其他方法。

您可以使用 tf.py_func 来完成,请参阅示例 here。 parse 函数会简单地将文件名从字节解码为字符串并调用 np.load.

更新:像这样:

def read_npy_file(item):
    data = np.load(item.decode())
    return data.astype(np.float32)

file_list = ['/foo/bar.npy', '/foo/baz.npy']

dataset = tf.data.Dataset.from_tensor_slices(file_list)

dataset = dataset.map(
        lambda item: tuple(tf.py_func(read_npy_file, [item], [tf.float32,])))

实际上可以使用 TensorFlow 而不是 TFRecords 直接读取 NPY 文件。关键部分是 tf.data.FixedLengthRecordDataset and tf.io.decode_raw, along with a look at the documentation of the NPY format。为简单起见,我们假设给出了一个包含形状为 (N, K) 的数组的 float32 NPY 文件,并且您事先知道特征的数量 K,以及它是一个 float32 数组这一事实。 NPY 文件只是一个带有小 header 的二进制文件,后面是原始数组数据(object 数组不同,但我们现在正在考虑数字)。简而言之,你可以用这样的函数找到这个 header 的大小:

def npy_header_offset(npy_path):
    with open(str(npy_path), 'rb') as f:
        if f.read(6) != b'\x93NUMPY':
            raise ValueError('Invalid NPY file.')
        version_major, version_minor = f.read(2)
        if version_major == 1:
            header_len_size = 2
        elif version_major == 2:
            header_len_size = 4
        else:
            raise ValueError('Unknown NPY file version {}.{}.'.format(version_major, version_minor))
        header_len = sum(b << (8 * i) for i, b in enumerate(f.read(header_len_size)))
        header = f.read(header_len)
        if not header.endswith(b'\n'):
            raise ValueError('Invalid NPY file.')
        return f.tell()

有了它,您可以创建这样的数据集:

import tensorflow as tf

npy_file = 'my_file.npy'
num_features = ...
dtype = tf.float32
header_offset = npy_header_offset(npy_file)
dataset = tf.data.FixedLengthRecordDataset([npy_file], num_features * dtype.size, header_bytes=header_offset)

此数据集的每个元素都包含一长串字节,表示一个示例。您现在可以对其进行解码以获得实际数组:

dataset = dataset.map(lambda s: tf.io.decode_raw(s, dtype))

但是,元素将具有不确定的形状,因为 TensorFlow 不会跟踪字符串的长度。你可以强制执行形状,因为你知道特征的数量:

dataset = dataset.map(lambda s: tf.reshape(tf.io.decode_raw(s, dtype), (num_features,)))

同样,您可以选择在批处理后执行此步骤,也可以根据自己的喜好组合。

限制是你必须提前知道特征的数量。从 NumPy header 中提取它是可能的,只是有点麻烦,而且在任何情况下都很难从 TensorFlow 中提取它,因此需要提前知道文件名。另一个限制是,实际上,该解决方案要求您为每个数据集仅使用一个文件或具有相同 header 大小的文件,尽管如果您知道所有数组都具有相同的大小,那么实际上应该是案.

诚然,如果考虑这种方法,最好有一个没有 headers 的纯二进制文件,并且对特征数量进行硬编码或从不同的来源读取它们。 .

设置问题

我有一个包含图像的文件夹,这些图像被输入到 InceptionV3 模型中以提取特征。这似乎是整个过程的一个巨大瓶颈。作为解决方法,我从每个图像中提取特征,然后以 .npy 格式将它们存储在磁盘上。

现在我有两个文件夹,一个用于图像,一个用于相应的 .npy 文件。 tf.data.Dataset 管道中的 .npy 文件加载存在明显问题。

解决方法

我在 show attend and tell 上看到了 TensorFlow 的官方教程,它对这个线程(和我)遇到的问题有很好的解决方法。

加载 numpy 文件

首先,我们需要创建一个映射函数,它接受 .npy 文件名和 returns numpy 数组。

# Load the numpy files
def map_func(feature_path):
  feature = np.load(feature_path)
  return feature

使用tf.numpy_function

使用 tf.numpy_function 我们可以包装任何 python 函数并将其用作 TensorFlow 操作。该函数必须接受 numpy 对象(这正是我们想要的)。

我们用所有 .npy 文件名的列表创建一个 tf.data.Dataset

dataset = tf.data.Dataset.from_tensor_slices(feature_paths)

然后我们使用 tf.data.Dataset API 的 map 函数来完成剩下的任务。

# Use map to load the numpy files in parallel
dataset = dataset.map(lambda item: tf.numpy_function(
          map_func, [item], tf.float16),
          num_parallel_calls=tf.data.AUTOTUNE)