召回阶段ANN方法HNSW介绍

1. 召回阶段的利器

在推荐系统和RAG(Retrieval-Augmented Generation)系统中,召回阶段是检索大量候选项的关键环节。HNSW(Hierarchical Navigable Small World)作为一种高效的近似最近邻搜索(ANN)方法,在这些系统的召回阶段发挥重要作用。

1.1 推荐系统中的召回阶段

召回阶段用于从海量候选项(如商品、电影、文章等)中选取较小的一部分,以供后续排序阶段进一步筛选。

传统最近邻搜索(NNS)方法计算复杂度高,特别是在大规模数据集上难以实时响应,因此通常使用近似最近邻搜索(ANN)。

HNSW通过构建多层小世界图结构,使得查询时能够快速找到近似最近邻,比传统方法(如暴力搜索或k-d树)更高效,适合处理大规模数据。

例如,在个性化推荐系统中,当用户点击某个商品或文章时,系统可以利用HNSW快速找到与该物品相似的候选集合。

1.2 RAG系统中的召回阶段

RAG系统需要从外部知识库(如文档数据库、知识图谱、向量数据库)中检索相关内容,以增强生成式模型的回答能力。

由于文本嵌入通常存储为高维向量,计算精确最近邻(NNS)代价昂贵,因此通常采用ANN(Approximate Nearest Neighbor search,近似最近邻搜索)方法,如HNSW(Hierarchical Navigate Small Word)、FAISS等。

HNSW在RAG的向量检索任务中用于高效地从知识库中找出与用户问题相关的文本块,提高查询效率和生成回答的质量。

例如,在基于RAG的问答系统中,当用户提出问题时,系统会先计算问题的向量表示,然后利用HNSW从知识库中检索最相关的文档,最终将检索结果输入LLM(大语言模型)进行回答。

无论是在推荐系统还是RAG系统,召回阶段都是高效信息检索的关键,而HNSW作为一种流行的ANN方法,能够在大规模数据上实现低延迟、高召回率的近似最近邻搜索。

2. HNSW

2.1 HNSW基础

向量搜索索引通常分为以下四类:

  1. 基于哈希(Hash-based)

  2. 基于树(Tree-based)

  3. 基于聚类(Cluster-based)

  4. 基于图(Graph-based)

HNSW 属于基于图(Graph-based)的索引算法。它结合了两个核心概念:

概率跳表(Probability Skip List)

可导航小世界(Navigable Small World, NSW)图

2.1.1 概率跳表(Probability Skip List)

1. 传统链表的局限性

在计算机科学中,链表(Linked List)是一种基本的数据结构,其中每个元素(节点)都包含一个指向下一个元素的指针。然而,链表的一个主要问题是随机访问的时间复杂度是 O(n)。这意味着如果我们想在链表中查找某个元素,需要逐个遍历,效率较低。

2. 跳表(Skip List)的改进

跳表是一种改进版的链表,它通过引入多个层级(Level)来加速查找过程:

顶层索引连接跨度更大的节点,可以快速跳跃,减少搜索路径长度;

随着层级下降,索引的跨度逐渐变短,提供更精确的访问路径;

最底层是完整的原始链表,包含所有元素。

这种设计可以将查找时间复杂度从 O(n) 降低到 O(log n),但代价是增加了一些存储开销(从 O(n) 变为 O(n log n))。

在这里插入图片描述

在跳表中,我们可以看到:

最高层的链表跨越多个节点,提供长跳跃,使得搜索变得高效。

每下降一层,索引的跨度变短,逐渐精细化查找,最终在最底层找到目标元素。

(1)跳表的搜索过程

搜索从最上层开始,每次选择下一个节点,直到遇到比目标值 大的节点

然后回退到上一个节点,并进入下一层继续搜索。

直到到达最底层,找到目标元素。

这种跳跃式搜索方式极大地减少了搜索所需的步骤,使得查询复杂度从 O(n)(单链表) 降至 O(log n)

(2)跳表的插入过程

插入过程是概率性的:

  1. 先确定该元素首次出现在哪一层。

  2. 每一层的元素出现在更高层的概率是固定的(p)

  3. 如果元素首次出现在层 l,它也会被加入到 l-1, l-2… 这些更底层。

平衡性分析:虽然可能出现某一层的索引结构不够理想(类似普通链表),但这种概率极低,因此跳表在大多数情况下都能保证高效的搜索性能。

