使用向量检索非结构化数据

当传统的关键词搜索无法满足语义理解、商品推荐或以图搜图等复杂应用场景时,您需要一种更为先进的相似性搜索技术。PolarSearch的向量检索功能通过将文本、图像等非结构化数据转化为多维向量,能够在海量数据中快速而精准地找到与查询目标最为相似的结果,从而有效提升应用的智能化水平。

功能简介

向量检索,也称为相似性搜索,是一种通过比较向量之间的“距离”来查找最相似数据的技术,它与依赖精确关键词匹配的传统搜索有着本质区别。

它的核心思想是将现实世界中的文本、图像、音频等非结构化数据,通过深度学习模型(如LLM)转换为向量嵌入(Embedding) 的数值表示。这些多维向量能够捕捉数据的深层语义信息。

当您发起一次查询时,PolarSearch会将您的查询内容同样转换为向量,然后执行k-最近邻(k-Nearest Neighbor, k-NN)搜索。这是一种核心算法,其目标是在海量数据中找到与您的查询向量“距离”最近的k个向量。这里的k是一个由您定义的数字(例如,当k=5时,即代表查找最相似的5个结果)。最终,PolarSearch会返回这k个最相似的结果。

为实现高效检索,PolarSearch依赖两大核心组件:向量索引和向量存储优化。

  • 向量索引:为了避免在海量数据中进行全量计算,需要预先建立向量索引。索引能够根据向量数据的特征构建一种为查询而优化的数据结构,在查询时能够大幅缩小搜索范围,从而显著提升检索性能。PolarSearch内支持了多种类型的向量索引,以下为您主要介绍业界主流的HNSWIVF索引:

    • HNSW (Hierarchical Navigable Small World):一种基于图的索引,具有高性能和高召回率的优点,但内存开销也相应较大。适用于对查询延迟和精度要求极高,且数据集大小在内存容量范围内的场景。

    • IVF (Inverted File):一种基于聚类的倒排索引,内存占用较低,更适合需要处理超大规模数据集且内存受限的场景,但其搜索精度通常略低于HNSW。

  • 向量存储优化:向量数据,尤其是高维向量,会占用大量内存和存储空间。PolarSearch提供多种优化技术来降低资源消耗。

    • 向量量化:通过降低向量数值的精度来压缩数据,显著减少空间占用,是一种在压缩率和精度之间取得平衡的技术。PolarSearch支持乘积量化(PQ)、标量量化(SQ)和二值量化(BQ)。

    • 基于磁盘的存储:对于低内存环境,允许将部分索引数据存储在磁盘上,以较低的内存成本运行向量检索服务,代价是会适当增加查询延迟。

注意事项

在使用PolarSearch向量检索功能时,请您注意以下几点:

  • 索引训练要求IVF索引和PQ(乘积量化)技术在使用前需要一个独立的训练步骤。您需要提供一部分具有代表性的向量数据来训练模型,否则索引无法正常工作。

  • 内存开销HNSW索引虽然性能优异,但其图结构需要完全加载到内存中,会产生较高的内存开销。请在选择前评估您的集群内存资源。

  • 性能与成本权衡基于磁盘的向量搜索会适当增加查询延迟,请根据您的业务场景进行评估。

  • 自动训练:二值量化(BQ)的训练过程在索引构建期间自动处理,您无需进行额外的训练操作。

操作指南

要使用PolarSearch向量检索,您需要先创建一个配置好的向量检索的索引。以下是关键步骤与参数配置说明。

1. 启用k-NN并定义向量字段

在创建索引时,您需要将index.knn设置为true来启用k-NN搜索,并定义一个knn_vector类型的字段用于存储向量。

