制作数据集¶
一个完整的 XequiNet 数据集包含三个文件,数据集本体 data.lmdb,记录数据集单位信息的文件 info.json,以及记录数据集划分的文件 <split>.json。前两个文件的文件名固定为 data.lmdb 和 info.json,而 <split>.json 的名字可以自定义,因此可以同时存在复数个不同的划分。
LMDB 数据集文件¶
LMDB(Lightning Memory-Mapped Database)是一种高性能的嵌入式键值存储数据库,非常适合作为机器学习数据集的格式。主要优点包括,LMDB 是使用基于内存映射的读写方式,加载数据时的效率非常高,而且支持多进程的并发读写,这一点非常适应分布式训练的需求。
但是 LMDB 好像是不太支持并发写入的(这一点不太确定),而且写入的效率比较一般,因此制作数据集的脚本通常需要运行很长时间。API 又比较抽象,不是很容易掌握(我也只是依葫芦画瓢地在用)。
XequiNet 使用的 LMDB 文件是由一个个键值对组成的,键是从 0 开始的序号所对应的 8 位二进制字节序列,值是一个 XequiData 类数据的实例。
index -> key -> value
0 -> b'\x00\x00\x00\x00\x00\x00\x00\x00' -> xequidata_0
1 -> b'\x01\x00\x00\x00\x00\x00\x00\x00' -> xequidata_1
...
133884 -> b'\xfc\n\x02\x00\x00\x00\x00\x00' -> xequidata_133884
构建数据集的脚本可以参考以下代码:
import lmdb
import pickle
from xequinet.data import XequiData
lmdb_data = lmdb.open(
path="data.lmdb",
map_size=2**40, # Here is 1 TB. You can set other size suitable for your dataset
subdir=False,
sync=False,
writemap=False,
meminit=False,
map_async=True,
create=True,
readonly=False,
lock=True,
)
for i in range(<dataset_len>):
# all the components are torch.Tensor
# components of float points must be consistent (dtype should be the same)
datapoint = XequiData(
atomic_numbers=... # [n_atoms,] int
pos=... # [n_atoms, 3] float/double
pbc=... # [1, 3] bool (optional)
cell=... # [1, 3, 3] float/double (optional)
energy=... # [1,] float/double
forces=... # [n_atoms, 3] float/double
virial=... # [1, 3, 3] float/double
xxx=... # any other property is OK
)
with lmdb_data.begin(write=True) as txn:
key = i.to_bytes(8, byteorder="little")
txn.put(key, pickle.dumps(datapoint))
...
小贴士💡,在训练分子体系的能量性质时,这里推荐扣除分子中每个原子单独的能量,这样可以有效降低能量性质的量级,使训练变得简单。而且原子能量相当于一个常数,因此不会影响梯度性质的计算。
举一个简单的例子,对于水分子 H2O,我们在 B3LYP/def2-SVP 等级下计算单点能,结果为,
而单独计算 H 和 O 原子的单点能结果分别为 \(E(\text{H}) = -0.5012 \text{ Eh}\) 和 \(E(\text{O}) = -74.9987 \text{ Eh}\),所以水分子扣除原子能量之后的能量数值为,
可见量级一下子就减小了下来。此处的原子能量可以使用与标签相同等级的方法计算获得,也可以对整个数据集的能量进行线性拟合获得。
对于周期性的材料体系(也就是 VASP 计算得到的结果),通常不扣除原子能量也可以很好地拟合,因为通常第一性原理软件输出的能量为相对赝势的能量,本身量级不大。如果需要扣除能量的话建议扣除相应元素最稳定单质的能量,也就是拟合生成能(具体单质结构可以在 Material Project 上查询)。
小贴士2🙂,强烈建议使用 Pandas 通过 csv 或 parquet 等格式的文件记录数据集的信息,包括序号、化学组分、所属子集、能量值等简单且重要的信息,比如像这样,
| Index | Formula | Subset | Energy | Charge | ... |
|---|---|---|---|---|---|
| 0 | CH4 | monomer | -3.054 | 0 | |
| 1 | H2O | monomer | -3.032 | 0 | |
| 2 | H3O+ | ion | -3.033 | 1 | |
| ... |
这样的好处是,因为 LMDB 读起来还是很费劲的,所以要对数据集进行统计或筛选时,不需要每次都打开 LMDB 进行信息获取,只需要读轻量级的 Pandas Frame 就可以了。
INFO 文件¶
info.json 文件主要是用来储存数据集的相关信息的,其中大部分信息其实是写给自己和别人看的,比如计算等级、数据集来源等等。只有单位部分是程序真正会去读的。比如下面这个示例:
{
"units": {
"energy": "eV",
"pos": "Ang",
"forces": "eV/Ang"
},
"method": "XYGJ-OS/cc-pVQZ",
"atomic_energies": {
"H": -0.5,
"C": -37.8,
"O": -75.1
}
}
在这个 JSON 文件中,程序在加载数据时只会读取这一部分,
{
"units": {
"energy": "eV",
"pos": "Ang",
"forces": "eV/Ang"
}
}
这里的单位就是 LMDB 文件中,数据所使用的单位。由于不同的数据集使用的单位都不一样,一般量化软件喜欢用原子单位,第一性原理软件用电子伏特会多一点。程序会根据声明的单位,将数据的单位换算成模型使用的单位。具体支持的单位见单位。
小贴士👀,请注意 JSON 文件的格式,推荐使用 vscode 之类的编辑器来判断格式是否正确。
训练、测试和验证集划分文件¶
一般来说数据集需要划分成训练集、验证集和测试集。使用 JSON 文件来划分数据集,如:
{
"trian": [0, 1, 2, 3, 4, 5],
"valid": [6, 7, 8],
"test": [9, 10, 11]
}
该 JSON 文件表示第 0, 1, 2, 3, 4, 5 个数据点为训练集,第 6, 7, 8 个数据点为验证集,第 9, 10, 11 个数据点为测试集。其中训练过程中仅仅会用到训练集和验证集,而测试过程中仅会读取测试集。由于同一数据集可能会用到不同的划分,因此可以同时存在复数个 <split>.json 文件,只需要在 CONFIG 文件中声明所使用的 JSON 文件的名字(不包含 .json 后缀)即可。
数据集存放方式¶
将上述三个文件存放到同一个目录中,这里展示一个例子:
spice-v2/
├── data.lmdb
├── info.json
└── random42.json
数据集存放的路径为 dir_to_data_path/spice-v2/(这个路径也就是之后写在 CONFIG 文件中的数据集路径),其中 data.lmdb 是数据集文件,info.json 是记录单位信息的文件,这两个文件名字是固定的;random42.json 是数据集划分文件,名字是 random42(这个名字是写在 CONFIG 文件中的 split 文件名)。
这里要注意的是,虽然 LMDB 支持并发读,但是并发的太多也是不行的,会很卡,所以如果有多个训练任务需要读取同一个数据集的话,可以复制几份到临时目录上进行读取(比如节点专属的固态硬盘上),避免进程之间互相打架。