2.1.2 可导航小世界(NSW)

跳表让我们理解了如何通过多层索引提高搜索速度,但 HNSW 还使用了可导航小世界(Navigable Small World, NSW)图,它是一个基于图的近似最近邻搜索(ANN)算法。

(1)NSW 的基本概念

NSW 主要用于在数据集中查找近似最近邻,其核心思想是:

每个节点在网络中有不同的连接,包括短程(短距离)、中程(中距离)和长程(长距离)连接

查询时,我们从某个预定义的入口点出发,依次评估相邻节点,并跳跃到最接近目标的节点。

这个过程持续进行,直到找到最近邻。

(2)NSW 的搜索过程

在这里插入图片描述

  1. 从某个固定的入口(蓝色)点开始搜索(类似跳表的顶层)。

  2. 贪心选择当前与目标最相近的节点,并向其连接的下一个节点跳跃。

  3. 重复步骤 2,直到找到最接近查询向量的点。

这种搜索方式被称为贪心搜索(Greedy Search),其优势在于:

避免了暴力搜索的 O(n) 复杂度

通过长距离跳跃,快速接近目标

(3)NSW 在大规模数据集上的局限性

适用于小型 NSW(几百到几千个节点),但对于大规模数据(百万级),贪心搜索的效果会下降。

解决方法:

增加节点的连接数(短、中、长范围连接),增强全局可达性。

但这也会导致网络复杂度上升,进而增加搜索时间。

极端情况:如果每个节点都与所有其他节点连接,那么 NSW 退化为暴力搜索(O(n)),失去优化的意义。

2.1.3 NSW 如何应用于向量搜索

(1)NSW 适用于向量搜索

设想数据集中的所有向量都是 NSW 图中的节点

长距离连接对应于不相似的向量,可以帮助快速跳跃到不同区域。

短距离连接对应于相似的向量,可以帮助精细搜索。

采用相似性度量(如 L2 距离、内积等)来决定相邻节点的连接方式。

(2)NSW 搜索示例

给定一个查询向量,我们从某个固定的入口点开始搜索。

逐步沿着最相似的向量跳跃,逐步收敛到最近邻

2.2 HNSW详解

2.2.1 HNSW 背景

在进行向量搜索(Vector Search)或向量相似性搜索(Vector Similarity Search)时,我们通常面对数亿甚至数十亿个向量。普通的 NSW(Navigable Small World)在小规模数据上运行良好,但当数据规模增长时,NSW 结构的效率会降低,因此我们需要更好的图结构来提升搜索速度和准确性。

HNSW(分层可导航小世界)通过引入跳表(Skip List)的概念来扩展 NSW,使其适用于大规模数据集。HNSW 的关键思想是在 NSW 图的基础上构建多个层级

​ • 最高层(Layer 2) 仅包含少量节点,并且具有长距离连接,使得搜索能够跨越较大的数据空间。

​ • 底层(Layer 0) 包含所有节点,连接距离较短,提供精细的最近邻搜索能力。

​ • 搜索过程最上层开始,逐步向下层推进,最终在最底层找到最接近的邻居。

2.2.2 HNSW 结构解析

HNSW 结合了 NSW 图结构跳表的分层策略,形成了一个高效的 ANN 搜索算法在这里插入图片描述

(1)HNSW 分层结构

上图展示了 HNSW 的多层结构:

Layer 2(最顶层): 仅包含少量节点,节点之间的连接跨度较大。

Layer 1(中间层): 连接密度更高,搜索精度提高。

Layer 0(底层): 包含所有节点,提供精确的最近邻搜索。

特性: 层级越高,节点数越少,但连接跨度越大,使得搜索可以先进行大范围跳跃,然后逐步精细化查找,最终找到最近邻。

(2)HNSW 搜索过程

​ • 步骤 1:从最高层(Layer 2)开始

​ • 选择一个预定义的入口点,在该层进行贪心搜索,找到当前层最接近查询向量的节点。

​ • 步骤 2:向下一层(Layer 1)移动

​ • 在较低层中,以上一层找到的最接近点作为起点,继续进行贪心搜索。

​ • 步骤 3:继续向底层(Layer 0)推进

​ • 这一过程不断重复,直到到达底层,在最精确的索引级别找到最近邻。