核心参数

  • engine:固定为faiss。

    说明

    Faiss (Facebook AI Similarity Search) 是一个由Meta AI开发的高性能开源库,专门用于高效的相似度搜索和海量向量数据聚类,PolarSearch使用Faiss作为其核心向量检索引擎。

  • dimension:用于指定向量的维度,需要与您模型产出的向量维度完全一致。

  • data_type:定义向量的数据类型。默认为float,您也可以选择bytebinary以优化存储。

  • space_type:定义向量相似度的计算方式(距离度量)。支持的范围如下:

    space_type

    距离度量

    说明

    l2

    L2(欧几里得距离)

    计算平方差和的平方根,对数值大小敏感。

    l1

    L1(曼哈顿距离)

    对向量各维度差值的绝对值求和。

    cosinesimil

    余弦相似度

    测量向量间的夹角,更关注方向而非大小。

    innerproduct

    内积

    计算向量点积,常用于排序场景。

    hamming

    汉明距离

    计算二进制向量中不同元素的数量。

    chebyshev

    L∞(切比雪夫距离)

    仅考虑向量各维度差值绝对值的最大值。

2. 选择并配置索引方法(HNSWIVF)

选型建议

HNSWIVF在性能、资源消耗和精度上各有侧重,适用于不同的业务场景。您可以参考下表进行快速选型:

对比维度

HNSW

IVF

查询延迟

极低。通过层级化的图结构快速定位,搜索路径短。

较低。需要先定位到簇,再在簇内搜索,路径相对较长。

召回率(精度)

高。图的连接性更好,不容易漏掉近邻点。

中到高。存在边缘效应(查询点在簇的边界),可能损失一定精度,可通过调整nprobes参数缓解。

内存占用

高。需要将完整的图结构加载到内存中。

低。主要存储聚类中心和倒排列表,内存开销远低于HNSW。

构建时间

较长。构建高质量的图结构需要复杂的计算。

较快。但需要一个额外的训练步骤来生成聚类中心。

适用场景

对查询性能和精度有极致要求,且内存资源充足的场景。例如:实时语义搜索、人脸识别。

数据集规模巨大,内存资源受限,且可以接受微小精度损失的成本敏感型场景。例如:海量商品推荐、大规模图片库检索。

操作说明

method中配置索引的具体实现方法和参数。

HNSW

HNSW通过IndexHNSWFlat实现,适用于对性能和召回率有高要求的场景。

核心参数

参数

取值范围

说明

m

正整数。

图中每个节点的最大邻居(出度)数量。此值决定了图的密度,且是影响索引质量和内存占用的最关键参数。

  • 值越大:图的连接性越好,搜索路径更优,召回率更高。但同时索引构建更慢,内存占用也越大。

  • 值越小:构建速度快,内存占用小。但可能导致搜索过早陷入局部最优,影响召回率。

  • 实践建议:通常建议取值为864之间。可以从1632开始尝试,根据召回率和内存占用的测试结果进行调整。

ef_construction

正整数,且通常应大于m

构建索引时,动态邻居列表的大小。它控制了构建图期间的搜索深度和广度。此值主要影响索引的构建时间和最终质量。

  • 值越大:在插入新节点时能探索更多的潜在邻居,构建出的图质量更高(有利于召回率),但构建时间会显著增加。

  • 实践建议:通常建议设置为m2倍或更高。如果对构建时间不敏感但追求高质量索引,可以设置为500或更高。

ef_search

正整数。

查询时,动态邻居列表的大小。它控制了查询期间的搜索深度。

说明

此参数不在创建索引时指定,而是在查询时或在索引的settings中全局设置。它是影响查询延迟和召回率的直接因素。

  • 值越大:查询时会探索更多的节点,召回率更高,但查询耗时也越长。

  • 实践建议:此值没有固定选型建议,需要通过业务压测找到延迟和召回率的最佳平衡点。可以从一个较小的值(如50100)开始,逐步增加并观察性能变化。

说明

实际创建HNSW索引时,请将下述<my-hnsw-index>替换为您的索引名称,<my_vector_field>替换为您的字段名称。同时,其他核心参数dimensiondata_typespace_typem以及ef_construction等参数请根据实际业务需求配置。

// HNSW索引创建示例,请将<my-hnsw-index>替换为您的实际的索引名称
PUT /<my-hnsw-index>
{
  "settings": {
    "index": { 
      "knn": true 
    } 
  },
  "mappings": {
    "properties": {
      "<my_vector_field>": {//请将<my_vector_field>替换为您的实际的字段名称
        "type": "knn_vector",
        "dimension": 128,
        "data_type": "float",
        "method": {
          "name": "hnsw",
          "engine": "faiss",
          "space_type": "l2",
          "parameters": {
            "m": 16,
            "ef_construction": 256,
            "ef_search": 512 // ef_search通常在查询时指定,此处为示例
          }
        }
      }
    }
  }
}

