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

How to speed up Gensim Word2vec model load time?

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

我正在使用 Google (GoogleNews-vectors-negative300) 的 300 万个单词的预训练模型。

所以我使用 Gensim 加载模型:

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

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

那么我可以做些什么来加快加载时间?

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

每当我使用 google 新闻数据集时,我都会遇到这个问题。问题是数据集中的单词比你需要的多得多。有大量的错别字,什么不是。我所做的是扫描我正在处理的数据,构建一个包含 50k 个最常见单词的字典,使用 Gensim 获取向量并保存字典。加载这本词典需要半秒而不是 2 分钟。

如果您没有特定的数据集,您可以使用来自大数据集的 50 或 100k 个最常见的单词,例如来自 WMT 的 news dataset 来帮助您入门。

其他选项是始终保持 Gensim 运行ning。您可以 create a FIFO 为脚本 运行ning Gensim。该脚本的作用类似于 "server",可以读取 "client" 写入的文件,监视向量请求。

我认为最优雅的解决方案是 运行 提供词嵌入的 Web 服务。以 word2vec API 为例。安装后,获取 "restaurant" 的嵌入非常简单:

curl http://127.0.0.1:5000/word2vec/model?word=restaurant

在最新的 gensim 版本中,您可以使用可选的 limit 参数从文件的开头加载子集到 load_word2vec_format()。 (GoogleNews 向量似乎大致按照最频繁到最不频繁的顺序排列,因此前 N 个通常是您想要的 N 大小的子集。因此使用 limit=500000 获取最频繁的 500,000 个单词向量– 仍然是一个相当大的词汇量 – 节省了 memory/load-time 的 5/6。)

所以这可能有点帮助。但是,如果您为每个 Web 请求重新加载,您仍然会受到加载的 IO 绑定速度和存储每个重新加载的冗余内存开销的伤害。

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

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

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

但是,如果您正在执行像 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 程序,用于内存映射加载向量, 将整个数组强制放入内存。我们还希望这个程序挂起,直到外部终止(保持映射有效),注意不要重新计算已经规范的向量。这需要另一个技巧,因为加载的 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

这仍然需要一段时间,但只需要完成一次,before/outside 任何网络请求。当进程处于活动状态时,向量会保持映射到内存中。此外,unless/until 还有其他虚拟内存压力,向量应该保持加载在内存中。这对下一步很重要。

最后,在您的 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

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

所以这个web-reqeust load(), 和任何后续访问, 都可以重新使用先前进程已经带入地址的数据-space 和活动内存。需要对每个向量进行相似性计算的操作仍然需要时间来访问多个 GB 的 RAM,并执行 calculations/sorting,但不再需要额外的磁盘 IO 和冗余的重新规范化。

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

但是如果你有足够的 RAM,这最终会以相当快的方式使 original/natural 直接加载和使用代码 "just work",而无需额外的 Web 服务接口,因为机器的共享文件映射内存用作服务接口。

我真的很喜欢 vzhong 的嵌入库。 https://github.com/vzhong/embeddings

它在 SQLite 中存储单词向量,这意味着我们不需要加载模型,只需从数据库中获取相应的向量即可:D

成功方法:

model = Word2Vec.load_word2vec_format('wikipedia-pubmed-and-PMC-w2v.bin',binary=True)
model.init_sims(replace=True)
model.save('bio_word')

稍后加载模型

Word2Vec.load('bio_word',mmap='r')

更多信息:https://groups.google.com/forum/#!topic/gensim/OvWlxJOAsCo