这种逐层向下的搜索方式借鉴了跳表的思想,使得搜索过程兼顾了全局跳跃能力(高层搜索)和局部精细查找能力(底层搜索)。

2.2.3 HNSW 的插入过程

HNSW 的插入过程与跳表类似:

  1. 在最高层查找最近邻

在最上层(例如 Layer 2),找到与新向量 v 最接近的邻居。

  1. 逐层向下查找最近邻

依次进入下一层(Layer 1、Layer 0),重复搜索过程,确保在各个层级都找到最近邻。

  1. 确定连接方式

设定最大连接数 M(HNSW 论文中使用 M=16 或 M=32),决定每个节点最多可以连接多少个邻居。

通常,邻居选择为当前层中最接近的 M 个节点,但也可以采用其他启发式策略。

注意: HNSW 不会强制约束同一层中节点之间的最小距离,因此可能会出现图结构不均衡的情况。但随着数据规模增大,这种情况的概率极低。

2.2.4. HNSW 层级控制

随机性

在 HNSW 中,一个向量出现在高层的概率是指数递减的

论文使用的计算公式是: floor ( − ln ⁡ ( rand ( 0 , 1 ) ) ) \text{floor}(-\ln(\text{rand}(0,1))) floor(ln(rand(0,1)))

其中,rand(0,1) 是 [0,1] 范围内的随机数,服从均匀分布。

这个公式意味着:

大多数节点仅出现在底层(Layer 0)

极少数节点会出现在高层(Layer 1, Layer 2),从而起到”索引枢纽”的作用,连接更多远距离的点。

这与跳表的分层概率策略相似,使得搜索时能够快速跳跃,同时保证局部的精确性。

HNSW 采用层次结构(multi-layer hierarchy),每个向量被插入时,需要决定它会出现在哪些层。这由参数 L(层数) 和 m_L(level multiplier) 控制。

(1)层级分配的概率

每个向量的插入层数 由一个概率函数决定。

m_L(level multiplier) 是一个归一化因子,影响层级分配的概率:

m_L ≈ 0 时,所有向量都插入到Layer 0(底层),没有多层结构。

m_L 较大 时,更多向量会被分配到更高层

经验法则(Rule of Thumb):

HNSW 的研究者发现最佳性能通常在 m L = 1 / l n ( M ) m_L = 1/ln(M) mL=1/ln(M)时取得,其中 M 是最大邻居数

(2)层级分配的影响

降低 m_L(更多向量只出现在底层)

可以减少不同层之间的共享邻居(overlap)。

但会增加搜索路径的平均长度(因为高层更少)。

增加 m_L(更多向量分布在不同层)

减少搜索的遍历次数,提高速度。

但不同层之间的邻居可能会重复,增加存储和计算成本。

2.2.5 HNSW 关键优化点

利用层级结构减少搜索路径:先在高层进行大范围搜索,再逐层向下细化搜索。

控制连接数 M 以优化效率:M 过小会影响搜索精度,M 过大会导致计算量增加。

概率性分层策略:保证大多数点在底层进行精细搜索,少量枢纽节点在高层提供快速跳跃能力。

2.2.6 HNSW vs 其他 ANN 方法

方法数据结构查询速度构建成本适用于大规模数据
Brute Force线性搜索慢(O(n))
KD-Tree树结构中等(O(log n))
IVF (Inverted File)聚类索引
HNSW分层图结构极高中等✔✔

2.2.7 关键参数回顾

参数作用影响
L(层数)控制 HNSW 的层级深度层数越多,全局搜索能力越强,但索引构建时间增加
m_L(level multiplier)控制向量出现在高层的概率低 m_L:大部分点在底层,搜索路径变长;高 m_L:点分布在多层,搜索更快
efConstruction控制构建时寻找的最近邻候选数低 efConstruction:连接少,搜索快但可能不准确;高 efConstruction:连接多,搜索准确但索引大
M_max限制每个点的最大连接数连接数过多会增加存储开销,但过少可能影响搜索质量
M_max0仅作用于 Layer 0,限制底层最大连接数底层通常需要更多连接,以提高搜索精度

2.3 HNSW 的实现解析

HNSW(Hierarchical Navigable Small Worlds)是一种基于图的**近似最近邻搜索(ANN)**方法,由于其复杂性,这里只实现一个基础版本。我们将逐步分析代码并详细解释其各个部分的逻辑。

