返回介绍

3.1 数据集基础

发布于 2024-01-25 22:02:55 字数 6406 浏览 0 评论 0 收藏 0

首先,让我们创建一个文件来保存数据集:

HDF5文件里的任何数据集都有一个名字。让我们看看当我们把一个新的NumPy数组赋给文件中任意一个名字会发生什么:

我们存入的是一个NumPy数组,但取出的却是另外的东西:一个h5py.Dataset类的实例。这是一个代理对象,它会代理你的请求读写磁盘上的HDF5数据集。

3.1.1 类型和形状

让我们浏览这个Dataset对象。如果你使用的是IPython,输入“dset.”然后按TAB键查看对象特征;如果不是IPython,输入“dir(dset)”。输出会有很多,我们着重关注dtype和shape:

每一个数据集的类型都在创建时就被确定下来不可更改。HDF5有一个丰富的类型机制可以轻松处理NumPy内建类型,仅有极少数的例外情况。因此,h5py使用NumPy的dtype对象来表示数据集的类型。

还有一个我们已经熟悉的特征shape:

一个数据集的形状也是在创建时被定义,不过之后我们会看到它是可以被改变的。和NumPy数组一样,HDF5数据集的维度可以从0维(标量,shape为())一直到32维,每一维最多可以有263−1个元素。

3.1.2 读和写

数据集的作用就是让我们获取其中的数据。首先让我们看看当我们读取整个数据集时会发生什么。

对一个Dataset对象进行切片操作会返回一个NumPy数组。这背后实际发生的事情是:h5py根据你的切片选择查找数据集对应的部分,然后让HDF5从磁盘上读取数据。也就是说,如果不考虑缓存,一个切片操作就会对磁盘进行一次读或写。

接下来再让我们看看数据集的部分更新。

成功!

提示


 Dataset对象跟NumPy数组是如此相似,以至于你可能会在运算代码中将两者混用。一开始它也许可以工作,但是由于Dataset的数据是在磁盘上而不是内存中,所以这么做最终都会导致性能出现问题。

3.1.3 创建空数据集

你不需要准备好NumPy数组来创建数据集。File对象的create_dataset方法可以根据你指定的形状和类型创建空数据集。你也可以仅指定形状,此时默认会使用np.float32(单精度浮点)作为类型。

HDF5在磁盘空间的分配上非常聪明,它会分配刚够满足写入数据需要的空间。这里有一个例子,假设你创建了一个1维数据集能够装下4GB数据。

现在我们往里面写数据。为了公平起见,我们要求HDF5冲洗缓存将数据实际写入磁盘。

看看磁盘上实际的文件大小。

3.1.4 显式指定存储类型来节省空间

说到类型,几秒钟的思考可以节省你大量的磁盘空间和I/O时间。create_dataset方法可以接受几乎任何NumPy的dtype类型,更关键的是它甚至不需要完全符合你之后写入数据集的真实数据的类型。

HDF5常被用于存储浮点数据,比如数字转换器的时序、股票价格和计算机仿真等。基本上任何“真实世界”中的数据都不是整型。

通常为了保持计算精度,让取整带来的误差最小,我们会在内存中使用8字节双精度浮点(NumPy dtype float64)进行计算。但是在将这些数据存入磁盘的时候会选择4字节单精度浮点(float32),节省了一半的文件大小。

假设我们有这样一个NumPy数组,名叫bigdata:

我们可以用一个简单的赋值语句将其存入文件,建立一个双精度浮点数据集:

或者我们可以指定HDF5以单精度浮点保存它:

你指定哪个dtype,你的数据就以哪种类型保存:

3.1.5 自动类型转换和直读

但是数据究竟应该在什么时候以及什么情况下从内存中的双精度转换到文件中的单精度呢?这个问题性能攸关。毕竟,如果你有一个占用了90%内存的数据集,而你又需要在保存它之前先复制一份,那问题就出现了。

HDF5库自己会处理类型转换,而且还是在读写文件时“同时进行”。Python层面上不会发生任何事。你的数组进去,对应的字节写入磁盘。HDF5内建了很多函数对应处理各种格式之间的转换,包括NumPy支持的所有浮点和整型类型。

那么反过来又会如何呢?假设我们磁盘里有一个单精度浮点数据集,但希望读出来的是一个双精度浮点数据集可不可以?有一些理由支持我们这个需求。比如,结果集可能非常大,在Python中进行转换时无法同时在内存中装下单精度和双精度的版本。又或者我们希望从磁盘读取时就“同时进行”类型转换以节省应用程序的运行时间。

对于大数组,最好的办法是直接读入一个预分配的指定类型的NumPy数组。假设我们已有上面例子的那个单精度数据集,并希望读入一个双精度NumPy数组:

我们在Python这边先分配一个新的双精度数组:

这里的np.empty创建了一个数组,和np.zeros或np.ones不同之处在于它不会对数组元素进行初始化。现在我们请求HDF5将数据直接读入我们的输出数组:

搞定!HDF5将我们请求的数据填入空数组。不需要多余的数组或转换时间。

提示


 使用read_direct时不需要每次都读入整个数据集。

3.1.6 用astype读

有时候你可能会觉得创建一个数组然后传给read_direct这种做法还是太麻烦了。还有另一个方法可以告知HDF5你需要的类型:使用数据集对象上附加的astype环境管理器。

这里有一个例子。假设我们想要读取上面例子中数据集的前1 000个元素,并让HDF5帮我们将它们从单精度转换成双精度:

当你用read_direct或astype从数据集中读取数据时,或者当你将NumPy数据写入已存在的数据集时,HDF5都会自动进行类型转换。下面是一些小建议。

1.通常来说,你只能在相通类型之间进行转换。比如,你可以将整型转成浮点,浮点转成其他精度的浮点,但你不能将字符串转成浮点或整型。尝试这么做只会让你得到一个没有任何帮助的IOError。

2.当你试图转换成“较小”的类型(如float64转换成float32,或“S10”转换成“S5”)时HDF5会对值进行取整或“截断”:

当此类事情发生时程序不会有任何警告,所以你必须自己追溯涉及的类型。

3.1.7 改变形状

这是create_dataset的又一个神秘玄机。之前提到过在dtype之外还有一个“shape”参数。你可以在创建数据集时指定一个跟输入的数组不同的形状,只要两个形状的元素个数相等。

假设我们有一个数组保存了100张640×480像素的图片,每张图片由480根“扫描线”组成,每根扫描线有640个像素点:

现在假设我们需要将每张图片保存成“上半部分”和“下半部分”而又不需要在读取的时候进行切片,那么只需创建一个数据集并指定新的形状:

这么做不会有任何性能损失。就好像内建函数np.reshape一样,只有索引重新进行了调整。

3.1.8 默认填充值

当你创建一个全新数据集时,你会发现默认值填的都是0:

某些应用程序会期望是非零的默认值。比如让未修改的元素等于-1或在浮点数据集中用NaN。

HDF5提供了一个默认填充值。在你读取数据集中未写入的区域时会返回该值。默认填充值在数据读取时处理,所以它不占用实际的存储空间。该值在创建数据集时定义,且不可更改:

fillvalue属性返回数据集的默认填充值:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文