You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效存储大量CSR稀疏数组?解决HDF5+Pickle报错问题

问题:如何高效存储10万+CSR格式稀疏数组?

最初尝试将CSR矩阵转为密集数组后存入HDF5,但因一次性分配内存导致内存不足:

from scipy.sparse import csr_matrix
import h5py

As = [ csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]]),
       csr_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
       csr_matrix([[2, 0, 0], [0, 3, 0], [0, 0, 4]]) ]

np_matrices = [mat.toarray() for mat in As]

with h5py.File(filename, "w") as f:
    f.create_dataset("matrices", data=np_matrices)

尝试用Pickle序列化后存入HDF5时,出现错误:VLEN strings do not support embedded NULLs,代码如下:

import pickle
import h5py

pickled_obj = pickle.dumps(As)

with h5py.File('obj.hdf5', 'w') as f:
    dset = f.create_dataset('obj', data=pickled_obj) 

求解决该错误或更高效的存储方案。


解决方案

方法1:拆分CSR核心组件存入HDF5(推荐)

CSR矩阵的核心是data、indices、indptr三个数组,外加shape属性。可以把所有矩阵的这些组件分别存入HDF5,同时记录每个矩阵的shape,既节省内存又避免序列化问题。

示例代码:

from scipy.sparse import csr_matrix, csr_array
import h5py
import numpy as np

As = [ csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]]),
       csr_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
       csr_matrix([[2, 0, 0], [0, 3, 0], [0, 0, 4]]) ]

# 收集所有矩阵的核心数据
all_data = []
all_indices = []
all_indptr = []
all_shapes = []
data_offset = 0

for mat in As:
    all_data.extend(mat.data)
    all_indices.extend(mat.indices)
    # 给当前矩阵的indptr加上全局数据偏移,避免索引冲突
    all_indptr.extend(mat.indptr + data_offset)
    all_shapes.append(mat.shape)
    data_offset += len(mat.data)

# 转为numpy数组
all_data = np.array(all_data)
all_indices = np.array(all_indices)
all_indptr = np.array(all_indptr)
all_shapes = np.array(all_shapes)

# 写入HDF5
with h5py.File('csr_matrices.h5', 'w') as f:
    f.create_dataset('data', data=all_data)
    f.create_dataset('indices', data=all_indices)
    f.create_dataset('indptr', data=all_indptr)
    f.create_dataset('shapes', data=all_shapes)

# 读取还原
with h5py.File('csr_matrices.h5', 'r') as f:
    data = f['data'][:]
    indices = f['indices'][:]
    indptr = f['indptr'][:]
    shapes = f['shapes'][:]

restored_As = []
ptr_pos = 0
for shape in shapes:
    # 提取当前矩阵的indptr并还原偏移
    mat_indptr = indptr[ptr_pos:ptr_pos + shape[0]+1] - indptr[ptr_pos]
    # 提取对应的数据和索引
    mat_data = data[indptr[ptr_pos]:indptr[ptr_pos + shape[0]]]
    mat_indices = indices[indptr[ptr_pos]:indptr[ptr_pos + shape[0]]]
    # 重建CSR矩阵
    restored_mat = csr_array((mat_data, mat_indices, mat_indptr), shape=shape)
    restored_As.append(restored_mat)
    ptr_pos += shape[0] + 1

方法2:修正Pickle+HDF5的存储方式

之前的错误是因为HDF5默认将Pickle二进制数据识别为字符串,而Pickle包含NULL字节。只需创建字节型数据集即可解决:

示例代码:

import pickle
import h5py
import numpy as np

As = [ csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]]),
       csr_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
       csr_matrix([[2, 0, 0], [0, 3, 0], [0, 0, 4]]) ]

pickled_obj = pickle.dumps(As)

# 写入HDF5(指定字节类型)
with h5py.File('obj.hdf5', 'w') as f:
    dset = f.create_dataset('obj', shape=(1,), dtype=h5py.special_dtype(vlen=bytes))
    dset[0] = pickled_obj

# 读取还原
with h5py.File('obj.hdf5', 'r') as f:
    pickled_data = f['obj'][0]
    restored_As = pickle.loads(pickled_data)

方法3:用Zarr分块存储稀疏矩阵

Zarr支持分块存储,适合大规模数据,且原生支持稀疏数组(需安装zarr和sparse库):

示例代码:

from scipy.sparse import csr_matrix
import zarr
from sparse import COO

As = [ csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]]),
       csr_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
       csr_matrix([[2, 0, 0], [0, 3, 0], [0, 0, 4]]) ]

# 创建Zarr存储目录
store = zarr.DirectoryStore('csr_zarr_store')
root = zarr.group(store=store)

# 逐个存入矩阵(CSR转COO格式)
for i, mat in enumerate(As):
    coo_mat = COO.from_scipy_sparse(mat)
    root.create_dataset(f'matrix_{i}', data=coo_mat)

# 读取还原
restored_As = []
for i in range(len(As)):
    coo_mat = root[f'matrix_{i}'][:]
    restored_mat = coo_mat.to_scipy_sparse('csr')
    restored_As.append(restored_mat)

方法4:批量写入单个NPZ文件

手动将多个矩阵的组件打包成字典,存入NPZ文件,轻量且高效:

示例代码:

from scipy.sparse import csr_matrix
import numpy as np

As = [ csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]]),
       csr_matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
       csr_matrix([[2, 0, 0], [0, 3, 0], [0, 0, 4]]) ]

# 构建存储字典
save_dict = {}
for i, mat in enumerate(As):
    save_dict[f'data_{i}'] = mat.data
    save_dict[f'indices_{i}'] = mat.indices
    save_dict[f'indptr_{i}'] = mat.indptr
    save_dict[f'shape_{i}'] = np.array(mat.shape)

# 写入NPZ
np.savez('multiple_csr.npz', **save_dict)

# 读取还原
loaded_dict = np.load('multiple_csr.npz')
restored_As = []
num_matrices = len([k for k in loaded_dict.keys() if k.startswith('data_')])

for i in range(num_matrices):
    data = loaded_dict[f'data_{i}']
    indices = loaded_dict[f'indices_{i}']
    indptr = loaded_dict[f'indptr_{i}']
    shape = tuple(loaded_dict[f'shape_{i}'])
    restored_mat = csr_matrix((data, indices, indptr), shape=shape)
    restored_As.append(restored_mat)

内容的提问来源于stack exchange,提问作者Betelgeuse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 12:50:22