2.3.1 创建数据集

首先,我们需要一个 128 维的向量数据集:

import numpy as np
dataset = np.random.normal(size=(1000, 128))

这会生成一个 1000 个样本、每个样本 128 维的标准正态分布向量数据集。

2.3.2 构建 HNSW 索引

HNSW 结构包含多个层,每一层都是一个 NSW 图。我们用一个 list of lists(即嵌套列表)来存储索引:

L = 5  # 5 层 HNSW
index = [[] for _ in range(L)]
  • L = 5 代表 HNSW 结构有 5 层
  • index 是一个包含 L 个空列表的列表,每个列表代表 HNSW 的一层。

每个节点存储的信息

在每一层的图中,每个节点存储为一个 三元组 (3-tuple)

(vector, links, lower_layer_index)
  • vector:向量数据。
  • links:当前层的邻居节点索引列表。
  • lower_layer_index:当前节点在下一层(更底层)对应的索引(最底层的这个值设为 None)。

2.3.3 搜索 HNSW 层

HNSW 的搜索过程是 逐层向下 进行的,因此我们先实现一个 单层搜索 方法 _search_layer。

搜索单层

def _search_layer(graph, entry, query, ef=1):
    best = (np.linalg.norm(graph[entry][0] - query), entry)  # 初始最优点
    nns = [best]  # 记录最近邻
    visit = set(best)  # 已访问的节点集合
    candid = [best]  # 候选点(优先队列)
    heapify(candid)

    while candid:
        cv = heappop(candid)  # 取出当前最接近的点

        if nns[-1][0] < cv[0]:  # 如果当前最近邻比候选更好,则停止
            break

        # 遍历候选点 cv 的所有邻居
        for e in graph[cv[1]][1]:
            d = np.linalg.norm(graph[e][0] - query)
            if (d, e) not in visit:
                visit.add((d, e))

                # 仅插入比当前最近邻更好的点
                if d < nns[-1][0] or len(nns) < ef:
                    heappush(candid, (d, e))
                    insort(nns, (d, e))
                    if len(nns) > ef:
                        nns.pop()

    return nns

解释

  • 该函数在指定 graph(当前层)中搜索 query 的最近邻。
  • 优先队列(heap)+ 贪心搜索(Greedy Search)
  • 采用 heap(小根堆)维护候选集合 candid。
  • 使用 nns 存储当前找到的最近邻集合(大小最多 ef)。
  • 搜索终止条件
  • 当 candid 为空(没有更多候选)。
  • 当 nns 的最后一个点比 candid 里所有点都更优。

2.3.4 最高层搜索 HNSW

我们实现 search() 来进行多层搜索:

def search(index, query, ef=1):
    if not index[0]:  # 若索引为空,直接返回
        return []

    best_v = 0  # 设定初始最优点
    for graph in index:
        best_d, best_v = _search_layer(graph, best_v, query, ef=1)[0]
        if graph[best_v][2]:  # 若存在下一层,继续搜索
            best_v = graph[best_v][2]
        else:
            return _search_layer(graph, best_v, query, ef=ef)

解释

  1. 从最高层开始搜索
  • 先从 index[0](最高层)进入。
  • 在每一层调用 _search_layer() 找到 query 在该层的最近邻 best_v。
  • 如果当前层有更低层,则继续进入更低层搜索。
  1. 到达底层后,返回最终搜索结果

2.3.5 HNSW 插入策略

确定插入层

HNSW 采用随机层级分配

def _get_insert_layer(L, mL):
    l = -int(np.log(np.random.random()) * mL)
    return min(l, L)

随机数取对数,确保高层节点较少,低层节点较多。

这与跳表的层级分布类似。

插入操作

def insert(vec, efc=10):
    if not index[0]:  # 若索引为空,直接初始化
        i = None
        for graph in index[::-1]:
            graph.append((vec, [], i))
            i = 0
        return

    l = _get_insert_layer(1/np.log(L))

    start_v = 0
    for n, graph in enumerate(index):

        if n < l:  # 在层 L 以上层搜索最近邻
            _, start_v = _search_layer(graph, start_v, vec, ef=1)[0]
        else:
            node = (vec, [], len(index[n+1]) if n < L-1 else None)
            nns = _search_layer(graph, start_v, vec, ef=efc)
            for nn in nns:
                node[1].append(nn[1])  # 建立邻居关系
                graph[nn[1]][1].append(len(graph))
            graph.append(node)

        # 设置起始点为下一层最近邻
        start_v = graph[start_v][2]

