我正在构建一个聊天机器人,因此我需要使用Word2Vec对用户的输入进行矢量化。
我使用谷歌300万字的预训练模型(GoogleNews-vectors-negative300)。
所以我使用Gensim加载模型:
import gensim
model = gensim.models.KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)
问题是加载模型大约需要2分钟。我不能让用户等那么久。
那么我该怎么办才能加快加载时间呢?
我想过将300万个单词及其相应的向量中的每一个放入MongoDB数据库。这肯定会加快速度,但直觉告诉我这不是一个好主意。
答案 0 :(得分:37)
在最近的gensim版本中,您可以使用可选的limit
参数加载从文件前面开始的子集到load_word2vec_format()
。 (GoogleNews向量似乎按照大致最低到最低的顺序排列,因此前N个通常是您想要的N大小子集。因此,使用limit=500000
获取最频繁的500,000个单词& #39;向量 - 仍然是一个相当大的词汇表 - 节省了5/6的内存/加载时间。)
所以这可能会有所帮助。但是,如果您为每个Web请求重新加载,您仍然会受到加载IO绑定速度以及存储每个重新加载的冗余内存开销的影响。
您可以结合使用一些技巧来提供帮助。
请注意,在以原始word2vec.c格式加载此类矢量后,您可以使用gensim的原生save()
重新保存它们。如果您将它们保存为未压缩,并且后备阵列足够大(并且GoogleNews设置肯定足够大),则后备阵列将以原始二进制格式转储到单独的文件中。该文件以后可以使用gensim的本地[load(filename, mmap='r')][1]
选项从磁盘进行内存映射。
最初,这会使负载看起来很快 - 而不是从磁盘读取所有数组,操作系统只会将虚拟地址区域映射到磁盘数据,所以一段时间后,当代码访问这些内存位置时,必要的范围将从磁盘读取。到目前为止一切都很好!
但是,如果您正在执行most_similar()
这样的典型操作,那么您稍后会面临很大的滞后。这是因为此操作需要对所有向量进行初始扫描和计算(在第一次调用时,为每个单词创建单位长度归一化向量),然后对所有向量进行另一次扫描和计算赋范向量(在每次调用时,找到N个最相似的向量)。这些全扫描访问将整个阵列分页到RAM中 - 再次耗费几分钟的磁盘IO。
您想要的是避免冗余地执行单元规范化,并且只需支付一次IO成本。这需要将向量保留在内存中,以供所有后续Web请求(甚至多个并行Web请求)重用。幸运的是,内存映射在这里也有帮助,尽管需要一些额外的准备步骤。
首先,使用load_word2vec_format()
加载word2vec.c格式向量。然后,使用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
这仍然需要一段时间,但只需要在任何网络请求之前/之外进行一次。当进程处于活动状态时,向量会保持映射到内存中。此外,除非/直到存在其他虚拟内存压力,否则向量应保持在存储器中。这对于接下来的事情非常重要。
最后,在您的网络请求处理代码中,您现在可以执行以下操作:
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的进程将被指示在该位置重新使用该数据。)。
因此,这个web-reqeust load()
,以及任何后续访问都可以重复使用先前进程已经带入地址空间和活动内存的数据。需要对每个向量进行相似性计算的操作仍然需要时间来访问多GB的RAM,并进行计算/排序,但不再需要额外的磁盘IO和冗余的重新规范化。
如果系统面临其他内存压力,阵列的范围可能会从内存中消失,直到下一次读取它们为止。如果机器没有RAM来完全加载矢量,那么每次扫描都需要混合分页输入和性能将是令人沮丧的坏事无论如何。 (在这种情况下:获得更多RAM或使用更小的向量集。)
但是,如果你有足够的内存,那么最终会使原始/自然的加载和使用直接代码"只是工作"以非常快的方式,没有额外的Web服务接口,因为机器的共享文件映射存储器用作服务接口。
答案 1 :(得分:1)
每当我使用Google新闻数据集时,我都会遇到这个问题。问题是数据集中的单词数量超过了您所需要的数量。有大量的拼写错误,什么不是。我所做的是扫描我正在处理的数据,建立一个说50k最常用单词的字典,用Gensim获取向量并保存字典。加载此词典需要半秒而不是2分钟。
如果您没有特定数据集,则可以使用大数据集中的50或100k最常用单词,例如来自WMT的news dataset来启动。
其他选择是始终保持Gensim运行。您可以create a FIFO获取运行Gensim的脚本。该脚本就像一个"服务器"可以读取"客户端"写道,看着矢量请求。
我认为最优雅的解决方案是运行提供单词嵌入的Web服务。以word2vec API为例。安装完成后,进入"餐厅"就像这样简单:
curl http://127.0.0.1:5000/word2vec/model?word=restaurant
答案 2 :(得分:1)
我真的很喜欢vzhong的嵌入式库。 https://github.com/vzhong/embeddings
它在SQLite中存储单词向量,这意味着我们不需要加载模型,只需从DB中获取相应的向量:D
答案 3 :(得分:0)
成功方法:
model = Word2Vec.load_word2vec_format('wikipedia-pubmed-and-PMC-w2v.bin',binary = True) model.init_sims(替换=真) model.save('bio_word')
Word2Vec.load('bio_word',mmap ='r')