paint-brush
利用 GPT-3 改进搜索解决方案经过@weblabtechnology
965 讀數
965 讀數

利用 GPT-3 改进搜索解决方案

经过 WebLab Technology12m2023/04/13
Read on Terminal Reader

太長; 讀書

在大多数情况下,公司通过简单地直接查询 OLTP 数据库来保持非常基本的搜索级别。 Apache Lucene、Apache Solr、Elasticsearch、Sphinx、MeiliSearch、Typesense 等在处理复杂查询和使用过滤器方面往往相对更快、更好。
featured image - 利用 GPT-3 改进搜索解决方案
WebLab Technology HackerNoon profile picture
0-item


现代应用程序通常包含搜索解决方案,使用户能够按需快速访问现有内容。很难想象任何其他功能可以有效地检索此信息,从而使搜索成为大多数应用程序中的基本功能。

定制的搜索解决方案选项

同时,即使查询和搜索的需求非常普遍,不同的应用程序也会采用截然不同的方法。


在大多数情况下,公司通过简单地直接查询 OLTP 数据库来保持非常基本的搜索级别。请求可能如下所示: SELECT id, title FROM, entities WHERE, description LIKE '%bow%


然而,更常见的是,它们以复杂的、多级的表连接结构表示,这些结构难以阅读、缓慢且原始。它们无法理解上下文,需要进行大量定制,并且很难正确实施。


虽然可以通过物化视图、查询缓存和其他技术来缩短查询执行时间,但增加的复杂性会导致主要更新和一致的搜索结果之间存在相当大的滞后。



原始的基于数据库的搜索解决方案的更有效替代方案可能构成开源搜索引擎,例如 Apache Lucene、Apache Solr、Elasticsearch、Sphinx、MeiliSearch、Typesense 等。


这些在处理复杂查询和使用过滤器方面往往相对更快并且更好。但是一旦将这些搜索引擎与谷歌搜索或 DuckDuckGo 等同类搜索引擎进行比较,就会发现开源解决方案无法构建适当的搜索上下文和查询模式——如果用户提供模糊的搜索请求,它们将无法理解查询。

从查询中提取含义


想象一下,您根本不记得那种带有酸味的黄色柑橘类水果的名字!但是您想在该应用程序中搜索有关如何种植这种神秘水果的文章。你如何进行搜索?


您的问题可能是“如何在室内种植黄酸柑橘”。上述任何开源搜索引擎都可能很难返回此查询的相关结果,即使数据库确实包含有关种植“柠檬”的文章。


这是因为从查询中提取意义是一项自然语言任务,没有 AI 组件就不太可能解决。 GPT-3擅长这项任务。


OpenAI 提供嵌入 API基于 GPT-3,将自然语言文本转换为浮点数向量。嵌入本质上是一个低维空间,高维向量可以转换到其中。在这种情况下,高维向量是文本,低维向量是固定大小的输出向量。向量之间的距离表示它们相关的强度。距离越小,相关性越高。通过将文本重新定义为基于矢量的形式,该任务被简化为经典的 ML 搜索问题。


选择合适的型号

选择模型


文档文本到矢量代表的转换可以在后台发生,而搜索查询的矢量化应该在运行时发生。 OpenAI 提供了几个 GPT-3 模型系列:


 text-search-ada-doc-001: 1024 text-search-babbage-doc-001: 2048 text-search-curie-doc-001: 4096 text-search-davinci-doc-001: 12288


更高的向量维度会导致更多的嵌入信息,因此也会导致更高的成本和更慢的搜索。


文档通常很长,查询通常很短且不完整。因此,考虑到内容的密度和大小,任何文档的矢量化都与任何查询的矢量化有很大不同。 OpenAI 知道这一点,因此他们提供了两个配对模型, -doc-query

 text-search-ada-query-001: 1024 text-search-babbage-query-001: 2048 text-search-curie-queryc-001: 4096 text-search-davinci-query-001: 12288


重要的是要注意查询和文档必须都使用相同的模型系列并且具有相同的输出向量长度。

示例数据集

通过示例可能最容易观察和理解此搜索解决方案的强大功能。对于这个例子,让我们借鉴TMDB 5000 电影数据集其中包含来自 TMDb 的大约 5,000 部电影的元数据。我们将构建一个仅依赖电影文档而非评论的搜索解决方案。


数据集包含大量列,但我们的矢量化过程将仅围绕标题和概述列构建。

嵌入图图像


记录示例:

 Title: Harry Potter and the Half-Blood Prince Overview: As Harry begins his sixth year at Hogwarts, he discovers an old book marked as 'Property of the Half-Blood Prince', and begins to learn more about Lord Voldemort's dark past.


