【问题标题】:How to speed up Gensim Word2vec model load time?如何加快 Gensim Word2vec 模型的加载时间?
【发布时间】:2017-08-16 14:51:13
【问题描述】:

我正在构建一个聊天机器人,因此我需要使用 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 数据库中。这肯定会加快速度,但直觉告诉我这不是一个好主意。

【问题讨论】:

    标签: deep-learning gensim word2vec


    【解决方案1】:

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

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

    其他选项是始终保持 Gensim 运行。您可以create a FIFO 获取运行 Gensim 的脚本。该脚本就像一个“服务器”,可以读取“客户端”写入的文件,监视向量请求。

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

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

    【讨论】:

    • 完美!这些是一些很棒的想法,正是我正在寻找的那种解决方案。您是否发现数据库解决方案存在任何潜在缺陷?如果是这样,我总是可以按照您的建议建立一个最常用单词(50-100k)的字典,但如果可行的话,我宁愿拥有全部 300 万个单词。
    • 好吧,我只记得 word2vec 作为 Web 服务解决方案。所以它有点像 FIFO 概念,但要好得多。您只需使用 Web 请求调用脚本。因此,您可以拥有 300 万个单词而不将它们放入数据库中,并且脚本始终在运行,因此没有加载时间。该仓库甚至为您提供了一系列嵌入的链接。
    【解决方案2】:

    在最近的 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] 选项从磁盘进行内存映射。

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

    但是,如果您正在执行像 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 或使用更小的向量集。)

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        成功方法:

        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

        【讨论】:

        • 感谢您的回答,如需更多理解,请格式化您的回答。并尝试解释为什么答案是解决方案。请查看the guidelines on answering
        • 这样一个简单的解决方案!
        猜你喜欢
        • 2021-03-31
        • 1970-01-01
        • 2018-07-19
        • 1970-01-01
        • 2017-09-21
        • 1970-01-01
        • 2014-04-02
        • 1970-01-01
        • 2019-05-26
        相关资源
        最近更新 更多