HDF5 添加 numpy 数组很慢

HDF5 adding numpy arrays slow

第一次使用 hdf5,你能帮我找出问题所在吗,为什么添加 3d numpy 数组很慢。 预处理需要 3 秒,添加 3d numpy 数组 (100x512x512) 30 秒并随每个样本上升

首先,我创建了 hdf:

def create_h5(fname_):
  """
  Run only once
  to create h5 file for dicom images
  """
  f = h5py.File(fname_, 'w', libver='latest') 

  dtype_ = h5py.special_dtype(vlen=bytes)


  num_samples_train = 1397
  num_samples_test = 1595 - 1397
  num_slices = 100

  f.create_dataset('X_train', (num_samples_train, num_slices, 512, 512), 
    dtype=np.int16, maxshape=(None, None, 512, 512), 
    chunks=True, compression="gzip", compression_opts=4)
  f.create_dataset('y_train', (num_samples_train,), dtype=np.int16, 
    maxshape=(None, ), chunks=True, compression="gzip", compression_opts=4)
  f.create_dataset('i_train', (num_samples_train,), dtype=dtype_, 
    maxshape=(None, ), chunks=True, compression="gzip", compression_opts=4)          
  f.create_dataset('X_test', (num_samples_test, num_slices, 512, 512), 
    dtype=np.int16, maxshape=(None, None, 512, 512), chunks=True, 
    compression="gzip", compression_opts=4)
  f.create_dataset('y_test', (num_samples_test,), dtype=np.int16, maxshape=(None, ), chunks=True, 
    compression="gzip", compression_opts=4)
  f.create_dataset('i_test', (num_samples_test,), dtype=dtype_, 
    maxshape=(None, ), 
    chunks=True, compression="gzip", compression_opts=4)

  f.flush()
  f.close()
  print('HDF5 file created')

然后我运行代码更新hdf文件:

num_samples_train = 1397
num_samples_test = 1595 - 1397

lbl = pd.read_csv(lbl_fldr + 'stage1_labels.csv')

patients = os.listdir(dicom_fldr)
patients.sort()

f = h5py.File(h5_fname, 'a') #r+ tried

train_counter = -1
test_counter = -1

for sample in range(0, len(patients)):    

    sw_start = time.time()

    pat_id = patients[sample]
    print('id: %s sample: %d \t train_counter: %d test_counter: %d' %(pat_id, sample, train_counter+1, test_counter+1), flush=True)

    sw_1 = time.time()
    patient = load_scan(dicom_fldr + patients[sample])        
    patient_pixels = get_pixels_hu(patient)       
    patient_pixels = select_slices(patient_pixels)

    if patient_pixels.shape[0] != 100:
        raise ValueError('Slices != 100: ', patient_pixels.shape[0])



    row = lbl.loc[lbl['id'] == pat_id]

    if row.shape[0] > 1:
        raise ValueError('Found duplicate ids: ', row.shape[0])

    print('Time preprocessing: %0.2f' %(time.time() - sw_1), flush=True)



    sw_2 = time.time()
    #found test patient
    if row.shape[0] == 0:
        test_counter += 1

        f['X_test'][test_counter] = patient_pixels
        f['i_test'][test_counter] = pat_id
        f['y_test'][test_counter] = -1


    #found train
    else: 
        train_counter += 1

        f['X_train'][train_counter] = patient_pixels
        f['i_train'][train_counter] = pat_id
        f['y_train'][train_counter] = row.cancer

    print('Time saving: %0.2f' %(time.time() - sw_2), flush=True)

    sw_el = time.time() - sw_start
    sw_rem = sw_el* (len(patients) - sample)
    print('Elapsed: %0.2fs \t rem: %0.2fm %0.2fh ' %(sw_el, sw_rem/60, sw_rem/3600), flush=True)


f.flush()
f.close()

速度缓慢几乎可以肯定是由于压缩和分块造成的。很难做到这一点。在我过去的项目中,我经常不得不关闭压缩,因为它太慢了,尽管我并没有放弃一般的 HDF5 压缩的想法。

首先您应该尝试确认压缩和分块是导致性能问题的原因。关闭分块和压缩(即省略 chunks=True, compression="gzip", compression_opts=4 参数)并重试。我怀疑它会快很多。

如果您想使用压缩,您必须了解分块的工作原理,因为 HDF 会压缩数据 chunk-by-chunk。 Google吧,不过至少看了section on chunking from the h5py docs。以下引述至关重要:

Chunking has performance implications. It’s recommended to keep the total size of your chunks between 10 KiB and 1 MiB, larger for larger datasets. Also keep in mind that when any element in a chunk is accessed, the entire chunk is read from disk.

通过设置 chunks=True,您可以让 h5py 自动为您确定块大小(打印数据集的 chunks 属性 以查看它们是什么)。假设第一个维度(您的 sample 维度)中的块大小为 5 。这意味着当您添加一个样本时,底层 HDF 库将从磁盘读取包含该样本的所有块(因此总共它将完全读取 5 个样本)。对于每个块,HDF 都会读取它、解压缩它、添加新数据、压缩它并将它写回磁盘。不用说,这很慢。 HDF 具有块缓存这一事实减轻了这种情况,因此未压缩的块可以驻留在内存中。然而,块缓存似乎相当小(参见 here),所以我认为在您的 for-loop 的每次迭代中,所有块都被换入和换出缓存。我在 h5py 中找不到任何设置来改变块缓存大小。

您可以通过将元组分配给 chunks 关键字参数来显式设置块大小。考虑到所有这些,您可以尝试不同的块大小。我的第一个实验是将第一个(样本)维度中的块大小设置为 1,这样就可以在不将其他样本读入缓存的情况下访问单个样本。让我知道这是否有帮助,我很想知道。

即使您找到适合写入数据的块大小,读取时它可能仍然很慢,具体取决于您读取的切片。选择块大小时,请记住您的应用程序通常如何读取数据。您可能需要调整 file-creation 例程以适应这些块大小(例如逐块填充数据集)。或者您可以认为这样做根本不值得,并创建未压缩的 HDF5 文件。

最后,我会在 create_dataset 调用中设置 shuffle=True。这可能会让您获得更好的压缩率。但是,它不应该影响性能。

您必须设置适当的块缓存大小。 例如:

您多次将数据添加到 HDF5 数据集,这可能会导致对块的多次写入访问。如果 chunk-chache 太低,它会像这样工作:

读取->解压->添加数据->压缩->写入

因此我建议您设置合适的块缓存大小(默认仅为 1 MB)。这可以通过低级 API 或 h5py-chache 来完成 https://pypi.python.org/pypi/h5py-cache/1.0

只需更改打开 HDF5 文件的行。

此外,应添加到数据集中的 numpy 数组的维数应该一致。

这是

A=np.random.rand(1000,1000)
for i in xrange(0,200):
    dset[:,:,i:i+1]=A[:,:,np.newaxis]

在我的笔记本电脑上比那快 7 倍

A=np.random.rand(1000,1000)
for i in xrange(0,200):
    dset[:,:,i]=A