让我们将数据集映射到准备好索引的文本中:


 datafile_path = "./tmdb_5000_movies.csv" df = pd.read_csv(datafile_path) def combined_info(row): columns = ['title', 'overview'] columns_to_join = [f"{column.capitalize()}: {row[column]}" for column in columns] return '\n'.join(columns_to_join) df['combined_info'] = df.apply(lambda row: combined_info(row), axis=1)


嵌入过程很简单:

 def get_embedding(text, model="text-search-babbage-doc-001"): text = text.replace("\n", " ") return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding'] get_embedding(df['combined_info'][0])


此代码块输出一个列表,其大小等于模型正在运行的参数,在text-search-babbage-doc-001情况下为 2048。

类似的嵌入过程应该应用于我们想要搜索的所有文档:


 df['combined_info_search'] = df['combined_info'].apply(lambda x: get_embedding(x, model='text-search-babbage-doc-001')) df.to_csv('./tmdb_5000_movies_search.csv', index=False)


combined_info_search将保存 combined_text 的矢量表示。

而且,令人惊讶的是,已经是这样了!最后,我们准备执行示例搜索查询:


 from openai.embeddings_utils import get_embedding, cosine_similarity def search_movies(df, query, n=3, pprint=True): embedding = get_embedding( query, engine="text-search-babbage-query-001" ) df["similarities"] = df.combined_info_search.apply(lambda x: cosine_similarity(x, embedding)) res = ( df.sort_values("similarities", ascending=False) .head(n) .combined_info ) if pprint: for r in res: print(r[:200]) print() return res res = search_movies(df, "movie about the wizardry school", n=3)

这些是我们得到的结果:

 Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Harry Potter and the Goblet of FireOverview: Harry starts his fourth year at Hogwarts, competes in the treacherous Triwizard Tournament and faces the evil Lord Voldemort. Ron and Hermione help Harry manage the pressure — but Voldemort lurks, awaiting his chance to destroy Harry and all that he stands for. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.


Harry Potter and the Philosopher's Stone ”的概述包含词“wizardry”和“school”,并且在搜索输出中排在第一位。第二个结果不再包含“学校”一词,但仍保留接近“巫师”、“三强争霸”的词。第三个结果只包含“巫术”的同义词——魔法。


当然,此数据库中还有许多其他电影以学校或巫师(或两者)为特色,但以上是唯一返回给我们的电影。这清楚地证明了搜索解决方案有效并且确实理解了我们查询的上下文。


我们使用了只有 2048 个参数的 Babbage 模型。 Davinci 有六倍多 (12,288) 个参数,因此在高度复杂的查询方面可以表现得更好。


搜索解决方案有时可能无法生成与某些查询相关的输出。例如,查询“关于学校巫师的电影”产生:


 Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Dumb and Dumberer: When Harry Met LloydOverview: This wacky prequel to the 1994 blockbuster goes back to the lame-brained Harry and Lloyd's days as classmates at a Rhode Island high school, where the unprincipled principal puts the pair in remedial courses as part of a scheme to fleece the school. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.


您可能想知道“阿呆与笨蛋:当哈利遇见劳埃德”在这里做什么?值得庆幸的是,这个问题没有在参数更多的参数上重现。

计算查询和文档之间的距离

搜索输出应包含按相关性降序排列的文档。为此,我们应该知道当前查询与每个文档之间的距离。长度越短,输出的相关性就越高。然后,在定义的最大范围之后,我们应该停止考虑剩余文档的相关性。


相关输出的图像


在上述示例中,我们使用了余弦相似度由于向量空间的高维性,计算距离。使用 babbage 模型,我们有 2048 个参数。


距离计算算法倾向于用单个数字表示查询和文档之间的这种相似性(差异)。然而,我们不能依赖欧氏距离因为维度灾难——距离太相似了。这是因为欧几里得距离在大约七个维度之外变得非常不切实际——在这一点上,距离落入相同的桶并且变得几乎相同。


如果愿意,您可以在此处查看生成的存储库:

或者,您可以在此处的Google Colab 中使用它。

时间复杂度

我们使用蛮力方法对文档进行排序。让我们确定:

● n:训练数据集中的点数

● d:数据维度


蛮力解决方案的搜索时间复杂度为O(n * d * n * log(n)) 。参数d取决于模型(在 Babbage 的情况下,它等于 2048),而由于排序步骤,我们有O(nlog(n))块。


在这个阶段提醒我们自己,更小的模型更快、更便宜是很重要的。例如,在搜索案例相似度计算步骤中,Ada 模型快了两倍,而 Davinci 模型慢了六倍。


在我的 M1 Pro 上,查询和 2048 个维度的 4803 个文档之间的余弦相似度计算花费了 1260 毫秒。在当前的实现中,计算所需的时间将随着文档总数线性增长。同时,该方法支持计算并行化。

蛮力解决方案的替代方案