解释

  1. 若索引为空,直接插入所有层

  2. 确定插入层 l

  3. 找到 l 层的最近邻

逐层搜索 query 在 l 以上层的最近邻 start_v。

  1. 在 l 层及以下插入 vec

通过 _search_layer 找到最近邻,并建立邻居关系。

2.3.6 HNSW 代码封装

将所有代码封装成一个 HNSW 类:

from bisect import insort
from heapq import heapify, heappop, heappush

import numpy as np

class HNSW:

    def __init__(self, L=5, mL=0.62, efc=10):
        self._L = L
        self._mL = mL
        self._efc = efc
        self._index = [[] for _ in range(L)]

    def search(self, query, ef=1):
        if not self._index[0]: return []
        best_v = 0
        for graph in self._index:
            best_d, best_v = HNSW._search_layer(graph, best_v, query, ef=1)[0]
            if graph[best_v][2]: best_v = graph[best_v][2]
            else: return HNSW._search_layer(graph, best_v, query, ef=ef)

    def insert(self, vec, efc=10):
        if not self._index[0]:
            i = None
            for graph in self._index[::-1]: graph.append((vec, [], i)); i = 0
            return
        l = self._get_insert_layer()
        start_v = 0
        for n, graph in enumerate(self._index):
            if n < l:
                _, start_v = self._search_layer(graph, start_v, vec, ef=1)[0]
            else:
                node = (vec, [], len(self._index[n+1]) if n < self._L-1 else None)
                nns = self._search_layer(graph, start_v, vec, ef=efc)
                for nn in nns: node[1].append(nn[1]); graph[nn[1]][1].append(len(graph))
                graph.append(node)
            start_v = graph[start_v][2]

总结

​ • 搜索: 从顶层搜索,逐层向下寻找最近邻。

​ • 插入: 计算插入层,在该层及以下找到最近邻并建立连接。

​ • 索引存储: 采用 list of lists 结构,每层存储 (vector, neighbors, lower_layer_index)。

这样,我们就实现了一个简化版的 HNSW! 🚀

3. 参考