IVF

IVF通过IndexIVFFlat实现,适用于内存受限的超大规模数据集场景。

核心参数

参数

取值范围

说明

nlist

正整数。

聚类中心的数量。索引会将整个向量空间划分为nlist个区域(簇)。此值是影响IVF性能的基础。

  • 值越大:划分的区域越精细,每个簇包含的向量越少,查询时需要扫描的数据量更少,速度更快。但可能增加边缘效应,导致召回率下降,同时内存占用也会增加。

  • 值越小:每个簇包含的向量多,搜索速度慢。但召回率可能更高。

  • 实践建议:一个常见的经验法则是将nlist设置在4 * sqrt(N)16 * sqrt(N)之间,其中N是向量总数。例如,对于100万个向量,sqrt(N) = 1000,那么nlist可以考虑设置为400016000之间。通常从10244096开始是一个不错的起点。

nprobes

正整数,且通常应小于nlist

查询时,需要搜索的聚类中心(簇)的数量。此值是在查询速度和召回率之间进行权衡的最直接参数。

  • 值越大:查询时会访问更多的簇,搜索范围更广,能有效缓解边缘效应,召回率更高;但查询速度会线性下降。

  • 值越小:查询速度快,但如果查询向量恰好落在多个簇的边界,很可能因为搜索范围不够大而找不到最近邻,导致召回率低。

  • 实践建议:通常从一个较小的值开始,如1020,然后根据对召回率的要求逐步增加,直到找到可接受的性能平衡点。

说明

实际创建IVF索引时,请将下述<my-ivf-index>替换为您的索引名称,<my_vector_field>替换为您的字段名称。同时,其他核心参数dimensiondata_typespace_typenlistnprobes等参数请根据实际业务需求配置。

// IVF索引创建示例,请将<my-ivf-index>替换为您的实际的索引名称
PUT /<my-ivf-index>
{
  "settings": {
    "index": { 
      "knn": true 
    } 
  },
  "mappings": {
    "properties": {
      "<my_vector_field>": {// 请将<my_vector_field>替换为您的实际的字段名称
        "type": "knn_vector",
        "dimension": 4,
        "data_type": "byte",
        "method": {
          "name": "ivf",
          "engine": "faiss",
          "space_type": "l2",
          "parameters": {
            "nlist": 1024,
            "nprobes": 10 // nprobes通常在查询时指定,此处为示例
          }
        }
      }
    }
  }
}

3. (可选)配置存储优化

向量数据,尤其是高维浮点型向量,会占用大量内存。PolarSearch提供多种存储优化技术,通过对向量进行压缩(量化)或改变存储介质,在内存成本、查询性能和搜索精度之间取得平衡。

选型建议

在选择具体的优化策略前,您可以参考下表,快速找到最适合您业务场景的方案。

优化策略

压缩率

精度影响

训练要求

CPU开销

适用场景

标量量化 (SQ)

低 (固定2倍)

极小

无需训练

对搜索精度要求极高,希望在几乎不损失精度的前提下,获得适度内存优化的场景。

二值量化 (BQ)

高 (8-32倍)

较大

无需训练

中等

对内存极度敏感,可以接受一定(甚至较大)精度损失,以换取最大程度内存节省的场景。

乘积量化 (PQ)

最高

中等

需要训练

中等

数据集巨大,需要极致的压缩率,且愿意投入时间进行模型训练以平衡精度和内存的场景。

基于磁盘的向量存储

-

较大

无需训练

较高

内存资源极其有限,宁愿牺牲查询延迟(因磁盘I/O),也要将内存占用降至最低的成本敏感型场景。

操作说明