在搜索解决方案中,查询应该尽快完成。而这个代价通常是在训练和预缓存时间方面付出的。我们可以使用像 kd 树、r 树或 ball 树这样的数据结构。考虑来自的文章 迈向数据科学关于这些方法的计算复杂度分析:它们都导致计算复杂度接近O(k * log(n)) ,其中k是我们希望在单个批次中返回的元素数。


Kd 树、ball 树和 r 树构成了用于存储和高效搜索 N 维空间中的点的数据结构,例如我们的意义向量。


Kd 和球树是基于树的数据结构,它们使用迭代的二进制分区方案将空间划分为区域,树中的每个节点代表一个子区域。 Kd 树在搜索特定范围内的点或找到给定点的最近邻居时特别有效。


类似地,r 树也用于在 N 维空间中存储点,但是,它们在特定区域内搜索点或查找给定点一定距离内的所有点时效率更高。重要的是,r-trees 使用与 kd 树和 ball 树不同的分区方案;他们将空间划分为“矩形”而不是二进制分区。


树的实现不在本文讨论范围之内,不同的实现将导致不同的搜索输出。

查询时间

也许,当前搜索解决方案最显着的缺点是我们必须调用外部 OpenAI API 来检索查询嵌入向量。无论我们的算法能够多快地找到最近的邻居,都需要一个顺序阻塞步骤。


 Text-search-babbage-query-001 Number of dimensions: 2048 Number of queries: 100 Average duration: 225ms Median duration: 207ms Max duration: 1301ms Min duration: 176ms


 Text-search-ada-query-002 Number of dimensions: 1536 Number of queries: 100 Average duration: 264ms Median duration: 250ms Max duration: 859ms Min duration: 215ms


 Text-search-davinci-query-001 Number of dimensions: 12288 Number of queries: 100 Average duration: 379ms Median duration: 364ms Max duration: 1161ms Min duration: 271ms


如果我们以中位数作为参考点,我们可以看到 ada-002 慢了 +28%,而 davinci-001 慢了 +76%。

GPT-3 搜索嵌入的局限性

参考Nils Reimer 关于密集文本嵌入模型比较的文章,我们可以得出结论,GPT-3 没有提供出色的性能或输出质量,并且需要依赖相当慢的外部 API。 GPT-3 能够支持最多 4096 个令牌(大约 3072 个单词)的输入,但是,没有通过 API 提供的截断服务,并且尝试对超过 4096 个令牌的文本进行编码将导致错误。因此,用户(您)有责任确定实际可以编码的文本量。


此外,OpenAI 的培训成本相对较高。

培训费用图片

或者,您可以考虑尝试TAS-B或者多 qa-mpnet-base-dot-v1 .

Elasticsearch 中的近似最近邻搜索

Elasticsearch 8.0 支持高效的近似最近邻搜索 (ANN),可用于比任何线性 KNN 更快地解决我们的问题量级。 Elasticsearch 8.0 利用称为分层导航小世界图 (HNSW) 的 ANN 算法,根据相似性将向量组织成图。在包含 1000 万个嵌入向量的数据集上进行测试,我们在一台专注于计算的机器上使用 ANN 实现了每秒 200 次查询的惊人性能,而使用 KNN 每秒仅进行 2 次查询。两者均由 Elasticsearch 提供。


根据 ElasticSearch 的关于 ANN 发布的博文密集向量搜索的基准,这个性能的成本是召回的5%。这意味着大约十分之一的最接近的向量并不是真正相关的。我们应用了一种混合方法来改进这个结果:请求 k+l 而不是 k 并应用过滤来删除异常值。可悲的是,没有分页功能。因此,这种方法仅适用于某些用例。


正如您希望看到的那样,GPT-3 嵌入并不是解决任何和所有搜索问题的完美解决方案,因为它的索引复杂性、成本以及搜索操作(甚至是近似值)的高计算复杂性。尽管如此,GPT-3 Embeddings 仍然是任何为具有不频繁查询和适度索引要求的搜索解决方案寻找强大主干的人的绝佳选择。


此外,还值得补充的是,微软最近 __宣布__ Bing 搜索引擎现在使用 GPT 3.5 的新升级版本,称为“Prometheus”,最初是为搜索开发的。根据公告,新的 Prometheus 语言模型允许 Bing 增加相关性、更准确地注释片段、提供更新鲜的结果、理解地理定位并提高安全性。这可能会为将自回归语言模型用于搜索解决方案开辟新的可能性,我们一定会继续关注这一点。


参考:

  1. 新的和改进的嵌入模型
  2. k 最近邻计算复杂度
  3. scipy.spatial.KDTree
  4. kd树
  5. OpenAI GPT-3 文本嵌入——真的是密集文本嵌入的最新技术水平吗?
  6. 在 Elasticsearch 8.0 中引入近似最近邻搜索


也发布在这里。