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

Posted

技术标签:

【中文标题】将 .npy(numpy 文件)输入 tensorflow 数据管道【英文标题】:Feeding .npy (numpy files) into tensorflow data pipeline 【发布时间】:2018-07-31 01:32:46 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

您可以使用 tf.py_func 来实现,参见示例 here。 解析函数将简单地将文件名从字节解码为字符串并调用 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,])))

【讨论】:

"解析函数将简单地将文件名从字节解码为字符串并调用 np.load。"你能提供一个代码吗? 呃会不会很慢...? 是的,确认这很慢。 Python 读取 Numpy 后增加了很多开销。 @ch271828n 慢到什么程度?【参考方案2】:

实际上可以使用 TensorFlow 而不是 TFRecords 直接读取 NPY 文件。关键部分是tf.data.FixedLengthRecordDatasettf.io.decode_raw,以及查看NPY format 的文档。为简单起见,假设给定一个包含形状为(N, K) 的数组的float32 NPY 文件,并且您事先知道K 的特征数量,以及它是一个float32 数组的事实。 NPY 文件只是一个带有小标题的二进制文件,后跟原始数组数据(对象数组不同,但我们现在正在考虑数字)。简而言之,你可以用这样的函数找到这个 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 标头中提取它,只是有点麻烦,而且在任何情况下都很难从 TensorFlow 中提取,因此需要提前知道文件名。另一个限制是,实际上,该解决方案要求您要么每个数据集仅使用一个文件,要么使用具有相同标头大小的文件,尽管如果您知道所有数组都具有相同的大小,那么实际上应该是这种情况。

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

【讨论】:

从 tensorflow 2.0 开始,tf.decode_raw 已移至 tf.io.decode_raw。 tensorflow.org/api_docs/python/tf/io/decode_raw?hl=en【参考方案3】:

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

使用 NumPy 数组

如果您的所有输入数据都适合内存,从它们创建数据集的最简单方法是将它们转换为 tf.Tensor 对象并使用 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 文件,然后使用其他方法。

【讨论】:

我看过该指南,但不幸的是,它不适合记忆! 非常感谢,但是将我的 numpy 文件转换为 TFRecord 是我最不想做的事情,因为我有大约 5,000,000 个文件,而且这需要很长时间。我想我会采用 keras 生成器的想法。再次感谢! 您的 5,000,000 个文件中的每个文件都无法放入内存? 我的情况与 OP 相似,我有大约一百万个小文件,使用简单的 Keras 生成器就像一个魅力。不幸的是,它不能很好地处理多处理并且比 tf.data API 慢,所以我最终将整个数据集转换为 TFRecord 文件,Keras 生成器的性能提高了很多,但这只是我,它可以在其他情况下有所不同。 我遇到过与@jackz314类似的情况,但在我的情况下,加载速度并没有增加。【参考方案4】:

问题设置

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

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

解决方法

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

加载 numpy 文件

首先我们需要创建一个映射函数,它接受.npy 文件名并返回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)

【讨论】:

以上是关于将 .npy(numpy 文件)输入 tensorflow 数据管道的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 numpy.savez 将带有子数组的数组保存到单独的 .npy 文件中

正确将 png 转换为 npy numpy 数组(图像到数组)

尝试保存 .npy (numpy) 文件时出错

Numpy | 23 IO

Numpy用于数组的文件输入输出

Numpy将矩阵附加到Tensor