<think>好的,我现在需要帮助用户理解Faiss中的向量召回。首先,用户之前询问了top_k的含义,现在又问向量召回,可能是在推荐系统或搜索场景中使用Faiss。 首先,用户可能对Faiss的基础有一定了解,但需要更深入的概念。向量召回通常指的是从大规模数据中快速找到相似项的过程,这在推荐、搜索中很常见。我需要解释清楚召回在Faiss中的角色,以及它与其他步骤的区别,比如排序。 接下来,用户可能想知道Faiss如何实现高效的向量召回。可能需要提到索引类型,比如IVF、HNSW,以及它们如何加速搜索。同时,用户可能关心实际应用中的参数设置,比如nprobe对召回率和性能的影响。 用户可能还有实际应用中的问题,比如如何平衡速度和准确性。需要结合之前的top_k参数,说明如何调整参数来优化召回结果。此外,可能要注意到用户提到的“召回”在推荐系统中的两层含义,既要解释Faiss的召回,也要对比业务中的召回阶段。 还要考虑用户可能的误区,比如混淆精确搜索和近似搜索。Faiss的召回属于近似最近邻搜索,需要在效率和精度之间权衡。需要说明为什么选择Faiss,比如处理高维数据、大规模数据集的优势。 最后,需要给出示例代码,让用户看到具体如何使用Faiss进行向量召回,并结合之前的top_k示例,展示完整流程。同时提醒注意事项,比如预处理、参数调优的重要性,以及结果后处理的必要性。</think>在 **Faiss** 中,“向量召回”(Vector Recall)通常指从大规模向量库中**快速检索出与查询向量最相似的一批候选结果**的过程。这是推荐系统、搜索引擎、语义匹配等场景的核心步骤之一,目的是从海量数据中筛选出“相关性高”的候选集,供后续业务逻辑(如精排、过滤)处理。 --- ### **核心概念** 1. **召回的本质** - **输入**:一个查询向量(例如用户兴趣的嵌入表示)。 - **输出**:一组候选向量(例如商品、文档、图片的嵌入表示),通常是相似度最高的 `top_k` 结果。 - **目标**:在可接受的延迟内,尽可能覆盖所有潜在相关的结果(高召回率)。 2. **与“精排”的区别** | 阶段 | 目标 | 数据量 | 算法复杂度 | |---|---|---|---| | **召回** | 快速筛选候选集(粗筛) | 亿级向量 | 低复杂度(近似算法) | | **精排** | 精准排序候选集(细排) | 千级向量 | 高复杂度(复杂模型) | --- ### **Faiss 如何实现高效向量召回?** Faiss 的核心是通过**索引结构**(Index)加速相似性搜索,主要依赖以下技术: #### **1. 索引类型与加速原理** | 索引类型 | 原理 | 适用场景 | |----------------------|---------------| | **IVF (倒排文件)** | 将向量聚类为 `nlist` 个桶,搜索时仅检查最近邻的几个聚类桶(`nprobe` 参数控制)。 | 大规模数据,平衡速度与精度。 | | **HNSW (层级图)** | 构建多层图结构,通过跳跃连接快速定位近邻。 | 高维数据,追求高召回率。 | | **PQ (乘积量化)** | 压缩向量为低维编码,通过量化近似距离计算。 | 内存敏感场景(如十亿级数据)。| | **Flat (暴力搜索)** | 精确计算所有距离,无压缩。 | 小规模数据或需100%召回率。 | #### **2. 关键参数与召回率** - **`nprobe`(IVF索引)**:搜索的聚类桶数量。增大 `nprobe` 会提高召回率,但降低速度。 - **`efSearch`(HNSW索引)**:搜索时的遍历深度。值越大,召回率越高,但延迟增加。 - **`k`(top_k)**:最终返回的候选数量,直接影响召回结果的上界。 --- ### **Faiss 向量召回流程** ```python import faiss import numpy as np # 1. 准备数据 dim = 512 # 向量维度 database_size = 1_000_000 queries = np.random.rand(10, dim).astype('float32') # 10个查询向量 vectors = np.random.rand(database_size, dim).astype('float32') # 向量库 # 2. 构建索引(以IVF+PQ为例) nlist = 1024 # 聚类中心数 quantizer = faiss.IndexFlatL2(dim) index = faiss.IndexIVFPQ(quantizer, dim, nlist, 16, 8) # PQ将向量压缩为16x8bit index.train(vectors) # 训练索引 index.add(vectors) # 添加数据 # 3. 设置搜索参数 index.nprobe = 32 # 搜索32个最近的聚类桶 # 4. 执行召回(返回每个查询的top 100) k = 100 distances, ids = index.search(queries, k) # ids.shape = (10, 100),表示10个查询各自召回100个候选ID ``` --- ### **实际应用场景** 1. **推荐系统** - 用户行为向量召回相似商品(如“看了又看”)。 2. **语义搜索** - 文本嵌入向量召回相关文档或问答对。 3. **图像检索** - 以图搜图,通过图像嵌入召回相似图片。 --- ### **召回效果优化方向** | 方向 | 方法 | |--| | **索引选择** | 根据数据规模、维度、精度要求选择索引(如十亿级数据用 `IVF_PQ`)。 | | **参数调优** | 调整 `nprobe`、`efSearch` 平衡速度与召回率。 | | **向量归一化** | 若使用余弦相似度,需先将向量归一化(Faiss 的 `IndexFlatIP` 支持内积)。| | **多阶段召回** | 结合多个索引或算法(如先用IVF粗筛,再用HNSW精筛)。 | --- ### **注意事项** 1. **召回率 vs 延迟** 召回率越高,计算开销越大。需根据业务需求权衡(如实时搜索要求低延迟,可能牺牲部分召回率)。 2. **数据分布** 若数据分布不均匀(如存在热点向量),需调整聚类参数或使用混合索引。 3. **动态更新** Faiss 部分索引(如 `IVF`)支持动态添加向量,但频繁更新可能影响性能,需定期重建索引。 --- ### **常见误区** - **“召回”与“业务召回层”的区别** Faiss 的召回是技术层面的相似性搜索,而业务系统中的召回层可能包含多路召回(如协同过滤 + 向量召回 + 规则召回)。 - **精确召回 vs 近似召回** Faiss 默认使用近似搜索(追求速度),若需100%精确结果,需使用 `IndexFlatL2` 或 `IndexFlatIP`(但速度极慢)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值