标量量化(SQ)

  • 工作原理:将标准的32位浮点(float)向量转换为16位浮点(fp16)向量进行存储,使内存占用直接减半。在计算距离时,会解码回32位进行,因此对精度的影响非常小。

  • 内存估算

    • 公式:内存 (GB) ≈ 1.1 * (2 * dimension + 8 * m) * num_vectors / 1024^3

    • 参数详解:

      • dimension:向量的维度。

      • m:HNSW索引中的m参数,即每个节点的最大邻居数。

      • num_vectors:向量总数。

      • 1.1:约10%的系统开销系数。

    • 示例:假设您有 100 万个向量,每个向量的维度为 256,每个向量的维度m为 16。内存需求可以估算如下:1.1 * (2 * 256 + 8 * 16) * 1,000,000 ~= 0.656 GB

  • 使用示例

    // HNSW + 标量量化(SQ)示例
    PUT /<my-sq-index>
    {
      "settings": {
        "index": { 
          "knn": true 
        }
      },
      "mappings": {
        "properties": {
          "<my_vector_field>": {
            "type": "knn_vector",
            "dimension": 128,
            "method": {
              "name": "hnsw",
              "engine": "faiss",
              "parameters": {
                "m": 16,
                "ef_construction": 256,
                "encoder": {// 启用SQ
                  "name": "fp16"
                 } 
              }
            }
          }
        }
      }
    }
    

二值量化(BQ)

  • 工作原理:将浮点向量的每个维度压缩为二进制位(01)进行存储,从而实现极高的压缩率。训练过程在索引构建时自动完成。

  • 内存估算

    • 公式:内存 (GB) ≈ 1.1 * ((dimension * bits / 8) + 8 * m) * num_vectors / 1024^3

    • 参数详解:

      • dimension:向量的维度。

      • bits:每个维度用多少个二进制位表示,可选值为1,2,4。bits越小,压缩率越高,但精度损失越大。

      • m:HNSW索引中的m参数。

      • num_vectors:向量总数。

    • 示例:假设您有100万个向量,每个向量的维度为256,每个向量的维度m16。以下部分提供了各种压缩值对内存需求的估算。

      • 1位量化(32倍压缩):在1位量化中,每个维度用1位表示,相当于32倍压缩系数。内存需求可以估算如下:1.1 * ((256 * 1 / 8) + 8 * 16) * 1,000,000 ~= 0.176 GB

      • 2位量化(16倍压缩):在2位量化中,每个维度用2位表示,相当于16倍压缩系数。内存需求可以估算如下:1.1 * ((256 * 2 / 8) + 8 * 16) * 1,000,000 ~= 0.211 GB

  • 使用示例

    // HNSW + 二值量化(BQ)示例
    PUT /<my-bq-index>
    {
      "settings" : { 
        "index": { 
          "knn": true 
        } 
      },
      "mappings": {
        "properties": {
          "<my_vector_field>": {
            "type": "knn_vector",
            "dimension": 128,
            "method": {
                "name": "hnsw",
                "engine": "faiss",
                "parameters": {
                  "m": 16,
                  "ef_construction": 512,
                  "encoder": {
                    "name": "binary",
                    "parameters": {// 启用BQ,使用1位量化
                      "bits": 1 
                    }
                  }
                }
            }
          }
        }
      }
    }
    

乘积量化(PQ)

