es 中 混合检索的实现

431 阅读4分钟

背景

随着大模型的火爆,RAG 的检索方式也大火。随之出现了各种RAG 的变体。文本 + 向量的检索方式,就是其中最为重要的一种变体。文本检索主要用ES来实现。向量检索有各种向量库,如Milvus, Chroma。 ES在最新版中实现了向量检索,用一个工具就可以文本 + 向量 的检索。

名词解释:

ES (elastic search) 是一款开源的实现全文检索的工具。

混合检索:文本检索和向量检索同时使用,以提高检索的准确度。

ES的安装

ES在8.0 版本以后,实现了向量检索。 最新版是8.17,同时考虑到es的版本要与ik 分词器的版本一致,这里选择了8.16.1 的版本。

安装软件:

  • ES 8.16.1

    从官网下载 https://www.elastic.co/downloads/elasticsearch
    
    docker 安装
    docker run -d --name es -e "ES_JAVA_OPTS=-Xms512m -Xmx4024m" -e "discovery.type=single-node" -e "xpack.security.enabled=false" -e "xpack.security.transport.ssl.enabled=false" -e "ELASTIC_PASSWORD=xxx" -v es-data4:/home/xxx/es4/data -v es-plugins4:/home/xxx/es4/plugin --privileged -p 9200:9200 -p 9300:9300  elasticsearch:8.16.1
    -e 设置参数
        ES_JAVA_OPTS=-Xms512m -Xmx4024m  设置java 的内存大小
        discovery.type=single-node  设置es为单节点
        xpack.security.enabled=false
        xpack.security.transport.ssl.enabled=false   设置xpack 不使用ssl 证书连接 
        ELASTIC_PASSWORD=xxxx, 设置连接密码
    -v 
        es-data4:/home/xxx/es4/data  设置数据目录
        es-plugins4:/home/xxx/es4/plugin   设置插件目录
    
    docker source 现在很多不能用了,需要更新数据源。
    或 docker run ...  docker.elastic.co/elasticsearch/elasticsearch:8.16.1
    
  • IK分词器 8.16.1 用于es 实现分词检索

https://github.com/infinilabs/analysis-ik
  • ES HEAD: chrome 插件版,用于查看es 中数据(谷歌商店下载)

xpack: es8.0 以后,需要证书才能连接。在配置中关闭ssl证书的使用。\config\elasticsearch.yml 修改 xpack.security.enabled=false xpack.security.transport.ssl.enabled=false

在docker 中加参数也可。

python 客户端

python 中实现es检索的api,主要有 elasticsearch,zdppy_elasticsearch 这2个包。下面的操作,主要以elasticsearch 这个包为主。

安装:

pip install elasticsearch

数据导入

  1. 字段索引类型的选择:

    ES 中 文本检索 + 向量检索相关索引类型

### 文本类型
    text(全文检索)
    keyword(关键词)
### 数值类型
    long(长整型)
    integer(整型)
    double(双精度)
    float(单精度)
    half_float(半精度)
### 日期类型
    date(日期)
### 布尔类型
    boolean
### 向量类型
    dense_vector(密集向量)
    sparse_vector(稀疏向量)
  1. 向量化:openai的text-embedding-ada-002, cohere的embed-multilingual-v3.0 ,jina的, 开源库有:m3e, bge

  2. 向量相似度计算:余弦相似度(cosine)、点积(dotProduct)、L1距离(曼哈顿距离)和L2距离(欧几里德距离)

"mappings": {
    "properties": {
          "title": {"type": "text","analyzer":"ik_smart"},
          "content": {"type": "text","analyzer":"ik_smart"},
          "embedding": {"type": "dense_vector", "dims": 768, "similarity": "cosine"}
      }
  }

索引中有3个字段,title,content 是text类型,ik_smart分词器; embedding 是dense_vector 向量类型, 向量长度768 ,计算相似度函数:cosine。 embedding 是把 title + content 字段相加,用m3e 向量化。

数据导入

from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer("moka-ai/m3e-base")

def get_embedding(text):
    vecs = model.encode(text).tolist()
    return vecs

def insert_document(document):
    embedding = get_embedding(document['summary'] + ' ' + document['title'])
    es.index(index=index_name, document={
        'title':document['title'],
        'summary':document['summary'],
        'embedding': embedding
    })

documents = [
    {"title": "Python 编程基础", "summary": "Python 是一种高级编程语言,广泛用于数据分析、机器学习等领域。"},
    {"title": "Elasticsearch 教程", "summary": "Elasticsearch 是一个分布式搜索和分析引擎,支持全文搜索和向量搜索。"}
]

混合检索的实现

key_boostqu = [      {"match": {"title": {"query": qa,"boost":0.9}}},      {"match": {"content": {"query": qa,"boost":0.1}}}  ]
  query = {
      "query": {
          "bool": {
              "should": key_boostqu
          }
      }
  }
  knn_query = {
      'field': 'embedding',
      'query_vector': get_embedding(qa),
      'k': 10,
      'num_candidates': 50,
      'boost':9
  }
   results = es.search(index=index_name, query={
           'bool': {
              **search_query
           }
       },
       knn=knn_query
       
  )

es.search 中query,knn 实现文本检索 + 向量检索的混合。其中boost 参数表示权重。取值范围: 0 ~ 10. 在实际使用中,可以给向量检索,文本检索分配不同的权重。这参数需要调,来实现不同的检索排序。在实践中,向量检索权重 = 9, 标题检索权重=0.9, 正文检索权重=0.1。

rrf 参数:可以自动给文本检索,向量检索分配权重。不需要人工的给boost 分配权重。但这功能不在免费版中。需升级到白金版。

  es.search(index = index_name,query={},knn ={},rank={
    "rrf": {
        "window_size": 100,
        "rank_constant": 20
        }
    }
)