如何加快 Gensim Word2vec 模型加载时间?

2023-11-21

我正在构建一个聊天机器人,因此需要使用 Word2Vec 对用户的输入进行矢量化。

我正在使用 Google 提供的包含 300 万个单词的预训练模型 (GoogleNews-vectors-male300)。

所以我使用 Gensim 加载模型:

import gensim
model = gensim.models.KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)

问题是加载模型大约需要 2 分钟。我不能让用户等那么久。

那么我该怎么做才能加快加载时间呢?

我想过将这 300 万个单词及其对应的向量分别放入 MongoDB 数据库中。这肯定会加快速度,但直觉告诉我这不是一个好主意。


在最近的 gensim 版本中,您可以使用可选的从文件前面开始加载子集limit参数为load_word2vec_format()。 (GoogleNews 向量似乎大致按最频繁到最不频繁的顺序排列,因此前 N 个通常是您想要的 N 大小的子集。因此使用limit=500000获得最常见的 500,000 个单词向量——仍然是一个相当大的词汇量——节省了 5/6 的内存/加载时间。)

所以这可能会有所帮助。但是,如果您为每个 Web 请求重新加载,您仍然会受到加载 IO 限制速度以及存储每个重新加载的冗余内存开销的影响。

您可以结合使用一些技巧来提供帮助。

请注意,以原始 word2vec.c 格式加载此类向量后,您可以使用 gensim 的本机重新保存它们save()。如果您以未压缩的方式保存它们,并且后备数组足够大(并且 GoogleNews 集绝对足够大),则后备数组将以原始二进制格式转储到单独的文件中。该文件稍后可以使用 gensim 的本机从磁盘进行内存映射[load(filename, mmap='r')][1] option.

最初,这将使负载看起来很快——操作系统不会从磁盘读取所有数组,而是仅将虚拟地址区域映射到磁盘数据,以便一段时间后,当代码访问这些内存位置时,将读取必要的范围-来自磁盘。到目前为止,一切都很好!

但是,如果您正在执行类似的典型操作most_similar(),您仍然会面临很大的滞后,只是稍后而已。这是因为此操作需要对所有向量进行初始扫描和计算(在第一次调用时,为每个单词创建单位长度标准化向量),然后对所有标准化向量进行另一次扫描和计算(在每次调用,找到 N 个最相似的向量)。这些全扫描访问会将整个阵列分页到 RAM 中,这又会耗费几分钟的磁盘 IO。

您想要的是避免冗余地进行单位标准化,并且只需支付一次 IO 成本。这需要将向量保留在内存中,以便所有后续 Web 请求(甚至多个并行 Web 请求)重复使用。幸运的是,内存映射在这里也可以提供帮助,尽管需要一些额外的准备步骤。

首先,加载 word2vec.c 格式的向量,load_word2vec_format()。然后,使用model.init_sims(replace=True)强制单位标准化,破坏性地就地(破坏非标准化向量)。

然后,将模型保存到新的文件名前缀: model.save('GoogleNews-vectors-gensim-normed.bin'`。(请注意,这实际上会在磁盘上创建多个文件,需要将这些文件保存在一起才能保存模型)重新加载。)

现在,我们将制作一个简短的 Python 程序,用于内存映射加载向量,and将整个数组强制放入内存。我们还希望该程序挂起直到外部终止(保持映射处于活动状态),and注意不要重新计算已经标准化的向量。这需要另一个技巧,因为加载的 KeyedVectors 实际上不知道向量是规范的。 (通常只保存原始向量,并在需要时重新计算标准化版本。)

大致如下应该有效:

from gensim.models import KeyedVectors
from threading import Semaphore
model = KeyedVectors.load('GoogleNews-vectors-gensim-normed.bin', mmap='r')
model.syn0norm = model.syn0  # prevent recalc of normed vectors
model.most_similar('stuff')  # any word will do: just to page all in
Semaphore(0).acquire()  # just hang until process killed

这仍然需要一段时间,但只需要在任何网络请求之前/之外完成一次。当进程处于活动状态时,向量保持映射到内存中。此外,除非/直到存在其他虚拟内存压力,否则向量应保持加载在内存中。这对于接下来的事情很重要。

最后,在您的 Web 请求处理代码中,您现在可以执行以下操作:

model = KeyedVectors.load('GoogleNews-vectors-gensim-normed.bin', mmap='r')
model.syn0norm = model.syn0  # prevent recalc of normed vectors
# … plus whatever else you wanted to do with the model

多个进程可以共享只读内存映射文件。 (也就是说,一旦操作系统知道文件 X 位于 RAM 中的某个位置,所有其他也需要 X 的只读映射版本的进程将被引导在该位置重新使用该数据。)。

所以这个网络请求load(), 以及任何后续访问,都可以重用先前进程已经带入地址空间和活动内存的数据。需要对每个向量进行相似性计算的操作仍然需要花费时间来访问多个 GB 的 RAM,并进行计算/排序,但不再需要额外的磁盘 IO 和冗余的重新标准化。

如果系统面临其他内存压力,数组的范围可能会超出内存范围,直到下一次读取将它们分页回来。如果机器缺乏 RAM 来完全加载向量,则每次扫描都需要混合分页-进进出出,无论如何,性能都会令人沮丧地糟糕。 (在这种情况下:获取更多 RAM 或使用较小的向量集。)

但是,如果您确实有足够的 RAM,这最终会使原始/自然加载和直接使用的代码以相当快的方式“正常工作”,而无需额外的 Web 服务接口,因为机器的共享文件映射内存功能作为服务接口。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何加快 Gensim Word2vec 模型加载时间? 的相关文章

随机推荐