PQ是一种先进的向量压缩技术,它能实现比SQBQ更高的压缩率,但代价是需要一个独立的训练步骤来构建压缩模型。

  • 工作原理

    1. 向量切分:首先,将一个原始的高维向量(例如256维)切分为m个等长的低维子向量。例如,将256维向量按m=32切分,会得到328维的子向量。

    2. 码本训练:接着,系统会为每一个子向量空间独立学习一个“码本”(Codebook)。这个码本包含2^code_size个中心点(也称质心)。这个训练过程通常使用K-均值聚类算法完成。

    3. 量化编码:训练完成后,在对新向量进行编码时,其每个子向量不再存储原始的浮点值,而是被替换为该子向量空间码本中距离它最近的那个中心点的ID。如果code_size8,则ID范围是0-255,正好用1个字节存储。

    4. 最终结果:一个原始向量就被转换成了一组中心点ID的序列,从而实现了极高的压缩。

  • 训练要求:PQ的性能严重依赖于训练数据的质量。您必须提供一组与您最终要检索的数据分布相似的向量来进行训练。

    • 训练数据来源:可以是您要索引的向量数据的子集。

    • 建议的训练数据量:

      • 结合HNSW使用时:建议训练向量数量为 2^code_size * 1000

      • 结合IVF使用时:建议训练向量数量为 max(1000 * nlist, 2^code_size * 1000)

  • 内存估算:以HNSW+PQ为例。因为当HNSWPQ结合使用时,其内存计算公式较为复杂,因为它包含了压缩向量、HNSW图结构和PQ码本三部分的开销。

    • 公式:内存 (字节) ≈ 1.1 * ( (per_vector_cost) * num_vectors + (codebook_cost) )

      • per_vector_cost = (pq_code_size / 8 * pq_m) + 24 + (8 * hnsw_m)

      • codebook_cost = num_segments * (2^pq_code_size) * 4 * dimension

    • 参数详解:

      • num_vectors:向量总数。

      • dimension:原始向量的维度。

      • pq_m:向量切分的段数。dimension必须能被pq_m整除。

      • pq_code_size:每个子向量码本的大小,以比特为单位。通常为8。

      • hnsw_m:HNSW索引中的m参数,即每个节点的最大邻居数。

      • num_segments:一个底层技术参数,代表索引被分成的段数。在估算时可以按集群分片数或一个保守值(如100)来计算。

      • 1.1:约10%的系统开销系数。

      • 248:HNSW图结构中每个节点的固定开销和指针开销。

      • 4:代表码本中的中心点坐标使用32位浮点数(4字节)存储。

    • 示例:假设您有100万个向量(num_vectors),每个向量的维度(dimension)为256,每个向量切分段数(pq_m)为32,每个子向量码本的大小(pq_code_size)为8,HNSW索引的m参数为16,num_segments100。

      1. 计算单个向量的开销(per_vector_cost):

        1. 压缩后向量大小 = pq_code_size / 8 * pq_m = 8 / 8 * 32 = 32字节。

        2. HNSW图开销 = 24 + 8 * hnsw_m = 24 + 8 * 16 = 152字节。

        3. per_vector_cost = 32 + 152 = 184字节

      2. 计算码本的总开销(codebook_cost):

        1. codebook_cost = num_segments * (2^pq_code_size) * 4 * dimension。

        2. codebook_cost = 100 * (2^8) * 4 * 256 = 100 * 256 * 4 * 256 = 26,214,400字节。

      3. 计算总内存:

        1. 总内存 ≈ 1.1 * (per_vector_cost * num_vectors + codebook_cost)

        2. 总内存 ≈ 1.1 * (184 * 1,000,000 + 26,214,400) ≈ 231,235,840 字节 ≈ 0.215 GB

  • 使用示例

    // HNSW + 乘积量化(PQ)示例
    PUT /<my-hnswpq-index>
    {
      "settings" : { 
        "index": { 
          "knn": true 
        } 
      },
      "mappings": {
        "properties": {
          "<my_vector_field>": {
            "type": "knn_vector",
            "dimension": 128, // 维度必须能被 m 整除
            "method": {
                "name": "hnsw",
                "engine": "faiss",
                "parameters": {
                  "m": 16, // HNSWm参数
                  "ef_construction": 512,
                  "encoder": {
                    "name": "pq",
                    "parameters": {
                      "m": 4, // PQm参数:将128维切为432维
                      "code_size": 8
                    }
                  }
                }
            }
          }
        }
      }
    }

基于磁盘的向量存储

  • 工作原理:基于磁盘的向量搜索是利用内部的量化技术压缩向量,并将主要的图结构存储在磁盘上,而不是堆内存中。这种内存优化可以大幅节省内存,但搜索延迟会略有增加,同时仍能保持较高的召回率。

  • 内存估算:无固定公式。实际物理内存占用由操作系统根据访问模式动态管理。

  • 使用示例

    // 基于磁盘存储的示例
    PUT /<my-ondisk-index>
    {
      "settings" : { 
        "index": {
           "knn": true 
        } 
      },
      "mappings": {
        "properties": {
          "<my_vector_field>": {
            "type": "knn_vector",
            "dimension": 128,
            "mode": "on_disk" // 启用基于磁盘的模式
          }
        }
      }
    }