如何更快地运行 Fasttext get_nearest_neighbors()?

问题描述

我正在尝试使用 Fasttext 提取僧伽罗语中的变形/相似词。 但是 FastText 需要 1 秒来处理 2.64 个单词。如何在不改变模型大小的情况下提高速度?

我的代码如下:

import fasttext
fasttext.util.download_model('si',if_exists='ignore')  # Sinhala
ft = fasttext.load_model('cc.si.300.bin')
words_file = open(r'/Datasets/si_words_filtered.txt')
words = words_file.readlines()
words = words[0:300]
synon_dict = dict()
from tqdm import tqdm_notebook
for i in tqdm_notebook(range(len(words))):
    word = words[i].strip()
    synon = ft.get_nearest_neighbors(word)[0][1] ### takes a lot of time
    if is_strictly_sinhala_word(synon):
        synon_dict[word] = synon
import json
with open("out.json","w",encoding='utf8') as f:
    json.dump(synon_dict,f,ensure_ascii=False)

解决方法

进行完全准确的 get_nearest_neighbors() 类型的计算本质上是相当昂贵的,需要针对每个新词对集合中的每个词进行查找和计算。

由于这组向量的大小看起来接近或超过 2GB,当仅加载词向量时,这意味着扫描 2GB 的可寻址内存可能是运行时的主要因素。

尝试一些可能会有所帮助的方法:

  • 确保您有足够的 RAM - 如果使用任何“交换”/虚拟内存,这会使运行速度变慢。
  • 避免所有不必要的比较 - 例如,在昂贵的步骤之前执行您的is_strictly_sinhala_word()检查,这样如果对结果不感兴趣,您可以跳过昂贵的步骤。此外,您可以考虑缩小完整的词向量集,以消除那些您不太可能想要作为响应的词向量。这可能涉及丢弃您知道不属于感兴趣语言的单词或所有低频单词。 (如果您可以在尝试 get_nearest_neighbors() 之前尽可能多地舍弃最近邻词的一半,它的速度大约会快两倍。)下面详细介绍这些选项。
  • 尝试其他词向量库,看看它们是否有任何改进。例如,Python Gensim 项目可以加载纯词向量集(例如,cc.si.300.vec 纯词文件)或 FastText 模型(.bin 文件),并提供 {{1 }} 函数有一些额外的选项 & 可能,在某些情况下,提供不同的性能。 (不过,官方的 Facebook Fasttext .most_similar() 可能还不错。)
  • 使用“近似最近邻”库预先构建词向量空间的索引,然后可以提供超快的最近邻查找 - 尽管存在无法找到准确的前 N ​​个邻居的风险。有许多这样的库 - 请参阅此 benchmarking project,其中比较了 20 多个。但是,添加此步骤会使事情复杂化,并且在这种复杂性和不完美结果之间进行权衡可能不值得付出努力和节省时间。所以,请记住,如果您的需求足够大且没有其他帮助,这是一种可能性。

关于精简搜索到的向量集:

  • Gensim .get_nearest_neighbors() 函数可以加载 KeyedVectors.load_word2vec_format() 仅单词文件,它有一个选项 .vec,它只会从文件中读取指定数量的单词。看起来您的数据集的 limit 文件有超过 800k 字 - 但如果您选择仅加载 400k,您的 .vec 计算速度将提高两倍。 (而且,由于此类文件通常会使用最常用的单词预先加载文件,因此可能不会担心丢失较罕见的单词。)
  • 同样,即使您加载所有向量,Gensim .most_similar() 函数也有一个 .most_similar() 选项,可以将搜索限制为该计数的第 1 个单词,即还可以加快处理速度或帮助删除可能不太感兴趣的晦涩词汇。
  • restrict_vocab 文件可能更易于使用,例如,如果您想对单词进行预过滤以消除非僧伽罗语单词。 (注意:通常的 .vec 文本格式需要第 1 行来声明单词数和单词维数,但您可以忽略它,然后使用 .load_word2vec_format() 选项加载,而是使用 2 个完整的传递文件以获取计数。)