单个大型矩阵究竟该如何存储?

.mat与.h5之间的抉择

Posted by Y's Blog on June 20, 2019

前言

最近,某Y在创建模型的训练数据集时遇到了一个非常头疼的问题--生成的训练数据占用了过大的存储空间致使磁盘爆满!可能因为以前都是在体量较小的数据库上进行研究,故而第一次碰到这个问题的某Y是非常崩溃的。相信被下面这句话支配的恐惧感(尤其是在没有管理员权限的服务器上)很多和某Y一样的小白都深有感触吧 😭

Unable to create xxx: Disk quota exceeded

然而也正是这句话促使了某Y认真研究起数据的存储格式以及不同存储格式之间的效率问题。

通常,由于MATLAB是科研工作者的常用工具,.mat也成了最常见的数据存储格式之一。然而,当遇到大型矩阵时,.mat究竟是不是一个最有效的存储格式呢?有没有什么更有效的存储格式呢?(结论可直接跳至这里)下面某Y通过两组实验来对比下.mat.h5的存储效率:

Experiment 1: 大型随机矩阵

在这个实验中, 我们着重比较的是两种不同存储格式对于大型随机矩阵的存储效率。首先,我们通过numpy创建一个10000 × 10000的大型随机矩阵

import numpy as np
a = np.random.rand(10000, 10000)

随后,我们将分别利用python中提供的.mat存储工具包scipy以及.h5的存储工具h5py来存储这个大型随机矩阵。

1.1: .mat × scipy

这里我们首先导入scipy并利用其中的savemat函数进行存储。存储的文件名为matfile.mat。注意存储对象需编码成字典形式才能存储{'variable_name':data}。此处我们将矩阵存储为变量elem

import scipy.io as sio
sio.savemat('matfile.mat', {'elem':a})

存储完毕后我们进入到数据存储位置并使用如下命令检查文件大小

ls -lh matfile.mat

可以看到系统输出的文件大小为763M

接下来我们尝试用h5py来存储该矩阵。

1.2: .h5 × h5py

HDF的全称是Hierarchical Data Format(层级数据格式)。这是一种专门用来存储和管理大型数据的文件格式。其最初是由美国国家超级计算应用中心(NCSA)开发,现在则由非营利社团HDF Group管理运营。在这里我们提到的.h5是该格式的第五代,也就是HDF5对应的文件名后缀。python中的相关工具包为h5py。

这里我们首先导入h5py并以其中的File object对象来存储和管理数据。此处h5file.h5为待创建的存储文件。和使用savemt相同,这里我们需要先定义存储对象为elem, 再将矩阵以data=的形式赋予该变量

import h5py
with h5py.File('h5file.h5', 'w') as hf:
    hf.create_dataset('elem', data=a)

存储完毕后我们进入到数据存储位置并使用如下命令检查文件大小

ls -lh h5file.h5

文件大小依旧为763M!!某Y看到这个的时候内心是绝望的,难道说好的高效牛p都是骗人的吗?难道我的磁盘空间没救了吗?带着这个疑问,某Y又做了详细的功课并发现一个惊人的功能--压缩。是的,h5py还提供了compressioncompression_opts这两个变量可供设置。而在compression中,共有三种压缩方式可供选择:gziplzfszip!这可不得了,我们赶紧试验下利用压缩后文件会有怎样的变化,这里我们选择文档中推荐的压缩方法gzip,同时compression_opts我们尝试了默认值4以及最高值9

with h5py.File('h5file_com4.h5', 'w') as hf:
    hf.create_dataset('elem', data=a, compression='gzip', compression_opts=4)
    
with h5py.File('h5file_com9.h5', 'w') as hf:
    hf.create_dataset('elem', data=a, compression='gzip', compression_opts=9)

压缩后的文件体积缩小了!两种压缩效率得到的文件均为720M!另外值得一提的是使用默认值4的时候,存储速度明显慢于最高值9。这时候可能有人就会质疑了:会不会有信息损失呢?为了回答这个疑问,我们继续做了下面实验,从存储文件中将我们的随机矩阵读入python并与原始矩阵对比

with h5py.File('h5file_com4.h5', 'r') as hf:
    a_com4 = np.array(hf['elem'])

with h5py.File('h5file_com9.h5', 'r') as hf:
    a_com9 = np.array(hf['elem'])
    
np.sum(a_com4-a), np.sum(a_com9-a)

对比结果是

(0.0, 0.0)

等等,那么scipy在存储.mat时是否提供了压缩功能了呢?仔细查询文档后发现,scipy也提供了压缩功能!某Y真的白用了这么久的scipy(希望我不是一个人)。。。自惭形愧🙈。。。为了测试scipy的压缩效率,我们将参数do_compression设置为True

sio.savemat('matfile_com.mat', {'elem':a}, do_compression=True)

压缩后的文件大小也为720M,与h5py压缩后的文件大小相同。

1.3: 耗时

在压缩率相同的情况下,我们进一步比较了耗时。经测试,scipy耗时49.3秒,而h5py耗时39.9秒。

不多说了,h5py用起来好嘛!

Experiment 2: 大型稀疏矩阵

某Y突然想到,自己的数据集中,样本大部分为大型的稀疏矩阵,那么不同存储方式是否会有不同的存储效率呢?为此我们设计了第二个实验。稀疏矩阵指的是大部分元素为零的矩阵。因此在存储此类矩阵时,不同的存储方式通常都会采取一定的措施大幅度压缩文件体积。这里我们在极端情况,即矩阵元素全部为0,下比较不同的存储方式之。

a = np.zeros((10000, 10000))

2.1: scipy × .mat

首先我们用scipy来对矩阵进行存储

sio.savemat('matfile.mat', {'elem':a})

存储完成后,我可以看到未经压缩的文件大小为763M。接下来我们命令scipy进行压缩存储

sio.savemat('matfile_com.mat', {'elem':a}, do_compression=True)

进行压缩存储得到的.mat文件为760K!!!

2.2: h5py × .h5

接下来我们用h5py进行压缩存储

with h5py.File('h5file.h5', 'w') as hf:
    hf.create_dataset('elem', data=a, compression='gzip', compression_opts=9)

最终生成大小为1.3M的.h5文件。虽然压缩效率低于scipy,但是相较于原文件,其大小也只有最初的0.17%

1.3: 耗时

在存储大型稀疏矩阵时,scipy耗时8.5秒,而h5py耗时4.9秒。可见h5py虽然存储效率低于scipy,但是却有着较高的压缩速度。

结论

通过上面的两组实验,我们简单总结如下:

  • 如果你只在乎压缩后文件的大小,请用.mat格式进行存储。不过一定要记得do_compression=True
  • 如果你只在乎压缩文件的耗时,请用.h5格式进行存储,并将compression_opts设置为9
  • 如果你没有特别在乎的方面或者使用喜好的话,综合考虑可选择.h5格式进行存储(毕竟不是所有的情况都是全0矩阵)

后话

某Y发现了这个后对之前创建的数据进行了压缩存储(.h5)。通过du -lh查看后,某Y惊喜地发现其中一个数据集从193G压缩到了16G,而另一个数据集则是从556G压缩到了48G!!!要知道服务器上分配的空间才1T!!!anyway,得救了😆😆😆;当然还有很多存储特性没有在这里分析,比如h5py的group特性。写这篇博客的初衷是希望大家不要踩我踩过的坑,希望大家科研学习生活顺利😉~~~