HDF5格式数据集的引入

1、HDF5 简介

层级数据格式(Hierarchical Data Format:HDF)是设计用来存储和组织大量数据的一组文件格式(HDF4,HDF5)。它最初开发于美国国家超级计算应用中心,现在由非营利社团HDF Group支持,其任务是确保HDF5技术的持续开发和存储在HDF中数据的持续可访问性。

伴随着这个目标,HDF库和相关工具可在自由的类BSD许可证下获得用于一般使用。HDF被很多商业和非商业软件平台所支持,包括Java、MATLAB、Scilab、Octave、Mathematica、IDL、Python、R、Fortran和Julia。可免费获得的HDF发行中包括了库,命令行实用程序,测试包源代码,Java接口,和基于Java的HDF查看器(HDFView)[1]。

当前版本是HDF5,在设计和API上与主要的遗留版本HDF4有显著区别。

摘自 维基百科
https://zh.wikipedia.org/zh-cn/HDF

HDF5 文件一般以 .h5 或者 .hdf5 作为后缀名,需要专门的软件才能打开预览文件的内容。HDF5 文件结构中有 2 primary objects: Groups 和 Datasets。

  • Groups 就类似于文件夹,每个 HDF5 文件其实就是根目录 (root) group’/‘,可以看成目录的容器,其中可以包含一个或多个 dataset 及其它的 group。
  • Datasets 类似于 NumPy 中的数组 array,可以当作数组的数据集合 。

每个 dataset 可以分成两部分: 原始数据 (raw) data values 和 元数据 metadata (a set of data that describes and gives information about other data => raw data)。

整个 HDF5 文件的结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+-- /
| +-- group_1
| | +-- dataset_1_1
| | | +-- attribute_1_1_1
| | | +-- attribute_1_1_2
| | | +-- ...
| | |
| | +-- dataset_1_2
| | | +-- attribute_1_2_1
| | | +-- attribute_1_2_2
| | | +-- ...
| | |
| | +-- ...
| |
| +-- group_2
| | +-- dataset_2_1
| | | +-- attribute_2_1_1
| | | +-- attribute_2_1_2
| | | +-- ...
| | |
| | +-- dataset_2_2
| | | +-- attribute_2_2_1
| | | +-- attribute_2_2_2
| | | +-- ...
| | |
| | +-- ...
| |
| +-- ...
|

2、如何使用 HDF5

HDF5 是一种特殊的数据格式,因此无法被直接打开。需要特殊的译码工具才能读取到真正的数据。一般来说,工具可以分为以下几种:

  • 集成工具
  • 语言插件

集成工具一般指能够提供:打开、新建、修改、保存。从而实现对文件的管理。但 HDF5 是一种面向大数据的数据格式,用作数据集的场景下比较多。对于语义并不看重,因此集成式的工具并不讨好。

语言插件一般是 HDF5 官方提供文件驱动,其它语言的开源组织进行开发,提供给其它开发者使用的代码库。也有不调用官方驱动,而是自己根据编码格式实现了读写等操作的工具库。我在这里将同时讲解两个方式,我自己使用的是 C++ 语言上社区的工具库 HighFive ,以及 HDF5 官方开源的集成预览工具

3、在 Linux 上使用 HighFive 库管理 HDF5

3.1 HighFive 简介

HighFive 是一个现代的、只有头文件的、 C++11 友好的 libhdf5 接口。

HighFive 支持 STL Vector/string,Boost: : UBLAS,Boost: : Multi-array 和 Xtensor。它处理 C++ 从/到 HDF5 的自动类型映射。HighFive 不需要额外的库(参见依赖项)。

它通过定义(并导出) HighFive 对象的形式,可以与其他 CMake 项目很好地集成在一起。

HighFive 官方文档:https://bluebrain.github.io/HighFive/

3.2 集成 HighFive 至 CMAKE 项目

我是按照官方文档直接安装的,这是官方指导文档,描述的非常清晰:

https://bluebrain.github.io/HighFive/md__2home_2runner_2work_2_high_five_2_high_five_2doc_2installation.html

HighFiveHDF5 官方库上层的工具库,因此需要先安装 HDF5 后才能安装 HighFive

3.2.1 安装 HDF5

