Deliver Better Recommendations with Qdrant’s new API
TL;DR · AI 摘要
Qdrant 1.6 推出更灵活的推荐 API,支持正负样本混合输入和多种搜索策略,提升推荐系统效果。
核心要点
- Qdrant 1.6 支持正负样本混合输入,提升推荐系统灵活性。
- 新 API 提供 average_vector 和 best_score 两种搜索策略。
- 推荐 API 向后兼容,无需代码修改即可升级。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- Qdrant 1.6 推荐 API
- 新功能
- 支持正负样本混合输入
- 提供 average_vector 和 best_score 策略
- 向后兼容
- 应用场景
- 电商推荐
- 内容推荐
- 约会应用
金句 / Highlights
值得收藏与分享的关键句。
Qdrant 1.6 支持正负样本混合输入,提升推荐系统灵活性。
新 API 提供 average_vector 和 best_score 两种搜索策略。
推荐 API 向后兼容,无需代码修改即可升级。
通过 Qdrant 的新 API 提供更好的推荐
返回 Qdrant 文章
Kacper Łukawski
·
2023 年 10 月 25 日
对于像 Qdrant 这样的向量搜索引擎,最常见的用例是使用单个查询向量进行语义搜索。给定查询,我们可以将其向量化(嵌入)并找到索引中最接近的点。但向量相似性不仅仅用于搜索,推荐系统就是一个很好的例子。推荐可以被视为一个多目标搜索,我们希望找到与正面示例接近且与负面示例远离的项目。这种使用向量数据库的方式有多种应用,包括电子商务、内容或甚至约会应用的推荐系统。
Qdrant 已经提供了一段时间的推荐 API,而在最新的版本 Qdrant 1.6 中,我们很高兴为您提供更多的灵活性和对推荐 API 的控制。在这里,我们将讨论一些内部实现,并展示它们如何在实践中使用。
旧版推荐 API 的回顾
Qdrant 之前的推荐 API 有一些限制。首先,必须为正面和负面示例点传递向量 ID。如果您想直接使用向量嵌入,您必须在集合中创建一个新点,或者通过使用搜索 API 来模拟推荐 API 的行为。此外,在之前的 Qdrant 版本中,您始终需要提供至少一个正面示例。这一要求是基于将多个样本组合成一个查询向量所使用的算法。这是一种简单但有效的方法。然而,如果您唯一拥有的信息是用户不喜欢某些物品,您无法直接使用它。
Qdrant 1.6 带来了一个更灵活的 API。现在,您可以提供正面和负面示例的 ID 和向量。您甚至可以在单个请求中将它们组合在一起。这使得新的实现与旧版本兼容,因此您可以轻松升级现有的 Qdrant 实例,而无需对代码进行任何更改。并且 API 的默认行为仍然与之前相同。然而,我们扩展了 API,因此现在您可以选择如何查找推荐点的策略。
POST /collections/{collection_name}/points/recommend
{
"positive": [100, 231],
"negative": [718, [0.2, 0.3, 0.4, 0.5]],
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "London"
}
}
]
},
"strategy": "average_vector",
"limit": 3
}请求中有两个关键变化。首先,我们可以调整搜索策略,并将其设置为 average_vector(默认)或 best_score。此外,我们可以将 ID(718)和嵌入([0.2, 0.3, 0.4, 0.5])作为正面和负面示例传递。
HNSW ANN 示例和策略
让我们从一个示例开始,帮助您理解 HNSW 图。假设您想前往另一个大陆上的一个小城市:
- 您从家乡出发,乘坐巴士前往当地机场。
- 然后,乘坐航班前往最近的枢纽。
- 从那里,您需要乘坐另一班航班前往目标大陆的枢纽。
- 希望最后一班航班能到达您的目标城市。
- 您仍然需要通过当地的交通方式再走一段路才能到达最终地址。
这次旅程类似于 Qdrant 在近似最近邻搜索中使用 HNSW 图的方式。
HNSW 是一个由向量(嵌入)组成的多层图,连接关系基于向量的接近程度。最顶层的点最少,这些点之间的距离最大。随着层数的深入,点的数量逐渐增多,点之间的距离也逐渐变小。图的构建方式使得每一层的点都连接到其最近的邻居。
特定层中的所有点也存在于下一层中,因此可以在不改变位置的情况下切换搜索层。在交通网络的例子中,顶层可能代表航空枢纽,这些枢纽连接良好,但机场之间的距离较大。中层则由地方机场、铁路和巴士组成,密度更高,距离更小。最后,最底层由本地交通工具组成,这是最密集的一层,点之间的距离也最小。
在旅行时,你不需要检查所有可能的连接。你可以选择一次国际航班,然后是本地航班,最后是巴士或出租车。所有决定都是基于点之间的距离做出的。
HNSW 中的搜索过程也基于类似的图遍历方式。从顶层的入口点开始,找到其最近的点,然后使用该点作为进入下一层(更密集层)的入口点。这个过程一直重复,直到到达最底层。已访问的点和原始查询向量之间的距离会被保存在内存中。如果当前点的邻居中没有比当前最佳匹配更好的点,那么可以停止遍历,因为这已经是一个局部最小值。我们从最大的尺度开始,然后逐步缩小范围。
在这个过于简化的例子中,我们假设点之间的距离是唯一重要的因素。实际上,我们可能还希望考虑其他标准,例如票价,或者由于某些限制而避开某些特定地点。这意味着,选择最佳匹配有各种策略,这在向量推荐中也是如此。我们可以通过改变在遍历过程中计算候选点得分的方式来确定遍历 HNSW 图的路径。默认行为是基于纯距离,但 Qdrant 1.6 为推荐 API 提供了两种策略。
平均向量
默认策略称为 average_vector,它基于正例和负例的平均值。它简化了推荐过程,将其转换为单个向量搜索。它支持点 ID 和向量作为参数。例如,你可以基于现有点的过去交互记录和查询向量嵌入来获取推荐。在内部,该机制基于正例和负例的平均值,并通过以下公式计算:
$$ \text{平均向量} = \text{avg}(\text{正向量}) + \left( \text{avg}(\text{正向量}) - \text{avg}(\text{负向量}) \right) $$
average_vector 将推荐问题转换为单个向量搜索。
新的亮点 - 最佳得分
新的策略称为 best_score。它不依赖于平均值,更加灵活。它允许你仅传递负样本,并在内部使用一种略微更复杂的算法。
在 HNSW 图遍历的每一步中,都会选择最佳得分。我们分别计算已遍历的点与每个正例和负例之间的距离。在最佳得分策略中,不再只有一个查询向量,而是有一组正例和负例的查询。因此,对于查询中的每个样本,我们都会得到一组距离值,每个样本对应一个距离值。下一步,我们只需分别取正例和负例的最佳得分,生成两个独立的值。最佳得分只是查询点到正例和负例的最近距离。其思想是:如果一个点比任何正例更接近任何负例,我们就不希望它被选中。我们对接近负例的情况进行惩罚,因此,我们不再直接使用相似度值,而是检查它是否更接近正例还是负例。以下公式用于计算遍历的潜在点的得分:
// Sigmoid 函数用于将得分归一化到 0 到 1 之间
let
sigmoid
=
|
x
|
0.5
*
(
1.0
+
(
x
/
(
1.0
+
x
.
abs
())));
let
score
=
if
best_positive_score
>
best_negative_score
{
sigmoid
(
best_positive_score
)
}
else
{
-
sigmoid
(
best_negative_score
)
};如果该点更接近负例,我们会通过取最佳负例得分的负平方值来惩罚它。对于更接近的负例,候选点的得分将始终小于或等于零,从而显著降低选择该点的可能性。然而,如果最佳负例得分高于最佳正例得分,我们仍然倾向于那些远离负例的点。该过程有效地将遍历过程从负例中拉离。
如果你想了解更多关于 HNSW 内部机制的内容,可以阅读关于可过滤 HNSW 的文章,该文章对主题进行了全面的介绍。
食物发现演示
我们的食物发现演示是一个基于新推荐 API 构建的应用程序。它允许你根据喜欢和不喜欢的照片找到一顿饭。由于新的 Qdrant 版本,有一些更新:
- 在推荐请求中包含多个文本查询的能力。之前,我们只允许传递一个查询以解决冷启动问题。现在,你可以传递多个查询,并将它们与喜欢/不喜欢的照片混合。这得益于参数的新灵活性。我们可以在同一个请求中传递点 ID 和嵌入向量,而用户查询显然不属于集合的一部分。
- 在推荐策略之间切换。你现在可以选择使用平均向量或最佳得分的评分算法。
策略之间的差异
食物发现演示的 UI 允许你切换策略。最佳向量是默认的,但只需单击一次,你就可以看到使用旧的平均向量策略时结果的差异。
如果你只选择一个正例,两种算法的工作方式是相同的。
##### 一个正例
只有当你开始添加更多示例,特别是当你选择一些负例时,差异才会显现出来。
##### 一个正例和一个负例
我们添加的喜欢和不喜欢的示例越多,最佳得分策略的结果就会越多样化。在旧策略中,只有一个向量,所以所有示例都与它相似。而新策略则分别考虑所有示例,使多样性更加丰富。
##### 多个正例和负例
选择合适的策略取决于数据集,嵌入向量在此过程中起着重要作用。因此,总是值得尝试这两种方法,并在特定情况下比较结果。
#### 仅处理负例
在我们的 Food Discovery 演示中,仅传递负例图像可以作为一种异常检测机制。虽然数据集本应只包含食物照片,但实际上并非如此。一种简单的找出这些异常值的方法是将食物图像作为负例传递,这样得到的结果将是“最不像”食物的图像。在我们的情况下,你将会看到药瓶和书籍。
average_vector 策略仍然需要至少提供一个正例!然而,由于演示中使用的集合是为余弦距离设置的,我们使用了前一篇文章中描述的技巧来模拟它。简而言之,如果你只传递负例,它们的向量会被平均,然后将得到的向量取反,作为查询发送到搜索端点。
##### 仅负例
然而,这两种方法返回的结果不同,因此它们各自在不同问题和不同数据集的使用场景中都有其适用之处。
#### 多模态的挑战
Food Discovery 使用了 CLIP 嵌入模型,这是一个多模态模型,能够将图像和文本编码到相同的向量空间中。使用该模型可以进行图像查询、文本查询,或者两者的结合。我们在更新后的演示中利用了这一机制,允许你传递文本查询以进一步筛选结果。
##### 单个文本查询
文本查询可能会与喜欢和不喜欢的照片混合,因此你可以将它们合并到一个请求中。然而,如果你开始添加负例,可能会对新策略所实现的结果感到惊讶。
##### 带有负例的单个文本查询
这是一个与嵌入本身相关的问题。我们的数据集中包含许多彼此之间非常接近的图像嵌入。另一方面,我们的文本查询与大多数图像嵌入相距较远,但与其中一些相对接近,因此文本到图像的搜索似乎效果不错。当所有查询项都来自同一领域(例如仅文本)时,一切运行良好。然而,如果我们混合正文本和负图像嵌入,最佳得分的结果会被负样本所淹没,因为它们与数据集嵌入更接近。如果你遇到这样的问题,average_vector 策略可能是更好的选择。
试试演示
Food Discovery 演示可在在线上使用,因此你可以测试并查看差异。这是一个开源项目,因此你可以轻松地在自己的环境中部署。源代码可在 GitHub 仓库中找到,README 文件描述了设置过程。由于计算嵌入需要一些时间,我们预先计算了它们,并将其导出为快照,这可以轻松地导入到任何 Qdrant 实例中。尽管如此,Qdrant Cloud 是最简单的开始方式。