如何从 pickle 文件中一次加载一行?
How to load one line at a time from a pickle file?
我有一个大型数据集:20,000 x 40,000 作为 numpy 数组。我已将其保存为 pickle 文件。
我不想将这个庞大的数据集读入内存,而是一次只读其中的几行(比如 100 行),用作小批量。
我怎样才能从 pickle 文件中只读取几行随机选择的(没有替换的)行?
由于你不知道pickle的内部工作原理,所以你需要使用另一种存储方式。下面的脚本使用 tobytes()
函数将数据逐行保存在原始文件中。
由于每行的长度已知,因此可以通过 seek()
和 read()
计算和访问它在文件中的偏移量。之后,用 frombuffer()
函数将其转换回数组。
然而,最大的免责声明是数组的大小没有保存(这也可以添加,但需要更多的复杂性)并且这种方法可能不像 pickled 数组那样可移植。
正如@PadraicCunningham 在他的 , a memmap 中指出的那样,这可能是一个替代的优雅解决方案。
关于性能的评论:阅读评论后我做了一个简短的基准测试。在我的机器(16GB RAM,加密的 SSD)上,我能够在 24 秒内进行 40000 次随机行读取(当然是使用 20000x40000 矩阵,而不是示例中的 10x10)。
from __future__ import print_function
import numpy
import random
def dumparray(a, path):
lines, _ = a.shape
with open(path, 'wb') as fd:
for i in range(lines):
fd.write(a[i,...].tobytes())
class RandomLineAccess(object):
def __init__(self, path, cols, dtype):
self.dtype = dtype
self.fd = open(path, 'rb')
self.line_length = cols*dtype.itemsize
def read_line(self, line):
offset = line*self.line_length
self.fd.seek(offset)
data = self.fd.read(self.line_length)
return numpy.frombuffer(data, self.dtype)
def close(self):
self.fd.close()
def main():
lines = 10
cols = 10
path = '/tmp/array'
a = numpy.zeros((lines, cols))
dtype = a.dtype
for i in range(lines):
# add some data to distinguish lines
numpy.ndarray.fill(a[i,...], i)
dumparray(a, path)
rla = RandomLineAccess(path, cols, dtype)
line_indices = list(range(lines))
for _ in range(20):
line_index = random.choice(line_indices)
print(line_index, rla.read_line(line_index))
if __name__ == '__main__':
main()
您可以将泡菜增量写入文件,这样您就可以加载它们
也逐渐增加。
举个例子。在这里,我们遍历列表的项目,并且
依次腌制。
>>> import cPickle
>>> myData = [1, 2, 3]
>>> f = open('mydata.pkl', 'wb')
>>> pickler = cPickle.Pickler(f)
>>> for e in myData:
... pickler.dump(e)
<cPickle.Pickler object at 0x7f3849818f68>
<cPickle.Pickler object at 0x7f3849818f68>
<cPickle.Pickler object at 0x7f3849818f68>
>>> f.close()
现在我们可以反向执行相同的过程并根据需要加载每个对象。为了
示例的目的,假设我们只想要第一项而不想要
想要遍历整个文件。
>>> f = open('mydata.pkl', 'rb')
>>> unpickler = cPickle.Unpickler(f)
>>> unpickler.load()
1
此时文件流只前进到第一个
目的。其余对象未加载,这正是您的行为
想。为了证明,您可以尝试阅读文件的其余部分并查看其余部分
还坐在那里
>>> f.read()
'I2\n.I3\n.'
谢谢大家。我最终找到了一个解决方法(一台有更多 RAM 的机器,这样我就可以将数据集加载到内存中)。
我有一个大型数据集:20,000 x 40,000 作为 numpy 数组。我已将其保存为 pickle 文件。
我不想将这个庞大的数据集读入内存,而是一次只读其中的几行(比如 100 行),用作小批量。
我怎样才能从 pickle 文件中只读取几行随机选择的(没有替换的)行?
由于你不知道pickle的内部工作原理,所以你需要使用另一种存储方式。下面的脚本使用 tobytes()
函数将数据逐行保存在原始文件中。
由于每行的长度已知,因此可以通过 seek()
和 read()
计算和访问它在文件中的偏移量。之后,用 frombuffer()
函数将其转换回数组。
然而,最大的免责声明是数组的大小没有保存(这也可以添加,但需要更多的复杂性)并且这种方法可能不像 pickled 数组那样可移植。
正如@PadraicCunningham 在他的
关于性能的评论:阅读评论后我做了一个简短的基准测试。在我的机器(16GB RAM,加密的 SSD)上,我能够在 24 秒内进行 40000 次随机行读取(当然是使用 20000x40000 矩阵,而不是示例中的 10x10)。
from __future__ import print_function
import numpy
import random
def dumparray(a, path):
lines, _ = a.shape
with open(path, 'wb') as fd:
for i in range(lines):
fd.write(a[i,...].tobytes())
class RandomLineAccess(object):
def __init__(self, path, cols, dtype):
self.dtype = dtype
self.fd = open(path, 'rb')
self.line_length = cols*dtype.itemsize
def read_line(self, line):
offset = line*self.line_length
self.fd.seek(offset)
data = self.fd.read(self.line_length)
return numpy.frombuffer(data, self.dtype)
def close(self):
self.fd.close()
def main():
lines = 10
cols = 10
path = '/tmp/array'
a = numpy.zeros((lines, cols))
dtype = a.dtype
for i in range(lines):
# add some data to distinguish lines
numpy.ndarray.fill(a[i,...], i)
dumparray(a, path)
rla = RandomLineAccess(path, cols, dtype)
line_indices = list(range(lines))
for _ in range(20):
line_index = random.choice(line_indices)
print(line_index, rla.read_line(line_index))
if __name__ == '__main__':
main()
您可以将泡菜增量写入文件,这样您就可以加载它们 也逐渐增加。
举个例子。在这里,我们遍历列表的项目,并且 依次腌制。
>>> import cPickle
>>> myData = [1, 2, 3]
>>> f = open('mydata.pkl', 'wb')
>>> pickler = cPickle.Pickler(f)
>>> for e in myData:
... pickler.dump(e)
<cPickle.Pickler object at 0x7f3849818f68>
<cPickle.Pickler object at 0x7f3849818f68>
<cPickle.Pickler object at 0x7f3849818f68>
>>> f.close()
现在我们可以反向执行相同的过程并根据需要加载每个对象。为了 示例的目的,假设我们只想要第一项而不想要 想要遍历整个文件。
>>> f = open('mydata.pkl', 'rb')
>>> unpickler = cPickle.Unpickler(f)
>>> unpickler.load()
1
此时文件流只前进到第一个 目的。其余对象未加载,这正是您的行为 想。为了证明,您可以尝试阅读文件的其余部分并查看其余部分 还坐在那里
>>> f.read()
'I2\n.I3\n.'
谢谢大家。我最终找到了一个解决方法(一台有更多 RAM 的机器,这样我就可以将数据集加载到内存中)。