总的说是以下几步:

  • 使用系统包管理器安装
  • 在一个项目中使用模块
  1. 使用系统包管理器直接安装

我的系统是 Ubuntu , 直接使用系统包管理器即可安装

1
sudo apt-get install libhdf5-dev
  1. 在 CMakeLists.txt 中引入依赖

示例依赖如下:

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.19)
project(Dummy)

find_package(HDF5 REQUIRED)
add_executable(dummy dummy.cpp)
target_link_libraries(dummy HDF5::HDF5)

也就是通过 find_package 指令引入依赖,并在对应的可执行文件中加入依赖,也就是这几行:

1
2
3
find_package(HDF5 REQUIRED)

target_link_libraries(你的执行入口 HDF5::HDF5)
  1. 测试是否引入成功

官方给出了一段测试代码,如下:

1
2
3
4
5
6
7
#include <hdf5.h>

int main() {
auto file = H5Fcreate("foo.h5", H5F_ACC_EXCL, H5P_DEFAULT, H5P_DEFAULT);
H5Fclose(file);
return 0;
}

你可以直接将其设置为启动对象并测试。

3.2.2 安装 HighFive

HighFive 是一个远不如 HDF5 官方库热门的项目,因此并没有被大部分系统包管理器所管理。

官方提供了 Spack 上的一键安装,但我没有 Spack , 因此选择手动安装。如果你已经有 Spack 了,那么可以通过 Spack 一键安装,具体可见:

https://bluebrain.github.io/HighFive/md__2home_2runner_2work_2_high_five_2_high_five_2doc_2installation.html

我选择手动克隆并编译,具体操作如下:

  1. 克隆

    1
    2
    3
    4
    git clone --recursive https://github.com/BlueBrain/HighFive.git
    cd HighFive
    git checkout v2.8.0
    git submodule update --init --recursive
  2. 配置、编译、安装

执行下面的命令:

1
2
3
cmake -DCMAKE_INSTALL_PREFIX=build/install -DHIGHFIVE_USE_BOOST=Off -B build .
cmake --build build --parallel
cmake --install build

第一步遇到了报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CMake Error at CMakeLists.txt:167 (add_subdirectory):
The source directory

/home/HighFive-master/deps/catch2

does not contain a CMakeLists.txt file.


CMake Error at tests/unit/CMakeLists.txt:2 (include):
include could not find requested file:

Catch


CMake Error at tests/unit/CMakeLists.txt:14 (catch_discover_tests):
Unknown CMake command "catch_discover_tests".


-- Configuring incomplete, errors occurred!

定位后是没有 Catch2 。Catch2 是一个测试库, HighFive 使用它进行测试,如果出现了这个报错,可以执行下面这行代码来取消测试,这是官方给出的解决方法。

1
git submodule update --init --recursive
  1. 验证

和刚刚的验证一样,可以通过编写代码进行测试:

1
2
3
4
5
6
#include <highfive/highfive.hpp>

int main() {
auto file = HighFive::File("foo.h5", HighFive::File::Create);
return 0;
}

CMakeLists.txt 中做如下修改:

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.19)
project(UseHighFive)

find_package(HighFive REQUIRED)
add_executable(dummy dummy.cpp)
target_link_libraries(dummy HighFive)

官方文档没有在这节强调,而是在上节的安装后提了一嘴。

这个安装不是全局安装,因此你需要在对应项目中指定安装的位置,下面是原文:

Later you’d pass the installation directory, i.e. ${PWD}/build/install, to CMAKE_PREFIX_PATH.

所以我们的 CMakeLists.txt 还要加一项修改,找到你克隆项目的目录作为 PWD ,然后拼接得到安装路径,来指明软件/库安装路径前缀

1
list(APPEND CMAKE_PREFIX_PATH "/home/HighFive/build/install") # 安装路径

因此,最后的修改为:

1
2
3
4
5
# HDF5 文件管理工具构建
list(APPEND CMAKE_PREFIX_PATH "/home/HighFive/build/install") # 安装路径
find_package(HighFive REQUIRED)
add_library(dataset_util INTERFACE util/dataset_util.h)
target_link_libraries(dataset_util INTERFACE HighFive)

4、在 windows 上使用 hdfview 预览数据集

直接下载官方提供的预览器。下面是官网:

https://github.com/HDFGroup/hdfview/releases

安装后随便打开一个HDF5文件,即可看到文件的组织结构。我这里拿 fashion-mnist-784-euclidean.hdf5 这个数据集来举例,打开后可以看到是如下图的界面:

hdf5_1.png

这里展示了一行数据,这是本数据集专有的 距离算法 的信息。这行的四个列分别表示:数据名称是 distance ,数据在 HDF5 中的存储格式是 UTF8 + 变长 + String + 以HDF5的空格分隔符分割 的格式存储的;数组长度是标量;数组内容是 euclidean

翻译一下,其实就是存储了一个名为 distance 的字符串,内容是 euclidean 。这其实是我们这个大数据集的一些特殊信息,这个数据集是一个向量相似度计算的数据集,所以需要特意声明这个数据集适合的距离计算公式,也就是 euclidean

hdf5_2.png

这部分的内容是独立存在的,并不属于任何一个子数据集。

接着我们可以看到左侧有若干个数据集,如下图:

hdf5_3.png

这是我们真正要使用的数据集,其中包含了四个数据集。任意点开数据集,可以发现并没有直接显示出来数据,但是我们可以通过双击数据集名称来预览数据集,以及通过点击 General Object Info 来阅读数据集的各种信息。我这里拿 test 数据集来举例:

hdf5_4.png

General Object Info 后可以阅读数据集的对象信息,最重要的是核对两项,维度 (demension) 和 数据类型 (data type)。维度数据是 10000 * 784 ,数据类型是 32bit float ,接着我们再去核对数据集官方的介绍:

hdf5_5.png

PS:我的数据集来自 ANN-Benchmarks 项目
项目地址: https://github.com/erikbern/ann-benchmarks/tree/main

可以看到,是和官方描述完全一致的, 10000 * 784 中的 784 是单条向量的维度, 10000 是测试向量的数量。以此为例,你能够读懂这个数据集中每个 item 的含义了。

双击数据集名称后,可以直接预览数据集的具体数据:

hdf5_6.png

可以看到和刚刚 General Object Info 中展示的一致。

5、在代码中解析使用数据集

通过上面步骤 4 ,我们预览了数据集,确定了数据集中数据的格式。以 test 数据集为例,我们知道了该数据集是一个 2 维的 float 数组。(32 bit 浮点数在 c++ 中是4字节的单精度浮点数 float ,数据是 10000 * 784 代表数据集是二维的)

因此我们需要查询我们的代码工具库,看看如何才能将数据解析成对象来使用。下面是我找到的官方示例,实例中的数据是一个 double 类型的二维数组,与我们的要求一致。

https://github.com/BlueBrain/HighFive/blob/master/src/examples/select_partial_dataset_cpp11.cpp

因此,在对代码进行一定程度的修改,就可以使用了。我的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#pragma once
#ifndef DATASET_UTIL_H_
#define DATASET_UTIL_H_

#define DATASET_FOLDER "/home/hnsw_dataset/"
#define FASHION_MNIST "fashion-mnist-784-euclidean.hdf5"

#include <highfive/highfive.hpp>

namespace hnswlib_experiment {

class DatasetUtil {
public:

void openDataset() {

using namespace HighFive;

// 以只读模式打开文件
std::string dataset_url = DATASET_FOLDER;
dataset_url.append(FASHION_MNIST);
HighFive::File file(dataset_url, File::ReadOnly);

std::vector<float> test;

// Get the dataset
auto dataset = file.getDataSet("test");

// now we read back 10000x784 values
std::vector<std::vector<float>> result;
dataset.select({0, 0}, {10000, 784}).read(result);

// we print out 4 values
for (auto i: result) {
for (auto j: i) {
std::cout << " " << j;
}
std::cout << "\n";
}
}
};

} // hnswlib_experiment

#endif // DATASET_UTIL_H_

在输出行打个断点,只输出一个向量,可以看到结果如下:

hdf5_7.png

我们打开数据集预览,核对一下,发现无误:

hdf5_8.png

结语

到这里,我们的教程就结束了。希望能帮你节省一些时间。

授人以鱼不如授人以渔,多查阅官方文档和社区,增强自己解决问题的能力才是王道。我的配置教程全部以官方文档为主线,在遇到问题时查询 Google