Python3:两个具有不同大小的 numpy 向量的字典消耗相同数量的 RAM

Python3: two dictionaries with numpy vectors of different size consume the same amount of RAM

我有两个 python 字典 {word: np.array(float)},在第一个字典中我使用 300 维的 numpy 向量,在第二个字典中(键是相同的)- 150 维。第一个文件大小为 4.3 GB,第二个文件大小为 2.2 GB。

当我使用 sys.getsizeof() 检查加载的对象时,我得到:

import sys
import pickle
import numpy as np

对于大字典:

with open("big.pickle", 'rb') as f:
    source = pickle.load(f)

sys.getsizeof(source)
#201326688

all(val.size==300 for key, val in source.items())
#True

Linux top 命令显示 6.22GB:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                   
 4669 hcl       20   0 6933232 6,224g  15620 S   0,0 19,9   0:11.74 python3

对于小词典:

with open("small.pickle", 'rb') as f:
    source = pickle.load(f)

sys.getsizeof(source)
# 201326688  # Strange!

all(val.size==150 for key, val in source.items())
#True

但是当我使用 linux top 命令查看 python3 进程时,我看到 6.17GB:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                   
 4515 hcl       20   0 6875596 6,170g  16296 S   0,0 19,7   0:08.77 python3 

两个词典都是在Python3中使用pickle.HIGHEST_PROTOCOL保存的,我不想使用json,因为可能存在编码错误和加载缓慢的问题。此外,使用 numpy 数组对我来说很重要,因为我为这些向量计算 np.dot

如何为其中包含较小向量的字典缩小 RAM?

更精确的内存测量:

#big:
sum(val.nbytes for key, val in source.items())
4456416000


#small:

sum(val.nbytes for key, val in source.items())
2228208000

编辑:感谢@etene 的提示,我已经设法使用 hdf5 保存和加载我的模型:

节省:

import pickle
import numpy as np
import h5py


with open("reduced_150_normalized.pickle", 'rb') as f:
    source = pickle.load(f)

# list to save order
keys = []
values = []

for k, v in source.items():
    keys.append(k)
    values.append(v)

values = np.array(values)
print(values.shape)

with open('model150_keys.pickle',"wb") as f:
    pickle.dump(keys, f,protocol=pickle.HIGHEST_PROTOCOL) # do not store stings in h5! Everything will hang

h5f = h5py.File('model150_values.h5', 'w')
h5f.create_dataset('model_values', data=values)


h5f.close()

生成长度为 3713680 的关键短语列表和形状为 (3713680, 150).

的向量数组

正在加载:

import pickle
import numpy as np
import h5py

with open('model150_keys.pickle',"rb") as f:
    keys = pickle.load(f) # do not store stings in h5! Everything will hang

# we will construct model by reading h5 file line-by-line
h5f = h5py.File('model150_values.h5','r')
d=h5f['model_values']

print(len(keys))
print(d.shape)

model = {}

for i,key in enumerate(keys):    
    model[key]=np.array(d[i,:])

h5f.close()

现在我确实只消耗了 3GB 的 RAM:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                   
 5012 hcl       20   0 3564672 2,974g  17800 S   0,0  9,5   4:25.27 python3 

@etene,你可以写下你的评论作为答案,我会选择它。

剩下的唯一问题是加载现在需要相当长的时间(5 分钟),这可能是因为在 hdf5 文件中为 numpy 数组中的每个位置进行了查找。如果我可以通过第二个坐标以某种方式迭代 hdf5,而不加载到 RAM,那就太好了。


EDIT2:按照@hpaulj 的建议,我以块的形式加载文件,现在它和 pickle 一样快,甚至在使用 10k 块时更快(4 秒):

import pickle
import numpy as np
import h5py

with open('model150_keys.pickle',"rb") as f:
    keys = pickle.load(f) # do not store stings in h5! Everything will hang

# we will construct model by reading h5 file line-by-line
h5f = h5py.File('model150_values.h5','r')
d=h5f['model_values']

print(len(keys))
print(d.shape)

model = {}

# we will load in chunks to speed up loading
for i,key in enumerate(keys):    
    if i%10000==0:
        data = d[i:i+10000,:]

    model[key]=data[i%10000,:]

h5f.close()

print(len(model))

谢谢大家!!!

总结我们在评论中发现的内容:

  • sys.getsizeof 使用相同的键为两个 dict 返回相同的值是正常行为。来自文档:"Only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to."
  • 一次反序列化所有数据会消耗大量 RAM; this Numpy discussion thread 提到 HDF5 文件格式是一种以小批量读取数据、减少内存使用的解决方案。
  • 但是,由于磁盘 i/o,小批量读取也会对性能产生影响。感谢@hpaulj,@slowpoke 能够确定适合他的更大批量大小。

TL;DR 对于未来的读者:如果它真的很大,不要一次反序列化整个数据集,这会占用不可预测的 RAM 量。使用专用格式(例如 HDF5)并将数据分割成大小合理的批次,请记住较小的读取 = 更多的磁盘 i/o,较大的读取 = 更多的内存使用。